feat(mpuniapp): 落地智能销售移动工作台与5栏TabBar纯正架构(工作台、名片、雷达、潜客、我的)

这个提交包含在:
wikiboss
2026-09-21 15:34:21 +08:00
父节点 17882ca1cb
当前提交 2d0b655200
共有 10 个文件被更改,包括 1971 次插入 和 241 次删除

查看文件

@@ -8,33 +8,36 @@ const pagesJsonPath = path.join(MP_ROOT, 'pages.json');
const profileUvuePath = path.join(MP_ROOT, 'pages/owner/profile/index.uvue');
const cardHomeUtsPath = path.join(MP_ROOT, 'pages/owner/card/useCardHome.uts');
test('1. pages.json TabBar 信息架构纯正性审查 (只保留名片主 4 大工作台,移除微官网)', () => {
test('1. pages.json TabBar 信息架构纯正性审查 (工作台、名片、雷达、潜客、我的 5 栏标准)', () => {
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, 4, 'TabBar 应当仅包含 4 个核心工作台 Tab');
assert.equal(tabBarList.length, 5, 'TabBar 应当精确包含 5 个核心工作台 Tab');
// 验证不包含微官网 pages/portal/index
const hasPortalInTabBar = tabBarList.some((item: any) => item.pagePath === 'pages/portal/index');
assert.equal(hasPortalInTabBar, false, '微官网 pages/portal/index 严禁作为 TabBar 常驻项,应作为独立落地页');
// 验证 4 个 Tab 顺序与路由
assert.equal(tabBarList[0].pagePath, 'pages/owner/card/index', 'TabBar 第 1 项必须是名片');
assert.equal(tabBarList[0].text, '名片');
// 验证 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/radar', 'TabBar 第 2 项必须是雷达');
assert.equal(tabBarList[1].text, '雷达');
assert.equal(tabBarList[1].pagePath, 'pages/owner/card/index', 'TabBar 第 2 项必须是名片');
assert.equal(tabBarList[1].text, '名片');
assert.equal(tabBarList[2].pagePath, 'pages/owner/leads', 'TabBar 第 3 项必须是潜客');
assert.equal(tabBarList[2].text, '潜客');
assert.equal(tabBarList[2].pagePath, 'pages/owner/radar', 'TabBar 第 3 项必须是雷达');
assert.equal(tabBarList[2].text, '雷达');
assert.equal(tabBarList[3].pagePath, 'pages/owner/profile/index', 'TabBar 第 4 项必须是我的');
assert.equal(tabBarList[3].text, '我的');
assert.equal(tabBarList[3].pagePath, 'pages/owner/leads', 'TabBar 第 4 项必须是潜客');
assert.equal(tabBarList[3].text, '潜客');
// 验证 pages[0] 首屏页面为名片页
assert.equal(config.pages[0].path, 'pages/owner/card/index', '应用首屏 pages[0] 应设为名片首页');
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] 应设为工作台首页');
// 验证微官网依然保留在 pages 路由列表中供扫码和独立预览访问
const hasPortalInPages = config.pages.some((item: any) => item.path === 'pages/portal/index');
@@ -44,7 +47,8 @@ test('1. pages.json TabBar 信息架构纯正性审查 (只保留名片主 4 大
test('2. profile/index.uvue 的 openPage 导航守卫与新 TabBar 保持 100% 一致', () => {
const content = fs.readFileSync(profileUvuePath, 'utf-8');
// 验证 tabPages 数组不包含 portal/index,且包含 4 个管理页
// 验证 tabPages 数组不包含 portal/index,且包含 5 个工作台管理页
assert.ok(content.includes("'/pages/owner/workplace/index'"), 'tabPages 必须包含 /pages/owner/workplace/index');
assert.ok(content.includes("'/pages/owner/card/index'"), 'tabPages 必须包含 /pages/owner/card/index');
assert.ok(content.includes("'/pages/owner/radar'"), 'tabPages 必须包含 /pages/owner/radar');
assert.ok(content.includes("'/pages/owner/leads'"), 'tabPages 必须包含 /pages/owner/leads');

查看文件

@@ -0,0 +1,79 @@
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] 应设为工作台');
});
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');
});
test('4. 工作台视图 index.uvue 结构与规范审查 (Bento Grid 模组、零 Emoji、安全区避让、单悬浮主 CTA)', () => {
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 模组与主 CTA
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'), '必须包含今日待办商机模组');
assert.ok(content.includes('workplace-cta-bar'), '必须包含常驻底部悬浮主 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)');
});