diff --git a/.env.development b/.env.development index 48b285d8..eeb7c994 100644 --- a/.env.development +++ b/.env.development @@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC" VITE_APP_CODE = "OMC" # 应用版本 -VITE_APP_VERSION = "2.241028" +VITE_APP_VERSION = "2.241102" # 接口基础URL地址-不带/后缀 VITE_API_BASE_URL = "/omc-api" diff --git a/.env.production b/.env.production index f57b94ba..9adcf8b4 100644 --- a/.env.production +++ b/.env.production @@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC" VITE_APP_CODE = "OMC" # 应用版本 -VITE_APP_VERSION = "2.241028" +VITE_APP_VERSION = "2.241102" # 接口基础URL地址-不带/后缀 VITE_API_BASE_URL = "/omc-api" diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index b2cc3859..034df964 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -567,6 +567,8 @@ export default { rowInfo: "Info", type: "Type", duration: "Duration", + seizureTime: "Call Start Time", + releaseTime: "Hangup Time", caller: "Caller", called: "Called", result: "Result", @@ -1069,6 +1071,8 @@ export default { expression:'Expression', description:' Description', kpiSet:' Statistical Settings', + sixHoursAgo:'Six hours ago', + threeHoursAgo:'Three hours ago.', delCustomTip:'Confirm deletion of data item with record number {num}?', delCustom:' Successfully delete record number {num} custom indicator', addCustom:' Add custom indicator', @@ -1095,6 +1099,7 @@ export default { "layout3": "Layout 3" }, kpiOverView:{ + "kpiChartTitle":"Overview of NE metrics", "changeLine":"Change to Line Charts", "changeBar":"Change to Bar Charts", "chooseShowMetrics":"Select the metric you want to display", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index c64365a0..a0905711 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -567,6 +567,8 @@ export default { rowInfo: "记录信息", type: "记录类型", duration: "通话时长", + seizureTime: "呼叫开始时间", + releaseTime: "挂断结束时间", caller: "主叫", called: "被叫", result: "结果", @@ -1069,6 +1071,8 @@ export default { expression:'计算公式', description:'描述', kpiSet:'统计设置', + sixHoursAgo:'6小时前', + threeHoursAgo:'3小时前', delCustomTip:'确认删除记录编号为 {num} 的数据项?', delCustom:'成功删除记录编号为 {num} 自定义指标', addCustom:'添加自定义指标', @@ -1095,6 +1099,7 @@ export default { "layout3": "布局3" }, kpiOverView:{ + "kpiChartTitle":"网元指标概览", "changeLine":"切换为折线图", "changeBar":"切换为柱状图", "chooseShowMetrics":"选择需要显示的指标", @@ -1341,8 +1346,8 @@ export default { filter: "全局过滤", startTime: '开始时间', endTime: '结束时间', - today: '昨天', - yesterday: '今天', + today: '今天', + yesterday: '昨天', week: '本周', month: '本月', avgLoad: '平均负载', diff --git a/src/views/dashboard/imsCDR/index.vue b/src/views/dashboard/imsCDR/index.vue index a93a455e..2e308c3a 100644 --- a/src/views/dashboard/imsCDR/index.vue +++ b/src/views/dashboard/imsCDR/index.vue @@ -167,6 +167,13 @@ let tableColumns: ColumnsType = [ return cdrJSON.calledParty; }, }, + { + title: t('views.dashboard.cdr.result'), + dataIndex: 'cdrJSON', + key: 'cause', + align: 'left', + width: 150, + }, { title: t('views.dashboard.cdr.duration'), dataIndex: 'cdrJSON', @@ -181,20 +188,29 @@ let tableColumns: ColumnsType = [ }, }, { - title: t('views.dashboard.cdr.result'), + title: t('views.dashboard.cdr.seizureTime'), dataIndex: 'cdrJSON', - key: 'cause', align: 'left', - width: 150, - }, - { - title: t('views.dashboard.cdr.time'), - dataIndex: 'cdrJSON', - align: 'center', - width: 150, + width: 200, customRender(opt) { const cdrJSON = opt.value; - return parseDateToStr(+cdrJSON.releaseTime * 1000); + if (typeof cdrJSON.seizureTime === 'number') { + return parseDateToStr(+cdrJSON.seizureTime * 1000); + } + return cdrJSON.seizureTime; + }, + }, + { + title: t('views.dashboard.cdr.releaseTime'), + dataIndex: 'cdrJSON', + align: 'left', + width: 200, + customRender(opt) { + const cdrJSON = opt.value; + if (typeof cdrJSON.releaseTime === 'number') { + return parseDateToStr(+cdrJSON.releaseTime * 1000); + } + return cdrJSON.releaseTime; }, }, { @@ -774,61 +790,73 @@ onBeforeUnmount(() => { diff --git a/src/views/neUser/pcf/index.vue b/src/views/neUser/pcf/index.vue index 93408d67..662de620 100644 --- a/src/views/neUser/pcf/index.vue +++ b/src/views/neUser/pcf/index.vue @@ -780,9 +780,6 @@ onMounted(() => { ok-text="TXT" ok-type="default" @confirm="fnExportList('txt')" - :show-cancel="false" - cancel-text="CSV" - @cancel="fnExportList('csv')" > diff --git a/src/views/perfManage/kpiCReport/index.vue b/src/views/perfManage/kpiCReport/index.vue index ed147d0f..dce804f7 100644 --- a/src/views/perfManage/kpiCReport/index.vue +++ b/src/views/perfManage/kpiCReport/index.vue @@ -24,10 +24,9 @@ import { onBeforeUnmount, } from 'vue'; import { PageContainer } from 'antdv-pro-layout'; -import { message, Modal } from 'ant-design-vue/lib'; -import { ColumnsType } from 'ant-design-vue/lib/table'; -import { SizeType } from 'ant-design-vue/lib/config-provider'; -import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'; +import { message, Modal } from 'ant-design-vue/es'; +import { SizeType } from 'ant-design-vue/es/config-provider'; +import { MenuInfo } from 'ant-design-vue/es/menu/src/interface'; import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue'; import { RESULT_CODE_ERROR, @@ -43,6 +42,7 @@ import saveAs from 'file-saver'; import { generateColorRGBA } from '@/utils/generate-utils'; import { OptionsType, WS } from '@/plugins/ws-websocket'; import { useRoute } from 'vue-router'; +import dayjs, { Dayjs } from 'dayjs'; const neInfoStore = useNeInfoStore(); const route = useRoute(); const { t, currentLocale } = useI18n(); @@ -78,11 +78,24 @@ let neCascaderOptions = ref[]>([]); /**记录开始结束时间 */ let queryRangePicker = ref<[string, string]>(['', '']); +/**时间选择 */ +let ranges = ref>({ + [t('views.perfManage.customTarget.sixHoursAgo')]: [ + dayjs().subtract(6, 'hours'), + dayjs(), + ], + [t('views.perfManage.customTarget.threeHoursAgo')]: [ + dayjs().subtract(3, 'hours'), + dayjs(), + ], + [t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()], +}); + /**表格字段列 */ -let tableColumns = ref([]); +let tableColumns = ref([]); /**表格字段列排序 */ -let tableColumnsDnd = ref([]); +let tableColumnsDnd = ref([]); /**表格分页器参数 */ let tablePagination = reactive({ @@ -261,12 +274,14 @@ function fnGetListTitle() { tableColumns.value = []; tableColumnsDnd.value = []; fnRanderChartData(); - return false; + return false; } tableColumns.value = []; - const columns: ColumnsType = []; + const columns: any[] = []; for (const item of res.data) { - const kpiDisplay = item[`unit`]? item[`title`]+ `(${item['unit']})`:item[`title`]; + const kpiDisplay = item[`unit`] + ? item[`title`] + `(${item['unit']})` + : item[`title`]; const kpiValue = item[`kpiId`]; columns.push({ title: kpiDisplay, @@ -627,9 +642,9 @@ onMounted(() => { const now = new Date(); now.setMinutes(0, 0, 0); - queryRangePicker.value[0] = parseDateToStr(now.getTime()); + queryRangePicker.value[0] = `${now.getTime()}`; now.setMinutes(59, 59, 59); - queryRangePicker.value[1] = parseDateToStr(now.getTime()); + queryRangePicker.value[1] = `${now.getTime()}`; fnGetListTitle(); // 绘图 fnRanderChart(); @@ -659,10 +674,7 @@ onBeforeUnmount(() => { - + { :allow-clear="false" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" - value-format="YYYY-MM-DD HH:mm:ss" + value-format="x" + :ranges="ranges" style="width: 100%" > diff --git a/src/views/perfManage/kpiOverView/index.vue b/src/views/perfManage/kpiOverView/index.vue index e8914166..1163c55b 100644 --- a/src/views/perfManage/kpiOverView/index.vue +++ b/src/views/perfManage/kpiOverView/index.vue @@ -3,22 +3,35 @@ import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'; import * as echarts from 'echarts/core'; import { LegendComponent } from 'echarts/components'; import { LineChart, BarChart } from 'echarts/charts'; -import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components'; +import { + GridComponent, + TooltipComponent, + TitleComponent, +} from 'echarts/components'; import { CanvasRenderer } from 'echarts/renderers'; import { getKPITitle, listKPIData } from '@/api/perfManage/goldTarget'; import useI18n from '@/hooks/useI18n'; -import { message, Modal } from 'ant-design-vue'; -import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { message } from 'ant-design-vue'; +import { + RESULT_CODE_ERROR, + RESULT_CODE_SUCCESS, +} from '@/constants/result-constants'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; import { OptionsType, WS } from '@/plugins/ws-websocket'; import { generateColorRGBA } from '@/utils/generate-utils'; -import { BarChartOutlined, LineChartOutlined, UnorderedListOutlined } from '@ant-design/icons-vue'; +import { + BarChartOutlined, + LineChartOutlined, + UnorderedListOutlined, + DownOutlined, + MoreOutlined, +} from '@ant-design/icons-vue'; // 在这里定义 ChartDataItem 接口 interface ChartDataItem { - date: string; // 将存储完整的时间字符串,包含时分秒 - [kpiId: string]: string | number; // 动态指标值 + date: string; // 将存储完整的时间字符串,包含时分秒 + [kpiId: string]: string | number; // 动态指标 } echarts.use([ @@ -28,7 +41,7 @@ echarts.use([ TooltipComponent, TitleComponent, CanvasRenderer, - LegendComponent + LegendComponent, ]); // WebSocket连接 const ws = ref(null); @@ -36,7 +49,7 @@ const ws = ref(null); //日期范围响应式变量 const dateRange = ref<[string, string]>([ dayjs().startOf('day').valueOf().toString(), - dayjs().valueOf().toString() + dayjs().valueOf().toString(), ]); //实时数据状态 const isRealtime = ref(false); @@ -49,7 +62,7 @@ let chart: echarts.ECharts | null = null; //observer 变量 监听图表容器大小 let observer: ResizeObserver | null = null; -//日期变化时触发数据变化时触发 +//日期变化时更新图表数据 const handleDateChange = ( value: [string, string] | [Dayjs, Dayjs], dateStrings: [string, string] @@ -61,21 +74,19 @@ const handleDateChange = ( dateRange.value = [ dayjs(dateStrings[0]).valueOf().toString(), - dayjs(dateStrings[1]).valueOf().toString() + dayjs(dateStrings[1]).valueOf().toString(), ]; - - fetchChartData(); }; -//切换实时数据方法 +//切换实时数据 const toggleRealtime = () => { fnRealTimeSwitch(isRealtime.value); }; // 定义所有网元类型 const ALL_NE_TYPES = ['AMF', 'SMF', 'UPF', 'MME', 'IMS', 'SMSC'] as const; -type NeType = typeof ALL_NE_TYPES[number]; +type NeType = (typeof ALL_NE_TYPES)[number]; // 定义要筛选的指标 ID,按网元类型组织 const TARGET_KPI_IDS: Record = { @@ -84,13 +95,13 @@ const TARGET_KPI_IDS: Record = { UPF: ['UPF.03', 'UPF.04', 'UPF.05', 'UPF.06'], MME: ['MME.A.01', 'MME.A.02', 'MME.A.03'], IMS: ['SCSCF.01', 'SCSCF.02', 'SCSCF.05', 'SCSCF.06'], - SMSC: ['SMSC.A.01', 'SMSC.A.02', 'SMSC.A.03'] + SMSC: ['SMSC.A.01', 'SMSC.A.02', 'SMSC.A.03'], }; // 实时数据开关函数 const fnRealTimeSwitch = (bool: boolean) => { if (bool) { - if(!ws.value) { + if (!ws.value) { ws.value = new WS(); } chartData.value = []; @@ -104,18 +115,18 @@ const fnRealTimeSwitch = (bool: boolean) => { onerror: wsError, }; ws.value.connect(options); - } else if(ws.value) { - ws.value.close();//断开链接 - ws.value = null;//清空链接 + } else if (ws.value) { + ws.value.close(); //断开链接 + ws.value = null; //清空链接 } -} +}; // 接收数据后错误回调 const wsError = () => { message.error(t('common.websocketError')); -} +}; -// 收数据回调 +// 接收数据后回调 const wsMessage = (res: Record) => { const { code, data } = res; if (code === RESULT_CODE_ERROR) { @@ -136,10 +147,10 @@ const wsMessage = (res: Record) => { const newData: ChartDataItem = { date: kpiEvent.timeGroup ? kpiEvent.timeGroup.toString() - : Date.now().toString() + : Date.now().toString(), }; - // 只添加已选中的指标数据 + // 只添加已选中的指标的数据 selectedKPIs.value.forEach(kpiId => { if (kpiEvent[kpiId] !== undefined) { newData[kpiId] = Number(kpiEvent[kpiId]); @@ -152,7 +163,7 @@ const wsMessage = (res: Record) => { updateChartData(newData); }; -// 取图表数据 +// 获取图表数据方法 const fetchChartData = async () => { if (kpiColumns.value.length === 0) { console.warn('No KPI columns available'); @@ -180,18 +191,16 @@ const fetchChartData = async () => { sortField: 'timeGroup', sortOrder: 'asc', interval: 5, - kpiIds: TARGET_KPI_IDS[neType].join(',') + kpiIds: TARGET_KPI_IDS[neType].join(','), }; - - const res = await listKPIData(params); if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { allData.push(...res.data); } } - // 按时间分组合并数据 + // 按时间分组合数据 const groupedData = new Map(); allData.forEach(item => { const timeKey = item.timeGroup; @@ -215,7 +224,6 @@ const fetchChartData = async () => { return dataItem; }); - updateChart(); } catch (error) { console.error('Failed to fetch chart data:', error); @@ -223,10 +231,10 @@ const fetchChartData = async () => { } }; -// 添加一个 Map 来存储每个指标的固定颜色 +// 添加一个 Map 来存储每个指标的临时固定颜色 const kpiColors = new Map(); -// 加图表类型的响应式变量 +// 定义图表类型的响应式变量 const chartType = ref<'line' | 'bar'>('line'); // 添加切换图表类型的方法 @@ -235,51 +243,55 @@ const toggleChartType = () => { updateChart(); }; -// 修改 updateChart 函数中的系列配置 +// 更新图表 const updateChart = () => { - if (!chart || !kpiColumns.value.length) return; - - const filteredColumns = kpiColumns.value.filter(col => selectedKPIs.value.includes(col.kpiId)); - const legendData = filteredColumns.map(item => item.title); - + if (!chart || !kpiColumns.value.length) return; //首先检查图表实例和指标是否存在 + //过滤出已选择的指标列 + const filteredColumns = kpiColumns.value.filter(col => + selectedKPIs.value.includes(col.kpiId) + ); + const legendData = filteredColumns.map(item => item.title); //创建图例数据数组,包含所有选中的指标的标题 + //为每个选中的指标创建一个系列配置 const series = filteredColumns.map(item => { const color = kpiColors.get(item.kpiId) || generateColorRGBA(); if (!kpiColors.has(item.kpiId)) { - kpiColors.set(item.kpiId, color); + kpiColors.set(item.kpiId, color); //保持指标颜色的临时一致性 } return { name: item.title, type: chartType.value, // 使用当前选择的图表类型 - data: chartData.value.length > 0 - ? chartData.value.map(dataItem => dataItem[item.kpiId] || 0) - : [0], + data: + chartData.value.length > 0 + ? chartData.value.map(dataItem => dataItem[item.kpiId] || 0) + : [0], smooth: chartType.value === 'line', // 只在折线图时使用平滑 symbol: chartType.value === 'line' ? 'circle' : undefined, // 只在折线图时显示标记 symbolSize: chartType.value === 'line' ? 6 : undefined, showSymbol: chartType.value === 'line', - itemStyle: { color } + itemStyle: { color }, }; }); - + //图表配置对象 const option = { title: { - text: '网元指标概览', - left: 'center' + text: t('views.perfManage.kpiOverView.kpiChartTitle'), + left: 'center', }, tooltip: { trigger: 'axis', - position: function(pt: any) { + position: function (pt: any) { return [pt[0], '10%']; }, }, legend: { + //图例配置 data: legendData, type: 'scroll', orient: 'horizontal', top: 25, textStyle: { - fontSize: 12 + fontSize: 12, }, selected: Object.fromEntries(legendData.map(name => [name, true])), show: true, @@ -289,46 +301,50 @@ const updateChart = () => { padding: [5, 10], }, grid: { + //网格配置 left: '3%', right: '4%', bottom: '3%', top: 100, - containLabel: true + containLabel: true, }, xAxis: { + //x轴配置 type: 'category', boundaryGap: false, - data: chartData.value.length > 0 - ? chartData.value.map(item => { - // 将时间戳转换为包含时分秒的格式 - return dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss'); - }) - : [''], + data: + chartData.value.length > 0 + ? chartData.value.map(item => { + // 将时间戳转换为包含时分秒的格式 + return dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss'); + }) + : [''], axisLabel: { formatter: (value: string) => { // 自定义 x 轴标签的显示格式 return dayjs(value).format('YYYY-MM-DD HH:mm:ss'); }, rotate: 0, - interval: 'auto', // 自动计算显示间隔 - align: 'right' - } + interval: 'auto', // 自动计算显示间隔 + align: 'right', + }, }, yAxis: { + // y轴配置 type: 'value', axisLabel: { - formatter: '{value}' + formatter: '{value}', }, // 添加自动计算的分割段数 splitNumber: 5, // 添加自动计算的最小/最大值围 - scale: true + scale: true, }, - series: series + series: series, //配置数据 }; - chart.setOption(option); - chart.resize(); + chart.setOption(option); //使用新的配置更新图表 + chart.resize(); //调整图表大小以适应容器 // 如果已经有 observer,先断开连接 if (observer) { @@ -361,7 +377,6 @@ onMounted(async () => { chart = echarts.init(container); if (kpiColumns.value.length > 0) { - updateChart(); await fetchChartData(); } else { @@ -378,101 +393,136 @@ onMounted(async () => { } }); -// 定义指标列类型 +// 定义指列类型 interface KPIColumn { title: string; dataIndex: string; key: string; kpiId: string; + neType: string; // 添加网元类型字段 } // 存储指标列信 const kpiColumns = ref([]); -// 添加选中标的的状态 +// 添加选中指标的的状态 const selectedKPIs = ref([]); // 添加对话框可见性状态 const isModalVisible = ref(false); +// 添加临时存储下拉框选择的数组 +const tempSelectedKPIs = ref([]); -// 打开对话框 +// 添加一个变量保存打开对话框时的选择状态 +const originalSelectedKPIs = ref([]); + +// 打开对话框的方法 const showKPISelector = () => { + // 保存当前的选择状态 + originalSelectedKPIs.value = [...selectedKPIs.value]; + + // 初始化临时选择为当前已选择的其他指标 + const primaryKPIs = Object.values(TARGET_KPI_IDS).flat(); + tempSelectedKPIs.value = selectedKPIs.value.filter( + kpiId => !primaryKPIs.includes(kpiId) + ); isModalVisible.value = true; }; -// 添加保存选中指标到 localStorage 的方法 +// 保存选中指标到 localStorage 的方法 const saveSelectedKPIs = () => { localStorage.setItem('selectedKPIs', JSON.stringify(selectedKPIs.value)); }; -// 窗口确认按钮 -const handleModalOk = () => { - saveSelectedKPIs(); // 保存选择 - updateChart(); +// 取消按钮的处理方法 +const handleModalCancel = () => { + // 恢复到打开对话框时的选择状态 + selectedKPIs.value = [...originalSelectedKPIs.value]; + // 清空临时选择 + tempSelectedKPIs.value = []; isModalVisible.value = false; }; -// 取消按钮 -const handleModalCancel = () => { +// 确认按钮的处理方法 +const handleModalOk = () => { + // 获取主要指标列表 + const primaryKPIs = Object.values(TARGET_KPI_IDS).flat(); + + // 获取当前在主界面选中的主要指标 + const selectedPrimaryKPIs = selectedKPIs.value.filter(kpiId => + primaryKPIs.includes(kpiId) + ); + + // 合并选中的主要指标和临时选中的其他指标 + selectedKPIs.value = Array.from( + new Set([ + ...selectedPrimaryKPIs, // 只包含已选中的主要指标 + ...tempSelectedKPIs.value, // 临时选中的其他指标 + ]) + ); + + // 清空临时选择和原始选择 + tempSelectedKPIs.value = []; + originalSelectedKPIs.value = []; + + // 保存选择并更新图表 + saveSelectedKPIs(); + updateChart(); isModalVisible.value = false; }; // 获取网元指标 const fetchSpecificKPI = async () => { - const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0]; + const language = + currentLocale.value.split('_')[0] === 'zh' + ? 'cn' + : currentLocale.value.split('_')[0]; try { let allKPIs: KPIColumn[] = []; - // 使用 ALL_NE_TYPES 遍历网元类型 + // 1. 获取所有网元的全部指标 for (const neType of ALL_NE_TYPES) { - - const res = await getKPITitle(neType); + const res = await getKPITitle(neType.toUpperCase()); if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { - // 过滤当前网元类型的指标 - const filteredKPIs = res.data.filter(item => - TARGET_KPI_IDS[neType].some(targetId => - item.kpiId === targetId || // 完全匹配 - item.kpiId.startsWith(targetId) // 前缀匹配 - ) - ); - - // 转换并添加到总指标列表 - const formattedKPIs = filteredKPIs.map(item => ({ + // 转换指标格式 + const formattedKPIs = res.data.map(item => ({ title: item[`${language}Title`], dataIndex: item.kpiId, key: item.kpiId, - kpiId: item.kpiId + kpiId: item.kpiId, + neType: neType, // 添加网元类型信息 })); + + // 添加到所有指标数组 allKPIs = [...allKPIs, ...formattedKPIs]; } } - // 更新指标列信息 + // 2. 更新所有指标到 kpiColumns kpiColumns.value = allKPIs; - // 尝试加载保存的选择 + // 3. 尝试加载保存的选择 const savedKPIs = localStorage.getItem('selectedKPIs'); if (savedKPIs) { - // 确保保存的选择仍然存在于前指标中 - const validSavedKPIs = JSON.parse(savedKPIs).filter( - (kpiId: string) => kpiColumns.value.some(col => col.kpiId === kpiId) + // 确保保存的选择仍然存在于当前指标中 + const validSavedKPIs = JSON.parse(savedKPIs).filter((kpiId: string) => + kpiColumns.value.some(col => col.kpiId === kpiId) ); if (validSavedKPIs.length > 0) { selectedKPIs.value = validSavedKPIs; } else { - // 如果没有有效的保存选择,则默认全选 - selectedKPIs.value = kpiColumns.value.map(col => col.kpiId); + // 如果没有有效的保存选择,则默认选择要指标 + selectedKPIs.value = Object.values(TARGET_KPI_IDS).flat(); } } else { - // 如果没有保存的选择,则默认全选 - selectedKPIs.value = kpiColumns.value.map(col => col.kpiId); + // 如果没有保存的选择,则默认选择重要指标 + selectedKPIs.value = Object.values(TARGET_KPI_IDS).flat(); } if (kpiColumns.value.length === 0) { - console.warn('No matching KPIs found'); + console.warn('No KPIs found'); } else { - console.log(`Found ${kpiColumns.value.length} total KPIs:`, - kpiColumns.value.map(kpi => kpi.kpiId)); + console.log(`Found ${kpiColumns.value.length} total KPIs`); } return kpiColumns.value; @@ -483,9 +533,9 @@ const fetchSpecificKPI = async () => { } }; -// 确保只保留一个 onUnmounted 钩子 +// onUnmounted 钩子 onUnmounted(() => { - if(ws.value && ws.value.state() === WebSocket.OPEN) { + if (ws.value && ws.value.state() === WebSocket.OPEN) { ws.value.close(); } if (observer) { @@ -502,7 +552,7 @@ onUnmounted(() => { const { t, currentLocale } = useI18n(); -// 修改 updateChartData 方法,只更新数据而不重新渲染整个图表 +// 更新图表数据方法 const updateChartData = (newData: ChartDataItem) => { chartData.value.push(newData); if (chartData.value.length > 100) { @@ -514,17 +564,17 @@ const updateChartData = (newData: ChartDataItem) => { xAxis: { data: chartData.value.map(item => dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss') - ) + ), }, series: selectedKPIs.value.map(kpiId => ({ type: chartType.value, // 使用当前选择的图表类型 - data: chartData.value.map(item => item[kpiId] || 0) - })) + data: chartData.value.map(item => item[kpiId] || 0), + })), }); } }; -// 修改 groupedKPIs 计算属性,使用 TARGET_KPI_IDS 来分组 +// groupedKPIs 计算属性,使用 TARGET_KPI_IDS 来分组过滤 const groupedKPIs = computed(() => { const groups: Record = {}; @@ -532,15 +582,52 @@ const groupedKPIs = computed(() => { // 使用 TARGET_KPI_IDS 中定义的指标 ID 来过滤 const targetIds = TARGET_KPI_IDS[neType]; groups[neType] = kpiColumns.value.filter(kpi => - targetIds.some(targetId => - kpi.kpiId === targetId || // 完全匹配 - kpi.kpiId.startsWith(targetId) // 前缀匹配 - ) + targetIds.includes(kpi.kpiId) ); }); return groups; }); +// 计算其他指标 +const secondaryKPIs = computed(() => { + const groups: Record = {}; + + if (kpiColumns.value.length === 0) { + console.warn('No KPI columns available'); + return groups; + } + + ALL_NE_TYPES.forEach(neType => { + // 获取当前网元类型的主要指标 ID + const primaryIds = TARGET_KPI_IDS[neType]; + + // 从所有指标中筛选出当前网元其他指标 + groups[neType] = kpiColumns.value.filter(kpi => { + // 检查是否不在主要指标列表中 + const isNotPrimary = !primaryIds.includes(kpi.kpiId); + + // 检查是否属于当前网元类型 + // 使用 getKPITitle API 返回的原始数据中的网元类型信息 + const isCurrentNeType = kpi.neType === neType; + + return isCurrentNeType && isNotPrimary; + }); + }); + return groups; +}); + +// 添加处理其他指标选择变化的方法 +const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => { + if (checked) { + // 如果选中,将指标 ID 添加到临时列表 + if (!tempSelectedKPIs.value.includes(kpiId)) { + tempSelectedKPIs.value = [...tempSelectedKPIs.value, kpiId]; + } + } else { + // 如果取消选中,从临时列表中移除指标 ID + tempSelectedKPIs.value = tempSelectedKPIs.value.filter(id => id !== kpiId); + } +}; diff --git a/src/views/system/setting/components/change-home-index.vue b/src/views/system/setting/components/change-home-index.vue index e96f039b..61464683 100644 --- a/src/views/system/setting/components/change-home-index.vue +++ b/src/views/system/setting/components/change-home-index.vue @@ -5,6 +5,7 @@ import useI18n from '@/hooks/useI18n'; import { listMenu } from '@/api/system/menu'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { getConfigKey, changeValue } from '@/api/system/config'; +import { parseDataToTree } from '@/utils/parse-tree-utils'; const { t } = useI18n(); type StateType = { @@ -59,14 +60,21 @@ onMounted(() => { listMenu(toRaw({ status: 1 })).then(res => { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { // 过滤旧前端菜单以及不是菜单类型以及路径为空 - res.data = res.data - .filter(i => i.perms !== 'page' && i.menuType === 'M' && i.component) - .map((item: any) => { - state.options.push({ - label: item.menuName, - value: item.component, - }); - }); + res.data = res.data.filter(i => i.perms !== 'page' && i.menuType !== 'B'); + state.options = parseDataToTree(res.data, 'menuId'); + const setDisabledAndComponent = (item: any) => { + if (!item.component) { + item.disabled = true; + item.component = 'Null' + item.menuId; + } + }; + + state.options.forEach((item: any) => { + setDisabledAndComponent(item); // 处理父菜单 + if (item.children && Array.isArray(item.children)) { + item.children.forEach(setDisabledAndComponent); // 处理子菜单 + } + }); } }); @@ -84,13 +92,22 @@ onMounted(() => {