Files
Mdeical_Sur_Report/AGENTS.md

290 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md
> 本文档面向 AI 编程助手。阅读者应假定对项目一无所知。所有信息均基于项目实际代码和开发历史,不做假设性推断。
---
## 项目概述
**手术图文病历报告系统**(版本 1.3是一款纯前端、单页应用SPA面向医院手术室场景用于
- 通过富文本编辑器撰写结构化手术图文报告
- 上传手术视频并自动/手动抽取关键帧,拖拽插入报告
- 管理报告模板、用户权限、系统设置
- 导出 PDF / JSON 格式的报告和模板
**核心特征**
- **无后端服务器**:所有数据(用户、报告、模板、设置、素材)持久化在浏览器 `localStorage`
- **离线可用**:部署后即为静态文件,无需网络 API
- **A4 打印优先**:编辑器按 A4 尺寸210mm × 297mm排版支持浏览器打印转 PDF
- **角色权限控制**:三级角色 `super`(超级管理员)/`admin`(科室管理员)/`user`(医生)
---
## 技术栈
| 层级 | 技术 |
|------|------|
| 框架 | React 19 + TypeScript 5.8 |
| 构建工具 | Vite 6 |
| 样式 | Tailwind CSS v4使用 `@theme``@import "tailwindcss"` 新语法) |
| 路由 | React Router DOM v7 |
| 图标 | lucide-react |
| 动画 | motion |
| AI SDK | `@google/genai`(依赖已安装,但当前源码中**未实际调用**任何 LLM API |
| 运行时 | 纯浏览器客户端;`express` 仅在依赖列表中,未被源码使用 |
---
## 项目结构
```
├── public/ # 静态资源favicon、logo_square.png
├── src/
│ ├── components/
│ │ └── Sidebar.tsx # 左侧导航栏(角色过滤、自动折叠)
│ ├── pages/
│ │ ├── Login.tsx # 登录页 + 全局初始化(默认用户/模板/字段/素材)
│ │ ├── Dashboard.tsx # 工作台统计卡片、SVG 趋势图)
│ │ ├── ReportEditor.tsx# 核心报告编辑器2,200+ 行,最大文件)
│ │ ├── ReportManage.tsx# 报告列表(搜索、筛选、批量操作、历史回溯)
│ │ ├── ReportView.tsx # 报告只读查看 + 打印
│ │ ├── TemplateManage.tsx # 模板编辑器1,600+ 行,自定义 Undo/Redo
│ │ ├── UserManage.tsx # 用户管理RBAC、签名上传、模板权限
│ │ └── SystemSettings.tsx # 系统设置抽帧配置、AI API、默认模板
│ ├── utils/
│ │ ├── storage.ts # localStorage / sessionStorage 封装
│ │ ├── print.ts # iframe 打印工具A4 样式、@page 边距)
│ │ └── defaultContent.ts # 默认模板 HTML腹腔镜胆囊切除术报告
│ ├── App.tsx # BrowserRouter + 路由表
│ ├── main.tsx # React 根挂载StrictMode
│ ├── types.ts # 核心 TypeScript 类型User/Report/Template/FormField 等)
│ └── index.css # Tailwind 入口 + @theme 变量 + 打印媒体查询
├── Dockerfile # 多阶段构建node builder → nginx alpine
├── docker-compose.yaml # 映射宿主机 4002 → 容器 80
├── nginx.conf # SPA 回退、Gzip、静态缓存
├── vite.config.ts # Vite + Tailwind 插件、GEMINI_API_KEY 注入
├── tsconfig.json # ES2022、react-jsx、路径别名 `@/*`
├── package.json
├── .env.example # GEMINI_API_KEY、APP_URL
├── index.html # 入口 HTML标题 "My Google AI Studio App"
└── 过往经验/ # 开发经验记录(经验记录-1.md / 经验记录-2.md
```
---
## 构建与部署命令
```bash
# 开发
npm run dev # vite --port=3000 --host=0.0.0.0
# 生产构建
npm run build # vite build → dist/
npm run preview # vite preview默认端口 4173
# 清理
npm run clean # rm -rf dist
# 类型检查(唯一 lint 手段,无 ESLint
npm run lint # tsc --noEmit
```
**Docker 部署**
```bash
docker-compose up -d --build
# 宿主机访问 http://localhost:4002
```
**无 Docker 环境部署**Windows 等):
```bash
npm run build
npm run preview
# 或任何静态文件服务器托管 dist/
```
---
## 数据持久化与状态管理
- **无全局状态库**(无 Redux、Zustand、Context API
- 每个页面独立通过 `useState` + `useEffect` 管理状态
- **`localStorage` 即数据库**。关键键名:
- `users` — 用户列表
- `currentUser` — 当前登录用户
- `reports` — 报告列表
- `templates` — 模板列表
- `systemSettings` — 系统设置
- `formFieldsConfig` — 动态字段配置
- `imageAssets` — 系统素材库Base64 图片)
- `reportEditorDraft_${username}` — 每用户报告草稿
- `customTimeFormats` — 用户自定义时间格式缓存
⚠️ **localStorage 容量约 5MB**。关键帧图片采用 Canvas 压缩(最大宽度 800px、JPEG 质量 0.6)以避免超限。`storage.ts` 中的异常已改为 `console.error` 输出,不再静默吞掉。
---
## 核心模块说明
### 1. 富文本编辑器ReportEditor.tsx / TemplateManage.tsx
- **底层**:原生 `contentEditable` + `document.execCommand`
- **智能字段Smart Field**:三层嵌套结构
```html
<span class="smart-field-wrapper" contenteditable="false">
<span class="field-label">标签:</span>
<span class="field-value" contenteditable="true" data-bind="key"></span>
</span>&#8203;
```
- 外层 `contenteditable="false"` 保护标签不被逐字删除
- 输入层 `data-bind` 实现与右侧表单的双向绑定
- 末尾追加 `&#8203;`(零宽空格)防止排版换行异常
- **图片占位符**`<span class="image-placeholder">`,支持 `data-mode="frame|manual"` 分类隔离
- **自定义 Undo/Redo**TemplateManage 中已实现基于 HTML 字符串快照的自定义历史栈(`undoStack`/`redoStack`),取代不可靠的浏览器原生 undo
### 2. 视频分析ReportEditor.tsx
- 上传本地视频 → 生成 object URL
- 自动抽帧:按 `systemSettings.framePositions` 百分比位置逐帧截图
- 手动截图:点击按钮从当前播放时间捕获
- 图片压缩Canvas 等比缩放至最大 800px 宽JPEG 质量 0.6
- 拖拽/一键插入:填充到第一个空置的 `image-placeholder:not([data-mode="manual"])`
- 自动帧插入:非阻塞 `setTimeout` 队列式插入,避免阻塞抽帧循环
### 3. 打印系统src/utils/print.ts
- 创建隐藏 iframe写入带 A4 打印样式的 HTML
- `@page { margin: 15mm 10mm; }` 为**每一页**纸张独立分配边距
- `body { padding: 0 }` — 不可用 body padding 代替 @page margin否则第二页及后续页边距失效
- 打印前临时设置 `document.title` 并注入 iframe `<title>`,确保 PDF 默认文件名正确
- 打印后恢复原始标题
### 4. 角色权限RBAC
| 角色 | 权限 |
|------|------|
| `super` | 全部页面、全部数据 |
| `admin` | 仅管理本科室用户;可管理模板;不能看系统设置中的 AI 配置 |
| `user` | 仅创建/查看/编辑自己的报告;可见被分配模板 |
- `Sidebar.tsx` 根据 `currentUser.role` 过滤导航项
- `UserManage.tsx` 中 admin 只能管理 `department` 与自己相同的用户
---
## 开发规范与约定
### 代码风格
- TypeScript 严格模式未开启;`skipLibCheck: true`
- 路径别名 `@/` 映射到项目根目录(`src/` 同级)
- **无 ESLint、无 Prettier、无格式化配置**
- 所有字符串插值、UI 文案、注释均以**中文**为主
### 关键开发教训(必读)
以下经验来自 `过往经验/` 中的 40+ 条记录,是修改本项目时**最容易踩的坑**
#### A. contentEditable 与 DOM 操作
1. **插入 HTML 必须为紧凑单行**:使用 `document.execCommand('insertHTML', ...)` 或 `Range.insertNode()` 时,多行模板字符串中的缩进/换行会被浏览器解析为额外文本节点,破坏排版和光标行为。
2. **删除字段用 `Range.selectNode + execCommand('delete')`**:直接 `target.remove()` 会绕过浏览器撤销栈,且可能在 WebKit 中误删父级 `<p>`。
3. **任何直接操作 DOM 修改编辑器内容后,必须紧跟**
```ts
contentRef.current = editorRef.current.innerHTML;
```
4. **在表格 `<td>` 内插入复杂 inline 元素时**:优先使用块级 `<div>` 作为外层容器,`execCommand('insertHTML')` 对 `<td>` 内的 inline-flex 嵌套会自动"拍平"结构。
5. **对齐操作弃用 `execCommand('justifyLeft'...)`**:改用直接设置 `block.style.textAlign = align`,避免浏览器对混合排版(文字 + 智能字段/占位符)的肢解。
#### B. 自动保存与 Ref/State 同步
1. **永远不要将 `useRef` 作为自动保存的唯一数据源**React 18 `StrictMode` 的"挂载 → 卸载 → 重挂载"会导致 ref 在首次卸载时仍保持初始空值,从而用空数据覆盖有效的 localStorage draft。
2. **自动保存函数应直接从最新的 React state 和 DOM 读取数据**,通过 `useCallback` + 完整 dependency 数组保证闭包新鲜;或从 `stateRef` / `contentRef` 读取稳定快照,但必须在**所有**数据恢复路径中同步 ref。
3. **`setState` 是异步的**`setCapturedFrames(next); saveDraftToStorage();` 的写法会导致闭包读到旧值。若需即时保存,应在 `setState` 回调中触发保存,或从 ref 读取。
4. **组件卸载时 DOM 可能已失效**`editorRef.current?.innerHTML` 在卸载阶段可能为空,应优先使用 `contentRef.current`(内存引用)。
5. **异步循环中不要 `await` 阻塞主流程**:自动帧插入使用 `setTimeout` 推入事件队列,而非 `await new Promise(...)`。
#### C. 图片占位符体系
1. 占位符涉及**三处必须同步修改**`defaultContent.ts`(静态模板)、`ReportEditor.tsx`(运行时插入/填充/删除恢复)、`TemplateManage.tsx`(模板管理)。
2. 占位符创建时需写入 `max-width` / `max-height`;填充后改为 `width:auto; height:auto`,让图片 shrink-wrap删除恢复时需回读 `maxWidth/maxHeight` 重置尺寸。
3. 提示文字使用 `position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); text-align:center;`,要求父容器带 `position:relative`。
4. 占位符分类隔离:通过 `data-mode="frame|manual"` 区分;自动插入和拖拽填充时必须用 `:not([data-mode="manual"])` 过滤。
#### D. 时间/日期字段格式系统
1. 存储格式与显示格式分离:`YYYY-MM-DD` / `HH:mm` 存储;`YYYY年MM月DD日` / `hh:mm A` 显示。
2. 必须同时实现**正向格式化**(存储 → 显示)和**反向解析**(显示 → 存储),否则编辑器内直接编辑 smart field 会导致数据混乱。
3. 12h/24h 判断使用包含性判断:`field.timeFormat.includes('hh') || field.timeFormat.includes('A')`,避免精确匹配无法覆盖自定义格式。
4. 默认值策略:「固定时间」/`specific` 与「当前时间」/`current`。自动填充必须加「仅当值为空时触发」保护,防止编辑已有报告时覆盖用户数据。
5. 时间格式 token 体系:`YYYY`、`MM`、`DD`、`HH`、`hh`、`mm`、`A`。避免使用简写别名如 `'24h'`、`'12h'` 作为存储值。
#### E. 事件与交互
1. **工具栏/字段库按钮必须加 `onMouseDown={(e) => e.preventDefault()}`**:防止点击时编辑器失焦导致 `Selection/Range` 丢失。
2. **插入操作前恢复 `savedRangeRef`**:作为焦点流失后的兜底保险。
3. **双向联动高亮**:通过 `activeFieldKey` 状态 + `useEffect` 直接操作 DOM style`backgroundColor`、`outline`),避免触发组件重渲染导致光标丢失。点击非字段区域时清空高亮。
4. 打印样式必须通过 `@media print` 强制抹除所有交互高亮内联样式(`outline: none !important; box-shadow: none !important;`)。
#### F. 数据初始化与默认值
1. `Login.tsx` 的 `initData()` 是全局唯一初始化入口默认用户、默认模板、默认字段配置、默认设置、素材预加载logo均应在此处完成。
2. 新增 `localStorage` key 时需提供合理的默认值或降级处理。
3. `resetToDefault` / 恢复出厂设置函数必须**包含所有** `SystemSettings` 字段,不能遗漏新增配置项。
4. 修改 `DEFAULT_FORM_FIELDS` 默认值后,已有用户的 `localStorage` 中旧配置不会自动更新;若变更影响核心功能,应考虑启动时做配置迁移或版本校验。
5. 批量操作后必须同步清理 `selectedIds` 和当前选中状态,避免选中已删除项。
---
## 测试策略
**当前状态:零自动化测试。**
- 无单元测试、无集成测试、无 E2E 测试
- 无 Jest、Vitest、Playwright、Cypress 配置
- 唯一类型检查:`npm run lint``tsc --noEmit`
**建议补充方向**(如用户要求):
- `storage.ts` 的 JSON 序列化/反序列化
- `types.ts` 中日期/时间格式化与解析函数的正反向一致性
- 报告编辑器的草稿保存/恢复逻辑
---
## 安全注意事项
1. **密码明文存储**:用户密码以明文形式保存在 `localStorage` 的 `users` 数组中。这是纯前端架构的固有限制,**不适合生产环境处理真实敏感数据**。
2. **无 HTTPS 强制**Docker 部署默认 HTTP 80 端口。
3. **无 API 鉴权**:无后端,因此无 Token、Session、CSRF 防护概念。
4. **XSS 风险**:报告和模板内容直接以 HTML 字符串存储并在 `innerHTML` 中渲染。当前通过 `contentEditable` 限制输入来源,但若导入外部 JSON 模板/报告,需警惕恶意脚本。
5. **Gemini API Key**:通过 Vite `define` 注入客户端,构建后 key 会暴露在静态 JS 中(当前源码未实际调用)。
---
## 部署环境变量
复制 `.env.example` 为 `.env`
```
GEMINI_API_KEY="YOUR_KEY" # Google Gemini API Key当前未在业务代码中使用
APP_URL="YOUR_APP_URL" # 应用托管 URL
```
Vite 构建时仅将 `GEMINI_API_KEY` 注入 `process.env.GEMINI_API_KEY`,其余变量不自动暴露给客户端。
---
## 默认账号(首次登录或清空数据后)
| 账号 | 密码 | 角色 |
|------|------|------|
| admin | 123456 | super |
| manager | 123456 | admin |
| doctor / 0001 | 123456 | user |
---
## 修改前必读检查清单
在修改任何涉及以下内容的功能前,请先搜索并同步检查所有相关文件:
- [ ] **智能字段结构** → `types.ts`、`defaultContent.ts`、`ReportEditor.tsx`、`TemplateManage.tsx`、`index.css`、`print.ts`
- [ ] **图片占位符(创建/填充/删除恢复)** → `defaultContent.ts`、`ReportEditor.tsx`、`TemplateManage.tsx`
- [ ] **打印样式** → `print.ts`、`index.css``@media print`
- [ ] **时间/日期格式** → `types.ts`、`ReportEditor.tsx`、`TemplateManage.tsx`
- [ ] **数据初始化/默认值** → `Login.tsx`、`SystemSettings.tsx`
- [ ] **自动保存/草稿** → `ReportEditor.tsx` 中的 `saveDraftToStorage`、`stateRef`、`contentRef`