Update algorithm and API documentation for smoking tracking app
- Enhanced the algorithm documentation to clarify the calculation of smoking intervals, including default values and edge cases. - Updated API documentation to reflect changes in response formats, including the addition of `exceeded_yesterday` and adjustments to time formatting to RFC3339. - Improved descriptions of user profile management and AI suggestions in the product documentation, ensuring consistency across all related files. - Removed outdated sequence diagram and UI documentation files to streamline project resources.
This commit is contained in:
+17
-17
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
| 参数 | 来源 | 默认值 | 说明 |
|
| 参数 | 来源 | 默认值 | 说明 |
|
||||||
|------|------|--------|------|
|
|------|------|--------|------|
|
||||||
| base_interval | profile.baseline_interval_minutes | 60 分钟 | 用户初始平均抽烟间隔 |
|
| base_interval | profile.baseline_interval_minutes | 60 分钟 | 用户初始平均抽烟间隔(为空/0 时默认 60) |
|
||||||
| resisted_7d | 近7天忍住次数 | 0 | level=0,num=0 的记录数 |
|
| resisted_7d | 近7天忍住次数 | 0 | level=0,num=0 的记录数 |
|
||||||
| bonus_interval | 计算得出 | 0 | 奖励延长时间 |
|
| bonus_interval | 计算得出 | 0 | 奖励延长时间 |
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
bonus_interval = min(floor(resisted_7d / 5) * 5, 60)
|
bonus_interval = min(floor(resisted_7d / 5) * 5, 60)
|
||||||
final_interval = base_interval + bonus_interval
|
final_interval = clamp(base_interval + bonus_interval, 5, 240)
|
||||||
```
|
```
|
||||||
|
|
||||||
**示例**:
|
**示例**:
|
||||||
@@ -41,17 +41,22 @@ final_interval = base_interval + bonus_interval
|
|||||||
|
|
||||||
### 2.4 睡眠规避
|
### 2.4 睡眠规避
|
||||||
|
|
||||||
若计算出的时间落在睡眠区间,顺延到次日起床时间:
|
若计算出的时间落在睡眠区间,顺延到**下一次**起床时间(可能是当天也可能是次日):
|
||||||
|
|
||||||
```
|
```
|
||||||
if suggested_time in [sleep_time, wake_up_time]:
|
if suggested_time in [sleep_time, wake_up_time]:
|
||||||
suggested_time = next_day_wake_up_time
|
suggested_time = next_day_wake_up_time
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.5 算法流程图
|
### 2.5 边界与兜底
|
||||||
|
|
||||||
|
- 若没有历史记录,则以“当前时间”作为 `last_smoke_at` 参与计算。
|
||||||
|
- 若生成未来日期计划(如明天),默认建议不早于该日起床时间;未配置作息时按 `07:00` 处理。
|
||||||
|
|
||||||
|
### 2.6 算法流程图
|
||||||
|
|
||||||
```
|
```
|
||||||
获取上次抽烟时间 (last_smoke_at)
|
获取上次抽烟时间 (last_smoke_at, 若无记录则取当前时间)
|
||||||
↓
|
↓
|
||||||
获取用户基础间隔 (base_interval_minutes)
|
获取用户基础间隔 (base_interval_minutes)
|
||||||
↓
|
↓
|
||||||
@@ -59,7 +64,7 @@ if suggested_time in [sleep_time, wake_up_time]:
|
|||||||
↓
|
↓
|
||||||
计算奖励间隔: bonus = min(floor(resisted_7d / 5) * 5, 60)
|
计算奖励间隔: bonus = min(floor(resisted_7d / 5) * 5, 60)
|
||||||
↓
|
↓
|
||||||
计算建议时间: suggested = last_smoke_at + base + bonus
|
计算建议时间: suggested = last_smoke_at + base + bonus (并限制在 5~240 分钟区间)
|
||||||
↓
|
↓
|
||||||
检查是否在睡眠时间?
|
检查是否在睡眠时间?
|
||||||
├── 是 → 顺延到起床时间
|
├── 是 → 顺延到起床时间
|
||||||
@@ -91,20 +96,14 @@ if suggested_time in [sleep_time, wake_up_time]:
|
|||||||
|
|
||||||
### 3.2 AI 建议内容
|
### 3.2 AI 建议内容
|
||||||
|
|
||||||
AI 会生成以下内容:
|
AI 会生成以下内容(实际接口格式):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"advice": "昨天你的吸烟量比限额少了2支,这是一个巨大的胜利!数据显示你的烟瘾在下午2点左右达到顶峰——今天试着那个时候去散散步。",
|
"not_before_at": "2026-01-05T10:18:00+08:00",
|
||||||
"time_nodes": [
|
"suggested_at": "2026-01-05T10:28:00+08:00",
|
||||||
{ "time": "09:30", "type": "suggested", "note": "第一支,早餐后" },
|
"time_nodes": ["09:30", "12:30", "15:30", "19:00", "22:00"],
|
||||||
{ "time": "12:30", "type": "suggested", "note": "午餐后" },
|
"advice": "昨天你的吸烟量比限额少了2支,这是一个巨大的胜利!数据显示你的烟瘾在下午2点左右达到顶峰——今天试着那个时候去散散步。"
|
||||||
{ "time": "15:30", "type": "suggested", "note": "下午茶时间" },
|
|
||||||
{ "time": "19:00", "type": "suggested", "note": "晚餐后" },
|
|
||||||
{ "time": "22:00", "type": "suggested", "note": "睡前最后一支" }
|
|
||||||
],
|
|
||||||
"daily_target": 5,
|
|
||||||
"tips": ["2点是你的高峰期,准备一颗薄荷糖", "试着用深呼吸替代"]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -299,6 +298,7 @@ function getMotivationMessage(context) {
|
|||||||
| 忍住成功率 | 忍住次数 / (忍住+抽烟次数) | 意志力评估 |
|
| 忍住成功率 | 忍住次数 / (忍住+抽烟次数) | 意志力评估 |
|
||||||
| 平均间隔 | 总时长 / 抽烟次数 | 递减效果 |
|
| 平均间隔 | 总时长 / 抽烟次数 | 递减效果 |
|
||||||
| 最长无烟时长 | 最大间隔记录 | 成就激励 |
|
| 最长无烟时长 | 最大间隔记录 | 成就激励 |
|
||||||
|
| 较昨日减少 | 昨日支数 - 今日支数(可为负) | 若为负,表示今天超出昨日 |
|
||||||
|
|
||||||
### 8.2 周报数据结构
|
### 8.2 周报数据结构
|
||||||
|
|
||||||
|
|||||||
+17
-13
@@ -360,6 +360,7 @@ curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
|
|||||||
默认策略(不使用 AI):
|
默认策略(不使用 AI):
|
||||||
- 基础间隔:优先使用 `GET /api/v1/smoke/profile` 返回的 `baseline_interval_minutes`;若不存在则默认 `60` 分钟。
|
- 基础间隔:优先使用 `GET /api/v1/smoke/profile` 返回的 `baseline_interval_minutes`;若不存在则默认 `60` 分钟。
|
||||||
- 阶梯式延时:最近 7 天内每累计 `5` 条“忍住记录(level=0,num=0)”,在基础间隔上 `+5` 分钟(最多 `+60` 分钟)。
|
- 阶梯式延时:最近 7 天内每累计 `5` 条“忍住记录(level=0,num=0)”,在基础间隔上 `+5` 分钟(最多 `+60` 分钟)。
|
||||||
|
- 间隔兜底:最终间隔会限制在 `5~240` 分钟之间。
|
||||||
- 若用户已补全作息时间,会自动规避睡眠区间:若计算出的时间落在睡眠区间,顺延到下一次起床时间。
|
- 若用户已补全作息时间,会自动规避睡眠区间:若计算出的时间落在睡眠区间,顺延到下一次起床时间。
|
||||||
|
|
||||||
AI 生成说明:
|
AI 生成说明:
|
||||||
@@ -373,22 +374,23 @@ AI 生成说明:
|
|||||||
"message": "success",
|
"message": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"source": "default",
|
"source": "default",
|
||||||
"not_before_at": "2026-01-05 10:18:00",
|
"not_before_at": "2026-01-05T10:18:00+08:00",
|
||||||
"suggested_at": "2026-01-05 10:18:00",
|
"suggested_at": "2026-01-05T10:18:00+08:00",
|
||||||
"last_smoke_at": "2026-01-05 09:30:00",
|
"last_smoke_at": "2026-01-05T09:30:00+08:00",
|
||||||
"today_count": 3,
|
"today_count": 3,
|
||||||
"resisted_count": 1,
|
"resisted_count": 1,
|
||||||
"reduced_from_yesterday": 2,
|
"reduced_from_yesterday": 2,
|
||||||
|
"exceeded_yesterday": false,
|
||||||
"default": {
|
"default": {
|
||||||
"last_smoke_at": "2026-01-05 09:30:00",
|
"last_smoke_at": "2026-01-05T09:30:00+08:00",
|
||||||
"next_smoke_at": "2026-01-05 10:18:00",
|
"next_smoke_at": "2026-01-05T10:18:00+08:00",
|
||||||
"base_interval_minutes": 48,
|
"base_interval_minutes": 48,
|
||||||
"interval_minutes": 48,
|
"interval_minutes": 48,
|
||||||
"stage": 0,
|
"stage": 0,
|
||||||
"resisted_7d": 3,
|
"resisted_7d": 3,
|
||||||
"sleep_adjusted": false,
|
"sleep_adjusted": false,
|
||||||
"algorithm": "staircase_delay_v1",
|
"algorithm": "staircase_delay_v1",
|
||||||
"as_of": "2026-01-05 10:00:00"
|
"as_of": "2026-01-05T10:00:00+08:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,19 +403,20 @@ AI 生成说明:
|
|||||||
"message": "success",
|
"message": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"source": "ai",
|
"source": "ai",
|
||||||
"not_before_at": "2026-01-05 10:18:00",
|
"not_before_at": "2026-01-05T10:18:00+08:00",
|
||||||
"suggested_at": "2026-01-05 10:28:00",
|
"suggested_at": "2026-01-05T10:28:00+08:00",
|
||||||
"last_smoke_at": "2026-01-05 09:30:00",
|
"last_smoke_at": "2026-01-05T09:30:00+08:00",
|
||||||
"today_count": 3,
|
"today_count": 3,
|
||||||
"resisted_count": 1,
|
"resisted_count": 1,
|
||||||
"reduced_from_yesterday": 2,
|
"reduced_from_yesterday": 2,
|
||||||
|
"exceeded_yesterday": false,
|
||||||
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
|
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
|
||||||
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
|
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
|
||||||
"default": { "algorithm": "staircase_delay_v1" },
|
"default": { "algorithm": "staircase_delay_v1" },
|
||||||
"ai": {
|
"ai": {
|
||||||
"plan_date": "2026-01-05",
|
"plan_date": "2026-01-05",
|
||||||
"not_before_at": "2026-01-05 10:18:00",
|
"not_before_at": "2026-01-05T10:18:00+08:00",
|
||||||
"suggested_at": "2026-01-05 10:28:00",
|
"suggested_at": "2026-01-05T10:28:00+08:00",
|
||||||
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
|
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
|
||||||
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
|
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
|
||||||
"prompt_version": "v1",
|
"prompt_version": "v1",
|
||||||
@@ -425,7 +428,8 @@ AI 生成说明:
|
|||||||
```
|
```
|
||||||
|
|
||||||
字段说明(新增首页字段):
|
字段说明(新增首页字段):
|
||||||
- `last_smoke_at`:上次“实际抽烟”时间(忽略忍住记录),格式 `YYYY-MM-DD HH:MM:SS`。
|
- `last_smoke_at`:上次“实际抽烟”时间(忽略忍住记录),格式 `RFC3339`(含时区)。
|
||||||
- `today_count`:今日抽烟支数(累加 `num`)。
|
- `today_count`:今日抽烟支数(累加 `num`)。
|
||||||
- `resisted_count`:今日克制次数(`level=0 && num=0`)。
|
- `resisted_count`:今日克制次数(`level=0 && num=0`)。
|
||||||
- `reduced_from_yesterday`:较昨日减少的支数(`max(昨日支数 - 今日支数, 0)`)。
|
- `reduced_from_yesterday`:较昨日减少的支数(允许为负数;为负时表示“今天超出昨日”)。
|
||||||
|
- `exceeded_yesterday`:是否超出昨日(`true` 表示今天超出昨日,前端可用作单独标识)。
|
||||||
|
|||||||
+11
-11
@@ -28,10 +28,10 @@
|
|||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| 问候语 | 根据时段显示(早上好/下午好等) + 用户昵称 | 本地计算 + profile |
|
| 问候语 | 根据时段显示(早上好/下午好等) + 用户昵称 | 本地计算 + profile |
|
||||||
| AI 提示卡片 | 发现的抽烟规律/建议(可关闭) | `GET /ai/advice` 缓存 |
|
| AI 提示卡片 | 发现的抽烟规律/建议(可关闭) | `GET /ai/advice` 缓存 |
|
||||||
| 计时环 | 距上次抽烟时间(时:分:秒) | `dashboard.minutes_since_last` |
|
| 计时环 | 距上次抽烟时间(时:分:秒) | `GET /next_smoke_time` 的 `last_smoke_at`(前端计时) |
|
||||||
| 下次建议时间 | 显示建议的下次抽烟时间点 | `GET /next_smoke_time` |
|
| 下次建议时间 | 显示建议的下次抽烟时间点 | `GET /next_smoke_time` |
|
||||||
| 今日已抽 | X / 目标数,较昨日 ±N | `dashboard.today_count` |
|
| 今日已抽 | X / 目标数,较昨日 ±N | `next_smoke_time.today_count` + `next_smoke_time.reduced_from_yesterday`(可为负) + `next_smoke_time.exceeded_yesterday`(标识“超出昨日”) |
|
||||||
| 烟瘾发作已抵抗 | 忍住次数统计 | 筛选 `level=0,num=0` 记录 |
|
| 烟瘾发作已抵抗 | 忍住次数统计 | `next_smoke_time.resisted_count` |
|
||||||
| 记录抽烟按钮 | 快速记录一次抽烟 | `POST /logs` |
|
| 记录抽烟按钮 | 快速记录一次抽烟 | `POST /logs` |
|
||||||
| 想抽忍住了按钮 | 记录成功抵抗 | `POST /logs/resisted` |
|
| 想抽忍住了按钮 | 记录成功抵抗 | `POST /logs/resisted` |
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
| 筛选 Tabs | 全部 / 已抽烟 / 已忍住 | 前端筛选 |
|
| 筛选 Tabs | 全部 / 已抽烟 / 已忍住 | 前端筛选 |
|
||||||
| 时间线 | 按日期分组展示 | `GET /logs` |
|
| 时间线 | 按日期分组展示 | `GET /logs` |
|
||||||
| 记录卡片 | 类型、时间、原因标签、间隔时间 | logs 数据 |
|
| 记录卡片 | 类型、时间、原因标签、间隔时间 | logs 数据 |
|
||||||
| 左滑操作 | 编辑 / 删除 | `PUT/DELETE /logs/:id` |
|
| 左滑操作 | 编辑 / 删除 | `POST/DELETE /logs/:id` |
|
||||||
| 新增按钮 | 浮动按钮快速新增 | 跳转记录流程 |
|
| 新增按钮 | 浮动按钮快速新增 | 跳转记录流程 |
|
||||||
|
|
||||||
### 2.5 个人中心 (profile_&_settings)
|
### 2.5 个人中心 (profile_&_settings)
|
||||||
@@ -92,11 +92,11 @@
|
|||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| 用户信息 | 头像、昵称 | 微信授权 |
|
| 用户信息 | 头像、昵称 | 微信授权 |
|
||||||
| 目标展示 | 目标戒烟日期、连续天数 | profile |
|
| 目标展示 | 目标戒烟日期、连续天数 | profile |
|
||||||
| 目标设定 | 调整每日限额与戒烟日期 | `PUT /profile` |
|
| 目标设定 | 调整每日限额与戒烟日期 | `POST /profile` |
|
||||||
| AI 计划调整 | 个性化辅导风格设置 | profile 扩展 |
|
| AI 计划调整 | 个性化辅导风格设置 | profile 扩展 |
|
||||||
| 通知设置 | 提醒时间、频率 | 本地存储 |
|
| 通知设置 | 提醒时间、频率 | 本地存储 |
|
||||||
| 会员解锁 | PRO 功能 / 广告解锁 | 会员系统 |
|
| 会员解锁 | PRO 功能 / 广告解锁 | 会员系统 |
|
||||||
| 基础设置 | 作息时间等 | `PUT /profile` |
|
| 基础设置 | 作息时间等 | `POST /profile` |
|
||||||
| 隐私与数据 | 数据导出、账号注销 | 待扩展 |
|
| 隐私与数据 | 数据导出、账号注销 | 待扩展 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -126,7 +126,7 @@ Step 5: 作息时间 (wake_up_time, sleep_time)
|
|||||||
↓
|
↓
|
||||||
Step 6: 设置目标 (目标日期、每日限额)
|
Step 6: 设置目标 (目标日期、每日限额)
|
||||||
↓
|
↓
|
||||||
提交 profile (PUT /profile)
|
提交 profile (POST /profile)
|
||||||
↓
|
↓
|
||||||
进入首页
|
进入首页
|
||||||
```
|
```
|
||||||
@@ -186,8 +186,8 @@ Step 6: 设置目标 (目标日期、每日限额)
|
|||||||
```
|
```
|
||||||
[并行请求]
|
[并行请求]
|
||||||
├── GET /profile (用户信息,判断是否需引导)
|
├── GET /profile (用户信息,判断是否需引导)
|
||||||
├── GET /dashboard (今日统计,计时器数据)
|
├── GET /next_smoke_time (首页汇总 + 下次建议时间)
|
||||||
└── GET /next_smoke_time (下次建议时间)
|
└── GET /dashboard (看板数据,可延迟)
|
||||||
|
|
||||||
[延迟加载]
|
[延迟加载]
|
||||||
└── GET /ai/advice (AI提示卡片,非关键)
|
└── GET /ai/advice (AI提示卡片,非关键)
|
||||||
@@ -195,8 +195,8 @@ Step 6: 设置目标 (目标日期、每日限额)
|
|||||||
|
|
||||||
**缓存策略**:
|
**缓存策略**:
|
||||||
- profile: 登录后缓存,变更时更新
|
- profile: 登录后缓存,变更时更新
|
||||||
- dashboard: 每次进入刷新,后台定时更新
|
- next_smoke_time: 每次进入刷新,下一次记录后刷新
|
||||||
- next_smoke_time: 缓存至下次记录
|
- dashboard: 进入看板时刷新
|
||||||
- ai/advice: 按天缓存
|
- ai/advice: 按天缓存
|
||||||
|
|
||||||
### 5.2 数据预加载
|
### 5.2 数据预加载
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
### 2.2 典型场景
|
### 2.2 典型场景
|
||||||
- 抽完烟立刻记一笔(含时间/原因/数量)。
|
- 抽完烟立刻记一笔(含时间/原因/数量)。
|
||||||
- 想抽但忍住:快速一键记录(`level=0,num=0`)。
|
- 想抽但忍住:快速一键记录(`level=0,num=0`)。
|
||||||
- 首页查看:下次建议时间、距离上次实际抽烟的间隔、今天累计。
|
- 首页查看:下次建议时间、上次实际抽烟时间、今日累计、今日克制、较昨日增减。
|
||||||
- 每天晚上/第二天查看 AI 总结与行动建议(解锁后)。
|
- 每天晚上/第二天查看 AI 总结与行动建议(解锁后)。
|
||||||
- 生成“今天/明天时间节点计划”(解锁后),帮助前端展示时间线/日程。
|
- 生成“今天/明天时间节点计划”(解锁后),帮助前端展示时间线/日程。
|
||||||
|
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
|
|
||||||
### 3.2 “最后一次抽烟”
|
### 3.2 “最后一次抽烟”
|
||||||
- 定义为“最后一次实际抽烟事件”(忽略忍住记录)。
|
- 定义为“最后一次实际抽烟事件”(忽略忍住记录)。
|
||||||
- 后端看板 `minutes_since_last` 已按该口径计算。
|
- 首页口径使用 `next_smoke_time` 返回的 `last_smoke_at` 作为计时起点(前端可自行计算间隔)。
|
||||||
|
- 看板的 `minutes_since_last` 仍按该口径计算。
|
||||||
|
|
||||||
### 3.3 默认“下次建议时间”(不使用 AI)
|
### 3.3 默认“下次建议时间”(不使用 AI)
|
||||||
- 基础间隔来自 `GET /api/v1/smoke/profile` 的 `baseline_interval_minutes`(若无则默认 60 分钟)。
|
- 基础间隔来自 `GET /api/v1/smoke/profile` 的 `baseline_interval_minutes`(若无则默认 60 分钟)。
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
- 若已存在 AI 时间节点:返回 `source=ai`
|
- 若已存在 AI 时间节点:返回 `source=ai`
|
||||||
- 否则:返回 `source=default`
|
- 否则:返回 `source=default`
|
||||||
- 生成 AI 节点(需要解锁):`GET /api/v1/smoke/next_smoke_time?date=today&mode=ai`
|
- 生成 AI 节点(需要解锁):`GET /api/v1/smoke/next_smoke_time?date=today&mode=ai`
|
||||||
|
- 接口会同时返回首页汇总字段:`last_smoke_at`、`today_count`、`resisted_count`、`reduced_from_yesterday`(可为负,负数代表“超出昨日”)、`exceeded_yesterday`(前端用于标识“超出昨日”)。
|
||||||
|
|
||||||
### 4.4 AI 解锁(按天)
|
### 4.4 AI 解锁(按天)
|
||||||
- 看广告完成后回调:`POST /api/v1/smoke/ai/advice_unlocks {date}`
|
- 看广告完成后回调:`POST /api/v1/smoke/ai/advice_unlocks {date}`
|
||||||
@@ -78,8 +80,8 @@
|
|||||||
- 每日 AI 建议:`GET /api/v1/smoke/ai/advice?date=...`
|
- 每日 AI 建议:`GET /api/v1/smoke/ai/advice?date=...`
|
||||||
- AI 时间节点计划:`GET /api/v1/smoke/next_smoke_time?date=...&mode=ai`
|
- AI 时间节点计划:`GET /api/v1/smoke/next_smoke_time?date=...&mode=ai`
|
||||||
|
|
||||||
## 5. 页面能力清单(对应 UI 文档)
|
## 5. 页面能力清单
|
||||||
- 首页:今日累计、距上次实际抽烟、下次建议时间(默认/AI)、时间节点列表、快速入口(抽烟/忍住)。
|
- 首页:上次实际抽烟时间(用于计时)、今日累计、今日克制、较昨日增减(可为负并标识“超出昨日”)、下次建议时间(默认/AI)、时间节点列表、快速入口(抽烟/忍住)。
|
||||||
- 记录页:快速添加抽烟、快速忍住、补录真实时间 `smoke_at`。
|
- 记录页:快速添加抽烟、快速忍住、补录真实时间 `smoke_at`。
|
||||||
- 列表页:按日期筛选、分页、区分“抽烟/忍住”标签、支持编辑/删除。
|
- 列表页:按日期筛选、分页、区分“抽烟/忍住”标签、支持编辑/删除。
|
||||||
- 看板页:周视图/区间视图,展示每日支数与 `minutes_since_last`。
|
- 看板页:周视图/区间视图,展示每日支数与 `minutes_since_last`。
|
||||||
@@ -94,13 +96,12 @@
|
|||||||
|
|
||||||
### 6.2 Smoke 模块 API
|
### 6.2 Smoke 模块 API
|
||||||
- 详见:`docs/smoke/API.md`
|
- 详见:`docs/smoke/API.md`
|
||||||
- 时序图:`docs/smoke/SEQUENCE.md`
|
|
||||||
|
|
||||||
## 7. 权限与错误处理(产品口径)
|
## 7. 权限与错误处理(产品口径)
|
||||||
- 未登录:统一提示“请先登录”,引导重新登录。
|
- 未登录:统一提示“请先登录”,引导重新登录。
|
||||||
- AI 未解锁(403):弹窗/页内提示“观看广告解锁当天生成”,提供“去观看”按钮。
|
- AI 未解锁(403):弹窗/页内提示“观看广告解锁当天生成”,提供“去观看”按钮。
|
||||||
- AI 服务不可用(503):提示“稍后再试”,不阻断默认策略展示。
|
- AI 服务不可用(503):提示“稍后再试”,不阻断默认策略展示。
|
||||||
- 没有记录:列表/看板显示空态;首页 `minutes_since_last` 不显示或显示“暂无数据”。
|
- 没有记录:列表/看板显示空态;首页 `last_smoke_at` 不显示或展示“暂无数据”。
|
||||||
|
|
||||||
## 8. 埋点与指标(建议)
|
## 8. 埋点与指标(建议)
|
||||||
- D1/D7 留存:进入首页、完成补全、记录次数(抽烟/忍住)、打开看板、生成 AI 建议。
|
- D1/D7 留存:进入首页、完成补全、记录次数(抽烟/忍住)、打开看板、生成 AI 建议。
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
|
|
||||||
本小程序用于记录抽烟情况(日期、原因、烟瘾程度、数量等)。
|
本小程序用于记录抽烟情况(日期、原因、烟瘾程度、数量等)。
|
||||||
|
|
||||||
## 时序图(前端流程)
|
## 产品与流程文档
|
||||||
|
|
||||||
见:`docs/smoke/SEQUENCE.md`
|
|
||||||
|
|
||||||
## 产品与 UI 文档
|
|
||||||
|
|
||||||
- 产品说明(PRD):`docs/smoke/PRODUCT.md`
|
- 产品说明(PRD):`docs/smoke/PRODUCT.md`
|
||||||
- UI/UX 设计说明:`docs/smoke/UI.md`
|
- 算法与 AI 策略:`docs/smoke/ALGORITHM.md`
|
||||||
|
|
||||||
## 依赖
|
## 依赖
|
||||||
|
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
# 戒烟/抽烟记录小程序:时序图
|
|
||||||
|
|
||||||
以下时序图使用 Mermaid(在支持 Mermaid 的 Markdown 渲染器中可直接预览)。
|
|
||||||
|
|
||||||
## 1) 登录与鉴权(获取 Bearer Token)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
participant MP as 小程序(前端)
|
|
||||||
participant API as 后端API(Gin)
|
|
||||||
participant WX as 微信 jscode2session
|
|
||||||
participant DB as MySQL
|
|
||||||
participant Redis as Redis(可选)
|
|
||||||
|
|
||||||
MP->>WX: wx.login() 获取 code
|
|
||||||
MP->>API: POST /api/v1/auth/login {mini_program_id, code, ...可选资料}
|
|
||||||
API->>DB: 读取 mini_programs(app_id/app_secret)
|
|
||||||
API->>WX: code 换取 openid/session_key
|
|
||||||
API->>DB: upsert users(mini_program_id+open_id)
|
|
||||||
alt 启用 Redis session cache
|
|
||||||
API->>Redis: 写入 session_key -> user 缓存
|
|
||||||
end
|
|
||||||
API-->>MP: {session_key} 作为后续 Authorization: Bearer <session_key>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2) 首次进入:基础信息补全(Profile)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
participant MP as 小程序(前端)
|
|
||||||
participant API as 后端API
|
|
||||||
participant DB as MySQL
|
|
||||||
|
|
||||||
MP->>API: GET /api/v1/smoke/profile (Bearer)
|
|
||||||
API->>DB: 查询 fa_smoke_user_profile(uid)
|
|
||||||
API-->>MP: exists/is_completed/baseline_interval_minutes/作息等
|
|
||||||
alt 需要补全
|
|
||||||
MP->>API: POST /api/v1/smoke/profile {...基础烟量/动机/动力/作息...}
|
|
||||||
API->>DB: upsert fa_smoke_user_profile(uid)
|
|
||||||
API-->>MP: 返回最新 profile + baseline_interval_minutes
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3) 记录抽烟 / 记录“忍住”
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
participant MP as 小程序(前端)
|
|
||||||
participant API as 后端API
|
|
||||||
participant DB as MySQL
|
|
||||||
|
|
||||||
alt 实际抽烟
|
|
||||||
MP->>API: POST /api/v1/smoke/logs {smoke_at?, remark?, level?, num?} (Bearer)
|
|
||||||
API->>DB: INSERT fa_smoke_log(uid, smoke_time, smoke_at, remark, level, num)
|
|
||||||
API-->>MP: 返回记录
|
|
||||||
else 想抽但忍住
|
|
||||||
MP->>API: POST /api/v1/smoke/logs/resisted {smoke_at?, remark?} (Bearer)
|
|
||||||
API->>DB: INSERT fa_smoke_log(uid, ..., level=0, num=0)
|
|
||||||
API-->>MP: 返回记录(用于列表展示)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4) 首页:获取“下次抽烟记录时间”(默认 + AI 自动切换)
|
|
||||||
|
|
||||||
说明:统一使用 `GET /api/v1/smoke/next_smoke_time`。
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
participant MP as 小程序(前端)
|
|
||||||
participant API as 后端API
|
|
||||||
participant DB as MySQL
|
|
||||||
|
|
||||||
MP->>API: GET /api/v1/smoke/next_smoke_time?date=today&mode=auto (Bearer)
|
|
||||||
API->>DB: 查 fa_smoke_ai_advice(type=next_smoke_time, advice_date=today) 是否存在
|
|
||||||
alt 已存在 AI 建议且 time_nodes 非空
|
|
||||||
API->>DB: 查 fa_smoke_ai_next_smoke(ai_advice_id) 取 not_before/suggested/nodes
|
|
||||||
API-->>MP: source=ai + 时间节点 + advice + default(兜底信息)
|
|
||||||
else 不存在 AI 建议
|
|
||||||
API->>DB: 查 fa_smoke_user_profile(uid) 取 baseline_interval/作息
|
|
||||||
API->>DB: 查 fa_smoke_log 取最后一次实际抽烟 + 最近7天忍住次数
|
|
||||||
API-->>MP: source=default + default.next_smoke_at
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5) 生成 AI “下次抽烟时间节点”(需要每日广告解锁)
|
|
||||||
|
|
||||||
说明:前端先“看广告解锁”,再用 `mode=ai` 生成当天/明天的 AI 时间节点;生成结果缓存“一天一份”。
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
participant MP as 小程序(前端)
|
|
||||||
participant API as 后端API
|
|
||||||
participant DB as MySQL
|
|
||||||
participant AI as AI Provider(OpenAI-compatible)
|
|
||||||
|
|
||||||
MP->>API: POST /api/v1/smoke/ai/advice_unlocks {date: "YYYY-MM-DD"} (Bearer)
|
|
||||||
API->>DB: upsert fa_smoke_ai_advice_unlocks(uid, unlock_date=date)
|
|
||||||
API-->>MP: unlocked=true
|
|
||||||
|
|
||||||
MP->>API: GET /api/v1/smoke/next_smoke_time?date=YYYY-MM-DD&mode=ai (Bearer)
|
|
||||||
API->>DB: 校验是否解锁(或会员)
|
|
||||||
alt 未解锁
|
|
||||||
API-->>MP: 403 需要观看广告解锁
|
|
||||||
else 已解锁
|
|
||||||
API->>DB: 读取最近3天 fa_smoke_log(含忍住) + profile + 默认策略
|
|
||||||
API->>AI: /chat/completions(严格JSON输出)
|
|
||||||
API->>DB: 写 fa_smoke_ai_advice(type=next_smoke_time, advice_date=date, ...)
|
|
||||||
API->>DB: 写 fa_smoke_ai_next_smoke(每个时间点一条:not_before/suggested/node)
|
|
||||||
API-->>MP: source=ai + 时间节点
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6) 每日 AI 戒烟建议(会员/广告解锁)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
participant MP as 小程序(前端)
|
|
||||||
participant API as 后端API
|
|
||||||
participant DB as MySQL
|
|
||||||
participant AI as AI Provider(OpenAI-compatible)
|
|
||||||
|
|
||||||
MP->>API: GET /api/v1/smoke/ai/advice?date=YYYY-MM-DD (Bearer)
|
|
||||||
API->>DB: 查 fa_smoke_ai_advice(type=daily_advice, advice_date=date) 是否已缓存
|
|
||||||
alt 已缓存
|
|
||||||
API-->>MP: advice
|
|
||||||
else 未缓存
|
|
||||||
API->>DB: 校验会员或广告解锁(unlock_date=date)
|
|
||||||
alt 未解锁/非会员
|
|
||||||
API-->>MP: 403 need vip_or_ad
|
|
||||||
else 已解锁/会员
|
|
||||||
API->>DB: 读取当天 fa_smoke_log(节点/总量)
|
|
||||||
API->>AI: /chat/completions 生成建议
|
|
||||||
API->>DB: 写 fa_smoke_ai_advice(type=daily_advice,...)
|
|
||||||
API-->>MP: advice
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
# 戒烟/抽烟记录小程序:UI/UX 设计说明
|
|
||||||
|
|
||||||
本文档面向前端实现,包含信息架构、页面结构、组件与交互细节,以及与后端 API 的映射。
|
|
||||||
|
|
||||||
## 1. 信息架构(IA)
|
|
||||||
|
|
||||||
推荐 4 个 Tab:
|
|
||||||
1) 首页
|
|
||||||
2) 记录
|
|
||||||
3) 看板
|
|
||||||
4) 我的
|
|
||||||
|
|
||||||
页面路由(建议):
|
|
||||||
- 首页:NextTimeCard + QuickActions + TodaySummary + RecentList
|
|
||||||
- 记录:新增抽烟、忍住、历史列表入口
|
|
||||||
- 看板:周视图/区间视图
|
|
||||||
- 我的:基础信息、AI/解锁入口、帮助与隐私
|
|
||||||
|
|
||||||
## 2. 视觉与组件规范(简版)
|
|
||||||
|
|
||||||
### 2.1 视觉风格
|
|
||||||
- 主色:清爽、偏健康(绿色/蓝绿)+ 强对比强调色(橙/红用于“抽烟”风险提示,但避免羞辱语气)。
|
|
||||||
- 卡片化布局:关键指标卡片(下次时间/今日累计/间隔)。
|
|
||||||
|
|
||||||
### 2.2 通用组件
|
|
||||||
- `AppHeader`:标题 + 日期切换(today/tomorrow)
|
|
||||||
- `MetricCard`:标题/主数值/辅助说明/状态标识(AI/默认)
|
|
||||||
- `PrimaryButton` / `SecondaryButton`
|
|
||||||
- `Tag`:抽烟/忍住/动机标签
|
|
||||||
- `Timeline`:时间节点列表(AI time_nodes)
|
|
||||||
- `EmptyState` / `ErrorState` / `Skeleton`
|
|
||||||
- `ModalPaywall`:广告解锁提示(403)
|
|
||||||
|
|
||||||
### 2.3 文案口径
|
|
||||||
- 避免指责:使用“建议/可以尝试/你已经在进步”。
|
|
||||||
- 忍住记录:用“忍住了”/“成功延后”作为正反馈。
|
|
||||||
|
|
||||||
## 3. 页面详设
|
|
||||||
|
|
||||||
## 3.1 首页(核心)
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
- 让用户一眼看到:下一次建议时间、今天累计、距离上次实际抽烟的间隔、快速记录入口。
|
|
||||||
|
|
||||||
### 布局(从上到下)
|
|
||||||
1) 顶部:日期选择(默认 today,可切 tomorrow)
|
|
||||||
2) `NextTimeCard`
|
|
||||||
3) `QuickActions`(两按钮:抽烟/忍住)
|
|
||||||
4) `TodaySummary`(今日累计、minutes_since_last)
|
|
||||||
5) `AITipsCard`(可选:每日 AI 建议入口)
|
|
||||||
6) `RecentList`(最近记录 10~20 条)
|
|
||||||
|
|
||||||
### NextTimeCard(关键)
|
|
||||||
数据来源:
|
|
||||||
- `GET /api/v1/smoke/next_smoke_time?date=today|tomorrow&mode=auto`
|
|
||||||
展示字段:
|
|
||||||
- `source`:
|
|
||||||
- `ai`:展示 `not_before_at`、`suggested_at`、`time_nodes`(Timeline),并展示一句 `advice`
|
|
||||||
- `default`:展示 `default.next_smoke_at`(同时映射为 `not_before_at/suggested_at`)
|
|
||||||
交互:
|
|
||||||
- “生成AI计划”按钮:调用同接口但 `mode=ai`
|
|
||||||
- 403:弹 `ModalPaywall`,引导先看广告解锁
|
|
||||||
- 503:提示 AI 暂不可用,仍保留默认卡片
|
|
||||||
|
|
||||||
解锁流程(按天):
|
|
||||||
1) 前端看广告完成
|
|
||||||
2) `POST /api/v1/smoke/ai/advice_unlocks {date: 计划日期}`
|
|
||||||
3) 再调 `GET /api/v1/smoke/next_smoke_time?date=...&mode=ai`
|
|
||||||
|
|
||||||
### QuickActions(抽烟/忍住)
|
|
||||||
- 抽烟:进入“新增抽烟”轻表单(默认 `num=1, level=2`)
|
|
||||||
- 忍住:一键记录(可选弹小输入框写 remark),调用 `POST /api/v1/smoke/logs/resisted`
|
|
||||||
|
|
||||||
### TodaySummary
|
|
||||||
数据来源:
|
|
||||||
- `GET /api/v1/smoke/dashboard`(默认本周)+ 也可在首页只展示 `today_count` 与 `minutes_since_last`
|
|
||||||
展示:
|
|
||||||
- 今日支数(sum num)
|
|
||||||
- 距上次实际抽烟分钟(后端已忽略忍住记录)
|
|
||||||
|
|
||||||
### RecentList
|
|
||||||
数据来源:
|
|
||||||
- `GET /api/v1/smoke/logs/latest?limit=20`
|
|
||||||
展示规则:
|
|
||||||
- `num==0 && level==0`:标记为“忍住了”,颜色更积极(如绿色),不展示“支数”
|
|
||||||
- 其它:展示 `num` + `level` + `remark` + `smoke_at`(优先)/创建时间
|
|
||||||
交互:
|
|
||||||
- 点击进入详情(编辑/删除)
|
|
||||||
|
|
||||||
## 3.2 记录页(新增/补录)
|
|
||||||
|
|
||||||
### 页面目标
|
|
||||||
- 让记录成本极低(3 秒内完成一条)。
|
|
||||||
|
|
||||||
### 模块
|
|
||||||
1) 快速新增抽烟(表单)
|
|
||||||
2) 快速忍住(按钮 + 可选 remark)
|
|
||||||
3) 历史记录入口(跳列表)
|
|
||||||
|
|
||||||
### 新增抽烟表单
|
|
||||||
字段:
|
|
||||||
- `smoke_at`:默认当前时间,可手动改
|
|
||||||
- `num`:Stepper(1~10)
|
|
||||||
- `level`:1~5(或 0~5,但 0 仅用于忍住)
|
|
||||||
- `remark`:常用标签(压力/无聊/社交/提神)+ 自定义输入
|
|
||||||
API:
|
|
||||||
- `POST /api/v1/smoke/logs`
|
|
||||||
|
|
||||||
## 3.3 历史列表页
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
- 可回溯、可筛选、可编辑。
|
|
||||||
|
|
||||||
UI:
|
|
||||||
- 顶部筛选:日期范围(start/end)、分页加载
|
|
||||||
- 列表项:时间、类型(抽烟/忍住)、num/level、remark
|
|
||||||
API:
|
|
||||||
- `GET /api/v1/smoke/logs?page=&page_size=&start=&end=`
|
|
||||||
|
|
||||||
空态:
|
|
||||||
- “还没有记录,先从右下角按钮开始”
|
|
||||||
|
|
||||||
## 3.4 记录详情页(编辑/删除)
|
|
||||||
展示:
|
|
||||||
- smoke_time、smoke_at、remark、level、num
|
|
||||||
编辑:
|
|
||||||
- `POST /api/v1/smoke/logs/:id`(支持清空 smoke_at/smoke_time 传空字符串)
|
|
||||||
删除:
|
|
||||||
- `DELETE /api/v1/smoke/logs/:id`
|
|
||||||
|
|
||||||
## 3.5 看板页
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
- 展示趋势:周视图/区间视图,给用户反馈与成就感。
|
|
||||||
|
|
||||||
UI:
|
|
||||||
- 顶部:日期范围选择(默认本周)
|
|
||||||
- 柱状图:weekly.count
|
|
||||||
- 指标:today_count、minutes_since_last
|
|
||||||
API:
|
|
||||||
- `GET /api/v1/smoke/dashboard?start=&end=`
|
|
||||||
|
|
||||||
## 3.6 AI 建议页(每日)
|
|
||||||
|
|
||||||
入口:
|
|
||||||
- 首页卡片/我的页面
|
|
||||||
|
|
||||||
展示:
|
|
||||||
- Markdown 渲染(建议内容可能包含列表)
|
|
||||||
API:
|
|
||||||
- `GET /api/v1/smoke/ai/advice?date=YYYY-MM-DD`(默认昨天)
|
|
||||||
解锁:
|
|
||||||
- 若 403,弹引导看广告 -> `POST /api/v1/smoke/ai/advice_unlocks {date}`
|
|
||||||
|
|
||||||
## 3.7 基础信息(Profile)页
|
|
||||||
|
|
||||||
目标:
|
|
||||||
- 获取默认策略与 AI 个性化所需信息;尽量简短可跳过但提示价值。
|
|
||||||
|
|
||||||
字段建议(分组)
|
|
||||||
1) 基础烟量:日均支数 `baseline_cigs_per_day`
|
|
||||||
2) 烟龄/价格:`smoking_years`、`pack_price_cent`
|
|
||||||
3) 抽烟动机(多选):压力/无聊/社交/提神 + 自定义
|
|
||||||
4) 戒烟动力(多选):健康/家人/省钱 + 自定义
|
|
||||||
5) 作息:起床/入睡(HH:MM)
|
|
||||||
|
|
||||||
API:
|
|
||||||
- `GET /api/v1/smoke/profile`
|
|
||||||
- `POST /api/v1/smoke/profile`
|
|
||||||
|
|
||||||
## 4. 关键交互与状态
|
|
||||||
|
|
||||||
### 4.1 AI 生成(按天一次)
|
|
||||||
推荐状态机:
|
|
||||||
- `idle` -> `generating` -> `success`/`locked(403)`/`failed(5xx)`
|
|
||||||
|
|
||||||
403(未解锁):
|
|
||||||
- 弹窗:说明“观看一次广告解锁当天生成”
|
|
||||||
- 按钮:`去观看`(前端完成广告后调用 unlock 接口)
|
|
||||||
|
|
||||||
### 4.2 日期选择(today/tomorrow)
|
|
||||||
- 首页切 tomorrow 时:
|
|
||||||
- `GET /api/v1/smoke/next_smoke_time?date=tomorrow&mode=auto`
|
|
||||||
- 仅在用户主动点“生成AI计划”时才 `mode=ai`(且 unlock date=tomorrow)
|
|
||||||
|
|
||||||
### 4.3 忍住记录的视觉反馈
|
|
||||||
- 提交成功 toast:`已记录:忍住了`
|
|
||||||
- 列表项显示“忍住”Tag,并用绿色/正向文案。
|
|
||||||
|
|
||||||
## 5. API 映射速查表
|
|
||||||
|
|
||||||
- 登录:`POST /api/v1/auth/login`(返回 session_key)
|
|
||||||
- Profile:`GET/POST /api/v1/smoke/profile`
|
|
||||||
- 新增抽烟:`POST /api/v1/smoke/logs`
|
|
||||||
- 忍住:`POST /api/v1/smoke/logs/resisted`
|
|
||||||
- 列表:`GET /api/v1/smoke/logs`、`GET /api/v1/smoke/logs/latest`
|
|
||||||
- 看板:`GET /api/v1/smoke/dashboard`
|
|
||||||
- 下次时间:`GET /api/v1/smoke/next_smoke_time?date=&mode=`
|
|
||||||
- 每日 AI 建议:`GET /api/v1/smoke/ai/advice?date=`
|
|
||||||
- 广告解锁:`POST /api/v1/smoke/ai/advice_unlocks {date}`
|
|
||||||
@@ -21,6 +21,7 @@ type nextSmokeTimeUnifiedResponse struct {
|
|||||||
TodayCount int `json:"today_count"`
|
TodayCount int `json:"today_count"`
|
||||||
Resisted int `json:"resisted_count"`
|
Resisted int `json:"resisted_count"`
|
||||||
Reduced int `json:"reduced_from_yesterday"`
|
Reduced int `json:"reduced_from_yesterday"`
|
||||||
|
Exceeded bool `json:"exceeded_yesterday"`
|
||||||
TimeNodes []string `json:"time_nodes,omitempty"`
|
TimeNodes []string `json:"time_nodes,omitempty"`
|
||||||
Advice string `json:"advice,omitempty"`
|
Advice string `json:"advice,omitempty"`
|
||||||
Default nextSmokeDefaultResponse `json:"default"`
|
Default nextSmokeDefaultResponse `json:"default"`
|
||||||
@@ -84,7 +85,7 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
|
|||||||
if t == nil {
|
if t == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return t.In(time.Local).Format(dateTimeLayout)
|
return t.In(time.Local).Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
|
||||||
formatTimeString := func(value string) string {
|
formatTimeString := func(value string) string {
|
||||||
@@ -93,10 +94,10 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if t, err := time.Parse(time.RFC3339, value); err == nil {
|
if t, err := time.Parse(time.RFC3339, value); err == nil {
|
||||||
return t.In(time.Local).Format(dateTimeLayout)
|
return t.In(time.Local).Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
if t, err := time.ParseInLocation(dateTimeLayout, value, time.Local); err == nil {
|
if t, err := time.ParseInLocation(dateTimeLayout, value, time.Local); err == nil {
|
||||||
return t.In(time.Local).Format(dateTimeLayout)
|
return t.In(time.Local).Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -137,6 +138,7 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
|
|||||||
TodayCount: homeSummary.TodayCount,
|
TodayCount: homeSummary.TodayCount,
|
||||||
Resisted: homeSummary.ResistedCount,
|
Resisted: homeSummary.ResistedCount,
|
||||||
Reduced: homeSummary.ReducedFromYesterday,
|
Reduced: homeSummary.ReducedFromYesterday,
|
||||||
|
Exceeded: homeSummary.ExceededYesterday,
|
||||||
Default: formatDefault(defaultSuggestion),
|
Default: formatDefault(defaultSuggestion),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ type SmokeHomeSummary struct {
|
|||||||
TodayCount int
|
TodayCount int
|
||||||
ResistedCount int
|
ResistedCount int
|
||||||
ReducedFromYesterday int
|
ReducedFromYesterday int
|
||||||
|
ExceededYesterday bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DashboardWeeklyStat 表示某一天的抽烟支数以及是否为今天。
|
// DashboardWeeklyStat 表示某一天的抽烟支数以及是否为今天。
|
||||||
@@ -290,9 +291,7 @@ func (s *SmokeLogService) HomeSummary(ctx context.Context, uid int, asOf time.Ti
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduced := int(yesterdayCount - todayCount)
|
reduced := int(yesterdayCount - todayCount)
|
||||||
if reduced < 0 {
|
exceeded := reduced < 0
|
||||||
reduced = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastSmokeAt *time.Time
|
var lastSmokeAt *time.Time
|
||||||
var last smokemodel.SmokeLog
|
var last smokemodel.SmokeLog
|
||||||
@@ -315,6 +314,7 @@ func (s *SmokeLogService) HomeSummary(ctx context.Context, uid int, asOf time.Ti
|
|||||||
TodayCount: int(todayCount),
|
TodayCount: int(todayCount),
|
||||||
ResistedCount: int(resistedCount),
|
ResistedCount: int(resistedCount),
|
||||||
ReducedFromYesterday: reduced,
|
ReducedFromYesterday: reduced,
|
||||||
|
ExceededYesterday: exceeded,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user