feat:图表自适应布局重构优化
This commit is contained in:
@@ -215,10 +215,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, watch,nextTick } from 'vue'
|
||||
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import * as echarts from 'echarts/core'
|
||||
import { LineChart } from 'echarts/charts'
|
||||
import { GridComponent ,GraphicComponent} from 'echarts/components'
|
||||
import { GridComponent, GraphicComponent, TooltipComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import useNeInfoStore from '@/store/modules/neinfo'
|
||||
import { WS } from '@/plugins/ws-websocket'
|
||||
@@ -226,7 +226,7 @@ import { listKPIData ,getbusyhour, getMosHour, getCctHour} from '@/api/perfManag
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
echarts.use([LineChart, GridComponent, CanvasRenderer, GraphicComponent])
|
||||
echarts.use([LineChart, GridComponent, CanvasRenderer, GraphicComponent, TooltipComponent])
|
||||
|
||||
const callsChartRef = ref<HTMLDivElement | null>(null)
|
||||
const mosChartRef = ref<HTMLDivElement | null>(null)
|
||||
@@ -237,6 +237,59 @@ const cctChartRef = ref<HTMLDivElement | null>(null)
|
||||
const regChartRef = ref<HTMLDivElement | null>(null)
|
||||
const failedRegChartRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// 存储ResizeObserver实例
|
||||
const chartObservers = new Map<HTMLElement, ResizeObserver>()
|
||||
|
||||
// 图表实例映射
|
||||
const chartInstances = new Map<HTMLElement, echarts.ECharts>()
|
||||
|
||||
// 设置图表ResizeObserver
|
||||
function setupChartResizeObserver(container: HTMLElement, chart: echarts.ECharts) {
|
||||
// 如果已存在observer,先断开连接
|
||||
if (chartObservers.has(container)) {
|
||||
chartObservers.get(container)?.disconnect()
|
||||
chartObservers.delete(container)
|
||||
}
|
||||
|
||||
// 创建新的ResizeObserver
|
||||
const observer = new ResizeObserver(() => {
|
||||
if (chart && !chart.isDisposed()) {
|
||||
// 使用requestAnimationFrame确保在DOM更新完成后执行resize
|
||||
requestAnimationFrame(() => {
|
||||
chart.resize()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 开始观察容器尺寸变化
|
||||
observer.observe(container)
|
||||
|
||||
// 存储observer实例
|
||||
chartObservers.set(container, observer)
|
||||
}
|
||||
|
||||
// 清理单个图表的ResizeObserver
|
||||
function cleanupChartObserver(container: HTMLElement) {
|
||||
const observer = chartObservers.get(container)
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
chartObservers.delete(container)
|
||||
}
|
||||
chartInstances.delete(container)
|
||||
}
|
||||
|
||||
// 全局resize处理函数,用于处理页面缩放变化
|
||||
function handleGlobalResize() {
|
||||
// 延迟执行以确保DOM完全更新
|
||||
requestAnimationFrame(() => {
|
||||
chartInstances.forEach((chart, container) => {
|
||||
if (chart && !chart.isDisposed()) {
|
||||
chart.resize()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// IMS网元列表
|
||||
const imsNeList = ref<{ neId: string, neName: string }[]>([])
|
||||
// 当前选中的IMS网元ID
|
||||
@@ -258,6 +311,9 @@ const wsStatus = ref('no connection')
|
||||
onMounted(async () => {
|
||||
// console.log('组件挂载,开始获取IMS网元列表') // 调试信息
|
||||
|
||||
// 添加全局窗口resize监听器,处理页面缩放变化
|
||||
window.addEventListener('resize', handleGlobalResize)
|
||||
|
||||
const res = await useNeInfoStore().fnNelist()
|
||||
// console.log('获取到的网元列表响应:', res) // 调试信息
|
||||
|
||||
@@ -305,10 +361,7 @@ async function fetchHistoryData(neId: string) {
|
||||
pageNum: 1
|
||||
}
|
||||
|
||||
// console.log('获取历史数据参数:', params)
|
||||
|
||||
const res = await listKPIData(params)
|
||||
//console.log('历史数据响应:', res)
|
||||
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 将历史数据转换为与实时数据相同的格式
|
||||
@@ -317,15 +370,10 @@ async function fetchHistoryData(neId: string) {
|
||||
data: item
|
||||
}))
|
||||
|
||||
//console.log('转换后的历史数据:', historyData)
|
||||
|
||||
// 将历史数据添加到实时数据数组中(追加而不是覆盖)
|
||||
// 注意:这里直接赋值,因为这是初始加载历史数据
|
||||
imsRealtimeRawData.value = historyData
|
||||
|
||||
//console.log('历史数据加载完成,数据点数量:', imsRealtimeRawData.value.length)
|
||||
//console.log('最新历史数据时间戳:', historyData.length > 0 ? historyData[historyData.length - 1].timestamp : '无数据')
|
||||
|
||||
// 更新所有图表
|
||||
updateActiveCallsChart()
|
||||
updateFailedCallsChart()
|
||||
@@ -529,13 +577,28 @@ function subscribeImsRealtime(neId: string) {
|
||||
})
|
||||
}
|
||||
|
||||
// 组件卸载时关闭WebSocket
|
||||
// 组件卸载时关闭WebSocket和清理ResizeObserver
|
||||
onBeforeUnmount(() => {
|
||||
// 移除全局resize监听器
|
||||
window.removeEventListener('resize', handleGlobalResize)
|
||||
|
||||
if (imsWs.value) {
|
||||
imsWs.value.close()
|
||||
imsWs.value = null
|
||||
}
|
||||
wsStatus.value = 'Disconnect' // 更新状态
|
||||
|
||||
// 清理所有ResizeObserver
|
||||
chartObservers.forEach((observer, container) => {
|
||||
observer.disconnect()
|
||||
// 销毁对应的图表实例
|
||||
const chart = chartInstances.get(container)
|
||||
if (chart && !chart.isDisposed()) {
|
||||
chart.dispose()
|
||||
}
|
||||
})
|
||||
chartObservers.clear()
|
||||
chartInstances.clear()
|
||||
})
|
||||
|
||||
// 更新active calls图表
|
||||
@@ -547,17 +610,8 @@ function updateActiveCallsChart() {
|
||||
lineColor: '#1890ff',
|
||||
areaColor: 'rgba(24,144,255,0.1)',
|
||||
labelColor: '#1890ff',
|
||||
dataType: 'realtime',
|
||||
formatValue: (value: number) => value.toString(),
|
||||
timeCalculator: (data) => {
|
||||
const displayDataLength = Math.min(data.length, 30)
|
||||
const oldestDisplayIndex = Math.max(0, data.length - displayDataLength)
|
||||
const oldestDisplayData = data[oldestDisplayIndex]
|
||||
if (oldestDisplayData && oldestDisplayData.timestamp) {
|
||||
return calculateRelativeTime(oldestDisplayData.timestamp)
|
||||
}
|
||||
return '--'
|
||||
}
|
||||
dataType: 'realtime-enhanced', // 使用增强的实时数据类型
|
||||
formatValue: (value: number) => value.toString()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -575,17 +629,8 @@ function updateFailedCallsChart() {
|
||||
lineColor: '#faad14',
|
||||
areaColor: 'rgba(250,173,20,0.1)',
|
||||
labelColor: '#faad14',
|
||||
dataType: 'realtime',
|
||||
formatValue: (value: number) => value.toString(),
|
||||
timeCalculator: (data) => {
|
||||
const displayDataLength = Math.min(data.length, 30)
|
||||
const oldestDisplayIndex = Math.max(0, data.length - displayDataLength)
|
||||
const oldestDisplayData = data[oldestDisplayIndex]
|
||||
if (oldestDisplayData && oldestDisplayData.timestamp) {
|
||||
return calculateRelativeTime(oldestDisplayData.timestamp)
|
||||
}
|
||||
return '--'
|
||||
}
|
||||
dataType: 'realtime-enhanced', // 使用增强的实时数据类型
|
||||
formatValue: (value: number) => value.toString()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -598,17 +643,8 @@ function updateActiveRegistrationsChart() {
|
||||
lineColor: '#1890ff',
|
||||
areaColor: 'rgba(24,144,255,0.1)',
|
||||
labelColor: '#1890ff',
|
||||
dataType: 'realtime',
|
||||
formatValue: (value: number) => value.toString(),
|
||||
timeCalculator: (data) => {
|
||||
const displayDataLength = Math.min(data.length, 30)
|
||||
const oldestDisplayIndex = Math.max(0, data.length - displayDataLength)
|
||||
const oldestDisplayData = data[oldestDisplayIndex]
|
||||
if (oldestDisplayData && oldestDisplayData.timestamp) {
|
||||
return calculateRelativeTime(oldestDisplayData.timestamp)
|
||||
}
|
||||
return '--'
|
||||
}
|
||||
dataType: 'realtime-enhanced', // 使用增强的实时数据类型
|
||||
formatValue: (value: number) => value.toString()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -626,17 +662,8 @@ function updateFailedRegistrationsChart() {
|
||||
lineColor: '#f5222d',
|
||||
areaColor: 'rgba(245,34,45,0.1)',
|
||||
labelColor: '#f5222d',
|
||||
dataType: 'realtime',
|
||||
formatValue: (value: number) => value.toString(),
|
||||
timeCalculator: (data) => {
|
||||
const displayDataLength = Math.min(data.length, 30)
|
||||
const oldestDisplayIndex = Math.max(0, data.length - displayDataLength)
|
||||
const oldestDisplayData = data[oldestDisplayIndex]
|
||||
if (oldestDisplayData && oldestDisplayData.timestamp) {
|
||||
return calculateRelativeTime(oldestDisplayData.timestamp)
|
||||
}
|
||||
return '--'
|
||||
}
|
||||
dataType: 'realtime-enhanced', // 使用增强的实时数据类型
|
||||
formatValue: (value: number) => value.toString()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1038,7 +1065,7 @@ function updateChart(config: {
|
||||
lineColor: string,
|
||||
areaColor: string,
|
||||
labelColor: string,
|
||||
dataType?: 'realtime' | 'hourly',
|
||||
dataType?: 'realtime' | 'hourly' | 'realtime-enhanced',
|
||||
formatValue?: (value: number) => string,
|
||||
timeCalculator?: (data: any[]) => string
|
||||
}) {
|
||||
@@ -1048,8 +1075,14 @@ function updateChart(config: {
|
||||
let chart = echarts.getInstanceByDom(config.chartRef.value)
|
||||
if (!chart) {
|
||||
chart = echarts.init(config.chartRef.value)
|
||||
|
||||
// 设置ResizeObserver以监听容器尺寸变化
|
||||
setupChartResizeObserver(config.chartRef.value, chart)
|
||||
}
|
||||
|
||||
// 更新图表实例映射
|
||||
chartInstances.set(config.chartRef.value, chart)
|
||||
|
||||
// 准备图表数据
|
||||
const chartData = config.data.map(config.dataExtractor)
|
||||
|
||||
@@ -1059,12 +1092,66 @@ function updateChart(config: {
|
||||
const xAxisData = [1, 2, 3, 4, 5]
|
||||
|
||||
chart.setOption({
|
||||
grid: { left: 0, right: 30, top: 10, bottom: 10 },
|
||||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||||
grid: {
|
||||
left: 5, // 给图表左侧留出少量空间,避免被容器边界遮挡
|
||||
right: 20, // 减少右侧边距,为gap腾出空间
|
||||
top: 5, // 减少顶部边距
|
||||
bottom: (config.dataType === 'hourly' || config.dataType === 'realtime-enhanced') ? 25 : 5 // 调整底部边距
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'none'
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
if (!params || params.length === 0) return ''
|
||||
const param = params[0]
|
||||
if (config.dataType === 'hourly') {
|
||||
return `${param.name}Hour: 0`
|
||||
} else if (config.dataType === 'realtime-enhanced') {
|
||||
const minutesAgo = defaultData.length - 1 - param.dataIndex
|
||||
return `${minutesAgo}Min ago: 0`
|
||||
} else {
|
||||
return ` ${param.dataIndex + 1}: 0`
|
||||
}
|
||||
},
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
textStyle: { color: '#fff', fontSize: 12 },
|
||||
borderWidth: 0,
|
||||
padding: [6, 10],
|
||||
extraCssText: 'border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.2);'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: config.dataType === 'hourly',
|
||||
data: config.dataType === 'hourly' ?
|
||||
(() => {
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
const maxHour = Math.min(currentHour, 4) // 最多显示到4小时或当前小时
|
||||
return Array.from({ length: maxHour + 1 }, (_, i) =>
|
||||
i === maxHour ? `${i}Hour` : i.toString()
|
||||
)
|
||||
})() : xAxisData,
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
color: '#999',
|
||||
interval: 0
|
||||
},
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
yAxis: { type: 'value', show: false },
|
||||
series: [{
|
||||
data: defaultData,
|
||||
type: 'line', symbol: 'none',
|
||||
data: (config.dataType === 'hourly' || config.dataType === 'realtime-enhanced') ? defaultData.map((value, index) => ({
|
||||
value,
|
||||
itemStyle: {
|
||||
color: '#d9d9d9' // 空数据时所有数据点都使用灰色
|
||||
}
|
||||
})) : defaultData,
|
||||
type: 'line',
|
||||
symbol: 'circle', // 所有图表都显示数据点,以支持hover tooltip
|
||||
symbolSize: 4, // 设置数据点大小
|
||||
lineStyle: { width: 2, color: '#d9d9d9' },
|
||||
areaStyle: { color: 'rgba(217,217,217,0.1)' }
|
||||
}],
|
||||
@@ -1082,7 +1169,7 @@ function updateChart(config: {
|
||||
|
||||
// 实时数据需要补充和限制数据点
|
||||
let processedData = [...chartData]
|
||||
if (config.dataType === 'realtime') {
|
||||
if (config.dataType === 'realtime' || config.dataType === 'realtime-enhanced') {
|
||||
// 如果数据不足,补充默认数据
|
||||
while (processedData.length < 5) {
|
||||
processedData.unshift(0)
|
||||
@@ -1094,7 +1181,28 @@ function updateChart(config: {
|
||||
}
|
||||
|
||||
// 生成时间轴数据
|
||||
const xAxisData = Array.from({ length: processedData.length }, (_, i) => i + 1)
|
||||
let xAxisData: (string | number)[]
|
||||
if (config.dataType === 'hourly') {
|
||||
// 小时数据:生成绝对时间标签,基于实际数据长度
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
|
||||
// 生成统一格式的时间标签,避免标签宽度不一致
|
||||
xAxisData = Array.from({ length: processedData.length }, (_, i) => i.toString())
|
||||
} else if (config.dataType === 'realtime-enhanced') {
|
||||
// 增强实时数据:生成分钟级时间标签,但只显示关键时间点
|
||||
xAxisData = Array.from({ length: processedData.length }, (_, i) => {
|
||||
const minutesAgo = processedData.length - 1 - i
|
||||
// 只为特定时间点生成标签,其他返回空字符串
|
||||
if (i === 0 || i === processedData.length - 1 || minutesAgo % 5 === 0) {
|
||||
return minutesAgo.toString()
|
||||
}
|
||||
return '' // 空标签,但数据点仍然存在
|
||||
})
|
||||
} else {
|
||||
// 普通实时数据:使用数字序列
|
||||
xAxisData = Array.from({ length: processedData.length }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 计算最大值、最小值、最新值
|
||||
const maxValue = Math.max(...processedData)
|
||||
@@ -1102,30 +1210,89 @@ function updateChart(config: {
|
||||
const latestValue = processedData[processedData.length - 1]
|
||||
|
||||
chart.setOption({
|
||||
grid: { left: 0, right: 30, top: 10, bottom: 10 },
|
||||
xAxis: { type: 'category', show: false, data: xAxisData },
|
||||
grid: {
|
||||
left: 5, // 给图表左侧留出少量空间,避免被容器边界遮挡
|
||||
right: 20, // 减少右侧边距,为gap腾出空间
|
||||
top: 5, // 减少顶部边距
|
||||
bottom: (config.dataType === 'hourly' || config.dataType === 'realtime-enhanced') ? 25 : 5 // 调整底部边距
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'none'
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
if (!params || params.length === 0) return ''
|
||||
const param = params[0]
|
||||
const formatValue = config.formatValue || ((val: number) => val.toString())
|
||||
const value = typeof param.value === 'object' ? param.value.value : param.value
|
||||
if (config.dataType === 'hourly') {
|
||||
return `${param.name}Hour: ${formatValue(value)}`
|
||||
} else if (config.dataType === 'realtime-enhanced') {
|
||||
// 对于增强实时数据,计算实际的分钟数
|
||||
const minutesAgo = processedData.length - 1 - param.dataIndex
|
||||
return `${minutesAgo}Min ago: ${formatValue(value)}`
|
||||
} else {
|
||||
// 对于普通实时数据,显示数据点索引和值
|
||||
return `数据点 ${param.dataIndex + 1}: ${formatValue(value)}`
|
||||
}
|
||||
},
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
textStyle: { color: '#fff', fontSize: 12 },
|
||||
borderWidth: 0,
|
||||
padding: [6, 10],
|
||||
extraCssText: 'border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.2);'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: config.dataType === 'hourly' || config.dataType === 'realtime-enhanced', // 小时数据和增强实时数据显示x轴
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
color: '#999',
|
||||
interval: 0, // 显示所有标签(已在数据生成阶段控制标签密度)
|
||||
rotate: 0,
|
||||
margin: 8,
|
||||
overflow: 'truncate',
|
||||
formatter: undefined // 移除Hour后缀,保持所有标签格式一致
|
||||
},
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
yAxis: { type: 'value', show: false },
|
||||
series: [{
|
||||
data: processedData,
|
||||
type: 'line', symbol: 'none',
|
||||
data: processedData.map((value, index) => ({
|
||||
value,
|
||||
itemStyle: {
|
||||
color: index === processedData.length - 1
|
||||
? config.lineColor // 最新值使用原色
|
||||
: `${config.lineColor}80` // 其他数据点使用50%透明度的颜色
|
||||
}
|
||||
})),
|
||||
type: 'line',
|
||||
symbol: 'circle', // 所有图表都显示数据点,以支持hover tooltip
|
||||
symbolSize: 4, // 设置数据点大小
|
||||
lineStyle: { width: 2, color: config.lineColor },
|
||||
areaStyle: { color: config.areaColor }
|
||||
}],
|
||||
graphic: []
|
||||
})
|
||||
|
||||
// 添加数值标签
|
||||
addChartLabels({
|
||||
container: config.chartRef.value,
|
||||
maxValue,
|
||||
minValue,
|
||||
latestValue,
|
||||
labelColor: config.labelColor,
|
||||
formatValue: config.formatValue
|
||||
})
|
||||
// 只为普通实时数据添加侧边数值标签,小时数据和增强实时数据通过hover tooltip显示
|
||||
if (config.dataType !== 'hourly' && config.dataType !== 'realtime-enhanced') {
|
||||
addChartLabels({
|
||||
container: config.chartRef.value,
|
||||
maxValue,
|
||||
minValue,
|
||||
latestValue,
|
||||
labelColor: config.labelColor,
|
||||
formatValue: config.formatValue,
|
||||
dataType: config.dataType
|
||||
})
|
||||
}
|
||||
|
||||
// 添加时间标签(如果有时间计算器)
|
||||
if (config.timeCalculator && config.data.length > 0) {
|
||||
// 添加时间标签(只为普通实时数据添加,小时数据和增强实时数据通过xAxis显示)
|
||||
if (config.data.length > 0 && config.dataType !== 'hourly' && config.dataType !== 'realtime-enhanced' && config.timeCalculator) {
|
||||
addTimeLabels({
|
||||
container: config.chartRef.value,
|
||||
timeText: config.timeCalculator(config.data)
|
||||
@@ -1140,7 +1307,8 @@ function addChartLabels(config: {
|
||||
minValue: number,
|
||||
latestValue: number,
|
||||
labelColor: string,
|
||||
formatValue?: (value: number) => string
|
||||
formatValue?: (value: number) => string,
|
||||
dataType?: 'realtime' | 'hourly'
|
||||
}) {
|
||||
if (!config.container) return
|
||||
|
||||
@@ -1150,18 +1318,60 @@ function addChartLabels(config: {
|
||||
|
||||
const formatValue = config.formatValue || ((val: number) => val.toString())
|
||||
|
||||
// 小时数据使用改进的定位策略 - 相对于图表容器定位
|
||||
if (config.dataType === 'hourly') {
|
||||
// 直接在图表容器内定位,确保图表容器有相对定位
|
||||
const chartContainer = config.container
|
||||
|
||||
// 创建hourly标签的辅助函数
|
||||
const createHourlyLabel = (value: string, color: string, position: string) => {
|
||||
const label = document.createElement('div')
|
||||
label.className = 'chart-label hourly-label'
|
||||
label.style.cssText = `
|
||||
position: absolute;
|
||||
right: -45px; /* 相对于图表容器右侧外部定位,稍微靠近一些 */
|
||||
${position}
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: ${color};
|
||||
pointer-events: none;
|
||||
text-align: left; /* 标签文本左对齐,向右延伸 */
|
||||
min-width: 40px;
|
||||
white-space: nowrap;
|
||||
z-index: 10;
|
||||
`
|
||||
label.textContent = value
|
||||
return label
|
||||
}
|
||||
|
||||
// 清除旧的hourly标签
|
||||
const existingHourlyLabels = chartContainer.querySelectorAll('.hourly-label')
|
||||
existingHourlyLabels.forEach((label: any) => label.remove())
|
||||
|
||||
// 只显示当前值标签 - 居中显示
|
||||
chartContainer.appendChild(createHourlyLabel(formatValue(config.latestValue), config.labelColor, 'top: 50%; transform: translateY(-50%);'))
|
||||
|
||||
return // 早期返回,不执行下面的标准标签逻辑
|
||||
}
|
||||
|
||||
// 实时数据使用原有逻辑
|
||||
const rightPosition = '8px'
|
||||
|
||||
// 添加右侧数值标注
|
||||
const maxLabel = document.createElement('div')
|
||||
maxLabel.className = 'chart-label'
|
||||
maxLabel.style.cssText = `
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
right: ${rightPosition};
|
||||
top: 8px; /* 实时数据固定位置 */
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
text-align: right;
|
||||
min-width: 40px;
|
||||
white-space: nowrap;
|
||||
`
|
||||
maxLabel.textContent = formatValue(config.maxValue)
|
||||
|
||||
@@ -1169,14 +1379,17 @@ function addChartLabels(config: {
|
||||
latestLabel.className = 'chart-label'
|
||||
latestLabel.style.cssText = `
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
right: ${rightPosition};
|
||||
top: 50%; /* 实时数据居中 */
|
||||
transform: translateY(-50%);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: ${config.labelColor};
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
text-align: right;
|
||||
min-width: 40px;
|
||||
white-space: nowrap;
|
||||
`
|
||||
latestLabel.textContent = formatValue(config.latestValue)
|
||||
|
||||
@@ -1184,13 +1397,16 @@ function addChartLabels(config: {
|
||||
minLabel.className = 'chart-label'
|
||||
minLabel.style.cssText = `
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
right: ${rightPosition};
|
||||
bottom: 8px; /* 实时数据固定位置 */
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
text-align: right;
|
||||
min-width: 40px;
|
||||
white-space: nowrap;
|
||||
`
|
||||
minLabel.textContent = formatValue(config.minValue)
|
||||
|
||||
@@ -1237,6 +1453,72 @@ function addTimeLabels(config: {
|
||||
config.container.appendChild(nowTimeLabel)
|
||||
}
|
||||
|
||||
// 小时数据专用时间标签函数
|
||||
function addHourlyTimeLabels(config: {
|
||||
container: any,
|
||||
data: any[]
|
||||
}) {
|
||||
if (!config.container || !config.data || config.data.length === 0) return
|
||||
|
||||
// 计算当前小时
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
|
||||
// 清除之前的时间标签
|
||||
const existingTimeLabels = config.container.querySelectorAll('.chart-time-label')
|
||||
existingTimeLabels.forEach((label: any) => label.remove())
|
||||
|
||||
// 添加左侧时间标签(0点)
|
||||
const startTimeLabel = document.createElement('div')
|
||||
startTimeLabel.className = 'chart-time-label'
|
||||
startTimeLabel.style.cssText = `
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
bottom: -20px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
`
|
||||
startTimeLabel.textContent = '0'
|
||||
|
||||
// 添加中间时间标签(如果当前小时大于6,显示中间时间)
|
||||
if (currentHour > 6) {
|
||||
const midHour = Math.floor(currentHour / 2)
|
||||
const midTimeLabel = document.createElement('div')
|
||||
midTimeLabel.className = 'chart-time-label'
|
||||
midTimeLabel.style.cssText = `
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: -20px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
`
|
||||
midTimeLabel.textContent = midHour.toString()
|
||||
config.container.appendChild(midTimeLabel)
|
||||
}
|
||||
|
||||
// 添加右侧时间标签(当前小时Hour)
|
||||
const endTimeLabel = document.createElement('div')
|
||||
endTimeLabel.className = 'chart-time-label'
|
||||
endTimeLabel.style.cssText = `
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: -20px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
`
|
||||
endTimeLabel.textContent = `${currentHour}Hour`
|
||||
|
||||
config.container.appendChild(startTimeLabel)
|
||||
config.container.appendChild(endTimeLabel)
|
||||
}
|
||||
|
||||
// 通用计算值函数
|
||||
function calculateMetricValue(config: {
|
||||
data: any[] | null,
|
||||
@@ -2027,22 +2309,28 @@ function calculateCctChange() {
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch; /* 改为stretch,让子元素填满高度 */
|
||||
position: relative; /* 为绝对定位的小时数据标签提供定位上下文 */
|
||||
gap: 8px; /* 减少gap,进一步缩小间隙 */
|
||||
height: 100px; /* 设置固定高度,参考kpiKeyTarget的做法 */
|
||||
}
|
||||
.trend-chart {
|
||||
flex: 2;
|
||||
height: 60px;
|
||||
flex: 3; /* 增加图表区域的flex比例,参考kpiKeyTarget */
|
||||
min-width: 0; /* 允许flex容器收缩,参考kpiKeyTarget */
|
||||
height: 100%; /* 占满父容器高度 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 25px; /* 为底部时间标注留出空间 */
|
||||
padding-bottom: 25px; /* 底部留出时间轴空间 */
|
||||
/* 移除margin-bottom和padding-top,使用父容器高度控制 */
|
||||
}
|
||||
.mini-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 60px; /* 设置固定图表高度 */
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: visible; /* 确保标签可以超出容器边界 */
|
||||
}
|
||||
.card-subtext {
|
||||
font-size: 12px;
|
||||
@@ -2055,10 +2343,13 @@ function calculateCctChange() {
|
||||
}
|
||||
.metric-info {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
min-width: 90px;
|
||||
min-width: 90px; /* 保持最小宽度确保数值显示完整 */
|
||||
flex-shrink: 0; /* 防止收缩,确保数值显示区域稳定 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center; /* 垂直居中对齐 */
|
||||
/* 移除margin,使用父容器的gap控制间隙 */
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 24px;
|
||||
|
||||
Reference in New Issue
Block a user