fix: 5项交互修复 - 删除图片恢复虚线框、prompt文案统一、移除overflow、多选输入解耦、label加可多选
This commit is contained in:
@@ -60,6 +60,7 @@ export default function ReportEditor() {
|
||||
});
|
||||
const [anesthesiaOptions, setAnesthesiaOptions] = useState<string[]>(['全麻', '局麻', '腰麻', '硬膜外麻醉', '静脉麻醉', '吸入麻醉']);
|
||||
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
|
||||
const [multiInputText, setMultiInputText] = useState<Record<string, string>>({});
|
||||
const [touched, setTouched] = useState<Record<string, boolean>>({});
|
||||
const [formFields, setFormFields] = useState<FormField[]>([]);
|
||||
const [imagePickerOpen, setImagePickerOpen] = useState(false);
|
||||
@@ -307,6 +308,8 @@ export default function ReportEditor() {
|
||||
<span class="delete-btn" contenteditable="false">×</span>
|
||||
<span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">插入/点击放置图片</span>
|
||||
`;
|
||||
placeholder.style.border = '1px dashed #cbd5e1';
|
||||
placeholder.style.background = '#f8fafc';
|
||||
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
|
||||
saveDraftToStorage();
|
||||
} else {
|
||||
@@ -423,24 +426,23 @@ export default function ReportEditor() {
|
||||
|
||||
const insertImage = () => {
|
||||
editorRef.current?.focus();
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用英文逗号分隔(如: 100,50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
if (input === null) return;
|
||||
const parts = input.split(',').map(s => s.trim());
|
||||
const widthStr = parts[0] || '';
|
||||
const heightStr = parts[1] || '';
|
||||
|
||||
let width = parseInt(widthStr) || 0;
|
||||
let height = parseInt(heightStr) || 0;
|
||||
if (!widthStr && !heightStr) {
|
||||
width = 200;
|
||||
height = 200;
|
||||
} else if (widthStr && !heightStr) {
|
||||
height = 200;
|
||||
} else if (!widthStr && heightStr) {
|
||||
width = 200;
|
||||
let width = 200;
|
||||
let height = 200;
|
||||
while (true) {
|
||||
const input = prompt('请输入占位符的最大宽度和高度(px),用*号分隔(如: 100*50)。留空则默认宽高为 200*200。(提示: 正文一行文字高度约为 20 像素左右)', '');
|
||||
if (input === null) return;
|
||||
const trimmed = input.trim();
|
||||
if (trimmed === '') break;
|
||||
const parts = trimmed.split('*').map(s => s.trim());
|
||||
if (parts.length === 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
|
||||
width = parseInt(parts[0]) || 0;
|
||||
height = parseInt(parts[1]) || 0;
|
||||
break;
|
||||
}
|
||||
alert('格式错误,请确保使用 * 分隔两个数字,例如 100*50');
|
||||
}
|
||||
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;overflow:hidden;';
|
||||
let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;';
|
||||
if (width > 0) styleStr += `width:${width}px;`;
|
||||
if (height > 0) styleStr += `height:${height}px;`;
|
||||
|
||||
@@ -1301,16 +1303,13 @@ export default function ReportEditor() {
|
||||
return Array.from(new Set(text.split(/[,,;;、]/).map(s => s.trim()).filter(Boolean)));
|
||||
};
|
||||
|
||||
const handleMultiChange = (text: string) => {
|
||||
const handleMultiCommit = (text: string) => {
|
||||
const values = parseMultiInput(text);
|
||||
const next = { ...reportData, [field.key]: values };
|
||||
setReportData(next);
|
||||
stateRef.current = { ...stateRef.current, reportData: next };
|
||||
saveDraftToStorage();
|
||||
};
|
||||
|
||||
const handleMultiCommit = (text: string) => {
|
||||
const values = parseMultiInput(text);
|
||||
const currentOpts = field.options || multiSelectOptions[field.key] || [];
|
||||
const newOpts = values.filter(v => !currentOpts.includes(v));
|
||||
if (newOpts.length > 0) {
|
||||
@@ -1327,9 +1326,11 @@ export default function ReportEditor() {
|
||||
}
|
||||
};
|
||||
|
||||
const currentInputText = multiInputText[field.key] !== undefined ? multiInputText[field.key] : displayText;
|
||||
|
||||
return (
|
||||
<div key={field.key} className="space-y-1 select-dropdown-root relative">
|
||||
<label className="block text-xs font-bold text-text-main">{field.label}</label>
|
||||
<label className="block text-xs font-bold text-text-main">{field.label}(可多选)</label>
|
||||
<div
|
||||
className="w-full px-3 py-2 border border-border rounded-lg bg-white flex flex-wrap gap-1 items-center min-h-[42px] cursor-text"
|
||||
onClick={() => setOpenDropdown(field.key)}
|
||||
@@ -1338,14 +1339,28 @@ export default function ReportEditor() {
|
||||
type="text"
|
||||
className="outline-none text-sm w-full bg-transparent"
|
||||
placeholder="输入或选择,多个用逗号分隔"
|
||||
value={displayText}
|
||||
onChange={(e) => handleMultiChange(e.target.value)}
|
||||
value={currentInputText}
|
||||
onChange={(e) => {
|
||||
setMultiInputText(prev => ({ ...prev, [field.key]: e.target.value }));
|
||||
}}
|
||||
onFocus={() => setOpenDropdown(field.key)}
|
||||
onBlur={(e) => handleMultiCommit(e.target.value)}
|
||||
onBlur={(e) => {
|
||||
handleMultiCommit(e.target.value);
|
||||
setMultiInputText(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[field.key];
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleMultiCommit((e.target as HTMLInputElement).value);
|
||||
setMultiInputText(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[field.key];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -1357,9 +1372,15 @@ export default function ReportEditor() {
|
||||
key={opt}
|
||||
className="px-3 py-2 text-sm hover:bg-slate-50 cursor-pointer flex justify-between items-center"
|
||||
onClick={() => {
|
||||
const newText = currentValues.length > 0 ? `${displayText}, ${opt}` : opt;
|
||||
handleMultiChange(newText);
|
||||
const base = multiInputText[field.key] !== undefined ? multiInputText[field.key] : displayText;
|
||||
const newText = base.length > 0 ? `${base}, ${opt}` : opt;
|
||||
setMultiInputText(prev => ({ ...prev, [field.key]: newText }));
|
||||
handleMultiCommit(newText);
|
||||
setMultiInputText(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[field.key];
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>{opt}</span>
|
||||
|
||||
@@ -183,6 +183,8 @@ export default function TemplateManage() {
|
||||
<span class="delete-btn" contenteditable="false">×</span>
|
||||
<span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">插入/点击放置图片</span>
|
||||
`;
|
||||
placeholder.style.border = '1px dashed #cbd5e1';
|
||||
placeholder.style.background = '#f8fafc';
|
||||
} else {
|
||||
const range = document.createRange();
|
||||
range.selectNode(placeholder);
|
||||
|
||||
39
过往经验/经验记录.md
39
过往经验/经验记录.md
@@ -398,3 +398,42 @@ if ((settings.autoInsertDelay || 0) > 0) {
|
||||
- 存储层的异常捕获**绝不应静默吞掉**,至少要输出日志,必要时还应弹出用户提示。
|
||||
- 对于需要存储大量图片的医疗/图文报告系统,应将 `localStorage` 逐步迁移到 `IndexedDB`,从根本上解除 5MB 容量瓶颈。
|
||||
- 在开发测试阶段,应使用高分辨率视频和大批量关键帧进行压力测试,提前暴露存储容量问题。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 记录 14:5 项交互修复(虚线框恢复、prompt 文案、删除按钮、多选输入、label 提示)
|
||||
|
||||
**A. 具体问题**
|
||||
用户提出 5 个修复需求:
|
||||
1. 删除 `image-placeholder` 中的图片后,虚线框消失——`fillPlaceholderSrc` 中设置了 `border='none'`,但删除图片的代码没有恢复;
|
||||
2. `ReportEditor.tsx` 的 `insertImage` prompt 文案仍显示旧版 "用英文逗号分隔",未同步修改;
|
||||
3. 新生成的 `image-placeholder` 右上角红色 `×` 显示不完全——`ReportEditor.tsx` 的 `insertImage` 中 `overflow:hidden` 未移除;
|
||||
4. 多选框无法输入 `,`、`;`、`,`、`、` 等分隔符——`onChange` 实时调用 `parseMultiInput` + `filter(Boolean)`,末尾的分隔符被瞬间吃掉;
|
||||
5. 多选框 label 缺少 "(可多选)" 提示。
|
||||
|
||||
**B. 产生问题原因**
|
||||
1. 上批次修改时,`ReportEditor.tsx` 的 `insertImage` 替换未成功匹配(旧字符串与文件实际内容有微小差异),导致该函数保留了旧代码。删除图片逻辑同样缺少 border/background 恢复。
|
||||
2. `overflow:hidden` 仅在新版 `TemplateManage.tsx` 中被移除,`ReportEditor.tsx` 中仍保留。
|
||||
3. 多选框使用受控 `value={displayText}` + `onChange={handleMultiChange}`,每次输入都会触发 `split(/[,,;;、]/)` 和 `filter(Boolean)`。当用户输入一个逗号时,split 产生空字符串,filter 将其过滤,输入框值立即回退到无逗号状态。
|
||||
4. label 渲染时直接使用 `{field.label}`,未追加多选提示。
|
||||
|
||||
**C. 解决问题方案**
|
||||
1. **恢复虚线框**:在 `ReportEditor.tsx` 和 `TemplateManage.tsx` 的删除图片分支中,增加:
|
||||
```ts
|
||||
placeholder.style.border = '1px dashed #cbd5e1';
|
||||
placeholder.style.background = '#f8fafc';
|
||||
```
|
||||
2. **修正 prompt 文案**:将 `ReportEditor.tsx` 的 `insertImage` 重写为与 `TemplateManage.tsx` 一致的新版逻辑(`*` 分隔 + while 循环校验)。
|
||||
3. **移除 overflow:hidden**:从 `ReportEditor.tsx` 的 `styleStr` 中删除 `overflow:hidden;`。
|
||||
4. **多选输入解耦**:引入本地状态 `multiInputText: Record<string, string>`,
|
||||
- `onChange` 仅更新 `multiInputText`,不触发拆分;
|
||||
- `onBlur` 和 `Enter` 时才调用 `handleMultiCommit` 执行 `split` + `filter(Boolean)` + 保存新选项;
|
||||
- 输入框 `value` 优先读取 `multiInputText[field.key]`,无本地缓存时回退到 `displayText`。
|
||||
5. **label 追加提示**:`{field.label}(可多选)`。
|
||||
|
||||
**D. 后续如何避免问题**
|
||||
- 同类型函数在多个文件中存在时,务必逐个文件 grep 确认修改结果,不能假设一次替换就能覆盖所有实例。
|
||||
- 任何 "实时解析输入" 的逻辑都必须警惕 `filter(Boolean)` 对空字符串的过滤效应——如果允许用户输入分隔符,应使用独立状态缓存原始输入,仅在确认时(blur/enter)执行解析。
|
||||
- `StrReplaceFile` 的批量替换若返回 "Applied N edit(s) with M total replacement(s)" 且 M < N,应立即检查未匹配的文件,避免遗漏。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user