diff --git a/docs/superpowers/specs/2026-09-01-account-package-cards-qr-pay-design.md b/docs/superpowers/specs/2026-09-01-account-package-cards-qr-pay-design.md new file mode 100644 index 0000000..b61dc69 --- /dev/null +++ b/docs/superpowers/specs/2026-09-01-account-package-cards-qr-pay-design.md @@ -0,0 +1,135 @@ +# 账户中心套餐卡片与支付二维码弹窗设计 + +日期:2026-09-01 +范围:只改 `laic-frontend` 的 `AccountView`(及必要样式),不改后台、不改代理。 + +## 背景 + +账户中心流量购买与 SIM 充值已接上「选套餐 → 创建业务订单 → POST payments」主链路,但 UI 仍有两处缺口: + +1. 套餐选择仍是默认横排 `t-radio` 文本行,视觉上像未完成;卡片网格此前只定了方向,代码未落地。 +2. `POST /v1/account/payments` 已返回 `providerPayload`(实测为 AgPay 二维码 PNG),前端只 toast「请按返回凭证完成支付」,用户看不到码。 + +## 目标 + +1. 流量页与 SIM 充值弹窗的套餐选择改为**卡片网格**(名称 / 容量 / 价格 / 有效期分区;选中高亮)。 +2. 购买并支付 / 去支付成功后弹出**独立支付弹窗**:金额、业务类型、订单号、二维码、关闭;提供「我已支付,刷新状态」。 +3. 流量与 SIM 共用同一支付弹窗逻辑。 +4. 验收:本地看卡片样式;走通流量下单 → 弹窗出码;刷新状态可读支付/订单结果。 + +## 非目标 + +- 不引入微信支付 JS SDK / 原生扫码 SDK。 +- 不做自动轮询支付结果(仅手动刷新)。 +- 不改后端订单/支付协议、不修 `payment_idempotency_key` 空串唯一索引问题。 +- 不 seed SIM 卡/套餐;SIM 无数据时弹窗与卡片样式仍可用,但充值主路径可缺卡。 +- 不改发票、账号安全、概览其它区块。 + +## 现状 + +- 文件:`src/views/AccountView/AccountView.vue` +- 流量套餐:`t-radio-group` + `t-radio`,文案单行 `名称 · GB · ¥x / 天` +- SIM 套餐:充值弹窗内同样横排 radio +- 支付:`createPayment` → `describePayment` toast;`urls.PAYMENT(id)` 已存在但未用于 UI 刷新 +- 原型参考:`jcWeb 3` 通信卡充值有 `carrier-package-grid` 按钮卡;流量页旧稿是按 GB 快捷量,不是套餐卡 + +## 设计 + +### 1. 套餐卡片网格 + +保留 `t-radio-group` / `t-radio` 的无障碍选择与 `v-model`,只改结构与样式: + +```vue + + + {{ pkg.name }} + {{ amountLabel }} + {{ money(pkg.price) }} + 有效期 {{ pkg.validityDays }} 天 + + +``` + +样式要点: + +- 网格:`repeat(auto-fill, minmax(140px, 1fr))`,间距 8–10px +- 卡片:白底、细边框、圆角;选中态蓝边 + 浅蓝底(对齐现有 `--blue` / `#f3f8fc`) +- 隐藏原生 radio 圆点,整卡可点 +- SIM 充值弹窗复用同一 class;字段映射:`amountGb`、`moneyFromFen(saleFeeFen)`、`validityMonths` + +右侧「订单概要」不变;选中卡片后摘要可显示当前套餐金额(可选增强:有则显示,无则保持现文案)。 + +### 2. 支付二维码弹窗 + +新增状态: + +```js +payDialogOpen +payDialogLoading +payDialogRefreshing +payDialogError +payDialogMeta = { + businessType, // traffic_order | sim_recharge_order + businessOrderId, + transactionId, + amountText, + status, + qrSrc, // data:image/png;base64,... 或可直接用于 + title +} +``` + +触发: + +- `createTrafficOrderAndPay` / `payTrafficOrder` +- SIM:`createSimRechargeAndPay` / `payPendingSimOrder` + +成功拿到 payment 后: + +1. 解析 `providerPayload` → `qrSrc` +2. 打开 `t-dialog`(`header=微信支付` / `扫码支付`) +3. toast 可保留一句短提示,或以弹窗为主、toast 降级为失败提示 + +弹窗内容: + +- 金额(大字) +- 业务说明(云媒体流量 / 通信卡充值)+ 订单号 +- 二维码 ``;无图时展示错误文案与「重新拉起支付」 +- 底部:`关闭`、`我已支付,刷新状态` + +刷新: + +- `GET urls.PAYMENT(transactionId)` +- 若 `status === 'paid' | 'success'`:toast 成功,关弹窗,刷新订单/余额/SIM +- 若仍 `processing/unpaid`:更新文案「尚未到账,可稍后重试」 +- 同步刷新对应业务列表(`loadOrders` / `loadSim` / balance) + +### 3. providerPayload 解析 + +实测 payload 为 PNG 二进制经 JSON 字符串化后的内容。前端兼容: + +1. 已是 `data:image/...` → 直接用 +2. 已是 `http(s)://` 或 `/...` URL → 直接用 +3. 看起来像 base64(无空白或标准 base64)→ `data:image/png;base64,` + 原文 +4. 其它:标记无图,弹窗提示「支付已创建,但二维码无法展示」 + +不引入第三方 QR 库。 + +## 验收 + +1. 账户 → 云媒体流量:套餐为卡片网格,选中高亮,不再是单行默认 radio。 +2. 点击「购买并支付」:创建订单 + 支付成功后弹出二维码弹窗,图可见。 +3. 「去支付」同样弹窗。 +4. 「我已支付,刷新状态」会请求支付详情并刷新订单列表。 +5. SIM 充值弹窗套餐同为卡片;有支付结果时共用支付弹窗(无卡时不阻塞流量验收)。 + +## 风险 + +- 若后端偶发返回非 PNG/非 base64 的 payload,弹窗需有降级文案,不能白屏。 +- 取消订单后若仍写入空 `payment_idempotency_key`,可能再次误报「存在未付款订单」——属后端问题,本设计不修;测试前可清空串或取消后置 NULL。 +- 真机微信扫码入账依赖外部支付回调,本轮以「出码 + 刷新接口」为前端验收标准。