diff --git a/package.json b/package.json index 4801fb8f..71bbe0a8 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "vue-i18n": "^9.13.1", "vue-router": "^4.4.0", "vue3-smooth-dnd": "^0.0.6", - "vuedraggable": "^4.1.0", "xlsx": "~0.18.5" }, "devDependencies": { diff --git a/src/views/perfManage/kpiKeyTarget/index.vue b/src/views/perfManage/kpiKeyTarget/index.vue index d662656f..57616248 100644 --- a/src/views/perfManage/kpiKeyTarget/index.vue +++ b/src/views/perfManage/kpiKeyTarget/index.vue @@ -11,11 +11,12 @@ import useI18n from '@/hooks/useI18n'; import { parseDateToStr } from '@/utils/date-utils'; import dayjs, { Dayjs } from 'dayjs'; import useNeInfoStore from '@/store/modules/neinfo'; -import { message, Switch, Form } from 'ant-design-vue'; +import { message } from 'ant-design-vue'; import { ColumnsType } from 'ant-design-vue/es/table'; import { generateColorRGBA } from '@/utils/generate-utils'; import { LineSeriesOption } from 'echarts/charts'; import { OptionsType, WS } from '@/plugins/ws-websocket'; +import { Select } from 'ant-design-vue'; const { t, currentLocale } = useI18n(); const neInfoStore = useNeInfoStore(); @@ -31,10 +32,92 @@ const handleRealTimeSwitch = (checked: any) => { }; -// 网元数组 -const networkElementTypes = ['udm', 'upf', 'amf', 'smf', 'ims','ausf'] as const; -// 定义 ChartType -type ChartType = typeof networkElementTypes[number]; // 将 i 的类型改为 ChartType +// 定义所有可能的网元类型 +const ALL_NE_TYPES = ['ims', 'amf', 'udm', 'smf', 'pcf','upf','mme','mocngw','smsc','cbc','ausf'] as const; +type AllChartType = typeof ALL_NE_TYPES[number]; + +// 使用 ref 来使 networkElementTypes 变为响应式,并使用 ALL_NE_TYPES 初始化 +const networkElementTypes = ref([...ALL_NE_TYPES]); + +// 添加选择的网元类型,也使用 ALL_NE_TYPES 初始化 +const selectedNeTypes = ref([...ALL_NE_TYPES]); + +// 监听 selectedNeTypes 的变化 +watch(selectedNeTypes, (newTypes) => { + //console.log('Selected types changed:', newTypes); + if (JSON.stringify(newTypes) !== JSON.stringify(networkElementTypes.value)) { + networkElementTypes.value = newTypes; + + // 更新 chartOrder + chartOrder.value = chartOrder.value.filter(item => newTypes.includes(item.i)); + + newTypes.forEach((type) => { + if (!chartOrder.value.some(item => item.i === type)) { + chartOrder.value.push({ + x: (chartOrder.value.length % 2) * 6, + y: Math.floor(chartOrder.value.length / 2) * 4, + w: 6, + h: 4, + i: type, + }); + } + // 确保 chartStates 包含新的网元类型 + if (!chartStates[type]) { + chartStates[type] = createChartState(); + } + }); + + //console.log('Updated chartOrder:', chartOrder.value); + + // 保存选中的网元类型到本地存储 + localStorage.setItem('selectedNeTypes', JSON.stringify(newTypes)); + + // 重新初始化图表 + nextTick(() => { + initCharts(); + }); + } +}, { deep: true }); + +// 初始化所有图表的函数 +const initCharts = async () => { + //console.log('Initializing charts for:', networkElementTypes.value); + + // 清除不再需要的图表 + Object.keys(chartStates).forEach((key) => { + if (!networkElementTypes.value.includes(key as AllChartType)) { + const state = chartStates[key as AllChartType]; + if (state.chart.value) { + state.chart.value.dispose(); + } + if (state.observer.value) { + state.observer.value.disconnect(); + } + delete chartStates[key as AllChartType]; + } + }); + + // 初始化或更新需要的图表 + for (const type of networkElementTypes.value) { + //console.log('Initializing chart for:', type); + if (!chartStates[type]) { + chartStates[type] = createChartState(); + } + try { + await fetchKPITitle(type); + await nextTick(); + initChart(type); + await fetchData(type); + } catch (error) { + console.error(`Error initializing chart for ${type}:`, error); + } + } + + //console.log('Finished initializing charts'); + + // 保存更新后的布局 + localStorage.setItem('chartOrder', JSON.stringify(chartOrder.value)); +}; // 添加类型定义 interface LayoutItem { @@ -42,17 +125,18 @@ interface LayoutItem { y: number; w: number; h: number; - i: ChartType; // 将 i 的类型改为 ChartType + i: AllChartType; // 将 ChartType 改为 AllChartType } type Layout = LayoutItem[]; //构建响应式数组储存图表类型数据 const chartOrder = ref( - networkElementTypes.map((type, index) => ({ + JSON.parse(localStorage.getItem('chartOrder') || 'null') || + networkElementTypes.value.map((type, index) => ({ x: index % 2 * 6, // 每行两个图表,宽度为6 y: Math.floor(index / 2) * 4, // 每个图表占据 4 个单位高度 - w: 6, // 宽度为6个单位 + w: 6, // 宽度为6单位 h: 4, // 高度为4个单位 i: type, // 使用网元类型作为唯一标识符 })) @@ -60,15 +144,20 @@ const chartOrder = ref( // 改变布局触发更新 const handleLayoutUpdated = (newLayout: Layout) => { - chartOrder.value = newLayout; - nextTick(() => { - newLayout.forEach((item) => { - const state = chartStates[item.i]; - if (state.chart.value) { - state.chart.value.resize(); - } + const filteredLayout = newLayout.filter(item => networkElementTypes.value.includes(item.i)); + if (JSON.stringify(filteredLayout) !== JSON.stringify(chartOrder.value)) { + chartOrder.value = filteredLayout; + // 保存布局到 localStorage + localStorage.setItem('chartOrder', JSON.stringify(chartOrder.value)); + nextTick(() => { + chartOrder.value.forEach((item) => { + const state = chartStates[item.i]; + if (state?.chart.value) { + state.chart.value.resize(); + } + }); }); - }); + } }; // 监听 chartOrder 的变化 @@ -116,26 +205,30 @@ const createChartState = () => { }; }; -// 为每种图表类型创建状态 -const chartStates: Record> = Object.fromEntries( - networkElementTypes.map(type => [type, createChartState()]) -) as Record>; +// 为每种图表类型创建状 +const chartStates: Record> = Object.fromEntries( + networkElementTypes.value.map(type => [type, createChartState()]) +) as Record>; //日期选择器 -interface RangePicker extends Record { +interface RangePicker extends Record { placeholder: [string, string]; ranges: Record; } // 创建日期选择器状态 const rangePicker = reactive({ - ...Object.fromEntries(networkElementTypes.map(type => [ + ...Object.fromEntries(networkElementTypes.value.map(type => [ type, + // [ + // dayjs('2024-09-20 00:00:00').valueOf().toString(),//拟数据的日期设2024.9.20 + // dayjs('2024-09-20 23:59:59').valueOf().toString() + // ] [ - dayjs('2024-09-20 00:00:00').valueOf().toString(),//模拟数据的日期设为默认日期 - dayjs('2024-09-20 23:59:59').valueOf().toString() + dayjs().startOf('day').valueOf().toString(), // 当天 0 点 0 分 0 秒 + dayjs().valueOf().toString() // 当前时间 ] - ])) as Record, + ])) as Record, placeholder: [t('views.monitor.monitor.startTime'), t('views.monitor.monitor.endTime')] as [string, string], ranges: { [t('views.monitor.monitor.yesterday')]: [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')], @@ -146,93 +239,100 @@ const rangePicker = reactive({ }); // 创建可复用的图表初始化函数 -const initChart = (type: ChartType) => { - nextTick(() => { - const state = chartStates[type]; - const container = state.chartDom.value; - if (!container) return; - - state.chart.value = markRaw(echarts.init(container, 'light')); - const option: echarts.EChartsOption = { - tooltip: { - trigger: 'axis', - position: function(pt: any) { - return [pt[0], '10%']; - }, - }, - xAxis: { - type: 'category', - boundaryGap: false, - data: [], - }, - yAxis: { - type: 'value', - boundaryGap: [0, '100%'], - }, - legend: { - type: 'scroll', - orient: 'horizontal', - top: -5, - itemWidth: 20, - textStyle: { - color: '#646A73', - }, - icon: 'circle', - selected: {}, - }, - grid: { - left: '10%', - right: '10%', - bottom: '15%', - }, - dataZoom: [ - { - type: 'inside', - start: 0, - end: 100, - }, - { - start: 0, - end: 100, - }, - ], - series: [], - }; - state.chart.value.setOption(option); - state.chart.value.resize(); // 确保图表正确调整大小 - - // 创建 ResizeObserver 实例 - state.observer.value = new ResizeObserver(() => { - if (state.chart.value) { - state.chart.value.resize(); +const initChart = (type: AllChartType) => { + const tryInit = (retries = 3) => { + nextTick(() => { + const state = chartStates[type]; + if (!state) { + console.warn(`Chart state for ${type} not found`); + return; } - }); - - // 开始观察图表容器 - state.observer.value.observe(container); - }); -}; - -//结束拖拽事件 -const onDragEnd = () => { - nextTick(() => { - chartOrder.value.forEach((type:any) => { - const state = chartStates[type as ChartType]; - if (state.chart.value) { - state.chart.value.resize(); // 调整图表大小 - // 重新设置图表选项,保留原有数 - state.chart.value.setOption({ - xAxis: { data: state.chartDataXAxisData }, - series: state.chartDataYSeriesData, - }); + const container = state.chartDom.value; + if (!container) { + if (retries > 0) { + console.warn(`Chart container for ${type} not found, retrying... (${retries} attempts left)`); + setTimeout(() => tryInit(retries - 1), 100); + } else { + console.error(`Chart container for ${type} not found after multiple attempts`); + } + return; } + + if (state.chart.value) { + state.chart.value.dispose(); + } + + state.chart.value = markRaw(echarts.init(container)); + const option: echarts.EChartsOption = { + tooltip: { + trigger: 'axis', + position: function(pt: any) { + return [pt[0], '10%']; + }, + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: [], + }, + yAxis: { + type: 'value', + boundaryGap: [0, '100%'], + }, + legend: { + type: 'scroll', + orient: 'horizontal', + top: -5, + itemWidth: 20, + textStyle: { + color: '#646A73', + }, + icon: 'circle', + selected: {}, + }, + grid: { + left: '10%', + right: '10%', + bottom: '15%', + }, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100, + }, + { + start: 0, + end: 100, + }, + ], + series: [], + }; + state.chart.value.setOption(option); + state.chart.value.resize(); // 确保图表正确调整大小 + + // 创建 ResizeObserver 实例 + if (state.observer.value) { + state.observer.value.disconnect(); + } + state.observer.value = new ResizeObserver(() => { + if (state.chart.value) { + state.chart.value.resize(); + } + }); + + // 开始观察图表容器 + state.observer.value.observe(container); }); - }); + }; + + tryInit(); }; -// 可复用的数据获取函数 -const fetchData = async (type: ChartType) => { + +// 可复用的数据获函数 +const fetchData = async (type: AllChartType) => { const state = chartStates[type]; // 直接使用 type const neId = '001'; state.tableState.loading = true; @@ -250,11 +350,12 @@ const fetchData = async (type: ChartType) => { }); if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { state.tableState.data = res.data; - nextTick(() => { + await nextTick(() => { renderChart(type); }); } } catch (error) { + console.log("123") console.error(error); message.error(t('common.getInfoFail')); } finally { @@ -278,7 +379,7 @@ function fnRealTimeSwitch(bool: boolean) { const options: OptionsType = { url: '/ws', params: { - subGroupID: networkElementTypes.map(type => `10_${type.toUpperCase()}_001`).join(','), + subGroupID: networkElementTypes.value.map(type => `10_${type.toUpperCase()}_001`).join(','), }, onmessage: wsMessage, onerror: wsError, @@ -295,56 +396,59 @@ function fnRealTimeSwitch(bool: boolean) { } } -// 接收数据后错误回调 +// 接收数据后错误回 function wsError() { message.error(t('common.websocketError')); } -// 接收数据后回调 +// 修改 wsMessage 数 function wsMessage(res: Record) { - //const res = JSON.parse(event.data); const { code, data } = res; if (code === RESULT_CODE_ERROR) { console.warn(res.msg); return; } - // 订阅组信息 if (!data?.groupId) { return; } - // 处理四个图表的数据 - networkElementTypes.forEach((type) => { + networkElementTypes.value.forEach((type) => { const state = chartStates[type]; const kpiEvent:any = data.data[type.toUpperCase()]; if (kpiEvent) { - // 更新 X 轴数据 if (kpiEvent.timeGroup) { - state.chartDataXAxisData.push(parseDateToStr(+kpiEvent.timeGroup)); + const newTime = parseDateToStr(+kpiEvent.timeGroup); + state.chartDataXAxisData.push(newTime); if (state.chartDataXAxisData.length > 100) { state.chartDataXAxisData.shift(); } - } - // 更新 Y 轴数据 - state.chartDataYSeriesData.forEach(series => { - if (kpiEvent[series.customKey as string] !== undefined) { - series.data.push(+kpiEvent[series.customKey as string]); - if (series.data.length > 100) { - series.data.shift(); + // 使用 appendData 方法追加数据 + state.chartDataYSeriesData.forEach(series => { + if (kpiEvent[series.customKey as string] !== undefined) { + const newValue = +kpiEvent[series.customKey as string]; + if (state.chart.value) { + state.chart.value.appendData({ + seriesIndex: state.chartDataYSeriesData.indexOf(series), + data: [[newTime, newValue]] + }); + } + // 保持数据长度不超过100 + if (series.data.length > 100) { + series.data.shift(); + } } - } - }); - - // 更新图表 - if (state.chart.value) { - state.chart.value.setOption({ - xAxis: { data: state.chartDataXAxisData }, - series: state.chartDataYSeriesData, }); + + // 更新 X 轴 + if (state.chart.value) { + state.chart.value.setOption({ + xAxis: { data: state.chartDataXAxisData } + }); + } } } }); @@ -359,7 +463,7 @@ interface CustomSeriesOption extends Omit { data: (number | LineDataItem)[]; } // 创建可复用的图表渲染函数 -const renderChart = (type: ChartType) => { +const renderChart = (type: AllChartType) => { const state = chartStates[type]; if (state.chart.value == null) { return; @@ -426,11 +530,12 @@ const renderChart = (type: ChartType) => { }; -// 获取表头数据 -const fetchKPITitle = async (type: ChartType) => { +// 获取头数据 +const fetchKPITitle = async (type: AllChartType) => { const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0]; try { const res = await getKPITitle(type.toUpperCase()); + console.log(res); if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { chartStates[type].tableColumns.value = res.data.map(item => ({ title: item[`${language}Title`], @@ -444,21 +549,57 @@ const fetchKPITitle = async (type: ChartType) => { })); } } catch (error) { + console.log("321") console.error(error); message.warning(t('common.getInfoFail')); } }; -// 初始化所有图表 +// 定义默认选择的网元类型 +const DEFAULT_NE_TYPES: AllChartType[] = ['udm', 'amf', 'upf', 'ims']; + +// 在 onMounted 钩子中 onMounted(async () => { ws.value = new WS(); await neInfoStore.fnNelist(); - for (const type of networkElementTypes) { - await fetchKPITitle(type); - initChart(type); - fetchData(type); // 确保这行存在 + + // 从本地存储中读取选中的网元类型 + const savedSelectedNeTypes = localStorage.getItem('selectedNeTypes'); + if (savedSelectedNeTypes) { + const parsedSelectedNeTypes = JSON.parse(savedSelectedNeTypes) as AllChartType[]; + selectedNeTypes.value = parsedSelectedNeTypes; + networkElementTypes.value = parsedSelectedNeTypes; + } else { + // 如果没有保存的选中网元类型,则使用默认选择 + selectedNeTypes.value = [...DEFAULT_NE_TYPES]; + networkElementTypes.value = [...DEFAULT_NE_TYPES]; + // 保存这个默认选择到本地存储 + localStorage.setItem('selectedNeTypes', JSON.stringify(DEFAULT_NE_TYPES)); } + // 初始化或更新 chartOrder + const savedLayout = localStorage.getItem('chartOrder'); + if (savedLayout) { + const parsedLayout = JSON.parse(savedLayout); + // 只保留当前选中的网元类型的布局 + chartOrder.value = parsedLayout.filter((item: LayoutItem) => networkElementTypes.value.includes(item.i)); + } + + // 如果 chartOrder 为空或者不包含所有选中的网元,重新创建布局 + if (chartOrder.value.length === 0 || chartOrder.value.length !== networkElementTypes.value.length) { + chartOrder.value = networkElementTypes.value.map((type, index) => ({ + x: index % 2 * 6, + y: Math.floor(index / 2) * 4, + w: 6, + h: 4, + i: type, + })); + } + + //console.log('Initialized networkElementTypes:', networkElementTypes.value); + //console.log('Initialized chartOrder:', chartOrder.value); + + await initCharts(); }); // 在组件卸载时销毁图表实例 @@ -479,7 +620,7 @@ onUnmounted(() => {
-
+
@@ -555,6 +708,10 @@ onUnmounted(() => {