Files
msh-system/msh_single_uniapp/pages/tool/nutrition-knowledge.vue
scottpan 6f2dc27fbc chore: update pom.xml Lombok config and deploy settings
- Update Maven compiler plugin to support Lombok annotation processing
- Add deploy.conf for automated deployment
- Update backend models and controllers
- Update frontend pages and API
2026-03-04 12:21:29 +08:00

554 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="nutrition-page">
<!-- Tab切换 -->
<view class="tab-container">
<view
class="tab-item"
:class="{ active: currentTab === 'nutrients' }"
@click="switchTab('nutrients')"
>
<text>营养素</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'guide' }"
@click="switchTab('guide')"
>
<text>饮食指南</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'articles' }"
@click="switchTab('articles')"
>
<text>科普文章</text>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 营养素百科Tab内容 -->
<view v-if="currentTab === 'nutrients'" class="tab-content">
<view class="intro-text">了解关键营养素科学管理慢性肾病饮食</view>
<view class="nutrient-list">
<view
class="nutrient-card"
v-for="(item, index) in nutrientList"
:key="index"
@click="goToNutrientDetail(item)"
>
<view class="nutrient-icon-wrapper">
<text class="nutrient-icon">{{ item.icon }}</text>
</view>
<view class="nutrient-info">
<view class="nutrient-header">
<text class="nutrient-name">{{ item.name }}</text>
<text class="nutrient-english">{{ item.english }}</text>
</view>
<text class="nutrient-desc">{{ item.description }}</text>
<view class="nutrient-tag" :class="item.tagClass">
<text>{{ item.tag }}</text>
</view>
</view>
<view class="arrow-icon">
<text></text>
</view>
</view>
</view>
</view>
<!-- 饮食指南Tab内容来自 v2_knowledgetype=guide -->
<view v-if="currentTab === 'guide'" class="tab-content">
<view class="knowledge-list">
<view
class="knowledge-item"
v-for="(item, index) in (guideList || [])"
:key="item.knowledgeId || item.id || index"
@click="goToDetail(item)"
>
<view class="knowledge-cover" v-if="item.coverImage || item.cover_image">
<image :src="item.coverImage || item.cover_image" mode="aspectFill" class="cover-img" />
</view>
<view class="knowledge-icon" v-else>
<text>{{ item.icon }}</text>
</view>
<view class="knowledge-info">
<view class="knowledge-title">{{ item.title }}</view>
<view class="knowledge-desc">{{ item.desc }}</view>
<view class="knowledge-meta">
<text class="meta-text">{{ item.time }}</text>
<text class="meta-dot">·</text>
<text class="meta-text">{{ item.views }}</text>
</view>
</view>
</view>
</view>
<view v-if="(guideList || []).length === 0" class="empty-placeholder">
<text>暂无饮食指南数据</text>
</view>
</view>
<!-- 科普文章Tab内容来自 v2_knowledgetype=article -->
<view v-if="currentTab === 'articles'" class="tab-content">
<view class="knowledge-list">
<view
class="knowledge-item"
v-for="(item, index) in (articleList || [])"
:key="item.knowledgeId || item.id || index"
@click="goToDetail(item)"
>
<view class="knowledge-cover" v-if="item.coverImage || item.cover_image">
<image :src="item.coverImage || item.cover_image" mode="aspectFill" class="cover-img" />
</view>
<view class="knowledge-icon" v-else>
<text>{{ item.icon }}</text>
</view>
<view class="knowledge-info">
<view class="knowledge-title">{{ item.title }}</view>
<view class="knowledge-desc">{{ item.desc }}</view>
<view class="knowledge-meta">
<text class="meta-text">{{ item.time }}</text>
<text class="meta-dot">·</text>
<text class="meta-text">{{ item.views }}</text>
</view>
</view>
</view>
</view>
<view v-if="(articleList || []).length === 0" class="empty-placeholder">
<text>暂无科普文章数据</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
currentTab: 'nutrients',
guideList: [],
articleList: [],
nutrientList: [
{
name: '蛋白质',
english: 'Protein',
icon: '🥩',
description: '构成人体组织的重要营养素',
tag: '需控制',
tagClass: 'control'
},
{
name: '钾',
english: 'Potassium (K)',
icon: '🍌',
description: '维持神经肌肉功能的重要元素',
tag: '严格控制',
tagClass: 'strict'
},
{
name: '磷',
english: 'Phosphorus (P)',
icon: '🥜',
description: '骨骼健康的重要矿物质',
tag: '严格控制',
tagClass: 'strict'
},
{
name: '钠',
english: 'Sodium (Na)',
icon: '🧂',
description: '调节体液平衡的电解质',
tag: '适量控制',
tagClass: 'moderate'
},
{
name: '钙',
english: 'Calcium (Ca)',
icon: '🥛',
description: '骨骼和牙齿的主要成分',
tag: '适量补充',
tagClass: 'supplement'
},
{
name: '水分',
english: 'Water',
icon: '💧',
description: '生命活动必需的基础物质',
tag: '需控制',
tagClass: 'control'
}
]
}
},
onLoad(options) {
if (options && options.id) {
// 有 id 时切换到科普文章 tabswitchTab 内会调用 loadKnowledgeList 加载列表
this.switchTab('articles');
} else {
// 无 id 时默认当前 tab 为「营养素」;用户切换到「饮食指南」或「科普文章」时由 switchTab 触发 loadKnowledgeList
this.currentTab = 'nutrients';
}
},
methods: {
formatKnowledgeTime(val) {
if (!val) return '';
const d = new Date(val);
if (isNaN(d.getTime())) return String(val);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
},
async loadKnowledgeList() {
// 营养素列表使用本地静态数据不从API加载
if (this.currentTab === 'nutrients') {
return;
}
// type 与后端一致guide / articlev2_knowledge 表 type 字段)
const typeParam = this.currentTab === 'guide' ? 'guide' : 'article';
try {
const { getKnowledgeList } = await import('@/api/tool.js');
const result = await getKnowledgeList({
type: typeParam,
page: 1,
limit: 50
});
// 兼容 result.data.list 或 result.data 为数组
let rawList = [];
if (result && result.data) {
if (Array.isArray(result.data.list)) {
rawList = result.data.list;
} else if (Array.isArray(result.data)) {
rawList = result.data;
}
}
const list = (rawList || []).map(item => ({
...item,
desc: item.desc || item.summary || '',
time: item.time || (item.publishedAt || item.createdAt ? this.formatKnowledgeTime(item.publishedAt || item.createdAt) : ''),
views: item.views != null ? item.views : (item.viewCount != null ? item.viewCount : 0),
icon: item.icon || '📄',
coverImage: item.coverImage || item.cover_image || ''
}));
if (this.currentTab === 'guide') {
this.guideList = list;
} else if (this.currentTab === 'articles') {
this.articleList = list;
}
} catch (error) {
console.error('加载知识列表失败:', error);
const msg = (error && (error.message || error.msg)) || '加载列表失败';
uni.showToast({
title: String(msg),
icon: 'none'
});
// 确保列表始终为数组,不设为 undefined
if (this.currentTab === 'guide') {
this.guideList = Array.isArray(this.guideList) ? this.guideList : [];
} else if (this.currentTab === 'articles') {
this.articleList = Array.isArray(this.articleList) ? this.articleList : [];
}
}
},
async switchTab(tab) {
this.currentTab = tab;
await this.loadKnowledgeList();
},
goToNutrientDetail(item) {
uni.navigateTo({
url: `/pages/tool/nutrient-detail?name=${item.name}`
})
},
goToDetail(item) {
if (!item) {
uni.showToast({ title: '暂无详情', icon: 'none' });
return;
}
// 兼容后端 knowledgeId / id / knowledge_id
const id = item.knowledgeId ?? item.id ?? item.knowledge_id;
if (id === undefined || id === null || id === '') {
uni.showToast({ title: '暂无详情', icon: 'none' });
return;
}
// 饮食指南、科普文章使用知识详情页(调用 tool/knowledge/detail 接口)
uni.navigateTo({
url: `/pages/tool/knowledge-detail?id=${id}`
});
}
}
}
</script>
<style lang="scss" scoped>
.nutrition-page {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f4f5f7;
}
/* Tab切换 */
.tab-container {
background: #ffffff;
border-bottom: 1rpx solid #d0dbea;
display: flex;
padding: 12rpx 32rpx;
gap: 16rpx;
}
.tab-item {
flex: 1;
height: 75rpx;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border: 1rpx solid #d0dbea;
text {
font-size: 28rpx;
color: #9fa5c0;
}
&.active {
background: #ff6b35;
border-color: #ff6b35;
text {
color: #ffffff;
}
}
}
/* 内容滚动区域 */
.content-scroll {
flex: 1;
overflow-y: auto;
}
.tab-content {
padding: 32rpx;
}
.intro-text {
font-size: 28rpx;
color: #9fa5c0;
margin-bottom: 32rpx;
line-height: 1.2;
}
/* 营养素列表 */
.nutrient-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.nutrient-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
align-items: center;
gap: 32rpx;
}
.nutrient-icon-wrapper {
width: 112rpx;
height: 112rpx;
border-radius: 50%;
background: #e3fff1;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.nutrient-icon {
font-size: 60rpx;
}
.nutrient-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.nutrient-header {
display: flex;
align-items: baseline;
gap: 16rpx;
}
.nutrient-name {
font-size: 32rpx;
color: #101828;
font-weight: 500;
}
.nutrient-english {
font-size: 24rpx;
color: #99a1af;
}
.nutrient-desc {
font-size: 28rpx;
color: #4a5565;
line-height: 1.5;
}
.nutrient-tag {
padding: 8rpx 24rpx;
border-radius: 50rpx;
display: inline-block;
align-self: flex-start;
text {
font-size: 24rpx;
}
&.control {
background: #fef2f2;
border: 1rpx solid #ffc9c9;
text {
color: #e7000b;
}
}
&.strict {
background: #fef2f2;
border: 1rpx solid #ffc9c9;
text {
color: #e7000b;
}
}
&.moderate {
background: #fff8e1;
border: 1rpx solid #ffe0b2;
text {
color: #ff9800;
}
}
&.supplement {
background: #f0fdf4;
border: 1rpx solid #b9f8cf;
text {
color: #00a63e;
}
}
}
.arrow-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
text {
font-size: 40rpx;
color: #9fa5c0;
}
}
/* 空状态 */
.empty-placeholder {
padding: 200rpx 0;
text-align: center;
color: #9fa5c0;
font-size: 28rpx;
}
/* 知识列表 */
.knowledge-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.knowledge-item {
background: #ffffff;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 20rpx 30rpx -6rpx rgba(0, 0, 0, 0.1), 0 8rpx 12rpx -4rpx rgba(0, 0, 0, 0.1);
display: flex;
gap: 32rpx;
}
.knowledge-cover {
width: 128rpx;
height: 128rpx;
border-radius: 32rpx;
overflow: hidden;
flex-shrink: 0;
}
.cover-img {
width: 100%;
height: 100%;
}
.knowledge-icon {
width: 128rpx;
height: 128rpx;
border-radius: 32rpx;
background: #f4f5f7;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
text {
font-size: 60rpx;
}
}
.knowledge-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.knowledge-title {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
margin-bottom: 12rpx;
}
.knowledge-desc {
font-size: 24rpx;
color: #9fa5c0;
line-height: 1.6;
margin-bottom: 16rpx;
}
.knowledge-meta {
display: flex;
align-items: center;
gap: 24rpx;
.meta-text {
font-size: 24rpx;
color: #9fa5c0;
}
.meta-dot {
font-size: 24rpx;
color: #d0dbea;
}
}
}
</style>