From e782b94370da4b44e0f308f723b7d94148575a37 Mon Sep 17 00:00:00 2001
From: hejianhao <15708179461@qq.com>
Date: 星期二, 18 八月 2026 10:11:35 +0800
Subject: [PATCH] feat0806: 提交遗漏的APP文件

---
 traditionalApp/pages/bankCard/service.js                           |   15 
 traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue         |  105 ++++
 traditionalApp/static/image/mine/yinghangka.png                    |    0 
 traditionalApp/static/image/tabBar/Yproject.png                    |    0 
 traditionalApp/static/image/mine/dingdan.png                       |    0 
 traditionalApp/pages/healthPopularization/service.js               |    8 
 traditionalApp/static/image/healthCoursesHub/gongfa.png            |    0 
 traditionalApp/static/image/project/jieshao.png                    |    0 
 traditionalApp/static/image/mine/zhibodingdan.png                  |    0 
 traditionalApp/static/image/project/dongzuo.png                    |    0 
 traditionalApp/pages/passwordInput/passwordInput.vue               |  185 ++++++++
 traditionalApp/static/image/home/weizan-live.png                   |    0 
 traditionalApp/pages/good/projectDetail.vue                        |  204 ++++++++
 traditionalApp/static/image/tabBar/project.png                     |    0 
 traditionalApp/static/image/mine/tongji.png                        |    0 
 traditionalApp/static/image/mine/zhibotongji.png                   |    0 
 traditionalApp/static/image/mine/yinghanka.png                     |    0 
 traditionalApp/static/image/project/moshi.png                      |    0 
 traditionalApp/pages/good/static-content.js                        |  125 +++++
 traditionalApp/pages/healthPopularization/healthPopularization.vue |  205 ++++++++
 traditionalApp/static/image/healthCoursesHub/zhishi.png            |    0 
 traditionalApp/static/image/healthCoursesHub/kepu.png              |    0 
 traditionalApp/pages/bankCard/bankCard.vue                         |  289 ++++++++++++
 traditionalApp/pages/good/projectList.vue                          |  210 +++++++++
 24 files changed, 1,346 insertions(+), 0 deletions(-)

