This commit is contained in:
nepiedg
2026-01-25 11:45:16 +08:00
commit c883ae7b17
44 changed files with 5945 additions and 0 deletions
+362
View File
@@ -0,0 +1,362 @@
<template>
<view class="page">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progressWidth }"></view>
</view>
<view class="content">
<view v-if="step === 1" class="step">
<text class="step-title">你每天抽多少支烟</text>
<text class="step-desc">这将帮助我们为你制定个性化的戒烟计划</text>
<view class="input-group">
<view class="input-row">
<view class="input-btn" @tap="decreaseCigs">-</view>
<text class="input-value">{{ formData.baseline_cigs_per_day }}</text>
<view class="input-btn" @tap="increaseCigs">+</view>
</view>
<text class="input-unit">/</text>
</view>
</view>
<view v-if="step === 2" class="step">
<text class="step-title">你的烟龄是多久</text>
<text class="step-desc">了解你的吸烟历史有助于更好地帮助你</text>
<view class="options">
<view
v-for="option in smokingYearsOptions"
:key="option.value"
class="option"
:class="{ 'option-active': formData.smoking_years === option.value }"
@tap="formData.smoking_years = option.value"
>
{{ option.label }}
</view>
</view>
</view>
<view v-if="step === 3" class="step">
<text class="step-title">你为什么想戒烟</text>
<text class="step-desc">选择对你最重要的原因可多选</text>
<view class="options options-wrap">
<view
v-for="option in quitMotivationOptions"
:key="option"
class="option"
:class="{ 'option-active': formData.quit_motivations.includes(option) }"
@tap="toggleMotivation(option)"
>
{{ option }}
</view>
</view>
</view>
<view v-if="step === 4" class="step">
<text class="step-title">你通常什么时候起床和睡觉</text>
<text class="step-desc">我们会在你的休息时间避免打扰你</text>
<view class="time-row">
<view class="time-item">
<text class="time-label">起床时间</text>
<picker mode="time" :value="formData.wake_up_time" @change="onWakeTimeChange">
<view class="time-picker">{{ formData.wake_up_time }}</view>
</picker>
</view>
<view class="time-item">
<text class="time-label">睡觉时间</text>
<picker mode="time" :value="formData.sleep_time" @change="onSleepTimeChange">
<view class="time-picker">{{ formData.sleep_time }}</view>
</picker>
</view>
</view>
</view>
<view v-if="step === 5" class="step">
<text class="step-title">每包烟多少钱</text>
<text class="step-desc">我们会帮你计算省下的钱</text>
<view class="input-group">
<view class="price-input">
<text class="price-prefix">¥</text>
<input
type="digit"
v-model="priceYuan"
class="price-field"
placeholder="0"
/>
</view>
<text class="input-unit">/</text>
</view>
</view>
</view>
<view class="footer safe-area-bottom">
<view v-if="step > 1" class="btn btn-secondary" @tap="prevStep">上一步</view>
<view class="btn btn-primary flex-1" @tap="nextStep">
{{ step === 5 ? '开始戒烟之旅' : '下一步' }}
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useProfileStore } from '@/stores/profile'
const profileStore = useProfileStore()
const step = ref(1)
const totalSteps = 5
const formData = ref({
baseline_cigs_per_day: 10,
smoking_years: 5,
quit_motivations: [],
smoke_motivations: [],
wake_up_time: '07:30',
sleep_time: '23:00',
pack_price_cent: 2500
})
const priceYuan = ref('25')
const progressWidth = computed(() => `${(step.value / totalSteps) * 100}%`)
const smokingYearsOptions = [
{ label: '少于1年', value: 1 },
{ label: '1-3年', value: 2 },
{ label: '3-5年', value: 4 },
{ label: '5-10年', value: 7 },
{ label: '10年以上', value: 15 }
]
const quitMotivationOptions = [
'身体健康',
'家人孩子',
'省钱',
'形象气质',
'工作需要',
'伴侣要求'
]
function increaseCigs() {
formData.value.baseline_cigs_per_day++
}
function decreaseCigs() {
if (formData.value.baseline_cigs_per_day > 1) {
formData.value.baseline_cigs_per_day--
}
}
function toggleMotivation(option) {
const index = formData.value.quit_motivations.indexOf(option)
if (index > -1) {
formData.value.quit_motivations.splice(index, 1)
} else {
formData.value.quit_motivations.push(option)
}
}
function onWakeTimeChange(e) {
formData.value.wake_up_time = e.detail.value
}
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
}
formData.value.pack_price_cent = Math.round(parseFloat(priceYuan.value || '0') * 100)
try {
uni.showLoading({ title: '保存中...' })
await profileStore.saveProfile(formData.value)
uni.hideLoading()
uni.switchTab({ url: '/pages/index/index' })
} catch (e) {
uni.hideLoading()
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background-color: var(--color-bg);
display: flex;
flex-direction: column;
}
.progress-bar {
height: 8rpx;
background-color: var(--color-bg-card);
}
.progress-fill {
height: 100%;
background-color: var(--color-primary);
transition: width 0.3s ease;
}
.content {
flex: 1;
padding: 64rpx 48rpx;
}
.step {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(20rpx);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.step-title {
font-size: 48rpx;
font-weight: 700;
display: block;
margin-bottom: 16rpx;
}
.step-desc {
font-size: 28rpx;
color: var(--color-text-secondary);
display: block;
margin-bottom: 64rpx;
}
.input-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
}
.input-row {
display: flex;
align-items: center;
gap: 48rpx;
}
.input-btn {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background-color: var(--color-bg-card);
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
color: var(--color-primary);
}
.input-value {
font-size: 96rpx;
font-weight: 700;
min-width: 160rpx;
text-align: center;
}
.input-unit {
font-size: 28rpx;
color: var(--color-text-secondary);
}
.options {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.options-wrap {
flex-direction: row;
flex-wrap: wrap;
}
.option {
padding: 24rpx 32rpx;
background-color: var(--color-bg-card);
border-radius: 16rpx;
font-size: 30rpx;
border: 2rpx solid transparent;
}
.options-wrap .option {
flex: 0 0 auto;
}
.option-active {
background-color: rgba(74, 222, 128, 0.1);
border-color: var(--color-primary);
color: var(--color-primary);
}
.time-row {
display: flex;
gap: 32rpx;
}
.time-item {
flex: 1;
}
.time-label {
font-size: 26rpx;
color: var(--color-text-secondary);
display: block;
margin-bottom: 12rpx;
}
.time-picker {
background-color: var(--color-bg-card);
padding: 32rpx;
border-radius: 16rpx;
font-size: 40rpx;
text-align: center;
}
.price-input {
display: flex;
align-items: center;
background-color: var(--color-bg-card);
padding: 24rpx 32rpx;
border-radius: 16rpx;
gap: 8rpx;
}
.price-prefix {
font-size: 48rpx;
color: var(--color-text-secondary);
}
.price-field {
font-size: 64rpx;
font-weight: 700;
width: 200rpx;
text-align: center;
}
.footer {
display: flex;
gap: 24rpx;
padding: 32rpx 48rpx;
background-color: var(--color-bg);
}
</style>