Files
smt/pages/onboarding/index.vue
T

404 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<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 option-tag"
: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"
placeholder-style="color: #6B7280"
/>
</view>
<text class="input-unit">/</text>
</view>
</view>
</view>
<view class="footer">
<view v-if="step > 1" class="btn-secondary" @tap="prevStep">上一步</view>
<view class="btn-primary" :class="{ 'btn-full': step === 1 }" @tap="nextStep">
{{ step === 5 ? '开始戒烟之旅 🚀' : '下一步' }}
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useProfileStore } from '@/stores/profile'
import { useLogin } from '@/hooks/useLogin'
const profileStore = useProfileStore()
const { waitForLogin } = useLogin()
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' })
}
}
onMounted(async () => {
await waitForLogin()
})
</script>
<style scoped>
.page {
min-height: 100vh;
background-color: #0D1F17;
display: flex;
flex-direction: column;
}
.progress-bar {
height: 8rpx;
background-color: #1A3325;
}
.progress-fill {
height: 100%;
background-color: #4ADE80;
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: 44rpx;
font-weight: 700;
color: #FFFFFF;
display: block;
margin-bottom: 16rpx;
line-height: 1.3;
}
.step-desc {
font-size: 28rpx;
color: #9CA3AF;
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: #1A3325;
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
color: #4ADE80;
}
.input-value {
font-size: 96rpx;
font-weight: 700;
color: #FFFFFF;
min-width: 160rpx;
text-align: center;
}
.input-unit {
font-size: 28rpx;
color: #9CA3AF;
}
.options {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.options-wrap {
flex-direction: row;
flex-wrap: wrap;
}
.option {
padding: 28rpx 36rpx;
background-color: #1A3325;
border-radius: 16rpx;
font-size: 30rpx;
color: #FFFFFF;
border: 2rpx solid transparent;
}
.option-tag {
padding: 20rpx 28rpx;
border-radius: 32rpx;
}
.option-active {
background-color: rgba(74, 222, 128, 0.15);
border-color: #4ADE80;
color: #4ADE80;
}
.time-row {
display: flex;
gap: 32rpx;
}
.time-item { flex: 1; }
.time-label {
font-size: 26rpx;
color: #9CA3AF;
display: block;
margin-bottom: 12rpx;
}
.time-picker {
background-color: #1A3325;
padding: 32rpx;
border-radius: 16rpx;
font-size: 40rpx;
color: #FFFFFF;
text-align: center;
}
.price-input {
display: flex;
align-items: center;
background-color: #1A3325;
padding: 24rpx 32rpx;
border-radius: 16rpx;
gap: 8rpx;
}
.price-prefix {
font-size: 48rpx;
color: #9CA3AF;
}
.price-field {
font-size: 64rpx;
font-weight: 700;
color: #FFFFFF;
width: 200rpx;
text-align: center;
}
.footer {
display: flex;
gap: 24rpx;
padding: 32rpx 48rpx;
padding-bottom: 64rpx;
background-color: #0D1F17;
}
.btn-primary {
flex: 1;
height: 96rpx;
background-color: #4ADE80;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
color: #0D1F17;
}
.btn-full { flex: 1; }
.btn-secondary {
height: 96rpx;
padding: 0 48rpx;
background-color: #1A3325;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #FFFFFF;
border: 2rpx solid #374151;
}
</style>