2026-04-16-18-51-06 - 修复路由切换后视频分析图片丢失问题

This commit is contained in:
2026-04-16 18:58:35 +08:00
parent 11278d0bcd
commit 396a8cab0b
6 changed files with 452 additions and 16 deletions

View File

@@ -67,9 +67,7 @@ export default function ReportEditor() {
const videoInputRef = useRef<HTMLInputElement>(null);
const contentLoadedRef = useRef(false);
const contentRef = useRef('');
const stateRef = useRef({ reportData, videos, capturedFrames, activeTab });
stateRef.current = { reportData, videos, capturedFrames, activeTab };
const stateRef = useRef({ reportData, videos, capturedFrames, activeTab, loadedTemplateId });
const draftKey = currentUser ? `reportEditorDraft_${currentUser.username}` : '';
@@ -88,12 +86,11 @@ export default function ReportEditor() {
if (key) {
storage.set(key, {
content: contentRef.current,
loadedTemplateId,
draftReportId: reportId || null,
...stateRef.current
});
}
}, [reportId, loadedTemplateId]);
}, [reportId]);
useEffect(() => {
const user = storage.get<User | null>('currentUser', null);
@@ -128,6 +125,13 @@ export default function ReportEditor() {
contentRef.current = draft.content;
contentLoadedRef.current = true;
setLoadedTemplateId(draft.loadedTemplateId || '');
stateRef.current = {
...stateRef.current,
reportData: draft.reportData,
videos: draft.videos,
capturedFrames: draft.capturedFrames,
loadedTemplateId: draft.loadedTemplateId || ''
};
setTimeout(() => updatePageHeight(), 0);
}
} else {
@@ -146,6 +150,12 @@ export default function ReportEditor() {
contentRef.current = found.content;
}
contentLoadedRef.current = true;
stateRef.current = {
...stateRef.current,
reportData: found,
videos: found.videos || [],
capturedFrames: found.capturedFrames || []
};
setTimeout(() => updatePageHeight(), 0);
}
if (found.capturedFrames) {
@@ -176,6 +186,13 @@ export default function ReportEditor() {
contentRef.current = draft.content;
contentLoadedRef.current = true;
setLoadedTemplateId(draft.loadedTemplateId || '');
stateRef.current = {
...stateRef.current,
reportData: draft.reportData,
videos: draft.videos,
capturedFrames: draft.capturedFrames,
loadedTemplateId: draft.loadedTemplateId || ''
};
setTimeout(() => updatePageHeight(), 0);
}
}
@@ -185,6 +202,7 @@ export default function ReportEditor() {
const tpl = filteredTemplates.find(t => t.id === settings.defaultTemplate);
if (tpl) {
setLoadedTemplateId(tpl.id);
stateRef.current = { ...stateRef.current, loadedTemplateId: tpl.id };
editorRef.current.innerHTML = tpl.content;
contentRef.current = tpl.content;
} else {
@@ -398,6 +416,7 @@ export default function ReportEditor() {
}));
const combined = [...videos, ...newVideos];
setVideos(combined);
stateRef.current = { ...stateRef.current, videos: combined };
setCurrentVideoIndex(videos.length); // select first newly uploaded video
if (videoInputRef.current) videoInputRef.current.value = '';
saveDraftToStorage();
@@ -407,15 +426,18 @@ export default function ReportEditor() {
const idx = videos.findIndex(v => v.id === id);
const updated = videos.filter(v => v.id !== id);
setVideos(updated);
stateRef.current = { ...stateRef.current, videos: updated };
if (currentVideoIndex >= updated.length) {
setCurrentVideoIndex(updated.length > 0 ? 0 : -1);
} else if (currentVideoIndex === idx && updated.length > 0) {
setCurrentVideoIndex(0);
}
setCapturedFrames(prev => prev.filter(f => f.videoIndex !== idx).map(f => {
const nextFrames = capturedFrames.filter(f => f.videoIndex !== idx).map(f => {
if (f.videoIndex > idx) return { ...f, videoIndex: f.videoIndex - 1 };
return f;
}).sort((a, b) => a.time - b.time));
}).sort((a, b) => a.time - b.time);
setCapturedFrames(nextFrames);
stateRef.current = { ...stateRef.current, capturedFrames: nextFrames };
saveDraftToStorage();
};
@@ -449,7 +471,9 @@ export default function ReportEditor() {
dataUrl: canvas.toDataURL('image/jpeg', 0.9),
isManual: true
};
setCapturedFrames(prev => [...prev, newFrame].sort((a, b) => a.time - b.time));
const nextFrames = [...capturedFrames, newFrame].sort((a, b) => a.time - b.time);
setCapturedFrames(nextFrames);
stateRef.current = { ...stateRef.current, capturedFrames: nextFrames };
saveDraftToStorage();
};
@@ -468,8 +492,9 @@ export default function ReportEditor() {
const wasPlaying = !video.paused;
if (wasPlaying) video.pause();
const newFrames: CapturedFrame[] = [];
for (const pos of positions) {
let accumulatedFrames = [...capturedFrames];
for (let i = 0; i < positions.length; i++) {
const pos = positions[i];
const time = (pos / 100) * dur;
video.currentTime = time;
await new Promise<void>(resolve => {
@@ -482,7 +507,7 @@ export default function ReportEditor() {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
newFrames.push({
const newFrame: CapturedFrame = {
id: Date.now() + Math.random(),
videoIndex: currentVideoIndex,
videoName: videos[currentVideoIndex].name,
@@ -490,9 +515,27 @@ export default function ReportEditor() {
timeFormatted: formatTime(time),
dataUrl: canvas.toDataURL('image/jpeg', 0.9),
isManual: false
});
};
accumulatedFrames = [...accumulatedFrames, newFrame].sort((a, b) => a.time - b.time);
setCapturedFrames(accumulatedFrames);
stateRef.current = { ...stateRef.current, capturedFrames: accumulatedFrames };
if (settings.autoInsertFrames && settings.autoInsertFrameIndices?.includes(i) && editorRef.current) {
if ((settings.autoInsertDelay || 0) > 0) {
await new Promise<void>(r => setTimeout(r, (settings.autoInsertDelay || 0) * 1000));
}
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image)') as HTMLElement | null;
if (emptyPlaceholder) {
emptyPlaceholder.innerHTML = `
<span class="delete-btn" contenteditable="false">×</span>
<img src="${newFrame.dataUrl}" style="max-width: 100%; height: auto; display: block; margin: 0 auto;" draggable="false">
`;
emptyPlaceholder.classList.add('has-image');
}
}
}
if (settings.autoInsertFrames && editorRef.current) {
contentRef.current = editorRef.current.innerHTML;
}
setCapturedFrames(prev => [...prev, ...newFrames].sort((a, b) => a.time - b.time));
if (wasPlaying) video.play();
saveDraftToStorage();
};
@@ -596,8 +639,7 @@ export default function ReportEditor() {
if (tpl) {
editorRef.current.innerHTML = tpl.content;
contentRef.current = tpl.content;
setLoadedTemplateId(tpl.id);
setReportData({
const nextReportData = {
title: tpl.name || '腹腔镜胆囊切除术报告',
patientName: '',
hospitalId: '',
@@ -615,11 +657,21 @@ export default function ReportEditor() {
anesthesiologist: [],
anesthesiaType: '',
status: 'draft'
});
};
setLoadedTemplateId(tpl.id);
setReportData(nextReportData);
setVideos([]);
setCapturedFrames([]);
setCurrentVideoIndex(-1);
prevVideoCountRef.current = 0;
stateRef.current = {
...stateRef.current,
loadedTemplateId: tpl.id,
reportData: nextReportData,
videos: [],
capturedFrames: [],
activeTab: stateRef.current.activeTab
};
updatePageHeight();
saveDraftToStorage();
}
@@ -641,6 +693,13 @@ export default function ReportEditor() {
contentRef.current = draft.content;
contentLoadedRef.current = true;
setLoadedTemplateId(draft.loadedTemplateId || '');
stateRef.current = {
...stateRef.current,
reportData: draft.reportData,
videos: draft.videos,
capturedFrames: draft.capturedFrames,
loadedTemplateId: draft.loadedTemplateId || ''
};
setTimeout(() => updatePageHeight(), 0);
return;
}
@@ -655,6 +714,12 @@ export default function ReportEditor() {
editorRef.current.innerHTML = found.content;
}
contentLoadedRef.current = true;
stateRef.current = {
...stateRef.current,
reportData: found,
videos: found.videos || [],
capturedFrames: found.capturedFrames || []
};
setTimeout(() => updatePageHeight(), 0);
return;
}
@@ -664,6 +729,13 @@ export default function ReportEditor() {
contentRef.current = draft.content;
contentLoadedRef.current = true;
setLoadedTemplateId(draft.loadedTemplateId || '');
stateRef.current = {
...stateRef.current,
reportData: draft.reportData,
videos: draft.videos,
capturedFrames: draft.capturedFrames,
loadedTemplateId: draft.loadedTemplateId || ''
};
setTimeout(() => updatePageHeight(), 0);
return;
}
@@ -678,6 +750,7 @@ export default function ReportEditor() {
const tpl = filteredTemplates.find(t => t.id === settings.defaultTemplate);
if (tpl) {
setLoadedTemplateId(tpl.id);
stateRef.current = { ...stateRef.current, loadedTemplateId: tpl.id };
editorRef.current.innerHTML = tpl.content;
} else {
editorRef.current.innerHTML = defaultReportContent;