Files
hms/wiki/miniprogram-quality-checklist.md
iven d26ea64ab2 docs(wiki): 全面重写小程序质量规范清单 — 12类44条规则,覆盖164条git历史
从 164 条小程序 git 提交中全量抽象问题模式:
- 新增 4 类规则:前后端接口契约(字段对齐/API路径/必填字段)、安全(XSS/输入验证/敏感数据/权限)、定时器与副作用清理(setTimeout)、开发环境(DevTools优化)
- 补充已有类别:请求缓存去重(1.4)、统一组件库(3.5-3.6)、认证恢复(5.3-5.4)、对齐原型(7.4-7.5)、ErrorBoundary(9.3)
- 溯源表从 13 条扩至 38 条,每条对应具体修复提交
- 自查脚本从 6 项扩至 10 项
- 统计概览:44 规则 / 66+ fix 提交 / 20 CRITICAL+HIGH
2026-05-17 20:33:25 +08:00

29 KiB
Raw Blame History

小程序质量规范清单

最后更新: 2026-05-17 | 来源164 条 git 提交历史 + 两轮深度审计,全量问题模式抽象

用途: 新页面/新模块开发时的自检清单PR Review 的检查依据,新项目启动时的基线规范。每条规则都来自真实 bug 修复,不是理论推导。


目录

  1. #1 并发与请求层 — 限流器、长轮询、Token 刷新、缓存策略
  2. #2 导航与路由 — 页栈保护、reLaunch 去重、分包预加载
  3. #3 组件与渲染 — render body 定义、ScrollView 嵌套、懒加载、统一组件库
  4. #4 数据与内存 — 数组上限、去重索引一致性、Storage 清理
  5. #5 认证与会话 — 模块级缓存清理、logout 完整性、认证恢复
  6. #6 前后端接口契约 — 字段对齐、类型同步、API 路径一致
  7. #7 样式与布局 — CSS 变量主题、长者模式、设计 token、对齐原型
  8. #8 安全 — XSS 防护、输入验证、敏感数据、权限校验
  9. #9 错误处理与日志 — 静默吞错、生产日志、用户提示、ErrorBoundary
  10. #10 定时器与副作用清理 — setTimeout/setInterval 清理、useSafeTimeout
  11. #11 开发环境 — DevTools 卡死、source map、文件描述符限制
  12. #12 审计与交付 — 提交前检查、PR Review、里程碑审计

1 并发与请求层

规则 1.1 — 全局并发限制器

根因: 微信 wx.request 并发上限为 10超出排队。无限制发请求导致饥饿。 案例: ConcurrencyLimiter(8) 时长轮询占满 8 个槽位 25-30s所有新请求排队超时。 提交: 9d50ef7 74bffb4

  • 所有 HTTP 请求必须经过全局 ConcurrencyLimiter
  • 并发上限 ≤ 12微信限制 10留 2 个给框架内部请求)
  • Token 刷新必须直接调用 Taro.request() 绕过限流器(否则 401 重试时死锁)
// ✅ 正确Token 刷新绕过限流器
async function doRefresh(): Promise<boolean> {
  const res = await Taro.request({ url: refreshTokenUrl, method: 'POST', ... });
}

// ❌ 错误Token 刷新走限流器,所有槽位被占时死锁
async function doRefresh(): Promise<boolean> {
  return api.post('/auth/refresh', { refresh_token }); // 死锁!
}

规则 1.2 — 长轮询独立通道

根因: 长轮询 hang 25-30s 占用并发槽位,与普通 API 竞争。 案例: 咨询页长轮询 + Tab 切换 = 开发者工具卡死。 提交: 9d50ef7 5baa518

  • 长轮询必须使用 requestUnlimited(绕过限流器的独立通道)
  • 必须有连续失败上限(默认 10 次),达到后停止
  • 必须有 generation counter 模式,组件卸载/参数变化时旧轮询自动失效
  • 成功轮询间隔 ≥ 3 秒(不能 delay=0 立即递归,否则 CPU 飙升)

