refactor: trim smoke home API usage

This commit is contained in:
nepiedg
2026-04-26 22:05:21 +08:00
parent e28854b97d
commit 374168d959
7 changed files with 49 additions and 562 deletions
+2 -2
View File
@@ -254,9 +254,9 @@ function calculateMoneySaved(packPriceCent, cigsPerPack, baselineCigsPerDay, act
---
## 7. 激励语生成(后端统一
## 7. 激励语生成(首页内联返回
接口:`GET /api/v1/smoke/motivation`(详见 `docs/smoke/API.md`
接口:`GET /api/v1/smoke/home``motivation` 字段。
根据用户状态生成不同的激励语:
+16 -43
View File
@@ -60,18 +60,14 @@
├─────────────────────────────────────────────────────────┤
│ 0ms ─ 页面骨架屏渲染 │
│ │ │
│ ├──── 并行请求 ──────────────────────────────────── │
│ ├──── 串行守卫 + 单接口数据 ──────────────────────── │
│ │ ├── /profile (检查用户状态) │
│ │ ── /dashboard (核心数据) │
│ │ └── /next_smoke_time (建议时间) │
│ │ ── /home (首页核心数据)
│ │ │
│ 200ms ─ 核心数据返回,渲染计时器+统计卡片 │
│ │ │
│ 300ms ─ 首屏渲染完成 │
│ │ │
│ │ ┌── 延迟加载 ────────────────────────────── │
│ │ └── /ai/advice (AI提示卡片) │
│ │ │
│ 500ms ─ 完整页面渲染 │
└─────────────────────────────────────────────────────────┘
```
@@ -79,29 +75,14 @@
### 3.2 缓存策略
```javascript
// stores/dashboard.js
import { defineStore } from 'pinia'
// 首页使用 /smoke/home 单接口返回当前屏所需字段。
// 页面级刷新由 onShow 触发,避免维护额外 dashboard store 和重复请求。
const homeData = ref(null)
export const useDashboardStore = defineStore('dashboard', {
state: () => ({
todayCount: 0,
minutesSinceLast: 0,
weekly: [],
nextSmokeTime: null,
lastFetchTime: 0,
cacheExpiry: 30 * 1000
}),
actions: {
async fetchDashboard(forceRefresh = false) {
const now = Date.now()
if (!forceRefresh && now - this.lastFetchTime < this.cacheExpiry) {
return
}
// 发起请求...
}
}
})
async function fetchRecordHomeData() {
const res = await api.getHome()
homeData.value = res.data || {}
}
```
### 3.3 计时器优化
@@ -240,35 +221,27 @@ export const request = {
```javascript
// pages/index/index.vue
import { ref, onMounted } from 'vue'
import { useDashboardStore } from '@/stores/dashboard'
import * as api from '@/api/smoke'
import * as api from '@/api'
const loading = ref(true)
const dashboardStore = useDashboardStore()
const homeData = ref(null)
async function initPage() {
loading.value = true
try {
const [profileRes, dashboardRes, nextTimeRes] = await Promise.all([
api.getProfile(),
api.getDashboard(),
api.getNextSmokeTime()
])
if (!profileRes.data.exists || !profileRes.data.is_completed) {
const profileRes = await api.getSmokeProfile()
if (!profileRes.exists || !profileRes.is_completed) {
uni.redirectTo({ url: '/pages/onboarding/index' })
return
}
dashboardStore.setDashboard(dashboardRes.data)
dashboardStore.setNextSmokeTime(nextTimeRes.data)
const homeRes = await api.getHome()
homeData.value = homeRes.data || {}
} finally {
loading.value = false
}
setTimeout(loadAiAdvice, 300)
}
onMounted(initPage)
+17 -288
View File
@@ -54,18 +54,7 @@ curl -X POST 'http://127.0.0.1:8080/api/v1/smoke/logs' \
}
```
## 2) 获取单条记录
`GET /api/v1/smoke/logs/:id`
curl 示例:
```bash
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
-H 'Authorization: Bearer wx-session-key'
```
## 3) 列表查询(分页)
## 2) 列表查询(分页)
`GET /api/v1/smoke/logs?page=1&page_size=20&start=2025-12-01&end=2025-12-31&type=all`
@@ -93,71 +82,7 @@ curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
}
```
## 4) 获取看板概览
`GET /api/v1/smoke/dashboard?start=2026-01-01&end=2026-01-07`
参数:
- `start`:起始日期(含,格式 `YYYY-MM-DD`),默认“本周一”
- `end`:截止日期(含,格式 `YYYY-MM-DD`),默认“本周日”。若只传 `start``end` 默认为 `start + 6 天`
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"today_count": 6,
"minutes_since_last": 42,
"weekly": [
{ "date": "2026-01-01", "count": 2, "is_today": false },
{ "date": "2026-01-02", "count": 1, "is_today": false },
{ "date": "2026-01-03", "count": 0, "is_today": false },
{ "date": "2026-01-04", "count": 0, "is_today": false },
{ "date": "2026-01-05", "count": 3, "is_today": true },
{ "date": "2026-01-06", "count": 0, "is_today": false },
{ "date": "2026-01-07", "count": 0, "is_today": false }
]
}
}
```
字段说明:
- `today_count`:当天吸烟总支数(累加 `num`
- `minutes_since_last`:距最后一次“实际抽烟”(忽略 `level=0 && num=0` 的忍住记录)的分钟数,通过最近一条 `smoke_at/smoke_time/createtime` 计算;若历史为空则字段不存在
- `weekly`:起止日期内每日汇总,`count` 为当日总支数,`is_today` 标记当前日期(即便不在 `start/end` 范围内也会标记为 `false`
## 5) 最近记录列表(轻量版)
`GET /api/v1/smoke/logs/latest?limit=20`
参数:
- `limit`:返回条数,默认 `20`,最大 `100`
成功响应示例:
```json
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 123,
"smoke_time": "2026-01-05T00:00:00+08:00",
"smoke_at": "2026-01-05T09:12:00+08:00",
"remark": "压力大",
"level": 3,
"num": 2,
"createtime": 1736049120
}
]
}
}
```
## 6) 更新记录
## 3) 更新记录
`POST /api/v1/smoke/logs/:id`
@@ -178,7 +103,7 @@ curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
- 如果你想“清空 smoke_at”,请传空字符串:`{"smoke_at":""}`
- 如果传 `null` 或者不传 `smoke_time`,后端会认为你没有修改该字段。
## 7) 删除记录(软删除)
## 4) 删除记录(软删除)
`DELETE /api/v1/smoke/logs/:id`
@@ -194,157 +119,53 @@ curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
}
```
## 8) 获取 AI 戒烟建议(会员 + 广告解锁并行
`GET /api/v1/smoke/ai/advice?date=2026-01-02`
说明:
- `date` 可选,默认“昨天”(建议针对哪一天的数据)。
- 权限:会员用户直接可用;非会员需要先对该 `date` 完成“看广告解锁”(见下一个接口)。
- 建议结果会按 `uid + date + prompt_version` 缓存(表:`fa_smoke_ai_advice`)。
## 9) 首页整合接口(Home Dashboard
## 5) 首页整合接口(Home
`GET /api/v1/smoke/home`
此接口把首页 UI 所需核心模块一次返回,避免前端串行请求多个接口。返回示例:
此接口只返回首页、AI 时间页和 AI 日总结页正在消费的核心字段,避免生成或传递无用模块。返回示例:
```json
{
"code": 200,
"message": "success",
"data": {
"greeting": {
"title": "早安,Alex",
"subtitle": "今天也是清爽的一天",
"nickname": "Alex",
"time_of_day": "morning",
"avatar_url": "https://example.com/avatar.jpg"
},
"profile": {
"exists": true,
"is_completed": true,
"awake_minutes": 900,
"baseline_interval_minutes": 60,
"profile": {
"baseline_cigs_per_day": 10,
"pack_price_cent": 3200,
"wake_up_time": "07:20",
"sleep_time": "23:30"
}
},
"advice_card": {
"title": "智能控烟建议",
"date": "2026-01-04",
"message": "根据你的习惯,下午2点是烟瘾高峰,可以试试短暂散步。",
"status": "available"
},
"campaign_card": {
"title": "绿色生活,从戒烟开始",
"subtitle": "BRAND CAMPAIGN",
"badge": "广告"
},
"timer": {
"label": "距上次抽烟",
"last_smoke_at": "2026-01-05T07:42:00+08:00",
"seconds_since_last": 9900,
"next_suggested_at": "2026-01-05T10:30:00+08:00",
"next_suggested_clock": "10:30",
"not_before_at": "2026-01-05T10:30:00+08:00",
"suggestion_source": "default",
"suggestion_algorithm": "staircase_delay_v1"
"suggestion_source": "default"
},
"summary": {
"today_count": 3,
"daily_target": 10,
"resisted_count": 1,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false,
"profile_completed": true
"exceeded_yesterday": false
},
"motivation": {
"message": "太棒了!你刚刚成功抵抗了一次烟瘾",
"type": "praise"
},
"quick_actions": [
{ "type": "log_smoke", "title": "记录抽烟", "primary": false },
{ "type": "resist", "title": "想抽忍住了", "primary": true }
],
"data_sources": {
"ai_advice_date": "2026-01-04",
"plan_date": "2026-01-05"
}
}
}
```
字段说明:
- `greeting.title`:问候语 + 昵称(如“下午好,Alex”)。
- `greeting.subtitle`:副标题/心情提示文案。
- `greeting.nickname`:昵称(无昵称时使用“朋友”)。
- `greeting.time_of_day`:时间段标识(`morning`/`noon`/`afternoon`/`evening`)。
- `greeting.avatar_url`:头像 URL。
- `profile.exists`:是否存在用户档案。
- `profile.profile`:档案详情对象(可能为空)。
- `profile.is_completed`:是否已完成 onboarding。
- `profile.awake_minutes`:清醒时长(分钟)。
- `profile.baseline_interval_minutes`:基准间隔(分钟)。
- `advice_card.title`AI 提示卡片标题。
- `advice_card.date`:建议对应日期。
- `advice_card.message`AI 建议内容。
- `advice_card.status``available``locked`(需解锁)、`unavailable`AI 服务未配置)、`no_data`(所需日期没有记录)、`empty`(初始化)。
- `advice_card.model`:模型名称(有则返回)。
- `campaign_card.title`:活动标题。
- `campaign_card.subtitle`:活动副标题。
- `campaign_card.badge`:活动角标(如“广告”)。
- `timer.label`:展示标题(如“距上次抽烟”)。
- `timer.last_smoke_at`:最近一次实际抽烟时间(RFC3339)。
- `timer.seconds_since_last`:距上次抽烟的秒数(无记录返回 `-1`)。
- `timer.next_suggested_at`:建议下次抽烟时间(RFC3339)。
- `timer.next_suggested_clock`:仅时分显示(如“16:30”)。
- `timer.not_before_at`:不早于的时间点(当前与 `next_suggested_at` 一致)。
- `timer.suggestion_source`:建议来源(`default`/`ai`)。
- `timer.suggestion_algorithm`:算法版本(`staircase_delay_v1`)。
- `timer` 说明:`seconds_since_last` 基于服务器当前时间计算,`last_smoke_at` 若补录未来时间会截断到 `as_of`;当 `plan_date=今天` 时会补齐过期间隔确保 `next_suggested_at` 在未来。
- `summary.today_count`:今日吸烟支数累加。
- `summary.daily_target`:每日目标(线性递减:以 `onboarding_completed_at` 为起点,按 `quit_date` 线性下降到 0
- `summary.daily_target`:每日目标。
- `summary.resisted_count`:今日忍住次数。
- `summary.reduced_from_yesterday`:与昨日的绝对差值(非负)。
- `summary.exceeded_yesterday`:是否比昨天多。
- `summary.profile_completed`:是否已完成基础信息
- `daily_summary`:当天已缓存的 AI 日总结;无缓存时为 `null`
- `motivation.message`:激励语文案。
- `motivation.type`:激励语类型。
- `quick_actions[].type`:动作类型(`log_smoke`/`resist`)。
- `quick_actions[].title`:按钮文案。
- `quick_actions[].primary`:是否主按钮。
- `data_sources.ai_advice_date`AI 建议日期。
- `data_sources.plan_date`:当前计划日期。
如需 AI 时间节点完整版,可继续调用 `GET /ai/next_smoke_time`;首页接口只返回默认建议,避免额外的 AI 生成成本。
未满足权限时的建议响应(示例):
```json
{
"code": 403,
"message": "需要会员或观看广告解锁后才可生成建议",
"data": {
"date": "2026-01-02",
"need": "vip_or_ad"
}
}
```
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"date": "2026-01-02",
"advice": "..."
}
}
```
如需生成 AI 时间节点,请调用 `GET /api/v1/smoke/ai/next_smoke_time`;首页接口只读取缓存,不主动生成 AI 建议,避免额外性能成本。
## 10) 看广告解锁(用于非会员)
@@ -451,33 +272,13 @@ curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
成功响应:同 `GET /api/v1/smoke/profile`(返回最新 `profile` + `is_completed` + `baseline_interval_minutes`)。
## 13) 想抽但忍住了(写入一条 level=0,num=0 的记录)
## 13) 获取 AI 下次抽烟建议
`POST /api/v1/smoke/logs/resisted`
请求体(示例):
```json
{
"smoke_time": "2026-01-05",
"smoke_at": "2026-01-05 10:20:00",
"remark": "压力大,想抽但忍住了",
"level": 0,
"num": 0
}
```
`GET /api/v1/smoke/ai/next_smoke_time`
说明:
- 该接口会在 `fa_smoke_log` 中新增一条记录:`level=0``num=0`,用于更直观记录“想抽/忍住”的过程
- 这类记录不会影响 `today_count/weekly.count` 的支数统计(因为 `num=0`
## 14) 获取“下次抽烟记录时间”(默认 + AI 自动切换)
`GET /api/v1/smoke/next_smoke_time`
说明:
- 用于首页展示“建议的下次记录时间”。
- 已整合首页所需汇总字段(上次抽烟时间/今日抽烟支数/今日克制次数/较昨日减少支数)。
- 如果指定日期存在 AI 给出的时间节点(`time_nodes` 不为空),则优先使用 AI 的建议;否则使用默认策略。
- 用于 AI 建议页生成当天时间节点
- 首页只通过 `GET /smoke/home` 读取已缓存的 AI 结果,不主动生成 AI,避免首页加载时产生额外性能成本
- 可选参数:
- `date`:计划日期(默认今天),支持 `YYYY-MM-DD``today/tomorrow`
- `mode`(默认 `auto`
@@ -495,74 +296,21 @@ AI 生成说明:
-`mode=ai` 时,会把最近 3 天的抽烟数据(含“忍住记录”)作为输入提供给 AI,用于更贴合近期模式生成时间节点。
- 未解锁时会返回 `403`:提示需要观看广告解锁。
成功响应(示例:回落到默认):
```json
{
"code": 200,
"message": "success",
"data": {
"source": "default",
"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-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-05T10:00:00+08:00"
}
}
}
```
当存在 AI 建议且包含 `time_nodes` 时,响应会是(示例):
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"source": "ai",
"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-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",
"model": "gpt-4.1-mini",
"provider": "openai-compatible"
}
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。"
}
}
```
字段说明(新增首页字段):
- `last_smoke_at`:上次“实际抽烟”时间(忽略忍住记录),格式 `RFC3339`(含时区)。
- `today_count`:今日抽烟支数(累加 `num`)。
- `resisted_count`:今日克制次数(`num=0`)。
- `reduced_from_yesterday`:较昨日减少的支数(允许为负数;为负时表示“今天超出昨日”)。
- `exceeded_yesterday`:是否超出昨日(`true` 表示今天超出昨日,前端可用作单独标识)。
## 15) 数据统计分析(趋势 + 健康 + 省钱)
## 14) 数据统计分析(趋势 + 健康 + 省钱)
`GET /api/v1/smoke/stats?range=week|month|year&date=2026-01-07`
@@ -624,22 +372,3 @@ AI 生成说明:
- `money.expected_total`:按“统计周期内有记录的天数”×`baseline_cigs_per_day` 计算;不统计无日志的天数。
- `money.saved_cent`:按 `max(expected_total - actual_total, 0)` 计算,避免出现负值。
- `health.available=false`:表示无历史记录。
## 16) 激励语(后端统一生成)
`GET /api/v1/smoke/motivation`
说明:
- 基于当日数据(如 `today_count``resisted_count``last_smoke_at`)与 `quit_motivations` 生成一句激励语。
成功响应(示例):
```json
{
"code": 200,
"message": "success",
"data": {
"message": "今天的表现很稳,继续保持!记住你的目标:身体健康。",
"type": "encourage"
}
}
```