补充会员与泥点前端改造技术设计文档

- 新增技术设计文档,记录拷问结论、现状核实、改动清单与验收判据
- docs/README.md 登记该技术设计

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-10-03 12:12:29 +08:00
parent 6b4b248879
commit 484db6cb7b
2 changed files with 303 additions and 0 deletions
@@ -0,0 +1,302 @@
# 【技术设计】会员与泥点前端改造
> 定位:承接 [`【技术设计】泥点三池与会员计费后端设计-2026-10-02`](./【技术设计】泥点三池与会员计费后端设计-2026-10-02.md)(后端契约与接口已落地)
> 与原型 [`充值与会员.html`](../../充值与会员.html),给出前端(`packages/shared` + `src` + `apps/ai-game-creator-shell`)的改造设计。
> 范围:共享充值弹层、钱包入口、三处消费方的 controller 与客户端接口;不含后端实现。
> 状态:**已实施(2026-10-03)**。评审已通过,按 §11 清单落地;实现与验证结果见 §11、§13。
---
## 0. 一句话
把共享充值弹层从「一列泥点商品」升级为与原型一致的「会员与泥点」双 Tab 弹层
(套餐卡 + 月/年切换 + 会员状态 + 补差升级 + 六档充值 + 套餐对比 + 确认页),
金额与权益只读后端目录与报价接口,三处消费方共用同一套组件。
---
## 1. 已确认决策(拷问结论)
| # | 决策 | 结论 |
|---|---|---|
| 1 | 改造层级 | 扩展**共享** `PlatformProfileRechargeModal` 及其 controller,三处消费方共用 |
| 2 | 与原型对齐 | **完全像素级对齐**:两个 Tab、四张套餐卡、对比表、数量估算、确认页都做 |
| 3 | 上线方式 | **直接全量开放**,不加功能开关;会员 UI 对所有用户可见 |
| 4 | 钱包入口 | 改用**原型入口形态**(可用总额 + 行内月度/永久 + 「会员与泥点」按钮) |
| 5 | 入口次级功能 | 保留「使用详情」「兑换码」为次级入口,不因贴原型而砍功能 |
| 6 | 余额池 | 弹层内显示**三池**(每日免费 / 月度 / 永久)并按真实扣减顺序说明 |
| 7 | 金额真源 | **服务端报价**:购买用目录价、升级调 `POST /api/profile/membership/upgrade-quote`,前端不计费 |
| 8 | 确认页 | **做**确认页;**不做**前端倒计时,也**不做**价格 compare / 不一致提示 |
| 9 | 支付方式 | **仅微信**,渠道按运行环境自动解析,不出现支付宝 |
| 10 | 套餐文案 | 价格/每期泥点/模型/并发/标题读后端 catalog;「数量估算」「推荐之选」等营销文案放前端常量 |
| 11 | 消费方范围 | Web 平台入口、图片编辑器、AGC 桌面壳**三处都接** |
| 12 | 文档流程 | 先出本设计文档评审,再动代码 |
| 13 | `review.txt` | 后端评审问题**不作为本次门禁**,本次只做前端 |
---
## 2. 现状(已核实)
- 共享弹层 `packages/shared/src/components/PlatformProfileRechargeModal/index.tsx`(538 行)
只渲染 `center.pointProducts` 一列商品,标题「购买更多泥点」;props 只有
`center / isLoading / error / submittingProductId / nativePayment / onClose / onRetry / onBuy / onConfirmNativePayment / onCloseNativePayment`。
- 同目录 `index.test.tsx` 目前**断言不暴露会员**:`queryByRole('tablist')` 为 `null`、`queryByText('Starter')` 为 `null`,
用例名即 `shows the point-product empty state without exposing membership purchase`。本次必须改这些断言。
- Web 平台入口 `src/components/platform-entry/PlatformEntryActiveFlowShell.tsx` 与
图片编辑器 `src/components/image-editor/ImageCanvasEditorView.tsx` **共用**同一个 controller
`src/components/platform-entry/usePlatformProfileCenterController.ts`(1923 行)→ Web 端只需改一处。
- AGC 桌面壳走独立链路:`apps/ai-game-creator-shell/src/features/app-shell/useAccountWallet.ts`
→ `apps/ai-game-creator-shell/src/services/accountHost.ts`(Tauri `invoke`)
→ `apps/ai-game-creator-shell/src-tauri/src/account_api.rs`(命令注册在 `desktop.rs`)。
- 三端都**还没有**升级报价的客户端方法;Web 端现有客户端为
`getPlatformProfileRechargeCenter` / `createPlatformProfileRechargeOrder(productId, paymentChannel)` /
`confirmWechatPlatformProfileRechargeOrder` / `watchWechatPlatformProfileRechargeOrder`。
- 契约侧 `packages/shared/src/contracts/runtime.ts` 已有
`ProfileMembershipPlan` / `ProfileMembershipCycleKind` / `ProfileMembershipPlanRecord` /
`ProfileMembership` / `ProfileMembershipUpgradeQuote` / `ProfileRechargeCenterResponse`。
- 真实支付渠道只有微信(`wechat_mp` / `wechat_mp_virtual` / `wechat_jsapi` / `wechat_h5` / `wechat_native` / `mock`),
由 `resolveProfileRechargePaymentChannel` 按运行环境解析,**没有支付宝**。
- 会员下单复用 `POST /api/profile/recharge/orders`,`product_id = membership-{plan}-{cycleKind}`
(如 `membership-pro-yearly`),会员商品没有商品配置行,标题与价格来自目录表。
---
## 3. 契约与需要新增的前端接口
### 3.1 复用(不改)
- `GET /api/profile/recharge-center` → `ProfileRechargeCenterResponse`
(`mudPointBalance` 三池 / `membership` / `membershipPlans` / `pointProducts`)。
- `POST /api/profile/recharge/orders { productId, paymentChannel }` → 泥点与会员下单共用。
- `POST /api/profile/recharge/orders/{orderId}/wechat/confirm` → 微信支付确认。
### 3.2 新增(前端缺、后端已有)
Web 端 `src/services/platform-entry/platformProfileClient.ts` 新增:
```ts
export function getPlatformProfileMembershipUpgradeQuote(
targetPlan: ProfileMembershipPlan,
options: PlatformProfileRequestOptions = {},
) {
return requestPlatformProfileJson<ProfileMembershipUpgradeQuote>(
'/membership/upgrade-quote',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ targetPlan }),
},
'读取升级报价失败',
options,
);
}
```
AGC 桌面壳需要新增同名能力:`accountHost.ts` 增加 `getClientProfileMembershipUpgradeQuote(targetPlan)`,
并在 `src-tauri/src/account_api.rs` 新增 `#[tauri::command] read_profile_membership_upgrade_quote(target_plan: String)`,
在 `src-tauri/src/desktop.rs` 的命令清单里注册。这是桌面壳唯一的非纯前端改动。
### 3.3 会员下单 productId
前端按 `membership-${plan}-${cycleKind}`(`yearly` / `monthly`)拼串后走既有下单接口,
不再向后端请求商品行;`plan === 'normal'` 不可下单。
---
## 4. 信息架构与组件拆分
弹层保持「一个 `index.tsx` + 内部子组件」的现有约定,另抽一个纯常量模块。
```
PlatformProfileRechargeModal/
├── index.tsx // Shell / Tab / 余额概览 / 套餐卡 / 周期切换 / 会员状态 / 对比表 / 充值网格 / 确认页 / 成功态 / 微信二维码
├── membershipCopy.ts // 「数量估算」文案、推荐档位、省费标签等纯前端常量
├── index.css // 复用现有 class,按需补样式
└── index.test.tsx
```
信息架构(对齐原型):
```
顶部入口(PlatformMudPointWalletEntry)
└─ 弹层「会员与泥点」
├─ Tab:会员订阅 | 泥点充值
├─ 余额概览(三池:每日免费 / 月度 / 永久 + 各自失效时间)
├─ 会员订阅面板
│ ├─ 当前会员状态(非会员时隐藏)
│ ├─ 月付 / 年付切换(有效期内锁定为当前周期)
│ ├─ 套餐卡 × 4(Starter / Plus / Pro / Max,来自 catalog,enabled 过滤,normal 不出现)
│ ├─ 套餐对比表(4 行 × 4 档)
│ └─ 底栏主 CTA
├─ 泥点充值面板
│ ├─ 六档金额网格(¥6/18/30/68/128/328 ↔ 60/180/300/680/1280/3280 泥点,¥1 = 10 泥点)
│ └─ 底栏「充值 ¥X」
└─ 确认页(弹窗)
├─ 价格与权益明细(购买:目录价 + 首期额度;升级:补差 + 补点 + 有效期不变)
├─ 支付方式(仅微信,自动解析渠道)
└─ 支付成功态
└─ 微信扫码 / 跳转支付(复用现有 native 支付弹窗,不改)
```
### 4.1 新增 props(`PlatformProfileRechargeModalProps`)
```ts
export type PlatformProfileMembershipConfirmState = {
/** 目标档位与周期;normal 不会出现在这里。 */
plan: ProfileMembershipPlan;
cycleKind: ProfileMembershipCycleKind;
/** 首次购买时来自目录价;升级时来自 upgrade-quote。 */
amountCents: number;
/** 服务端返回的补点;首次购买为 0。 */
grantedPointsDelta: number;
/** 升级后的月度余额;首次购买为首期额度。 */
monthlyBalanceAfter: number | null;
mode: 'purchase' | 'upgrade';
};
// 追加到现有 props:
membershipQuote: ProfileMembershipUpgradeQuote | null;
isLoadingMembershipQuote: boolean;
membershipQuoteError: string | null;
membershipConfirm: PlatformProfileMembershipConfirmState | null;
onRequestMembershipQuote: (plan: ProfileMembershipPlan) => void;
onRequestMembershipPurchase: (plan: ProfileMembershipPlan, cycleKind: ProfileMembershipCycleKind) => void;
onCancelMembershipConfirm: () => void;
onConfirmMembershipPayment: () => void;
```
`onBuy(product)` 只服务泥点商品;会员走 `onRequestMembershipQuote` / `onRequestMembershipPurchase`。
---
## 5. 数据流与状态
- 套餐卡的价格、每期泥点、模型权限、并发上限、标题全部读 `center.membershipPlans`
(按 `sortOrder` 升序、`enabled` 过滤、排除 `normal`)。前端**不**存第二份价格。
- 点套餐判定:
- 当前有效会员且 `plan !== 当前档位` 且 `cycleKind === 当前周期` → 调 `onRequestMembershipQuote(plan)`;
- 否则(非会员 / 已到期)→ 直接 `onRequestMembershipPurchase(plan, cycleKind)` 用目录价开确认页。
- 周期切换:有效期内锁定为 `membership.cycleKind`,另一档禁用并提示「到期后可重新选择」。
- 可升级判定用 `rank`(`center.membershipPlans`),高于当前才能升;同档与降档按钮禁用。
- 确认页金额:升级取 `membershipQuote.amountCents`,购买取目录价;**不做**任何本地计费与价格比对。
- 支付:确认页「去支付」→ 控制器用既有 `createPlatformProfileRechargeOrder(productId, channel)`,
渠道走 `resolveProfileRechargePaymentChannel`;桌面/浏览器差异沿用现有 native 二维码弹窗。
- 支付成功后刷新 recharge center;成功态展示本期泥点与三池余额。
---
## 6. 交互细则
- 与原型一致,但**去掉**原型里的本地 2 分钟报价倒计时与「报价已失效」逻辑
(金额真源在后端,下单与支付确认由后端重算)。
- **不做**价格 compare:不在前端比较「页面展示价」与「服务端返回金额」,也不做不一致提示。
- 移动端优先:弹层宽度、套餐卡网格、对比表横向滚动,遵循现有 `PlatformProfileRechargeModalShell` 尺寸约定。
- 可访问性:Tab 用 `role="tablist"` / `role="tab"` / `aria-selected` + 左右方向键;
套餐卡与充值卡用 `role="radio"` + `aria-checked`;确认页与支付弹窗沿用 `role="dialog"`。
---
## 7. 文案与常量(`membershipCopy.ts`)
只放后端没有、且属于营销表达的内容,按 `plan` token 匹配:
- 每档「每月预计可完成」数量估算与说明;
- 「推荐之选」标签(原型为 `plus`);
- 年付「省 ¥X」文案(按目录 `yearPriceCents` 与 `monthPriceCents × 12` 计算,只用于展示);
- 并发文案:`concurrentJobLimit >= 128` 显示「不设套餐上限」,否则「同时运行 N 个独立任务」;
- 模型权限文案:`basic` →「基础模型」,`full` →「基础及高性能模型」。
未知 `plan` token 时给通用兜底文案,不抛错。
---
## 8. 三处消费方接入清单
| 消费方 | 文件 | 改动 |
|---|---|---|
| Web 平台入口 | `src/components/platform-entry/PlatformEntryActiveFlowShell.tsx` | 传新增 props;入口按钮文案改「会员与泥点」 |
| 图片编辑器 | `src/components/image-editor/ImageCanvasEditorView.tsx` | 同上(共用 controller) |
| Web/编辑器 controller | `src/components/platform-entry/usePlatformProfileCenterController.ts` | 新增报价状态、确认状态、会员下单 handler |
| Web 客户端 | `src/services/platform-entry/platformProfileClient.ts` | 新增 `getPlatformProfileMembershipUpgradeQuote` |
| AGC 桌面壳 | `apps/ai-game-creator-shell/src/features/app-shell/useAccountWallet.ts`、`AccountWallet.tsx` | 新增报价状态与会员下单 handler |
| AGC 客户端 | `apps/ai-game-creator-shell/src/services/accountHost.ts` | 新增 `getClientProfileMembershipUpgradeQuote` |
| AGC Tauri | `apps/ai-game-creator-shell/src-tauri/src/account_api.rs`、`desktop.rs` | 新增并注册 `read_profile_membership_upgrade_quote` |
| 共享入口 | `packages/shared/src/components/PlatformMudPointWalletEntry/index.tsx` | 入口形态按原型调整,保留次级入口 |
---
## 9. 测试计划
- 共享弹层(`index.test.tsx`):
- 改写现有两条「不暴露会员」断言为「暴露会员 Tab 与套餐卡」;
- 新增:四档套餐按 `sortOrder` 渲染、`normal` 与 `enabled=false` 不出现;
- 新增:周期切换在有效期内锁定;
- 新增:点套餐触发 `onRequestMembershipQuote`;
- 新增:确认页展示金额与补点,且**不出现**倒计时与价格 compare 文案;
- 新增:六档充值网格与底栏充值按钮触发 `onBuy`/下单;
- 新增:三池余额展示(含每日免费重置值)。
- Web controller:报价成功/失败、会员下单 productId 拼串、支付成功刷新。
- AGC 壳:`accountHost` 新命令的 invoke 参数与 `useAccountWallet` 报价状态。
- 回归:`PlatformEntryActiveFlowShell.test.tsx`、`ImageCanvasEditorView.test.tsx` 中与弹层标题/入口文案相关的断言同步更新。
- 命令:定向 vitest + `npm run check:encoding` + `git diff --check`。
---
## 10. 验收判据
1. 打开「会员与泥点」弹层可见两个 Tab、四张套餐卡、周期切换、套餐对比表与六档充值网格。
2. 套餐价格、每期泥点、模型权限、并发上限与后端 `membershipPlans` 完全一致,前端无第二份价格。
3. 点套餐进确认页:购买显示目录价,升级显示服务端报价金额与补点;确认页无倒计时、无价格不一致提示。
4. 支付方式只有微信,渠道按运行环境解析,不出现支付宝。
5. 会员与泥点下单都走既有 `POST /api/profile/recharge/orders`,会员 productId 为 `membership-{plan}-{cycleKind}`。
6. Web 平台入口、图片编辑器、AGC 桌面壳三处行为一致,入口保留「使用详情」「兑换码」。
7. 三池余额(每日免费 / 月度 / 永久)与失效时间展示正确,弹层与入口不出现 `NaN`。
8. `npm run check:encoding`、定向 vitest、`git diff --check` 通过。
---
## 11. 实施清单(按顺序)
1. `packages/shared/src/contracts/runtime.ts`:如有缺字段补齐(本轮预计只读,不改形状)。
2. `src/services/platform-entry/platformProfileClient.ts`:新增升级报价客户端方法。
3. `packages/shared/.../PlatformProfileRechargeModal/membershipCopy.ts`:新增前端常量。
4. `packages/shared/.../PlatformProfileRechargeModal/index.tsx`:双 Tab + 会员面板 + 充值网格 + 确认页。
5. `packages/shared/.../PlatformMudPointWalletEntry/index.tsx`:入口形态按原型调整。
6. `src/components/platform-entry/usePlatformProfileCenterController.ts`:报价/确认/下单状态与 handler。
7. `PlatformEntryActiveFlowShell.tsx`、`ImageCanvasEditorView.tsx`:传新 props。
8. AGC:`accountHost.ts` + `account_api.rs` + `desktop.rs` + `useAccountWallet.ts` + `AccountWallet.tsx`。
9. 更新与新增测试,跑定向 vitest 与编码检查。
---
## 12. 风险与后续
- **后端已知缺陷**(见 `review.txt`):结算不比对 `order.amount_cents`、报价接口写库、
旧 `tier` 无迁移等。按用户决定**不作为前端门禁**;作为后端跟进项单独处理。
- 确认页只展示服务端金额,不做事前拦截;若后端金额与展示不同,以支付确认结果为准。
- AGC 桌面壳新增 Tauri 命令属于壳内 Rust 改动,需要与桌面端一起回归。
- 「完全像素级对齐」意味着弹层代码量与测试量显著上升,后续可按效果评估是否精简对比表。
---
## 13. 实施与验证结果(2026-10-03)
### 13.1 落地清单
- 共享弹层 `PlatformProfileRechargeModal`:双 Tab、三池余额概览、套餐卡、周期切换(有效期内锁定)、会员状态、套餐对比表、六档充值网格与底栏充值按钮、确认页(仅微信支付,无倒计时、无价格对比);新增 `membershipCopy.ts` 承载营销文案与并发/模型展示口径。
- 钱包入口 `PlatformMudPointWalletEntry`:改为「可用总额 + 行内月度/永久 + 会员与泥点」,`使用详情` / `兑换码` 作为次级入口保留。
- Web:`platformProfileClient.ts` 新增 `getPlatformProfileMembershipUpgradeQuote`;`usePlatformProfileCenterController` 新增报价状态与 `submitMembershipCheckout`;平台入口与图片编辑器已传参。
- AGC 桌面壳:`accountHost.ts` 新增 `getClientProfileMembershipUpgradeQuote`;`account_api.rs` 新增并注册 `read_profile_membership_upgrade_quote`;`useAccountWallet` 新增报价状态与会员结算;`AccountWallet.tsx` 已传参。
### 13.2 验证证据
- `npm run typecheck` 通过(`tsconfig.typecheck-guardrails.json`)。
- `npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
- 定向 vitest:共享弹层 6/6;平台入口 22/22;图片编辑器顶栏 5/5;`usePlatformProfileCenterController.recharge` 8/8;AGC `accountHost` + `walletStore` 17/17。
- `npm run check:encoding` 通过(5249 个文件);`git diff --check` 通过。
- `cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check` 通过。
- ⚠ **未执行 `cargo check`**:仓库无 Rust 构建缓存,冷编译成本过高;新增 Tauri 命令只复用同文件既有 `require_session` / `build_client` / `bounded_business_id` / `request_json`,四个签名已逐一核对,风险集中在类型名拼写。
### 13.3 既有问题(非本次引入)
- `ImageCanvasEditorView.test.tsx > shows the breakdown read failure without inventing rows` 在改动前的基线同样失败(已用 `git stash` 对照验证):弹层打开时仍处于 loading 分支,断言未等待错误态。属既有时序问题,本次未修改该行为。