feat:关键指标、黄金指标仪表盘多网元实现
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||
import { LineOutlined } from '@ant-design/icons-vue';
|
||||
import { TableColumnType } from 'ant-design-vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
const { t, currentLocale } = useI18n();
|
||||
//定义KPI接口
|
||||
interface KPIBase {
|
||||
@@ -147,6 +148,50 @@ const KPI_TITLE: Record<string, string> = {
|
||||
'SCSCF.08': 'MT Call Attempt',
|
||||
};
|
||||
|
||||
// 添加网元信息 store
|
||||
const neInfoStore = useNeInfoStore();
|
||||
|
||||
// 添加网元列表相关变量
|
||||
const neList = ref<Record<NeType, {neId: string, neName: string}[]>>({
|
||||
AMF: [],
|
||||
UPF: [],
|
||||
IMS: []
|
||||
});
|
||||
|
||||
// 添加获取网元列表的函数
|
||||
const fetchNeList = async () => {
|
||||
try {
|
||||
const res = await neInfoStore.fnNelist();
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 初始化网元列表
|
||||
ALL_NE_TYPES.forEach(type => {
|
||||
neList.value[type] = [];
|
||||
});
|
||||
|
||||
// 过滤并分类网元
|
||||
res.data.forEach(ne => {
|
||||
const neType = ne.neType as NeType;
|
||||
if (ALL_NE_TYPES.includes(neType)) {
|
||||
neList.value[neType].push({
|
||||
neId: ne.neId,
|
||||
neName: ne.neName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('网元列表获取成功:', neList.value);
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch NE list:', error);
|
||||
message.error(t('common.getInfoFail'));
|
||||
}
|
||||
};
|
||||
|
||||
// 实时数据开关函数
|
||||
const fnRealTimeSwitch = (bool: boolean) => {
|
||||
if (bool) {
|
||||
@@ -163,15 +208,18 @@ const fnRealTimeSwitch = (bool: boolean) => {
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
subGroupID: ALL_NE_TYPES.map(type => `10_${type}_001`).join(','),
|
||||
// 为所有网元创建订阅
|
||||
subGroupID: ALL_NE_TYPES.flatMap(type =>
|
||||
neList.value[type].map(ne => `10_${type}_${ne.neId}`)
|
||||
).join(','),
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
};
|
||||
ws.value.connect(options);
|
||||
} else if (ws.value) {
|
||||
ws.value.close(); //断开链接
|
||||
ws.value = null; //清空链接
|
||||
ws.value.close();
|
||||
ws.value = null;
|
||||
tableLoading.value = false;
|
||||
}
|
||||
};
|
||||
@@ -181,26 +229,6 @@ const wsError = () => {
|
||||
message.error(t('common.websocketError'));
|
||||
};
|
||||
|
||||
const handleWebSocketMessage = (kpiEvent: any) => {
|
||||
if (!kpiEvent) return;
|
||||
|
||||
// 构造新的数据点
|
||||
const newData: ChartDataItem = {
|
||||
date: kpiEvent.timeGroup?.toString() || Date.now().toString(),
|
||||
};
|
||||
|
||||
// 添加已选中的指标的数据
|
||||
selectedKPIs.value.forEach(kpiId => {
|
||||
newData[kpiId] = Number(kpiEvent[kpiId]) || 0;
|
||||
});
|
||||
|
||||
// 更新数据
|
||||
updateChartData(newData);
|
||||
if (tableLoading.value) {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
};
|
||||
//成功回调
|
||||
const wsMessage = (res: Record<string, any>) => {
|
||||
if (!chart) {
|
||||
return;
|
||||
@@ -210,8 +238,32 @@ const wsMessage = (res: Record<string, any>) => {
|
||||
tableLoading.value = false;
|
||||
return;
|
||||
}
|
||||
handleWebSocketMessage(data.data);
|
||||
|
||||
// 解析订阅组ID获取网元类型和ID
|
||||
const [_, neType, neId] = data.groupId.split('_');
|
||||
if (!neType || !neId) return;
|
||||
|
||||
const kpiEvent = data.data;
|
||||
if (!kpiEvent) return;
|
||||
|
||||
// 构造新的数据点
|
||||
const newData: ChartDataItem = {
|
||||
date: kpiEvent.timeGroup?.toString() || Date.now().toString(),
|
||||
};
|
||||
|
||||
// 为每个网元的每个指标添加数据
|
||||
for (const kpiId of TARGET_KPI_IDS[neType as NeType] || []) {
|
||||
const key = `${kpiId}_${neId}`;
|
||||
newData[key] = Number(kpiEvent[kpiId]) || 0;
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
updateChartData(newData);
|
||||
if (tableLoading.value) {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 添加数据处理函数
|
||||
const processChartData = (rawData: any[]) => {
|
||||
const groupedData = new Map<string, any>(); //数据按时间分组
|
||||
@@ -252,35 +304,66 @@ const fetchChartData = async () => {
|
||||
}
|
||||
|
||||
// 创建并行请求数组
|
||||
const requests = ALL_NE_TYPES.map(async neType => {
|
||||
const params = {
|
||||
neType,
|
||||
neId: '001',
|
||||
startTime: String(startTime),
|
||||
endTime: String(endTime),
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
interval: 60 * 15,
|
||||
kpiIds: TARGET_KPI_IDS[neType].join(','),
|
||||
};
|
||||
const requests = [];
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
for (const ne of neList.value[neType]) {
|
||||
const params = {
|
||||
neType,
|
||||
neId: ne.neId,
|
||||
startTime: String(startTime),
|
||||
endTime: String(endTime),
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
interval: 60 * 15,
|
||||
kpiIds: TARGET_KPI_IDS[neType].join(','),
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await listKPIData(params);
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
return res.data;
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch data for ${neType}:`, error);
|
||||
return [];
|
||||
requests.push(
|
||||
listKPIData(params).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
return res.data.map(item => ({
|
||||
...item,
|
||||
_neId: ne.neId,
|
||||
neName: `${neType}-${ne.neId}`
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 并行执行所有请求
|
||||
const results = await Promise.all(requests);
|
||||
const allData = results.flat();
|
||||
|
||||
chartData.value = processChartData(allData);
|
||||
// 按时间分组处理数据
|
||||
const groupedData = new Map<string, any>();
|
||||
allData.forEach(item => {
|
||||
const timeKey = item.timeGroup;
|
||||
if (!groupedData.has(timeKey)) {
|
||||
groupedData.set(timeKey, { timeGroup: timeKey });
|
||||
}
|
||||
Object.assign(groupedData.get(timeKey), item);
|
||||
});
|
||||
|
||||
// 转换为图表数据格式
|
||||
chartData.value = Array.from(groupedData.values())
|
||||
.sort((a, b) => Number(a.timeGroup) - Number(b.timeGroup))
|
||||
.map(item => {
|
||||
const dataItem: ChartDataItem = { date: item.timeGroup.toString() };
|
||||
// 为每个网元的每个指标添加数据
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
for (const ne of neList.value[neType]) {
|
||||
for (const kpiId of TARGET_KPI_IDS[neType]) {
|
||||
const key = `${kpiId}_${ne.neId}`;
|
||||
dataItem[key] = Number(item[kpiId]) || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataItem;
|
||||
});
|
||||
|
||||
updateChart();
|
||||
updateKpiStats();
|
||||
} catch (error) {
|
||||
@@ -436,26 +519,31 @@ const themeObserver = new MutationObserver(() => {
|
||||
|
||||
const updateChart = () => {
|
||||
if (!chart || !kpiColumns.value.length) return;
|
||||
//获取图表配置
|
||||
const commonConfig = getSeriesConfig();
|
||||
//构建数据系列
|
||||
const series = selectedKPIs.value
|
||||
.map(kpiId => {
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
if (!kpi) return null;
|
||||
//为每个KPI分配临时的固定颜色
|
||||
const color = kpiColors.get(kpiId) || generateColorRGBA();
|
||||
kpiColors.set(kpiId, color);
|
||||
|
||||
return {
|
||||
name: kpi.title,
|
||||
type: 'line',
|
||||
data: chartData.value.map(item => item[kpiId] || 0),
|
||||
itemStyle: { color },
|
||||
...commonConfig,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
const series = [];
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
for (const ne of neList.value[neType]) {
|
||||
for (const kpiId of TARGET_KPI_IDS[neType]) {
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
if (!kpi) continue;
|
||||
|
||||
const key = `${kpiId}_${ne.neId}`;
|
||||
const color = kpiColors.get(key) || generateColorRGBA();
|
||||
kpiColors.set(key, color);
|
||||
|
||||
series.push({
|
||||
name: `${kpi.title}(${ne.neId})`,
|
||||
type: 'line',
|
||||
data: chartData.value.map(item => item[key] || 0),
|
||||
itemStyle: { color },
|
||||
symbol: 'none',
|
||||
symbolSize: 6,
|
||||
smooth: 0.6,
|
||||
showSymbol: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
@@ -463,36 +551,29 @@ const updateChart = () => {
|
||||
left: 'center',
|
||||
// 添加文字颜色配置,根据主题切换
|
||||
textStyle: {
|
||||
color:
|
||||
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
: '#333',
|
||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
: '#333',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
confine: true, // 限制 tooltip 显示范围
|
||||
backgroundColor:
|
||||
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? 'rgba(48, 48, 48, 0.8)'
|
||||
: 'rgba(255, 255, 255, 0.9)',
|
||||
borderColor:
|
||||
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#555'
|
||||
: '#ddd',
|
||||
confine: true,
|
||||
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? 'rgba(48, 48, 48, 0.8)'
|
||||
: 'rgba(255, 255, 255, 0.9)',
|
||||
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#555'
|
||||
: '#ddd',
|
||||
textStyle: {
|
||||
color:
|
||||
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
: '#333',
|
||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
: '#333',
|
||||
},
|
||||
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);',
|
||||
},
|
||||
legend: {
|
||||
data: selectedKPIs.value.map(
|
||||
kpiId =>
|
||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId
|
||||
),
|
||||
data: series.map(s => s.name),
|
||||
type: 'scroll',
|
||||
orient: 'horizontal',
|
||||
top: 25,
|
||||
@@ -500,11 +581,9 @@ const updateChart = () => {
|
||||
fontSize: 12,
|
||||
},
|
||||
selected: Object.fromEntries(
|
||||
selectedKPIs.value.map(kpiId => [
|
||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
||||
selectedRows.value.length === 0
|
||||
? true
|
||||
: selectedRows.value.includes(kpiId),
|
||||
kpiStats.value.map(item => [
|
||||
item.title,
|
||||
selectedRows.value.length === 0 || selectedRows.value.includes(item.kpiId)
|
||||
])
|
||||
),
|
||||
show: false,
|
||||
@@ -514,17 +593,20 @@ const updateChart = () => {
|
||||
padding: [5, 10],
|
||||
},
|
||||
grid: {
|
||||
//网格配置
|
||||
left: '6%',
|
||||
right: '6%',
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
// 指定x轴类型为类目轴,适用于离散的类目数据
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: chartData.value.map(item =>
|
||||
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||
),
|
||||
axisLabel: {
|
||||
formatter: '{value}',
|
||||
// formatter: '{value}',
|
||||
color:
|
||||
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
@@ -536,49 +618,14 @@ const updateChart = () => {
|
||||
color: getSplitLineColor(),
|
||||
},
|
||||
},
|
||||
//控制坐标轴两边留白
|
||||
// 当为折线图时(isLine为true)时不留白,柱状图时留白
|
||||
// 这样可以让折线图从原点开始,柱状图有合适的间距
|
||||
boundaryGap: false,
|
||||
// 设置x轴的数据
|
||||
// 将时间戳转换为格式化的时间字符串
|
||||
data: chartData.value.map(item =>
|
||||
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||
),
|
||||
//设置坐标轴刻度标签的样式
|
||||
// axisLabel: {
|
||||
// interval: function(index: number, value: string) {
|
||||
// const currentTime = dayjs(value);
|
||||
// const minutes = currentTime.minute();
|
||||
// const seconds = currentTime.second();
|
||||
//
|
||||
// // 始终显示小时的起始和结束时间点
|
||||
// if ((minutes === 0 && seconds === 0) ||
|
||||
// (minutes === 59 && seconds === 59)) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// // 对于中间的时间点,使用 auto 的逻辑
|
||||
// if (index % Math.ceil(chartData.value.length / 6) === 0) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// },
|
||||
// rotate: 0,
|
||||
// align: 'center',
|
||||
// hideOverlap: true
|
||||
// }
|
||||
},
|
||||
yAxis: {
|
||||
// y轴配置
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}',
|
||||
color:
|
||||
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
: '#333',
|
||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||
? '#CACADA'
|
||||
: '#333',
|
||||
},
|
||||
// 添加自计算的分割段数
|
||||
splitNumber: 5,
|
||||
@@ -595,32 +642,19 @@ const updateChart = () => {
|
||||
};
|
||||
if (chart) {
|
||||
requestAnimationFrame(() => {
|
||||
chart!.setOption(option, true); //使用新的配置更新图表
|
||||
chart!.resize(); //调整图表大小适应容器
|
||||
if (chart) { // 添加额外的空值检查
|
||||
chart.setOption(option, true);
|
||||
chart.resize();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 如果已经有 observer,先断开连接
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
// 创建新的 ResizeObserver
|
||||
observer = new ResizeObserver(() => {
|
||||
if (chart) {
|
||||
chart.resize();
|
||||
}
|
||||
});
|
||||
|
||||
// 观察图表容器
|
||||
const container = document.getElementById('chartContainer');
|
||||
if (container) {
|
||||
observer.observe(container);
|
||||
}
|
||||
};
|
||||
|
||||
//钩子函数
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 获取网元列表
|
||||
await fetchNeList();
|
||||
// 获取所有网元的指标
|
||||
await fetchSpecificKPI();
|
||||
await nextTick();
|
||||
@@ -635,6 +669,15 @@ onMounted(async () => {
|
||||
} else {
|
||||
console.warn('No KPI columns available after fetching');
|
||||
}
|
||||
|
||||
// 创建 ResizeObserver 实例监听图表容器大小变化
|
||||
observer = new ResizeObserver(() => {
|
||||
if (chart) {
|
||||
chart.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
} else if (chart) {
|
||||
console.warn('Chart already initialized, skipping initialization');
|
||||
} else {
|
||||
@@ -672,21 +715,24 @@ const fetchSpecificKPI = async () => {
|
||||
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const titleIDs = Object.values(TARGET_KPI_IDS).flat();
|
||||
const formattedKPIs = res.data.map(item => {
|
||||
let title = item[`${language}Title`];
|
||||
if (titleIDs.includes(item.kpiId)) {
|
||||
title = KPI_TITLE[item.kpiId] || title;
|
||||
}
|
||||
return {
|
||||
title: title,
|
||||
dataIndex: item.kpiId,
|
||||
key: item.kpiId,
|
||||
kpiId: item.kpiId,
|
||||
neType: neType,
|
||||
};
|
||||
});
|
||||
// 只获取 TARGET_KPI_IDS 中定义的指标
|
||||
const filteredKPIs = res.data
|
||||
.filter(item => TARGET_KPI_IDS[neType].includes(item.kpiId))
|
||||
.map(item => {
|
||||
let title = item[`${language}Title`];
|
||||
if (titleIDs.includes(item.kpiId)) {
|
||||
title = KPI_TITLE[item.kpiId] || title;
|
||||
}
|
||||
return {
|
||||
title: title,
|
||||
dataIndex: item.kpiId,
|
||||
key: item.kpiId,
|
||||
kpiId: item.kpiId,
|
||||
neType: neType,
|
||||
}
|
||||
});
|
||||
|
||||
allKPIs = [...allKPIs, ...formattedKPIs];
|
||||
allKPIs = [...allKPIs, ...filteredKPIs];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,14 +791,19 @@ const updateChartData = (newData: ChartDataItem) => {
|
||||
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||
),
|
||||
},
|
||||
series: selectedKPIs.value.map(kpiId => {
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
return {
|
||||
type: 'line',
|
||||
data: chartData.value.map(item => item[kpiId] || 0),
|
||||
name: kpi?.title || kpiId,
|
||||
};
|
||||
}),
|
||||
series: ALL_NE_TYPES.flatMap(type =>
|
||||
neList.value[type].map(ne =>
|
||||
TARGET_KPI_IDS[type].map(kpiId => {
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
const key = `${kpiId}_${ne.neId}`;
|
||||
return {
|
||||
type: 'line',
|
||||
data: chartData.value.map(item => item[key] || 0),
|
||||
name: `${kpi?.title || kpiId}(${ne.neId})`,
|
||||
};
|
||||
})
|
||||
)
|
||||
).flat(),
|
||||
};
|
||||
chart.setOption(option);
|
||||
});
|
||||
@@ -778,52 +829,39 @@ const kpiStats = ref<KPIStats[]>([]);
|
||||
// 添加一个计算函数来更新统计数据
|
||||
const updateKpiStats = () => {
|
||||
if (!chartData.value.length || !kpiColumns.value.length) {
|
||||
kpiStats.value = selectedKPIs.value.map(kpiId => {
|
||||
// 找到对应的KPI标题
|
||||
let title = kpiId;
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
if (kpi) {
|
||||
title = kpi.title;
|
||||
}
|
||||
return {
|
||||
kpiId: kpiId,
|
||||
title: title,
|
||||
max: 0,
|
||||
min: 0,
|
||||
avg: 0,
|
||||
total: 0,
|
||||
};
|
||||
});
|
||||
kpiStats.value = [];
|
||||
return;
|
||||
}
|
||||
kpiStats.value = selectedKPIs.value
|
||||
.map(kpiId => {
|
||||
// 找到对应的KPI标题
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
if (!kpi) return null;
|
||||
|
||||
// 获取该指标的所有数值
|
||||
const values = chartData.value.map(item => Number(item[kpiId]) || 0);
|
||||
kpiStats.value = [];
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
for (const ne of neList.value[neType]) {
|
||||
for (const kpiId of TARGET_KPI_IDS[neType]) {
|
||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||
if (!kpi) continue;
|
||||
|
||||
// 计算总值
|
||||
const total = Number(
|
||||
values.reduce((sum, val) => sum + val, 0).toFixed(2)
|
||||
);
|
||||
const key = `${kpiId}_${ne.neId}`;
|
||||
const values = chartData.value.map(item => Number(item[key]) || 0);
|
||||
|
||||
// 计算平均值
|
||||
const avg =
|
||||
values.length > 0 ? Number((total / values.length).toFixed(2)) : 0;
|
||||
if (values.length === 0) continue;
|
||||
|
||||
return {
|
||||
kpiId: kpiId,
|
||||
title: kpi.title,
|
||||
max: Math.max(...values),
|
||||
min: Math.min(...values),
|
||||
avg: avg,
|
||||
total: total,
|
||||
};
|
||||
})
|
||||
.filter((item): item is KPIStats => item !== null);
|
||||
const total = Number(values.reduce((sum, val) => sum + val, 0).toFixed(2));
|
||||
const avg = Number((total / values.length).toFixed(2));
|
||||
|
||||
kpiStats.value.push({
|
||||
kpiId: key,
|
||||
title: `${kpi.title}(${ne.neId})`,
|
||||
max: Math.max(...values),
|
||||
min: Math.min(...values),
|
||||
avg: avg,
|
||||
total: total,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图表显示
|
||||
updateChartLegendSelect();
|
||||
};
|
||||
|
||||
// 添加表列定义
|
||||
@@ -903,11 +941,9 @@ const updateChartLegendSelect = () => {
|
||||
if (!chart) return;
|
||||
|
||||
const legendSelected = Object.fromEntries(
|
||||
selectedKPIs.value.map(kpiId => [
|
||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
||||
selectedRows.value.length === 0
|
||||
? true
|
||||
: selectedRows.value.includes(kpiId),
|
||||
kpiStats.value.map(item => [
|
||||
item.title,
|
||||
selectedRows.value.length === 0 || selectedRows.value.includes(item.kpiId)
|
||||
])
|
||||
);
|
||||
|
||||
@@ -1037,6 +1073,9 @@ const tableRowConfig = computed(() => {
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
height: calc(100vh - 200px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
[data-theme='light'] .chart-wrapper {
|
||||
@@ -1048,8 +1087,10 @@ const tableRowConfig = computed(() => {
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 400px;
|
||||
height: 450px;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
|
||||
Reference in New Issue
Block a user