规则 1.3 — reLaunch 去重

根因: 401 时多个并发请求同时触发 Taro.reLaunch('/pages/login/index')案例: 3 个请求同时 401 → 3 次 reLaunch → 页面栈混乱。 提交: 9d50ef7

  • reLaunch 必须全局去重Promise 锁 + 2s 冷却期)
  • 401 重试上限为 1 次,避免无限循环

规则 1.4 — 请求缓存与去重

根因: Tab 切换时同一 API 被多次并发调用,浪费请求且竞态更新。 案例: 首页/健康页每次 useDidShow 都重新请求,快速切换 Tab 触发大量重复。 提交: 6d151bb 447126b

  • GET 请求必须有响应缓存TTL 60s同 URL 短时间内命中缓存
  • 同时进行的相同 GET 请求必须去重inflight Promise 共享)
  • 缓存按 patientId 隔离,切换患者时自动失效
  • clearRequestCache() 在 logout、切换患者时调用

规则 1.5 — 请求层错误处理

提交: fcce2f5 4ca9027

  • catch 块不允许完全静默(至少 console.warn
  • Token 刷新函数必须有死锁风险注释
  • 网络超时/异常必须有用户友好提示(不显示原始 error message
  • 后端 error_code 必须有前端映射表(ERROR_CODE_MAP

2 导航与路由

规则 2.1 — 页栈溢出保护

根因: 微信 navigateTo 页栈上限 10 层,超出 navigateTo:fail案例: 患者列表→详情→报告→化验→趋势→咨询→...→第 10 层崩溃。 提交: 59dd5ef 74bffb4

  • 所有 Taro.navigateTo 必须替换为 safeNavigateTo
  • safeNavigateTo:页栈 ≥ 9 时自动降级为 redirectTo
  • 新代码 review 时全局搜索 Taro.navigateTo,不应存在任何直接调用

规则 2.2 — 分包预加载与拆包策略

根因: 主包过大导致首次加载慢;单页分包浪费空间。 案例: consultation 原在主包,移出后主包减重。 提交: 59dd5ef 4c38fcd

  • 高频入口页必须配置 preloadRule(首页预加载核心分包)
  • 单页分包合并(多个单页合并为一个分包减少开销)
  • 医生端独立分包(pkg-doctor-core / pkg-doctor-clinical
  • TabBar 页面必须在 app.config.ts 正确声明

规则 2.3 — 页面生命周期防重入

根因: useEffect + usePageData(useDidShow) 双重初始化。 案例: 分包页 navigateTo 后超时,双重 API 调用叠加。 提交: 1fd2c7a 59dd5ef

  • 数据加载统一通过 usePageData 单次回调,不额外添加 useEffect 初始化
  • 需要跳过首次 mount 的场景用 mountedRef 守卫
  • 加载状态用 loadingRef 防止并发重复加载(不用 useState,避免异步竞态)
  • usePageData 支持 throttleMs 防抖(默认 5000ms

3 组件与渲染

规则 3.1 — 禁止在 render body 内定义组件

根因: 每次父组件 render 创建新的组件引用React 销毁旧实例重建新实例。 案例: dialysis/create 的 InputField 在 render 内定义14 个 Input 每次 render 全部销毁重建,输入焦点丢失。 提交: fcce2f5

  • 组件必须定义在函数组件外部(模块顶层)
  • 如需访问父组件状态,通过 props 传入value/onChange 回调)
  • Code review 时搜索 const Xxx = () => 出现在 return 之前、组件函数内部的模式
// ✅ 正确:组件定义在模块顶层,通过 props 传值
function InputField({ label, value, onChange }: Props) {
  return <View>...</View>;
}

// ❌ 错误:组件定义在 render body 内
export default function MyForm() {
  const InputField = ({ label, field }) => ( <View>...</View> ); // 每次 render 重建!
}

规则 3.2 — 禁止双重 ScrollView

根因: PageShell 默认 scroll=true,内层再嵌套 ScrollView scrollY,两容器冲突。 案例: ai-report/list 页面滚动不流畅。 提交: fcce2f5

  • 使用内层 ScrollView 时,外层 PageShell 必须设置 scroll={false}
  • 或者不使用内层 ScrollView,只用 PageShell 自带滚动

规则 3.3 — 图片懒加载

提交: fcce2f5

  • 列表页、聊天消息中的 <Image> 必须添加 lazyLoad 属性
  • 首屏可见图片不加 lazyLoad(避免延迟渲染)
  • 图片 URL 必须使用 HTTPS微信强制要求

规则 3.4 — 列表渲染优化

提交: 74bffb4

  • 长列表使用虚拟滚动或分页加载(onScrollToLower
  • 渲染层消息上限(如 MAX_RENDER_MESSAGES = 200),超出截断并提示
  • 状态层消息上限(如 MAX_STATE_MESSAGES = 300),防止内存增长

规则 3.5 — 统一组件库使用

根因: 早期页面各自用原生 View/Text 手写布局60 页面风格不统一。 案例: 迁移了 66 页面到 PageShell + ContentCard + StatusTag + LoadingCard + SearchSection + PaginationBar。 提交: 80794c9900c9ba12 次迁移提交)

  • 新页面必须使用统一组件库:PageShell / ContentCard / StatusTag / LoadingCard / SearchSection / PaginationBar / EmptyState / ErrorState
  • 不允许在页面中手写 .card / .list-item 等重复的卡片样式
  • 需要新组件时先扩展现有组件(添加 prop不要新建
  • 组件必须有独立的 .scss 文件,不允许在页面 .scss 中覆盖组件内部样式

规则 3.6 — React 导入

根因: Taro 4.x 的 JSX transform 不自动注入 React但某些组件需要 React 引用。 案例: 迁移组件库后运行时报 React is not defined提交: 1786f0d

  • 使用 JSX 的文件必须显式 import React from 'react'(即使 Taro 4.x 有自动 transform
  • 组件使用 React APIReact.memoReact.useRef)时必须有导入
  • SCSS 导入路径使用 ./index.scss(相对当前组件),不使用 @/components/... 绝对路径到 scss

4 数据与内存

规则 4.1 — 数组必须有上限

根因: 未设上限的数组在长时间运行或高频数据场景下无限增长。 案例: BLE 设备 readings 数组持续追加,长时间连接后占大量内存。 提交: fcce2f5

  • 所有累积型数组必须设 MAX 常量(如 MAX_LIVE_READINGS = 200
  • 追加时检查长度,超出时 slice(-MAX) 保留最新数据
  • 适用于:设备数据缓存、聊天消息、日志队列等

规则 4.2 — 去重索引与数据一致

根因: trimToMax 丢弃旧数据后,去重索引 seenKeys 仍保留已丢弃数据的 key。 案例: DataBuffer seenKeys 膨胀,拒绝本应接受的新数据。 提交: fcce2f5

  • 数据裁剪时必须同步重建去重索引
  • flush()/clear() 时必须清空索引
  • 不要依赖 Set 自动清理——它不会自动 shrink

规则 4.3 — Storage 使用规范

根因: Storage 滥用导致数据残留、切换用户数据泄漏。 案例: 用 Storage 传递页面间数据,导致详情页显示上一个患者的信息。 提交: 0bf1822 3c828bf

  • 禁止用 Storage 传递页面间数据 — 必须通过 API 获取或 URL 参数
  • logout 时必须清理所有业务相关 Storage key
  • 带动态 key 的缓存(如 ble_buffer_{id}_{bucket})必须用 getStorageInfoSync().keys 遍历清理
  • 模块级缓存变量JS 内存中的 let 变量)也必须在 logout 时重置
  • 敏感数据token使用 secure-storage(加密),不使用 Taro.setStorageSync 明文存储

5 认证与会话

规则 5.1 — 模块级缓存必须可清理

根因: auth.ts 顶层的缓存变量 logout 后不清除restore() 恢复已登出用户数据。 案例: 切换账号后看到上一个用户的头像和名称。 提交: fcce2f5

  • 所有模块级缓存变量(let 声明的 JSON/对象缓存)必须在 logout 时重置为初始值
  • restore() 方法必须在开头读取最新缓存,不依赖残留值
  • restore() 内部做变更检测,无变化时跳过 set() 避免不必要的重渲染

规则 5.2 — Token 刷新安全

根因: getHeaders() 中做同步 Token 刷新预检查,所有请求被阻塞。 案例: getHeaders 中 await tryRefreshToken() 导致并发请求排队 30s。 提交: 9d50ef7 447126b

  • getHeaders() 中不做同步 Token 刷新预检查(仅依赖 401 重试路径)
  • 刷新失败时必须清除所有认证相关 Storage
  • 刷新进行中用 Promise 去重,防止多个并发请求同时触发刷新
  • 刷新失败时设置 isLoggingOut 标记,防止后续请求反复尝试刷新

规则 5.3 — 页面级认证恢复

根因: Tab 切换回来后 auth store 未恢复,页面显示为未登录状态。 案例: 首页 Tab 正常 → 切到咨询 Tab → 回到首页,用户信息消失。 提交: 6632985 c314093

  • 所有 TabBar 页面必须在 useDidShow 中调用 auth.restore()
  • 子页面依赖 usePageData 自动恢复
  • 不在 useEffect 中重复恢复(避免双重初始化)
  • 退出登录后刷新必须清除 isLoggingOut 标记(clearLoggingOut()),否则重新登录后请求被拒

规则 5.4 — 角色与权限

根因: 前端未区分医生/护士/患者角色,所有页面入口暴露给所有用户。 案例: 患者能看到医生端入口,点击后 403。 提交: c38967a 81c174a 3dac6a9

  • 医生端入口必须通过 isDoctor() / isNurse() 守卫
  • 页面组件使用 useDoctorClass() 切换样式模式
  • 精简菜单:未实现的模块入口必须移除,不显示空白页

6 前后端接口契约

规则 6.1 — 字段对齐

根因: 前端 TypeScript 接口与后端 DTO 字段名/类型不一致,运行时静默失败。 案例: 33 个接口端到端验证发现 8 个字段不对齐。 提交: 8316281 c53f562 c38967a

  • 新增前端 service 函数时,必须对照后端 DTO 逐字段验证
  • 后端 DTO 变更时,必须同步更新前端 TypeScript 接口
  • 必填字段前端必须有默认值和校验
  • 日期字段统一为 stringYYYY-MM-DD),数值字段注意 number | undefined

规则 6.2 — API 路径一致

根因: 前端硬编码 API 路径,后端路由变更后前端 404。 案例: FHIR 端点从 /fhir 迁移到 /api/v1/fhir,前端请求 404。 提交: 4ca9027 8316281

  • API 路径使用 process.env.TARO_APP_API_URL + 相对路径,不硬编码
  • Service 层统一路径前缀,不在组件中直接拼接 URL
  • 后端新增端点必须同步添加前端调用函数,不允许留空

规则 6.3 — 必填字段完整性

根因: 前端提交表单缺少后端必填字段,导致 400 错误。 案例: submitRecord 缺少 task_id 字段,后端 CreateFollowUpRecordReq 拒绝。 提交: fbb28e6 603af83

  • 表单提交前检查所有后端 Required 字段是否已填
  • 缺少字段时给出具体提示(如"缺少患者信息"),不是笼统的"操作失败"
  • 隐式字段(如 patient_idversion)从路由参数或 store 获取,不依赖用户输入

7 样式与布局

规则 7.1 — CSS 变量主题

根因: 硬编码 px 值和颜色号,无法统一调整。 案例: 68 个 SCSS 文件手动 px 值,修改字号需要改 68 个文件。 提交: 890c132 551d19d

  • 所有颜色、字号、间距使用 var(--tk-*) 设计 token
  • 不允许硬编码颜色值(如 #333rgb(0,0,0)
  • 深色模式通过 CSS 变量级联覆盖,不使用条件 class 切换
  • 新增 design token 在 tokens.scss 统一定义,不在页面中 --custom-var

规则 7.2 — 长者模式

根因: 线性放大(所有 px 乘以 1.2)导致布局错乱。 案例: 长者模式字号过大,卡片内容溢出。 提交: 4335f7e 257ca94

  • 所有页面字号 ≥ 22px长者模式使用 .elder-mode CSS 变量覆盖
  • 非线性放大:标题放大比例 > 正文(如 h1: 1.3x, body: 1.15x
  • 触摸目标 ≥ 44px
  • 长者模式排除特定页面(如登录页,否则键盘遮挡)

规则 7.3 — 医生/患者双模式

提交: 95e219a

  • 医生端使用 .doctor-mode 靛蓝覆盖
  • 共用页面通过 useDoctorClass() / useElderClass() 切换
  • 不为同一页面维护两套代码

规则 7.4 — 对齐设计原型

根因: 开发时凭感觉写 UI与设计稿差距大。 案例: 文章列表/详情页原型对齐、首页渐变头部移除、Profile 积分卡片等分。 提交: b8ce19f b84becf 63d8b7a 7b2c033 6c42d54 29d77e8

  • 新页面开发前先看原型稿,记录关键字号/间距/颜色
  • 完成后截图与原型对比(不是"差不多",是像素级)
  • 组件变体(如 SegmentTabs pill样式必须与原型一致
  • 卡片布局必须用 ContentCard不手写 div 模拟

规则 7.5 — 状态色统一

根因: 各页面自行定义状态颜色(绿色/红色/黄色),色调不一致。 提交: 4a95a83

  • 状态色必须对齐设计系统色板(成功绿/警告黄/危险红/信息蓝)
  • 使用 StatusTag 组件显示状态,不自行拼写 class 名
  • CSS 变量定义一次,全局引用

8 安全

规则 8.1 — XSS 防护

根因: AI 报告内容直接用 dangerouslySetInnerHTML 渲染,未过滤。 案例: AI 分析结果注入 <script> 标签。 提交: 931edc3 8f35394

  • 所有用户输入和外部内容AI 生成、富文本)必须经过 sanitizeHtml
  • Markdown → HTML 必须经过 sanitize
  • 不使用 dangerouslySetInnerHTML,用 Taro <RichText nodes={...}> 替代

规则 8.2 — 输入验证

根因: 前端不做验证,后端 400 错误不明确。 案例: 患者创建空名称成功后端无校验日期格式不对dayjs 对象未格式化)。 提交: 3424a33 603af83

  • 所有表单提交前验证必填字段
  • 日期字段必须格式化为 YYYY-MM-DD 字符串(不传 dayjs 对象)
  • 数值字段必须转为 number(不传字符串)
  • 后端返回 Validation 错误时,前端展示具体字段提示

规则 8.3 — 敏感数据

根因: Token 明文存储在 Storage微信开发者工具可直接查看。 提交: 83fe89c 447126b

  • Access token / refresh token 必须使用 secure-storageAES-256-GCM 加密)
  • 不在 URL 参数中传递 token
  • 不在 console.log 中打印 token
  • 请求头中的 token 通过 getHeaders() 统一注入,不在组件中手动拼接

规则 8.4 — 权限校验

根因: 前端不做权限校验,依赖后端 403 拦截。 案例: 患者端用了 health.health-data.list 权限码,应该是 health.points.list提交: 3424a33 c38967a

  • 医生端接口必须使用医生端 serviceservices/doctor/),不用患者端 service
  • 权限码拼写与后端 handler 一致(复数形式如 alerts.manage 不是 alert.manage
  • 关键操作(删除、关闭会话)必须有二次确认弹窗

9 错误处理与日志

规则 9.1 — 生产日志保留策略

根因: 生产构建移除了所有 console异常时无法排查。 提交: 59dd5ef dc98394

  • 生产构建保留 console.warnconsole.error
  • 仅移除 console.logconsole.infoconsole.debug
  • 关键路径(请求层、认证层)的异常必须有 console.warn
// config/prod.ts
pure_funcs: ['console.log', 'console.info', 'console.debug'],

规则 9.2 — 用户友好错误提示

提交: 4ca9027 3424a33

  • 错误映射表 ERROR_CODE_MAP 覆盖所有后端 error_code
  • 不向用户展示原始 error message 或 HTTP 状态码
  • 网络超时、服务器错误、权限不足各有独立提示文案

规则 9.3 — ErrorBoundary

根因: 页面 JS 异常导致白屏,无降级 UI。 提交: a63043f(第一版)8f35394(增强版)

  • 全局 ErrorBoundary 包裹 App 根组件
  • ErrorBoundary 必须有降级 UI"页面出错了,点击重试"
  • 不在 ErrorBoundary 中吃掉异常(至少 console.error

规则 9.4 — Markdown 渲染安全

根因: 简单正则无法正确处理 HTML 分组。 案例: AI 报告每个 <li> 独立成列表。 提交: fcce2f5

  • Markdown → HTML 转换必须正确处理连续同类标签的分组
  • 使用 sanitizeHtml 防止 XSS
  • 复杂 Markdown 场景考虑引入成熟库(如 marked + DOMPurify

10 定时器与副作用清理

规则 10.1 — setTimeout 必须清理

根因: setTimeout(() => Taro.navigateBack(), 1000) 在组件卸载后仍执行,操作已不存在的页面。 案例: 10 个页面有此问题。 提交: fed1759

  • 所有 setTimeout 必须使用 useSafeTimeout hook组件卸载自动清理
  • 不允许裸写 setTimeout(除非在 try/catch 中且有 clearTimeout
// ✅ 正确
const { safeSetTimeout } = useSafeTimeout();
safeSetTimeout(() => Taro.navigateBack(), 1000);

// ❌ 错误
setTimeout(() => Taro.navigateBack(), 1000); // 组件卸载后仍执行!

规则 10.2 — useEffect 清理

  • useEffect 中注册的事件监听、BLE 通知必须在 cleanup 中移除
  • 异步操作必须有 mountedRefabort controller 守卫
  • 长轮询使用 generation counter不需要手动清理 timer

11 开发环境

规则 11.1 — DevTools 性能优化

根因: Vite dev server 默认配置导致开发者工具卡顿。 案例: filesystem 缓存未开启 → 每次编译从零开始source map → EMFILE。 提交: 0f6f7a2 9c7ce93

  • config/dev.ts 开启 filesystem 缓存(cacheDir
  • 真机调试时关闭 source mapsourceMapType: ''
  • 开启 prebundle 减少 Vite 按需编译开销
  • 真机调试 EMFILE 时检查文件描述符限制

规则 11.2 — project.config.json

根因: 自动化端口未开启MCP 无法连接。 提交: c314093 8f35394

  • automationAudits 必须为 trueMCP 自动化测试前提)
  • urlCheck 设为 false(开发环境不检查合法域名)
  • compileHotReLoad 开启加速开发迭代

12 审计与交付

规则 12.1 — 提交前必检清单

每次提交小程序代码前,逐项确认:

  • grep -r "Taro.navigateTo" src/ — 无直接调用(全部用 safeNavigateTo
  • grep -r "const.*=.*() =>" src/ — render body 内无组件定义
  • grep -r "ScrollView" src/ — 无双重 ScrollView 嵌套
  • 长轮询使用 requestUnlimited 而非 api.get
  • 数组累积操作有 MAX 上限
  • <Image> 列表使用 lazyLoad
  • 新页面使用统一组件库PageShell / ContentCard 等)
  • 前端 service 函数与后端 DTO 字段对齐
  • setTimeout 使用 useSafeTimeout
  • 表单提交验证必填字段

规则 12.2 — PR Review 检查点

检查项 搜索模式 期望结果
直接导航 Taro.navigateTo 0 匹配
render body 组件 const [A-Z].*=.*(=>|{) 在 return 前 0 匹配
双重滚动 PageShell 子树含 ScrollView 有 ScrollView 则 scroll={false}
长轮询限流 pollFn:.*api\.(get|post) 应使用 requestUnlimited
无上限数组 .push( 无后续 slice 应有 MAX 截断
模块缓存泄漏 logout 方法不含缓存重置 应清空所有模块级变量
硬编码样式 #[0-9a-f]{3,6}\d+px 应使用 var(--tk-*)
Storage 传数据 setStorageSync.*detail 应通过 API 获取
裸 setTimeout setTimeout( 无 useSafeTimeout 应使用 hook
XSS 风险 dangerouslySetInnerHTML 应使用 RichText + sanitize
明文 token setStorageSync.*token 应使用 secureSet

规则 12.3 — 里程碑审计

每个功能里程碑完成后,执行:

  • 全页面导航测试(覆盖 10 层页栈场景)
  • Tab 快速切换测试(连续切换 5 次 Tab无卡死
  • 弱网测试3G 模拟,长轮询不阻塞 UI
  • 内存泄漏检查(长时间停留后页面不卡顿)
  • 登录/登出流程(切换账号后数据正确)
  • 多角色测试(患者/医生/护士/管理员各走一遍核心链路)
  • 长者模式覆盖(所有页面 ≥ 22px触摸目标 ≥ 44px
  • 设计原型对比(截图与原型稿像素级对比)

附录 A — 问题溯源表(全量)

以下每条规则对应的具体 bug 和修复提交,按规则编号排序:

规则 问题 严重度 首次修复提交
1.1 并发限制器 长轮询占满槽位导致请求饥饿 CRITICAL 9d50ef7
1.2 长轮询通道 咨询页长轮询阻塞所有 API CRITICAL 9d50ef7
1.3 reLaunch 去重 401 多次 reLaunch 崩溃 HIGH 9d50ef7
1.4 请求缓存去重 Tab 切换重复请求 + 竞态更新 MEDIUM 6d151bb
1.5 错误处理 tryRefreshToken 静默吞异常 HIGH a63043f
2.1 页栈保护 深层导航超 10 层失败 HIGH 59dd5ef
2.2 分包策略 consultation 在主包增大体积 MEDIUM 4c38fcd
2.3 防重入 useEffect + usePageData 双重加载 MEDIUM 1fd2c7a
3.1 render body InputField 每次 render 重建 HIGH fcce2f5
3.2 双重 ScrollView ai-report/list 滚动冲突 CRITICAL fcce2f5
3.3 图片懒加载 咨询详情图片无 lazyLoad MEDIUM fcce2f5
3.4 列表优化 聊天消息无上限 DOM 爆炸 MEDIUM 74bffb4
3.5 统一组件库 60 页面各自手写布局 HIGH 80794c9
3.6 React 导入 组件库运行时 React is not defined HIGH 1786f0d
4.1 数组上限 BLE readings 无限增长 MEDIUM fcce2f5
4.2 索引一致 DataBuffer seenKeys 不清理 MEDIUM fcce2f5
4.3 Storage 传数据 详情页用 Storage 传递数据 HIGH 0bf1822
5.1 缓存清理 auth.ts 模块缓存 logout 未清 MEDIUM fcce2f5
5.2 Token 安全 getHeaders 同步刷新阻塞 30s CRITICAL 9d50ef7
5.3 认证恢复 Tab 切换后 auth store 未恢复 HIGH 6632985
5.4 角色权限 患者看到医生端入口 MEDIUM c38967a
6.1 字段对齐 33 接口前端/后端字段不匹配 HIGH 8316281
6.2 API 路径 FHIR 端点迁移后前端 404 MEDIUM 8316281
6.3 必填字段 submitRecord 缺 task_id 后端 400 HIGH fbb28e6
7.1 CSS 变量 68 SCSS 硬编码 px MEDIUM 890c132
7.2 长者模式 线性放大导致布局错乱 MEDIUM 4335f7e
7.4 对齐原型 文章页/首页/Profile 与原型差距大 HIGH 63d8b7a
7.5 状态色 各页面状态颜色不一致 LOW 4a95a83
8.1 XSS AI 报告内容未过滤 CRITICAL 931edc3
8.2 输入验证 患者空名称创建成功 HIGH 603af83
8.3 敏感数据 Token 明文存储 HIGH 447126b
8.4 权限码 积分端点权限码错误 403 HIGH 3424a33
9.1 生产日志 safeReLaunch 静默吞错 LOW fcce2f5
9.3 ErrorBoundary 页面异常白屏无降级 HIGH a63043f
9.4 Markdown li 元素未合并到 ul MEDIUM fcce2f5
10.1 setTimeout 10 页面 setTimeout 无清理 HIGH fed1759
11.1 DevTools 开发者工具编译卡顿 MEDIUM 0f6f7a2
11.2 project.config MCP 自动化端口未开启 LOW c314093

附录 B — 快速自查脚本

# 在 apps/miniprogram/ 目录下运行

echo "=== 1. 直接 Taro.navigateTo ==="
grep -rn "Taro\.navigateTo" src/ --include="*.tsx" --include="*.ts"

echo "=== 2. render body 组件定义 ==="
grep -rn "const [A-Z].*=.*=>" src/ --include="*.tsx" | head -20

echo "=== 3. ScrollView 嵌套 ==="
grep -rn "ScrollView" src/ --include="*.tsx" -l | while read f; do
  if grep -q "PageShell" "$f" && grep -q "ScrollView" "$f"; then
    if ! grep -q "scroll={false}" "$f"; then
      echo "⚠️  $f — PageShell + ScrollView 但无 scroll={false}"
    fi
  fi
done

echo "=== 4. 长轮询未用 requestUnlimited ==="
grep -rn "pollFn.*api\.\(get\|post\)" src/ --include="*.tsx"

echo "=== 5. 无上限数组累积 ==="
grep -rn "\.push(" src/ --include="*.ts" -A2 | grep -v "slice\|MAX\|limit" | head -20

echo "=== 6. Image 无 lazyLoad列表页==="
grep -rn "<Image" src/ --include="*.tsx" -l | while read f; do
  if grep -q "\.map(" "$f" && ! grep -q "lazyLoad" "$f"; then
    echo "⚠️  $f — 列表页 Image 无 lazyLoad"
  fi
done

echo "=== 7. 硬编码样式 ==="
grep -rn "#[0-9a-fA-F]\{3,6\}" src/ --include="*.scss" | grep -v "var(--" | head -10
grep -rn "[0-9]\+px" src/ --include="*.scss" | grep -v "var(--\|token" | head -10

echo "=== 8. 裸 setTimeout ==="
grep -rn "setTimeout(" src/ --include="*.tsx" | grep -v "useSafeTimeout\|safeSetTimeout\|clearTimeout" | head -10

echo "=== 9. Storage 传数据 ==="
grep -rn "setStorageSync.*detail\|getStorageSync.*detail" src/ --include="*.tsx" --include="*.ts"

echo "=== 10. 明文 token ==="
grep -rn "setStorageSync.*token\|getStorageSync.*token" src/ --include="*.ts" | grep -v "secure"

echo "=== 检查完成 ==="

附录 C — 统计概览

类别 规则数 对应修复提交数 涉及 CRITICAL/HIGH
1. 并发与请求层 5 6 3
2. 导航与路由 3 5 2
3. 组件与渲染 6 16 3
4. 数据与内存 3 3 1
5. 认证与会话 4 7 2
6. 前后端接口契约 3 4 3
7. 样式与布局 5 10 1
8. 安全 4 6 3
9. 错误处理与日志 4 5 1
10. 定时器与副作用 2 1 1
11. 开发环境 2 3 0
12. 审计与交付 3
合计 44 66+ 20