// 助教看板页 — 排序×技能×时间三重筛选,4 种维度卡片 // TODO: 联调时替换 mock 数据为真实 API 调用 import { formatMoney, formatCount } from '../../utils/money' import { formatHours } from '../../utils/time' export {} /** 排序维度 → 卡片模板映射 */ type DimType = 'perf' | 'salary' | 'sv' | 'task' const SORT_TO_DIM: Record = { perf_desc: 'perf', perf_asc: 'perf', salary_desc: 'salary', salary_asc: 'salary', sv_desc: 'sv', task_desc: 'task', } const SORT_OPTIONS = [ { value: 'perf_desc', text: '定档业绩最高' }, { value: 'perf_asc', text: '定档业绩最低' }, { value: 'salary_desc', text: '工资最高' }, { value: 'salary_asc', text: '工资最低' }, { value: 'sv_desc', text: '客源储值最高' }, { value: 'task_desc', text: '任务完成最多' }, ] const SKILL_OPTIONS = [ { value: 'all', text: '不限' }, { value: 'chinese', text: '🎱 中式/追分' }, { value: 'snooker', text: '斯诺克' }, { value: 'mahjong', text: '🀄 麻将/棋牌' }, { value: 'karaoke', text: '🎤 团建/K歌' }, ] const TIME_OPTIONS = [ { value: 'month', text: '本月' }, { value: 'quarter', text: '本季度' }, { value: 'last_month', text: '上月' }, { value: 'last_3m', text: '前3个月(不含本月)' }, { value: 'last_quarter', text: '上季度' }, { value: 'last_6m', text: '最近6个月(不含本月,不支持客源储值最高)' }, ] /** 等级 → 样式类映射 */ const LEVEL_CLASS: Record = { star: 'level--star', senior: 'level--high', middle: 'level--mid', junior: 'level--low', // 兼容旧中文 key 过渡期 '星级': 'level--star', '高级': 'level--high', '中级': 'level--mid', '初级': 'level--low', } /** 技能 → 样式类映射 */ const SKILL_CLASS: Record = { '🎱': 'skill--chinese', '斯': 'skill--snooker', '🀄': 'skill--mahjong', '🎤': 'skill--karaoke', } interface CoachItem { id: string name: string initial: string avatarGradient: string level: string // 英文 key: star | senior | middle | junior levelClass: string skills: Array<{ text: string; cls: string }> topCustomers: string[] // 定档业绩维度 perfHours: number perfHoursBefore?: number perfGap?: string perfReached: boolean // 工资维度 salary: number salaryPerfHours: number salaryPerfBefore?: number // 客源储值维度 svAmount: number svCustomerCount: number svConsume: number // 任务维度 taskRecall: number taskCallback: number } /** Mock 数据(忠于 H5 原型 6 位助教) */ const MOCK_COACHES: CoachItem[] = [ { id: 'c1', name: '小燕', initial: '小', avatarGradient: 'blue', level: 'star', levelClass: LEVEL_CLASS['star'], skills: [{ text: '🎱', cls: SKILL_CLASS['🎱'] }], topCustomers: ['💖 王先生', '💖 李女士', '💛 赵总'], perfHours: 86.2, perfHoursBefore: 92.0, perfGap: '距升档 13.8h', perfReached: false, salary: 12680, salaryPerfHours: 86.2, salaryPerfBefore: 92.0, svAmount: 45200, svCustomerCount: 18, svConsume: 8600, taskRecall: 18, taskCallback: 14, }, { id: 'c2', name: '泡芙', initial: '泡', avatarGradient: 'green', level: 'senior', levelClass: LEVEL_CLASS['senior'], skills: [{ text: '斯', cls: SKILL_CLASS['斯'] }], topCustomers: ['💖 陈先生', '💛 刘女士', '💛 黄总'], perfHours: 72.5, perfHoursBefore: 78.0, perfGap: '距升档 7.5h', perfReached: false, salary: 10200, salaryPerfHours: 72.5, salaryPerfBefore: 78.0, svAmount: 38600, svCustomerCount: 15, svConsume: 6200, taskRecall: 15, taskCallback: 13, }, { id: 'c3', name: 'Lucy', initial: 'A', avatarGradient: 'pink', level: 'star', levelClass: LEVEL_CLASS['star'], skills: [{ text: '🎱', cls: SKILL_CLASS['🎱'] }, { text: '斯', cls: SKILL_CLASS['斯'] }], topCustomers: ['💖 张先生', '💛 周女士', '💛 吴总'], perfHours: 68.0, perfHoursBefore: 72.5, perfGap: '距升档 32.0h', perfReached: false, salary: 9800, salaryPerfHours: 68.0, salaryPerfBefore: 72.5, svAmount: 32100, svCustomerCount: 14, svConsume: 5800, taskRecall: 12, taskCallback: 13, }, { id: 'c4', name: 'Mia', initial: 'M', avatarGradient: 'amber', level: 'middle', levelClass: LEVEL_CLASS['middle'], skills: [{ text: '🀄', cls: SKILL_CLASS['🀄'] }], topCustomers: ['💛 赵先生', '💛 吴女士', '💛 孙总'], perfHours: 55.0, perfGap: '距升档 5.0h', perfReached: false, salary: 7500, salaryPerfHours: 55.0, svAmount: 28500, svCustomerCount: 12, svConsume: 4100, taskRecall: 10, taskCallback: 10, }, { id: 'c5', name: '糖糖', initial: '糖', avatarGradient: 'violet', level: 'junior', levelClass: LEVEL_CLASS['junior'], skills: [{ text: '🎱', cls: SKILL_CLASS['🎱'] }], topCustomers: ['💛 钱先生', '💛 孙女士', '💛 周总'], perfHours: 42.0, perfHoursBefore: 45.0, perfReached: true, salary: 6200, salaryPerfHours: 42.0, salaryPerfBefore: 45.0, svAmount: 22000, svCustomerCount: 10, svConsume: 3500, taskRecall: 8, taskCallback: 10, }, { id: 'c6', name: '露露', initial: '露', avatarGradient: 'cyan', level: 'middle', levelClass: LEVEL_CLASS['middle'], skills: [{ text: '🎤', cls: SKILL_CLASS['🎤'] }], topCustomers: ['💛 郑先生', '💛 冯女士', '💛 陈总'], perfHours: 38.0, perfGap: '距升档 22.0h', perfReached: false, salary: 5100, salaryPerfHours: 38.0, svAmount: 18300, svCustomerCount: 9, svConsume: 2800, taskRecall: 6, taskCallback: 9, }, ] Page({ data: { pageState: 'loading' as 'loading' | 'empty' | 'normal' | 'error', activeTab: 'coach' as 'finance' | 'customer' | 'coach', selectedSort: 'perf_desc', sortOptions: SORT_OPTIONS, selectedSkill: 'all', skillOptions: SKILL_OPTIONS, selectedTime: 'month', timeOptions: TIME_OPTIONS, /** 当前维度类型,控制卡片模板 */ dimType: 'perf' as DimType, coaches: [] as CoachItem[], allCoaches: [] as CoachItem[], /** 筛选栏可见性(滚动隐藏/显示) */ 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_COACHES if (!data || data.length === 0) { this.setData({ pageState: 'empty' }) return } // 格式化数字字段为展示字符串 const enriched = data.map((c) => ({ ...c, perfHoursLabel: formatHours(c.perfHours), perfHoursBeforeLabel: c.perfHoursBefore ? formatHours(c.perfHoursBefore) : undefined, salaryLabel: formatMoney(c.salary), salaryPerfHoursLabel: formatHours(c.salaryPerfHours), salaryPerfBeforeLabel: c.salaryPerfBefore ? formatHours(c.salaryPerfBefore) : undefined, svAmountLabel: formatMoney(c.svAmount), svCustomerCountLabel: formatCount(c.svCustomerCount, '人'), svConsumeLabel: formatMoney(c.svConsume), taskRecallLabel: formatCount(c.taskRecall, '次'), taskCallbackLabel: formatCount(c.taskCallback, '次'), })) this.setData({ allCoaches: enriched, coaches: enriched, 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 === 'customer') { wx.navigateTo({ url: '/pages/board-customer/board-customer' }) } }, onSortChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) { const val = e.detail.value this.setData({ selectedSort: val, dimType: SORT_TO_DIM[val] || 'perf', }) }, onSkillChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) { this.setData({ selectedSkill: e.detail.value }) }, onTimeChange(e: WechatMiniprogram.CustomEvent<{ value: string }>) { this.setData({ selectedTime: e.detail.value }) }, onRetry() { this.loadData() }, onCoachTap(e: WechatMiniprogram.TouchEvent) { const id = e.currentTarget.dataset.id as string wx.navigateTo({ url: '/pages/coach-detail/coach-detail?id=' + id }) }, })