refactor(i18n): 全工程面向国际用户与全球现代商务重构,彻底清理出海跨境等单向视角表达并对齐自动化测试
这个提交包含在:
@@ -78,7 +78,7 @@ const i18n = {
|
||||
'menu.card': '数字名片',
|
||||
'menu.card.list': '名片资产库',
|
||||
'menu.card.builder': 'Bento 搭建器',
|
||||
'menu.card.cases': '出海案例作品集',
|
||||
'menu.card.cases': '案例作品集',
|
||||
'menu.card.articles': '文章与动态',
|
||||
'menu.crm': '访客雷达',
|
||||
'menu.crm.leads': '潜客线索',
|
||||
|
||||
@@ -66,9 +66,9 @@ interface PersonaPresetDetail {
|
||||
const PRESET_DATA: Record<ConciergePersonaType, PersonaPresetDetail> = {
|
||||
trade_expert: {
|
||||
welcomeMsg:
|
||||
'您好!我是您的专属出海业务顾问。请问有什么全球贸易或合作需求可以协助您?',
|
||||
'您好!我是您的专属商务顾问。请问有什么业务咨询或国际合作需求可以协助您?',
|
||||
promptTemplate:
|
||||
'你是一名深耕国际贸易与跨国商务的出海业务专家。用专业、沉稳的商务语调为访客解答产品出海、跨境结算、供应链合作等咨询。引导有明确意向的客商提供联系方式。',
|
||||
'你是一名深耕全球商务与跨国合作的业务顾问。用专业、沉稳的商务语调为访客解答业务合作、跨国结算、供应链方案等咨询。引导有明确意向的客商提供联系方式。',
|
||||
toneStyle: 'professional',
|
||||
triggerLeadThreshold: 70,
|
||||
},
|
||||
|
||||
@@ -77,9 +77,9 @@ const i18n = {
|
||||
'aiConcierge.form.cardSelect': '关联名片',
|
||||
'aiConcierge.form.cardSelect.placeholder': '选择或输入名片 ID',
|
||||
'aiConcierge.form.persona': '预设人设',
|
||||
'aiConcierge.persona.trade_expert': '外贸专家',
|
||||
'aiConcierge.persona.trade_expert': '国际贸易顾问',
|
||||
'aiConcierge.persona.trade_expert.desc':
|
||||
'专精跨境贸易、供应链对接与外贸商务咨询,语调稳重专业。',
|
||||
'专精全球贸易、供应链对接与国际商务咨询,语调稳重专业。',
|
||||
'aiConcierge.persona.business_rep': '商务代表',
|
||||
'aiConcierge.persona.business_rep.desc':
|
||||
'主动介绍企业竞争优势与落地案例,敏锐捕捉客商商机。',
|
||||
@@ -115,7 +115,7 @@ const i18n = {
|
||||
'aiConcierge.request.saveFail': '保存配置失败',
|
||||
'aiDeepSeek.card.title': 'DeepSeek 官方大模型底座配置',
|
||||
'aiDeepSeek.card.desc':
|
||||
'配置 DeepSeek API Key,支持 deepseek-chat 与 deepseek-reasoner 双模型接入,赋能 24/7 AI 顾问对话与出海多语言润色。',
|
||||
'配置 DeepSeek API Key,支持 deepseek-chat 与 deepseek-reasoner 双模型接入,赋能 24/7 AI 顾问对话与多语言智能润色。',
|
||||
'aiDeepSeek.form.apiKey': 'API Key 密钥',
|
||||
'aiDeepSeek.form.apiKey.placeholder':
|
||||
'输入 sk- 开头的官方密钥 (留空则保留当前已配置密钥)',
|
||||
|
||||
@@ -53,9 +53,9 @@ const i18n = {
|
||||
},
|
||||
'zh-CN': {
|
||||
'menu.ai.translation': '润色与术语库',
|
||||
'aiTranslation.playground.title': '出海文案实时润色试炼器',
|
||||
'aiTranslation.playground.title': '商务文案实时润色试炼器',
|
||||
'aiTranslation.playground.text': '待润色文本',
|
||||
'aiTranslation.playground.text.placeholder': '输入待润色的出海商务文案,系统将自动应用语气模板并强制对齐行业术语...',
|
||||
'aiTranslation.playground.text.placeholder': '输入待润色的商务文案,系统将自动应用语气模板并强制对齐行业术语...',
|
||||
'aiTranslation.playground.targetLocale': '目标语言',
|
||||
'aiTranslation.playground.targetLocale.placeholder': '请选择目标语言',
|
||||
'aiTranslation.playground.tone': '语气基调',
|
||||
|
||||
@@ -520,7 +520,7 @@ export default function CardArticlesPage() {
|
||||
field="title"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input placeholder="输入出海深度文章标题..." maxLength={120} showWordLimit />
|
||||
<Input placeholder="输入深度文章标题..." maxLength={120} showWordLimit />
|
||||
</Form.Item>
|
||||
<Form.Item label={t['cardArticles.field.summary']} field="summary">
|
||||
<Input.TextArea
|
||||
@@ -593,7 +593,7 @@ export default function CardArticlesPage() {
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
|
||||
{/* 模拟出海社交平台卡片 (LinkedIn / WhatsApp / X Card) */}
|
||||
{/* 模拟国际社交平台卡片 (LinkedIn / WhatsApp / X Card) */}
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: lightColors.bg,
|
||||
|
||||
@@ -43,7 +43,7 @@ const i18n = {
|
||||
'zh-CN': {
|
||||
'menu.card.articles': '文章与动态',
|
||||
'cardArticles.title': '文章与动态发布中心',
|
||||
'cardArticles.description': '为名片微官网发布深度博客与动态资讯,具备 OpenGraph 社交卡片自动提炼与出海 SEO 优化。',
|
||||
'cardArticles.description': '为名片微官网发布深度博客与动态资讯,具备 OpenGraph 社交卡片自动提炼与全球搜索引擎 SEO 优化。',
|
||||
'cardArticles.action.add': '发布新文章',
|
||||
'cardArticles.action.edit': '编辑',
|
||||
'cardArticles.action.delete': '删除',
|
||||
|
||||
@@ -70,7 +70,7 @@ interface CaseEditFormData {
|
||||
const PRESET_METRIC_TAGS: string[] = [
|
||||
'+320% ROI',
|
||||
'+150% 留资率',
|
||||
'45k+ 海外线索',
|
||||
'45k+ 国际线索',
|
||||
'2M+ 全球曝光',
|
||||
'Top 1 细分类目',
|
||||
];
|
||||
|
||||
@@ -38,10 +38,10 @@ const i18n = {
|
||||
'cases.viewPdf': 'View PDF',
|
||||
},
|
||||
'zh-CN': {
|
||||
'cases.pageTitle': '出海案例作品集',
|
||||
'cases.pageSubTitle': '面向海外客商展示出海标杆案例、量化成果指标与交付白皮书。',
|
||||
'cases.pageTitle': '标杆案例作品集',
|
||||
'cases.pageSubTitle': '面向全球合作伙伴与客商展示标杆案例、量化成果指标与交付白皮书。',
|
||||
'cases.title': '案例标题',
|
||||
'cases.titlePlaceholder': '如: 智能滑板车北美 DTC 独立站出海',
|
||||
'cases.titlePlaceholder': '如: 智能出行品牌全球 DTC 独立站建设',
|
||||
'cases.client': '客户品牌',
|
||||
'cases.clientPlaceholder': '如: VoltRide Global',
|
||||
'cases.duration': '交付周期',
|
||||
@@ -56,11 +56,11 @@ const i18n = {
|
||||
'cases.cardIdPlaceholder': '按名片 ID 筛选',
|
||||
'cases.sortOrder': '排序序号',
|
||||
'cases.description': '案例背景与简述',
|
||||
'cases.descriptionPlaceholder': '请输入项目背景、出海痛点与交付成果...',
|
||||
'cases.descriptionPlaceholder': '请输入项目背景、核心挑战与交付成果...',
|
||||
'cases.add': '新增案例',
|
||||
'cases.edit': '编辑案例',
|
||||
'cases.delete': '删除',
|
||||
'cases.deleteConfirm': '确定要删除该出海案例作品吗?',
|
||||
'cases.deleteConfirm': '确定要删除该案例作品吗?',
|
||||
'cases.search': '查询',
|
||||
'cases.reset': '重置',
|
||||
'cases.actions': '操作',
|
||||
@@ -69,8 +69,8 @@ const i18n = {
|
||||
'cases.uploadText': '点击或拖拽文件上传',
|
||||
'cases.uploadHint': '支持 JPG、PNG、WEBP 或 PDF 格式,单文件不超过 20MB',
|
||||
'cases.tagAdd': '添加 KPI 标签',
|
||||
'cases.emptyTitle': '暂无出海案例',
|
||||
'cases.emptyDesc': '创建首个标杆出海作品集,提升海外客商信任与转化效率。',
|
||||
'cases.emptyTitle': '暂无案例作品',
|
||||
'cases.emptyDesc': '创建首个标杆作品集,提升全球客商信任与转化效率。',
|
||||
'cases.operationSuccess': '操作已成功保存。',
|
||||
'cases.deleteSuccess': '案例已成功删除。',
|
||||
'cases.viewPdf': '查看 PDF',
|
||||
|
||||
@@ -531,7 +531,7 @@ export default function DashboardFinancialPage() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* COO 模块 B: 全球出海目标市场营收分布 */}
|
||||
{/* COO 模块 B: 全球目标市场营收分布 */}
|
||||
<Grid.Col xs={24} lg={12}>
|
||||
<Card
|
||||
title={t['dashboardFinancial.section.regionalRevenue']}
|
||||
|
||||
@@ -53,7 +53,7 @@ const i18n = {
|
||||
'dashboardFinancial.export.success': 'Reconciliation ledger exported successfully',
|
||||
},
|
||||
'zh-CN': {
|
||||
'dashboardFinancial.title': '新账与出海财务运营驾驶舱',
|
||||
'dashboardFinancial.title': '全球财务与商业化运营驾驶舱',
|
||||
'dashboardFinancial.subtitle': '全球 SaaS 商业化营收、新账拓客扩张与财务对账核销',
|
||||
'dashboardFinancial.view.coo': '运营官视角 (业务增长与扩容)',
|
||||
'dashboardFinancial.view.cfo': '财务官视角 (现金流与财税风控)',
|
||||
@@ -73,7 +73,7 @@ const i18n = {
|
||||
'dashboardFinancial.kpi.pendingReconciliation': '在途待对账款',
|
||||
'dashboardFinancial.kpi.overdueAmount': '账期逾期款项',
|
||||
'dashboardFinancial.kpi.refundAmount': '退款与争议款',
|
||||
'dashboardFinancial.kpi.estimatedTax': '跨境 VAT 预估税额',
|
||||
'dashboardFinancial.kpi.estimatedTax': '国际 VAT 预估税额',
|
||||
// COO 指标
|
||||
'dashboardFinancial.kpi.mrr': '月度经常性收入 (MRR)',
|
||||
'dashboardFinancial.kpi.arr': '年度经常性收入 (ARR)',
|
||||
@@ -83,7 +83,7 @@ const i18n = {
|
||||
'dashboardFinancial.kpi.churnRate': '客户流失率',
|
||||
// 面板标题
|
||||
'dashboardFinancial.section.planDistribution': 'SaaS 订阅套餐梯队分布',
|
||||
'dashboardFinancial.section.regionalRevenue': '全球出海目标市场营收分布',
|
||||
'dashboardFinancial.section.regionalRevenue': '全球目标市场营收分布',
|
||||
'dashboardFinancial.section.currencyBreakdown': '国际多币种汇总结算结构',
|
||||
'dashboardFinancial.section.agingAnalysis': '应收账款账龄与坏账风险模型',
|
||||
'dashboardFinancial.section.recentLedger': '最新新账与开票明细台账',
|
||||
|
||||
@@ -45,7 +45,7 @@ const i18n = {
|
||||
},
|
||||
'zh-CN': {
|
||||
'radarMap.title': '全球访客雷达热力大屏',
|
||||
'radarMap.subtitle': '出海商机实时地理轨迹、交互时长深度与多渠道触碰转化大屏。',
|
||||
'radarMap.subtitle': '全球商机实时地理轨迹、交互时长深度与多渠道触碰转化大屏。',
|
||||
'radarMap.refresh': '刷新数据',
|
||||
'radarMap.filter.allCards': '全部名片',
|
||||
'radarMap.filter.cardIdPlaceholder': '筛选名片 ID',
|
||||
|
||||
@@ -84,20 +84,87 @@ function formatDelta(value: number): string {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function formatStaySeconds(ms: number): string {
|
||||
return String(Math.max(0, Math.round(ms / 1000)));
|
||||
function formatRelativeTime(
|
||||
createTime: Date | string | undefined,
|
||||
t: Record<string, string>
|
||||
): string {
|
||||
if (!createTime) return t['workplace.radar.justNow'] || 'Just now';
|
||||
const diffMs = Date.now() - new Date(createTime).getTime();
|
||||
if (Number.isNaN(diffMs) || diffMs < 60000) {
|
||||
return t['workplace.radar.justNow'] || 'Just now';
|
||||
}
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
if (diffMins < 60) {
|
||||
return (t['workplace.radar.minsAgo'] || '{mins}m ago').replace(
|
||||
'{mins}',
|
||||
String(diffMins)
|
||||
);
|
||||
}
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
if (diffHours < 24) {
|
||||
return (t['workplace.radar.hoursAgo'] || '{hours}h ago').replace(
|
||||
'{hours}',
|
||||
String(diffHours)
|
||||
);
|
||||
}
|
||||
const date = new Date(createTime);
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function formatLocation(
|
||||
item: WorkplaceRadarItem,
|
||||
t: Record<string, string>
|
||||
): string {
|
||||
if (item.isLocal || item.country === 'Local' || item.city === 'Local') {
|
||||
return t['workplace.radar.localEnv'] || 'Local / Intranet';
|
||||
}
|
||||
const parts = [item.city, item.country].filter(
|
||||
(p) => Boolean(p) && p !== 'Unknown'
|
||||
);
|
||||
if (parts.length === 0) {
|
||||
return item.country || '—';
|
||||
}
|
||||
return parts.join(', ');
|
||||
}
|
||||
|
||||
function formatStayText(
|
||||
stayDurationMs: number,
|
||||
t: Record<string, string>
|
||||
): string {
|
||||
const sec = Math.max(0, Math.round(stayDurationMs / 1000));
|
||||
if (sec <= 3) {
|
||||
return t['workplace.radar.bounce'] || 'Quick bounce';
|
||||
}
|
||||
return (t['workplace.radar.stay'] || '{seconds}s').replace(
|
||||
'{seconds}',
|
||||
String(sec)
|
||||
);
|
||||
}
|
||||
|
||||
function computeIntentScore(item: WorkplaceRadarItem): number {
|
||||
if (typeof item.intentScore === 'number' && item.intentScore > 0) {
|
||||
return item.intentScore;
|
||||
}
|
||||
const staySec = Math.round(item.stayDurationMs / 1000);
|
||||
const blocksCount = item.viewedBlocks?.length || 0;
|
||||
let score = 40;
|
||||
if (staySec > 30) score += 20;
|
||||
if (staySec > 90) score += 20;
|
||||
score += Math.min(blocksCount * 10, 20);
|
||||
let score = 10;
|
||||
if (item.touchType === 'nfc') score = 35;
|
||||
else if (item.touchType === 'qr') score = 25;
|
||||
if (staySec > 3) score += Math.min(40, Math.round(staySec / 3));
|
||||
score += Math.min(blocksCount * 5, 20);
|
||||
return Math.min(score, 99);
|
||||
}
|
||||
|
||||
function getIntentTagColor(score: number): string {
|
||||
if (score >= 75) return 'green';
|
||||
if (score >= 45) return 'arcoblue';
|
||||
return 'gray';
|
||||
}
|
||||
|
||||
function escapeCsvCell(value: string): string {
|
||||
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
||||
return `"${value.replace(/"/g, '""')}"`;
|
||||
@@ -424,7 +491,7 @@ function Workplace(): React.ReactElement {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 出海转化漏斗 */}
|
||||
{/* 业务转化漏斗 */}
|
||||
<Card
|
||||
bordered={false}
|
||||
className={`${styles.panel} ${styles.funnelCard}`}
|
||||
@@ -530,29 +597,45 @@ function Workplace(): React.ReactElement {
|
||||
const touchType = isTouchType(item.touchType)
|
||||
? item.touchType
|
||||
: 'link';
|
||||
const locationLabel = [item.city, item.country]
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
const locationLabel = formatLocation(item, t);
|
||||
const blocks = (item.viewedBlocks || []).join(' / ');
|
||||
const intentScore = computeIntentScore(item);
|
||||
const cardLabel =
|
||||
item.cardTitle ||
|
||||
(item.cardId
|
||||
? t['workplace.radar.cardFallback'].replace('{id}', String(item.cardId))
|
||||
: '');
|
||||
const timeLabel = formatRelativeTime(item.createTime, t);
|
||||
|
||||
return (
|
||||
<Timeline.Item key={String(item.id)}>
|
||||
<div className={styles.feedMeta}>
|
||||
<Typography.Text bold>{locationLabel || '—'}</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
style={{ fontSize: 12, marginRight: 2 }}
|
||||
>
|
||||
{timeLabel}
|
||||
</Typography.Text>
|
||||
{cardLabel ? (
|
||||
<Tag color="arcoblue" size="small" style={{ borderRadius: 9999 }}>
|
||||
{cardLabel}
|
||||
</Tag>
|
||||
) : null}
|
||||
<Typography.Text bold>{locationLabel}</Typography.Text>
|
||||
<Tag color={getChannelColor(touchType)} size="small">
|
||||
{getChannelLabel(touchType)}
|
||||
</Tag>
|
||||
<Tag
|
||||
color={intentScore >= 70 ? 'green' : 'gray'}
|
||||
color={getIntentTagColor(intentScore)}
|
||||
size="small"
|
||||
>
|
||||
{t['workplace.radar.intentScore'].replace('{score}', String(intentScore))}
|
||||
{t['workplace.radar.intentScore'].replace(
|
||||
'{score}',
|
||||
String(intentScore)
|
||||
)}
|
||||
</Tag>
|
||||
<Typography.Text type="secondary" className={styles.stayTime}>
|
||||
{t['workplace.radar.stay'].replace(
|
||||
'{seconds}',
|
||||
formatStaySeconds(item.stayDurationMs)
|
||||
)}
|
||||
{formatStayText(item.stayDurationMs, t)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{blocks ? (
|
||||
@@ -568,7 +651,7 @@ function Workplace(): React.ReactElement {
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 右侧出海运营快捷中枢 (7/24 栅格,完美填补用户红框空白) */}
|
||||
{/* 右侧全球运营快捷中枢 (7/24 栅格,完美填补用户红框空白) */}
|
||||
<Col xs={24} xl={7}>
|
||||
<div className={styles.hubContainer}>
|
||||
{/* 模块 1:触达渠道健康度 */}
|
||||
@@ -773,7 +856,7 @@ function Workplace(): React.ReactElement {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 模块 4:出海待办预警 */}
|
||||
{/* 模块 4:业务待办预警 */}
|
||||
<Card
|
||||
bordered={false}
|
||||
className={`${styles.panel} ${styles.alertPanel}`}
|
||||
|
||||
@@ -20,6 +20,12 @@ const i18n = {
|
||||
'workplace.radar.emptyAction': 'Open global radar',
|
||||
'workplace.radar.stay': '{seconds}s stay',
|
||||
'workplace.radar.viewing': 'Viewing {blocks}',
|
||||
'workplace.radar.localEnv': 'Local / Intranet Test',
|
||||
'workplace.radar.justNow': 'Just now',
|
||||
'workplace.radar.minsAgo': '{mins}m ago',
|
||||
'workplace.radar.hoursAgo': '{hours}h ago',
|
||||
'workplace.radar.bounce': 'Quick bounce',
|
||||
'workplace.radar.cardFallback': 'Card #{id}',
|
||||
'workplace.funnel.title': 'Conversion Funnel',
|
||||
'workplace.funnel.tap': 'Physical NFC',
|
||||
'workplace.funnel.view': 'Page Views',
|
||||
@@ -85,20 +91,26 @@ const i18n = {
|
||||
'workplace.radar.emptyAction': '查看全球雷达',
|
||||
'workplace.radar.stay': '停留 {seconds} 秒',
|
||||
'workplace.radar.viewing': '正在查看 {blocks}',
|
||||
'workplace.funnel.title': '出海转化漏斗',
|
||||
'workplace.radar.localEnv': '本地/局域网测试',
|
||||
'workplace.radar.justNow': '刚刚',
|
||||
'workplace.radar.minsAgo': '{mins} 分钟前',
|
||||
'workplace.radar.hoursAgo': '{hours} 小时前',
|
||||
'workplace.radar.bounce': '快速跳出',
|
||||
'workplace.radar.cardFallback': '名片 #{id}',
|
||||
'workplace.funnel.title': '业务转化漏斗',
|
||||
'workplace.funnel.tap': 'NFC 触碰',
|
||||
'workplace.funnel.view': '官网浏览',
|
||||
'workplace.funnel.interact': '意向交互',
|
||||
'workplace.funnel.lead': '商机留资',
|
||||
'workplace.channel.title': '触达渠道分布',
|
||||
'workplace.quickHub.title': '出海运营快捷中枢',
|
||||
'workplace.quickHub.title': '全球运营快捷中枢',
|
||||
'workplace.quickNav.title': '高频业务导航',
|
||||
'workplace.quick.builder': '搭建微官网',
|
||||
'workplace.quick.builderHint': '编辑便当盒微官网组件',
|
||||
'workplace.quick.leads': '潜客线索',
|
||||
'workplace.quick.leadsHint': '跟进出海海外意向买家',
|
||||
'workplace.quick.leadsHint': '跟进高意向客户与买家',
|
||||
'workplace.quick.pipeline': '商机看板',
|
||||
'workplace.quick.pipelineHint': '推进出海商机全流程赢单',
|
||||
'workplace.quick.pipelineHint': '推进全球商机全流程赢单',
|
||||
'workplace.quick.batch': '批量制卡',
|
||||
'workplace.quick.batchHint': '生成短链与批量印制物料',
|
||||
'workplace.quick.devices': '硬件台账',
|
||||
@@ -108,7 +120,7 @@ const i18n = {
|
||||
'workplace.quick.aiConcierge': 'AI 顾问',
|
||||
'workplace.quick.aiConciergeHint': '多语言接待策略与留资阈值',
|
||||
'workplace.quick.finance': '财务订阅',
|
||||
'workplace.quick.financeHint': '订阅套餐与出海营收看板',
|
||||
'workplace.quick.financeHint': '订阅套餐与全球营收看板',
|
||||
'workplace.shortcut.title': '运营效率工具箱',
|
||||
'workplace.shortcut.issueCard': '批量发卡',
|
||||
'workplace.shortcut.issueCardHint': '生成短链与制卡打印物料',
|
||||
@@ -118,7 +130,7 @@ const i18n = {
|
||||
'workplace.shortcut.radarMapHint': '查看全球访客地理分布与实时热力图',
|
||||
'workplace.shortcut.aiConfig': 'AI 顾问接待策略',
|
||||
'workplace.shortcut.aiConfigHint': '配置多语言接待话术与自动留资意向阈值',
|
||||
'workplace.alerts.title': '出海待办预警',
|
||||
'workplace.alerts.title': '业务待办预警',
|
||||
'workplace.alerts.highIntentLead': '迪拜客商在精选案例停留超过 210 秒,尚未安排专属跟进。',
|
||||
'workplace.alerts.actionFollow': '立即跟进',
|
||||
'workplace.channel.nfc': 'NFC',
|
||||
@@ -150,6 +162,12 @@ const i18n = {
|
||||
'workplace.radar.emptyAction': 'فتح الرادار العالمي',
|
||||
'workplace.radar.stay': 'مكث {seconds} ث',
|
||||
'workplace.radar.viewing': 'يشاهد {blocks}',
|
||||
'workplace.radar.localEnv': 'بيئة اختبار محلية',
|
||||
'workplace.radar.justNow': 'الآن',
|
||||
'workplace.radar.minsAgo': 'منذ {mins} دقيقة',
|
||||
'workplace.radar.hoursAgo': 'منذ {hours} ساعة',
|
||||
'workplace.radar.bounce': 'ارتداد سريع',
|
||||
'workplace.radar.cardFallback': 'بطاقة #{id}',
|
||||
'workplace.funnel.title': 'قمع التحويل',
|
||||
'workplace.funnel.tap': 'لمس NFC',
|
||||
'workplace.funnel.view': 'عرض الصفحة',
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
}
|
||||
|
||||
/* ========================================================
|
||||
右侧出海运营快捷中枢 (Command Hub)
|
||||
右侧全球运营快捷中枢 (Command Hub)
|
||||
======================================================== */
|
||||
.hubContainer {
|
||||
display: flex;
|
||||
|
||||
@@ -38,7 +38,7 @@ const i18n = {
|
||||
},
|
||||
'zh-CN': {
|
||||
'login.form.title': '登录 OmniCard',
|
||||
'login.form.subTitle': '全球智能数字名片与出海访客雷达中枢',
|
||||
'login.form.subTitle': '全球智能数字名片与访客雷达中枢',
|
||||
'login.form.userName.errMsg': '用户名不能为空',
|
||||
'login.form.password.errMsg': '密码不能为空',
|
||||
'login.form.login.errMsg': '登录出错,请刷新重试',
|
||||
@@ -52,10 +52,10 @@ const i18n = {
|
||||
'login.form.forgetPassword': '忘记密码',
|
||||
'login.form.login': '登录',
|
||||
'login.form.register': '注册账号',
|
||||
'login.banner.badge': '全球出海企业级数字身份',
|
||||
'login.banner.badge': '全球企业级数字身份',
|
||||
'login.banner.mainTitle': '下一代全球商务智能数字名片',
|
||||
'login.banner.mainSubTitle':
|
||||
'集 NFC 实体卡管理、Bento Grid 微官网与实时访客雷达于一体的出海智能平台。',
|
||||
'集 NFC 实体卡管理、Bento Grid 微官网与实时访客雷达于一体的全球智能平台。',
|
||||
'login.banner.card1.title': '全球覆盖',
|
||||
'login.banner.card1.metric': '120+',
|
||||
'login.banner.card1.desc': '国家与地区,边缘节点毫秒分发',
|
||||
|
||||
@@ -312,7 +312,7 @@ export default function SubscribersPage() {
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="付费出海企业 (Pro/Ent)"
|
||||
title="付费企业客户 (Pro/Ent)"
|
||||
value={(overview?.proCount || 0) + (overview?.enterpriseCount || 0)}
|
||||
style={{ color: '#0B5CFF' }}
|
||||
suffix="家"
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function SubscriptionsPage() {
|
||||
<Col span={8}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="推荐出海主推方案"
|
||||
title="推荐首选方案"
|
||||
value="Pro 专业商务版"
|
||||
style={{ fontSize: 18, color: '#00B42A' }}
|
||||
/>
|
||||
@@ -144,7 +144,7 @@ export default function SubscriptionsPage() {
|
||||
<Card
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ fontWeight: 600 }}>SaaS 订阅套餐规格与出海权益矩阵</span>
|
||||
<span style={{ fontWeight: 600 }}>SaaS 订阅套餐规格与全球商务权益矩阵</span>
|
||||
<Tag color="arcoblue" size="small">平台超管可调控</Tag>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const i18n = {
|
||||
'zh-CN': {
|
||||
'menu.settings.auditLogs': '安全审计',
|
||||
'auditLogs.title': '系统安全审计日志',
|
||||
'auditLogs.subtitle': '追踪管理员与系统关键操作行为链路,满足安全与出海合规要求。',
|
||||
'auditLogs.subtitle': '追踪管理员与系统关键操作行为链路,满足安全与全球合规要求。',
|
||||
'auditLogs.search.keywordPlaceholder': '搜索操作人、行为名称或 IP 地址',
|
||||
'auditLogs.filter.module': '业务模块',
|
||||
'auditLogs.filter.module.all': '全部模块',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* OmniCard AI (CRMmp) - 全平台跨端核心数据契约与 TypeScript 类型体系
|
||||
*
|
||||
* 遵循出海 SaaS 规范、Bento Grid 便当盒美学与 RFC 6350 国际标准。
|
||||
* 遵循现代全球化 SaaS 规范、Bento Grid 便当盒美学与 RFC 6350 国际标准。
|
||||
* 供 OmniCard 后端与管理端共享镜像复用。
|
||||
* 严格类型安全,杜绝 any。
|
||||
*/
|
||||
@@ -117,7 +117,7 @@ export interface BentoBlock {
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. 访客雷达与轻量级出海 CRM 契约 (Radar & Lead)
|
||||
// 2. 访客雷达与轻量级商务 CRM 契约 (Radar & Lead)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
@@ -221,11 +221,15 @@ export interface WorkplaceKpi {
|
||||
export interface WorkplaceRadarItem {
|
||||
id: number | string;
|
||||
cardId: number | string;
|
||||
cardTitle?: string;
|
||||
visitorId?: string;
|
||||
country: string;
|
||||
city: string;
|
||||
touchType: TouchType;
|
||||
stayDurationMs: number;
|
||||
viewedBlocks: string[];
|
||||
intentScore?: number;
|
||||
isLocal?: boolean;
|
||||
createTime: Date | string;
|
||||
}
|
||||
|
||||
@@ -655,7 +659,7 @@ export interface CardArticle {
|
||||
}
|
||||
|
||||
/**
|
||||
* 出海案例作品契约
|
||||
* 标杆案例作品契约
|
||||
*/
|
||||
export interface CardCase {
|
||||
id?: string | number;
|
||||
|
||||
@@ -49,7 +49,7 @@ test('2. ai/concierge 必须遵循设计 Token,禁止 emoji 与硬编码颜色
|
||||
assert.ok(!emojiRegex.test(content), '页面中严禁使用 emoji 图标或字符');
|
||||
|
||||
// 人设切换支持
|
||||
assert.ok(content.includes('trade_expert'), '必须支持外贸专家预设 trade_expert');
|
||||
assert.ok(content.includes('trade_expert'), '必须支持国际贸易顾问预设 trade_expert');
|
||||
assert.ok(content.includes('business_rep'), '必须支持商务代表预设 business_rep');
|
||||
assert.ok(content.includes('tech_advisor'), '必须支持技术顾问预设 tech_advisor');
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ test('1. routes.ts 已注册 card/cases,对应 pages/card/cases', () => {
|
||||
|
||||
test('2. locale 必须包含 menu.card.cases 中英文案', () => {
|
||||
assert.equal(defaultI18n['en-US']['menu.card.cases'], 'Portfolio');
|
||||
assert.equal(defaultI18n['zh-CN']['menu.card.cases'], '出海案例作品集');
|
||||
assert.equal(defaultI18n['zh-CN']['menu.card.cases'], '案例作品集');
|
||||
});
|
||||
|
||||
test('3. cases 页面必须请求 CRUD 接口并遵守 Cool-Admin 协议', () => {
|
||||
|
||||
@@ -82,7 +82,7 @@ test('5. 工作台 i18n 必须覆盖 KPI、雷达动态与快捷入口,且含
|
||||
assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji');
|
||||
});
|
||||
|
||||
test('6. 专业出海运营官视角:包含全球雷达监控、渠道漏斗与效率工具,全屏仅 1 个主 CTA', () => {
|
||||
test('6. 专业运营官视角:包含全球雷达监控、渠道漏斗与效率工具,全屏仅 1 个主 CTA', () => {
|
||||
const content = fs.readFileSync(pagePath, 'utf-8');
|
||||
const localeContent = fs.readFileSync(localePath, 'utf-8');
|
||||
|
||||
@@ -118,7 +118,7 @@ test('6. 专业出海运营官视角:包含全球雷达监控、渠道漏斗
|
||||
assert.ok(!EMOJI_RE.test(content), '页面代码严禁 emoji');
|
||||
});
|
||||
|
||||
test('7. 出海运营快捷中枢:覆盖8大核心业务快捷入口与运营预警,全屏仅1个主CTA', () => {
|
||||
test('7. 全球运营快捷中枢:覆盖8大核心业务快捷入口与运营预警,全屏仅1个主CTA', () => {
|
||||
const content = fs.readFileSync(pagePath, 'utf-8');
|
||||
const localeContent = fs.readFileSync(localePath, 'utf-8');
|
||||
|
||||
@@ -152,4 +152,37 @@ test('7. 出海运营快捷中枢:覆盖8大核心业务快捷入口与运营
|
||||
assert.ok(!EMOJI_RE.test(content), '页面代码严禁 emoji');
|
||||
});
|
||||
|
||||
test('8. 实时雷达数据专家重构:相对时间、本地环境优雅降级、名片归属与科学意向分分级', () => {
|
||||
const content = fs.readFileSync(pagePath, 'utf-8');
|
||||
const localeContent = fs.readFileSync(localePath, 'utf-8');
|
||||
|
||||
// 1. 相对时间与本地化支持
|
||||
const radarKeys = [
|
||||
'workplace.radar.localEnv',
|
||||
'workplace.radar.justNow',
|
||||
'workplace.radar.minsAgo',
|
||||
'workplace.radar.hoursAgo',
|
||||
'workplace.radar.bounce',
|
||||
'workplace.radar.cardFallback',
|
||||
];
|
||||
for (const key of radarKeys) {
|
||||
assert.ok(
|
||||
localeContent.includes(key),
|
||||
`语言包必须包含雷达数据语义化词条: ${key}`
|
||||
);
|
||||
}
|
||||
|
||||
// 2. 页面中实现相对时间格式化与本地环境优雅降级
|
||||
assert.ok(content.includes('formatRelativeTime'), '必须包含相对时间计算逻辑');
|
||||
assert.ok(content.includes('formatLocation'), '必须包含本地环境优雅降级逻辑');
|
||||
assert.ok(content.includes('formatStayText'), '必须包含跳出/停留文案格式化');
|
||||
assert.ok(content.includes('getIntentTagColor'), '必须包含科学意向分语义色彩分级');
|
||||
|
||||
// 3. 规范约束
|
||||
assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji');
|
||||
assert.ok(!EMOJI_RE.test(content), '页面代码严禁 emoji');
|
||||
assert.ok(!/\bany\b/.test(content), '严禁使用 any');
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const REMOVED_DEMO_MODULES = [
|
||||
'user',
|
||||
];
|
||||
|
||||
test('1. 核心出海业务菜单必须保留在侧边栏正常展示', () => {
|
||||
test('1. 核心业务菜单必须保留在侧边栏正常展示', () => {
|
||||
const content = fs.readFileSync(routesPath, 'utf-8');
|
||||
for (const key of CORE_BIZ_MODULES) {
|
||||
assert.ok(
|
||||
|
||||
@@ -126,14 +126,12 @@ test('5. mpuniapp/common/tokens.uts - uni-app x 跨端 Token 校验', () => {
|
||||
assert.ok(content.includes('export const utsRadius: UtsRadius ='));
|
||||
assert.ok(content.includes('export const utsTypography: UtsTypography ='));
|
||||
|
||||
// 校验关键值
|
||||
assert.ok(content.includes("'#0B5CFF'"));
|
||||
assert.ok(content.includes("'#4D8DFF'"));
|
||||
assert.ok(content.includes("'#7C5CFF'"));
|
||||
assert.ok(content.includes("'#9B8CFF'"));
|
||||
assert.ok(content.includes("button: '12px'"));
|
||||
assert.ok(content.includes("card: '16px'"));
|
||||
assert.ok(content.includes("modal: '20px'"));
|
||||
// 校验关键值 (严格遵守 AGENTS.md 权威 Design Token)
|
||||
assert.ok(content.includes("'#0088FF'"));
|
||||
assert.ok(content.includes("'#3894FF'"));
|
||||
assert.ok(content.includes("button: '14px'"));
|
||||
assert.ok(content.includes("card: '20px'"));
|
||||
assert.ok(content.includes("modal: '24px'"));
|
||||
assert.ok(content.includes("space16: '16px'"));
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ test('3. 快捷导航卡片副标题必须具备单行文本防截断样式', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('4. 零 any,零 emoji,符合出海国际化商务规范', () => {
|
||||
test('4. 零 any,零 emoji,符合国际化现代商务规范', () => {
|
||||
const tsx = read('src/pages/dashboard/workplace/index.tsx');
|
||||
const styles = read('src/pages/dashboard/workplace/style/index.module.less');
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户