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

14 KiB
Raw Permalink Blame History

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 - 新增常量

export const USER_MODE_KEY = 'user_mode'  // 新增

2. stores/user.js - 扩展用户模式

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 - 添加新页面

{
  "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 - 模式选择页

<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       │
└─────────────────────────┘

核心代码:

<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 根            │
│                         │
│  ─────────────────────  │
│  历史记录 >             │
└─────────────────────────┘

核心代码:

<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 - 添加模式切换

在个人中心添加:

<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
2. 修改 stores/user.js
3. 创建 pages/mode-select/index.vue
4. 修改 pages.json
1-2h 待开始
第二阶段 1. 创建 pages/quit-home/index.vue
2. 打卡 API 对接
3. 省钱金额计算
2-3h 待开始
第三阶段 1. 创建 pages/record-home/index.vue
2. 一键记录 API 对接
1-2h 待开始
第四阶段 1. 修改 pages/profile/index.vue 模式切换
2. 修改引导流程跳转逻辑
1h 待开始

总预估:5-8 小时


⚠️ 注意事项

1. TabBar 问题

如果要让两种模式共用 TabBar,首页需要根据 mode 动态渲染:

<!-- pages/index/index.vue -->
<template>
  <QuitHome v-if="userStore.mode === 'quit'" />
  <RecordHome v-else />
</template>

2. 数据兼容

老用户没有 mode 字段,需要引导选择模式:

// 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 #戒烟 #小程序 #双模式