// 客户看板页 — 8 个维度查看前 100 名客户 // TODO: 联调时替换 mock 数据为真实 API 调用 export {} /** 维度类型 → 卡片模板映射 */ type DimType = 'recall' | 'potential' | 'balance' | 'recharge' | 'recent' | 'spend60' | 'freq60' | 'loyal' const DIMENSION_TO_DIM: Record = { recall: 'recall', potential: 'potential', balance: 'balance', recharge: 'recharge', recent: 'recent', spend60: 'spend60', freq60: 'freq60', loyal: 'loyal', } const DIMENSION_OPTIONS = [ { value: 'recall', text: '最应召回' }, { value: 'potential', text: '最大消费潜力' }, { value: 'balance', text: '最高余额' }, { value: 'recharge', text: '最近充值' }, { value: 'recent', text: '最近到店' }, { value: 'spend60', text: '最高消费 近60天' }, { value: 'freq60', text: '最频繁 近60天' }, { value: 'loyal', text: '最专一 近60天' }, ] const PROJECT_OPTIONS = [ { value: 'all', text: '全部' }, { value: 'chinese', text: '🎱 中式/追分' }, { value: 'snooker', text: '斯诺克' }, { value: 'mahjong', text: '🀄 麻将/棋牌' }, { value: 'karaoke', text: '🎤 团建/K歌' }, ] interface AssistantInfo { name: string cls: string // assistant--assignee / assistant--abandoned / assistant--normal heartScore: number // 0-10,heart-icon 组件用 badge?: string // 跟 / 弃 badgeCls?: string // assistant-badge--follow / assistant-badge--drop } interface CustomerItem { id: string name: string initial: string avatarCls: string // 召回维度 idealDays: number elapsedDays: number overdueDays: number visits30d: number balance: string recallIndex: string // 消费潜力维度 potentialTags: Array<{ text: string; theme: string }> spend30d: string avgVisits: string avgSpend: string // 余额维度 lastVisit: string monthlyConsume: string availableMonths: string // 充值维度 lastRecharge: string rechargeAmount: string recharges60d: string currentBalance: string // 消费60天 spend60d: string visits60d: string highSpendTag: boolean // 频率60天 avgInterval: string weeklyVisits: Array<{ val: number; pct: number }> // 专一度 intimacy: string coachName: string coachRatio: string topCoachName: string topCoachHeart: number topCoachScore: string coachDetails: Array<{ name: string cls: string heartScore: number badge?: string avgDuration: string serviceCount: string coachSpend: string relationIdx: number }> // 最近到店 visitFreq: string daysAgo: number // 助教 assistants: AssistantInfo[] } /** Mock 数据(忠于 H5 原型 3 位客户) */ const MOCK_CUSTOMERS: CustomerItem[] = [ { id: 'u1', name: '王先生', initial: '王', avatarCls: 'avatar--amber', idealDays: 7, elapsedDays: 15, overdueDays: 8, visits30d: 3, balance: '¥2,680', recallIndex: '9.2', potentialTags: [ { text: '高频', theme: 'primary' }, { text: '高客单', theme: 'warning' }, ], spend30d: '¥4,200', avgVisits: '6.2次', avgSpend: '¥680', lastVisit: '3天前', monthlyConsume: '¥3,500', availableMonths: '0.8月', lastRecharge: '2月15日', rechargeAmount: '¥5,000', recharges60d: '2次', currentBalance: '¥2,680', spend60d: '¥8,400', visits60d: '12', highSpendTag: true, avgInterval: '5.0天', intimacy: '92', topCoachName: '小燕', topCoachHeart: 9.2, topCoachScore: '9.2', coachDetails: [ { name: '小燕', cls: 'assistant--assignee', heartScore: 9.2, badge: '跟', avgDuration: '2.3h', serviceCount: '14', coachSpend: '¥4,200', relationIdx: 9.2 }, { name: '泡芙', cls: 'assistant--normal', heartScore: 6.5, avgDuration: '1.5h', serviceCount: '8', coachSpend: '¥2,100', relationIdx: 7.2 }, ], weeklyVisits: [ { val: 2, pct: 60 }, { val: 2, pct: 60 }, { val: 1, pct: 30 }, { val: 3, pct: 100 }, { val: 2, pct: 60 }, { val: 3, pct: 100 }, { val: 1, pct: 30 }, { val: 3, pct: 100 }, ], coachName: '小燕', coachRatio: '78%', visitFreq: '6.2次/月', daysAgo: 3, assistants: [ { name: '小燕', cls: 'assistant--assignee', heartScore: 9.2, badge: '跟', badgeCls: 'assistant-badge--follow' }, { name: '泡芙', cls: 'assistant--normal', heartScore: 6.5 }, ], }, { id: 'u2', name: '李女士', initial: '李', avatarCls: 'avatar--pink', idealDays: 10, elapsedDays: 22, overdueDays: 12, visits30d: 1, balance: '¥8,200', recallIndex: '8.5', potentialTags: [ { text: '高余额', theme: 'success' }, { text: '低频', theme: 'gray' }, ], spend30d: '¥1,800', avgVisits: '2.1次', avgSpend: '¥860', lastVisit: '12天前', monthlyConsume: '¥1,800', availableMonths: '4.6月', lastRecharge: '1月20日', rechargeAmount: '¥10,000', recharges60d: '1次', currentBalance: '¥8,200', spend60d: '¥3,600', visits60d: '4', highSpendTag: false, avgInterval: '15.0天', intimacy: '68', topCoachName: 'Amy', topCoachHeart: 8.5, topCoachScore: '8.5', coachDetails: [ { name: 'Amy', cls: 'assistant--assignee', heartScore: 8.5, badge: '跟', avgDuration: '1.8h', serviceCount: '10', coachSpend: '¥3,600', relationIdx: 8.5 }, { name: '小燕', cls: 'assistant--abandoned', heartScore: 4.2, badge: '弃', avgDuration: '0.8h', serviceCount: '3', coachSpend: '¥600', relationIdx: 4.2 }, ], weeklyVisits: [ { val: 1, pct: 40 }, { val: 1, pct: 40 }, { val: 0, pct: 10 }, { val: 1, pct: 40 }, { val: 1, pct: 40 }, { val: 0, pct: 10 }, { val: 1, pct: 40 }, { val: 1, pct: 40 }, ], coachName: 'Amy', coachRatio: '62%', visitFreq: '2.1次/月', daysAgo: 12, assistants: [ { name: 'Amy', cls: 'assistant--assignee', heartScore: 8.5, badge: '跟', badgeCls: 'assistant-badge--follow' }, { name: '小燕', cls: 'assistant--abandoned', heartScore: 4.2, badge: '弃', badgeCls: 'assistant-badge--drop' }, ], }, { id: 'u3', name: '张先生', initial: '张', avatarCls: 'avatar--blue', idealDays: 5, elapsedDays: 8, overdueDays: 3, visits30d: 5, balance: '¥1,200', recallIndex: '7.8', potentialTags: [ { text: '高频', theme: 'primary' }, ], spend30d: '¥3,500', avgVisits: '8.0次', avgSpend: '¥440', lastVisit: '1天前', monthlyConsume: '¥3,200', availableMonths: '0.4月', lastRecharge: '3月1日', rechargeAmount: '¥3,000', recharges60d: '3次', currentBalance: '¥1,200', spend60d: '¥7,000', visits60d: '16', highSpendTag: true, avgInterval: '3.8天', intimacy: '95', topCoachName: '泡芙', topCoachHeart: 9.5, topCoachScore: '9.5', coachDetails: [ { name: '泡芙', cls: 'assistant--assignee', heartScore: 9.5, badge: '跟', avgDuration: '2.1h', serviceCount: '11', coachSpend: '¥3,300', relationIdx: 9.5 }, { name: '小燕', cls: 'assistant--normal', heartScore: 6.8, avgDuration: '1.3h', serviceCount: '6', coachSpend: '¥1,800', relationIdx: 6.8 }, { name: 'Amy', cls: 'assistant--abandoned', heartScore: 3.2, badge: '弃', avgDuration: '0.5h', serviceCount: '2', coachSpend: '¥400', relationIdx: 3.2 }, ], weeklyVisits: [ { val: 2, pct: 50 }, { val: 3, pct: 75 }, { val: 2, pct: 50 }, { val: 4, pct: 100 }, { val: 3, pct: 75 }, { val: 3, pct: 75 }, { val: 2, pct: 50 }, { val: 3, pct: 75 }, ], coachName: '泡芙', coachRatio: '85%', visitFreq: '8.0次/月', daysAgo: 1, assistants: [ { name: '泡芙', cls: 'assistant--assignee', heartScore: 9.5, badge: '跟', badgeCls: 'assistant-badge--follow' }, ], }, ] Page({ data: { pageState: 'loading' as 'loading' | 'empty' | 'normal', activeTab: 'customer' as 'finance' | 'customer' | 'coach', selectedDimension: 'recall', dimensionOptions: DIMENSION_OPTIONS, selectedProject: 'all', projectOptions: PROJECT_OPTIONS, /** 当前维度类型,控制卡片模板 */ dimType: 'recall' as DimType, customers: [] as CustomerItem[], allCustomers: [] as CustomerItem[], totalCount: 0, /** 筛选栏可见性 */ filterBarVisible: true, }, _lastScrollTop: 0, _scrollAcc: 0, _scrollDir: null as 'up' | 'down' | null, onLoad() { this.loadData() }, onPullDownRefresh() { this.loadData() setTimeout(() => wx.stopPullDownRefresh(), 500) }, /** 滚动隐藏/显示筛选栏 */ onPageScroll(e: { scrollTop: number }) { const y = e.scrollTop const delta = y - this._lastScrollTop this._lastScrollTop = y if (y <= 8) { if (!this.data.filterBarVisible) this.setData({ filterBarVisible: true }) this._scrollAcc = 0 this._scrollDir = null return } if (Math.abs(delta) <= 2) return const dir = delta > 0 ? 'down' : 'up' if (dir !== this._scrollDir) { this._scrollDir = dir this._scrollAcc = 0 } this._scrollAcc += Math.abs(delta) const threshold = dir === 'up' ? 12 : 24 if (this._scrollAcc < threshold) return const visible = dir === 'up' if (this.data.filterBarVisible !== visible) { this.setData({ filterBarVisible: visible }) } this._scrollAcc = 0 }, loadData() { this.setData({ pageState: 'loading' }) setTimeout(() => { const data = MOCK_CUSTOMERS if (!data || data.length === 0) { this.setData({ pageState: 'empty' }) return } this.setData({ allCustomers: data, customers: data, totalCount: data.length, pageState: 'normal', }) }, 400) }, onTabChange(e: WechatMiniprogram.TouchEvent) { const tab = e.currentTarget.dataset.tab as string if (tab === 'finance') { wx.switchTab({ url: '/pages/board-finance/board-finance' }) } else if (tab === 'coach') { wx.navigateTo({ url: '/pages/board-coach/board-coach' }) } }, onDimensionChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) { const val = e.detail.value this.setData({ selectedDimension: val, dimType: DIMENSION_TO_DIM[val] || 'recall', }) }, onProjectChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) { this.setData({ selectedProject: e.detail.value }) }, onCustomerTap(e: WechatMiniprogram.TouchEvent) { const id = e.currentTarget.dataset.id as string wx.navigateTo({ url: '/pages/customer-detail/customer-detail?id=' + id }) }, })