hejianhao
2026-08-18 0142ef5a8abf3558cb429a79761ee50b482f6189
feat0806: 解压APP端静态页面
7个文件已修改
461 ■■■■■ 已修改文件
traditionalApp/pages.json 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/articleDetail/articleDetail.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/good/good.vue 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/healthCourses/healthCourses.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/home/home.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/mine/mine.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages/publish/publish.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traditionalApp/pages.json
@@ -30,6 +30,18 @@
            }
        },
        {
            "path": "pages/good/projectList",
            "style": {
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/good/projectDetail",
            "style": {
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/mineInfo/mineInfo",
            "style": {
                "navigationBarTitleText": "个人中心"
@@ -307,6 +319,12 @@
            }
        },
        {
            "path": "pages/bankCard/bankCard",
            "style": {
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/myPatients/myPatients",
            "style": {
                "navigationStyle": "custom"
@@ -335,6 +353,25 @@
            "style": {
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/passwordInput/passwordInput",
            "style": {
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/healthCoursesHub/healthCoursesHub",
            "style": {
                "navigationStyle": "custom"
            }
        },
        {
            "path": "pages/healthPopularization/healthPopularization",
            "style": {
                "navigationStyle": "custom",
                "enablePullDownRefresh": true
            }
        }
    ],
    "tabBar": {
@@ -349,16 +386,16 @@
                "text": "首页"
            },
            {
                "pagePath": "pages/healthCourses/healthCourses",
                "pagePath": "pages/healthCoursesHub/healthCoursesHub",
                "iconPath": "static/image/tabBar/book.png",
                "selectedIconPath": "static/image/tabBar/YBook.png",
                "text": "养生课程"
            },
            {
                "pagePath": "pages/publish/publish",
                "iconPath": "static/image/tabBar/study.png",
                "selectedIconPath": "static/image/tabBar/YStudy.png",
                "text": "科普"
                "pagePath": "pages/good/good",
                "iconPath": "static/image/tabBar/project.png",
                "selectedIconPath": "static/image/tabBar/Yproject.png",
                "text": "项目中心"
            },
            {
                "pagePath": "pages/mine/mine",
traditionalApp/pages/articleDetail/articleDetail.vue
@@ -22,6 +22,7 @@
        <!-- 内容区域 -->
        <view class="content-area">
            <text class="content-title">{{ detail.title || '健康科普详情' }}</text>
            <text v-if="isVideoType && videoSubtitle" class="content-subtitle">{{ videoSubtitle }}</text>
            <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>
@@ -37,6 +38,7 @@
    interface PopularizationDetail {
        id ?: number | null
        title : string
        subtitle : string
        coverImage : string
        contentType ?: number | null
        contentTypeText : string
@@ -57,6 +59,7 @@
        return {
            id: null,
            title: '',
            subtitle: '',
            coverImage: '',
            contentType: null,
            contentTypeText: '',
@@ -70,6 +73,14 @@
     * 根据内容类型判断当前详情是否属于视频详情页。
     */
    const isVideoType = computed(() => Number(detail.value.contentType) === 2)
    /**
     * 视频详情副标题,兼容后台 subtitle / summary / description 字段。
     */
    const videoSubtitle = computed(() => {
        const source = detail.value as PopularizationDetail & { summary ?: string; description ?: string; subTitle ?: string }
        return String(source.subtitle || source.subTitle || source.summary || source.description || '').trim()
    })
    /**
     * 将富文本正文转换为 `rich-text` 可直接消费的节点字符串。
@@ -251,6 +262,15 @@
        line-height: 48rpx;
    }
    .content-subtitle {
        display: block;
        margin-top: 12rpx;
        font-size: 12px;
        font-family: 'PingFang SC', 'PingFang-SC', sans-serif;
        color: #5B6475;
        line-height: 1.5;
    }
    .rich-content {
        margin-top: 24rpx;
        width: 100%;
traditionalApp/pages/good/good.vue
@@ -1,24 +1,174 @@
<template>
    <view class="container">
        <view class="status-bar-placeholder" :style="{ height: statusBarHeight + 'px' }"></view>
        <view class="nav-bar">
            <text class="nav-title">项目中心</text>
        </view>
        <view v-if="!unlocked" class="password-content">
            <view class="input-wrap">
                <input
                    class="password-input"
                    password
                    v-model="password"
                    placeholder="请输入密码"
                    placeholder-class="input-placeholder"
                    :focus="autoFocus"
                    @confirm="handleConfirm"
                />
            </view>
            <view class="confirm-btn" :class="{ disabled: !password }" @tap="handleConfirm">确认</view>
        </view>
        <view v-else class="entry-content">
            <image class="entry-image" src="/static/image/project/jieshao.png" mode="widthFix"
                hover-class="entry-image--pressed" hover-stay-time="80" @tap="goList('platform', '平台介绍')"></image>
            <image class="entry-image" src="/static/image/project/moshi.png" mode="widthFix"
                hover-class="entry-image--pressed" hover-stay-time="80" @tap="goList('mode', '营销模式')"></image>
            <image class="entry-image" src="/static/image/project/dongzuo.png" mode="widthFix"
                hover-class="entry-image--pressed" hover-stay-time="80" @tap="goList('action', '营销动作')"></image>
        </view>
    </view>
</template>
<script setup lang="ts">
    import { onLoad } from "@dcloudio/uni-app";
    import { ref } from "vue";
import { onMounted, ref } from 'vue'
    onLoad((params) => {
/** 项目中心访问密码,后续改为接口读取后台配置。 */
const PROJECT_CENTER_PASSWORD = '123456'
    });
const statusBarHeight = ref(0)
const password = ref('')
const autoFocus = ref(true)
const unlocked = ref(false)
onMounted(() => {
    uni.getSystemInfo({
        success: (res) => {
            statusBarHeight.value = res.statusBarHeight || 0
        }
    })
})
/**
 * 校验项目中心密码,通过后展示三个入口。
 */
function handleConfirm() {
    if (!password.value) {
        return
    }
    if (password.value !== PROJECT_CENTER_PASSWORD) {
        uni.showToast({
            title: '密码错误,请重新输入',
            icon: 'none'
        })
        return
    }
    unlocked.value = true
    password.value = ''
    autoFocus.value = false
}
/**
 * 进入对应入口的静态列表页。
 */
function goList(type: string, title: string) {
    uni.navigateTo({
        url: `/pages/good/projectList?type=${encodeURIComponent(type)}&title=${encodeURIComponent(title)}`
    })
}
</script>
<style>
    page {
        background-color: #fff;
    }
page, html {
    background-color: #ffffff;
}
</style>
<style scoped lang="scss">
    .container {}
</style>
.container {
    width: 750rpx;
    min-height: 100vh;
    background-color: #ffffff;
}
.status-bar-placeholder {
    width: 100%;
}
.nav-bar {
    width: 100%;
    height: 88rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    .nav-title {
        font-size: 36rpx;
        font-weight: 600;
        color: #1a1a1a;
    }
}
.password-content {
    padding: 80rpx 40rpx 0;
    box-sizing: border-box;
}
.input-wrap {
    width: 670rpx;
    height: 108rpx;
    border: 1rpx solid #e5e5e5;
    border-radius: 16rpx;
    display: flex;
    align-items: center;
    padding: 0 32rpx;
    box-sizing: border-box;
    background: #ffffff;
    margin-bottom: 48rpx;
    .password-input {
        width: 100%;
        height: 100%;
        font-size: 32rpx;
        color: #333;
    }
    .input-placeholder {
        color: #999;
        font-size: 32rpx;
    }
}
.confirm-btn {
    width: 670rpx;
    height: 104rpx;
    background: #c63520;
    border-radius: 16rpx;
    color: #ffffff;
    font-size: 34rpx;
    font-weight: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    &.disabled {
        opacity: 0.5;
    }
}
.entry-content {
    padding: 32rpx 40rpx 0;
    box-sizing: border-box;
    .entry-image {
        display: block;
        width: 670rpx;
        margin-bottom: 0;
        &--pressed {
            opacity: 0.92;
        }
    }
}
</style>
traditionalApp/pages/healthCourses/healthCourses.vue
@@ -5,7 +5,8 @@
        
        <!-- 导航栏 -->
        <view class="nav-bar">
            <view class="nav-title">养生课程</view>
            <image class="back-icon" src="/static/image/home/left.png" mode="aspectFit" @tap="goBack"></image>
            <view class="nav-title">养生功法</view>
            <image class="search-icon" src="/static/image/healthCourses/search.png" mode="aspectFit" @tap="goSearch"></image>
        </view>
@@ -153,6 +154,20 @@
function goSearch() {
    uni.navigateTo({
        url: '/pages/search/search'
    })
}
/**
 * 返回上一页;没有历史栈时回到养生课程中转页。
 */
function goBack() {
    const currentPages = getCurrentPages()
    if (currentPages.length > 1) {
        uni.navigateBack()
        return
    }
    uni.switchTab({
        url: '/pages/healthCoursesHub/healthCoursesHub'
    })
}
@@ -327,20 +342,30 @@
    height: 88rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    justify-content: space-between;
    padding-left: 32rpx;
    padding-right: 32rpx;
    position: relative;
    background-color: #fff;
    box-sizing: border-box;
    .back-icon {
        flex-shrink: 0;
        width: 40rpx;
        height: 40rpx;
    }
}
.nav-title {
    font-size: 36rpx;
    flex: 1;
    text-align: center;
    font-size: 32rpx;
    font-weight: 600;
    color: #333;
    color: #1A1A1A;
}
.search-icon {
    position: absolute;
    right: 48rpx;
    flex-shrink: 0;
    width: 28rpx;
    height: 28rpx;
}
traditionalApp/pages/home/home.vue
@@ -39,16 +39,32 @@
                </view>
            </view>
            <!-- Quick Entry: 微赞直播 + 经营者培训 -->
            <view class="quick-entry-row">
                <view class="quick-entry-card" hover-class="quick-entry-card--pressed" hover-stay-time="80"
                    @tap="goWeiZanLive">
                    <image src="/static/image/home/weizan-live.png" mode="aspectFill" class="quick-entry-card__img"></image>
                </view>
                <view class="quick-entry-card" hover-class="quick-entry-card--pressed" hover-stay-time="80"
                    @tap="goOperatorTraining">
                    <template v-if="operatorTrainingLiveCourse && operatorTrainingLiveCourse.displayStatus === 'LIVING'">
                        <image :src="operatorTrainingLiveCourse.displayImage || defaultLiveCover" mode="aspectFill"
                            class="quick-entry-card__img"></image>
                        <image src="/static/image/home/zhibo.png" class="quick-entry-card__living-tag"
                            mode="aspectFill"></image>
                    </template>
                    <template v-else>
                        <image :src="operatorTrainingLiveCourse?.displayImage || defaultLiveCover" mode="aspectFill"
                            class="quick-entry-card__img"></image>
                    </template>
                </view>
            </view>
            <!-- Live Section -->
            <view v-if="liveCourseList.length" class="section">
                <view class="section-header">
                    <text class="section-title">养生直播</text>
                    <image src="/static/image/home/conentHead.png" mode="aspectFill" class="header-deco"></image>
                    <image src="/static/image/home/circle.png" class="header-circle" mode="aspectFill"></image>
                </view>
            <view v-if="advancedLiveCourseList.length" class="section">
                <view class="live-list">
                    <view v-for="item in liveCourseList" :key="item.courseId" class="live-card">
                    <view v-for="item in advancedLiveCourseList" :key="item.courseId" class="live-card">
                        <image :src="item.displayImage || defaultLiveCover" mode="aspectFill" class="live-bg"
                            @tap="handleLiveImageClick(item)"></image>
                        <image v-if="item.displayStatus === 'LIVING'" src="/static/image/home/zhibo.png"
@@ -66,9 +82,9 @@
            </view>
            <!-- Course Section -->
            <view v-if="featuredHealthCourseList.length || featuredPopularizationList.length" class="section">
            <view v-if="featuredHealthCourseList.length" class="section">
                <view class="section-header">
                    <text class="section-title">精选课程介绍</text>
                    <text class="section-title">精选课程</text>
                    <image src="/static/image/home/conentHead.png" class="header-deco"></image>
                    <image src="/static/image/home/circle.png" style="left: 238rpx !important;" class="header-circle"
                        mode="aspectFill"></image>
@@ -99,8 +115,16 @@
                        </view>
                    </view>
                </view>
            </view>
                <!-- Articles Section -->
            <!-- Popularization Section -->
            <view v-if="featuredPopularizationList.length" class="section">
                <view class="section-header">
                    <text class="section-title">养生科普</text>
                    <image src="/static/image/home/conentHead.png" class="header-deco"></image>
                    <image src="/static/image/home/circle.png" style="left: 238rpx !important;" class="header-circle"
                        mode="aspectFill"></image>
                </view>
                <view v-if="featuredPopularizationList.length" class="article-grid">
                    <view v-for="item in featuredPopularizationList" :key="item.id" class="article-card"
                        @tap="goPopularizationDetail(item.id)">
@@ -175,6 +199,21 @@
    const shouldUsePlainIosPlayIcon = computed(() => {
        const systemInfo = uni.getSystemInfoSync();
        return String(systemInfo.platform || '').toLowerCase() === 'ios';
    });
    const weiZanLiveH5Url = 'https://www.vzan.com';
    const advancedLiveCourseList = computed(() => {
        if (!Array.isArray(liveCourseList.value) || liveCourseList.value.length === 0) {
            return [];
        }
        return liveCourseList.value.filter((item) => !String(item.title || '').includes('筑基'));
    });
    const operatorTrainingLiveCourse = computed(() => {
        const list = advancedLiveCourseList.value;
        if (!Array.isArray(list) || list.length === 0) {
            return null;
        }
        const livingCourse = list.find((item) => item.displayStatus === 'LIVING');
        return livingCourse || list[0];
    });
    /**
@@ -271,6 +310,36 @@
        });
        return;
    }
    /**
     * 跳转微赞直播 H5 页面(后端接口暂未提供,先使用静态链接)。
     */
    const goWeiZanLive = () => {
        uni.navigateTo({
            url: `/pages/webview/webview?url=${encodeURIComponent(weiZanLiveH5Url)}&title=${encodeURIComponent('微赞直播')}`
        });
    };
    /**
     * 经营者培训入口跳转:
     *  - 先进入密码验证页,验证成功后:
     *    - 有正在直播的场次(LIVING)→ 跳转直播视频播放页(watchLive)
     *    - 无正在直播 → 跳转课程介绍页(liveDetail)
     */
    const goOperatorTraining = () => {
        const targetCourse = operatorTrainingLiveCourse.value;
        let targetPageUrl = '';
        if (!targetCourse || !targetCourse.courseId) {
            targetPageUrl = '/pages/liveDetail/liveDetail';
        } else if (targetCourse.displayStatus === 'LIVING') {
            targetPageUrl = `/pages/watchLive/watchLive?courseId=${targetCourse.courseId}`;
        } else {
            targetPageUrl = `/pages/liveDetail/liveDetail?id=${targetCourse.courseId}`;
        }
        uni.navigateTo({
            url: `/pages/passwordInput/passwordInput?targetUrl=${encodeURIComponent(targetPageUrl)}`
        });
    };
    /**
     * 请求首页养生直播模块列表。
@@ -689,6 +758,45 @@
        transform: translateY(-1rpx);
    }
    /* Quick Entry Row */
    .quick-entry-row {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 702rpx;
        margin: 26rpx auto 0;
        .quick-entry-card {
            position: relative;
            width: 340rpx;
            height: 160rpx;
            border-radius: 16rpx;
            overflow: hidden;
            background: #fff;
            box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
            transition: transform 0.12s ease, box-shadow 0.12s ease;
            .quick-entry-card__img {
                width: 100%;
                height: 100%;
                display: block;
            }
            .quick-entry-card__living-tag {
                width: 72rpx;
                height: 24rpx;
                position: absolute;
                right: 10rpx;
                top: 10rpx;
            }
        }
        .quick-entry-card--pressed {
            transform: scale(0.97);
            box-shadow: 0 3rpx 10rpx rgba(0, 0, 0, 0.05);
        }
    }
    /* Sections */
    .section {
traditionalApp/pages/mine/mine.vue
@@ -137,6 +137,7 @@
            { title: '我的下载', icon: '/static/image/mine/downLoad.png', url: '/pages/downloads/downloads', requiresLogin: true },
            { title: '我的患者', icon: '/static/image/mine/person.png', url: '/pages/myPatients/myPatients', requiresLogin: true },
            { title: '诊所信息', icon: '/static/image/mine/hosptial.png', url: '/pages/clinicInfo/clinicInfo', requiresLogin: true },
            { title: '银行卡设置', icon: '/static/image/mine/yinghangka.png', url: '/pages/bankCard/bankCard', requiresLogin: true },
            { title: '二维码', icon: '/static/image/mine/code.png', url: '/pages/inviteUser/inviteUser', requiresLogin: true },
        ]
    }
@@ -149,11 +150,19 @@
    ]
})
const WEIZAN_LIVE_STATS_URL = 'https://www.vzan.com'
const WEIZAN_LIVE_ORDER_URL = 'https://www.vzan.com'
const bottomMenuList = computed<MenuItem[]>(() => {
    const baseList = [
        { title: '我的课程', icon: '/static/image/mine/myClass.png', url: '/pages/myCourses/myCourses', iconClass: 'icon-myclass', requiresLogin: true },
        { title: '帮助中心', icon: '/static/image/mine/help.png', url: '/pages/helpCenter/helpCenter', iconClass: 'icon-help' },
        { title: '意见反馈', icon: '/static/image/mine/message.png', url: '/pages/feedback/feedback', iconClass: 'icon-feedback' },
    ]
    const liveOrderItem = { title: '直播订单', icon: '/static/image/mine/dingdan.png', url: buildWebviewUrl(WEIZAN_LIVE_ORDER_URL, '直播订单'), iconClass: 'icon-live-order', requiresLogin: true }
    const liveMenuList = [
        { title: '直播统计', icon: '/static/image/mine/tongji.png', url: buildWebviewUrl(WEIZAN_LIVE_STATS_URL, '直播统计'), iconClass: 'icon-live-stats', requiresLogin: true },
        liveOrderItem,
    ]
    const agreementList = [
        { title: '用户协议', icon: '/static/image/mine/coustom.png', url: '/pages/protocol/protocol?type=user', iconClass: 'icon-user-agree' },
@@ -161,15 +170,27 @@
    ]
    if (userRole.value === 'doctor') {
        return [
            ...liveMenuList,
            ...baseList,
            { title: '平台资质', icon: '/static/image/mine/chimaping.png', url: '/pages/qualification/qualification', iconClass: 'icon-platform' },
            ...agreementList,
        ]
    }
    return [...baseList, ...agreementList]
    return [
        liveOrderItem,
        ...baseList,
        ...agreementList,
    ]
})
/**
 * 生成微赞 H5 内嵌页地址。
 */
function buildWebviewUrl(url: string, title: string) {
    return `/pages/webview/webview?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`
}
/**
 * 创建个人中心默认数据,便于首屏兜底。
 */
function createDefaultUserCenter(): UserCenterData {
traditionalApp/pages/publish/publish.vue
@@ -5,7 +5,9 @@
        
        <!-- 搜索头部区域/导航栏 -->
        <view class="nav-bar">
            <text class="nav-title">健康科普</text>
            <image class="back-icon" src="/static/image/home/left.png" mode="aspectFit" @tap="goBack"></image>
            <text class="nav-title">健康知识</text>
            <view class="nav-right-placeholder"></view>
        </view>
        <!-- 科普内容区域 -->
@@ -19,7 +21,7 @@
                </view>
            </view>
            <view v-else-if="loading" class="status-text">加载中...</view>
            <view v-else class="status-text">暂无健康科普内容</view>
            <view v-else class="status-text">暂无健康知识内容</view>
        </view>
    </view>
</template>
@@ -43,7 +45,7 @@
const defaultCoverImage = '/static/image/home/zixun.png'
/**
 * 页面加载时初始化状态栏高度并拉取健康科普列表。
 * 页面加载时初始化状态栏高度并拉取健康知识列表。
 */
onMounted(() => {
    uni.getSystemInfo({
@@ -53,6 +55,20 @@
    });
    loadPopularizationList()
});
/**
 * 返回上一页;没有历史栈时回到养生课程中转页。
 */
function goBack() {
    const currentPages = getCurrentPages()
    if (currentPages.length > 1) {
        uni.navigateBack()
        return
    }
    uni.switchTab({
        url: '/pages/healthCoursesHub/healthCoursesHub'
    })
}
/**
 * 下拉刷新健康科普列表,并在结束后关闭原生刷新状态。
@@ -112,15 +128,29 @@
    height: 88rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    justify-content: space-between;
    padding: 0 32rpx;
}
.back-icon {
    width: 40rpx;
    height: 40rpx;
    flex-shrink: 0;
}
.nav-title {
    flex: 1;
    text-align: center;
    font-size: 36rpx;
    font-weight: 600;
    color: #333;
}
.nav-right-placeholder {
    width: 40rpx;
    flex-shrink: 0;
}
.content {
    margin-top: 32rpx;
    padding: 0 32rpx;