From f54c3ee93616ab7911e12385e5a5cee82ea6d4a3 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 10 Jun 2026 20:24:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E4=B8=AA=E4=BA=BA=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E7=8A=B6=E6=80=81=E5=BC=B9=E5=B1=82=E4=B8=8E=E6=89=AB?= =?UTF-8?q?=E7=A0=81=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 PlatformStatusDialog 统一支付结果与确认中状态弹层 新增 PlatformProfileQrScannerModal 统一个人中心扫码面板 RpgEntryHomeView 改用共享组件并删除内联支付与扫码实现 更新 PlatformUiKit 收口文档与团队决策记录 --- .hermes/shared-memory/decision-log.md | 1 + ...】PlatformUiKit弹窗组件收口计划-2026-06-08.md | 1 + .../common/PlatformStatusDialog.test.tsx | 67 +++++ .../common/PlatformStatusDialog.tsx | 154 ++++++++++ .../PlatformProfileQrScannerModal.test.tsx | 144 +++++++++ .../PlatformProfileQrScannerModal.tsx | 196 ++++++++++++ src/components/rpg-entry/RpgEntryHomeView.tsx | 280 ++---------------- 7 files changed, 583 insertions(+), 260 deletions(-) create mode 100644 src/components/common/PlatformStatusDialog.test.tsx create mode 100644 src/components/common/PlatformStatusDialog.tsx create mode 100644 src/components/platform-entry/PlatformProfileQrScannerModal.test.tsx create mode 100644 src/components/platform-entry/PlatformProfileQrScannerModal.tsx diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 9ea92175..586ebef8 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -44,6 +44,7 @@ - 2026-06-10 追加:RPG 首页个人中心的邀请好友 / 填邀请码 / 玩家社区三态弹层统一抽到 `src/components/platform-entry/PlatformProfileReferralModal.tsx`;首页不再内联邀请码规范化、社区二维码卡片和邀请用户头像行,后续 profile 侧同类二级弹层优先按“独立组件 + `PlatformProfileSecondaryModalShell`”继续收口。 - 2026-06-10 追加:RPG 首页个人中心的账户充值弹层统一抽到 `src/components/platform-entry/PlatformProfileRechargeModal.tsx`;充值 tab、套餐卡片、Native 二维码生成和确认支付入口不再内联在 `RpgEntryHomeView`,后续 profile 侧充值入口优先复用同一个组件。 - 2026-06-10 追加:RPG 首页个人中心的泥点账单、每日任务和兑换码弹层统一抽到 `src/components/platform-entry/PlatformProfileWalletLedgerModal.tsx`、`src/components/platform-entry/PlatformProfileTaskCenterModal.tsx` 与 `src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx`;`RpgEntryHomeView` 只保留打开条件和数据流,标准 profile 弹层内容以后优先沉到 `platform-entry` 独立组件,不在首页继续堆叠。 +- 2026-06-10 追加:个人中心支付结果提示与支付确认遮罩统一抽到 `src/components/common/PlatformStatusDialog.tsx`,扫码面板统一抽到 `src/components/platform-entry/PlatformProfileQrScannerModal.tsx`;`RpgEntryHomeView` 只保留支付结果 kind 到 `success / loading / cancel / error` 的映射、确认遮罩开关和扫码结果写回,不再内联 profile 状态弹层壳层、二维码摄像头启动或 `BarcodeDetector` 轮询。后续 profile 侧同类“状态图标 + 标题正文 + 可选主动作”弹层优先复用 `PlatformStatusDialog`,扫码类弹层优先复用 `PlatformProfileQrScannerModal`。 - 2026-06-09 追加:通用输入 Composer 的上传参考图、发送和移除参考图已迁移到 `PlatformIconButton`;图标上传仍使用 `asChild="label"` 保留 label + file input 语义,公共组件会自动写入隐藏文本,确保内嵌 file input 继承可访问名称。 - 2026-06-10 追加:creation-agent composer 的上传文档 / 上传参考图入口使用 `PlatformIconButton` 默认 `platformIcon`;工作台只保留动态 label、title、busy 状态和 picker 回调,发送按钮继续保留主题色动作布局。验证命令:`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformIconButton.test.tsx`。 - 2026-06-10 追加:作品详情顶部返回 / 分享和封面轮播上一张 / 下一张入口使用 `PlatformIconButton variant="platformIcon"`;详情页保留原 `platform-work-detail__*` 局部 class 控制位置和尺寸,点赞、复制三态等专用动作暂不迁移。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformIconButton.test.tsx`。 diff --git a/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md b/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md index cff0c72a..ba453282 100644 --- a/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md +++ b/docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md @@ -240,6 +240,7 @@ 19.3.16. RPG 首页个人中心的邀请好友 / 填邀请码 / 玩家社区三态弹层抽到 `src/components/platform-entry/PlatformProfileReferralModal.tsx`;组件统一复用 `PlatformProfileSecondaryModalShell` 承接居中白底浮层、floatingPlain 关闭按钮和成功 / 失败提示区,`RpgEntryHomeView` 不再内联邀请码规范化、社区二维码卡片和邀请用户头像行。组件级验证新增 `src/components/platform-entry/PlatformProfileReferralModal.test.tsx`,首页继续复用 `RpgEntryHomeView.recharge.test.tsx` 的邀请链路断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "renders invite panel with shared profile content|submits redeem panel with the shared form shell|renders community QR panels|profile community shortcut shows reward subtitle and invited users|invite query opens redeem modal directly for logged in users|profile redeem invite query modal submits code after login"`、`npm run typecheck`。 19.3.17. RPG 首页个人中心的账户充值弹层抽到 `src/components/platform-entry/PlatformProfileRechargeModal.tsx`;组件承接 Native 二维码生成、点数 / 会员 tab、套餐卡片、空态和错误重试,继续复用 `PlatformProfileModalShell` 与平台白底卡片 token,`RpgEntryHomeView` 不再内联 `useWechatNativeQrCode`、`RechargeProductCard` 和 `ProfileRechargeModal`。组件级验证新增 `src/components/platform-entry/PlatformProfileRechargeModal.test.tsx`,首页继续复用 `RpgEntryHomeView.recharge.test.tsx` 的充值入口与 Native 二维码断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "renders point products and forwards buy action|shows empty state when the selected tab has no products|profile recharge modal shows native qr code on desktop web by default|create tab wallet chip opens recharge when recharge entry is enabled"`、`npm run typecheck`。 19.3.18. RPG 首页个人中心的泥点账单、每日任务和兑换码三类标准 profile 弹层分别抽到 `src/components/platform-entry/PlatformProfileWalletLedgerModal.tsx`、`src/components/platform-entry/PlatformProfileTaskCenterModal.tsx` 与 `src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx`;账单继续复用 `PlatformProfileSecondaryModalShell`,任务和兑换码继续复用 `PlatformProfileModalShell`,页面不再内联账单余额 badge、任务领取列表和兑换码输入提交实现。三者均新增组件级测试,并继续复用 `RpgEntryHomeView.recharge.test.tsx` 的真实入口断言。验证命令:`npm run test -- src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "renders ledger entries with shared balance presentation|retries from the shared error state|renders claimable tasks and forwards claim action|keeps incomplete tasks disabled|submits on button click and enter key|disables submit when the code is blank|opens wallet ledger modal from narrative coin card|profile daily task shortcut reflects task progress and claim updates|wallet ledger modal shows empty and error states|opens reward code modal from profile action on mobile|create tab wallet chip opens reward code when recharge entry is hidden"`、`npm run typecheck`。 +19.3.19. RPG 首页个人中心的支付结果提示、支付确认遮罩与扫码面板继续向共享组件收口:支付结果 / 确认中弹层统一抽到 `src/components/common/PlatformStatusDialog.tsx`,扫码面板统一抽到 `src/components/platform-entry/PlatformProfileQrScannerModal.tsx`;`RpgEntryHomeView` 仅保留支付状态映射、扫码打开关闭和结果写回,不再内联 `RechargePaymentResultModal`、`RechargePaymentConfirmationMask`、`ProfileQrScannerModal`、`BarcodeDetector` 启动逻辑和 profile 弹层壳层参数。组件级验证新增 `src/components/common/PlatformStatusDialog.test.tsx` 与 `src/components/platform-entry/PlatformProfileQrScannerModal.test.tsx`,首页继续复用 `RpgEntryHomeView.recharge.test.tsx` 的支付 / 扫码入口断言。验证命令:`npm run test -- src/components/common/PlatformStatusDialog.test.tsx`、`npm run test -- src/components/platform-entry/PlatformProfileQrScannerModal.test.tsx`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile recharge modal jumps to h5 payment on mobile web by default|profile recharge modal posts mini program payment request and reacts to success hash result|profile recharge modal releases submitting state and shows virtual payment failure detail|profile recharge modal eventually shows error text even when hashchange is not dispatched|profile recharge modal resumes virtual payment confirmation when pageshow returns with paid order|profile recharge modal blocks tab navigation while virtual payment confirmation is pending|profile scan action opens camera scanner instead of recharge panel"`、`npm run typecheck`。 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/PlatformStatusDialog.test.tsx b/src/components/common/PlatformStatusDialog.test.tsx new file mode 100644 index 00000000..cebb330d --- /dev/null +++ b/src/components/common/PlatformStatusDialog.test.tsx @@ -0,0 +1,67 @@ +/* @vitest-environment jsdom */ + +import { fireEvent, render, screen } from '@testing-library/react'; +import { expect, test, vi } from 'vitest'; + +import { PlatformStatusDialog } from './PlatformStatusDialog'; + +test('renders result state with description and primary action', () => { + const onClose = vi.fn(); + const onAction = vi.fn(); + + render( + , + ); + + const dialog = screen.getByRole('dialog', { name: '支付成功' }); + const badge = dialog.querySelector('.platform-icon-badge'); + const action = screen.getByRole('button', { name: '知道了' }); + const visibleDescription = dialog.querySelector( + '.mt-3.text-sm.font-semibold.leading-6.text-\\[var\\(--platform-text-soft\\)\\]', + ); + + expect(dialog).toBeTruthy(); + expect(visibleDescription?.textContent).toBe('账户状态已刷新'); + expect(badge?.className).toContain('text-[var(--platform-success-text)]'); + expect(action.className).toContain('platform-primary-button'); + + fireEvent.click(action); + expect(onAction).toHaveBeenCalledTimes(1); +}); + +test('supports blocking confirming state without close action', () => { + const onClose = vi.fn(); + + render( + , + ); + + const dialog = screen.getByRole('dialog', { name: '正在确认支付' }); + const overlay = dialog.parentElement as HTMLElement; + const spinner = dialog.querySelector('.platform-icon-badge svg'); + + expect(overlay.className).toContain('z-[95]'); + expect(spinner?.getAttribute('class')).toContain('animate-spin'); + expect( + screen.queryByRole('button', { name: '关闭' }) || + screen.queryByRole('button', { name: '知道了' }), + ).toBeNull(); + + fireEvent.click(overlay); + fireEvent.keyDown(window, { key: 'Escape' }); + + expect(onClose).not.toHaveBeenCalled(); +}); diff --git a/src/components/common/PlatformStatusDialog.tsx b/src/components/common/PlatformStatusDialog.tsx new file mode 100644 index 00000000..0352a29e --- /dev/null +++ b/src/components/common/PlatformStatusDialog.tsx @@ -0,0 +1,154 @@ +import type { ReactNode } from 'react'; + +import { + AlertCircle, + CheckCircle2, + Loader2, + XCircle, +} from 'lucide-react'; + +import { PlatformActionButton } from './PlatformActionButton'; +import { PlatformIconBadge } from './PlatformIconBadge'; +import { UnifiedModal } from './UnifiedModal'; + +export type PlatformStatusDialogStatus = + | 'success' + | 'cancel' + | 'error' + | 'loading' + | 'confirming'; + +type PlatformStatusDialogAction = { + label: string; + onClick: () => void; + disabled?: boolean; +}; + +type PlatformStatusDialogProps = { + open?: boolean; + status: PlatformStatusDialogStatus; + title: string; + description?: ReactNode; + onClose: () => void; + action?: PlatformStatusDialogAction; + closeDisabled?: boolean; + zIndexClassName?: string; + overlayClassName?: string; + panelClassName?: string; + bodyClassName?: string; + iconClassName?: string; +}; + +type PlatformStatusVisualConfig = { + icon: ReactNode; + iconClassName: string; +}; + +const DEFAULT_OVERLAY_CLASS = + 'platform-profile-modal-overlay bg-slate-950/72 backdrop-blur-xl'; +const DEFAULT_PANEL_CLASS = + 'platform-remap-surface !max-w-sm rounded-[1.4rem]'; +const DEFAULT_BODY_CLASS = 'px-5 pb-5 pt-6 text-center'; + +function getStatusVisualConfig( + status: PlatformStatusDialogStatus, +): PlatformStatusVisualConfig { + switch (status) { + case 'success': + return { + icon: