master
2358328281@qq.com 3 months ago
parent 98f77a9818
commit 26449fbea5

@ -601,8 +601,8 @@ const viewReport = async (filePath) => {
}
.history-list {
min-height: 80px;
max-height: 200px;
min-height: 80px;
max-height: 240px;
overflow-y: auto;
.history-items {

@ -227,6 +227,20 @@ const form = reactive({
const MAX_FILE_SIZE = 50 * 1024 * 1024;
const MAX_PASTE_IMAGE_SIZE = 5 * 1024 * 1024;
// 跟踪尚未完成的粘贴图片 FileReader,弹窗关闭/卸载时 abort,
// 避免 onload 在 editor 已销毁后调用 dangerouslyInsertHtml 导致 Vue 3.5 patch 异常
const pendingPasteReaders = new Set();
const cancelPendingPasteReaders = () => {
for (const r of pendingPasteReaders) {
try {
r.abort();
} catch (_) {
/* ignore */
}
}
pendingPasteReaders.clear();
};
const handleAttachmentChange = (file) => {
if (file?.size > MAX_FILE_SIZE) {
ElMessage.warning('文件不能超过 50MB');
@ -373,7 +387,9 @@ const handleEditorPasteImage = (editor, event) => {
return;
}
const reader = new FileReader();
pendingPasteReaders.add(reader);
reader.onload = (e) => {
pendingPasteReaders.delete(reader);
const dataUrl = e.target?.result;
if (!dataUrl) {
ElMessage.error('图片读取失败');
@ -382,17 +398,37 @@ const handleEditorPasteImage = (editor, event) => {
try {
if (savedSelection) editor.select(savedSelection);
} catch (_) {
try {
if (typeof editor.focus === 'function') editor.focus();
} catch (__) {
/* editor 已销毁 */
}
}
// editor 可能已被弹窗关闭流程销毁/卸载,insertHtml 需读套保护
try {
if (typeof editor.focus === 'function') editor.focus();
editor.dangerouslyInsertHtml(
`<img src="${dataUrl}" style="max-width:100%;height:auto;" />`,
);
if (typeof editor.updateView === 'function') editor.updateView();
// ⚠️ 千万别同步 form.description!dangerouslyInsertHtml 是只改 editor 内部、直接操作 Slate 节点的底层 API,
// 同步给 v-model 会触发 wangEditor 的自动 setHtml,叠加后让 Slate operations 历史
// 里残留旧路径 (如 [0, 2]),下次任何操作就报 "Cannot find a descendant path"。
// 正确做法:v-model 保持空,提交时从 editor.getHtml() 取最新内容。
} catch (err) {
console.warn(
'[OrderFormDialog] 粘贴图片插入失败(editor 可能已销毁):',
err?.message,
);
}
if (typeof editor.focus === 'function') editor.focus();
editor.dangerouslyInsertHtml(
`<img src="${dataUrl}" style="max-width:100%;height:auto;" />`,
);
if (typeof editor.updateView === 'function') editor.updateView();
};
reader.onerror = () => {
pendingPasteReaders.delete(reader);
ElMessage.error('图片处理失败');
};
reader.onabort = () => {
pendingPasteReaders.delete(reader);
};
reader.readAsDataURL(file);
});
return false;
@ -414,6 +450,19 @@ const handleEditorCreated = (editor) => {
};
const resetForm = () => {
// 显式清空 wangEditor 内部内容和 Slate selection 状态。
// 原因:paste 图片后 editor 内部有内容,v-show 下弹窗不卸载组件,
// 二次打开时不调用 setHtml 会导致 Slate 内部残留旧路径的 selection。
const editor = editorRef.value;
if (editor) {
try {
if (typeof editor.setHtml === 'function') {
editor.setHtml('');
}
} catch (_) {
/* editor 已销毁或不可用 */
}
}
Object.assign(form, {
hospitalId: undefined,
title: '',
@ -485,6 +534,8 @@ watch(
const closeDialog = () => emit('update:modelValue', false);
const onClosed = () => {
// 弹窗关闭时 abort 未完成的粘贴 FileReader,防止 onload 跨生命周期访问已销毁的 editor
cancelPendingPasteReaders();
resetForm();
emit('closed');
};
@ -493,6 +544,13 @@ const onSubmit = async () => {
// dept / submitter 去掉两端空格后再校验,避免仅含空格的"伪必填"绕过
form.department = (form.department || '').trim();
form.submitter = (form.submitter || '').trim();
// 提交前从 wangEditor 同步富文本内容到 form.description。
// dangerouslyInsertHtml 只改 editor 内部不触发 v-model,避免了上一轮踩的坑,
// 代价是 form.description 不会随粘贴自动更新,所以提交时从 getHtml() 拿最新内容。
const editor = editorRef.value;
if (editor && typeof editor.getHtml === 'function') {
form.description = editor.getHtml() || '';
}
await formRef.value.validate(async (valid) => {
if (!valid) return;
const formData = new FormData();
@ -521,6 +579,7 @@ const removePasteListener = () => {
};
onBeforeUnmount(() => {
cancelPendingPasteReaders();
removePasteListener();
pasteHandlerRef = null;
const editor = editorRef.value;

@ -91,7 +91,7 @@
<el-table class="order-table" :data="list" v-loading="loading" stripe
header-cell-class-name="order-table-header-cell">
<el-table-column :label="$t('table.id')" prop="orderNo" width="160" />
<el-table-column :label="$t('table.id')" prop="orderNo" width="90" show-overflow-tooltip />
<el-table-column :label="$t('table.hospital')" prop="hospitalName" min-width="160" show-overflow-tooltip />
<el-table-column :label="$t('table.priority')" width="90">
<template #default="{ row }">
@ -151,7 +151,7 @@
<!-- 快速提交工单 弹窗(复用公共 OrderFormDialog,mode='create') -->
<OrderFormDialog v-model="quickVisible" mode="create" :hospitals="hospitals" :submitting="quickSubmitting"
@submit="onQuickSubmit" />
@submit="onQuickSubmit" @closed="console.log('close')" />
<!-- 工单详情 弹窗 -->
<OrderDetailDialog v-model="detailVisible" :order-id="currentOrderId" @updated="loadList" />

@ -66,7 +66,7 @@
</div> -->
<el-table class="admin-table" :data="list" v-loading="loading" stripe
header-cell-class-name="admin-table-header-cell">
<el-table-column :label="$t('table.id')" prop="orderNo" width="160" />
<el-table-column :label="$t('table.id')" prop="orderNo" width="90" show-overflow-tooltip />
<el-table-column :label="$t('table.priority')" width="90">
<template #default="{ row }">
<div class="priority-row" :class="priorityClass(row.priority)">{{ row.priority }}</div>

Loading…
Cancel
Save