Files
Neo-ZQYY/apps/miniprogram - 副本/miniprogram/pages/board-customer/board-customer.ts

301 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 客户看板页 — 8 个维度查看前 100 名客户
// TODO: 联调时替换 mock 数据为真实 API 调用
export {}
/** 维度类型 → 卡片模板映射 */
type DimType = 'recall' | 'potential' | 'balance' | 'recharge' | 'recent' | 'spend60' | 'freq60' | 'loyal'
const DIMENSION_TO_DIM: Record<string, DimType> = {
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-10heart-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 })
},
})