diff --git a/backend/test/mpuniapp/owner-lead-detail.test.ts b/backend/test/mpuniapp/owner-lead-detail.test.ts index db4a869..152ce53 100644 --- a/backend/test/mpuniapp/owner-lead-detail.test.ts +++ b/backend/test/mpuniapp/owner-lead-detail.test.ts @@ -389,7 +389,7 @@ describe('OmniCard AI 名片主潜客详情业务逻辑 (useLeadDetail.uts)', () }); }); - describe('5. 国际销售触达与 AI 赋能函数', () => { + describe('5. 国际销售触达与智能话术函数', () => { it('5.1 formatWhatsAppUrl 清洗国际号码并生成 wa.me 链接', () => { const { formatWhatsAppUrl } = loadUseLeadDetail(); diff --git a/backend/test/mpuniapp/portal.test.ts b/backend/test/mpuniapp/portal.test.ts index f2f0fe3..0b1cdc0 100644 --- a/backend/test/mpuniapp/portal.test.ts +++ b/backend/test/mpuniapp/portal.test.ts @@ -332,4 +332,72 @@ describe('OmniCard AI 用户端微官网 Bento 首页与 Hero 业务逻辑测试 expect(visible.map((b: any) => b.type)).toEqual(['case', 'article', 'social']); }); }); + + describe('7. 顶栏返回入口与智能导航 (executePortalGoBack)', () => { + it('7.1 当页面栈深度大于 1 时调用 uni.navigateBack', () => { + // @ts-ignore + const { executePortalGoBack } = require('../../../mpuniapp/pages/portal/usePortal.uts'); + let navigatedBack = false; + let switchedTabUrl = ''; + + (global as any).getCurrentPages = () => [{}, {}]; + (global as any).uni = { + ...(global as any).uni, + navigateBack: () => { + navigatedBack = true; + }, + switchTab: (p: { url: string }) => { + switchedTabUrl = p.url; + }, + }; + + const result = executePortalGoBack(true); + expect(navigatedBack).toBe(true); + expect(result.action).toBe('navigateBack'); + expect(switchedTabUrl).toBe(''); + }); + + it('7.2 当页面栈为 1 且为名片主 (isOwner) 时安全跳转至名片管理首页', () => { + // @ts-ignore + const { executePortalGoBack } = require('../../../mpuniapp/pages/portal/usePortal.uts'); + let navigatedBack = false; + let switchedTabUrl = ''; + + (global as any).getCurrentPages = () => [{}]; + (global as any).uni = { + ...(global as any).uni, + navigateBack: () => { + navigatedBack = true; + }, + switchTab: (p: { url: string }) => { + switchedTabUrl = p.url; + }, + }; + + const result = executePortalGoBack(true); + expect(navigatedBack).toBe(false); + expect(result.action).toBe('switchTab'); + expect(result.targetUrl).toBe('/pages/owner/card/index'); + expect(switchedTabUrl).toBe('/pages/owner/card/index'); + }); + + it('7.3 当页面栈为 1 且为访客时安全跳转至大厅', () => { + // @ts-ignore + const { executePortalGoBack } = require('../../../mpuniapp/pages/portal/usePortal.uts'); + let switchedTabUrl = ''; + + (global as any).getCurrentPages = () => [{}]; + (global as any).uni = { + ...(global as any).uni, + switchTab: (p: { url: string }) => { + switchedTabUrl = p.url; + }, + }; + + const result = executePortalGoBack(false); + expect(result.targetUrl).toBe('/pages/owner/hall/index'); + expect(switchedTabUrl).toBe('/pages/owner/hall/index'); + }); + }); }); + diff --git a/mpuniapp/common/locale.uts b/mpuniapp/common/locale.uts index cf93adc..ce981f2 100644 --- a/mpuniapp/common/locale.uts +++ b/mpuniapp/common/locale.uts @@ -22,6 +22,7 @@ export interface LocaleMeta { export type OmniMessageKey = | 'common.confirm' | 'common.cancel' + | 'common.back' | 'common.save' | 'common.delete' | 'common.edit' @@ -31,6 +32,7 @@ export type OmniMessageKey = | 'common.error' | 'common.empty' | 'common.app_name' + | 'portal.preview_mode' | 'card.save_contact' | 'card.exchange' | 'card.ai_chat' @@ -89,6 +91,8 @@ export type OmniMessageKey = | 'portal.appointment_time_call' | 'portal.appointment_notes' | 'portal.appointment_notes_placeholder' + | 'portal.appointment_photos' + | 'portal.appointment_photos_tip' | 'portal.appointment_submit' | 'portal.appointment_submitting' | 'portal.appointment_success_tip' @@ -109,6 +113,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': '确认', 'common.cancel': '取消', + 'common.back': '返回', 'common.save': '保存', 'common.delete': '删除', 'common.edit': '编辑', @@ -118,6 +123,7 @@ export const omniLocales: Record = { 'common.error': '操作失败', 'common.empty': '暂无数据', 'common.app_name': 'OmniCard', + 'portal.preview_mode': '预览中', 'card.save_contact': '保存到通讯录', 'card.exchange': '交换名片', 'card.ai_chat': 'AI 咨询', @@ -141,41 +147,43 @@ export const omniLocales: Record = { 'portal.twitter': 'X', 'portal.wechat': '微信', 'portal.email': '邮件', - 'portal.phone': '电话呼出', - 'portal.case': '精选案例', - 'portal.article': '深度洞见', - 'portal.social': '社媒矩阵', - 'portal.video': '展会视频', - 'portal.block': '微官网内容', - 'portal.contact_unset': '未设置该联系方式', + 'portal.phone': '电话呼叫', + 'portal.case': '案例详情', + 'portal.article': '深度文章', + 'portal.social': '社交矩阵', + 'portal.video': '视频演示', + 'portal.block': '便当盒内容', + 'portal.contact_unset': '该联系方式未设置', 'portal.copied': '已复制', - 'portal.copied_link': '已复制链接', - 'portal.copied_whatsapp': '已复制 WhatsApp 链接', - 'portal.copied_linkedin': '已复制 LinkedIn 链接', - 'portal.copied_email': '已复制邮箱地址', - 'portal.copied_telegram': '已复制 Telegram 链接', - 'portal.copied_twitter': '已复制 X 链接', - 'portal.copied_wechat': '已复制微信号', - 'portal.collect': '收入大厅', - 'portal.collected': '已收入大厅', - 'portal.appointment': '商务预约', - 'portal.appointment_desc': '预约 30 分钟跨国视频会谈或展会现场洽谈', + 'portal.copied_link': '直链已复制', + 'portal.copied_whatsapp': 'WhatsApp 号码已复制', + 'portal.copied_linkedin': 'LinkedIn 链接已复制', + 'portal.copied_email': '邮箱地址已复制', + 'portal.copied_telegram': 'Telegram 账号已复制', + 'portal.copied_twitter': 'X (Twitter) 链接已复制', + 'portal.copied_wechat': '微信号已复制', + 'portal.collect': '收入名片夹', + 'portal.collected': '已收入', + 'portal.appointment': '预约商谈', + 'portal.appointment_desc': '快速预约 30 分钟全球跨时区视频会议或线下拜访', 'portal.book_now': '立即预约', 'portal.google_cal': 'Google 日历', 'portal.outlook_cal': 'Outlook 日历', - 'portal.ics_download': '下载日历文件 (ICS)', - 'portal.copied_meeting_link': '已复制预约日历链接', - 'portal.appointment_form_title': '预约商务洽谈', - 'portal.appointment_form_desc': '提交您的会谈意向与需求,将即时通知名片主', + 'portal.ics_download': '下载 ICS 文件', + 'portal.copied_meeting_link': '日程邀请链接已复制', + 'portal.appointment_form_title': '预约商务会谈', + 'portal.appointment_form_desc': '填写您的合作意向与时间偏好,需求将实时同步至名片主', 'portal.appointment_name': '您的姓名', - 'portal.appointment_contact': '手机 / WhatsApp / 邮箱', + 'portal.appointment_contact': '联系电话 / WhatsApp / 微信', 'portal.appointment_company': '所属公司 (选填)', - 'portal.appointment_time': '期望会谈形式 / 时间', - 'portal.appointment_time_video': '线上视频 (30分钟)', - 'portal.appointment_time_offline': '展会现场洽谈', - 'portal.appointment_time_call': '电话快速沟通', - 'portal.appointment_notes': '合作需求 / 洽谈议题', + 'portal.appointment_time': '期望沟通时段 / 形式', + 'portal.appointment_time_video': '线上视频沟通 (30分钟)', + 'portal.appointment_time_offline': '展会现场洽谈 / 线下拜访', + 'portal.appointment_time_call': '快速电话沟通', + 'portal.appointment_notes': '合作需求与议题简述', 'portal.appointment_notes_placeholder': '请简要说明您的业务需求或采购意向...', + 'portal.appointment_photos': '名片或需求照片 (选填)', + 'portal.appointment_photos_tip': '支持上传名片、采购清单或展位样品照片,最多3张', 'portal.appointment_submit': '提交预约需求', 'portal.appointment_submitting': '提交中...', 'portal.appointment_success_tip': '预约已提交!名片主已收到您的需求。', @@ -189,6 +197,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': 'Confirm', 'common.cancel': 'Cancel', + 'common.back': 'Back', 'common.save': 'Save', 'common.delete': 'Delete', 'common.edit': 'Edit', @@ -198,6 +207,7 @@ export const omniLocales: Record = { 'common.error': 'Error occurred', 'common.empty': 'No data available', 'common.app_name': 'OmniCard', + 'portal.preview_mode': 'Preview', 'card.save_contact': 'Save Contact', 'card.exchange': 'Exchange Card', 'card.ai_chat': 'AI Assistant', @@ -209,9 +219,9 @@ export const omniLocales: Record = { 'radar.visitor_trace': 'Visitor Trail', 'radar.lead_score': 'Lead Score', 'crm.contacts': 'Contacts', - 'crm.leads': 'Customers', + 'crm.leads': 'Leads', 'crm.follow_up': 'Follow-up', - 'portal.unavailable': 'This page is unavailable', + 'portal.unavailable': 'This page is currently unavailable', 'portal.retry': 'Reload', 'portal.pronounce': 'Pronounce', 'portal.pronouncing': 'Playing', @@ -222,12 +232,12 @@ export const omniLocales: Record = { 'portal.wechat': 'WeChat', 'portal.email': 'Email', 'portal.phone': 'Call', - 'portal.case': 'Case study', + 'portal.case': 'Case Study', 'portal.article': 'Insight', 'portal.social': 'Social', 'portal.video': 'Video', 'portal.block': 'Content', - 'portal.contact_unset': 'This contact method is not set', + 'portal.contact_unset': 'This contact method is not configured', 'portal.copied': 'Copied', 'portal.copied_link': 'Link copied', 'portal.copied_whatsapp': 'WhatsApp link copied', @@ -238,15 +248,15 @@ export const omniLocales: Record = { 'portal.copied_wechat': 'WeChat ID copied', 'portal.collect': 'Save to Hall', 'portal.collected': 'Saved to Hall', - 'portal.appointment': 'Schedule Meeting', - 'portal.appointment_desc': 'Book a 30-min global video sync or in-person talk', + 'portal.appointment': 'Book Appointment', + 'portal.appointment_desc': 'Book a 30-min global video meeting or in-person chat', 'portal.book_now': 'Book Now', 'portal.google_cal': 'Google Calendar', 'portal.outlook_cal': 'Outlook Calendar', 'portal.ics_download': 'Download ICS File', 'portal.copied_meeting_link': 'Calendar link copied', - 'portal.appointment_form_title': 'Schedule Business Meeting', - 'portal.appointment_form_desc': 'Submit your meeting intent & requirements to the card owner', + 'portal.appointment_form_title': 'Book a Meeting', + 'portal.appointment_form_desc': 'Share your project goals and preferred time directly with the card owner', 'portal.appointment_name': 'Your Name', 'portal.appointment_contact': 'Phone / WhatsApp / Email', 'portal.appointment_company': 'Company (Optional)', @@ -256,6 +266,8 @@ export const omniLocales: Record = { 'portal.appointment_time_call': 'Quick Phone Call', 'portal.appointment_notes': 'Topics & Requirements', 'portal.appointment_notes_placeholder': 'Briefly describe your project or partnership goals...', + 'portal.appointment_photos': 'Card / Photo Attachments (Optional)', + 'portal.appointment_photos_tip': 'Upload business card, project brief or product sample (up to 3)', 'portal.appointment_submit': 'Submit Appointment', 'portal.appointment_submitting': 'Submitting...', 'portal.appointment_success_tip': 'Appointment submitted! The card owner will contact you shortly.', @@ -269,6 +281,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': 'تأكيد', 'common.cancel': 'إلغاء', + 'common.back': 'رجوع', 'common.save': 'حفظ', 'common.delete': 'حذف', 'common.edit': 'تعديل', @@ -278,6 +291,7 @@ export const omniLocales: Record = { 'common.error': 'حدث خطأ', 'common.empty': 'لا توجد بيانات', 'common.app_name': 'OmniCard', + 'portal.preview_mode': 'معاينة', 'card.save_contact': 'حفظ جهة الاتصال', 'card.exchange': 'تبادل البطاقات', 'card.ai_chat': 'مساعد الذكاء الاصطناعي', @@ -336,6 +350,8 @@ export const omniLocales: Record = { 'portal.appointment_time_call': 'مكالمة هاتفية سريعة', 'portal.appointment_notes': 'المتطلبات وموضوع النقاش', 'portal.appointment_notes_placeholder': 'صف بإيجاز احتياجاتك أو أهداف التعاون...', + 'portal.appointment_photos': 'صور البطاقة أو متطلبات العمل (اختياري)', + 'portal.appointment_photos_tip': 'يمكنك إرفاق بطاقة عملك أو متطلباتك (بحد أقصى 3 صور)', 'portal.appointment_submit': 'إرسال طلب الموعد', 'portal.appointment_submitting': 'جاري الإرسال...', 'portal.appointment_success_tip': 'تم إرسال طلبك بنجاح! سيتواصل معك صاحب البطاقة قريباً.', @@ -349,6 +365,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': 'Confirmar', 'common.cancel': 'Cancelar', + 'common.back': 'Volver', 'common.save': 'Guardar', 'common.delete': 'Eliminar', 'common.edit': 'Editar', @@ -358,6 +375,7 @@ export const omniLocales: Record = { 'common.error': 'Ha ocurrido un error', 'common.empty': 'No hay datos', 'common.app_name': 'OmniCard', + 'portal.preview_mode': 'Vista previa', 'card.save_contact': 'Guardar contacto', 'card.exchange': 'Intercambiar tarjeta', 'card.ai_chat': 'Asistente IA', @@ -416,6 +434,8 @@ export const omniLocales: Record = { 'portal.appointment_time_call': 'Llamada Rápida', 'portal.appointment_notes': 'Requisitos y Temas', 'portal.appointment_notes_placeholder': 'Describe brevemente tus necesidades o propuesta...', + 'portal.appointment_photos': 'Fotos de tarjeta o requisitos (Opcional)', + 'portal.appointment_photos_tip': 'Sube tu tarjeta de presentación o fotos del proyecto (hasta 3)', 'portal.appointment_submit': 'Enviar Solicitud', 'portal.appointment_submitting': 'Enviando...', 'portal.appointment_success_tip': '¡Solicitud enviada! El titular se pondrá en contacto pronto.', @@ -429,6 +449,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': 'Bestätigen', 'common.cancel': 'Abbrechen', + 'common.back': 'Zurück', 'common.save': 'Speichern', 'common.delete': 'Löschen', 'common.edit': 'Bearbeiten', @@ -438,6 +459,7 @@ export const omniLocales: Record = { 'common.error': 'Ein Fehler ist aufgetreten', 'common.empty': 'Keine Daten vorhanden', 'common.app_name': 'OmniCard', + 'portal.preview_mode': 'Vorschau', 'card.save_contact': 'Kontakt speichern', 'card.exchange': 'Visitenkarte tauschen', 'card.ai_chat': 'KI-Assistent', @@ -496,6 +518,8 @@ export const omniLocales: Record = { 'portal.appointment_time_call': 'Kurzer Telefonanruf', 'portal.appointment_notes': 'Themen & Anforderungen', 'portal.appointment_notes_placeholder': 'Beschreiben Sie kurz Ihr Vorhaben oder Ihre Fragen...', + 'portal.appointment_photos': 'Visitenkarte / Projektfotos (Optional)', + 'portal.appointment_photos_tip': 'Visitenkarte, Projektskizze oder Produktfotos hochladen (max. 3)', 'portal.appointment_submit': 'Termin anfragen', 'portal.appointment_submitting': 'Wird gesendet...', 'portal.appointment_success_tip': 'Anfrage gesendet! Der Karteninhaber meldet sich in Kürze.', @@ -509,6 +533,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': 'Confirmer', 'common.cancel': 'Annuler', + 'common.back': 'Retour', 'common.save': 'Enregistrer', 'common.delete': 'Supprimer', 'common.edit': 'Modifier', @@ -518,6 +543,7 @@ export const omniLocales: Record = { 'common.error': 'Une erreur est survenue', 'common.empty': 'Aucune donnée', 'common.app_name': 'OmniCard', + 'portal.preview_mode': 'Aperçu', 'card.save_contact': 'Enregistrer le contact', 'card.exchange': 'Échanger la carte', 'card.ai_chat': 'Assistant IA', @@ -576,6 +602,8 @@ export const omniLocales: Record = { 'portal.appointment_time_call': 'Appel rapide', 'portal.appointment_notes': 'Besoins & Sujets', 'portal.appointment_notes_placeholder': 'Décrivez brièvement votre projet ou vos attentes...', + 'portal.appointment_photos': 'Carte de visite / Photos (Optionnel)', + 'portal.appointment_photos_tip': 'Ajoutez votre carte ou photos du projet (jusqu\'à 3)', 'portal.appointment_submit': 'Envoyer la demande', 'portal.appointment_submitting': 'Envoi en cours...', 'portal.appointment_success_tip': 'Demande envoyée ! Le titulaire vous contactera sous peu.', @@ -589,6 +617,7 @@ export const omniLocales: Record = { messages: { 'common.confirm': '確認', 'common.cancel': 'キャンセル', + 'common.back': '戻る', 'common.save': '保存', 'common.delete': '削除', 'common.edit': '編集', @@ -598,6 +627,7 @@ export const omniLocales: Record = { 'common.error': 'エラーが発生しました', 'common.empty': 'データがありません', 'common.app_name': 'OmniCard', + 'portal.preview_mode': 'プレビュー', 'card.save_contact': '連絡先に保存', 'card.exchange': '名刺を交換', 'card.ai_chat': 'AI アシスタント', @@ -656,6 +686,8 @@ export const omniLocales: Record = { 'portal.appointment_time_call': 'お電話でのクイック相談', 'portal.appointment_notes': 'ご相談内容・ご要望', 'portal.appointment_notes_placeholder': '事業内容やご興味のある点をご記入ください...', + 'portal.appointment_photos': '名刺・資料写真(任意)', + 'portal.appointment_photos_tip': '名刺や商品、仕様書の写真を最大3枚まで添付できます', 'portal.appointment_submit': '予約・相談を送信', 'portal.appointment_submitting': '送信中...', 'portal.appointment_success_tip': 'ご予約を送信しました。担当者より追ってご連絡いたします。', diff --git a/mpuniapp/pages/owner/leads-detail.uvue b/mpuniapp/pages/owner/leads-detail.uvue index e17259b..b88aad9 100644 --- a/mpuniapp/pages/owner/leads-detail.uvue +++ b/mpuniapp/pages/owner/leads-detail.uvue @@ -125,12 +125,12 @@ - + - AI Copilot + {{ aiScriptTitle }} {{ generateAiLabel }} @@ -424,6 +424,10 @@ const saveLabel = computed((): string => { return resolveLeadDetailText('save'); }); +const aiScriptTitle = computed((): string => { + return resolveLeadDetailText('aiScriptTitle'); +}); + function t(key: LeadDetailCopyKey): string { return resolveLeadDetailText(key); } diff --git a/mpuniapp/pages/owner/profile/index.uvue b/mpuniapp/pages/owner/profile/index.uvue index a250feb..8d74817 100644 --- a/mpuniapp/pages/owner/profile/index.uvue +++ b/mpuniapp/pages/owner/profile/index.uvue @@ -1475,6 +1475,7 @@ onShow(() => { color: #0B5CFF; background-color: #F7F8FA; border-color: #E3E7ED; + box-shadow: 0 0 0 1px #667085; } /* Error Notice */ diff --git a/mpuniapp/pages/owner/useLeadDetail.uts b/mpuniapp/pages/owner/useLeadDetail.uts index d127aa0..598ffcf 100644 --- a/mpuniapp/pages/owner/useLeadDetail.uts +++ b/mpuniapp/pages/owner/useLeadDetail.uts @@ -51,7 +51,8 @@ export type LeadDetailCopyKey = | 'applyScriptAction' | 'scriptCopiedToast' | 'savedContactToast' - | 'leadTitle'; + | 'leadTitle' + | 'aiScriptTitle'; export type LeadDetailPageOptions = { id?: string; @@ -217,6 +218,9 @@ export function resolveLeadDetailText(key: LeadDetailCopyKey): string { if (key === 'leadTitle') { return zh ? '潜客档案' : 'Lead Profile'; } + if (key === 'aiScriptTitle') { + return zh ? '智能跟进建议' : 'AI Copilot'; + } if (key === 'noteSavedToast') { return zh ? '备忘已保存' : 'Note saved'; } diff --git a/mpuniapp/pages/portal/index.uvue b/mpuniapp/pages/portal/index.uvue index f2cd241..f9833c4 100644 --- a/mpuniapp/pages/portal/index.uvue +++ b/mpuniapp/pages/portal/index.uvue @@ -3,10 +3,24 @@ - - - - + + + + + ‹ + + + + {{ previewModeLabel }} + + + + {{ headerLinkText }} @@ -203,7 +217,7 @@ - + - - - - {{ textOf('portal.appointment_form_title') }} - × + + + + + {{ textOf('portal.appointment_form_title') }} + × + + {{ textOf('portal.appointment_form_desc') }} - {{ textOf('portal.appointment_form_desc') }} - - - - {{ textOf('portal.appointment_time') }} - - - {{ opt.label }} + + + {{ textOf('portal.appointment_time') }} + + + {{ opt.label }} + - - - - {{ textOf('portal.appointment_name') }} * - + + {{ textOf('portal.appointment_name') }} * + + + + + + {{ textOf('portal.appointment_contact') }} * + + + + + + {{ textOf('portal.appointment_company') }} + + + + + + {{ textOf('portal.appointment_notes') }} +