# 前端展示规范总表（小程序）

> **文档编号**：DS-DISPLAY-001  
> **版本**：v1.0.0  
> **适用范围**：NeoZQYY 小程序所有前端页面  
> **最后更新**：2026-03-18  
> **关联文档**：[日期时间展示规范](./DATETIME-DISPLAY-STANDARD.md)

---

## 目录

1. [金额展示规范](#1-金额展示规范)
2. [课时与时长展示规范](#2-课时与时长展示规范)
3. [数字与计数展示规范](#3-数字与计数展示规范)
4. [空值与缺失值规范](#4-空值与缺失值规范)
5. [百分比展示规范](#5-百分比展示规范)
6. [等级文案规范](#6-等级文案规范)
7. [任务截止日期展示规范](#7-任务截止日期展示规范)
8. [评分与星级展示规范](#8-评分与星级展示规范)
9. [Mock 数据规范](#9-mock-数据规范)

---

## 1. 金额展示规范

### 1.1 规则总表

| 场景 | 格式 | 示例 |
|---|---|---|
| 正常金额 | `¥N,NNN`（千分位逗号，无小数） | `¥12,680` |
| 负数金额 | `-¥N,NNN`（负号在 ¥ 前） | `-¥368` |
| 零值 | `¥0` | `¥0` |
| 空值 / undefined | `--` | `--` |
| 大额金额 | 不简写，保留完整数字 | `¥120,000` |

### 1.2 禁止事项

- ❌ 禁止 `¥-368`（负号位置错误）
- ❌ 禁止 `¥0.00`（零值不保留小数）
- ❌ 禁止 `¥12万`（不做大额简写）
- ❌ 禁止 `toLocaleString()` 直接用于展示（各设备行为不一致）
- ❌ Mock 数据中金额字段统一为 `number` 类型，不含 `¥` 前缀，由格式化层统一添加

### 1.3 实现参考（`utils/money.ts`，待新建）

```typescript
export function formatMoney(value: number | null | undefined): string {
  if (value === null || value === undefined) return '--'
  if (value === 0) return '¥0'
  const abs = Math.round(Math.abs(value))
  const formatted = abs.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
  return value < 0 ? `-¥${formatted}` : `¥${formatted}`
}
```

### 1.4 当前涉及位置（待统一）

| 文件 | 问题 |
|---|---|
| `pages/task-list/task-list.ts` | `perf.customerBalance.toLocaleString()` 裸调 |
| `pages/performance-records/performance-records.ts` | Mock 字符串硬编码 `¥510`、`¥280` 等 |
| `pages/task-detail/task-detail.ts` | `serviceRecords.income` 硬编码 `¥200` 等 |
| `pages/board-coach/board-coach.ts` | `salary`、`svAmount` 硬编码 `¥12,680` 等 |

---

## 2. 课时与时长展示规范

### 2.1 规则总表

| 场景 | 格式 | 示例 |
|---|---|---|
| 整数小时 | `Nh` | `2h` |
| 非整数小时（保留1位） | `N.Nh` | `2.5h` |
| 带折算备注 | `实际h（折后 原始h）` | `2.0h（折后 2.5h）` |
| 零值 | `0h` | `0h` |
| 空值 | `--` | `--` |

### 2.2 实现参考（追加至 `utils/time.ts`）

```typescript
export function formatHours(hours: number | null | undefined): string {
  if (hours === null || hours === undefined) return '--'
  if (hours === 0) return '0h'
  return hours % 1 === 0 ? `${hours}h` : `${hours.toFixed(1)}h`
}
```

### 2.3 折算标注字段约定

- `hours`：折算后实际计入课时（`number`）
- `hoursRaw`：折算前原始小时数（`number | undefined`）
- 仅当 `hoursRaw` 存在且与 `hours` 不同时展示括号备注
- WXML 示例：`{{item.hours}}h{{item.hoursRaw ? '（折后 ' + item.hoursRaw + 'h）' : ''}}`

### 2.4 当前使用折算标注的位置（待统一）

| 文件 | 当前写法 | 目标写法 |
|---|---|---|
| `pages/performance-records/performance-records.ts` r7 | `deduct: '(折0.5h)'` 字符串 | `hours: 2, hoursRaw: 2.5`（number） |
| `pages/performance-records/performance-records.ts` r16 | 同上 | 同上 |
| `pages/performance-records/performance-records.ts` r21 | 同上 | 同上 |
| `pages/performance-records/performance-records.ts` r27 | 同上 | 同上 |
| `pages/task-detail/task-detail.ts` serviceRecords r1 | `durationRaw: '3.0h'` 字符串 | 改为 `number` 类型 |
| `components/service-record-card` | `hours-raw` prop 字符串 | 对齐 `number` 类型后格式化 |

---

## 3. 数字与计数展示规范

### 3.1 规则总表

| 场景 | 规则 | 示例 |
|---|---|---|
| 计数类（笔/次/人） | Mock 只存 `number`，单位由前端模板拼接 | API 返回 `32`，WXML 展示 `32笔` |
| 大数字（≥ 1000） | 千分位逗号 | `1,280` |
| 零值 | `--` | `--` |
| 空值 / undefined | `--` | `--` |

### 3.2 单位映射表

| 字段语义 | 单位 |
|---|---|
| 服务记录数 / 交易笔数 | `笔` |
| 到店次数 / 服务次数 | `次` |
| 客户数 / 人数 | `人` |
| 任务数 | `个` |
| 工龄 | `年` |

### 3.3 实现参考（追加至 `utils/money.ts`）

```typescript
export function formatCount(
  value: number | null | undefined,
  unit: string
): string {
  if (value === null || value === undefined || value === 0) return '--'
  const n = value >= 1000
    ? value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
    : String(value)
  return `${n}${unit}`
}
```

### 3.4 当前涉及位置（待统一）

| 文件 | 问题 |
|---|---|
| `pages/performance-records/performance-records.ts` | `totalCount: '32笔'` 字符串硬编码单位 |
| `pages/customer-service-records/customer-service-records.ts` | `monthCount: '6次'` 字符串硬编码单位 |
| `pages/board-coach/board-coach.ts` | `svCustomerCount: '18'` 裸字符串无单位 |

---

## 4. 空值与缺失值规范

### 4.1 统一占位符：`--`

| 场景 | 处理方式 |
|---|---|
| 数值型字段缺失（金额/课时/评分） | 展示 `--` |
| 文本型字段缺失（名称/备注/标签） | 展示 `--` |
| 列表为空 | 空态组件 `<t-empty />`，不用 `--` |
| 图片加载失败 | 默认占位图，不用 `--` |

### 4.2 禁止事项

- ❌ 禁止向用户展示 `undefined`、`null`、`NaN`、空字符串
- ❌ 禁止使用 `暂无`、`无`、`N/A`、`-` 等其他占位符
- ❌ 禁止直接绑定可能为 `undefined` 的字段而不加兜底

### 4.3 WXS 兜底函数（追加至 `utils/format.wxs`）

```javascript
function safe(val) {
  if (val === undefined || val === null || val === '') return '--'
  return val
}
// WXML 用法：{{fmt.safe(item.someField)}}
```

---

## 5. 百分比展示规范

### 5.1 规则总表

| 场景 | 格式 | 示例 |
|---|---|---|
| 展示文字 | 保留1位小数 + `%` | `58.3%`、`120.0%` |
| 进度条 CSS 宽度 | 保留1位小数，截断至100防溢出 | `width:58.3%` |
| 超过100%的展示文字 | 正常展示，不截断 | `120.0%` |
| 零值 | `0%` | `0%` |
| 空值 | `--` | `--` |

### 5.2 实现参考（追加至 `utils/money.ts`）

```typescript
export function formatPercent(value: number | null | undefined): string {
  if (value === null || value === undefined) return '--'
  if (value === 0) return '0%'
  return `${value.toFixed(1)}%`
}

export function toProgressWidth(value: number): string {
  return `${Math.min(100, Math.max(0, value)).toFixed(1)}%`
}
```

### 5.3 当前涉及位置（待统一）

| 文件 | 问题 |
|---|---|
| `pages/task-list/task-list.ts` | `filledPct` 计算用 `Math.round(*1000)/10`，精度逻辑分散 |
| `pages/task-list/task-list.wxml` | `width: {{perfData.tierProgress}}%` 无小数位 |

---

## 6. 等级文案规范

### 6.1 助教等级

**英文 Key 标准（单一数据源：`vi-colors.ts` `COACH_LEVEL_COLORS`）：**

| 英文 Key | 中文展示文案 |
|---|---|
| `junior` | 初级 |
| `middle` | 中级 |
| `senior` | 高级 |
| `star` | ⭐ 星级 |

**规范要求：**
- API / Mock 统一返回英文 key
- 所有组件、页面、常量统一使用英文 key，中文文案只在渲染层转换
- `coach-level-tag` 组件的 `LEVEL_MAP` 改为英文 key（当前为中文 key）

**当前待统一的位置：**

| 文件 | 问题 |
|---|---|
| `components/coach-level-tag/coach-level-tag.ts` | `LEVEL_MAP` key 为中文，需改为英文 |
| `pages/board-coach/board-coach.ts` | `level: '星级'` 等直接使用中文字符串 |
| `pages/board-coach/board-coach.ts` `LEVEL_CLASS` | key 为中文，需改为英文 |
| `utils/mock-data.ts` | 助教相关 Mock `level` 字段使用中文 |

### 6.2 关系等级

**英文 Key 标准（单一数据源：`vi-colors.ts` `RELATIONSHIP_LEVELS`）：**

| 英文 Key | 中文展示文案 | 分数范围 |
|---|---|---|
| `excellent` | 很好 | `> 8.5` |
| `good` | 良好 | `6 ~ 8.5` |
| `normal` | 一般 | `3.5 ~ 6` |
| `poor` | 待发展 | `< 3.5` |

**规范要求：**
- `task-detail.ts` 中的 `updateRelationshipDisplay` 应调用 `vi-colors.ts` 的 `getRelationshipLevel()`，不得重复维护判断逻辑
- 中文文案统一从 `RELATIONSHIP_LEVELS[key].name` 读取

**当前待统一的位置：**

| 文件 | 问题 |
|---|---|
| `pages/task-detail/task-detail.ts` | `updateRelationshipDisplay` 内联等级判断逻辑，与 `vi-colors.ts` 重复 |

### 6.3 客户标签类别

**英文 Key 标准（`vi-colors.ts` `CUSTOMER_TAG_COLORS`）：**

| 英文 Key | 中文展示文案 |
|---|---|
| `basic_info` | 客户基础 |
| `consumption` | 消费习惯 |
| `play_pref` | 玩法偏好 |
| `promo_pref` | 促销偏好 |
| `social` | 社交关系 |
| `feedback` | 重要反馈 |

**规范要求：**
- `CUSTOMER_TAG_COLORS` 的 key 从中文改为英文
- Mock 数据中 `category` 字段统一使用英文 key

**当前待统一的位置：**

| 文件 | 问题 |
|---|---|
| `utils/vi-colors.ts` `CUSTOMER_TAG_COLORS` | key 为中文，需改为英文 |
| `utils/mock-data.ts` `mockRetentionClues` | `category` 字段已是英文，保持不变 |

> 第 7～9 章及附录见：[DISPLAY-STANDARDS-2.md](./DISPLAY-STANDARDS-2.md)
