fix(portal): 微官网预览页新增极简返回按钮与预览态徽标,修复自定义导航栏无法回退的问题

这个提交包含在:
wikiboss
2026-09-21 15:37:06 +08:00
父节点 2d0b655200
当前提交 38506bbd40
共有 8 个文件被更改,包括 397 次插入 和 127 次删除

查看文件

@@ -389,7 +389,7 @@ describe('OmniCard AI 名片主潜客详情业务逻辑 (useLeadDetail.uts)', ()
}); });
}); });
describe('5. 国际销售触达与 AI 赋能函数', () => { describe('5. 国际销售触达与智能话术函数', () => {
it('5.1 formatWhatsAppUrl 清洗国际号码并生成 wa.me 链接', () => { it('5.1 formatWhatsAppUrl 清洗国际号码并生成 wa.me 链接', () => {
const { formatWhatsAppUrl } = loadUseLeadDetail(); const { formatWhatsAppUrl } = loadUseLeadDetail();

查看文件

@@ -332,4 +332,72 @@ describe('OmniCard AI 用户端微官网 Bento 首页与 Hero 业务逻辑测试
expect(visible.map((b: any) => b.type)).toEqual(['case', 'article', 'social']); 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 = export type OmniMessageKey =
| 'common.confirm' | 'common.confirm'
| 'common.cancel' | 'common.cancel'
| 'common.back'
| 'common.save' | 'common.save'
| 'common.delete' | 'common.delete'
| 'common.edit' | 'common.edit'
@@ -31,6 +32,7 @@ export type OmniMessageKey =
| 'common.error' | 'common.error'
| 'common.empty' | 'common.empty'
| 'common.app_name' | 'common.app_name'
| 'portal.preview_mode'
| 'card.save_contact' | 'card.save_contact'
| 'card.exchange' | 'card.exchange'
| 'card.ai_chat' | 'card.ai_chat'
@@ -89,6 +91,8 @@ export type OmniMessageKey =
| 'portal.appointment_time_call' | 'portal.appointment_time_call'
| 'portal.appointment_notes' | 'portal.appointment_notes'
| 'portal.appointment_notes_placeholder' | 'portal.appointment_notes_placeholder'
| 'portal.appointment_photos'
| 'portal.appointment_photos_tip'
| 'portal.appointment_submit' | 'portal.appointment_submit'
| 'portal.appointment_submitting' | 'portal.appointment_submitting'
| 'portal.appointment_success_tip' | 'portal.appointment_success_tip'
@@ -109,6 +113,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': '确认', 'common.confirm': '确认',
'common.cancel': '取消', 'common.cancel': '取消',
'common.back': '返回',
'common.save': '保存', 'common.save': '保存',
'common.delete': '删除', 'common.delete': '删除',
'common.edit': '编辑', 'common.edit': '编辑',
@@ -118,6 +123,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': '操作失败', 'common.error': '操作失败',
'common.empty': '暂无数据', 'common.empty': '暂无数据',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': '预览中',
'card.save_contact': '保存到通讯录', 'card.save_contact': '保存到通讯录',
'card.exchange': '交换名片', 'card.exchange': '交换名片',
'card.ai_chat': 'AI 咨询', 'card.ai_chat': 'AI 咨询',
@@ -141,41 +147,43 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.twitter': 'X', 'portal.twitter': 'X',
'portal.wechat': '微信', 'portal.wechat': '微信',
'portal.email': '邮件', 'portal.email': '邮件',
'portal.phone': '电话呼出', 'portal.phone': '电话呼叫',
'portal.case': '精选案例', 'portal.case': '案例详情',
'portal.article': '深度洞见', 'portal.article': '深度文章',
'portal.social': '社媒矩阵', 'portal.social': '社交矩阵',
'portal.video': '展会视频', 'portal.video': '视频演示',
'portal.block': '微官网内容', 'portal.block': '便当盒内容',
'portal.contact_unset': '未设置该联系方式', 'portal.contact_unset': '该联系方式未设置',
'portal.copied': '已复制', 'portal.copied': '已复制',
'portal.copied_link': '已复制链接', 'portal.copied_link': '直链已复制',
'portal.copied_whatsapp': '已复制 WhatsApp 链接', 'portal.copied_whatsapp': 'WhatsApp 号码已复制',
'portal.copied_linkedin': '已复制 LinkedIn 链接', 'portal.copied_linkedin': 'LinkedIn 链接已复制',
'portal.copied_email': '已复制邮箱地址', 'portal.copied_email': '邮箱地址已复制',
'portal.copied_telegram': '已复制 Telegram 链接', 'portal.copied_telegram': 'Telegram 账号已复制',
'portal.copied_twitter': '已复制 X 链接', 'portal.copied_twitter': 'X (Twitter) 链接已复制',
'portal.copied_wechat': '已复制微信号', 'portal.copied_wechat': '微信号已复制',
'portal.collect': '收入大厅', 'portal.collect': '收入名片夹',
'portal.collected': '已收入大厅', 'portal.collected': '已收入',
'portal.appointment': '商务预约', 'portal.appointment': '预约商谈',
'portal.appointment_desc': '预约 30 分钟跨国视频会谈或展会现场洽谈', 'portal.appointment_desc': '快速预约 30 分钟全球跨时区视频会议或线下拜访',
'portal.book_now': '立即预约', 'portal.book_now': '立即预约',
'portal.google_cal': 'Google 日历', 'portal.google_cal': 'Google 日历',
'portal.outlook_cal': 'Outlook 日历', 'portal.outlook_cal': 'Outlook 日历',
'portal.ics_download': '下载日历文件 (ICS)', 'portal.ics_download': '下载 ICS 文件',
'portal.copied_meeting_link': '已复制预约日历链接', 'portal.copied_meeting_link': '日程邀请链接已复制',
'portal.appointment_form_title': '预约商务洽谈', 'portal.appointment_form_title': '预约商务会谈',
'portal.appointment_form_desc': '提交您的会谈意向与需求,将即时通知名片主', 'portal.appointment_form_desc': '填写您的合作意向与时间偏好,需求将实时同步至名片主',
'portal.appointment_name': '您的姓名', 'portal.appointment_name': '您的姓名',
'portal.appointment_contact': '手机 / WhatsApp / 邮箱', 'portal.appointment_contact': '联系电话 / WhatsApp / 微信',
'portal.appointment_company': '所属公司 (选填)', 'portal.appointment_company': '所属公司 (选填)',
'portal.appointment_time': '期望会谈形式 / 时间', 'portal.appointment_time': '期望沟通时段 / 形式',
'portal.appointment_time_video': '线上视频 (30分钟)', 'portal.appointment_time_video': '线上视频沟通 (30分钟)',
'portal.appointment_time_offline': '展会现场洽谈', 'portal.appointment_time_offline': '展会现场洽谈 / 线下拜访',
'portal.appointment_time_call': '电话快速沟通', 'portal.appointment_time_call': '快速电话沟通',
'portal.appointment_notes': '合作需求 / 洽谈议题', 'portal.appointment_notes': '合作需求与议题简述',
'portal.appointment_notes_placeholder': '请简要说明您的业务需求或采购意向...', 'portal.appointment_notes_placeholder': '请简要说明您的业务需求或采购意向...',
'portal.appointment_photos': '名片或需求照片 (选填)',
'portal.appointment_photos_tip': '支持上传名片、采购清单或展位样品照片,最多3张',
'portal.appointment_submit': '提交预约需求', 'portal.appointment_submit': '提交预约需求',
'portal.appointment_submitting': '提交中...', 'portal.appointment_submitting': '提交中...',
'portal.appointment_success_tip': '预约已提交!名片主已收到您的需求。', 'portal.appointment_success_tip': '预约已提交!名片主已收到您的需求。',
@@ -189,6 +197,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': 'Confirm', 'common.confirm': 'Confirm',
'common.cancel': 'Cancel', 'common.cancel': 'Cancel',
'common.back': 'Back',
'common.save': 'Save', 'common.save': 'Save',
'common.delete': 'Delete', 'common.delete': 'Delete',
'common.edit': 'Edit', 'common.edit': 'Edit',
@@ -198,6 +207,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': 'Error occurred', 'common.error': 'Error occurred',
'common.empty': 'No data available', 'common.empty': 'No data available',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': 'Preview',
'card.save_contact': 'Save Contact', 'card.save_contact': 'Save Contact',
'card.exchange': 'Exchange Card', 'card.exchange': 'Exchange Card',
'card.ai_chat': 'AI Assistant', 'card.ai_chat': 'AI Assistant',
@@ -209,9 +219,9 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'radar.visitor_trace': 'Visitor Trail', 'radar.visitor_trace': 'Visitor Trail',
'radar.lead_score': 'Lead Score', 'radar.lead_score': 'Lead Score',
'crm.contacts': 'Contacts', 'crm.contacts': 'Contacts',
'crm.leads': 'Customers', 'crm.leads': 'Leads',
'crm.follow_up': 'Follow-up', 'crm.follow_up': 'Follow-up',
'portal.unavailable': 'This page is unavailable', 'portal.unavailable': 'This page is currently unavailable',
'portal.retry': 'Reload', 'portal.retry': 'Reload',
'portal.pronounce': 'Pronounce', 'portal.pronounce': 'Pronounce',
'portal.pronouncing': 'Playing', 'portal.pronouncing': 'Playing',
@@ -222,12 +232,12 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.wechat': 'WeChat', 'portal.wechat': 'WeChat',
'portal.email': 'Email', 'portal.email': 'Email',
'portal.phone': 'Call', 'portal.phone': 'Call',
'portal.case': 'Case study', 'portal.case': 'Case Study',
'portal.article': 'Insight', 'portal.article': 'Insight',
'portal.social': 'Social', 'portal.social': 'Social',
'portal.video': 'Video', 'portal.video': 'Video',
'portal.block': 'Content', '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': 'Copied',
'portal.copied_link': 'Link copied', 'portal.copied_link': 'Link copied',
'portal.copied_whatsapp': 'WhatsApp 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.copied_wechat': 'WeChat ID copied',
'portal.collect': 'Save to Hall', 'portal.collect': 'Save to Hall',
'portal.collected': 'Saved to Hall', 'portal.collected': 'Saved to Hall',
'portal.appointment': 'Schedule Meeting', 'portal.appointment': 'Book Appointment',
'portal.appointment_desc': 'Book a 30-min global video sync or in-person talk', 'portal.appointment_desc': 'Book a 30-min global video meeting or in-person chat',
'portal.book_now': 'Book Now', 'portal.book_now': 'Book Now',
'portal.google_cal': 'Google Calendar', 'portal.google_cal': 'Google Calendar',
'portal.outlook_cal': 'Outlook Calendar', 'portal.outlook_cal': 'Outlook Calendar',
'portal.ics_download': 'Download ICS File', 'portal.ics_download': 'Download ICS File',
'portal.copied_meeting_link': 'Calendar link copied', 'portal.copied_meeting_link': 'Calendar link copied',
'portal.appointment_form_title': 'Schedule Business Meeting', 'portal.appointment_form_title': 'Book a Meeting',
'portal.appointment_form_desc': 'Submit your meeting intent & requirements to the card owner', 'portal.appointment_form_desc': 'Share your project goals and preferred time directly with the card owner',
'portal.appointment_name': 'Your Name', 'portal.appointment_name': 'Your Name',
'portal.appointment_contact': 'Phone / WhatsApp / Email', 'portal.appointment_contact': 'Phone / WhatsApp / Email',
'portal.appointment_company': 'Company (Optional)', 'portal.appointment_company': 'Company (Optional)',
@@ -256,6 +266,8 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.appointment_time_call': 'Quick Phone Call', 'portal.appointment_time_call': 'Quick Phone Call',
'portal.appointment_notes': 'Topics & Requirements', 'portal.appointment_notes': 'Topics & Requirements',
'portal.appointment_notes_placeholder': 'Briefly describe your project or partnership goals...', '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_submit': 'Submit Appointment',
'portal.appointment_submitting': 'Submitting...', 'portal.appointment_submitting': 'Submitting...',
'portal.appointment_success_tip': 'Appointment submitted! The card owner will contact you shortly.', 'portal.appointment_success_tip': 'Appointment submitted! The card owner will contact you shortly.',
@@ -269,6 +281,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': 'تأكيد', 'common.confirm': 'تأكيد',
'common.cancel': 'إلغاء', 'common.cancel': 'إلغاء',
'common.back': 'رجوع',
'common.save': 'حفظ', 'common.save': 'حفظ',
'common.delete': 'حذف', 'common.delete': 'حذف',
'common.edit': 'تعديل', 'common.edit': 'تعديل',
@@ -278,6 +291,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': 'حدث خطأ', 'common.error': 'حدث خطأ',
'common.empty': 'لا توجد بيانات', 'common.empty': 'لا توجد بيانات',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': 'معاينة',
'card.save_contact': 'حفظ جهة الاتصال', 'card.save_contact': 'حفظ جهة الاتصال',
'card.exchange': 'تبادل البطاقات', 'card.exchange': 'تبادل البطاقات',
'card.ai_chat': 'مساعد الذكاء الاصطناعي', 'card.ai_chat': 'مساعد الذكاء الاصطناعي',
@@ -336,6 +350,8 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.appointment_time_call': 'مكالمة هاتفية سريعة', 'portal.appointment_time_call': 'مكالمة هاتفية سريعة',
'portal.appointment_notes': 'المتطلبات وموضوع النقاش', 'portal.appointment_notes': 'المتطلبات وموضوع النقاش',
'portal.appointment_notes_placeholder': 'صف بإيجاز احتياجاتك أو أهداف التعاون...', 'portal.appointment_notes_placeholder': 'صف بإيجاز احتياجاتك أو أهداف التعاون...',
'portal.appointment_photos': 'صور البطاقة أو متطلبات العمل (اختياري)',
'portal.appointment_photos_tip': 'يمكنك إرفاق بطاقة عملك أو متطلباتك (بحد أقصى 3 صور)',
'portal.appointment_submit': 'إرسال طلب الموعد', 'portal.appointment_submit': 'إرسال طلب الموعد',
'portal.appointment_submitting': 'جاري الإرسال...', 'portal.appointment_submitting': 'جاري الإرسال...',
'portal.appointment_success_tip': 'تم إرسال طلبك بنجاح! سيتواصل معك صاحب البطاقة قريباً.', 'portal.appointment_success_tip': 'تم إرسال طلبك بنجاح! سيتواصل معك صاحب البطاقة قريباً.',
@@ -349,6 +365,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': 'Confirmar', 'common.confirm': 'Confirmar',
'common.cancel': 'Cancelar', 'common.cancel': 'Cancelar',
'common.back': 'Volver',
'common.save': 'Guardar', 'common.save': 'Guardar',
'common.delete': 'Eliminar', 'common.delete': 'Eliminar',
'common.edit': 'Editar', 'common.edit': 'Editar',
@@ -358,6 +375,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': 'Ha ocurrido un error', 'common.error': 'Ha ocurrido un error',
'common.empty': 'No hay datos', 'common.empty': 'No hay datos',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': 'Vista previa',
'card.save_contact': 'Guardar contacto', 'card.save_contact': 'Guardar contacto',
'card.exchange': 'Intercambiar tarjeta', 'card.exchange': 'Intercambiar tarjeta',
'card.ai_chat': 'Asistente IA', 'card.ai_chat': 'Asistente IA',
@@ -416,6 +434,8 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.appointment_time_call': 'Llamada Rápida', 'portal.appointment_time_call': 'Llamada Rápida',
'portal.appointment_notes': 'Requisitos y Temas', 'portal.appointment_notes': 'Requisitos y Temas',
'portal.appointment_notes_placeholder': 'Describe brevemente tus necesidades o propuesta...', '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_submit': 'Enviar Solicitud',
'portal.appointment_submitting': 'Enviando...', 'portal.appointment_submitting': 'Enviando...',
'portal.appointment_success_tip': '¡Solicitud enviada! El titular se pondrá en contacto pronto.', 'portal.appointment_success_tip': '¡Solicitud enviada! El titular se pondrá en contacto pronto.',
@@ -429,6 +449,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': 'Bestätigen', 'common.confirm': 'Bestätigen',
'common.cancel': 'Abbrechen', 'common.cancel': 'Abbrechen',
'common.back': 'Zurück',
'common.save': 'Speichern', 'common.save': 'Speichern',
'common.delete': 'Löschen', 'common.delete': 'Löschen',
'common.edit': 'Bearbeiten', 'common.edit': 'Bearbeiten',
@@ -438,6 +459,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': 'Ein Fehler ist aufgetreten', 'common.error': 'Ein Fehler ist aufgetreten',
'common.empty': 'Keine Daten vorhanden', 'common.empty': 'Keine Daten vorhanden',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': 'Vorschau',
'card.save_contact': 'Kontakt speichern', 'card.save_contact': 'Kontakt speichern',
'card.exchange': 'Visitenkarte tauschen', 'card.exchange': 'Visitenkarte tauschen',
'card.ai_chat': 'KI-Assistent', 'card.ai_chat': 'KI-Assistent',
@@ -496,6 +518,8 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.appointment_time_call': 'Kurzer Telefonanruf', 'portal.appointment_time_call': 'Kurzer Telefonanruf',
'portal.appointment_notes': 'Themen & Anforderungen', 'portal.appointment_notes': 'Themen & Anforderungen',
'portal.appointment_notes_placeholder': 'Beschreiben Sie kurz Ihr Vorhaben oder Ihre Fragen...', '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_submit': 'Termin anfragen',
'portal.appointment_submitting': 'Wird gesendet...', 'portal.appointment_submitting': 'Wird gesendet...',
'portal.appointment_success_tip': 'Anfrage gesendet! Der Karteninhaber meldet sich in Kürze.', 'portal.appointment_success_tip': 'Anfrage gesendet! Der Karteninhaber meldet sich in Kürze.',
@@ -509,6 +533,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': 'Confirmer', 'common.confirm': 'Confirmer',
'common.cancel': 'Annuler', 'common.cancel': 'Annuler',
'common.back': 'Retour',
'common.save': 'Enregistrer', 'common.save': 'Enregistrer',
'common.delete': 'Supprimer', 'common.delete': 'Supprimer',
'common.edit': 'Modifier', 'common.edit': 'Modifier',
@@ -518,6 +543,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': 'Une erreur est survenue', 'common.error': 'Une erreur est survenue',
'common.empty': 'Aucune donnée', 'common.empty': 'Aucune donnée',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': 'Aperçu',
'card.save_contact': 'Enregistrer le contact', 'card.save_contact': 'Enregistrer le contact',
'card.exchange': 'Échanger la carte', 'card.exchange': 'Échanger la carte',
'card.ai_chat': 'Assistant IA', 'card.ai_chat': 'Assistant IA',
@@ -576,6 +602,8 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.appointment_time_call': 'Appel rapide', 'portal.appointment_time_call': 'Appel rapide',
'portal.appointment_notes': 'Besoins & Sujets', 'portal.appointment_notes': 'Besoins & Sujets',
'portal.appointment_notes_placeholder': 'Décrivez brièvement votre projet ou vos attentes...', '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_submit': 'Envoyer la demande',
'portal.appointment_submitting': 'Envoi en cours...', 'portal.appointment_submitting': 'Envoi en cours...',
'portal.appointment_success_tip': 'Demande envoyée ! Le titulaire vous contactera sous peu.', 'portal.appointment_success_tip': 'Demande envoyée ! Le titulaire vous contactera sous peu.',
@@ -589,6 +617,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
messages: { messages: {
'common.confirm': '確認', 'common.confirm': '確認',
'common.cancel': 'キャンセル', 'common.cancel': 'キャンセル',
'common.back': '戻る',
'common.save': '保存', 'common.save': '保存',
'common.delete': '削除', 'common.delete': '削除',
'common.edit': '編集', 'common.edit': '編集',
@@ -598,6 +627,7 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'common.error': 'エラーが発生しました', 'common.error': 'エラーが発生しました',
'common.empty': 'データがありません', 'common.empty': 'データがありません',
'common.app_name': 'OmniCard', 'common.app_name': 'OmniCard',
'portal.preview_mode': 'プレビュー',
'card.save_contact': '連絡先に保存', 'card.save_contact': '連絡先に保存',
'card.exchange': '名刺を交換', 'card.exchange': '名刺を交換',
'card.ai_chat': 'AI アシスタント', 'card.ai_chat': 'AI アシスタント',
@@ -656,6 +686,8 @@ export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'portal.appointment_time_call': 'お電話でのクイック相談', 'portal.appointment_time_call': 'お電話でのクイック相談',
'portal.appointment_notes': 'ご相談内容・ご要望', 'portal.appointment_notes': 'ご相談内容・ご要望',
'portal.appointment_notes_placeholder': '事業内容やご興味のある点をご記入ください...', 'portal.appointment_notes_placeholder': '事業内容やご興味のある点をご記入ください...',
'portal.appointment_photos': '名刺・資料写真(任意)',
'portal.appointment_photos_tip': '名刺や商品、仕様書の写真を最大3枚まで添付できます',
'portal.appointment_submit': '予約・相談を送信', 'portal.appointment_submit': '予約・相談を送信',
'portal.appointment_submitting': '送信中...', 'portal.appointment_submitting': '送信中...',
'portal.appointment_success_tip': 'ご予約を送信しました。担当者より追ってご連絡いたします。', 'portal.appointment_success_tip': 'ご予約を送信しました。担当者より追ってご連絡いたします。',

查看文件

@@ -125,12 +125,12 @@
</view> </view>
</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="lead-card lead-section ai-card">
<view class="ai-header-row"> <view class="ai-header-row">
<view class="ai-identity"> <view class="ai-identity">
<view class="ai-pulse-dot"></view> <view class="ai-pulse-dot"></view>
<text class="ai-badge-text">AI Copilot</text> <text class="ai-badge-text">{{ aiScriptTitle }}</text>
</view> </view>
<view class="ai-gen-action" @click="handleGenerateAiScript"> <view class="ai-gen-action" @click="handleGenerateAiScript">
<text class="ai-gen-btn-text">{{ generateAiLabel }}</text> <text class="ai-gen-btn-text">{{ generateAiLabel }}</text>
@@ -424,6 +424,10 @@ const saveLabel = computed((): string => {
return resolveLeadDetailText('save'); return resolveLeadDetailText('save');
}); });
const aiScriptTitle = computed((): string => {
return resolveLeadDetailText('aiScriptTitle');
});
function t(key: LeadDetailCopyKey): string { function t(key: LeadDetailCopyKey): string {
return resolveLeadDetailText(key); return resolveLeadDetailText(key);
} }

查看文件

@@ -1475,6 +1475,7 @@ onShow(() => {
color: #0B5CFF; color: #0B5CFF;
background-color: #F7F8FA; background-color: #F7F8FA;
border-color: #E3E7ED; border-color: #E3E7ED;
box-shadow: 0 0 0 1px #667085;
} }
/* Error Notice */ /* Error Notice */

查看文件

@@ -51,7 +51,8 @@ export type LeadDetailCopyKey =
| 'applyScriptAction' | 'applyScriptAction'
| 'scriptCopiedToast' | 'scriptCopiedToast'
| 'savedContactToast' | 'savedContactToast'
| 'leadTitle'; | 'leadTitle'
| 'aiScriptTitle';
export type LeadDetailPageOptions = { export type LeadDetailPageOptions = {
id?: string; id?: string;
@@ -217,6 +218,9 @@ export function resolveLeadDetailText(key: LeadDetailCopyKey): string {
if (key === 'leadTitle') { if (key === 'leadTitle') {
return zh ? '潜客档案' : 'Lead Profile'; return zh ? '潜客档案' : 'Lead Profile';
} }
if (key === 'aiScriptTitle') {
return zh ? '智能跟进建议' : 'AI Copilot';
}
if (key === 'noteSavedToast') { if (key === 'noteSavedToast') {
return zh ? '备忘已保存' : 'Note saved'; return zh ? '备忘已保存' : 'Note saved';
} }

查看文件

@@ -3,10 +3,24 @@
<!-- 顶部受控局部极光微光晕 (面积 < 8%) --> <!-- 顶部受控局部极光微光晕 (面积 < 8%) -->
<view class="ambient-aurora-glow"></view> <view class="ambient-aurora-glow"></view>
<!-- 顶栏:多语言与主客态操作 --> <!-- 顶栏:返回按钮、名片主预览微标签、多语言与主客态操作 -->
<view class="locale-bar"> <view class="locale-bar portal-top-bar">
<view class="locale-selector-container"> <view class="top-bar-left">
<i18n-selector :model-value="currentLang" @change="switchLang" /> <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>
<view class="locale-exchange-wrap" @click="handleHeaderAction"> <view class="locale-exchange-wrap" @click="handleHeaderAction">
<text class="locale-exchange-link">{{ headerLinkText }}</text> <text class="locale-exchange-link">{{ headerLinkText }}</text>
@@ -203,7 +217,7 @@
</view> </view>
</scroll-view> </scroll-view>
<!-- 4. AI 智能顾问微光悬浮入口 (Secondary CTA) --> <!-- 4. 在线顾问悬浮入口 (Secondary CTA) -->
<view <view
v-if="state.card != null" v-if="state.card != null"
class="ai-entry-capsule" class="ai-entry-capsule"
@@ -239,88 +253,107 @@
</view> </view>
<!-- 预约与需求提报表单 --> <!-- 预约与需求提报表单 -->
<view v-else class="appointment-form-wrap"> <scroll-view v-else scroll-y="true" class="appointment-sheet-scroll">
<view class="sheet-header"> <view class="appointment-form-wrap">
<view class="sheet-title-row"> <view class="sheet-header">
<text class="sheet-title">{{ textOf('portal.appointment_form_title') }}</text> <view class="sheet-title-row">
<text class="sheet-close" @click="closeAppointmentSheet">×</text> <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> </view>
<text class="sheet-subtitle">{{ textOf('portal.appointment_form_desc') }}</text>
</view>
<!-- 期望会谈形式快捷胶囊 --> <!-- 期望会谈形式快捷胶囊 -->
<view class="sheet-field"> <view class="sheet-field">
<text class="sheet-field-label">{{ textOf('portal.appointment_time') }}</text> <text class="sheet-field-label">{{ textOf('portal.appointment_time') }}</text>
<view class="sheet-time-chips"> <view class="sheet-time-chips">
<view <view
v-for="opt in appointmentTimeOptions" v-for="opt in appointmentTimeOptions"
:key="opt.key" :key="opt.key"
class="sheet-time-chip" class="sheet-time-chip"
:class="{ 'sheet-time-chip-active': appointmentForm.preferredTime === opt.label }" :class="{ 'sheet-time-chip-active': appointmentForm.preferredTime === opt.label }"
@click="selectPreferredTime(opt.label)" @click="selectPreferredTime(opt.label)"
> >
<text <text
class="sheet-time-chip-text" class="sheet-time-chip-text"
:class="{ 'sheet-time-chip-text-active': appointmentForm.preferredTime === opt.label }" :class="{ 'sheet-time-chip-text-active': appointmentForm.preferredTime === opt.label }"
>{{ opt.label }}</text> >{{ opt.label }}</text>
</view>
</view> </view>
</view> </view>
</view>
<!-- 姓名输入 (必填) --> <!-- 姓名输入 (必填) -->
<view class="sheet-field"> <view class="sheet-field">
<text class="sheet-field-label">{{ textOf('portal.appointment_name') }} *</text> <text class="sheet-field-label">{{ textOf('portal.appointment_name') }} *</text>
<input <input
class="sheet-field-input" class="sheet-field-input"
v-model="appointmentForm.name" v-model="appointmentForm.name"
:placeholder="textOf('portal.appointment_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" :disabled="appointmentFormSubmitting"
/> @click="submitAppointmentForm"
>
<text class="primary-cta-btn-text">{{ appointmentFormSubmitting ? textOf('portal.appointment_submitting') : textOf('portal.appointment_submit') }}</text>
</button>
</view> </view>
</scroll-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>
</view> </view>
</view> </view>
</view> </view>
@@ -347,6 +380,7 @@ import {
checkIsCardOwner, checkIsCardOwner,
resolvePortalCtaText, resolvePortalCtaText,
resolvePortalHeaderLinkText, resolvePortalHeaderLinkText,
executePortalGoBack,
trackAppointmentAction, trackAppointmentAction,
PortalQuickAction, PortalQuickAction,
} from './usePortal.uts'; } from './usePortal.uts';
@@ -364,6 +398,7 @@ import {
} from '../../common/locale.uts'; } from '../../common/locale.uts';
import I18nSelector from '../../components/i18n-selector/i18n-selector.uvue'; import I18nSelector from '../../components/i18n-selector/i18n-selector.uvue';
import AudioPronounce from '../../components/audio-pronounce/audio-pronounce.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 { releaseAudioPlayer } from './useAudioPlayer.uts';
import { utsLightColors, utsRadius, utsSpacing, utsTypography } from '../../common/tokens.uts'; import { utsLightColors, utsRadius, utsSpacing, utsTypography } from '../../common/tokens.uts';
@@ -435,6 +470,14 @@ const headerLinkText = computed((): string => {
return resolvePortalHeaderLinkText(isOwner.value, currentLang.value); 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 => { const collectLabel = computed((): string => {
return resolvePortalCollectText('label', currentLang.value); return resolvePortalCollectText('label', currentLang.value);
}); });
@@ -615,6 +658,10 @@ function openExchange(): void {
}); });
} }
function handleGoBack(): void {
executePortalGoBack(isOwner.value);
}
function handleHeaderAction(): void { function handleHeaderAction(): void {
if (!state.card) return; if (!state.card) return;
const cardId = state.card!.id != null ? String(state.card!.id) : currentCardId.value; const cardId = state.card!.id != null ? String(state.card!.id) : currentCardId.value;
@@ -642,6 +689,7 @@ type AppointmentFormState = {
company: string; company: string;
preferredTime: string; preferredTime: string;
notes: string; notes: string;
photos: string[];
}; };
const isAppointmentSheetOpen = ref(false); const isAppointmentSheetOpen = ref(false);
@@ -654,6 +702,7 @@ const appointmentForm = reactive<AppointmentFormState>({
company: '', company: '',
preferredTime: '', preferredTime: '',
notes: '', notes: '',
photos: [] as string[],
}); });
const appointmentTimeOptions = computed((): Array<{ key: string; label: string }> => [ const appointmentTimeOptions = computed((): Array<{ key: string; label: string }> => [
@@ -681,6 +730,7 @@ function closeAppointmentSheet(): void {
appointmentForm.contact = ''; appointmentForm.contact = '';
appointmentForm.company = ''; appointmentForm.company = '';
appointmentForm.notes = ''; appointmentForm.notes = '';
appointmentForm.photos = [] as string[];
appointmentSubmitted.value = false; appointmentSubmitted.value = false;
} }
} }
@@ -702,7 +752,10 @@ async function submitAppointmentForm(): Promise<void> {
appointmentFormSubmitting.value = true; appointmentFormSubmitting.value = true;
try { try {
const isEmail = contact.includes('@'); 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({ await submitLead({
cardId: state.card!.id, cardId: state.card!.id,
@@ -854,12 +907,13 @@ onUnload(() => {
} }
.portal-rtl .hero-company-arrow, .portal-rtl .hero-company-arrow,
.portal-rtl .locale-exchange-arrow, .portal-rtl .locale-exchange-arrow,
.portal-rtl .bento-arrow-icon { .portal-rtl .bento-arrow-icon,
.portal-rtl .portal-back-arrow {
display: inline-block; display: inline-block;
transform: scaleX(-1); transform: scaleX(-1);
} }
/* 顶栏 */ /* 顶栏与返回入口 */
.locale-bar { .locale-bar {
position: relative; position: relative;
display: flex; display: flex;
@@ -867,11 +921,63 @@ onUnload(() => {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
min-height: 44px; min-height: 44px;
padding-inline: 16px; padding-inline: 12px 16px;
background-color: transparent; background-color: transparent;
box-sizing: border-box; box-sizing: border-box;
z-index: 10; 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 { .locale-selector-container {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1284,7 +1390,7 @@ onUnload(() => {
height: calc(100px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px)); height: calc(100px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px));
} }
/* 4. AI 智能中枢入口胶囊 (次级入口,轻量极光微光晕) */ /* 4. 在线顾问入口胶囊 (次级入口,轻量极光微光晕) */
.ai-entry-capsule { .ai-entry-capsule {
position: fixed; position: fixed;
inset-inline-end: 16px; inset-inline-end: 16px;
@@ -1494,6 +1600,10 @@ onUnload(() => {
color: var(--oc-text-2, #64748B); color: var(--oc-text-2, #64748B);
} }
/* 商务预约与需求提报表单样式 */ /* 商务预约与需求提报表单样式 */
.appointment-sheet-scroll {
max-height: 75vh;
overflow-y: auto;
}
.appointment-form-wrap { .appointment-form-wrap {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1503,12 +1613,27 @@ onUnload(() => {
flex-direction: column; flex-direction: column;
margin-block-end: 12px; 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 { .sheet-field-label {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: var(--oc-text, #101828); color: var(--oc-text, #101828);
margin-block-end: 6px; 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 { .sheet-time-chips {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -1552,12 +1677,13 @@ onUnload(() => {
box-sizing: border-box; box-sizing: border-box;
} }
.sheet-field-textarea { .sheet-field-textarea {
min-height: 72px; min-height: 96px;
height: 72px; height: 96px;
border: 1px solid var(--oc-border, #EBF1F7); border: 1px solid var(--oc-border, #EBF1F7);
border-radius: 12px; border-radius: 12px;
padding: 10px 14px; padding: 10px 14px;
font-size: 16px; font-size: 16px;
line-height: 1.5;
color: var(--oc-text, #101828); color: var(--oc-text, #101828);
background-color: #FFFFFF; background-color: #FFFFFF;
box-sizing: border-box; box-sizing: border-box;

查看文件

@@ -566,3 +566,38 @@ export function resolvePortalHeaderLinkText(isOwner: boolean, locale?: OmniLocal
return getOmniText(loc, 'card.exchange'); 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 };
}