fix: undo stack, field insertion wrap, backspace/delete precision; feat: signature size limit & isSigned control
- 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)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = `<img src="${signatureData}" class="report-signature-img" alt="签名" draggable="false" />`;
|
||||
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 = '';
|
||||
}
|
||||
|
||||
@@ -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 = `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value" data-bind="${field.key}" contenteditable="true" style="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;"> </span><span class="delete-btn" contenteditable="false">×</span></span>`;
|
||||
const html = `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value" data-bind="${field.key}" contenteditable="true" style="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;"> </span><span class="delete-btn" contenteditable="false">×</span></span>​`;
|
||||
document.execCommand('insertHTML', false, html);
|
||||
editorRef.current?.focus();
|
||||
};
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const smartField = (key: string) => `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value" data-bind="${key}" contenteditable="true" style="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;"> </span><span class="delete-btn" contenteditable="false">×</span></span>`;
|
||||
const smartField = (key: string) => `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value" data-bind="${key}" contenteditable="true" style="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;"> </span><span class="delete-btn" contenteditable="false">×</span></span>​`;
|
||||
|
||||
export const defaultReportContent = `
|
||||
<!-- 医院Logo -->
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user