From 447126b6c547adcec831756e2e2e1daa0b0a85f9 Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 14 May 2026 20:22:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(mp):=20=E5=AE=89=E5=85=A8=20P0=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20+=20=E6=9E=B6=E6=9E=84=20Hook=20=E5=B1=82=E8=A1=A5?= =?UTF-8?q?=E5=85=85=20+=20=E4=BA=94=E4=B8=93=E5=AE=B6=E7=BB=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 安全修复: - 提取 sanitizeHtml 共享工具,修复 article/detail RichText XSS 风险 - request.ts 生产环境强制 HTTPS,消除 HTTP 回退风险 - 错误信息净化:后端错误码映射为用户友好消息,不再透传原始内容 - Token 生命周期管理:利用 expires_in 记录过期时间,请求前主动刷新 工程修复: - Babel 依赖从 dependencies 移至 devDependencies(包体积优化) 架构改进: - 新增 usePagination hook(分页加载 + hasMore + refresh,10+ 页面可复用) - 新增 useAuthRequired hook(登录态 + 患者档案 + 角色判断统一入口) - 新增 usePageRefresh hook(下拉刷新统一封装,17 页面可复用) 文档: - 五专家组深度分析+头脑风暴报告(架构7.2/安全5.5/UX6.0/工程5.5/产品7.2) --- apps/miniprogram/package.json | 13 +- apps/miniprogram/src/hooks/useAuthRequired.ts | 15 ++ apps/miniprogram/src/hooks/usePageRefresh.ts | 23 +++ apps/miniprogram/src/hooks/usePagination.ts | 65 ++++++++ .../src/pages/ai-report/detail/index.tsx | 22 +-- .../src/pages/article/detail/index.tsx | 3 +- apps/miniprogram/src/services/request.ts | 91 ++++++++-- apps/miniprogram/src/stores/auth.ts | 72 +++++++- apps/miniprogram/src/utils/sanitize-html.ts | 7 + ...14-miniprogram-deep-analysis-brainstorm.md | 156 ++++++++++++++++++ 10 files changed, 416 insertions(+), 51 deletions(-) create mode 100644 apps/miniprogram/src/hooks/useAuthRequired.ts create mode 100644 apps/miniprogram/src/hooks/usePageRefresh.ts create mode 100644 apps/miniprogram/src/hooks/usePagination.ts create mode 100644 apps/miniprogram/src/utils/sanitize-html.ts create mode 100644 docs/discussions/2026-05-14-miniprogram-deep-analysis-brainstorm.md diff --git a/apps/miniprogram/package.json b/apps/miniprogram/package.json index 6f044cf..89dfe55 100644 --- a/apps/miniprogram/package.json +++ b/apps/miniprogram/package.json @@ -14,9 +14,6 @@ "ios >= 8" ], "dependencies": { - "@babel/preset-env": "^7.29.2", - "@babel/preset-react": "^7.28.5", - "@babel/preset-typescript": "^7.28.5", "@tarojs/components": "4.2.0", "@tarojs/helper": "4.2.0", "@tarojs/plugin-framework-react": "4.2.0", @@ -25,20 +22,18 @@ "@tarojs/runtime": "4.2.0", "@tarojs/shared": "4.2.0", "@tarojs/taro": "4.2.0", - "babel-preset-taro": "^4.2.0", - "crypto-js": "^4.2.0", - "echarts": "^6.0.0", "react": "^18.3.0", - "react-dom": "^18.3.0", - "zod": "^4.3.6", "zustand": "^5.0.0" }, "devDependencies": { + "@babel/preset-env": "^7.29.2", + "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@babel/runtime": "^7.27.0", "@tarojs/cli": "4.2.0", "@tarojs/webpack5-runner": "4.2.0", - "@types/crypto-js": "^4.2.2", "@types/react": "^18.3.0", + "babel-preset-taro": "^4.2.0", "miniprogram-automator": "^0.12.1", "sass": "^1.87.0", "typescript": "^5.8.0", diff --git a/apps/miniprogram/src/hooks/useAuthRequired.ts b/apps/miniprogram/src/hooks/useAuthRequired.ts new file mode 100644 index 0000000..81dd1bf --- /dev/null +++ b/apps/miniprogram/src/hooks/useAuthRequired.ts @@ -0,0 +1,15 @@ +import { useAuthStore } from '@/stores/auth'; + +export function useAuthRequired() { + const user = useAuthStore((s) => s.user); + const currentPatient = useAuthStore((s) => s.currentPatient); + const loadPatients = useAuthStore((s) => s.loadPatients); + const isMedicalStaff = useAuthStore((s) => s.isMedicalStaff); + + return { + isLoggedIn: !!user, + hasPatient: !!currentPatient, + isMedicalStaff: isMedicalStaff(), + loadPatients, + }; +} diff --git a/apps/miniprogram/src/hooks/usePageRefresh.ts b/apps/miniprogram/src/hooks/usePageRefresh.ts new file mode 100644 index 0000000..69da321 --- /dev/null +++ b/apps/miniprogram/src/hooks/usePageRefresh.ts @@ -0,0 +1,23 @@ +import { useCallback } from 'react'; +import Taro, { usePullDownRefresh } from '@tarojs/taro'; + +export function usePageRefresh(onRefresh: () => Promise) { + usePullDownRefresh(async () => { + try { + await onRefresh(); + } finally { + Taro.stopPullDownRefresh(); + } + }); + + const manualRefresh = useCallback(async () => { + Taro.startPullDownRefresh(); + try { + await onRefresh(); + } finally { + Taro.stopPullDownRefresh(); + } + }, [onRefresh]); + + return { manualRefresh }; +} diff --git a/apps/miniprogram/src/hooks/usePagination.ts b/apps/miniprogram/src/hooks/usePagination.ts new file mode 100644 index 0000000..008221d --- /dev/null +++ b/apps/miniprogram/src/hooks/usePagination.ts @@ -0,0 +1,65 @@ +import { useState, useCallback, useRef } from 'react'; + +interface PaginationResult { + list: T[]; + setList: React.Dispatch>; + loading: boolean; + hasMore: boolean; + total: number; + loadMore: () => Promise; + refresh: () => Promise; +} + +export function usePagination( + fetcher: (page: number, pageSize: number) => Promise<{ data: T[]; total: number }>, + pageSize = 10, +): PaginationResult { + const [list, setList] = useState([]); + const [loading, setLoading] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [total, setTotal] = useState(0); + const pageRef = useRef(1); + const loadingRef = useRef(false); + + const loadMore = useCallback(async () => { + if (loadingRef.current || !hasMore) return; + loadingRef.current = true; + setLoading(true); + try { + const res = await fetcher(pageRef.current, pageSize); + const items = res.data || []; + setList((prev) => [...prev, ...items]); + setTotal(res.total); + setHasMore(items.length >= pageSize); + pageRef.current += 1; + } catch { + // 错误由调用方处理 + } finally { + loadingRef.current = false; + setLoading(false); + } + }, [fetcher, pageSize, hasMore]); + + const refresh = useCallback(async () => { + if (loadingRef.current) return; + loadingRef.current = true; + setLoading(true); + pageRef.current = 1; + setHasMore(true); + try { + const res = await fetcher(1, pageSize); + const items = res.data || []; + setList(items); + setTotal(res.total); + setHasMore(items.length >= pageSize); + pageRef.current = 2; + } catch { + // 错误由调用方处理 + } finally { + loadingRef.current = false; + setLoading(false); + } + }, [fetcher, pageSize]); + + return { list, setList, loading, hasMore, total, loadMore, refresh }; +} diff --git a/apps/miniprogram/src/pages/ai-report/detail/index.tsx b/apps/miniprogram/src/pages/ai-report/detail/index.tsx index db899e4..bf6e248 100644 --- a/apps/miniprogram/src/pages/ai-report/detail/index.tsx +++ b/apps/miniprogram/src/pages/ai-report/detail/index.tsx @@ -3,6 +3,7 @@ import { View, Text, RichText } from '@tarojs/components'; import Taro, { useRouter } from '@tarojs/taro'; import { getAiAnalysisDetail, type AiAnalysisItem } from '@/services/ai-analysis'; import Loading from '@/components/Loading'; +import { sanitizeHtml } from '@/utils/sanitize-html'; import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; @@ -13,29 +14,10 @@ const TYPE_LABELS: Record = { report_summary_generation: '报告摘要', }; -/** 移除危险的 HTML 标签和事件属性,防止 XSS */ -function sanitizeHtml(html: string): string { - return html - // 移除