diff --git a/traditionalApp/pages/bankCard/bankCard.vue b/traditionalApp/pages/bankCard/bankCard.vue
new file mode 100644
index 0000000..6e13785
--- /dev/null
+++ b/traditionalApp/pages/bankCard/bankCard.vue
@@ -0,0 +1,289 @@
+<template>
+	<view class="container">
+		<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
+			<view class="nav-content">
+				<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>
+		</view>
+
+		<view class="form">
+			<view class="form-item" v-for="item in fieldList" :key="item.key">
+				<view class="label">{{ item.label }}</view>
+				<view class="input-wrapper">
+					<input
+						class="input"
+						type="text"
+						:placeholder="item.placeholder"
+						placeholder-class="placeholder-style"
+						v-model="form[item.key]"
+					/>
+				</view>
+				<view class="line"></view>
+			</view>
+		</view>
+
+		<view class="submit-btn" :class="{ disabled: submitting }" @tap="submit">保存</view>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue'
+import { getBankCardDetail, updateBankCard } from './service.js'
+
+type BankCardFieldKey = 'bankName' | 'accountName' | 'accountNumber' | 'branchName'
+
+interface BankCardForm {
+	bankName: string
+	accountName: string
+	accountNumber: string
+	branchName: string
+}
+
+const statusBarHeight = ref(20)
+const submitting = ref(false)
+const form = reactive<BankCardForm>({
+	bankName: '',
+	accountName: '',
+	accountNumber: '',
+	branchName: '',
+})
+const fieldList: Array<{ key: BankCardFieldKey; label: string; placeholder: string }> = [
+	{ key: 'bankName', label: '银行名称', placeholder: '请输入银行名称' },
+	{ key: 'accountName', label: '账户名称', placeholder: '请输入账户名称' },
+	{ key: 'accountNumber', label: '账号', placeholder: '请输入账号' },
+	{ key: 'branchName', label: '支行名称', placeholder: '请输入支行名称' },
+]
+
+onMounted(() => {
+	const systemInfo = uni.getSystemInfoSync()
+	statusBarHeight.value = systemInfo.statusBarHeight || 20
+	loadBankCard()
+})
+
+/**
+ * 拉取已保存的银行卡信息并回显。
+ */
+async function loadBankCard() {
+	try {
+		const response = await getBankCardDetail()
+		if (!isSuccessResponse(response) || !response.data) {
+			return
+		}
+		fillForm(response.data)
+	} catch {
+		// 接口暂未就绪时保留空白表单,不影响页面展示。
+	}
+}
+
+/**
+ * 校验并保存银行卡信息。
+ */
+async function submit() {
+	if (submitting.value) {
+		return
+	}
+	if (!validateForm()) {
+		return
+	}
+	submitting.value = true
+	try {
+		const payload = {
+			bankName: form.bankName.trim(),
+			accountName: form.accountName.trim(),
+			accountNumber: form.accountNumber.trim(),
+			branchName: form.branchName.trim(),
+		}
+		const response = await updateBankCard(payload)
+		if (!isSuccessResponse(response)) {
+			return
+		}
+		if (response.data) {
+			fillForm(response.data)
+		}
+		uni.showToast({
+			title: '保存成功',
+			icon: 'success',
+		})
+		setTimeout(() => {
+			goBack()
+		}, 800)
+	} catch (error: any) {
+		uni.showToast({
+			title: error?.message || '银行卡信息保存失败,请稍后重试',
+			icon: 'none',
+		})
+	} finally {
+		submitting.value = false
+	}
+}
+
+/**
+ * 保存前校验四个必填字段。
+ */
+function validateForm() {
+	if (!form.bankName.trim()) {
+		showToast('请输入银行名称')
+		return false
+	}
+	if (!form.accountName.trim()) {
+		showToast('请输入账户名称')
+		return false
+	}
+	if (!form.accountNumber.trim()) {
+		showToast('请输入账号')
+		return false
+	}
+	if (!form.branchName.trim()) {
+		showToast('请输入支行名称')
+		return false
+	}
+	return true
+}
+
+/**
+ * 将接口返回的银行卡信息回填到表单。
+ */
+function fillForm(value: unknown) {
+	const source = (value && typeof value === 'object' ? value : {}) as Record<string, unknown>
+	form.bankName = normalizeOptionalText(source.bankName)
+	form.accountName = normalizeOptionalText(source.accountName)
+	form.accountNumber = normalizeOptionalText(source.accountNumber || source.accountNo)
+	form.branchName = normalizeOptionalText(source.branchName)
+}
+
+function isSuccessResponse(response: any) {
+	return response && (response.code === 0 || response.code === 200)
+}
+
+function showToast(title: string) {
+	uni.showToast({
+		title,
+		icon: 'none',
+	})
+}
+
+function normalizeOptionalText(value: unknown) {
+	if (typeof value !== 'string') {
+		return ''
+	}
+	return value.trim()
+}
+
+function goBack() {
+	const currentPages = getCurrentPages()
+	if (currentPages.length > 1) {
+		uni.navigateBack()
+		return
+	}
+	uni.switchTab({
+		url: '/pages/mine/mine',
+	})
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+	min-height: 100vh;
+	background-color: #ffffff;
+	display: flex;
+	flex-direction: column;
+}
+
+.custom-navbar {
+	width: 100%;
+	background-color: #ffffff;
+	position: relative;
+	z-index: 100;
+}
+
+.nav-content {
+	height: 88rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 0 32rpx;
+}
+
+.back-icon {
+	width: 40rpx;
+	height: 40rpx;
+}
+
+.nav-title {
+	font-size: 36rpx;
+	color: #1A1A1A;
+	font-weight: bold;
+	position: absolute;
+	left: 50%;
+	transform: translateX(-50%);
+}
+
+.nav-right-placeholder {
+	width: 40rpx;
+}
+
+.form {
+	width: 335px;
+	margin: 40rpx auto 0;
+}
+
+.form-item {
+	margin-bottom: 40rpx;
+}
+
+.label {
+	font-family: 'PingFang SC', 'PingFang-SC', sans-serif;
+	font-size: 12px;
+	line-height: 20px;
+	color: #333333;
+	margin-bottom: 16rpx;
+}
+
+.input-wrapper {
+	width: 335px;
+	height: 48px;
+	display: flex;
+	align-items: center;
+}
+
+.input {
+	width: 100%;
+	height: 24px;
+	font-family: 'PingFang SC', 'PingFang-SC', sans-serif;
+	font-size: 16px;
+	line-height: 24px;
+	color: #333333;
+}
+
+.placeholder-style {
+	font-family: 'PingFang SC', 'PingFang-SC', sans-serif;
+	font-size: 16px;
+	line-height: 24px;
+	color: #999999;
+}
+
+.line {
+	width: 335px;
+	height: 1px;
+	background: #F2F3F5;
+}
+
+.submit-btn {
+	width: 335px;
+	height: 48px;
+	line-height: 48px;
+	text-align: center;
+	background-color: #C63520;
+	color: #ffffff;
+	font-size: 16px;
+	font-family: 'PingFang SC', 'PingFang-SC', sans-serif;
+	border-radius: 8px;
+	margin: 80rpx auto 0;
+}
+
+.submit-btn.disabled {
+	opacity: 0.6;
+}
+</style>
diff --git a/traditionalApp/pages/bankCard/service.js b/traditionalApp/pages/bankCard/service.js
new file mode 100644
index 0000000..1d40f6c
--- /dev/null
+++ b/traditionalApp/pages/bankCard/service.js
@@ -0,0 +1,15 @@
+import request from '@/utils/request.js'
+
+/**
+ * 获取 APP 银行卡设置详情。
+ */
+export const getBankCardDetail = () => {
+	return request.get('/api/app/bank-card', {}, false, false)
+}
+
+/**
+ * 保存 APP 银行卡设置。
+ */
+export const updateBankCard = (data) => {
+	return request.put('/api/app/bank-card', data)
+}
diff --git a/traditionalApp/pages/good/projectDetail.vue b/traditionalApp/pages/good/projectDetail.vue
new file mode 100644
index 0000000..5da54c0
--- /dev/null
+++ b/traditionalApp/pages/good/projectDetail.vue
@@ -0,0 +1,204 @@
+<template>
+	<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>
+			<text class="nav-title">详情</text>
+		</view>
+
+		<view v-if="isVideoType" class="video-container">
+			<video class="video-player" :src="detail.videoUrl" controls :autoplay="false"></video>
+		</view>
+
+		<view v-else-if="detail.coverImage" class="cover-container">
+			<image class="cover-image" :src="detail.coverImage" mode="aspectFill"></image>
+		</view>
+
+		<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="!detail.id" class="status-text">暂无详情内容</view>
+		</view>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import { getProjectContentDetail } from './static-content.js'
+
+interface ProjectDetail {
+	id?: number | null
+	title: string
+	subtitle: string
+	coverImage: string
+	contentType?: number | null
+	content: string
+	videoUrl: string
+}
+
+const statusBarHeight = ref(0)
+const detail = ref<ProjectDetail>(createDefaultDetail())
+
+function createDefaultDetail(): ProjectDetail {
+	return {
+		id: null,
+		title: '',
+		subtitle: '',
+		coverImage: '',
+		contentType: null,
+		content: '',
+		videoUrl: '',
+	}
+}
+
+const isVideoType = computed(() => Number(detail.value.contentType) === 2)
+
+const videoSubtitle = computed(() => String(detail.value.subtitle || '').trim())
+
+const contentNodes = computed(() => buildRichTextNodes(detail.value.content, '暂无详情内容'))
+
+onMounted(() => {
+	uni.getSystemInfo({
+		success: (res) => {
+			statusBarHeight.value = res.statusBarHeight || 0
+		}
+	})
+})
+
+onLoad((options) => {
+	const type = options?.type ? decodeURIComponent(options.type) : ''
+	const id = options?.id ? decodeURIComponent(options.id) : ''
+	const matched = getProjectContentDetail(type, id)
+	detail.value = matched ? { ...createDefaultDetail(), ...matched } : createDefaultDetail()
+})
+
+/**
+ * 将富文本内容转换为 `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>'
+}
+
+/**
+ * 返回上一页;没有历史栈时回到项目中心。
+ */
+function goBack() {
+	const currentPages = getCurrentPages()
+	if (currentPages.length > 1) {
+		uni.navigateBack()
+		return
+	}
+	uni.switchTab({
+		url: '/pages/good/good'
+	})
+}
+</script>
+
+<style>
+page,
+html {
+	background-color: #F5F6F8;
+}
+</style>
+
+<style lang="scss" scoped>
+.container {
+	min-height: 100vh;
+	background-color: #F5F6F8;
+}
+
+.status-bar-placeholder {
+	width: 100%;
+}
+
+.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;
+}
+
+.nav-title {
+	flex: 1;
+	text-align: center;
+	font-size: 36rpx;
+	font-weight: 500;
+	color: #333;
+}
+
+.video-container {
+	width: 100%;
+	height: 402rpx;
+}
+
+.video-player {
+	width: 100%;
+	height: 100%;
+}
+
+.cover-container {
+	width: 100%;
+	height: 402rpx;
+}
+
+.cover-image {
+	width: 100%;
+	height: 100%;
+}
+
+.content-area {
+	padding: 32rpx;
+	background-color: #F5F6F8;
+}
+
+.content-title {
+	font-size: 32rpx;
+	font-weight: 500;
+	color: #333;
+	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%;
+	font-size: 28rpx;
+	line-height: 1.8;
+	color: #333;
+}
+
+.status-text {
+	margin-top: 24rpx;
+	font-size: 26rpx;
+	line-height: 40rpx;
+	color: #86909C;
+}
+</style>
diff --git a/traditionalApp/pages/good/projectList.vue b/traditionalApp/pages/good/projectList.vue
new file mode 100644
index 0000000..639a8e3
--- /dev/null
+++ b/traditionalApp/pages/good/projectList.vue
@@ -0,0 +1,210 @@
+<template>
+	<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>
+			<text class="nav-title">{{ pageTitle }}</text>
+			<view class="nav-right-placeholder"></view>
+		</view>
+
+		<view class="content">
+			<view v-if="articleList.length" class="article-list">
+				<view class="article-item" v-for="item in articleList" :key="item.id" @tap="goDetail(item)">
+					<image class="article-img" :src="item.coverImage" mode="aspectFill"></image>
+					<view v-if="isVideoType(item)" class="play-btn">
+						<text class="play-icon">▶</text>
+					</view>
+					<view class="article-shadow">
+						<text class="article-text">{{ item.title }}</text>
+					</view>
+				</view>
+			</view>
+			<view v-else class="status-text">暂无内容</view>
+		</view>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { onLoad } from '@dcloudio/uni-app'
+import { ref } from 'vue'
+import { PROJECT_CONTENT_MAP } from './static-content.js'
+
+interface ProjectListItem {
+	id: number
+	title: string
+	coverImage: string
+	contentType: number
+}
+
+const statusBarHeight = ref(0)
+const pageTitle = ref('项目中心')
+const listType = ref('')
+const articleList = ref<ProjectListItem[]>([])
+
+onLoad((options) => {
+	uni.getSystemInfo({
+		success: (res) => {
+			statusBarHeight.value = res.statusBarHeight || 0
+		}
+	})
+	const title = options?.title ? decodeURIComponent(options.title) : ''
+	const type = options?.type ? decodeURIComponent(options.type) : ''
+	pageTitle.value = title || '项目中心'
+	listType.value = type
+	articleList.value = PROJECT_CONTENT_MAP[type] || []
+})
+
+/**
+ * 视频类型在封面上展示播放按钮。
+ */
+function isVideoType(item: ProjectListItem) {
+	return Number(item.contentType) === 2
+}
+
+/**
+ * 进入项目中心详情页,图文和视频共用同一详情结构。
+ */
+function goDetail(item: ProjectListItem) {
+	uni.navigateTo({
+		url: `/pages/good/projectDetail?type=${encodeURIComponent(listType.value)}&id=${item.id}`
+	})
+}
+
+/**
+ * 返回项目中心主页面。
+ */
+function goBack() {
+	const currentPages = getCurrentPages()
+	if (currentPages.length > 1) {
+		uni.navigateBack()
+		return
+	}
+	uni.switchTab({
+		url: '/pages/good/good'
+	})
+}
+</script>
+
+<style>
+page, html {
+	background-color: #fff;
+}
+</style>
+
+<style lang="scss" scoped>
+.container {
+	min-height: 100vh;
+	background-color: #fff;
+}
+
+.status-bar-placeholder {
+	width: 100%;
+}
+
+.nav-bar {
+	height: 88rpx;
+	display: flex;
+	align-items: 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;
+	padding-bottom: 40rpx;
+}
+
+.status-text {
+	text-align: center;
+	font-size: 24rpx;
+	color: #C0C4CC;
+	padding: 80rpx 0;
+}
+
+.article-list {
+	display: flex;
+	justify-content: space-between;
+	flex-wrap: wrap;
+}
+
+.article-item {
+	width: 328rpx;
+	height: 328rpx;
+	border-radius: 16rpx;
+	position: relative;
+	overflow: hidden;
+	margin-bottom: 24rpx;
+}
+
+.article-img {
+	width: 100%;
+	height: 100%;
+}
+
+.play-btn {
+	position: absolute;
+	left: 50%;
+	top: 50%;
+	width: 80rpx;
+	height: 80rpx;
+	margin-left: -40rpx;
+	margin-top: -40rpx;
+	border-radius: 50%;
+	background: rgba(0, 0, 0, 0.5);
+	border: 2rpx solid rgba(255, 255, 255, 0.9);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 2;
+}
+
+.play-icon {
+	color: #fff;
+	font-size: 32rpx;
+	line-height: 1;
+	margin-left: 4rpx;
+}
+
+.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;
+	box-sizing: border-box;
+	z-index: 1;
+}
+
+.article-text {
+	color: #FFFFFF;
+	font-size: 24rpx;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	width: 100%;
+}
+</style>
diff --git a/traditionalApp/pages/good/static-content.js b/traditionalApp/pages/good/static-content.js
new file mode 100644
index 0000000..dc4d21c
--- /dev/null
+++ b/traditionalApp/pages/good/static-content.js
@@ -0,0 +1,125 @@
+const DEFAULT_COVER = '/static/image/home/zixun.png'
+const DEMO_VIDEO_URL = 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4'
+
+/**
+ * 项目中心静态内容。contentType:1 图文,2 视频。后续改为接口数据。
+ */
+export const PROJECT_CONTENT_MAP = {
+	platform: [
+		{
+			id: 1,
+			title: '平台介绍图文一',
+			coverImage: DEFAULT_COVER,
+			contentType: 1,
+			content: '<p>这里是平台介绍图文内容,后续将替换为后台配置的正文。</p><p>当前为静态占位数据。</p>',
+			videoUrl: '',
+		},
+		{
+			id: 2,
+			title: '平台介绍视频一',
+			subtitle: '了解平台定位与核心能力',
+			coverImage: DEFAULT_COVER,
+			contentType: 2,
+			content: '',
+			videoUrl: DEMO_VIDEO_URL,
+		},
+		{
+			id: 3,
+			title: '平台介绍图文二',
+			coverImage: DEFAULT_COVER,
+			contentType: 1,
+			content: '<p>平台介绍图文二的静态正文。</p>',
+			videoUrl: '',
+		},
+		{
+			id: 4,
+			title: '平台介绍视频二',
+			subtitle: '快速熟悉平台使用方式',
+			coverImage: DEFAULT_COVER,
+			contentType: 2,
+			content: '',
+			videoUrl: DEMO_VIDEO_URL,
+		},
+	],
+	mode: [
+		{
+			id: 1,
+			title: '营销模式图文一',
+			coverImage: DEFAULT_COVER,
+			contentType: 1,
+			content: '<p>这里是营销模式图文内容,后续将替换为后台配置的正文。</p>',
+			videoUrl: '',
+		},
+		{
+			id: 2,
+			title: '营销模式视频一',
+			subtitle: '拆解常见营销模式要点',
+			coverImage: DEFAULT_COVER,
+			contentType: 2,
+			content: '',
+			videoUrl: DEMO_VIDEO_URL,
+		},
+		{
+			id: 3,
+			title: '营销模式图文二',
+			coverImage: DEFAULT_COVER,
+			contentType: 1,
+			content: '<p>营销模式图文二的静态正文。</p>',
+			videoUrl: '',
+		},
+		{
+			id: 4,
+			title: '营销模式视频二',
+			subtitle: '对照案例理解模式落地',
+			coverImage: DEFAULT_COVER,
+			contentType: 2,
+			content: '',
+			videoUrl: DEMO_VIDEO_URL,
+		},
+	],
+	action: [
+		{
+			id: 1,
+			title: '营销动作图文一',
+			coverImage: DEFAULT_COVER,
+			contentType: 1,
+			content: '<p>这里是营销动作图文内容,后续将替换为后台配置的正文。</p>',
+			videoUrl: '',
+		},
+		{
+			id: 2,
+			title: '营销动作视频一',
+			subtitle: '掌握关键营销动作步骤',
+			coverImage: DEFAULT_COVER,
+			contentType: 2,
+			content: '',
+			videoUrl: DEMO_VIDEO_URL,
+		},
+		{
+			id: 3,
+			title: '营销动作图文二',
+			coverImage: DEFAULT_COVER,
+			contentType: 1,
+			content: '<p>营销动作图文二的静态正文。</p>',
+			videoUrl: '',
+		},
+		{
+			id: 4,
+			title: '营销动作视频二',
+			subtitle: '结合场景执行营销动作',
+			coverImage: DEFAULT_COVER,
+			contentType: 2,
+			content: '',
+			videoUrl: DEMO_VIDEO_URL,
+		},
+	],
+}
+
+/**
+ * 按入口类型和内容主键读取一条静态详情。
+ */
+export function getProjectContentDetail(type, id) {
+	const list = PROJECT_CONTENT_MAP[type] || []
+	const numericId = Number(id)
+	return list.find((item) => item.id === numericId) || null
+}
diff --git a/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue b/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue
new file mode 100644
index 0000000..48956ac
--- /dev/null
+++ b/traditionalApp/pages/healthCoursesHub/healthCoursesHub.vue
@@ -0,0 +1,105 @@
+<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 class="content">
+			<image class="entry-image" src="/static/image/healthCoursesHub/gongfa.png" mode="widthFix"
+				hover-class="entry-image--pressed" hover-stay-time="80" @tap="goHealthCourses"></image>
+			<image class="entry-image" src="/static/image/healthCoursesHub/kepu.png" mode="widthFix"
+				hover-class="entry-image--pressed" hover-stay-time="80" @tap="goHealthPopularization"></image>
+			<image class="entry-image" src="/static/image/healthCoursesHub/zhishi.png" mode="widthFix"
+				hover-class="entry-image--pressed" hover-stay-time="80" @tap="goHealthKnowledge"></image>
+		</view>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+
+const statusBarHeight = ref(0)
+
+onMounted(() => {
+	uni.getSystemInfo({
+		success: (res) => {
+			statusBarHeight.value = res.statusBarHeight || 0
+		}
+	})
+})
+
+/**
+ * 进入原养生课程列表,作为「养生功法」二级页。
+ */
+const goHealthCourses = () => {
+	uni.navigateTo({
+		url: '/pages/healthCourses/healthCourses'
+	})
+}
+
+/**
+ * 进入复制后的养生科普二级页。
+ */
+const goHealthPopularization = () => {
+	uni.navigateTo({
+		url: '/pages/healthPopularization/healthPopularization'
+	})
+}
+
+/**
+ * 进入原健康科普页,作为「健康知识」二级页。
+ */
+const goHealthKnowledge = () => {
+	uni.navigateTo({
+		url: '/pages/publish/publish'
+	})
+}
+</script>
+
+<style>
+page, html {
+	background-color: #ffffff;
+}
+</style>
+
+<style scoped lang="scss">
+.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;
+	}
+}
+
+.content {
+	padding: 32rpx 40rpx 0;
+	box-sizing: border-box;
+
+	.entry-image {
+		display: block;
+		width: 670rpx;
+		margin-bottom: 0;
+
+		&--pressed {
+			opacity: 0.92;
+		}
+	}
+}
+</style>
diff --git a/traditionalApp/pages/healthPopularization/healthPopularization.vue b/traditionalApp/pages/healthPopularization/healthPopularization.vue
new file mode 100644
index 0000000..140f81a
--- /dev/null
+++ b/traditionalApp/pages/healthPopularization/healthPopularization.vue
@@ -0,0 +1,205 @@
+<template>
+	<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>
+			<text class="nav-title">养生科普</text>
+			<view class="nav-right-placeholder"></view>
+		</view>
+
+		<view class="content">
+			<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>
+				</view>
+			</view>
+			<view v-else-if="loading" class="status-text">加载中...</view>
+			<view v-else class="status-text">暂无养生科普内容</view>
+		</view>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import { onPullDownRefresh } from '@dcloudio/uni-app'
+import { getHealthPopularizationList } from './service'
+
+interface PopularizationListItem {
+	id: number | string
+	title: string
+	coverImage?: string
+	contentType?: number
+	clickCount?: number
+}
+
+const statusBarHeight = ref(0);
+const loading = ref(false)
+const articleList = ref<PopularizationListItem[]>([])
+const defaultCoverImage = '/static/image/home/zixun.png'
+
+/**
+ * 页面加载时初始化状态栏高度并拉取养生科普列表。
+ */
+onMounted(() => {
+	uni.getSystemInfo({
+		success: (res) => {
+			statusBarHeight.value = res.statusBarHeight || 0;
+		}
+	});
+	loadPopularizationList()
+});
+
+/**
+ * 下拉刷新养生科普列表,并在结束后关闭原生刷新状态。
+ */
+onPullDownRefresh(async () => {
+	try {
+		await loadPopularizationList()
+	} finally {
+		uni.stopPullDownRefresh()
+	}
+})
+
+/**
+ * 返回上一页;没有历史栈时回到养生课程中转页。
+ */
+function goBack() {
+	const currentPages = getCurrentPages()
+	if (currentPages.length > 1) {
+		uni.navigateBack()
+		return
+	}
+	uni.switchTab({
+		url: '/pages/healthCoursesHub/healthCoursesHub'
+	})
+}
+
+/**
+ * 拉取管理后台已启用的养生科普列表数据。
+ */
+async function loadPopularizationList() {
+	loading.value = true
+	try {
+		const response = await getHealthPopularizationList()
+		if (response?.code !== 0 || !Array.isArray(response.data)) {
+			articleList.value = []
+			return
+		}
+		articleList.value = response.data.filter((item: PopularizationListItem) => !!item?.id)
+	} catch {
+		articleList.value = []
+	} finally {
+		loading.value = false
+	}
+}
+
+/**
+ * 跳转养生科普详情页。
+ */
+const goToDetail = (popularizationId?: number | string | null) => {
+	uni.navigateTo({
+		url: popularizationId
+			? `/pages/articleDetail/articleDetail?id=${popularizationId}`
+			: '/pages/articleDetail/articleDetail'
+	});
+};
+</script>
+
+<style>
+page, html {
+	background-color: #fff;
+}
+</style>
+
+<style lang="scss" scoped>
+.container {
+	min-height: 100vh;
+	background-color: #fff;
+}
+
+.nav-bar {
+	height: 88rpx;
+	display: flex;
+	align-items: 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;
+	padding-bottom: 40rpx;
+}
+
+.status-text {
+	text-align: center;
+	font-size: 24rpx;
+	color: #C0C4CC;
+	padding: 80rpx 0;
+}
+
+.article-list {
+	display: flex;
+	justify-content: space-between;
+	flex-wrap: wrap;
+}
+
+.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;
+	box-sizing: border-box;
+}
+
+.article-text {
+	color: #FFFFFF;
+	font-size: 24rpx;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	width: 100%;
+}
+</style>
diff --git a/traditionalApp/pages/healthPopularization/service.js b/traditionalApp/pages/healthPopularization/service.js
new file mode 100644
index 0000000..419cab1
--- /dev/null
+++ b/traditionalApp/pages/healthPopularization/service.js
@@ -0,0 +1,8 @@
+import request from '@/utils/request.js'
+
+/**
+ * 获取 APP 养生科普列表(与原健康科普共用接口)。
+ */
+export const getHealthPopularizationList = () => {
+	return request.get('/api/app/health-popularizations', {}, false, false)
+}
diff --git a/traditionalApp/pages/passwordInput/passwordInput.vue b/traditionalApp/pages/passwordInput/passwordInput.vue
new file mode 100644
index 0000000..37520a1
--- /dev/null
+++ b/traditionalApp/pages/passwordInput/passwordInput.vue
@@ -0,0 +1,185 @@
+<template>
+	<view class="container">
+		<view class="status-bar-placeholder" :style="{ height: statusBarHeight + 'px' }"></view>
+		<view class="nav-bar">
+			<image class="back-icon" src="http://obs.haizhentong.com/file/left-c8c6ad1c120949bab803e2b648a2e69b.png"
+				mode="aspectFit" @tap="handleBack"></image>
+			<view class="nav-right-placeholder"></view>
+		</view>
+
+		<view class="content">
+			<view class="page-title">直播间密码</view>
+
+			<view class="input-wrap">
+				<input
+					class="password-input"
+					type="text"
+					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>
+</template>
+
+<script setup lang="ts">
+	import { onLoad } from '@dcloudio/uni-app'
+	import { ref } from 'vue'
+
+	const OPERATOR_TRAINING_PASSWORD = '123456'
+	const statusBarHeight = ref(0)
+	const password = ref('')
+	const autoFocus = ref(true)
+	const targetUrl = ref('')
+
+	onLoad((options) => {
+		uni.getSystemInfo({
+			success: (res) => {
+				statusBarHeight.value = res.statusBarHeight || 0
+			}
+		})
+
+		if (options?.targetUrl) {
+			targetUrl.value = decodeURIComponent(options.targetUrl)
+		}
+	})
+
+	const handleBack = () => {
+		uni.navigateBack()
+	}
+
+	const handleConfirm = () => {
+		if (!password.value) {
+			return
+		}
+
+		if (password.value !== OPERATOR_TRAINING_PASSWORD) {
+			uni.showToast({
+				title: '密码错误,请重新输入',
+				icon: 'none'
+			})
+			return
+		}
+
+		if (targetUrl.value) {
+			uni.redirectTo({
+				url: targetUrl.value,
+				fail: () => {
+					uni.navigateTo({
+						url: targetUrl.value
+					})
+				}
+			})
+		} else {
+			uni.navigateBack()
+		}
+	}
+</script>
+
+<style>
+	page,
+	html {
+		background: #fff;
+	}
+</style>
+
+<style scoped lang="scss">
+	.container {
+		width: 750rpx;
+		min-height: 100vh;
+		background-color: #ffffff;
+		position: relative;
+	}
+
+	.status-bar-placeholder {
+		width: 100%;
+	}
+
+	.nav-bar {
+		width: 100%;
+		height: 88rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding-left: 32rpx;
+		padding-right: 32rpx;
+		box-sizing: border-box;
+		z-index: 100;
+		background-color: #fff;
+
+		.back-icon {
+			flex-shrink: 0;
+			width: 19rpx;
+			height: 32rpx;
+		}
+
+		.nav-right-placeholder {
+			width: 19rpx;
+		}
+	}
+
+	.content {
+		padding: 0 40rpx;
+		box-sizing: border-box;
+	}
+
+	.page-title {
+		font-size: 68rpx;
+		font-family: 'PingFang SC', 'PingFang-SC', sans-serif;
+		font-weight: 600;
+		color: #1a1a1a;
+		line-height: 84rpx;
+		margin-left: 0;
+		margin-top: 140rpx;
+		margin-bottom: 60rpx;
+	}
+
+	.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: 60rpx;
+
+		.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: linear-gradient(90deg, #c63520 0%, #c63520 100%);
+		border-radius: 16rpx;
+		color: #ffffff;
+		font-size: 34rpx;
+		font-weight: 500;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		&.disabled {
+			opacity: 0.5;
+		}
+	}
+</style>
diff --git a/traditionalApp/static/image/healthCoursesHub/gongfa.png b/traditionalApp/static/image/healthCoursesHub/gongfa.png
new file mode 100644
index 0000000..acb5549
--- /dev/null
+++ b/traditionalApp/static/image/healthCoursesHub/gongfa.png
Binary files differ
diff --git a/traditionalApp/static/image/healthCoursesHub/kepu.png b/traditionalApp/static/image/healthCoursesHub/kepu.png
new file mode 100644
index 0000000..904f5ad
--- /dev/null
+++ b/traditionalApp/static/image/healthCoursesHub/kepu.png
Binary files differ
diff --git a/traditionalApp/static/image/healthCoursesHub/zhishi.png b/traditionalApp/static/image/healthCoursesHub/zhishi.png
new file mode 100644
index 0000000..9082009
--- /dev/null
+++ b/traditionalApp/static/image/healthCoursesHub/zhishi.png
Binary files differ
diff --git a/traditionalApp/static/image/home/weizan-live.png b/traditionalApp/static/image/home/weizan-live.png
new file mode 100644
index 0000000..9c0a2a5
--- /dev/null
+++ b/traditionalApp/static/image/home/weizan-live.png
Binary files differ
diff --git a/traditionalApp/static/image/mine/dingdan.png b/traditionalApp/static/image/mine/dingdan.png
new file mode 100644
index 0000000..001b40d
--- /dev/null
+++ b/traditionalApp/static/image/mine/dingdan.png
Binary files differ
diff --git a/traditionalApp/static/image/mine/tongji.png b/traditionalApp/static/image/mine/tongji.png
new file mode 100644
index 0000000..c42d718
--- /dev/null
+++ b/traditionalApp/static/image/mine/tongji.png
Binary files differ
diff --git a/traditionalApp/static/image/mine/yinghangka.png b/traditionalApp/static/image/mine/yinghangka.png
new file mode 100644
index 0000000..1719c33
--- /dev/null
+++ b/traditionalApp/static/image/mine/yinghangka.png
Binary files differ
diff --git a/traditionalApp/static/image/mine/yinghanka.png b/traditionalApp/static/image/mine/yinghanka.png
new file mode 100644
index 0000000..1719c33
--- /dev/null
+++ b/traditionalApp/static/image/mine/yinghanka.png
Binary files differ
diff --git a/traditionalApp/static/image/mine/zhibodingdan.png b/traditionalApp/static/image/mine/zhibodingdan.png
new file mode 100644
index 0000000..001b40d
--- /dev/null
+++ b/traditionalApp/static/image/mine/zhibodingdan.png
Binary files differ
diff --git a/traditionalApp/static/image/mine/zhibotongji.png b/traditionalApp/static/image/mine/zhibotongji.png
new file mode 100644
index 0000000..c42d718
--- /dev/null
+++ b/traditionalApp/static/image/mine/zhibotongji.png
Binary files differ
diff --git a/traditionalApp/static/image/project/dongzuo.png b/traditionalApp/static/image/project/dongzuo.png
new file mode 100644
index 0000000..7acb7df
--- /dev/null
+++ b/traditionalApp/static/image/project/dongzuo.png
Binary files differ
diff --git a/traditionalApp/static/image/project/jieshao.png b/traditionalApp/static/image/project/jieshao.png
new file mode 100644
index 0000000..6fe626d
--- /dev/null
+++ b/traditionalApp/static/image/project/jieshao.png
Binary files differ
diff --git a/traditionalApp/static/image/project/moshi.png b/traditionalApp/static/image/project/moshi.png
new file mode 100644
index 0000000..29d6597
--- /dev/null
+++ b/traditionalApp/static/image/project/moshi.png
Binary files differ
diff --git a/traditionalApp/static/image/tabBar/Yproject.png b/traditionalApp/static/image/tabBar/Yproject.png
new file mode 100644
index 0000000..fee3bf3
--- /dev/null
+++ b/traditionalApp/static/image/tabBar/Yproject.png
Binary files differ
diff --git a/traditionalApp/static/image/tabBar/project.png b/traditionalApp/static/image/tabBar/project.png
new file mode 100644
index 0000000..3656855
--- /dev/null
+++ b/traditionalApp/static/image/tabBar/project.png
Binary files differ

--
Gitblit v1.7.1