`,确保 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 中误删父级 ``。
3. **任何直接操作 DOM 修改编辑器内容后,必须紧跟**:
```ts
contentRef.current = editorRef.current.innerHTML;
```
4. **在表格 `
` 内插入复杂 inline 元素时**:优先使用块级 ` ` 作为外层容器,`execCommand('insertHTML')` 对 ` | ` 内的 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`
|