# 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 标签: ​ ``` - 外层 `contenteditable="false"` 保护标签不被逐字删除 - 输入层 `data-bind` 实现与右侧表单的双向绑定 - 末尾追加 `​`(零宽空格)防止排版换行异常 - **图片占位符**:``,支持 `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 ``,确保 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`