feat(密钥): 用户 API 密钥服务端化——V115 按账号绑定存储 + 后台密钥管理页 + 桌面端全站拦截与配置引导
- 新增 usersecret 模块:外观专利/货源查询密钥从本地 localStorage 迁移至 biz_user_api_secret(AES 加密、按 uid 绑定) - 后台「密钥管理」页:脱敏展示、立即检测、清空;每日 04:30 分布式锁定时巡检 - 桌面端:密钥设置面板走服务端、未配齐引导 /setup-secrets、代理余量展示 - 删除专利汇令牌全链路与密钥保留时长选择器
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { http } from './http'
|
||||
import { unwrap } from './envelope'
|
||||
|
||||
export interface AdminUserSecretItem {
|
||||
id: number
|
||||
userId: number
|
||||
username: string
|
||||
moduleKey: string
|
||||
moduleLabel: string
|
||||
masked: string
|
||||
exists: boolean
|
||||
checkStatus: string
|
||||
checkCode: string
|
||||
checkMessage: string
|
||||
checkLatencyMs: number | null
|
||||
checkedAt: string | null
|
||||
source: string
|
||||
updatedAt: string | null
|
||||
}
|
||||
|
||||
export interface AdminUserSecretPage {
|
||||
items: AdminUserSecretItem[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export interface UserSecretQuery {
|
||||
keyword?: string
|
||||
moduleKey?: string
|
||||
checkStatus?: string
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
/** 分页查询用户密钥(脱敏):GET /api/admin/user-secrets */
|
||||
export async function fetchUserSecretList(params: UserSecretQuery): Promise<AdminUserSecretPage> {
|
||||
const { data } = await http.get('/api/admin/user-secrets', { params })
|
||||
return unwrap<AdminUserSecretPage>(data)
|
||||
}
|
||||
|
||||
/** 立即检测指定密钥:POST /api/admin/user-secrets/{id}/check */
|
||||
export async function checkUserSecret(id: number) {
|
||||
const { data } = await http.post(`/api/admin/user-secrets/${id}/check`)
|
||||
return unwrap<{
|
||||
moduleKey: string
|
||||
checkStatus: string
|
||||
checkCode: string
|
||||
checkMessage: string
|
||||
checkLatencyMs: number | null
|
||||
checkedAt: string | null
|
||||
viaProxy: boolean
|
||||
}>(data)
|
||||
}
|
||||
|
||||
/** 清空指定用户密钥:DELETE /api/admin/user-secrets/{id} */
|
||||
export async function deleteUserSecret(id: number): Promise<void> {
|
||||
const { data } = await http.delete(`/api/admin/user-secrets/${id}`)
|
||||
unwrap<unknown>(data)
|
||||
}
|
||||
@@ -25,6 +25,10 @@ export const OPERATION_GUIDES: Record<string, OperationGuideData> = {
|
||||
steps: ['新增或调整菜单', '设置层级', '拖动排序'],
|
||||
},
|
||||
admin_group_manage: OPERATION_GUIDE_FALLBACK,
|
||||
admin_user_secrets: {
|
||||
text: '用户密钥按账号绑定,列表只展示脱敏值。可对单条立即检测连通性;清空后该用户需要重新配置密钥。',
|
||||
steps: ['筛选用户或状态', '立即检测连通性', '必要时清空'],
|
||||
},
|
||||
admin_dedupe_total_data: {
|
||||
text: '支持按关键词、用户、分组和国家筛选;导入前先确认所选分组,导出会按日期范围生成文件。',
|
||||
steps: ['选择分组', '筛选或导入', '核对并导出'],
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import OldPagination from '@/components/OldPagination.vue'
|
||||
import {
|
||||
checkUserSecret,
|
||||
deleteUserSecret,
|
||||
fetchUserSecretList,
|
||||
type AdminUserSecretItem,
|
||||
} from '@/api/user-secrets'
|
||||
|
||||
const loading = ref(false)
|
||||
const rows = ref<AdminUserSecretItem[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const keyword = ref('')
|
||||
const moduleFilter = ref('')
|
||||
const statusFilter = ref('')
|
||||
/** 正在检测的行 id,用于按钮 loading 态。 */
|
||||
const checkingId = ref<number | null>(null)
|
||||
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
|
||||
|
||||
const MODULE_OPTIONS = [
|
||||
{ value: '', label: '全部类型' },
|
||||
{ value: 'appearance-patent', label: '外观专利密钥' },
|
||||
{ value: 'similar-asin', label: '货源查询密钥' },
|
||||
]
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部状态' },
|
||||
{ value: 'passed', label: '检测通过' },
|
||||
{ value: 'failed', label: '检测失败' },
|
||||
{ value: 'error', label: '无法判定' },
|
||||
{ value: 'unknown', label: '未检测' },
|
||||
]
|
||||
|
||||
/** 状态药丸:与店铺密钥页 whitelistStatusMeta 同一视觉语言。 */
|
||||
function statusMeta(status: string) {
|
||||
switch (status) {
|
||||
case 'passed':
|
||||
return { label: '检测通过', tone: 'is-allowed' }
|
||||
case 'failed':
|
||||
return { label: '检测失败', tone: 'is-blocked' }
|
||||
case 'error':
|
||||
return { label: '无法判定', tone: 'is-warn' }
|
||||
default:
|
||||
return { label: '未检测', tone: 'is-unknown' }
|
||||
}
|
||||
}
|
||||
|
||||
/** 状态药丸悬停详情:检测时间 + 检测消息 + 耗时。 */
|
||||
function statusTooltip(row: AdminUserSecretItem) {
|
||||
const parts: string[] = []
|
||||
if (row.checkMessage) parts.push(row.checkMessage)
|
||||
if (row.checkedAt) parts.push(`检测时间:${formatDateTime(row.checkedAt)}`)
|
||||
if (row.checkLatencyMs != null) parts.push(`耗时:${row.checkLatencyMs}ms`)
|
||||
if (row.source) parts.push(`来源:${row.source}`)
|
||||
return parts.join(';') || '暂无检测记录'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await fetchUserSecretList({
|
||||
keyword: keyword.value.trim() || undefined,
|
||||
moduleKey: moduleFilter.value || undefined,
|
||||
checkStatus: statusFilter.value || undefined,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
})
|
||||
rows.value = result?.items || []
|
||||
total.value = Number(result?.total || 0)
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function search() {
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
keyword.value = ''
|
||||
moduleFilter.value = ''
|
||||
statusFilter.value = ''
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
|
||||
/** 立即检测:真实请求一次 LLM 接口并把结果落库。 */
|
||||
async function check(row: AdminUserSecretItem) {
|
||||
checkingId.value = row.id
|
||||
try {
|
||||
const result = await checkUserSecret(row.id)
|
||||
if (result?.checkStatus === 'passed') {
|
||||
ElMessage.success('检测通过')
|
||||
} else if (result?.checkStatus === 'failed') {
|
||||
ElMessage.warning(result.checkMessage || '检测未通过')
|
||||
} else {
|
||||
ElMessage.warning(result?.checkMessage || '本次无法判定')
|
||||
}
|
||||
load()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '检测失败')
|
||||
} finally {
|
||||
checkingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(row: AdminUserSecretItem) {
|
||||
const who = row.username || `UID ${row.userId}`
|
||||
if (!window.confirm(`确定清空「${who}」的${row.moduleLabel || row.moduleKey}吗?清空后该用户需要重新配置。`)) return
|
||||
try {
|
||||
await deleteUserSecret(row.id)
|
||||
ElMessage.success('已清空')
|
||||
load()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '清空失败')
|
||||
}
|
||||
}
|
||||
|
||||
function changePage(next: number) {
|
||||
if (next < 1 || next > totalPages.value) return
|
||||
page.value = next
|
||||
load()
|
||||
}
|
||||
|
||||
function changeSize(size: number) {
|
||||
pageSize.value = size
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="user-secrets-view">
|
||||
<section class="panel-box">
|
||||
<div class="secrets-head">
|
||||
<h3>用户密钥列表</h3>
|
||||
</div>
|
||||
|
||||
<div class="form-row secrets-filter-row">
|
||||
<div class="form-group" style="min-width: 220px">
|
||||
<label>用户名 / UID</label>
|
||||
<input v-model="keyword" type="text" placeholder="模糊搜索用户名或输入用户ID" @keyup.enter="search" />
|
||||
</div>
|
||||
<div class="form-group" style="min-width: 170px">
|
||||
<label>密钥类型</label>
|
||||
<select v-model="moduleFilter">
|
||||
<option v-for="option in MODULE_OPTIONS" :key="option.value" :value="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="min-width: 150px">
|
||||
<label>连通性状态</label>
|
||||
<select v-model="statusFilter">
|
||||
<option v-for="option in STATUS_OPTIONS" :key="option.value" :value="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<div class="filter-actions">
|
||||
<button class="btn" type="button" @click="search">查询</button>
|
||||
<button class="btn btn-ghost" type="button" @click="reset">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="secrets-table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 58px">序号</th>
|
||||
<th style="width: 200px">用户</th>
|
||||
<th style="width: 150px">密钥类型</th>
|
||||
<th style="width: 220px">密钥(脱敏)</th>
|
||||
<th style="width: 150px">连通性</th>
|
||||
<th style="width: 130px">来源</th>
|
||||
<th style="width: 170px">更新时间</th>
|
||||
<th style="width: 170px">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="rows.length">
|
||||
<tr v-for="(row, index) in rows" :key="row.id">
|
||||
<td>{{ (page - 1) * pageSize + index + 1 }}</td>
|
||||
<td>
|
||||
<div class="user-cell">
|
||||
<span class="user-name">{{ row.username || '—' }}</span>
|
||||
<span class="user-uid">UID {{ row.userId }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ row.moduleLabel || row.moduleKey }}</td>
|
||||
<td>
|
||||
<span v-if="row.exists" class="mono-mask">{{ row.masked || '已配置' }}</span>
|
||||
<span v-else class="empty-value">未配置</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="wh-pill"
|
||||
:class="statusMeta(row.checkStatus).tone"
|
||||
:title="statusTooltip(row)"
|
||||
>
|
||||
{{ statusMeta(row.checkStatus).label }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ row.source || '—' }}</td>
|
||||
<td>{{ formatDateTime(row.updatedAt) }}</td>
|
||||
<td class="ops-cell">
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
type="button"
|
||||
:disabled="checkingId === row.id || !row.exists"
|
||||
@click="check(row)"
|
||||
>
|
||||
{{ checkingId === row.id ? '检测中…' : '立即检测' }}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" type="button" @click="remove(row)">清空</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-else-if="loading">
|
||||
<td colspan="8" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="8" class="empty-tip">{{ keyword || moduleFilter || statusFilter ? '暂无匹配记录' : '暂无用户密钥记录' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<OldPagination :total="total" :page="page" :page-size="pageSize" @change="changePage" @size-change="changeSize" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 像素复刻旧版 admin.html 面板风格(与店铺密钥页同一视觉语言)。 */
|
||||
.user-secrets-view {
|
||||
font-family: inherit;
|
||||
color: #24384d;
|
||||
}
|
||||
.panel-box {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 20px 22px 24px;
|
||||
border: 1px solid #d8e3ee;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(145deg, #ffffff, #f9fbfd);
|
||||
box-shadow: 0 1px 2px rgba(39, 67, 94, 0.04), 0 14px 30px -24px rgba(39, 67, 94, 0.28);
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 650;
|
||||
color: #24384d;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.secrets-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.secrets-filter-row {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 14px 18px;
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.form-group label {
|
||||
color: #5b6f83;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
min-width: 0;
|
||||
min-height: 42px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #cbd9e6;
|
||||
border-radius: 9px;
|
||||
background: #f8fbfd;
|
||||
color: #24384d;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color-scheme: light;
|
||||
outline: none;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||||
}
|
||||
.form-group input:hover,
|
||||
.form-group select:hover {
|
||||
border-color: #9fb7cd;
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
background: #ffffff;
|
||||
border-color: #5f85ad;
|
||||
box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16);
|
||||
}
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-height: 42px;
|
||||
padding: 9px 18px;
|
||||
border: 1px solid #4f78a5;
|
||||
border-radius: 9px;
|
||||
background: linear-gradient(135deg, #5f85ad, #4f78a5);
|
||||
color: #ffffff;
|
||||
font-family: inherit;
|
||||
font-size: 13.5px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 18px -14px rgba(79, 120, 165, 0.72);
|
||||
transition: background 160ms ease, box-shadow 160ms ease, transform 120ms ease;
|
||||
}
|
||||
.btn:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #7094ba, #5d83ac);
|
||||
box-shadow: 0 11px 22px -14px rgba(79, 120, 165, 0.8);
|
||||
}
|
||||
.btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.btn-danger {
|
||||
background: linear-gradient(135deg, #c06d77, #b35f6a);
|
||||
border-color: #b35f6a;
|
||||
}
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #cb7c84, #b96570);
|
||||
}
|
||||
.btn-ghost {
|
||||
background: #ffffff;
|
||||
border-color: #c7d7e5;
|
||||
color: #4f78a5;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-ghost:hover:not(:disabled) {
|
||||
background: #edf5fb;
|
||||
border-color: #95b1cb;
|
||||
color: #2f5d8b;
|
||||
}
|
||||
.btn-sm {
|
||||
min-height: 36px;
|
||||
padding: 7px 12px;
|
||||
}
|
||||
.secrets-table-scroll {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
border: 1px solid #dbe5ee;
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
}
|
||||
.secrets-table-scroll table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
min-width: 1200px;
|
||||
}
|
||||
.secrets-table-scroll th,
|
||||
.secrets-table-scroll td {
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.5;
|
||||
border-bottom: 1px solid #e0e8ef;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.secrets-table-scroll th {
|
||||
background: #edf4fa;
|
||||
color: #4e6479;
|
||||
border-bottom-color: #d5e1eb;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
.secrets-table-scroll tbody tr:hover td {
|
||||
background: #f1f7fb;
|
||||
}
|
||||
.secrets-table-scroll tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.user-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.user-name {
|
||||
color: #24384d;
|
||||
font-weight: 600;
|
||||
}
|
||||
.user-uid {
|
||||
color: #8293a5;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
.mono-mask {
|
||||
font-family: Consolas, "Cascadia Mono", monospace;
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.empty-value {
|
||||
color: #a7b4c1;
|
||||
}
|
||||
.ops-cell {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.empty-tip {
|
||||
color: #8293a5;
|
||||
text-align: center;
|
||||
}
|
||||
.wh-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
cursor: help;
|
||||
}
|
||||
.wh-pill.is-allowed {
|
||||
background: #e8f6ee;
|
||||
border-color: #b9e0c9;
|
||||
color: #2f7d52;
|
||||
}
|
||||
.wh-pill.is-blocked {
|
||||
background: #fdecef;
|
||||
border-color: #f2c4cd;
|
||||
color: #b04a5a;
|
||||
}
|
||||
.wh-pill.is-warn {
|
||||
background: #fdf6e3;
|
||||
border-color: #f0dfae;
|
||||
color: #8f6d1e;
|
||||
}
|
||||
.wh-pill.is-unknown {
|
||||
background: #f0f3f6;
|
||||
border-color: #d6dee6;
|
||||
color: #6b7d8f;
|
||||
}
|
||||
</style>
|
||||
@@ -17,6 +17,7 @@ export const adminPages: AdminPageDef[] = [
|
||||
{ path: 'account/users', menuKey: 'admin_users', title: '用户管理', load: () => import('@/pages/account/UsersPage.vue') },
|
||||
{ path: 'account/menus', menuKey: 'admin_columns', title: '菜单管理', load: () => import('@/pages/account/MenusPage.vue') },
|
||||
{ path: 'account/groups', menuKey: 'admin_group_manage', title: '数据权限分组', load: () => import('@/pages/account/GroupsPage.vue') },
|
||||
{ path: 'account/user-secrets', menuKey: 'admin_user_secrets', title: '密钥管理', load: () => import('@/pages/account/UserSecretsPage.vue') },
|
||||
{ path: 'shop-center/duplicate-check', menuKey: 'admin_shop_data_duplicate_check', title: '店铺撞款监控', load: () => import('@/pages/tasks/DuplicateCheckPage.vue') },
|
||||
{ path: 'shop-center/keys', menuKey: 'admin_shop_keys', title: '店铺密钥管理', load: () => import('@/pages/shop/ShopKeysPage.vue') },
|
||||
{ path: 'shop-center/shops', menuKey: 'admin_shop_manage', title: '店铺管理', load: () => import('@/pages/shop/ShopManagePage.vue') },
|
||||
|
||||
@@ -13,7 +13,7 @@ import { adminPages } from '../src/router/routes.ts'
|
||||
|
||||
test('test_task_010_lazy_page_boundary_normal_primary_path', () => {
|
||||
// 正常主路径:所有注册页面都是懒加载器,可被路由异步边界包裹。
|
||||
assert.equal(adminPages.length, 16)
|
||||
assert.equal(adminPages.length, 17)
|
||||
for (const page of adminPages) {
|
||||
assert.equal(isLazyLoader(page.load), true, `页面 ${page.path} 必须是懒加载函数`)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ test('test_task_012_route_error_page_normal_repeated_operation_is_idempotent', (
|
||||
test('test_task_012_route_error_page_boundary_empty_input', () => {
|
||||
// 边界空值:错误页文件存在,且不进入业务路由注册表。
|
||||
assert.equal(existsSync(join(process.cwd(), NOT_FOUND)), true)
|
||||
assert.equal(adminPages.length, 16, '错误页不应计入业务路由')
|
||||
assert.equal(adminPages.length, 17, '错误页不应计入业务路由')
|
||||
})
|
||||
|
||||
test('test_task_012_route_error_page_boundary_single_item', () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
|
||||
test('test_task_008_domain_route_registry_normal_primary_path', () => {
|
||||
// 正常主路径:注册表首批 account 域页面登记为可消费路由记录。
|
||||
assert.equal(adminPages.length, 16)
|
||||
assert.equal(adminPages.length, 17)
|
||||
assert.equal(adminRouteRecords.length, adminPages.length)
|
||||
const first = adminRouteRecords[0]
|
||||
assert.equal(first.path, 'account/users')
|
||||
|
||||
Reference in New Issue
Block a user