fix(portal): 微官网预览页新增极简返回按钮与预览态徽标,修复自定义导航栏无法回退的问题
这个提交包含在:
@@ -389,7 +389,7 @@ describe('OmniCard AI 名片主潜客详情业务逻辑 (useLeadDetail.uts)', ()
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. 国际销售触达与 AI 赋能函数', () => {
|
||||
describe('5. 国际销售触达与智能话术函数', () => {
|
||||
it('5.1 formatWhatsAppUrl 清洗国际号码并生成 wa.me 链接', () => {
|
||||
const { formatWhatsAppUrl } = loadUseLeadDetail();
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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<OmniLocale, OmniLocaleConfig> = {
|
||||
messages: {
|
||||
'common.confirm': '确认',
|
||||
'common.cancel': '取消',
|
||||
'common.back': '返回',
|
||||
'common.save': '保存',
|
||||
'common.delete': '删除',
|
||||
'common.edit': '编辑',
|
||||
@@ -118,6 +123,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
messages: {
|
||||
'common.confirm': 'تأكيد',
|
||||
'common.cancel': 'إلغاء',
|
||||
'common.back': 'رجوع',
|
||||
'common.save': 'حفظ',
|
||||
'common.delete': 'حذف',
|
||||
'common.edit': 'تعديل',
|
||||
@@ -278,6 +291,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
messages: {
|
||||
'common.confirm': '確認',
|
||||
'common.cancel': 'キャンセル',
|
||||
'common.back': '戻る',
|
||||
'common.save': '保存',
|
||||
'common.delete': '削除',
|
||||
'common.edit': '編集',
|
||||
@@ -598,6 +627,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
|
||||
'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<OmniLocale, OmniLocaleConfig> = {
|
||||
'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': 'ご予約を送信しました。担当者より追ってご連絡いたします。',
|
||||
|
||||
@@ -125,12 +125,12 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. AI Copilot Icebreaker Script Bento Card -->
|
||||
<!-- 3. Follow-up Script Bento Card -->
|
||||
<view class="lead-card lead-section ai-card">
|
||||
<view class="ai-header-row">
|
||||
<view class="ai-identity">
|
||||
<view class="ai-pulse-dot"></view>
|
||||
<text class="ai-badge-text">AI Copilot</text>
|
||||
<text class="ai-badge-text">{{ aiScriptTitle }}</text>
|
||||
</view>
|
||||
<view class="ai-gen-action" @click="handleGenerateAiScript">
|
||||
<text class="ai-gen-btn-text">{{ generateAiLabel }}</text>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1475,6 +1475,7 @@ onShow(() => {
|
||||
color: #0B5CFF;
|
||||
background-color: #F7F8FA;
|
||||
border-color: #E3E7ED;
|
||||
box-shadow: 0 0 0 1px #667085;
|
||||
}
|
||||
|
||||
/* Error Notice */
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -3,10 +3,24 @@
|
||||
<!-- 顶部受控局部极光微光晕 (面积 < 8%) -->
|
||||
<view class="ambient-aurora-glow"></view>
|
||||
|
||||
<!-- 顶栏:多语言与主客态操作 -->
|
||||
<view class="locale-bar">
|
||||
<view class="locale-selector-container">
|
||||
<i18n-selector :model-value="currentLang" @change="switchLang" />
|
||||
<!-- 顶栏:返回按钮、名片主预览微标签、多语言与主客态操作 -->
|
||||
<view class="locale-bar portal-top-bar">
|
||||
<view class="top-bar-left">
|
||||
<view
|
||||
class="portal-back-btn"
|
||||
role="button"
|
||||
:aria-label="backLabel"
|
||||
@click="handleGoBack"
|
||||
>
|
||||
<text class="portal-back-arrow">‹</text>
|
||||
</view>
|
||||
<view v-if="isOwner" class="preview-badge">
|
||||
<view class="preview-dot"></view>
|
||||
<text class="preview-badge-text">{{ previewModeLabel }}</text>
|
||||
</view>
|
||||
<view class="locale-selector-container">
|
||||
<i18n-selector :model-value="currentLang" @change="switchLang" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="locale-exchange-wrap" @click="handleHeaderAction">
|
||||
<text class="locale-exchange-link">{{ headerLinkText }}</text>
|
||||
@@ -203,7 +217,7 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 4. AI 智能顾问微光悬浮入口 (Secondary CTA) -->
|
||||
<!-- 4. 在线顾问悬浮入口 (Secondary CTA) -->
|
||||
<view
|
||||
v-if="state.card != null"
|
||||
class="ai-entry-capsule"
|
||||
@@ -239,88 +253,107 @@
|
||||
</view>
|
||||
|
||||
<!-- 预约与需求提报表单 -->
|
||||
<view v-else class="appointment-form-wrap">
|
||||
<view class="sheet-header">
|
||||
<view class="sheet-title-row">
|
||||
<text class="sheet-title">{{ textOf('portal.appointment_form_title') }}</text>
|
||||
<text class="sheet-close" @click="closeAppointmentSheet">×</text>
|
||||
<scroll-view v-else scroll-y="true" class="appointment-sheet-scroll">
|
||||
<view class="appointment-form-wrap">
|
||||
<view class="sheet-header">
|
||||
<view class="sheet-title-row">
|
||||
<text class="sheet-title">{{ textOf('portal.appointment_form_title') }}</text>
|
||||
<text class="sheet-close" @click="closeAppointmentSheet">×</text>
|
||||
</view>
|
||||
<text class="sheet-subtitle">{{ textOf('portal.appointment_form_desc') }}</text>
|
||||
</view>
|
||||
<text class="sheet-subtitle">{{ textOf('portal.appointment_form_desc') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 期望会谈形式快捷胶囊 -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_time') }}</text>
|
||||
<view class="sheet-time-chips">
|
||||
<view
|
||||
v-for="opt in appointmentTimeOptions"
|
||||
:key="opt.key"
|
||||
class="sheet-time-chip"
|
||||
:class="{ 'sheet-time-chip-active': appointmentForm.preferredTime === opt.label }"
|
||||
@click="selectPreferredTime(opt.label)"
|
||||
>
|
||||
<text
|
||||
class="sheet-time-chip-text"
|
||||
:class="{ 'sheet-time-chip-text-active': appointmentForm.preferredTime === opt.label }"
|
||||
>{{ opt.label }}</text>
|
||||
<!-- 期望会谈形式快捷胶囊 -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_time') }}</text>
|
||||
<view class="sheet-time-chips">
|
||||
<view
|
||||
v-for="opt in appointmentTimeOptions"
|
||||
:key="opt.key"
|
||||
class="sheet-time-chip"
|
||||
:class="{ 'sheet-time-chip-active': appointmentForm.preferredTime === opt.label }"
|
||||
@click="selectPreferredTime(opt.label)"
|
||||
>
|
||||
<text
|
||||
class="sheet-time-chip-text"
|
||||
:class="{ 'sheet-time-chip-text-active': appointmentForm.preferredTime === opt.label }"
|
||||
>{{ opt.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 姓名输入 (必填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_name') }} *</text>
|
||||
<input
|
||||
class="sheet-field-input"
|
||||
v-model="appointmentForm.name"
|
||||
:placeholder="textOf('portal.appointment_name')"
|
||||
<!-- 姓名输入 (必填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_name') }} *</text>
|
||||
<input
|
||||
class="sheet-field-input"
|
||||
v-model="appointmentForm.name"
|
||||
:placeholder="textOf('portal.appointment_name')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式输入 (必填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_contact') }} *</text>
|
||||
<input
|
||||
class="sheet-field-input"
|
||||
v-model="appointmentForm.contact"
|
||||
:placeholder="textOf('portal.appointment_contact')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 公司名称 (选填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_company') }}</text>
|
||||
<input
|
||||
class="sheet-field-input"
|
||||
v-model="appointmentForm.company"
|
||||
:placeholder="textOf('portal.appointment_company')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 需求与议题说明 (多行文本框) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_notes') }}</text>
|
||||
<textarea
|
||||
class="sheet-field-textarea"
|
||||
v-model="appointmentForm.notes"
|
||||
:placeholder="textOf('portal.appointment_notes_placeholder')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
:maxlength="500"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 名片或需求照片 (选填附件) -->
|
||||
<view class="sheet-field">
|
||||
<view class="sheet-field-header-row">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_photos') }}</text>
|
||||
<text class="sheet-field-tip">{{ textOf('portal.appointment_photos_tip') }}</text>
|
||||
</view>
|
||||
<view class="sheet-uploader-wrap">
|
||||
<ImageUploader
|
||||
v-model="appointmentForm.photos"
|
||||
:multiple="true"
|
||||
:maxCount="3"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button
|
||||
class="primary-cta-btn appointment-submit-btn"
|
||||
:class="{ 'appointment-submit-btn-disabled': appointmentFormSubmitting }"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
@click="submitAppointmentForm"
|
||||
>
|
||||
<text class="primary-cta-btn-text">{{ appointmentFormSubmitting ? textOf('portal.appointment_submitting') : textOf('portal.appointment_submit') }}</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式输入 (必填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_contact') }} *</text>
|
||||
<input
|
||||
class="sheet-field-input"
|
||||
v-model="appointmentForm.contact"
|
||||
:placeholder="textOf('portal.appointment_contact')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 公司名称 (选填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_company') }}</text>
|
||||
<input
|
||||
class="sheet-field-input"
|
||||
v-model="appointmentForm.company"
|
||||
:placeholder="textOf('portal.appointment_company')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 需求与议题说明 (选填) -->
|
||||
<view class="sheet-field">
|
||||
<text class="sheet-field-label">{{ textOf('portal.appointment_notes') }}</text>
|
||||
<textarea
|
||||
class="sheet-field-textarea"
|
||||
v-model="appointmentForm.notes"
|
||||
:placeholder="textOf('portal.appointment_notes_placeholder')"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button
|
||||
class="primary-cta-btn appointment-submit-btn"
|
||||
:class="{ 'appointment-submit-btn-disabled': appointmentFormSubmitting }"
|
||||
:disabled="appointmentFormSubmitting"
|
||||
@click="submitAppointmentForm"
|
||||
>
|
||||
<text class="primary-cta-btn-text">{{ appointmentFormSubmitting ? textOf('portal.appointment_submitting') : textOf('portal.appointment_submit') }}</text>
|
||||
</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -347,6 +380,7 @@ import {
|
||||
checkIsCardOwner,
|
||||
resolvePortalCtaText,
|
||||
resolvePortalHeaderLinkText,
|
||||
executePortalGoBack,
|
||||
trackAppointmentAction,
|
||||
PortalQuickAction,
|
||||
} from './usePortal.uts';
|
||||
@@ -364,6 +398,7 @@ import {
|
||||
} from '../../common/locale.uts';
|
||||
import I18nSelector from '../../components/i18n-selector/i18n-selector.uvue';
|
||||
import AudioPronounce from '../../components/audio-pronounce/audio-pronounce.uvue';
|
||||
import ImageUploader from '../../components/image-uploader/image-uploader.uvue';
|
||||
import { releaseAudioPlayer } from './useAudioPlayer.uts';
|
||||
import { utsLightColors, utsRadius, utsSpacing, utsTypography } from '../../common/tokens.uts';
|
||||
|
||||
@@ -435,6 +470,14 @@ const headerLinkText = computed((): string => {
|
||||
return resolvePortalHeaderLinkText(isOwner.value, currentLang.value);
|
||||
});
|
||||
|
||||
const backLabel = computed((): string => {
|
||||
return textOf('common.back');
|
||||
});
|
||||
|
||||
const previewModeLabel = computed((): string => {
|
||||
return textOf('portal.preview_mode');
|
||||
});
|
||||
|
||||
const collectLabel = computed((): string => {
|
||||
return resolvePortalCollectText('label', currentLang.value);
|
||||
});
|
||||
@@ -615,6 +658,10 @@ function openExchange(): void {
|
||||
});
|
||||
}
|
||||
|
||||
function handleGoBack(): void {
|
||||
executePortalGoBack(isOwner.value);
|
||||
}
|
||||
|
||||
function handleHeaderAction(): void {
|
||||
if (!state.card) return;
|
||||
const cardId = state.card!.id != null ? String(state.card!.id) : currentCardId.value;
|
||||
@@ -642,6 +689,7 @@ type AppointmentFormState = {
|
||||
company: string;
|
||||
preferredTime: string;
|
||||
notes: string;
|
||||
photos: string[];
|
||||
};
|
||||
|
||||
const isAppointmentSheetOpen = ref(false);
|
||||
@@ -654,6 +702,7 @@ const appointmentForm = reactive<AppointmentFormState>({
|
||||
company: '',
|
||||
preferredTime: '',
|
||||
notes: '',
|
||||
photos: [] as string[],
|
||||
});
|
||||
|
||||
const appointmentTimeOptions = computed((): Array<{ key: string; label: string }> => [
|
||||
@@ -681,6 +730,7 @@ function closeAppointmentSheet(): void {
|
||||
appointmentForm.contact = '';
|
||||
appointmentForm.company = '';
|
||||
appointmentForm.notes = '';
|
||||
appointmentForm.photos = [] as string[];
|
||||
appointmentSubmitted.value = false;
|
||||
}
|
||||
}
|
||||
@@ -702,7 +752,10 @@ async function submitAppointmentForm(): Promise<void> {
|
||||
appointmentFormSubmitting.value = true;
|
||||
try {
|
||||
const isEmail = contact.includes('@');
|
||||
const fullNotes = `[商务预约] 期望形式: ${appointmentForm.preferredTime || '未指定'}${appointmentForm.notes.trim() ? `\n需求与议题: ${appointmentForm.notes.trim()}` : ''}`;
|
||||
const photoNotes = appointmentForm.photos.length > 0
|
||||
? `\n附带资料照片: ${appointmentForm.photos.join(' , ')}`
|
||||
: '';
|
||||
const fullNotes = `[商务预约] 期望形式: ${appointmentForm.preferredTime || '未指定'}${appointmentForm.notes.trim() ? `\n需求与议题: ${appointmentForm.notes.trim()}` : ''}${photoNotes}`;
|
||||
|
||||
await submitLead({
|
||||
cardId: state.card!.id,
|
||||
@@ -854,12 +907,13 @@ onUnload(() => {
|
||||
}
|
||||
.portal-rtl .hero-company-arrow,
|
||||
.portal-rtl .locale-exchange-arrow,
|
||||
.portal-rtl .bento-arrow-icon {
|
||||
.portal-rtl .bento-arrow-icon,
|
||||
.portal-rtl .portal-back-arrow {
|
||||
display: inline-block;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/* 顶栏 */
|
||||
/* 顶栏与返回入口 */
|
||||
.locale-bar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -867,11 +921,63 @@ onUnload(() => {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding-inline: 16px;
|
||||
padding-inline: 12px 16px;
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
}
|
||||
.top-bar-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.portal-back-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
min-width: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid var(--oc-border, #EBF1F7);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
|
||||
cursor: pointer;
|
||||
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
.portal-back-btn:active {
|
||||
opacity: 0.75;
|
||||
}
|
||||
.portal-back-arrow {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: var(--oc-text, #101828);
|
||||
line-height: 1;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.preview-badge {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-inline: 8px;
|
||||
height: 24px;
|
||||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||||
border: 1px solid rgba(0, 136, 255, 0.2);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
.preview-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--oc-primary, #0088FF);
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
.preview-badge-text {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--oc-primary, #0088FF);
|
||||
}
|
||||
.locale-selector-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1284,7 +1390,7 @@ onUnload(() => {
|
||||
height: calc(100px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
/* 4. AI 智能中枢入口胶囊 (次级入口,轻量极光微光晕) */
|
||||
/* 4. 在线顾问入口胶囊 (次级入口,轻量极光微光晕) */
|
||||
.ai-entry-capsule {
|
||||
position: fixed;
|
||||
inset-inline-end: 16px;
|
||||
@@ -1494,6 +1600,10 @@ onUnload(() => {
|
||||
color: var(--oc-text-2, #64748B);
|
||||
}
|
||||
/* 商务预约与需求提报表单样式 */
|
||||
.appointment-sheet-scroll {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.appointment-form-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1503,12 +1613,27 @@ onUnload(() => {
|
||||
flex-direction: column;
|
||||
margin-block-end: 12px;
|
||||
}
|
||||
.sheet-field-header-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-block-end: 6px;
|
||||
}
|
||||
.sheet-field-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--oc-text, #101828);
|
||||
margin-block-end: 6px;
|
||||
}
|
||||
.sheet-field-tip {
|
||||
font-size: 11px;
|
||||
color: var(--oc-text-2, #64748B);
|
||||
}
|
||||
.sheet-uploader-wrap {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.sheet-time-chips {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -1552,12 +1677,13 @@ onUnload(() => {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.sheet-field-textarea {
|
||||
min-height: 72px;
|
||||
height: 72px;
|
||||
min-height: 96px;
|
||||
height: 96px;
|
||||
border: 1px solid var(--oc-border, #EBF1F7);
|
||||
border-radius: 12px;
|
||||
padding: 10px 14px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: var(--oc-text, #101828);
|
||||
background-color: #FFFFFF;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -566,3 +566,38 @@ export function resolvePortalHeaderLinkText(isOwner: boolean, locale?: OmniLocal
|
||||
return getOmniText(loc, 'card.exchange');
|
||||
}
|
||||
|
||||
export type PortalGoBackResult = {
|
||||
action: string;
|
||||
targetUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 微官网/预览页顶栏返回上一页或主控台智能路由处理
|
||||
*/
|
||||
export function executePortalGoBack(isOwner: boolean): PortalGoBackResult {
|
||||
try {
|
||||
if (typeof getCurrentPages === 'function') {
|
||||
const pages = getCurrentPages();
|
||||
if (pages != null && pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
return { action: 'navigateBack', targetUrl: '' };
|
||||
}
|
||||
}
|
||||
} catch (_e) {}
|
||||
|
||||
const target = isOwner ? '/pages/owner/card/index' : '/pages/owner/hall/index';
|
||||
try {
|
||||
uni.switchTab({
|
||||
url: target,
|
||||
});
|
||||
} catch (_e) {
|
||||
try {
|
||||
uni.reLaunch({
|
||||
url: target,
|
||||
});
|
||||
} catch (_err) {}
|
||||
}
|
||||
return { action: 'switchTab', targetUrl: target };
|
||||
}
|
||||
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户