继续收口视觉小说素材选择弹窗

视觉小说素材选择弹窗复用 PlatformToolModalShell

保留上传、AI生成、历史素材读取和选择回调

补充测试断言共享白底工具弹窗壳层

同步 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
2026-06-11 17:22:29 +08:00
parent 9b40f6b453
commit 291ab06a5b
4 changed files with 98 additions and 117 deletions

View File

@@ -2100,6 +2100,7 @@
- 决策RPG runtime overlay 里的标准 dark CTA 和可点击 dark row 也继续纳入这条收口线。`RpgAdventurePanelOverlays.tsx` 的 goal panel“知道了”、任务详情里的“领取任务 / 返回交付”、任务完成提示里的“打开任务日志”都改为委托 `PlatformActionButton surface="editorDark"`;设置面板里的“运行统计”入口改为 `PlatformSubpanel as="button" surface="dark"`。像素风 choice button、HUD launcher、奖励物品格和输入 composer 保持 runtime 专属语义,不继续硬并到普通平台按钮。 - 决策RPG runtime overlay 里的标准 dark CTA 和可点击 dark row 也继续纳入这条收口线。`RpgAdventurePanelOverlays.tsx` 的 goal panel“知道了”、任务详情里的“领取任务 / 返回交付”、任务完成提示里的“打开任务日志”都改为委托 `PlatformActionButton surface="editorDark"`;设置面板里的“运行统计”入口改为 `PlatformSubpanel as="button" surface="dark"`。像素风 choice button、HUD launcher、奖励物品格和输入 composer 保持 runtime 专属语义,不继续硬并到普通平台按钮。
- 决策:`PlatformToolModalShell` 继续承接 RPG 结果页发布检查弹窗;`RpgCreationResultActionBar.tsx` 只保留发布检查、封面预览、封面设置和发布动作语义,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close、body/footer spacing 和遮罩关闭逻辑。后续结果页 / 工具页里同形态的白底 portal 弹窗优先迁移到 `PlatformToolModalShell`;编辑器大壳、暗色 runtime overlay 和需要专属布局的面板继续保留局部 shell。 - 决策:`PlatformToolModalShell` 继续承接 RPG 结果页发布检查弹窗;`RpgCreationResultActionBar.tsx` 只保留发布检查、封面预览、封面设置和发布动作语义,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close、body/footer spacing 和遮罩关闭逻辑。后续结果页 / 工具页里同形态的白底 portal 弹窗优先迁移到 `PlatformToolModalShell`;编辑器大壳、暗色 runtime overlay 和需要专属布局的面板继续保留局部 shell。
- 决策:`PlatformToolModalShell` 继续承接方洞结果页图片槽弹窗;`SquareHoleResultView.tsx` 的封面 / 背景 / 形状 / 洞口图片查看与历史选择弹窗只保留当前图、上传、AI 生成和历史素材选择语义,不再直接维护 `createPortal`、主题 overlay、白底 remap panel、header close 和滚动 body。该弹窗使用 `ariaLabel` 保持“封面图查看 / 背景图查看”等固定可访问名称,历史生成区继续由 `PlatformAssetPickerGrid` 承接读取、错误和空态。 - 决策:`PlatformToolModalShell` 继续承接方洞结果页图片槽弹窗;`SquareHoleResultView.tsx` 的封面 / 背景 / 形状 / 洞口图片查看与历史选择弹窗只保留当前图、上传、AI 生成和历史素材选择语义,不再直接维护 `createPortal`、主题 overlay、白底 remap panel、header close 和滚动 body。该弹窗使用 `ariaLabel` 保持“封面图查看 / 背景图查看”等固定可访问名称,历史生成区继续由 `PlatformAssetPickerGrid` 承接读取、错误和空态。
- 决策:`PlatformToolModalShell` 继续承接视觉小说结果页素材选择弹窗;`VisualNovelAssetPickerDialog` 只保留本地上传、AI 图片生成、历史素材读取、错误提示和素材选择回调,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close 和滚动 body。视觉小说音频生成弹窗需要保留生成中禁止关闭实体编辑器弹窗需要保留编辑 footer后续逐个迁移并补对应交互测试。
- 决策NPC dark modal footer 和暗色明细空态也继续纳入同一条收口线。`NpcModals.tsx` 里的交易 / 赠礼 / 招募弹窗 footer 按钮和物品详情“关闭”按钮都改为委托 `PlatformActionButton surface="editorDark"`,交易右侧“请选择一件物品”提示改为 `PlatformEmptyState surface="editorDark"``CharacterInfoShared.tsx``BuildContributionDetailPanel` 空明细也改为 `PlatformEmptyState surface="editorDark"`。数量 stepper、赠礼 / 招募 option card、标签强度按钮这类带独立业务语义的控件继续保留局部实现。 - 决策NPC dark modal footer 和暗色明细空态也继续纳入同一条收口线。`NpcModals.tsx` 里的交易 / 赠礼 / 招募弹窗 footer 按钮和物品详情“关闭”按钮都改为委托 `PlatformActionButton surface="editorDark"`,交易右侧“请选择一件物品”提示改为 `PlatformEmptyState surface="editorDark"``CharacterInfoShared.tsx``BuildContributionDetailPanel` 空明细也改为 `PlatformEmptyState surface="editorDark"`。数量 stepper、赠礼 / 招募 option card、标签强度按钮这类带独立业务语义的控件继续保留局部实现。
- 决策:详情页头部动作组合统一收口到 `src/components/common/PlatformDetailTopbar.tsx``src/components/common/PlatformDetailShareActions.tsx``PlatformDetailTopbar` 只负责返回按钮、标题居中槽位和右侧动作槽位的布局,可在 `pill` / `icon` 返回入口之间切换;`PlatformDetailShareActions` 只负责“前置 badge 区块 + 作品号复制 + 分享复制”这组稳定动作,并允许按页面关闭复制或分享其中一项。`RpgEntryWorldDetailView.tsx` 已接入 overlay 版完整动作组,`PlatformWorkDetailView.tsx` 已接入 icon topbar 与 solid 版作品号复制动作,同时继续保留公开详情页自己的顶部 icon 分享入口和分享反馈提示。后续详情页若只是复用返回、标题、作品号复制或分享动作排列,优先组合这两个薄组件,不把作者、摘要、封面、轮播或业务 CTA 塞进共享配置对象。 - 决策:详情页头部动作组合统一收口到 `src/components/common/PlatformDetailTopbar.tsx``src/components/common/PlatformDetailShareActions.tsx``PlatformDetailTopbar` 只负责返回按钮、标题居中槽位和右侧动作槽位的布局,可在 `pill` / `icon` 返回入口之间切换;`PlatformDetailShareActions` 只负责“前置 badge 区块 + 作品号复制 + 分享复制”这组稳定动作,并允许按页面关闭复制或分享其中一项。`RpgEntryWorldDetailView.tsx` 已接入 overlay 版完整动作组,`PlatformWorkDetailView.tsx` 已接入 icon topbar 与 solid 版作品号复制动作,同时继续保留公开详情页自己的顶部 icon 分享入口和分享反馈提示。后续详情页若只是复用返回、标题、作品号复制或分享动作排列,优先组合这两个薄组件,不把作者、摘要、封面、轮播或业务 CTA 塞进共享配置对象。
- 验证方式:`npm run test -- src/components/common/PlatformAsyncStatePanel.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/common/PlatformSegmentedTabs.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx``npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/PixelCloseButton.test.tsx src/components/CharacterChatModal.test.tsx src/components/MapModal.test.tsx``npm run test -- src/components/common/useMudPointConfirmController.test.tsx src/components/match3d-result/Match3DResultView.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/rpg-creation-result/RpgCreationResultActionBar.test.tsx src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx``npm run test -- src/components/common/CopyFeedbackButton.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx src/components/auth/AccountModal.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.npcChat.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx``npm run typecheck``npm run check:encoding``git diff --check` - 验证方式:`npm run test -- src/components/common/PlatformAsyncStatePanel.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/common/PlatformSegmentedTabs.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx``npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/PixelCloseButton.test.tsx src/components/CharacterChatModal.test.tsx src/components/MapModal.test.tsx``npm run test -- src/components/common/useMudPointConfirmController.test.tsx src/components/match3d-result/Match3DResultView.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/rpg-creation-result/RpgCreationResultActionBar.test.tsx src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx``npm run test -- src/components/common/CopyFeedbackButton.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx src/components/auth/AccountModal.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.npcChat.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx``npm run typecheck``npm run check:encoding``git diff --check`

