Files
smt/docs/smt双模式改造方案.md
T
你çšnepiedg 31e504a997 feat: 添加模式选择功能与页面更新
- 在 onboarding 页面中新增使用模式选择功能,用户可选择“戒烟打卡”或“记录抽烟”模式
- 更新个人资料页面以显示当前模式并允许用户切换模式
- 在 pages.json 中注册新的模式选择页面
- 优化首页和其他相关页面以适应新模式功能
2026-03-18 00:06:01 +08:00

533 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# smt 双模式改造方案
> 创建时间:2026-03-17
> 状态:待开发
> 相关文档:[[戒烟产品分析-smt-vs-quit-checkin]]
---
## 📁 现有项目结构
```
smt/
├── api/ # 接口封装
│ ├── auth.js # 登录认证
│ ├── smoke.js # 抽烟记录 API
│ ├── profile.js # 用户资料 API
│ ├── request.js # 请求封装
│ └── index.js # 统一导出
├── components/ # 业务组件
│ └── smoke-record-dialog/ # 记录弹框组件
├── config/ # 环境配置
│ └── index.js # BASE_URL 配置
├── hooks/ # 组合式逻辑
│ └── useLogin.js # 登录相关 hook
├── pages/ # 页面
│ ├── index/ # 首页 ⭐ 需要改造
│ ├── stats/ # 统计页
│ ├── logs/ # 历史记录页
│ ├── ai/ # AI 建议
│ ├── ai_summary/ # AI 总结
│ ├── share/ # 分享页
│ ├── profile/ # 个人中心
│ ├── quit-plan/ # 戒烟计划
│ └── onboarding/ # 新用户引导 ⭐ 需要改造
├── stores/ # Pinia 状态管理
│ ├── user.js # 用户状态 ⭐ 需要扩展
│ ├── profile.js # 用户资料 ⭐ 需要扩展
│ ├── logs.js # 记录状态
│ ├── dashboard.js # 首页数据
│ └── index.js # 统一导出
├── utils/ # 工具函数
│ ├── storage.js # 本地存储 ⭐ 需要扩展
│ ├── time.js # 时间处理
│ └── format.js # 格式化
├── static/ # 静态资源
├── App.vue # 应用入口
├── main.js # 主入口
├── pages.json # 页面配置 ⭐ 需要修改
└── manifest.json # 小程序配置
```
---
## 🔧 需要修改/新增的文件
### 新增文件
| 文件路径 | 说明 |
|----------|------|
| `pages/mode-select/index.vue` | **新增** - 模式选择引导页 |
| `pages/quit-home/index.vue` | **新增** - 戒烟打卡模式首页 |
| `pages/record-home/index.vue` | **新增** - 记录抽烟模式首页 |
| `components/quit-checkin-btn/index.vue` | **新增** - 打卡按钮组件 |
| `components/simple-counter/index.vue` | **新增** - 简易计数器组件 |
### 需要修改的文件
| 文件路径 | 修改内容 |
|----------|----------|
| `stores/user.js` | 添加 `mode` 字段存储用户模式 |
| `stores/profile.js` | 添加 `quitDays` 戒烟天数计算 |
| `utils/storage.js` | 添加 `USER_MODE_KEY` 常量 |
| `pages.json` | 添加新页面路由,修改首页逻辑 |
| `pages/onboarding/index.vue` | 完成引导后跳转到模式选择页 |
| `pages/profile/index.vue` | 添加模式切换入口 |
| `pages/index/index.vue` | 根据模式渲染不同首页(可选方案) |
---
## 📝 详细代码修改
### 1. `utils/storage.js` - 新增常量
```javascript
export const USER_MODE_KEY = 'user_mode' // 新增
```
---
### 2. `stores/user.js` - 扩展用户模式
```javascript
import { defineStore } from 'pinia'
import { storage, USER_KEY, SESSION_KEY, USER_MODE_KEY } from '@/utils/storage'
export const useUserStore = defineStore('user', {
state: () => ({
user: storage.get(USER_KEY),
sessionKey: storage.get(SESSION_KEY),
isLoggedIn: !!storage.get(SESSION_KEY),
mode: storage.get(USER_MODE_KEY) || null // 'quit' | 'record' | null
}),
actions: {
setUser(user, sessionKey) {
this.user = user
this.sessionKey = sessionKey
this.isLoggedIn = true
storage.set(USER_KEY, user)
storage.set(SESSION_KEY, sessionKey)
},
setMode(mode) {
this.mode = mode
storage.set(USER_MODE_KEY, mode)
},
logout() {
this.user = null
this.sessionKey = null
this.isLoggedIn = false
storage.remove(USER_KEY)
storage.remove(SESSION_KEY)
}
}
})
```
---
### 3. `pages.json` - 添加新页面
```json
{
"pages": [
{
"path": "pages/mode-select/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/quit-home/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/record-home/index",
"style": {
"navigationBarTitleText": "记录抽烟"
}
},
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom"
}
}
]
}
```
---
### 4. `pages/mode-select/index.vue` - 模式选择页
```vue
<template>
<view class="page">
<view class="content">
<text class="title">你想怎么戒烟</text>
<text class="subtitle">选择适合你的方式</text>
<view class="options">
<view class="option option-quit" @tap="selectMode('quit')">
<text class="option-icon">🌟</text>
<view class="option-text">
<text class="option-title">我要戒烟打卡</text>
<text class="option-desc">记录坚持的天数获得成就感</text>
</view>
</view>
<view class="option option-record" @tap="selectMode('record')">
<text class="option-icon">📊</text>
<view class="option-text">
<text class="option-title">我要记录抽烟</text>
<text class="option-desc">跟踪抽烟频率分析戒烟进度</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
function selectMode(mode) {
userStore.setMode(mode)
if (mode === 'quit') {
uni.redirectTo({ url: '/pages/quit-home/index' })
} else {
uni.redirectTo({ url: '/pages/record-home/index' })
}
}
</script>
```
---
### 5. `pages/quit-home/index.vue` - 戒烟打卡首页
**界面设计:**
```
┌─────────────────────────┐
│ 🔥 已坚持 23 天 │
│ │
│ ┌─────────┐ │
│ │ 打卡 │ │
│ │ 今天没抽│ │
│ └─────────┘ │
│ │
│ 💰 已省下 184 元 │
│ 🫁 肺部正在恢复中... │
│ │
│ ───────────────────── │
│ 今日打卡 ✓ 08:30 │
└─────────────────────────┘
```
**核心代码:**
```vue
<template>
<view class="page">
<view class="header">
<text class="days-label">已坚持</text>
<text class="days-value">{{ quitDays }}</text>
<text class="days-unit"></text>
</view>
<view class="checkin-section">
<view class="checkin-btn" :class="{ 'checked': todayChecked }" @tap="checkin">
<text class="checkin-icon">{{ todayChecked ? '✓' : '🔥' }}</text>
<text class="checkin-text">{{ todayChecked ? '今日已打卡' : '打卡' }}</text>
</view>
</view>
<view class="stats-row">
<view class="stat-item">
<text class="stat-value">¥{{ savedMoney }}</text>
<text class="stat-label">已省下</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ healthProgress }}%</text>
<text class="stat-label">健康恢复</text>
</view>
</view>
<view class="health-tips">
<text class="tip-icon">🫁</text>
<text class="tip-text">{{ healthTip }}</text>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'
import { useProfileStore } from '@/stores/profile'
const userStore = useUserStore()
const profileStore = useProfileStore()
const todayChecked = ref(false)
const quitStartDate = ref(null)
const quitDays = computed(() => {
if (!quitStartDate.value) return 0
const diff = Date.now() - new Date(quitStartDate.value).getTime()
return Math.floor(diff / (1000 * 60 * 60 * 24))
})
const savedMoney = computed(() => {
const pricePerPack = profileStore.profile?.pack_price_cent || 2500
const cigsPerDay = profileStore.profile?.baseline_cigs_per_day || 10
const savedCigs = quitDays.value * cigsPerDay
return ((savedCigs / 20) * (pricePerPack / 100)).toFixed(0)
})
const healthProgress = computed(() => {
if (quitDays.value >= 365) return 100
if (quitDays.value >= 180) return 85
if (quitDays.value >= 90) return 70
if (quitDays.value >= 30) return 50
if (quitDays.value >= 14) return 30
if (quitDays.value >= 7) return 15
return 5
})
const healthTip = computed(() => {
if (quitDays.value >= 365) return '恭喜!你的肺部功能已基本恢复'
if (quitDays.value >= 180) return '你的血液循环已显著改善'
if (quitDays.value >= 30) return '你的味觉和嗅觉正在恢复'
if (quitDays.value >= 7) return '你的肺活量开始增加'
return '坚持下去,身体正在恢复中'
})
function checkin() {
if (todayChecked.value) return
todayChecked.value = true
// TODO: 调用 API 保存打卡记录
uni.showToast({ title: '打卡成功!', icon: 'success' })
}
onMounted(() => {
// 获取戒烟开始日期
// TODO: 从 API 或本地存储获取
})
</script>
```
---
### 6. `pages/record-home/index.vue` - 记录抽烟首页
**界面设计:**
```
┌─────────────────────────┐
│ 今天抽了 3 根 │
│ │
│ ┌─────────┐ │
│ │ +1 根 │ │
│ │ 点击记录│ │
│ └─────────┘ │
│ │
│ 昨日:4 根 │
│ 本周:18 根 │
│ │
│ ───────────────────── │
│ 历史记录 > │
└─────────────────────────┘
```
**核心代码:**
```vue
<template>
<view class="page">
<view class="counter-section">
<text class="counter-label">今天抽了</text>
<text class="counter-value">{{ todayCount }}</text>
<text class="counter-unit"></text>
</view>
<view class="add-btn" @tap="addOne">
<text class="add-icon">+</text>
<text class="add-text">记录一根</text>
</view>
<view class="summary">
<view class="summary-item">
<text class="summary-label">昨日</text>
<text class="summary-value">{{ yesterdayCount }} </text>
</view>
<view class="summary-item">
<text class="summary-label">本周</text>
<text class="summary-value">{{ weekCount }} </text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import * as api from '@/api'
const todayCount = ref(0)
const yesterdayCount = ref(0)
const weekCount = ref(0)
async function addOne() {
todayCount.value++
// TODO: 调用 API 记录
try {
await api.createLog({
num: 1,
smoke_time: new Date().toISOString(),
smoke_at: new Date().toISOString()
})
uni.showToast({ title: '已记录', icon: 'success', duration: 1000 })
} catch (e) {
console.error('记录失败:', e)
}
}
</script>
<style scoped>
.add-btn {
width: 300rpx;
height: 300rpx;
border-radius: 50%;
background: linear-gradient(135deg, #EF4444, #F87171);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 60rpx auto;
box-shadow: 0 20rpx 40rpx rgba(239, 68, 68, 0.3);
}
.add-icon {
font-size: 80rpx;
color: #FFF;
font-weight: 300;
}
.add-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
margin-top: 8rpx;
}
</style>
```
---
### 7. `pages/profile/index.vue` - 添加模式切换
在个人中心添加:
```vue
<view class="menu-item" @tap="showModeSwitch">
<text class="menu-icon"></text>
<text class="menu-text">模式切换</text>
<text class="menu-value">{{ modeText }}</text>
</view>
<script setup>
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const modeText = computed(() => {
return userStore.mode === 'quit' ? '戒烟打卡' : '记录抽烟'
})
function showModeSwitch() {
uni.showActionSheet({
itemList: ['戒烟打卡模式', '记录抽烟模式'],
success: (res) => {
const modes = ['quit', 'record']
userStore.setMode(modes[res.tapIndex])
uni.showToast({ title: '已切换', icon: 'success' })
}
})
}
</script>
```
---
## 📋 开发顺序
| 阶段 | 任务 | 预估时间 | 状态 |
|------|------|----------|------|
| **第一阶段** | 1. 修改 `utils/storage.js`<br>2. 修改 `stores/user.js`<br>3. 创建 `pages/mode-select/index.vue`<br>4. 修改 `pages.json` | 1-2h | ⬜ 待开始 |
| **第二阶段** | 1. 创建 `pages/quit-home/index.vue`<br>2. 打卡 API 对接<br>3. 省钱金额计算 | 2-3h | ⬜ 待开始 |
| **第三阶段** | 1. 创建 `pages/record-home/index.vue`<br>2. 一键记录 API 对接 | 1-2h | ⬜ 待开始 |
| **第四阶段** | 1. 修改 `pages/profile/index.vue` 模式切换<br>2. 修改引导流程跳转逻辑 | 1h | ⬜ 待开始 |
**总预估:5-8 小时**
---
## ⚠️ 注意事项
### 1. TabBar 问题
如果要让两种模式共用 TabBar,首页需要根据 mode 动态渲染:
```vue
<!-- pages/index/index.vue -->
<template>
<QuitHome v-if="userStore.mode === 'quit'" />
<RecordHome v-else />
</template>
```
### 2. 数据兼容
老用户没有 mode 字段,需要引导选择模式:
```javascript
// App.vue 或首页
if (!userStore.mode) {
uni.redirectTo({ url: '/pages/mode-select/index' })
}
```
### 3. API 对接
打卡功能需要后端新增接口:
```
POST /api/checkin
{
"date": "2026-03-17",
"quit_days": 23
}
```
或复用现有 resisted 接口。
---
## 🔗 相关文档
- [[戒烟产品分析-smt-vs-quit-checkin]]
- [[smt - 技术文档]]
- [[quit-checkin - 技术文档]]
---
#技术开发 #smt #戒烟 #小程序 #双模式