- [P0安全底座] 多租户数据闭环:公开留资与雷达足迹自动逆向追溯绑定名片 tenantId 与归属销售 userId - [P0性能稳定] 消除内存全量加载:公海池分批带条件扫描回收,雷达国家分布改用 SQL 原生 GROUP BY 聚合查询彻底消除 OOM 隐患 - [P0商业变现] 商业化配额门禁切面:在 Cool-Admin modifyBefore 生命周期织入 assertQuotaAvailable 校验,阻断超额名片创建 - [P0环境解耦] 客户端解耦本地硬编码:request.uts 支持 api_base_url 本地覆盖,生产数据库名修正为 crmmpdata - [P1高并发削峰] 雷达足迹批量缓冲引擎:引入 CrmRadarBufferService 环形缓冲队列与阈值/定时批量 Bulk Insert 落盘 - [P2中枢租户化] AI 智能中枢多租户 BYOK 与 Token 成本风控:支持企业自带 Key 隔离、Token 消耗账本、用量配额门禁与名片人设个性化联动 - [跨端规范] 对齐 Uni-app x 移动微官网原生首屏与底部原生 tabBar 规范 - [测试驱动] 全工程 116 个测试套件,1,175 个单元与集成测试用例全部 100% 绿色通过,管理后台 Vite 打包无错误
157 行
5.4 KiB
TypeScript
157 行
5.4 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const MP_ROOT = path.resolve('m:/2026项目/CRMmp/mpuniapp');
|
|
|
|
// 18 个全量页面路由列表
|
|
const ALL_PAGES = [
|
|
'pages/portal/index.uvue',
|
|
'pages/portal/exchange.uvue',
|
|
'pages/portal/case-detail.uvue',
|
|
'pages/portal/article-detail.uvue',
|
|
'pages/portal/ai-chat.uvue',
|
|
'pages/common/login.uvue',
|
|
'pages/owner/hall/index.uvue',
|
|
'pages/owner/card/index.uvue',
|
|
'pages/owner/card/edit.uvue',
|
|
'pages/owner/card/qrcode.uvue',
|
|
'pages/owner/radar.uvue',
|
|
'pages/owner/leads.uvue',
|
|
'pages/owner/leads-detail.uvue',
|
|
'pages/owner/profile/index.uvue',
|
|
'pages/owner/nfc/scan.uvue',
|
|
'pages/owner/nfc/mode.uvue',
|
|
'pages/owner/nfc/security.uvue',
|
|
'pages/owner/team/overview.uvue',
|
|
];
|
|
|
|
// 3 个核心通用组件
|
|
const ALL_COMPONENTS = [
|
|
'components/i18n-selector/i18n-selector.uvue',
|
|
'components/audio-pronounce/audio-pronounce.uvue',
|
|
'components/image-uploader/image-uploader.uvue',
|
|
];
|
|
|
|
// 4 个 TabBar 页面
|
|
const TABBAR_PAGES = [
|
|
'pages/owner/hall/index.uvue',
|
|
'pages/owner/card/index.uvue',
|
|
'pages/owner/leads.uvue',
|
|
'pages/owner/profile/index.uvue',
|
|
];
|
|
|
|
// 包含表单输入框的页面
|
|
const FORM_PAGES = [
|
|
'pages/portal/exchange.uvue',
|
|
'pages/common/login.uvue',
|
|
'pages/owner/card/edit.uvue',
|
|
'pages/owner/leads-detail.uvue',
|
|
'pages/owner/nfc/scan.uvue',
|
|
'pages/owner/nfc/mode.uvue',
|
|
];
|
|
|
|
// 包含底部悬浮主 CTA 栏的页面
|
|
const BOTTOM_CTA_PAGES = [
|
|
'pages/portal/index.uvue',
|
|
'pages/portal/exchange.uvue',
|
|
'pages/portal/case-detail.uvue',
|
|
'pages/portal/article-detail.uvue',
|
|
'pages/common/login.uvue',
|
|
'pages/owner/card/edit.uvue',
|
|
'pages/owner/card/qrcode.uvue',
|
|
'pages/owner/leads-detail.uvue',
|
|
'pages/owner/nfc/scan.uvue',
|
|
'pages/owner/nfc/mode.uvue',
|
|
'pages/owner/nfc/security.uvue',
|
|
'pages/owner/team/overview.uvue',
|
|
];
|
|
|
|
test('1. 全端页面与 3 个组件文件均存在', () => {
|
|
for (const pagePath of ALL_PAGES) {
|
|
const fullPath = path.join(MP_ROOT, pagePath);
|
|
assert.ok(fs.existsSync(fullPath), `页面必须存在: ${pagePath}`);
|
|
}
|
|
for (const compPath of ALL_COMPONENTS) {
|
|
const fullPath = path.join(MP_ROOT, compPath);
|
|
assert.ok(fs.existsSync(fullPath), `组件必须存在: ${compPath}`);
|
|
}
|
|
});
|
|
|
|
test('2. 零 Emoji 违规审查 (严禁使用 Emoji 作为功能图标或排版内容)', () => {
|
|
const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
|
|
const filesToCheck = [...ALL_PAGES, ...ALL_COMPONENTS];
|
|
for (const relPath of filesToCheck) {
|
|
const fullPath = path.join(MP_ROOT, relPath);
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const match = emojiRegex.exec(content);
|
|
assert.ok(!match, `文件 ${relPath} 严禁含有 Emoji: ${match ? match[0] : ''}`);
|
|
}
|
|
});
|
|
|
|
test('3. 底部悬浮主 CTA 栏全面适配移动浏览器视口安全区 (env(safe-area-inset-bottom))', () => {
|
|
for (const relPath of BOTTOM_CTA_PAGES) {
|
|
const fullPath = path.join(MP_ROOT, relPath);
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const hasSafeArea =
|
|
content.includes('env(safe-area-inset-bottom') ||
|
|
content.includes('constant(safe-area-inset-bottom');
|
|
assert.ok(
|
|
hasSafeArea,
|
|
`页面 ${relPath} 的底部悬浮 CTA 栏必须包含 safe-area-inset-bottom 视口安全区适配`
|
|
);
|
|
}
|
|
});
|
|
|
|
test('4. TabBar 页面底部元素避让 var(--window-bottom)', () => {
|
|
for (const relPath of TABBAR_PAGES) {
|
|
const fullPath = path.join(MP_ROOT, relPath);
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const hasWindowBottom = content.includes('var(--window-bottom');
|
|
assert.ok(
|
|
hasWindowBottom,
|
|
`TabBar 页面 ${relPath} 必须包含 var(--window-bottom) 避让原生 TabBar`
|
|
);
|
|
}
|
|
});
|
|
|
|
test('5. 移动浏览器表单输入框防缩放适配 (font-size 必须为 16px)', () => {
|
|
for (const relPath of FORM_PAGES) {
|
|
const fullPath = path.join(MP_ROOT, relPath);
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
// 检查 .field-input 或 .notes-input 样式中的 font-size 是否为 16px
|
|
const has16px =
|
|
(content.includes('.field-input') || content.includes('.notes-input')) &&
|
|
(content.includes('font-size: 16px;') || content.includes('font-size: 16px'));
|
|
assert.ok(
|
|
has16px,
|
|
`表单页面 ${relPath} 中的输入框必须设置 font-size: 16px 以防 iOS Safari 聚焦强制缩放`
|
|
);
|
|
}
|
|
});
|
|
|
|
test('6. 核心可交互按钮满足触控标准 (height >= 44px 或 min-height >= 44px)', () => {
|
|
const ctaFiles = [...BOTTOM_CTA_PAGES, ...TABBAR_PAGES];
|
|
for (const relPath of ctaFiles) {
|
|
const fullPath = path.join(MP_ROOT, relPath);
|
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
const hasMin44 =
|
|
content.includes('height: 48px') ||
|
|
content.includes('min-height: 44px') ||
|
|
content.includes('height: 44px');
|
|
assert.ok(
|
|
hasMin44,
|
|
`页面 ${relPath} 的主操作按钮必须保证触控高度 >= 44px`
|
|
);
|
|
}
|
|
});
|
|
|
|
test('7. 设计 Token 与瑞士国际主义排版规范引用', () => {
|
|
// 检查 portal/index.uvue 是否已完全接入设计 Token
|
|
const portalPath = path.join(MP_ROOT, 'pages/portal/index.uvue');
|
|
const portalContent = fs.readFileSync(portalPath, 'utf-8');
|
|
assert.ok(portalContent.includes('pageTheme'), 'portal/index.uvue 必须绑定 pageTheme');
|
|
assert.ok(portalContent.includes('--oc-primary'), 'portal/index.uvue 必须引用 --oc-primary Token');
|
|
});
|