From 012fc44f08c9e87cf34be482d417c431e7c55911 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 28 Oct 2024 10:48:17 +0800 Subject: [PATCH 001/110] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E4=BE=9D=E8=B5=96=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 34 +++++++++++++++++----------------- src/main.ts | 7 +++---- vite.config.ts | 14 +------------- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 71bbe0a8..76e7b0c8 100644 --- a/package.json +++ b/package.json @@ -16,31 +16,31 @@ "@antv/g6": "~4.8.24", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-yaml": "^6.1.1", - "@codemirror/merge": "^6.6.3", + "@codemirror/merge": "^6.7.2", "@codemirror/theme-one-dark": "^6.1.2", - "@tato30/vue-pdf": "^1.10.0", - "@vueuse/core": "~10.10.1", + "@tato30/vue-pdf": "^1.11.2", + "@vueuse/core": "^11.1.0", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "ant-design-vue": "^3.2.20", - "antdv-pro-layout": "~3.3.5", - "antdv-pro-modal": "^3.1.0", + "ant-design-vue": "^4.2.5", + "antdv-pro-layout": "^4.1.8", + "antdv-pro-modal": "^4.0.5", "codemirror": "^6.0.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.11", "echarts": "~5.5.0", "file-saver": "^2.0.5", "grid-layout-plus": "^1.0.5", - "intl-tel-input": "^23.8.1", + "intl-tel-input": "^24.6.0", "js-base64": "^3.7.7", "js-cookie": "^3.0.5", "localforage": "^1.10.0", "nprogress": "^0.2.0", "p-queue": "~8.0.1", - "pinia": "^2.1.7", - "vue": "~3.3.13", - "vue-i18n": "^9.13.1", - "vue-router": "^4.4.0", + "pinia": "^2.2.4", + "vue": "^3.5.12", + "vue-i18n": "^10.0.4", + "vue-router": "^4.4.5", "vue3-smooth-dnd": "^0.0.6", "xlsx": "~0.18.5" }, @@ -48,14 +48,14 @@ "@types/crypto-js": "^4.2.2", "@types/file-saver": "^2.0.7", "@types/js-cookie": "^3.0.6", - "@types/node": "^18.0.0", + "@types/node": "^22.7.7", "@types/nprogress": "^0.2.3", - "@vitejs/plugin-vue": "^5.0.5", + "@vitejs/plugin-vue": "^5.1.4", "less": "^4.2.0", - "typescript": "~5.4.5", - "unplugin-vue-components": "~0.26.0", - "vite": "~5.3.1", + "typescript": "^5.6.3", + "unplugin-vue-components": "^0.27.4", + "vite": "5.4.10", "vite-plugin-compression": "~0.5.1", - "vue-tsc": "~2.0.22" + "vue-tsc": "^2.1.8" } } diff --git a/src/main.ts b/src/main.ts index 358d7988..f770c37a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,16 +4,15 @@ import App from './App.vue'; import router from './router'; import directive from './directive'; import i18n from './i18n'; -import ProModal from "antdv-pro-modal"; -import 'antdv-pro-layout/dist/style.css'; + import 'antdv-pro-modal/dist/style.css'; -import 'ant-design-vue/dist/antd.variable.min.css'; +import 'antdv-pro-layout/dist/style.css'; +import 'ant-design-vue/dist/reset.css'; const app = createApp(App); app.use(store); app.use(router); app.use(directive); app.use(i18n); -app.use(ProModal); app.mount('#app'); diff --git a/vite.config.ts b/vite.config.ts index bd38b8ef..ac1af06f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -41,18 +41,6 @@ export default defineConfig(({ mode }) => { }, extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], }, - css: { - preprocessorOptions: { - less: { - // DO NOT REMOVE THIS LINE - javascriptEnabled: true, - modifyVars: { - // hack: `true; @import 'ant-design-vue/dist/antd.variable.less'`, - // '@primary-color': '#eb2f96', // 全局主色 - }, - }, - }, - }, build: { target: 'esnext', // Use 'esnext' to support the latest features sourcemap: false, @@ -94,7 +82,7 @@ export default defineConfig(({ mode }) => { AntDesignVueResolver({ importStyle: false, resolveIcons: true, - cjs: true, // 避免es模块打包缺失 + cjs: false, // 统一使用es模块打包 }), ], }), From 327e82e05772e6f31165da4f0be3f7d858f431c0 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 28 Oct 2024 10:51:11 +0800 Subject: [PATCH 002/110] =?UTF-8?q?fix:=20=E5=B8=83=E5=B1=80=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=8D=87=E7=BA=A7=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 119 ++++++++---------- src/assets/background_dark.jpg | Bin 0 -> 114 bytes .../{background.jpg => background_light.jpg} | Bin src/assets/svg/dark.svg | 1 + src/assets/svg/light.svg | 1 + src/hooks/useTheme.ts | 2 +- src/i18n/locales/en-US.ts | 19 ++- src/i18n/locales/zh-CN.ts | 23 +++- src/layouts/BasicLayout.vue | 48 ++++--- src/layouts/components/RightContent.vue | 56 +++++++-- src/layouts/components/Tabs.vue | 17 ++- src/store/modules/layout.ts | 103 +++++++++++++-- src/store/modules/user.ts | 10 +- 13 files changed, 279 insertions(+), 120 deletions(-) create mode 100644 src/assets/background_dark.jpg rename src/assets/{background.jpg => background_light.jpg} (100%) create mode 100644 src/assets/svg/dark.svg create mode 100644 src/assets/svg/light.svg diff --git a/src/App.vue b/src/App.vue index 020167b8..fc9f4331 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,22 +1,48 @@ diff --git a/src/assets/background_dark.jpg b/src/assets/background_dark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1b64e7dff794db8d1a9744d940d72c7f66d3ddac GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*z3?w}c`(6i9Ea{HEjtmSN`?>!lvI6;R0X`wF zCT3Q81}5%N4AwvqaZeY=5RU7~KmPy!&(3zKgZbML!6mbJ7@h~Gem(Un^)yfkgQu&X J%Q~loCIFL(AJzZ> literal 0 HcmV?d00001 diff --git a/src/assets/background.jpg b/src/assets/background_light.jpg similarity index 100% rename from src/assets/background.jpg rename to src/assets/background_light.jpg diff --git a/src/assets/svg/dark.svg b/src/assets/svg/dark.svg new file mode 100644 index 00000000..9b27cc61 --- /dev/null +++ b/src/assets/svg/dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/light.svg b/src/assets/svg/light.svg new file mode 100644 index 00000000..161b3bd9 --- /dev/null +++ b/src/assets/svg/light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index 4ae9108c..e9cd450d 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -1,5 +1,5 @@ import { onBeforeMount } from 'vue'; -import { ConfigProvider, message } from 'ant-design-vue/lib'; +import { ConfigProvider, message } from 'ant-design-vue/es'; import { CACHE_LOCAL_PRIMARY_COLOR } from '@/constants/cache-keys-constants'; import { localGet, localSet } from '@/utils/cache-local-utils'; diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index aad058da..a277f380 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -192,6 +192,7 @@ export default { lockPasswd: "Unlock Password", lockPasswdTip: "No password can be set", fullscreen: "Full Screen", + theme: "Theme light/dark mode", logout: "Logout", profile: "Profile", settings: "Settings", @@ -317,6 +318,8 @@ export default { color: "Style color scheme", colorActions: "Overall style color scheme setting", colorRandomly: "Randomization", + theme: "Theme dark and light modes", + themeActions: "Toggle light/dark mode", navTheme: "Dark Menu", navThemeActions: "Menus that can only change the navigation mode", fixedHeader: "Fixed top navigation bar", @@ -1072,7 +1075,19 @@ export default { element:'Element', granularity:'Granularity', unit:'Unit', - } + }, + kpiKeyTarget:{ + "fullWidthLayout":"Full Width", + "twoColumnLayout":"Two Column", + "saveLayout": "Save Layout", + "restoreSaved": "Restore Layout", + "saveSuccess": " '{name}' saved successfully", + "restoreSavedSuccess": " '{name}' restored successfully", + "noSavedLayout": "No saved layout found for '{name}'", + "layout1": "Layout 1", + "layout2": "Layout 2", + "layout3": "Layout 3" + }, }, traceManage: { analysis: { @@ -1288,7 +1303,7 @@ export default { }, exportFile:{ fileName:'File Source', - downTip: "Confirm the download file name is [{fileName}] File?", + downTip: "Confirm the download file name is [{fileName}] File?", downTipErr: "Failed to get file", deleteTip: "Confirm the delete file name is [{fileName}] File?", deleteTipErr: "Failed to delete file", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 3f878c47..bce5a90c 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -192,6 +192,7 @@ export default { lockPasswd: "解锁密码", lockPasswdTip: "可不设置密码", fullscreen: "全屏显示", + theme: "主题明/暗模式", logout: "退出登录", profile: "个人中心", settings: "个人设置", @@ -317,6 +318,8 @@ export default { color: "风格配色", colorActions: "整体风格配色设置", colorRandomly: "随机", + theme: "主题明暗模式", + themeActions: "切换浅色/暗黑模式", navTheme: "深色菜单", navThemeActions: "只能改变导航模式的菜单", fixedHeader: "固定顶部导航栏", @@ -1072,7 +1075,19 @@ export default { element:'元素', granularity:'颗粒度', unit:'单位', - } + }, + kpiKeyTarget:{ + "fullWidthLayout":"全宽布局", + "twoColumnLayout":"两列布局", + "saveLayout": "保存布局", + "restoreSaved": "恢复布局", + "saveSuccess": " {name} 保存成功", + "restoreSavedSuccess": " {name} 恢复成功", + "noSavedLayout": "没有找到保存的布局 {name}", + "layout1": "布局1", + "layout2": "布局2", + "layout3": "布局3" + }, }, traceManage: { analysis: { @@ -1127,7 +1142,7 @@ export default { stopNotRun: "{title} 任务未运行", }, task: { - traceId: '跟踪编号', + traceId: '跟踪编号', trackType: '跟踪类型', trackTypePlease: '请选择跟踪类型', creater: '创建人', @@ -1288,7 +1303,7 @@ export default { }, exportFile:{ fileName:'文件来源', - downTip: "确认下载文件名为 【{fileName}】 文件?", + downTip: "确认下载文件名为 【{fileName}】 文件?", downTipErr: "文件获取失败", deleteTip: "确认删除文件名为 【{fileName}】 文件?", deleteTipErr: "文件删除失败", @@ -2110,7 +2125,7 @@ export default { hostSelectMore: "加载更多 {num}", hostSelectHeader: "主机列表", }, - ps:{ + ps:{ realTimeHigh:"高", realTimeLow:"低", realTimeRegular:"常规", diff --git a/src/layouts/BasicLayout.vue b/src/layouts/BasicLayout.vue index 9a3f9f4d..2d5dddcc 100644 --- a/src/layouts/BasicLayout.vue +++ b/src/layouts/BasicLayout.vue @@ -1,10 +1,9 @@ - + - + @@ -364,7 +362,7 @@ onUnmounted(() => { - + diff --git a/src/layouts/components/Tabs.vue b/src/layouts/components/Tabs.vue index b72f87b9..341bcd01 100644 --- a/src/layouts/components/Tabs.vue +++ b/src/layouts/components/Tabs.vue @@ -4,9 +4,9 @@ import { computed, watch } from 'vue'; import { useRouter } from 'vue-router'; import useTabsStore from '@/store/modules/tabs'; import useI18n from '@/hooks/useI18n'; -const { t } = useI18n(); const tabsStore = useTabsStore(); const router = useRouter(); +const { t } = useI18n(); defineProps({ /**标签栏宽度 */ @@ -112,7 +112,7 @@ watch(router.currentRoute, v => tabsStore.tabOpen(v), { immediate: true }); tabsStore.tabOpen(v), { immediate: true }); - + @@ -199,7 +199,18 @@ watch(router.currentRoute, v => tabsStore.tabOpen(v), { immediate: true }); } } +[data-theme='dark'] .tabs { + background: #141414; +} + .tabs :deep(.ant-tabs-nav:before) { border-bottom: none; } +.tabs :deep(.ant-tabs-nav-list .ant-tabs-tab) { + border-radius: 8px; +} +.tabs :deep(.ant-tabs-nav-list .ant-tabs-tab.ant-tabs-tab-active) { + border-bottom-right-radius: unset; + border-bottom-left-radius: unset; +} diff --git a/src/store/modules/layout.ts b/src/store/modules/layout.ts index c297498b..21c9cf34 100644 --- a/src/store/modules/layout.ts +++ b/src/store/modules/layout.ts @@ -1,16 +1,23 @@ -import { CACHE_LOCAL_PROCONFIG } from '@/constants/cache-keys-constants'; -import { localGetJSON, localSetJSON } from '@/utils/cache-local-utils'; +import { theme } from 'ant-design-vue/es'; +import type { ThemeConfig } from 'ant-design-vue/es/config-provider/context'; import { defineStore } from 'pinia'; +import { + CACHE_LOCAL_PRIMARY_COLOR, + CACHE_LOCAL_PROCONFIG, +} from '@/constants/cache-keys-constants'; +import { + localGet, + localGetJSON, + localSetJSON, +} from '@/utils/cache-local-utils'; /**布局参数类型 */ type LayoutStore = { - /**布局设置抽屉显示 */ - visible: boolean; /**布局配置 */ proConfig: { /**导航布局 */ layout: 'side' | 'top' | 'mix'; - /**全局主题色,需要导入样式文件 */ + /**全局主题色*/ theme: 'dark' | 'light'; /**菜单导航主题色 */ menuTheme: 'dark' | 'light'; @@ -29,10 +36,33 @@ type LayoutStore = { /**内容区域-导航标签项 */ tabRender: any | boolean | undefined; }; + /**主题配置 */ + themeConfig: ThemeConfig; /**水印内容 */ waterMarkContent: string; }; +/** + * 获取随机颜色范围 + * @returns 颜色 + */ +function getRandomColor(): string { + const colors: string[] = [ + '#f5222d', + '#fa541c', + '#fa8c16', + '#a0d911', + '#13c2c2', + '#1890ff', + '#722ed1', + '#eb2f96', + '#faad14', + '#52c41a', + ]; + const i = Math.floor(Math.random() * 10); + return colors[i]; +} + /**判断是否关闭内容区域 */ const proRender = (render: any) => (render === false ? false : undefined); @@ -40,7 +70,7 @@ const proRender = (render: any) => (render === false ? false : undefined); const proConfigLocal: LayoutStore['proConfig'] = localGetJSON( CACHE_LOCAL_PROCONFIG ) || { - layout: 'mix', + layout: 'side', theme: 'light', menuTheme: 'light', fixSiderbar: true, @@ -50,7 +80,6 @@ const proConfigLocal: LayoutStore['proConfig'] = localGetJSON( const useLayoutStore = defineStore('layout', { state: (): LayoutStore => ({ - visible: false, proConfig: { layout: proConfigLocal.layout, theme: proConfigLocal.theme, @@ -63,13 +92,27 @@ const useLayoutStore = defineStore('layout', { menuHeaderRender: proRender(proConfigLocal.menuHeaderRender), tabRender: proRender(proConfigLocal.tabRender), }, + themeConfig: { + algorithm: [theme.darkAlgorithm], + // algorithm: themeColor["dark"], + token: { + // colorBgContainer: "#fff", + colorPrimary: localGet(CACHE_LOCAL_PRIMARY_COLOR) || '#1890ff', + // borderRadius: 6, + }, + }, waterMarkContent: import.meta.env.VITE_APP_NAME, }), - actions: { - /**改变显示状态 */ - changeVisibleLayoutSetting() { - this.visible = !this.visible; + getters: { + getColorPrimary(): string { + let color = '#1890ff'; + if (this.themeConfig.token) { + color = this.themeConfig.token.colorPrimary || color; + } + return color; }, + }, + actions: { /**修改水印文字 */ changeWaterMark(text: string) { this.waterMarkContent = text; @@ -77,10 +120,48 @@ const useLayoutStore = defineStore('layout', { /**修改布局设置 */ changeConf(key: string, value: boolean | string | number | undefined) { if (Reflect.has(this.proConfig, key)) { + console.log(key, value); + if (key === 'theme') { + // const themeColor = { + // light: theme.defaultAlgorithm, + // compact: theme.compactAlgorithm, + // dark: theme.darkAlgorithm, + // }; + if (value === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + this.themeConfig.algorithm = [theme.darkAlgorithm]; + } else { + document.documentElement.setAttribute('data-theme', 'light'); + this.themeConfig.algorithm = [theme.defaultAlgorithm]; + } + } Reflect.set(this.proConfig, key, value); localSetJSON(CACHE_LOCAL_PROCONFIG, this.proConfig); } }, + /**主题色初始化 */ + initPrimaryColor() { + // 主题色初始化 + this.changePrimaryColor(this.getColorPrimary); + // 明暗模式初始化 + const themeMode = this.proConfig.theme; + document.documentElement.setAttribute('data-theme', themeMode); + this.changeConf('theme', themeMode); + }, + /** + * 主题色变更 + * @param color 颜色 + */ + changePrimaryColor(color?: string) { + if (!color) { + color = getRandomColor(); + } + + if (this.themeConfig && this.themeConfig.token) { + this.themeConfig.token.colorPrimary = color; + localStorage.setItem(CACHE_LOCAL_PRIMARY_COLOR, color); + } + }, }, }); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 1a9b1932..a7bb65a6 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,7 +1,7 @@ import defaultAvatar from '@/assets/images/default_avatar.png'; import useLayoutStore from './layout'; import { login, logout, getInfo } from '@/api/login'; -import { getToken, setToken, removeToken } from '@/plugins/auth-token'; +import { setToken, removeToken } from '@/plugins/auth-token'; import { defineStore } from 'pinia'; import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; @@ -133,10 +133,10 @@ const useUserStore = defineStore('user', { } // 水印文字信息=用户昵称 手机号 - let waterMarkContent = this.userName; - if (this.phonenumber) { - waterMarkContent = `${this.userName} ${this.phonenumber}`; - } + // let waterMarkContent = this.userName; + // if (this.phonenumber) { + // waterMarkContent = `${this.userName} ${this.phonenumber}`; + // } // useLayoutStore().changeWaterMark(waterMarkContent); useLayoutStore().changeWaterMark(''); } From 3c058ec107f971da5f9b6fac5b628b50497f881e Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 28 Oct 2024 10:53:03 +0800 Subject: [PATCH 003/110] =?UTF-8?q?feat:=20PCF=E8=A1=A5=E5=85=85=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0online=E5=92=8Coffline=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=9C=89=E5=8F=96=E6=B6=88=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/neUser/pcf/index.vue | 112 ++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/src/views/neUser/pcf/index.vue b/src/views/neUser/pcf/index.vue index 00831faa..94771746 100644 --- a/src/views/neUser/pcf/index.vue +++ b/src/views/neUser/pcf/index.vue @@ -1,9 +1,10 @@ @@ -56,13 +66,31 @@ function fnColorChange(e: Event) { + + {{ t('views.account.settings.theme') }} + + + {{ t('views.account.settings.navTheme') }} diff --git a/src/views/dashboard/overview/components/Topology/index.vue b/src/views/dashboard/overview/components/Topology/index.vue index 8c2116e1..54c57cc5 100644 --- a/src/views/dashboard/overview/components/Topology/index.vue +++ b/src/views/dashboard/overview/components/Topology/index.vue @@ -2,7 +2,7 @@ import { onMounted, ref } from 'vue'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { listAllNeInfo } from '@/api/ne/neInfo'; -import { message } from 'ant-design-vue/lib'; +import { message } from 'ant-design-vue/es'; import { getGraphData } from '@/api/monitor/topology'; import { Graph, GraphData, Tooltip } from '@antv/g6'; import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge'; diff --git a/src/views/dashboard/smscCDR/index.vue b/src/views/dashboard/smscCDR/index.vue index 30fc6a5b..0c160722 100644 --- a/src/views/dashboard/smscCDR/index.vue +++ b/src/views/dashboard/smscCDR/index.vue @@ -1,10 +1,10 @@ +const isSaving = ref(false); +const isRestoring = ref(false); +//保存布局按钮 +const handleSaveLayout = async (info: MenuInfo) => { + if (typeof info.key === 'string' && !isSaving.value) { + isSaving.value = true; + isRestoring.value = true; + try { + await saveCurrentLayout(info.key); + } finally { + setTimeout(()=>{ + isSaving.value = false; + isRestoring.value = false; + },700) + } + } else { + console.error('Invalid layout key or operation in progress'); + } +}; +//恢复布局按钮 +const handleRestoreLayout = async (info: MenuInfo) => { + if (typeof info.key === 'string' && !isRestoring.value) { + isRestoring.value = true; + isSaving.value = true; + try { + await restoreSavedLayout(info.key); + } finally { + setTimeout(()=>{ + isSaving.value = false; + isRestoring.value = false; + },700) + } + } else { + console.error('Invalid layout key or operation in progress'); + } +}; + +// 保存当前布局 +const saveCurrentLayout = async (layoutName: string) => { + const savedLayouts = JSON.parse(localStorage.getItem('savedLayouts') || '{}'); + savedLayouts[layoutName] = { + layout: chartOrder.value, + selectedTypes: selectedNeTypes.value + }; + localStorage.setItem('savedLayouts', JSON.stringify(savedLayouts)); + message.success(t('views.perfManage.kpiKeyTarget.saveSuccess', { name: t(`views.perfManage.kpiKeyTarget.${layoutName}`)})); +}; +//恢复已保存的布局 +const restoreSavedLayout = async (layoutName: string) => { + const savedLayouts = JSON.parse(localStorage.getItem('savedLayouts') || '{}'); + const savedLayout = savedLayouts[layoutName]; + if (savedLayout && Array.isArray(savedLayout.selectedTypes) && Array.isArray(savedLayout.layout)) { + selectedNeTypes.value = savedLayout.selectedTypes; + networkElementTypes.value = savedLayout.selectedTypes; + + // 更新布局 + chartOrder.value = savedLayout.layout.filter((item: LayoutItem) => + savedLayout.selectedTypes.includes(item.i) + ); + + // 如果有当前选中的网元类型不在保存的布局中,添加它们 + const missingTypes = savedLayout.selectedTypes.filter((type: AllChartType) => + !chartOrder.value.some(item => item.i === type) + ); + missingTypes.forEach((type: AllChartType) => { + chartOrder.value.push({ + x: (chartOrder.value.length % 2) * 6, + y: Math.floor(chartOrder.value.length / 2) * 4, + w: 6, + h: 4, + i: type, + }); + }); + + localStorage.setItem('chartOrder', JSON.stringify(chartOrder.value)); + localStorage.setItem('selectedNeTypes', JSON.stringify(selectedNeTypes.value)); + + await nextTick(); + await initCharts(); + + message.success(t('views.perfManage.kpiKeyTarget.restoreSavedSuccess', { name: t(`views.perfManage.kpiKeyTarget.${layoutName}`)})); + } else { + message.warning(t('views.perfManage.kpiKeyTarget.noSavedLayout', { name: t(`views.perfManage.kpiKeyTarget.${layoutName}`)})); + } +}; + +// 应用全宽布局 +const applyFullWidthLayout = () => { + isManuallyUpdating.value = true; + const newLayout = selectedNeTypes.value.map((type, index) => ({ + x: 0, + y: index * 8, + w: 12, + h: 8, + i: type, + })); + + nextTick(() => { + Object.assign(chartOrder.value, newLayout) + // chartOrder.value = newLayout; + localStorage.setItem('chartOrder', JSON.stringify(newLayout)); + initCharts(); + // 直接更新图表,不调用 handleLayoutUpdated + chartOrder.value.forEach((item) => { + const state = chartStates[item.i]; + if (state?.chart.value) { + state.chart.value.resize(); + renderChart(item.i); + } + }); + isManuallyUpdating.value = false; + }); +}; + +// 应用两列布局 +const applyTwoColumnLayout = () => { + isManuallyUpdating.value = true; + const newLayout = selectedNeTypes.value.map((type, index) => ({ + x: (index % 2) * 6, + y: Math.floor(index / 2) * 4, + w: 6, + h: 4, + i: type, + })); + nextTick(() => { + // chartOrder.value = newLayout; + Object.assign(chartOrder.value, newLayout) + localStorage.setItem('chartOrder', JSON.stringify(newLayout)); + initCharts(); + // 直接更新图表,不调用 handleLayoutUpdated + chartOrder.value.forEach((item) => { + const state = chartStates[item.i]; + if (state?.chart.value) { + state.chart.value.resize(); + renderChart(item.i); + } + }); + isManuallyUpdating.value = false; + }); +}; + From 6ee9d464fb43ce429e2e453aa8bed625e3f385cd Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 2 Nov 2024 15:46:26 +0800 Subject: [PATCH 025/110] =?UTF-8?q?feat:=20PCF=E5=AF=BC=E5=87=BA=E6=9C=89?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/neUser/pcf/index.vue | 3 --- 1 file changed, 3 deletions(-) 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')" > From 33a8ce97d33aa92ab446169aa2cb0adc9dac2d6b Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 2 Nov 2024 15:47:18 +0800 Subject: [PATCH 026/110] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=202.241102?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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" From 544c3697bdedeee188525f031ee8479caf7af184 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 2 Nov 2024 15:48:04 +0800 Subject: [PATCH 027/110] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=202.241102?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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" From a731a6408bd1685c039e273a8a4decfa03eaac3f Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 4 Nov 2024 11:19:25 +0800 Subject: [PATCH 028/110] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=B8=83=E5=B1=80mix=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=BA=95=E9=83=A8=E5=AE=BD=E5=BA=A6100%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +++--- src/store/modules/layout.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 76e7b0c8..ac642491 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "@codemirror/merge": "^6.7.2", "@codemirror/theme-one-dark": "^6.1.2", "@tato30/vue-pdf": "^1.11.2", - "@vueuse/core": "^11.1.0", + "@vueuse/core": "11.2.0", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", "ant-design-vue": "^4.2.5", - "antdv-pro-layout": "^4.1.8", + "antdv-pro-layout": "^4.1.9", "antdv-pro-modal": "^4.0.5", "codemirror": "^6.0.1", "crypto-js": "^4.2.0", @@ -37,7 +37,7 @@ "localforage": "^1.10.0", "nprogress": "^0.2.0", "p-queue": "~8.0.1", - "pinia": "^2.2.4", + "pinia": "2.2.6", "vue": "^3.5.12", "vue-i18n": "^10.0.4", "vue-router": "^4.4.5", diff --git a/src/store/modules/layout.ts b/src/store/modules/layout.ts index 21c9cf34..ea718703 100644 --- a/src/store/modules/layout.ts +++ b/src/store/modules/layout.ts @@ -70,7 +70,7 @@ const proRender = (render: any) => (render === false ? false : undefined); const proConfigLocal: LayoutStore['proConfig'] = localGetJSON( CACHE_LOCAL_PROCONFIG ) || { - layout: 'side', + layout: 'mix', theme: 'light', menuTheme: 'light', fixSiderbar: true, From d8487d7cd7288f15c50297a0fb82dc13283ebdf7 Mon Sep 17 00:00:00 2001 From: zhongzm Date: Mon, 4 Nov 2024 16:04:11 +0800 Subject: [PATCH 029/110] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E6=8C=87=E6=A0=87=E5=88=97=E8=A1=A8=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=89=93=E5=BC=80=EF=BC=8C=E6=96=87=E6=9C=AC=E9=94=99=E4=B9=B1?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/perfManage/kpiOverView/index.vue | 78 +++++++--------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/src/views/perfManage/kpiOverView/index.vue b/src/views/perfManage/kpiOverView/index.vue index c76bdb5b..660c355d 100644 --- a/src/views/perfManage/kpiOverView/index.vue +++ b/src/views/perfManage/kpiOverView/index.vue @@ -3,30 +3,17 @@ 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 } from 'ant-design-vue'; -import { - RESULT_CODE_ERROR, - RESULT_CODE_SUCCESS, -} from '@/constants/result-constants'; +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, - DownOutlined, - MoreOutlined, -} from '@ant-design/icons-vue'; +import { BarChartOutlined, LineChartOutlined, UnorderedListOutlined, DownOutlined, MoreOutlined } from '@ant-design/icons-vue'; // 在这里定义 ChartDataItem 接口 interface ChartDataItem { @@ -315,9 +302,9 @@ const updateChart = () => { data: chartData.value.length > 0 ? chartData.value.map(item => { - // 将时间戳转换为包含时分秒的格式 - return dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss'); - }) + // 将时间戳转换为包含时分秒的格式 + return dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss'); + }) : [''], axisLabel: { formatter: (value: string) => { @@ -325,7 +312,7 @@ const updateChart = () => { return dayjs(value).format('YYYY-MM-DD HH:mm:ss'); }, rotate: 0, - interval: 'auto', // 自动计算显示间隔 + interval: 'auto', // 自动计算显示��隔 align: 'right', }, }, @@ -337,7 +324,7 @@ const updateChart = () => { }, // 添加自动计算的分割段数 splitNumber: 5, - // 添加自动计算的最小/最大值围 + // 添加自动计算的最小/最大值范围 scale: true, }, series: series, //配置数据 @@ -511,7 +498,7 @@ const fetchSpecificKPI = async () => { if (validSavedKPIs.length > 0) { selectedKPIs.value = validSavedKPIs; } else { - // 如果没有有效的保存选择,则默认选择��要指标 + // 如果没有有效的保存选择,则默认选择其他指标 selectedKPIs.value = Object.values(TARGET_KPI_IDS).flat(); } } else { @@ -653,26 +640,24 @@ const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => { {{ chartType === 'line' - ? t('views.perfManage.kpiOverView.changeLine') - : t('views.perfManage.kpiOverView.changeBar') + ? t('views.perfManage.kpiOverView.changeBar') + : t('views.perfManage.kpiOverView.changeLine') }}
- - { {{ neType.toUpperCase() }} From 5ddf83d1fdaebec092a49ee7bee315d7826e8471 Mon Sep 17 00:00:00 2001 From: lai <371757574@qq.com> Date: Thu, 7 Nov 2024 14:09:26 +0800 Subject: [PATCH 039/110] =?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=88=B3=E4=B8=94=E5=8A=A0=E4=B8=8A=E5=BF=AB=E6=8D=B7=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/locales/en-US.ts | 2 ++ src/i18n/locales/zh-CN.ts | 6 +++-- src/views/perfManage/kpiCReport/index.vue | 32 ++++++++++++++++------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 88714a2a..47d8c18e 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -1065,6 +1065,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', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 58e460e8..df937e84 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -1065,6 +1065,8 @@ export default { expression:'计算公式', description:'描述', kpiSet:'统计设置', + sixHoursAgo:'6小时前', + threeHoursAgo:'3小时前', delCustomTip:'确认删除记录编号为 {num} 的数据项?', delCustom:'成功删除记录编号为 {num} 自定义指标', addCustom:'添加自定义指标', @@ -1337,8 +1339,8 @@ export default { filter: "全局过滤", startTime: '开始时间', endTime: '结束时间', - today: '昨天', - yesterday: '今天', + today: '今天', + yesterday: '昨天', week: '本周', month: '本月', avgLoad: '平均负载', diff --git a/src/views/perfManage/kpiCReport/index.vue b/src/views/perfManage/kpiCReport/index.vue index b19f4996..dce804f7 100644 --- a/src/views/perfManage/kpiCReport/index.vue +++ b/src/views/perfManage/kpiCReport/index.vue @@ -42,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(); @@ -77,6 +78,19 @@ 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([]); @@ -260,12 +274,14 @@ function fnGetListTitle() { tableColumns.value = []; tableColumnsDnd.value = []; fnRanderChartData(); - return false; + return false; } tableColumns.value = []; 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, @@ -626,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(); @@ -658,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%" > From 15b81eef97c0e211f043b7108fa341e9ae178a74 Mon Sep 17 00:00:00 2001 From: lai <371757574@qq.com> Date: Thu, 7 Nov 2024 14:10:00 +0800 Subject: [PATCH 040/110] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=A1=86=E4=B8=BA=E6=A0=91=E7=8A=B6=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting/components/change-home-index.vue | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/views/system/setting/components/change-home-index.vue b/src/views/system/setting/components/change-home-index.vue index b782276b..82255bf9 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(() => { @@ -695,7 +697,6 @@ onMounted(() => { - { > { }, ]" > - diff --git a/src/views/perfManage/kpiCReport/index.vue b/src/views/perfManage/kpiCReport/index.vue index fd342b4d..1ae622a8 100644 --- a/src/views/perfManage/kpiCReport/index.vue +++ b/src/views/perfManage/kpiCReport/index.vue @@ -318,6 +318,8 @@ function fnGetListTitle() { var language = currentLocale.value.split('_')[0]; if (language === 'zh') language = 'cn'; + if (!state.neType[0]) return false; + // 获取表头文字 listCustom({ neType: state.neType[0], status: 'Active' }) .then(res => { @@ -580,7 +582,7 @@ function fnRanderChartData() { } chartDataXAxisData = Array.from(new Set(chartDataXAxisData)); - ///console.log(queryParams.sortOrder, chartDataXAxisData); + //console.log(queryParams.sortOrder, chartDataXAxisData); //console.log(chartDataXAxisData, chartDataYSeriesData); // 绘制图数据 @@ -724,6 +726,7 @@ onMounted(() => { res.data.forEach((item: any) => { typeArr.push(item.neType); }); + typeArr = Array.from(new Set(typeArr)); neInfoStore.fnNelist().then(res => { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.data.length > 0) { From ebde50f58befaae2a3cf3639dce584289ab7b6a7 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 23 Nov 2024 16:40:10 +0800 Subject: [PATCH 098/110] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=202.241123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.production | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index 79cf4615..e72986be 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.241115" +VITE_APP_VERSION = "2.241123" # 接口基础URL地址-不带/后缀 VITE_API_BASE_URL = "/omc-api" diff --git a/.env.production b/.env.production index 03f3ec15..d2fddb62 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.241115" +VITE_APP_VERSION = "2.241123" # 接口基础URL地址-不带/后缀 VITE_API_BASE_URL = "/omc-api" From 7d69d3c21df1eba6180dfb15d5e143b5bc598d2e Mon Sep 17 00:00:00 2001 From: zhongzm Date: Sat, 23 Nov 2024 17:01:41 +0800 Subject: [PATCH 099/110] =?UTF-8?q?fix=EF=BC=9A=E6=A0=B7=E5=BC=8F=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=81=E6=98=8E=E6=9A=97=E4=B8=BB=E9=A2=98=E9=80=82?= =?UTF-8?q?=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/perfManage/kpiKeyTarget/index.vue | 809 ++++++++++++++++---- 1 file changed, 639 insertions(+), 170 deletions(-) diff --git a/src/views/perfManage/kpiKeyTarget/index.vue b/src/views/perfManage/kpiKeyTarget/index.vue index a4798631..d14640c5 100644 --- a/src/views/perfManage/kpiKeyTarget/index.vue +++ b/src/views/perfManage/kpiKeyTarget/index.vue @@ -20,7 +20,14 @@ import { TableColumnType } from 'ant-design-vue'; const { t, currentLocale } = useI18n(); const neInfoStore = useNeInfoStore(); - +//日期快捷选择 +const ranges = ref([ + {label:t('views.perfManage.customTarget.sixHoursAgo'),value:[dayjs().subtract(6, 'hours'), + dayjs(),]}, + {label:t('views.perfManage.customTarget.threeHoursAgo'),value:[dayjs().subtract(3, 'hours'), + dayjs(),]}, + {label:t('views.monitor.monitor.today'),value:[dayjs().startOf('day'), dayjs()]}, +]) //WebSocket连接 const ws = ref(null); @@ -51,26 +58,61 @@ const selectedNeTypes = ref([]); const latestSelectedTypes = ref([]); // watch 监控函数 -watch(selectedNeTypes, async (newTypes) => { - networkElementTypes.value = [...newTypes]; // 使用展开运算符创建新数组 +watch(selectedNeTypes, async (newTypes, oldTypes) => { + networkElementTypes.value = [...newTypes]; latestSelectedTypes.value = [...newTypes]; + // 获取被移除和新增的类型 + const removedTypes = oldTypes.filter(type => !newTypes.includes(type)); + const addedTypes = newTypes.filter(type => !oldTypes.includes(type)); + + // 清理被移除的图表状态 + removedTypes.forEach(type => { + const state = chartStates[type]; + if (state) { + if (state.chart.value) { + state.chart.value.dispose(); + } + if (state.observer.value) { + state.observer.value.disconnect(); + } + state.chart.value = null; + state.observer.value = null; + state.chartDataXAxisData = []; + state.chartDataYSeriesData = []; + state.tableState.data = []; + } + }); + // 确保每个选中的类型都有对应的图表状态 newTypes.forEach(type => { if (!chartStates[type]) { - chartStates[type] = createChartState(); + chartStates[type] = createChartState(type); } }); // 初始化新选中的图表 for (const type of newTypes) { try { - if (!chartStates[type].chart.value) { + // 只有当图表不存在或者是新增的类型时才初始化 + const needsInit = !chartStates[type].chart.value || addedTypes.includes(type); + + if (needsInit) { await fetchKPITitle(type); await nextTick(); await initChart(type); + + // 只有在非实时模式下且是新增的网元时才获取数据 + if (!realTimeEnabled.value && addedTypes.includes(type)) { + await fetchData(type); + } + } + + // 恢复选中状态 + if (selectedRows.value[type]) { + await nextTick(); + updateChartLegendSelect(type, selectedRows.value[type]); } - await fetchData(type); } catch (error) { console.error(`Error initializing chart for ${type}:`, error); } @@ -106,7 +148,7 @@ useDebounceFn(() => { // 确保 chartStates 包含新的网元类型 newTypes.forEach((type) => { if (!chartStates[type]) { - chartStates[type] = createChartState(); + chartStates[type] = createChartState(type); } }); @@ -120,6 +162,10 @@ useDebounceFn(() => { }, 300); // 改变状态时重新初始化图表 数 const initCharts = async () => { + // 保存当前的选中状态和图表状态 + const currentSelectedStates = { ...selectedRows.value }; + const savedChartStates = new Map(Object.entries(chartStates)); + // 清除不再需要的图表 Object.keys(chartStates).forEach((key) => { if (!networkElementTypes.value.includes(key as AllChartType)) { @@ -130,34 +176,39 @@ const initCharts = async () => { if (state.observer.value) { state.observer.value.disconnect(); } - delete chartStates[key as AllChartType]; + // 不要删除状态,只是清理图表实例 + state.chart.value = null; + state.observer.value = null; } }); // 初始化或更新需要的图表 for (const type of networkElementTypes.value) { - if (!chartStates[type]) { - chartStates[type] = createChartState(); - } try { + if (!chartStates[type]) { + // 检查是否有保存的状态 + const savedState = savedChartStates.get(type); + if (savedState) { + chartStates[type] = savedState; + } else { + chartStates[type] = createChartState(type); + } + } + await fetchKPITitle(type); await nextTick(); await initChart(type); await fetchData(type); + + // 恢复选中状态 + if (currentSelectedStates[type]) { + await nextTick(); + updateChartLegendSelect(type, currentSelectedStates[type]); + } } catch (error) { console.error(`Error initializing chart for ${type}:`, error); } } - - // 添加延时检查 - setTimeout(() => { - networkElementTypes.value.forEach(type => { - const state = chartStates[type]; - if (state && !state.chart.value) { - initChart(type); - } - }); - }, 200); }; // 定义表格状态类型 @@ -170,10 +221,12 @@ type TableStateType = { }; // 创建可复用的状态 -const createChartState = () => { +const createChartState = (neType: AllChartType) => { const chartDom = ref(null); const chart = ref(null); const observer = ref(null); + const currentTheme = ref(document.documentElement.getAttribute('data-theme')); + const themeObserver = ref(null); return { chartDom, @@ -190,12 +243,16 @@ const createChartState = () => { chartLegendSelected: {} as Record, chartDataXAxisData: [] as string[], chartDataYSeriesData: [] as CustomSeriesOption[], + seriesColors: reactive(new Map()), + currentTheme, + themeObserver, + neType, }; }; // 图表类型状态 const chartStates: Record> = Object.fromEntries( - networkElementTypes.value.map(type => [type, createChartState()]) + networkElementTypes.value.map(type => [type, createChartState(type)]) ) as Record>; //期择器 @@ -225,6 +282,9 @@ const initChart = (type: AllChartType) => { return; } + // 更新当前主题 + state.currentTheme.value = document.documentElement.getAttribute('data-theme'); + // 等待 DOM 更新 await nextTick(); @@ -239,68 +299,147 @@ const initChart = (type: AllChartType) => { return; } + // 确保旧的图表实例被正确销毁 if (state.chart.value) { state.chart.value.dispose(); + state.chart.value = null; } + // 创建新的图表实例 state.chart.value = markRaw(echarts.init(container)); - const option: echarts.EChartsOption = { - tooltip: { - trigger: 'axis', - position: function(pt: any) { - const [x, y] = pt; - const chartDom = state.chartDom.value; - if (!chartDom) return [x, y]; - const rect = chartDom.getBoundingClientRect(); - const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; - const viewportHeight = window.innerHeight; - const relativeY = rect.top + y - scrollTop; - if (relativeY + 100 > viewportHeight) { - return [x, '10%']; - } - return [x, y + 10]; - }, - axisPointer: { - type: 'line', - z: 0 - }, - className: `chart-tooltip-${type}`, - z: 1000, - extraCssText: 'z-index: 1000; pointer-events: none;', - confine: true, - }, - xAxis: { - type: 'category', - boundaryGap: false, - data: state.chartDataXAxisData, - }, - yAxis: { - type: 'value', - boundaryGap: [0, '100%'], - }, - legend: { - type: 'scroll', - orient: 'horizontal', - top: -5, - itemWidth: 20, - textStyle: { - color: '#646A73', - }, - show:false, - icon: 'circle', - selected: state.chartLegendSelected, - }, - grid: { - left: '10%', - right: '10%', - bottom: '15%', - }, - series: state.chartDataYSeriesData as SeriesOption[], - }; - state.chart.value.setOption(option); - state.chart.value.resize(); - // 创建 ResizeObserver 实例 + // 更新图表配置 + const updateChartTheme = (type: AllChartType) => { + if (!state.chart.value) return; + + const splitLineColor = getSplitLineColor(); + const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; + + const option = { + tooltip: { + trigger: 'axis', + position: function (point: number[], _params: any, _dom: HTMLElement, _rect: any, size: { viewSize: number[], contentSize: number[] }) { + const [x] = point; + const [viewWidth] = size.viewSize; + const [tooltipWidth] = size.contentSize; + + // 计算距离右边界的距离 + const rightSpace = viewWidth - x; + + // 如果右侧空间不足以显示 tooltip(加上20px的安全距离) + if (rightSpace < tooltipWidth + 20) { + // 向左显示,tooltip 右边界对齐鼠标位置 + return [x - tooltipWidth - 10, point[1]]; + } + + // 默认向右显示,tooltip 左边界对齐鼠标位置 + return [x + 10, point[1]]; + }, + axisPointer: { + type: 'line', + z: 0 + }, + className: `chart-tooltip-${type}`, + z: 1000, + extraCssText: 'z-index: 1000; pointer-events: none;', + confine: true, + backgroundColor: isDark ? 'rgba(48, 48, 48, 0.8)' : 'rgba(255, 255, 255, 0.9)', + borderColor: isDark ? '#555' : '#ddd', + textStyle: { + color: isDark ? '#CACADA' : '#333' + } + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: state.chartDataXAxisData, + axisLabel: { + formatter: '{value}', + color: isDark ? '#CACADA' : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor + } + } + }, + yAxis: { + type: 'value', + boundaryGap: [0, '100%'], + axisLabel: { + formatter: '{value}', + color: isDark ? '#CACADA' : '#333' + }, + splitNumber: 5, + scale: true, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor + } + } + }, + legend: { + type: 'scroll', + orient: 'horizontal', + top: -5, + itemWidth: 20, + textStyle: { + color: isDark ? '#CACADA' : '#646A73', + }, + show: false, + icon: 'circle', + selected: state.chartLegendSelected, + backgroundColor: isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.9)', + borderColor: isDark ? '#555' : '#ddd', + }, + grid: { + left: '10%', + right: '10%', + bottom: '15%', + }, + series: state.chartDataYSeriesData.map(series => { + const color = state.seriesColors.get(series.customKey as string) || generateColorRGBA(); + return { + ...series, + itemStyle: { color }, + lineStyle: { color }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: color.replace(')', ',0.8)') }, + { offset: 1, color: color.replace(')', ',0.3)') }, + ]), + }, + }; + }) as SeriesOption[], + }; + + state.chart.value.setOption(option, true); + }; + + // 初始化时更新一次 + updateChartTheme(type); + + // 添加主题变化监听 + if (state.themeObserver.value) { + state.themeObserver.value.disconnect(); + } + + const observer = new MutationObserver(() => { + state.currentTheme.value = document.documentElement.getAttribute('data-theme'); + updateChartTheme(type); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'] + }); + + // 正确地设置 ref 的值 + state.themeObserver.value = observer; + + // 重新绑定 ResizeObserver if (state.observer.value) { state.observer.value.disconnect(); } @@ -309,12 +448,7 @@ const initChart = (type: AllChartType) => { state.chart.value.resize(); } }); - state.observer.value.observe(container); - - // 确保在图表初始化后更新统计信息 - await nextTick(); - await renderChart(type); }); }; @@ -358,9 +492,10 @@ function fnRealTimeSwitch(bool: boolean) { if (!ws.value) { ws.value = new WS(); } - // 清空所有图表的现有数据 + // 清空所有图表的现有数据并设置 loading 状态 Object.values(chartStates).forEach(state => { state.tableState.seached = false; + state.tableState.loading = true; state.tableState.data = []; // 清空表格数据 state.chartDataXAxisData = []; state.chartDataYSeriesData.forEach(series => { @@ -387,14 +522,11 @@ function fnRealTimeSwitch(bool: boolean) { if (ws.value) { Object.values(chartStates).forEach(state => { state.tableState.seached = true; + state.tableState.loading = false; }); ws.value.close(); ws.value = null; - - // 重新获取历史数据 - selectedNeTypes.value.forEach(type => { - fetchData(type); - }); + // 关闭实时数据时保留当前数据,不清空也不重新获取 } } } @@ -430,6 +562,11 @@ function wsMessage(res: Record) { // 只有在实时数据模式下才更新图表 if (realTimeEnabled.value) { + // 如果是第一条数据,关闭 loading 状态 + if (state.tableState.data.length === 0) { + state.tableState.loading = false; + } + // 更新表格数据 state.tableState.data.unshift(kpiEvent); if (state.tableState.data.length > 100) { @@ -464,9 +601,9 @@ function wsMessage(res: Record) { // 更新统计信息 nextTick(() => { - const stats = getKpiStats(neType); + const stats = getKpiStats(state.neType); if (stats.length > 0) { - renderChart(neType); + renderChart(state.neType); } }); } @@ -476,11 +613,14 @@ interface CustomSeriesOption extends Omit { customKey?: string; data: Array<[string, number]>; } -// 创建可复用的图表渲染函数 +// 建可复用的图表渲染函数 const renderChart = async (type: AllChartType) => { const state = chartStates[type]; if (!state?.chart.value) return; + // 保存当前的选中状态 + const currentSelectedKpiIds = selectedRows.value[type] || []; + // 重置数据 state.chartLegendSelected = {}; state.chartDataXAxisData = []; @@ -489,14 +629,23 @@ const renderChart = async (type: AllChartType) => { // 处理数据 for (const column of state.tableColumns.value) { if (['neName', 'startIndex', 'timeGroup'].includes(column.key as string)) continue; - const color = generateColorRGBA(); - state.chartDataYSeriesData.push({ + + // 检查是否已有该指标的颜色,如果没有才生成新颜色 + let color = state.seriesColors.get(column.key as string); + if (!color) { + color = generateColorRGBA(); + state.seriesColors.set(column.key as string, color); + } + + const seriesData = { name: column.title as string, customKey: column.key as string, type: 'line', symbol: 'none', + smooth:0.6, sampling: 'lttb', itemStyle: { color }, + lineStyle: { color }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: color.replace(')', ',0.8)') }, @@ -504,7 +653,9 @@ const renderChart = async (type: AllChartType) => { ]), }, data: [], - } as CustomSeriesOption); + } as CustomSeriesOption; + + state.chartDataYSeriesData.push(seriesData); state.chartLegendSelected[column.title as string] = true; } @@ -517,14 +668,51 @@ const renderChart = async (type: AllChartType) => { } } + // 更新图表时考虑当前选中状态 + const legendSelected: Record = {}; + state.chartDataYSeriesData.forEach(series => { + const title = state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey; + if (typeof title === 'string') { + // 如果没有选中的指标或选中列表为空,显示所有指标 + // 否则只显示选中的指标 + legendSelected[title] = !currentSelectedKpiIds.length || + currentSelectedKpiIds.includes(series.customKey as string); + } + }); + // 更新图表 state.chart.value.setOption( { - legend: { selected: state.chartLegendSelected }, + legend: { + selected: legendSelected, + textStyle: { + color: document.documentElement.getAttribute('data-theme') === 'dark' + ? '#CACADA' + : '#646A73', + }, + backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark' + ? 'rgba(0, 0, 0, 0.2)' + : 'rgba(255, 255, 255, 0.9)', + borderColor: document.documentElement.getAttribute('data-theme') === 'dark' + ? '#555' + : '#ddd', + }, xAxis: { data: state.chartDataXAxisData, type: 'category', boundaryGap: false, + axisLabel: { + formatter: '{value}', + color: document.documentElement.getAttribute('data-theme') === 'dark' + ? '#CACADA' + : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: getSplitLineColor() + } + } }, series: state.chartDataYSeriesData as SeriesOption[], }, @@ -539,7 +727,10 @@ const fetchKPITitle = async (type: AllChartType) => { try { const res = await getKPITitle(type.toUpperCase()); if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { - // 添加时间列作为第一列 + // 保存现有的颜色映 + const existingColors = new Map(chartStates[type].seriesColors); + + // 设置新的列 chartStates[type].tableColumns.value = [ { title: t('views.perfManage.kpiKeyTarget.time'), @@ -552,17 +743,28 @@ const fetchKPITitle = async (type: AllChartType) => { return dayjs(Number(text)).format('YYYY-MM-DD HH:mm:ss'); } }, - ...res.data.map(item => ({ - title: item[`${language}Title`], - dataIndex: item.kpiId, - align: 'left', - key: item.kpiId, - resizable: true, - width: 100, - minWidth: 150, - maxWidth: 300, - })) + ...res.data.map(item => { + const kpiId = item.kpiId; + // 如果没有现有的颜色,生成新的颜色 + if (!existingColors.has(kpiId)) { + existingColors.set(kpiId, generateColorRGBA()); + } + + return { + title: item[`${language}Title`], + dataIndex: kpiId, + align: 'left', + key: kpiId, + resizable: true, + width: 100, + minWidth: 150, + maxWidth: 300, + }; + }) ]; + + // 更新颜色映射 + chartStates[type].seriesColors = existingColors; } } catch (error) { console.error(error); @@ -573,7 +775,168 @@ const fetchKPITitle = async (type: AllChartType) => { // 定义默认选择的网元类型 const DEFAULT_NE_TYPES: AllChartType[] = ['udm', 'amf', 'upf', 'ims']; -// 在 onMounted 钩子中 +// 添加一个函数来获取当前主题下的网格线颜色 +const getSplitLineColor = () => { + return document.documentElement.getAttribute('data-theme') === 'dark' + ? '#333333' + : '#E8E8E8'; +}; + +// 修改主题观察器 +const themeObserver = new MutationObserver(() => { + const splitLineColor = getSplitLineColor(); + const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; + + Object.values(chartStates).forEach(state => { + const chart = state.chart?.value; + if (!chart) return; + + requestAnimationFrame(() => { + // 获取当前选中的指标IDs + const currentSelectedKpiIds = selectedRows.value[state.neType] || []; + + // 重新生成所有指标的颜色并更新到 seriesColors + state.chartDataYSeriesData.forEach(series => { + const color = generateColorRGBA(); + state.seriesColors.set(series.customKey as string, color); + }); + + // 重新计算图例选中状态 + const legendSelected: Record = {}; + state.chartDataYSeriesData.forEach(series => { + const title = state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey; + if (typeof title === 'string') { + // 如果没有选中的指标或选中列表为空,显示所有指标 + // 否则只显示选中的指标 + legendSelected[title] = !currentSelectedKpiIds.length || + currentSelectedKpiIds.includes(series.customKey as string); + } + }); + + const option = { + tooltip: { + trigger: 'axis', + position: function (point: number[], _params: any, _dom: HTMLElement, _rect: any, size: { viewSize: number[], contentSize: number[] }) { + const [x] = point; + const [viewWidth] = size.viewSize; + const [tooltipWidth] = size.contentSize; + + // 计算距离右边界的距离 + const rightSpace = viewWidth - x; + + // 如果右侧空间不足以显示 tooltip(加上20px的安全距离) + if (rightSpace < tooltipWidth + 20) { + // 向左显示,tooltip 右边界对齐鼠标位置 + return [x - tooltipWidth - 10, point[1]]; + } + + // 默认向右显示,tooltip 左边界对齐鼠标位置 + return [x + 10, point[1]]; + }, + axisPointer: { + type: 'line', + z: 0 + }, + className: `chart-tooltip-${state.neType}`, + z: 1000, + extraCssText: 'z-index: 1000; pointer-events: none;', + confine: true, + backgroundColor: isDark ? 'rgba(48, 48, 48, 0.8)' : 'rgba(255, 255, 255, 0.9)', + borderColor: isDark ? '#555' : '#ddd', + textStyle: { + color: isDark ? '#CACADA' : '#333' + } + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: state.chartDataXAxisData, + axisLabel: { + color: isDark ? '#CACADA' : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor + } + } + }, + yAxis: { + type: 'value', + boundaryGap: [0, '100%'], + axisLabel: { + color: isDark ? '#CACADA' : '#333' + }, + splitNumber: 5, + scale: true, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor + } + } + }, + legend: { + show: false, + selected: legendSelected, + textStyle: { + color: isDark ? '#CACADA' : '#646A73' + }, + backgroundColor: isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.9)', + borderColor: isDark ? '#555' : '#ddd', + }, + series: state.chartDataYSeriesData.map(series => { + const color = state.seriesColors.get(series.customKey as string) as string; + return { + ...series, + type: 'line', + symbol: 'none', + smooth:0.6, + sampling: 'lttb', + itemStyle: { color }, + lineStyle: { color }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: color.replace(')', ',0.8)') }, + { offset: 1, color: color.replace(')', ',0.3)') }, + ]), + }, + data: series.data + }; + }) + }; + + chart.setOption(option, { + notMerge: false, + lazyUpdate: false + }); + + // 强制重新渲染 + chart.resize(); + + // 如果有选中的指标,确保图例状态正确 + if (currentSelectedKpiIds.length > 0) { + nextTick(() => { + updateChartLegendSelect(state.neType, currentSelectedKpiIds); + }); + } + + // 强制更新表格以刷新图标颜色 + nextTick(() => { + const stats = getKpiStats(state.neType); + if (stats.length > 0) { + // 触发表格重新渲染 + state.tableState.loading = true; + nextTick(() => { + state.tableState.loading = false; + }); + } + }); + }); + }); +}); + +// 在 onMounted 中添加题观察器 onMounted(async () => { ws.value = new WS(); await neInfoStore.fnNelist(); @@ -585,16 +948,21 @@ onMounted(async () => { 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)); } + + // 添加主题观察器 + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'] + }); + await initCharts(); }); -// 在组件卸载时销毁图表实例 +// 在 onUnmounted 中断开主题观察器 onUnmounted(() => { if(ws.value && ws.value.state() === WebSocket.OPEN) { ws.value.close(); @@ -606,7 +974,11 @@ onUnmounted(() => { if (state.observer.value) { state.observer.value.disconnect(); } + if (state.themeObserver.value) { + state.themeObserver.value.disconnect(); + } }); + themeObserver.disconnect(); }); // 添加 KPIStats 接口定义 @@ -641,7 +1013,69 @@ const getKpiStats = (type: AllChartType) => { }).filter((item): item is KPIStats => item !== null); }; -// 添加表格列配置 +// 修改选中行的状态,改为数组存储个选中的指标ID +const selectedRows = ref>({} as Record); + +// 修改处理行点击的方法,支持多选 +const handleRowClick = (record: KPIStats, type: AllChartType) => { + const state = chartStates[type]; + if (!state?.chart.value) return; + + // 确保选中数组已初始化 + if (!selectedRows.value[type]) { + selectedRows.value[type] = []; + } + + const index = selectedRows.value[type].indexOf(record.kpiId); + if (index > -1) { + // 如果已选中,则取消选中 + selectedRows.value[type].splice(index, 1); + } else { + // 如果未选中,则添加到选中列表 + selectedRows.value[type].push(record.kpiId); + } + + // 更新图表显示 + updateChartLegendSelect(type, selectedRows.value[type]); +}; + +// 修改更新图表图例选中的方法,支持多选 +const updateChartLegendSelect = (type: AllChartType, selectedKpiIds?: string[]) => { + const state = chartStates[type]; + const chart = state?.chart?.value; + if (!chart) return; + + // 确保图表数据已经准备好 + if (state.chartDataYSeriesData.length === 0) { + return; + } + + const legendSelected: Record = {}; + state.chartDataYSeriesData.forEach(series => { + const title = state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey; + if (typeof title === 'string') { + // 如果没有选中的指标或选中列表为空,显示所有指标 + // 否则只显示选中的指标 + legendSelected[title] = !selectedKpiIds?.length || selectedKpiIds.includes(series.customKey as string); + } + }); + + const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; + + chart.setOption({ + legend: { + show: false, + selected: legendSelected, + textStyle: { + color: isDark ? '#CACADA' : '#646A73', + }, + backgroundColor: isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.9)', + borderColor: isDark ? '#555' : '#ddd', + } + }); +}; + +// 修改表格行的样式配置 const statsColumns: TableColumnType[] = [ { title: '', @@ -649,10 +1083,12 @@ const statsColumns: TableColumnType[] = [ width: 80, customRender: ({ record }: { record: KPIStats }) => { const state = chartStates[record.neType]; - const series = state?.chartDataYSeriesData.find(s => s.customKey === record.kpiId); + // 从 seriesColors 中获取最新的颜色 + const color = state?.seriesColors.get(record.kpiId); + return h(LineOutlined, { style: { - color: series?.itemStyle?.color || '#000', + color: color || '#000', fontSize: '40px', fontWeight: 'bold', } as Record @@ -683,46 +1119,6 @@ const statsColumns: TableColumnType[] = [ } ]; -// 添加选中行的状态 -const selectedRows = ref>({} as Record); - -// 添加处理行点击的方法 -const handleRowClick = (record: KPIStats, type: AllChartType) => { - const state = chartStates[type]; - if (!state?.chart.value) return; - - if (selectedRows.value[type] === record.kpiId) { - // 如果点击的是当前中的行,则取消选中 - selectedRows.value[type] = null; - // 更新图表,显示所有指标 - updateChartLegendSelect(type); - } else { - // 选中新行 - selectedRows.value[type] = record.kpiId; - // 更新图表,只显示选中的指标 - updateChartLegendSelect(type, record.kpiId); - } -}; - -// 添加更新图表图例选中态的方法 -const updateChartLegendSelect = (type: AllChartType, selectedKpiId?: string) => { - const state = chartStates[type]; - if (!state?.chart.value) return; - - const legendSelected = Object.fromEntries( - state.chartDataYSeriesData.map(series => [ - state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey, - selectedKpiId ? series.customKey === selectedKpiId : true - ]) - ); - - state.chart.value.setOption({ - legend: { - selected: legendSelected - } - }); -}; - // 1. 添加 tab 切换处理函数 const handleTabChange = async (activeKey: string, type: AllChartType) => { if (activeKey === 'stats') { @@ -733,7 +1129,7 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { await nextTick(); const stats = getKpiStats(type); if (stats.length === 0 && state.chartDataYSeriesData.length > 0) { - // 如果统计信息为空图表有数,重新渲染图表 + // 如果统计信为空图表有,重新渲染图表 await renderChart(type); } } @@ -754,7 +1150,11 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { - + @@ -776,7 +1176,9 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { { :loading="chartStates[type].tableState.loading" :custom-row="(record) => ({ onClick: () => handleRowClick(record, type), - class: record.kpiId === selectedRows[type] ? 'selected-row' : '' + class: selectedRows[type]?.includes(record.kpiId) ? 'selected-row' : '' })" /> @@ -835,15 +1237,38 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { display: flex; flex-direction: column; gap: 16px; + min-width: 1200px; + overflow-x: auto; + overflow-y: visible; + + &::-webkit-scrollbar { + width: 0; + height: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 3px; + + &:hover { + background: #999; + } + } + + &::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; + } } .chart-card { width: 100%; height: 500px; - position:relative; + position: relative; + min-width: 1168px; :deep(.ant-card-body) { - height: calc(100% - 57px); // 减去卡片头部高度 + height: calc(100% - 57px); padding: 16px !important; display: flex; flex-direction: column; @@ -961,16 +1386,16 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { } &::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.2); + background: #ccc; border-radius: 3px; &:hover { - background: rgba(0, 0, 0, 0.3); + background: #999; } } &::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.05); + background: #f1f1f1; border-radius: 3px; } } @@ -978,22 +1403,36 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { // 暗色主题下的滚动条样式 [data-theme='dark'] { - :deep(.ant-table-body) { + .charts-container { &::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); + background: #4c4c4c; &:hover { - background: rgba(255, 255, 255, 0.3); + background: #666; } } &::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.05); + background: #2a2a2a; + } + } + + :deep(.ant-table-body) { + &::-webkit-scrollbar-thumb { + background: #4c4c4c; + + &:hover { + background: #666; + } + } + + &::-webkit-scrollbar-track { + background: #2a2a2a; } } } -// 表格交互样式 +// 表格交样式 :deep(.ant-table-tbody) { tr { cursor: pointer; @@ -1003,13 +1442,13 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { } &.selected-row { - background-color: rgba(24, 144, 255, 0.1); + background-color: rgba(24, 144, 255, 0.1) !important; } } } [data-theme='dark'] :deep(.selected-row) { - background-color: rgba(24, 144, 255, 0.2); + background-color: rgba(24, 144, 255, 0.2) !important; } // 基础组件样式覆盖 @@ -1033,4 +1472,34 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => { flex-shrink: 0; max-width: 100%; } + +:deep(.ant-checkbox-group) { + &.ant-checkbox-group-disabled { + cursor: not-allowed; + + .ant-checkbox-wrapper-disabled { + cursor: not-allowed; + + &:hover { + .ant-checkbox-inner { + border-color: #d9d9d9; + } + } + } + } +} + +[data-theme='dark'] { + :deep(.ant-checkbox-group) { + &.ant-checkbox-group-disabled { + .ant-checkbox-wrapper-disabled { + &:hover { + .ant-checkbox-inner { + border-color: #434343; + } + } + } + } + } +} From 83cb3f8728fe5a75c5501a8a18948e8508202872 Mon Sep 17 00:00:00 2001 From: zhongzm Date: Mon, 25 Nov 2024 10:48:53 +0800 Subject: [PATCH 100/110] test --- src/views/perfManage/kpiKeyTarget/index.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/perfManage/kpiKeyTarget/index.vue b/src/views/perfManage/kpiKeyTarget/index.vue index d14640c5..b36edb0c 100644 --- a/src/views/perfManage/kpiKeyTarget/index.vue +++ b/src/views/perfManage/kpiKeyTarget/index.vue @@ -18,6 +18,7 @@ import { useDebounceFn } from '@vueuse/core'; import { LineOutlined } from '@ant-design/icons-vue'; import { TableColumnType } from 'ant-design-vue'; const { t, currentLocale } = useI18n(); +//test const neInfoStore = useNeInfoStore(); //日期快捷选择 From 1b2826068097d8f1fcfb061ba6a2c1eb1ded7268 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 25 Nov 2024 11:59:46 +0800 Subject: [PATCH 101/110] =?UTF-8?q?feat:=20=E7=B3=BB=E7=BB=9F=E5=85=8D?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=8F=82=E6=95=B0=E8=B7=AF=E7=94=B1=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/index.ts | 7 ++++++- src/store/modules/app.ts | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/router/index.ts b/src/router/index.ts index 4ac48d3f..b16ea27c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -182,7 +182,12 @@ router.beforeEach(async (to, from, next) => { next({ name: 'Index' }); } - const token = getToken(); + let token = getToken(); + + // 免用户登录认证 + if (!appStore.loginAuth) { + token = '== Not Login Auth =='; + } // 没有token if (!token) { diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts index f44eae2f..fe11126c 100644 --- a/src/store/modules/app.ts +++ b/src/store/modules/app.ts @@ -20,6 +20,8 @@ type AppStore = { buildTime: string; /**系统引导使用 */ bootloader: boolean; + // 用户登录认证 + loginAuth: boolean; // 序列号 serialNum: string; /**应用版权声明 */ @@ -52,6 +54,7 @@ const useAppStore = defineStore('app', { version: `-`, buildTime: `-`, bootloader: false, + loginAuth: false, serialNum: `-`, copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`, logoType: 'icon', @@ -85,6 +88,7 @@ const useAppStore = defineStore('app', { if (this.bootloader) { removeToken(); } + this.loginAuth = res.data.loginAuth === 'true'; this.serialNum = res.data.serialNum; this.appName = res.data.title; this.copyright = res.data.copyright; From 5d4a04ecf2fbf63130300e7a3fef53a1a1437350 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 25 Nov 2024 18:55:16 +0800 Subject: [PATCH 102/110] =?UTF-8?q?feat:=20=E5=8E=BB=E9=99=A4=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=85=8D=E7=99=BB=E5=BD=95=E5=8A=A0=E5=AF=86=E5=8F=82?= =?UTF-8?q?=E6=95=B0data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/http-fetch.ts | 2 +- src/router/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/http-fetch.ts b/src/plugins/http-fetch.ts index df9da37b..c4f33e56 100644 --- a/src/plugins/http-fetch.ts +++ b/src/plugins/http-fetch.ts @@ -186,7 +186,7 @@ function beforeRequest(options: OptionsType): OptionsType | Promise { if (paramStr) { const separator = options.url.includes('?') ? '&' : '?'; // 请求加密 - if (options.crypto) { + if (options.crypto && token !== '== Not Login Auth ==') { const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY); options.url += `${separator}data=${encodeURIComponent(data)}`; } else { diff --git a/src/router/index.ts b/src/router/index.ts index b16ea27c..a2f467e0 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -8,7 +8,7 @@ import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; import BasicLayout from '../layouts/BasicLayout.vue'; import BlankLayout from '../layouts/BlankLayout.vue'; -import { getToken } from '@/plugins/auth-token'; +import { getToken, setToken } from '@/plugins/auth-token'; import { validHttp } from '@/utils/regular-utils'; import useUserStore from '@/store/modules/user'; import useAppStore from '@/store/modules/app'; @@ -187,6 +187,7 @@ router.beforeEach(async (to, from, next) => { // 免用户登录认证 if (!appStore.loginAuth) { token = '== Not Login Auth =='; + setToken('== Not Login Auth =='); } // 没有token From a9a094d04adb1713f0d2e16c8bd40078908ff105 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 25 Nov 2024 20:02:16 +0800 Subject: [PATCH 103/110] =?UTF-8?q?revet:=20=E5=8E=BB=E9=99=A4=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=85=8D=E7=99=BB=E5=BD=95=E5=8A=A0=E5=AF=86=E5=8F=82?= =?UTF-8?q?=E6=95=B0data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/http-fetch.ts | 4 ++-- src/router/index.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/http-fetch.ts b/src/plugins/http-fetch.ts index c4f33e56..7a4a8849 100644 --- a/src/plugins/http-fetch.ts +++ b/src/plugins/http-fetch.ts @@ -186,7 +186,7 @@ function beforeRequest(options: OptionsType): OptionsType | Promise { if (paramStr) { const separator = options.url.includes('?') ? '&' : '?'; // 请求加密 - if (options.crypto && token !== '== Not Login Auth ==') { + if (options.crypto) { const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY); options.url += `${separator}data=${encodeURIComponent(data)}`; } else { @@ -198,7 +198,7 @@ function beforeRequest(options: OptionsType): OptionsType | Promise { if (options.method === 'get') return options; // 非get参数提交 - let body = options.data + let body = options.data; if (body instanceof FormData) { options.body = body; } else if (body) { diff --git a/src/router/index.ts b/src/router/index.ts index a2f467e0..b16ea27c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -8,7 +8,7 @@ import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; import BasicLayout from '../layouts/BasicLayout.vue'; import BlankLayout from '../layouts/BlankLayout.vue'; -import { getToken, setToken } from '@/plugins/auth-token'; +import { getToken } from '@/plugins/auth-token'; import { validHttp } from '@/utils/regular-utils'; import useUserStore from '@/store/modules/user'; import useAppStore from '@/store/modules/app'; @@ -187,7 +187,6 @@ router.beforeEach(async (to, from, next) => { // 免用户登录认证 if (!appStore.loginAuth) { token = '== Not Login Auth =='; - setToken('== Not Login Auth =='); } // 没有token From 6620ac7279e46865d102288b5c3693cdac8ab750 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Thu, 28 Nov 2024 15:49:23 +0800 Subject: [PATCH 104/110] =?UTF-8?q?fix:=20Para5G=E5=8F=82=E6=95=B0UPF?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BD=91=E5=8D=A1=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ne/neQuickSetup/components/Para5GForm.vue | 227 +++++++++++------- 1 file changed, 141 insertions(+), 86 deletions(-) diff --git a/src/views/ne/neQuickSetup/components/Para5GForm.vue b/src/views/ne/neQuickSetup/components/Para5GForm.vue index b4528fb7..84032680 100644 --- a/src/views/ne/neQuickSetup/components/Para5GForm.vue +++ b/src/views/ne/neQuickSetup/components/Para5GForm.vue @@ -50,9 +50,10 @@ let fromState = ref({ ims_sip_ip: '192.168.8.110', upf_type: 'LightUPF', upf_driver_type: 'vmxnet3', - upf_card_name: 'eth0', + upfn3_card_name: 'eth0', upfn3_pci: '0000:00:00.0', upfn3_mac: '00:00:00:00:00:00', + upfn6_card_name: 'eth0', upfn6_pci: '0000:00:00.0', upfn6_mac: '00:00:00:00:00:00', }, @@ -243,7 +244,12 @@ watch( - + + + + + vmxnet3 + dpdk + host + + + - + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - From 68b9c5fa5ee50782b5449fd7d13ec0beeac4f2ab Mon Sep 17 00:00:00 2001 From: zhongzm Date: Thu, 28 Nov 2024 18:45:11 +0800 Subject: [PATCH 105/110] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E7=B4=AF?= =?UTF-8?q?=E5=8A=A0=E5=80=BC=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/locales/en-US.ts | 1 + src/i18n/locales/zh-CN.ts | 1 + src/views/perfManage/kpiOverView/index.vue | 26 ++++++++++++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index de715fb2..5566b4da 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -1111,6 +1111,7 @@ export default { "maxValue":"Max Value", "minValue":"Min Value", "avgValue":"Average Value", + "totalValue":"Worth Value", "kpiChartTitle":"Overview of NE metrics", "changeLine":"Change to Line Charts", "changeBar":"Change to Bar Charts", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index d65403f0..5a77cb6b 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -1111,6 +1111,7 @@ export default { "maxValue":"最大值", "minValue":"最小值", "avgValue":"平均值", + "totalValue":"总值", "kpiChartTitle":"网元指标概览", "changeLine":"切换为折线图", "changeBar":"切换为柱状图", diff --git a/src/views/perfManage/kpiOverView/index.vue b/src/views/perfManage/kpiOverView/index.vue index b7a900e3..5998c22e 100644 --- a/src/views/perfManage/kpiOverView/index.vue +++ b/src/views/perfManage/kpiOverView/index.vue @@ -14,7 +14,6 @@ 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 { viewTransitionTheme } from 'antdv-pro-layout'; const { t, currentLocale } = useI18n(); //定义KPI接口 interface KPIBase{ @@ -394,7 +393,7 @@ const themeObserver = new MutationObserver(() => { extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);' }, // 重新设置系列数据 - series + series }; // 使用新的配置更新图表 @@ -564,7 +563,7 @@ const updateChart = () => { ? '#CACADA' : '#333' }, - // 添加自���计算的分割段数 + // 添加自计算的分割段数 splitNumber: 5, // 添加自动计算的最小/最大值范围 scale: true, @@ -745,7 +744,8 @@ interface KPIStats { title: string; max: number; min: number; - avg:number; + avg: number; + total: number; } // 添加计算属性,用于计算每个指标的最大值和最小值 @@ -766,9 +766,12 @@ const updateKpiStats = () => { // 获取该指标的所有数值 const values = chartData.value.map(item => Number(item[kpiId]) || 0); + // 计算总值 + const total = Number(values.reduce((sum, val) => sum + val, 0).toFixed(2)); + // 计算平均值 const avg = values.length > 0 - ? Number((values.reduce((sum, val) => sum + val, 0) / values.length).toFixed(2)) + ? Number((total / values.length).toFixed(2)) : 0; return { @@ -776,7 +779,8 @@ const updateKpiStats = () => { title: kpi.title, max: Math.max(...values), min: Math.min(...values), - avg:avg + avg: avg, + total: total }; }).filter((item): item is KPIStats => item !== null); }; @@ -805,7 +809,7 @@ const statsColumns: TableColumnType[] = [ title: t('views.perfManage.kpiOverView.kpiName'), dataIndex: 'title', key: 'title', - width: '63%', + width: '50%', }, { title: t('views.perfManage.kpiOverView.maxValue'), @@ -830,6 +834,14 @@ const statsColumns: TableColumnType[] = [ width: '12%', sorter: (a: KPIStats, b: KPIStats) => a.avg - b.avg, sortDirections: ['ascend', 'descend'], + }, + { + title: t('views.perfManage.kpiOverView.totalValue'), + dataIndex: 'total', + key: 'total', + width: '12%', + sorter: (a: KPIStats, b: KPIStats) => a.total - b.total, + sortDirections: ['ascend', 'descend'], } ]; From 154569304c5299a2c61086edd7287f232b13510e Mon Sep 17 00:00:00 2001 From: zhongzm Date: Thu, 28 Nov 2024 18:57:35 +0800 Subject: [PATCH 106/110] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/perfManage/kpiOverView/index.vue | 65 +++++++++++++--------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/views/perfManage/kpiOverView/index.vue b/src/views/perfManage/kpiOverView/index.vue index 5998c22e..c1243128 100644 --- a/src/views/perfManage/kpiOverView/index.vue +++ b/src/views/perfManage/kpiOverView/index.vue @@ -352,13 +352,13 @@ const themeObserver = new MutationObserver(() => { selected: Object.fromEntries( selectedKPIs.value.map(kpiId => [ kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId, - selectedRow.value ? kpiId === selectedRow.value : true + selectedRows.value.length === 0 ? true : selectedRows.value.includes(kpiId) ]) ) }, tooltip: { trigger: 'axis', - position: function (point: number[], params: any, dom: HTMLElement, rect: any, size: { viewSize: number[], contentSize: number[] }) { + position: function (point: number[], size: { viewSize: number[], contentSize: number[] }) { const [x, y] = point; const [viewWidth] = size.viewSize; const [tooltipWidth, tooltipHeight] = size.contentSize; @@ -442,7 +442,7 @@ const updateChart = () => { }, tooltip: { trigger: 'axis', - position: function (point: number[], params: any, dom: HTMLElement, rect: any, size: { viewSize: number[], contentSize: number[] }) { + position: function (point: number[], size: { viewSize: number[], contentSize: number[] }) { const [x, y] = point; const [viewWidth] = size.viewSize; const [tooltipWidth, tooltipHeight] = size.contentSize; @@ -489,7 +489,7 @@ const updateChart = () => { selected: Object.fromEntries( selectedKPIs.value.map(kpiId => [ kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId, - selectedRow.value ? kpiId === selectedRow.value : true + selectedRows.value.length === 0 ? true : selectedRows.value.includes(kpiId) ]) ), show: false, @@ -786,7 +786,7 @@ const updateKpiStats = () => { }; -// 添加表格列定义 +// 添加表列定义 const statsColumns: TableColumnType[] = [ { title: '', @@ -845,32 +845,31 @@ const statsColumns: TableColumnType[] = [ } ]; -// 添加一个变量来跟踪当前选中的行 -const selectedRow = ref(null); +// 将 selectedRow 改为 selectedRows 数组 +const selectedRows = ref([]); -// 添加处理行点击的方法 +// 修改处理行点击的方法 const handleRowClick = (record: KPIStats) => { - if (selectedRow.value === record.kpiId) { - // 如果点击的是当前选中的行,则取消选中 - selectedRow.value = null; - // 更新图表,显示所有指标 - updateChartLegendSelect(); + const index = selectedRows.value.indexOf(record.kpiId); + if (index > -1) { + // 如果已经选中,则取消选中 + selectedRows.value.splice(index, 1); } else { - // 选中新行 - selectedRow.value = record.kpiId; - // 更新图表,只显示选中的指标 - updateChartLegendSelect(record.kpiId); + // 添加新的选中项 + selectedRows.value.push(record.kpiId); } + // 更新图表显示 + updateChartLegendSelect(); }; -// 添加更新表图例选中态的方法 -const updateChartLegendSelect = (selectedKpiId?: string) => { +// 修改更新图表图例选中态的方法 +const updateChartLegendSelect = () => { if (!chart) return; const legendSelected = Object.fromEntries( selectedKPIs.value.map(kpiId => [ kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId, - selectedKpiId ? kpiId === selectedKpiId : true + selectedRows.value.length === 0 ? true : selectedRows.value.includes(kpiId) ]) ); @@ -880,6 +879,14 @@ const updateChartLegendSelect = (selectedKpiId?: string) => { } }); }; + +// 修改表格行的自定义配置 +const tableRowConfig = computed(() => { + return (record: KPIStats) => ({ + onClick: () => handleRowClick(record), + class: selectedRows.value.includes(record.kpiId) ? 'selected-row' : '' + }); +}); diff --git a/src/views/perfManage/goldTarget/index.vue b/src/views/perfManage/goldTarget/index.vue index d2a87a82..eba2f160 100644 --- a/src/views/perfManage/goldTarget/index.vue +++ b/src/views/perfManage/goldTarget/index.vue @@ -23,6 +23,7 @@ import { nextTick, onBeforeUnmount, h, + watch } from 'vue'; import { PageContainer } from 'antdv-pro-layout'; import { message, Modal, TableColumnType } from 'ant-design-vue/es'; @@ -43,6 +44,8 @@ import { generateColorRGBA } from '@/utils/generate-utils'; import { OptionsType, WS } from '@/plugins/ws-websocket'; import { useRoute } from 'vue-router'; import { LineOutlined } from '@ant-design/icons-vue'; +import useLayoutStore from '@/store/modules/layout'; +const layoutStore = useLayoutStore(); const neInfoStore = useNeInfoStore(); const route = useRoute(); const { t, currentLocale } = useI18n(); @@ -216,6 +219,22 @@ const statsColumns: TableColumnType[] = [ key: 'title', width: '65%', }, + { + title: t('views.perfManage.kpiOverView.totalValue'), + dataIndex: 'total', + key: 'total', + width: '24%', + sorter: (a: any, b: any) => a.total - b.total, + sortDirections: ['ascend', 'descend'], + }, + { + title: t('views.perfManage.kpiOverView.avgValue'), + dataIndex: 'avg', + key: 'avg', + width: '24%', + sorter: (a: any, b: any) => a.avg - b.avg, + sortDirections: ['ascend', 'descend'], + }, { title: t('views.perfManage.kpiOverView.maxValue'), dataIndex: 'max', @@ -389,11 +408,22 @@ function fnGetList() { const values = tableState.data.map((item: any) => { return item[columns.key] ? Number(item[columns.key]) : 0; }); + + // 计算总值 + const total = Number(values.reduce((sum, val) => sum + val, 0).toFixed(2)); + + // 计算平均值 + const avg = values.length > 0 + ? Number((total / values.length).toFixed(2)) + : 0; + kpiStats.value.push({ kpiId: columns.key, title: columns.title, max: values.length > 0 ? Math.max(...values) : 0, min: values.length > 0 ? Math.min(...values) : 0, + avg: avg, + total: total }); } } @@ -418,6 +448,7 @@ function fnRanderChart() { position: function (pt: any) { return [pt[0], '10%']; }, + confine: true, // 限制 tooltip 显示范围 }, xAxis: { //x类别轴 @@ -500,6 +531,9 @@ function fnRanderChartData() { key: `${columns.key}`, type: 'line', symbol: 'none', + symbolSize: 6, + smooth: 0.6, + showSymbol: true, sampling: 'lttb', itemStyle: { color: color, @@ -549,8 +583,32 @@ function fnRanderChartData() { xAxis: { type: 'category', boundaryGap: false, + axisLabel: { + color: document.documentElement.getAttribute('data-theme') === 'dark' + ? '#CACADA' + : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: getSplitLineColor() + } + }, data: chartDataXAxisData, }, + yAxis: { + axisLabel: { + color: document.documentElement.getAttribute('data-theme') === 'dark' + ? '#CACADA' + : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: getSplitLineColor() + } + }, + }, series: chartDataYSeriesData, }, { @@ -647,29 +705,38 @@ function wsMessage(res: Record) { } // 添加一个变量来跟踪当前选中的行 -const selectedRow = ref(null); +const selectedRow = ref([]); // 添加处理行点击的方法 function handleRowClick(record: any) { - if (selectedRow.value === record.kpiId) { - // 如果点击的是当前选中的行,则取消选中 - selectedRow.value = null; - // 更新图表,显示所有指标 - for (let key in chartLegendSelected) { - chartLegendSelected[key] = true; - } + const index = selectedRow.value.indexOf(record.kpiId); + + // 如果已经选中,取消选中 + if (index > -1) { + selectedRow.value.splice(index, 1); + chartLegendSelected[record.title] = false; } else { - // 选中新行 - selectedRow.value = record.kpiId; - // 更新图表,只显示选中的指标 - for (let key in chartLegendSelected) { - if (key === record.title) { - chartLegendSelected[key] = true; - } else { + // 添加新的选中项 + selectedRow.value.push(record.kpiId); + + // 如果只有一个选中项,重置为 false + if (selectedRow.value.length === 1) { + Object.keys(chartLegendSelected).forEach(key => { chartLegendSelected[key] = false; - } + }); } + + chartLegendSelected[record.title] = true; } + + // 如果没有选中项,设置所有图例为 true + if (selectedRow.value.length === 0) { + Object.keys(chartLegendSelected).forEach(key => { + chartLegendSelected[key] = true; + }); + } + + // 更新图表设置 kpiChart.value.setOption({ legend: { selected: chartLegendSelected, @@ -677,6 +744,73 @@ function handleRowClick(record: any) { }); } +// 添加一个函数来获取当前主题下的网格线颜色 +function getSplitLineColor() { + return document.documentElement.getAttribute('data-theme') === 'dark' + ? '#333333' + : '#E8E8E8'; // 亮色模式返回 undefined,使用默认颜色 +} + +// 监听主题变化 +watch( + () => layoutStore.proConfig.theme, // 监听的值 + (newValue) => { + if (kpiChart.value) { + const splitLineColor = getSplitLineColor(); + // 绘制图数据 + kpiChart.value.setOption( + { + tooltip: { + trigger: 'axis', + position: function (pt: any) { + return [pt[0], '10%']; + }, + confine: true, // 限制 tooltip 显示范围 + backgroundColor: newValue === 'dark' + ? 'rgba(48, 48, 48, 0.8)' + : 'rgba(255, 255, 255, 0.9)', + borderColor: newValue === 'dark' + ? '#555' + : '#ddd', + textStyle: { + color: newValue === 'dark' + ? '#CACADA' + : '#333' + }, + }, + xAxis: { + axisLabel: { + color: newValue === 'dark' + ? '#CACADA' + : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor + } + } + }, + yAxis: { + axisLabel: { + color: newValue === 'dark' + ? '#CACADA' + : '#333' + }, + splitLine: { + show: true, + lineStyle: { + color: splitLineColor + } + } + } + } + ); + + } + } +); + onMounted(() => { // 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF PCF // 获取网元网元列表 @@ -714,11 +848,16 @@ onMounted(() => { } // 查询当前小时 - const nowDate: Date = new Date(); - nowDate.setMinutes(0, 0, 0); - queryRangePicker.value[0] = `${nowDate.getTime()}`; - nowDate.setMinutes(59, 59, 59); - queryRangePicker.value[1] = `${nowDate.getTime()}`; + const now = new Date(); + now.setMinutes(0, 0, 0); + // 设置起始时间为整点前一小时 + const startTime = new Date(now); + startTime.setHours(now.getHours() - 1); + queryRangePicker.value[0] = `${startTime.getTime()}`; + // 设置结束时间为整点 + const endTime = new Date(now); + endTime.setMinutes(59, 59, 59); + queryRangePicker.value[1] = `${endTime.getTime()}`; fnGetListTitle(); // 绘图 fnRanderChart(); @@ -739,68 +878,42 @@ onBeforeUnmount(() => {