# 账户中心套餐卡片与支付二维码弹窗设计
日期: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。
- 真机微信扫码入账依赖外部支付回调,本轮以「出码 + 刷新接口」为前端验收标准。