feat(mpuniapp): 完善微官网访客在线提交商务预约与需求功能
这个提交包含在:
@@ -4,41 +4,31 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const MP_ROOT = path.resolve('m:/2026项目/CRMmp/mpuniapp');
|
||||
const calendarUtsPath = path.join(MP_ROOT, 'common/calendar.uts');
|
||||
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/calendar.uts 文件与核心导出函数契约检查', () => {
|
||||
assert.ok(fs.existsSync(calendarUtsPath), '必须存在 common/calendar.uts');
|
||||
const content = fs.readFileSync(calendarUtsPath, 'utf-8');
|
||||
assert.ok(content.includes('export function generateGoogleCalendarUrl'), '必须导出 generateGoogleCalendarUrl');
|
||||
assert.ok(content.includes('export function generateOutlookCalendarUrl'), '必须导出 generateOutlookCalendarUrl');
|
||||
assert.ok(content.includes('export function getMeetingIcsDownloadUrl'), '必须导出 getMeetingIcsDownloadUrl');
|
||||
assert.ok(content.includes('export function buildCalendarInviteBundle'), '必须导出 buildCalendarInviteBundle');
|
||||
});
|
||||
|
||||
test('2. common/types.uts 中 BentoBlockType 与预约配置契约对齐', () => {
|
||||
assert.ok(fs.existsSync(typesUtsPath), '必须存在 common/types.uts');
|
||||
const content = fs.readFileSync(typesUtsPath, 'utf-8');
|
||||
assert.ok(content.includes("'appointment'"), "BentoBlockType 必须包含 'appointment'");
|
||||
assert.ok(content.includes('meetingUrl?: string'), 'CardPortalDetail / CardProfile 必须包含 meetingUrl 属性');
|
||||
assert.ok(content.includes('MeetingInviteOptions'), '必须导出 MeetingInviteOptions 接口');
|
||||
});
|
||||
|
||||
test('3. common/locale.uts 必须覆盖 7 种国际主流语言的商务预约词条', () => {
|
||||
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.google_cal',
|
||||
'portal.outlook_cal',
|
||||
'portal.ics_download',
|
||||
'portal.copied_meeting_link',
|
||||
'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',
|
||||
];
|
||||
|
||||
for (const key of requiredKeys) {
|
||||
@@ -48,84 +38,51 @@ test('3. common/locale.uts 必须覆盖 7 种国际主流语言的商务预约
|
||||
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':"), `${loc} 必须包含 portal.appointment`);
|
||||
assert.ok(content.includes("'portal.book_now':"), `${loc} 必须包含 portal.book_now`);
|
||||
assert.ok(content.includes("'portal.appointment_form_title':"), `${loc} 必须包含 portal.appointment_form_title`);
|
||||
assert.ok(content.includes("'portal.appointment_submit':"), `${loc} 必须包含 portal.appointment_submit`);
|
||||
}
|
||||
});
|
||||
|
||||
test('4. pages/portal/index.uvue 中集成商务日程预约卡片与底部抽屉', () => {
|
||||
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') || content.includes('appointment-box'), '必须包含预约卡片组件/元素');
|
||||
assert.ok(content.includes('appointment-card'), '必须包含 Bento 预约卡片');
|
||||
assert.ok(content.includes('openAppointmentSheet'), '必须包含打开预约抽屉的方法 openAppointmentSheet');
|
||||
assert.ok(content.includes('appointment-sheet') || content.includes('appointment-drawer'), '必须包含预约底部抽屉');
|
||||
assert.ok(content.includes('appointment-sheet'), '必须包含预约需求抽屉');
|
||||
});
|
||||
|
||||
// 抽屉内必须提供三种日历同步渠道
|
||||
assert.ok(content.includes('handleCalendarSync'), '必须包含日历同步处理 handleCalendarSync');
|
||||
assert.ok(content.includes("'google'"), '必须支持 Google Calendar');
|
||||
assert.ok(content.includes("'outlook'"), '必须支持 Outlook Calendar');
|
||||
assert.ok(content.includes("'ics'"), '必须支持 ICS 日历文件');
|
||||
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('submitAppointmentForm'), '必须包含提交预约需求方法 submitAppointmentForm');
|
||||
assert.ok(content.includes('appointmentFormSubmitting'), '必须包含防重复提交状态控制');
|
||||
});
|
||||
|
||||
test('4. 提交预约信息必须直接调用 submitLead 接口将需求提交给名片主', () => {
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
test('5. pages/portal/usePortal.uts 中必须上报 appointment 雷达轨迹', () => {
|
||||
assert.ok(fs.existsSync(usePortalUtsPath), '必须存在 usePortal.uts');
|
||||
const content = fs.readFileSync(usePortalUtsPath, 'utf-8');
|
||||
// 必须适配输入框防缩放或触控区
|
||||
assert.ok(
|
||||
content.includes("touchType: 'appointment'") || content.includes('trackAppointmentAction'),
|
||||
'预约动作必须向雷达上报 touchType appointment 推进商机'
|
||||
);
|
||||
});
|
||||
|
||||
test('6. 日历链接生成与时间戳算法规范断言', () => {
|
||||
const content = fs.readFileSync(calendarUtsPath, 'utf-8');
|
||||
|
||||
// Google Calendar 规范检查
|
||||
assert.ok(
|
||||
content.includes('calendar.google.com/calendar/render'),
|
||||
'Google Calendar 必须指向官方 render 终端'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('action=TEMPLATE'),
|
||||
'Google Calendar 必须使用 TEMPLATE 动作'
|
||||
);
|
||||
|
||||
// Outlook 规范检查
|
||||
assert.ok(
|
||||
content.includes('outlook.live.com/calendar/0/deeplink/compose'),
|
||||
'Outlook Calendar 必须使用官方 compose 深度链接'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('rru=addevent'),
|
||||
'Outlook Calendar 必须指定 rru=addevent'
|
||||
);
|
||||
|
||||
// 后端 RFC 5545 ICS 接口规范检查
|
||||
assert.ok(
|
||||
content.includes('/open/card/portal/meeting-ics'),
|
||||
'ICS 下载必须复用后端 /open/card/portal/meeting-ics 接口'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('summary='),
|
||||
'ICS 请求参数必须包含 summary'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('startTime='),
|
||||
'ICS 请求参数必须包含 startTime'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('endTime='),
|
||||
'ICS 请求参数必须包含 endTime'
|
||||
);
|
||||
|
||||
// UTC RFC 5545 时间戳对齐
|
||||
assert.ok(
|
||||
content.includes('getUTCFullYear') && content.includes('getUTCMonth'),
|
||||
'时间戳格式化必须采用 UTC 时区计算避免跨国时区偏差'
|
||||
content.includes('appointment-submit-btn'),
|
||||
'预约提交按钮必须具备独立规范样式类'
|
||||
);
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户