13404089107
2026-05-15 24147cb2de6f8761e7d0d27e49e00f8d499ae8d8
feat: 优化文章详情页图片自适应,完善课程ID解析逻辑

1. 为文章详情页富文本图片添加移动端自适应样式,适配小屏幕设备
2. 完善课程详情页的课程ID解析函数,支持从hash路由获取参数
3. 统一格式化两个页面的代码缩进、样式写法与多余空行
4. 更新应用版本至1.0.14,修正manifest.json格式问题
3个文件已修改
485 ■■■■■ 已修改文件
traditionalApp/manifest.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/articleDetail/articleDetail.vue 375 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/courseDetail/courseDetail.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/manifest.json
@@ -3,8 +3,8 @@
    "name" : "海诊康道",
    "appid" : "__UNI__1A90541",
    "description" : "",
    "versionName" : "1.0.11",
    "versionCode" : 111,
    "versionName" : "1.0.14",
    "versionCode" : 114,
    "transformPx" : false,
    /* 5+App特有相关 */
    "app-plus" : {
traditionalApp/pages/articleDetail/articleDetail.vue
@@ -2,7 +2,7 @@
    <view class="container">
        <!-- 状态栏占位 -->
        <view class="status-bar-placeholder" :style="{ height: statusBarHeight + 'px' }"></view>
        <!-- 导航栏 -->
        <view class="nav-bar">
            <image class="back-icon" src="/static/image/home/left.png" mode="aspectFit" @tap="goBack"></image>
@@ -11,12 +11,7 @@
        <!-- 视频播放器区域 -->
        <view v-if="isVideoType" class="video-container">
            <video
                class="video-player"
                :src="detail.videoUrl"
                controls
                :autoplay="false"
            ></video>
            <video class="video-player" :src="detail.videoUrl" controls :autoplay="false"></video>
        </view>
        <!-- 图文封面区域 -->
@@ -35,217 +30,219 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getHealthPopularizationDetail } from './service'
    import { computed, onMounted, ref } from 'vue'
    import { onLoad } from '@dcloudio/uni-app'
    import { getHealthPopularizationDetail } from './service'
interface PopularizationDetail {
    id?: number | null
    title: string
    coverImage: string
    contentType?: number | null
    contentTypeText: string
    content: string
    videoUrl: string
    videoFileName: string
}
const statusBarHeight = ref(0)
const loading = ref(false)
const popularizationId = ref('')
const detail = ref<PopularizationDetail>(createDefaultDetail())
/**
 * 创建健康科普详情页默认数据,避免接口返回前页面闪空。
 */
