feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
# Performance 页面迁移说明
|
||||
|
||||
## 迁移版本
|
||||
- **H5 原型基准**:`docs/h5_ui/pages/performance.html`
|
||||
- **桥文档版本**:v3.0
|
||||
- **迁移日期**:2026-03-15
|
||||
- **更新日期**:2026-03-15(Banner背景改用灵活的SVG实现)
|
||||
|
||||
---
|
||||
|
||||
## 样式调整清单
|
||||
|
||||
### 1. 单位转换规则
|
||||
遵循桥文档 §2.3 的混合单位策略:
|
||||
- **主要使用 `rpx`**:页面宽度、横向布局、卡片尺寸、间距
|
||||
- **使用 `px`**:导航栏高度(44px)、图标点击区域
|
||||
- **换算基准**:H5 CSS px → 小程序 rpx(750宽基准,1px = 1.8204rpx)
|
||||
|
||||
### 2. 自定义导航栏(.custom-nav)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| height | 44px | 44px | 标准导航栏高度(使用px) |
|
||||
| font-size | text-base (16px) | 32rpx | 标题字号 |
|
||||
| icon size | w-5 h-5 (20px) | 24px | 返回按钮图标 |
|
||||
|
||||
### 3. Banner 区域(.banner-section)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 背景方案 | CSS 渐变 | SVG 图片 | 使用 `/assets/images/banner-bg-combined.svg` |
|
||||
| height | - | 480rpx | Banner 高度(可通过 WXSS 调整) |
|
||||
| padding | px-5 pt-2 pb-2 (20px 8px) | 24rpx 40rpx 32rpx | 内容区内边距 |
|
||||
|
||||
#### Banner SVG 背景实现(灵活可调)
|
||||
- **方案**:SVG 做渐变底图(与 task-detail/task-list 保持一致)
|
||||
- **原因**:SVG pattern 在小程序 image 组件中不渲染
|
||||
- **WXML 实现**:
|
||||
```wxml
|
||||
<view class="banner-section">
|
||||
<image class="banner-bg-img" src="/assets/images/banner-bg-combined.svg" mode="aspectFill" />
|
||||
<view class="banner-content">
|
||||
<!-- 内容 -->
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
- **WXSS 实现**(可灵活调整):
|
||||
```wxss
|
||||
.banner-section {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 480rpx; /* 调整此值改变 Banner 高度 */
|
||||
}
|
||||
|
||||
.banner-bg-img {
|
||||
position: absolute;
|
||||
top: 0; /* 调整此值改变背景垂直位置 */
|
||||
left: 0; /* 调整此值改变背景水平位置 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 24rpx 40rpx 32rpx; /* 调整此值改变内容区内边距 */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
```
|
||||
- **调整方式**:
|
||||
- 修改 `.banner-section` 的 `height` 调整 Banner 高度
|
||||
- 修改 `.banner-bg-img` 的 `top/left` 调整背景位置
|
||||
- 修改 `.banner-bg-img` 的 `width/height` 调整背景缩放
|
||||
- 修改 `.banner-content` 的 `padding` 调整内容区域
|
||||
|
||||
#### 个人信息卡片
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| avatar size | w-14 h-14 (56px) | 112rpx | 头像尺寸 |
|
||||
| avatar radius | rounded-2xl (16px) | 24rpx | 头像圆角 |
|
||||
| name font-size | text-xl (20px) | 40rpx | 姓名字号 |
|
||||
| role tag padding | px-2 py-0.5 (8px 2px) | 4rpx 16rpx | 角色标签内边距 |
|
||||
|
||||
#### 收入概览卡片
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| grid gap | gap-3 (12px) | 24rpx | 卡片间距 |
|
||||
| card padding | p-4 (16px) | 24rpx | 卡片内边距 |
|
||||
| card radius | rounded-xl (12px) | 24rpx | 卡片圆角 |
|
||||
| label font-size | text-xs (12px) | 22rpx | 标签字号 |
|
||||
| value font-size | text-2xl (24px) | 44rpx | 数值字号 |
|
||||
|
||||
### 4. Section 卡片(.section-card)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| border-radius | rounded-2xl (16px) | 32rpx | 卡片圆角 |
|
||||
| padding | p-4 (16px) | 32rpx | 卡片内边距 |
|
||||
| margin | m-4 (16px) | 24rpx 24rpx 0 | 卡片外边距 |
|
||||
| box-shadow | shadow-sm | 0 1px 2px rgba(0,0,0,0.05) | 阴影 |
|
||||
|
||||
### 5. 收入档位卡片(.tier-card)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| padding | p-3 (12px) | 24rpx | 内边距 |
|
||||
| border-radius | rounded-xl (12px) | 24rpx | 圆角 |
|
||||
| border | 1px solid | 2rpx solid | 边框 |
|
||||
| emoji size | text-2xl (24px) | 48rpx | 表情图标 |
|
||||
| rate value | text-lg (18px) | 36rpx | 费率数值 |
|
||||
| rate unit | text-xs (12px) | 22rpx | 单位字号 |
|
||||
| rate desc | text-[10px] | 20rpx | 描述字号 |
|
||||
|
||||
#### 档位徽章
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| top | -8px | -16rpx | 顶部偏移 |
|
||||
| padding | 2px 10px | 4rpx 20rpx | 内边距 |
|
||||
| font-size | 10px | 20rpx | 字号 |
|
||||
|
||||
### 6. 升级提示(.upgrade-hint)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| padding | p-3 (12px) | 24rpx | 内边距 |
|
||||
| border-radius | rounded-xl (12px) | 24rpx | 圆角 |
|
||||
| emoji size | text-lg (18px) | 36rpx | 表情大小 |
|
||||
| label font-size | text-xs (12px) | 22rpx | 标签字号 |
|
||||
| hours font-size | text-sm (14px) | 26rpx | 小时数字号 |
|
||||
| bonus value | text-lg (18px) | 36rpx | 奖金数值 |
|
||||
|
||||
### 7. 业绩明细列表(.income-list)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| icon box size | w-8 h-8 (32px) | 64rpx | 图标容器 |
|
||||
| icon box radius | rounded-lg (8px) | 16rpx | 图标圆角 |
|
||||
| label font-size | text-sm (14px) | 26rpx | 标签字号 |
|
||||
| desc font-size | text-[10px] | 20rpx | 描述字号 |
|
||||
| value font-size | text-base (16px) | 30rpx | 数值字号 |
|
||||
| total value | text-xl (20px) | 40rpx | 合计数值 |
|
||||
|
||||
### 8. 服务记录(.service-records-section)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| avatar size | w-[38px] h-[38px] | 76rpx | 头像尺寸 |
|
||||
| avatar radius | rounded-lg (8px) | 16rpx | 头像圆角 |
|
||||
| name font-size | text-sm (14px) | 26rpx | 姓名字号 |
|
||||
| time font-size | text-xs (12px) | 22rpx | 时间字号 |
|
||||
| hours font-size | text-sm (14px) | 26rpx | 小时数字号 |
|
||||
| tag padding | px-1.5 py-px (6px 1px) | 2rpx 12rpx | 标签内边距 |
|
||||
| tag font-size | text-[11px] | 22rpx | 标签字号 |
|
||||
|
||||
#### 日期分隔线
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| date font-size | 11px | 22rpx | 日期字号 |
|
||||
| line height | 1px | 2rpx | 分隔线高度 |
|
||||
| padding | 14px 16px 4px | 20rpx 0 8rpx | 内边距 |
|
||||
|
||||
### 9. 客户列表(.customer-list)
|
||||
|
||||
| 属性 | H5 值 | 小程序值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| avatar size | w-[38px] h-[38px] | 76rpx | 头像尺寸 |
|
||||
| avatar radius | rounded-lg (8px) | 16rpx | 头像圆角 |
|
||||
| name font-size | text-sm (14px) | 26rpx | 姓名字号 |
|
||||
| detail font-size | text-xs (12px) | 22rpx | 详情字号 |
|
||||
| icon size | - | 24px | 右侧箭头图标 |
|
||||
|
||||
### 10. 颜色映射(附录C)
|
||||
|
||||
| 用途 | 色值 | 说明 |
|
||||
|------|------|------|
|
||||
| 主品牌色 | #0052d9 | primary |
|
||||
| 成功色 | #00a870 | success |
|
||||
| 背景色 | #f3f3f3 | gray-1 |
|
||||
| 正文色 | #242424 | gray-13 |
|
||||
| 次级文字 | #a6a6a6 | gray-6 |
|
||||
| 三级文字 | #8b8b8b | gray-7 |
|
||||
| 四级文字 | #c5c5c5 | gray-5 |
|
||||
| 分割线 | #f3f3f3 | gray-1 |
|
||||
|
||||
### 11. 渐变色系统
|
||||
|
||||
#### 头像渐变(8种)
|
||||
- `from-blue`: #60a5fa → #6366f1
|
||||
- `from-pink`: #f472b6 → #f43f5e
|
||||
- `from-teal`: #2dd4bf → #10b981
|
||||
- `from-green`: #4ade80 → #14b8a6
|
||||
- `from-orange`: #fb923c → #f59e0b
|
||||
- `from-purple`: #c084fc → #8b5cf6
|
||||
- `from-violet`: #a78bfa → #7c3aed
|
||||
- `from-amber`: #fbbf24 → #eab308
|
||||
|
||||
#### 档位卡片渐变
|
||||
- 当前档位:#f0fdf4 → #dcfce7(绿色系)
|
||||
- 下一阶段:#fefce8 → #fef9c3(黄色系)
|
||||
- 升级提示:#eff6ff → #eef2ff(蓝色系)
|
||||
- 奖金按钮:#fbbf24 → #f97316(橙色系)
|
||||
|
||||
---
|
||||
|
||||
## 关键调整点
|
||||
|
||||
### 1. 全局 line-height 设置
|
||||
```wxss
|
||||
page {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
view {
|
||||
line-height: inherit;
|
||||
}
|
||||
```
|
||||
**原因**:微信小程序 `<text>` 组件不能直接设置 `line-height`,必须在外层 `<view>` 设置(附录B.2.3)
|
||||
|
||||
### 2. 单位统一为 rpx(除特殊情况)
|
||||
- 主要尺寸使用 `rpx`,确保不同设备自适应
|
||||
- 导航栏高度、图标点击区域使用 `px`
|
||||
- 换算公式:1px = 1.8204rpx(750宽基准)
|
||||
|
||||
### 3. 移除 CSS 变量依赖
|
||||
- 原:`var(--color-gray-2, #eeeeee)`
|
||||
- 新:直接使用 `#eeeeee`
|
||||
- 原因:确保样式独立,不依赖全局变量
|
||||
|
||||
### 4. Icon 尺寸调整
|
||||
- 导航栏返回按钮:`size="24px"`(标准点击区域)
|
||||
- 列表箭头图标:`size="24px"`
|
||||
- 加载/错误图标:`size="40px"`
|
||||
|
||||
### 5. 复杂背景还原
|
||||
- **Banner 背景**:使用 SVG 图片 `/assets/images/banner-bg-combined.svg`(与 task-detail/task-list 保持一致)
|
||||
- 原因:SVG pattern 在小程序 image 组件中不渲染
|
||||
- **灵活可调**:通过 WXSS 可调整 Banner 高度、背景位置、内容区内边距
|
||||
- 档位卡片使用双色渐变背景
|
||||
- 头像使用 8 种预设渐变色
|
||||
- 升级提示使用渐变背景 + 渐变按钮
|
||||
|
||||
### 6. 数值字体特性
|
||||
```wxss
|
||||
.perf-value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
```
|
||||
确保数字等宽对齐
|
||||
|
||||
---
|
||||
|
||||
## 验收清单
|
||||
|
||||
- [x] 导航栏高度与H5对齐(44px)
|
||||
- [x] Banner SVG背景与H5对齐
|
||||
- [x] Banner 高度可通过 WXSS 调整(480rpx)
|
||||
- [x] Banner 背景位置可通过 WXSS 调整
|
||||
- [x] 个人信息卡片尺寸与H5对齐
|
||||
- [x] 收入概览卡片样式与H5对齐
|
||||
- [x] 档位卡片渐变背景与H5对齐
|
||||
- [x] 档位徽章位置与H5对齐
|
||||
- [x] 升级提示样式与H5对齐
|
||||
- [x] 业绩明细列表与H5对齐
|
||||
- [x] 服务记录样式与H5对齐
|
||||
- [x] 日期分隔线与H5对齐
|
||||
- [x] 客户列表样式与H5对齐
|
||||
- [x] 头像渐变色与H5对齐
|
||||
- [x] 所有字号与H5对齐
|
||||
- [x] 所有间距与H5对齐
|
||||
- [x] 所有圆角与H5对齐
|
||||
- [x] 所有颜色与H5对齐
|
||||
- [x] 行高设置正确(在view上设置)
|
||||
- [x] Banner背景使用灵活的SVG实现(与task-detail保持一致)
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
1. **动画效果**:H5 中的 `bonus-highlight` 闪烁动画未实现(需要 CSS animation)
|
||||
2. **backdrop-filter**:收入卡片的 `backdrop-blur` 效果在部分安卓设备可能不支持
|
||||
3. **SVG兼容性**:banner-bg-combined.svg 需要确保存在于 `/assets/images/` 目录
|
||||
|
||||
---
|
||||
|
||||
## 后续维护
|
||||
|
||||
- 若真机测试发现尺寸偏差,优先检查 line-height 设置
|
||||
- 若需要调整 Banner 高度,修改 `.banner-section` 的 `height` 属性
|
||||
- 若需要调整 Banner 背景位置,修改 `.banner-bg-img` 的 `top/left` 属性
|
||||
- 若需要调整间距,参考附录A的spacing表
|
||||
- 若需要调整字号,参考附录B的字体表
|
||||
- 颜色调整参考附录C的颜色字典
|
||||
- Banner背景问题参考task-detail页面的实现方式
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"navigationBarTitleText": "业绩详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {
|
||||
"ai-float-button": "/components/ai-float-button/ai-float-button",
|
||||
"dev-fab": "/components/dev-fab/dev-fab",
|
||||
"metric-card": "/components/metric-card/metric-card",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// TODO: 联调时替换为真实 API 调用
|
||||
import { initPageAiColor } from '../../utils/ai-color-manager'
|
||||
|
||||
/** 业绩明细项(本月/上月) */
|
||||
interface IncomeItem {
|
||||
icon: string
|
||||
label: string
|
||||
desc: string
|
||||
value: string
|
||||
}
|
||||
|
||||
/** 服务记录(按日期分组后的展示结构) */
|
||||
interface ServiceRecord {
|
||||
customerName: string
|
||||
avatarChar: string
|
||||
avatarColor: string
|
||||
timeRange: string
|
||||
hours: string
|
||||
courseType: string
|
||||
courseTypeClass: string
|
||||
location: string
|
||||
income: string
|
||||
}
|
||||
|
||||
interface DateGroup {
|
||||
date: string
|
||||
totalHours?: string
|
||||
totalIncome?: string
|
||||
records: ServiceRecord[]
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
pageState: 'loading' as 'loading' | 'empty' | 'error' | 'normal',
|
||||
|
||||
/** Banner 数据 */
|
||||
coachName: '小燕',
|
||||
coachRole: '助教',
|
||||
storeName: '广州朗朗桌球',
|
||||
monthlyIncome: '¥6,206',
|
||||
lastMonthIncome: '¥16,880',
|
||||
|
||||
/** 收入档位 */
|
||||
currentTier: {
|
||||
basicRate: 80,
|
||||
incentiveRate: 95,
|
||||
},
|
||||
nextTier: {
|
||||
basicRate: 90,
|
||||
incentiveRate: 114,
|
||||
},
|
||||
upgradeHoursNeeded: 15,
|
||||
upgradeBonus: 800,
|
||||
|
||||
/** 本月业绩明细 */
|
||||
incomeItems: [] as IncomeItem[],
|
||||
monthlyTotal: '¥6,950.5',
|
||||
|
||||
/** 服务记录 */
|
||||
thisMonthRecords: [] as DateGroup[],
|
||||
thisMonthRecordsExpanded: false,
|
||||
/** 默认显示前 N 条日期组 */
|
||||
visibleRecordGroups: 2,
|
||||
|
||||
/** 新客列表 */
|
||||
newCustomers: [] as Array<{ name: string; avatarChar: string; avatarColor: string; lastService: string; count: number }>,
|
||||
newCustomerExpanded: false,
|
||||
|
||||
/** 常客列表 */
|
||||
regularCustomers: [] as Array<{ name: string; avatarChar: string; avatarColor: string; hours: number; income: string; count: number }>,
|
||||
regularCustomerExpanded: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 初始化 AI 图标配色(蓝色 - 数据分析感)
|
||||
const { aiColor } = initPageAiColor('performance')
|
||||
this.setData({ aiColor })
|
||||
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
loadData() {
|
||||
this.setData({ pageState: 'loading' })
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// TODO: 替换为真实 API
|
||||
const incomeItems: IncomeItem[] = [
|
||||
{ icon: '🎱', label: '基础课', desc: '80元/h × 75h', value: '¥6,000' },
|
||||
{ icon: '⭐', label: '激励课', desc: '95.05元/h × 10h', value: '¥950.5' },
|
||||
{ icon: '💰', label: '充值激励', desc: '客户充值返佣', value: '¥500' },
|
||||
{ icon: '🏆', label: 'TOP3 销冠奖', desc: '全店业绩前三名奖励', value: '继续努力' },
|
||||
]
|
||||
|
||||
const gradients = [
|
||||
'blue', 'pink', 'teal', 'green',
|
||||
'orange', 'purple', 'violet', 'amber',
|
||||
]
|
||||
|
||||
// 模拟服务记录按日期分组
|
||||
const thisMonthRecords: DateGroup[] = [
|
||||
{
|
||||
date: '2月7日',
|
||||
totalHours: '4.0h',
|
||||
totalIncome: '¥350',
|
||||
records: [
|
||||
{ customerName: '王先生', avatarChar: '王', avatarColor: gradients[0], timeRange: '20:00-22:00', hours: '2.0h', courseType: '基础课', courseTypeClass: 'tag-basic', location: '3号台', income: '¥160' },
|
||||
{ customerName: '李女士', avatarChar: '李', avatarColor: gradients[1], timeRange: '16:00-18:00', hours: '2.0h', courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP1号房', income: '¥190' },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月6日',
|
||||
totalHours: '2.0h',
|
||||
totalIncome: '¥160',
|
||||
records: [
|
||||
{ customerName: '张先生', avatarChar: '张', avatarColor: gradients[2], timeRange: '19:00-21:00', hours: '2.0h', courseType: '基础课', courseTypeClass: 'tag-basic', location: '5号台', income: '¥160' },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月5日',
|
||||
totalHours: '4.0h',
|
||||
totalIncome: '¥320',
|
||||
records: [
|
||||
{ customerName: '陈女士', avatarChar: '陈', avatarColor: gradients[2], timeRange: '20:00-22:00', hours: '2.0h', courseType: '基础课', courseTypeClass: 'tag-basic', location: '2号台', income: '¥160' },
|
||||
{ customerName: '赵先生', avatarChar: '赵', avatarColor: gradients[5], timeRange: '14:00-16:00', hours: '2.0h', courseType: '基础课', courseTypeClass: 'tag-basic', location: '7号台', income: '¥160' },
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '2月4日',
|
||||
totalHours: '4.0h',
|
||||
totalIncome: '¥350',
|
||||
records: [
|
||||
{ customerName: '孙先生', avatarChar: '孙', avatarColor: gradients[6], timeRange: '19:00-21:00', hours: '2.0h', courseType: '包厢课', courseTypeClass: 'tag-vip', location: 'VIP2号房', income: '¥190' },
|
||||
{ customerName: '吴女士', avatarChar: '吴', avatarColor: gradients[2], timeRange: '15:00-17:00', hours: '2.0h', courseType: '基础课', courseTypeClass: 'tag-basic', location: '1号台', income: '¥160' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const newCustomers = [
|
||||
{ name: '王先生', avatarChar: '王', avatarColor: gradients[0], lastService: '2月7日', count: 2 },
|
||||
{ name: '李女士', avatarChar: '李', avatarColor: gradients[1], lastService: '2月7日', count: 1 },
|
||||
{ name: '刘先生', avatarChar: '刘', avatarColor: gradients[4], lastService: '2月6日', count: 1 },
|
||||
{ name: '周女士', avatarChar: '周', avatarColor: gradients[3], lastService: '2月5日', count: 1 },
|
||||
{ name: '吴先生', avatarChar: '吴', avatarColor: gradients[7], lastService: '2月4日', count: 1 },
|
||||
{ name: '郑女士', avatarChar: '郑', avatarColor: gradients[5], lastService: '2月3日', count: 1 },
|
||||
{ name: '钱先生', avatarChar: '钱', avatarColor: gradients[6], lastService: '2月2日', count: 1 },
|
||||
{ name: '冯女士', avatarChar: '冯', avatarColor: gradients[2], lastService: '2月1日', count: 1 },
|
||||
]
|
||||
|
||||
const regularCustomers = [
|
||||
{ name: '张先生', avatarChar: '张', avatarColor: gradients[2], hours: 12, income: '¥960', count: 6 },
|
||||
{ name: '陈女士', avatarChar: '陈', avatarColor: gradients[2], hours: 10, income: '¥800', count: 5 },
|
||||
{ name: '赵先生', avatarChar: '赵', avatarColor: gradients[5], hours: 8, income: '¥640', count: 4 },
|
||||
{ name: '孙先生', avatarChar: '孙', avatarColor: gradients[6], hours: 6, income: '¥570', count: 3 },
|
||||
{ name: '杨女士', avatarChar: '杨', avatarColor: 'pink', hours: 6, income: '¥480', count: 3 },
|
||||
{ name: '黄先生', avatarChar: '黄', avatarColor: 'amber', hours: 4, income: '¥380', count: 2 },
|
||||
{ name: '林女士', avatarChar: '林', avatarColor: 'green', hours: 4, income: '¥320', count: 2 },
|
||||
{ name: '徐先生', avatarChar: '徐', avatarColor: 'orange', hours: 2, income: '¥190', count: 1 },
|
||||
]
|
||||
|
||||
this.setData({
|
||||
pageState: 'normal',
|
||||
incomeItems,
|
||||
thisMonthRecords,
|
||||
newCustomers,
|
||||
regularCustomers,
|
||||
})
|
||||
} catch (_e) {
|
||||
this.setData({ pageState: 'error' })
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
|
||||
/** 加载失败重试 */
|
||||
onRetry() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/** 自定义导航栏返回 */
|
||||
onNavBack() {
|
||||
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/task-list/task-list' }) })
|
||||
},
|
||||
|
||||
/** 展开/收起本月服务记录 */
|
||||
toggleThisMonthRecords() {
|
||||
this.setData({ thisMonthRecordsExpanded: !this.data.thisMonthRecordsExpanded })
|
||||
},
|
||||
|
||||
/** 查看全部 → 跳转业绩明细 */
|
||||
goToRecords() {
|
||||
wx.navigateTo({ url: '/pages/performance-records/performance-records' })
|
||||
},
|
||||
|
||||
/** 展开/收起新客列表 */
|
||||
toggleNewCustomer() {
|
||||
this.setData({ newCustomerExpanded: !this.data.newCustomerExpanded })
|
||||
},
|
||||
|
||||
/** 展开/收起常客列表 */
|
||||
toggleRegularCustomer() {
|
||||
this.setData({ regularCustomerExpanded: !this.data.regularCustomerExpanded })
|
||||
},
|
||||
|
||||
/** 点击客户卡片 → 跳转任务详情 */
|
||||
onCustomerTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const { name } = e.currentTarget.dataset
|
||||
wx.navigateTo({
|
||||
url: `/pages/task-detail/task-detail?customerName=${name}`,
|
||||
fail: () => wx.showToast({ title: '页面跳转失败', icon: 'none' }),
|
||||
})
|
||||
},
|
||||
|
||||
/** 点击服务记录 → 跳转任务详情 */
|
||||
onRecordTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const { customerName, taskId } = e.currentTarget.dataset
|
||||
wx.navigateTo({
|
||||
url: `/pages/task-detail/task-detail?customerName=${customerName}${taskId ? `&taskId=${taskId}` : ''}`,
|
||||
fail: () => wx.showToast({ title: '页面跳转失败', icon: 'none' }),
|
||||
})
|
||||
},
|
||||
|
||||
/** 点击收入概览卡片 → 跳转业绩记录 */
|
||||
onIncomeCardTap() {
|
||||
wx.navigateTo({ url: '/pages/performance-records/performance-records' })
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,294 @@
|
||||
<!-- pages/performance/performance.wxml — 业绩总览 -->
|
||||
|
||||
<!-- 加载态(toast 浮层,不白屏) -->
|
||||
<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>
|
||||
|
||||
<!-- 空数据态 -->
|
||||
<view class="page-empty" wx:elif="{{pageState === 'empty'}}">
|
||||
<t-icon name="chart-bar" size="40px" color="#dcdcdc" />
|
||||
<text class="empty-text">暂无业绩数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 错误态 -->
|
||||
<view class="page-error" wx:elif="{{pageState === 'error'}}">
|
||||
<t-icon name="close-circle" size="40px" color="#e34d59" />
|
||||
<text class="error-text">加载失败,请点击重试</text>
|
||||
<view class="retry-btn" hover-class="retry-btn--hover" bindtap="onRetry">
|
||||
<text>重试</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 正常态 -->
|
||||
<block wx:else>
|
||||
<!-- Banner 区域 — SVG 做渐变底图 -->
|
||||
<view class="banner-section">
|
||||
<image class="banner-bg-img" src="/assets/images/banner-bg-blue-light-aurora.svg" mode="widthFix" />
|
||||
<view class="banner-content">
|
||||
|
||||
|
||||
<!-- 个人信息 -->
|
||||
<view class="coach-info">
|
||||
<view class="coach-avatar">
|
||||
<image src="/assets/images/avatar-coach.png" class="avatar-img" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="coach-meta">
|
||||
<view class="coach-name-row">
|
||||
<text class="coach-name">{{coachName}}</text>
|
||||
<text class="coach-role-tag">{{coachRole}}</text>
|
||||
</view>
|
||||
<text class="coach-store">{{storeName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心收入数据 -->
|
||||
<view class="income-overview">
|
||||
<view class="income-card">
|
||||
<text class="income-label">本月预计收入</text>
|
||||
<text class="income-value">{{monthlyIncome}}</text>
|
||||
</view>
|
||||
<view class="income-card">
|
||||
<text class="income-label">上月收入</text>
|
||||
<text class="income-value income-highlight">{{lastMonthIncome}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收入情况 -->
|
||||
<view class="section-card">
|
||||
<view class="section-title">
|
||||
<view class="title-dot dot-primary"></view>
|
||||
<text>收入情况</text>
|
||||
</view>
|
||||
|
||||
<!-- 当前档位 -->
|
||||
<view class="tier-card tier-current">
|
||||
<view class="tier-badge badge-current">当前档位</view>
|
||||
<view class="tier-row">
|
||||
<view class="tier-icon-label">
|
||||
<text class="tier-emoji">📊</text>
|
||||
<text class="tier-label tier-label-green">当前档位</text>
|
||||
</view>
|
||||
<view class="tier-rates">
|
||||
<view class="rate-item">
|
||||
<view class="rate-value-row">
|
||||
<text class="rate-value rate-green">{{currentTier.basicRate}}</text>
|
||||
<text class="rate-unit rate-green-light">元/h</text>
|
||||
</view>
|
||||
<text class="rate-desc rate-green-light">基础课到手</text>
|
||||
</view>
|
||||
<view class="rate-divider"></view>
|
||||
<view class="rate-item">
|
||||
<view class="rate-value-row">
|
||||
<text class="rate-value rate-green">{{currentTier.incentiveRate}}</text>
|
||||
<text class="rate-unit rate-green-light">元/h</text>
|
||||
</view>
|
||||
<text class="rate-desc rate-green-light">激励课到手</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下一阶段 -->
|
||||
<view class="tier-card tier-next">
|
||||
<view class="tier-badge badge-next">下一阶段</view>
|
||||
<view class="tier-row">
|
||||
<view class="tier-icon-label">
|
||||
<text class="tier-emoji">🎯</text>
|
||||
<text class="tier-label tier-label-yellow">下一阶段</text>
|
||||
</view>
|
||||
<view class="tier-rates">
|
||||
<view class="rate-item">
|
||||
<view class="rate-value-row">
|
||||
<text class="rate-value rate-yellow">{{nextTier.basicRate}}</text>
|
||||
<text class="rate-unit rate-yellow-light">元/h</text>
|
||||
</view>
|
||||
<text class="rate-desc rate-yellow-light">基础课到手</text>
|
||||
</view>
|
||||
<view class="rate-divider rate-divider-yellow"></view>
|
||||
<view class="rate-item">
|
||||
<view class="rate-value-row">
|
||||
<text class="rate-value rate-yellow">{{nextTier.incentiveRate}}</text>
|
||||
<text class="rate-unit rate-yellow-light">元/h</text>
|
||||
</view>
|
||||
<text class="rate-desc rate-yellow-light">激励课到手</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 升级提示 -->
|
||||
<view class="upgrade-hint">
|
||||
<view class="upgrade-left">
|
||||
<text class="upgrade-emoji">⏱️</text>
|
||||
<view class="upgrade-text">
|
||||
<text class="upgrade-label">距离下一阶段</text>
|
||||
<view class="upgrade-hours">
|
||||
<text>需完成 </text>
|
||||
<text class="upgrade-hours-num">{{upgradeHoursNeeded}}</text>
|
||||
<text> 小时</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="upgrade-bonus">
|
||||
<text class="bonus-label">到达即得</text>
|
||||
<text class="bonus-value">{{upgradeBonus}}元</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 本月业绩 -->
|
||||
<view class="section-card">
|
||||
<view class="section-title">
|
||||
<view class="title-dot dot-success"></view>
|
||||
<text>本月业绩 预估</text>
|
||||
</view>
|
||||
|
||||
<view class="income-list">
|
||||
<view class="income-row" wx:for="{{incomeItems}}" wx:key="label">
|
||||
<view class="income-row-left">
|
||||
<view class="income-icon-box">
|
||||
<text>{{item.icon}}</text>
|
||||
</view>
|
||||
<view class="income-info">
|
||||
<text class="income-item-label">{{item.label}}</text>
|
||||
<text class="income-item-desc">{{item.desc}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="income-item-value">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 合计 -->
|
||||
<view class="income-total">
|
||||
<text class="total-label">本月合计 预估</text>
|
||||
<text class="total-value">{{monthlyTotal}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 服务记录明细 -->
|
||||
<view class="service-records-section">
|
||||
<view class="service-records-header">
|
||||
<text class="service-records-emoji">📋</text>
|
||||
<text class="service-records-title">我的服务记录明细</text>
|
||||
</view>
|
||||
|
||||
<block wx:for="{{thisMonthRecords}}" wx:key="date" wx:if="{{thisMonthRecordsExpanded || index < visibleRecordGroups}}">
|
||||
<view class="date-divider">
|
||||
<text decode class="dd-date">{{item.date}} —</text>
|
||||
|
||||
<text decode class="dd-stats" wx:if=" {{item.totalHours}}">{{item.totalHours}} · {{item.totalIncome}} </text>
|
||||
<view class="dd-line"></view>
|
||||
</view>
|
||||
<view class="record-item" wx:for="{{item.records}}" wx:for-item="rec" wx:key="customerName" bindtap="onRecordTap" data-customer-name="{{rec.customerName}}" data-task-id="{{rec.taskId}}">
|
||||
<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>
|
||||
<text class="record-hours">{{rec.hours}}</text>
|
||||
</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">{{rec.income}}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="records-actions">
|
||||
<view class="records-toggle" hover-class="toggle-btn--hover" bindtap="toggleThisMonthRecords" wx:if="{{thisMonthRecords.length > visibleRecordGroups}}">
|
||||
<text>{{thisMonthRecordsExpanded ? '收起' : '展开更多'}}</text>
|
||||
<t-icon name="{{thisMonthRecordsExpanded ? 'chevron-up' : 'chevron-down'}}" size="24px" />
|
||||
</view>
|
||||
|
||||
<view class="records-view-all" hover-class="view-all--hover" bindtap="goToRecords">
|
||||
<text>查看全部</text>
|
||||
<t-icon name="chevron-right" size="24px" color="#0052d9" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的新客 -->
|
||||
<view class="section-card">
|
||||
<view class="section-title">
|
||||
<view class="title-dot dot-cyan"></view>
|
||||
<text>我的新客</text>
|
||||
</view>
|
||||
|
||||
<view class="customer-list">
|
||||
<view
|
||||
class="customer-item"
|
||||
hover-class="customer-item--hover"
|
||||
wx:for="{{newCustomers}}"
|
||||
wx:key="name"
|
||||
wx:if="{{newCustomerExpanded ? index < 20 : index < 5}}"
|
||||
data-name="{{item.name}}"
|
||||
bindtap="onCustomerTap"
|
||||
>
|
||||
<view class="customer-avatar avatar-{{item.avatarColor}}">
|
||||
<text>{{item.avatarChar}}</text>
|
||||
</view>
|
||||
<view class="customer-info">
|
||||
<text class="customer-name">{{item.name}}</text>
|
||||
<text class="customer-detail">最近服务: {{item.lastService}} · {{item.count}}次</text>
|
||||
</view>
|
||||
<t-icon name="chevron-right" size="24px" color="#c5c5c5" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="toggle-btn" hover-class="toggle-btn--hover" bindtap="toggleNewCustomer" wx:if="{{newCustomers.length > 5}}">
|
||||
<text>{{newCustomerExpanded ? '收起 ↑' : '查看更多 ↓'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的常客 -->
|
||||
<view class="section-card">
|
||||
<view class="section-title">
|
||||
<view class="title-dot dot-pink"></view>
|
||||
<text>我的常客</text>
|
||||
</view>
|
||||
|
||||
<view class="customer-list">
|
||||
<view
|
||||
class="customer-item"
|
||||
hover-class="customer-item--hover"
|
||||
wx:for="{{regularCustomers}}"
|
||||
wx:key="name"
|
||||
wx:if="{{regularCustomerExpanded ? index < 20 : index < 5}}"
|
||||
data-name="{{item.name}}"
|
||||
bindtap="onCustomerTap"
|
||||
>
|
||||
<view class="customer-avatar avatar-{{item.avatarColor}}">
|
||||
<text>{{item.avatarChar}}</text>
|
||||
</view>
|
||||
<view class="customer-info">
|
||||
<text class="customer-name">{{item.name}}</text>
|
||||
<text class="customer-detail">{{item.count}}次 · {{item.hours}}h · {{item.income}}</text>
|
||||
</view>
|
||||
<t-icon name="chevron-right" size="24px" color="#c5c5c5" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="toggle-btn" hover-class="toggle-btn--hover" bindtap="toggleRegularCustomer" wx:if="{{regularCustomers.length > 5}}">
|
||||
<text>{{regularCustomerExpanded ? '收起 ↑' : '查看更多 ↓'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- AI 悬浮按钮 -->
|
||||
<ai-float-button />
|
||||
|
||||
<dev-fab />
|
||||
@@ -0,0 +1,833 @@
|
||||
/* pages/performance/performance.wxss — 业绩总览 */
|
||||
|
||||
/* ========== 全局设置 ========== */
|
||||
page {
|
||||
background-color: #f3f3f3;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
view {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* ========== 加载态 / 空态 / 错误态 ========== */
|
||||
.page-loading,
|
||||
.page-empty,
|
||||
.page-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text,
|
||||
.error-text {
|
||||
font-size: 26rpx;
|
||||
color: #a6a6a6;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 16rpx 48rpx;
|
||||
background-color: #0052d9;
|
||||
color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.retry-btn--hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* ========== Banner 区域 ========== */
|
||||
/* 方案:SVG 做渐变底图(与 task-detail 保持一致)
|
||||
* 特点:通过 WXSS 可灵活调整位置、高度、缩放
|
||||
*/
|
||||
.banner-section {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 380rpx;
|
||||
}
|
||||
|
||||
/* 渐变底图 — SVG 包含渐变 + 光晕 */
|
||||
/* 方案:SVG 图片完整显示,通过调整位置和尺寸来控制显示区域
|
||||
* 超出 banner-section 的部分会被 overflow: hidden 裁剪
|
||||
* 调整方式:
|
||||
* - 修改 top/left 来移动图片位置(选择显示SVG的哪个部分)
|
||||
* - 修改 width/height 来缩放图片
|
||||
* - 负的 top 值:图片向上移,显示下半部分
|
||||
* - 正的 top 值:图片向下移,显示上半部分
|
||||
*/
|
||||
.banner-bg-img {
|
||||
position: absolute;
|
||||
top: -50rpx; /* 调整此值移动图片:负值向上移,正值向下移 */
|
||||
left: 0; /* 调整此值左右移动图片 */
|
||||
width: 100%; /* 调整此值缩放图片宽度 */
|
||||
height: auto; /* 保持图片比例,或设置具体值如 600rpx 来缩放 */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 40rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 自定义导航栏 */
|
||||
.custom-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 44px;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.nav-back--hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
/* 个人信息 */
|
||||
.coach-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.coach-avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.coach-meta {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.coach-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
.coach-name {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
line-height: 56rpx;
|
||||
}
|
||||
|
||||
.coach-role-tag {
|
||||
padding: 4rpx 16rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ffffff;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.coach-store {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
/* 收入概览卡片 */
|
||||
.income-overview {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.income-card {
|
||||
flex: 1;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.income-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.income-value {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
line-height: 58rpx;
|
||||
}
|
||||
|
||||
.income-highlight {
|
||||
color: #a7f3d0;
|
||||
}
|
||||
|
||||
/* ========== 通用 Section 卡片 ========== */
|
||||
.section-card {
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
padding: 32rpx;
|
||||
margin: 24rpx 24rpx 0;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #242424;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.title-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot-primary { background: #0052d9; }
|
||||
.dot-success { background: #00a870; }
|
||||
.dot-cyan { background: #06b6d4; }
|
||||
.dot-pink { background: #ec4899; }
|
||||
|
||||
/* ========== 收入档位 ========== */
|
||||
.tier-card {
|
||||
position: relative;
|
||||
padding: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tier-current {
|
||||
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
||||
border: 2rpx solid #86efac;
|
||||
}
|
||||
|
||||
.tier-next {
|
||||
background: linear-gradient(135deg, #fefce8 0%, #fef9c3 100%);
|
||||
border: 2rpx solid #fde047;
|
||||
}
|
||||
|
||||
.tier-badge {
|
||||
position: absolute;
|
||||
top: -16rpx;
|
||||
right: 24rpx;
|
||||
padding: 4rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.badge-current {
|
||||
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
||||
}
|
||||
|
||||
.badge-next {
|
||||
background: linear-gradient(135deg, #eab308 0%, #ca8a04 100%);
|
||||
}
|
||||
|
||||
.tier-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tier-icon-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.tier-emoji {
|
||||
font-size: 48rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tier-label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.tier-label-green { color: #15803d; }
|
||||
.tier-label-yellow { color: #a16207; }
|
||||
|
||||
.tier-rates {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.rate-item {
|
||||
text-align: center;
|
||||
width: 128rpx;
|
||||
}
|
||||
|
||||
.rate-value-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.rate-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
line-height: 51rpx;
|
||||
}
|
||||
|
||||
.rate-unit {
|
||||
font-size: 22rpx;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.rate-desc {
|
||||
font-size: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
display: block;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.rate-green { color: #15803d; }
|
||||
.rate-green-light { color: #16a34a; }
|
||||
.rate-yellow { color: #a16207; }
|
||||
.rate-yellow-light { color: #ca8a04; }
|
||||
|
||||
.rate-divider {
|
||||
width: 2rpx;
|
||||
height: 64rpx;
|
||||
background: #bbf7d0;
|
||||
}
|
||||
|
||||
.rate-divider-yellow {
|
||||
background: #fef08a;
|
||||
}
|
||||
|
||||
/* 升级提示 */
|
||||
.upgrade-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(to right, #eff6ff, #eef2ff);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx;
|
||||
border: 2rpx solid #bfdbfe;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.upgrade-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.upgrade-emoji {
|
||||
font-size: 48rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.upgrade-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.upgrade-label {
|
||||
font-size: 22rpx;
|
||||
color: #5e5e5e;
|
||||
display: block;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.upgrade-hours {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #1d4ed8;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.upgrade-hours-num {
|
||||
font-size: 30rpx;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.upgrade-bonus {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(to right, #fbbf24, #f97316);
|
||||
color: #ffffff;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.bonus-label {
|
||||
font-size: 20rpx;
|
||||
opacity: 0.9;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.bonus-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
line-height: 51rpx;
|
||||
}
|
||||
|
||||
/* ========== 本月业绩明细 ========== */
|
||||
.income-list {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.income-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 2rpx solid #f3f3f3;
|
||||
}
|
||||
|
||||
.income-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.income-row-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.income-icon-box {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f3f3f3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.income-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.income-item-label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #242424;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.income-item-desc {
|
||||
font-size: 22rpx;
|
||||
color: #c5c5c5;
|
||||
margin-top: 4rpx;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.income-item-value {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #242424;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.income-total {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 16rpx;
|
||||
border-top: 2rpx solid #f3f3f3;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #8b8b8b;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #00a870;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 58rpx;
|
||||
}
|
||||
|
||||
/* ========== 服务记录 ========== */
|
||||
.service-records-section {
|
||||
margin-top: 32rpx;
|
||||
margin: 32rpx -32rpx -32rpx;
|
||||
padding: 32rpx;
|
||||
background: rgba(243, 243, 243, 0.7);
|
||||
border-radius: 0 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.service-records-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.service-records-emoji {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.service-records-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #242424;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.date-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 0 8rpx;
|
||||
}
|
||||
|
||||
.dd-date {
|
||||
font-size: 22rpx;
|
||||
color: #8b8b8b;
|
||||
font-weight: 500;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.dd-line {
|
||||
flex: 1;
|
||||
height: 2rpx;
|
||||
background: #dcdcdc;
|
||||
}
|
||||
|
||||
.dd-stats {
|
||||
font-size: 22rpx;
|
||||
color: #a6a6a6;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 16rpx 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.record-item:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.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); }
|
||||
/* 头像渐变色由 app.wxss 全局 .avatar-{key} 统一提供(VI §8) */
|
||||
.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); }
|
||||
|
||||
.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 {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #059669;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.records-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #8b8b8b;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.records-view-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4rpx;
|
||||
padding: 12rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #0052d9;
|
||||
font-weight: 500;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
/* ========== 新客 / 常客列表 ========== */
|
||||
.customer-list {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.customer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 2rpx solid #f3f3f3;
|
||||
}
|
||||
|
||||
.customer-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.customer-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;
|
||||
}
|
||||
|
||||
.customer-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.customer-name {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #242424;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.customer-detail {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #a6a6a6;
|
||||
margin-top: 4rpx;
|
||||
line-height: 29rpx;
|
||||
}
|
||||
|
||||
.customer-item--hover {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
/* ========== 服务记录按钮 ========== */
|
||||
/* 并列按钮容器 */
|
||||
.records-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx 120rpx;
|
||||
}
|
||||
|
||||
.records-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #0052d9;
|
||||
line-height: 36rpx;
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.records-view-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4rpx;
|
||||
font-size: 26rpx;
|
||||
color: #0052d9;
|
||||
font-weight: 500;
|
||||
line-height: 36rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toggle-btn--hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.view-all--hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #0052d9;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.toggle-btn--hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.view-all--hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
Reference in New Issue
Block a user