diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index 62e7cec..f476bb4 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -1,133 +1,234 @@
-
-
-
-
-
+
+
+
-
-
-
+
+
+
+
-
+
-
-
-
- 今日戒烟状态
- {{ quitSubtitle }}
-
-
- {{ todayChecked ? '今日已打卡' : '今日待打卡' }}
+
+
+
+
+
+
+ {{ quitDays }}
+ 天
+
+ 已坚持戒烟
-
-
-
- 已坚持
-
- {{ quitDays }}
- 天
-
-
-
- 已省下
-
- ¥{{ savedMoney }}
-
+
+
+
+ 💰
+
+ ¥{{ savedMoney }}
+ 已省下
-
-
- {{ healthProgress }}%
- 恢复进度
+
+
+ 🚬
+
+ {{ avoidedCigs }}
+ 少抽(支)
+
+
+
+
+ ⏱️
+
+ {{ lifeSaved }}
+ 延长(小时)
-
-
- {{ todayChecked ? '今日已打卡' : '今天没抽,去打卡' }}
- {{ todayChecked ? `已于 ${todayCheckinTime} 记录` : '把今天记成无烟的一天' }}
+
+
+
+
+
+ ✓
+ +
+
+
+
+ {{ todayChecked ? '今日已打卡' : '今天没抽烟' }}
+ {{ todayChecked ? `${todayCheckinTime} 已记录` : '点击打卡,记录无烟的一天' }}
+
+
+ +1
+
-
- 今日提醒
- {{ quitEncouragement }}
-
+
+
+
+
+
+
+
+
+
+ {{ milestone.label }}
+
+
+
+
+
+
+
+ "
+ {{ quitEncouragement }}
+ {{ healthTip }}
+
-
-
-
-
-
-
- 记录
-
+
+
+
+
+
+
+
+ 记录
+
+
+
+
+
+
+
+
+ {{ timerDisplay }}
+ 距上次抽烟
-
-
-
-
- {{ timerDisplay }}
- 距上次抽烟
-
-
-
-
-
-
-
- {{ resistedCount }}
- 次
-
- 今日忍住
-
-
-
- {{ todayCount }}
- 根
-
- 今日已抽
+
+
+
+
+ {{ resistedCount }}
+ 次
+ 今日忍住
- {{ nextSmokeTimeText ? `建议下次 ${nextSmokeTimeText}` : changeText }}
+
+
+ {{ todayCount }}
+ 根
+
+ 今日已抽
+
+
+ {{ nextSmokeTimeText ? `建议下次 ${nextSmokeTimeText}` : changeText }}
+
+
+
+
+
+ 记录
+
+
+ 忍住
+
+
+
+
+
+
+
+
+
+
+
+ 💰
+
+
+ ¥{{ recordSavedMoney }}
+ 累计节省
-
-
- 记录
+
+
+ ⏱️
-
- 忍住
+
+ {{ recordLifeSaved }}h
+ 延长生命
+
+
+
+ 💪
+
+
+ {{ totalResistedCount }}
+ 累计忍住
+
+
+
+
+
+ 📉
+
+
+ {{ reducePercent }}%
+ 减少比例
+
+
+
+
+
+ 💡
+ {{ recordHealthTip }}
-
+
+
@@ -160,26 +261,6 @@ const isQuitMode = computed(() => userStore.mode === 'quit')
const homeSummary = computed(() => homeData.value?.summary || {})
const homeTimer = computed(() => homeData.value?.timer || {})
-const userName = computed(() => homeData.value?.greeting?.nickname || userStore.user?.nickname || '戒烟用户')
-const userAvatar = computed(() => homeData.value?.greeting?.avatar_url || userStore.user?.avatar_url || 'https://linghu-wmr.oss-cn-beijing.aliyuncs.com/smt/avatar.png')
-
-const greetingTitle = computed(() => {
- const hour = new Date().getHours()
- let greeting = '晚上好'
- if (hour < 6) greeting = '凌晨好'
- else if (hour < 12) greeting = '早上好'
- else if (hour < 14) greeting = '中午好'
- else if (hour < 18) greeting = '下午好'
- return `${greeting},${userName.value}`
-})
-
-const greetingSubtitle = computed(() => {
- if (isQuitMode.value) {
- return todayChecked.value ? '今天已经记下来了,继续保持。' : '先把今天记成无烟的一天。'
- }
- return '记录越及时,后面的趋势越准。'
-})
-
const todayCount = computed(() => homeSummary.value.today_count ?? 0)
const dailyTarget = computed(() => {
const target = homeSummary.value.daily_target
@@ -214,66 +295,127 @@ const nextSmokeTimeText = computed(() => {
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
})
+// 戒烟模式计算
const baselineCigsPerDay = computed(() => profileStore.profile?.baseline_cigs_per_day || 10)
const packPriceYuan = computed(() => (profileStore.profile?.pack_price_cent || 2500) / 100)
+
const quitDays = computed(() => {
if (!quitState.value.lastCheckinDate) return 0
const gap = diffDays(quitState.value.lastCheckinDate, formatDate(new Date()))
if (gap > 1) return 0
return Number(quitState.value.streakDays || 0)
})
+
const todayChecked = computed(() => quitState.value.lastCheckinDate === formatDate(new Date()))
const todayCheckinTime = computed(() => formatClock(quitState.value.lastCheckinAt))
+
const savedMoney = computed(() => {
const total = (quitDays.value * baselineCigsPerDay.value / 20) * packPriceYuan.value
return Math.round(total)
})
+
+const avoidedCigs = computed(() => {
+ return quitDays.value * baselineCigsPerDay.value
+})
+
+const lifeSaved = computed(() => {
+ // 每支烟减少约11分钟生命,换算成小时
+ return Math.round(quitDays.value * baselineCigsPerDay.value * 11 / 60)
+})
+
const healthProgress = computed(() => {
if (quitDays.value >= 365) return 100
- if (quitDays.value >= 180) return 86
- if (quitDays.value >= 90) return 72
- if (quitDays.value >= 30) return 58
- if (quitDays.value >= 14) return 38
- if (quitDays.value >= 7) return 18
- if (quitDays.value >= 1) return 6
+ if (quitDays.value >= 180) return 85
+ if (quitDays.value >= 90) return 70
+ if (quitDays.value >= 30) return 55
+ if (quitDays.value >= 14) return 35
+ if (quitDays.value >= 7) return 20
+ if (quitDays.value >= 3) return 10
+ if (quitDays.value >= 1) return 5
return 0
})
+
const healthTip = computed(() => {
- if (quitDays.value >= 365) return '肺部功能已进入长期恢复阶段'
- if (quitDays.value >= 180) return '血液循环和咳嗽症状通常会继续改善'
- if (quitDays.value >= 30) return '味觉和嗅觉通常会逐步恢复'
- if (quitDays.value >= 7) return '一周后,呼吸会比开始时更轻松一些'
- if (quitDays.value >= 1) return '第一阶段最难,坚持住就有意义'
- return '打卡从今天开始,先拿下第一天'
-})
-const quitSubtitle = computed(() => {
- if (!quitState.value.lastCheckinDate) return '还没有开始记录,先拿下第一天。'
- const gap = diffDays(quitState.value.lastCheckinDate, formatDate(new Date()))
- if (gap > 1) return '连续记录已中断,今天重新开始。'
- if (todayChecked.value) return '今天已经打卡,明天继续。'
- return '别漏掉今天这次打卡。'
+ if (quitDays.value >= 365) return '肺部功能显著改善,心血管疾病风险大幅降低'
+ if (quitDays.value >= 180) return '血液循环持续改善,肺功能逐步恢复'
+ if (quitDays.value >= 90) return '味觉嗅觉恢复,呼吸更加顺畅'
+ if (quitDays.value >= 30) return '咳嗽减少,体能开始恢复'
+ if (quitDays.value >= 14) return '尼古丁戒断症状明显减轻'
+ if (quitDays.value >= 7) return '一氧化碳水平恢复正常'
+ if (quitDays.value >= 3) return '呼吸开始变得顺畅'
+ if (quitDays.value >= 1) return '身体开始自我修复'
+ return '开始戒烟,身体即将启动修复'
})
+
const quitEncouragement = computed(() => {
- if (todayChecked.value) return '今天的无烟记录已经落下,尽量把第一支拖得更晚一些。'
- if (quitDays.value === 0) return '先不要想很久,只把今天守住就够了。'
- return `连续 ${quitDays.value} 天很难得,今天再补上一天。`
+ if (todayChecked.value) {
+ if (quitDays.value >= 30) return '太棒了!坚持一个月以上,你已经战胜了最难的部分'
+ if (quitDays.value >= 7) return '一周没抽了!身体正在快速恢复中'
+ if (quitDays.value >= 3) return '三天没抽了,最难的时期正在过去'
+ return '今天打卡成功,继续保持!'
+ }
+ if (quitDays.value === 0) return '迈出第一步,从今天开始无烟生活'
+ if (quitDays.value < 3) return '坚持住,前三天是最关键的'
+ if (quitDays.value < 7) return '你已经走了很远,继续加油'
+ return `已坚持 ${quitDays.value} 天,你很了不起`
})
+
+const healthMilestones = computed(() => [
+ { days: 1, label: '1天' },
+ { days: 7, label: '1周' },
+ { days: 30, label: '1月' },
+ { days: 90, label: '3月' },
+ { days: 365, label: '1年' }
+])
+
const todayCountPercent = computed(() => {
if (!dailyTarget.value || dailyTarget.value <= 0) return todayCount.value > 0 ? 100 : 0
const percent = Math.round((todayCount.value / dailyTarget.value) * 100)
return Math.min(Math.max(percent, 0), 100)
})
+
const todayCountRingStyle = computed(() => {
const angle = Math.round(todayCountPercent.value * 3.6)
return {
background: `conic-gradient(#3fcba2 0deg ${angle}deg, #d8eee6 ${angle}deg 360deg)`
}
})
-const quitProgressRingStyle = computed(() => {
- const angle = Math.round(healthProgress.value * 3.6)
- return {
- background: `conic-gradient(#14b882 0deg ${angle}deg, #e8edf2 ${angle}deg 360deg)`
+
+// 记录模式健康数据计算
+const recordSavedMoney = computed(() => {
+ // 基于今日比目标少抽的数量计算
+ const saved = Math.max(0, (dailyTarget.value - todayCount.value) * (packPriceYuan.value / 20))
+ return saved.toFixed(1)
+})
+
+const recordLifeSaved = computed(() => {
+ // 每少抽一支烟延长约11分钟生命
+ const minutes = Math.max(0, (dailyTarget.value - todayCount.value) * 11)
+ return Math.round(minutes / 60 * 10) / 10
+})
+
+const totalResistedCount = computed(() => {
+ // 从首页数据获取累计忍住次数
+ return homeSummary.value.total_resisted ?? resistedCount.value
+})
+
+const reducePercent = computed(() => {
+ if (dailyTarget.value <= 0) return 0
+ const reduced = Math.max(0, dailyTarget.value - todayCount.value)
+ return Math.round((reduced / dailyTarget.value) * 100)
+})
+
+const recordHealthTip = computed(() => {
+ if (todayCount.value === 0) {
+ return '今天还没抽烟,继续保持!'
}
+ if (todayCount.value < dailyTarget.value) {
+ return `今天比目标少抽了${dailyTarget.value - todayCount.value}根,很棒!`
+ }
+ if (todayCount.value === dailyTarget.value) {
+ return '今天已达到目标,加油!'
+ }
+ return '今天超标了,明天继续努力'
})
function defaultQuitState() {
@@ -468,71 +610,72 @@ onShareAppMessage(() => ({
.page {
min-height: 100vh;
position: relative;
- background: #F4F6F8;
+ background: #F8FAFB;
overflow: hidden;
box-sizing: border-box;
}
/* ===== 背景装饰 ===== */
-.bg-orb {
+.bg-gradient {
position: absolute;
- border-radius: 50%;
- pointer-events: none;
- filter: blur(80rpx);
-}
-
-.bg-orb-1 {
- width: 600rpx;
+ top: 0;
+ left: 0;
+ right: 0;
height: 600rpx;
- top: -100rpx;
- left: -150rpx;
- background: radial-gradient(circle, rgba(20, 184, 130, 0.1) 0%, transparent 70%);
+ background: linear-gradient(180deg, #E6F7F1 0%, #F0FDF9 40%, #F8FAFB 100%);
+ pointer-events: none;
}
-.bg-orb-2 {
- width: 500rpx;
- height: 500rpx;
- top: 300rpx;
- right: -180rpx;
- background: radial-gradient(circle, rgba(99, 102, 241, 0.08) 0%, transparent 70%);
-}
-
-.bg-orb-3 {
- width: 400rpx;
- height: 400rpx;
- bottom: 200rpx;
- left: 50rpx;
- background: radial-gradient(circle, rgba(20, 184, 130, 0.06) 0%, transparent 70%);
-}
-
-.bg-grid {
+.bg-pattern {
position: absolute;
- inset: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 400rpx;
background-image:
- linear-gradient(rgba(0,0,0,0.02) 1rpx, transparent 1rpx),
- linear-gradient(90deg, rgba(0,0,0,0.02) 1rpx, transparent 1rpx);
- background-size: 80rpx 80rpx;
+ radial-gradient(circle at 20% 30%, rgba(26, 163, 122, 0.08) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(52, 200, 160, 0.06) 0%, transparent 40%);
pointer-events: none;
}
/* ===== 骨架屏 ===== */
.skeleton-wrap {
- padding: 32rpx 28rpx;
+ padding: 48rpx 32rpx;
position: relative;
z-index: 1;
}
-.sk-header, .sk-hero, .sk-card, .sk-action {
- background: linear-gradient(90deg, #ebebeb 25%, #f5f5f5 50%, #ebebeb 75%);
+.sk-circle {
+ width: 280rpx;
+ height: 280rpx;
+ border-radius: 50%;
+ background: linear-gradient(90deg, #E5E7EB 25%, #F3F4F6 50%, #E5E7EB 75%);
background-size: 200% 100%;
- animation: shimmer 1.6s infinite;
- border-radius: 24rpx;
+ animation: shimmer 1.5s infinite;
+ margin: 0 auto 48rpx;
}
-.sk-hero { height: 320rpx; margin-bottom: 24rpx; border-radius: 36rpx; }
-.sk-card { height: 120rpx; border-radius: 28rpx; margin-bottom: 24rpx; }
-.sk-card-wide { height: 140rpx; }
-.sk-action { height: 140rpx; border-radius: 28rpx; }
+.sk-row {
+ height: 32rpx;
+ border-radius: 16rpx;
+ background: linear-gradient(90deg, #E5E7EB 25%, #F3F4F6 50%, #E5E7EB 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+ margin-bottom: 24rpx;
+}
+
+.sk-row-short {
+ width: 60%;
+}
+
+.sk-card {
+ height: 160rpx;
+ border-radius: 24rpx;
+ background: linear-gradient(90deg, #E5E7EB 25%, #F3F4F6 50%, #E5E7EB 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+ margin-top: 32rpx;
+}
@keyframes shimmer {
0% { background-position: -200% 0; }
@@ -540,15 +683,312 @@ onShareAppMessage(() => ({
}
/* ===== 主内容 ===== */
-.nav-placeholder, .dashboard, .skeleton-wrap {
+.nav-placeholder, .content {
position: relative;
z-index: 1;
}
-.dashboard {
- padding: 20rpx 20rpx 180rpx;
+.content {
+ padding: 24rpx 24rpx 200rpx;
}
+/* ===== 戒烟模式 - 英雄卡片 ===== */
+.hero-card {
+ background: linear-gradient(135deg, #FFFFFF 0%, #F0FDF9 100%);
+ border-radius: 32rpx;
+ padding: 48rpx 32rpx;
+ margin-bottom: 24rpx;
+ box-shadow: 0 8rpx 32rpx rgba(26, 163, 122, 0.08);
+ border: 1rpx solid rgba(26, 163, 122, 0.1);
+}
+
+.days-display {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-bottom: 40rpx;
+}
+
+.days-ring {
+ width: 200rpx;
+ height: 200rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #D1FAE5 0%, #A7F3D0 100%);
+ padding: 16rpx;
+ box-shadow: 0 8rpx 24rpx rgba(26, 163, 122, 0.15);
+}
+
+.days-ring-inner {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background: #FFFFFF;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ box-shadow: inset 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
+}
+
+.days-number {
+ font-size: 72rpx;
+ font-weight: 800;
+ color: #059669;
+ line-height: 1;
+ font-family: 'DIN Alternate', -apple-system, sans-serif;
+}
+
+.days-label {
+ font-size: 28rpx;
+ color: #6B7280;
+ margin-top: 4rpx;
+}
+
+.days-subtitle {
+ font-size: 28rpx;
+ color: #6B7280;
+ margin-top: 16rpx;
+}
+
+/* ===== 数据指标行 ===== */
+.metrics-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ padding: 24rpx 0;
+ background: rgba(26, 163, 122, 0.04);
+ border-radius: 20rpx;
+}
+
+.metric-item {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+}
+
+.metric-icon {
+ font-size: 32rpx;
+}
+
+.metric-content {
+ display: flex;
+ flex-direction: column;
+}
+
+.metric-value {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #111827;
+ font-family: 'DIN Alternate', -apple-system, sans-serif;
+}
+
+.metric-label {
+ font-size: 22rpx;
+ color: #9CA3AF;
+}
+
+.metric-divider {
+ width: 1rpx;
+ height: 48rpx;
+ background: rgba(26, 163, 122, 0.15);
+}
+
+/* ===== 打卡区域 ===== */
+.checkin-section {
+ margin-bottom: 24rpx;
+}
+
+.checkin-card {
+ display: flex;
+ align-items: center;
+ padding: 32rpx;
+ background: #FFFFFF;
+ border-radius: 24rpx;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+ border: 2rpx solid rgba(26, 163, 122, 0.1);
+}
+
+.checkin-done {
+ background: linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%);
+ border-color: rgba(26, 163, 122, 0.2);
+}
+
+.checkin-icon-wrap {
+ margin-right: 24rpx;
+}
+
+.checkin-icon {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #059669 0%, #047857 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4rpx 12rpx rgba(5, 150, 105, 0.3);
+}
+
+.checkin-icon text {
+ font-size: 36rpx;
+ color: #FFFFFF;
+ font-weight: 600;
+}
+
+.checkin-icon-done {
+ background: linear-gradient(135deg, #10B981 0%, #059669 100%);
+}
+
+.checkin-content {
+ flex: 1;
+}
+
+.checkin-title {
+ display: block;
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #111827;
+}
+
+.checkin-desc {
+ display: block;
+ font-size: 24rpx;
+ color: #6B7280;
+ margin-top: 4rpx;
+}
+
+.checkin-badge {
+ padding: 8rpx 20rpx;
+ background: #059669;
+ border-radius: 999rpx;
+}
+
+.checkin-badge-text {
+ font-size: 24rpx;
+ font-weight: 600;
+ color: #FFFFFF;
+}
+
+/* ===== 健康恢复进度 ===== */
+.health-section {
+ background: #FFFFFF;
+ border-radius: 24rpx;
+ padding: 32rpx;
+ margin-bottom: 24rpx;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20rpx;
+}
+
+.section-title {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #374151;
+}
+
+.section-value {
+ font-size: 28rpx;
+ font-weight: 700;
+ color: #059669;
+}
+
+.health-bar {
+ height: 12rpx;
+ background: #E5E7EB;
+ border-radius: 999rpx;
+ overflow: hidden;
+ margin-bottom: 24rpx;
+}
+
+.health-bar-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #10B981 0%, #059669 100%);
+ border-radius: 999rpx;
+ transition: width 0.5s ease;
+}
+
+.health-milestones {
+ display: flex;
+ justify-content: space-between;
+}
+
+.milestone-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8rpx;
+}
+
+.milestone-dot {
+ width: 16rpx;
+ height: 16rpx;
+ border-radius: 50%;
+ background: #E5E7EB;
+ border: 2rpx solid #D1D5DB;
+}
+
+.milestone-done .milestone-dot {
+ background: #10B981;
+ border-color: #059669;
+}
+
+.milestone-text {
+ font-size: 20rpx;
+ color: #9CA3AF;
+}
+
+.milestone-done .milestone-text {
+ color: #059669;
+ font-weight: 500;
+}
+
+/* ===== 今日激励 ===== */
+.motivation-section {
+ margin-bottom: 24rpx;
+}
+
+.motivation-card {
+ background: linear-gradient(135deg, #FFFFFF 0%, #FEF3C7 100%);
+ border-radius: 24rpx;
+ padding: 32rpx;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+}
+
+.motivation-quote {
+ position: absolute;
+ top: 16rpx;
+ left: 24rpx;
+ font-size: 64rpx;
+ color: rgba(245, 158, 11, 0.2);
+ font-family: Georgia, serif;
+ line-height: 1;
+}
+
+.motivation-text {
+ display: block;
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #92400E;
+ line-height: 1.6;
+ padding-left: 32rpx;
+}
+
+.motivation-tip {
+ display: block;
+ font-size: 24rpx;
+ color: #B45309;
+ margin-top: 16rpx;
+ padding-left: 32rpx;
+ line-height: 1.5;
+}
+
+/* ===== 记录模式 ===== */
.summary-card {
background: rgba(255, 255, 255, 0.85);
-webkit-backdrop-filter: blur(20px);
@@ -556,7 +996,6 @@ onShareAppMessage(() => ({
border-radius: 40rpx;
border: 2rpx solid rgba(255, 255, 255, 0.9);
padding: 40rpx 36rpx;
- margin-bottom: 32rpx;
box-shadow: 0 24rpx 48rpx rgba(20, 184, 130, 0.05), inset 0 2rpx 8rpx rgba(255, 255, 255, 1);
position: relative;
overflow: hidden;
@@ -621,57 +1060,6 @@ onShareAppMessage(() => ({
color: #123329;
}
-.summary-subtitle {
- display: block;
- margin-top: 10rpx;
- font-size: 24rpx;
- line-height: 1.5;
- color: #7b8a84;
-}
-
-.summary-subtitle-record {
- margin-top: 8rpx;
- color: #5b8d7a;
-}
-
-.summary-mode-badge {
- padding: 12rpx 20rpx;
- border-radius: 999rpx;
- flex-shrink: 0;
-}
-
-.summary-mode-badge-quit {
- background: rgba(20, 184, 130, 0.1);
- border: 1px solid rgba(20, 184, 130, 0.15);
-}
-
-.summary-mode-badge-record {
- background: rgba(255, 255, 255, 0.7);
- border: 1rpx solid rgba(77, 144, 119, 0.2);
-}
-
-.summary-mode-text {
- font-size: 22rpx;
- font-weight: 700;
- color: #14b882;
-}
-
-.summary-card-record .summary-mode-text {
- color: #2a7b61;
-}
-
-.summary-main {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 20rpx;
-}
-
-.summary-main-record {
- align-items: stretch;
- gap: 18rpx;
-}
-
.record-overview {
display: flex;
align-items: center;
@@ -803,14 +1191,6 @@ onShareAppMessage(() => ({
color: #52776a;
}
-.summary-metric-note {
- display: block;
- margin-top: 8rpx;
- font-size: 22rpx;
- color: #999999;
- line-height: 1.5;
-}
-
.record-group-note {
display: block;
margin-top: 12rpx;
@@ -823,61 +1203,6 @@ onShareAppMessage(() => ({
border-radius: 16rpx;
}
-.primary-row {
- display: flex;
- gap: 20rpx;
- margin-bottom: 28rpx;
-}
-
-.primary-row-double {
- display: grid;
- grid-template-columns: 1fr 1fr;
-}
-
-.primary-pill {
- flex: 1;
- min-height: 120rpx;
- padding: 24rpx 32rpx;
- border-radius: 36rpx;
- display: flex;
- flex-direction: column;
- justify-content: center;
- background: rgba(255, 255, 255, 0.9);
- -webkit-backdrop-filter: blur(10px);
- backdrop-filter: blur(10px);
- border: 2rpx solid rgba(255, 255, 255, 1);
- box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.03);
- transition: all 0.2s ease;
-}
-
-.primary-pill:active {
- transform: scale(0.98);
-}
-
-.primary-pill-checkin {
- background: linear-gradient(135deg, #14b882 0%, #0ca674 100%);
- border: none;
- box-shadow: 0 16rpx 32rpx rgba(20, 184, 130, 0.25);
-}
-
-.primary-pill-done {
- background: rgba(255, 255, 255, 0.8);
- border: 2rpx solid rgba(255, 255, 255, 0.9);
- box-shadow: none;
-}
-
-.primary-pill-smoke {
- background: linear-gradient(135deg, #4f46e5 0%, #3730a3 100%);
- border: none;
- box-shadow: 0 16rpx 32rpx rgba(79, 70, 229, 0.25);
-}
-
-.primary-pill-resist {
- background: linear-gradient(135deg, #14b882 0%, #0ca674 100%);
- border: none;
- box-shadow: 0 16rpx 32rpx rgba(20, 184, 130, 0.25);
-}
-
.record-action-row {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -885,7 +1210,7 @@ onShareAppMessage(() => ({
margin-top: 32rpx;
}
-.record-action-row .primary-pill {
+.primary-pill {
min-height: 96rpx;
padding: 0 20rpx;
align-items: center;
@@ -897,25 +1222,19 @@ onShareAppMessage(() => ({
backdrop-filter: blur(10px);
border: 2rpx solid rgba(255, 255, 255, 1);
box-shadow: 0 12rpx 28rpx rgba(83, 157, 128, 0.1);
+ display: flex;
}
-.record-action-row .primary-pill-smoke {
+.primary-pill-smoke {
background: linear-gradient(180deg, rgba(255, 242, 240, 0.9) 0%, rgba(255, 224, 218, 0.9) 100%);
border-color: rgba(255, 255, 255, 0.8);
}
-.record-action-row .primary-pill-resist {
+.primary-pill-resist {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(241, 255, 248, 0.95) 100%);
}
.primary-pill-title {
- display: block;
- font-size: 32rpx;
- font-weight: 800;
- color: #ffffff;
-}
-
-.record-action-row .primary-pill-title {
font-size: 28rpx;
font-weight: 700;
line-height: 1;
@@ -929,128 +1248,110 @@ onShareAppMessage(() => ({
color: #1f755e;
}
-.primary-pill-desc {
- display: block;
- margin-top: 8rpx;
- font-size: 24rpx;
- line-height: 1.5;
- color: rgba(255, 255, 255, 0.85);
- font-weight: 500;
+/* ===== 记录模式 - 健康数据卡片 ===== */
+.health-data-card {
+ background: linear-gradient(135deg, #FFFFFF 0%, #F0FDF9 100%);
+ border-radius: 24rpx;
+ padding: 32rpx;
+ margin-top: 24rpx;
+ box-shadow: 0 8rpx 24rpx rgba(26, 163, 122, 0.08);
+ border: 1rpx solid rgba(26, 163, 122, 0.1);
}
-.primary-pill-done .primary-pill-title {
- color: #666666;
+.health-data-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ margin-bottom: 28rpx;
}
-.primary-pill-done .primary-pill-desc {
- color: #aaaaaa;
-}
-
-/* ===== 提示卡 ===== */
-.tip-card {
- padding: 36rpx 32rpx;
- border-radius: 36rpx;
- background: rgba(255, 255, 255, 0.85);
- -webkit-backdrop-filter: blur(20px);
- backdrop-filter: blur(20px);
- border: 2rpx solid rgba(255, 255, 255, 0.9);
- box-shadow: 0 12rpx 32rpx rgba(20, 184, 130, 0.04);
- position: relative;
- overflow: hidden;
-}
-
-.tip-card::after {
- content: '"';
- position: absolute;
- top: -10rpx;
- right: 30rpx;
- font-size: 140rpx;
- color: rgba(20, 184, 130, 0.06);
- font-family: Georgia, serif;
- line-height: 1;
- pointer-events: none;
-}
-
-.tip-title {
- display: inline-block;
- padding: 6rpx 16rpx;
- border-radius: 12rpx;
- background: rgba(20, 184, 130, 0.1);
- font-size: 22rpx;
+.health-data-title {
+ font-size: 30rpx;
font-weight: 700;
- color: #14b882;
- letter-spacing: 1rpx;
- margin-bottom: 20rpx;
+ color: #111827;
}
-.tip-text {
- display: block;
- font-size: 28rpx;
- font-weight: 600;
- color: #333333;
- line-height: 1.6;
-}
-
-.tip-extra {
- display: block;
- margin-top: 12rpx;
+.health-data-subtitle {
font-size: 24rpx;
- line-height: 1.5;
- color: #888888;
+ color: #9CA3AF;
}
-/* 补充缺失的戒烟模式统同样式 */
-.summary-metrics {
+.health-metrics {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+}
+
+.health-metric-item {
display: flex;
- flex-direction: column;
- gap: 24rpx;
- flex: 1;
+ align-items: center;
+ gap: 16rpx;
+ padding: 20rpx;
+ background: rgba(26, 163, 122, 0.04);
+ border-radius: 16rpx;
}
-.summary-metric {
- display: flex;
- flex-direction: column;
- gap: 8rpx;
-}
-
-.summary-ring {
- width: 180rpx;
- height: 180rpx;
- padding: 12rpx;
- border-radius: 50%;
+.health-metric-icon {
+ width: 56rpx;
+ height: 56rpx;
+ border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
- flex-shrink: 0;
- box-shadow: inset 0 2rpx 8rpx rgba(0,0,0,0.02), 0 4rpx 12rpx rgba(255,255,255,0.8);
- background: rgba(20, 184, 130, 0.05);
+ font-size: 28rpx;
}
-.summary-ring-inner {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: #ffffff;
- box-shadow: 0 4rpx 16rpx rgba(20, 184, 130, 0.08);
+.health-metric-icon-money {
+ background: linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%);
+}
+
+.health-metric-icon-time {
+ background: linear-gradient(135deg, #DBEAFE 0%, #BFDBFE 100%);
+}
+
+.health-metric-icon-resist {
+ background: linear-gradient(135deg, #D1FAE5 0%, #A7F3D0 100%);
+}
+
+.health-metric-icon-reduce {
+ background: linear-gradient(135deg, #FCE7F3 0%, #FBCFE8 100%);
+}
+
+.health-metric-content {
display: flex;
flex-direction: column;
- align-items: center;
- justify-content: center;
- text-align: center;
}
-.summary-ring-value {
- font-size: 32rpx;
- line-height: 1.2;
- font-weight: 800;
- color: #14b882;
+.health-metric-value {
+ font-size: 28rpx;
+ font-weight: 700;
+ color: #111827;
font-family: 'DIN Alternate', -apple-system, sans-serif;
}
-.summary-ring-label {
- margin-top: 6rpx;
- font-size: 20rpx;
- font-weight: 500;
- color: #7b8a84;
+.health-metric-label {
+ font-size: 22rpx;
+ color: #6B7280;
+ margin-top: 2rpx;
+}
+
+.health-tip-bar {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ margin-top: 24rpx;
+ padding: 16rpx 20rpx;
+ background: rgba(26, 163, 122, 0.06);
+ border-radius: 12rpx;
+}
+
+.health-tip-icon {
+ font-size: 24rpx;
+}
+
+.health-tip-text {
+ font-size: 24rpx;
+ color: #059669;
+ line-height: 1.5;
}
diff --git a/src/pages/onboarding/index.vue b/src/pages/onboarding/index.vue
index 5112883..2e19426 100644
--- a/src/pages/onboarding/index.vue
+++ b/src/pages/onboarding/index.vue
@@ -1,23 +1,20 @@
-
- {{ step }} / {{ totalSteps }}
-
-
-
-
+ 完善你的信息
+ 帮助我们更好地为你服务
-
-
- 使用模式
+
+
+
+ 使用模式
{{ item.label }}
@@ -26,28 +23,28 @@
-
- 你每天抽多少支烟?
- {{ baselineDesc }}
-
-
- -
- {{ formData.baseline_cigs_per_day }}
- +
+
+
+
+ 每天抽烟
+
+ -
+ {{ formData.baseline_cigs_per_day }}
+ +
+ 支/天
- 支/天
-
- 你的烟龄是多久?
- 了解你的吸烟历史有助于更好地帮助你
-
+
+
+ 烟龄
+
{{ option.label }}
@@ -55,15 +52,15 @@
-
- {{ motivationTitle }}
- {{ motivationDesc }}
-
+
+
+ {{ formData.mode === 'quit' ? '戒烟原因' : '记录原因' }}(可多选)
+
{{ option }}
@@ -71,18 +68,18 @@
-
- 你通常什么时候起床和睡觉?
- 我们会在你的休息时间避免打扰你
+
+
+ 作息时间
- 起床时间
+ 起床
{{ formData.wake_up_time }}
- 睡觉时间
+ 睡觉
{{ formData.sleep_time }}
@@ -90,29 +87,30 @@
-
- 每包烟多少钱?
- 我们会帮你计算省下的钱
-
-
- ¥
+
+
+
+ 每包烟价格
+
+ ¥
+ 元/包
- 元/包
-
+
+
+
@@ -130,9 +128,8 @@ const userStore = useUserStore()
const { waitForLogin } = useLogin()
const navBarHeight = ref(0)
-const step = ref(1)
-const totalSteps = 5
const modeSaving = ref(false)
+
const modeOptions = [
{ value: 'quit', label: '戒烟打卡', desc: '按天记录今天没抽' },
{ value: 'record', label: '记录抽烟', desc: '按支数记录变化' }
@@ -142,7 +139,7 @@ const formData = ref({
mode: 'record',
baseline_cigs_per_day: 10,
smoking_years: 5,
- quit_motivations: [],
+ quit_motivations: ['身体健康'],
smoke_motivations: [],
wake_up_time: '07:30',
sleep_time: '23:00',
@@ -151,20 +148,14 @@ const formData = ref({
const priceYuan = ref('25')
-const progressWidth = computed(() => `${(step.value / totalSteps) * 100}%`)
-const currentMode = computed(() => formData.value.mode || userStore.mode || 'record')
-const isRecordMode = computed(() => currentMode.value === 'record')
-const baselineDesc = computed(() => isRecordMode.value ? '这会成为你后续记录和统计的基线' : '这将帮助我们为你制定更合适的戒烟节奏')
-const motivationTitle = computed(() => isRecordMode.value ? '你为什么想先开始记录抽烟?' : '你为什么想戒烟?')
-const motivationDesc = computed(() => isRecordMode.value ? '选择最符合你当前状态的原因(可多选)' : '选择对你最重要的原因(可多选)')
-const finishButtonText = computed(() => isRecordMode.value ? '开始记录之旅' : '开始戒烟之旅')
+const submitButtonText = computed(() => formData.value.mode === 'quit' ? '开始戒烟之旅' : '开始记录之旅')
const smokingYearsOptions = [
- { label: '少于1年', value: 1 },
+ { label: '<1年', value: 1 },
{ label: '1-3年', value: 2 },
{ label: '3-5年', value: 4 },
{ label: '5-10年', value: 7 },
- { label: '10年以上', value: 15 }
+ { label: '>10年', value: 15 }
]
const quitMotivationOptions = [
@@ -217,18 +208,7 @@ function onSleepTimeChange(e) {
formData.value.sleep_time = e.detail.value
}
-function prevStep() {
- if (step.value > 1) {
- step.value--
- }
-}
-
-async function nextStep() {
- if (step.value < totalSteps) {
- step.value++
- return
- }
-
+async function handleSubmit() {
formData.value.pack_price_cent = Math.round(parseFloat(priceYuan.value || '0') * 100)
try {
@@ -255,6 +235,7 @@ onMounted(async () => {
} catch (e) {
navBarHeight.value = statusBarH + 44
}
+
await waitForLogin()
try {
const profileData = await profileStore.fetchProfile()
@@ -265,7 +246,9 @@ onMounted(async () => {
mode: profile.mode || userStore.mode || formData.value.mode,
baseline_cigs_per_day: profile.baseline_cigs_per_day || formData.value.baseline_cigs_per_day,
smoking_years: profile.smoking_years || formData.value.smoking_years,
- quit_motivations: Array.isArray(profile.quit_motivations) ? profile.quit_motivations : formData.value.quit_motivations,
+ quit_motivations: Array.isArray(profile.quit_motivations) && profile.quit_motivations.length > 0
+ ? profile.quit_motivations
+ : formData.value.quit_motivations,
smoke_motivations: Array.isArray(profile.smoke_motivations) ? profile.smoke_motivations : formData.value.smoke_motivations,
wake_up_time: profile.wake_up_time || formData.value.wake_up_time,
sleep_time: profile.sleep_time || formData.value.sleep_time,
@@ -302,61 +285,48 @@ onShareAppMessage(() => {
}
.nav-area {
- padding-left: 32rpx;
- padding-right: 32rpx;
+ padding: 24rpx 32rpx;
background: transparent;
}
-.nav-row {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 12rpx 0;
+.nav-title {
+ display: block;
+ font-size: 40rpx;
+ font-weight: 700;
+ color: #111827;
}
-.step-indicator {
- font-size: 24rpx;
- font-weight: 600;
- color: #667085;
- background-color: rgba(255, 255, 255, 0.76);
- padding: 6rpx 24rpx;
- border-radius: 999rpx;
-}
-
-.progress-bar {
- height: 6rpx;
- background-color: rgba(255, 255, 255, 0.5);
- border-radius: 999rpx;
+.nav-subtitle {
+ display: block;
margin-top: 8rpx;
-}
-
-.progress-fill {
- height: 100%;
- background: linear-gradient(90deg, #32c59d, #1aa37a);
- border-radius: 999rpx;
- transition: width 0.3s ease;
+ font-size: 26rpx;
+ color: #6B7280;
}
.content {
flex: 1;
- padding: 0 48rpx;
- display: flex;
- flex-direction: column;
- justify-content: center;
+ padding: 0 32rpx;
}
-.mode-section {
+.form-section {
margin-bottom: 40rpx;
}
-.mode-section-label {
+.section-label {
display: block;
margin-bottom: 16rpx;
- font-size: 24rpx;
+ font-size: 28rpx;
font-weight: 600;
- color: #667085;
+ color: #374151;
}
+.section-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+/* Mode switch */
.mode-switch {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -365,219 +335,159 @@ onShareAppMessage(() => {
.mode-switch-item {
padding: 24rpx;
- border-radius: 24rpx;
+ border-radius: 20rpx;
background: rgba(255, 255, 255, 0.8);
border: 2rpx solid rgba(255, 255, 255, 0.66);
- box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.06);
- backdrop-filter: blur(24rpx);
- -webkit-backdrop-filter: blur(24rpx);
+ box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.05);
}
.mode-switch-item-active {
- background: rgba(255, 255, 255, 0.92);
- border-color: rgba(255, 255, 255, 0.78);
+ background: rgba(255, 255, 255, 0.96);
+ border-color: rgba(26, 163, 122, 0.3);
+ box-shadow: 0 8rpx 24rpx rgba(26, 163, 122, 0.1);
}
.mode-switch-title {
display: block;
font-size: 28rpx;
- font-weight: 700;
+ font-weight: 600;
color: #111827;
}
.mode-switch-desc {
display: block;
- margin-top: 10rpx;
+ margin-top: 8rpx;
font-size: 22rpx;
- line-height: 1.5;
+ line-height: 1.4;
color: #6b7280;
}
-.step {
- animation: fadeIn 0.3s ease;
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(20rpx);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-.step-title {
- font-size: 44rpx;
- font-weight: 700;
- color: #111827;
- display: block;
- margin-bottom: 16rpx;
- line-height: 1.3;
-}
-
-.step-desc {
- font-size: 28rpx;
- color: #6B7280;
- display: block;
- margin-bottom: 56rpx;
-}
-
-.input-group {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 24rpx;
-}
-
-.input-row {
+/* Inline input */
+.inline-input {
display: flex;
align-items: center;
- gap: 48rpx;
-}
-
-.input-btn {
- width: 96rpx;
- height: 96rpx;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.86);
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 48rpx;
- color: #1aa37a;
- border: 2rpx solid rgba(255, 255, 255, 0.72);
- box-shadow: 0 10rpx 24rpx rgba(15, 23, 42, 0.06);
-}
-
-.input-value {
- font-size: 96rpx;
- font-weight: 700;
- color: #111827;
- min-width: 160rpx;
- text-align: center;
-}
-
-.input-unit {
- font-size: 28rpx;
- color: #6B7280;
-}
-
-.options {
- display: flex;
- flex-direction: column;
gap: 16rpx;
}
-.options-wrap {
- flex-direction: row;
- flex-wrap: wrap;
+.inline-btn {
+ width: 56rpx;
+ height: 56rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.9);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ color: #1aa37a;
+ border: 2rpx solid rgba(255, 255, 255, 0.72);
+ box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.06);
}
-.option {
- padding: 28rpx 36rpx;
- background-color: rgba(255, 255, 255, 0.84);
- border-radius: 16rpx;
- font-size: 30rpx;
+.inline-value {
+ font-size: 36rpx;
+ font-weight: 700;
color: #111827;
+ min-width: 48rpx;
+ text-align: center;
+}
+
+.inline-unit {
+ font-size: 26rpx;
+ color: #6B7280;
+}
+
+.inline-field {
+ width: 100rpx;
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #111827;
+ text-align: center;
+ background: rgba(255, 255, 255, 0.9);
+ padding: 12rpx 16rpx;
+ border-radius: 12rpx;
border: 2rpx solid rgba(255, 255, 255, 0.72);
- box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
+}
+
+/* Options row */
+.options-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx;
}
.option-tag {
- padding: 20rpx 28rpx;
- border-radius: 32rpx;
+ padding: 16rpx 28rpx;
+ border-radius: 999rpx;
+ background: rgba(255, 255, 255, 0.84);
+ font-size: 26rpx;
+ color: #374151;
+ border: 2rpx solid rgba(255, 255, 255, 0.72);
+ box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
}
-.option-active {
- background-color: rgba(255, 255, 255, 0.96);
- border-color: rgba(255, 255, 255, 0.82);
+.option-tag-active {
+ background: rgba(26, 163, 122, 0.12);
+ border-color: rgba(26, 163, 122, 0.3);
color: #1a7f61;
}
+/* Time row */
.time-row {
display: flex;
- gap: 32rpx;
+ gap: 24rpx;
}
-.time-item { flex: 1; }
+.time-item {
+ flex: 1;
+}
.time-label {
- font-size: 26rpx;
+ font-size: 24rpx;
color: #6B7280;
display: block;
- margin-bottom: 12rpx;
+ margin-bottom: 8rpx;
}
.time-picker {
- background-color: rgba(255, 255, 255, 0.86);
- padding: 32rpx;
- border-radius: 16rpx;
- font-size: 40rpx;
+ background: rgba(255, 255, 255, 0.9);
+ padding: 20rpx 24rpx;
+ border-radius: 12rpx;
+ font-size: 32rpx;
+ font-weight: 600;
color: #111827;
text-align: center;
border: 2rpx solid rgba(255, 255, 255, 0.72);
- box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
+ box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
}
-.price-input {
- display: flex;
- align-items: center;
- background-color: rgba(255, 255, 255, 0.86);
- padding: 24rpx 32rpx;
- border-radius: 16rpx;
- gap: 8rpx;
- border: 2rpx solid rgba(255, 255, 255, 0.72);
- box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
-}
-
-.price-prefix {
- font-size: 48rpx;
- color: #9CA3AF;
-}
-
-.price-field {
- font-size: 64rpx;
- font-weight: 700;
- color: #111827;
- width: 200rpx;
- text-align: center;
+.bottom-space {
+ height: 160rpx;
}
+/* Footer */
.footer {
- display: flex;
- gap: 24rpx;
- padding: 32rpx 48rpx;
- padding-bottom: 64rpx;
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 24rpx 32rpx;
+ padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+ background: linear-gradient(180deg, transparent 0%, rgba(248, 250, 252, 0.95) 40%);
}
.btn-primary {
- flex: 1;
height: 96rpx;
background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
- font-size: 32rpx;
- font-weight: 600;
- color: #FFFFFF;
box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.22);
}
-.btn-full { flex: 1; }
-
-.btn-secondary {
- height: 96rpx;
- padding: 0 48rpx;
- background-color: rgba(255, 255, 255, 0.86);
- border-radius: 48rpx;
- display: flex;
- align-items: center;
- justify-content: center;
+.btn-text {
font-size: 32rpx;
- color: #111827;
- border: 2rpx solid rgba(15, 23, 42, 0.08);
+ font-weight: 600;
+ color: #FFFFFF;
}