Skip to content

Commit 41ed7fc

Browse files
committed
fix: 图片导入在裁剪前不再释放解码后的原图
拆分 use-question-import 时,「离开页面释放原图」的 effect 依赖了每次渲染都会 变化的 imageFlow 对象,选好照片后的重新渲染就会释放原图,确认裁剪时 drawImage 报 "The image source is detached",图片导入无法创建识别任务。 - 释放逻辑移回持有原图引用的 useImageImportFlow,只在卸载时执行 - 新增单测:选好照片后重新渲染不释放原图,离开页面时释放一次
1 parent 42c90e1 commit 41ed7fc

3 files changed

Lines changed: 22 additions & 5 deletions

File tree

‎src/__tests__/unit/use-question-import.test.tsx‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,24 @@ describe('题目导入流程', () => {
127127
expect(mocks.post).not.toHaveBeenCalledWith('/api/analyze', expect.anything(), expect.anything());
128128
});
129129

130+
it('选好照片后页面重新渲染不会释放解码后的原图,只在离开页面时释放', async () => {
131+
mocks.createImportTask.mockResolvedValueOnce(task());
132+
mocks.fetchImportTask.mockResolvedValue(task());
133+
await render();
134+
await act(async () => { await flow.selectImage(new File(['x'], 'photo.heic', { type: 'image/heic' })); });
135+
// 裁剪器打开等状态变化会触发多次重新渲染,此时原图必须保留,裁剪还要从原图绘制
136+
await render();
137+
expect(pipeline.dispose).not.toHaveBeenCalled();
138+
139+
await act(async () => { await flow.cropRegions(FULL, false); });
140+
expect(pipeline.renderRegion).toHaveBeenCalledWith(prepared, FULL[0]);
141+
expect(pipeline.dispose).not.toHaveBeenCalled();
142+
143+
await act(async () => root.unmount());
144+
expect(pipeline.dispose).toHaveBeenCalledTimes(1);
145+
root = createRoot(container);
146+
});
147+
130148
it('识别中时页面显示任务状态卡,可继续操作', async () => {
131149
mocks.createImportTask.mockResolvedValueOnce(task());
132150
mocks.fetchImportTask.mockResolvedValue(task());

‎src/lib/hooks/use-image-import-flow.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useRef, useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
44
import { useRouter } from "next/navigation";
55
import { getAnalysisErrorMessage } from "@/lib/question/analysis-error";
66
import { getErrorMessage } from "@/lib/error-message";
@@ -54,6 +54,9 @@ export function useImageImportFlow(deps: ImageFlowDeps) {
5454
const [completedStages, setCompletedStages] = useState<ReturnType<typeof buildStageList>>([]);
5555
const preparedRef = useRef<PreparedImage | null>(null);
5656

57+
// 离开页面时释放解码后的原图与预览图;只在卸载时执行,重新渲染不能释放(裁剪还要用原图)
58+
useEffect(() => () => preparedRef.current?.dispose(), []);
59+
5760
const targetCtx = { fixedSubjectId, confirmBeforeImport, importSubjectId };
5861

5962
const decodeErrorMessage = (error: ImageDecodeError) =>
@@ -210,6 +213,5 @@ export function useImageImportFlow(deps: ImageFlowDeps) {
210213
discardParts: () => setParts([]),
211214
closeBatch: () => setBatchImages(null),
212215
closeCropper: () => setIsCropperOpen(false),
213-
disposePrepared: () => preparedRef.current?.dispose(),
214216
};
215217
}

‎src/lib/hooks/use-question-import.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,6 @@ export function useQuestionImport({ logPrefix, analyzeSubjectId, importSubjectId
6868
clearSaveError: () => saver.setSaveError(null),
6969
});
7070

71-
// 离开页面时释放解码后的原图与预览图
72-
useEffect(() => () => imageFlow.disposePrepared(), [imageFlow]);
73-
7471
useEffect(() => {
7572
fetchConfirmBeforeImport().then(setConfirmBeforeImport);
7673
apiClient.get<AppConfig>("/api/settings")

0 commit comments

Comments
 (0)