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 typesUtsPath = path.join(MP_ROOT, 'common/types.uts'); const localeUtsPath = path.join(MP_ROOT, 'common/locale.uts'); const portalIndexUvuePath = path.join(MP_ROOT, 'pages/portal/index.uvue'); const usePortalUtsPath = path.join(MP_ROOT, 'pages/portal/usePortal.uts'); test('1. common/locale.uts 必须覆盖 7 种语言的预约需求表单国际化词条', () => { assert.ok(fs.existsSync(localeUtsPath), '必须存在 common/locale.uts'); const content = fs.readFileSync(localeUtsPath, 'utf-8'); const requiredKeys = [ 'portal.appointment', 'portal.appointment_desc', 'portal.book_now', 'portal.appointment_form_title', 'portal.appointment_form_desc', 'portal.appointment_name', 'portal.appointment_contact', 'portal.appointment_company', 'portal.appointment_time', 'portal.appointment_notes', 'portal.appointment_notes_placeholder', 'portal.appointment_submit', 'portal.appointment_submitting', 'portal.appointment_success_tip', 'portal.appointment_fill_required', 'portal.appointment_photos', 'portal.appointment_photos_tip', ]; for (const key of requiredKeys) { assert.ok(content.includes(`'${key}'`), `必须在 OmniMessageKey 中定义 ${key}`); } const locales = ['zh-CN', 'en-US', 'ar-SA', 'es-ES', 'de-DE', 'fr-FR', 'ja-JP']; for (const loc of locales) { assert.ok(content.includes(`'${loc}':`), `必须配置语言 ${loc}`); assert.ok(content.includes("'portal.appointment_form_title':"), `${loc} 必须包含 portal.appointment_form_title`); assert.ok(content.includes("'portal.appointment_photos':"), `${loc} 必须包含 portal.appointment_photos`); assert.ok(content.includes("'portal.appointment_submit':"), `${loc} 必须包含 portal.appointment_submit`); } }); test('2. pages/portal/index.uvue 包含 Bento 预约卡片并唤起需求提报抽屉', () => { assert.ok(fs.existsSync(portalIndexUvuePath), '必须存在 index.uvue'); const content = fs.readFileSync(portalIndexUvuePath, 'utf-8'); // 必须包含预约卡片与唤起方法 assert.ok(content.includes('appointment-card'), '必须包含 Bento 预约卡片'); assert.ok(content.includes('openAppointmentSheet'), '必须包含打开预约抽屉的方法 openAppointmentSheet'); assert.ok(content.includes('appointment-sheet'), '必须包含预约需求抽屉'); assert.ok( content.includes('appointment-sheet-scroll') || content.includes('sheet-scroll-body'), '预约抽屉必须包含可滚动容器以适配长表单在移动端正常滚动' ); }); test('3. 抽屉内必须包含多行需求文本框与可选的照片上传组件', () => { const content = fs.readFileSync(portalIndexUvuePath, 'utf-8'); // 表单字段输入项 assert.ok(content.includes('appointmentForm.name'), '必须绑定客户姓名输入项'); assert.ok(content.includes('appointmentForm.contact'), '必须绑定联系方式输入项'); assert.ok(content.includes('appointmentForm.company'), '必须绑定公司输入项'); assert.ok(content.includes('appointmentForm.notes'), '必须绑定需求与议题说明 textarea'); assert.ok( content.includes(' { const content = fs.readFileSync(portalIndexUvuePath, 'utf-8'); assert.ok(content.includes('submitLead(') || content.includes('submitLead'), '必须调用 submitLead 接口提交预约线索'); assert.ok(content.includes("touchType: 'appointment'") || content.includes('trackAppointmentAction'), '提交后必须上报 appointment 雷达轨迹'); assert.ok(content.includes('notes:'), '提交数据必须封装预约时间与需求 notes'); assert.ok( content.includes('photos') && content.includes('appointmentForm.photos'), '提交备注必须整合访客上传的资料/名片照片' ); }); test('5. 移动端 UI 规范审查:零非业务 Emoji、输入框防缩放与按钮触控区', () => { const content = fs.readFileSync(portalIndexUvuePath, 'utf-8'); // 零 Emoji 审查(严禁在代码中写死非业务 emoji 图标) const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; assert.equal(emojiRegex.test(content), false, 'index.uvue 严禁含有装饰性 Emoji'); // 必须适配输入框防缩放或触控区 assert.ok( content.includes('appointment-submit-btn'), '预约提交按钮必须具备独立规范样式类' ); });