From 087636155772a710fac223b68667b0cb360415f3 Mon Sep 17 00:00:00 2001 From: kdletters Date: Thu, 25 Jun 2026 23:44:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=94=BB=E5=B8=83=E6=97=A0?= =?UTF-8?q?=E6=95=88=E9=A1=B9=E7=9B=AE=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 显式项目读取失败时返回项目页 403 和 404 项目访问错误不再停留在画布 补充无效项目 ID 回项目页测试 --- .../useImageCanvasProjectPersistence.test.tsx | 31 +++++++++++++++++++ .../useImageCanvasProjectPersistence.ts | 27 +++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx b/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx index 7412477ae..60d6f9b0c 100644 --- a/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx +++ b/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx @@ -4,6 +4,7 @@ import { act, render, screen, waitFor } from '@testing-library/react'; import { useCallback, useMemo, useRef, useState } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { ApiClientError } from '../../services/apiClient'; import type { EditorProjectSnapshot } from '../../services/image-editor/editorProjectClient'; import type { CanvasGenerationDialogState, @@ -1243,4 +1244,34 @@ describe('useImageCanvasProjectPersistence', () => { expect(loadEditorProjectMock).not.toHaveBeenCalled(); expect(screen.getByTestId('project-id').textContent).toBe('-'); }); + + it.each([ + [404, '项目不存在'], + [403, '无权访问项目'], + ])( + 'returns to the project page when an explicit project fails with %i', + async (status, message) => { + window.history.replaceState( + null, + '', + '/editor/canvas?projectid=missing-project', + ); + loadEditorProjectMock.mockRejectedValueOnce( + new ApiClientError({ + message, + status, + code: status === 404 ? 'not_found' : 'forbidden', + }), + ); + + render(); + + await waitFor(() => { + expect(window.location.pathname).toBe('/project'); + }); + expect(window.location.search).toBe(''); + expect(loadEditorProjectMock).toHaveBeenCalledWith('missing-project'); + expect(loadOrCreateRecentEditorProjectMock).not.toHaveBeenCalled(); + }, + ); }); diff --git a/src/components/image-editor/useImageCanvasProjectPersistence.ts b/src/components/image-editor/useImageCanvasProjectPersistence.ts index c608c7752..a85bdbe4e 100644 --- a/src/components/image-editor/useImageCanvasProjectPersistence.ts +++ b/src/components/image-editor/useImageCanvasProjectPersistence.ts @@ -7,6 +7,7 @@ import { } from 'react'; import { ApiClientError } from '../../services/apiClient'; +import { replaceAppHistoryPath } from '../../routing/appPageRoutes'; import { uploadEditorMediaAssetFile } from '../../services/image-editor/editorMediaAssetUploadClient'; import { putEditorProjectCoverCache } from '../../services/image-editor/editorProjectCoverCache'; import { @@ -80,7 +81,14 @@ type ImageCanvasProjectPersistenceSetters = { function isEditorAuthError(error: unknown) { return ( error instanceof ApiClientError && - (error.status === 401 || error.status === 403) + error.status === 401 + ); +} + +function isEditorProjectAccessError(error: unknown) { + return ( + error instanceof ApiClientError && + (error.status === 403 || error.status === 404) ); } @@ -166,6 +174,18 @@ function readEditorProjectSessionCache(projectId: string | null) { } } +function removeEditorProjectSessionCache(projectId: string) { + const storage = getEditorProjectSessionStorage(); + if (!storage) { + return; + } + try { + storage.removeItem(editorProjectSessionCacheKey(projectId)); + } catch { + // ignore storage cleanup errors + } +} + function writeEditorProjectSessionCache(project: EditorProjectSnapshot) { if (projectSnapshotContainsInlineMedia(project)) { return; @@ -673,6 +693,11 @@ export function useImageCanvasProjectPersistence({ openEditorLoginModal(() => { window.location.reload(); }); + return; + } + if (projectIdFromQuery && isEditorProjectAccessError(error)) { + removeEditorProjectSessionCache(projectIdFromQuery); + replaceAppHistoryPath('/project'); } });