From 701fd427775b37ca2861f4c8fd2fe63bc9146586 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 10 Jun 2026 17:15:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E8=BD=BB=E9=87=8F=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E5=BC=B9=E7=AA=97=E4=B8=8E=E4=B8=AA=E4=BA=BA=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E5=9B=BE=E6=A0=87=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UnifiedModal 新增无头部模式并补齐对应可访问性测试 RpgEntryHomeView 的支付结果提示、支付确认遮罩与个人中心顶栏图标按钮改用共享组件 更新 PlatformUiKit 收口计划与 .hermes 决策记录 --- .hermes/shared-memory/decision-log.md | 1 + ...】PlatformUiKit弹窗组件收口计划-2026-06-08.md | 4 +- src/components/common/UnifiedModal.test.tsx | 22 +++ src/components/common/UnifiedModal.tsx | 61 ++++---- .../RpgEntryHomeView.recharge.test.tsx | 20 ++- src/components/rpg-entry/RpgEntryHomeView.tsx | 139 +++++++++--------- 6 files changed, 147 insertions(+), 100 deletions(-) diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index ad62c3d1..a4285c93 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -55,6 +55,7 @@ - 2026-06-10 追加:`PlatformModalCloseButton variant="editorDark"` 承接 RPG 暗色弹窗中非像素风的圆形 X 关闭入口,根节点固定带 `platform-modal-close-button--editor-dark` 稳定类名;自定义选择弹窗头部关闭按钮已迁移,并补齐 `aria-label`,业务 JSX 不再手写暗色关闭按钮边框、底色、hover 和默认 X 图标。验证命令:`npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/SelectionCustomizationModals.test.tsx`。 - 2026-06-10 追加:`PlatformModalCloseButton variant="pixel"` 承接 `UnifiedModal variant="pixel"` 头部圆形关闭入口;`UnifiedModal` 只选择 `platformIcon / pixel` 变体并保留 closeDisabled、Backdrop、Escape 和 portal 语义,不再手写 X 图标、aria 和关闭按钮 class。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformModalCloseButton.test.tsx src/components/common/UnifiedConfirmDialog.test.tsx`。 - 2026-06-10 追加:`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口标准平台弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、原有标题层级和“不响应 Escape / backdrop”的交互语义;RPG 首页个人中心里的昵称修改、账户充值、每日任务和兑换码弹窗已迁移到 `UnifiedModal`,支付结果 / 支付确认遮罩 / 泥点账单这类头部结构不同的弹窗继续保留专用实现。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。 +- 2026-06-10 追加:`UnifiedModal` 新增 `showHeader`,用于收口不需要标准头部但仍要保留 dialog 无障碍语义、遮罩和层级控制的轻量弹窗;RPG 首页个人中心的支付结果提示与支付确认遮罩已迁移到 `showHeader={false}` 模式,业务页只保留 icon badge、文案与按钮,不再手写 backdrop、aria 和白底壳层。个人中心移动端顶栏“扫码”“打开设置”入口统一使用 `PlatformIconButton`,并继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置与主题色。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformIconButton.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。 - 2026-06-09 追加:RPG 大编辑器暗色面板内的保存和角色槽动作继续走本地 `ActionButton`,不再混用白底平台 `platform-button` class;平台白底动作收口和编辑器暗色动作收口保持两套视觉边界。 - 2026-06-10 追加:`PlatformActionButton surface="editorDark"` 承接 RPG 暗色弹窗 / 运行面板里的普通取消、确认、刷新和编组动作,支持 `size="xxs"` 与 `tone="success" | "warning"`;`tone="accent"` 承接暗色壳层内的琥珀实心 CTA,`tone="accentSoft"` 承接依赖局部 accent 变量的柔和强调按钮。角色自定义 footer、自定义世界生成 footer、地图切换确认、营地编组普通动作和角色聊天刷新动作已迁移。暗色可选项卡仍使用 `PlatformDarkOptionCard`,像素风发送 / 强品牌动作继续保留专用布局。验证命令:`npm run test -- src/components/common/platformActionButtonModel.test.ts src/components/common/PlatformActionButton.test.tsx src/components/SelectionCustomizationModals.test.tsx src/components/CompanionCampModal.test.tsx src/components/MapModal.test.tsx src/components/CharacterChatModal.test.tsx`。 - 2026-06-10 追加:RPG 首页创作 / 草稿顶栏的钱包快捷入口通过同文件 `TopbarWalletShortcutButton` 复用 `PlatformActionButton tone="accentSoft" shape="pill" size="xs"` 与 `PlatformIconBadge`;移动端 / 桌面端继续保留 `.platform-mobile-create-wallet-chip`、`.platform-desktop-create-wallet-chip` 和 `.platform-desktop-search` 兼容 class,承接余额截断、桌面顶栏胶囊壳和既有测试锚点,点击语义仍统一走 `openRechargeOrRewardCodeModal`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。 diff --git a/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md b/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md index bee37d27..836c63c9 100644 --- a/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md +++ b/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md @@ -223,7 +223,9 @@ 19.2. 拼图广场详情 hero 的返回、上一张 / 下一张关卡图入口迁移到 `PlatformIconButton variant="darkMini"`,修改作品和进入第 1 关迁移到 `PlatformActionButton`,分享动作继续使用 `CopyFeedbackButton` 但复用共享动作按钮 chrome;详情页只保留轮播、复制和跳转语义,不再手写 hero 区按钮壳。 19.3. 个人中心充值商品卡里的“购买 / 处理中”胶囊暂保留局部 `span`,不直接套用 `PlatformActionButton`,避免在 `PlatformSubpanel as="button"` 内再嵌套交互按钮;待出现第二个同形态的非交互 action chip 后,再决定是否沉淀独立的共享展示基元。 19.3.1. RPG 首页创作 / 草稿顶栏的钱包快捷入口迁移到同文件适配器 `TopbarWalletShortcutButton`,内部复用 `PlatformActionButton tone="accentSoft" shape="pill" size="xs"` 与 `PlatformIconBadge`;移动端和桌面端继续保留 `.platform-mobile-create-wallet-chip`、`.platform-desktop-create-wallet-chip` 和 `.platform-desktop-search` 兼容 class,承接移动端余额截断、桌面顶栏胶囊底色以及既有测试锚点。入口点击仍统一走 `openRechargeOrRewardCodeModal`,不把充值 / 兑换码平台分流逻辑改散到两个顶栏分支里。 -19.3.2. 个人中心昵称修改、账户充值、每日任务和兑换码四类标准头部弹窗迁移到 `UnifiedModal`;`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、居中浮层布局和标题层级。上述弹窗统一通过 `closeOnBackdrop={false}`、`closeOnEscape={false}` 保持原有交互语义,不把 backdrop / Escape 关闭行为悄悄带进个人中心;支付确认遮罩、支付结果提示和泥点账单这类头部结构不同或浮动关闭语义不同的弹窗继续保留专用实现,等出现更多同构 case 再扩充 `UnifiedModal` 能力。 +19.3.2. 个人中心昵称修改、账户充值、每日任务和兑换码四类标准头部弹窗迁移到 `UnifiedModal`;`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、居中浮层布局和标题层级。上述弹窗统一通过 `closeOnBackdrop={false}`、`closeOnEscape={false}` 保持原有交互语义,不把 backdrop / Escape 关闭行为悄悄带进个人中心;泥点账单这类头部结构和 footer 语义明显不同的弹窗继续保留专用实现,等出现更多同构 case 再扩充 `UnifiedModal` 能力。 +19.3.3. 个人中心支付结果提示和支付确认遮罩迁移到 `UnifiedModal` 的 headerless 模式;`UnifiedModal` 新增 `showHeader`,用于在保留 `role="dialog"`、可访问名称、遮罩和 z-index 语义的同时,允许业务页自己排版 icon badge、轻量标题和正文。支付结果提示与确认遮罩统一使用 `showHeader={false}`、`showCloseButton={false}`、`closeOnBackdrop={false}`、`closeOnEscape={false}`,继续保持阻断式确认语义;业务页只保留图标、文案和按钮,不再手写 backdrop、dialog aria 和面板壳层。 +19.3.4. 个人中心移动端顶栏的“扫码”“打开设置”入口迁移到 `PlatformIconButton`;页面继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置、尺寸和主题色,交互语义与可访问名称统一由共享按钮承接,不再在 `RpgEntryHomeView` 里手写图标按钮的 `type`、`aria-label` 和基础 chrome。 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 顶栏按钮壳。 20. 平台方形上传入口和紧凑虚线新增入口迁移到 `PlatformUploadTile`,上传后的图片预览迁移到 `PlatformUploadPreviewCard`;反馈页上传凭证入口 / 预览、敲木鱼工作台新增功德词条入口、通用创作图片面板的提示词参考图缩略图、抓大鹅封面编辑参考图缩略图、通用输入 Composer 已选参考图条、creation-agent 已选参考图条和拼图结果页关卡引用图横条已先迁移。方形缩略图使用默认 `layout="square"`,横向“已选择参考图 / 文件名 / 素材名 / 移除”条使用 `layout="inline"`;只读引用图条不传 `onRemove`,避免公共组件额外渲染删除入口。后续继续收口结果页素材上传、工作台参考图上传、紧凑虚线新增入口等上传 / 动作块时,业务页只保留文件选择、预览数组、预览回调、删除回调、校验逻辑或新增回调,上传方块外观、主副文案、缩略图壳、预览按钮、标题行、横向已选条、移除按钮和禁用态统一由 Module 承接;工具栏中的小图标上传仍继续使用 `PlatformIconButton asChild="label"`。 diff --git a/src/components/common/UnifiedModal.test.tsx b/src/components/common/UnifiedModal.test.tsx index 9710018d..f801a1d1 100644 --- a/src/components/common/UnifiedModal.test.tsx +++ b/src/components/common/UnifiedModal.test.tsx @@ -63,6 +63,28 @@ test('supports disabling escape close while keeping the custom close button chro expect(screen.getByRole('dialog', { name: '个人中心弹窗' })).toBeTruthy(); }); +test('supports headerless dialogs while preserving the accessible name', () => { + render( + {}} + showHeader={false} + showCloseButton={false} + portal={false} + > +
窗口内容
+
, + ); + + const dialog = screen.getByRole('dialog', { name: '支付成功' }); + + expect(dialog).toBeTruthy(); + expect(screen.queryByRole('button', { name: '关闭' })).toBeNull(); + expect(screen.getByText('窗口内容')).toBeTruthy(); +}); + test('respects closeDisabled for every default close path', () => { const onClose = vi.fn(); render( diff --git a/src/components/common/UnifiedModal.tsx b/src/components/common/UnifiedModal.tsx index 9610215c..83d2f025 100644 --- a/src/components/common/UnifiedModal.tsx +++ b/src/components/common/UnifiedModal.tsx @@ -25,6 +25,7 @@ type UnifiedModalProps = { onClose: () => void; variant?: UnifiedModalVariant; size?: UnifiedModalSize; + showHeader?: boolean; closeDisabled?: boolean; closeOnBackdrop?: boolean; closeOnEscape?: boolean; @@ -86,6 +87,7 @@ function UnifiedModalContent({ onClose, variant = 'platform', size = 'md', + showHeader = true, closeDisabled = false, closeOnBackdrop = true, closeOnEscape = true, @@ -173,42 +175,51 @@ function UnifiedModalContent({
event.stopPropagation()} > -
-
-
- {title} -
- {description ? ( + {showHeader ? ( +
+
- {description} + {title}
+ {description ? ( +
+ {description} +
+ ) : null} +
+ {showCloseButton ? ( + ) : null}
- {showCloseButton ? ( - - ) : null} -
+ ) : null}
+ {/* 无头部弹窗仍需要把描述挂到 aria-describedby,避免只剩可访问名称没有上下文。 */} + {!showHeader && description ? ( +
+ {description} +
+ ) : null} {children}
{footer ? ( diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx index d59c9cc9..aa9088c8 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx @@ -2903,12 +2903,20 @@ test('mobile profile page matches the reference layout sections', async () => { const topbar = container.querySelector('.platform-mobile-topbar'); expect(topbar).toBeTruthy(); - expect( - within(topbar as HTMLElement).getByRole('button', { name: '扫码' }), - ).toBeTruthy(); - expect( - within(topbar as HTMLElement).getByRole('button', { name: '打开设置' }), - ).toBeTruthy(); + const scanButton = within(topbar as HTMLElement).getByRole('button', { + name: '扫码', + }); + const settingsButton = within(topbar as HTMLElement).getByRole('button', { + name: '打开设置', + }); + expect(scanButton).toBeTruthy(); + expect(settingsButton).toBeTruthy(); + expect(scanButton.className).toContain('platform-icon-button'); + expect(scanButton.className).toContain('platform-profile-header__icon-button'); + expect(settingsButton.className).toContain('platform-icon-button'); + expect(settingsButton.className).toContain( + 'platform-profile-header__icon-button', + ); expect( within(topbar as HTMLElement).queryByRole('button', { name: /充值/u, diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx index 59e3104e..b052f344 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.tsx @@ -3337,69 +3337,76 @@ function RechargePaymentResultModal({ : 'text-[var(--platform-button-danger-text)]'; return ( -
-
-
-
+ +
+
+ {result.message} +
+ + 知道了 + + ); } function RechargePaymentConfirmationMask({ orderId }: { orderId: string }) { return ( -
-
-
-
+ undefined} + showHeader={false} + showCloseButton={false} + closeOnBackdrop={false} + closeOnEscape={false} + portal={false} + size="sm" + zIndexClassName="z-[95]" + overlayClassName={PROFILE_MODAL_OVERLAY_CLASS} + panelClassName="platform-remap-surface !max-w-sm rounded-[1.4rem]" + bodyClassName="px-5 pb-5 pt-6 text-center" + > +
+
+ 订单 {orderId} 正在同步到账状态,请先停留在当前页面。 +
+ ); } @@ -7402,22 +7409,18 @@ export function RpgEntryHomeView({ {isAuthenticated && activeTab === 'profile' ? (
- - + />
) : isAuthenticated && (activeTab === 'create' || activeTab === 'saves') ? (