feat: chat integration, tenant admin spec, backend chat service, miniprogram updates, DEMO moved to tmp, XCX-TEST removed, migrations & docs
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"navigationBarTitleText": "业绩明细",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"coach-level-tag": "/components/coach-level-tag/coach-level-tag",
|
||||
"ai-float-button": "/components/ai-float-button/ai-float-button",
|
||||
"dev-fab": "/components/dev-fab/dev-fab",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
import { mockPerformanceRecords } from '../../utils/mock-data'
|
||||
import type { PerformanceRecord } from '../../utils/mock-data'
|
||||
import { nameToAvatarColor } from '../../utils/avatar-color'
|
||||
import { formatMoney, formatCount } from '../../utils/money'
|
||||
import { formatHours } from '../../utils/time'
|
||||
|
||||
/** 按日期分组后的展示结构 */
|
||||
interface DateGroup {
|
||||
date: string
|
||||
totalHours: number
|
||||
totalIncome: number
|
||||
totalHoursLabel: string
|
||||
totalIncomeLabel: string
|
||||
records: RecordItem[]
|
||||
}
|
||||
|
||||
interface RecordItem {
|
||||
id: string
|
||||
customerName: string
|
||||
avatarChar: string
|
||||
avatarColor: string
|
||||
timeRange: string
|
||||
hours: number // 折算后课时(小时,number)
|
||||
hoursRaw?: number // 折算前课时(小时,number,可选)
|
||||
courseType: string
|
||||
courseTypeClass: string
|
||||
location: string
|
||||
income: number // 收入(元,整数)
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
pageState: 'loading' as 'loading' | 'empty' | 'error' | 'normal',
|
||||
|
||||
/** Banner */
|
||||
coachName: '小燕',
|
||||
coachLevel: '星级',
|
||||
storeName: '球会名称店',
|
||||
|
||||
/** 月份切换 */
|
||||
currentYear: 2026,
|
||||
currentMonth: 2,
|
||||
monthLabel: '2026年2月',
|
||||
canGoPrev: true,
|
||||
canGoNext: false,
|
||||
|
||||
/** 统计概览 */
|
||||
totalCount: 0,
|
||||
totalHours: 0,
|
||||
totalIncome: 0,
|
||||
totalCountLabel: '--',
|
||||
totalHoursLabel: '--',
|
||||
totalHoursRawLabel: '',
|
||||
totalIncomeLabel: '--',
|
||||
|
||||
/** 按日期分组的记录 */
|
||||
dateGroups: [] as DateGroup[],
|
||||
|
||||
/** 所有记录(用于筛选) */
|
||||
allRecords: [] as PerformanceRecord[],
|
||||
|
||||
/** 分页 */
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
hasMore: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadData(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (!this.data.hasMore) return
|
||||
this.setData({ page: this.data.page + 1 })
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
loadData(cb?: () => void) {
|
||||
this.setData({ pageState: 'loading' })
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// TODO: 替换为真实 API,按月份请求
|
||||
const allRecords = mockPerformanceRecords
|
||||
|
||||
const dateGroups: DateGroup[] = [
|
||||
{
|
||||
date: '2月7日',
|
||||
totalHours: 6.0, totalHoursLabel: formatHours(6.0), totalIncomeLabel: formatMoney(510),
|
||||
totalIncome: 510,
|
||||
records: [
|
||||
{ id: 'r1', customerName: '王先生', avatarChar: '王', avatarColor: nameToAvatarColor('王'), timeRange: '20:00-22:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '3号台', income: 160 },
|
||||
{ id: 'r2', customerName: '李女士', avatarChar: '李', avatarColor: nameToAvatarColor('李'), timeRange: '16:00-18:00', hours: 2.0, courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP1号房', income: 190 },
|
||||
{ id: 'r3', customerName: '陈女士', avatarChar: '陈', avatarColor: nameToAvatarColor('陈'), timeRange: '10:00-12:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '2号台', income: 160 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月6日',
|
||||
totalHours: 3.5, totalHoursLabel: formatHours(3.5), totalIncomeLabel: formatMoney(280),
|
||||
totalIncome: 280,
|
||||
records: [
|
||||
{ id: 'r4', customerName: '张先生', avatarChar: '张', avatarColor: nameToAvatarColor('张'), timeRange: '19:00-21:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '5号台', income: 160 },
|
||||
{ id: 'r5', customerName: '刘先生', avatarChar: '刘', avatarColor: nameToAvatarColor('刘'), timeRange: '15:30-17:00', hours: 1.5, courseType: '打赏课', courseTypeClass: 'tag-tip', location: '打赏', income: 120 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月5日',
|
||||
totalHours: 4.0, totalHoursLabel: formatHours(4.0), totalIncomeLabel: formatMoney(320),
|
||||
totalIncome: 320,
|
||||
records: [
|
||||
{ id: 'r6', customerName: '陈女士', avatarChar: '陈', avatarColor: nameToAvatarColor('陈'), timeRange: '20:00-22:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '2号台', income: 160 },
|
||||
{ id: 'r7', customerName: '赵先生', avatarChar: '赵', avatarColor: nameToAvatarColor('赵'), timeRange: '14:00-16:00', hours: 2.0, hoursRaw: 2.5, courseType: '基础课', courseTypeClass: 'tag-basic', location: '7号台', income: 160 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月4日',
|
||||
totalHours: 4.0, totalHoursLabel: formatHours(4.0), totalIncomeLabel: formatMoney(320),
|
||||
totalIncome: 350,
|
||||
records: [
|
||||
{ id: 'r8', customerName: '孙先生', avatarChar: '孙', avatarColor: nameToAvatarColor('孙'), timeRange: '19:00-21:00', hours: 2.0, courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP2号房', income: 190 },
|
||||
{ id: 'r9', customerName: '吴女士', avatarChar: '吴', avatarColor: nameToAvatarColor('吴'), timeRange: '15:00-17:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '1号台', income: 160 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月3日',
|
||||
totalHours: 3.5, totalHoursLabel: formatHours(3.5), totalIncomeLabel: formatMoney(280),
|
||||
totalIncome: 280,
|
||||
records: [
|
||||
{ id: 'r10', customerName: '郑先生', avatarChar: '郑', avatarColor: nameToAvatarColor('郑'), timeRange: '20:00-22:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '4号台', income: 160 },
|
||||
{ id: 'r11', customerName: '黄女士', avatarChar: '黄', avatarColor: nameToAvatarColor('黄'), timeRange: '14:30-16:00', hours: 1.5, courseType: '打赏课', courseTypeClass: 'tag-tip', location: '打赏', income: 120 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月2日',
|
||||
totalHours: 4.0, totalHoursLabel: formatHours(4.0), totalIncomeLabel: formatMoney(320),
|
||||
totalIncome: 350,
|
||||
records: [
|
||||
{ id: 'r12', customerName: '林先生', avatarChar: '林', avatarColor: nameToAvatarColor('林'), timeRange: '19:00-21:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '6号台', income: 160 },
|
||||
{ id: 'r13', customerName: '何女士', avatarChar: '何', avatarColor: nameToAvatarColor('何'), timeRange: '13:00-15:00', hours: 2.0, courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP3号房', income: 190 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月1日',
|
||||
totalHours: 6.0, totalHoursLabel: formatHours(6.0), totalIncomeLabel: formatMoney(510),
|
||||
totalIncome: 510,
|
||||
records: [
|
||||
{ id: 'r14', customerName: '王先生', avatarChar: '王', avatarColor: nameToAvatarColor('王'), timeRange: '20:30-22:30', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '3号台', income: 160 },
|
||||
{ id: 'r15', customerName: '马先生', avatarChar: '马', avatarColor: nameToAvatarColor('马'), timeRange: '16:00-18:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '8号台', income: 160 },
|
||||
{ id: 'r16', customerName: '罗女士', avatarChar: '罗', avatarColor: nameToAvatarColor('罗'), timeRange: '12:30-14:30', hours: 2.0, hoursRaw: 2.5, courseType: '基础课', courseTypeClass: 'tag-basic', location: '2号台', income: 160 },
|
||||
{ id: 'r17', customerName: '梁先生', avatarChar: '梁', avatarColor: nameToAvatarColor('梁'), timeRange: '10:00-12:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '5号台', income: 160 },
|
||||
{ id: 'r18', customerName: '宋女士', avatarChar: '宋', avatarColor: nameToAvatarColor('宋'), timeRange: '8:30-10:00', hours: 1.5, courseType: '打赏课', courseTypeClass: 'tag-tip', location: '打赏', income: 120 },
|
||||
{ id: 'r19', customerName: '谢先生', avatarChar: '谢', avatarColor: nameToAvatarColor('谢'), timeRange: '7:00-8:00', hours: 1.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '1号台', income: 80 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '1月31日',
|
||||
totalHours: 5.5, totalHoursLabel: formatHours(5.5), totalIncome: 470, totalIncomeLabel: formatMoney(470),
|
||||
records: [
|
||||
{ id: 'r20', customerName: '韩女士', avatarChar: '韩', avatarColor: nameToAvatarColor('韩'), timeRange: '21:00-23:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '4号台', income: 160 },
|
||||
{ id: 'r21', customerName: '唐先生', avatarChar: '唐', avatarColor: nameToAvatarColor('唐'), timeRange: '18:30-20:30', hours: 2.0, hoursRaw: 2.5, courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP2号房', income: 190 },
|
||||
{ id: 'r22', customerName: '冯女士', avatarChar: '冯', avatarColor: nameToAvatarColor('冯'), timeRange: '14:00-16:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '6号台', income: 160 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '1月30日',
|
||||
totalHours: 3.5, totalHoursLabel: formatHours(3.5), totalIncomeLabel: formatMoney(280),
|
||||
totalIncome: 280,
|
||||
records: [
|
||||
{ id: 'r23', customerName: '张先生', avatarChar: '张', avatarColor: nameToAvatarColor('张'), timeRange: '19:30-21:30', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '5号台', income: 160 },
|
||||
{ id: 'r24', customerName: '刘先生', avatarChar: '刘', avatarColor: nameToAvatarColor('刘'), timeRange: '14:30-16:00', hours: 1.5, courseType: '打赏课', courseTypeClass: 'tag-tip', location: '打赏', income: 120 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '1月29日',
|
||||
totalHours: 4.0, totalHoursLabel: formatHours(4.0), totalIncomeLabel: formatMoney(320),
|
||||
totalIncome: 320,
|
||||
records: [
|
||||
{ id: 'r25', customerName: '陈女士', avatarChar: '陈', avatarColor: nameToAvatarColor('陈'), timeRange: '20:00-22:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '2号台', income: 160 },
|
||||
{ id: 'r26', customerName: '李女士', avatarChar: '李', avatarColor: nameToAvatarColor('李'), timeRange: '13:00-15:00', hours: 2.0, courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP1号房', income: 190 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '1月28日',
|
||||
totalHours: 4.0, totalHoursLabel: formatHours(4.0), totalIncomeLabel: formatMoney(320),
|
||||
totalIncome: 350,
|
||||
records: [
|
||||
{ id: 'r27', customerName: '赵先生', avatarChar: '赵', avatarColor: nameToAvatarColor('赵'), timeRange: '19:00-21:00', hours: 2.0, hoursRaw: 2.5, courseType: '基础课', courseTypeClass: 'tag-basic', location: '7号台', income: 160 },
|
||||
{ id: 'r28', customerName: '董先生', avatarChar: '董', avatarColor: nameToAvatarColor('董'), timeRange: '15:00-17:00', hours: 2.0, courseType: '基础课', courseTypeClass: 'tag-basic', location: '3号台', income: 160 },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '1月27日',
|
||||
totalHours: 4.0, totalHoursLabel: formatHours(4.0), totalIncomeLabel: formatMoney(320),
|
||||
totalIncome: 350,
|
||||
records: [
|
||||
{ id: 'r29', customerName: '孙先生', avatarChar: '孙', avatarColor: nameToAvatarColor('孙'), timeRange: '20:00-22:00', hours: 2.0, courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP2号房', income: 190 },
|
||||
{ id: 'r30', customerName: '黄女士', avatarChar: '黄', avatarColor: nameToAvatarColor('黄'), timeRange: '14:30-16:30', hours: 1.5, courseType: '打赏课', courseTypeClass: 'tag-tip', location: '打赏', income: 120 },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
this.setData({
|
||||
pageState: dateGroups.length > 0 ? 'normal' : 'empty',
|
||||
allRecords,
|
||||
dateGroups,
|
||||
totalCount: 32,
|
||||
totalHours: 59.0,
|
||||
totalIncome: 4720,
|
||||
totalCountLabel: formatCount(32, '笔'),
|
||||
totalHoursLabel: formatHours(59.0),
|
||||
totalHoursRawLabel: formatHours(63.5),
|
||||
totalIncomeLabel: formatMoney(4720),
|
||||
hasMore: false,
|
||||
})
|
||||
} catch (_err) {
|
||||
this.setData({ pageState: 'error' })
|
||||
}
|
||||
|
||||
cb?.()
|
||||
}, 500)
|
||||
},
|
||||
|
||||
/** 重试加载 */
|
||||
onRetry() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/** 返回上一页 */
|
||||
onNavBack() {
|
||||
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/task-list/task-list' }) })
|
||||
},
|
||||
|
||||
/** 切换月份 */
|
||||
switchMonth(e: WechatMiniprogram.TouchEvent) {
|
||||
const direction = e.currentTarget.dataset.direction as 'prev' | 'next'
|
||||
let { currentYear, currentMonth } = this.data
|
||||
|
||||
if (direction === 'prev') {
|
||||
currentMonth--
|
||||
if (currentMonth < 1) {
|
||||
currentMonth = 12
|
||||
currentYear--
|
||||
}
|
||||
} else {
|
||||
currentMonth++
|
||||
if (currentMonth > 12) {
|
||||
currentMonth = 1
|
||||
currentYear++
|
||||
}
|
||||
}
|
||||
|
||||
// 不能超过当前月
|
||||
const now = new Date()
|
||||
const nowYear = now.getFullYear()
|
||||
const nowMonth = now.getMonth() + 1
|
||||
const canGoNext = currentYear < nowYear || (currentYear === nowYear && currentMonth < nowMonth)
|
||||
|
||||
this.setData({
|
||||
currentYear,
|
||||
currentMonth,
|
||||
monthLabel: `${currentYear}年${currentMonth}月`,
|
||||
canGoNext,
|
||||
canGoPrev: true,
|
||||
})
|
||||
|
||||
this.loadData()
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,130 @@
|
||||
<wxs src="../../utils/format.wxs" module="fmt" />
|
||||
<!-- toast 加载浮层(fixed,不销毁内容,不白屏) -->
|
||||
<view class="g-toast-loading" wx:if="{{pageState === 'loading'}}">
|
||||
<view class="g-toast-loading-inner">
|
||||
<t-loading theme="circular" size="40rpx" />
|
||||
<text class="g-toast-loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误态(全屏,仅在 error 时展示) -->
|
||||
<view class="page-error" wx:if="{{pageState === 'error'}}">
|
||||
<t-icon name="close-circle" size="120rpx" color="#e34d59" />
|
||||
<text class="error-text">加载失败,请点击重试</text>
|
||||
<view class="retry-btn" hover-class="retry-btn--hover" bindtap="onRetry">
|
||||
<text>重试</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主体内容(始终挂载,不随 loading 销毁) -->
|
||||
<block wx:if="{{pageState !== 'error'}}">
|
||||
<!-- Banner 区域(复用助教详情样式) -->
|
||||
<view class="banner-section">
|
||||
<image class="banner-bg-img" src="/assets/images/banner-bg-coral-aurora.svg" mode="widthFix" />
|
||||
<view class="banner-overlay">
|
||||
<view class="coach-header">
|
||||
<view class="avatar-box">
|
||||
<image class="avatar-img" src="/assets/images/avatar-coach.png" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="info-middle">
|
||||
<view class="name-row">
|
||||
<text class="coach-name">{{coachName}}</text>
|
||||
<coach-level-tag level="{{coachLevel}}" />
|
||||
</view>
|
||||
<view class="skill-row">
|
||||
<text class="store-name-text">{{storeName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 月份切换 -->
|
||||
<view class="month-switcher">
|
||||
<view class="month-btn {{canGoPrev ? '' : 'month-btn-disabled'}}" hover-class="month-btn--hover" data-direction="prev" bindtap="switchMonth">
|
||||
<t-icon name="chevron-left" size="32rpx" />
|
||||
</view>
|
||||
<text class="month-label">{{monthLabel}}</text>
|
||||
<view class="month-btn {{canGoNext ? '' : 'month-btn-disabled'}}" hover-class="month-btn--hover" data-direction="next" bindtap="switchMonth">
|
||||
<t-icon name="chevron-right" size="32rpx" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计概览 -->
|
||||
<view class="stats-overview">
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">总记录</text>
|
||||
<text class="stat-value">{{totalCountLabel}}</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">总业绩时长</text>
|
||||
<text class="stat-value stat-primary">{{totalHoursLabel}}</text>
|
||||
<text class="stat-hint">预估</text>
|
||||
<text class="stat-hours-raw" wx:if="{{totalHoursRawLabel}}">折前 {{totalHoursRawLabel}}</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">收入</text>
|
||||
<text class="stat-value stat-success">{{totalIncomeLabel}}</text>
|
||||
<text class="stat-hint">预估</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空态 -->
|
||||
<view class="page-empty" wx:if="{{pageState === 'empty'}}">
|
||||
<t-icon name="chart-bar" size="120rpx" color="#dcdcdc" />
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 记录列表 -->
|
||||
<view class="records-container" wx:elif="{{pageState === 'normal' || pageState === 'loading'}}">
|
||||
<view class="records-card">
|
||||
<block wx:for="{{dateGroups}}" wx:key="date">
|
||||
<!-- 日期分隔线 -->
|
||||
<view class="date-divider">
|
||||
<text decode class="dd-date">{{item.date}} —</text>
|
||||
<text decode class="dd-stats" wx:if="{{item.totalHoursLabel}}">{{item.totalHoursLabel}} · 预估 {{item.totalIncomeLabel}} </text>
|
||||
<view class="dd-line"></view>
|
||||
</view>
|
||||
|
||||
<!-- 该日期下的记录 -->
|
||||
<view class="record-item" wx:for="{{item.records}}" wx:for-item="rec" wx:key="id"
|
||||
hover-class="record-item--hover">
|
||||
<view class="record-avatar avatar-{{rec.avatarColor}}">
|
||||
<text>{{rec.avatarChar}}</text>
|
||||
</view>
|
||||
<view class="record-content">
|
||||
<view class="record-top">
|
||||
<view class="record-name-time">
|
||||
<text class="record-name">{{rec.customerName}}</text>
|
||||
<text class="record-time">{{rec.timeRange}}</text>
|
||||
</view>
|
||||
<view class="record-hours-wrap">
|
||||
<text class="record-hours">{{fmt.hours(rec.hours)}}</text>
|
||||
<text class="record-hours-deduct" wx:if="{{rec.hoursRaw}}">(折后 {{fmt.hours(rec.hoursRaw)}})</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-bottom">
|
||||
<view class="record-tags">
|
||||
<text class="course-tag {{rec.courseTypeClass}}">{{rec.courseType}}</text>
|
||||
<text class="record-location">{{rec.location}}</text>
|
||||
</view>
|
||||
<text class="record-income">我的预估收入 <text class="record-income-val">{{fmt.money(rec.income)}}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 列表底部提示 -->
|
||||
<view class="list-end-hint">
|
||||
<text>— 已加载全部记录 —</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- AI 悬浮按钮 -->
|
||||
<ai-float-button />
|
||||
|
||||
<dev-fab />
|
||||
@@ -0,0 +1,492 @@
|
||||
/* pages/performance-records/performance-records.wxss */
|
||||
|
||||
page {
|
||||
background-color: #f3f3f3;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
view {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 加载态 / 空态 / 错误态
|
||||
* ============================================ */
|
||||
|
||||
/* toast 风格加载(浮在页面中央,不全屏变白) */
|
||||
.toast-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast-loading-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background: rgba(36, 36, 36, 0.75);
|
||||
border-radius: 24rpx;
|
||||
padding: 36rpx 48rpx;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.toast-loading-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
|
||||
.page-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.page-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60vh;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 28rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 16rpx 48rpx;
|
||||
background: #0052d9;
|
||||
color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.retry-btn--hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* Banner(复用助教详情)
|
||||
* ============================================ */
|
||||
.banner-section {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-bg-img {
|
||||
position: absolute;
|
||||
top: -50rpx;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.banner-overlay {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 36rpx 40rpx;
|
||||
}
|
||||
|
||||
.coach-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.avatar-box {
|
||||
width: 98rpx;
|
||||
height: 98rpx;
|
||||
border-radius: 32rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.info-middle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.coach-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.skill-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.skill-tag {
|
||||
padding: 4rpx 16rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* 助教等级 tag — 遵循 VI 规范(§5 助教等级配色) */
|
||||
.coach-level-tag {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.coach-level-star { color: #fbbf24; background: #fffef0; }
|
||||
.coach-level-senior { color: #e91e63; background: #ffe6e8; }
|
||||
.coach-level-middle { color: #ed7b2f; background: #fff3e6; }
|
||||
.coach-level-junior { color: #0052d9; background: #ecf2fe; }
|
||||
|
||||
/* 球会名称 — 纯文字,无背景 */
|
||||
.store-name-text {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 月份切换
|
||||
* ============================================ */
|
||||
.month-switcher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 48rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #ffffff;
|
||||
border-bottom: 2rpx solid #eeeeee;
|
||||
}
|
||||
|
||||
.month-btn {
|
||||
padding: 12rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.month-btn-disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.month-btn--hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.month-label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #242424;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 统计概览
|
||||
* ============================================ */
|
||||
.stats-overview {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #ffffff;
|
||||
border-bottom: 2rpx solid #eeeeee;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 20rpx;
|
||||
color: #a6a6a6;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #242424;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.stat-primary {
|
||||
color: #0052d9;
|
||||
}
|
||||
|
||||
.stat-success {
|
||||
color: #00a870;
|
||||
}
|
||||
|
||||
.stat-sub-hint {
|
||||
font-size: 20rpx;
|
||||
color: #c5c5c5;
|
||||
margin-top: 2rpx;
|
||||
line-height: 26rpx;
|
||||
}
|
||||
|
||||
.stat-hours-raw {
|
||||
font-size: 20rpx;
|
||||
color: #a6a6a6;
|
||||
margin-top: 2rpx;
|
||||
line-height: 26rpx;
|
||||
}
|
||||
|
||||
.stat-hint {
|
||||
font-size: 20rpx;
|
||||
color: #ed7b2f;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
height: 80rpx;
|
||||
background: #eeeeee;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
* 记录列表
|
||||
* ============================================ */
|
||||
.records-container {
|
||||
padding: 24rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.records-card {
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.date-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 32rpx 8rpx;
|
||||
}
|
||||
|
||||
.dd-date {
|
||||
font-size: 22rpx;
|
||||
color: #8b8b8b;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.dd-line {
|
||||
flex: 1;
|
||||
height: 2rpx;
|
||||
background: #dcdcdc;
|
||||
}
|
||||
|
||||
.dd-stats {
|
||||
font-size: 22rpx;
|
||||
color: #a6a6a6;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
}
|
||||
|
||||
.record-item--hover {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
/* 头像 */
|
||||
.record-avatar {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-from-blue { background: linear-gradient(135deg, #60a5fa, #6366f1); }
|
||||
.avatar-from-pink { background: linear-gradient(135deg, #f472b6, #f43f5e); }
|
||||
.avatar-from-teal { background: linear-gradient(135deg, #2dd4bf, #10b981); }
|
||||
.avatar-from-green { background: linear-gradient(135deg, #4ade80, #14b8a6); }
|
||||
.avatar-from-orange { background: linear-gradient(135deg, #fb923c, #f59e0b); }
|
||||
.avatar-from-purple { background: linear-gradient(135deg, #c084fc, #8b5cf6); }
|
||||
.avatar-from-violet { background: linear-gradient(135deg, #a78bfa, #7c3aed); }
|
||||
.avatar-from-amber { background: linear-gradient(135deg, #fbbf24, #eab308); }
|
||||
.avatar-from-sky { background: linear-gradient(135deg, #38bdf8, #0ea5e9); }
|
||||
.avatar-from-lime { background: linear-gradient(135deg, #a3e635, #65a30d); }
|
||||
.avatar-from-rose { background: linear-gradient(135deg, #fb7185, #e11d48); }
|
||||
.avatar-from-fuchsia{ background: linear-gradient(135deg, #e879f9, #a21caf); }
|
||||
.avatar-from-slate { background: linear-gradient(135deg, #94a3b8, #475569); }
|
||||
.avatar-from-indigo { background: linear-gradient(135deg, #818cf8, #4338ca); }
|
||||
.avatar-from-cyan { background: linear-gradient(135deg, #22d3ee, #0891b2); }
|
||||
.avatar-from-yellow { background: linear-gradient(135deg, #facc15, #ca8a04); }
|
||||
|
||||
/* 内容区 */
|
||||
.record-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.record-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.record-name-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.record-name {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #242424;
|
||||
flex-shrink: 0;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 22rpx;
|
||||
color: #a6a6a6;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.record-hours-wrap {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.record-hours {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #059669;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.record-hours-deduct {
|
||||
font-size: 20rpx;
|
||||
color: #a6a6a6;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.record-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.record-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.course-tag {
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.tag-basic {
|
||||
background: #ecfdf5;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.tag-vip {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tag-tip {
|
||||
background: #fffbeb;
|
||||
color: #a16207;
|
||||
}
|
||||
|
||||
.record-location {
|
||||
font-size: 22rpx;
|
||||
color: #8b8b8b;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.record-income {
|
||||
font-size: 22rpx;
|
||||
color: #c5c5c5;
|
||||
flex-shrink: 0;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.record-income-val {
|
||||
font-weight: 500;
|
||||
color: #5e5e5e;
|
||||
}
|
||||
|
||||
/* 列表底部提示 */
|
||||
.list-end-hint {
|
||||
text-align: center;
|
||||
padding: 24rpx 0 28rpx;
|
||||
font-size: 22rpx;
|
||||
color: #c5c5c5;
|
||||
}
|
||||
Reference in New Issue
Block a user