View File

@@ -261,6 +261,7 @@
19.3.35. 详情页头部动作组合收口到 `src/components/common/PlatformDetailTopbar.tsx``src/components/common/PlatformDetailShareActions.tsx`;前者只承接“返回 / 标题 / 右侧动作槽位”的 topbar 骨架,并允许 `pill` / `icon` 两种返回按钮语义,后者只承接“前置 badge 区块 + 作品号复制 + 分享复制”这一组稳定动作,不吸收详情页自己的标题、摘要、作者、封面轮播或业务 CTA。`RpgEntryWorldDetailView.tsx` 已接入完整的 overlay 版动作组合,统一世界主题 badge、作者、发布时间、作品号和分享入口`PlatformWorkDetailView.tsx` 已接入 icon topbar 与 solid 版作品号复制动作,并继续保留公开详情页自己的顶部 icon 分享入口和分享反馈提示。后续同类详情页若只是复用返回按钮骨架、标题居中布局或作品号 / 分享动作排列,优先直接组合这两个 Module不要把整页 detail header 抽成巨型配置对象。验证命令:`npx vitest run src/components/common/PlatformDetailTopbar.test.tsx src/components/common/PlatformDetailShareActions.test.tsx src/components/rpg-entry/RpgEntryWorldDetailView.test.tsx src/components/platform-entry/PlatformWorkDetailView.test.tsx``npm run typecheck``npm run check:encoding``git diff --check -- src/components/common/PlatformDetailTopbar.tsx src/components/common/PlatformDetailTopbar.test.tsx src/components/common/PlatformDetailShareActions.tsx src/components/common/PlatformDetailShareActions.test.tsx src/components/rpg-entry/RpgEntryWorldDetailView.tsx src/components/platform-entry/PlatformWorkDetailView.tsx` 19.3.35. 详情页头部动作组合收口到 `src/components/common/PlatformDetailTopbar.tsx``src/components/common/PlatformDetailShareActions.tsx`;前者只承接“返回 / 标题 / 右侧动作槽位”的 topbar 骨架,并允许 `pill` / `icon` 两种返回按钮语义,后者只承接“前置 badge 区块 + 作品号复制 + 分享复制”这一组稳定动作,不吸收详情页自己的标题、摘要、作者、封面轮播或业务 CTA。`RpgEntryWorldDetailView.tsx` 已接入完整的 overlay 版动作组合,统一世界主题 badge、作者、发布时间、作品号和分享入口`PlatformWorkDetailView.tsx` 已接入 icon topbar 与 solid 版作品号复制动作,并继续保留公开详情页自己的顶部 icon 分享入口和分享反馈提示。后续同类详情页若只是复用返回按钮骨架、标题居中布局或作品号 / 分享动作排列,优先直接组合这两个 Module不要把整页 detail header 抽成巨型配置对象。验证命令:`npx vitest run src/components/common/PlatformDetailTopbar.test.tsx src/components/common/PlatformDetailShareActions.test.tsx src/components/rpg-entry/RpgEntryWorldDetailView.test.tsx src/components/platform-entry/PlatformWorkDetailView.test.tsx``npm run typecheck``npm run check:encoding``git diff --check -- src/components/common/PlatformDetailTopbar.tsx src/components/common/PlatformDetailTopbar.test.tsx src/components/common/PlatformDetailShareActions.tsx src/components/common/PlatformDetailShareActions.test.tsx src/components/rpg-entry/RpgEntryWorldDetailView.tsx src/components/platform-entry/PlatformWorkDetailView.tsx`
19.3.36. `PlatformToolModalShell` 继续承接 RPG 结果页发布检查弹窗;`RpgCreationResultActionBar.tsx` 只保留发布检查、封面预览、封面设置和发布动作语义,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close、body/footer spacing 和遮罩关闭逻辑。后续结果页 / 工具页里同形态的白底 portal 弹窗优先迁移到 `PlatformToolModalShell`;编辑器大壳、暗色 runtime overlay 和需要专属布局的面板继续保留局部 shell。 19.3.36. `PlatformToolModalShell` 继续承接 RPG 结果页发布检查弹窗;`RpgCreationResultActionBar.tsx` 只保留发布检查、封面预览、封面设置和发布动作语义,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close、body/footer spacing 和遮罩关闭逻辑。后续结果页 / 工具页里同形态的白底 portal 弹窗优先迁移到 `PlatformToolModalShell`;编辑器大壳、暗色 runtime overlay 和需要专属布局的面板继续保留局部 shell。
19.3.37. `PlatformToolModalShell` 继续承接方洞结果页图片槽弹窗;`SquareHoleResultView.tsx` 的封面 / 背景 / 形状 / 洞口图片查看与历史选择弹窗只保留当前图、上传、AI 生成和历史素材选择语义,不再直接维护 `createPortal`、主题 overlay、白底 remap panel、header close 和滚动 body。该弹窗使用 `ariaLabel` 保持“封面图查看 / 背景图查看”等固定可访问名称,历史生成区继续由 `PlatformAssetPickerGrid` 承接读取、错误和空态。 19.3.37. `PlatformToolModalShell` 继续承接方洞结果页图片槽弹窗;`SquareHoleResultView.tsx` 的封面 / 背景 / 形状 / 洞口图片查看与历史选择弹窗只保留当前图、上传、AI 生成和历史素材选择语义,不再直接维护 `createPortal`、主题 overlay、白底 remap panel、header close 和滚动 body。该弹窗使用 `ariaLabel` 保持“封面图查看 / 背景图查看”等固定可访问名称,历史生成区继续由 `PlatformAssetPickerGrid` 承接读取、错误和空态。
19.3.38. `PlatformToolModalShell` 继续承接视觉小说结果页素材选择弹窗;`VisualNovelAssetPickerDialog` 只保留本地上传、AI 图片生成、历史素材读取、错误提示和素材选择回调,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close 和滚动 body。视觉小说音频生成弹窗需要保留生成中禁止关闭实体编辑器弹窗需要保留编辑 footer本轮先不混入同一提交后续逐个迁移并补对应交互测试。
19.3.35. 白底 / 暗色面板里的轻量空态和普通 CTA 继续按共享组件收口:`PuzzleResultView.tsx` 的“还没有可编辑的拼图草稿”、`RpgCreationAssetDebugPanel.tsx` 的“没有可诊断项”、`VisualNovelEntityGrid` 的空实体列表、`AccountModal.tsx` 里账号安全分区的“无安全限制 / 无登录设备 / 无操作记录”以及 `LoginScreen.tsx` 的“当前登录入口暂不可用”都改为 `PlatformEmptyState``Match3DResultView.tsx` 的引用素材列表直接交给 `PlatformAssetPickerGrid` 自己处理空态。`AdventureEntityModal.tsx` 的私聊按钮、`InventoryPanel.tsx` 的锻造 / 合成按钮、`RpgAdventurePanel.tsx` 底部 `队伍 / 背包 / 换一换 / 退出聊天` 按钮,以及 `RpgAdventurePanelOverlays.tsx` 里的“查看任务 / 保存并退出”都改为 `PlatformActionButton surface="editorDark"`,业务页只贴回局部 sky / emerald / runtime 皮肤。后续白底子面板里的只读空态优先使用 `PlatformEmptyState surface="subpanel"`;暗色编辑 / 运行面板里的普通动作优先使用 `PlatformActionButton surface="editorDark"`,若还需要 stopPropagation、局部字号或图标排版可保留薄包装层但不要再回退到原生 `<button>` 基础 chrome。验证命令`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/auth/AccountModal.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.npcChat.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformActionButton.test.tsx``npm run check:encoding` 19.3.35. 白底 / 暗色面板里的轻量空态和普通 CTA 继续按共享组件收口:`PuzzleResultView.tsx` 的“还没有可编辑的拼图草稿”、`RpgCreationAssetDebugPanel.tsx` 的“没有可诊断项”、`VisualNovelEntityGrid` 的空实体列表、`AccountModal.tsx` 里账号安全分区的“无安全限制 / 无登录设备 / 无操作记录”以及 `LoginScreen.tsx` 的“当前登录入口暂不可用”都改为 `PlatformEmptyState``Match3DResultView.tsx` 的引用素材列表直接交给 `PlatformAssetPickerGrid` 自己处理空态。`AdventureEntityModal.tsx` 的私聊按钮、`InventoryPanel.tsx` 的锻造 / 合成按钮、`RpgAdventurePanel.tsx` 底部 `队伍 / 背包 / 换一换 / 退出聊天` 按钮,以及 `RpgAdventurePanelOverlays.tsx` 里的“查看任务 / 保存并退出”都改为 `PlatformActionButton surface="editorDark"`,业务页只贴回局部 sky / emerald / runtime 皮肤。后续白底子面板里的只读空态优先使用 `PlatformEmptyState surface="subpanel"`;暗色编辑 / 运行面板里的普通动作优先使用 `PlatformActionButton surface="editorDark"`,若还需要 stopPropagation、局部字号或图标排版可保留薄包装层但不要再回退到原生 `<button>` 基础 chrome。验证命令`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/auth/AccountModal.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.npcChat.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformActionButton.test.tsx``npm run check:encoding`
19.3.36. `VisualNovelEntityGrid` 的空态也继续收口到 `PlatformEmptyState surface="subpanel" size="inline"`;角色 / 场景 / 剧情阶段共用这一网格组件后,白底实体列表里的“暂无角色 / 暂无场景 / 暂无剧情阶段”等同构空态不再回退成 `PlatformSubpanel`。同时,`RpgCreationRoleAssetStudioModalImpl.tsx``RpgCreationEntityEditorShared.tsx` 保留局部 `ActionButton` 语义壳,但按钮本体已统一委托给 `PlatformActionButton surface="editorDark"`,只在包装层补最小的 `stopPropagation`、tone 映射和局部 class 适配。后续类似“暗色编辑器局部包装按钮”优先沿用这种薄包装模式,不再直接手写原生 `<button>` 基础 chrome。验证命令`npm run test -- src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformActionButton.test.tsx``npm run check:encoding` 19.3.36. `VisualNovelEntityGrid` 的空态也继续收口到 `PlatformEmptyState surface="subpanel" size="inline"`;角色 / 场景 / 剧情阶段共用这一网格组件后,白底实体列表里的“暂无角色 / 暂无场景 / 暂无剧情阶段”等同构空态不再回退成 `PlatformSubpanel`。同时,`RpgCreationRoleAssetStudioModalImpl.tsx``RpgCreationEntityEditorShared.tsx` 保留局部 `ActionButton` 语义壳,但按钮本体已统一委托给 `PlatformActionButton surface="editorDark"`,只在包装层补最小的 `stopPropagation`、tone 映射和局部 class 适配。后续类似“暗色编辑器局部包装按钮”优先沿用这种薄包装模式,不再直接手写原生 `<button>` 基础 chrome。验证命令`npm run test -- src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformActionButton.test.tsx``npm run check:encoding`
19.3.37. 暗色编辑器里的局部动作按钮继续往共享 `editorDark` button 收口:`CustomWorldNpcVisualEditor.tsx` 的本地 `ActionButton``SkillEffectPreview.tsx` 的“重新预览”按钮都改为委托 `PlatformActionButton surface="editorDark"`。这类局部包装仍可保留 `stopPropagation`、图标布局、`tone` 映射和少量局部视觉覆写,但按钮本体不再直接使用原生 `<button>` 承接边框 / 底色 / hover / disabled chrome。验证命令`npm run test -- src/components/common/PlatformActionButton.test.tsx``npm run typecheck``npm run check:encoding``git diff --check` 19.3.37. 暗色编辑器里的局部动作按钮继续往共享 `editorDark` button 收口:`CustomWorldNpcVisualEditor.tsx` 的本地 `ActionButton``SkillEffectPreview.tsx` 的“重新预览”按钮都改为委托 `PlatformActionButton surface="editorDark"`。这类局部包装仍可保留 `stopPropagation`、图标布局、`tone` 映射和少量局部视觉覆写,但按钮本体不再直接使用原生 `<button>` 承接边框 / 底色 / hover / disabled chrome。验证命令`npm run test -- src/components/common/PlatformActionButton.test.tsx``npm run typecheck``npm run check:encoding``git diff --check`

