fix
This commit is contained in:
54
pnpm-lock.yaml
generated
54
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '报表历史',
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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',
|
||||
|
||||
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