|
|
@@ -0,0 +1,201 @@
|
|
|
+<template>
|
|
|
+ <view class="page">
|
|
|
+ <view class="nav-bar">
|
|
|
+ <view class="nav-back" @tap="goBack">
|
|
|
+ <text class="back-text">‹ 返回</text>
|
|
|
+ </view>
|
|
|
+ <text class="nav-title">饮食推荐</text>
|
|
|
+ <text class="nav-edit" @tap="toggleEdit">{{ isEditing ? '完成' : '编辑' }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view class="filter-bar" scroll-x>
|
|
|
+ <view class="filter-item" v-for="(cat, idx) in categories" :key="idx"
|
|
|
+ :class="'filter-' + (selectedCategory === cat ? 'active' : '')"
|
|
|
+ @tap="selectedCategory = cat">
|
|
|
+ <text>{{ cat }}</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <view class="sort-bar">
|
|
|
+ <text class="sort-text" @tap="sortDesc = !sortDesc">排序: {{ sortDesc ? '推荐↓' : '推荐↑' }}</text>
|
|
|
+ <text class="sort-text" @tap="showRecommendedOnly = !showRecommendedOnly">{{ showRecommendedOnly ? '全部' : '仅推荐' }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view class="content" scroll-y>
|
|
|
+ <view class="food-card" v-for="(item, idx) in filteredFoods" :key="idx">
|
|
|
+ <view class="food-header">
|
|
|
+ <text class="food-name">{{ item.foodName || item.name }}</text>
|
|
|
+ <view class="food-cat-tag">
|
|
|
+ <text>{{ item.category }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="food-detail-row" v-if="!isEditing">
|
|
|
+ <text class="food-nutrition">蛋白{{ item.protein || '--' }} | 脂肪{{ item.fat || '--' }} | 碳水{{ item.carb || '--' }} | 纤维{{ item.fiber || '--' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="food-detail-row" v-if="!isEditing && item.energy">
|
|
|
+ <text class="food-nutrition">能量 {{ item.energy }}KJ</text>
|
|
|
+ </view>
|
|
|
+ <view class="food-edit-row" v-if="isEditing">
|
|
|
+ <text class="edit-label">推荐指数:</text>
|
|
|
+ <input class="edit-input" type="number" v-model="item.score" />
|
|
|
+ </view>
|
|
|
+ <view class="food-score" :class="(item.score || 0) >= 5 ? 'score-good' : 'score-normal'">
|
|
|
+ <text>推荐: {{ item.score }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="empty-hint" v-if="filteredFoods.length === 0">暂无数据</view>
|
|
|
+ <view class="bottom-spacer"></view>
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <view class="bottom-bar" v-if="isEditing">
|
|
|
+ <view class="btn-cancel" @tap="cancelEdit">取消</view>
|
|
|
+ <view class="btn-save" @tap="saveEdit">保存</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getReportDetail, editHealthReport, getKnowledgeSection } from '../../utils/api.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ reportId: null,
|
|
|
+ reportData: null,
|
|
|
+ foods: [],
|
|
|
+ isEditing: false,
|
|
|
+ editFoods: [],
|
|
|
+ selectedCategory: '全部',
|
|
|
+ sortDesc: true,
|
|
|
+ showRecommendedOnly: false,
|
|
|
+ categories: ['全部', '主食', '蔬菜', '水果', '肉类', '其它']
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ filteredFoods: function() {
|
|
|
+ var list = this.isEditing ? this.editFoods : this.foods
|
|
|
+ if (this.selectedCategory !== '全部') {
|
|
|
+ list = list.filter(function(item) {
|
|
|
+ return (item.category || '') === this.selectedCategory
|
|
|
+ }.bind(this))
|
|
|
+ }
|
|
|
+ if (this.showRecommendedOnly) {
|
|
|
+ list = list.filter(function(item) {
|
|
|
+ return (item.score || 0) >= 5
|
|
|
+ })
|
|
|
+ }
|
|
|
+ list = [].concat(list)
|
|
|
+ list.sort(function(a, b) {
|
|
|
+ var sa = a.score || 0
|
|
|
+ var sb = b.score || 0
|
|
|
+ return this.sortDesc ? sb - sa : sa - sb
|
|
|
+ }.bind(this))
|
|
|
+ return list
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad: function(options) {
|
|
|
+ this.reportId = options.reportId ? parseInt(options.reportId) : null
|
|
|
+ if (this.reportId) {
|
|
|
+ this.loadData()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ goBack: function() {
|
|
|
+ uni.navigateBack()
|
|
|
+ },
|
|
|
+ loadData: function() {
|
|
|
+ var self = this
|
|
|
+ Promise.all([
|
|
|
+ getReportDetail(this.reportId),
|
|
|
+ getKnowledgeSection('食物库')
|
|
|
+ ]).then(function(results) {
|
|
|
+ var reportRes = results[0]
|
|
|
+ var kbRes = results[1]
|
|
|
+
|
|
|
+ // 构建 KB 食物 map
|
|
|
+ var kbFoodMap = {}
|
|
|
+ if (kbRes.code === 200 && kbRes.data && kbRes.data['数据']) {
|
|
|
+ var kbFoods = kbRes.data['数据']
|
|
|
+ for (var i = 0; i < kbFoods.length; i++) {
|
|
|
+ var f = kbFoods[i]
|
|
|
+ kbFoodMap[f['名称']] = f
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reportRes.code === 200 && reportRes.data) {
|
|
|
+ self.reportData = reportRes.data
|
|
|
+ var reportFoods = reportRes.data.foods || []
|
|
|
+ // 合并 KB 营养数据(个人 JSON 只保存名称和推荐指数,营养数据从 KB 查)
|
|
|
+ self.foods = reportFoods.map(function(item) {
|
|
|
+ var name = item.foodName || item.name
|
|
|
+ var kb = kbFoodMap[name] || {}
|
|
|
+ return {
|
|
|
+ foodName: name,
|
|
|
+ name: name,
|
|
|
+ score: item.score,
|
|
|
+ category: item.category || kb['分类'] || '其它',
|
|
|
+ protein: kb['蛋白g'] || item.protein,
|
|
|
+ fat: kb['脂肪g'] || item.fat,
|
|
|
+ carb: kb['碳水化合物g'] || item.carb,
|
|
|
+ fiber: kb['总膳食纤维g'] || item.fiber,
|
|
|
+ energy: kb['能量KJ'] || item.energy
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ toggleEdit: function() {
|
|
|
+ this.isEditing = true
|
|
|
+ this.editFoods = JSON.parse(JSON.stringify(this.foods))
|
|
|
+ },
|
|
|
+ cancelEdit: function() {
|
|
|
+ this.isEditing = false
|
|
|
+ this.editFoods = []
|
|
|
+ },
|
|
|
+ saveEdit: function() {
|
|
|
+ var self = this
|
|
|
+ editHealthReport(self.reportId, { foods: self.editFoods }, 0).then(function(res) {
|
|
|
+ if (res.code === 200) {
|
|
|
+ uni.showToast({ title: '已保存', icon: 'success' })
|
|
|
+ self.isEditing = false
|
|
|
+ self.loadData()
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '保存失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.page { min-height: 100vh; background: #f8f8f8; }
|
|
|
+.nav-bar { display: flex; align-items: center; padding: 20rpx 30rpx; background: #fff; position: relative; }
|
|
|
+.nav-back { padding: 10rpx 0; }
|
|
|
+.back-text { font-size: 28rpx; color: #4A9BD7; }
|
|
|
+.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 600; }
|
|
|
+.nav-edit { font-size: 28rpx; color: #4A9BD7; padding: 10rpx 0; }
|
|
|
+.filter-bar { background: #fff; padding: 10rpx 20rpx; white-space: nowrap; }
|
|
|
+.filter-item { display: inline-block; padding: 12rpx 28rpx; margin: 0 10rpx; border-radius: 30rpx; font-size: 26rpx; background: #f0f0f0; color: #666; }
|
|
|
+.filter-active { background: #4A9BD7; color: #fff; }
|
|
|
+.sort-bar { display: flex; justify-content: space-between; padding: 16rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; }
|
|
|
+.sort-text { font-size: 24rpx; color: #999; }
|
|
|
+.content { padding: 20rpx 30rpx; height: calc(100vh - 200rpx); }
|
|
|
+.food-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
|
|
|
+.food-header { display: flex; align-items: center; margin-bottom: 12rpx; }
|
|
|
+.food-name { font-size: 30rpx; font-weight: 600; color: #333; flex: 1; }
|
|
|
+.food-cat-tag { padding: 4rpx 16rpx; background: #f0f0f0; border-radius: 20rpx; font-size: 22rpx; color: #666; }
|
|
|
+.food-detail-row { margin-bottom: 6rpx; }
|
|
|
+.food-nutrition { font-size: 24rpx; color: #999; }
|
|
|
+.food-edit-row { display: flex; align-items: center; margin-bottom: 6rpx; }
|
|
|
+.edit-label { font-size: 26rpx; color: #666; margin-right: 12rpx; }
|
|
|
+.edit-input { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 8rpx 12rpx; font-size: 26rpx; width: 120rpx; }
|
|
|
+.food-score { margin-top: 8rpx; font-size: 24rpx; }
|
|
|
+.score-good { color: #10B981; }
|
|
|
+.score-normal { color: #999; }
|
|
|
+.empty-hint { text-align: center; color: #ccc; padding: 60rpx 0; font-size: 28rpx; }
|
|
|
+.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; gap: 20rpx; }
|
|
|
+.btn-cancel { flex: 1; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #666; }
|
|
|
+.btn-save { flex: 1; padding: 20rpx; background: #4A9BD7; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #fff; }
|
|
|
+.bottom-spacer { height: 40rpx; }
|
|
|
+</style>
|