From f7c72700531c8494590b4534b6aa1d89cd78eb8f Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Fri, 17 Apr 2026 12:41:07 +0800 Subject: [PATCH] fix: undo stack, field insertion wrap, backspace/delete precision; feat: signature size limit & isSigned control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace direct DOM remove() with Range+execCommand('delete') in TemplateManage click and keydown handlers to restore undo stack - Append ​ zero-width space to smart-field-wrapper HTML in insertSmartField and defaultContent.ts to prevent unwanted line breaks - Refactor ReportEditor surgeonSignature rendering to depend on isSigned field - Add isSigned to DEFAULT_FORM_FIELDS (single_select: 已签字/未签字) - Change surgeonSignature to visibleInForm=true, isSystemLocked=false - Constrain signature image with max-width:120px, max-height:40px, object-fit:contain in CSS and print.ts - Add weak-blocking signature validation prompts in saveReport('completed') - Update experience record (#19) --- src/index.css | 12 +- src/pages/ReportEditor.tsx | 24 +++- src/pages/TemplateManage.tsx | 16 ++- src/types.ts | 3 +- src/utils/defaultContent.ts | 2 +- src/utils/print.ts | 2 +- 工程分析/实现方案-2026-04-17-12-34-56.md | 157 +++++++++++++++++++++++ 工程分析/测试方案-2026-04-17-12-34-56.md | 56 ++++++++ 工程分析/经验记录.md | 41 ++++++ 工程分析/需求分析-2026-04-17-12-34-56.md | 67 ++++++++++ 10 files changed, 367 insertions(+), 13 deletions(-) create mode 100644 工程分析/实现方案-2026-04-17-12-34-56.md create mode 100644 工程分析/测试方案-2026-04-17-12-34-56.md create mode 100644 工程分析/需求分析-2026-04-17-12-34-56.md diff --git a/src/index.css b/src/index.css index 3e87168..38ad15a 100644 --- a/src/index.css +++ b/src/index.css @@ -157,11 +157,13 @@ display: block; } .report-signature-img { - height: 2.4em; + max-width: 120px; + max-height: 40px; width: auto; + height: auto; + object-fit: contain; vertical-align: middle; display: inline-block; - margin: -0.3em 0; } } @@ -203,10 +205,12 @@ display: none !important; } .report-signature-img { - height: 2.4em !important; + max-width: 120px !important; + max-height: 40px !important; width: auto !important; + height: auto !important; + object-fit: contain !important; vertical-align: middle !important; display: inline-block !important; - margin: -0.3em 0 !important; } } diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index e988411..572a895 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -36,6 +36,7 @@ export default function ReportEditor() { assistant: [], anesthesiologist: [], anesthesiaType: '', + isSigned: '未签字', reportNote: '', status: 'draft' }); @@ -855,6 +856,21 @@ export default function ReportEditor() { return; } + if (status === 'completed') { + const hasSignatureField = editorRef.current?.querySelector('[data-bind="surgeonSignature"]'); + if (hasSignatureField) { + const isSigned = (reportData as any).isSigned === '已签字'; + const hasSignatureImage = !!currentUser?.signature; + if (!isSigned) { + const proceed = window.confirm('提示:模板中包含【手术者签名】字段,但您在基本信息中未选择"已签字"。是否继续完成报告?'); + if (!proceed) return; + } else if (!hasSignatureImage) { + const proceed = window.confirm('提示:您选择了"已签字",但您的账号尚未上传电子签名图片。报告中将不显示签名图片,是否继续完成?'); + if (!proceed) return; + } + } + } + const content = editorRef.current?.innerHTML || ''; const now = new Date().toISOString(); const finalReport: Report = { @@ -941,8 +957,9 @@ export default function ReportEditor() { const fieldKey = el.getAttribute('data-bind')!; if (fieldKey === 'surgeonSignature') { + const isSigned = (reportData as any).isSigned === '已签字'; const signatureData = currentUser?.signature; - if (signatureData) { + if (isSigned && signatureData) { const imgHtml = `签名`; if (el.innerHTML !== imgHtml) { el.innerHTML = imgHtml; @@ -950,8 +967,9 @@ export default function ReportEditor() { el.style.backgroundColor = 'transparent'; } } else { - if (el.innerText !== '【请上传电子签】') { - el.innerText = '【请上传电子签】'; + const placeholder = isSigned ? '【请上传电子签】' : '【未签字】'; + if (el.innerText !== placeholder) { + el.innerText = placeholder; el.style.border = ''; el.style.backgroundColor = ''; } diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx index 9a3277e..29abc35 100644 --- a/src/pages/TemplateManage.tsx +++ b/src/pages/TemplateManage.tsx @@ -125,7 +125,12 @@ export default function TemplateManage() { if (smartField && targetEl.closest('.delete-btn')) { e.stopPropagation(); e.preventDefault(); - smartField.remove(); + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNode(smartField); + sel?.removeAllRanges(); + sel?.addRange(range); + document.execCommand('delete'); saveTemplateContent(); return; } @@ -216,7 +221,12 @@ export default function TemplateManage() { if (target) { e.preventDefault(); - target.remove(); + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNode(target); + sel?.removeAllRanges(); + sel?.addRange(range); + document.execCommand('delete'); saveTemplateContent(); } }; @@ -249,7 +259,7 @@ export default function TemplateManage() { alert(`字段 "${field.label}" 已存在,请勿重复插入。`); return; } - const html = ` ×`; + const html = ` ×​`; document.execCommand('insertHTML', false, html); editorRef.current?.focus(); }; diff --git a/src/types.ts b/src/types.ts index 37759c1..c2531c3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -130,5 +130,6 @@ export const DEFAULT_FORM_FIELDS: FormField[] = [ { key: 'assistant', label: '助手', category: '多选', type: 'multi_select', visibleInForm: true, isSystemLocked: false, options: ['赵医生', '钱医生', '孙医生'] }, { key: 'anesthesiologist', label: '麻醉师', category: '多选', type: 'multi_select', visibleInForm: true, isSystemLocked: false, options: ['周医生', '吴医生', '郑医生'] }, { key: 'anesthesiaType', label: '麻醉方式', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['全麻', '局麻', '腰麻', '硬膜外麻醉', '静脉麻醉', '吸入麻醉'] }, - { key: 'surgeonSignature', label: '手术者签名', category: '图片', type: 'signature', visibleInForm: false, isSystemLocked: true }, + { key: 'isSigned', label: '手术者签名确认', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['已签字', '未签字'] }, + { key: 'surgeonSignature', label: '手术者签名', category: '图片', type: 'signature', visibleInForm: true, isSystemLocked: false }, ]; diff --git a/src/utils/defaultContent.ts b/src/utils/defaultContent.ts index fa55202..0bc9c18 100644 --- a/src/utils/defaultContent.ts +++ b/src/utils/defaultContent.ts @@ -1,4 +1,4 @@ -const smartField = (key: string) => ` ×`; +const smartField = (key: string) => ` ×​`; export const defaultReportContent = ` diff --git a/src/utils/print.ts b/src/utils/print.ts index f4e8e3c..cae0db5 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -37,7 +37,7 @@ export const printDocument = (htmlContent: string) => { .smart-field-wrapper { display: inline-flex; align-items: center; margin: 0 2px; vertical-align: text-bottom; } .smart-field-wrapper .field-label { color: #64748b; user-select: none; } .smart-field-wrapper .field-value { min-width: 32px; padding: 0 4px; margin: 0 2px; border: 1px solid #cbd5e1; border-radius: 2px; display: inline-block; background: #f8fafc; color: #0f172a; line-height: 1.2; font-size: inherit; vertical-align: text-bottom; box-sizing: border-box; min-height: 1.2em; outline: none; } - .report-signature-img { height: 2.4em; width: auto; vertical-align: middle; display: inline-block; margin: -0.3em 0; } + .report-signature-img { max-width: 120px; max-height: 40px; width: auto; height: auto; object-fit: contain; vertical-align: middle; display: inline-block; } @media print { .smart-field-wrapper .field-value { border: none !important; border-bottom: 1px solid #000 !important; border-radius: 0 !important; background: transparent !important; padding: 0 2px !important; } } diff --git a/工程分析/实现方案-2026-04-17-12-34-56.md b/工程分析/实现方案-2026-04-17-12-34-56.md new file mode 100644 index 0000000..485ec68 --- /dev/null +++ b/工程分析/实现方案-2026-04-17-12-34-56.md @@ -0,0 +1,157 @@ +# 实现方案 — 撤销栈修复、字段删除交互优化与签名字段闭环(2026-04-17-12-34-56) + +## 一、修改文件清单 + +1. `src/pages/TemplateManage.tsx` — 删除逻辑改用 `execCommand('delete')`;插入 HTML 增加零宽空格防换行 +2. `src/types.ts` — 修改 `surgeonSignature` 显隐属性;新增 `isSigned` 字段 +3. `src/pages/ReportEditor.tsx` — 初始值增加 `isSigned`;签名同步逻辑重构;完成报告签名校验 +4. `src/index.css` — 签名图片尺寸约束 +5. `src/utils/print.ts` — 打印样式同步签名尺寸约束 + +## 二、详细改动 + +### 2.1 `src/pages/TemplateManage.tsx` + +#### A. 点击红 X 删除改用 `execCommand('delete')` +```ts +if (smartField && targetEl.closest('.delete-btn')) { + e.stopPropagation(); + e.preventDefault(); + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNode(smartField); + sel?.removeAllRanges(); + sel?.addRange(range); + document.execCommand('delete'); + saveTemplateContent(); + return; +} +``` + +#### B. 键盘 Backspace/Delete 改用 `execCommand('delete')` +在 `handleKeyDown` 中,当定位到 `smart-field-wrapper` 目标后: +```ts +if (target) { + e.preventDefault(); + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNode(target); + sel?.removeAllRanges(); + sel?.addRange(range); + document.execCommand('delete'); + saveTemplateContent(); +} +``` + +#### C. 插入 HTML 防换行 +在 `insertSmartField` 的 HTML 字符串末尾增加 `​`(零宽空格),作为行内锚点,防止浏览器将字段挤到新行: +```html +...​ +``` + +### 2.2 `src/types.ts` + +- 将 `surgeonSignature` 改为: + ```ts + { key: 'surgeonSignature', label: '手术者签名', category: '图片', type: 'signature', visibleInForm: true, isSystemLocked: false } + ``` +- 在 `DEFAULT_FORM_FIELDS` 末尾追加(放在 `surgeonSignature` 之前或之后均可): + ```ts + { key: 'isSigned', label: '手术者签名确认', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['已签字', '未签字'] }, + ``` + +### 2.3 `src/pages/ReportEditor.tsx` + +#### A. 初始 `reportData` 增加 `isSigned` +```ts +const [reportData, setReportData] = useState>({ + // ... 其他字段 + isSigned: '未签字', + // ... +}); +``` + +#### B. 签名同步逻辑重构 +将 `surgeonSignature` 的特殊处理从 `useEffect` 移到更前面的位置,逻辑改为: +```ts +if (fieldKey === 'surgeonSignature') { + const isSigned = (reportData as any).isSigned === '已签字'; + const signatureData = currentUser?.signature; + if (isSigned && signatureData) { + const imgHtml = `签名`; + if (el.innerHTML !== imgHtml) { + el.innerHTML = imgHtml; + el.style.border = 'none'; + el.style.backgroundColor = 'transparent'; + } + } else { + const placeholder = isSigned ? '【请上传电子签】' : '【未签字】'; + if (el.innerText !== placeholder) { + el.innerText = placeholder; + el.style.border = ''; + el.style.backgroundColor = ''; + } + } + return; +} +``` + +#### C. 完成报告签名校验 +在 `saveReport` 的 `status === 'completed'` 分支中,在现有患者信息校验之后追加: +```ts +const hasSignatureField = editorRef.current?.querySelector('[data-bind="surgeonSignature"]'); +if (hasSignatureField) { + const isSigned = reportData.isSigned === '已签字'; + const hasSignatureImage = !!currentUser?.signature; + if (!isSigned) { + const proceed = window.confirm('提示:模板中包含【手术者签名】字段,但您在基本信息中未选择"已签字"。是否继续完成报告?'); + if (!proceed) return; + } else if (!hasSignatureImage) { + const proceed = window.confirm('提示:您选择了"已签字",但您的账号尚未上传电子签名图片。报告中将不显示签名图片,是否继续完成?'); + if (!proceed) return; + } +} +``` + +### 2.4 `src/index.css` + +修改 `.report-signature-img`: +```css +.report-signature-img { + max-width: 120px; + max-height: 40px; + width: auto; + height: auto; + object-fit: contain; + vertical-align: middle; + display: inline-block; +} +``` + +在 `@media print` 中同步: +```css +@media print { + .report-signature-img { + max-width: 120px !important; + max-height: 40px !important; + width: auto !important; + height: auto !important; + object-fit: contain !important; + vertical-align: middle !important; + display: inline-block !important; + } +} +``` + +### 2.5 `src/utils/print.ts` + +在 iframe 的 `