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/owner/workplace/index.uvue', '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/card/cases.uvue', 'pages/owner/card/articles.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', ]; // 5 个 TabBar 页面 const TABBAR_PAGES = [ 'pages/owner/workplace/index.uvue', 'pages/owner/card/index.uvue', 'pages/owner/radar.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/card/cases.uvue', 'pages/owner/card/articles.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'); });