View File

@@ -113,6 +113,11 @@ test('visual novel upload-only asset picker uses PlatformEmptyState chrome', asy
const uploadOnlyEmptyState = const uploadOnlyEmptyState =
within(pickerDialog).getByText('选择本地文件上传'); within(pickerDialog).getByText('选择本地文件上传');
expect(pickerDialog.className).toContain('platform-remap-surface');
expect(pickerDialog.className).toContain(
'shadow-[0_24px_80px_rgba(0,0,0,0.55)]',
);
expect(pickerDialog.className).toContain('!max-w-4xl');
expect(uploadOnlyEmptyState.className).toContain('border-dashed'); expect(uploadOnlyEmptyState.className).toContain('border-dashed');
expect(uploadOnlyEmptyState.className).toContain('rounded-[1.35rem]'); expect(uploadOnlyEmptyState.className).toContain('rounded-[1.35rem]');
expect(uploadOnlyEmptyState.className).toContain('min-h-24'); expect(uploadOnlyEmptyState.className).toContain('min-h-24');

View File

@@ -51,6 +51,7 @@ import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs'; import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel'; import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
import { import {
PlatformSelectField, PlatformSelectField,
PlatformTextField, PlatformTextField,
@@ -409,7 +410,6 @@ function VisualNovelAssetPickerDialog({
onSelect: (asset: VisualNovelAssetReference) => void; onSelect: (asset: VisualNovelAssetReference) => void;
}) { }) {
const authUi = useAuthUi(); const authUi = useAuthUi();
const platformTheme = authUi?.platformTheme ?? 'light';
const fileInputRef = useRef<HTMLInputElement | null>(null); const fileInputRef = useRef<HTMLInputElement | null>(null);
const [historyAssets, setHistoryAssets] = useState< const [historyAssets, setHistoryAssets] = useState<
VisualNovelAssetReference[] VisualNovelAssetReference[]
@@ -527,126 +527,100 @@ function VisualNovelAssetPickerDialog({
} }
}; };
if (typeof document === 'undefined') { return (
return null; <PlatformToolModalShell
} open
title={config.title}
return createPortal( onClose={onClose}
<div closeLabel="关闭"
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[170] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`} zIndexClassName="z-[170]"
onClick={(event) => { size="xl"
if (event.target === event.currentTarget) { panelClassName="!max-h-[min(92vh,42rem)] !max-w-4xl"
onClose();
}
}}
> >
<section <div className="mb-4 flex flex-wrap gap-2">
role="dialog" <PlatformActionButton
aria-modal="true" tone="secondary"
aria-label={config.title} disabled={disabled || isUploading || isGeneratingImage}
className="platform-modal-shell platform-remap-surface flex max-h-[min(92vh,42rem)] w-full max-w-4xl flex-col overflow-hidden rounded-t-[1.45rem] sm:rounded-[1.45rem]" onClick={() => fileInputRef.current?.click()}
onClick={(event) => event.stopPropagation()} className="min-h-10"
> >
<header className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-4 py-3"> {isUploading ? (
<h2 className="min-w-0 truncate text-base font-black text-[var(--platform-text-strong)]"> <Loader2 className="h-4 w-4 animate-spin" />
{config.title} ) : (
</h2> <Upload className="h-4 w-4" />
<PlatformModalCloseButton )}
label="关闭"
variant="platformIcon" </PlatformActionButton>
className="h-9 w-9" {config.imageGeneratorConfig && config.previewTone === 'image' ? (
onClick={onClose} <PlatformActionButton
title="关闭" disabled={disabled || isUploading || isGeneratingImage}
/> onClick={() => {
</header> void handleGenerateImage();
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-4"> }}
<div className="mb-4 flex flex-wrap gap-2"> className="min-h-10"
<PlatformActionButton >
tone="secondary" {isGeneratingImage ? (
disabled={disabled || isUploading || isGeneratingImage} <Loader2 className="h-4 w-4 animate-spin" />
onClick={() => fileInputRef.current?.click()} ) : (
className="min-h-10" <Sparkles className="h-4 w-4" />
> )}
{isUploading ? ( AI生成
<Loader2 className="h-4 w-4 animate-spin" /> </PlatformActionButton>
) : ( ) : null}
<Upload className="h-4 w-4" /> <input
)} ref={fileInputRef}
type="file"
</PlatformActionButton> accept={config.accept}
{config.imageGeneratorConfig && config.previewTone === 'image' ? ( disabled={disabled || isUploading || isGeneratingImage}
<PlatformActionButton onChange={(event) => {
disabled={disabled || isUploading || isGeneratingImage} void handleUpload(event);
onClick={() => { }}
void handleGenerateImage(); className="hidden"
}} aria-label={`上传${config.title}文件`}
className="min-h-10" />
> </div>
{isGeneratingImage ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Sparkles className="h-4 w-4" />
)}
AI生成
</PlatformActionButton>
) : null}
<input
ref={fileInputRef}
type="file"
accept={config.accept}
disabled={disabled || isUploading || isGeneratingImage}
onChange={(event) => {
void handleUpload(event);
}}
className="hidden"
aria-label={`上传${config.title}文件`}
/>
</div>
{!config.historyKind ? ( {!config.historyKind ? (
<PlatformEmptyState <PlatformEmptyState
surface="dashed" surface="dashed"
size="inline" size="inline"
tone="base" tone="base"
className="mb-4 min-h-24" className="mb-4 min-h-24"
> >
</PlatformEmptyState> </PlatformEmptyState>
) : null} ) : null}
{error ? ( {error ? (
<PlatformStatusMessage <PlatformStatusMessage
tone="error" tone="error"
surface="platform" surface="platform"
size="md" size="md"
className="mb-4 rounded-2xl" className="mb-4 rounded-2xl"
> >
{error} {error}
</PlatformStatusMessage> </PlatformStatusMessage>
) : null} ) : null}
{config.historyKind ? ( {config.historyKind ? (
<PlatformAssetPickerGrid <PlatformAssetPickerGrid
items={historyAssets} items={historyAssets}
isLoading={isLoadingHistory} isLoading={isLoadingHistory}
error={null} error={null}
loadingLabel="读取中..." loadingLabel="读取中..."
emptyLabel="暂无历史素材" emptyLabel="暂无历史素材"
disabled={disabled} disabled={disabled}
getKey={(asset) => asset.assetObjectId} getKey={(asset) => asset.assetObjectId}
getImageSrc={(asset) => asset.imageSrc} getImageSrc={(asset) => asset.imageSrc}
getImageAlt={(asset) => asset.ownerLabel || '历史素材'} getImageAlt={(asset) => asset.ownerLabel || '历史素材'}
getTitle={(asset) => asset.ownerLabel || '未记录账号'} getTitle={(asset) => asset.ownerLabel || '未记录账号'}
getSubtitle={(asset) => formatHistoryAssetDate(asset.createdAt)} getSubtitle={(asset) => formatHistoryAssetDate(asset.createdAt)}
onSelect={onSelect} onSelect={onSelect}
gridClassName="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4" gridClassName="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4"
emptyClassName="min-h-40 rounded-[1.15rem] bg-white/56" emptyClassName="min-h-40 rounded-[1.15rem] bg-white/56"
/> />
) : null} ) : null}
</div> </PlatformToolModalShell>
</section>
</div>,
document.body,
); );
} }