diff --git a/src/index.css b/src/index.css index bee5f3f..ea0e089 100644 --- a/src/index.css +++ b/src/index.css @@ -127,24 +127,35 @@ .smart-field-wrapper .field-value:empty::before { content: '\200b'; } + .smart-field-wrapper .field-value:focus { + background-color: #e2e8f0; + border-color: #94a3b8; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25); + } .smart-field-wrapper .delete-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - margin-right: 2px; + position: absolute; + top: -8px; + right: -8px; + width: 16px; + height: 16px; background: #ef4444; color: white; border-radius: 50%; font-size: 10px; - line-height: 1; + line-height: 16px; + text-align: center; cursor: pointer; user-select: none; + display: none; + z-index: 10; } .smart-field-wrapper .delete-btn:hover { background: #dc2626; } + .template-editor-mode .smart-field-wrapper:hover .delete-btn, + .template-editor-mode .smart-field-wrapper:focus-within .delete-btn { + display: block; + } } @media print { diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index e3b7c1d..5db9d6c 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -1224,6 +1224,8 @@ export default function ReportEditor() { if (field.type === 'multi_select') { const isOpen = openDropdown === field.key; const opts = field.options || multiSelectOptions[field.key] || []; + const rawValue = (reportData as any)[field.key]; + const tags = Array.isArray(rawValue) ? rawValue : (rawValue ? [String(rawValue)] : []); return (
测试内容
', + surgeon: '张医生', // 脏数据:应该是数组 + assistant: [], + anesthesiologist: ['周医生'] + }; + const reports = JSON.parse(localStorage.getItem('reports') || '[]'); + reports.push(dirtyReport); + localStorage.setItem('reports', JSON.stringify(reports)); + ``` +2. 刷新页面,进入【报告管理】,点击编辑该测试报告。 +3. 确认页面**没有白屏崩溃**,右侧【基本信息】中的"手术者"字段正确显示为单标签 `张医生`,且可以正常添加/删除标签。 +4. 删除该测试报告,避免污染数据。 + +## 三、预期结果 + +- `npm run lint` 0 错误。 +- TemplateManage 中字段聚焦时有明显高亮效果。 +- TemplateManage 中删除按钮仅悬浮/聚焦时显示在右上角,点击可删除字段。 +- ReportEditor 中完全看不到删除按钮。 +- ReportEditor 能兼容非数组类型的 multi_select 脏数据,不崩溃。 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 253fa31..3e34f3f 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -377,6 +377,38 @@ if ((settings.autoInsertDelay || 0) > 0) { --- +## 记录 17:字段聚焦高亮、删除按钮显隐隔离与 multi_select 脏数据崩溃修复 + +**A. 具体问题** +1. `TemplateManage` 中编辑智能字段时缺少视觉焦点反馈,用户体验不够直观。 +2. 红色 × 删除按钮始终显示在字段内部左侧,且在任何包含 `smart-field-wrapper` 的页面(包括 `ReportEditor`)都会显示。 +3. `ReportEditor` 加载某些历史报告时崩溃,报错 `(y[x.key] || []).map is not a function`。 + +**B. 产生问题原因** +1. 之前没有为 `.field-value` 定义 `:focus` 状态的 CSS 样式。 +2. `delete-btn` 使用 `display: inline-flex` 默认常驻显示,且没有针对页面做显隐隔离。 +3. `multi_select` 字段(如 `surgeon`、`assistant`)的渲染直接对值调用 `.map()`,但旧数据或异常存储可能将其保存为字符串(如 `"张医生"` 而非 `["张医生"]`),导致 `.map` 在字符串上调用时抛出 `TypeError`。 + +**C. 解决问题方案** +1. **聚焦高亮**:在 `index.css` 中为 `.smart-field-wrapper .field-value:focus` 增加背景色加深(`#e2e8f0`)、边框变深(`#94a3b8`)和蓝色外发光(`box-shadow: 0 0 0 2px rgba(59,130,246,0.25)`)的样式,配合 `transition` 实现平滑反馈。 +2. **删除按钮定位与显隐隔离**: + - 将 `delete-btn` 从字段内部移到 `.field-value` 之后,并给 `.smart-field-wrapper` 增加 `position:relative`,使 `delete-btn` 可绝对定位到右上角(`top: -8px; right: -8px`)。 + - 默认 `display: none`;在 `TemplateManage` 的编辑器容器上增加 `template-editor-mode` class,通过 `.template-editor-mode .smart-field-wrapper:hover .delete-btn` 和 `:focus-within .delete-btn` 控制仅在 TemplateManage 中悬浮/聚焦时显示。 + - `ReportEditor` 的编辑器容器没有 `template-editor-mode`,因此删除按钮不会显示。 +3. **类型安全修复**:在 `ReportEditor.tsx` 的 `multi_select` 渲染分支中,增加 `Array.isArray` 检查: + ```ts + const rawValue = (reportData as any)[field.key]; + const tags = Array.isArray(rawValue) ? rawValue : (rawValue ? [String(rawValue)] : []); + ``` + 确保无论旧数据是数组、字符串还是空值,都能安全渲染为标签列表。 + +**D. 后续如何避免问题** +- 任何需要在不同页面显隐不同的 UI 元素,应通过容器级 class 做样式隔离,而不是依赖全局显示/隐藏。 +- `contentEditable` 控件的焦点状态必须有明确的视觉反馈(背景/边框/阴影变化),否则用户难以感知当前编辑位置。 +- 对从持久化存储读取的数组类型数据,在 React 渲染前务必做 `Array.isArray` 校验,防止历史脏数据导致整页崩溃。 + +--- + ## 记录 14:智能字段插入间距修复与 Backspace 防误删 **A. 具体问题** diff --git a/工程分析/需求分析-2026-04-17-11-14-28.md b/工程分析/需求分析-2026-04-17-11-14-28.md new file mode 100644 index 0000000..a9576e7 --- /dev/null +++ b/工程分析/需求分析-2026-04-17-11-14-28.md @@ -0,0 +1,50 @@ +# 需求分析 — 字段聚焦高亮、删除按钮显隐控制与 .map Bug 修复(2026-04-17-11-14-28) + +## 一、需求来源 + +用户反馈 TemplateManage 中字段删除按钮位置和显示时机需要优化,同时要求字段获得焦点时有视觉高亮反馈,并修复 ReportEditor 中多选字段渲染崩溃的 Bug。 + +## 二、具体需求拆解 + +### 需求 1:field-value 聚焦高亮 + +**期望**:当光标落入 `.field-value`(`contenteditable="true"` 的输入框)时,背景色加深、边框色变化,并带一个短暂的过渡动画,让用户明确感知当前正在编辑哪个字段。 + +### 需求 2:删除按钮定位与显隐控制 + +**期望**: +- 红色 × 删除按钮从字段内部左侧移到**右上角**(绝对定位)。 +- 仅在鼠标**悬浮在 `.field-value` 上**或 `.field-value` 获得焦点时显示删除按钮。 +- **ReportEditor 中完全不显示**该删除按钮(编辑器中不应有删除字段的入口)。 + +### 需求 3:修复 ReportEditor `.map is not a function` 崩溃 + +**问题**: +``` +Uncaught TypeError: (y[x.key] || []).map is not a function +``` +发生在 `ReportEditor.tsx` 渲染 `multi_select` 类型字段时: +```tsx +{((reportData as any)[field.key] || []).map((tag: string) => ... +``` + +**原因**:历史脏数据或异常情况下,`surgeon`/`assistant`/`anesthesiologist` 等字段的值不是数组,而是字符串或其他类型,导致 `.map()` 调用崩溃。 + +**期望**:在渲染前对值做类型安全检查,非数组时安全转换为空数组或包裹为单元素数组,避免页面白屏。 + +## 三、影响范围分析 + +| 文件 | 改动说明 | +|------|----------| +| `src/pages/TemplateManage.tsx` | `insertSmartField` 的 HTML 结构调整(wrapper 增加 `position:relative`,delete-btn 改为绝对定位在右上角)。编辑器 div 增加专属 class `template-editor-mode` 用于样式隔离。 | +| `src/utils/defaultContent.ts` | `smartField()` 辅助函数同步调整 HTML 结构,与 `insertSmartField` 保持一致。 | +| `src/index.css` | 增加 `.field-value:focus` 高亮样式;重写 `.delete-btn` 样式为绝对定位;增加 `.template-editor-mode .smart-field-wrapper:hover .delete-btn` / `:focus-within .delete-btn` 显隐控制;确保 `.print-content .delete-btn` 打印隐藏。 | +| `src/pages/ReportEditor.tsx` | 修复 `multi_select` 渲染处的 `.map` 调用,增加 `Array.isArray` 安全转换。 | + +## 四、验收标准 + +- [ ] TemplateManage 中点击 field-value,背景色明显加深并有过渡动画。 +- [ ] TemplateManage 中鼠标悬浮/聚焦 field-value 时,右上角出现红色 ×;移开后隐藏。 +- [ ] ReportEditor 中所有 smart-field-wrapper 均不显示红色 ×。 +- [ ] ReportEditor 加载存在脏数据(非数组类型)的报告时,multi_select 字段不再崩溃,页面正常渲染。 +- [ ] `npm run lint` 无编译错误。