From 87c4b63ce203f0fb34241eda92caeccbaceb7f54 Mon Sep 17 00:00:00 2001
From: quling <377956897@qq.com>
Date: 星期六, 22 八月 2026 15:11:01 +0800
Subject: [PATCH] feat0806: APP养生科普健康知识功能改造

---
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/controller/AppHealthPopularizationController.java |    8 +
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/service/AppHealthPopularizationService.java       |    8 +
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/vo/AppHealthPopularizationDetailVO.java           |    2 
 traditionalApp/pages/publish/service.js                                                                                              |    6 +
 traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue                                                                           |    8 +-
 traditionalApp/pages/articleDetail/articleDetail.vue                                                                                 |   27 ++++++
 traditionalApp/pages/publish/publish.vue                                                                                             |   69 +++++++++--------
 traditionalApp/pages/healthPopularization/service.js                                                                                 |    6 +
 traditionalApp/pages/healthPopularization/healthPopularization.vue                                                                   |   69 +++++++++--------
 9 files changed, 125 insertions(+), 78 deletions(-)

diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/controller/AppHealthPopularizationController.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/controller/AppHealthPopularizationController.java
index 9769c59..a11f045 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/controller/AppHealthPopularizationController.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/controller/AppHealthPopularizationController.java
@@ -8,6 +8,7 @@
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -31,10 +32,13 @@
 
     /**
      * 获取 APP 健康科普列表。
+     *
+     * @param contentType 内容类型:1 健康知识、2 养生科普;不传则返回全部。
      */
     @GetMapping("/health-popularizations")
-    public ApiResponse<List<AppHealthPopularizationListItemVO>> list() {
-        return ApiResponse.ok(appHealthPopularizationService.list());
+    public ApiResponse<List<AppHealthPopularizationListItemVO>> list(
+            @RequestParam(value = "contentType", required = false) Integer contentType) {
+        return ApiResponse.ok(appHealthPopularizationService.list(contentType));
     }
 
     /**
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/service/AppHealthPopularizationService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/service/AppHealthPopularizationService.java
index f4436bf..d6b6597 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/service/AppHealthPopularizationService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/service/AppHealthPopularizationService.java
@@ -30,12 +30,15 @@
 
     /**
      * 获取 APP 健康科普列表页数据。
+     *
+     * @param contentType 内容类型:1 健康知识、2 养生科普;为空则返回全部。
      */
-    public List<AppHealthPopularizationListItemVO> list() {
+    public List<AppHealthPopularizationListItemVO> list(Integer contentType) {
         List<HealthPopularization> items = healthPopularizationMapper.selectList(new LambdaQueryWrapper<HealthPopularization>()
                 .eq(HealthPopularization::getDeleted, 0)
                 .eq(HealthPopularization::getStatus, STATUS_ENABLED)
-                .orderByAsc(HealthPopularization::getSort)
+                .eq(contentType != null, HealthPopularization::getContentType, contentType)
+                .orderByDesc(HealthPopularization::getSort)
                 .orderByDesc(HealthPopularization::getId));
         if (items == null || items.isEmpty()) {
             return Collections.emptyList();
@@ -68,6 +71,7 @@
         detailVO.setContent(trimToEmpty(item.getContent()));
         detailVO.setVideoUrl(trimToEmpty(item.getVideoUrl()));
         detailVO.setVideoFileName(trimToEmpty(item.getVideoFileName()));
+        detailVO.setVideoDescription(trimToEmpty(item.getVideoDescription()));
         return detailVO;
     }
 
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/vo/AppHealthPopularizationDetailVO.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/vo/AppHealthPopularizationDetailVO.java
index 556e4ef..fb8951f 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/vo/AppHealthPopularizationDetailVO.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/vo/AppHealthPopularizationDetailVO.java
@@ -25,4 +25,6 @@
     private String videoUrl;
 
     private String videoFileName;
+
+    private String videoDescription;
 }
diff --git a/traditionalApp/pages/articleDetail/articleDetail.vue b/traditionalApp/pages/articleDetail/articleDetail.vue
index cbbb0f5..c1fc71c 100644
--- a/traditionalApp/pages/articleDetail/articleDetail.vue
+++ b/traditionalApp/pages/articleDetail/articleDetail.vue
@@ -26,6 +26,11 @@
 			<rich-text v-if="!isVideoType" :nodes="contentNodes" class="rich-content"></rich-text>
 			<view v-if="loading" class="status-text">加载中...</view>
 			<view v-else-if="!detail.id" class="status-text">暂无详情内容</view>
+
+			<!-- 视频介绍:仅视频类型且有介绍文案时展示 -->
+			<view v-if="isVideoType && videoDescription" class="video-intro">
+				<text class="video-intro-text">{{ videoDescription }}</text>
+			</view>
 		</view>
 	</view>
 </template>
@@ -45,6 +50,7 @@
 		content : string
 		videoUrl : string
 		videoFileName : string
+		videoDescription : string
 	}
 
 	const statusBarHeight = ref(0)
@@ -66,6 +72,7 @@
 			content: '',
 			videoUrl: '',
 			videoFileName: '',
+			videoDescription: '',
 		}
 	}
 
