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

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

查看文件

@@ -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();
}
});
});