# AGENTS.md — 手术图文病历报告系统(AI手术图文报告系统 V2.0) > 本文档面向 AI 编程助手。如果你正在阅读本文件,说明你对本项目一无所知。以下内容全部基于项目实际代码与配置,请勿假设任何未在本文中显式说明的内容。 --- ## 1. 项目概述 本项目是一个**纯前端**的医疗图文报告管理应用,面向医院手术记录场景。核心能力包括: - 通过富文本编辑器撰写手术图文报告 - 上传手术视频并自动/手动截取关键帧插入报告 - 模板管理(创建、维护标准模板,新建报告时自动加载默认模板) - 基于角色的权限控制(超级管理员 / 管理员 / 普通用户) - AI 辅助撰写(支持 Kimi、DeepSeek、OpenAI 及自定义兼容接口) - 报告批量操作(删除、导出 PDF / JSON) - 讯飞语音输入(WebSocket 实时转写) **重要前提**:本项目没有任何后端服务。所有数据(用户、报告、模板、视频帧、系统配置)全部持久化在浏览器 `localStorage` 中。认证与授权逻辑完全在客户端执行。 默认测试账号: - `admin` / `123456` - `manager` / `123456` - `doctor` / `123456` --- ## 2. 技术栈 | 层级 | 技术 | |------|------| | 框架 | React 19 + TypeScript ~5.8.2 | | 构建工具 | Vite 6.2.0(`@vitejs/plugin-react`) | | 路由 | React Router DOM v7(`BrowserRouter`) | | 样式 | Tailwind CSS v4.1.14(通过 `@tailwindcss/vite` 插件引入,非 PostCSS) | | 图标 | `lucide-react` | | 动画 | `motion`(Framer Motion 继任者) | | AI SDK | `@google/genai`(依赖列表中,实际主要使用 OpenAI 兼容 API) | | 其他关键依赖 | `diff`(字符级差异比对)、`crypto-js`(讯飞 HMAC-SHA256 鉴权) | `package.json` 中虽然声明了 `express` 与 `@types/express`,但源码中未见服务端代码,推测为历史遗留或未使用的依赖。 --- ## 3. 项目结构 ``` src/ ├── App.tsx # 根组件,定义所有路由 ├── main.tsx # 应用入口(React 19 + StrictMode) ├── types.ts # 全局类型定义与默认值常量 ├── index.css # Tailwind v4 引入 + 自定义编辑器/打印/A4 样式 ├── components/ │ └── Sidebar.tsx # 全局侧边导航栏(带角色过滤、自动收起) ├── pages/ # 所有页面均为大而整的单文件组件 │ ├── Login.tsx # 登录 + 初始数据种子写入 │ ├── Dashboard.tsx # 统计看板、SVG 趋势图、快捷入口 │ ├── ReportEditor.tsx # 核心编辑器(~2900 行):富文本、视频、AI、Diff、表单 │ ├── ReportManage.tsx # 报告列表、检索、批量操作、历史版本恢复 │ ├── ReportView.tsx # 只读报告展示 + 打印触发 │ ├── TemplateManage.tsx # 模板编辑器(~1700 行)+ 字段库侧边栏 │ ├── UserManage.tsx # RBAC 用户 CRUD + 电子签名 │ └── SystemSettings.tsx # 抽帧配置、AI 服务商、讯飞语音参数 └── utils/ ├── storage.ts # localStorage/sessionStorage 封装(含 XOR 加密) ├── print.ts # iframe 静默渲染 + window.print() 实现 A4 打印 └── defaultContent.ts # 默认手术报告 HTML 模板(含 data-bind 占位) ``` **架构特点**: - 无外部状态管理库,全部使用 React 原生 `useState` / `useRef` / `useEffect` - 无组件拆分策略,页面级组件体积巨大(`ReportEditor.tsx`、`TemplateManage.tsx` 均超 1500 行) - 无懒加载,所有页面在 `App.tsx` 中直接静态导入 - 共享布局仅有 `Sidebar.tsx` 一处 --- ## 4. 构建与运行命令 ```bash # 安装依赖 npm install # 本地开发(端口 3000,监听 0.0.0.0) npm run dev # 类型检查(无单元测试) npm run lint # 等价于 tsc --noEmit # 生产构建 npm run build # 预览生产构建(端口 4173) npm run preview # 清理构建产物 npm run clean # rm -rf dist ``` **环境变量**(复制 `.env.example` 为 `.env.local`): - `GEMINI_API_KEY`:Gemini AI API 密钥(构建时通过 Vite `define` 注入为 `process.env.GEMINI_API_KEY`) - `APP_URL`:部署后的访问地址,用于自引用链接 **Windows 特别注意**:重新部署前若使用 `npm run preview`,需手动终止旧的 `vite preview` 进程,避免端口冲突。 --- ## 5. 开发规范与代码风格 以下规范全部来自 `过往经验/` 中的踩坑记录,**必须遵守**: ### 5.1 contentEditable 编辑器规范 - **插入 HTML 时,模板字符串必须为紧凑单行**,禁止多行缩进/换行,否则浏览器会解析出额外文本节点破坏排版。 - **删除特殊节点**优先使用 `Range.selectNode(target) + document.execCommand('delete')`,而非直接 `target.remove()`,以保留浏览器原生撤销栈。 - **所有工具栏/侧边栏按钮**必须加 `onMouseDown={(e) => e.preventDefault()}`,防止点击时编辑器失焦导致光标位置丢失。 - **自定义 Undo/Redo**:结构性变更(插入/删除智能字段、表格等)应基于 `innerHTML` 快照实现自定义历史栈(`undoStack` / `redoStack`),并在操作前执行 `pushHistory()`。 - **键盘事件拦截**:对 `contenteditable="false"` 的内联控件,若处于块级边界,必须拦截 Backspace/Delete(以及 Ctrl+Z/Ctrl+Y),防止浏览器误删父级 `
`。
- **防换行技巧**:在 `inline-block` / `inline-flex` 控件后追加 ``(零宽空格)作为行内锚点。
### 5.2 自动保存与草稿机制(高频踩坑区)
- **禁止单独依赖 `useRef` 作为自动保存的唯一数据源**。React 18 `StrictMode` 的模拟卸载会导致 ref 仍保持初始空值,从而用空数据覆盖有效的 `localStorage` 草稿。
- 在 `ReportEditor.tsx` 中新增任何 `useState` 时,必须三问:
1. 是否需要持久化到 draft?
2. 是否已加入 `stateRef` 初始化?
3. 是否已在 `saveDraftToStorage`、所有恢复分支、以及 state→ref 同步 effect 中补齐?
- `contentRef.current = editorRef.current.innerHTML` 必须紧跟在任何直接操作 DOM 修改编辑器内容的代码之后。
### 5.3 图片与视频处理
- **LocalStorage 容量预警**:视频抽帧必须使用 Canvas 等比压缩(最大宽度 800px,JPEG 质量 0.6),单张控制在 30KB~80KB,避免 5MB 上限静默失败。
- **存储层异常捕获绝不应静默吞掉**,至少输出 `console.error`。
- **占位符体系**:所有 `.image-placeholder` 必须携带 `data-mode="frame|manual"` 属性。修改占位符的创建/填充/删除逻辑时,必须同步检索所有入口:`fillPlaceholderSrc`、`fillPlaceholder`、`autoCaptureFrames`、拖拽填充、`TemplateManage.tsx`,以及 `defaultContent.ts` 中的预置模板。
- 填充图片后需将占位符外壳的固定 `width/height` 改为 `auto`,同时保留 `max-width/max-height` 作为硬限制。
### 5.4 智能字段与动态表单
- **DOM 三层结构**:`` → `` → ``。
- **双向绑定**:富文本→表单通过 `handleEditorInput` 中 `e.target.hasAttribute('data-bind')` 实时更新;表单→富文本通过 `useEffect` 监听 `reportData`,仅在 `el.innerText !== newValue` 时才重写 DOM,防止光标跳动。
- **时间/日期字段**:显示格式与存储格式必须分离,且同时实现正向格式化与反向解析;12h/24h 判断使用 `includes('hh') || includes('A')` 等包含性判断,兼容自定义格式。
### 5.5 AI 功能开发规范
- **模型兼容性**:调用 OpenAI 兼容 API 时,必须根据「是否有图片附件」动态决定 `content` 类型(`string` vs `array`),纯文本模型发送数组会 400。
- **System Prompt 设计**:
- 条件应只依赖用户意图开关(如 `aiModifyEnabled`),不应依赖具体 UI 状态。
- 向大模型发送局部修改请求时,必须提供全局上下文,但同时设置**严格的内容边界(Fencing)**,明确声明"全局参考仅供理解,严禁输出"其他模块内容。
- 对关键输出字段(如 `updatedHtml`)使用「绝对强制」「绝对不允许」等最强措辞,并在前端增加缺失校验兜底。
- **降级机制**:目标区域注入必须配备降级方案(如光标处 `execCommand('insertHTML')`);修改模式开启后应自动兜底锁定第一个可用 AI 区域。
- **DOM 结构修复**:在读取 `.ai-content` 数据前,必须将因用户回车而溢出为兄弟节点的 ` ` 重新移回容器内。
- **样式与撤销**:AI 内容注入必须使用 `Range.selectNodeContents + execCommand('insertHTML')` 以保留撤销栈;返回的 HTML 需在后处理阶段统一注入内联字体样式(`font-family: SimSun; font-size: 12pt;`);注入前需清洗(去掉 `
`、段落间空白)。
- **Diff 对比**:使用 `diff` 库做字符级差异比对,并在注入前去掉 diff 高亮标签。
### 5.6 数据迁移与类型安全
- 任何涉及 `localStorage` 数据结构变更的重构,必须在初始化入口提供**自动迁移逻辑**,使用 `(obj as any).oldField` 安全访问旧字段,避免类型污染和配置丢失。
- 从持久化存储读取的数组类型数据,渲染前务必做 `Array.isArray` 校验,防止历史脏数据导致整页崩溃。
- 当将格式简写迁移为标准 token 时,必须全局搜索所有硬编码默认值,并在初始化时建立无效值清理机制。
### 5.7 UI/UX 通用规范
- **禁用原生 `prompt`/`confirm`/`alert`**,统一使用项目风格的自定义 Modal 组件。
- **URL 拼接**:必须对基础路径做尾部斜杠移除 `.replace(/\/+$/, '')`。
- **打印边距**:使用 `@page { margin: ... }` 为每一页物理纸张独立分配边距,切勿依赖 `body { padding: ... }`。
- **滚动容器内展开**:点击展开/折叠的交互组件,应考虑增加 `scrollIntoView` 兜底,防止布局突变导致点击失效。
- **表格列变更**:保持 `` 与 `