219 lines
6.7 KiB
Markdown
219 lines
6.7 KiB
Markdown
# 日期时间展示规范(小程序前端)
|
||
|
||
> **文档编号**:DS-DATETIME-001
|
||
> **版本**:v1.1.0
|
||
> **适用范围**:NeoZQYY 小程序所有前端页面
|
||
> **最后更新**:2026-03-18
|
||
|
||
---
|
||
|
||
## 一、设计原则
|
||
|
||
- **就近优先**:越近的时间用越自然的语言表达,减少认知负担。
|
||
- **精度递减**:随时间距离增大,展示精度逐步降低(秒 → 分 → 时 → 天 → 日期)。
|
||
- **一致性**:全端统一规则,禁止页面间自行实现差异逻辑。
|
||
- **可读性优先**:格式选择以用户直觉理解为准,而非技术精确。
|
||
|
||
---
|
||
|
||
## 二、展示规则总表
|
||
|
||
| 时间距当前的范围 | 展示格式 | 示例 |
|
||
|---|---|---|
|
||
| 0 秒 ~ 119 秒(不足 2 分钟) | `刚刚` | 刚刚 |
|
||
| 2 分钟 ~ 59 分钟 | `N分钟前` | 2分钟前 / 59分钟前 |
|
||
| 1 小时 ~ 23 小时 59 分 | `N小时前` | 1小时前 / 23小时前 |
|
||
| 24 小时 ~ 71 小时 59 分(1天~3天内) | `N天前` | 1天前 / 3天前 |
|
||
| 超过 72 小时(3天前以上) | `YYYY-MM-DD` | 2026-03-10 |
|
||
|
||
> **注**:所有时间差计算以**服务端返回的 UTC 时间戳**为基准,前端根据设备本地时区渲染,不得使用客户端本地时间作为「当前时间」基准。
|
||
|
||
---
|
||
|
||
## 三、逐级规则详解
|
||
|
||
### 3.1 刚刚(0 ~ 119 秒)
|
||
|
||
- **触发条件**:`now - timestamp < 120 秒`
|
||
- **展示文案**:`刚刚`
|
||
- **说明**:2 分钟内的时间差对用户无感知意义,统一收敛为「刚刚」,不展示具体秒数或分钟数。
|
||
|
||
```
|
||
示例:发布于 5 秒前 → 刚刚
|
||
发布于 90 秒前 → 刚刚
|
||
发布于 119 秒前 → 刚刚
|
||
```
|
||
|
||
---
|
||
|
||
### 3.2 N 分钟前(2 ~ 59 分钟)
|
||
|
||
- **触发条件**:`120 秒 ≤ now - timestamp < 3600 秒`
|
||
- **展示格式**:`Math.floor(diff / 60) + '分钟前'`
|
||
- **最小值**:2分钟前
|
||
- **最大值**:59分钟前
|
||
|
||
```
|
||
示例:
|
||
diff = 120s → 2分钟前
|
||
diff = 3540s → 59分钟前
|
||
```
|
||
|
||
> **不得** 展示「1分钟前」(119 秒内均为「刚刚」);**不得** 展示「60分钟前」,3600 秒起跳进入「小时」维度。
|
||
|
||
---
|
||
|
||
### 3.3 N 小时前(1 ~ 23 小时)
|
||
|
||
- **触发条件**:`3600 秒 ≤ now - timestamp < 86400 秒`
|
||
- **展示格式**:`Math.floor(diff / 3600) + '小时前'`
|
||
- **最小值**:1小时前
|
||
- **最大值**:23小时前
|
||
|
||
```
|
||
示例:
|
||
diff = 3600s → 1小时前
|
||
diff = 82800s → 23小时前
|
||
```
|
||
|
||
> **不得** 展示「24小时前」,86400 秒起跳进入「天」维度。
|
||
|
||
---
|
||
|
||
### 3.4 N 天前(1 ~ 3 天)
|
||
|
||
- **触发条件**:`86400 秒 ≤ now - timestamp < 259200 秒`(即 1 天 ~ 不足 72 小时)
|
||
- **展示格式**:`Math.floor(diff / 86400) + '天前'`
|
||
- **最小值**:1天前
|
||
- **最大值**:3天前
|
||
|
||
```
|
||
示例:
|
||
diff = 86400s → 1天前
|
||
diff = 259199s → 3天前
|
||
```
|
||
|
||
> **临界说明**:72小时(259200秒)整数点,即 diff ≥ 259200 秒,切换为日期格式。
|
||
|
||
---
|
||
|
||
### 3.5 日期展示(超过 3 天)
|
||
|
||
- **触发条件**:`now - timestamp ≥ 259200 秒`(≥ 72 小时)
|
||
- **展示格式**:
|
||
- **同年**:`MM-DD`(省略年份,如 `03-10`)
|
||
- **跨年**:`YYYY-MM-DD`(保留年份,如 `2025-08-21`)
|
||
- **分隔符**:使用连字符 `-`,不使用斜杠 `/` 或中文「年月日」
|
||
- **不展示时分秒**:日期维度下不展示具体时刻。
|
||
|
||
```
|
||
示例(当前日期 2026-03-18):
|
||
时间戳 2026-03-10 → 03-10
|
||
时间戳 2025-08-21 → 2025-08-21(跨年展示完整年份)
|
||
```
|
||
|
||
---
|
||
|
||
## 四、判断流程图
|
||
|
||
```
|
||
输入:服务端时间戳 timestamp
|
||
│
|
||
▼
|
||
diff = now - timestamp(单位:秒)
|
||
│
|
||
┌─────┴───────┐
|
||
│ diff < 120 │──► 刚刚
|
||
└─────┬───────┘
|
||
│
|
||
┌─────┴──────────┐
|
||
│ diff < 3600 │──► N分钟前(N = floor(diff/60),2≤N≤59)
|
||
└─────┬──────────┘
|
||
│
|
||
┌─────┴──────────┐
|
||
│ diff < 86400 │──► N小时前(N = floor(diff/3600),1≤N≤23)
|
||
└─────┬──────────┘
|
||
│
|
||
┌─────┴──────────┐
|
||
│ diff < 259200 │──► N天前(N = floor(diff/86400),1≤N≤3)
|
||
└─────┬──────────┘
|
||
│
|
||
▼
|
||
日期格式(同年MM-DD / 跨年YYYY-MM-DD)
|
||
```
|
||
|
||
---
|
||
|
||
## 五、前端实现参考(JavaScript)
|
||
|
||
```javascript
|
||
/**
|
||
* 格式化时间展示
|
||
* @param {number} timestamp - 服务端返回的 Unix 时间戳(毫秒)
|
||
* @returns {string} 格式化后的时间文案
|
||
*/
|
||
function formatRelativeTime(timestamp) {
|
||
const now = Date.now();
|
||
const diff = Math.floor((now - timestamp) / 1000); // 转换为秒
|
||
|
||
if (diff < 120) {
|
||
return '刚刚';
|
||
}
|
||
|
||
if (diff < 3600) {
|
||
return `${Math.floor(diff / 60)}分钟前`;
|
||
}
|
||
|
||
if (diff < 86400) {
|
||
return `${Math.floor(diff / 3600)}小时前`;
|
||
}
|
||
|
||
if (diff < 259200) { // 72小时 = 3天
|
||
return `${Math.floor(diff / 86400)}天前`;
|
||
}
|
||
|
||
// 超过3天,展示日期
|
||
const date = new Date(timestamp);
|
||
const nowDate = new Date(now);
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
|
||
if (year === nowDate.getFullYear()) {
|
||
return `${month}-${day}`; // 同年省略年份
|
||
}
|
||
return `${year}-${month}-${day}`; // 跨年显示完整年份
|
||
}
|
||
```
|
||
|
||
> **微信小程序说明**:可直接将此函数挂载到 `utils/time.js`,在 WXML 中通过 `wxs` 模块或 `Page.data` 绑定使用。
|
||
|
||
---
|
||
|
||
## 六、边界与特殊情况处理
|
||
|
||
| 场景 | 处理方式 |
|
||
|---|---|
|
||
| `timestamp` 为空 / undefined | 展示 `--`,不报错 |
|
||
| `timestamp` 为未来时间(服务端时钟偏差) | 统一展示 `刚刚`(diff < 0 时按 0 处理) |
|
||
| 用户设备时区异常 | 以服务端返回的格式化日期字段(fallback)为准 |
|
||
| 时间戳单位(秒 vs 毫秒) | 统一由服务端约定为**毫秒**,前端不做猜测 |
|
||
|
||
---
|
||
|
||
## 七、禁止事项
|
||
|
||
- ❌ 禁止展示「1分钟前」「60分钟前」「24小时前」「0分钟前」等临界歧义文案(2分钟内统一展示「刚刚」)
|
||
- ❌ 禁止在「天」维度之后继续展示相对文案(如「7天前」「30天前」)
|
||
- ❌ 禁止使用中文「年月日」格式(如「2026年3月10日」),统一使用 `-` 分隔符
|
||
- ❌ 禁止在超3天后展示时分秒
|
||
- ❌ 禁止各页面自行定义时间格式化函数,必须引用 `utils/time.js` 统一工具
|
||
|
||
---
|
||
|
||
## 八、关联文档
|
||
|
||
- [VI 设计规范](./VI-DESIGN-SYSTEM.md)
|
||
- [组件库规范](../components/)
|
||
- `apps/miniprogram/miniprogram/utils/time.js`(工具函数实现位置)
|