docs(wiki): 多主题系统文档更新 + .gitignore 清理
- frontend.md: 4 套主题视觉人格 + 技术架构 + 温润东方详细 token - index.md: 症状导航更新 - miniprogram.md: 小程序审计报告 - .gitignore: 排除 .logs/ plans/ playwright-report/ 临时文件
This commit is contained in:
29
.gitignore
vendored
29
.gitignore
vendored
@@ -35,3 +35,32 @@ docs/debug-*.png
|
||||
# Development env
|
||||
.env.development
|
||||
docker/docker-compose.override.yml
|
||||
.agents/skills/
|
||||
.claude/skills/
|
||||
.kiro/skills/
|
||||
.trae/skills/
|
||||
.windsurf/skills/
|
||||
skills/
|
||||
|
||||
# Logs
|
||||
.logs/
|
||||
*.log
|
||||
|
||||
# Playwright reports
|
||||
**/playwright-report/
|
||||
|
||||
# Plans
|
||||
plans/
|
||||
|
||||
# MCP config
|
||||
.mcp.json
|
||||
|
||||
# Superpowers temp
|
||||
.superpowers/brainstorm/
|
||||
|
||||
# Test temp files
|
||||
.test_token*
|
||||
chi_sim.traineddata
|
||||
|
||||
# Local settings
|
||||
.claude/settings.local.json
|
||||
1
dev.ps1
1
dev.ps1
@@ -26,6 +26,7 @@ $env:ERP__AUTH__SUPER_ADMIN_PASSWORD = "Admin@2026"
|
||||
$env:ERP__REDIS__URL = "redis://:redis_KBCYJk@129.204.154.246:6379"
|
||||
$env:ERP__WECHAT__APPID = "wx20f4ef9cc2ec66c5"
|
||||
$env:ERP__WECHAT__SECRET = "placeholder_wechat_secret"
|
||||
$env:ERP__WECHAT__DEV_MODE = "true"
|
||||
$env:ERP__HEALTH__AES_KEY = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
||||
$env:ERP__HEALTH__HMAC_KEY = "f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Web 前端
|
||||
updated: 2026-04-26
|
||||
updated: 2026-04-28
|
||||
status: stable
|
||||
tags: [frontend, react, antd, vite, spa]
|
||||
---
|
||||
@@ -51,10 +51,53 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8.
|
||||
|
||||
- 使用 Ant Design 组件库,不自造轮子
|
||||
- 中文优先,所有文案通过 i18n key 引用
|
||||
- 支持暗色/亮色主题切换
|
||||
- 支持 4 套主题切换:信任蓝 / 温润东方 / 深邃夜色 / 翡翠清雅
|
||||
- 侧边栏按模块分组:基础模块 / 行业模块
|
||||
- 表单验证使用 Ant Design Form 的 validateRules
|
||||
|
||||
### 2.1 多主题系统(4 套内置主题)
|
||||
|
||||
> 自 2026-04-28 起采用多主题架构。用户可在顶栏主题切换器中选择偏好主题,选择持久化到 localStorage。
|
||||
|
||||
#### 4 套主题视觉人格
|
||||
|
||||
| 主题 | 主色 | 背景 | 圆角 | 性格 |
|
||||
|------|------|------|------|------|
|
||||
| **信任蓝** (blue) | `#2563EB` | `#F8FAFC` 冷灰 | 10/12/6px | 专业·企业 |
|
||||
| **温润东方** (warm) | `#C4623A` | `#F5F0EB` 暖米 | 12/16/8px | 温润·人文 |
|
||||
| **深邃夜色** (dark) | `#60A5FA` | `#0F172A` 深蓝黑 | 10/12/6px | 护眼·专注 |
|
||||
| **翡翠清雅** (emerald) | `#5B7A5E` | `#F4F7F4` 浅绿灰 | 10/14/8px | 清新·健康 |
|
||||
|
||||
#### 技术架构
|
||||
|
||||
- **CSS 变量层** — `:root` 默认为 blue,`[data-theme='xxx']` 覆盖全部视觉 token(`apps/web/src/index.css`)
|
||||
- **Ant Design 动态主题** — `ConfigProvider` 的 `theme` prop 按 ThemeName 选择不同配置(`apps/web/src/App.tsx`)
|
||||
- **Zustand 持久化** — `useAppStore().theme` + `localStorage('hms-theme')`(`apps/web/src/stores/app.ts`)
|
||||
- **暗色检测** — `useThemeMode()` hook 从 store 读取,不再比对色值(`apps/web/src/hooks/useThemeMode.ts`)
|
||||
|
||||
#### 温润东方风详细 Token(与小程序端共享)
|
||||
|
||||
> 小程序端源文件:`apps/miniprogram/src/styles/variables.scss` + `mixins.scss`
|
||||
|
||||
| 角色 | CSS 变量 | 色值 |
|
||||
|------|----------|------|
|
||||
| 主色 | `--erp-primary` | `#C4623A` |
|
||||
| 背景 | `--erp-bg-page` | `#F5F0EB` |
|
||||
| 容器 | `--erp-bg-container` | `#FFFFFF` |
|
||||
| 主文字 | `--erp-text-primary` | `#2D2A26` |
|
||||
| 次文字 | `--erp-text-secondary` | `#7A756E` |
|
||||
| 边框 | `--erp-border` | `#E8E2DC` |
|
||||
| 成功 | `--erp-success` | `#5B7A5E` |
|
||||
| 警告 | `--erp-warning` | `#C4873A` |
|
||||
| 错误 | `--erp-error` | `#B54A4A` |
|
||||
|
||||
#### 禁止事项
|
||||
|
||||
- 禁止紫色渐变、禁止 emoji 作图标
|
||||
- 禁止左侧彩色边框卡片标示状态(改用 tag 标签)
|
||||
- 禁止无意义的渐变背景
|
||||
- 禁止装饰性 icon 遍地配
|
||||
|
||||
## 3. 关键文件 + 数据流
|
||||
|
||||
### 核心文件
|
||||
@@ -62,9 +105,11 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8.
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `apps/web/src/main.tsx` | React 入口 |
|
||||
| `apps/web/src/App.tsx` | 路由定义(公开 + 受保护) |
|
||||
| `apps/web/src/layouts/MainLayout.tsx` | SaaS 后台管理布局 |
|
||||
| `apps/web/src/App.tsx` | 路由定义 + Ant Design 动态主题(4 套) |
|
||||
| `apps/web/src/layouts/MainLayout.tsx` | SaaS 后台管理布局 + ThemeSwitcher 集成 |
|
||||
| `apps/web/src/stores/` | 4 个 Zustand store |
|
||||
| `apps/web/src/components/ThemeSwitcher.tsx` | 主题选择下拉面板 |
|
||||
| `apps/web/src/hooks/useThemeMode.ts` | 暗色模式检测(从 store 读取) |
|
||||
| `apps/web/src/api/` | 28 个 API 服务文件(含 7 个健康模块 API) |
|
||||
| `apps/web/vite.config.ts` | Vite 配置 + API 代理 |
|
||||
|
||||
@@ -145,7 +190,7 @@ React 19.2.4 / Ant Design 6.3.5 / React Router 7.14.0 / Zustand 5.0.12 / Vite 8.
|
||||
|
||||
| Store | 状态 |
|
||||
|-------|------|
|
||||
| `app.ts` | theme(light/dark), sidebarCollapsed |
|
||||
| `app.ts` | theme(blue/warm/dark/emerald), sidebarCollapsed, localStorage 持久化 |
|
||||
| `auth.ts` | user, isAuthenticated, localStorage 持久化 |
|
||||
| `message.ts` | unreadCount, recentMessages, 请求去重 |
|
||||
| `plugin.ts` | plugins 列表, 动态菜单, schema 缓存, 请求去重 |
|
||||
@@ -208,6 +253,7 @@ ws://localhost:5174/ws/* → ws://localhost:3000/* (WebSocket)
|
||||
|
||||
| 日期 | 变更 |
|
||||
|------|------|
|
||||
| 2026-04-28 | 多主题系统:4 套主题(blue/warm/dark/emerald)+ CSS 变量 + Ant Design 动态主题 + ThemeSwitcher + useThemeMode 修复 |
|
||||
| 2026-04-26 | 全面更新:22 条健康路由(+12 内容/积分/统计/活动/AI)、11 个共享组件、77 个 TSX 文件 |
|
||||
| 2026-04-26 | 从 CLAUDE.md 迁移:UI 布局规范(§8) |
|
||||
| 2026-04-26 | VitalSignsChart 重设计:概览卡片条 + 点击展开详情图,5 指标独立 Y 轴 |
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
| erp-health 实体 | 34 个 Entity(17k 行 Rust) |
|
||||
| erp-ai 实体 | 3 个 Entity(1.7k 行 Rust) |
|
||||
| Web 前端 | 77 个 TSX + 56 个 TS = 133 个源文件(48 个页面 + 22 健康页面 + 11 健康组件) |
|
||||
| 微信小程序 | Taro 4.2 + React 18,12 个页面 |
|
||||
| 微信小程序 | Taro 4.2 + React 18,40 个页面(31 患者端 + 9 医护端),5 个 TabBar |
|
||||
| 前端单元测试 | 3 个(vitest)+ 4 E2E spec(playwright) |
|
||||
| 后端测试 | 36 个(workspace)+ 83 validation 纯函数测试 |
|
||||
| 总代码量 | Rust ~63k 行 + 前端 TSX/TS ~133 文件 + 小程序 ~7.5k 行 |
|
||||
@@ -40,7 +40,10 @@
|
||||
| 迁移文件缺失报错 | [[database]] 迁移注册 | migration/src/lib.rs | 已应用的迁移文件被删除,需创建 stub |
|
||||
| MCP 连接失败 `split` error | [[miniprogram]] MCP 联调 | project.config.json | 未开启 `automationAudits` |
|
||||
| MCP mp_screenshot 超时 | [[miniprogram]] MCP 联调 | automator 已知 bug | 用 `page_getElement` 替代截图 |
|
||||
| MCP 导航后跳回登录页 | [[miniprogram]] MCP 联调 §6.4 | storage 被清空 | 加密 token 写入后立即 reLaunch |
|
||||
| MCP 导航后跳回登录页 | [[miniprogram]] MCP 联调 §6.4 | storage 被清空 | 明文 token 写入后立即 reLaunch |
|
||||
| MCP token 注入后仍 401 | [[miniprogram]] MCP 联调 §6.1 | 用了生产构建 | dev 构建(`NODE_ENV=development`)+ 空密钥 |
|
||||
| 积分商城 Tab 页空白 | [[miniprogram]] 待优化 | 未关联患者档案 | 需增加降级 UI 引导建档 |
|
||||
| MCP 批量审计页面栈溢出 | [[miniprogram]] MCP 联调 §6.6 | `navigateTo` 超 10 层 | 改用 `reLaunch` 逐页测试 |
|
||||
|
||||
## 模块导航
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ psql: `D:\postgreSQL\bin\psql.exe -U postgres -h localhost -d erp`
|
||||
| `ERP__REDIS__URL` | `redis://:redis_KBCYJk@129.204.154.246:6379` |
|
||||
| `ERP__WECHAT__APPID` | `wx20f4ef9cc2ec66c5` |
|
||||
| `ERP__WECHAT__SECRET` | 微信小程序 Secret |
|
||||
| `ERP__WECHAT__DEV_MODE` | `true`(开发时跳过 jscode2session,允许 DevTools 模拟器登录) |
|
||||
| `ERP__HEALTH__AES_KEY` | 64 字符 hex 编码(32 字节) |
|
||||
| `ERP__HEALTH__HMAC_KEY` | 64 字符 hex 编码(32 字节) |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 微信小程序(患者端)
|
||||
updated: 2026-04-26
|
||||
updated: 2026-04-27
|
||||
status: active
|
||||
tags: [miniprogram, taro, wechat, patient]
|
||||
---
|
||||
@@ -20,7 +20,67 @@ tags: [miniprogram, taro, wechat, patient]
|
||||
|
||||
### 版本
|
||||
|
||||
Taro 4.2 / React 18 / TypeScript / Zustand 5 / Sass / Zod
|
||||
Taro 4.2 / React 18 / TypeScript / Zustand 5 / Sass / Zod / ECharts 6(按需引入)
|
||||
|
||||
> **依赖统一(2026-04-27):** 移除 `echarts-taro3-react`(内嵌 Taro 3.0.8 + React 16,导致 webpack 模块 ID 不一致 → `Cannot read property 'call' of undefined`)。改为自定义 `EcCanvas` 组件 + `echarts/core` 按需引入。`taro.js` chunk 从 152KB 降至 133KB。
|
||||
|
||||
### 1.1 设计系统:温润东方风
|
||||
|
||||
> 自 2026-04-27 起采用。所有新增/修改页面必须遵循此设计系统,保持视觉一致性。
|
||||
>
|
||||
> 设计系统源文件:`apps/miniprogram/src/styles/variables.scss` + `mixins.scss`
|
||||
|
||||
**设计哲学**:Kenya Hara 式东方极简。温润米底 + 单一赤土橙贯穿全场,留白呼吸感优先。
|
||||
|
||||
#### 色彩
|
||||
|
||||
| 角色 | 变量名 | 色值 | 用途 |
|
||||
|------|--------|------|------|
|
||||
| 强调色 | `$pri` | `#C4623A` | 按钮、链接、活跃 tab、品牌色 |
|
||||
| 强调浅 | `$pri-l` | `#F0DDD4` | 图标底色、高亮背景 |
|
||||
| 强调深 | `$pri-d` | `#8B3E1F` | 渐变终点、暗色变体 |
|
||||
| 背景 | `$bg` | `#F5F0EB` | 页面底色(warm cream) |
|
||||
| 卡片 | `$card` | `#FFFFFF` | 白色卡片 |
|
||||
| 辅助底 | `$surface-alt` | `#EDE8E2` | 次级表面、分隔 |
|
||||
| 主文字 | `$tx` | `#2D2A26` | warm black,标题和正文 |
|
||||
| 次文字 | `$tx2` | `#7A756E` | 辅助说明、标签 |
|
||||
| 淡文字 | `$tx3` | `#A8A29E` | 占位、时间戳、箭头 |
|
||||
| 边框 | `$bd` / `$bd-l` | `#E8E2DC` / `#F0EBE5` | 分隔线、卡片边框 |
|
||||
| 成功 | `$acc` / `$acc-l` | `#5B7A5E` / `#E8F0E8` | sage green 正常状态 |
|
||||
| 警告 | `$wrn` / `$wrn-l` | `#C4873A` / `#FFF3E0` | warm amber 偏高/待确认 |
|
||||
| 危险 | `$dan` / `$dan-l` | `#B54A4A` / `#FDEAEA` | muted red 异常/删除 |
|
||||
|
||||
#### 字体
|
||||
|
||||
| 用途 | 字体栈 | 说明 |
|
||||
|------|--------|------|
|
||||
| 标题 / 数据数字 | `Georgia, Times New Roman, serif` | 衬线体,传递专业与温度。用 `@include serif-number` |
|
||||
| 正文 / 辅助 | `-apple-system, PingFang SC, sans-serif` | 系统无衬线,清晰可读 |
|
||||
| 章节标题 | `@include section-title` | 30rpx / bold / serif,统一样式 |
|
||||
|
||||
#### 圆角 / 阴影 / 间距
|
||||
|
||||
| 元素 | 圆角 | 阴影 |
|
||||
|------|------|------|
|
||||
| 卡片 | `$r` 12px | `$shadow-md` (0 2px 12px rgba(45,42,38,0.08)) |
|
||||
| 小元素(标签、输入框) | `$r-sm` 8px | `$shadow-sm` (0 1px 4px rgba(45,42,38,0.04)) |
|
||||
| 浮层、弹窗 | `$r-lg` 16px | `$shadow-lg` (0 8px 32px rgba(45,42,38,0.12)) |
|
||||
| 水平间距 | 页面两侧 24rpx,卡片内 24-28rpx | |
|
||||
| 垂直间距 | 区块间 24rpx,卡片内标题与内容 20rpx | |
|
||||
|
||||
#### 组件规范
|
||||
|
||||
**按钮**:主按钮 `$pri` 实色 + 白字 + 圆角 12px + 阴影。次按钮 `$pri` 边框透明底。禁用 `$surface-alt` 底 + `$tx3` 字。
|
||||
|
||||
**状态标签**:用 `@include tag(背景色, 文字色)` 生成。不用粗边框、不用 emoji 状态图标。颜色映射:正常 → `$acc`/`$acc-l`,偏高 → `$wrn`/`$wrn-l`,异常 → `$dan`/`$dan-l`,默认 → `$tx2`/`$bd-l`。
|
||||
|
||||
**健康数据卡片**:2×2 网格,衬线大数字居中,小标签 + 状态标签在底部。数据用 `@include serif-number` 等宽数字。
|
||||
|
||||
**列表项**:白色卡片容器,每行 14-24rpx padding,`$bd-l` 底部分隔。右侧箭头用 `›` 字符。无左侧彩色 border accent。
|
||||
|
||||
**图标**:禁止用 emoji 作图标。用线性 SVG 或首字衬线体作为图标占位。图标底色用 `$pri-l`。
|
||||
|
||||
**禁止事项**:紫色渐变 / emoji 作图标 / 圆角卡片+左彩色 border / 无意义渐变背景 / 装饰性 icon 遍地配。
|
||||
|
||||
## 2. 关键文件 + 数据流
|
||||
|
||||
@@ -122,10 +182,11 @@ POST /auth/wechat/login { code }
|
||||
| `analytics.ts` | 数据分析 |
|
||||
| `wechat-templates.ts` | 微信模板消息 ID |
|
||||
|
||||
### 组件(9 个)
|
||||
### 组件(10 个)
|
||||
|
||||
| 组件 | 用途 |
|
||||
|------|------|
|
||||
| `EcCanvas` | ECharts Canvas 包装(Taro 4 兼容,按需引入 echarts/core) |
|
||||
| `EmptyState` | 空状态占位 |
|
||||
| `ErrorBoundary` | 错误边界捕获 |
|
||||
| `ErrorState` | 错误状态展示 |
|
||||
@@ -133,7 +194,7 @@ POST /auth/wechat/login { code }
|
||||
| `HealthCard` | 健康数据卡片 |
|
||||
| `Loading` | 加载状态 |
|
||||
| `StepIndicator` | 步骤指示器 |
|
||||
| `TrendChart` | 趋势图表(ECharts) |
|
||||
| `TrendChart` | 趋势图表(基于 EcCanvas) |
|
||||
| `WeekCalendar` | 周日历组件 |
|
||||
|
||||
### 集成契约
|
||||
@@ -214,15 +275,21 @@ secret = "<通过环境变量 ERP__WECHAT__SECRET 设置>"
|
||||
| 登录成功但前端报失败 `btoa is not defined` | `secure-storage.ts` 使用 Web API | 改用 `Taro.arrayBufferToBase64` |
|
||||
| 微信登录 500 `wechat_users.created_by 不存在` | 迁移创建的表缺少标准字段 | `ALTER TABLE` 补列 |
|
||||
| 401 循环重定向 | 首页未登录时 `request.ts` 反复 redirectTo | 检查当前路径避免重复跳转 |
|
||||
| `Cannot read property 'call' of undefined` | `echarts-taro3-react` 内嵌 Taro 3.0.8 + React 16,与 Taro 4.2 + React 18 产生双实例,webpack 模块 ID 不匹配 | 移除 `echarts-taro3-react`,改为自定义 `EcCanvas` + `echarts/core` 按需引入 |
|
||||
|
||||
### 待优化
|
||||
|
||||
| 问题 | 级别 | 说明 |
|
||||
|------|------|------|
|
||||
| URL 拼接构建查询参数 | P2 | `request.ts` 应支持 params 对象 |
|
||||
| 加密密钥硬编码 | P0 | 需外部化到 `TARO_APP_ENCRYPTION_KEY` 环境变量 |
|
||||
| 加密密钥硬编码 | ~~P0~~ 已解决 | 已外部化到 `TARO_APP_ENCRYPTION_KEY` 环境变量 |
|
||||
| Auth token 日志输出 | P0 | 生产环境需移除 console.log |
|
||||
| 生产配置 | P2 | `urlCheck`/`minified` 需区分环境 |
|
||||
| 积分商城降级 UI | P0 | 未关联患者档案时 Tab 页空白,需引导用户建档 |
|
||||
| daily-monitoring 无 Zod 验证 | P1 | 对齐 health/input 的验证标准 |
|
||||
| 文章列表返回草稿 | P1 | 患者端应只展示 `published` 状态文章 |
|
||||
| Token 刷新竞态 | P0 | 多个 API 同时 401 时各自独立刷新,可能锁死用户 |
|
||||
| ECharts 全量引入 | P2 | 趋势页 455KiB,按需引入可减少 ~80% |
|
||||
|
||||
### 注意事项
|
||||
|
||||
@@ -238,10 +305,20 @@ secret = "<通过环境变量 ERP__WECHAT__SECRET 设置>"
|
||||
### 6.1 前置条件
|
||||
|
||||
1. **后端运行** — `cargo run` 启动 `http://localhost:3000`
|
||||
2. **小程序已构建** — `cd apps/miniprogram && pnpm build:weapp`
|
||||
2. **小程序以 dev 模式构建** — 必须使用 `NODE_ENV=development` 构建(详见 §6.4)
|
||||
```bash
|
||||
cd apps/miniprogram
|
||||
# 清空加密密钥(必须,否则 encrypt/decrypt 不一致)
|
||||
echo 'TARO_APP_API_URL=http://localhost:3000/api/v1
|
||||
TARO_APP_ENCRYPTION_KEY=' > .env
|
||||
# dev 模式构建
|
||||
NODE_ENV=development npx taro build --type weapp
|
||||
```
|
||||
3. **微信开发者工具已打开项目** — 加载 `apps/miniprogram/dist/`
|
||||
4. **自动化端口已开启** — `project.config.json` 中 `"automationAudits": true`,端口 9420
|
||||
|
||||
> ⚠️ **为什么必须 dev 模式?** 生产构建(`pnpm build:weapp`)设置 `NODE_ENV=production`,`secure-storage.ts` 的 `decrypt()` 在密钥为空时会抛异常 "TARO_APP_ENCRYPTION_KEY 未设置,生产环境禁止明文读取"。dev 模式允许空密钥走明文存储。
|
||||
|
||||
### 6.2 建立连接
|
||||
|
||||
```
|
||||
@@ -273,29 +350,70 @@ secret = "<通过环境变量 ERP__WECHAT__SECRET 设置>"
|
||||
|
||||
### 6.4 绕过微信登录
|
||||
|
||||
MCP 无法模拟微信 OAuth,需要通过 `mp_callWx` 直接写入加密 auth 数据:
|
||||
MCP 无法模拟微信 OAuth(`Taro.login()` 返回的 code 走真实微信 `jscode2session` 接口,DevTools 模拟器会返回 mock code 导致后端 500)。
|
||||
|
||||
#### 方案一:明文 token 注入(推荐,用于自动化测试)
|
||||
|
||||
**原理:** 以 dev 模式重编译(空加密密钥),`secure-storage.ts` 走明文路径,直接用 `wx.setStorageSync` 写入。
|
||||
|
||||
**步骤:**
|
||||
|
||||
```
|
||||
1. 调用后端 API 获取 admin token:
|
||||
POST /api/v1/auth/login { username: "admin", password: "Admin@2026" }
|
||||
1. 准备:确保已按 §6.1 以 dev 模式构建
|
||||
|
||||
2. 用 CryptoJS.AES.encrypt(token, ENC_KEY) 加密
|
||||
2. 获取 admin token:
|
||||
POST http://localhost:3000/api/v1/auth/login
|
||||
Body: { "username": "admin", "password": "Admin@2026" }
|
||||
返回: { data: { access_token, refresh_token, user: { id, username, ... } } }
|
||||
|
||||
3. 通过 mp_callWx 写入 storage:
|
||||
setStorageSync("access_token", encrypted_token)
|
||||
setStorageSync("refresh_token", encrypted_refresh)
|
||||
setStorageSync("user_data", encrypted_user_json)
|
||||
setStorageSync("user_roles", encrypted_roles_json)
|
||||
setStorageSync("tenant_id", encrypted_tenant_id)
|
||||
setStorageSync("current_patient", patient_object)
|
||||
setStorageSync("current_patient_id", patient_id)
|
||||
3. 通过 automator.evaluate() 注入明文 storage:
|
||||
mp.evaluate((at, rt, ud, ur, tid, pid) => {
|
||||
wx.setStorageSync('access_token', at);
|
||||
wx.setStorageSync('refresh_token', rt);
|
||||
wx.setStorageSync('user_data', ud);
|
||||
wx.setStorageSync('user_roles', ur);
|
||||
wx.setStorageSync('tenant_id', tid);
|
||||
wx.setStorageSync('current_patient_id', pid);
|
||||
wx.setStorageSync('current_patient', {
|
||||
id: pid, name: 'TestPatient', gender: 'male',
|
||||
birth_date: '1990-01-15', status: 'active'
|
||||
});
|
||||
}, accessToken, refreshToken, userDataJson, rolesJson, tenantId, patientId);
|
||||
|
||||
4. reLaunch 到首页
|
||||
4. reLaunch 到首页:
|
||||
mp.reLaunch('/pages/index/index');
|
||||
await sleep(3000);
|
||||
```
|
||||
|
||||
**ENC_KEY**: `0a17b71d46064b06f993c9c202b342425e311a79f5be026d830562e7ad51f522`
|
||||
**关键 tenant/patient ID:**
|
||||
- `tenant_id`: `019d80da-7a2c-7820-b0a3-3d5266a3a324`
|
||||
- `current_patient_id`: `019dcd34-bc4d-72c1-8c19-77ce1f4839d6`
|
||||
|
||||
> ⚠️ 注意:写完 storage 后必须立即 reLaunch,否则 app 的 API 请求会因 token 无效触发 401 → logout 清空 storage。
|
||||
**为什么不用 mp_callWx?**
|
||||
- 加密 token(~2100 字符)通过 `mp_callWx setStorageSync` 传输时会被截断/损坏
|
||||
- `automator.evaluate()` 直接在小程序 JS 上下文执行,无传输问题
|
||||
- `require('./utils/secure-storage')` 在 evaluate 中不可用(webpack 用数字 ID 注册模块),只能用 `wx.setStorageSync`
|
||||
|
||||
#### 方案二:加密 token 注入(用于真实环境测试)
|
||||
|
||||
如果需要测试加密存储路径(密钥非空),可以:
|
||||
|
||||
```
|
||||
1. 恢复 .env 中的加密密钥:
|
||||
TARO_APP_ENCRYPTION_KEY=0a17b71d46064b06f993c9c202b342425e311a79f5be026d830562e7ad51f522
|
||||
|
||||
2. 重新构建: pnpm build:weapp
|
||||
|
||||
3. 使用 inject-auth.cjs 脚本(内含 CryptoJS AES 加密逻辑)
|
||||
```
|
||||
|
||||
> ⚠️ 实测发现加密 token 通过 MCP 传输存在截断问题,方案二仅在需要测试加密存储时使用。
|
||||
|
||||
#### 注意事项
|
||||
|
||||
- 写完 storage 后**必须立即 reLaunch**,否则 app 的 API 请求会因 token 无效触发 401 → logout 清空 storage
|
||||
- `reLaunch` 后等待 2-3 秒再进行后续操作,页面需要时间加载和初始化
|
||||
- 恢复 `.env` 加密密钥后需重新构建,否则 `secure-storage.ts` 行为不一致
|
||||
|
||||
### 6.5 TabBar 页面列表
|
||||
|
||||
@@ -312,25 +430,93 @@ MCP 无法模拟微信 OAuth,需要通过 `mp_callWx` 直接写入加密 auth
|
||||
| 问题 | 原因 | 替代方案 |
|
||||
|------|------|----------|
|
||||
| **mp_screenshot 超时** | `miniprogram-automator` 的 `screenshot()` 方法在当前 DevTools 版本卡死 | 用 `page_getElement` + `page_getData` 获取 UI 状态 |
|
||||
| **navigateTo 超时** | 某些页面的 API 调用阻塞了页面渲染回调 | 页面实际已加载,用 `mp_currentPage` 确认路径 |
|
||||
| **auth 重定向** | app 的 request interceptor 检测 401 后自动跳转 login 并清空 storage | 确保 token 有效,reLaunch 后等待足够时间 |
|
||||
| **getStorageSync 返回空** | reLaunch 触发 app 初始化,401 → logout 清空了 storage | 检查 token 是否过期,必要时重新注入 |
|
||||
| **navigateTo 超出 10 页栈** | 小程序 webview 限制最多 10 层页面栈 | 批量测试用 `reLaunch` 逐页导航 |
|
||||
| **加密 token MCP 传输截断** | AES 加密后的 token(~2100 字符)通过 `mp_callWx` 传输时损坏 | 使用 `evaluate()` 在 JS 上下文内直接写入 |
|
||||
| **require() 在 evaluate 不可用** | webpack 用数字 ID 注册模块,非文件路径 | 直接使用 `wx.setStorageSync` / `wx.getStorageSync` |
|
||||
| **CryptoJS 非全局变量** | CryptoJS 打包在 webpack bundle 内,evaluate 上下文无法访问 | 绕过加密:dev 模式 + 空密钥 |
|
||||
| **auth 重定向** | request interceptor 检测 401 后自动跳转 login 并清空 storage | 确保 token 有效,reLaunch 后等待 2-3 秒 |
|
||||
| **生产构建 decrypt 抛异常** | 空密钥 + `NODE_ENV=production` 时 `decrypt()` 直接 throw | 使用 `NODE_ENV=development` 构建 |
|
||||
|
||||
### 6.7 端到端验证脚本
|
||||
### 6.7 自动化审计脚本
|
||||
|
||||
`apps/miniprogram/e2e-final.cjs` 使用 `miniprogram-automator` Node.js 库执行完整链路验证:
|
||||
| 脚本 | 用途 | 运行 |
|
||||
|------|------|------|
|
||||
| `apps/miniprogram/inject-auth.cjs` | 注入明文 token(dev 模式) | `node inject-auth.cjs` |
|
||||
| `apps/miniprogram/audit-pages.cjs` | 批量审计 24 个非 TabBar 页面 | `node audit-pages.cjs` |
|
||||
| `apps/miniprogram/audit-detail-pages.cjs` | 审计 11 个详情页(假 ID 优雅降级) | `node audit-detail-pages.cjs` |
|
||||
| `apps/miniprogram/e2e-final.cjs` | 完整 E2E 链路验证(11 UI + 10 API) | `node e2e-final.cjs` |
|
||||
|
||||
- 11 条 UI 链路(页面导航 + 元素交互)
|
||||
- 10 个 API 数据闭环验证
|
||||
- 加密 storage 注入绕过微信登录
|
||||
- 超时保护避免卡死
|
||||
#### 批量审计脚本模板
|
||||
|
||||
运行:`cd apps/miniprogram && node e2e-final.cjs`
|
||||
```javascript
|
||||
const automator = require('miniprogram-automator');
|
||||
|
||||
async function main() {
|
||||
const mp = await automator.connect({ wsEndpoint: 'ws://localhost:9420' });
|
||||
const results = { ok: [], crash: [], login: [] };
|
||||
|
||||
for (const pageUrl of PAGES) {
|
||||
try {
|
||||
await mp.reLaunch(`/${pageUrl}`);
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
const current = await mp.currentPage();
|
||||
if (current.path === pageUrl.split('?')[0]) {
|
||||
results.ok.push(pageUrl);
|
||||
} else if (current.path === 'pages/login/index') {
|
||||
results.login.push(pageUrl);
|
||||
} else {
|
||||
results.crash.push(pageUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
results.crash.push(pageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`OK: ${results.ok.length}, Login: ${results.login.length}, Crash: ${results.crash.length}`);
|
||||
await mp.disconnect();
|
||||
}
|
||||
main();
|
||||
```
|
||||
|
||||
### 6.8 审计结果(2026-04-27 实测)
|
||||
|
||||
通过 MCP 自动化工具对全部 40 个页面进行渲染审计:
|
||||
|
||||
| 类别 | 数量 | 结果 |
|
||||
|------|------|------|
|
||||
| TabBar 页面 | 5 | 5/5 OK |
|
||||
| 患者端子页面 | 24 | 24/24 OK |
|
||||
| 详情页(假 ID) | 11 | 11/11 优雅降级 |
|
||||
| **合计** | **40** | **40/40 全部通过** |
|
||||
|
||||
核心 API 数据链路验证(13 端点):
|
||||
|
||||
| API 端点 | 状态 | 说明 |
|
||||
|----------|------|------|
|
||||
| GET /health/vital-signs/today | 200 | |
|
||||
| GET /health/patients | 200 | |
|
||||
| GET /health/doctors | 200 | |
|
||||
| GET /health/appointments | 200 | |
|
||||
| GET /health/consultation-sessions | 200 | |
|
||||
| GET /health/follow-up-tasks | 200 | |
|
||||
| GET /health/articles | 200 | |
|
||||
| GET /health/points/products | 200 | |
|
||||
| GET /ai/analysis/history | 200 | |
|
||||
| GET /health/points/account | 404 | admin 无患者档案(预期) |
|
||||
| GET /health/points/transactions | 404 | 同上 |
|
||||
| GET /health/points/orders | 404 | 同上 |
|
||||
| GET /health/points/checkin/status | 404 | 同上 |
|
||||
|
||||
> 积分端点 404 说明:路由已注册在 `module.rs:454-484`,`resolve_patient_id()` 要求登录用户关联患者档案。admin 是管理员账号,无患者档案,因此返回 "当前用户未关联患者档案"。这是正确的业务逻辑。
|
||||
|
||||
详细审计报告见 `docs/discussions/2026-04-27-miniprogram-audit-report.md`。
|
||||
|
||||
## 7. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|------|------|
|
||||
| 2026-04-27 | **移除 echarts-taro3-react**:内嵌 Taro 3 + React 16 导致 webpack 模块加载失败,改为自定义 `EcCanvas` 组件 + `echarts/core` 按需引入;更新版本说明 + 历史教训 + 组件列表 |
|
||||
| 2026-04-27 | **MCP 联调全面更新**:§6.1 增加 dev 构建前置条件,§6.4 重写为明文 token 注入法(评估两种方案),§6.6 补充 7 条已知限制,新增 §6.7 审计脚本说明 + §6.8 实测审计结果(40/40 页面通过);§5 补充 4 条审计发现 |
|
||||
| 2026-04-27 | 新增 §6 MCP 联调章节:连接、操作列表、绕过登录、已知限制、e2e 脚本 |
|
||||
| 2026-04-26 | 全面更新:40 页面(含 9 个医护端页面)、15 目录、5 个 Tab 页、积分商城、线下活动 |
|
||||
| 2026-04-25 | 全面更新:20 页面、10 服务、9 组件、Zod 验证、加密密钥外部化说明 |
|
||||
|
||||
Reference in New Issue
Block a user