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:
nepiedg
2026-01-25 08:51:25 +00:00
parent b67dc32369
commit f80c3e8f45
9 changed files with 62 additions and 402 deletions
+17 -17
View File
@@ -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 的记录数 |
| bonus_interval | 计算得出 | 0 | 奖励延长时间 |
@@ -31,7 +31,7 @@
```
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 睡眠规避
若计算出的时间落在睡眠区间,顺延到次日起床时间
若计算出的时间落在睡眠区间,顺延到**下一次**起床时间(可能是当天也可能是次日)
```
if suggested_time in [sleep_time, 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)
@@ -59,7 +64,7 @@ if suggested_time in [sleep_time, wake_up_time]:
计算奖励间隔: 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 建议内容
AI 会生成以下内容:
AI 会生成以下内容(实际接口格式)
```json
{
"advice": "昨天你的吸烟量比限额少了2支,这是一个巨大的胜利!数据显示你的烟瘾在下午2点左右达到顶峰——今天试着那个时候去散散步。",
"time_nodes": [
{ "time": "09:30", "type": "suggested", "note": "第一支,早餐后" },
{ "time": "12:30", "type": "suggested", "note": "午餐后" },
{ "time": "15:30", "type": "suggested", "note": "下午茶时间" },
{ "time": "19:00", "type": "suggested", "note": "晚餐后" },
{ "time": "22:00", "type": "suggested", "note": "睡前最后一支" }
],
"daily_target": 5,
"tips": ["2点是你的高峰期,准备一颗薄荷糖", "试着用深呼吸替代"]
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:28:00+08:00",
"time_nodes": ["09:30", "12:30", "15:30", "19:00", "22:00"],
"advice": "昨天你的吸烟量比限额少了2支,这是一个巨大的胜利!数据显示你的烟瘾在下午2点左右达到顶峰——今天试着那个时候去散散步。"
}
```
@@ -299,6 +298,7 @@ function getMotivationMessage(context) {
| 忍住成功率 | 忍住次数 / (忍住+抽烟次数) | 意志力评估 |
| 平均间隔 | 总时长 / 抽烟次数 | 递减效果 |
| 最长无烟时长 | 最大间隔记录 | 成就激励 |
| 较昨日减少 | 昨日支数 - 今日支数(可为负) | 若为负,表示今天超出昨日 |
### 8.2 周报数据结构
+17 -13
View File
@@ -360,6 +360,7 @@ curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
默认策略(不使用 AI):
- 基础间隔:优先使用 `GET /api/v1/smoke/profile` 返回的 `baseline_interval_minutes`;若不存在则默认 `60` 分钟。
- 阶梯式延时:最近 7 天内每累计 `5` 条“忍住记录(level=0,num=0)”,在基础间隔上 `+5` 分钟(最多 `+60` 分钟)。
- 间隔兜底:最终间隔会限制在 `5~240` 分钟之间。
- 若用户已补全作息时间,会自动规避睡眠区间:若计算出的时间落在睡眠区间,顺延到下一次起床时间。
AI 生成说明:
@@ -373,22 +374,23 @@ AI 生成说明:
"message": "success",
"data": {
"source": "default",
"not_before_at": "2026-01-05 10:18:00",
"suggested_at": "2026-01-05 10:18:00",
"last_smoke_at": "2026-01-05 09:30:00",
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:18:00+08:00",
"last_smoke_at": "2026-01-05T09:30:00+08:00",
"today_count": 3,
"resisted_count": 1,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false,
"default": {
"last_smoke_at": "2026-01-05 09:30:00",
"next_smoke_at": "2026-01-05 10:18:00",
"last_smoke_at": "2026-01-05T09:30:00+08:00",
"next_smoke_at": "2026-01-05T10:18:00+08:00",
"base_interval_minutes": 48,
"interval_minutes": 48,
"stage": 0,
"resisted_7d": 3,
"sleep_adjusted": false,
"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",
"data": {
"source": "ai",
"not_before_at": "2026-01-05 10:18:00",
"suggested_at": "2026-01-05 10:28:00",
"last_smoke_at": "2026-01-05 09:30:00",
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:28:00+08:00",
"last_smoke_at": "2026-01-05T09:30:00+08:00",
"today_count": 3,
"resisted_count": 1,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false,
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
"default": { "algorithm": "staircase_delay_v1" },
"ai": {
"plan_date": "2026-01-05",
"not_before_at": "2026-01-05 10:18:00",
"suggested_at": "2026-01-05 10:28:00",
"not_before_at": "2026-01-05T10:18:00+08:00",
"suggested_at": "2026-01-05T10:28:00+08:00",
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
"prompt_version": "v1",
@@ -425,7 +428,8 @@ AI 生成说明:
```
字段说明(新增首页字段):
- `last_smoke_at`:上次“实际抽烟”时间(忽略忍住记录),格式 `YYYY-MM-DD HH:MM:SS`
- `last_smoke_at`:上次“实际抽烟”时间(忽略忍住记录),格式 `RFC3339`(含时区)
- `today_count`:今日抽烟支数(累加 `num`)。
- `resisted_count`:今日克制次数(`level=0 && num=0`)。
- `reduced_from_yesterday`:较昨日减少的支数(`max(昨日支数 - 今日支数, 0)`)。
- `reduced_from_yesterday`:较昨日减少的支数(允许为负数;为负时表示“今天超出昨日”)。
- `exceeded_yesterday`:是否超出昨日(`true` 表示今天超出昨日,前端可用作单独标识)。
+11 -11
View File
@@ -28,10 +28,10 @@
|------|------|----------|
| 问候语 | 根据时段显示(早上好/下午好等) + 用户昵称 | 本地计算 + profile |
| AI 提示卡片 | 发现的抽烟规律/建议(可关闭) | `GET /ai/advice` 缓存 |
| 计时环 | 距上次抽烟时间(时:分:秒) | `dashboard.minutes_since_last` |
| 计时环 | 距上次抽烟时间(时:分:秒) | `GET /next_smoke_time``last_smoke_at`(前端计时) |
| 下次建议时间 | 显示建议的下次抽烟时间点 | `GET /next_smoke_time` |
| 今日已抽 | X / 目标数,较昨日 ±N | `dashboard.today_count` |
| 烟瘾发作已抵抗 | 忍住次数统计 | 筛选 `level=0,num=0` 记录 |
| 今日已抽 | X / 目标数,较昨日 ±N | `next_smoke_time.today_count` + `next_smoke_time.reduced_from_yesterday`(可为负) + `next_smoke_time.exceeded_yesterday`(标识“超出昨日”) |
| 烟瘾发作已抵抗 | 忍住次数统计 | `next_smoke_time.resisted_count` |
| 记录抽烟按钮 | 快速记录一次抽烟 | `POST /logs` |
| 想抽忍住了按钮 | 记录成功抵抗 | `POST /logs/resisted` |
@@ -81,7 +81,7 @@
| 筛选 Tabs | 全部 / 已抽烟 / 已忍住 | 前端筛选 |
| 时间线 | 按日期分组展示 | `GET /logs` |
| 记录卡片 | 类型、时间、原因标签、间隔时间 | logs 数据 |
| 左滑操作 | 编辑 / 删除 | `PUT/DELETE /logs/:id` |
| 左滑操作 | 编辑 / 删除 | `POST/DELETE /logs/:id` |
| 新增按钮 | 浮动按钮快速新增 | 跳转记录流程 |
### 2.5 个人中心 (profile_&_settings)
@@ -92,11 +92,11 @@
|------|------|----------|
| 用户信息 | 头像、昵称 | 微信授权 |
| 目标展示 | 目标戒烟日期、连续天数 | profile |
| 目标设定 | 调整每日限额与戒烟日期 | `PUT /profile` |
| 目标设定 | 调整每日限额与戒烟日期 | `POST /profile` |
| AI 计划调整 | 个性化辅导风格设置 | profile 扩展 |
| 通知设置 | 提醒时间、频率 | 本地存储 |
| 会员解锁 | PRO 功能 / 广告解锁 | 会员系统 |
| 基础设置 | 作息时间等 | `PUT /profile` |
| 基础设置 | 作息时间等 | `POST /profile` |
| 隐私与数据 | 数据导出、账号注销 | 待扩展 |
---
@@ -126,7 +126,7 @@ Step 5: 作息时间 (wake_up_time, sleep_time)
Step 6: 设置目标 (目标日期、每日限额)
提交 profile (PUT /profile)
提交 profile (POST /profile)
进入首页
```
@@ -186,8 +186,8 @@ Step 6: 设置目标 (目标日期、每日限额)
```
[并行请求]
├── GET /profile (用户信息,判断是否需引导)
├── GET /dashboard (今日统计,计时器数据)
└── GET /next_smoke_time (下次建议时间)
├── GET /next_smoke_time (首页汇总 + 下次建议时间)
└── GET /dashboard (看板数据,可延迟)
[延迟加载]
└── GET /ai/advice (AI提示卡片,非关键)
@@ -195,8 +195,8 @@ Step 6: 设置目标 (目标日期、每日限额)
**缓存策略**
- profile: 登录后缓存,变更时更新
- dashboard: 每次进入刷新,后台定时更
- next_smoke_time: 缓存至下次记录
- next_smoke_time: 每次进入刷新,下一次记录后刷
- dashboard: 进入看板时刷新
- ai/advice: 按天缓存
### 5.2 数据预加载
+7 -6
View File
@@ -22,7 +22,7 @@
### 2.2 典型场景
- 抽完烟立刻记一笔(含时间/原因/数量)。
- 想抽但忍住:快速一键记录(`level=0,num=0`)。
- 首页查看:下次建议时间、距离上次实际抽烟的间隔、今累计。
- 首页查看:下次建议时间、上次实际抽烟时间、今累计、今日克制、较昨日增减
- 每天晚上/第二天查看 AI 总结与行动建议(解锁后)。
- 生成“今天/明天时间节点计划”(解锁后),帮助前端展示时间线/日程。
@@ -34,7 +34,8 @@
### 3.2 “最后一次抽烟”
- 定义为“最后一次实际抽烟事件”(忽略忍住记录)。
- 后端看板 `minutes_since_last` 已按该口径计算
- 首页口径使用 `next_smoke_time` 返回的 `last_smoke_at` 作为计时起点(前端可自行计算间隔)
- 看板的 `minutes_since_last` 仍按该口径计算。
### 3.3 默认“下次建议时间”(不使用 AI)
- 基础间隔来自 `GET /api/v1/smoke/profile``baseline_interval_minutes`(若无则默认 60 分钟)。
@@ -71,6 +72,7 @@
- 若已存在 AI 时间节点:返回 `source=ai`
- 否则:返回 `source=default`
- 生成 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 解锁(按天)
- 看广告完成后回调:`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/next_smoke_time?date=...&mode=ai`
## 5. 页面能力清单(对应 UI 文档)
- 首页:今日累计、距上次实际抽烟、下次建议时间(默认/AI)、时间节点列表、快速入口(抽烟/忍住)。
## 5. 页面能力清单
- 首页:上次实际抽烟时间(用于计时)、今日累计、今日克制、较昨日增减(可为负并标识“超出昨日”)、下次建议时间(默认/AI)、时间节点列表、快速入口(抽烟/忍住)。
- 记录页:快速添加抽烟、快速忍住、补录真实时间 `smoke_at`
- 列表页:按日期筛选、分页、区分“抽烟/忍住”标签、支持编辑/删除。
- 看板页:周视图/区间视图,展示每日支数与 `minutes_since_last`
@@ -94,13 +96,12 @@
### 6.2 Smoke 模块 API
- 详见:`docs/smoke/API.md`
- 时序图:`docs/smoke/SEQUENCE.md`
## 7. 权限与错误处理(产品口径)
- 未登录:统一提示“请先登录”,引导重新登录。
- AI 未解锁(403):弹窗/页内提示“观看广告解锁当天生成”,提供“去观看”按钮。
- AI 服务不可用(503):提示“稍后再试”,不阻断默认策略展示。
- 没有记录:列表/看板显示空态;首页 `minutes_since_last` 不显示或示“暂无数据”。
- 没有记录:列表/看板显示空态;首页 `last_smoke_at` 不显示或示“暂无数据”。
## 8. 埋点与指标(建议)
- D1/D7 留存:进入首页、完成补全、记录次数(抽烟/忍住)、打开看板、生成 AI 建议。
+2 -6
View File
@@ -2,14 +2,10 @@
本小程序用于记录抽烟情况(日期、原因、烟瘾程度、数量等)。
## 时序图(前端流程)
见:`docs/smoke/SEQUENCE.md`
## 产品与 UI 文档
## 产品与流程文档
- 产品说明(PRD):`docs/smoke/PRODUCT.md`
- UI/UX 设计说明`docs/smoke/UI.md`
- 算法与 AI 策略`docs/smoke/ALGORITHM.md`
## 依赖
-143
View File
@@ -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
```
-200
View File
@@ -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`Stepper1~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}`
+5 -3
View File
@@ -21,6 +21,7 @@ type nextSmokeTimeUnifiedResponse struct {
TodayCount int `json:"today_count"`
Resisted int `json:"resisted_count"`
Reduced int `json:"reduced_from_yesterday"`
Exceeded bool `json:"exceeded_yesterday"`
TimeNodes []string `json:"time_nodes,omitempty"`
Advice string `json:"advice,omitempty"`
Default nextSmokeDefaultResponse `json:"default"`
@@ -84,7 +85,7 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
if t == nil {
return ""
}
return t.In(time.Local).Format(dateTimeLayout)
return t.In(time.Local).Format(time.RFC3339)
}
formatTimeString := func(value string) string {
@@ -93,10 +94,10 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
return ""
}
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 {
return t.In(time.Local).Format(dateTimeLayout)
return t.In(time.Local).Format(time.RFC3339)
}
return value
}
@@ -137,6 +138,7 @@ func (h *SmokeHandler) GetNextSmokeTime(c *gin.Context) {
TodayCount: homeSummary.TodayCount,
Resisted: homeSummary.ResistedCount,
Reduced: homeSummary.ReducedFromYesterday,
Exceeded: homeSummary.ExceededYesterday,
Default: formatDefault(defaultSuggestion),
}
+3 -3
View File
@@ -123,6 +123,7 @@ type SmokeHomeSummary struct {
TodayCount int
ResistedCount int
ReducedFromYesterday int
ExceededYesterday bool
}
// DashboardWeeklyStat 表示某一天的抽烟支数以及是否为今天。
@@ -290,9 +291,7 @@ func (s *SmokeLogService) HomeSummary(ctx context.Context, uid int, asOf time.Ti
}
reduced := int(yesterdayCount - todayCount)
if reduced < 0 {
reduced = 0
}
exceeded := reduced < 0
var lastSmokeAt *time.Time
var last smokemodel.SmokeLog
@@ -315,6 +314,7 @@ func (s *SmokeLogService) HomeSummary(ctx context.Context, uid int, asOf time.Ti
TodayCount: int(todayCount),
ResistedCount: int(resistedCount),
ReducedFromYesterday: reduced,
ExceededYesterday: exceeded,
}, nil
}