Просмотр исходного кода

merge(C2): 饮食打卡餐次多选

Sisyphus 2 недель назад
Родитель
Сommit
9c4965741a

+ 9 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthMealController.java

@@ -225,7 +225,15 @@ public class HealthMealController {
             record.setMemberId(currentMemberId);
         }
         if (record.getMemberId() == null) return Result.error("memberId不能为空");
-        if (record.getMealType() == null || record.getMealType().trim().isEmpty()) return Result.error("请选择餐次");
+        // 餐次支持逗号拼接多选(如 "breakfast,lunch"):拆分 trim 去空后至少一项;
+        // 归一化后回写,保证入库值不含多余空格与空段(meal_type VARCHAR(32) 够用,无需迁移)
+        String mealTypeRaw = record.getMealType() == null ? "" : record.getMealType();
+        String mealType = Arrays.stream(mealTypeRaw.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.joining(","));
+        if (mealType.isEmpty()) return Result.error("请选择餐次");
+        record.setMealType(mealType);
         // 内容校验:文字(食物清单/备注)、图片、语音 至少有一项即可
         boolean hasContent = (record.getFoodItems() != null && !record.getFoodItems().trim().isEmpty())
                 || (record.getRemark() != null && !record.getRemark().trim().isEmpty())

+ 14 - 1
cfc-frontend/pages/diet/record-detail.vue

@@ -14,7 +14,7 @@
           <text class="card-title">基本信息</text>
           <view class="info-row">
             <text class="info-label">餐次</text>
-            <text class="info-value">{{ record.mealType }}</text>
+            <text class="info-value">{{ mealLabel(record.mealType) }}</text>
           </view>
           <view class="info-row">
             <text class="info-label">记录方式</text>
@@ -137,6 +137,19 @@ export default {
       var m = d.getMinutes().toString().padStart(2, '0')
       return h + ':' + m
     },
