- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
267 行
6.0 KiB
Plaintext
267 行
6.0 KiB
Plaintext
/**
|
||
* OmniCard AI (CRMmp) - 名片主参展分享码海报业务驱动层
|
||
* 解析 slug / id,拉取公开短链二维码,复制链接
|
||
*/
|
||
|
||
import { fetchCardQrcodeSvg } from '../../../common/api.uts';
|
||
import {
|
||
getCurrentLocale,
|
||
getOmniText,
|
||
OmniLocale,
|
||
} from '../../../common/locale.uts';
|
||
|
||
export type QrcodeQuery = {
|
||
slug?: string;
|
||
id?: string;
|
||
};
|
||
|
||
export type CardQrcodePoster = {
|
||
svg: string;
|
||
url: string;
|
||
};
|
||
|
||
export type LoadPosterApi = {
|
||
fetchCardQrcodeSvg: (slugOrId: string) => Promise<CardQrcodePoster>;
|
||
};
|
||
|
||
export type LoadPosterResult = {
|
||
svg: string;
|
||
url: string;
|
||
empty: boolean;
|
||
errorMessage: string;
|
||
};
|
||
|
||
export type QrcodeState = {
|
||
slugOrId: string;
|
||
svg: string;
|
||
url: string;
|
||
loading: boolean;
|
||
empty: boolean;
|
||
errorMessage: string;
|
||
};
|
||
|
||
export type QrcodeCopyKey =
|
||
| 'copyCta'
|
||
| 'generated'
|
||
| 'urlLabel'
|
||
| 'empty'
|
||
| 'error'
|
||
| 'loading'
|
||
| 'title';
|
||
|
||
const QRCODE_COPY: Record<OmniLocale, Record<QrcodeCopyKey, string>> = {
|
||
'zh-CN': {
|
||
title: '分享名片',
|
||
copyCta: '复制链接',
|
||
generated: '二维码已生成',
|
||
urlLabel: '短链',
|
||
empty: '暂无数据',
|
||
error: '出错了',
|
||
loading: '加载中...',
|
||
},
|
||
'en-US': {
|
||
title: 'Share Card',
|
||
copyCta: 'Copy link',
|
||
generated: 'QR code ready',
|
||
urlLabel: 'Short link',
|
||
empty: 'Nothing here yet',
|
||
error: 'Something went wrong',
|
||
loading: 'Loading...',
|
||
},
|
||
'ar-SA': {
|
||
title: 'مشاركة البطاقة',
|
||
copyCta: 'نسخ الرابط',
|
||
generated: 'رمز الاستجابة جاهز',
|
||
urlLabel: 'الرابط المختصر',
|
||
empty: 'لا توجد بيانات بعد',
|
||
error: 'حدث خطأ',
|
||
loading: 'جار التحميل...',
|
||
},
|
||
'es-ES': {
|
||
title: 'Compartir tarjeta',
|
||
copyCta: 'Copiar enlace',
|
||
generated: 'Codigo QR listo',
|
||
urlLabel: 'Enlace corto',
|
||
empty: 'Aun no hay datos',
|
||
error: 'Algo salio mal',
|
||
loading: 'Cargando...',
|
||
},
|
||
'de-DE': {
|
||
title: 'Karte teilen',
|
||
copyCta: 'Link kopieren',
|
||
generated: 'QR-Code bereit',
|
||
urlLabel: 'Kurzlink',
|
||
empty: 'Noch keine Daten',
|
||
error: 'Etwas ist schiefgelaufen',
|
||
loading: 'Wird geladen...',
|
||
},
|
||
'fr-FR': {
|
||
title: 'Partager la carte',
|
||
copyCta: 'Copier le lien',
|
||
generated: 'QR code pret',
|
||
urlLabel: 'Lien court',
|
||
empty: 'Aucune donnee',
|
||
error: 'Une erreur est survenue',
|
||
loading: 'Chargement...',
|
||
},
|
||
'ja-JP': {
|
||
title: '名刺をシェア',
|
||
copyCta: 'リンクをコピー',
|
||
generated: 'QRコードを生成しました',
|
||
urlLabel: 'ショートリンク',
|
||
empty: 'データがありません',
|
||
error: 'エラーが発生しました',
|
||
loading: '読み込み中...',
|
||
},
|
||
};
|
||
|
||
function trimText(value: string | number | null | undefined): string {
|
||
if (value == null) {
|
||
return '';
|
||
}
|
||
return String(value).trim();
|
||
}
|
||
|
||
/**
|
||
* 页面文案:能复用 locale.uts 的走词典,其余走本地七语表
|
||
*/
|
||
export function resolveQrcodeText(key: QrcodeCopyKey): string {
|
||
if (key === 'title') {
|
||
return getOmniText(getCurrentLocale(), 'card.share');
|
||
}
|
||
if (key === 'empty') {
|
||
return getOmniText(getCurrentLocale(), 'common.empty');
|
||
}
|
||
if (key === 'error') {
|
||
return getOmniText(getCurrentLocale(), 'common.error');
|
||
}
|
||
if (key === 'loading') {
|
||
return getOmniText(getCurrentLocale(), 'common.loading');
|
||
}
|
||
const locale = getCurrentLocale();
|
||
const bag = QRCODE_COPY[locale] != null ? QRCODE_COPY[locale] : QRCODE_COPY['en-US'];
|
||
return bag[key];
|
||
}
|
||
|
||
/**
|
||
* 路由查询优先 slug,否则 id
|
||
*/
|
||
export function resolveSlugOrId(query: QrcodeQuery | null | undefined): string {
|
||
if (query == null) {
|
||
return '';
|
||
}
|
||
const slug = trimText(query.slug);
|
||
if (slug !== '') {
|
||
return slug;
|
||
}
|
||
return trimText(query.id);
|
||
}
|
||
|
||
function resolvePosterApi(api?: LoadPosterApi): LoadPosterApi {
|
||
if (api != null) {
|
||
return api;
|
||
}
|
||
return {
|
||
fetchCardQrcodeSvg,
|
||
};
|
||
}
|
||
|
||
export function createQrcodeState(): QrcodeState {
|
||
return {
|
||
slugOrId: '',
|
||
svg: '',
|
||
url: '',
|
||
loading: false,
|
||
empty: false,
|
||
errorMessage: '',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 拉取公开 SVG 海报:可注入 fetchCardQrcodeSvg
|
||
*/
|
||
export async function loadPoster(
|
||
slugOrId: string | null | undefined,
|
||
api?: LoadPosterApi
|
||
): Promise<LoadPosterResult> {
|
||
const key = trimText(slugOrId);
|
||
if (key === '') {
|
||
return {
|
||
svg: '',
|
||
url: '',
|
||
empty: true,
|
||
errorMessage: '',
|
||
};
|
||
}
|
||
|
||
try {
|
||
const poster = await resolvePosterApi(api).fetchCardQrcodeSvg(key);
|
||
const svg = poster != null && poster.svg != null ? String(poster.svg) : '';
|
||
const url = poster != null && poster.url != null ? String(poster.url) : '';
|
||
return {
|
||
svg,
|
||
url,
|
||
empty: svg === '' && url === '',
|
||
errorMessage: '',
|
||
};
|
||
} catch (err) {
|
||
const message =
|
||
err instanceof Error
|
||
? err.message
|
||
: getOmniText(getCurrentLocale(), 'common.error');
|
||
return {
|
||
svg: '',
|
||
url: '',
|
||
empty: false,
|
||
errorMessage: message,
|
||
};
|
||
}
|
||
}
|
||
|
||
export async function loadPosterIntoState(
|
||
state: QrcodeState,
|
||
slugOrId: string | null | undefined,
|
||
api?: LoadPosterApi
|
||
): Promise<LoadPosterResult> {
|
||
state.slugOrId = trimText(slugOrId);
|
||
state.loading = true;
|
||
state.errorMessage = '';
|
||
const result = await loadPoster(state.slugOrId, api);
|
||
state.svg = result.svg;
|
||
state.url = result.url;
|
||
state.empty = result.empty;
|
||
state.errorMessage = result.errorMessage;
|
||
state.loading = false;
|
||
return result;
|
||
}
|
||
|
||
export function applyQrcodeOptions(
|
||
state: QrcodeState,
|
||
options: QrcodeQuery | null | undefined
|
||
): string {
|
||
const slugOrId = resolveSlugOrId(options);
|
||
state.slugOrId = slugOrId;
|
||
return slugOrId;
|
||
}
|
||
|
||
/**
|
||
* 主 CTA:复制短链
|
||
*/
|
||
export function copyPosterUrl(url: string | null | undefined): boolean {
|
||
const text = trimText(url);
|
||
if (text === '') {
|
||
return false;
|
||
}
|
||
|
||
uni.setClipboardData({
|
||
data: text,
|
||
success: () => {
|
||
uni.showToast({
|
||
title: getOmniText(getCurrentLocale(), 'portal.copied_link'),
|
||
icon: 'none',
|
||
});
|
||
},
|
||
});
|
||
return true;
|
||
}
|