收口危险操作确认弹窗

新增 PlatformDangerConfirmDialog 统一承接危险确认壳层
迁移平台入口删除作品确认复用共享危险确认弹窗
迁移 RPG 结果页重新生成确认复用共享危险确认弹窗
补充共享组件测试并更新 PlatformUiKit 收口文档与决策记录
This commit is contained in:
2026-06-10 22:17:13 +08:00
parent 40ff21f493
commit 4ef805282d
6 changed files with 154 additions and 10 deletions

View File

@@ -0,0 +1,68 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react';
import { expect, test, vi } from 'vitest';
import { PlatformDangerConfirmDialog } from './PlatformDangerConfirmDialog';
test('renders a standard danger confirmation with cancel and confirm actions', () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
render(
<PlatformDangerConfirmDialog
open
title="删除作品"
description="确认删除《潮雾列岛》吗?"
onClose={onClose}
onConfirm={onConfirm}
confirmLabel="确认删除"
>
</PlatformDangerConfirmDialog>,
);
const dialog = screen.getByRole('dialog', { name: '删除作品' });
expect(within(dialog).getByText('确认删除《潮雾列岛》吗?')).toBeTruthy();
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('forwards busy state and custom busy label for destructive actions', () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
render(
<PlatformDangerConfirmDialog
open
title="删除作品"
onClose={onClose}
onConfirm={onConfirm}
confirmLabel="确认删除"
busyConfirmLabel="删除中"
busy
closeOnBackdrop={false}
>
</PlatformDangerConfirmDialog>,
);
const dialog = screen.getByRole('dialog', { name: '删除作品' });
const confirmButton = within(dialog).getByRole('button', { name: '删除中' });
const cancelButton = within(dialog).getByRole('button', { name: '取消' });
expect((confirmButton as HTMLButtonElement).disabled).toBe(true);
expect((cancelButton as HTMLButtonElement).disabled).toBe(true);
fireEvent.click(confirmButton);
fireEvent.click(cancelButton);
expect(onClose).not.toHaveBeenCalled();
expect(onConfirm).not.toHaveBeenCalled();
});

View File

@@ -0,0 +1,78 @@
import type { ReactNode } from 'react';
import { UnifiedConfirmDialog } from './UnifiedConfirmDialog';
type PlatformDangerConfirmDialogProps = {
open: boolean;
title: string;
onClose: () => void;
onConfirm?: () => void;
description?: ReactNode;
children?: ReactNode;
confirmLabel?: string;
busy?: boolean;
busyConfirmLabel?: string;
cancelLabel?: string;
closeOnBackdrop?: boolean;
showCloseButton?: boolean;
portal?: boolean;
size?: 'sm' | 'md';
variant?: 'platform' | 'pixel';
overlayClassName?: string;
panelClassName?: string;
footerClassName?: string;
confirmClassName?: string;
};
/**
* 平台危险确认弹窗。
* 统一承接需要“确认 / 取消 + 危险主动作”语义的标准弹窗壳层。
*/
export function PlatformDangerConfirmDialog({
open,
title,
onClose,
onConfirm,
description,
children,
confirmLabel = '确认',
busy = false,
busyConfirmLabel,
cancelLabel = '取消',
closeOnBackdrop = true,
showCloseButton = true,
portal = true,
size = 'sm',
variant = 'platform',
overlayClassName,
panelClassName,
footerClassName,
confirmClassName,
}: PlatformDangerConfirmDialogProps) {
return (
<UnifiedConfirmDialog
open={open}
title={title}
description={description}
onClose={onClose}
onConfirm={onConfirm}
confirmLabel={confirmLabel}
busy={busy}
busyConfirmLabel={busyConfirmLabel}
cancelLabel={cancelLabel}
closeOnBackdrop={closeOnBackdrop}
showCloseButton={showCloseButton}
showCancel
confirmTone="danger"
portal={portal}
size={size}
variant={variant}
overlayClassName={overlayClassName}
panelClassName={panelClassName}
footerClassName={footerClassName}
confirmClassName={confirmClassName}
>
{children}
</UnifiedConfirmDialog>
);
}

View File

@@ -364,13 +364,13 @@ import {
import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformDangerConfirmDialog } from '../common/PlatformDangerConfirmDialog';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformStatusDialog } from '../common/PlatformStatusDialog';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PublishShareModal } from '../common/PublishShareModal';
import type { PublishShareModalPayload } from '../common/publishShareModalModel';
import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog';
import { UnifiedModal } from '../common/UnifiedModal';
import { resolveCreativeAgentTargetSelectionStage } from '../creative-agent/creativeAgentViewModel';
import {
@@ -17056,7 +17056,7 @@ export function PlatformEntryFlowShellImpl({
>
{workNotFoundRecoveryDialog?.message}
</PlatformStatusDialog>
<UnifiedConfirmDialog
<PlatformDangerConfirmDialog
open={Boolean(pendingDeleteCreationWork)}
title="删除作品"
description={
@@ -17070,14 +17070,12 @@ export function PlatformEntryFlowShellImpl({
size="sm"
overlayClassName={`platform-theme ${platformThemeClass} !items-center`}
panelClassName="platform-remap-surface rounded-[1.75rem]"
showCancel
confirmLabel="确认删除"
busyConfirmLabel="删除中"
confirmTone="danger"
onConfirm={confirmDeleteCreationWork}
>
{pendingDeleteCreationWork?.detail}
</UnifiedConfirmDialog>
</PlatformDangerConfirmDialog>
<AnimatePresence>
{publicSearchError ? (
<motion.div

View File

@@ -2,9 +2,9 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { rpgCreationAssetClient } from '../../services/rpg-creation/rpgCreationAssetClient';
import type { Character, CustomWorldProfile } from '../../types';
import { PlatformDangerConfirmDialog } from '../common/PlatformDangerConfirmDialog';
import { PlatformProgressBar } from '../common/PlatformProgressBar';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog';
import {
CustomWorldEntityCatalog,
type ResultTab,
@@ -344,17 +344,15 @@ export function RpgCreationResultView({
onProfileChange={onProfileChange}
/>
{regenerateConfirmOpen ? (
<UnifiedConfirmDialog
<PlatformDangerConfirmDialog
open
title="重新生成"
onClose={() => setRegenerateConfirmOpen(false)}
onConfirm={confirmRegenerate}
confirmLabel="确认重新生成"
confirmTone="danger"
showCancel
>
{`确认重新生成“${profile.name}”吗?重新生成会覆盖当前世界中的所有信息,包括你修改和新增的内容。`}
</UnifiedConfirmDialog>
</PlatformDangerConfirmDialog>
) : null}
</div>
);