function createDefaultDetail(): PopularizationDetail {
    return {
        id: null,
        title: '',
        coverImage: '',
        contentType: null,
        contentTypeText: '',
        content: '',
        videoUrl: '',
        videoFileName: '',
    interface PopularizationDetail {
        id ?: number | null
        title : string
        coverImage : string
        contentType ?: number | null
        contentTypeText : string
        content : string
        videoUrl : string
        videoFileName : string
    }
}
/**
 * 根据内容类型判断当前详情是否属于视频详情页。
 */
const isVideoType = computed(() => Number(detail.value.contentType) === 2)
    const statusBarHeight = ref(0)
    const loading = ref(false)
    const popularizationId = ref('')
    const detail = ref<PopularizationDetail>(createDefaultDetail())
/**
 * 将富文本正文转换为 `rich-text` 可直接消费的节点字符串。
 */
const contentNodes = computed(() => buildRichTextNodes(detail.value.content, '暂无详情内容'))
/**
 * 初始化导航栏所需的状态栏高度。
 */
onMounted(() => {
    uni.getSystemInfo({
        success: (res) => {
            statusBarHeight.value = res.statusBarHeight || 0
    /**
     * 创建健康科普详情页默认数据,避免接口返回前页面闪空。
     */
    function createDefaultDetail() : PopularizationDetail {
        return {
            id: null,
            title: '',
            coverImage: '',
            contentType: null,
            contentTypeText: '',
            content: '',
            videoUrl: '',
            videoFileName: '',
        }
    })
})
/**
 * 页面加载时读取科普主键并拉取详情接口。
 */
onLoad((options) => {
    popularizationId.value = typeof options?.id === 'string' ? options.id : ''
    loadDetail()
})
/**
 * 拉取健康科普详情接口并更新页面状态。
 */
async function loadDetail() {
    if (!popularizationId.value) {
        detail.value = createDefaultDetail()
        return
    }
    loading.value = true
    try {
        const response = await getHealthPopularizationDetail(popularizationId.value)
        if (response?.code !== 0 || !response.data) {
            showLoadFailedToast()
    /**
     * 根据内容类型判断当前详情是否属于视频详情页。
     */
    const isVideoType = computed(() => Number(detail.value.contentType) === 2)
    /**
     * 将富文本正文转换为 `rich-text` 可直接消费的节点字符串。
     */
    const contentNodes = computed(() => buildRichTextNodes(detail.value.content, '暂无详情内容'))
    /**
     * 初始化导航栏所需的状态栏高度。
     */
    onMounted(() => {
        uni.getSystemInfo({
            success: (res) => {
                statusBarHeight.value = res.statusBarHeight || 0
            }
        })
    })
    /**
     * 页面加载时读取科普主键并拉取详情接口。
     */
    onLoad((options) => {
        popularizationId.value = typeof options?.id === 'string' ? options.id : ''
        loadDetail()
    })
    /**
     * 拉取健康科普详情接口并更新页面状态。
     */
    async function loadDetail() {
        if (!popularizationId.value) {
            detail.value = createDefaultDetail()
            return
        }
        // 图文和视频共用一个详情接口,前端只按内容类型切换展示结构。
        detail.value = {
            ...createDefaultDetail(),
            ...response.data,
        loading.value = true
        try {
            const response = await getHealthPopularizationDetail(popularizationId.value)
            if (response?.code !== 0 || !response.data) {
                showLoadFailedToast()
                return
            }
            // 图文和视频共用一个详情接口,前端只按内容类型切换展示结构。
            detail.value = {
                ...createDefaultDetail(),
                ...response.data,
            }
        } catch {
            showLoadFailedToast()
        } finally {
            loading.value = false
        }
    } catch {
        showLoadFailedToast()
    } finally {
        loading.value = false
    }
}
/**
 * 详情加载失败时给出统一提示。
 */
function showLoadFailedToast() {
    uni.showToast({
        title: '健康科普详情加载失败',
        icon: 'none',
    })
}
    /**
     * 详情加载失败时给出统一提示。
     */
    function showLoadFailedToast() {
        uni.showToast({
            title: '健康科普详情加载失败',
            icon: 'none',
        })
    }
/**
 * 将富文本内容转换为 `rich-text` 可直接渲染的节点。
 */
function buildRichTextNodes(content?: string, fallbackText?: string) {
    if (typeof content === 'string' && content.trim()) {
        return content
    /**
     * 将富文本内容转换为 `rich-text` 可直接渲染的节点。
     */
    function buildRichTextNodes(content ?: string, fallbackText ?: string) {
        if (typeof content === 'string' && content.trim()) {
            return content.replace(/<img/gi,
                '<img style="max-width:100%;height:auto;margin:0 auto;display:block"')
        }
        if (typeof fallbackText === 'string' && fallbackText.trim()) {
            return `<p>${fallbackText.trim()}</p>`
        }
        return '<p></p>'
    }
    if (typeof fallbackText === 'string' && fallbackText.trim()) {
        return `<p>${fallbackText.trim()}</p>`
    }
    return '<p></p>'
}
/**
 * 返回上一页;若没有历史栈,则回到首页避免停留在空白上下文。
 */
function goBack() {
    const currentPages = getCurrentPages()
    if (currentPages.length > 1) {
        uni.navigateBack()
        return
    /**
     * 返回上一页;若没有历史栈,则回到首页避免停留在空白上下文。
     */
    function goBack() {
        const currentPages = getCurrentPages()
        if (currentPages.length > 1) {
            uni.navigateBack()
            return
        }
        uni.switchTab({
            url: '/pages/home/home'
        })
    }
    uni.switchTab({
        url: '/pages/home/home'
    })
}
</script>
<style>
page, html {
    background-color: #F5F6F8;
}
    page,
    html {
        background-color: #F5F6F8;
    }
</style>
<style lang="scss" scoped>
.container {
    min-height: 100vh;
    background-color: #F5F6F8;
}
    .container {
        min-height: 100vh;
        background-color: #F5F6F8;
    }
.nav-bar {
    height: 88rpx;
    display: flex;
    align-items: center;
    padding: 0 24rpx;
    background-color: #fff;
    position: relative;
}
    .nav-bar {
        height: 88rpx;
        display: flex;
        align-items: center;
        padding: 0 24rpx;
        background-color: #fff;
        position: relative;
    }
.back-icon {
    width: 40rpx;
    height: 40rpx;
    position: absolute;
    left: 24rpx;
    z-index: 10;
}
    .back-icon {
        width: 40rpx;
        height: 40rpx;
        position: absolute;
        left: 24rpx;
        z-index: 10;
    }
.nav-title {
    flex: 1;
    text-align: center;
    font-size: 36rpx;
    font-weight: 500;
    color: #333;
}
    .nav-title {
        flex: 1;
        text-align: center;
        font-size: 36rpx;
        font-weight: 500;
        color: #333;
    }
.video-container {
    width: 100%;
    height: 402rpx;
}
    .video-container {
        width: 100%;
        height: 402rpx;
    }
.video-player {
    width: 100%;
    height: 100%;
}
    .video-player {
        width: 100%;
        height: 100%;
    }
.cover-container {
    width: 100%;
    height: 402rpx;
}
    .cover-container {
        width: 100%;
        height: 402rpx;
    }
.cover-image {
    width: 100%;
    height: 100%;
}
    .cover-image {
        width: 100%;
        height: 100%;
    }
.content-area {
    padding: 32rpx;
    background-color: #F5F6F8;
}
    .content-area {
        padding: 32rpx;
        background-color: #F5F6F8;
    }
.content-title {
    font-size: 32rpx;
    font-weight: 500;
    color: #333;
    line-height: 48rpx;
}
    .content-title {
        font-size: 32rpx;
        font-weight: 500;
        color: #333;
        line-height: 48rpx;
    }
.rich-content {
    margin-top: 24rpx;
    width: 100%;
    font-size: 28rpx;
    line-height: 1.8;
    color: #333;
}
    .rich-content {
        margin-top: 24rpx;
        width: 100%;
        font-size: 28rpx;
        line-height: 1.8;
        color: #333;
    }
.status-text {
    margin-top: 24rpx;
    font-size: 26rpx;
    line-height: 40rpx;
    color: #86909C;
}
</style>
    .status-text {
        margin-top: 24rpx;
        font-size: 26rpx;
        line-height: 40rpx;
        color: #86909C;
    }
</style>
traditionalApp/pages/courseDetail/courseDetail.vue
@@ -205,7 +205,7 @@
                    <view v-if="!loading && allVideos.length === 0" class="empty-text">暂无课程章节</view>
                </view>
            </template>
            <view v-if="loading" class="loading-text">加载中...</view>
            <view v-if="isReadonlyDisplayMode" class="open-app-banner" @tap="openApp">
                <view class="app-logo-placeholder"></view>
@@ -529,32 +529,32 @@
     * 页面加载时读取课程主键并拉取详情接口。
     */
    onLoad((options) => {
    courseId.value = resolveCourseId(options)
        courseId.value = resolveCourseId(options)
        loadCourseDetail()
    })
/**
 * 统一解析课程 id,兼容 App 路由参数和 H5 分享页地址参数。
 */
function resolveCourseId(options?: Record<string, unknown>) {
    const routeCourseId = typeof options?.id === 'string' ? options.id.trim() : ''
    if (routeCourseId) {
        return routeCourseId
    }
    // #ifdef H5
    try {
        const searchCourseId = new URLSearchParams(window.location.search).get('id')?.trim() || ''
        if (searchCourseId) {
            return searchCourseId
    /**
     * 统一解析课程 id,兼容 App 路由参数和 H5 分享页地址参数。
     */
    function resolveCourseId(options ?: Record<string, unknown>) {
        const routeCourseId = typeof options?.id === 'string' ? options.id.trim() : ''
        if (routeCourseId) {
            return routeCourseId
        }
        const hashQuery = window.location.hash.split('?')[1] || ''
        return new URLSearchParams(hashQuery).get('id')?.trim() || ''
    } catch (error) {
        console.log('解析课程分享参数失败', error)
        // #ifdef H5
        try {
            const searchCourseId = new URLSearchParams(window.location.search).get('id')?.trim() || ''
            if (searchCourseId) {
                return searchCourseId
            }
            const hashQuery = window.location.hash.split('?')[1] || ''
            return new URLSearchParams(hashQuery).get('id')?.trim() || ''
        } catch (error) {
            console.log('解析课程分享参数失败', error)
        }
        // #endif
        return ''
    }
    // #endif
    return ''
}
    /**
     * 页面显示时开启播放器安全能力。
@@ -890,19 +890,19 @@
     * 拉起或跳转下载 APP
     */
    function openApp() {
    const openAppUrl = 'https://m.malink.cn/s/7bIzeu'
    // #ifdef H5
    window.location.href = openAppUrl
    return
    // #endif
    // #ifdef APP-PLUS
    plus.runtime.openURL(openAppUrl)
    return
    // #endif
    uni.showToast({
        title: '当前环境不支持打开链接',
        icon: 'none',
    })
        const openAppUrl = 'https://m.malink.cn/s/7bIzeu'
        // #ifdef H5
        window.location.href = openAppUrl
        return
        // #endif
        // #ifdef APP-PLUS
        plus.runtime.openURL(openAppUrl)
        return
        // #endif
        uni.showToast({
            title: '当前环境不支持打开链接',
            icon: 'none',
        })
    }
    /**
@@ -918,26 +918,26 @@
     * 生成课程 H5 分享链接,供微信好友打开页面详情。
     */
    function buildCourseShareUrl() {
    const shareBaseUrl = 'https://haizhentong.com/share/'
    const shareCourseId = resolveShareCourseId()
    if (!shareCourseId) {
        return shareBaseUrl
    }
    return `${shareBaseUrl}?id=${encodeURIComponent(shareCourseId)}`
        const shareBaseUrl = 'https://haizhentong.com/share/'
        const shareCourseId = resolveShareCourseId()
        if (!shareCourseId) {
            return shareBaseUrl
        }
        return `${shareBaseUrl}?id=${encodeURIComponent(shareCourseId)}`
    }
/**
 * 生成分享使用的课程 id,优先使用路由参数,详情数据兜底。
 */
function resolveShareCourseId() {
    if (courseId.value) {
        return courseId.value
    /**
     * 生成分享使用的课程 id,优先使用路由参数,详情数据兜底。
     */
    function resolveShareCourseId() {
        if (courseId.value) {
            return courseId.value
        }
        if (courseDetail.value.id !== undefined && courseDetail.value.id !== null) {
            return String(courseDetail.value.id)
        }
        return ''
    }
    if (courseDetail.value.id !== undefined && courseDetail.value.id !== null) {
        return String(courseDetail.value.id)
    }
    return ''
}
    /**
     * 通过微信好友聊天场景分享当前课程。
@@ -1924,4 +1924,4 @@
        background-color: #E5E6EB;
        color: #86909C;
    }
</style>
</style>