218 lines
6.6 KiB
TypeScript
218 lines
6.6 KiB
TypeScript
import { mockCoaches } from '../../utils/mock-data'
|
|
import { sortByTimestamp } from '../../utils/sort'
|
|
|
|
/** 助教详情(含绩效、备注等扩展数据) */
|
|
interface CoachDetail {
|
|
id: string
|
|
name: string
|
|
avatar: string
|
|
level: string
|
|
skills: string[]
|
|
workYears: number
|
|
customerCount: number
|
|
/** 绩效指标 */
|
|
performance: {
|
|
monthlyHours: number
|
|
monthlySalary: number
|
|
customerBalance: number
|
|
tasksCompleted: number
|
|
}
|
|
/** 收入明细 */
|
|
income: {
|
|
thisMonth: IncomeItem[]
|
|
lastMonth: IncomeItem[]
|
|
}
|
|
/** 备注列表 */
|
|
notes: NoteItem[]
|
|
}
|
|
|
|
interface IncomeItem {
|
|
label: string
|
|
amount: string
|
|
color: string
|
|
}
|
|
|
|
interface NoteItem {
|
|
id: string
|
|
content: string
|
|
timestamp: string
|
|
score: number
|
|
customerName: string
|
|
}
|
|
|
|
/** 内联 Mock 数据:助教详情扩展 */
|
|
const mockCoachDetail: CoachDetail = {
|
|
id: 'coach-001',
|
|
name: '小燕',
|
|
avatar: '/assets/images/avatar-default.png',
|
|
level: '星级',
|
|
skills: ['中🎱', '🎯 斯诺克'],
|
|
workYears: 3,
|
|
customerCount: 68,
|
|
performance: {
|
|
monthlyHours: 87.5,
|
|
monthlySalary: 6950,
|
|
customerBalance: 86200,
|
|
tasksCompleted: 38,
|
|
},
|
|
income: {
|
|
thisMonth: [
|
|
{ label: '基础课时费', amount: '¥3,500', color: 'primary' },
|
|
{ label: '激励课时费', amount: '¥1,800', color: 'success' },
|
|
{ label: '充值提成', amount: '¥1,200', color: 'warning' },
|
|
{ label: '酒水提成', amount: '¥450', color: 'purple' },
|
|
],
|
|
lastMonth: [
|
|
{ label: '基础课时费', amount: '¥3,800', color: 'primary' },
|
|
{ label: '激励课时费', amount: '¥1,900', color: 'success' },
|
|
{ label: '充值提成', amount: '¥1,100', color: 'warning' },
|
|
{ label: '酒水提成', amount: '¥400', color: 'purple' },
|
|
],
|
|
},
|
|
notes: [
|
|
{ id: 'n1', content: '本月表现优秀,客户好评率高', timestamp: '2026-03-05 14:30', score: 9, customerName: '管理员' },
|
|
{ id: 'n2', content: '需要加强斯诺克教学技巧', timestamp: '2026-02-28 10:00', score: 7, customerName: '管理员' },
|
|
{ id: 'n3', content: '客户王先生反馈服务态度很好', timestamp: '2026-02-20 16:45', score: 8, customerName: '王先生' },
|
|
],
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
/** 页面状态 */
|
|
pageState: 'loading' as 'loading' | 'empty' | 'normal',
|
|
/** 助教 ID */
|
|
coachId: '',
|
|
/** 助教详情 */
|
|
detail: null as CoachDetail | null,
|
|
/** Banner 指标 */
|
|
bannerMetrics: [] as Array<{ label: string; value: string }>,
|
|
/** 绩效指标卡片 */
|
|
perfCards: [] as Array<{ label: string; value: string; unit: string; sub: string; bgClass: string; valueColor: string }>,
|
|
/** 收入明细 Tab */
|
|
incomeTab: 'this' as 'this' | 'last',
|
|
/** 当前收入明细 */
|
|
currentIncome: [] as IncomeItem[],
|
|
/** 当前收入合计 */
|
|
incomeTotal: '',
|
|
/** 排序后的备注列表 */
|
|
sortedNotes: [] as NoteItem[],
|
|
/** 备注弹窗 */
|
|
noteModalVisible: false,
|
|
},
|
|
|
|
onLoad(options) {
|
|
const id = options?.id || ''
|
|
this.setData({ coachId: id })
|
|
this.loadData(id)
|
|
},
|
|
|
|
loadData(id: string) {
|
|
this.setData({ pageState: 'loading' })
|
|
|
|
setTimeout(() => {
|
|
// TODO: 替换为真实 API 调用 GET /api/coaches/:id
|
|
const basicCoach = mockCoaches.find((c) => c.id === id)
|
|
const detail: CoachDetail = basicCoach
|
|
? { ...mockCoachDetail, id: basicCoach.id, name: basicCoach.name }
|
|
: mockCoachDetail
|
|
|
|
if (!detail) {
|
|
this.setData({ pageState: 'empty' })
|
|
return
|
|
}
|
|
|
|
const bannerMetrics = [
|
|
{ label: '工龄', value: `${detail.workYears}年` },
|
|
{ label: '客户', value: `${detail.customerCount}人` },
|
|
]
|
|
|
|
const perfCards = [
|
|
{ label: '本月定档业绩', value: `${detail.performance.monthlyHours}`, unit: 'h', sub: '折算前 89.0h', bgClass: 'perf-blue', valueColor: 'text-primary' },
|
|
{ label: '本月工资(预估)', value: `¥${detail.performance.monthlySalary.toLocaleString()}`, unit: '', sub: '含预估部分', bgClass: 'perf-green', valueColor: 'text-success' },
|
|
{ label: '客源储值余额', value: `¥${detail.performance.customerBalance.toLocaleString()}`, unit: '', sub: `${detail.customerCount}位客户合计`, bgClass: 'perf-orange', valueColor: 'text-warning' },
|
|
{ label: '本月任务完成', value: `${detail.performance.tasksCompleted}`, unit: '个', sub: '覆盖 22 位客户', bgClass: 'perf-purple', valueColor: 'text-purple' },
|
|
]
|
|
|
|
const sorted = sortByTimestamp(detail.notes || [], 'timestamp') as NoteItem[]
|
|
|
|
this.setData({
|
|
pageState: 'normal',
|
|
detail,
|
|
bannerMetrics,
|
|
perfCards,
|
|
sortedNotes: sorted,
|
|
})
|
|
|
|
this.switchIncomeTab('this')
|
|
}, 500)
|
|
},
|
|
|
|
/** 切换收入明细 Tab */
|
|
switchIncomeTab(tab: 'this' | 'last') {
|
|
const detail = this.data.detail
|
|
if (!detail) return
|
|
|
|
const items = tab === 'this' ? detail.income.thisMonth : detail.income.lastMonth
|
|
// 计算合计(从格式化金额中提取数字)
|
|
const total = items.reduce((sum, item) => {
|
|
const num = parseFloat(item.amount.replace(/[¥,]/g, ''))
|
|
return sum + (isNaN(num) ? 0 : num)
|
|
}, 0)
|
|
|
|
this.setData({
|
|
incomeTab: tab,
|
|
currentIncome: items,
|
|
incomeTotal: `¥${total.toLocaleString()}`,
|
|
})
|
|
},
|
|
|
|
/** 点击收入 Tab */
|
|
onIncomeTabTap(e: WechatMiniprogram.CustomEvent) {
|
|
const tab = e.currentTarget.dataset.tab as 'this' | 'last'
|
|
this.switchIncomeTab(tab)
|
|
},
|
|
|
|
/** 打开备注弹窗 */
|
|
onAddNote() {
|
|
this.setData({ noteModalVisible: true })
|
|
},
|
|
|
|
/** 备注弹窗确认 */
|
|
onNoteConfirm(e: WechatMiniprogram.CustomEvent<{ score: number; content: string }>) {
|
|
const { score, content } = e.detail
|
|
// TODO: 替换为真实 API 调用 POST /api/xcx/notes
|
|
const newNote: NoteItem = {
|
|
id: `n-${Date.now()}`,
|
|
content,
|
|
timestamp: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
|
score,
|
|
customerName: '我',
|
|
}
|
|
const notes = [newNote, ...this.data.sortedNotes]
|
|
this.setData({
|
|
noteModalVisible: false,
|
|
sortedNotes: notes,
|
|
})
|
|
wx.showToast({ title: '备注已保存', icon: 'success' })
|
|
},
|
|
|
|
/** 备注弹窗取消 */
|
|
onNoteCancel() {
|
|
this.setData({ noteModalVisible: false })
|
|
},
|
|
|
|
/** 返回 */
|
|
onBack() {
|
|
wx.navigateBack()
|
|
},
|
|
|
|
/** 问问助手 */
|
|
onStartChat() {
|
|
const id = this.data.coachId || this.data.detail?.id || ''
|
|
wx.navigateTo({
|
|
url: `/pages/chat/chat?coachId=${id}`,
|
|
fail: () => wx.showToast({ title: '页面跳转失败', icon: 'none' }),
|
|
})
|
|
},
|
|
})
|