fix
This commit is contained in:
54
pnpm-lock.yaml
generated
54
pnpm-lock.yaml
generated
@@ -71,6 +71,9 @@ importers:
|
|||||||
vue-router:
|
vue-router:
|
||||||
specifier: '5'
|
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))
|
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:
|
devDependencies:
|
||||||
'@arco-plugins/vite-vue':
|
'@arco-plugins/vite-vue':
|
||||||
specifier: ^1.4.6
|
specifier: ^1.4.6
|
||||||
@@ -1551,6 +1554,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
|
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
clipboard@2.0.11:
|
||||||
|
resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -1865,6 +1871,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
delegate@3.2.0:
|
||||||
|
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
|
||||||
|
|
||||||
dir-glob@2.2.2:
|
dir-glob@2.2.2:
|
||||||
resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==}
|
resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2411,6 +2420,9 @@ packages:
|
|||||||
engines: {node: '>=0.6.0'}
|
engines: {node: '>=0.6.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
good-listener@1.2.2:
|
||||||
|
resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
|
||||||
|
|
||||||
gopd@1.2.0:
|
gopd@1.2.0:
|
||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3707,6 +3719,9 @@ packages:
|
|||||||
scule@1.3.0:
|
scule@1.3.0:
|
||||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||||
|
|
||||||
|
select@1.1.2:
|
||||||
|
resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
|
||||||
|
|
||||||
semver@5.7.2:
|
semver@5.7.2:
|
||||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -4056,6 +4071,9 @@ packages:
|
|||||||
text-table@0.2.0:
|
text-table@0.2.0:
|
||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
|
|
||||||
|
tiny-emitter@2.1.0:
|
||||||
|
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
|
||||||
|
|
||||||
tinyexec@1.0.2:
|
tinyexec@1.0.2:
|
||||||
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
|
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -4344,6 +4362,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.0.0
|
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:
|
vue-router@5.0.3:
|
||||||
resolution: {integrity: sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==}
|
resolution: {integrity: sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4365,6 +4388,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=5.0.0'
|
typescript: '>=5.0.0'
|
||||||
|
|
||||||
|
vue-web-terminal@3.4.1:
|
||||||
|
resolution: {integrity: sha512-+gU28qClqvIZQlzokcvDS2tbFpGfIJKIPc6dvLm2VYX110c6NOh7mV1YrcUESnaE5VQ9DgxqtIbr1YraEA/GRQ==}
|
||||||
|
|
||||||
vue@3.5.29:
|
vue@3.5.29:
|
||||||
resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
|
resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5884,6 +5910,12 @@ snapshots:
|
|||||||
slice-ansi: 8.0.0
|
slice-ansi: 8.0.0
|
||||||
string-width: 8.2.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:
|
cliui@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
@@ -6178,6 +6210,8 @@ snapshots:
|
|||||||
|
|
||||||
delayed-stream@1.0.0: {}
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
|
delegate@3.2.0: {}
|
||||||
|
|
||||||
dir-glob@2.2.2:
|
dir-glob@2.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-type: 3.0.0
|
path-type: 3.0.0
|
||||||
@@ -6882,6 +6916,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
|
|
||||||
|
good-listener@1.2.2:
|
||||||
|
dependencies:
|
||||||
|
delegate: 3.2.0
|
||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
@@ -8164,6 +8202,8 @@ snapshots:
|
|||||||
|
|
||||||
scule@1.3.0: {}
|
scule@1.3.0: {}
|
||||||
|
|
||||||
|
select@1.1.2: {}
|
||||||
|
|
||||||
semver@5.7.2: {}
|
semver@5.7.2: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
@@ -8630,6 +8670,8 @@ snapshots:
|
|||||||
|
|
||||||
text-table@0.2.0: {}
|
text-table@0.2.0: {}
|
||||||
|
|
||||||
|
tiny-emitter@2.1.0: {}
|
||||||
|
|
||||||
tinyexec@1.0.2: {}
|
tinyexec@1.0.2: {}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
@@ -8953,6 +8995,11 @@ snapshots:
|
|||||||
'@vue/devtools-api': 6.6.4
|
'@vue/devtools-api': 6.6.4
|
||||||
vue: 3.5.29(typescript@5.9.3)
|
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)):
|
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:
|
dependencies:
|
||||||
'@babel/generator': 7.29.1
|
'@babel/generator': 7.29.1
|
||||||
@@ -8983,6 +9030,13 @@ snapshots:
|
|||||||
'@vue/language-core': 3.2.5
|
'@vue/language-core': 3.2.5
|
||||||
typescript: 5.9.3
|
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):
|
vue@3.5.29(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/compiler-dom': 3.5.29
|
'@vue/compiler-dom': 3.5.29
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
import { request } from "@/api/request";
|
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 });
|
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 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");
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default {
|
|||||||
'menu.ops.systemSettings': 'System Settings',
|
'menu.ops.systemSettings': 'System Settings',
|
||||||
'menu.ops.systemSettings.menuManagement': 'Menu Management',
|
'menu.ops.systemSettings.menuManagement': 'Menu Management',
|
||||||
'menu.ops.systemSettings.systemLogs': 'System Logs',
|
'menu.ops.systemSettings.systemLogs': 'System Logs',
|
||||||
|
'menu.ops.systemSettings.licenseCenter': 'License Center',
|
||||||
'menu.ops.webTest': 'Web Test',
|
'menu.ops.webTest': 'Web Test',
|
||||||
'menu.ops.report': 'Report Management',
|
'menu.ops.report': 'Report Management',
|
||||||
'menu.ops.report.history': 'Report History',
|
'menu.ops.report.history': 'Report History',
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default {
|
|||||||
'menu.ops.systemSettings': '系统设置',
|
'menu.ops.systemSettings': '系统设置',
|
||||||
'menu.ops.systemSettings.menuManagement': '菜单管理',
|
'menu.ops.systemSettings.menuManagement': '菜单管理',
|
||||||
'menu.ops.systemSettings.systemLogs': '系统日志',
|
'menu.ops.systemSettings.systemLogs': '系统日志',
|
||||||
|
'menu.ops.systemSettings.licenseCenter': '许可授权中心',
|
||||||
'menu.ops.webTest': '网页测试',
|
'menu.ops.webTest': '网页测试',
|
||||||
'menu.ops.report': '报表管理',
|
'menu.ops.report': '报表管理',
|
||||||
'menu.ops.report.history': '报表历史',
|
'menu.ops.report.history': '报表历史',
|
||||||
|
|||||||
@@ -181,6 +181,11 @@ function extractRelativePath(childPath: string, parentPath: string): string {
|
|||||||
* @param parentIsFull 父级菜单的 is_full 字段
|
* @param parentIsFull 父级菜单的 is_full 字段
|
||||||
* @returns 子路由配置数组
|
* @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(
|
function transformChildRoutes(
|
||||||
children: ServerMenuItem[],
|
children: ServerMenuItem[],
|
||||||
parentComponent?: string,
|
parentComponent?: string,
|
||||||
@@ -188,10 +193,16 @@ function transformChildRoutes(
|
|||||||
parentIsFull?: boolean
|
parentIsFull?: boolean
|
||||||
): AppRouteRecordRaw[] {
|
): AppRouteRecordRaw[] {
|
||||||
return children.map((child) => {
|
return children.map((child) => {
|
||||||
// 优先使用子菜单自己的 component,否则继承父级的 component
|
// 计算子路由的相对路径(需先于 component 解析,供 path 兜底使用)
|
||||||
const componentPath = child.component || parentComponent
|
|
||||||
// 计算子路由的相对路径
|
|
||||||
const childFullPath = child.menu_path || child.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 relativePath = extractRelativePath(childFullPath, parentPath || '')
|
||||||
|
|
||||||
const route: AppRouteRecordRaw = {
|
const route: AppRouteRecordRaw = {
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ const OPS: AppRouteRecordRaw = {
|
|||||||
roles: ['*'],
|
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',
|
path: 'web-test',
|
||||||
name: 'WebTest',
|
name: 'WebTest',
|
||||||
|
|||||||
379
src/views/ops/pages/system-settings/license-center/index.vue
Normal file
379
src/views/ops/pages/system-settings/license-center/index.vue
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<Breadcrumb :items="['menu.ops.systemSettings', 'menu.ops.systemSettings.licenseCenter']" />
|
||||||
|
|
||||||
|
<a-card class="license-page-card" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<div class="card-head-title">
|
||||||
|
<div class="page-title">许可证信息</div>
|
||||||
|
<div class="page-subtitle">{{ license?.company_name || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="outline" :loading="loading" :disabled="loading" @click="loadLicense">
|
||||||
|
<template #icon><icon-refresh /></template>
|
||||||
|
刷新
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-spin :loading="loading" class="page-spin">
|
||||||
|
<a-empty v-if="!loading && loadError && !license" description="加载失败,请稍后重试" />
|
||||||
|
|
||||||
|
<template v-else-if="license">
|
||||||
|
<a-card class="section-card" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<span class="section-title">基本信息</span>
|
||||||
|
</template>
|
||||||
|
<div class="kv-list">
|
||||||
|
<div v-for="row in basicRows" :key="row.key" class="basic-row">
|
||||||
|
<span class="label">{{ row.label }}</span>
|
||||||
|
<span class="value" :class="{ 'value-code': row.key === 'machine_code' }">{{ row.display }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card class="section-card section-card--quota" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<span class="section-title">资源限制</span>
|
||||||
|
</template>
|
||||||
|
<p class="quota-hint">配额项为 0 时表示该维度不按许可证限制数量(由业务逻辑决定)。</p>
|
||||||
|
<div class="kv-list">
|
||||||
|
<div v-for="row in quotaRows" :key="row.key" class="quota-row">
|
||||||
|
<span class="label">{{ row.label }}</span>
|
||||||
|
<span class="value">{{ row.display }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</template>
|
||||||
|
</a-spin>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { IconRefresh } from '@arco-design/web-vue/es/icon'
|
||||||
|
import { fetchLicenseInfo, type LicenceConfig } from '@/api/ops/dcControl'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const loadError = ref(false)
|
||||||
|
const license = ref<LicenceConfig | null>(null)
|
||||||
|
|
||||||
|
const dash = (v: string | undefined | null) => (v != null && String(v).length > 0 ? String(v) : '—')
|
||||||
|
|
||||||
|
const formatQuota = (n: number | undefined) => {
|
||||||
|
if (n == null || Number.isNaN(Number(n))) return '—'
|
||||||
|
const v = Number(n)
|
||||||
|
return v > 0 ? String(v) : '无限制'
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickStr(o: Record<string, unknown>, snake: string, camel: string): string | undefined {
|
||||||
|
const v = o[snake] ?? o[camel]
|
||||||
|
return typeof v === 'string' ? v : v != null ? String(v) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickNum(o: Record<string, unknown>, snake: string, camel: string): number | undefined {
|
||||||
|
const v = o[snake] ?? o[camel]
|
||||||
|
if (v == null || v === '') return undefined
|
||||||
|
const n = Number(v)
|
||||||
|
return Number.isNaN(n) ? undefined : n
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将接口对象统一为 LicenceConfig(兼容 snake_case / camelCase) */
|
||||||
|
function normalizeLicensePayload(raw: Record<string, unknown>): LicenceConfig {
|
||||||
|
return {
|
||||||
|
title: pickStr(raw, 'title', 'title'),
|
||||||
|
version: pickStr(raw, 'version', 'version'),
|
||||||
|
company_name: pickStr(raw, 'company_name', 'companyName'),
|
||||||
|
create_time: pickStr(raw, 'create_time', 'createTime'),
|
||||||
|
expire_time: pickStr(raw, 'expire_time', 'expireTime'),
|
||||||
|
machine_code: pickStr(raw, 'machine_code', 'machineCode'),
|
||||||
|
max_database: pickNum(raw, 'max_database', 'maxDatabase'),
|
||||||
|
max_middleware: pickNum(raw, 'max_middleware', 'maxMiddleware'),
|
||||||
|
max_pc: pickNum(raw, 'max_pc', 'maxPc'),
|
||||||
|
max_server: pickNum(raw, 'max_server', 'maxServer'),
|
||||||
|
max_client: pickNum(raw, 'max_client', 'maxClient'),
|
||||||
|
max_user: pickNum(raw, 'max_user', 'maxUser'),
|
||||||
|
max_role: pickNum(raw, 'max_role', 'maxRole'),
|
||||||
|
max_permission: pickNum(raw, 'max_permission', 'maxPermission'),
|
||||||
|
max_menu: pickNum(raw, 'max_menu', 'maxMenu'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLicensePayloadRaw(v: unknown): v is Record<string, unknown> {
|
||||||
|
if (v == null || typeof v !== 'object' || Array.isArray(v)) return false
|
||||||
|
const o = v as Record<string, unknown>
|
||||||
|
const strHit = (s: string, c: string) => {
|
||||||
|
const x = o[s] ?? o[c]
|
||||||
|
return typeof x === 'string' && x.length > 0
|
||||||
|
}
|
||||||
|
const numHit = (s: string, c: string) => {
|
||||||
|
const x = o[s] ?? o[c]
|
||||||
|
return typeof x === 'number' && !Number.isNaN(x)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
strHit('company_name', 'companyName') ||
|
||||||
|
strHit('machine_code', 'machineCode') ||
|
||||||
|
strHit('title', 'title') ||
|
||||||
|
strHit('version', 'version') ||
|
||||||
|
numHit('max_database', 'maxDatabase') ||
|
||||||
|
numHit('max_middleware', 'maxMiddleware') ||
|
||||||
|
numHit('max_pc', 'maxPc') ||
|
||||||
|
numHit('max_server', 'maxServer') ||
|
||||||
|
numHit('max_client', 'maxClient') ||
|
||||||
|
numHit('max_user', 'maxUser') ||
|
||||||
|
numHit('max_role', 'maxRole') ||
|
||||||
|
numHit('max_permission', 'maxPermission') ||
|
||||||
|
numHit('max_menu', 'maxMenu')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLicenseResponse(res: unknown): {
|
||||||
|
code?: number
|
||||||
|
success?: boolean
|
||||||
|
data: unknown
|
||||||
|
message?: string
|
||||||
|
} {
|
||||||
|
if (res == null || typeof res !== 'object') {
|
||||||
|
return { data: undefined, message: undefined }
|
||||||
|
}
|
||||||
|
const r = res as Record<string, unknown>
|
||||||
|
const code = typeof r.code === 'number' ? r.code : undefined
|
||||||
|
const success = typeof r.success === 'boolean' ? r.success : undefined
|
||||||
|
const message =
|
||||||
|
typeof r.message === 'string' ? r.message : typeof r.msg === 'string' ? r.msg : undefined
|
||||||
|
|
||||||
|
let data: unknown = r.data ?? r.details ?? r.result
|
||||||
|
if (data == null && isLicensePayloadRaw(r)) {
|
||||||
|
data = r
|
||||||
|
}
|
||||||
|
return { code, success, data, message }
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseIndicatesSuccess(code: number | undefined, success: boolean | undefined): boolean {
|
||||||
|
if (success === false) return false
|
||||||
|
if (success === true) return true
|
||||||
|
if (code === undefined) return true
|
||||||
|
return code === 200 || code === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const basicRows = computed(() => {
|
||||||
|
const L = license.value
|
||||||
|
if (!L) return []
|
||||||
|
return [
|
||||||
|
{ key: 'company_name', label: '公司名称', display: dash(L.company_name) },
|
||||||
|
{ key: 'title', label: '版本标题', display: dash(L.title) },
|
||||||
|
{ key: 'version', label: '版本号', display: dash(L.version) },
|
||||||
|
{ key: 'machine_code', label: '机器码', display: dash(L.machine_code) },
|
||||||
|
{ key: 'create_time', label: '创建时间', display: dash(L.create_time) },
|
||||||
|
{ key: 'expire_time', label: '过期时间', display: dash(L.expire_time) },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const quotaRows = computed(() => {
|
||||||
|
const L = license.value
|
||||||
|
if (!L) return []
|
||||||
|
return [
|
||||||
|
{ key: 'max_database', label: '数据库', display: formatQuota(L.max_database) },
|
||||||
|
{ key: 'max_middleware', label: '中间件', display: formatQuota(L.max_middleware) },
|
||||||
|
{ key: 'max_pc', label: 'PC', display: formatQuota(L.max_pc) },
|
||||||
|
{ key: 'max_server', label: '服务器', display: formatQuota(L.max_server) },
|
||||||
|
{ key: 'max_client', label: '客户端', display: formatQuota(L.max_client) },
|
||||||
|
{ key: 'max_user', label: '用户', display: formatQuota(L.max_user) },
|
||||||
|
{ key: 'max_role', label: '角色', display: formatQuota(L.max_role) },
|
||||||
|
{ key: 'max_permission', label: '权限', display: formatQuota(L.max_permission) },
|
||||||
|
{ key: 'max_menu', label: '菜单', display: formatQuota(L.max_menu) },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadLicense() {
|
||||||
|
loading.value = true
|
||||||
|
loadError.value = false
|
||||||
|
try {
|
||||||
|
const res = await fetchLicenseInfo()
|
||||||
|
const { code, success, data, message } = parseLicenseResponse(res)
|
||||||
|
const ok = responseIndicatesSuccess(code, success)
|
||||||
|
if (data != null && typeof data === 'object' && !Array.isArray(data) && isLicensePayloadRaw(data) && ok) {
|
||||||
|
license.value = normalizeLicensePayload(data as Record<string, unknown>)
|
||||||
|
loadError.value = false
|
||||||
|
} else {
|
||||||
|
loadError.value = true
|
||||||
|
Message.error(message || '获取许可证失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
loadError.value = true
|
||||||
|
console.error('[license-center] fetchLicenseInfo', e)
|
||||||
|
Message.error('获取许可证失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadLicense()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'LicenseCenter',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
/* 与 system-settings / 报表等运维页一致 */
|
||||||
|
.container {
|
||||||
|
padding: 0 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.license-page-card {
|
||||||
|
:deep(.arco-card-body) {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-spin {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-card {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: var(--color-fill-1);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-card-header) {
|
||||||
|
border-bottom: 1px solid var(--color-border-2);
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-card-body) {
|
||||||
|
padding: 0;
|
||||||
|
background: var(--color-bg-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-card--quota {
|
||||||
|
:deep(.arco-card-body) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quota-hint {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 20px 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
line-height: 1.5;
|
||||||
|
background: var(--color-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kv-list {
|
||||||
|
padding: 0;
|
||||||
|
background: var(--color-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic-row,
|
||||||
|
.quota-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-bottom: 1px solid var(--color-border-1);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quota-row {
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-card-header) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quota-row .label {
|
||||||
|
width: auto;
|
||||||
|
min-width: 72px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user