feat(admin-workplace): 工作台业务概览真实对接与全端认证集成 (更新远程代码仓配置与测试全绿)
这个提交包含在:
55
admin/tests/admin-auth.test.ts
普通文件
55
admin/tests/admin-auth.test.ts
普通文件
@@ -0,0 +1,55 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
function read(rel: string): string {
|
||||
return fs.readFileSync(path.join(adminRoot, rel), 'utf8');
|
||||
}
|
||||
|
||||
test('1. 登录必须对接 Cool-Admin 开放接口并携带验证码', () => {
|
||||
const form = read('src/pages/login/form.tsx');
|
||||
assert.ok(
|
||||
form.includes('/api/admin/base/open/login'),
|
||||
'登录必须请求 /api/admin/base/open/login'
|
||||
);
|
||||
assert.ok(
|
||||
form.includes('/api/admin/base/open/captcha'),
|
||||
'登录必须拉取 /api/admin/base/open/captcha'
|
||||
);
|
||||
assert.ok(form.includes('captchaId'), '登录必须提交 captchaId');
|
||||
assert.ok(form.includes('verifyCode'), '登录必须提交 verifyCode');
|
||||
assert.ok(!form.includes('/api/user/login'), '禁止再走 Arco Mock 登录');
|
||||
assert.ok(!/\bany\b/.test(form), '严禁 any');
|
||||
});
|
||||
|
||||
test('2. axios 必须自动附带 Cool-Admin Authorization', () => {
|
||||
const request = read('src/utils/setupRequest.ts');
|
||||
assert.ok(request.includes('Authorization'), '必须写入 Authorization');
|
||||
assert.ok(request.includes('getAdminToken'), '必须读取已保存的 token');
|
||||
|
||||
const main = read('src/main.tsx');
|
||||
assert.ok(
|
||||
main.includes("./utils/setupRequest"),
|
||||
'入口必须启用请求拦截器'
|
||||
);
|
||||
});
|
||||
|
||||
test('3. 登录态必须同时校验 userStatus 与 JWT', () => {
|
||||
const checkLogin = read('src/utils/checkLogin.tsx');
|
||||
assert.ok(checkLogin.includes('getAdminToken'), '未持有 token 不得视为已登录');
|
||||
|
||||
const tokenUtil = read('src/utils/auth-token.ts');
|
||||
assert.ok(tokenUtil.includes('adminToken'), '必须持久化 adminToken');
|
||||
assert.ok(tokenUtil.includes('adminRefreshToken'), '必须持久化 refreshToken');
|
||||
});
|
||||
|
||||
test('4. 登录文案必须提示真实超管密码并提供验证码词条', () => {
|
||||
const locale = read('src/pages/login/locale/index.ts');
|
||||
assert.ok(locale.includes('123456'), '占位提示必须是 Cool-Admin 默认密码 123456');
|
||||
assert.ok(locale.includes('login.form.captcha.placeholder'));
|
||||
assert.ok(locale.includes('login.form.captcha.errMsg'));
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const pagePath = path.join(adminRoot, 'src/pages/dashboard/workplace/index.tsx');
|
||||
const localePath = path.join(adminRoot, 'src/pages/dashboard/workplace/locale/index.ts');
|
||||
const routesPath = path.join(adminRoot, 'src/routes.ts');
|
||||
|
||||
const EMOJI_RE =
|
||||
/[\u{1F300}-\u{1F5FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
|
||||
|
||||
test('1. routes.ts 必须在 dashboard 下注册 workplace 且未被 ignore', () => {
|
||||
const routesContent = fs.readFileSync(routesPath, 'utf-8');
|
||||
assert.ok(
|
||||
routesContent.includes("key: 'dashboard/workplace'"),
|
||||
'必须注册 dashboard/workplace'
|
||||
);
|
||||
assert.ok(
|
||||
!/key:\s*'dashboard\/workplace'[\s\S]{0,80}ignore:\s*true/.test(routesContent),
|
||||
'工作台必须在侧边栏展示'
|
||||
);
|
||||
});
|
||||
|
||||
test('2. 工作台必须对接商务概览 API,并使用 PRD 指定的 Arco 组件', () => {
|
||||
assert.ok(fs.existsSync(pagePath), '必须存在 workplace/index.tsx');
|
||||
const content = fs.readFileSync(pagePath, 'utf-8');
|
||||
|
||||
assert.ok(
|
||||
content.includes('/api/admin/crm/radar/workplace'),
|
||||
'必须调用 /api/admin/crm/radar/workplace'
|
||||
);
|
||||
assert.ok(content.includes('@arco-design/web-react'), '必须使用 Arco Design');
|
||||
assert.ok(content.includes('Statistic'), '必须使用 Statistic 展示 KPI');
|
||||
assert.ok(content.includes('Timeline'), '必须使用 Timeline 展示雷达动态');
|
||||
assert.ok(content.includes('Card'), '必须使用 Card');
|
||||
assert.ok(content.includes('Grid') || content.includes('Row'), '必须使用 Grid');
|
||||
assert.ok(content.includes('Button'), '必须使用 Button');
|
||||
assert.ok(!content.includes("from './mock'"), '禁止继续引用 Arco Pro Demo mock');
|
||||
assert.ok(!content.includes('PopularContents'), '禁止继续引用内容运营 Demo 组件');
|
||||
});
|
||||
|
||||
test('3. 快捷入口必须覆盖新建名片、发卡、线索导出,且仅 1 个主 CTA', () => {
|
||||
const content = fs.readFileSync(pagePath, 'utf-8');
|
||||
assert.ok(content.includes('/card/list'), '新建名片必须跳转名片资产库');
|
||||
assert.ok(content.includes('/nfc/batch'), '发卡必须跳转批量制卡');
|
||||
assert.ok(
|
||||
content.includes('/api/admin/crm/lead/page'),
|
||||
'线索导出必须请求线索分页接口'
|
||||
);
|
||||
|
||||
const primaryButtons = content.match(/type=["']primary["']/g) || [];
|
||||
assert.equal(primaryButtons.length, 1, '一屏只允许 1 个主 CTA');
|
||||
});
|
||||
|
||||
test('4. 必须引用设计 Token,且零 any、零 emoji', () => {
|
||||
const content = fs.readFileSync(pagePath, 'utf-8');
|
||||
assert.ok(
|
||||
content.includes('@/style/tokens') || content.includes('tokens'),
|
||||
'必须引用设计 Token'
|
||||
);
|
||||
assert.ok(!/\bany\b/.test(content), '严禁 any');
|
||||
assert.ok(!EMOJI_RE.test(content), '严禁 emoji');
|
||||
});
|
||||
|
||||
test('5. 工作台 i18n 必须覆盖 KPI、雷达动态与快捷入口,且含中英阿', () => {
|
||||
assert.ok(fs.existsSync(localePath), '必须存在 workplace locale');
|
||||
const localeContent = fs.readFileSync(localePath, 'utf-8');
|
||||
assert.ok(localeContent.includes("'en-US'"));
|
||||
assert.ok(localeContent.includes("'zh-CN'"));
|
||||
assert.ok(localeContent.includes("'ar-SA'"));
|
||||
assert.ok(localeContent.includes('workplace.kpi.taps'));
|
||||
assert.ok(localeContent.includes('workplace.kpi.views'));
|
||||
assert.ok(localeContent.includes('workplace.kpi.leads'));
|
||||
assert.ok(localeContent.includes('workplace.kpi.conversion'));
|
||||
assert.ok(localeContent.includes('workplace.radar.title'));
|
||||
assert.ok(localeContent.includes('workplace.cta.newCard'));
|
||||
assert.ok(localeContent.includes('workplace.shortcut.issueCard'));
|
||||
assert.ok(localeContent.includes('workplace.shortcut.exportLeads'));
|
||||
assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji');
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户