|
|
@@ -0,0 +1,242 @@
|
|
|
+<template>
|
|
|
+ <view class="preference-page">
|
|
|
+ <!-- 月食品预算 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="label">月食品预算(元)</text>
|
|
|
+ <slider :value="budget" :min="500" :max="10000" :step="100" show-value @change="onBudgetChange" />
|
|
|
+ <text class="hint">当前: {{ budget }}元/月</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 口味偏好 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="label">口味偏好</text>
|
|
|
+ <checkbox-group @change="onTasteChange">
|
|
|
+ <view class="checkbox-row">
|
|
|
+ <label class="checkbox-label" v-for="item in tasteOptions" :key="item">
|
|
|
+ <checkbox :value="item" :checked="tastePreferences.indexOf(item) >= 0" />
|
|
|
+ <text class="checkbox-text">{{ item }}</text>
|
|
|
+ </label>
|
|
|
+ </view>
|
|
|
+ </checkbox-group>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 菜系偏好 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="label">偏好菜系</text>
|
|
|
+ <picker :value="cuisineIndex" :range="cuisineList" @change="onCuisineChange">
|
|
|
+ <view class="picker-value">{{ cuisineList[cuisineIndex] || '请选择' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 每日餐数 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="label">每日餐数</text>
|
|
|
+ <picker :value="mealIndex" :range="mealList" @change="onMealCountChange">
|
|
|
+ <view class="picker-value">{{ mealList[mealIndex] || '请选择' }}</view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 烹饪能力 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="label">烹饪能力</text>
|
|
|
+ <radio-group @change="onCookingChange">
|
|
|
+ <view class="radio-row">
|
|
|
+ <label class="radio-label" v-for="item in cookingOptions" :key="item.value">
|
|
|
+ <radio :value="item.value" :checked="cookingAbility === item.value" />
|
|
|
+ <text class="radio-text">{{ item.label }}</text>
|
|
|
+ </label>
|
|
|
+ </view>
|
|
|
+ </radio-group>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <button class="save-btn" type="primary" :loading="saving" @click="save" :disabled="saving">
|
|
|
+ {{ saving ? '保存中...' : '保存设置' }}
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getNutritionProfile, saveNutritionProfile } from '@/utils/api'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'MealPreference',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ budget: 2000,
|
|
|
+ tastePreferences: [],
|
|
|
+ tasteOptions: ['清淡', '微辣', '甜', '酸', '咸', '鲜'],
|
|
|
+ cuisineIndex: 0,
|
|
|
+ cuisineList: ['中式', '粤菜', '川菜', '西式', '日式', '其他'],
|
|
|
+ mealIndex: 1,
|
|
|
+ mealList: ['2餐', '3餐', '4餐', '5餐'],
|
|
|
+ cookingAbility: 'simple',
|
|
|
+ cookingOptions: [
|
|
|
+ { value: 'simple', label: '简单(只会基础)' },
|
|
|
+ { value: 'medium', label: '一般(能炒家常菜)' },
|
|
|
+ { value: 'advanced', label: '较强(能做大菜)' }
|
|
|
+ ],
|
|
|
+ saving: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad() {
|
|
|
+ this.loadCurrentPreferences()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async loadCurrentPreferences() {
|
|
|
+ try {
|
|
|
+ var res = await getNutritionProfile({})
|
|
|
+ if (res.code === 0 && res.data) {
|
|
|
+ var profile = res.data
|
|
|
+ if (profile.budgetMonthly) {
|
|
|
+ this.budget = profile.budgetMonthly
|
|
|
+ }
|
|
|
+ if (profile.familyTastePreferences) {
|
|
|
+ try {
|
|
|
+ var parsed = JSON.parse(profile.familyTastePreferences)
|
|
|
+ if (Array.isArray(parsed)) {
|
|
|
+ this.tastePreferences = parsed
|
|
|
+ } else {
|
|
|
+ this.tastePreferences = [String(parsed)]
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.tastePreferences = profile.familyTastePreferences.split(',')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (profile.cuisineStyle) {
|
|
|
+ var idx = this.cuisineList.indexOf(profile.cuisineStyle)
|
|
|
+ if (idx >= 0) {
|
|
|
+ this.cuisineIndex = idx
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (profile.mealCount) {
|
|
|
+ var mealIdx = profile.mealCount - 2
|
|
|
+ if (mealIdx >= 0 && mealIdx < this.mealList.length) {
|
|
|
+ this.mealIndex = mealIdx
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (profile.cookingAbility) {
|
|
|
+ this.cookingAbility = profile.cookingAbility
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ // 首次加载无偏好,使用默认值
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onBudgetChange(e) {
|
|
|
+ this.budget = e.detail.value
|
|
|
+ },
|
|
|
+ onTasteChange(e) {
|
|
|
+ this.tastePreferences = e.detail.value
|
|
|
+ },
|
|
|
+ onCuisineChange(e) {
|
|
|
+ this.cuisineIndex = e.detail.value
|
|
|
+ },
|
|
|
+ onMealCountChange(e) {
|
|
|
+ this.mealIndex = e.detail.value
|
|
|
+ },
|
|
|
+ onCookingChange(e) {
|
|
|
+ this.cookingAbility = e.detail.value
|
|
|
+ },
|
|
|
+ async save() {
|
|
|
+ this.saving = true
|
|
|
+ try {
|
|
|
+ var params = {
|
|
|
+ budgetMonthly: this.budget,
|
|
|
+ familyTastePreferences: JSON.stringify(this.tastePreferences),
|
|
|
+ cuisineStyle: this.cuisineList[this.cuisineIndex],
|
|
|
+ mealCount: this.mealIndex + 2,
|
|
|
+ cookingAbility: this.cookingAbility
|
|
|
+ }
|
|
|
+ var res = await saveNutritionProfile(params)
|
|
|
+ if (res.code === 0) {
|
|
|
+ uni.showToast({ title: '保存成功', icon: 'success' })
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '保存失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.saving = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.preference-page {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: var(--bg, #FFF7ED);
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
+}
|
|
|
+.form-section {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 30rpx;
|
|
|
+ box-shadow: var(--shadow-md, 0 2rpx 12rpx rgba(0,0,0,0.06));
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.label {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ display: block;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.hint {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: var(--color-primary, #F97316);
|
|
|
+ margin-top: 10rpx;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.picker-value {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: var(--color-primary, #F97316);
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ background: #FFF0E0;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+.checkbox-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.checkbox-label {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.checkbox-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-left: 8rpx;
|
|
|
+}
|
|
|
+.radio-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.radio-label {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.radio-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-left: 8rpx;
|
|
|
+}
|
|
|
+.save-btn {
|
|
|
+ margin: 30rpx 0;
|
|
|
+ background: var(--color-primary, #F97316);
|
|
|
+ border: none;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ font-size: 32rpx;
|
|
|
+ height: 88rpx;
|
|
|
+ line-height: 88rpx;
|
|
|
+}
|
|
|
+</style>
|