收口未保存离开确认弹窗

新增 PlatformUnsavedLeaveConfirmDialog 统一承接未保存离开确认壳层
迁移 RPG 创作编辑器的关闭未保存修改与退出未保存结果确认复用共享组件
补充共享组件测试并更新 PlatformUiKit 收口文档与决策记录
This commit is contained in:
2026-06-10 22:26:05 +08:00
parent 4ef805282d
commit 7005792580
5 changed files with 154 additions and 18 deletions

View File

@@ -40,6 +40,7 @@
- 2026-06-10 追加:标准泥点消耗确认弹窗统一收口到 `src/components/common/PlatformMudPointConfirmDialog.tsx`;该 Module 专门承接“确认消耗泥点 + 消耗 N 泥点”的同形态确认骨架,当前已覆盖 `PuzzleCreationWorkspace.tsx``Match3DCreationWorkspace.tsx``PuzzleResultView.tsx``Match3DResultView.tsx`。后续遇到同形态泥点确认时,业务页只传点数、补充说明和确认回调,不再重复拼接 `UnifiedConfirmDialog` 正文;`RpgCreationRoleAssetStudioModalImpl` 这类节奏和内容结构不同的泥点弹层继续单独评估,留作后续轮次处理。
- 2026-06-10 追加:`RpgCreationRoleAssetStudioModalImpl.tsx` 的角色形象生成 / 动作草稿生成确认也并入 `PlatformMudPointConfirmDialog`;共享组件通过自定义 title 与补充说明承接工坊语义,工坊页不再单独维护 `UnifiedConfirmDialog` 的标准泥点文案骨架。后续同类“确认消耗泥点 + 补充说明”场景继续优先复用该 Module。
- 2026-06-10 追加:平台危险确认统一收口到 `src/components/common/PlatformDangerConfirmDialog.tsx`;该 Module 专门承接“确认 / 取消 + 危险主动作”的标准骨架,当前已覆盖 `PlatformEntryFlowShellImpl.tsx` 的删除作品确认和 `RpgCreationResultViewImpl.tsx` 的重新生成确认。后续删除、覆盖、清空等危险动作优先复用该 Module不再在业务页重复拼接 `UnifiedConfirmDialog``showCancel + confirmTone=\"danger\"` 组合。
- 2026-06-10 追加:平台未保存离开确认统一收口到 `src/components/common/PlatformUnsavedLeaveConfirmDialog.tsx`;该 Module 专门承接“继续编辑 + 确认离开”的标准骨架,当前已覆盖 `RpgCreationEntityEditorShared.tsx` 里的关闭未保存修改、生成结果未保存退出和普通结果未保存退出确认。后续同类未保存离开场景优先复用该 Module不再在业务页重复拼接 `UnifiedConfirmDialog``showCancel + cancelLabel=\"继续编辑\"` 组合和重复壳层 class。
- 2026-06-10 追加RPG 首页个人中心里的统计卡、统计骨架、常用功能入口、设置行和法律信息入口统一抽到 `src/components/platform-entry/PlatformProfilePrimitives.tsx`;这组纯展示原子以后优先通过 props 接收图片资源、点击回调和展示文案,不再继续塞回 `RpgEntryHomeView` 的账户控制逻辑里。新建 `PlatformProfilePrimitives.test.tsx` 作为组件级护栏,页面级布局与法律入口继续由 `RpgEntryHomeView.recharge.test.tsx` 兜底。
- 2026-06-10 追加RPG 首页个人中心的充值 / 钱包 / 每日任务 / 邀请 / 兑换码等商业与账户控制逻辑统一收口到 `src/components/platform-entry/usePlatformProfileCenterController.ts`controller 负责账户动作分流、商业状态派生与相关面板控制,`RpgEntryHomeView` 只保留展示、昵称头像编辑、扫码入口和页面级交互编排,不在页面组件里继续堆叠账户控制分支。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck`
- 2026-06-10 追加RPG 首页个人中心的“玩过 / 可继续”历史弹层统一抽到 `src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx``RpgEntryHomeView` 不再内联 `SaveArchiveCard``ProfilePlayedWorksModal` 和未连通的 `ProfileSaveArchivesModal`。当前产品语义已经把存档恢复并入“玩过”弹层的“可继续”分区,因此 controller 里的 `ProfilePopupPanel` 也去掉了没有真实入口的 `saveArchives` 分支。验证命令:`npm run test -- src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx``npm run typecheck`

View File

@@ -245,6 +245,7 @@
19.3.21. `PlatformStatusDialog` 继续收口规则阻断和搜索未命中提示:`CustomWorldEntityCatalog.tsx``minimum-playable` 规则阻断从删除确认分支中拆出,改由独立 `PlatformStatusDialog` 承接;`PlatformEntryFlowShellImpl` 的公开编号搜索弹层拆成“命中用户继续走 `UnifiedModal + PlatformSubpanel`”与“未找到结果改走 `PlatformStatusDialog`”两条分支。业务页不再让规则阻断提示和危险删除确认共用同一套 confirm config也不再在搜索结果 modal 内同时维护用户信息和错误态两套内容布局。验证命令:`npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "最后一个可扮演角色不可删除时使用平台状态弹窗"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "searching unmatched public work code shows not-found search result dialog|public code search shows public user summary in shared search result modal and clears it on close"``npm run typecheck`
19.3.22. 标准泥点消耗确认弹窗收口到 `src/components/common/PlatformMudPointConfirmDialog.tsx`;组件固定承接“确认消耗泥点 + 消耗 N 泥点”的同形态标题、正文骨架和确认 / 取消动作,业务页只保留点数、补充说明和确认回调。`PuzzleCreationWorkspace.tsx``Match3DCreationWorkspace.tsx``PuzzleResultView.tsx``Match3DResultView.tsx` 以及 `RpgCreationRoleAssetStudioModalImpl.tsx` 已迁移;其中角色形象生成 / 动作草稿生成继续通过自定义 title 和补充说明承接工坊语义,但不再各自拼接 `UnifiedConfirmDialog` 的相同文案和内容结构。后续同类泥点确认优先复用该 Module像 runtime 道具确认、预计消耗区间确认这类节奏不同的弹层再单独评估是否扩展变体。
19.3.23. 平台危险确认弹窗收口到 `src/components/common/PlatformDangerConfirmDialog.tsx`;组件固定承接“确认 / 取消 + 危险主动作”的标准骨架,并透传忙碌态、遮罩关闭策略、按钮文案和局部面板样式。`PlatformEntryFlowShellImpl.tsx` 的删除作品确认与 `RpgCreationResultViewImpl.tsx` 的重新生成确认已迁移;业务页继续保留标题、说明文案和确认回调,不再各自拼接 `UnifiedConfirmDialog` 的危险按钮配置。后续删除、覆盖、清空等危险动作优先复用该 Module再按需要补充更窄的语义 wrapper。
19.3.24. 平台未保存离开确认弹窗收口到 `src/components/common/PlatformUnsavedLeaveConfirmDialog.tsx`;组件固定承接“继续编辑 + 确认离开”的标准骨架,并按 `platform / pixel` 两类确认风格兜底默认遮罩和面板样式。`RpgCreationEntityEditorShared.tsx` 中的关闭未保存修改确认、生成结果未保存退出确认和普通结果未保存退出确认已迁移;业务页只保留标题、确认按钮文案和未保存提示内容,不再各自拼接 `UnifiedConfirmDialog` 的 cancel/confirm 组合和重复壳层 class。
19.3. creative-agent 首页的侧边栏菜单、账号入口、开启新对话、我的创作、首页激励 CTA 和 prompt suggestion 按钮迁移到 `PlatformIconButton` / `PlatformActionButton`;首页继续保留 `creative-agent-home__*` 本地 class 承接透明顶栏、抽屉和品牌化胶囊视觉,不把视觉回收和语义收口绑成一次大改。`Beta` 徽标和历史记录纯文本行暂保留本地实现,等出现更多同构轻量列表行后再评估是否抽新的共享 row primitive。
19.4. 大鱼吃小鱼结果页 hero 的返回入口迁移到 `PlatformIconButton variant="darkMini"`,测试 / 发布动作迁移到 `PlatformActionButton surface="editorDark"`;结果页只保留测试运行、发布提交和文案状态语义,不再手写 hero 顶栏按钮壳。
19.4.1. 大鱼吃小鱼结果页的发布失败弹层迁移到 `src/components/common/PlatformStatusDialog.tsx``PlatformStatusDialog` 补充自定义图标、可访问标签和动作按钮样式透传后,`BigFishResultView` 不再保留 `BigFishResultErrorModal` 内联的 `UnifiedConfirmDialog + PlatformIconBadge` 组合。结果页只保留失败文案和关闭回调,发布失败的状态图标、遮罩、白底面板和“知道了”主动作统一由共享状态弹层承接。验证命令:`npm run test -- src/components/common/PlatformStatusDialog.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx``npm run typecheck`

View File

@@ -0,0 +1,53 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react';
import { expect, test, vi } from 'vitest';
import { PlatformUnsavedLeaveConfirmDialog } from './PlatformUnsavedLeaveConfirmDialog';
test('renders the platform variant with leave/continue actions', () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
render(
<PlatformUnsavedLeaveConfirmDialog
open
title="确认关闭"
onClose={onClose}
onConfirm={onConfirm}
>
</PlatformUnsavedLeaveConfirmDialog>,
);
const dialog = screen.getByRole('dialog', { name: '确认关闭' });
expect(within(dialog).getByText('当前修改尚未保存,确认关闭吗?')).toBeTruthy();
fireEvent.click(within(dialog).getByRole('button', { name: '继续编辑' }));
expect(onClose).toHaveBeenCalledTimes(1);
fireEvent.click(within(dialog).getByRole('button', { name: '确认关闭' }));
expect(onConfirm).toHaveBeenCalledTimes(1);
});
test('supports pixel variant with custom confirm label', () => {
render(
<PlatformUnsavedLeaveConfirmDialog
open
title="确认退出"
confirmLabel="仍然退出"
variant="pixel"
onClose={() => {}}
onConfirm={() => {}}
>
</PlatformUnsavedLeaveConfirmDialog>,
);
const dialog = screen.getByRole('dialog', { name: '确认退出' });
expect(dialog.className).toContain('pixel-modal-shell');
expect(within(dialog).getByRole('button', { name: '仍然退出' })).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '继续编辑' })).toBeTruthy();
});

View File

@@ -0,0 +1,92 @@
import type { ReactNode } from 'react';
import { UnifiedConfirmDialog } from './UnifiedConfirmDialog';
type PlatformUnsavedLeaveConfirmDialogProps = {
open: boolean;
title?: string;
children?: ReactNode;
onClose: () => void;
onConfirm: () => void;
confirmLabel?: string;
cancelLabel?: string;
showCloseButton?: boolean;
closeOnBackdrop?: boolean;
portal?: boolean;
size?: 'sm' | 'md';
variant?: 'platform' | 'pixel';
overlayClassName?: string;
panelClassName?: string;
};
function resolveUnsavedLeaveOverlayClassName(
variant: 'platform' | 'pixel',
overlayClassName?: string,
) {
if (overlayClassName) {
return overlayClassName;
}
return variant === 'pixel' ? 'z-[140]' : 'z-[140] !items-center';
}
function resolveUnsavedLeavePanelClassName(
variant: 'platform' | 'pixel',
panelClassName?: string,
) {
if (panelClassName) {
return panelClassName;
}
return variant === 'platform'
? 'platform-remap-surface rounded-[1.5rem]'
: undefined;
}
/**
* 平台未保存离开确认弹窗。
* 统一承接关闭 / 退出前的“继续编辑 + 确认离开”语义和默认平台壳层。
*/
export function PlatformUnsavedLeaveConfirmDialog({
open,
title = '确认退出',
children,
onClose,
onConfirm,
confirmLabel,
cancelLabel = '继续编辑',
showCloseButton = true,
closeOnBackdrop = true,
portal = true,
size = 'sm',
variant = 'platform',
overlayClassName,
panelClassName,
}: PlatformUnsavedLeaveConfirmDialogProps) {
return (
<UnifiedConfirmDialog
open={open}
title={title}
onClose={onClose}
onConfirm={onConfirm}
confirmLabel={confirmLabel ?? title}
cancelLabel={cancelLabel}
showCloseButton={showCloseButton}
closeOnBackdrop={closeOnBackdrop}
showCancel
portal={portal}
size={size}
variant={variant}
overlayClassName={resolveUnsavedLeaveOverlayClassName(
variant,
overlayClassName,
)}
panelClassName={resolveUnsavedLeavePanelClassName(
variant,
panelClassName,
)}
>
{children}
</UnifiedConfirmDialog>
);
}

View File

@@ -105,9 +105,9 @@ import { PlatformSlotBadge } from '../common/PlatformSlotBadge';
import { PlatformStatusDialog } from '../common/PlatformStatusDialog';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformUnsavedLeaveConfirmDialog } from '../common/PlatformUnsavedLeaveConfirmDialog';
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
import { PlatformUploadTile } from '../common/PlatformUploadTile';
import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog';
import { CustomWorldCoverArtwork } from '../CustomWorldCoverArtwork';
import { CustomWorldNpcPortrait } from '../CustomWorldNpcVisualEditor';
import {
@@ -1471,19 +1471,15 @@ function CloseConfirmDialog({
confirmLabel?: string;
}) {
return (
<UnifiedConfirmDialog
<PlatformUnsavedLeaveConfirmDialog
open
title="确认关闭"
onClose={onCancel}
confirmLabel={confirmLabel}
cancelLabel="继续编辑"
showCancel
onConfirm={onConfirm}
overlayClassName="z-[140] !items-center"
panelClassName="platform-remap-surface rounded-[1.5rem]"
>
{message}
</UnifiedConfirmDialog>
</PlatformUnsavedLeaveConfirmDialog>
);
}
@@ -3383,22 +3379,19 @@ function SceneImageGenerationModal({
</ModalShell>
{isExitConfirmOpen ? (
<UnifiedConfirmDialog
<PlatformUnsavedLeaveConfirmDialog
open
title="确认退出"
onClose={() => setIsExitConfirmOpen(false)}
confirmLabel="仍然退出"
cancelLabel="继续编辑"
showCancel
onConfirm={() => {
setIsExitConfirmOpen(false);
onClose();
}}
variant="pixel"
overlayClassName="z-[140]"
>
退退
</UnifiedConfirmDialog>
</PlatformUnsavedLeaveConfirmDialog>
) : null}
</>
);
@@ -4029,22 +4022,18 @@ function CoverImageGenerationModal({
</ModalShell>
{isExitConfirmOpen ? (
<UnifiedConfirmDialog
<PlatformUnsavedLeaveConfirmDialog
open
title="确认退出"
onClose={() => setIsExitConfirmOpen(false)}
overlayClassName="z-[140]"
panelClassName="platform-remap-surface rounded-[1.5rem]"
confirmLabel="确认退出"
cancelLabel="继续编辑"
showCancel
onConfirm={() => {
setIsExitConfirmOpen(false);
onClose();
}}
>
退
</UnifiedConfirmDialog>
</PlatformUnsavedLeaveConfirmDialog>
) : null}
</>
);