import { mockTaskDetails } from '../../utils/mock-data' import type { TaskDetail, Note } from '../../utils/mock-data' import { sortByTimestamp } from '../../utils/sort' import { formatRelativeTime } from '../../utils/time' import { formatMoney } from '../../utils/money' /** 维客线索项 */ interface RetentionClue { tag: string tagColor: 'primary' | 'success' | 'purple' | 'error' emoji: string text: string source: string desc?: string expanded?: boolean } /** 服务记录项 */ interface ServiceRecord { table: string type: string typeClass: 'basic' | 'vip' | 'tip' | 'recharge' | 'incentive' /** 卡片类型:course=普通课,recharge=充值提成 */ recordType?: 'course' | 'recharge' duration: number // 折算后课时(小时,number) durationRaw?: number // 折算前课时(小时,number,可选) income: number // 收入(元,整数) /** 是否预估金额 */ isEstimate?: boolean drinks: string date: string } /** 服务汇总 */ interface ServiceSummary { totalHours: number totalIncome: number avgIncome: number } /** * 将 ISO/空格分隔的日期字符串格式化为中文短格式 * "2026-02-07T21:30" → "2月7日 21:30" * 与 customer-service-records 页面拼接方式保持一致 */ function formatServiceDate(dateStr: string): string { if (!dateStr) return dateStr // 兼容 "2026-02-07T21:30" 和 "2026-02-07 21:30" const normalized = dateStr.replace('T', ' ') const [datePart, timePart] = normalized.split(' ') if (!datePart) return dateStr const parts = datePart.split('-') if (parts.length < 3) return dateStr const month = parseInt(parts[1], 10) const day = parseInt(parts[2], 10) return timePart ? `${month}月${day}日 ${timePart}` : `${month}月${day}日` } Page({ data: { pageState: 'loading' as 'loading' | 'empty' | 'normal' | 'error', detail: null as TaskDetail | null, sortedNotes: [] as Note[], noteModalVisible: false, // --- 维客线索 --- retentionClues: [ { tag: '客户\n基础', tagColor: 'primary', emoji: '🎂', text: '生日 3月15日 · VIP会员 · 注册2年', source: 'By:系统', expanded: false }, { tag: '消费\n习惯', tagColor: 'success', emoji: '🌙', text: '常来夜场 · 月均4-5次', source: 'By:系统', expanded: false }, { tag: '消费\n习惯', tagColor: 'success', emoji: '💰', text: '高客单价', source: 'By:系统', desc: '近60天场均消费 ¥420,高于门店均值 ¥180;偏好夜场时段,酒水附加消费占比 35%', expanded: false }, { tag: '玩法\n偏好', tagColor: 'purple', emoji: '🎱', text: '偏爱中式八球 · 斯诺克进阶中 · 最近对花式九球也有兴趣偏爱中式八球 · 斯诺克进阶中 · 最近对花式九球也有兴趣', source: 'By:系统', desc: '中式八球占比 60%,斯诺克 30%;近2周开始尝试花式九球,技术水平中等偏上', expanded: false }, { tag: '重要\n反馈', tagColor: 'error', emoji: '⚠️', text: '上次提到想练斯诺克走位', source: 'By:小燕', desc: '2月7日到店时主动提及,希望有针对性的走位训练;建议下次安排斯诺克专项课程', expanded: false }, { tag: '社交\n偏好', tagColor: 'purple', emoji: '👥', text: '喜欢带朋友来玩 · 社交型客户', source: 'By:系统', desc: '70%的到店记录都是多人消费,经常介绍新客户;建议推荐团建套餐和会员推荐奖励', expanded: false }, { tag: '消费\n习惯', tagColor: 'success', emoji: '🍷', text: '酒水消费占比高 · 偏好高端酒水', source: 'By:系统', desc: '每次到店必点酒水,偏好芝华士、百威等品牌;酒水消费占总消费的40%', expanded: false }, { tag: '重要\n反馈', tagColor: 'error', emoji: '💬', text: '上次提到想办生日派对', source: 'By:Lucy', desc: '3月15日生日,想在店里办派对,预计10-15人;已记录需求,建议提前联系确认', expanded: false }, ] as RetentionClue[], // --- 话术参考 --- talkingPoints: [ '王哥您好,好久不见!最近店里新到了几张国际标准的斯诺克球桌,知道您是斯诺克爱好者,想邀请您有空来体验一下~', '王哥,最近忙吗?这周末我们有个老客户专属的球友交流赛,奖品还挺丰富的,您要不要来参加?', '王哥好呀,上次您提到想练练斯诺克的走位,我最近研究了一些新的训练方法,下次来的时候可以一起试试~', '王哥,好久没见您了,您的老位置 A12 号台一直给您留着呢!最近晚上人不多,环境特别好,随时欢迎您来~', '王哥您好,我们这个月推出了储值会员专属的夜场优惠套餐,包含球台+酒水,性价比很高,给您留意着呢~', ] as string[], copiedIndex: -1, // --- 近期服务记录 --- serviceSummary: { totalHours: 6.0, totalIncome: 510, avgIncome: 170 } as ServiceSummary, serviceRecords: [ { table: 'A12号台', type: '基础课', typeClass: 'basic', duration: 2.5, durationRaw: 3.0, income: 200, isEstimate: true, drinks: '🍷 百威x2 红牛x1', date: formatServiceDate('2026-02-07T21:30') }, { table: '3号台', type: '基础课', typeClass: 'basic', duration: 2.0, durationRaw: 2.0, income: 160, isEstimate: false, drinks: '🍷 可乐x1', date: formatServiceDate('2026-02-01T20:30') }, { table: 'VIP1号房', type: '包厢课', typeClass: 'vip', duration: 1.5, durationRaw: 1.5, income: 150, isEstimate: true, drinks: '🍷 芝华士x1 矿泉水x2', date: formatServiceDate('2026-01-28T19:00') }, { table: '', type: '充值', typeClass: 'recharge', recordType: 'recharge', duration: 0, durationRaw: 0, income: 80, isEstimate: false, drinks: '', date: formatServiceDate('2026-01-15T10:00') }, ] as ServiceRecord[], // --- 放弃弹窗 --- abandonModalVisible: false, // --- 手机号显示 --- phoneVisible: false, // --- 储值等级 --- storageLevel: '非常多', // --- 关系等级相关 --- relationLevel: 'excellent' as 'poor' | 'normal' | 'good' | 'excellent', relationLevelText: '很好', relationColor: '#e91e63', // --- Banner 背景(根据任务类型动态切换)--- bannerBgSvg: '/assets/images/banner-bg-red-aurora.svg', // --- 调试面板 --- showDebugPanel: false, debugTaskType: 'high_priority', debugHeartScore: 8.5, debugShowExpandBtn: true, // 调试:备注弹窗是否显示展开/收起按钮 // --- AI 配色 --- aiColor: 'indigo' as 'red' | 'orange' | 'yellow' | 'blue' | 'indigo' | 'purple', }, onLoad(options: { id?: string }) { const id = options?.id || '' // 随机 AI 配色 const aiColors = ['red', 'orange', 'yellow', 'blue', 'indigo', 'purple'] as const const aiColor = aiColors[Math.floor(Math.random() * aiColors.length)] this.setData({ aiColor }) this.loadData(id) }, loadData(id: string) { this.setData({ pageState: 'loading' }) setTimeout(() => { try { const detail = mockTaskDetails.find((t) => t.id === id) || mockTaskDetails[0] if (!detail) { this.setData({ pageState: 'empty' }) return } // 根据任务类型设置 Banner 背景 let bannerBgSvg = '/assets/images/banner-bg-red-aurora.svg' if (detail.taskType === 'high_priority') { bannerBgSvg = '/assets/images/banner-bg-red-aurora.svg' } else if (detail.taskType === 'priority_recall') { bannerBgSvg = '/assets/images/banner-bg-orange-aurora.svg' } else if (detail.taskType === 'relationship') { bannerBgSvg = '/assets/images/banner-bg-pink-aurora.svg' } else if (detail.taskType === 'callback') { bannerBgSvg = '/assets/images/banner-bg-teal-aurora.svg' } // 添加更多 mock 备注 const mockNotes: Note[] = [ { id: 'note-1', content: '已通过微信联系王先生,表示对新到的斯诺克球桌感兴趣,周末可能来体验。', tagType: 'customer', tagLabel: '客户:王先生', createdAt: '2026-03-10T16:30', score: 10 }, { id: 'note-2', content: '王先生最近出差较多,到店频率降低。建议等他回来后再约。', tagType: 'customer', tagLabel: '客户:王先生', createdAt: '2026-03-05T14:20', score: 7.5 }, { id: 'note-3', content: '上次到店时推荐了会员续费活动,客户说考虑一下。', tagType: 'customer', tagLabel: '客户:王先生', createdAt: '2026-02-28T18:45', score: 6 }, { id: 'note-4', content: '客户对今天的服务非常满意,特别提到小燕的教学很专业。', tagType: 'customer', tagLabel: '客户:王先生', createdAt: '2026-02-20T21:15', score: 9.5 }, { id: 'note-5', content: '完成高优先召回任务。客户反馈最近工作太忙,这周末会来店里。', tagType: 'customer', tagLabel: '客户:王先生', createdAt: '2026-02-15T10:30', score: 8 }, ] // 附加 timeLabel 字段 const notesWithLabel = mockNotes.map((n) => ({ ...n, timeLabel: formatRelativeTime(n.createdAt), })) const sorted = sortByTimestamp(notesWithLabel) as (Note & { timeLabel: string })[] this.updateRelationshipDisplay(detail.heartScore) this.setData({ pageState: 'normal', detail, sortedNotes: sorted, debugHeartScore: detail.heartScore, bannerBgSvg, }) } catch (_e) { this.setData({ pageState: 'error' }) } }, 500) }, /** 更新关系等级显示 */ updateRelationshipDisplay(score: number) { let level: 'poor' | 'normal' | 'good' | 'excellent' = 'poor' let text = '待发展' let color = '#64748b' // 蓝灰色,区别于蓝色爱心 if (score > 8.5) { level = 'excellent' text = '很好' color = '#e91e63' // 深粉色,区别于粉红爱心 } else if (score >= 6) { level = 'good' text = '良好' color = '#ea580c' // 深橙色,区别于橙色爱心 } else if (score >= 3.5) { level = 'normal' text = '一般' color = '#eab308' // 金黄色,区别于黄色爱心 } this.setData({ relationLevel: level, relationLevelText: text, relationColor: color, }) }, /** 返回 */ onBack() { wx.navigateBack() }, /** 重试 */ onRetry() { const id = this.data.detail?.id || '' this.loadData(id) }, /** 查看/隐藏手机号 */ onTogglePhone() { this.setData({ phoneVisible: !this.data.phoneVisible }) }, /** 复制手机号 */ onCopyPhone() { const phone = '13812345678' wx.setClipboardData({ data: phone, success: () => { wx.showToast({ title: '手机号码已复制', icon: 'none' }) }, }) }, /** 展开/收起维客线索描述 */ onToggleClue(e: WechatMiniprogram.BaseEvent) { const idx = e.currentTarget.dataset.index as number const key = `retentionClues[${idx}].expanded` const current = this.data.retentionClues[idx]?.expanded || false this.setData({ [key]: !current }) }, /** 复制话术 */ onCopySpeech(e: WechatMiniprogram.BaseEvent) { const idx = e.currentTarget.dataset.index as number const text = this.data.talkingPoints[idx] if (!text) return wx.setClipboardData({ data: text, success: () => { this.setData({ copiedIndex: idx }) setTimeout(() => this.setData({ copiedIndex: -1 }), 2000) }, }) }, /** 打开备注弹窗 */ onAddNote() { this.setData({ noteModalVisible: true }) }, /** 备注弹窗确认 */ onNoteConfirm(e: WechatMiniprogram.CustomEvent) { const { content } = e.detail wx.showToast({ title: '备注已保存', icon: 'success' }) this.setData({ noteModalVisible: false }) const newNote: Note = { id: `note-${Date.now()}`, content, tagType: 'customer', tagLabel: `客户:${this.data.detail?.customerName || ''}`, createdAt: new Date().toLocaleString('zh-CN', { hour12: false }), } this.setData({ sortedNotes: [newNote, ...this.data.sortedNotes] }) }, /** 备注弹窗取消 */ onNoteCancel() { this.setData({ noteModalVisible: false }) }, /** 删除备注 */ onDeleteNote(e: WechatMiniprogram.BaseEvent) { const noteId = e.currentTarget.dataset.id as string wx.showModal({ title: '删除备注', content: '确定要删除这条备注吗?删除后无法恢复。', confirmColor: '#e34d59', success: (res) => { if (res.confirm) { const notes = this.data.sortedNotes.filter((n) => n.id !== noteId) this.setData({ sortedNotes: notes }) wx.showToast({ title: '已删除', icon: 'success' }) } }, }) }, /** 放弃/取消放弃 */ onAbandon() { // 如果已放弃,直接取消放弃(不需要二次确认) if (this.data.detail?.status === 'abandoned') { this.cancelAbandon() return } // 否则打开放弃弹窗 this.setData({ abandonModalVisible: true }) }, /** 取消放弃 - 直接修改状态 */ cancelAbandon() { wx.showLoading({ title: '处理中...' }) setTimeout(() => { wx.hideLoading() // 更新状态为 pending this.setData({ 'detail.status': 'pending' }) wx.showToast({ title: '已取消放弃', icon: 'success' }) }, 500) }, /** 放弃 — 确认 */ onAbandonConfirm(_e: WechatMiniprogram.CustomEvent<{ reason: string }>) { this.setData({ abandonModalVisible: false }) wx.showLoading({ title: '处理中...' }) setTimeout(() => { wx.hideLoading() // 更新状态为 abandoned this.setData({ 'detail.status': 'abandoned' }) wx.showToast({ title: '已放弃该客户的维护', icon: 'none' }) }, 500) }, /** 放弃 — 取消 */ onAbandonCancel() { this.setData({ abandonModalVisible: false }) }, /** 问问助手 */ onAskAssistant() { const customerId = this.data.detail?.id || '' wx.navigateTo({ url: `/pages/chat/chat?customerId=${customerId}`, fail: () => wx.showToast({ title: '页面跳转失败', icon: 'none' }), }) }, /** 查看全部服务记录 */ onViewAllRecords() { const customerId = this.data.detail?.id || '' wx.navigateTo({ url: `/pages/customer-service-records/customer-service-records?customerId=${customerId}`, fail: () => wx.showToast({ title: '页面跳转失败', icon: 'none' }), }) }, /** noop — 阻止弹窗背景点击冒泡 */ noop() {}, /** 切换调试面板 */ toggleDebugPanel() { this.setData({ showDebugPanel: !this.data.showDebugPanel }) }, /** 调试 - 切换任务类型 */ onDebugTaskType(e: WechatMiniprogram.BaseEvent) { const type = e.currentTarget.dataset.type as string const typeMap: Record = { high_priority: '高优先召回', priority_recall: '优先召回', relationship: '关系构建', callback: '客户回访', } // 根据任务类型设置 Banner 背景 let bannerBgSvg = '/assets/images/banner-bg-red-aurora.svg' if (type === 'high_priority') { bannerBgSvg = '/assets/images/banner-bg-red-aurora.svg' } else if (type === 'priority_recall') { bannerBgSvg = '/assets/images/banner-bg-orange-aurora.svg' } else if (type === 'relationship') { bannerBgSvg = '/assets/images/banner-bg-pink-aurora.svg' } else if (type === 'callback') { bannerBgSvg = '/assets/images/banner-bg-teal-aurora.svg' } this.setData({ debugTaskType: type, 'detail.taskTypeLabel': typeMap[type] || '高优先召回', bannerBgSvg, }) }, /** 调试 - 调整关系数值 */ onDebugHeartScore(e: WechatMiniprogram.SliderChange) { const score = e.detail.value this.setData({ debugHeartScore: score, 'detail.heartScore': score, }) this.updateRelationshipDisplay(score) }, /** 调试 - 切换备注弹窗展开按钮 */ onDebugToggleExpandBtn(e: WechatMiniprogram.BaseEvent) { const value = e.currentTarget.dataset.value as boolean this.setData({ debugShowExpandBtn: value }) }, })