diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 5b6d3e83..9b7a3b9c 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -58,6 +58,7 @@ - 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-10 追加:RPG 首页发现页分类筛选弹窗和个人中心扫码面板改用 `UnifiedModal` 承接 backdrop、dialog 语义和层级;分类筛选保留本地选项 / 动作布局,扫码面板继续使用 `showHeader={false}` 保留深色自定义头部与摄像头 viewport,并显式维持 `closeOnBackdrop={false}`、`closeOnEscape={false}`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/common/UnifiedModal.test.tsx`。 - 2026-06-10 追加:RPG 首页个人中心泥点账单改用 `UnifiedModal showHeader={false}` 承接 `dialog` 语义和遮罩层级,同时保留渐变面板、`PlatformModalCloseButton variant="floating"`、余额 badge 与账单列表布局;账单继续显式维持 `closeOnBackdrop={false}`、`closeOnEscape={false}`,测试改为直接断言具名 dialog 和关闭后卸载。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "opens wallet ledger modal from narrative coin card|wallet ledger modal shows empty and error states" src/components/common/UnifiedModal.test.tsx`。 +- 2026-06-10 追加:RPG 首页个人中心“玩过作品”面板改用 `UnifiedModal showHeader={false}` 承接 `dialog` 语义和遮罩层级,同时保留 `PLAYED` kicker、总时长 badge、`PlatformModalCloseButton variant="floating"`、`可继续 / 玩过` 双分区与作品卡布局;存档入口继续留在同一个“玩过”面板内,不再回退成独立 `SAVE ARCHIVE` / `ARCHIVE` 壳层。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile played modal summary and work type use platform pill badges|profile played modal empty state uses platform empty state" src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "authenticated users can open save archives from the profile played panel|profile page keeps save archives inside played stats panel" src/components/common/UnifiedModal.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 2a223995..739cf293 100644 --- a/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md +++ b/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md @@ -228,6 +228,7 @@ 19.3.4. 个人中心移动端顶栏的“扫码”“打开设置”入口迁移到 `PlatformIconButton`;页面继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置、尺寸和主题色,交互语义与可访问名称统一由共享按钮承接,不再在 `RpgEntryHomeView` 里手写图标按钮的 `type`、`aria-label` 和基础 chrome。 19.3.5. 发现页分类筛选弹窗与个人中心扫码面板迁移到 `UnifiedModal`;分类筛选继续复用本地选项栅格和底部动作区样式,但 backdrop、dialog 语义、头部关闭入口和 `closeOnEscape={false}` 统一收口到共享壳层。扫码面板复用 `showHeader={false}` 模式保留深色自定义头部、摄像头 viewport 和状态提示,同时显式保持 `closeOnBackdrop={false}`、`closeOnEscape={false}`,确保不会把扫码中的资源清理语义改散到页面外层。 19.3.6. 个人中心泥点账单弹窗迁移到 `UnifiedModal` 的 headerless 模式;共享壳层承接 `dialog` 语义、层级和关闭策略,账单弹窗继续保留自定义渐变面板、浮动关闭按钮、余额 badge、列表 / 空态 / 错误态布局以及 `closeOnBackdrop={false}`、`closeOnEscape={false}` 的原有交互,不再手写 `fixed inset-0` 遮罩壳层。 +19.3.7. 个人中心“玩过作品”面板迁移到 `UnifiedModal` 的 headerless 模式;共享壳层承接 `dialog` 语义、层级与关闭策略,面板继续保留 `PLAYED` kicker、总时长 badge、浮动关闭按钮、`可继续 / 玩过` 双分区、作品卡与空态布局,以及 `closeOnBackdrop={false}`、`closeOnEscape={false}` 的原有交互。存档入口仍留在同一个“玩过”面板内,不再回退成独立的 `SAVE ARCHIVE` / `ARCHIVE` 壳层。 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/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx index 0f9a3187..91a4da0f 100644 --- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx +++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx @@ -328,6 +328,7 @@ async function openProfilePlayedWorks( await user.click( await screen.findByRole('button', { name: /已玩游戏数量/u }), ); + expect(await screen.findByRole('dialog', { name: '玩过' })).toBeTruthy(); expect(await screen.findByText('可继续')).toBeTruthy(); } diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx index 53307acd..77008c2d 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx @@ -2790,9 +2790,11 @@ test('profile played modal summary and work type use platform pill badges', asyn await user.click(screen.getByRole('button', { name: /已玩游戏数量/u })); - const playedModal = screen - .getByText('潮雾列岛') - .closest('.fixed') as HTMLElement; + const playedModal = await screen.findByRole('dialog', { name: '玩过' }); + + expect( + within(playedModal).getByRole('button', { name: '关闭玩过' }), + ).toBeTruthy(); const playedSectionLabel = within(playedModal) .getAllByText('玩过') .find((element) => element.className.includes('tracking-[0.18em]')); diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx index edb5c545..54813da8 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.tsx @@ -2813,6 +2813,8 @@ function ProfileReferralUserAvatar({ const PROFILE_MODAL_OVERLAY_CLASS = 'platform-modal-backdrop !items-center !justify-center !px-4 !py-6'; +const PROFILE_SECONDARY_MODAL_OVERLAY_CLASS = + '!items-center !bg-black/48 !px-3 !py-5 !backdrop-blur-none'; const PROFILE_MODAL_HEADER_CLASS = 'border-white/10 px-5 py-4'; const PROFILE_MODAL_TITLE_CLASS = 'text-base font-black'; const PROFILE_MODAL_DESCRIPTION_CLASS = @@ -3429,7 +3431,7 @@ function WalletLedgerModal({ portal={false} size="sm" zIndexClassName="z-[80]" - overlayClassName="!items-center !bg-black/48 !px-3 !py-5 !backdrop-blur-none" + overlayClassName={PROFILE_SECONDARY_MODAL_OVERLAY_CLASS} panelClassName="relative !max-h-[min(92vh,42rem)] !max-w-[30rem] bg-[linear-gradient(180deg,#fff7f8_0%,#ffffff_38%,#f8fafc_100%)] text-zinc-950 shadow-2xl !rounded-[1.35rem] sm:!rounded-[1.35rem]" bodyClassName="!p-0" > @@ -4241,137 +4243,149 @@ function ProfilePlayedWorksModal({ const hasPlayedWorks = playedWorks.length > 0; return ( -
-
+ +
-
-
-
- PLAYED -
-
玩过
- } - className="mt-2" - > - {formatTotalPlayTimeHours(stats?.totalPlayTimeMs ?? 0)} - +
+
+ PLAYED
- - {error ? ( - - {error} - - ) : null} - {saveError ? ( - - {saveError} - - ) : null} - - {isLoading ? ( -
- {Array.from({ length: 4 }).map((_, index) => ( -
- ))} -
- ) : hasArchiveEntries || hasPlayedWorks ? ( -
- {hasArchiveEntries ? ( -
- - 可继续 - -
- {saveEntries.map((entry) => ( - onResumeSave(entry)} - /> - ))} -
-
- ) : null} - - {hasPlayedWorks ? ( -
- - 玩过 - -
- {playedWorks.map((work) => ( - onOpenWork?.(work)} - surface="flat" - radius="sm" - padding="md" - interactive - className="w-full hover:border-[#ff4056]" - > -
-
-
- {work.worldTitle} -
- {work.worldSubtitle ? ( -
- {work.worldSubtitle} -
- ) : null} -
- - {formatPlayedWorkType(work.worldType)} - -
-
- - 作品号 {formatPlayedWorkId(work)} - - - 最近 {formatSnapshotTime(work.lastPlayedAt)} - - - 时长{' '} - {formatCompactPlayTime(work.lastObservedPlayTimeMs)} - -
-
- ))} -
-
- ) : null} -
- ) : ( - - 暂无玩过 - - )} +
玩过
+ } + className="mt-2" + > + {formatTotalPlayTimeHours(stats?.totalPlayTimeMs ?? 0)} +
+ + {error ? ( + + {error} + + ) : null} + {saveError ? ( + + {saveError} + + ) : null} + + {isLoading ? ( +
+ {Array.from({ length: 4 }).map((_, index) => ( +
+ ))} +
+ ) : hasArchiveEntries || hasPlayedWorks ? ( +
+ {hasArchiveEntries ? ( +
+ + 可继续 + +
+ {saveEntries.map((entry) => ( + onResumeSave(entry)} + /> + ))} +
+
+ ) : null} + + {hasPlayedWorks ? ( +
+ + 玩过 + +
+ {playedWorks.map((work) => ( + onOpenWork?.(work)} + surface="flat" + radius="sm" + padding="md" + interactive + className="w-full hover:border-[#ff4056]" + > +
+
+
+ {work.worldTitle} +
+ {work.worldSubtitle ? ( +
+ {work.worldSubtitle} +
+ ) : null} +
+ + {formatPlayedWorkType(work.worldType)} + +
+
+ + 作品号 {formatPlayedWorkId(work)} + + + 最近 {formatSnapshotTime(work.lastPlayedAt)} + + + 时长{' '} + {formatCompactPlayTime(work.lastObservedPlayTimeMs)} + +
+
+ ))} +
+
+ ) : null} +
+ ) : ( + + 暂无玩过 + + )}
-
+ ); }