refactor(i18n): 全工程面向国际用户与全球现代商务重构,彻底清理出海跨境等单向视角表达并对齐自动化测试

这个提交包含在:
wikiboss
2026-09-21 15:24:36 +08:00
父节点 ad9df7a7a4
当前提交 17882ca1cb
共有 135 个文件被更改,包括 12928 次插入 和 1653 次删除

查看文件

@@ -54,13 +54,13 @@ describe('T31 DeepSeek 大模型集成与适配服务 (deepseek.ts)', () => {
it('2.1 buildSystemPrompt 组装名片主、人设与知识库切片', () => {
const card = {
name: '张总',
company: '华强出海科技',
title: '海外商务总监',
company: '华强全球科技',
title: '国际商务总监',
};
const concierge = {
personaType: 'trade_expert' as const,
toneStyle: 'professional' as const,
promptTemplate: '你是一名资深出海贸易专家,精通中东与东南亚市场。',
promptTemplate: '你是一名资深国际贸易专家,精通中东与东南亚市场。',
};
const chunks = [
{ index: 1, content: '支持信用证结算与 FOB 迪拜港条款。' },
@@ -70,9 +70,9 @@ describe('T31 DeepSeek 大模型集成与适配服务 (deepseek.ts)', () => {
const systemPrompt = service.buildSystemPrompt(card, concierge, chunks);
expect(systemPrompt).toContain('张总');
expect(systemPrompt).toContain('华强出海科技');
expect(systemPrompt).toContain('海外商务总监');
expect(systemPrompt).toContain('你是一名资深出海贸易专家');
expect(systemPrompt).toContain('华强全球科技');
expect(systemPrompt).toContain('国际商务总监');
expect(systemPrompt).toContain('你是一名资深国际贸易专家');
expect(systemPrompt).toContain('支持信用证结算与 FOB 迪拜港条款。');
expect(systemPrompt).toContain('最小起订量 (MOQ) 为 500 台。');
});

查看文件

@@ -52,7 +52,7 @@ function localizeReq(
};
}
describe('T39: AI 出海润色 (modules/ai localize)', () => {
describe('T39: AI 多语言本地化润色 (modules/ai localize)', () => {
describe('1. AiLocalizeService.localize 入参校验', () => {
let service: AiLocalizeService;

查看文件

@@ -11,7 +11,7 @@ describe('Point 47: AI 销售潜客智能跟进邮件/消息地道草拟引擎',
service = new AiOutreachDraftService();
});
describe('1. formatOutreachPrompt (出海商务邮件地道提示词组装)', () => {
describe('1. formatOutreachPrompt (全球商务邮件地道提示词组装)', () => {
it('英语场景应包含地道商务开场、兴趣针对性提及与日程预约呼吁', () => {
const req: OutreachDraftRequest = {
leadName: 'Arthur Pendelton',

查看文件

@@ -103,7 +103,7 @@ describe('Point 07: AI 知识库分块切片与召回沙盒 (modules/ai knowledg
},
{
index: 1,
content: '雷达系统支持出海 CRM 访客轨迹追踪、停留时长与意向度自动评分。',
content: '雷达系统支持智能 CRM 访客轨迹追踪、停留时长与意向度自动评分。',
length: 33,
},
{
@@ -150,10 +150,10 @@ describe('Point 07: AI 知识库分块切片与召回沙盒 (modules/ai knowledg
it('应兼容接收纯字符串数组 chunks', () => {
const stringChunks = [
'出海多语言支持英文、中文、阿拉伯语',
'国际多语言支持英文、中文、阿拉伯语',
'国内网络环境配置说明',
];
const results = service.simulateRetrieval('阿拉伯语 出海', stringChunks, 2);
const results = service.simulateRetrieval('阿拉伯语 国际多语言', stringChunks, 2);
expect(results.length).toBe(1);
expect(results[0].content).toContain('阿拉伯语');
expect(results[0].score).toBeGreaterThan(0);

查看文件

@@ -12,7 +12,7 @@ describe('Point 61 (P1): AI 对话客商意图与情绪极性分析引擎', () =
analyzer = new AiSentimentIntentAnalyzer();
});
describe('1. analyzeConversationIntent (出海商务意图识别)', () => {
describe('1. analyzeConversationIntent (商务意图识别)', () => {
it('包含价格、报价、起订量或购买关键词应识别为 PURCHASE_INTENT', () => {
const msgs = [
'Hello, what is the MOQ for 500 NFC metal cards?',

查看文件

@@ -190,7 +190,7 @@ function seedBlocks(): Array<Partial<CardBentoEntity>> {
rowSpan: 1,
sortOrder: 1,
title: '个人简介与身份卡',
content: '全栈出海专家',
content: '全球商务专家',
status: 1,
},
{
@@ -211,7 +211,7 @@ function seedBlocks(): Array<Partial<CardBentoEntity>> {
colSpan: 2,
rowSpan: 2,
sortOrder: 2,
title: '新能源出海案例',
title: '新能源国际合作案例',
content: '中东客户成功交付',
status: 0,
},
@@ -334,7 +334,7 @@ describe('后台 Bento 画布 CRUD (Admin Card Bento CoolController)', () => {
type: 'video' as BentoBlockType,
colSpan: 2 as BentoColSpan,
rowSpan: 2 as BentoRowSpan,
title: '企业出海宣传片',
title: '企业宣传片',
mediaUrl: 'https://cdn.omnicard.ai/video/intro.mp4',
},
];

查看文件

@@ -74,7 +74,7 @@ describe('后台博客文章与动态发布中心 (Card Article CoolController &
it('完整文章入参时,应正确映射 title、summary 与 coverImage', () => {
const article = {
cardId: 101,
title: '出海战略新视界:2026 跨境数字名片白皮书',
title: '全球商务新视界:2026 数字名片白皮书',
summary: '本文深度剖析全球化商务场景下 NFC 与微官网结合的数字化获客新路径。',
content: '<p>正文内容...</p>',
coverImage: 'https://cdn.omnicard.ai/articles/cover-1.jpg',
@@ -91,18 +91,18 @@ describe('后台博客文章与动态发布中心 (Card Article CoolController &
const longHtml = `
<article>
<h1>一级标题</h1>
<p>在全球数字化浪潮席卷而来的今天,<strong>智能名片</strong>已经不仅是身份凭证,更是出海企业的第一道数字门面与线索转化漏斗。通过多语言自适应与 NFC 瞬时触达,销售代表可以在海外展会现场以极低摩擦完成商机留资与精准雷达跟进,大幅提升商机转化效率与客户体验。</p>
<p>在全球数字化浪潮席卷而来的今天,<strong>智能名片</strong>已经不仅是身份凭证,更是现代企业的第一道数字门面与线索转化漏斗。通过多语言自适应与 NFC 瞬时触达,销售代表可以在国际展会现场以极低摩擦完成商机留资与精准雷达跟进,大幅提升商机转化效率与客户体验。</p>
</article>
`;
const article = {
cardId: 101,
title: '出海营销指南',
title: '全球营销指南',
content: longHtml,
isPublished: true,
};
const og = service.generateOgMeta(article);
expect(og.title).toBe('出海营销指南');
expect(og.title).toBe('全球营销指南');
expect(og.description.length).toBeLessThanOrEqual(160);
expect(og.description).not.toContain('<p>');
expect(og.description).not.toContain('<strong>');

查看文件

@@ -5,7 +5,7 @@ import {
} from '../../../src/modules/card/service/batch-locale';
import { CardLocaleEntity } from '../../../src/modules/card/entity/locale';
describe('Point 43: 出海团队多语言名片批量生成向导与多语种字段镜像校验', () => {
describe('Point 43: 跨国团队多语言名片批量生成向导与多语种字段镜像校验', () => {
let service: CardBatchLocaleService;
let mockRepository: any;
let savedRecords: CardLocaleEntity[];
@@ -80,8 +80,8 @@ describe('Point 43: 出海团队多语言名片批量生成向导与多语种字
cardId: 101,
baseFields: {
name: '张三',
title: '出海销售总监',
company: '深圳数字智能出海科技',
title: '国际业务总监',
company: '数字智能科技',
slogan: '智能连接全球',
},
},
@@ -90,7 +90,7 @@ describe('Point 43: 出海团队多语言名片批量生成向导与多语种字
baseFields: {
name: '李四',
title: '中东区域代表',
company: '深圳数字智能出海科技',
company: '数字智能科技',
slogan: '专注海湾六国',
},
},
@@ -164,7 +164,7 @@ describe('Point 43: 出海团队多语言名片批量生成向导与多语种字
});
});
describe('3. inspectTeamLocaleCoverage (出海团队语言覆盖率与就绪度诊断)', () => {
describe('3. inspectTeamLocaleCoverage (跨国团队语言覆盖率与就绪度诊断)', () => {
it('应精准统计团队名片各语言的覆盖率与缺失名单', async () => {
savedRecords.push(
{ id: 1, cardId: 101, locale: 'en', name: 'Alex' } as CardLocaleEntity,

查看文件

@@ -153,7 +153,7 @@ describe('后端微官网模块 (modules/card) - Bento Grid 便当盒组件树
rowSpan: 1,
sortOrder: 1,
title: '个人简介与身份卡',
content: '全栈出海专家',
content: '全球商务专家',
status: 1,
},
{
@@ -174,7 +174,7 @@ describe('后端微官网模块 (modules/card) - Bento Grid 便当盒组件树
colSpan: 2,
rowSpan: 2,
sortOrder: 2,
title: '新能源出海案例',
title: '新能源国际合作案例',
content: '中东客户成功交付',
status: 0, // 禁用
},
@@ -275,7 +275,7 @@ describe('后端微官网模块 (modules/card) - Bento Grid 便当盒组件树
type: 'video' as BentoBlockType,
colSpan: 2 as BentoColSpan,
rowSpan: 2 as BentoRowSpan,
title: '企业出海宣传片',
title: '企业宣传片',
mediaUrl: 'https://cdn.omnicard.ai/video/intro.mp4',
},
];
@@ -336,7 +336,7 @@ describe('后端微官网模块 (modules/card) - Bento Grid 便当盒组件树
type: 'form' as BentoBlockType,
colSpan: 2 as BentoColSpan,
rowSpan: 1 as BentoRowSpan,
title: '外贸留资表单',
title: '商务留资表单',
metaJson: JSON.stringify({
fields: ['name', 'email', 'country', 'requirement'],
buttonText: 'Get Quote',
@@ -383,12 +383,12 @@ describe('后端微官网模块 (modules/card) - Bento Grid 便当盒组件树
const created = await service.saveBlock({
cardId: 100,
type: 'article',
title: '出海白皮书',
title: '全球商务白皮书',
colSpan: 1,
rowSpan: 1,
});
expect(created.id).toBeDefined();
expect(created.title).toBe('出海白皮书');
expect(created.title).toBe('全球商务白皮书');
});
it('deleteBlock 应能成功删除单个块', async () => {

查看文件

@@ -86,7 +86,7 @@ describe('Point 26: 国际商务日程预约排期与 ICS 日历订阅 (RFC 5545
const mockMeeting: MeetingEventOptions = {
uid: 'omnicard-meet-20260920-001@omnicard.ai',
summary: 'OmniCard AI 商务拓展战略会谈 (Alex & Partner)',
description: '讨论海外微官网落地方案, NFC 实体卡发行与访客雷达 CRM 对接。\n请准时参会。',
description: '讨论国际微官网落地方案, NFC 实体卡发行与访客雷达 CRM 对接。\n请准时参会。',
location: 'Zoom 会议室 (ID: 888-999-000), 新加坡滨海湾金融中心',
startTime: new Date(Date.UTC(2026, 8, 20, 14, 0, 0)),
endTime: new Date(Date.UTC(2026, 8, 20, 15, 0, 0)),
@@ -122,7 +122,7 @@ describe('Point 26: 国际商务日程预约排期与 ICS 日历订阅 (RFC 5545
expect(ics).toContain('DTSTART:20260920T140000Z\r\n');
expect(ics).toContain('DTEND:20260920T150000Z\r\n');
expect(ics).toContain('SUMMARY:OmniCard AI 商务拓展战略会谈 (Alex & Partner)\r\n');
expect(ics).toContain('DESCRIPTION:讨论海外微官网落地方案\\, NFC 实体卡发行与访客雷达 CRM 对接。\\n请准时参会。\r\n');
expect(ics).toContain('DESCRIPTION:讨论国际微官网落地方案\\, NFC 实体卡发行与访客雷达 CRM 对接。\\n请准时参会。\r\n');
expect(ics).toContain('LOCATION:Zoom 会议室 (ID: 888-999-000)\\, 新加坡滨海湾金融中心\r\n');
expect(ics).toContain('ORGANIZER;CN=Alex Zheng:mailto:alex@omnicard.ai\r\n');
expect(ics).toContain('URL:https://omnicard.ai/p/alex-zheng\r\n');
@@ -193,10 +193,10 @@ describe('Point 26: 国际商务日程预约排期与 ICS 日历订阅 (RFC 5545
portalController.ctx = mockCtx;
const result = await portalController.meetingIcs({
summary: '全球合作伙伴出海研讨会',
summary: '全球合作伙伴研讨会',
startTime: '2026-09-22T09:00:00Z',
endTime: '2026-09-22T10:00:00Z',
description: '探讨 2026 年品牌海外本土化与数字名片生态布局',
description: '探讨 2026 年品牌全球本土化与数字名片生态布局',
location: '线上 Google Meet',
organizerName: 'Alex Zheng',
organizerEmail: 'alex@omnicard.ai',
@@ -211,7 +211,7 @@ describe('Point 26: 国际商务日程预约排期与 ICS 日历订阅 (RFC 5545
const icsOutput = (responseBody as string) || (result as string);
expect(typeof icsOutput).toBe('string');
expect(icsOutput).toContain('BEGIN:VCALENDAR');
expect(icsOutput).toContain('SUMMARY:全球合作伙伴出海研讨会');
expect(icsOutput).toContain('SUMMARY:全球合作伙伴研讨会');
expect(icsOutput).toContain('DTSTART:20260922T090000Z');
expect(icsOutput).toContain('DTEND:20260922T100000Z');
expect(icsOutput).toContain('ORGANIZER;CN=Alex Zheng:mailto:alex@omnicard.ai');
@@ -266,8 +266,8 @@ describe('Point 26: 国际商务日程预约排期与 ICS 日历订阅 (RFC 5545
it('应正确从 ICS 字符串反向提取会议核心属性', () => {
const meeting: MeetingEventOptions = {
uid: 'roundtrip-test-001@omnicard.ai',
summary: '出海战略合作洽谈',
description: '讨论微官网与海外本地化支付通道',
summary: '全球战略合作洽谈',
description: '讨论微官网与全球本地化支付通道',
location: '线上 Google Meet',
startTime: '2026-09-25T08:00:00Z',
endTime: '2026-09-25T09:00:00Z',
@@ -282,8 +282,8 @@ describe('Point 26: 国际商务日程预约排期与 ICS 日历订阅 (RFC 5545
const parsed = parseIcsEvent(ics);
expect(parsed.uid).toBe('roundtrip-test-001@omnicard.ai');
expect(parsed.summary).toBe('出海战略合作洽谈');
expect(parsed.description).toBe('讨论微官网与海外本地化支付通道');
expect(parsed.summary).toBe('全球战略合作洽谈');
expect(parsed.description).toBe('讨论微官网与全球本地化支付通道');
expect(parsed.location).toBe('线上 Google Meet');
expect(parsed.status).toBe('CONFIRMED');
expect(typeof parsed.organizer).toBe('object');

查看文件

@@ -8,7 +8,7 @@ import {
adminCardCasesCurdOption,
} from '../../../src/modules/card/controller/admin/cases';
describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
describe('后台案例作品集 (Admin Card Cases CRUD)', () => {
const entityFile = path.join(
__dirname,
'../../../src/modules/card/entity/cases.ts'
@@ -59,10 +59,10 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
});
describe('2. 实体字段契约 (CardCasesEntity)', () => {
it('实体实例应支持出海案例必需契约字段', () => {
it('实体实例应支持案例必需契约字段', () => {
const item = new CardCasesEntity();
item.cardId = 1001;
item.title = '智能电动滑板车欧美 DTC 出海';
item.title = '智能电动滑板车欧美 DTC 全球拓展';
item.client = 'VoltRide Global';
item.duration = '2024.01 - 2024.06';
item.metrics = '+320% ROI, 45,000+ 留资量';
@@ -72,7 +72,7 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
item.sortOrder = 1;
expect(item.cardId).toBe(1001);
expect(item.title).toBe('智能电动滑板车欧美 DTC 出海');
expect(item.title).toBe('智能电动滑板车欧美 DTC 全球拓展');
expect(item.client).toBe('VoltRide Global');
expect(item.duration).toBe('2024.01 - 2024.06');
expect(item.metrics).toBe('+320% ROI, 45,000+ 留资量');
@@ -92,7 +92,7 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
Object.assign(new CardCasesEntity(), {
id: 1,
cardId: 101,
title: '跨境智能硬件众筹出海',
title: '全球智能硬件众筹发布',
client: 'TechNova',
duration: '3个月',
metrics: '+280% 众筹目标达成',
@@ -103,7 +103,7 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
Object.assign(new CardCasesEntity(), {
id: 2,
cardId: 101,
title: '东南亚 TikTok 跨境直播获客',
title: '东南亚 TikTok 国际直播获客',
client: 'FashionGo',
duration: '6个月',
metrics: '千万级曝光, +150% GMV',
@@ -163,9 +163,9 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
it('getCasesByCardId 应根据名片 cardId 筛选并按 sortOrder 升序排列', async () => {
const cases = await service.getCasesByCardId(101);
expect(cases).toHaveLength(2);
expect(cases[0].title).toBe('东南亚 TikTok 跨境直播获客');
expect(cases[0].title).toBe('东南亚 TikTok 国际直播获客');
expect(cases[0].sortOrder).toBe(1);
expect(cases[1].title).toBe('跨境智能硬件众筹出海');
expect(cases[1].title).toBe('全球智能硬件众筹发布');
expect(cases[1].sortOrder).toBe(2);
});
@@ -177,7 +177,7 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
it('创建案例并能查询出最新案例', async () => {
const created = await service.cardCasesEntity.save({
cardId: 101,
title: '拉美电商本土化品牌出海',
title: '拉美电商本土化品牌打造',
client: 'LatamDirect',
duration: '4个月',
metrics: '+90% 复购率',
@@ -187,7 +187,7 @@ describe('后台出海案例作品集 (Admin Card Cases CRUD)', () => {
});
expect(created.id).toBeDefined();
expect(created.title).toBe('拉美电商本土化品牌出海');
expect(created.title).toBe('拉美电商本土化品牌打造');
const found = await service.cardCasesEntity.findOneBy({ id: created.id });
expect(found).not.toBeNull();

查看文件

@@ -29,7 +29,7 @@ describe('Task 08: 后端公开短链路由与秒级微官网详情 API (Card Po
themeCode: 'corporate_navy',
// 敏感内部数据
userId: 9999,
remark: '重点出海客户核心高管,年费 VIP 专属',
remark: '重点合作客户核心高管,年费 VIP 专属',
internalConfig: '{"rateLimit": 1000, "debug": true, "internalTenantSecret": "sk_live_xyz"}',
createTime: new Date(),
updateTime: new Date(),
@@ -71,7 +71,7 @@ describe('Task 08: 后端公开短链路由与秒级微官网详情 API (Card Po
colSpan: 1,
rowSpan: 1,
sortOrder: 3,
title: '出海 SaaS 落地指南',
title: '全球 SaaS 落地指南',
content: '深度解读全球数字名片与微官网搭建...',
status: 1, // 有效
internalNotes: '文章需由法务团队二次审核', // 敏感
@@ -84,7 +84,7 @@ describe('Task 08: 后端公开短链路由与秒级微官网详情 API (Card Po
rowSpan: 1,
sortOrder: 1,
title: '关于我',
content: '10 年全球出海商务拓展经验',
content: '10 年全球商务拓展经验',
status: 1, // 有效
internalNotes: '置顶 Hero 块',
} as CardBentoEntity,

查看文件

@@ -6,7 +6,7 @@ import {
PosterElementBox,
} from '../../../src/modules/card/service/poster-layout';
describe('Point 54: 出海展会多语言电子邀请海报轻量级排版引擎', () => {
describe('Point 54: 商务展会多语言电子邀请海报轻量级排版引擎', () => {
let engine: CardPosterLayoutEngine;
beforeEach(() => {

查看文件

@@ -0,0 +1,121 @@
import { MOCK_CARDS_DATA, MockCardBundle } from '../../../src/modules/card/mock/seed-cards';
import { CardPortalService } from '../../../src/modules/card/service/portal';
import { CardInfoEntity } from '../../../src/modules/card/entity/info';
import { CardBentoEntity } from '../../../src/modules/card/entity/bento';
describe('多张多媒体名片数据 (配图与配视频) 规格与集成测试', () => {
it('1. 应包含至少 5 组不同国际行业的高保真名片数据包', () => {
expect(Array.isArray(MOCK_CARDS_DATA)).toBe(true);
expect(MOCK_CARDS_DATA.length).toBeGreaterThanOrEqual(5);
const slugs = MOCK_CARDS_DATA.map(item => item.card.slug);
const uniqueSlugs = new Set(slugs);
expect(uniqueSlugs.size).toBe(MOCK_CARDS_DATA.length); // Slug 必须唯一
});
it('2. 每张名片的基础信息必须完整且配有高分辨率头像与国际化联系方式', () => {
MOCK_CARDS_DATA.forEach((bundle: MockCardBundle) => {
const { card } = bundle;
expect(card.id).toBeDefined();
expect(card.name).toBeTruthy();
expect(card.title).toBeTruthy();
expect(card.company).toBeTruthy();
expect(card.slogan).toBeTruthy();
expect(card.status).toBe('published');
// 配图验证:头像必须是合法图片 URL (http/https)
expect(card.avatarUrl).toMatch(/^https?:\/\/.+\.(png|jpe?g|webp|svg)(\?.*)?$/i);
// 国际化联系方式
expect(card.phone).toMatch(/^\+[1-9]\d{1,14}$/); // E.164 规范
expect(card.email).toMatch(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
expect(card.timezone).toBeTruthy();
});
});
it('3. 每张名片必须包含有效的视频 Bento 块,且配有可播放视频流与专属海报封面', () => {
MOCK_CARDS_DATA.forEach((bundle: MockCardBundle) => {
const { bentoBlocks } = bundle;
expect(Array.isArray(bentoBlocks)).toBe(true);
expect(bentoBlocks.length).toBeGreaterThan(0);
// 必须包含至少一个 video 类型的 Bento 块
const videoBlocks = bentoBlocks.filter(b => b.type === 'video');
expect(videoBlocks.length).toBeGreaterThanOrEqual(1);
videoBlocks.forEach(videoBlock => {
expect(videoBlock.title).toBeTruthy();
// mediaUrl 作为视频封面海报,必须是合法图片 URL
expect(videoBlock.mediaUrl).toMatch(/^https?:\/\/.+\.(png|jpe?g|webp)(\?.*)?$/i);
// linkUrl 或 metaJson.videoUrl 必须是可播放的视频源 (mp4/m3u8/webm)
let videoUrl = videoBlock.linkUrl;
if (typeof videoBlock.metaJson === 'object' && videoBlock.metaJson !== null) {
videoUrl = (videoBlock.metaJson as any).videoUrl || videoUrl;
} else if (typeof videoBlock.metaJson === 'string') {
const parsed = JSON.parse(videoBlock.metaJson);
videoUrl = parsed.videoUrl || videoUrl;
}
expect(videoUrl).toMatch(/^https?:\/\/.+\.(mp4|webm|m3u8)(\?.*)?$/i);
});
});
});
it('4. 每张名片必须包含图文案例与文章,且均配置了真实多媒体封面配图', () => {
MOCK_CARDS_DATA.forEach((bundle: MockCardBundle) => {
// 检查案例
expect(Array.isArray(bundle.cases)).toBe(true);
expect(bundle.cases.length).toBeGreaterThanOrEqual(1);
bundle.cases.forEach(c => {
expect(c.title).toBeTruthy();
expect(c.client).toBeTruthy();
expect(c.coverImage).toMatch(/^https?:\/\/.+\.(png|jpe?g|webp)(\?.*)?$/i);
});
// 检查文章
expect(Array.isArray(bundle.articles)).toBe(true);
expect(bundle.articles.length).toBeGreaterThanOrEqual(1);
bundle.articles.forEach(a => {
expect(a.title).toBeTruthy();
expect(a.coverImage).toMatch(/^https?:\/\/.+\.(png|jpe?g|webp)(\?.*)?$/i);
});
});
});
it('5. 微官网服务 (CardPortalService) 能够无缝聚合各名片详情,正确渲染视频与图文块', async () => {
const cardInfoRepo: any = {
findOne: jest.fn().mockImplementation(async options => {
const slug = options?.where?.slug;
const id = options?.where?.id;
const found = MOCK_CARDS_DATA.find(
b => (slug && b.card.slug === slug) || (id && b.card.id === Number(id))
);
return found ? ({ ...found.card } as CardInfoEntity) : null;
}),
};
const cardBentoRepo: any = {
find: jest.fn().mockImplementation(async options => {
const cardId = options?.where?.cardId;
const found = MOCK_CARDS_DATA.find(b => b.card.id === Number(cardId));
return found ? found.bentoBlocks.map(blk => ({ ...blk } as CardBentoEntity)) : [];
}),
};
const portalService = new CardPortalService();
portalService.cardInfoEntity = cardInfoRepo;
portalService.cardBentoEntity = cardBentoRepo;
CardPortalService.clearAllCache();
for (const bundle of MOCK_CARDS_DATA) {
const detail = await portalService.getPortalDetail(bundle.card.slug);
expect(detail).toBeDefined();
expect(detail.name).toBe(bundle.card.name);
expect(detail.avatarUrl).toBe(bundle.card.avatarUrl);
// 验证视频块是否存在于公开微官网聚合结果中
const videoBlock = detail.bentoBlocks.find(b => b.type === 'video');
expect(videoBlock).toBeDefined();
expect(videoBlock?.mediaUrl).toBeTruthy();
}
});
});

查看文件

@@ -5,7 +5,7 @@ import {
ChannelAttributionEvent,
} from '../../../src/modules/crm/service/attribution';
describe('Point 46: 访客雷达多渠道来源归因与出海转化漏斗分析', () => {
describe('Point 46: 访客雷达多渠道来源归因与转化漏斗分析', () => {
let service: RadarAttributionService;
beforeEach(() => {
@@ -18,7 +18,7 @@ describe('Point 46: 访客雷达多渠道来源归因与出海转化漏斗分析
expect(normalizeTrafficChannel('booth', 'qrcode')).toBe('booth_qr');
});
it('应精准识别 WhatsApp 与 LinkedIn 社交出海渠道', () => {
it('应精准识别 WhatsApp 与 LinkedIn 国际社交渠道', () => {
expect(normalizeTrafficChannel('whatsapp', 'im')).toBe('whatsapp_chat');
expect(normalizeTrafficChannel('linkedin', 'social')).toBe('linkedin_share');
expect(normalizeTrafficChannel('email', 'sig')).toBe('email_signature');

查看文件

@@ -5,7 +5,7 @@ import {
TrafficFilterResult,
} from '../../../src/modules/crm/service/bot-filter';
describe('Point 63 (P2): 访客雷达异常流量与出海社交抓取机器爬虫清洗过滤', () => {
describe('Point 63 (P2): 访客雷达异常流量与社交抓取机器爬虫清洗过滤', () => {
let service: RadarBotFilterService;
beforeEach(() => {

查看文件

@@ -5,7 +5,7 @@ import { AdminCrmLeadController } from '../../../src/modules/crm/controller/admi
import { CrmLeadEntity } from '../../../src/modules/crm/entity/lead';
import { RadarTrack } from '../../../src/comm/types/omni';
describe('【BTP-20】线索流转自动化与出海 Webhook / 实时推送集成测试', () => {
describe('【BTP-20】线索流转自动化与外部 Webhook / 实时推送集成测试', () => {
let syncService: CrmSyncService;
let pushService: CrmRadarPushService;
let mockLead: CrmLeadEntity;

查看文件

@@ -5,7 +5,7 @@ import {
GdprUserDataBundle,
} from '../../../src/modules/crm/service/compliance';
describe('Point 53: 海外合规 GDPR / CCPA 访客数据匿名化脱敏与被遗忘权导出', () => {
describe('Point 53: 国际合规 GDPR / CCPA 访客数据匿名化脱敏与被遗忘权导出', () => {
let service: ComplianceGdprService;
beforeEach(() => {

查看文件

@@ -6,7 +6,7 @@ import {
CompanyProfileResult,
} from '../../../src/modules/crm/service/enrichment';
describe('Point 49: 出海潜客企业邮箱域名解析与商业主页自动富化', () => {
describe('Point 49: 潜客企业邮箱域名解析与商业主页自动富化', () => {
let service: LeadCompanyEnrichmentService;
beforeEach(() => {

查看文件

@@ -13,7 +13,7 @@ describe('Point 55: 全球访客雷达国家城市坐标聚合与热力散点引
service = new RadarGeoHeatmapService();
});
describe('1. resolveCityCoordinates (出海核心商务枢纽城市经纬度解析)', () => {
describe('1. resolveCityCoordinates (全球核心商务枢纽城市经纬度解析)', () => {
it('应精准解析迪拜、利雅得、新加坡与伦敦经纬度', () => {
const dubai = resolveCityCoordinates('AE', 'Dubai');
expect(dubai).toEqual([55.27, 25.20]);

查看文件

@@ -5,7 +5,7 @@ import {
LeadBehaviorTrigger,
} from '../../../src/modules/crm/service/lead-automation';
describe('Point 58 (P1): 出海潜客线索行为自动化阶段推进引擎', () => {
describe('Point 58 (P1): 潜客线索行为自动化阶段推进引擎', () => {
let service: LeadStageAutomationService;
beforeEach(() => {

查看文件

@@ -54,7 +54,7 @@ describe('多租户安全闭环: 公开留资与雷达足迹自动绑定名片
it('公开留资未显式传入 tenantId 时,必须根据 cardId 自动追溯绑定归属名片的 tenantId 和 userId', async () => {
const lead = await leadService.submitLead({
cardId: 101,
name: '海外采购商',
name: '国际采购商',
phone: '+971-50-9876543',
company: 'Dubai Trading LLC',
});
@@ -68,7 +68,7 @@ describe('多租户安全闭环: 公开留资与雷达足迹自动绑定名片
const lead = await leadService.submitLead({
cardId: 101,
tenantId: 999,
name: '海外采购商',
name: '国际采购商',
phone: '+971-50-9876543',
} as any);

查看文件

@@ -5,7 +5,7 @@ import {
LeadWebhookPayload,
} from '../../../src/modules/crm/service/webhook-dispatcher';
describe('Point 51: 出海商机实时 Webhook 触发器与 HMAC-SHA256 签名分发', () => {
describe('Point 51: 商机实时 Webhook 触发器与 HMAC-SHA256 签名分发', () => {
let dispatcher: LeadWebhookDispatcher;
const mockSecret = 'whsec_8f9a2b4c6e1d7g5h3j8k9l0m';
const mockPayload: LeadWebhookPayload = {

查看文件

@@ -1,6 +1,7 @@
import { AdminCrmRadarController } from '../../../src/modules/crm/controller/admin/radar';
import {
buildWorkplaceOverview,
calculateTrackIntentScore,
CrmRadarService,
} from '../../../src/modules/crm/service/radar';
import { CrmTrackEntity } from '../../../src/modules/crm/entity/track';
@@ -152,4 +153,97 @@ describe('商务概览工作台聚合 (getWorkplaceOverview)', () => {
expect(res.data.kpis.viewCount).toBe(2);
expect(controller.crmRadarService.getWorkplaceOverview).toHaveBeenCalled();
});
describe('数据专家重构:科学意向度与会话降噪增强', () => {
it('科学意向分算法 (calculateTrackIntentScore) 必须符合多因子阶梯模型', () => {
// 1. 直链 0 秒跳出,无任何浏览与滚动:低意向分 (10分)
const bounceScore = calculateTrackIntentScore({
touchType: 'link',
stayDurationMs: 0,
scrollDepth: 0,
viewedBlocks: [],
});
expect(bounceScore).toBe(10);
// 2. NFC 面对面触碰初始分:天然高权重 (35分)
const nfcInitialScore = calculateTrackIntentScore({
touchType: 'nfc',
stayDurationMs: 0,
});
expect(nfcInitialScore).toBe(35);
// 3. QR 扫码 + 30秒研读 + 2个模块 + 深度滚动:高意向买家 (25 + 20 + 10 + 10 = 65分)
const engagedScore = calculateTrackIntentScore({
touchType: 'qr',
stayDurationMs: 30000,
viewedBlocks: ['hero', 'products'],
scrollDepth: 75,
});
expect(engagedScore).toBe(65);
// 4. NFC 触碰 + 150秒深读 + 4个模块 + 深度滚动:极高意向买家 (封顶 99分)
const hotScore = calculateTrackIntentScore({
touchType: 'nfc',
stayDurationMs: 150000,
viewedBlocks: ['hero', 'products', 'cases', 'contact'],
scrollDepth: 95,
});
expect(hotScore).toBe(99);
});
it('buildWorkplaceOverview 应实现会话去噪与合并,并填充名片名称和本地标记', () => {
const cardList = [
{ id: 1, name: 'Alex Chen', title: '全球商务总监' },
];
// 同一访客 vis_101 先产生了 0 秒 PV,又更新了 45 秒有效停留
const t1 = track({
id: 10,
cardId: 1,
touchType: 'link',
country: 'Local',
city: 'Local',
stayDurationMs: 0,
createTime: new Date('2026-09-21T10:00:00'),
});
t1.visitorId = 'vis_101';
const t2 = track({
id: 11,
cardId: 1,
touchType: 'link',
country: 'Local',
city: 'Local',
stayDurationMs: 45000,
viewedBlocks: ['products'],
createTime: new Date('2026-09-21T10:01:00'),
});
t2.visitorId = 'vis_101';
const result = buildWorkplaceOverview(
[t1, t2],
[],
now,
10,
cardList
);
// 验证 1:去噪合并后同一会话只保留 1 条记录
expect(result.radarFeed).toHaveLength(1);
const feedItem = result.radarFeed[0];
// 验证 2:停留时长采用合并后的最高值 45 秒
expect(feedItem.stayDurationMs).toBe(45000);
expect(feedItem.viewedBlocks).toContain('products');
// 验证 3:名片名称正确注入
expect(feedItem.cardTitle).toBe('Alex Chen · 全球商务总监');
// 验证 4:识别为本地环境
expect(feedItem.isLocal).toBe(true);
// 验证 5:意向分由后端自动算出并大于 0 秒跳出分
expect(feedItem.intentScore).toBeGreaterThan(10);
});
});
});

查看文件

@@ -6,7 +6,7 @@ import {
canTransitionBatchStatus,
} from '../../../src/modules/nfc/service/manufacturing';
describe('Point 45: 跨境实体卡供应链生产批次工单生命周期与良率检验', () => {
describe('Point 45: 国际实体卡供应链生产批次工单生命周期与良率检验', () => {
let service: NfcManufacturingService;
beforeEach(() => {

查看文件

@@ -5,7 +5,7 @@ import {
} from '../../../src/modules/team/service/currency-tax';
import { TaxCalculationResult } from '../../../src/comm/types/omni';
describe('Point 57 (P1): 跨境多币种汇率换算与出海发票税制计算', () => {
describe('Point 57 (P1): 跨国多币种汇率换算与国际发票税制计算', () => {
let service: CurrencyTaxCalculatorService;
beforeEach(() => {
@@ -35,7 +35,7 @@ describe('Point 57 (P1): 跨境多币种汇率换算与出海发票税制计算'
});
});
describe('2. calculateInternationalTax (出海合规发票税制计算)', () => {
describe('2. calculateInternationalTax (国际合规发票税制计算)', () => {
it('阿联酋企业或个人消费应按 5% VAT 计税', () => {
const res = calculateInternationalTax({
amount: 1000,
@@ -50,7 +50,7 @@ describe('Point 57 (P1): 跨境多币种汇率换算与出海发票税制计算'
expect(res.taxName).toBe('UAE VAT');
});
it('欧盟企业间跨境 B2B (Reverse Charge) 应适用反向征税 0% VAT', () => {
it('欧盟企业间跨国 B2B (Reverse Charge) 应适用反向征税 0% VAT', () => {
const res = calculateInternationalTax({
amount: 2000,
currency: 'EUR',

查看文件

@@ -5,7 +5,7 @@ import {
assertLeadAccessPermission,
} from '../../../src/modules/team/service/data-scope';
describe('Point 52: 出海多租户团队成员细粒度数据隔离与销售管辖范围保护', () => {
describe('Point 52: 多租户团队成员细粒度数据隔离与销售管辖范围保护', () => {
let service: TeamDataScopeService;
beforeEach(() => {

查看文件

@@ -44,7 +44,7 @@ describe('【财务与运营新账统计】TeamFinanceAnalyticsService 单元测
expect(Array.isArray(result.planDistribution)).toBe(true);
expect(result.planDistribution.some((item) => item.plan === 'pro')).toBe(true);
// 出海区域分布
// 国际区域分布
expect(Array.isArray(result.regionalRevenue)).toBe(true);
// 流水明细

查看文件

@@ -103,7 +103,7 @@ describe('Point 19: 离职资产交接工单流程与历史线索迁移 单元
lead1.id = 1;
lead1.userId = 1001;
lead1.cardId = 101;
lead1.name = '重点出海客户 A';
lead1.name = '重点合作客户 A';
lead1.isPool = false;
lead1.status = 'contacted';
leads.push(lead1);

查看文件

@@ -91,7 +91,7 @@ describe('Point 28: 展会现场团队获客实时排行榜大屏广播 (Live Ra
u1.id = 101;
u1.name = 'Alex Rivera';
u1.tenantId = 1;
u1.departmentName = '出海一队';
u1.departmentName = '国际业务一队';
const c1 = new CardInfoEntity();
c1.id = 1;
@@ -103,7 +103,7 @@ describe('Point 28: 展会现场团队获客实时排行榜大屏广播 (Live Ra
u2.id = 102;
u2.name = 'Sarah Wang';
u2.tenantId = 1;
u2.departmentName = '出海二队';
u2.departmentName = '国际业务二队';
const c2 = new CardInfoEntity();
c2.id = 2;

查看文件

@@ -47,7 +47,7 @@ describe('Point 17: 后台组织架构、部门树与成员名片分配 (Team Me
users = [];
cards = [];
// 预置部门数据:总部 (1) -> 研发中心 (2) -> 前端组 (3); 总部 (1) -> 销售出海中心 (4)
// 预置部门数据:总部 (1) -> 研发中心 (2) -> 前端组 (3); 总部 (1) -> 国际业务拓展部 (4)
const d1 = new BaseSysDepartmentEntity();
d1.id = 1;
d1.name = '全球商务总部';
@@ -56,7 +56,7 @@ describe('Point 17: 后台组织架构、部门树与成员名片分配 (Team Me
const d2 = new BaseSysDepartmentEntity();
d2.id = 2;
d2.name = '海外研发中心';
d2.name = '国际研发中心';
d2.parentId = 1;
d2.orderNum = 10;
@@ -68,7 +68,7 @@ describe('Point 17: 后台组织架构、部门树与成员名片分配 (Team Me
const d4 = new BaseSysDepartmentEntity();
d4.id = 4;
d4.name = '出海业务拓展部';
d4.name = '国际业务拓展部';
d4.parentId = 1;
d4.orderNum = 20;
@@ -201,7 +201,7 @@ describe('Point 17: 后台组织架构、部门树与成员名片分配 (Team Me
expect(root.title).toBe('全球商务总部');
expect(root.children?.length).toBe(2);
// children 排序应为 海外研发中心 (orderNum: 10) 和 出海业务拓展部 (orderNum: 20)
// children 排序应为 国际研发中心 (orderNum: 10) 和 国际业务拓展部 (orderNum: 20)
expect(root.children?.[0].id).toBe(2);
expect(root.children?.[0].children?.length).toBe(1); // 前端组 (3)
expect(root.children?.[0].children?.[0].id).toBe(3);
@@ -211,7 +211,7 @@ describe('Point 17: 后台组织架构、部门树与成员名片分配 (Team Me
// 人数统计验证
expect(root.memberCount).toBe(1); // u1 在总部
expect(root.children?.[0].children?.[0].memberCount).toBe(1); // u2 在前端组
expect(root.children?.[1].memberCount).toBe(1); // u3 在出海业务部
expect(root.children?.[1].memberCount).toBe(1); // u3 在国际业务部
});
it('2.2 addDepartment 校验部门名称必填并成功入库', async () => {