From 93aa4ee19b230af8596b030a36258f66ac6acd89 Mon Sep 17 00:00:00 2001 From: weijuesen Date: Wed, 12 Aug 2026 10:54:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(miniapp):=20=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniapp/src/api/knowledge.ts | 34 +++++ miniapp/src/app.config.ts | 2 + .../pages/knowledge/detail/index.config.ts | 3 + .../pages/knowledge/detail/index.module.scss | 103 ++++++++++++++++ miniapp/src/pages/knowledge/detail/index.tsx | 108 ++++++++++++++++ miniapp/src/pages/knowledge/index.config.ts | 4 + miniapp/src/pages/knowledge/index.module.scss | 116 ++++++++++++++++++ miniapp/src/pages/knowledge/index.tsx | 92 ++++++++++++++ miniapp/src/pages/settings/index.tsx | 8 ++ miniapp/src/types/index.ts | 28 +++++ 10 files changed, 498 insertions(+) create mode 100644 miniapp/src/api/knowledge.ts create mode 100644 miniapp/src/pages/knowledge/detail/index.config.ts create mode 100644 miniapp/src/pages/knowledge/detail/index.module.scss create mode 100644 miniapp/src/pages/knowledge/detail/index.tsx create mode 100644 miniapp/src/pages/knowledge/index.config.ts create mode 100644 miniapp/src/pages/knowledge/index.module.scss create mode 100644 miniapp/src/pages/knowledge/index.tsx diff --git a/miniapp/src/api/knowledge.ts b/miniapp/src/api/knowledge.ts new file mode 100644 index 0000000..98b7c85 --- /dev/null +++ b/miniapp/src/api/knowledge.ts @@ -0,0 +1,34 @@ +import { get } from './request'; + +export interface Disease { + id: string; + name: string; + category: string; + pathogen?: string; + transmission?: string; + medium?: string; + incubation?: string; + symptoms?: string; + highRiskStage?: string; + highRiskCondition?: string; + lethalTime?: string; + spreadTrend?: string; + recurrence?: string; + detection?: string; + prevention?: string; + sortOrder?: number; +} + +export interface KnowledgeArticle { + id: string; + kind: string; + title: string; + summary?: string; + content?: string; + sortOrder?: number; +} + +export const listDiseases = () => get('/knowledge/diseases'); +export const getDisease = (id: string) => get(`/knowledge/diseases/${id}`); +export const listAIGuides = () => get('/knowledge/articles', { kind: 'ai_guide' }); +export const getArticle = (id: string) => get(`/knowledge/articles/${id}`); diff --git a/miniapp/src/app.config.ts b/miniapp/src/app.config.ts index 910bde2..4cae80f 100644 --- a/miniapp/src/app.config.ts +++ b/miniapp/src/app.config.ts @@ -11,6 +11,8 @@ export default defineAppConfig({ 'pages/video/index', 'pages/video/player/index', 'pages/settings/index', + 'pages/knowledge/index', + 'pages/knowledge/detail/index', ], window: { backgroundTextStyle: 'dark', diff --git a/miniapp/src/pages/knowledge/detail/index.config.ts b/miniapp/src/pages/knowledge/detail/index.config.ts new file mode 100644 index 0000000..c01a11b --- /dev/null +++ b/miniapp/src/pages/knowledge/detail/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '知识详情', +}); diff --git a/miniapp/src/pages/knowledge/detail/index.module.scss b/miniapp/src/pages/knowledge/detail/index.module.scss new file mode 100644 index 0000000..1614ee4 --- /dev/null +++ b/miniapp/src/pages/knowledge/detail/index.module.scss @@ -0,0 +1,103 @@ +@use '@/styles/variables.scss' as *; + +.detailPage { + min-height: 100vh; + background: $color-bg-page; + padding-bottom: $spacing-xl; + box-sizing: border-box; +} + +.hero { + background: linear-gradient(135deg, $color-primary 0%, $color-primary-dark 100%); + padding: $spacing-xl $spacing-lg; + display: flex; + align-items: center; + gap: $spacing-md; +} + +.heroName { + font-size: $font-size-xxl; + font-weight: $font-weight-bold; + color: $color-text-white; + flex: 1; + min-width: 0; +} + +.heroTag { + font-size: $font-size-xs; + color: $color-text-white; + background: rgba(255, 255, 255, 0.25); + padding: 4rpx $spacing-sm; + border-radius: $radius-xs; + flex-shrink: 0; +} + +.sectionList { + display: flex; + flex-direction: column; + gap: $spacing-md; + padding: $spacing-lg; +} + +.sectionCard { + background: $color-bg-card; + border-radius: $radius-md; + padding: $spacing-lg; + box-shadow: $shadow-card; +} + +.sectionLabel { + display: block; + font-size: $font-size-xs; + color: $color-text-tertiary; + margin-bottom: $spacing-xs; +} + +.sectionValue { + display: block; + font-size: $font-size-md; + color: $color-text-primary; + line-height: $line-height-loose; + word-break: break-all; +} + +.articleCard { + margin: $spacing-lg; + background: $color-bg-card; + border-radius: $radius-lg; + padding: $spacing-lg; + box-shadow: $shadow-card; +} + +.articleTitle { + display: block; + font-size: $font-size-xl; + font-weight: $font-weight-bold; + color: $color-text-primary; + margin-bottom: $spacing-md; +} + +.articleSummary { + display: block; + font-size: $font-size-sm; + color: $color-text-secondary; + margin-bottom: $spacing-md; + line-height: $line-height-normal; +} + +.articleContent { + display: block; + font-size: $font-size-md; + color: $color-text-primary; + line-height: $line-height-loose; + white-space: pre-wrap; + word-break: break-all; +} + +.emptyText { + display: block; + text-align: center; + font-size: $font-size-sm; + color: $color-text-tertiary; + padding: $spacing-xl 0; +} diff --git a/miniapp/src/pages/knowledge/detail/index.tsx b/miniapp/src/pages/knowledge/detail/index.tsx new file mode 100644 index 0000000..55d171b --- /dev/null +++ b/miniapp/src/pages/knowledge/detail/index.tsx @@ -0,0 +1,108 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { ScrollView, Text, View } from '@tarojs/components'; +import Taro, { useRouter } from '@tarojs/taro'; +import styles from './index.module.scss'; +import { + getArticle, + getDisease, + type Disease, + type KnowledgeArticle, +} from '@/api/knowledge'; + +const CATEGORY_LABELS: Record = { + viral: '病毒性', + fungal: '真菌性', + bacterial: '细菌性', + protozoan: '原虫性', + other: '其他', +}; + +const buildSections = (d: Disease) => + [ + { label: '病原', value: d.pathogen }, + { label: '传播途径', value: d.transmission }, + { label: '传染媒介', value: d.medium }, + { label: '潜伏期', value: d.incubation }, + { label: '主要症状', value: d.symptoms }, + { label: '高发阶段', value: d.highRiskStage }, + { label: '高发条件', value: d.highRiskCondition }, + { label: '致死时间', value: d.lethalTime }, + { label: '传播趋势', value: d.spreadTrend }, + { label: '连发性特征', value: d.recurrence }, + { label: '检测方法', value: d.detection }, + { label: '防治指南', value: d.prevention }, + ].filter((s) => s.value && s.value !== '—'); + +const DetailPage: React.FC = () => { + const router = useRouter(); + const kind = router.params.kind || 'disease'; + const id = router.params.id || ''; + const [disease, setDisease] = useState(null); + const [article, setArticle] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + const fetchData = useCallback(async () => { + if (!id) { + setError('缺少内容ID'); + setLoading(false); + return; + } + try { + if (kind === 'ai_guide') { + setArticle(await getArticle(id)); + } else { + setDisease(await getDisease(id)); + } + } catch (err) { + console.error('[KnowledgeDetail] 加载失败:', err); + setError(err instanceof Error ? err.message : '数据加载失败'); + } finally { + setLoading(false); + } + }, [id, kind]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + if (article) { + return ( + + + {article.title} + {article.summary ? {article.summary} : null} + {article.content} + + + ); + } + + if (disease) { + const sections = buildSections(disease); + return ( + + + {disease.name} + {CATEGORY_LABELS[disease.category] || disease.category} + + + {sections.map((s) => ( + + {s.label} + {s.value} + + ))} + + + ); + } + + return ( + + {loading ? '加载中...' : error || '内容不存在'} + + ); +}; + +export default DetailPage; diff --git a/miniapp/src/pages/knowledge/index.config.ts b/miniapp/src/pages/knowledge/index.config.ts new file mode 100644 index 0000000..c7b64cb --- /dev/null +++ b/miniapp/src/pages/knowledge/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '知识库', + enablePullDownRefresh: true, +}); diff --git a/miniapp/src/pages/knowledge/index.module.scss b/miniapp/src/pages/knowledge/index.module.scss new file mode 100644 index 0000000..2ed229c --- /dev/null +++ b/miniapp/src/pages/knowledge/index.module.scss @@ -0,0 +1,116 @@ +@use '@/styles/variables.scss' as *; + +.knowledgePage { + min-height: 100vh; + background: $color-bg-page; + padding: $spacing-md $spacing-lg; + box-sizing: border-box; +} + +.guideCard { + background: $color-bg-card; + border-radius: $radius-lg; + padding: $spacing-lg; + box-shadow: $shadow-card; + margin-bottom: $spacing-md; +} + +.guideHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: $spacing-sm; +} + +.guideBadge { + font-size: $font-size-xs; + color: $color-primary; + background: rgba(16, 185, 129, 0.1); + padding: 4rpx $spacing-sm; + border-radius: $radius-round; +} + +.guideArrow { + font-size: $font-size-lg; + color: $color-text-tertiary; +} + +.guideTitle { + display: block; + font-size: $font-size-lg; + font-weight: $font-weight-semibold; + color: $color-text-primary; + margin-bottom: $spacing-xs; +} + +.guideSummary { + display: block; + font-size: $font-size-sm; + color: $color-text-secondary; + line-height: $line-height-normal; +} + +.sectionTitle { + display: block; + font-size: $font-size-md; + font-weight: $font-weight-semibold; + color: $color-text-primary; + margin: $spacing-lg 0 $spacing-md; +} + +.diseaseList { + display: flex; + flex-direction: column; + gap: $spacing-md; +} + +.diseaseCard { + background: $color-bg-card; + border-radius: $radius-md; + padding: $spacing-lg; + box-shadow: $shadow-card; +} + +.diseaseHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: $spacing-sm; +} + +.diseaseName { + font-size: $font-size-md; + font-weight: $font-weight-medium; + color: $color-text-primary; + flex: 1; + min-width: 0; + @include text-ellipsis; +} + +.diseaseTag { + font-size: $font-size-xs; + color: $color-primary; + background: rgba(16, 185, 129, 0.1); + padding: 4rpx $spacing-sm; + border-radius: $radius-round; + flex-shrink: 0; + margin-left: $spacing-sm; +} + +.diseaseBrief { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + font-size: $font-size-sm; + color: $color-text-secondary; + line-height: $line-height-normal; +} + +.emptyText { + display: block; + text-align: center; + font-size: $font-size-sm; + color: $color-text-tertiary; + padding: $spacing-xl 0; +} diff --git a/miniapp/src/pages/knowledge/index.tsx b/miniapp/src/pages/knowledge/index.tsx new file mode 100644 index 0000000..d34d820 --- /dev/null +++ b/miniapp/src/pages/knowledge/index.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { ScrollView, Text, View } from '@tarojs/components'; +import Taro, { usePullDownRefresh } from '@tarojs/taro'; +import styles from './index.module.scss'; +import { + listAIGuides, + listDiseases, + type Disease, + type KnowledgeArticle, +} from '@/api/knowledge'; + +const CATEGORY_LABELS: Record = { + viral: '病毒性', + fungal: '真菌性', + bacterial: '细菌性', + protozoan: '原虫性', + other: '其他', +}; + +const KnowledgePage: React.FC = () => { + const [diseases, setDiseases] = useState([]); + const [guides, setGuides] = useState([]); + const [loading, setLoading] = useState(true); + + const fetchData = useCallback(async () => { + try { + const [diseaseRes, guideRes] = await Promise.all([ + listDiseases().catch(() => [] as Disease[]), + listAIGuides().catch(() => [] as KnowledgeArticle[]), + ]); + setDiseases(diseaseRes); + setGuides(guideRes); + } catch (err) { + console.error('[Knowledge] 数据加载失败:', err); + Taro.showToast({ + title: err instanceof Error ? err.message : '数据加载失败', + icon: 'none', + }); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + usePullDownRefresh(() => { + fetchData().then(() => Taro.stopPullDownRefresh()); + }); + + const openGuide = (a: KnowledgeArticle) => { + Taro.navigateTo({ url: `/pages/knowledge/detail/index?kind=ai_guide&id=${a.id}` }); + }; + + const openDisease = (d: Disease) => { + Taro.navigateTo({ url: `/pages/knowledge/detail/index?kind=disease&id=${d.id}` }); + }; + + return ( + + {guides.map((a) => ( + openGuide(a)}> + + AI 解读 + + + {a.title} + {a.summary ? {a.summary} : null} + + ))} + + 蚕病百科 + + {diseases.map((d) => ( + openDisease(d)}> + + {d.name} + {CATEGORY_LABELS[d.category] || d.category} + + {d.symptoms || '暂无症状描述'} + + ))} + {!loading && diseases.length === 0 ? ( + 暂无蚕病百科内容 + ) : null} + + + ); +}; + +export default KnowledgePage; diff --git a/miniapp/src/pages/settings/index.tsx b/miniapp/src/pages/settings/index.tsx index 369359b..f48d4f1 100644 --- a/miniapp/src/pages/settings/index.tsx +++ b/miniapp/src/pages/settings/index.tsx @@ -47,6 +47,9 @@ const SettingsPage: React.FC = () => { case 'thresholds': Taro.navigateTo({ url: '/pages/thresholds/index' }); break; + case 'knowledge': + Taro.navigateTo({ url: '/pages/knowledge/index' }); + break; case 'about': Taro.showModal({ title: '关于', @@ -158,6 +161,11 @@ const SettingsPage: React.FC = () => { 阈值管理 + handleMenuTap('knowledge')}> + 📖 + 知识库 + + handleMenuTap('wsStatus')}> 🔗 连接状态 diff --git a/miniapp/src/types/index.ts b/miniapp/src/types/index.ts index 02abb15..5ffc0b3 100644 --- a/miniapp/src/types/index.ts +++ b/miniapp/src/types/index.ts @@ -180,6 +180,34 @@ export interface DashboardStats { openAlarmCount: number; } +export interface Disease { + id: string; + name: string; + category: string; + pathogen?: string; + transmission?: string; + medium?: string; + incubation?: string; + symptoms?: string; + highRiskStage?: string; + highRiskCondition?: string; + lethalTime?: string; + spreadTrend?: string; + recurrence?: string; + detection?: string; + prevention?: string; + sortOrder?: number; +} + +export interface KnowledgeArticle { + id: string; + kind: string; + title: string; + summary?: string; + content?: string; + sortOrder?: number; +} + export interface ApiResponse { data: T; message?: string;