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'); const pagesJsonPath = path.join(MP_ROOT, 'pages.json'); const workplaceUvuePath = path.join(MP_ROOT, 'pages/owner/workplace/index.uvue'); const workplaceUtsPath = path.join(MP_ROOT, 'pages/owner/workplace/useWorkplace.uts'); const apiUtsPath = path.join(MP_ROOT, 'common/api.uts'); const typesUtsPath = path.join(MP_ROOT, 'common/types.uts'); test('1. pages.json 必须规范配置 5 栏纯正 TabBar (工作台、名片、雷达、潜客、我的)', () => { assert.ok(fs.existsSync(pagesJsonPath), 'pages.json 必须存在'); const content = fs.readFileSync(pagesJsonPath, 'utf-8'); const config = JSON.parse(content); const tabBarList = config.tabBar?.list || []; assert.equal(tabBarList.length, 5, 'TabBar 应当精确包含 5 个核心工作台 Tab'); // 验证 5 个 Tab 顺序与路由 assert.equal(tabBarList[0].pagePath, 'pages/owner/workplace/index', 'TabBar 第 1 项必须是工作台'); assert.equal(tabBarList[0].text, '工作台'); assert.equal(tabBarList[1].pagePath, 'pages/owner/card/index', 'TabBar 第 2 项必须是名片'); assert.equal(tabBarList[1].text, '名片'); assert.equal(tabBarList[2].pagePath, 'pages/owner/radar', 'TabBar 第 3 项必须是雷达'); assert.equal(tabBarList[2].text, '雷达'); assert.equal(tabBarList[3].pagePath, 'pages/owner/leads', 'TabBar 第 4 项必须是潜客'); assert.equal(tabBarList[3].text, '潜客'); assert.equal(tabBarList[4].pagePath, 'pages/owner/profile/index', 'TabBar 第 5 项必须是我的'); assert.equal(tabBarList[4].text, '我的'); // 验证 pages[0] 首屏页面为工作台首页且为沉浸式导航 assert.equal(config.pages[0].path, 'pages/owner/workplace/index', '应用首屏 pages[0] 应设为工作台'); assert.equal(config.pages[0].style?.navigationStyle, 'custom', '工作台页面应采用沉浸式 custom 导航,消除原生白底标题栏'); }); test('2. 核心契约与接口复用审查 (fetchWorkplaceOverview 与 WorkplaceOverviewResult)', () => { const typesContent = fs.readFileSync(typesUtsPath, 'utf-8'); assert.ok(typesContent.includes('WorkplaceOverviewResult'), 'types.uts 必须导出 WorkplaceOverviewResult'); assert.ok(typesContent.includes('WorkplaceKpi'), 'types.uts 必须导出 WorkplaceKpi'); assert.ok(typesContent.includes('WorkplaceRadarItem'), 'types.uts 必须导出 WorkplaceRadarItem'); const apiContent = fs.readFileSync(apiUtsPath, 'utf-8'); assert.ok(apiContent.includes('fetchWorkplaceOverview'), 'api.uts 必须封装 fetchWorkplaceOverview'); assert.ok(apiContent.includes('/admin/crm/radar/workplace'), '接口必须对齐后端 /admin/crm/radar/workplace'); }); test('3. 工作台状态机 useWorkplace.uts 逻辑完整性审查', () => { assert.ok(fs.existsSync(workplaceUtsPath), '必须存在 useWorkplace.uts 业务逻辑文件'); const content = fs.readFileSync(workplaceUtsPath, 'utf-8'); assert.ok(content.includes('createWorkplaceState'), '必须提供 createWorkplaceState'); assert.ok(content.includes('loadWorkplaceData'), '必须提供 loadWorkplaceData 聚合加载逻辑'); assert.ok(content.includes('resolveWorkplaceText'), '必须提供国际化文案解析 resolveWorkplaceText'); assert.ok(content.includes('fetchCardQrcodeSvg'), 'loadWorkplaceData 必须复用已有 fetchCardQrcodeSvg'); assert.ok(content.includes('activeQrSvg'), '状态机必须持有 activeQrSvg'); }); test('4. 工作台视图 index.uvue 结构与规范审查 (Bento Grid 模组、零 Emoji、安全区避让、沉浸式且无遮挡悬浮按钮)', () => { assert.ok(fs.existsSync(workplaceUvuePath), '必须存在 workplace/index.uvue 视图文件'); const content = fs.readFileSync(workplaceUvuePath, 'utf-8'); // 严禁装饰性 Emoji const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; assert.equal(emojiRegex.test(content), false, 'workplace/index.uvue 严禁含有 Emoji'); // 必须包含核心 Bento 模组与沉浸式顶栏 assert.ok(content.includes('workplace-topbar'), '必须包含沉浸式工作台顶栏'); assert.ok(content.includes('quick-showcase-section'), '必须包含闪电出示微展台模组'); assert.ok(content.includes('kpi-grid-section'), '必须包含 2x2 Bento 战情报表模组'); assert.ok(content.includes('radar-pulse-section'), '必须包含实时雷达高光模组'); assert.ok(content.includes('leads-todo-section'), '必须包含今日待办商机模组'); // 移除底部遮挡内容的悬浮主 CTA 栏 (一屏单主操作由微展台承载,名片Tab独立) assert.equal(content.includes('workplace-cta-bar'), false, '严禁包含遮挡内容的常驻底部悬浮主 CTA 栏'); // 必须避让安全区与 window-bottom assert.ok(content.includes('safe-area-inset-bottom'), '必须适配 env(safe-area-inset-bottom)'); assert.ok(content.includes('var(--window-bottom'), 'TabBar 页面必须避让 var(--window-bottom)'); }); test('5. 有真实 svg 时渲染二维码;无码/失败仅空态文案,禁止伪码占位', () => { assert.ok(fs.existsSync(workplaceUvuePath), '必须存在 workplace/index.uvue'); assert.ok(fs.existsSync(workplaceUtsPath), '必须存在 useWorkplace.uts'); const pageSource = fs.readFileSync(workplaceUvuePath, 'utf-8'); const utsSource = fs.readFileSync(workplaceUtsPath, 'utf-8'); const template = pageSource.split('\s*