|
@@ -1,15 +1,22 @@
|
|
|
<template>
|
|
<template>
|
|
|
<view class="page">
|
|
<view class="page">
|
|
|
- <!-- 搜索框 -->
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <view class="member-bar" v-if="memberList.length > 1">
|
|
|
|
|
+ <view class="member-tab" :class="{'active': currentMemberId === (item.id || item.userId)}"
|
|
|
|
|
+ v-for="item in memberList" :key="item.id || item.userId"
|
|
|
|
|
+ @tap="switchMember(item)">
|
|
|
|
|
+ <text class="member-name">{{ item.nickname || item.roleLabel || '全部' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
<view class="search-bar">
|
|
<view class="search-bar">
|
|
|
<view class="search-input-wrap">
|
|
<view class="search-input-wrap">
|
|
|
<text class="search-icon">🔍</text>
|
|
<text class="search-icon">🔍</text>
|
|
|
- <input class="search-input" v-model="searchText" placeholder="搜索食材名称,查看推荐值" confirm-type="search" />
|
|
|
|
|
|
|
+ <input class="search-input" v-model="searchText" placeholder="搜索食材名称" confirm-type="search" />
|
|
|
<text class="search-clear" v-if="searchText" @tap="searchText = ''">✕</text>
|
|
<text class="search-clear" v-if="searchText" @tap="searchText = ''">✕</text>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 分类筛选 + 排序 -->
|
|
|
|
|
<view class="filter-bar">
|
|
<view class="filter-bar">
|
|
|
<scroll-view class="cat-scroll" scroll-x>
|
|
<scroll-view class="cat-scroll" scroll-x>
|
|
|
<view class="cat-list">
|
|
<view class="cat-list">
|
|
@@ -32,38 +39,54 @@
|
|
|
</view>
|
|
</view>
|
|
|
</scroll-view>
|
|
</scroll-view>
|
|
|
<view class="sort-btn" @tap="toggleSort">
|
|
<view class="sort-btn" @tap="toggleSort">
|
|
|
- <text class="sort-btn-text">{{ sortDesc ? '↓ 推荐值从高到低' : '↑ 推荐值从低到高' }}</text>
|
|
|
|
|
|
|
+ <text class="sort-btn-text">{{ sortDesc ? '↓ 从高到低' : '↑ 从低到高' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
<scroll-view class="content" scroll-y>
|
|
<scroll-view class="content" scroll-y>
|
|
|
<view
|
|
<view
|
|
|
class="food-card"
|
|
class="food-card"
|
|
|
|
|
+ :class="item.bandClass"
|
|
|
v-for="(item, idx) in filteredFoods"
|
|
v-for="(item, idx) in filteredFoods"
|
|
|
- :key="idx"
|
|
|
|
|
- :class="'score-band-' + _scoreBand(item.score)"
|
|
|
|
|
|
|
+ :key="getItemKey(item)"
|
|
|
@tap="showFoodKnowledge(item)"
|
|
@tap="showFoodKnowledge(item)"
|
|
|
>
|
|
>
|
|
|
<view class="food-header">
|
|
<view class="food-header">
|
|
|
<text class="food-name">{{ item.foodName || item.name }}</text>
|
|
<text class="food-name">{{ item.foodName || item.name }}</text>
|
|
|
- <view class="food-cat-tag">
|
|
|
|
|
- <text>{{ item.category }}</text>
|
|
|
|
|
|
|
+ <view class="food-right">
|
|
|
|
|
+ <view class="status-tag" :class="item.statusClass">{{ item.statusLabel }}</view>
|
|
|
|
|
+ <text class="food-score-badge" :class="item.scoreClass">{{ item.score }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="food-meta">
|
|
|
|
|
+ <text class="meta-item" v-if="item.category">{{ item.category }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="score-bar-wrap">
|
|
|
|
|
+ <view class="score-bar-bg">
|
|
|
|
|
+ <view class="score-bar-fill" :class="item.barClass" :style="'width:' + item.barWidth + '%'"></view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <text class="score-val">{{ item.score }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view class="food-score-row">
|
|
|
|
|
- <text class="food-score-label">推荐值</text>
|
|
|
|
|
- <text class="food-score-value">{{ item.score }}</text>
|
|
|
|
|
|
|
+ <view class="change-row" v-if="item.changeDir">
|
|
|
|
|
+ <text class="change-label">较上次</text>
|
|
|
|
|
+ <text :class="item.changeClass">{{ item.changeText }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <mini-trend :data="item.trendPoints" v-if="item.trendPoints && item.trendPoints.length >= 3"></mini-trend>
|
|
|
|
|
+ <view class="food-nutrition" v-if="item.protein != null || item.fat != null || item.carb != null">
|
|
|
|
|
+ <text class="nut-item">蛋白 {{ item.protein }}g</text>
|
|
|
|
|
+ <text class="nut-item">脂肪 {{ item.fat }}g</text>
|
|
|
|
|
+ <text class="nut-item">碳水 {{ item.carb }}g</text>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="food-edit-row" v-if="isEditing">
|
|
<view class="food-edit-row" v-if="isEditing">
|
|
|
<text class="edit-label">推荐值:</text>
|
|
<text class="edit-label">推荐值:</text>
|
|
|
<input class="edit-input" type="number" v-model="item.score" />
|
|
<input class="edit-input" type="number" v-model="item.score" />
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
- <view class="empty-hint" v-if="filteredFoods.length === 0">暂无数据</view>
|
|
|
|
|
|
|
+ <view class="empty-hint" v-if="!loading && filteredFoods.length === 0">暂无数据</view>
|
|
|
|
|
+ <view class="loading-state" v-if="loading"><text class="loading-text">加载中...</text></view>
|
|
|
<view class="bottom-spacer"></view>
|
|
<view class="bottom-spacer"></view>
|
|
|
</scroll-view>
|
|
</scroll-view>
|
|
|
|
|
|
|
|
- <!-- 食材知识库弹窗 -->
|
|
|
|
|
<view class="knowledge-overlay" v-if="knowledgeVisible" @tap="closeKnowledge">
|
|
<view class="knowledge-overlay" v-if="knowledgeVisible" @tap="closeKnowledge">
|
|
|
<view class="knowledge-popup" @tap.stop>
|
|
<view class="knowledge-popup" @tap.stop>
|
|
|
<view class="knowledge-header">
|
|
<view class="knowledge-header">
|
|
@@ -102,9 +125,7 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <view class="edit-fab" v-if="!isEditing && reportId" @tap="toggleEdit">
|
|
|
|
|
- <text>编辑</text>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+ <view class="edit-fab" v-if="!isEditing && reportId" @tap="toggleEdit"><text>编辑</text></view>
|
|
|
<view class="bottom-bar" v-if="isEditing">
|
|
<view class="bottom-bar" v-if="isEditing">
|
|
|
<view class="btn-cancel" @tap="cancelEdit">取消</view>
|
|
<view class="btn-cancel" @tap="cancelEdit">取消</view>
|
|
|
<view class="btn-save" @tap="saveEdit">保存</view>
|
|
<view class="btn-save" @tap="saveEdit">保存</view>
|
|
@@ -113,14 +134,15 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { getHealthFoods, editHealthReport, queryFoodKnowledge } from '../../utils/api.js'
|
|
|
|
|
|
|
+import { getHealthFoods, editHealthReport, queryFoodKnowledge, getCautionFamilyMembers, getFoodCautionTrend } from '../../utils/api.js'
|
|
|
|
|
+import MiniTrend from '../growth/mini-trend.vue'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
|
|
+ components: { MiniTrend },
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
reportId: null,
|
|
reportId: null,
|
|
|
memberId: null,
|
|
memberId: null,
|
|
|
- reportData: null,
|
|
|
|
|
foods: [],
|
|
foods: [],
|
|
|
isEditing: false,
|
|
isEditing: false,
|
|
|
editFoods: [],
|
|
editFoods: [],
|
|
@@ -129,7 +151,11 @@ export default {
|
|
|
sortDesc: true,
|
|
sortDesc: true,
|
|
|
knowledgeVisible: false,
|
|
knowledgeVisible: false,
|
|
|
knowledgeData: {},
|
|
knowledgeData: {},
|
|
|
- _kbCache: {}
|
|
|
|
|
|
|
+ _kbCache: {},
|
|
|
|
|
+ loading: true,
|
|
|
|
|
+ currentMemberId: null,
|
|
|
|
|
+ memberList: [],
|
|
|
|
|
+ trendMap: {}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
computed: {
|
|
computed: {
|
|
@@ -139,10 +165,7 @@ export default {
|
|
|
var list = this.isEditing ? this.editFoods : this.foods
|
|
var list = this.isEditing ? this.editFoods : this.foods
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
for (var i = 0; i < list.length; i++) {
|
|
|
var c = list[i].category || '其它'
|
|
var c = list[i].category || '其它'
|
|
|
- if (!seen[c]) {
|
|
|
|
|
- seen[c] = true
|
|
|
|
|
- cats.push(c)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!seen[c]) { seen[c] = true; cats.push(c) }
|
|
|
}
|
|
}
|
|
|
return cats
|
|
return cats
|
|
|
},
|
|
},
|
|
@@ -161,7 +184,6 @@ export default {
|
|
|
return (item.category || '其它') === self.activeCategory
|
|
return (item.category || '其它') === self.activeCategory
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
- // 按推荐值排序(默认降序)
|
|
|
|
|
list = list.slice()
|
|
list = list.slice()
|
|
|
list.sort(function(a, b) {
|
|
list.sort(function(a, b) {
|
|
|
var sa = parseFloat(a.score) || 0
|
|
var sa = parseFloat(a.score) || 0
|
|
@@ -172,41 +194,70 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
onLoad: function(options) {
|
|
onLoad: function(options) {
|
|
|
- // 不传 reportId 时取当前用户最新报告的食材推荐
|
|
|
|
|
this.reportId = options.reportId ? parseInt(options.reportId) : null
|
|
this.reportId = options.reportId ? parseInt(options.reportId) : null
|
|
|
this.memberId = options.memberId ? parseInt(options.memberId) : null
|
|
this.memberId = options.memberId ? parseInt(options.memberId) : null
|
|
|
|
|
+ this.currentMemberId = this.memberId
|
|
|
|
|
+ this.loadMemberList()
|
|
|
this.loadData()
|
|
this.loadData()
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
- goBack: function() {
|
|
|
|
|
- uni.navigateBack()
|
|
|
|
|
|
|
+ getItemKey: function(item) { return item.foodId || item.id || item.name || Math.random() },
|
|
|
|
|
+ goBack: function() { uni.navigateBack() },
|
|
|
|
|
+ switchMember: function(item) {
|
|
|
|
|
+ this.currentMemberId = item.id || item.userId
|
|
|
|
|
+ this.loadData()
|
|
|
},
|
|
},
|
|
|
- switchCategory: function(cat) {
|
|
|
|
|
- this.activeCategory = cat
|
|
|
|
|
- },
|
|
|
|
|
- toggleSort: function() {
|
|
|
|
|
- this.sortDesc = !this.sortDesc
|
|
|
|
|
|
|
+ async loadMemberList() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ var res = await getCautionFamilyMembers()
|
|
|
|
|
+ if (res && res.code === 200 && res.data && res.data.length > 0) {
|
|
|
|
|
+ var allItem = { id: -1, userId: -1, nickname: '全部', roleLabel: '全部' }
|
|
|
|
|
+ this.memberList = [allItem].concat(res.data)
|
|
|
|
|
+ if (!this.currentMemberId) this.currentMemberId = -1
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch(e) {}
|
|
|
},
|
|
},
|
|
|
- /** 推荐值 → 背景色分级:<-30 红,>30 绿,中间每 10 换一档 */
|
|
|
|
|
- _scoreBand: function(score) {
|
|
|
|
|
- var s = parseFloat(score)
|
|
|
|
|
- if (isNaN(s)) return 'null'
|
|
|
|
|
- if (s < -30) return 'neg3'
|
|
|
|
|
- if (s < -20) return 'neg2'
|
|
|
|
|
- if (s < -10) return 'neg1'
|
|
|
|
|
- if (s < 0) return 'neg0'
|
|
|
|
|
- if (s < 10) return 'pos0'
|
|
|
|
|
- if (s < 20) return 'pos1'
|
|
|
|
|
- if (s < 30) return 'pos2'
|
|
|
|
|
- return 'pos3'
|
|
|
|
|
|
|
+ switchCategory: function(cat) { this.activeCategory = cat },
|
|
|
|
|
+ toggleSort: function() { this.sortDesc = !this.sortDesc },
|
|
|
|
|
+ _decorateItem: function(item) {
|
|
|
|
|
+ var s = item.score != null ? parseFloat(item.score) : NaN
|
|
|
|
|
+ var band, barClass, statusLabel, statusClass, scoreClass, barWidth
|
|
|
|
|
+ if (isNaN(s)) {
|
|
|
|
|
+ band = 'null'; barClass = 'mild'; statusLabel = '未知'; statusClass = 'neutral'; scoreClass = 'null'; barWidth = 50
|
|
|
|
|
+ } else if (s < -30) {
|
|
|
|
|
+ band = 'neg3'; barClass = 'danger'; statusLabel = '避免'; statusClass = 'bad'; scoreClass = 'neg3'; barWidth = 5
|
|
|
|
|
+ } else if (s < -20) {
|
|
|
|
|
+ band = 'neg2'; barClass = 'danger'; statusLabel = '避免'; statusClass = 'bad'; scoreClass = 'neg2'; barWidth = 15
|
|
|
|
|
+ } else if (s < -10) {
|
|
|
|
|
+ band = 'neg1'; barClass = 'warn'; statusLabel = '谨慎'; statusClass = 'caution'; scoreClass = 'neg1'; barWidth = 30
|
|
|
|
|
+ } else if (s < 0) {
|
|
|
|
|
+ band = 'neg0'; barClass = 'warn'; statusLabel = '少食'; statusClass = 'caution'; scoreClass = 'neg0'; barWidth = 45
|
|
|
|
|
+ } else if (s < 10) {
|
|
|
|
|
+ band = 'pos0'; barClass = 'mild'; statusLabel = '一般'; statusClass = 'ok'; scoreClass = 'pos0'; barWidth = 55
|
|
|
|
|
+ } else if (s < 20) {
|
|
|
|
|
+ band = 'pos1'; barClass = 'mild'; statusLabel = '一般'; statusClass = 'ok'; scoreClass = 'pos1'; barWidth = 65
|
|
|
|
|
+ } else if (s < 30) {
|
|
|
|
|
+ band = 'pos2'; barClass = 'safe'; statusLabel = '推荐'; statusClass = 'good'; scoreClass = 'pos2'; barWidth = 80
|
|
|
|
|
+ } else {
|
|
|
|
|
+ band = 'pos3'; barClass = 'safe'; statusLabel = '推荐'; statusClass = 'good'; scoreClass = 'pos3'; barWidth = Math.min(100, 50 + s)
|
|
|
|
|
+ }
|
|
|
|
|
+ item.bandClass = 'band-' + band
|
|
|
|
|
+ item.barClass = barClass
|
|
|
|
|
+ item.statusLabel = statusLabel
|
|
|
|
|
+ item.statusClass = 'status-' + statusClass
|
|
|
|
|
+ item.scoreClass = 'score-' + scoreClass
|
|
|
|
|
+ item.barWidth = barWidth
|
|
|
|
|
+ return item
|
|
|
},
|
|
},
|
|
|
loadData: function() {
|
|
loadData: function() {
|
|
|
var self = this
|
|
var self = this
|
|
|
- getHealthFoods(self.reportId, self.memberId).then(function(res) {
|
|
|
|
|
|
|
+ self.loading = true
|
|
|
|
|
+ getHealthFoods(self.reportId, self.currentMemberId > 0 ? self.currentMemberId : null).then(function(res) {
|
|
|
|
|
+ self.loading = false
|
|
|
if (res.code === 200 && res.data) {
|
|
if (res.code === 200 && res.data) {
|
|
|
- // 后端 Food 实体字段:name, category, score;兼容 foodName(旧 draft)
|
|
|
|
|
self.foods = (Array.isArray(res.data) ? res.data : []).map(function(item) {
|
|
self.foods = (Array.isArray(res.data) ? res.data : []).map(function(item) {
|
|
|
- return {
|
|
|
|
|
|
|
+ return self._decorateItem({
|
|
|
|
|
+ foodId: item.id,
|
|
|
foodName: item.name || item.foodName || '',
|
|
foodName: item.name || item.foodName || '',
|
|
|
name: item.name || item.foodName || '',
|
|
name: item.name || item.foodName || '',
|
|
|
score: item.score != null ? parseInt(item.score, 10) : 0,
|
|
score: item.score != null ? parseInt(item.score, 10) : 0,
|
|
@@ -215,17 +266,56 @@ export default {
|
|
|
fat: item.fat != null ? parseFloat(item.fat) : null,
|
|
fat: item.fat != null ? parseFloat(item.fat) : null,
|
|
|
carb: item.carbs != null ? parseFloat(item.carbs) : (item.carb != null ? parseFloat(item.carb) : null),
|
|
carb: item.carbs != null ? parseFloat(item.carbs) : (item.carb != null ? parseFloat(item.carb) : null),
|
|
|
fiber: item.fiber != null ? parseFloat(item.fiber) : null,
|
|
fiber: item.fiber != null ? parseFloat(item.fiber) : null,
|
|
|
- energy: item.energyKj != null ? parseFloat(item.energyKj) : (item.energy != null ? parseFloat(item.energy) : null)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ energy: item.energyKj != null ? parseFloat(item.energyKj) : (item.energy != null ? parseFloat(item.energy) : null),
|
|
|
|
|
+ changeDir: null,
|
|
|
|
|
+ changeText: '',
|
|
|
|
|
+ changeClass: '',
|
|
|
|
|
+ trendPoints: null
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
+ self.loadTrends()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.foods = []
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
|
|
+ }).catch(function() { self.loading = false })
|
|
|
|
|
+ },
|
|
|
|
|
+ loadTrends: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (!this.foods.length) return
|
|
|
|
|
+ var foodIds = this.foods.map(function(f) { return f.foodId }).filter(function(id) { return id != null })
|
|
|
|
|
+ if (!foodIds.length) return
|
|
|
|
|
+ getFoodCautionTrend(this.currentMemberId > 0 ? this.currentMemberId : null, foodIds).then(function(res) {
|
|
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
|
|
+ var trendMap = {}
|
|
|
|
|
+ var dataArray = res.data || []
|
|
|
|
|
+ for (var i = 0; i < dataArray.length; i++) trendMap[dataArray[i].foodId] = dataArray[i]
|
|
|
|
|
+ self.trendMap = trendMap
|
|
|
|
|
+ self.foods = self.foods.map(function(item) {
|
|
|
|
|
+ var t = trendMap[item.foodId]
|
|
|
|
|
+ if (!t) return item
|
|
|
|
|
+ var trendPoints = []
|
|
|
|
|
+ var pts = t.trend || []
|
|
|
|
|
+ for (var j = 0; j < pts.length; j++) trendPoints.push(pts[j].score)
|
|
|
|
|
+ var changeDir = t.changeDir || 'flat'
|
|
|
|
|
+ var changeText = ''
|
|
|
|
|
+ var changeClass = ''
|
|
|
|
|
+ if (changeDir === 'up') { changeText = '↑ 上升'; changeClass = 'change-up' }
|
|
|
|
|
+ else if (changeDir === 'down') { changeText = '↓ 下降'; changeClass = 'change-down' }
|
|
|
|
|
+ return Object.assign({}, item, {
|
|
|
|
|
+ trendPoints: trendPoints.length >= 3 ? trendPoints : null,
|
|
|
|
|
+ changeDir: changeDir,
|
|
|
|
|
+ changeText: changeText,
|
|
|
|
|
+ changeClass: changeClass
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ self.$forceUpdate()
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {})
|
|
|
},
|
|
},
|
|
|
showFoodKnowledge: function(item) {
|
|
showFoodKnowledge: function(item) {
|
|
|
var self = this
|
|
var self = this
|
|
|
var name = item.foodName || item.name || ''
|
|
var name = item.foodName || item.name || ''
|
|
|
if (!name) return
|
|
if (!name) return
|
|
|
- // 本地营养数据优先(已在列表里)
|
|
|
|
|
var local = {
|
|
var local = {
|
|
|
itemName: name,
|
|
itemName: name,
|
|
|
category: item.category || '',
|
|
category: item.category || '',
|
|
@@ -236,19 +326,13 @@ export default {
|
|
|
energy: item.energy,
|
|
energy: item.energy,
|
|
|
description: ''
|
|
description: ''
|
|
|
}
|
|
}
|
|
|
- // 有固定营养数据直接展示,否则查知识库
|
|
|
|
|
if (item.protein != null || item.fat != null || item.carbs != null) {
|
|
if (item.protein != null || item.fat != null || item.carbs != null) {
|
|
|
self.knowledgeData = local
|
|
self.knowledgeData = local
|
|
|
self.knowledgeVisible = true
|
|
self.knowledgeVisible = true
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- // 查知识库
|
|
|
|
|
var cacheKey = 'food:' + name
|
|
var cacheKey = 'food:' + name
|
|
|
- if (self._kbCache[cacheKey]) {
|
|
|
|
|
- self.knowledgeData = self._kbCache[cacheKey]
|
|
|
|
|
- self.knowledgeVisible = true
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (self._kbCache[cacheKey]) { self.knowledgeData = self._kbCache[cacheKey]; self.knowledgeVisible = true; return }
|
|
|
queryFoodKnowledge(name).then(function(res) {
|
|
queryFoodKnowledge(name).then(function(res) {
|
|
|
if (res.code === 200 && res.data) {
|
|
if (res.code === 200 && res.data) {
|
|
|
var d = res.data
|
|
var d = res.data
|
|
@@ -264,37 +348,32 @@ export default {
|
|
|
}
|
|
}
|
|
|
self._kbCache[cacheKey] = kd
|
|
self._kbCache[cacheKey] = kd
|
|
|
self.knowledgeData = kd
|
|
self.knowledgeData = kd
|
|
|
- } else {
|
|
|
|
|
- self.knowledgeData = local
|
|
|
|
|
- }
|
|
|
|
|
- self.knowledgeVisible = true
|
|
|
|
|
- }).catch(function() {
|
|
|
|
|
- self.knowledgeData = local
|
|
|
|
|
|
|
+ } else { self.knowledgeData = local }
|
|
|
self.knowledgeVisible = true
|
|
self.knowledgeVisible = true
|
|
|
- })
|
|
|
|
|
- },
|
|
|
|
|
- closeKnowledge: function() {
|
|
|
|
|
- this.knowledgeVisible = false
|
|
|
|
|
- this.knowledgeData = {}
|
|
|
|
|
- },
|
|
|
|
|
- toggleEdit: function() {
|
|
|
|
|
- this.isEditing = true
|
|
|
|
|
- this.editFoods = JSON.parse(JSON.stringify(this.foods))
|
|
|
|
|
- },
|
|
|
|
|
- cancelEdit: function() {
|
|
|
|
|
- this.isEditing = false
|
|
|
|
|
- this.editFoods = []
|
|
|
|
|
|
|
+ }).catch(function() { self.knowledgeData = local; self.knowledgeVisible = true })
|
|
|
},
|
|
},
|
|
|
|
|
+ closeKnowledge: function() { this.knowledgeVisible = false; this.knowledgeData = {} },
|
|
|
|
|
+ toggleEdit: function() { this.isEditing = true; this.editFoods = JSON.parse(JSON.stringify(this.foods)) },
|
|
|
|
|
+ cancelEdit: function() { this.isEditing = false; this.editFoods = [] },
|
|
|
saveEdit: function() {
|
|
saveEdit: function() {
|
|
|
var self = this
|
|
var self = this
|
|
|
- editHealthReport(self.reportId, { foods: self.editFoods }, 0).then(function(res) {
|
|
|
|
|
|
|
+ var clean = (this.editFoods || []).map(function(f) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: f.foodName || f.name,
|
|
|
|
|
+ score: f.score,
|
|
|
|
|
+ category: f.category,
|
|
|
|
|
+ protein: f.protein,
|
|
|
|
|
+ fat: f.fat,
|
|
|
|
|
+ carbs: f.carb,
|
|
|
|
|
+ fiber: f.fiber,
|
|
|
|
|
+ energyKj: f.energy
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ editHealthReport(self.reportId, { foods: clean }, 0).then(function(res) {
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
uni.showToast({ title: '已保存', icon: 'success' })
|
|
uni.showToast({ title: '已保存', icon: 'success' })
|
|
|
- self.isEditing = false
|
|
|
|
|
- self.loadData()
|
|
|
|
|
- } else {
|
|
|
|
|
- uni.showToast({ title: res.message || '保存失败', icon: 'none' })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ self.isEditing = false; self.loadData()
|
|
|
|
|
+ } else { uni.showToast({ title: res.message || '保存失败', icon: 'none' }) }
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -302,14 +381,38 @@ export default {
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
-.page { min-height: 100vh; background: #f8f8f8; }
|
|
|
|
|
|
|
+.page { min-height: 100vh; background: #FFF7ED; }
|
|
|
|
|
+
|
|
|
|
|
+/* 成员切换栏 */
|
|
|
|
|
+.member-bar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ overflow-x: auto;
|
|
|
|
|
+ padding: 16rpx 30rpx 8rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
|
|
+}
|
|
|
|
|
+.member-tab {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: 8rpx 20rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+.member-tab.active {
|
|
|
|
|
+ background: linear-gradient(135deg, #FF8C42, #FFB366);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
/* 搜索框 */
|
|
/* 搜索框 */
|
|
|
-.search-bar { padding: 20rpx 30rpx 8rpx; background: #fff; }
|
|
|
|
|
|
|
+.search-bar { padding: 16rpx 30rpx 8rpx; background: #fff; }
|
|
|
.search-input-wrap {
|
|
.search-input-wrap {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- background: #f5f5f5;
|
|
|
|
|
|
|
+ background: #FFF1E6;
|
|
|
border-radius: 40rpx;
|
|
border-radius: 40rpx;
|
|
|
padding: 12rpx 24rpx;
|
|
padding: 12rpx 24rpx;
|
|
|
}
|
|
}
|
|
@@ -318,49 +421,91 @@ export default {
|
|
|
.search-clear { font-size: 28rpx; color: #999; padding: 0 8rpx; }
|
|
.search-clear { font-size: 28rpx; color: #999; padding: 0 8rpx; }
|
|
|
|
|
|
|
|
/* 分类筛选 + 排序 */
|
|
/* 分类筛选 + 排序 */
|
|
|
-.filter-bar { display: flex; align-items: center; background: #fff; padding: 8rpx 30rpx 16rpx; box-sizing: border-box; width: 100%; }
|
|
|
|
|
|
|
+.filter-bar { display: flex; align-items: center; background: #fff; padding: 8rpx 30rpx 16rpx; box-sizing: border-box; }
|
|
|
.cat-scroll { flex: 1; white-space: nowrap; min-width: 0; }
|
|
.cat-scroll { flex: 1; white-space: nowrap; min-width: 0; }
|
|
|
.cat-list { display: inline-flex; padding-right: 8rpx; }
|
|
.cat-list { display: inline-flex; padding-right: 8rpx; }
|
|
|
.cat-chip { display: inline-flex; align-items: center; padding: 10rpx 24rpx; margin-right: 12rpx; background: #f5f5f5; border-radius: 30rpx; flex-shrink: 0; }
|
|
.cat-chip { display: inline-flex; align-items: center; padding: 10rpx 24rpx; margin-right: 12rpx; background: #f5f5f5; border-radius: 30rpx; flex-shrink: 0; }
|
|
|
-.cat-chip-active { background: #4A9BD7; }
|
|
|
|
|
|
|
+.cat-chip-active { background: #FF8C42; }
|
|
|
.cat-chip-text { font-size: 24rpx; color: #666; }
|
|
.cat-chip-text { font-size: 24rpx; color: #666; }
|
|
|
.cat-chip-active .cat-chip-text { color: #fff; }
|
|
.cat-chip-active .cat-chip-text { color: #fff; }
|
|
|
.sort-btn { flex-shrink: 0; margin-left: 16rpx; padding: 10rpx 20rpx; background: #FFF1E6; border-radius: 30rpx; }
|
|
.sort-btn { flex-shrink: 0; margin-left: 16rpx; padding: 10rpx 20rpx; background: #FFF1E6; border-radius: 30rpx; }
|
|
|
-.sort-btn-text { font-size: 24rpx; color: #C2410C; }
|
|
|
|
|
|
|
+.sort-btn-text { font-size: 24rpx; color: #C2410C; font-weight: 600; }
|
|
|
|
|
|
|
|
-.content { padding: 20rpx 30rpx; height: calc(100vh - 200rpx); }
|
|
|
|
|
|
|
+.content { padding: 20rpx 30rpx; height: calc(100vh - 220rpx); }
|
|
|
|
|
|
|
|
|
|
+/* 食材卡片 */
|
|
|
.food-card {
|
|
.food-card {
|
|
|
|
|
+ background: #fff;
|
|
|
border-radius: 16rpx;
|
|
border-radius: 16rpx;
|
|
|
padding: 24rpx;
|
|
padding: 24rpx;
|
|
|
margin-bottom: 16rpx;
|
|
margin-bottom: 16rpx;
|
|
|
- box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
|
|
|
|
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(255, 140, 66, 0.08);
|
|
|
|
|
+ border-left: 6rpx solid transparent;
|
|
|
}
|
|
}
|
|
|
-/* 推荐值背景色分级:<-30 深红,>30 深绿,中间每10换一档 */
|
|
|
|
|
-.score-band-neg3 { background: #C62828; }
|
|
|
|
|
-.score-band-neg2 { background: #E65100; }
|
|
|
|
|
-.score-band-neg1 { background: #F57C00; }
|
|
|
|
|
-.score-band-neg0 { background: #F9A825; }
|
|
|
|
|
-.score-band-pos0 { background: #AFB42B; }
|
|
|
|
|
-.score-band-pos1 { background: #7CB342; }
|
|
|
|
|
-.score-band-pos2 { background: #43A047; }
|
|
|
|
|
-.score-band-pos3 { background: #1B5E20; }
|
|
|
|
|
-.score-band-null { background: #f0f0f0; }
|
|
|
|
|
|
|
+.food-card.band-neg3, .food-card.band-neg2 { border-left-color: #C62828; }
|
|
|
|
|
+.food-card.band-neg1, .food-card.band-neg0 { border-left-color: #F57C00; }
|
|
|
|
|
+.food-card.band-pos0, .food-card.band-pos1 { border-left-color: #AFB42B; }
|
|
|
|
|
+.food-card.band-pos2, .food-card.band-pos3 { border-left-color: #43A047; }
|
|
|
|
|
+.food-card.band-null { border-left-color: #ccc; }
|
|
|
|
|
|
|
|
.food-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8rpx; }
|
|
.food-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8rpx; }
|
|
|
-.food-name { font-size: 30rpx; font-weight: 600; color: #fff; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
-.food-cat-tag { padding: 4rpx 16rpx; background: rgba(255,255,255,0.3); border-radius: 20rpx; font-size: 22rpx; color: #fff; flex-shrink: 0; margin-left: 12rpx; max-width: 40%; box-sizing: border-box; }
|
|
|
|
|
-.food-cat-tag text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
|
+.food-name { font-size: 30rpx; font-weight: 600; color: #1a1a1a; flex: 1; min-width: 0; }
|
|
|
|
|
+.food-right { display: flex; align-items: center; gap: 10rpx; flex-shrink: 0; }
|
|
|
|
|
+
|
|
|
|
|
+.status-tag {
|
|
|
|
|
+ padding: 4rpx 14rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+.status-bad { background: #FFEBEE; color: #C62828; }
|
|
|
|
|
+.status-caution { background: #FFF3E0; color: #E65100; }
|
|
|
|
|
+.status-ok { background: #FFF8E1; color: #F57C00; }
|
|
|
|
|
+.status-good { background: #E8F5E9; color: #2E7D32; }
|
|
|
|
|
+.status-neutral { background: #F5F5F5; color: #999; }
|
|
|
|
|
+
|
|
|
|
|
+.food-score-badge {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ min-width: 56rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 4rpx 10rpx;
|
|
|
|
|
+ border-radius: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.score-neg3, .score-neg2 { background: #FFEBEE; color: #C62828; }
|
|
|
|
|
+.score-neg1, .score-neg0 { background: #FFF3E0; color: #E65100; }
|
|
|
|
|
+.score-pos0, .score-pos1 { background: #FFF8E1; color: #F57C00; }
|
|
|
|
|
+.score-pos2, .score-pos3 { background: #E8F5E9; color: #2E7D32; }
|
|
|
|
|
+.score-null { background: #F5F5F5; color: #999; }
|
|
|
|
|
+
|
|
|
|
|
+.food-meta { margin-bottom: 12rpx; }
|
|
|
|
|
+.meta-item { font-size: 24rpx; color: #888; background: #f5f5f5; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
|
|
|
|
+
|
|
|
|
|
+.score-bar-wrap { display: flex; align-items: center; gap: 12rpx; margin-bottom: 10rpx; }
|
|
|
|
|
+.score-bar-bg { flex: 1; height: 12rpx; background: #fee2e2; border-radius: 6rpx; overflow: hidden; }
|
|
|
|
|
+.score-bar-fill { height: 100%; border-radius: 6rpx; transition: width 0.3s ease; }
|
|
|
|
|
+.score-bar-fill.safe { background: #43A047; }
|
|
|
|
|
+.score-bar-fill.mild { background: #AFB42B; }
|
|
|
|
|
+.score-bar-fill.warn { background: #F57C00; }
|
|
|
|
|
+.score-bar-fill.danger { background: #C62828; }
|
|
|
|
|
+.score-val { font-size: 24rpx; font-weight: 600; color: #666; min-width: 50rpx; text-align: right; }
|
|
|
|
|
|
|
|
-.food-score-row { display: flex; align-items: baseline; gap: 12rpx; }
|
|
|
|
|
-.food-score-label { font-size: 22rpx; color: rgba(255,255,255,0.85); }
|
|
|
|
|
-.food-score-value { font-size: 40rpx; font-weight: 700; color: #fff; }
|
|
|
|
|
|
|
+.change-row { display: flex; align-items: center; gap: 8rpx; margin-bottom: 8rpx; font-size: 24rpx; }
|
|
|
|
|
+.change-label { color: #999; }
|
|
|
|
|
+.change-up { color: #10B981; font-weight: 600; }
|
|
|
|
|
+.change-down { color: #EF4444; font-weight: 600; }
|
|
|
|
|
+
|
|
|
|
|
+.food-nutrition { display: flex; gap: 20rpx; flex-wrap: wrap; }
|
|
|
|
|
+.nut-item { font-size: 22rpx; color: #999; }
|
|
|
|
|
|
|
|
.food-edit-row { display: flex; align-items: center; margin-top: 8rpx; }
|
|
.food-edit-row { display: flex; align-items: center; margin-top: 8rpx; }
|
|
|
-.edit-label { font-size: 26rpx; color: #fff; margin-right: 12rpx; }
|
|
|
|
|
-.edit-input { border: 1rpx solid #fff; border-radius: 8rpx; padding: 8rpx 12rpx; font-size: 26rpx; width: 140rpx; color: #fff; background: rgba(255,255,255,0.2); }
|
|
|
|
|
|
|
+.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: 140rpx; color: #333; background: #fff; }
|
|
|
|
|
|
|
|
.empty-hint { text-align: center; color: #ccc; padding: 60rpx 0; font-size: 28rpx; }
|
|
.empty-hint { text-align: center; color: #ccc; padding: 60rpx 0; font-size: 28rpx; }
|
|
|
|
|
+.loading-state { text-align: center; padding: 80rpx 0; }
|
|
|
|
|
+.loading-text { font-size: 26rpx; color: #999; }
|
|
|
|
|
+.bottom-spacer { height: 40rpx; }
|
|
|
|
|
|
|
|
/* 知识库弹窗 */
|
|
/* 知识库弹窗 */
|
|
|
.knowledge-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
|
.knowledge-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
|
@@ -375,9 +520,8 @@ export default {
|
|
|
.knowledge-desc { margin-top: 16rpx; }
|
|
.knowledge-desc { margin-top: 16rpx; }
|
|
|
.knowledge-desc-text { font-size: 26rpx; color: #666; line-height: 1.6; }
|
|
.knowledge-desc-text { font-size: 26rpx; color: #666; line-height: 1.6; }
|
|
|
|
|
|
|
|
-.edit-fab { position: fixed; right: 30rpx; bottom: 40rpx; background: #4A9BD7; color: #fff; border-radius: 40rpx; padding: 16rpx 32rpx; font-size: 26rpx; box-shadow: 0 4rpx 16rpx rgba(74,155,215,0.4); }
|
|
|
|
|
|
|
+.edit-fab { position: fixed; right: 30rpx; bottom: 40rpx; background: linear-gradient(135deg, #FF8C42, #FFB366); color: #fff; border-radius: 40rpx; padding: 16rpx 32rpx; font-size: 26rpx; box-shadow: 0 4rpx 16rpx rgba(255,140,66,0.35); }
|
|
|
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; gap: 20rpx; }
|
|
.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-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>
|
|
|
|
|
|
|
+.btn-save { flex: 1; padding: 20rpx; background: linear-gradient(135deg, #FF8C42, #FFB366); border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #fff; }
|
|
|
|
|
+</style>
|