import assert from 'node:assert/strict' import { existsSync, readFileSync } from 'node:fs' import { resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') const read = (path) => readFileSync(resolve(root, path), 'utf8') function assertInOrder(source, labels, scope) { let previous = -1 for (const label of labels) { const position = source.indexOf(label) assert.notEqual(position, -1, `${scope}缺少“${label}”`) assert.ok(position > previous, `${scope}中“${label}”顺序错误`) previous = position } } const appConfig = JSON.parse(read('miniapp/app.json')) assert.equal(appConfig.window.backgroundColor.toUpperCase(), '#FAFAF8', '小程序页面背景必须为暖白 #FAFAF8') assert.deepEqual( appConfig.tabBar.list.map((item) => item.text), ['首页', '找对象', '消息', '活动', '我的'], '底部导航顺序必须固定', ) const requiredPages = [ 'pages/service-center/service-center', 'pages/service-station/service-station', 'pages/service-appointment/service-appointment', 'pages/my-appointments/my-appointments', 'pages/content/content', 'pages/content-detail/content-detail', 'pages/anti-fraud/anti-fraud', 'pages/service-program/service-program', 'pages/service-history/service-history', ] for (const page of requiredPages) { assert.ok(appConfig.pages.includes(page), `小程序未注册页面 ${page}`) for (const extension of ['js', 'json', 'wxml', 'wxss']) { assert.ok(existsSync(resolve(root, `miniapp/${page}.${extension}`)), `小程序页面文件缺失:${page}.${extension}`) } } const appStyle = read('miniapp/app.wxss') assert.match(appStyle, /--color-bg-page:\s*#FAFAF8/i, '全局背景令牌必须为 #FAFAF8') assert.match(appStyle, /--spacing-section:\s*48rpx/i, '模块间距必须为 24px(48rpx)') const sharedServiceStyle = read('miniapp/styles/service-page.wxss') assert.doesNotMatch(sharedServiceStyle, /@import\s+["']\.\.\/\.\.\/app\.wxss["']/, '共享页面样式不能错误导入 ../../app.wxss') const homeMarkup = read('miniapp/pages/index/index.wxml') assert.doesNotMatch(homeMarkup, /<[^>]+wx:elif="\{\{recommendations\.length\}\}"[^>]+wx:for=/, '推荐条件分支与循环必须分离,避免 wx:else 失联') assert.match(homeMarkup, //, '推荐列表必须使用 block 条件分支') assertInOrder(homeMarkup, [ '品牌区', '用户状态条', 'Banner 轮播', '8个核心入口', '今日推荐', '善缘公益服务', '近期公益活动', '善缘服务站', '婚恋课堂', '安全提醒', ], '首页') const homeLogic = read('miniapp/pages/index/index.js') assertInOrder(homeLogic, [ "label: '找对象'", "label: '公益红娘'", "label: '公益活动'", "label: '婚恋咨询'", "label: '婚恋课堂'", "label: '婚前辅导'", "label: '婚恋反诈'", "label: '善缘服务站'", ], '首页核心入口') assert.doesNotMatch(homeLogic, /功能开发中/, '首页核心入口不得使用“功能开发中”占位') const activityDetailMarkup = read('miniapp/pages/activity-detail/activity-detail.wxml') assert.doesNotMatch(activityDetailMarkup, /页面:\s*activity-detail/, '活动详情页不得是占位页面') assert.match(activityDetailMarkup, /活动信息/, '活动详情页必须展示活动信息') assert.match(activityDetailMarkup, /报名须知/, '活动详情页必须展示报名须知') const adminTheme = read('admin-web/src/theme/index.css') assert.match(adminTheme, /--sidebar-width:\s*220px/, '管理后台侧栏展开宽度必须为 220px') assert.match(adminTheme, /--color-bg-page:\s*#FAFAF8/i, '管理后台页面背景必须使用暖白体系') const adminLayout = read('admin-web/src/layouts/MainLayout.vue') assert.match(adminLayout, /善缘丹阳/, '后台品牌名称必须为善缘丹阳') assert.doesNotMatch(adminLayout, /#304156/i, '后台不得继续使用旧式深蓝侧栏') const adminApiContracts = [ ['admin-web/src/modules/banner/BannerManagementView.vue', '/operator/banners'], ['admin-web/src/modules/service-station/ServiceStationView.vue', '/operator/service-locations'], ['admin-web/src/modules/service-appointment/ServiceAppointmentView.vue', '/service-appointments/operator/list'], ['admin-web/src/modules/activity/ActivityInterestView.vue', '/activities/operator/activity-interest-summary'], ] for (const [file, endpoint] of adminApiContracts) { assert.ok(read(file).includes(endpoint), `${file} 未使用实际后台接口 ${endpoint}`) } assert.doesNotMatch(read('admin-web/src/modules/risk/ReportCenterView.vue'), /tableData\.value = res \|\|/, '举报列表不能把响应信封当数组') assert.doesNotMatch(read('admin-web/src/modules/appeal/AppealCenterView.vue'), /tableData\.value = res \|\|/, '申诉列表不能把响应信封当数组') console.log('UI contracts passed')