diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index f1c7c9e..85573d4 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -67,9 +67,9 @@ export default function ReportEditor() { const videoInputRef = useRef(null); const contentLoadedRef = useRef(false); const contentRef = useRef(''); - const stateRef = useRef({ reportData, videos, capturedFrames, activeTab }); + const stateRef = useRef({ reportData, videos, capturedFrames, activeTab, loadedTemplateId }); - stateRef.current = { reportData, videos, capturedFrames, activeTab }; + stateRef.current = { reportData, videos, capturedFrames, activeTab, loadedTemplateId }; const draftKey = currentUser ? `reportEditorDraft_${currentUser.username}` : ''; @@ -83,12 +83,12 @@ export default function ReportEditor() { }; const saveDraftToStorage = React.useCallback(() => { + stateRef.current = { reportData, videos, capturedFrames, activeTab, loadedTemplateId }; const user = storage.get('currentUser', null); const key = user ? `reportEditorDraft_${user.username}` : ''; if (key) { storage.set(key, { content: contentRef.current, - loadedTemplateId, draftReportId: reportId || null, ...stateRef.current }); @@ -398,6 +398,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 +408,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 +453,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(); }; @@ -482,7 +488,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 +496,25 @@ export default function ReportEditor() { timeFormatted: formatTime(time), dataUrl: canvas.toDataURL('image/jpeg', 0.9), isManual: false - }); + }; + newFrames.push(newFrame); + if (settings.autoInsertFrames && editorRef.current) { + const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image)') as HTMLElement | null; + if (emptyPlaceholder) { + emptyPlaceholder.innerHTML = ` + × + + `; + emptyPlaceholder.classList.add('has-image'); + } + } } - setCapturedFrames(prev => [...prev, ...newFrames].sort((a, b) => a.time - b.time)); + if (settings.autoInsertFrames && editorRef.current) { + contentRef.current = editorRef.current.innerHTML; + } + const nextFrames = [...capturedFrames, ...newFrames].sort((a, b) => a.time - b.time); + setCapturedFrames(nextFrames); + stateRef.current = { ...stateRef.current, capturedFrames: nextFrames }; if (wasPlaying) video.play(); saveDraftToStorage(); }; diff --git a/src/pages/SystemSettings.tsx b/src/pages/SystemSettings.tsx index 43530bc..f084330 100644 --- a/src/pages/SystemSettings.tsx +++ b/src/pages/SystemSettings.tsx @@ -14,7 +14,8 @@ export default function SystemSettings() { apiEndpoint: '', apiKey: '', defaultTemplate: '', - frameMode: 'uniform' + frameMode: 'uniform', + autoInsertFrames: false }); const [templates, setTemplates] = useState([]); const [isSaved, setIsSaved] = useState(false); @@ -172,6 +173,18 @@ export default function SystemSettings() { +
+ setSettings({ ...settings, autoInsertFrames: e.target.checked })} + className="w-4 h-4 accent-accent cursor-pointer" + /> + +
+

开启后,视频自动抽帧的每一帧将按顺序自动插入到报告的空置图片占位符中,插满后不再提示。

+
@@ -191,6 +204,11 @@ export default function SystemSettings() { className="input-minimal w-full pr-6 text-center" /> % + {settings.autoInsertFrames && ( + + + + )}