From ecf78bc7278f908c50eedc597a69c9491434ffea Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Sat, 21 Mar 2026 17:06:54 +0800 Subject: [PATCH 1/5] fix --- pnpm-lock.yaml | 54 +++ src/api/ops/dcControl.ts | 22 +- src/locale/en-US.ts | 1 + src/locale/zh-CN.ts | 1 + src/router/menu-data.ts | 17 +- src/router/routes/modules/ops.ts | 10 + .../system-settings/license-center/index.vue | 379 ++++++++++++++++++ 7 files changed, 480 insertions(+), 4 deletions(-) create mode 100644 src/views/ops/pages/system-settings/license-center/index.vue diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fc39cf..141b4d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: vue-router: specifier: '5' version: 5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) + vue-web-terminal: + specifier: ^3.4.1 + version: 3.4.1(typescript@5.9.3) devDependencies: '@arco-plugins/vite-vue': specifier: ^1.4.6 @@ -1551,6 +1554,9 @@ packages: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} + clipboard@2.0.11: + resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1865,6 +1871,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + delegate@3.2.0: + resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==} + dir-glob@2.2.2: resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==} engines: {node: '>=4'} @@ -2411,6 +2420,9 @@ packages: engines: {node: '>=0.6.0'} hasBin: true + good-listener@1.2.2: + resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -3707,6 +3719,9 @@ packages: scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + select@1.1.2: + resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4056,6 +4071,9 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -4344,6 +4362,11 @@ packages: peerDependencies: vue: ^3.0.0 + vue-json-viewer@3.0.4: + resolution: {integrity: sha512-pnC080rTub6YjccthVSNQod2z9Sl5IUUq46srXtn6rxwhW8QM4rlYn+CTSLFKXWfw+N3xv77Cioxw7B4XUKIbQ==} + peerDependencies: + vue: ^3.2.2 + vue-router@5.0.3: resolution: {integrity: sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==} peerDependencies: @@ -4365,6 +4388,9 @@ packages: peerDependencies: typescript: '>=5.0.0' + vue-web-terminal@3.4.1: + resolution: {integrity: sha512-+gU28qClqvIZQlzokcvDS2tbFpGfIJKIPc6dvLm2VYX110c6NOh7mV1YrcUESnaE5VQ9DgxqtIbr1YraEA/GRQ==} + vue@3.5.29: resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==} peerDependencies: @@ -5884,6 +5910,12 @@ snapshots: slice-ansi: 8.0.0 string-width: 8.2.0 + clipboard@2.0.11: + dependencies: + good-listener: 1.2.2 + select: 1.1.2 + tiny-emitter: 2.1.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -6178,6 +6210,8 @@ snapshots: delayed-stream@1.0.0: {} + delegate@3.2.0: {} + dir-glob@2.2.2: dependencies: path-type: 3.0.0 @@ -6882,6 +6916,10 @@ snapshots: dependencies: minimist: 1.2.8 + good-listener@1.2.2: + dependencies: + delegate: 3.2.0 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -8164,6 +8202,8 @@ snapshots: scule@1.3.0: {} + select@1.1.2: {} + semver@5.7.2: {} semver@6.3.1: {} @@ -8630,6 +8670,8 @@ snapshots: text-table@0.2.0: {} + tiny-emitter@2.1.0: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8953,6 +8995,11 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.29(typescript@5.9.3) + vue-json-viewer@3.0.4(vue@3.5.29(typescript@5.9.3)): + dependencies: + clipboard: 2.0.11 + vue: 3.5.29(typescript@5.9.3) + vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)): dependencies: '@babel/generator': 7.29.1 @@ -8983,6 +9030,13 @@ snapshots: '@vue/language-core': 3.2.5 typescript: 5.9.3 + vue-web-terminal@3.4.1(typescript@5.9.3): + dependencies: + vue: 3.5.29(typescript@5.9.3) + vue-json-viewer: 3.0.4(vue@3.5.29(typescript@5.9.3)) + transitivePeerDependencies: + - typescript + vue@3.5.29(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.29 diff --git a/src/api/ops/dcControl.ts b/src/api/ops/dcControl.ts index 5eea408..7935f02 100644 --- a/src/api/ops/dcControl.ts +++ b/src/api/ops/dcControl.ts @@ -1,5 +1,24 @@ import { request } from "@/api/request"; +/** 许可证配置(与 DC-Control `LicenceConfig` / 接口 `data` 一致;字段按实际响应可能部分缺失) */ +export interface LicenceConfig { + title?: string + version?: string + company_name?: string + create_time?: string + expire_time?: string + machine_code?: string + max_database?: number + max_middleware?: number + max_pc?: number + max_server?: number + max_client?: number + max_user?: number + max_role?: number + max_permission?: number + max_menu?: number +} + /** 获取 采集器 */ export const fetchCollectors = (data: { page: number, size: number, keyword?: string }) => request.get("/DC-Control/v1/collectors", { params: data }); @@ -19,4 +38,5 @@ export const updateCollector = (data: any) => request.put(`/DC-Control/v1/collec export const fetchCollectorStatistics = () => request.get("/DC-Control/v1/statistics"); /** 获取 许可证信息 */ -export const fetchLicenseInfo = () => request.get("/DC-Control/v1/license"); +export const fetchLicenseInfo = () => + request.get<{ code?: number; data?: LicenceConfig; message?: string }>("/DC-Control/v1/license"); diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts index dbab2ce..7b7c509 100644 --- a/src/locale/en-US.ts +++ b/src/locale/en-US.ts @@ -32,6 +32,7 @@ export default { 'menu.ops.systemSettings': 'System Settings', 'menu.ops.systemSettings.menuManagement': 'Menu Management', 'menu.ops.systemSettings.systemLogs': 'System Logs', + 'menu.ops.systemSettings.licenseCenter': 'License Center', 'menu.ops.webTest': 'Web Test', 'menu.ops.report': 'Report Management', 'menu.ops.report.history': 'Report History', diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index ba4f588..d73d41d 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -32,6 +32,7 @@ export default { 'menu.ops.systemSettings': '系统设置', 'menu.ops.systemSettings.menuManagement': '菜单管理', 'menu.ops.systemSettings.systemLogs': '系统日志', + 'menu.ops.systemSettings.licenseCenter': '许可授权中心', 'menu.ops.webTest': '网页测试', 'menu.ops.report': '报表管理', 'menu.ops.report.history': '报表历史', diff --git a/src/router/menu-data.ts b/src/router/menu-data.ts index 8ac3bad..1b33029 100644 --- a/src/router/menu-data.ts +++ b/src/router/menu-data.ts @@ -181,6 +181,11 @@ function extractRelativePath(childPath: string, parentPath: string): string { * @param parentIsFull 父级菜单的 is_full 字段 * @returns 子路由配置数组 */ +/** 服务端未配置 component 时按 menu_path 绑定视图(避免误用 redirect 导致白屏) */ +const MENU_PATH_COMPONENT_FALLBACK: { test: (fullPath: string) => boolean; component: string }[] = [ + { test: (p) => p.includes('license-center'), component: 'ops/pages/system-settings/license-center' }, +] + function transformChildRoutes( children: ServerMenuItem[], parentComponent?: string, @@ -188,10 +193,16 @@ function transformChildRoutes( parentIsFull?: boolean ): AppRouteRecordRaw[] { return children.map((child) => { - // 优先使用子菜单自己的 component,否则继承父级的 component - const componentPath = child.component || parentComponent - // 计算子路由的相对路径 + // 计算子路由的相对路径(需先于 component 解析,供 path 兜底使用) const childFullPath = child.menu_path || child.path || '' + + // 优先使用子菜单自己的 component,否则继承父级的 component;再按路径兜底 + let componentPath = child.component || parentComponent + const matchedFallback = MENU_PATH_COMPONENT_FALLBACK.find((fb) => fb.test(childFullPath)) + if (matchedFallback) { + componentPath = matchedFallback.component + } + const relativePath = extractRelativePath(childFullPath, parentPath || '') const route: AppRouteRecordRaw = { diff --git a/src/router/routes/modules/ops.ts b/src/router/routes/modules/ops.ts index 0cc058c..1631cc2 100644 --- a/src/router/routes/modules/ops.ts +++ b/src/router/routes/modules/ops.ts @@ -32,6 +32,16 @@ const OPS: AppRouteRecordRaw = { roles: ['*'], }, }, + { + path: 'license-center', + name: 'LicenseCenter', + component: () => import('@/views/ops/pages/system-settings/license-center/index.vue'), + meta: { + locale: 'menu.ops.systemSettings.licenseCenter', + requiresAuth: true, + roles: ['*'], + }, + }, { path: 'web-test', name: 'WebTest', diff --git a/src/views/ops/pages/system-settings/license-center/index.vue b/src/views/ops/pages/system-settings/license-center/index.vue new file mode 100644 index 0000000..34bd380 --- /dev/null +++ b/src/views/ops/pages/system-settings/license-center/index.vue @@ -0,0 +1,379 @@ + + + + + + + + 许可证信息 + {{ license?.company_name || '—' }} + + + + + + 刷新 + + + + + + + + + + 基本信息 + + + + {{ row.label }} + {{ row.display }} + + + + + + + 资源限制 + + 配额项为 0 时表示该维度不按许可证限制数量(由业务逻辑决定)。 + + + {{ row.label }} + {{ row.display }} + + + + + + + + + + + + + + From 87741c59ff865afc279d4dd21a5c0c0189efd4e6 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Sat, 21 Mar 2026 17:13:47 +0800 Subject: [PATCH 2/5] fix --- src/router/menu-data.ts | 27 +++++++++++++++++---------- src/router/routes/modules/ops.ts | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/router/menu-data.ts b/src/router/menu-data.ts index 1b33029..a69c164 100644 --- a/src/router/menu-data.ts +++ b/src/router/menu-data.ts @@ -181,10 +181,16 @@ function extractRelativePath(childPath: string, parentPath: string): string { * @param parentIsFull 父级菜单的 is_full 字段 * @returns 子路由配置数组 */ -/** 服务端未配置 component 时按 menu_path 绑定视图(避免误用 redirect 导致白屏) */ -const MENU_PATH_COMPONENT_FALLBACK: { test: (fullPath: string) => boolean; component: string }[] = [ - { test: (p) => p.includes('license-center'), component: 'ops/pages/system-settings/license-center' }, -] +/** 许可授权中心菜单路径(严格匹配末段,避免 menu_path 中含 query 等导致误伤其它菜单如用户管理) */ +const LICENSE_CENTER_VIEW = 'ops/pages/system-settings/license-center' + +function isLicenseCenterMenuPath(fullPath: string): boolean { + const path = String(fullPath ?? '') + .trim() + .split('?')[0] + .replace(/\/+$/, '') + return path.endsWith('/license-center') || path === 'license-center' +} function transformChildRoutes( children: ServerMenuItem[], @@ -193,14 +199,15 @@ function transformChildRoutes( parentIsFull?: boolean ): AppRouteRecordRaw[] { return children.map((child) => { - // 计算子路由的相对路径(需先于 component 解析,供 path 兜底使用) - const childFullPath = child.menu_path || child.path || '' + const childFullPath = String(child.menu_path ?? child.path ?? '').trim() - // 优先使用子菜单自己的 component,否则继承父级的 component;再按路径兜底 + // 已配置 component 的菜单绝不覆盖;仅对许可页做路径/code 兜底,避免 includes 误匹配 let componentPath = child.component || parentComponent - const matchedFallback = MENU_PATH_COMPONENT_FALLBACK.find((fb) => fb.test(childFullPath)) - if (matchedFallback) { - componentPath = matchedFallback.component + if ( + !child.component && + (isLicenseCenterMenuPath(childFullPath) || child.code === 'LicenseCenter') + ) { + componentPath = LICENSE_CENTER_VIEW } const relativePath = extractRelativePath(childFullPath, parentPath || '') diff --git a/src/router/routes/modules/ops.ts b/src/router/routes/modules/ops.ts index 1631cc2..555e435 100644 --- a/src/router/routes/modules/ops.ts +++ b/src/router/routes/modules/ops.ts @@ -34,7 +34,7 @@ const OPS: AppRouteRecordRaw = { }, { path: 'license-center', - name: 'LicenseCenter', + name: 'OpsLicenseCenter', component: () => import('@/views/ops/pages/system-settings/license-center/index.vue'), meta: { locale: 'menu.ops.systemSettings.licenseCenter', From 6ac95501335585d66dacd5b27a55f305b85a290d Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Sat, 21 Mar 2026 17:19:33 +0800 Subject: [PATCH 3/5] fix --- .../system-settings/system-logs/index.vue | 177 +++++++++++++----- 1 file changed, 128 insertions(+), 49 deletions(-) diff --git a/src/views/ops/pages/system-settings/system-logs/index.vue b/src/views/ops/pages/system-settings/system-logs/index.vue index 96df1d5..22d5a66 100644 --- a/src/views/ops/pages/system-settings/system-logs/index.vue +++ b/src/views/ops/pages/system-settings/system-logs/index.vue @@ -1,8 +1,7 @@ - - + - {{ record.level }} - - + {{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }} - - 查看 + + + + + + + {{ detailRecord.level }} + + + + {{ detailRecord.module }} + + + {{ detailRecord.operator }} + + + {{ detailRecord.ip }} + + + {{ detailRecord.createdAt }} + + + {{ detailRecord.requestId }} + + + {{ detailRecord.content }} + + + + @@ -50,7 +82,6 @@ import { Message } from '@arco-design/web-vue' import type { TableColumnData } from '@arco-design/web-vue/es/table/interface' import type { FormItem } from '@/components/search-form/types' -// 定义表格数据类型 interface LogRecord { id: number level: string @@ -59,32 +90,42 @@ interface LogRecord { operator: string ip: string createdAt: string + /** 用于时间范围筛选(毫秒时间戳) */ + timestamp: number + requestId: string } -// 模拟数据生成 const generateMockData = (count: number): LogRecord[] => { const levels = ['INFO', 'WARN', 'ERROR', 'DEBUG'] const modules = ['用户管理', '权限管理', '系统配置', '数据备份', '登录认证'] const operators = ['管理员', '张三', '李四', '系统', '定时任务'] - - return Array.from({ length: count }, (_, i) => ({ - id: i + 1, - level: levels[i % levels.length], - module: modules[i % modules.length], - content: `日志内容描述 ${i + 1}`, - operator: operators[i % operators.length], - ip: `192.168.${Math.floor(i / 255)}.${i % 255}`, - createdAt: new Date(Date.now() - i * 3600000).toLocaleString('zh-CN'), - })) + + return Array.from({ length: count }, (_, i) => { + const timestamp = Date.now() - i * 3600000 + return { + id: i + 1, + level: levels[i % levels.length], + module: modules[i % modules.length], + content: `日志内容描述 ${i + 1}:系统执行例行检查与状态同步。`, + operator: operators[i % operators.length], + ip: `192.168.${Math.floor(i / 255) % 256}.${i % 256}`, + createdAt: new Date(timestamp).toLocaleString('zh-CN'), + timestamp, + requestId: `req-${10000 + i}`, + } + }) } -// 状态管理 const loading = ref(false) const tableData = ref([]) +const allFilteredData = ref([]) + const formModel = ref({ level: '', module: '', operator: '', + keyword: '', + dateRange: [] as unknown[], }) const pagination = reactive({ @@ -93,7 +134,9 @@ const pagination = reactive({ total: 0, }) -// 表单项配置 +const detailVisible = ref(false) +const detailRecord = ref(null) + const formItems = computed(() => [ { field: 'level', @@ -126,9 +169,21 @@ const formItems = computed(() => [ type: 'input', placeholder: '请输入操作人', }, + { + field: 'keyword', + label: '关键词', + type: 'input', + placeholder: '搜索日志内容', + }, + { + field: 'dateRange', + label: '时间范围', + type: 'dateRange', + placeholder: '选择时间范围', + span: 16, + }, ]) -// 表格列配置 const columns = computed(() => [ { title: '序号', @@ -177,7 +232,6 @@ const columns = computed(() => [ }, ]) -// 获取日志级别颜色 const getLevelColor = (level: string) => { const colorMap: Record = { INFO: 'arcoblue', @@ -188,38 +242,54 @@ const getLevelColor = (level: string) => { return colorMap[level] || 'gray' } -// 模拟异步获取数据 -const fetchData = async () => { - loading.value = true - - // 模拟网络延迟 - await new Promise(resolve => setTimeout(resolve, 500)) - - let data = generateMockData(100) - - // 根据搜索条件过滤 - if (formModel.value.level) { - data = data.filter(item => item.level === formModel.value.level) +function applyFilters(source: LogRecord[]): LogRecord[] { + let data = source + const f = formModel.value + + if (f.level) { + data = data.filter(item => item.level === f.level) } - if (formModel.value.module) { - data = data.filter(item => item.module === formModel.value.module) + if (f.module) { + data = data.filter(item => item.module === f.module) } - if (formModel.value.operator) { - data = data.filter(item => item.operator.includes(formModel.value.operator)) + if (f.operator) { + data = data.filter(item => item.operator.includes(f.operator)) } - - // 更新分页 - pagination.total = data.length - - // 分页截取 + if (f.keyword?.trim()) { + const kw = f.keyword.trim() + data = data.filter(item => item.content.includes(kw)) + } + if (f.dateRange && f.dateRange.length === 2) { + const [start, end] = f.dateRange + const startMs = new Date(start as string | Date).getTime() + const endMs = new Date(end as string | Date).getTime() + if (!Number.isNaN(startMs) && !Number.isNaN(endMs)) { + data = data.filter(item => item.timestamp >= startMs && item.timestamp <= endMs) + } + } + + return data +} + +function slicePage(data: LogRecord[]) { const start = (pagination.current - 1) * pagination.pageSize const end = start + pagination.pageSize tableData.value = data.slice(start, end) - +} + +const fetchData = async () => { + loading.value = true + await new Promise(resolve => setTimeout(resolve, 400)) + + const base = generateMockData(100) + const filtered = applyFilters(base) + allFilteredData.value = filtered + pagination.total = filtered.length + slicePage(filtered) + loading.value = false } -// 事件处理 const handleSearch = () => { pagination.current = 1 fetchData() @@ -230,6 +300,8 @@ const handleReset = () => { level: '', module: '', operator: '', + keyword: '', + dateRange: [], } pagination.current = 1 fetchData() @@ -237,7 +309,7 @@ const handleReset = () => { const handlePageChange = (current: number) => { pagination.current = current - fetchData() + slicePage(allFilteredData.value) } const handleRefresh = () => { @@ -250,10 +322,10 @@ const handleDownload = () => { } const handleView = (record: LogRecord) => { - Message.info(`查看日志详情:${record.id}`) + detailRecord.value = record + detailVisible.value = true } -// 初始化加载数据 fetchData() @@ -267,4 +339,11 @@ export default { .container { padding: 0 20px 20px 20px; } + +.detail-content { + white-space: pre-wrap; + word-break: break-word; + line-height: 1.6; + color: var(--color-text-1); +} From 2c3195b9033e7caaa985d6037c1c88e4ab517237 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Sat, 21 Mar 2026 17:39:39 +0800 Subject: [PATCH 4/5] fix --- src/components/data-table/index.vue | 30 +- src/components/search-table/index.vue | 30 +- src/views/ops/pages/alert/notice/index.vue | 23 +- .../setting/components/PolicyFormDialog.vue | 2 +- .../setting/components/RuleManageDialog.vue | 2 +- .../alert/tackle/components/DetailDialog.vue | 2 +- src/views/ops/pages/alert/tackle/index.vue | 420 ++++++++---------- 7 files changed, 265 insertions(+), 244 deletions(-) diff --git a/src/components/data-table/index.vue b/src/components/data-table/index.vue index d14b927..1b312ae 100644 --- a/src/components/data-table/index.vue +++ b/src/components/data-table/index.vue @@ -60,7 +60,12 @@ :data="data" :bordered="bordered" :size="size" + :row-selection="rowSelection" + :scroll="scroll" @page-change="onPageChange" + @page-size-change="onPageSizeChange" + @selection-change="onSelectionChange" + @row-click="onRowClick" > @@ -72,7 +77,7 @@ diff --git a/src/views/ops/pages/alert/setting/components/PolicyFormDialog.vue b/src/views/ops/pages/alert/setting/components/PolicyFormDialog.vue index a94b593..025b37c 100644 --- a/src/views/ops/pages/alert/setting/components/PolicyFormDialog.vue +++ b/src/views/ops/pages/alert/setting/components/PolicyFormDialog.vue @@ -138,7 +138,7 @@ :data="dispatchRuleData" :pagination="false" :bordered="true" - size="small" + size="medium" > diff --git a/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue b/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue index 72cd570..f9d12db 100644 --- a/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue +++ b/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue @@ -31,7 +31,7 @@ :loading="ruleLoading" :pagination="rulePagination" :bordered="true" - size="small" + size="medium" @page-change="handlePageChange" > diff --git a/src/views/ops/pages/alert/tackle/components/DetailDialog.vue b/src/views/ops/pages/alert/tackle/components/DetailDialog.vue index 5d7edc5..2be029d 100644 --- a/src/views/ops/pages/alert/tackle/components/DetailDialog.vue +++ b/src/views/ops/pages/alert/tackle/components/DetailDialog.vue @@ -118,7 +118,7 @@ :data="processRecords" :columns="processColumns" :pagination="false" - size="small" + size="medium" > diff --git a/src/views/ops/pages/alert/tackle/index.vue b/src/views/ops/pages/alert/tackle/index.vue index e7961cf..e2ea0c6 100644 --- a/src/views/ops/pages/alert/tackle/index.vue +++ b/src/views/ops/pages/alert/tackle/index.vue @@ -1,188 +1,144 @@ - - - - - - - - - - 查询 - - - - - - 重置 - - - - - - - - - - - 批量确认 ({{ selectedRowKeys.length }}) - - + + + + + + + - + - - - {{ rowIndex + 1 }} - + + {{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }} + - - - {{ getStatusText(record.status) }} - - + + + {{ getStatusText(record.status) }} + + - - - {{ record.severity.name || record.severity.code }} - - + + + {{ record.severity.name || record.severity.code }} + + - - {{ formatDateTime(record.starts_at) }} - + + {{ formatDateTime(record.starts_at) }} + - - {{ formatDuration(record.duration) }} - + + {{ formatDuration(record.duration) }} + - - - {{ record.process_status }} - - - - + + + {{ record.process_status }} + + - + - - {{ record.processed_at ? formatDateTime(record.processed_at) : '-' }} - + + {{ record.processed_at ? formatDateTime(record.processed_at) : '-' }} + - - - {{ getNotifyStatusText(record.notify_status) }} - - - - + + + {{ getNotifyStatusText(record.notify_status) }} + + - + - - - - {{ key }}: {{ value }} - - - - - + + + {{ formatLabelsLine(record.labels) }} + + - + - - - - - - - 确认 + + + + 确认 + + + 解决 + + + 屏蔽 + + + 评论 + + handleMoreSelect(v, record)"> + + 更多 - - - - - 解决 - - - - - - 屏蔽 - - - - - - 评论 - - - - 更多 - - - - 详情 - 处理记录 - - - - - - + + 详情 + 处理记录 + + + + + - - - - - import { ref, reactive, computed, onMounted } from 'vue' import { Message } from '@arco-design/web-vue' -import { - IconSearch, - IconRefresh, - IconCheck, - IconCheckCircle, - IconEyeInvisible, - IconMessage, - IconDown, -} from '@arco-design/web-vue/es/icon' import { useRouter } from 'vue-router' - -import SearchForm from '@/components/search-form/index.vue' +import type { FormItem } from '@/components/search-form/types' +import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface' +import SearchTable from '@/components/search-table/index.vue' import { searchFormConfig } from './config/search-form' import { columns } from './config/columns' -import { fetchAlertRecords, createAlertProcess } from '@/api/ops/alertRecord' +import { fetchAlertRecords } from '@/api/ops/alertRecord' import { fetchAlertLevelList } from '@/api/ops/alertLevel' import AckDialog from './components/AckDialog.vue' @@ -218,21 +166,17 @@ import DetailDialog from './components/DetailDialog.vue' const router = useRouter() -// 状态管理 const loading = ref(false) const tableData = ref([]) const timeRange = ref([]) -const selectedRowKeys = ref([]) const currentRecord = ref({}) -// 对话框状态 const ackDialogVisible = ref(false) const resolveDialogVisible = ref(false) const silenceDialogVisible = ref(false) const commentDialogVisible = ref(false) const detailDialogVisible = ref(false) -// 分页 const pagination = reactive({ current: 1, pageSize: 20, @@ -242,16 +186,23 @@ const pagination = reactive({ pageSizeOptions: ['10', '20', '50', '100'], }) -// 行选择 -const rowSelection = computed(() => ({ - type: 'checkbox', - showCheckedAll: true, -})) +const severityOptions = ref([]) -// 搜索参数 -const searchParams = ref({}) +const formModel = ref>({ + keyword: '', + status: '', + severity_id: '', +}) + +const formItems = computed(() => + searchFormConfig.map((item) => { + if (item.field === 'severity_id') { + return { ...item, options: severityOptions.value } + } + return item + }), +) -// 加载告警级别列表 onMounted(async () => { await loadSeverityOptions() handleSearch() @@ -259,51 +210,58 @@ onMounted(async () => { const loadSeverityOptions = async () => { try { - const result = await fetchAlertLevelList({ page: 1, page_size: 100 }) - const severityConfig = searchFormConfig.find((item) => item.field === 'severity_id') - if (severityConfig && result.details) { - severityConfig.options = result.data.map((item: any) => ({ - label: item.name || item.code, - value: item.id, - })) - } + const res = await fetchAlertLevelList({ page: 1, page_size: 100 }) + const list = res.details?.data ?? (res as any).data ?? [] + severityOptions.value = list.map((item: any) => ({ + label: item.name || item.code, + value: item.id, + })) } catch (error) { console.error('加载告警级别失败:', error) } } -// 搜索 +const handleFormModelUpdate = (value: Record) => { + formModel.value = value +} + const handleSearch = () => { pagination.current = 1 loadData() } const handleReset = () => { + formModel.value = { + keyword: '', + status: '', + severity_id: '', + } timeRange.value = [] - searchParams.value = {} pagination.current = 1 loadData() } -// 加载数据 const loadData = async () => { loading.value = true try { const params: any = { page: pagination.current, page_size: pagination.pageSize, - ...searchParams.value, + keyword: formModel.value.keyword || undefined, + status: formModel.value.status || undefined, + } + if (formModel.value.severity_id !== '' && formModel.value.severity_id != null) { + params.severity_id = formModel.value.severity_id } - // 处理时间范围 if (timeRange.value && timeRange.value.length === 2) { params.start_time = new Date(timeRange.value[0]).toISOString() params.end_time = new Date(timeRange.value[1]).toISOString() } const result = await fetchAlertRecords(params) - tableData.value = result.details.data || [] - pagination.total = result.details.total || 0 + tableData.value = result.details?.data || [] + pagination.total = result.details?.total || 0 } catch (error) { console.error('加载数据失败:', error) Message.error('加载数据失败') @@ -312,30 +270,22 @@ const loadData = async () => { } } -// 分页 -const onPageChange = (page: number) => { +const handlePageChange = (page: number) => { pagination.current = page loadData() } -const onPageSizeChange = (pageSize: number) => { +const handlePageSizeChange = (pageSize: number) => { pagination.pageSize = pageSize pagination.current = 1 loadData() } -// 行选择 -const handleSelectionChange = (rowKeys: number[]) => { - selectedRowKeys.value = rowKeys -} - -// 行点击 -const handleRowClick = (record: any) => { +const handleRowClick = (record: any, _ev: Event) => { currentRecord.value = record detailDialogVisible.value = true } -// 处理操作 const handleAck = (record: any) => { currentRecord.value = record ackDialogVisible.value = true @@ -356,28 +306,6 @@ const handleComment = (record: any) => { commentDialogVisible.value = true } -// 批量确认 -const handleBatchAck = async () => { - try { - const promises = selectedRowKeys.value.map((id) => - createAlertProcess({ - alert_record_id: id, - action: 'ack', - operator: getCurrentUser(), - comment: '批量确认', - }) - ) - await Promise.all(promises) - Message.success(`成功确认 ${selectedRowKeys.value.length} 条告警`) - selectedRowKeys.value = [] - loadData() - } catch (error) { - console.error('批量确认失败:', error) - Message.error('批量确认失败') - } -} - -// 更多操作 const handleMoreAction = (action: string, record: any) => { currentRecord.value = record switch (action) { @@ -393,12 +321,22 @@ const handleMoreAction = (action: string, record: any) => { } } -// 操作成功回调 +const handleMoreSelect = ( + value: string | number | Record | undefined, + record: any, +) => { + handleMoreAction(String(value), record) +} + const handleSuccess = () => { loadData() } -// 格式化函数 +const handleRefresh = () => { + loadData() + Message.success('数据已刷新') +} + const formatDateTime = (datetime: string) => { if (!datetime) return '-' return new Date(datetime).toLocaleString('zh-CN') @@ -424,7 +362,15 @@ const parsedLabels = (labels: string) => { } } -// 状态相关 +/** 单行展示,避免 a-space wrap + 多 tag 撑高整行 td(与其它告警列表页行高一致) */ +const formatLabelsLine = (labels: string) => { + const obj = parsedLabels(labels) + if (!obj || typeof obj !== 'object') return '' + return Object.entries(obj as Record) + .map(([k, v]) => `${k}: ${v == null ? '' : String(v)}`) + .join(', ') +} + const getStatusColor = (status: string) => { const colorMap: Record = { firing: 'red', @@ -466,23 +412,35 @@ const getNotifyStatusText = (status: string) => { } return textMap[status] || status } + -function getCurrentUser() { - // TODO: 从全局状态获取当前用户 - return 'admin' + From 996f86d3d07e91d0037c325f033c09f4702eb223 Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Sat, 21 Mar 2026 17:53:40 +0800 Subject: [PATCH 5/5] fix --- src/components/menu/index.vue | 29 +++++++++--- src/components/menu/use-menu-tree.ts | 16 ++----- src/router/routes/modules/remote.ts | 45 ++----------------- .../system-settings/license-center/index.vue | 1 - 4 files changed, 30 insertions(+), 61 deletions(-) diff --git a/src/components/menu/index.vue b/src/components/menu/index.vue index 77d97bb..64bc4fe 100644 --- a/src/components/menu/index.vue +++ b/src/components/menu/index.vue @@ -6,7 +6,13 @@ import { listenerRouteChange } from '@/utils/route-listener' import { compile, computed, defineComponent, h, ref } from 'vue' import { useI18n } from 'vue-i18n' import type { RouteMeta } from 'vue-router' -import { RouteRecordRaw, useRoute, useRouter } from 'vue-router' +import { + isNavigationFailure, + NavigationFailureType, + RouteRecordRaw, + useRoute, + useRouter, +} from 'vue-router' import useMenuTree from './use-menu-tree' import { COMMON_ICONS } from '@/views/ops/pages/system-settings/menu-management/menuIcons' @@ -45,10 +51,20 @@ export default defineComponent({ selectedKey.value = [item.name as string] return } - console.log('item', item) - // Trigger router change - router.push({ - name: item.name, + const name = item.name + if (name == null || name === '') { + console.warn('[Menu] 无法跳转:路由缺少 name', item.path, item.meta?.locale) + return + } + if (!router.hasRoute(name as string)) { + console.warn('[Menu] 无法跳转:未注册的路由 name', name, item.meta?.locale) + return + } + router.push({ name }).catch((err) => { + if (isNavigationFailure(err, NavigationFailureType.duplicated)) { + return + } + console.error('[Menu] 路由跳转失败', name, err) }) } const findMenuOpenKeys = (target: string) => { @@ -80,7 +96,8 @@ export default defineComponent({ const keySet = new Set([...menuOpenKeys, ...openKeys.value]) openKeys.value = [...keySet] - selectedKey.value = [activeMenu || menuOpenKeys[menuOpenKeys.length - 1]] + const leafKey = (activeMenu || menuOpenKeys[menuOpenKeys.length - 1]) as string | undefined + selectedKey.value = leafKey ? [leafKey] : [] } }, true) const setCollapse = (val: boolean) => { diff --git a/src/components/menu/use-menu-tree.ts b/src/components/menu/use-menu-tree.ts index 7f88f03..ef49203 100644 --- a/src/components/menu/use-menu-tree.ts +++ b/src/components/menu/use-menu-tree.ts @@ -15,12 +15,12 @@ export default function useMenuTree() { return appClientMenus }) const menuTree = computed(() => { - const copyRouter = cloneDeep(appRoute.value) as RouteRecordNormalized[] + const copyRouter = cloneDeep(appRoute.value || []) as RouteRecordNormalized[] copyRouter.sort((a: RouteRecordNormalized, b: RouteRecordNormalized) => { return (a.meta.order || 0) - (b.meta.order || 0) }) - function travel(_routes: RouteRecordRaw[], layer: number) { - if (!_routes) return null + function travel(_routes: RouteRecordRaw[], layer: number): RouteRecordRaw[] { + if (!_routes?.length) return [] const collector: any = _routes.map((element) => { // no access @@ -44,16 +44,8 @@ export default function useMenuTree() { element.children = subItem return element } - // the else logic - if (layer > 1) { - element.children = subItem - return element - } - - if (element.meta?.hideInMenu === false) { - return element - } + // 子级全部被权限/隐藏规则过滤时,不再把父级当成可点击叶子(避免 push 父级 name 无对应页面) return null }) return collector.filter(Boolean) diff --git a/src/router/routes/modules/remote.ts b/src/router/routes/modules/remote.ts index 70ca8d8..cb9d2f1 100644 --- a/src/router/routes/modules/remote.ts +++ b/src/router/routes/modules/remote.ts @@ -1,43 +1,4 @@ -import { DEFAULT_LAYOUT } from '../base' -import { AppRouteRecordRaw } from '../types' +import type { AppRouteRecordRaw } from '../types' -const REMOTE: AppRouteRecordRaw = { - // path: '/dc', - // name: 'DC', - // component: DEFAULT_LAYOUT, - // meta: { - // locale: 'menu.dc', - // requiresAuth: true, - // icon: 'icon-desktop', - // order: 99, - // hideInMenu: true, - // }, - // children: [ - // { - // path: 'detail', - // name: 'DCDetail', - // component: () => import('@/views/ops/pages/dc/detail/index.vue'), - // meta: { - // locale: 'menu.dc.detail', - // requiresAuth: true, - // roles: ['*'], - // // is_full: true, - // isNewTab: true, - // }, - // }, - // { - // path: 'remote', - // name: 'DCRemote', - // component: () => import('@/views/ops/pages/dc/remote/index.vue'), - // meta: { - // locale: 'menu.dc.remote', - // requiresAuth: true, - // roles: ['*'], - // // is_full: true, - // isNewTab: true, - // }, - // }, - // ], -} - -export default REMOTE +/** 占位:勿导出空对象 `{}`,否则会被当作一条无效路由加入 router,导致部分菜单匹配异常 */ +export default [] as AppRouteRecordRaw[] diff --git a/src/views/ops/pages/system-settings/license-center/index.vue b/src/views/ops/pages/system-settings/license-center/index.vue index 34bd380..4394882 100644 --- a/src/views/ops/pages/system-settings/license-center/index.vue +++ b/src/views/ops/pages/system-settings/license-center/index.vue @@ -6,7 +6,6 @@ 许可证信息 - {{ license?.company_name || '—' }}
配额项为 0 时表示该维度不按许可证限制数量(由业务逻辑决定)。