fix(POSTV1-07): 自动关闭编辑器操作提示
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run

This commit is contained in:
suyx
2026-08-05 18:01:03 +08:00
parent 9d2aa879e9
commit 468bb5579d
2 changed files with 51 additions and 29 deletions
+43 -29
View File
@@ -160,6 +160,20 @@ export function EditorPage({ projectId }: { projectId: string }) {
const conflictExportGuardRef = useRef(new ConflictExportGuard()); const conflictExportGuardRef = useRef(new ConflictExportGuard());
const candidateMenuRef = useRef<HTMLDivElement | null>(null); const candidateMenuRef = useRef<HTMLDivElement | null>(null);
const candidateTriggerRef = useRef<HTMLButtonElement | null>(null); const candidateTriggerRef = useRef<HTMLButtonElement | null>(null);
const noticeTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
function showNotice(message: string) {
if (noticeTimerRef.current) clearTimeout(noticeTimerRef.current);
setNotice(message);
noticeTimerRef.current = setTimeout(() => {
setNotice("");
noticeTimerRef.current = undefined;
}, 3_000);
}
useEffect(() => () => {
if (noticeTimerRef.current) clearTimeout(noticeTimerRef.current);
}, []);
useEffect(() => { useEffect(() => {
let active = true; let active = true;
@@ -175,7 +189,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
setDraftAdjustments(initial.background.adjustments); setDraftAdjustments(initial.background.adjustments);
historyRef.current = new CanvasEditHistory(initial); historyRef.current = new CanvasEditHistory(initial);
elementControllerRef.current = new CanvasElementController(initial); elementControllerRef.current = new CanvasElementController(initial);
}).catch(() => { if (active) setNotice("编辑器暂时无法读取项目"); }); }).catch(() => { if (active) showNotice("编辑器暂时无法读取项目"); });
return () => { active = false; }; return () => { active = false; };
}, [projectId]); }, [projectId]);
@@ -291,7 +305,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
function applyPreview() { function applyPreview() {
if (!canvasState) return; if (!canvasState) return;
commitCanvas(updateBackgroundAdjustments(canvasState, draftAdjustments)); commitCanvas(updateBackgroundAdjustments(canvasState, draftAdjustments));
setNotice("底图调整已提交"); showNotice("底图调整已提交");
} }
function undo() { function undo() {
@@ -334,9 +348,9 @@ export function EditorPage({ projectId }: { projectId: string }) {
const palette = await paletteForAsset(pendingBackground); const palette = await paletteForAsset(pendingBackground);
commitCanvas(switchBackground(canvasState, pendingBackground, palette)); commitCanvas(switchBackground(canvasState, pendingBackground, palette));
setPendingBackground(undefined); setPendingBackground(undefined);
setNotice("已更换底图,覆盖元素保留,底图处理已重置"); showNotice("已更换底图,覆盖元素保留,底图处理已重置");
} catch { } catch {
setNotice("新底图无法读取,未更换底图或刷新色卡"); showNotice("新底图无法读取,未更换底图或刷新色卡");
} }
} }
@@ -353,7 +367,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
commitCanvas(next); commitCanvas(next);
setGuides([]); setGuides([]);
setCandidateMenu(undefined); setCandidateMenu(undefined);
setNotice(message); showNotice(message);
} }
function addSticker(sticker: StaticStickerCatalogItem) { function addSticker(sticker: StaticStickerCatalogItem) {
@@ -369,8 +383,8 @@ export function EditorPage({ projectId }: { projectId: string }) {
})); }));
commitElementOperation(controller, "贴纸已加入画布"); commitElementOperation(controller, "贴纸已加入画布");
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "canvas_element_limit_reached") setNotice("画布最多 50 个元素,请先删除现有元素。"); if (error instanceof Error && error.message === "canvas_element_limit_reached") showNotice("画布最多 50 个元素,请先删除现有元素。");
else setNotice("贴纸未能加入画布"); else showNotice("贴纸未能加入画布");
} }
} }
@@ -383,8 +397,8 @@ export function EditorPage({ projectId }: { projectId: string }) {
controller.add(createColorCardElement(definition, palette, newElementIdentity(), canvasState.elements.length)); controller.add(createColorCardElement(definition, palette, newElementIdentity(), canvasState.elements.length));
commitElementOperation(controller, "色卡已按原始底图加入画布"); commitElementOperation(controller, "色卡已按原始底图加入画布");
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "canvas_element_limit_reached") setNotice("画布最多 50 个元素,请先删除现有元素。"); if (error instanceof Error && error.message === "canvas_element_limit_reached") showNotice("画布最多 50 个元素,请先删除现有元素。");
else setNotice("无法从原始底图稳定提取五色,色卡未加入画布"); else showNotice("无法从原始底图稳定提取五色,色卡未加入画布");
} }
} }
@@ -394,7 +408,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
if (templateId === "DYN012") { if (templateId === "DYN012") {
const font = fontOption("FONT081"); const font = fontOption("FONT081");
if (!font || await ensureFont(font.fontId, font.url) !== "ready") { if (!font || await ensureFont(font.fontId, font.url) !== "ready") {
setNotice("DYN012 的 FONT081 替代字体不可用,未使用系统字体替代。"); showNotice("DYN012 的 FONT081 替代字体不可用,未使用系统字体替代。");
return; return;
} }
} }
@@ -407,8 +421,8 @@ export function EditorPage({ projectId }: { projectId: string }) {
commitElementOperation(controller, "动态值已确认并加入画布"); commitElementOperation(controller, "动态值已确认并加入画布");
setLocationDialog(undefined); setLocationDialog(undefined);
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "canvas_element_limit_reached") setNotice("画布最多 50 个元素,请先删除现有元素。"); if (error instanceof Error && error.message === "canvas_element_limit_reached") showNotice("画布最多 50 个元素,请先删除现有元素。");
else setNotice("动态贴纸未能加入画布"); else showNotice("动态贴纸未能加入画布");
} }
} }
@@ -456,7 +470,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
controller.replaceElement(overrideDynamicStickerValue(element, value)); controller.replaceElement(overrideDynamicStickerValue(element, value));
commitElementOperation(controller, "动态贴纸显示文字已更新"); commitElementOperation(controller, "动态贴纸显示文字已更新");
} catch { } catch {
setNotice("动态贴纸显示文字不能为空"); showNotice("动态贴纸显示文字不能为空");
} }
} }
@@ -480,7 +494,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
if (!template.fontUrl || !canvasState) return; if (!template.fontUrl || !canvasState) return;
const status = await ensureFont(template.defaultFontId, template.fontUrl); const status = await ensureFont(template.defaultFontId, template.fontUrl);
if (status !== "ready") { if (status !== "ready") {
setNotice("素材暂不可用,未使用系统字体替代。"); showNotice("素材暂不可用,未使用系统字体替代。");
return; return;
} }
const controller = controllerForCurrent(); const controller = controllerForCurrent();
@@ -490,8 +504,8 @@ export function EditorPage({ projectId }: { projectId: string }) {
commitElementOperation(controller, "文字模板已加入画布"); commitElementOperation(controller, "文字模板已加入画布");
void recordRecentTextTemplate(template.templateId, template.resourceVersion); void recordRecentTextTemplate(template.templateId, template.resourceVersion);
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "canvas_element_limit_reached") setNotice("画布最多 50 个元素,请先删除现有元素。"); if (error instanceof Error && error.message === "canvas_element_limit_reached") showNotice("画布最多 50 个元素,请先删除现有元素。");
else setNotice("文字模板未能加入画布"); else showNotice("文字模板未能加入画布");
} }
} }
@@ -503,7 +517,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
action(edit); action(edit);
return { ...current, draft: edit.value }; return { ...current, draft: edit.value };
} catch { } catch {
setNotice("文字参数不在允许范围内"); showNotice("文字参数不在允许范围内");
return current; return current;
} }
}); });
@@ -514,7 +528,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
if (!template?.fontUrl) return; if (!template?.fontUrl) return;
const status = await ensureFont(template.defaultFontId, template.fontUrl); const status = await ensureFont(template.defaultFontId, template.fontUrl);
if (status !== "ready") { if (status !== "ready") {
setNotice("素材暂不可用,未使用系统字体替代。"); showNotice("素材暂不可用,未使用系统字体替代。");
return; return;
} }
updateTextDraft((edit) => edit.switchTemplate(templateId)); updateTextDraft((edit) => edit.switchTemplate(templateId));
@@ -527,7 +541,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
} }
const option = fontOption(fontId); const option = fontOption(fontId);
if (!option || await ensureFont(option.fontId, option.url) !== "ready") { if (!option || await ensureFont(option.fontId, option.url) !== "ready") {
setNotice("字体素材暂不可用,未使用系统字体替代。"); showNotice("字体素材暂不可用,未使用系统字体替代。");
return; return;
} }
updateTextDraft((edit) => edit.setStyle({ fontOverride: fontId })); updateTextDraft((edit) => edit.setStyle({ fontOverride: fontId }));
@@ -546,8 +560,8 @@ export function EditorPage({ projectId }: { projectId: string }) {
void recordRecentTextTemplate(complete.template_or_asset_id, complete.resource_version); void recordRecentTextTemplate(complete.template_or_asset_id, complete.resource_version);
} }
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "text_content_required") setNotice("请输入文字内容或删除该元素。"); if (error instanceof Error && error.message === "text_content_required") showNotice("请输入文字内容或删除该元素。");
else setNotice("文字编辑未能完成"); else showNotice("文字编辑未能完成");
} }
} }
@@ -556,7 +570,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
const current = canvasState.elements.find((element) => element.element_id === textEdit.elementId); const current = canvasState.elements.find((element) => element.element_id === textEdit.elementId);
if (current) setTextEdit({ draft: structuredClone(current), elementId: current.element_id, originalTemplateId: current.template_or_asset_id }); if (current) setTextEdit({ draft: structuredClone(current), elementId: current.element_id, originalTemplateId: current.template_or_asset_id });
} }
setNotice("已取消未提交的文字修改"); showNotice("已取消未提交的文字修改");
} }
function pendingTextDraft() { function pendingTextDraft() {
@@ -624,7 +638,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
return; return;
} }
const ran = await conflictExportGuardRef.current.run(() => executeExport(options)); const ran = await conflictExportGuardRef.current.run(() => executeExport(options));
if (!ran) setNotice("版本冲突时仅允许导出本页版本一次"); if (!ran) showNotice("版本冲突时仅允许导出本页版本一次");
} }
async function retryExportDownload() { async function retryExportDownload() {
@@ -639,8 +653,8 @@ export function EditorPage({ projectId }: { projectId: string }) {
action(controller); action(controller);
commitElementOperation(controller, message); commitElementOperation(controller, message);
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "canvas_element_limit_reached") setNotice("画布最多 50 个元素,请先删除现有元素。"); if (error instanceof Error && error.message === "canvas_element_limit_reached") showNotice("画布最多 50 个元素,请先删除现有元素。");
else setNotice("对象操作未完成"); else showNotice("对象操作未完成");
} }
} }
@@ -668,7 +682,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
opacityGestureRef.current = undefined; opacityGestureRef.current = undefined;
if (gesture.last === gesture.base) return; if (gesture.last === gesture.base) return;
commitCanvas(gesture.last); commitCanvas(gesture.last);
setNotice("贴纸透明度已提交"); showNotice("贴纸透明度已提交");
} }
function duplicateSelection() { function duplicateSelection() {
@@ -688,7 +702,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
const controller = controllerForCurrent(); const controller = controllerForCurrent();
if (!controller || selectedIds.length === 0) return; if (!controller || selectedIds.length === 0) return;
clipboardRef.current = controller.copySelected(); clipboardRef.current = controller.copySelected();
setNotice("已复制到画布剪贴板"); showNotice("已复制到画布剪贴板");
} }
function pasteSelection() { function pasteSelection() {
@@ -698,7 +712,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
controller.pasteElements(clipboardRef.current, () => newElementIdentity()); controller.pasteElements(clipboardRef.current, () => newElementIdentity());
commitElementOperation(controller, "已粘贴画布对象"); commitElementOperation(controller, "已粘贴画布对象");
} catch (error) { } catch (error) {
if (error instanceof Error && error.message === "canvas_element_limit_reached") setNotice("画布最多 50 个元素,请先删除现有元素。"); if (error instanceof Error && error.message === "canvas_element_limit_reached") showNotice("画布最多 50 个元素,请先删除现有元素。");
} }
} }
@@ -728,7 +742,7 @@ export function EditorPage({ projectId }: { projectId: string }) {
const drag = dragRef.current; const drag = dragRef.current;
if (!drag) return; if (!drag) return;
commitCanvas(drag.last); commitCanvas(drag.last);
setNotice("对象位置已提交"); showNotice("对象位置已提交");
setGuides([]); setGuides([]);
dragRef.current = undefined; dragRef.current = undefined;
} }
+8
View File
@@ -281,4 +281,12 @@ test("POSTV1-07 keeps text selection stable and dismisses move feedback", async
await page.waitForTimeout(800); await page.waitForTimeout(800);
expect(backend.canvas.elements[0]?.position).toEqual(positionBefore); expect(backend.canvas.elements[0]?.position).toEqual(positionBefore);
expect(backend.saves).toBe(savesBefore); expect(backend.saves).toBe(savesBefore);
await page.mouse.move(clientX, clientY);
await page.mouse.down();
await page.mouse.move(clientX + 12, clientY);
await page.mouse.up();
await expect(page.getByText("对象位置已提交", { exact: true })).toBeVisible();
await expect.poll(() => backend.saves, { timeout: 5_000 }).toBeGreaterThan(savesBefore);
await expect(page.getByText("对象位置已提交", { exact: true })).toBeHidden({ timeout: 5_000 });
}); });