quling
2026-08-22 87c4b63ce203f0fb34241eda92caeccbaceb7f54
feat0806: APP养生科普健康知识功能改造
9个文件已修改
203 ■■■■■ 已修改文件
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/controller/AppHealthPopularizationController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/service/AppHealthPopularizationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/popularization/vo/AppHealthPopularizationDetailVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/articleDetail/articleDetail.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/healthPopularization/healthPopularization.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/healthPopularization/service.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/publish/publish.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/publish/service.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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));
    }
    /**
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;
    }
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;
}
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>
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>
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>
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)
}
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>
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)
}