微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
// 客户看板页 — 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-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 })
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user