diff --git a/TRACKING.md b/TRACKING.md index 39177667..ea81ffd8 100644 --- a/TRACKING.md +++ b/TRACKING.md @@ -130,3 +130,4 @@ - 2026-06-17 前端拆分第十三阶段:新增 `useImageCanvasAssetExportWorkflow`,把画布素材导出状态、单图右键导出、整包 ZIP 组包、图片去重、读取失败记录、metadata / manifest 和下载链接副作用从主视图抽出;主视图保留右键目标解析和状态提示渲染。验证命令:`npm run test -- src/components/image-editor/useImageCanvasAssetExportWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10003/editor/canvas` 清空会话后未登录刷新弹出 `账号入口`,登录临时开发账号后下载按钮启用,点击后触发真实下载 `未命名画布-画布素材-20260617.zip` 并显示导出状态;背景设置点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`,点击 `生成工具` 后 `生成图片` 对话框出现且 `AI画布工具栏` 保持可见,登录后控制台无前端 error。 - 2026-06-17 前端拆分第十四阶段:新增 `useImageCanvasLayerCommands`,把画布剪贴板、右键目标解析、复制 / 剪切 / 粘贴、创建副本、层级移动、分组 / 解组、显隐、锁定、翻转、删除选中图层、按 id 删除和单图导出委托从主视图抽出;主视图保留菜单定位、画布事件、生成、上传、项目持久化和实际导出下载。验证命令:`npm run test -- src/components/image-editor/useImageCanvasLayerCommands.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10003/editor/canvas` 清空会话后未登录刷新弹出 `账号入口`,关闭后 `画布背景色` 打开完整 `画布背景设置`,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`,点击 `生成工具` 后 `Image Generator` 占位框、`生成图片` 对话框和 `AI画布工具栏` 均可见;登录临时开发账号后新标签素材、画布图层、返回项目入口、小地图和底部工具栏可见,控制台无前端 error。 - 2026-06-17 前端拆分第十五阶段:新增 `useImageCanvasGenerationWorkflow`,把生成入口、规范 / 角色 / 图标 / 修改 / 快速编辑 / 角色动画状态机、真实生成提交、结果落图、失败恢复和删除图层后的生成态清理从主视图抽出;主视图保留画布事件、浮层定位、上传、项目资源持久化和历史捕获。验证命令:`npm run test -- src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10003/editor/canvas` 清空会话后未登录刷新弹出 `账号入口`,关闭登录后 `画布背景色` 打开完整 `画布背景设置` 面板,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;点击 `生成工具` 后 `Image Generator` 占位框、`生成图片` 对话框和 `AI画布工具栏` 均可见;登录临时开发账号后上传素材成功,素材数增加,点击素材可加入画布,切换 `图层` 面板可看到对应图层,登录后控制台无前端 error。 +- 2026-06-17 上传鉴权回归修正:普通素材上传入口在未登录时先打开 `账号入口`,不再先弹系统文件选择器;登录后用户再次点击上传即可打开文件选择器,避免浏览器拦截登录后异步触发的系统选择器。拖拽 / 已选中文件的续传逻辑仍保留,角色 / 图标生成参考图仍作为本地引用上传,不强制登录。验证命令:`npm run test -- src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`;浏览器回归:`http://127.0.0.1:10003/editor/canvas` 未登录刷新弹出 `账号入口`,`画布背景色` 打开完整 `画布背景设置`,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;未登录点击侧栏上传直接弹登录,不出现文件选择器;登录后再次点击上传可以打开文件选择器并上传成功,素材计数从 4 增至 5,`AI画布工具栏` 保持可见。 diff --git a/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md b/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md index 7bace166..0155262b 100644 --- a/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md +++ b/docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md @@ -109,6 +109,7 @@ - 承载图片画布上传工作流:隐藏文件 input、上传目标分发、未登录拦截和登录后续传、上传占位卡片、文件读取、素材落库、拖到画布建层、选中新图层、打开图层侧栏,以及角色 / 图标生成参考图上传。 - 主视图继续负责画布 drop 外层事件判断、素材库已有素材加入画布、项目资源持久化 hook 注入和画布历史捕获,避免上传 hook 反向成为画布全局状态真相。 - 该 hook 用独立单测覆盖登录续传、上传占位 / 成功回写、上传到画布建层、鉴权失败和生成参考图分发,主视图保留 DOM 级 smoke 覆盖侧栏上传、画布 drop 上传和文件夹定向上传。 + - 普通素材上传入口必须在打开系统文件选择器前检查登录态;未登录时先弹 `账号入口`,登录后由用户再次点击上传入口打开选择器,避免浏览器拦截异步触发的系统文件选择器。角色 / 图标生成参考图只作为本地引用进入生成表单,不强制登录。 ## 第十二阶段模块 diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx index 968b509f..ed1f7578 100644 --- a/src/components/image-editor/ImageCanvasEditorView.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.tsx @@ -1648,7 +1648,6 @@ export function ImageCanvasEditorView() { ; - uploadInputRef: RefObject; assetPointerDragRef: { current: AssetPointerDragState | null }; suppressAssetClickRef: { current: boolean }; assets: EditorAsset[]; @@ -86,7 +85,6 @@ type ImageCanvasSidebarViewProps = { SetStateAction<{ assetId: string; value: string } | null> >; setActiveUploadFolderId: Dispatch>; - setUploadTarget: Dispatch>; setUploadDropTarget: Dispatch>; setAssetPointerDrag: Dispatch>; setSelectedAssetIds: Dispatch>>; @@ -103,6 +101,7 @@ type ImageCanvasSidebarViewProps = { ) => void; updateAssetMoveDropFolder: (folderId: string | null) => void; addUploadedFiles: (files: FileList | File[], options?: UploadFilesOptions) => void; + requestUpload: (target: UploadTarget) => void; moveAssetToFolder: (assetId: string, folderId: string) => void; commitNewAssetFolder: () => void | Promise; toggleAssetFolder: (folderId: string) => void; @@ -133,7 +132,6 @@ type ImageCanvasSidebarViewProps = { export function ImageCanvasSidebarView({ activeSidebarPanel, assetListRef, - uploadInputRef, assetPointerDragRef, suppressAssetClickRef, assets, @@ -158,7 +156,6 @@ export function ImageCanvasSidebarView({ setRenamingFolder, setRenamingAsset, setActiveUploadFolderId, - setUploadTarget, setUploadDropTarget, setAssetPointerDrag, setSelectedAssetIds, @@ -169,6 +166,7 @@ export function ImageCanvasSidebarView({ onAssetMarqueePointerUp, updateAssetMoveDropFolder, addUploadedFiles, + requestUpload, moveAssetToFolder, commitNewAssetFolder, toggleAssetFolder, @@ -427,8 +425,7 @@ export function ImageCanvasSidebarView({ icon={ImagePlus} onClick={() => { setActiveUploadFolderId(folder.id); - setUploadTarget('asset'); - uploadInputRef.current?.click(); + requestUpload('asset'); }} /> diff --git a/src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx b/src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx index 0cba475b..5163c777 100644 --- a/src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx +++ b/src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx @@ -141,6 +141,9 @@ function UploadWorkflowHarness({ > 上传素材 + + ); } @@ -227,6 +236,40 @@ describe('useImageCanvasUploadWorkflow', () => { }); }); + it('opens login instead of the asset file picker when protected data is unavailable', () => { + const openEditorLoginModal = vi.fn(); + render( + , + ); + const uploadInput = screen.getByLabelText('上传图片文件') as HTMLInputElement; + const clickUploadInput = vi.spyOn(uploadInput, 'click'); + + fireEvent.click(screen.getByRole('button', { name: '请求素材上传' })); + + expect(openEditorLoginModal).toHaveBeenCalledTimes(1); + expect(clickUploadInput).not.toHaveBeenCalled(); + }); + + it('keeps generation reference uploads local and opens the file picker without login', () => { + const openEditorLoginModal = vi.fn(); + render( + , + ); + const uploadInput = screen.getByLabelText('上传图片文件') as HTMLInputElement; + const clickUploadInput = vi.spyOn(uploadInput, 'click'); + + fireEvent.click(screen.getByRole('button', { name: '请求角色规范上传' })); + + expect(openEditorLoginModal).not.toHaveBeenCalled(); + expect(clickUploadInput).toHaveBeenCalledTimes(1); + }); + it('creates an uploading asset card, adds a canvas layer, and patches the layer with the persisted asset id', async () => { const deferredAsset = createDeferred<{ assetId: string; diff --git a/src/components/image-editor/useImageCanvasUploadWorkflow.ts b/src/components/image-editor/useImageCanvasUploadWorkflow.ts index ed225811..c2b3878b 100644 --- a/src/components/image-editor/useImageCanvasUploadWorkflow.ts +++ b/src/components/image-editor/useImageCanvasUploadWorkflow.ts @@ -461,10 +461,24 @@ export function useImageCanvasUploadWorkflow({ ], ); - const requestUpload = useCallback((target: UploadTarget = 'asset') => { - setUploadTarget(target); - uploadInputRef.current?.click(); - }, []); + const openUploadPicker = useCallback( + (target: UploadTarget) => { + setUploadTarget(target); + uploadInputRef.current?.click(); + }, + [setUploadTarget], + ); + + const requestUpload = useCallback( + (target: UploadTarget = 'asset') => { + if (target === 'asset' && !canAccessProtectedDataRef.current) { + openEditorLoginModal(); + return; + } + openUploadPicker(target); + }, + [openEditorLoginModal, openUploadPicker], + ); const handleUploadInputChange = useCallback( (event: ChangeEvent) => {