/** * 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; }; 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' | 'saveSuccess' | 'saveFailed' | 'saveUnavailable'; const QRCODE_COPY: Record> = { 'zh-CN': { title: '分享名片', copyCta: '复制链接', generated: '二维码已生成', urlLabel: '短链', empty: '暂无数据', error: '出错了', loading: '加载中...', saveSuccess: '已保存到相册', saveFailed: '保存失败', saveUnavailable: '暂无可保存的图片', }, '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...', saveSuccess: 'Saved to Photos', saveFailed: 'Could not save', saveUnavailable: 'No image to save', }, 'ar-SA': { title: 'مشاركة البطاقة', copyCta: 'نسخ الرابط', generated: 'رمز الاستجابة جاهز', urlLabel: 'الرابط المختصر', empty: 'لا توجد بيانات بعد', error: 'حدث خطأ', loading: 'جار التحميل...', saveSuccess: 'تم الحفظ في الصور', saveFailed: 'تعذر الحفظ', saveUnavailable: 'لا توجد صورة للحفظ', }, '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...', saveSuccess: 'Guardado en Fotos', saveFailed: 'No se pudo guardar', saveUnavailable: 'No hay imagen para guardar', }, '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...', saveSuccess: 'In Fotos gespeichert', saveFailed: 'Speichern fehlgeschlagen', saveUnavailable: 'Kein Bild zum Speichern', }, '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...', saveSuccess: 'Enregistre dans Photos', saveFailed: 'Echec de l enregistrement', saveUnavailable: 'Aucune image a enregistrer', }, 'ja-JP': { title: '名刺をシェア', copyCta: 'リンクをコピー', generated: 'QRコードを生成しました', urlLabel: 'ショートリンク', empty: 'データがありません', error: 'エラーが発生しました', loading: '読み込み中...', saveSuccess: '写真に保存しました', saveFailed: '保存に失敗しました', saveUnavailable: '保存できる画像がありません', }, }; 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 { 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 { 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; } /** * 仅本地路径 / 网络图 / data:image 可入库;纯 SVG 标记不可直接进相册 */ export function isSaveablePosterImage(source: string | null | undefined): boolean { const text = trimText(source); if (text === '') { return false; } const lower = text.toLowerCase(); if (lower.indexOf('= 0 || lower.indexOf(' { return new Promise(resolve => { uni.saveImageToPhotosAlbum({ filePath: filePath, success: () => { uni.showToast({ title: resolveQrcodeText('saveSuccess'), icon: 'none', }); resolve(true); }, fail: (_err: unknown) => { toastSaveFailed(false); resolve(false); }, }); }); } function downloadRemoteImage(url: string): Promise { return new Promise(resolve => { uni.downloadFile({ url: url, success: (res: { statusCode?: number; tempFilePath?: string }) => { const path = res != null && res.tempFilePath != null ? String(res.tempFilePath).trim() : ''; const code = res != null && res.statusCode != null ? res.statusCode : 0; if (code === 200 && path !== '') { resolve(path); return; } resolve(''); }, fail: (_err: unknown) => { resolve(''); }, }); }); } /** * 保存海报到相册:必须等 uni.saveImageToPhotosAlbum 成功才 toast 已保存 */ export async function savePosterToAlbum( imageSource: string | null | undefined ): Promise { const source = trimText(imageSource); if (source === '' || !isSaveablePosterImage(source)) { toastSaveFailed(true); return false; } let filePath = source; const lower = source.toLowerCase(); if (lower.indexOf('http://') === 0 || lower.indexOf('https://') === 0) { filePath = await downloadRemoteImage(source); if (filePath === '') { toastSaveFailed(false); return false; } } return invokeSaveImageToPhotosAlbum(filePath); }