init
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
# 戒烟算法与 AI 策略说明
|
||||
|
||||
## 1. 核心理念
|
||||
|
||||
采用**渐进式递减**策略,而非突然戒断:
|
||||
- 科学研究表明,逐步减少比冷火鸡戒断成功率更高
|
||||
- 通过延长抽烟间隔,让身体逐渐适应尼古丁减少
|
||||
- AI 分析个人模式,提供个性化的递减计划
|
||||
|
||||
---
|
||||
|
||||
## 2. 默认递减算法 (staircase_delay_v1)
|
||||
|
||||
### 2.1 算法概述
|
||||
|
||||
```
|
||||
下次建议时间 = 上次抽烟时间 + 基础间隔 + 奖励间隔
|
||||
```
|
||||
|
||||
### 2.2 参数说明
|
||||
|
||||
| 参数 | 来源 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| base_interval | profile.baseline_interval_minutes | 60 分钟 | 用户初始平均抽烟间隔 |
|
||||
| resisted_7d | 近7天忍住次数 | 0 | level=0,num=0 的记录数 |
|
||||
| bonus_interval | 计算得出 | 0 | 奖励延长时间 |
|
||||
|
||||
### 2.3 奖励机制
|
||||
|
||||
每累计 **5 次忍住**,基础间隔 **+5 分钟**,最多 **+60 分钟**:
|
||||
|
||||
```
|
||||
bonus_interval = min(floor(resisted_7d / 5) * 5, 60)
|
||||
final_interval = base_interval + bonus_interval
|
||||
```
|
||||
|
||||
**示例**:
|
||||
- 用户基础间隔 48 分钟,近 7 天忍住 12 次
|
||||
- bonus = floor(12/5) * 5 = 10 分钟
|
||||
- 最终间隔 = 48 + 10 = 58 分钟
|
||||
|
||||
### 2.4 睡眠规避
|
||||
|
||||
若计算出的时间落在睡眠区间,顺延到次日起床时间:
|
||||
|
||||
```
|
||||
if suggested_time in [sleep_time, wake_up_time]:
|
||||
suggested_time = next_day_wake_up_time
|
||||
```
|
||||
|
||||
### 2.5 算法流程图
|
||||
|
||||
```
|
||||
获取上次抽烟时间 (last_smoke_at)
|
||||
↓
|
||||
获取用户基础间隔 (base_interval_minutes)
|
||||
↓
|
||||
统计近7天忍住次数 (resisted_7d)
|
||||
↓
|
||||
计算奖励间隔: bonus = min(floor(resisted_7d / 5) * 5, 60)
|
||||
↓
|
||||
计算建议时间: suggested = last_smoke_at + base + bonus
|
||||
↓
|
||||
检查是否在睡眠时间?
|
||||
├── 是 → 顺延到起床时间
|
||||
└── 否 → 返回建议时间
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. AI 增强算法
|
||||
|
||||
### 3.1 AI 时间节点规划
|
||||
|
||||
当用户解锁 AI 功能后,系统会:
|
||||
|
||||
1. **收集近 3 天数据**:
|
||||
- 每次抽烟的时间点
|
||||
- 每次忍住的时间点
|
||||
- 抽烟原因/场景标签
|
||||
|
||||
2. **分析抽烟模式**:
|
||||
- 高峰时段识别(如下午 2-4 点)
|
||||
- 触发场景识别(如压力、无聊、社交)
|
||||
- 间隔规律分析
|
||||
|
||||
3. **生成个性化时间节点**:
|
||||
- 避开高峰时段的前半小时
|
||||
- 在用户通常能忍住的时段设置节点
|
||||
- 逐日递增间隔
|
||||
|
||||
### 3.2 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点是你的高峰期,准备一颗薄荷糖", "试着用深呼吸替代"]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 AI Prompt 设计
|
||||
|
||||
```
|
||||
你是一位专业的戒烟辅导教练。基于用户的抽烟数据,提供个性化的戒烟建议。
|
||||
|
||||
用户档案:
|
||||
- 日均吸烟量:{baseline_cigs_per_day} 支
|
||||
- 烟龄:{smoking_years} 年
|
||||
- 抽烟动机:{smoke_motivations}
|
||||
- 戒烟动力:{quit_motivations}
|
||||
- 作息:{wake_up_time} - {sleep_time}
|
||||
|
||||
近3天数据:
|
||||
{recent_logs}
|
||||
|
||||
请分析:
|
||||
1. 用户的抽烟规律和高峰时段
|
||||
2. 主要的触发场景
|
||||
3. 成功忍住的模式
|
||||
|
||||
然后生成:
|
||||
1. 一段鼓励性的分析总结(2-3句话)
|
||||
2. 明天的建议抽烟时间节点(比今天少1支)
|
||||
3. 2-3条实用的应对建议
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 阶段划分
|
||||
|
||||
### 4.1 三阶段戒烟计划
|
||||
|
||||
| 阶段 | 时间 | 目标 | 策略 |
|
||||
|------|------|------|------|
|
||||
| 记录期 | Day 1-7 | 建立基线 | 正常抽烟,但每次都记录 |
|
||||
| 减量期 | Day 8-21 | 减少 50% | 每周目标递减,AI 指导 |
|
||||
| 巩固期 | Day 22-30 | 维持/归零 | 强化抵抗,心理建设 |
|
||||
|
||||
### 4.2 阶段进度计算
|
||||
|
||||
```javascript
|
||||
// utils/stage.js
|
||||
function calculateStage(startDate) {
|
||||
const daysSinceStart = daysBetween(startDate, new Date())
|
||||
|
||||
if (daysSinceStart <= 7) {
|
||||
return {
|
||||
stage: 1,
|
||||
name: '记录期',
|
||||
progress: daysSinceStart / 7,
|
||||
daysLeft: 7 - daysSinceStart
|
||||
}
|
||||
} else if (daysSinceStart <= 21) {
|
||||
return {
|
||||
stage: 2,
|
||||
name: '减量期',
|
||||
progress: (daysSinceStart - 7) / 14,
|
||||
daysLeft: 21 - daysSinceStart
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
stage: 3,
|
||||
name: '巩固期',
|
||||
progress: Math.min((daysSinceStart - 21) / 9, 1),
|
||||
daysLeft: Math.max(30 - daysSinceStart, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 每日目标计算
|
||||
|
||||
```javascript
|
||||
// utils/target.js
|
||||
function calculateDailyTarget(baseline, stage, dayInStage) {
|
||||
if (stage === 1) {
|
||||
return baseline
|
||||
}
|
||||
|
||||
if (stage === 2) {
|
||||
const reduction = (dayInStage / 14) * 0.5
|
||||
return Math.max(Math.round(baseline * (1 - reduction)), 1)
|
||||
}
|
||||
|
||||
if (stage === 3) {
|
||||
const targetRate = 0.25 - (dayInStage / 9) * 0.25
|
||||
return Math.max(Math.round(baseline * targetRate), 0)
|
||||
}
|
||||
|
||||
return baseline
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 健康恢复计算
|
||||
|
||||
基于医学研究的恢复时间线:
|
||||
|
||||
| 时间点 | 恢复指标 | 计算方式 |
|
||||
|--------|----------|----------|
|
||||
| 20分钟 | 心率血压恢复正常 | 固定 |
|
||||
| 8小时 | 血氧水平恢复 | 固定 |
|
||||
| 24小时 | 心脏病风险开始下降 | 固定 |
|
||||
| 48小时 | 嗅觉味觉开始恢复 | 固定 |
|
||||
| 2周 | 肺功能提升 15% | 线性计算 |
|
||||
| 1月 | 肺功能提升 30% | 线性计算 |
|
||||
| 3月 | 肺功能提升 50% | 线性计算 |
|
||||
| 1年 | 心脏病风险降低 50% | 线性计算 |
|
||||
|
||||
```javascript
|
||||
// utils/health.js
|
||||
function calculateLungRecovery(smokeFreeMinutes) {
|
||||
const days = smokeFreeMinutes / (24 * 60)
|
||||
|
||||
if (days < 14) {
|
||||
return (days / 14) * 15
|
||||
} else if (days < 30) {
|
||||
return 15 + ((days - 14) / 16) * 15
|
||||
} else if (days < 90) {
|
||||
return 30 + ((days - 30) / 60) * 20
|
||||
} else {
|
||||
return Math.min(50 + ((days - 90) / 275) * 50, 100)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 省钱计算
|
||||
|
||||
```javascript
|
||||
// utils/money.js
|
||||
function calculateMoneySaved(packPriceCent, cigsPerPack, baselineCigsPerDay, actualCigsTotal, days) {
|
||||
const expectedTotal = baselineCigsPerDay * days
|
||||
const savedCigs = expectedTotal - actualCigsTotal
|
||||
const savedPacks = savedCigs / cigsPerPack
|
||||
return Math.round(savedPacks * packPriceCent)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 激励语生成
|
||||
|
||||
根据用户状态生成不同的激励语:
|
||||
|
||||
```javascript
|
||||
// utils/motivation.js
|
||||
function getMotivationMessage(context) {
|
||||
const {
|
||||
minutesSinceLast,
|
||||
todayCount,
|
||||
dailyTarget,
|
||||
resistedToday,
|
||||
quitMotivations
|
||||
} = context
|
||||
|
||||
if (resistedToday > 0 && minutesSinceLast < 30) {
|
||||
return '太棒了!你刚刚成功抵抗了一次烟瘾'
|
||||
}
|
||||
|
||||
if (todayCount < dailyTarget * 0.5) {
|
||||
return '今天的表现非常出色,继续保持!'
|
||||
}
|
||||
|
||||
if (todayCount === dailyTarget - 1) {
|
||||
return '还剩最后一支配额,考虑把它留到睡前?'
|
||||
}
|
||||
|
||||
if (todayCount > dailyTarget) {
|
||||
return `没关系,明天是新的一天。记住你为什么要戒烟:${quitMotivations[0]}`
|
||||
}
|
||||
|
||||
return '保持连胜纪录!'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据分析指标
|
||||
|
||||
### 8.1 关键指标
|
||||
|
||||
| 指标 | 计算方式 | 用途 |
|
||||
|------|----------|------|
|
||||
| 日均吸烟量 | 周期内总量 / 天数 | 趋势对比 |
|
||||
| 周同比变化 | (本周 - 上周) / 上周 | 进度评估 |
|
||||
| 忍住成功率 | 忍住次数 / (忍住+抽烟次数) | 意志力评估 |
|
||||
| 平均间隔 | 总时长 / 抽烟次数 | 递减效果 |
|
||||
| 最长无烟时长 | 最大间隔记录 | 成就激励 |
|
||||
|
||||
### 8.2 周报数据结构
|
||||
|
||||
```javascript
|
||||
// 周报数据结构
|
||||
const weeklyReport = {
|
||||
period: { start: '2026-01-01', end: '2026-01-07' },
|
||||
totalCigs: 35,
|
||||
dailyAverage: 5,
|
||||
comparedToLastWeek: -20, // 百分比变化
|
||||
resistedCount: 12,
|
||||
longestGap: 180, // 分钟
|
||||
peakHours: ['14:00', '21:00'],
|
||||
topTriggers: ['压力大', '无聊'],
|
||||
achievements: ['连续7天记录', '单日忍住5次'],
|
||||
nextWeekTarget: 4
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,334 @@
|
||||
# 开发计划与任务拆分
|
||||
|
||||
## 1. 开发阶段概览
|
||||
|
||||
```
|
||||
Phase 1: 基础框架搭建 (2天)
|
||||
↓
|
||||
Phase 2: 首页核心功能 (3天) ★ 优先保证首页体验
|
||||
↓
|
||||
Phase 3: 记录与历史 (2天)
|
||||
↓
|
||||
Phase 4: 统计与图表 (2天)
|
||||
↓
|
||||
Phase 5: AI助手与个人中心 (3天)
|
||||
↓
|
||||
Phase 6: 优化与测试 (2天)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Phase 1: 基础框架搭建 (2天)
|
||||
|
||||
### 2.1 项目初始化
|
||||
- [ ] 创建 uni-app 项目 (Vue3 + JavaScript)
|
||||
- [ ] 配置路径别名 (@/)
|
||||
- [ ] 创建目录结构
|
||||
|
||||
### 2.2 基础配置
|
||||
- [ ] 配置 pages.json (页面路由 + TabBar)
|
||||
- [ ] 配置全局样式 (暗色主题变量)
|
||||
- [ ] 配置环境变量 (开发/生产 API 地址)
|
||||
|
||||
### 2.3 核心模块
|
||||
- [ ] 封装 request.js (请求拦截、Token管理、错误处理)
|
||||
- [ ] 封装 storage.js (本地存储工具)
|
||||
- [ ] 配置 Pinia stores 结构
|
||||
- [ ] 实现登录流程 (wx.login + 后端认证)
|
||||
|
||||
### 2.4 公共组件
|
||||
- [ ] 创建 Loading 组件
|
||||
- [ ] 创建 Skeleton 骨架屏组件
|
||||
- [ ] 创建 Button 组件 (主按钮/次按钮样式)
|
||||
- [ ] 创建 Card 组件 (卡片容器)
|
||||
|
||||
**交付物**:可运行的空白项目,登录功能正常
|
||||
|
||||
---
|
||||
|
||||
## 3. Phase 2: 首页核心功能 (3天) ★
|
||||
|
||||
> ⚠️ 首页是用户最常访问的页面,必须保证 < 500ms 首屏渲染
|
||||
|
||||
### 3.1 Day 1: 页面结构 + 数据层
|
||||
|
||||
#### API 封装
|
||||
- [ ] `api/smoke.js`: getDashboard()
|
||||
- [ ] `api/smoke.js`: getNextSmokeTime()
|
||||
- [ ] `api/profile.js`: getProfile()
|
||||
|
||||
#### Store 设计
|
||||
- [ ] `stores/dashboard.js`: 看板数据 + 缓存逻辑
|
||||
- [ ] `stores/user.js`: 用户信息 + 登录状态
|
||||
|
||||
#### 页面结构
|
||||
- [ ] 首页骨架屏
|
||||
- [ ] 页面布局 (header + timer + cards + buttons)
|
||||
|
||||
### 3.2 Day 2: 核心组件
|
||||
|
||||
#### 计时器组件 (TimerRing)
|
||||
- [ ] Canvas 绘制进度环
|
||||
- [ ] 时间格式化 (HH:MM:SS)
|
||||
- [ ] requestAnimationFrame 优化
|
||||
- [ ] 页面可见性处理 (切后台暂停)
|
||||
|
||||
#### 统计卡片
|
||||
- [ ] 今日已抽卡片 (X/目标, 较昨日±N)
|
||||
- [ ] 烟瘾发作已抵抗卡片
|
||||
|
||||
#### 快捷按钮
|
||||
- [ ] 记录抽烟按钮 → 弹出记录表单
|
||||
- [ ] 想抽忍住了按钮 → 快速提交
|
||||
|
||||
### 3.3 Day 3: 交互 + 优化
|
||||
|
||||
#### AI 提示卡片
|
||||
- [ ] 延迟加载 (300ms后)
|
||||
- [ ] 可关闭 (本地存储关闭状态)
|
||||
- [ ] 下次建议时间显示
|
||||
|
||||
#### 性能优化
|
||||
- [ ] 并行请求优化
|
||||
- [ ] 骨架屏过渡动画
|
||||
- [ ] 首屏性能埋点
|
||||
|
||||
#### 新用户引导
|
||||
- [ ] 检测 profile.exists
|
||||
- [ ] 跳转引导页逻辑
|
||||
|
||||
**交付物**:完整可用的首页,首屏 < 500ms
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 3: 记录与历史 (2天)
|
||||
|
||||
### 4.1 Day 1: 记录表单
|
||||
|
||||
#### 记录弹窗组件
|
||||
- [ ] 时间选择 (默认当前时间)
|
||||
- [ ] 原因标签选择 (压力大/无聊/社交/习惯等)
|
||||
- [ ] 备注输入
|
||||
- [ ] 支数选择 (默认1)
|
||||
|
||||
#### API 集成
|
||||
- [ ] `POST /logs` 新增记录
|
||||
- [ ] `POST /logs/resisted` 忍住记录
|
||||
- [ ] 提交后刷新首页数据
|
||||
|
||||
### 4.2 Day 2: 历史记录页
|
||||
|
||||
#### 列表页面
|
||||
- [ ] 筛选 Tabs (全部/已抽烟/已忍住)
|
||||
- [ ] 时间线布局
|
||||
- [ ] 按日期分组
|
||||
- [ ] 下拉刷新 + 上拉加载
|
||||
|
||||
#### 记录卡片
|
||||
- [ ] 类型图标 (抽烟/忍住)
|
||||
- [ ] 时间 + 原因标签
|
||||
- [ ] 间隔时间显示
|
||||
- [ ] 左滑操作 (编辑/删除)
|
||||
|
||||
#### 编辑/删除
|
||||
- [ ] 编辑弹窗
|
||||
- [ ] 删除确认
|
||||
- [ ] `PUT/DELETE /logs/:id`
|
||||
|
||||
**交付物**:完整的记录流程,历史记录页可用
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 4: 统计与图表 (2天)
|
||||
|
||||
### 5.1 Day 1: 统计页基础
|
||||
|
||||
#### 时间范围切换
|
||||
- [ ] 周/月/年 Tabs
|
||||
- [ ] 日期范围计算
|
||||
- [ ] 数据请求 (`GET /dashboard?start=&end=`)
|
||||
|
||||
#### 吸烟趋势图
|
||||
- [ ] 集成 uCharts / ECharts
|
||||
- [ ] 柱状图组件封装
|
||||
- [ ] 数据格式转换
|
||||
|
||||
#### 每周洞察卡片
|
||||
- [ ] AI 分析展示 (异步加载)
|
||||
|
||||
### 5.2 Day 2: 详细指标
|
||||
|
||||
#### 健康与储蓄卡片
|
||||
- [ ] 节省金额计算 + 环形进度
|
||||
- [ ] 肺部功能恢复 + 环形进度
|
||||
|
||||
#### 成就卡片
|
||||
- [ ] 连续记录天数
|
||||
- [ ] 已拒绝次数
|
||||
|
||||
#### 趋势对比
|
||||
- [ ] 周同比变化计算
|
||||
- [ ] 日均吸烟量
|
||||
|
||||
**交付物**:完整的统计页,图表正常显示
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 5: AI助手与个人中心 (3天)
|
||||
|
||||
### 6.1 Day 1: AI 助手页
|
||||
|
||||
#### 阶段进度卡片
|
||||
- [ ] 阶段计算逻辑
|
||||
- [ ] 进度条展示
|
||||
- [ ] 天数倒计时
|
||||
|
||||
#### 每日 AI 分析
|
||||
- [ ] 对话式 UI
|
||||
- [ ] `GET /ai/advice` 集成
|
||||
- [ ] 会员/广告解锁判断
|
||||
|
||||
#### 今日目标
|
||||
- [ ] 任务列表
|
||||
- [ ] 完成状态切换 (本地存储)
|
||||
|
||||
### 6.2 Day 2: 个人中心页
|
||||
|
||||
#### 用户信息
|
||||
- [ ] 头像 + 昵称展示
|
||||
- [ ] 目标戒烟日期
|
||||
- [ ] 连续天数
|
||||
|
||||
#### 设置项
|
||||
- [ ] 目标设定入口
|
||||
- [ ] AI 计划调整入口
|
||||
- [ ] 通知设置
|
||||
- [ ] 会员解锁
|
||||
|
||||
#### 基础设置
|
||||
- [ ] 作息时间设置
|
||||
- [ ] 每包价格设置
|
||||
|
||||
### 6.3 Day 3: 新用户引导
|
||||
|
||||
#### 引导页面
|
||||
- [ ] 分步表单 (5步)
|
||||
- [ ] 进度指示器
|
||||
- [ ] 动画过渡
|
||||
|
||||
#### 数据收集
|
||||
- [ ] 日均吸烟量
|
||||
- [ ] 烟龄
|
||||
- [ ] 抽烟/戒烟动机
|
||||
- [ ] 作息时间
|
||||
|
||||
#### 提交流程
|
||||
- [ ] `PUT /profile` 提交
|
||||
- [ ] 完成后跳转首页
|
||||
|
||||
**交付物**:AI助手页、个人中心页、引导流程完整
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase 6: 优化与测试 (2天)
|
||||
|
||||
### 7.1 Day 1: 性能优化
|
||||
|
||||
#### 首页优化
|
||||
- [ ] 首屏时间 < 500ms 验证
|
||||
- [ ] 图片懒加载
|
||||
- [ ] 组件按需加载
|
||||
|
||||
#### 缓存优化
|
||||
- [ ] 请求缓存策略检查
|
||||
- [ ] 本地存储清理策略
|
||||
|
||||
#### 包体积
|
||||
- [ ] 依赖分析
|
||||
- [ ] 无用代码移除
|
||||
- [ ] 分包加载配置
|
||||
|
||||
### 7.2 Day 2: 测试与修复
|
||||
|
||||
#### 功能测试
|
||||
- [ ] 全流程测试 (新用户 → 日常使用)
|
||||
- [ ] 边界情况测试
|
||||
- [ ] 网络异常测试
|
||||
|
||||
#### UI 适配
|
||||
- [ ] 不同机型测试
|
||||
- [ ] 安全区域适配
|
||||
|
||||
#### Bug 修复
|
||||
- [ ] 测试问题修复
|
||||
- [ ] 体验优化
|
||||
|
||||
**交付物**:可发布的小程序版本
|
||||
|
||||
---
|
||||
|
||||
## 8. 开发优先级
|
||||
|
||||
按重要性和依赖关系排序:
|
||||
|
||||
```
|
||||
P0 (必须完成):
|
||||
├── 登录认证
|
||||
├── 首页看板 (计时器 + 今日统计)
|
||||
├── 记录抽烟/忍住
|
||||
├── 历史记录查看
|
||||
└── 新用户引导
|
||||
|
||||
P1 (核心功能):
|
||||
├── 统计图表 (周视图)
|
||||
├── 下次建议时间
|
||||
├── 个人设置
|
||||
└── 基础 AI 建议
|
||||
|
||||
P2 (增强功能):
|
||||
├── 月/年统计
|
||||
├── AI 时间节点
|
||||
├── 会员/广告解锁
|
||||
└── 通知提醒
|
||||
|
||||
P3 (未来迭代):
|
||||
├── 成就系统
|
||||
├── 社交分享
|
||||
└── 数据导出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 技术风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对措施 |
|
||||
|------|------|----------|
|
||||
| 首页加载慢 | 用户体验差 | 骨架屏 + 并行请求 + 缓存 |
|
||||
| 图表库体积大 | 包体积超标 | 按需引入 + 分包 |
|
||||
| AI 接口慢 | 等待时间长 | 异步加载 + Loading状态 |
|
||||
| 网络不稳定 | 数据丢失 | 离线缓存 + 重试机制 |
|
||||
| 微信审核不通过 | 延期上线 | 提前了解审核规范 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 验收标准
|
||||
|
||||
### 首页
|
||||
- [ ] 首屏渲染 < 500ms
|
||||
- [ ] 计时器实时更新
|
||||
- [ ] 记录操作 < 3步完成
|
||||
|
||||
### 记录
|
||||
- [ ] 支持快速记录
|
||||
- [ ] 支持编辑/删除
|
||||
- [ ] 数据实时同步
|
||||
|
||||
### 统计
|
||||
- [ ] 图表正常显示
|
||||
- [ ] 数据计算准确
|
||||
- [ ] 切换流畅
|
||||
|
||||
### 整体
|
||||
- [ ] 无明显卡顿
|
||||
- [ ] 无崩溃闪退
|
||||
- [ ] 视觉符合设计稿
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
# 戒烟小程序 - 产品需求文档 (PRD)
|
||||
|
||||
## 1. 产品概述
|
||||
|
||||
### 1.1 产品定位
|
||||
一款基于 AI 辅助的科学戒烟小程序,通过记录抽烟行为、数据分析、智能递减计划帮助用户逐步减少吸烟量,最终实现戒烟目标。
|
||||
|
||||
### 1.2 核心价值
|
||||
- **记录追踪**:精确记录每次抽烟/忍住行为
|
||||
- **数据洞察**:可视化展示吸烟趋势与规律
|
||||
- **AI 指导**:个性化递减建议与时间节点规划
|
||||
- **正向激励**:省钱计算、健康恢复、成就系统
|
||||
|
||||
### 1.3 目标用户
|
||||
- 有戒烟意愿的吸烟者
|
||||
- 想要减少吸烟量的用户
|
||||
- 需要科学方法辅助戒烟的人群
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能模块
|
||||
|
||||
### 2.1 首页 (home_dashboard)
|
||||
|
||||
**核心目标**:快速记录 + 实时激励
|
||||
|
||||
| 元素 | 说明 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| 问候语 | 根据时段显示(早上好/下午好等) + 用户昵称 | 本地计算 + profile |
|
||||
| AI 提示卡片 | 发现的抽烟规律/建议(可关闭) | `GET /ai/advice` 缓存 |
|
||||
| 计时环 | 距上次抽烟时间(时:分:秒) | `dashboard.minutes_since_last` |
|
||||
| 下次建议时间 | 显示建议的下次抽烟时间点 | `GET /next_smoke_time` |
|
||||
| 今日已抽 | X / 目标数,较昨日 ±N | `dashboard.today_count` |
|
||||
| 烟瘾发作已抵抗 | 忍住次数统计 | 筛选 `level=0,num=0` 记录 |
|
||||
| 记录抽烟按钮 | 快速记录一次抽烟 | `POST /logs` |
|
||||
| 想抽忍住了按钮 | 记录成功抵抗 | `POST /logs/resisted` |
|
||||
|
||||
**性能要求**:
|
||||
- 首屏渲染 < 500ms
|
||||
- 关键数据(计时环)优先加载
|
||||
- 非关键数据(AI提示)异步延迟加载
|
||||
|
||||
### 2.2 统计页 (smoking_statistics)
|
||||
|
||||
**核心目标**:数据可视化 + 趋势分析
|
||||
|
||||
| 功能 | 说明 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| 周/月/年切换 | 切换统计时间范围 | `GET /dashboard?start=&end=` |
|
||||
| 每周洞察 | AI 分析本周表现 | `GET /ai/advice` |
|
||||
| 吸烟趋势图 | 柱状图展示每日吸烟量 | `dashboard.weekly` |
|
||||
| 趋势对比 | 较上周减少 X% | 本地计算 |
|
||||
| 日均吸烟量 | 统计周期内日均值 | 本地计算 |
|
||||
| 节省金额 | 基于减少量 × 单价计算 | profile + logs |
|
||||
| 肺部功能恢复 | 根据戒烟天数估算 | 固定公式 |
|
||||
| 连续记录天数 | 用户活跃天数 | logs 统计 |
|
||||
| 已拒绝次数 | 累计忍住次数 | `level=0,num=0` 统计 |
|
||||
|
||||
### 2.3 AI 助手页 (ai_quit_assistant)
|
||||
|
||||
**核心目标**:个性化指导 + 阶段管理
|
||||
|
||||
| 功能 | 说明 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| 减量计划卡片 | 当前阶段(第X/30天)、阶段名称、进度 | profile + 本地计算 |
|
||||
| 每日 AI 分析 | 对话式展示昨日分析与今日建议 | `GET /ai/advice` |
|
||||
| 今日目标 | 任务清单(喝水、散步、阅读激励等) | AI 生成 + 本地存储 |
|
||||
| 记录按钮 | 快速记录吸烟或烟瘾 | 跳转记录流程 |
|
||||
|
||||
**阶段划分**:
|
||||
1. 阶段1 - 记录期 (Day 1-7):建立基线数据
|
||||
2. 阶段2 - 减量期 (Day 8-21):逐步减少吸烟量
|
||||
3. 阶段3 - 巩固期 (Day 22-30):维持低量/零吸烟
|
||||
|
||||
### 2.4 历史记录页 (activity_history)
|
||||
|
||||
**核心目标**:记录管理 + 行为回顾
|
||||
|
||||
| 功能 | 说明 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| 筛选 Tabs | 全部 / 已抽烟 / 已忍住 | 前端筛选 |
|
||||
| 时间线 | 按日期分组展示 | `GET /logs` |
|
||||
| 记录卡片 | 类型、时间、原因标签、间隔时间 | logs 数据 |
|
||||
| 左滑操作 | 编辑 / 删除 | `PUT/DELETE /logs/:id` |
|
||||
| 新增按钮 | 浮动按钮快速新增 | 跳转记录流程 |
|
||||
|
||||
### 2.5 个人中心 (profile_&_settings)
|
||||
|
||||
**核心目标**:用户信息 + 设置管理
|
||||
|
||||
| 功能 | 说明 | 数据来源 |
|
||||
|------|------|----------|
|
||||
| 用户信息 | 头像、昵称 | 微信授权 |
|
||||
| 目标展示 | 目标戒烟日期、连续天数 | profile |
|
||||
| 目标设定 | 调整每日限额与戒烟日期 | `PUT /profile` |
|
||||
| AI 计划调整 | 个性化辅导风格设置 | profile 扩展 |
|
||||
| 通知设置 | 提醒时间、频率 | 本地存储 |
|
||||
| 会员解锁 | PRO 功能 / 广告解锁 | 会员系统 |
|
||||
| 基础设置 | 作息时间等 | `PUT /profile` |
|
||||
| 隐私与数据 | 数据导出、账号注销 | 待扩展 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 用户流程
|
||||
|
||||
### 3.1 新用户引导流程
|
||||
|
||||
```
|
||||
启动小程序
|
||||
↓
|
||||
微信登录 (wx.login)
|
||||
↓
|
||||
检查 profile (GET /profile)
|
||||
↓
|
||||
exists=false ? → 进入引导页
|
||||
↓
|
||||
Step 1: 每日吸烟量 (baseline_cigs_per_day)
|
||||
↓
|
||||
Step 2: 烟龄 (smoking_years)
|
||||
↓
|
||||
Step 3: 吸烟动机 (smoke_motivations)
|
||||
↓
|
||||
Step 4: 戒烟动力 (quit_motivations)
|
||||
↓
|
||||
Step 5: 作息时间 (wake_up_time, sleep_time)
|
||||
↓
|
||||
Step 6: 设置目标 (目标日期、每日限额)
|
||||
↓
|
||||
提交 profile (PUT /profile)
|
||||
↓
|
||||
进入首页
|
||||
```
|
||||
|
||||
### 3.2 日常使用流程
|
||||
|
||||
```
|
||||
打开首页
|
||||
↓
|
||||
查看计时器 + 下次建议时间
|
||||
↓
|
||||
[想抽烟时]
|
||||
├── 还没到建议时间 → 点击"想抽忍住了"
|
||||
└── 到了/忍不住 → 点击"记录抽烟"
|
||||
↓
|
||||
记录表单 (可选填原因)
|
||||
↓
|
||||
提交成功 → 更新首页数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
### 4.1 用户档案 (profile)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| baseline_cigs_per_day | int | 基准日吸烟量 |
|
||||
| smoking_years | int | 烟龄(年) |
|
||||
| pack_price_cent | int | 单包价格(分) |
|
||||
| smoke_motivations | []string | 抽烟动机 |
|
||||
| quit_motivations | []string | 戒烟动力 |
|
||||
| wake_up_time | string | 起床时间 HH:MM |
|
||||
| sleep_time | string | 入睡时间 HH:MM |
|
||||
| daily_target | int | 每日目标限额(扩展) |
|
||||
| quit_date | date | 目标戒烟日期(扩展) |
|
||||
|
||||
### 4.2 抽烟记录 (log)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| smoke_time | date | 记录日期 |
|
||||
| smoke_at | datetime | 实际抽烟时间 |
|
||||
| level | int | 烟瘾等级 0-5 (0=忍住) |
|
||||
| num | int | 支数 (0=忍住) |
|
||||
| remark | string | 原因备注 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 性能优化策略
|
||||
|
||||
### 5.1 首页加载优化
|
||||
|
||||
**目标**:首屏 < 500ms
|
||||
|
||||
```
|
||||
[并行请求]
|
||||
├── GET /profile (用户信息,判断是否需引导)
|
||||
├── GET /dashboard (今日统计,计时器数据)
|
||||
└── GET /next_smoke_time (下次建议时间)
|
||||
|
||||
[延迟加载]
|
||||
└── GET /ai/advice (AI提示卡片,非关键)
|
||||
```
|
||||
|
||||
**缓存策略**:
|
||||
- profile: 登录后缓存,变更时更新
|
||||
- dashboard: 每次进入刷新,后台定时更新
|
||||
- next_smoke_time: 缓存至下次记录
|
||||
- ai/advice: 按天缓存
|
||||
|
||||
### 5.2 数据预加载
|
||||
|
||||
- 首页加载时预取统计页首屏数据
|
||||
- TabBar 切换时使用缓存 + 后台刷新
|
||||
|
||||
---
|
||||
|
||||
## 6. 权限与会员
|
||||
|
||||
### 6.1 免费功能
|
||||
- 记录抽烟/忍住
|
||||
- 基础统计(周视图)
|
||||
- 首页计时器
|
||||
- 基础递减算法
|
||||
|
||||
### 6.2 会员/广告解锁功能
|
||||
- AI 每日建议
|
||||
- AI 时间节点规划
|
||||
- 月/年统计视图
|
||||
- 数据导出
|
||||
|
||||
---
|
||||
|
||||
## 7. 待扩展功能
|
||||
|
||||
- [ ] 成就系统 (连续X天、累计忍住X次等)
|
||||
- [ ] 社交分享 (戒烟打卡海报)
|
||||
- [ ] 提醒推送 (到达建议时间提醒)
|
||||
- [ ] 健康知识卡片
|
||||
- [ ] 紧急求助 (烟瘾强烈时的快速干预)
|
||||
+485
@@ -0,0 +1,485 @@
|
||||
# 技术实现方案
|
||||
|
||||
## 1. 技术栈
|
||||
|
||||
| 层级 | 技术选型 | 说明 |
|
||||
|------|----------|------|
|
||||
| 框架 | uni-app (Vue3 + JavaScript) | 跨平台小程序开发 |
|
||||
| 状态管理 | Pinia | 轻量级状态管理 |
|
||||
| 请求 | uni.request 封装 | 统一拦截、Token管理 |
|
||||
| 图表 | uCharts | 轻量级数据可视化 |
|
||||
| UI组件 | 自定义组件 | 符合设计规范 |
|
||||
| 存储 | uni.storage | 本地缓存 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 项目结构
|
||||
|
||||
```
|
||||
├── pages/ # 页面
|
||||
│ ├── index/ # 首页
|
||||
│ ├── stats/ # 统计
|
||||
│ ├── ai/ # AI助手
|
||||
│ ├── logs/ # 历史记录
|
||||
│ ├── profile/ # 个人中心
|
||||
│ └── onboarding/ # 新用户引导
|
||||
├── components/ # 组件
|
||||
│ ├── common/ # 通用组件
|
||||
│ ├── charts/ # 图表组件
|
||||
│ └── business/ # 业务组件
|
||||
├── stores/ # Pinia stores
|
||||
│ ├── user.js # 用户状态
|
||||
│ ├── smoke.js # 抽烟记录状态
|
||||
│ └── dashboard.js # 首页看板状态
|
||||
├── api/ # API封装
|
||||
│ ├── request.js # 请求基类
|
||||
│ ├── auth.js # 认证接口
|
||||
│ ├── smoke.js # 抽烟记录接口
|
||||
│ └── profile.js # 用户档案接口
|
||||
├── utils/ # 工具函数
|
||||
│ ├── time.js # 时间处理
|
||||
│ ├── storage.js # 存储封装
|
||||
│ └── format.js # 格式化
|
||||
├── hooks/ # 组合式函数
|
||||
│ ├── useTimer.js # 计时器逻辑
|
||||
│ ├── useDashboard.js # 看板数据
|
||||
│ └── useAuth.js # 认证逻辑
|
||||
└── static/ # 静态资源
|
||||
└── icons/ # TabBar图标
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 首页性能优化方案
|
||||
|
||||
### 3.1 加载策略
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 首页加载时序 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 0ms ─ 页面骨架屏渲染 │
|
||||
│ │ │
|
||||
│ ├──── 并行请求 ──────────────────────────────────── │
|
||||
│ │ ├── /profile (检查用户状态) │
|
||||
│ │ ├── /dashboard (核心数据) │
|
||||
│ │ └── /next_smoke_time (建议时间) │
|
||||
│ │ │
|
||||
│ 200ms ─ 核心数据返回,渲染计时器+统计卡片 │
|
||||
│ │ │
|
||||
│ 300ms ─ 首屏渲染完成 │
|
||||
│ │ │
|
||||
│ │ ┌── 延迟加载 ────────────────────────────── │
|
||||
│ │ └── /ai/advice (AI提示卡片) │
|
||||
│ │ │
|
||||
│ 500ms ─ 完整页面渲染 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 缓存策略
|
||||
|
||||
```javascript
|
||||
// stores/dashboard.js
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
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
|
||||
}
|
||||
// 发起请求...
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 3.3 计时器优化
|
||||
|
||||
```javascript
|
||||
// hooks/useTimer.js
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export function useTimer(minutesSinceLast) {
|
||||
const displayTime = ref('00:00:00')
|
||||
let rafId = null
|
||||
let lastTimestamp = 0
|
||||
|
||||
function tick(timestamp) {
|
||||
if (timestamp - lastTimestamp >= 1000) {
|
||||
lastTimestamp = timestamp
|
||||
updateDisplay()
|
||||
}
|
||||
rafId = requestAnimationFrame(tick)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
rafId = requestAnimationFrame(tick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(rafId)
|
||||
})
|
||||
|
||||
return { displayTime }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 骨架屏
|
||||
|
||||
首页使用骨架屏避免白屏:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view v-if="loading" class="skeleton">
|
||||
<view class="skeleton-header" />
|
||||
<view class="skeleton-timer" />
|
||||
<view class="skeleton-cards" />
|
||||
</view>
|
||||
<view v-else class="dashboard">
|
||||
<!-- 真实内容 -->
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心模块实现
|
||||
|
||||
### 4.1 认证模块
|
||||
|
||||
```javascript
|
||||
// api/auth.js
|
||||
import { request } from './request'
|
||||
import { MINI_PROGRAM_ID } from '@/config'
|
||||
|
||||
export async function login() {
|
||||
const [err, loginRes] = await uni.login({ provider: 'weixin' })
|
||||
if (err) throw err
|
||||
|
||||
const res = await request.post('/auth/login', {
|
||||
mini_program_id: MINI_PROGRAM_ID,
|
||||
code: loginRes.code
|
||||
})
|
||||
|
||||
uni.setStorageSync('session_key', res.data.session_key)
|
||||
uni.setStorageSync('user', res.data.user)
|
||||
|
||||
return res.data
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 请求封装
|
||||
|
||||
```javascript
|
||||
// api/request.js
|
||||
const BASE_URL = 'https://api.example.com/api/v1'
|
||||
|
||||
export const request = {
|
||||
async request(options) {
|
||||
const sessionKey = uni.getStorageSync('session_key')
|
||||
|
||||
const [err, res] = await uni.request({
|
||||
url: BASE_URL + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': sessionKey ? `Bearer ${sessionKey}` : ''
|
||||
}
|
||||
})
|
||||
|
||||
if (err) {
|
||||
uni.showToast({ title: '网络错误', icon: 'none' })
|
||||
throw err
|
||||
}
|
||||
|
||||
if (res.statusCode === 401) {
|
||||
const { login } = await import('./auth')
|
||||
await login()
|
||||
return this.request(options)
|
||||
}
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
throw new Error(res.data?.message || '请求失败')
|
||||
}
|
||||
|
||||
return res.data
|
||||
},
|
||||
|
||||
get(url, params) {
|
||||
return this.request({ url, method: 'GET', data: params })
|
||||
},
|
||||
|
||||
post(url, data) {
|
||||
return this.request({ url, method: 'POST', data })
|
||||
},
|
||||
|
||||
put(url, data) {
|
||||
return this.request({ url, method: 'PUT', data })
|
||||
},
|
||||
|
||||
delete(url) {
|
||||
return this.request({ url, method: 'DELETE' })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 首页数据加载
|
||||
|
||||
```javascript
|
||||
// pages/index/index.vue
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
import * as api from '@/api/smoke'
|
||||
|
||||
const loading = ref(true)
|
||||
const dashboardStore = useDashboardStore()
|
||||
|
||||
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) {
|
||||
uni.redirectTo({ url: '/pages/onboarding/index' })
|
||||
return
|
||||
}
|
||||
|
||||
dashboardStore.setDashboard(dashboardRes.data)
|
||||
dashboardStore.setNextSmokeTime(nextTimeRes.data)
|
||||
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
setTimeout(loadAiAdvice, 300)
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 页面路由配置
|
||||
|
||||
```json
|
||||
// pages.json
|
||||
{
|
||||
"pages": [
|
||||
{ "path": "pages/index/index" },
|
||||
{ "path": "pages/stats/index" },
|
||||
{ "path": "pages/ai/index" },
|
||||
{ "path": "pages/logs/index" },
|
||||
{ "path": "pages/profile/index" },
|
||||
{ "path": "pages/onboarding/index" }
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#6B7280",
|
||||
"selectedColor": "#4ADE80",
|
||||
"backgroundColor": "#0D1F17",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{ "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/icons/home.png", "selectedIconPath": "static/icons/home-active.png" },
|
||||
{ "pagePath": "pages/stats/index", "text": "统计", "iconPath": "static/icons/stats.png", "selectedIconPath": "static/icons/stats-active.png" },
|
||||
{ "pagePath": "pages/ai/index", "text": "AI助手", "iconPath": "static/icons/ai.png", "selectedIconPath": "static/icons/ai-active.png" },
|
||||
{ "pagePath": "pages/logs/index", "text": "记录", "iconPath": "static/icons/logs.png", "selectedIconPath": "static/icons/logs-active.png" },
|
||||
{ "pagePath": "pages/profile/index", "text": "我的", "iconPath": "static/icons/profile.png", "selectedIconPath": "static/icons/profile-active.png" }
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarBackgroundColor": "#0D1F17",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#0D1F17",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 状态管理设计
|
||||
|
||||
### 6.1 Store 划分
|
||||
|
||||
| Store | 职责 | 持久化 |
|
||||
|-------|------|--------|
|
||||
| userStore | 用户信息、登录状态 | 是 |
|
||||
| profileStore | 用户档案(基准数据) | 是 |
|
||||
| dashboardStore | 首页看板数据 | 否(实时) |
|
||||
| logsStore | 记录列表、分页状态 | 否 |
|
||||
|
||||
### 6.2 数据流
|
||||
|
||||
```
|
||||
用户操作 → Action → API请求 → 更新State → 视图更新
|
||||
↓
|
||||
更新本地缓存
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 图表实现
|
||||
|
||||
### 7.1 周统计柱状图
|
||||
|
||||
```javascript
|
||||
// components/charts/WeeklyChart.vue
|
||||
import { computed } from 'vue'
|
||||
|
||||
const chartData = computed(() => ({
|
||||
categories: props.weekly.map(d => formatWeekday(d.date)),
|
||||
series: [{
|
||||
name: '吸烟量',
|
||||
data: props.weekly.map(d => d.count)
|
||||
}]
|
||||
}))
|
||||
|
||||
const opts = {
|
||||
type: 'column',
|
||||
color: ['#4ADE80'],
|
||||
padding: [15, 15, 0, 5],
|
||||
xAxis: {
|
||||
disableGrid: true,
|
||||
fontColor: '#9CA3AF'
|
||||
},
|
||||
yAxis: {
|
||||
gridColor: '#374151',
|
||||
fontColor: '#9CA3AF'
|
||||
},
|
||||
extra: {
|
||||
column: {
|
||||
width: 20,
|
||||
barBorderRadius: [4, 4, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 进度环
|
||||
|
||||
首页计时器使用 Canvas 绘制进度环:
|
||||
|
||||
```javascript
|
||||
// components/TimerRing.vue
|
||||
function drawRing(progress) {
|
||||
const ctx = uni.createCanvasContext('timerCanvas', this)
|
||||
const centerX = 150
|
||||
const centerY = 150
|
||||
const radius = 120
|
||||
|
||||
// 背景环
|
||||
ctx.beginPath()
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
|
||||
ctx.setStrokeStyle('#1F3D2B')
|
||||
ctx.setLineWidth(12)
|
||||
ctx.stroke()
|
||||
|
||||
// 进度环
|
||||
ctx.beginPath()
|
||||
ctx.arc(centerX, centerY, radius, -Math.PI/2, -Math.PI/2 + progress * 2 * Math.PI)
|
||||
ctx.setStrokeStyle('#4ADE80')
|
||||
ctx.setLineWidth(12)
|
||||
ctx.setLineCap('round')
|
||||
ctx.stroke()
|
||||
|
||||
ctx.draw()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理
|
||||
|
||||
### 8.1 全局错误拦截
|
||||
|
||||
```javascript
|
||||
// api/request.js
|
||||
function handleError(error) {
|
||||
if (error.statusCode === 401) {
|
||||
return reLogin()
|
||||
}
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
return { needUnlock: true, error }
|
||||
}
|
||||
|
||||
if (error.errMsg && error.errMsg.includes('network')) {
|
||||
uni.showToast({ title: '网络连接失败', icon: 'none' })
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 页面级错误处理
|
||||
|
||||
```javascript
|
||||
// pages/index/index.vue
|
||||
async function initPage() {
|
||||
try {
|
||||
await loadData()
|
||||
} catch (error) {
|
||||
showRetry.value = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 性能监控
|
||||
|
||||
```javascript
|
||||
// utils/performance.js
|
||||
export function trackPageLoad(pageName) {
|
||||
const startTime = Date.now()
|
||||
|
||||
return {
|
||||
markFirstPaint() {
|
||||
const fp = Date.now() - startTime
|
||||
console.log(`[Perf] ${pageName} First Paint: ${fp}ms`)
|
||||
},
|
||||
|
||||
markFullyLoaded() {
|
||||
const loaded = Date.now() - startTime
|
||||
console.log(`[Perf] ${pageName} Fully Loaded: ${loaded}ms`)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 环境配置
|
||||
|
||||
```javascript
|
||||
// config/index.js
|
||||
const ENV = {
|
||||
development: {
|
||||
BASE_URL: 'http://127.0.0.1:8080/api/v1',
|
||||
MINI_PROGRAM_ID: 1
|
||||
},
|
||||
production: {
|
||||
BASE_URL: 'https://api.example.com/api/v1',
|
||||
MINI_PROGRAM_ID: 1
|
||||
}
|
||||
}
|
||||
|
||||
const env = process.env.NODE_ENV || 'development'
|
||||
export const { BASE_URL, MINI_PROGRAM_ID } = ENV[env]
|
||||
```
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
# 戒烟/抽烟记录 API
|
||||
|
||||
所有接口前缀:`/api/v1/smoke`
|
||||
除登录外都需要:`Authorization: Bearer <session_key>`(见:`docs/common/auth.md`)
|
||||
|
||||
## 1) 新增记录
|
||||
|
||||
`POST /api/v1/smoke/logs`
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"smoke_time": "2025-12-31",
|
||||
"smoke_at": "2025-12-31 08:30:00",
|
||||
"remark": "压力大",
|
||||
"level": 2,
|
||||
"num": 3
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
- `smoke_time` 可选;不传则默认“当天”。
|
||||
- `smoke_at` 可选;真实抽烟时间(格式 `YYYY-MM-DD HH:MM:SS`)。用于“按时间节点分析/AI 建议”;不传则可用 `createtime` 近似。
|
||||
- `level/num` 可选;不传时后端会按 `1` 处理。
|
||||
- 如果要记录“想抽但忍住了”,请传 `level=0` 且 `num=0`(会在 `fa_smoke_log` 中展示为一条记录,但不会影响看板的支数累加)。
|
||||
|
||||
curl 示例:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://127.0.0.1:8080/api/v1/smoke/logs' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer wx-session-key' \
|
||||
-d '{"smoke_time":"2025-12-31","smoke_at":"2025-12-31 08:30:00","remark":"压力大","level":2,"num":3}'
|
||||
```
|
||||
|
||||
成功响应示例(字段以实际为准):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 5202,
|
||||
"smoke_time": "2025-12-31T00:00:00+08:00",
|
||||
"smoke_at": "2025-12-31T08:30:00+08:00",
|
||||
"remark": "压力大",
|
||||
"createtime": 1735600000,
|
||||
"updatetime": 1735600000,
|
||||
"deletetime": null,
|
||||
"level": 2,
|
||||
"num": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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) 列表查询(分页)
|
||||
|
||||
`GET /api/v1/smoke/logs?page=1&page_size=20&start=2025-12-01&end=2025-12-31`
|
||||
|
||||
参数:
|
||||
- `page`:页码,默认 `1`
|
||||
- `page_size`:每页数量,默认 `20`,最大 `200`
|
||||
- `start/end`:可选,按 `smoke_time` 过滤(格式 `YYYY-MM-DD`)
|
||||
|
||||
成功响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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) 更新记录
|
||||
|
||||
`PUT /api/v1/smoke/logs/:id`
|
||||
|
||||
请求体(字段可选,按需传):
|
||||
|
||||
```json
|
||||
{
|
||||
"smoke_time": "2026-01-01",
|
||||
"smoke_at": "2026-01-01 21:10:00",
|
||||
"remark": "聚会",
|
||||
"level": 3,
|
||||
"num": 1
|
||||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
- 如果你想“清空 smoke_time”,请传空字符串:`{"smoke_time":""}`。
|
||||
- 如果你想“清空 smoke_at”,请传空字符串:`{"smoke_at":""}`。
|
||||
- 如果传 `null` 或者不传 `smoke_time`,后端会认为你没有修改该字段。
|
||||
|
||||
## 7) 删除记录(软删除)
|
||||
|
||||
`DELETE /api/v1/smoke/logs/:id`
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"deleted": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8) 获取 AI 戒烟建议(会员 + 广告解锁并行)
|
||||
|
||||
`GET /api/v1/smoke/ai/advice?date=2026-01-02`
|
||||
|
||||
说明:
|
||||
- `date` 可选,默认“昨天”(建议针对哪一天的数据)。
|
||||
- 权限:会员用户直接可用;非会员需要先对该 `date` 完成“看广告解锁”(见下一个接口)。
|
||||
- 建议结果会按 `uid + date + prompt_version` 缓存(表:`fa_smoke_ai_advice`)。
|
||||
|
||||
未满足权限时的建议响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 403,
|
||||
"message": "需要会员或观看广告解锁后才可生成建议",
|
||||
"data": {
|
||||
"date": "2026-01-02",
|
||||
"need": "vip_or_ad"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
成功响应(示例):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"date": "2026-01-02",
|
||||
"advice": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9) 看广告解锁(用于非会员)
|
||||
|
||||
`POST /api/v1/smoke/ai/advice_unlocks`
|
||||
|
||||
请求体:
|
||||
```json
|
||||
{
|
||||
"date": "2026-01-02",
|
||||
"ad_watched_at": "2026-01-03 09:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
- 该接口用于记录“已完成观看广告”,落库到 `fa_smoke_ai_advice_unlocks`(`uid + unlock_date` 唯一)。
|
||||
- `ad_watched_at` 可由后端取当前时间;如需审计/对账可保留前端上报并做校验。
|
||||
- 解锁是“按天”的:观看一次广告解锁一天内的 AI 生成功能(可用于「每日 AI 建议」以及「AI 下次抽烟时间节点」)。
|
||||
- 如果你要生成“明天”的 AI 时间节点,请把 `date` 传为明天日期(例如 `2026-01-06`)。
|
||||
|
||||
## 10) 获取用户基础信息(首次进入:判断是否需要补全)
|
||||
|
||||
`GET /api/v1/smoke/profile`
|
||||
|
||||
说明:
|
||||
- 首次进入小程序建议先调用该接口:若 `exists=false` 或 `is_completed=false`,前端进入“信息补全”流程。
|
||||
- `baseline_interval_minutes` 用于建立初始基准:在用户清醒时段内的“平均间隔(分钟)”。计算:`awake_minutes / baseline_cigs_per_day`。
|
||||
- 若未提供作息时间(起床/入睡),后端会用默认清醒时长 `16*60=960` 分钟参与计算。
|
||||
|
||||
成功响应(示例):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"exists": true,
|
||||
"profile": {
|
||||
"id": 1,
|
||||
"created_at": "2026-01-05T10:00:00+08:00",
|
||||
"updated_at": "2026-01-05T10:00:00+08:00",
|
||||
"baseline_cigs_per_day": 20,
|
||||
"smoking_years": 8,
|
||||
"pack_price_cent": 2500,
|
||||
"smoke_motivations": ["压力大", "社交"],
|
||||
"quit_motivations": ["身体健康", "省钱"],
|
||||
"wake_up_time": "07:30",
|
||||
"sleep_time": "23:30",
|
||||
"onboarding_completed_at": "2026-01-05T10:00:00+08:00"
|
||||
},
|
||||
"is_completed": true,
|
||||
"awake_minutes": 960,
|
||||
"baseline_interval_minutes": 48
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当 `exists=false`(尚未补全)时,响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"exists": false,
|
||||
"is_completed": false,
|
||||
"awake_minutes": 960,
|
||||
"baseline_interval_minutes": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段用途(补全页面可参考):
|
||||
- `baseline_cigs_per_day`(基础烟量/日均抽烟支数):建立初始基准,计算初始建议间隔时长。
|
||||
- `smoking_years`(烟龄/年)+ `pack_price_cent`(单包价格/分):用于看板计算“已省金额”和“恢复时长”等指标(公式可在看板端实现)。
|
||||
- `smoke_motivations`(抽烟动机):如 `压力大/无聊/社交/提神`,用于 AI 在分析 remark 时更有针对性。
|
||||
- `quit_motivations`(戒烟动力):如 `身体健康/家人孩子/省钱`,当用户产生动摇时 AI 可用这些信息做“情感阻断/自我提醒”。
|
||||
- `wake_up_time` + `sleep_time`(作息时间):用于自动规避睡眠时间,防止在用户睡觉时提醒其“坚持”。
|
||||
|
||||
## 11) 补全/更新用户基础信息(Upsert)
|
||||
|
||||
`PUT /api/v1/smoke/profile`
|
||||
|
||||
说明:
|
||||
- 字段按需传;首次进入建议一次性补全。
|
||||
- 作息时间格式:`HH:MM`(24 小时制),例如 `07:30`、`23:10`。
|
||||
- `pack_price_cent` 为“分”;若前端用“元”,请乘以 100。
|
||||
|
||||
请求体(示例):
|
||||
|
||||
```json
|
||||
{
|
||||
"baseline_cigs_per_day": 20,
|
||||
"smoking_years": 8,
|
||||
"pack_price_cent": 2500,
|
||||
"smoke_motivations": ["压力大", "社交"],
|
||||
"quit_motivations": ["身体健康", "省钱"],
|
||||
"wake_up_time": "07:30",
|
||||
"sleep_time": "23:30"
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:同 `GET /api/v1/smoke/profile`(返回最新 `profile` + `is_completed` + `baseline_interval_minutes`)。
|
||||
|
||||
## 12) 想抽但忍住了(写入一条 level=0,num=0 的记录)
|
||||
|
||||
`POST /api/v1/smoke/logs/resisted`
|
||||
|
||||
请求体(示例):
|
||||
```json
|
||||
{
|
||||
"smoke_time": "2026-01-05",
|
||||
"smoke_at": "2026-01-05 10:20:00",
|
||||
"remark": "压力大,想抽但忍住了"
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
- 该接口会在 `fa_smoke_log` 中新增一条记录:`level=0` 且 `num=0`,用于更直观记录“想抽/忍住”的过程。
|
||||
- 这类记录不会影响 `today_count/weekly.count` 的支数统计(因为 `num=0`)。
|
||||
|
||||
## 13) 获取“下次抽烟记录时间”(默认 + AI 自动切换)
|
||||
|
||||
`GET /api/v1/smoke/next_smoke_time`
|
||||
|
||||
说明:
|
||||
- 用于首页展示“建议的下次记录时间”。
|
||||
- 如果指定日期存在 AI 给出的时间节点(`time_nodes` 不为空),则优先使用 AI 的建议;否则使用默认策略。
|
||||
- 可选参数:
|
||||
- `date`:计划日期(默认今天),支持 `YYYY-MM-DD` 或 `today/tomorrow`。
|
||||
- `mode`(默认 `auto`)
|
||||
- `auto`:只在已存在 AI 时间节点时使用 AI(不主动生成)
|
||||
- `ai`:生成该 `date` 的 AI 时间节点(需要先看广告解锁;生成一次缓存一天)
|
||||
- `default`:永远返回默认策略
|
||||
|
||||
默认策略(不使用 AI):
|
||||
- 基础间隔:优先使用 `GET /api/v1/smoke/profile` 返回的 `baseline_interval_minutes`;若不存在则默认 `60` 分钟。
|
||||
- 阶梯式延时:最近 7 天内每累计 `5` 条“忍住记录(level=0,num=0)”,在基础间隔上 `+5` 分钟(最多 `+60` 分钟)。
|
||||
- 若用户已补全作息时间,会自动规避睡眠区间:若计算出的时间落在睡眠区间,顺延到下一次起床时间。
|
||||
|
||||
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",
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
# 认证与登录
|
||||
|
||||
## 1) 登录
|
||||
|
||||
`POST /api/v1/auth/login`
|
||||
|
||||
说明:小程序端调用 `wx.login()` 获取 `code`,后端用该 `code` 向微信 `jscode2session` 换取 `openid/session_key`,并在数据库中创建/更新用户记录。
|
||||
|
||||
请求示例:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://127.0.0.1:8080/api/v1/auth/login' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"mini_program_id": 2,
|
||||
"code": "wx.login 返回的 code",
|
||||
"nickname": "可选:昵称",
|
||||
"avatar_url": "可选:头像",
|
||||
"gender": 1,
|
||||
"phone": "可选:手机号"
|
||||
}'
|
||||
```
|
||||
|
||||
成功响应示例(节选):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 1,
|
||||
"mini_program_id": 1,
|
||||
"open_id": "oXXX",
|
||||
"nickname": "昵称",
|
||||
"avatar_url": "https://...",
|
||||
"gender": 1,
|
||||
"phone": "110"
|
||||
},
|
||||
"session_key": "wx-session-key",
|
||||
"mini_program": {
|
||||
"id": 1,
|
||||
"name": "某小程序",
|
||||
"app_id": "wx..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2) 受保护接口如何带 Token
|
||||
|
||||
后端把 `session_key` 当做 Token 使用,调用受保护接口时在 Header 中带:
|
||||
|
||||
```
|
||||
Authorization: Bearer <session_key>
|
||||
```
|
||||
|
||||
请求示例:
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs?page=1&page_size=20' \
|
||||
-H 'Authorization: Bearer wx-session-key'
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user