@@ -81,6 +88,11 @@
 		const source = detail.value as PopularizationDetail & { summary ?: string; description ?: string; subTitle ?: string }
 		return String(source.subtitle || source.subTitle || source.summary || source.description || '').trim()
 	})
+
+	/**
+	 * 视频介绍文案,去除首尾空白后用于详情底部展示。
+	 */
+	const videoDescription = computed(() => String(detail.value.videoDescription || '').trim())
 
 	/**
 	 * 将富文本正文转换为 `rich-text` 可直接消费的节点字符串。
@@ -285,4 +297,19 @@
 		line-height: 40rpx;
 		color: #86909C;
 	}
+
+	.video-intro {
+		margin-top: 32rpx;
+		padding-top: 24rpx;
+		border-top: 1rpx solid #EBEDF0;
+		display: flex;
+		flex-direction: column;
+	}
+
+	.video-intro-text {
+		font-size: 28rpx;
+		line-height: 1.8;
+		color: #5B6475;
+		white-space: pre-wrap;
+	}
 </style>
diff --git a/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue b/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue
index 48956ac..7d12012 100644
--- a/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue
+++ b/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue
@@ -39,20 +39,20 @@
 }
 
 /**
- * 进入复制后的养生科普二级页。
+ * 进入复制后的养生科普二级页(contentType=2 仅视频)。
  */
 const goHealthPopularization = () => {
 	uni.navigateTo({
-		url: '/pages/healthPopularization/healthPopularization'
+		url: '/pages/healthPopularization/healthPopularization?contentType=2'
 	})
 }
 
 /**
- * 进入原健康科普页,作为「健康知识」二级页。
+ * 进入原健康科普页,作为「健康知识」二级页(contentType=1 仅图文)。
  */
 const goHealthKnowledge = () => {
 	uni.navigateTo({
-		url: '/pages/publish/publish'
+		url: '/pages/publish/publish?contentType=1'
 	})
 }
 </script>
diff --git a/traditionalApp/pages/healthPopularization/healthPopularization.vue b/traditionalApp/pages/healthPopularization/healthPopularization.vue
index 140f81a..8a8045f 100644
--- a/traditionalApp/pages/healthPopularization/healthPopularization.vue
+++ b/traditionalApp/pages/healthPopularization/healthPopularization.vue
@@ -12,9 +12,7 @@
 			<view v-if="articleList.length" class="article-list">
 				<view class="article-item" v-for="item in articleList" :key="item.id" @tap="goToDetail(item.id)">
 					<image class="article-img" :src="item.coverImage || defaultCoverImage" mode="aspectFill"></image>
-					<view class="article-shadow">
-						<text class="article-text">{{ item.title }}</text>
-					</view>
+					<text class="article-title">{{ item.title }}</text>
 				</view>
 			</view>
 			<view v-else-if="loading" class="status-text">加载中...</view>