+    mealLabel: function(t) {
+      var m = { breakfast: '早餐', lunch: '午餐', dinner: '晚餐', snack: '加餐' }
+      if (!t) return '饮食'
+      // 兼容逗号拼接多餐次(breakfast,lunch → 早餐、午餐);未知值原样输出
+      var parts = String(t).split(',')
+      var labels = []
+      for (var i = 0; i < parts.length; i++) {
+        var v = parts[i] && parts[i].trim()
+        if (!v) continue
+        labels.push(m[v] || v)
+      }
+      return labels.length > 0 ? labels.join('、') : '饮食'
+    },
     formatDate: function(date) {
       var d = date
       var m = (d.getMonth() + 1).toString().padStart(2, '0')

+ 11 - 2
cfc-frontend/pages/diet/records.vue

@@ -144,7 +144,7 @@
         </view>
         <view class="record-card" v-for="r in records" :key="r.id" @tap="goDetail(r)">
           <view class="record-card-header">
-            <view class="meal-badge" :class="'meal-' + r.mealType">
+            <view class="meal-badge" :class="'meal-' + r.mealType.split(',')[0]">
               <text>{{ mealLabel(r.mealType) }}</text>
             </view>
             <view class="record-meta">
@@ -219,7 +219,16 @@ export default {
     goBack: function() { uni.navigateBack() },
     mealLabel: function(t) {
       var m = { breakfast: '早餐', lunch: '午餐', dinner: '晚餐', snack: '加餐' }
-      return m[t] || t || '饮食'
+      if (!t) return '饮食'
+      // 兼容逗号拼接多餐次(breakfast,lunch → 早餐、午餐);未知值原样输出
+      var parts = String(t).split(',')
+      var labels = []
+      for (var i = 0; i < parts.length; i++) {
+        var v = parts[i] && parts[i].trim()
+        if (!v) continue
+        labels.push(m[v] || v)
+      }
+      return labels.length > 0 ? labels.join('、') : '饮食'
     },
     // 统一组件选中回调:member 已由 memberNormalize 规范化,携带 canonical memberId
     onMemberSelect: function(member) {

+ 10 - 1
cfc-frontend/pages/growth-main/index.vue

@@ -410,7 +410,16 @@ export default {
     },
     mealTypeLabel: function(type) {
       var m = { breakfast: '早餐', lunch: '午餐', dinner: '晚餐', snack: '加餐', extra: '零食' }
-      return m[type] || type || '饮食'
+      if (!type) return '饮食'
+      // 兼容逗号拼接多餐次(breakfast,lunch → 早餐、午餐);未知值原样输出
+      var parts = String(type).split(',')
+      var labels = []
+      for (var i = 0; i < parts.length; i++) {
+        var v = parts[i] && parts[i].trim()
+        if (!v) continue
+        labels.push(m[v] || v)
+      }
+      return labels.length > 0 ? labels.join('、') : '饮食'
     },
     async loadAllData() {
       var self = this

+ 34 - 7
cfc-frontend/pages/growth/diet-checkin.vue

@@ -19,13 +19,13 @@
       <view class="card">
         <view class="card-header">
           <text class="card-icon">🍽️</text>
-          <text class="card-title">选择餐次</text>
+          <text class="card-title">选择餐次(可多选)</text>
         </view>
         <view class="card-body">
           <view class="tag-row">
             <TagChip v-for="m in mealTypes" :key="m.value" :label="m.label"
-              :active="form.mealType === m.value"
-              @select="form.mealType = m.value" />
+              :active="form.mealTypes.indexOf(m.value) > -1"
+              @select="toggleMealType(m.value)" />
           </view>
         </view>
       </view>
@@ -194,7 +194,7 @@ export default {
         { label: '加餐', value: 'snack' }
       ],
       form: {
-        mealType: '',
+        mealTypes: [],
         foodItems: '',
         remark: '',
         photos: [],
@@ -207,6 +207,18 @@ export default {
     }
   },
   computed: {
+    // 餐次多选 → 逗号拼接字符串(后端契约不变:mealType 仍为单值 String,VARCHAR(32) 够用)
+    // 固定按早餐→午餐→晚餐→加餐输出,避免点击顺序影响 records 页首项样式语义
+    mealType: function() {
+      var arr = this.form.mealTypes || []
+      if (arr.length <= 1) return arr.join(',')
+      var order = { breakfast: 0, lunch: 1, dinner: 2, snack: 3 }
+      return arr.slice().sort(function(a, b) {
+        var ia = order[a] === undefined ? 9 : order[a]
+        var ib = order[b] === undefined ? 9 : order[b]
+        return ia - ib
+      }).join(',')
+    },
     recordDurationText: function() {
       if (!this.form.voicePath) return ''
       var s = this.recordSeconds
@@ -280,9 +292,24 @@ export default {
       return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日 周' + weekdays[d.getDay()]
     },
     goBack: function() { uni.navigateBack() },
+    // 餐次 toggle 多选:已选则移除,未选则追加
+    toggleMealType: function(value) {
+      var arr = this.form.mealTypes
+      var i = arr.indexOf(value)
+      if (i > -1) { arr.splice(i, 1) } else { arr.push(value) }
+    },
     mealTypeLabel: function(type) {
       var m = { breakfast: '早餐', lunch: '午餐', dinner: '晚餐', snack: '加餐' }
-      return m[type] || type || '饮食'
+      if (!type) return '饮食'
+      // 兼容逗号拼接多餐次(breakfast,lunch → 早餐、午餐);未知值原样输出
+      var parts = String(type).split(',')
+      var labels = []
+      for (var i = 0; i < parts.length; i++) {
+        var v = parts[i] && parts[i].trim()
+        if (!v) continue
+        labels.push(m[v] || v)
+      }
+      return labels.length > 0 ? labels.join('、') : '饮食'
     },
     formatDate: function(dateStr) {
       if (!dateStr) return ''
@@ -448,7 +475,7 @@ export default {
     },
     submitCheckin: async function() {
       var self = this
-      if (!self.form.mealType) { uni.showToast({ title: '请选择餐次', icon: 'none' }); return }
+      if (!self.form.mealTypes || self.form.mealTypes.length === 0) { uni.showToast({ title: '请选择餐次', icon: 'none' }); return }
       // 内容校验:文字(食物清单/备注)、图片、语音 至少有一项
       var hasText = (self.form.foodItems && self.form.foodItems.trim()) || (self.form.remark && self.form.remark.trim())
       var hasPhoto = self.form.photos && self.form.photos.length > 0
@@ -475,7 +502,7 @@ export default {
           data: {
             memberId: self.memberId,
             taskId: self.taskId || undefined,
-            mealType: self.form.mealType,
+            mealType: self.mealType,
             foodItems: self.form.foodItems || '',
             remark: self.form.remark || '',
             photoUrls: photoUrls.join(','),