|
|
@@ -1,12 +1,43 @@
|
|
|
<template>
|
|
|
<view class="food-query-page">
|
|
|
+ <!-- 成员切换条 -->
|
|
|
+ <view class="member-bar" v-if="memberList.length > 1 || familyMode">
|
|
|
+ <view class="member-tab"
|
|
|
+ :class="{'active': currentView === 'family'}"
|
|
|
+ @tap="switchView('family')">
|
|
|
+ <text class="member-name">👨👩👧 家庭总览</text>
|
|
|
+ </view>
|
|
|
+ <view class="member-tab"
|
|
|
+ :class="{'active': currentView === 'personal'}"
|
|
|
+ @tap="switchView('personal')">
|
|
|
+ <text class="member-name">👤 个人</text>
|
|
|
+ </view>
|
|
|
+ <view class="member-tab"
|
|
|
+ v-for="(m, mi) in memberList"
|
|
|
+ :key="m.userId"
|
|
|
+ :class="{'active': currentMemberId === m.userId}"
|
|
|
+ @tap="switchMember(m)">
|
|
|
+ <text class="member-name">{{ m.nickname || m.roleLabel || '成员' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
|
|
|
- <view class="search-bar">
|
|
|
- <input class="search-input" placeholder="搜索食材..." v-model="searchQuery" @confirm="searchFood" />
|
|
|
- <view class="search-btn" @tap="searchFood">搜索</view>
|
|
|
+ <!-- 模式切换(仅家庭模式展示) -->
|
|
|
+ <view class="mode-bar" v-if="familyMode">
|
|
|
+ <view class="mode-btn" :class="{'active': sortType === 'desc'}" @tap="setSort('desc')">
|
|
|
+ <text>推荐值 ↓</text>
|
|
|
+ </view>
|
|
|
+ <view class="mode-btn" :class="{'active': sortType === 'asc'}" @tap="setSort('asc')">
|
|
|
+ <text>推荐值 ↑</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
- <scroll-view class="content" scroll-y>
|
|
|
+ <scroll-view class="content" scroll-y :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
|
|
+ <!-- 搜索栏 -->
|
|
|
+ <view class="search-bar">
|
|
|
+ <input class="search-input" placeholder="搜索食材..." v-model="searchQuery" @confirm="searchFood" />
|
|
|
+ <view class="search-btn" @tap="searchFood">搜索</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 分类标签 -->
|
|
|
<view class="category-tabs">
|
|
|
<view class="tab-item" :class="{'tab-active': activeCategory === 'all'}" @tap="switchCategory('all')">
|
|
|
@@ -27,35 +58,82 @@
|
|
|
<view class="tab-item" :class="{'tab-active': activeCategory === 'grain'}" @tap="switchCategory('grain')">
|
|
|
<text>谷物</text>
|
|
|
</view>
|
|
|
+ <view class="tab-item" :class="{'tab-active': activeCategory === 'dairy'}" @tap="switchCategory('dairy')">
|
|
|
+ <text>乳制品</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 筛选 -->
|
|
|
+ <!-- 筛选栏 -->
|
|
|
<view class="filter-bar">
|
|
|
<view class="filter-item" :class="{'filter-active': filterType === 'suitable'}" @tap="setFilter('suitable')">
|
|
|
<text>✅ 适合</text>
|
|
|
</view>
|
|
|
<view class="filter-item" :class="{'filter-active': filterType === 'unsuitable'}" @tap="setFilter('unsuitable')">
|
|
|
- <text>❌ 不适合</text>
|
|
|
+ <text>❌ 戒备</text>
|
|
|
+ </view>
|
|
|
+ <view class="filter-item" :class="{'filter-active': filterType === 'all'}" @tap="setFilter('all')">
|
|
|
+ <text>📋 全部</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 家庭总览 -->
|
|
|
+ <view class="family-overview" v-if="currentView === 'family' && familyOverview && familyOverview.length > 0">
|
|
|
+ <view class="overview-title">
|
|
|
+ <text>🏠 家庭推荐概况(共 {{ familyMembers.length }} 人)</text>
|
|
|
+ </view>
|
|
|
+ <view class="overview-item" v-for="(item, idx) in familyOverview" :key="idx">
|
|
|
+ <view class="overview-header">
|
|
|
+ <text class="food-name">{{ item.name }}</text>
|
|
|
+ <view class="score-badge" :class="item.color">
|
|
|
+ <text>{{ item.avgScore }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="overview-detail">
|
|
|
+ <text class="category-label">{{ item.category }}</text>
|
|
|
+ <view class="member-scores">
|
|
|
+ <text v-for="(s, mi) in item.memberScores" :key="mi"
|
|
|
+ class="member-score"
|
|
|
+ :class="s.score >= 0 ? 'score-green' : 'score-red'">
|
|
|
+ {{ s.name }}:{{ s.score >= 0 ? '+' : '' }}{{ s.score }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 食材列表 -->
|
|
|
- <view class="food-list">
|
|
|
- <view class="food-item" v-for="food in filteredFoods" :key="food.id">
|
|
|
- <view class="food-info">
|
|
|
- <text class="food-name">{{ food.name }}</text>
|
|
|
- <text class="food-category">{{ food.category }}</text>
|
|
|
- <text class="food-score" v-if="food.score">推荐分: {{ food.score }}</text>
|
|
|
+ <view class="food-list" v-if="currentView === 'personal'">
|
|
|
+ <view class="food-card" v-for="(item, idx) in filteredFoods" :key="item.id || idx">
|
|
|
+ <view class="food-left">
|
|
|
+ <view class="ingredient-badge" :class="item.scoreClass">
|
|
|
+ <text class="ingredient-badge-text">{{ idx + 1 }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="food-info">
|
|
|
+ <text class="food-name">{{ item.name }}</text>
|
|
|
+ <view class="food-meta" v-if="item.category">
|
|
|
+ <text class="meta-item">{{ item.category }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- <view class="food-status" :class="'status-' + food.status">
|
|
|
- <text>{{ food.statusText }}</text>
|
|
|
+ <view class="food-right">
|
|
|
+ <text class="score-value" :class="item.scoreClass">{{ item.score || '--' }}</text>
|
|
|
+ <view class="status-badge" :class="'badge-' + item.status">
|
|
|
+ <text>{{ item.statusText }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <view class="empty-state" v-if="filteredFoods.length === 0">
|
|
|
- <text>暂无食材数据</text>
|
|
|
+ <view class="empty-state" v-if="!loading && filteredFoods.length === 0 && currentView === 'personal'">
|
|
|
+ <text class="empty-icon">🥗</text>
|
|
|
+ <text class="empty-text">暂无匹配食材</text>
|
|
|
</view>
|
|
|
+
|
|
|
+ <view class="loading-state" v-if="loading">
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="bottom-spacer"></view>
|
|
|
</scroll-view>
|
|
|
</view>
|
|
|
</template>
|
|
|
@@ -69,65 +147,214 @@ export default {
|
|
|
searchQuery: '',
|
|
|
activeCategory: 'all',
|
|
|
filterType: 'suitable',
|
|
|
+ sortType: 'desc',
|
|
|
+ currentView: 'personal',
|
|
|
foods: [],
|
|
|
- allFoods: []
|
|
|
+ allFoods: [],
|
|
|
+ familyData: null,
|
|
|
+ familyMembers: [],
|
|
|
+ familyOverview: [],
|
|
|
+ currentMemberId: null,
|
|
|
+ memberList: [],
|
|
|
+ loading: false,
|
|
|
+ refreshing: false,
|
|
|
+ reportId: null
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
filteredFoods: function() {
|
|
|
var self = this
|
|
|
- var result = self.allFoods
|
|
|
-
|
|
|
+ var result = self.currentView === 'family'
|
|
|
+ ? self.familyOverview
|
|
|
+ : self.allFoods
|
|
|
+
|
|
|
// 分类筛选
|
|
|
if (self.activeCategory !== 'all') {
|
|
|
result = result.filter(function(f) {
|
|
|
- return f.category === self.activeCategory
|
|
|
+ return f.category && f.category.indexOf(self.activeCategory) !== -1
|
|
|
})
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 搜索筛选
|
|
|
if (self.searchQuery) {
|
|
|
result = result.filter(function(f) {
|
|
|
- return f.name.indexOf(self.searchQuery) >= 0
|
|
|
+ return (f.name && f.name.indexOf(self.searchQuery) !== -1) ||
|
|
|
+ (f.category && f.category.indexOf(self.searchQuery) !== -1)
|
|
|
})
|
|
|
}
|
|
|
-
|
|
|
- // 适合/不适合筛选
|
|
|
+
|
|
|
+ // 适合/戒备筛选
|
|
|
if (self.filterType === 'suitable') {
|
|
|
result = result.filter(function(f) {
|
|
|
return f.score >= 50
|
|
|
})
|
|
|
- } else {
|
|
|
+ } else if (self.filterType === 'unsuitable') {
|
|
|
result = result.filter(function(f) {
|
|
|
return f.score < 50
|
|
|
})
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ if (self.sortType === 'asc') {
|
|
|
+ result = result.slice().sort(function(a, b) {
|
|
|
+ return (a.score || 0) - (b.score || 0)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ result = result.slice().sort(function(a, b) {
|
|
|
+ return (b.score || 0) - (a.score || 0)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
return result
|
|
|
}
|
|
|
},
|
|
|
- onLoad: function() {
|
|
|
+ onLoad: function(options) {
|
|
|
+ this.reportId = options.reportId ? parseInt(options.reportId) : null
|
|
|
+ this.currentMemberId = options.memberId ? parseInt(options.memberId) : null
|
|
|
+
|
|
|
+ // 从报告入口进来时,默认显示戒备模式
|
|
|
+ if (this.reportId) {
|
|
|
+ this.filterType = 'unsuitable'
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loadMembers()
|
|
|
this.loadFoods()
|
|
|
+ this.loadFamilyData()
|
|
|
},
|
|
|
methods: {
|
|
|
goBack: function() {
|
|
|
uni.navigateBack()
|
|
|
},
|
|
|
+ loadMembers: function() {
|
|
|
+ var self = this
|
|
|
+ var token = uni.getStorageSync('token')
|
|
|
+ if (!token) return
|
|
|
+
|
|
|
+ uni.request({
|
|
|
+ url: config.API_BASE_URL + '/api/food/recommendation/members',
|
|
|
+ method: 'POST',
|
|
|
+ header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
+ success: function(res) {
|
|
|
+ if (res.data && res.data.code === 200 && res.data.data) {
|
|
|
+ self.memberList = res.data.data
|
|
|
+ if (!self.currentMemberId && self.memberList.length > 0) {
|
|
|
+ self.currentMemberId = self.memberList[0].userId
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
loadFoods: function() {
|
|
|
var self = this
|
|
|
var token = uni.getStorageSync('token')
|
|
|
if (!token) return
|
|
|
|
|
|
+ self.loading = true
|
|
|
+ var url = config.API_BASE_URL + '/api/health/foods'
|
|
|
+ var data = {}
|
|
|
+ if (self.reportId) data.reportId = self.reportId
|
|
|
+ if (self.currentMemberId) data.memberId = self.currentMemberId
|
|
|
+
|
|
|
uni.request({
|
|
|
- url: this.getApiUrl('/api/health/foods'),
|
|
|
+ url: url,
|
|
|
method: 'POST',
|
|
|
+ data: data,
|
|
|
header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
success: function(res) {
|
|
|
+ self.loading = false
|
|
|
if (res.data && res.data.code === 200 && res.data.data) {
|
|
|
- self.allFoods = res.data.data
|
|
|
+ self.allFoods = res.data.data.map(function(f) {
|
|
|
+ f.scoreClass = self.getScoreClass(f.score)
|
|
|
+ f.statusText = self.getStatusText(f.score)
|
|
|
+ f.status = f.score >= 50 ? 'suitable' : 'unsuitable'
|
|
|
+ return f
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function() {
|
|
|
+ self.loading = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ loadFamilyData: function() {
|
|
|
+ var self = this
|
|
|
+ var token = uni.getStorageSync('token')
|
|
|
+ if (!token) return
|
|
|
+
|
|
|
+ var familyId = uni.getStorageSync('familyId')
|
|
|
+ if (!familyId) return
|
|
|
+
|
|
|
+ uni.request({
|
|
|
+ url: config.API_BASE_URL + '/api/health/foods/by-family',
|
|
|
+ method: 'POST',
|
|
|
+ header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
+ success: function(res) {
|
|
|
+ if (res.data && res.data.code === 200 && res.data.data) {
|
|
|
+ self.familyData = res.data.data
|
|
|
+ self.familyMembers = Object.keys(res.data.data.byMember || {}).map(function(key) {
|
|
|
+ return { userId: key, nickname: res.data.data.memberNames[key] || '成员' }
|
|
|
+ })
|
|
|
+ self.buildFamilyOverview(res.data.data.totalList, res.data.data.byMember, res.data.data.memberNames)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ buildFamilyOverview: function(totalList, byMember, memberNames) {
|
|
|
+ var self = this
|
|
|
+ var overview = []
|
|
|
+ for (var i = 0; i < totalList.length; i++) {
|
|
|
+ var food = totalList[i]
|
|
|
+ var memberScores = []
|
|
|
+ var totalScore = 0
|
|
|
+ var count = 0
|
|
|
+
|
|
|
+ for (var j = 0; j < self.familyMembers.length; j++) {
|
|
|
+ var member = self.familyMembers[j]
|
|
|
+ var memberFoods = byMember[member.userId]
|
|
|
+ if (memberFoods) {
|
|
|
+ for (var k = 0; k < memberFoods.length; k++) {
|
|
|
+ if (memberFoods[k].foodId === food.id) {
|
|
|
+ var score = memberFoods[k].indexScore || 0
|
|
|
+ memberScores.push({ name: member.nickname, score: score })
|
|
|
+ totalScore += score
|
|
|
+ count++
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ if (memberScores.length > 0) {
|
|
|
+ var avgScore = Math.round(totalScore / memberScores.length)
|
|
|
+ overview.push({
|
|
|
+ id: food.id,
|
|
|
+ name: food.name,
|
|
|
+ category: food.category,
|
|
|
+ score: avgScore,
|
|
|
+ scoreClass: self.getScoreClass(avgScore),
|
|
|
+ statusText: self.getStatusText(avgScore),
|
|
|
+ status: avgScore >= 50 ? 'suitable' : 'unsuitable',
|
|
|
+ memberScores: memberScores
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按平均推荐值排序(从高到低)
|
|
|
+ overview.sort(function(a, b) {
|
|
|
+ return b.score - a.score
|
|
|
})
|
|
|
+
|
|
|
+ self.familyOverview = overview
|
|
|
+ },
|
|
|
+ getScoreClass: function(score) {
|
|
|
+ if (score === undefined || score === null) return 'neutral'
|
|
|
+ if (score >= 50) return 'good'
|
|
|
+ return 'bad'
|
|
|
+ },
|
|
|
+ getStatusText: function(score) {
|
|
|
+ if (score === undefined || score === null) return '未知'
|
|
|
+ if (score >= 50) return '适合'
|
|
|
+ return '戒备'
|
|
|
},
|
|
|
searchFood: function() {
|
|
|
// 搜索由 computed 自动处理
|
|
|
@@ -138,8 +365,24 @@ export default {
|
|
|
setFilter: function(type) {
|
|
|
this.filterType = type
|
|
|
},
|
|
|
- getApiUrl: function(path) {
|
|
|
- return config.api(path)
|
|
|
+ setSort: function(type) {
|
|
|
+ this.sortType = type
|
|
|
+ },
|
|
|
+ switchView: function(view) {
|
|
|
+ this.currentView = view
|
|
|
+ },
|
|
|
+ switchMember: function(member) {
|
|
|
+ this.currentMemberId = member.userId
|
|
|
+ this.currentView = 'personal'
|
|
|
+ this.loadFoods()
|
|
|
+ },
|
|
|
+ onRefresh: function() {
|
|
|
+ this.refreshing = true
|
|
|
+ this.loadFoods()
|
|
|
+ this.loadFamilyData()
|
|
|
+ setTimeout(function() {
|
|
|
+ this.refreshing = false
|
|
|
+ }.bind(this), 500)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -152,9 +395,54 @@ export default {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
-.nav-placeholder {
|
|
|
- width: 80rpx;
|
|
|
+
|
|
|
+/* 成员切换条 */
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+/* 模式切换(排序) */
|
|
|
+.mode-bar {
|
|
|
+ display: flex;
|
|
|
+ padding: 16rpx 30rpx;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+.mode-btn {
|
|
|
+ flex: 1;
|
|
|
+ padding: 12rpx 0;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+ font-size: 26rpx;
|
|
|
}
|
|
|
+.mode-btn.active {
|
|
|
+ background: linear-gradient(135deg, #10B981, #34D399);
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 搜索栏 */
|
|
|
.search-bar {
|
|
|
display: flex;
|
|
|
padding: 20rpx 30rpx;
|
|
|
@@ -176,10 +464,13 @@ export default {
|
|
|
border-radius: 30rpx;
|
|
|
font-size: 26rpx;
|
|
|
}
|
|
|
+
|
|
|
.content {
|
|
|
flex: 1;
|
|
|
padding: 20rpx 30rpx;
|
|
|
}
|
|
|
+
|
|
|
+/* 分类标签 */
|
|
|
.category-tabs {
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
@@ -199,6 +490,8 @@ export default {
|
|
|
color: #FF8C42;
|
|
|
background-color: #FFF5EB;
|
|
|
}
|
|
|
+
|
|
|
+/* 筛选栏 */
|
|
|
.filter-bar {
|
|
|
display: flex;
|
|
|
gap: 20rpx;
|
|
|
@@ -217,55 +510,192 @@ export default {
|
|
|
color: #4CAF50;
|
|
|
background-color: #E8F5E9;
|
|
|
}
|
|
|
+
|
|
|
+/* 家庭总览 */
|
|
|
+.family-overview {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.overview-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ padding-bottom: 16rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+}
|
|
|
+.overview-item {
|
|
|
+ padding: 16rpx 0;
|
|
|
+ border-bottom: 1rpx solid #f5f5f5;
|
|
|
+}
|
|
|
+.overview-item:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+.overview-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.overview-detail {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+.category-label {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.member-scores {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12rpx;
|
|
|
+}
|
|
|
+.member-score {
|
|
|
+ font-size: 20rpx;
|
|
|
+ padding: 4rpx 12rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+.score-green {
|
|
|
+ color: #10B981;
|
|
|
+ background: #E8F5E9;
|
|
|
+}
|
|
|
+.score-red {
|
|
|
+ color: #EF4444;
|
|
|
+ background: #FEE2E2;
|
|
|
+}
|
|
|
+
|
|
|
+/* 食材列表 */
|
|
|
.food-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 16rpx;
|
|
|
}
|
|
|
-.food-item {
|
|
|
+.food-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 18rpx 20rpx;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
|
|
display: flex;
|
|
|
+ align-items: center;
|
|
|
justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.food-left {
|
|
|
+ display: flex;
|
|
|
align-items: center;
|
|
|
- background-color: #FFFFFF;
|
|
|
- padding: 24rpx 30rpx;
|
|
|
- border-radius: 16rpx;
|
|
|
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.ingredient-badge {
|
|
|
+ width: 36rpx;
|
|
|
+ height: 36rpx;
|
|
|
+ border-radius: 10rpx;
|
|
|
+ background: linear-gradient(135deg, #FF8C42, #FFB366);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.ingredient-badge.good {
|
|
|
+ background: linear-gradient(135deg, #10B981, #34D399);
|
|
|
+}
|
|
|
+.ingredient-badge.bad {
|
|
|
+ background: linear-gradient(135deg, #EF4444, #DC2626);
|
|
|
+}
|
|
|
+.ingredient-badge-text {
|
|
|
+ font-size: 18rpx;
|
|
|
+ color: #FFFFFF;
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
|
.food-info {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 8rpx;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
.food-name {
|
|
|
font-size: 28rpx;
|
|
|
- font-weight: 500;
|
|
|
- color: #333;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a1a;
|
|
|
+ display: block;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
}
|
|
|
-.food-category {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #999;
|
|
|
+.food-meta {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8rpx;
|
|
|
+ margin-top: 6rpx;
|
|
|
}
|
|
|
-.food-score {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #FF8C42;
|
|
|
+.meta-item {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #888;
|
|
|
+ background: #f5f5f5;
|
|
|
+ padding: 4rpx 14rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
}
|
|
|
-.food-status {
|
|
|
- padding: 8rpx 16rpx;
|
|
|
+.food-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.score-value {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ min-width: 60rpx;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+.score-value.good { color: #10B981; }
|
|
|
+.score-value.bad { color: #EF4444; }
|
|
|
+.status-badge {
|
|
|
+ padding: 6rpx 16rpx;
|
|
|
border-radius: 20rpx;
|
|
|
font-size: 22rpx;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
-.status-suitable {
|
|
|
- background-color: #E8F5E9;
|
|
|
+.badge-suitable {
|
|
|
+ background: #E8F5E9;
|
|
|
color: #4CAF50;
|
|
|
}
|
|
|
-.status-unsuitable {
|
|
|
- background-color: #FFEBEE;
|
|
|
+.badge-unsuitable {
|
|
|
+ background: #FFEBEE;
|
|
|
color: #F44336;
|
|
|
}
|
|
|
+
|
|
|
+/* 空状态 */
|
|
|
.empty-state {
|
|
|
text-align: center;
|
|
|
- padding: 60rpx 0;
|
|
|
+ padding: 100rpx 40rpx;
|
|
|
+}
|
|
|
+.empty-icon {
|
|
|
+ font-size: 80rpx;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.empty-text {
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #333;
|
|
|
+ font-weight: 500;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+.loading-state {
|
|
|
+ text-align: center;
|
|
|
+ padding: 80rpx 0;
|
|
|
+}
|
|
|
+.loading-text {
|
|
|
font-size: 26rpx;
|
|
|
color: #999;
|
|
|
}
|
|
|
+.bottom-spacer { height: 40rpx; }
|
|
|
</style>
|