@@ -25,7 +23,7 @@
 
 <script setup lang="ts">
 import { ref, onMounted } from 'vue';
-import { onPullDownRefresh } from '@dcloudio/uni-app'
+import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
 import { getHealthPopularizationList } from './service'
 
 interface PopularizationListItem {
@@ -40,6 +38,18 @@
 const loading = ref(false)
 const articleList = ref<PopularizationListItem[]>([])
 const defaultCoverImage = '/static/image/home/zixun.png'
+// 内容类型:2 养生科普,默认按养生科普过滤。
+const contentType = ref<number>(2)
+
+/**
+ * 读取路由参数中的内容类型,用于按类型过滤列表。
+ */
+onLoad((options) => {
+	const raw = Number(options?.contentType)
+	if (!Number.isNaN(raw) && raw > 0) {
+		contentType.value = raw
+	}
+})
 
 /**
  * 页面加载时初始化状态栏高度并拉取养生科普列表。
@@ -84,7 +94,7 @@
 async function loadPopularizationList() {
 	loading.value = true
 	try {
-		const response = await getHealthPopularizationList()
+		const response = await getHealthPopularizationList(contentType.value)
 		if (response?.code !== 0 || !Array.isArray(response.data)) {
 			articleList.value = []
 			return
@@ -163,43 +173,36 @@
 
 .article-list {
 	display: flex;
-	justify-content: space-between;
-	flex-wrap: wrap;
+	flex-direction: column;
 }
 
 .article-item {
-	width: 328rpx;
-	height: 328rpx;
-	border-radius: 16rpx;
-	position: relative;
-	overflow: hidden;
-	margin-bottom: 24rpx;
-}
-
-.article-img {
-	width: 100%;
-	height: 100%;
-}
-
-.article-shadow {
-	position: absolute;
-	bottom: 0;
-	left: 0;
-	width: 100%;
-	height: 64rpx;
-	background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
 	display: flex;
 	align-items: center;
-	padding: 0 16rpx;
+	width: 100%;
+	padding: 24rpx 0;
+	border-bottom: 1rpx solid #F0F0F0;
 	box-sizing: border-box;
 }
 
-.article-text {
-	color: #FFFFFF;
-	font-size: 24rpx;
-	white-space: nowrap;
+.article-img {
+	width: 200rpx;
+	height: 150rpx;
+	border-radius: 12rpx;
+	flex-shrink: 0;
+	background-color: #F5F6F8;
+}
+
+.article-title {
+	flex: 1;
+	margin-left: 24rpx;
+	font-size: 30rpx;
+	line-height: 44rpx;
+	color: #333;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-line-clamp: 2;
 	overflow: hidden;
 	text-overflow: ellipsis;
-	width: 100%;
 }
 </style>
diff --git a/traditionalApp/pages/healthPopularization/service.js b/traditionalApp/pages/healthPopularization/service.js
index 419cab1..66cc99b 100644
--- a/traditionalApp/pages/healthPopularization/service.js
+++ b/traditionalApp/pages/healthPopularization/service.js
@@ -2,7 +2,9 @@
 
 /**
  * 获取 APP 养生科普列表(与原健康科普共用接口)。
+ * @param {number} [contentType] 内容类型:1 健康知识、2 养生科普;不传返回全部。
  */
-export const getHealthPopularizationList = () => {
-	return request.get('/api/app/health-popularizations', {}, false, false)
+export const getHealthPopularizationList = (contentType) => {
+	const params = contentType == null ? {} : { contentType }
+	return request.get('/api/app/health-popularizations', params, false, false)
 }
diff --git a/traditionalApp/pages/publish/publish.vue b/traditionalApp/pages/publish/publish.vue
index 9f48c75..e13aa36 100644
--- a/traditionalApp/pages/publish/publish.vue
+++ b/traditionalApp/pages/publish/publish.vue
@@ -15,9 +15,7 @@
 			<view v-if="articleList.length" class="article-list">
 				<view class="article-item" v-for="item in articleList" :key="item.id" @tap="goToDetail(item.id)">
 					<image class="article-img" :src="item.coverImage || defaultCoverImage" mode="aspectFill"></image>
-					<view class="article-shadow">
-						<text class="article-text">{{ item.title }}</text>
-					</view>
+					<text class="article-title">{{ item.title }}</text>
 				</view>
 			</view>
 			<view v-else-if="loading" class="status-text">加载中...</view>
@@ -28,7 +26,7 @@
 
 <script setup lang="ts">
 import { ref, onMounted } from 'vue';
-import { onPullDownRefresh } from '@dcloudio/uni-app'
+import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
 import { getHealthPopularizationList } from './service'
 
 interface PopularizationListItem {
@@ -43,6 +41,18 @@
 const loading = ref(false)
 const articleList = ref<PopularizationListItem[]>([])
 const defaultCoverImage = '/static/image/home/zixun.png'
+// 内容类型:1 健康知识,默认按健康知识过滤。
+const contentType = ref<number>(1)
+
+/**
+ * 读取路由参数中的内容类型,用于按类型过滤列表。
+ */
+onLoad((options) => {
+	const raw = Number(options?.contentType)
+	if (!Number.isNaN(raw) && raw > 0) {
+		contentType.value = raw
+	}
+})
 
 /**
  * 页面加载时初始化状态栏高度并拉取健康知识列表。
@@ -87,7 +97,7 @@
 async function loadPopularizationList() {
 	loading.value = true
 	try {
-		const response = await getHealthPopularizationList()
+		const response = await getHealthPopularizationList(contentType.value)
 		if (response?.code !== 0 || !Array.isArray(response.data)) {
 			articleList.value = []
 			return
@@ -166,43 +176,36 @@
 
 .article-list {
 	display: flex;
-	justify-content: space-between;
-	flex-wrap: wrap;
+	flex-direction: column;
 }
 
 .article-item {
-	width: 328rpx;
-	height: 328rpx;
-	border-radius: 16rpx;
-	position: relative;
-	overflow: hidden;
-	margin-bottom: 24rpx;
-}
-
-.article-img {
-	width: 100%;
-	height: 100%;
-}
-
-.article-shadow {
-	position: absolute;
-	bottom: 0;
-	left: 0;
-	width: 100%;
-	height: 64rpx;
-	background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
 	display: flex;
 	align-items: center;
-	padding: 0 16rpx;
+	width: 100%;
+	padding: 24rpx 0;
+	border-bottom: 1rpx solid #F0F0F0;
 	box-sizing: border-box;
 }
 
-.article-text {
-	color: #FFFFFF;
-	font-size: 24rpx;
-	white-space: nowrap;
+.article-img {
+	width: 200rpx;
+	height: 150rpx;
+	border-radius: 12rpx;
+	flex-shrink: 0;
+	background-color: #F5F6F8;
+}
+
+.article-title {
+	flex: 1;
+	margin-left: 24rpx;
+	font-size: 30rpx;
+	line-height: 44rpx;
+	color: #333;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-line-clamp: 2;
 	overflow: hidden;
 	text-overflow: ellipsis;
-	width: 100%;
 }
 </style>
diff --git a/traditionalApp/pages/publish/service.js b/traditionalApp/pages/publish/service.js
index a42ca25..914e865 100644
--- a/traditionalApp/pages/publish/service.js
+++ b/traditionalApp/pages/publish/service.js
@@ -2,7 +2,9 @@
 
 /**
  * 获取 APP 健康科普列表。
+ * @param {number} [contentType] 内容类型:1 健康知识、2 养生科普;不传返回全部。
  */
-export const getHealthPopularizationList = () => {
-	return request.get('/api/app/health-popularizations', {}, false, false)
+export const getHealthPopularizationList = (contentType) => {
+	const params = contentType == null ? {} : { contentType }
+	return request.get('/api/app/health-popularizations', params, false, false)
 }

--
Gitblit v1.7.1