309598bfa6
- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、 siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法 - 添加帮助文章和站点资讯的参数验证逻辑 - 更新测试文件添加新的API方法测试用例 - 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用 - 更新加入家谱页面为完整的申请流程界面 - 修改资讯详情页面为站点资讯展示页面 - 更新AxiosRequestUtil中认证处理逻辑 - 添加世系树渲染的HTML生成函数用于页面复用 - 更新文档中的API契约说明和页面规划
250 lines
7.8 KiB
Markdown
250 lines
7.8 KiB
Markdown
# VIP 套餐与订单 Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 在服务中心开放 VIP 套餐查看、响应选项创建订单和我的订单刷新闭环,不制造支付或取消能力。
|
||
|
||
**Architecture:** `utils/ApiClient.js` 唯一拥有 `/genealogy/pc/vip/**` 的三个接口;新增 `vip-pages.js` 规范化套餐、家谱选项和订单,使用响应中的字符串 ID 构造 `AppVipOrderBody`。`profile-services.html` 作为唯一 VIP 页面,只提供套餐选择、可选家谱选择、创建订单和订单刷新。
|
||
|
||
**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`。
|
||
|
||
## Global Constraints
|
||
|
||
- 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。
|
||
- 只调用 `GET /genealogy/pc/vip/packages`、`POST /genealogy/pc/vip/orders`、`GET /genealogy/pc/vip/orders`。
|
||
- `packageId` 只能来自套餐响应;`genealogyId` 只能来自 `genealogiesMine()` 响应,不提供文本输入。
|
||
- 页面不提供未经确认的支付方式选择;请求省略 `payType`,由后端默认 `wechat`。
|
||
- 不显示立即支付、模拟支付成功、取消订单、退款或关闭订单操作。
|
||
- 创建成功后重读订单列表并精确匹配同一 `orderId`。
|
||
- 不展示 `appUserId`、用户手机号、内部原始 JSON。
|
||
|
||
---
|
||
|
||
### Task 1: ApiClient VIP 契约
|
||
|
||
**Files:**
|
||
- Modify: `utils/ApiClient.js`
|
||
- Modify: `tests/api-client-contract.test.js`
|
||
|
||
**Interfaces:**
|
||
- Produces: `vipPackages()`、`createVipOrder(body)`、`vipOrders()`
|
||
|
||
- [ ] **Step 1: 写失败契约测试**
|
||
|
||
```js
|
||
await client.vipPackages();
|
||
await client.createVipOrder({
|
||
packageId: '2062179707935264769',
|
||
genealogyId: '2062179707935264770',
|
||
payType: 'wechat',
|
||
appUserId: 'must-drop',
|
||
payStatus: 'must-drop'
|
||
});
|
||
await client.vipOrders();
|
||
|
||
assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [
|
||
['get', '/genealogy/pc/vip/packages', undefined],
|
||
['post', '/genealogy/pc/vip/orders', {
|
||
packageId: '2062179707935264769',
|
||
genealogyId: '2062179707935264770',
|
||
payType: 'wechat'
|
||
}],
|
||
['get', '/genealogy/pc/vip/orders', undefined]
|
||
]);
|
||
```
|
||
|
||
- [ ] **Step 2: 运行 RED**
|
||
|
||
Run: `node --test tests/api-client-contract.test.js`
|
||
|
||
Expected: FAIL,VIP 方法不存在。
|
||
|
||
- [ ] **Step 3: 最小实现**
|
||
|
||
```js
|
||
vipPackages: function () {
|
||
return request('GET', '/genealogy/pc/vip/packages');
|
||
},
|
||
createVipOrder: function (body) {
|
||
return request('POST', '/genealogy/pc/vip/orders', {
|
||
body: pickDefined(body, ['packageId', 'genealogyId', 'payType'])
|
||
});
|
||
},
|
||
vipOrders: function () {
|
||
return request('GET', '/genealogy/pc/vip/orders');
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 运行 GREEN**
|
||
|
||
Run: `node --test tests/api-client-contract.test.js`
|
||
|
||
Expected: PASS。
|
||
|
||
---
|
||
|
||
### Task 2: 套餐、家谱选项和订单数据边界
|
||
|
||
**Files:**
|
||
- Create: `public/js/vip-pages.js`
|
||
- Create: `tests/vip-pages.test.js`
|
||
|
||
**Interfaces:**
|
||
- Produces:
|
||
- `normalizeVipPackage(item)`
|
||
- `normalizeVipPackages(data)`
|
||
- `normalizeGenealogyOption(item)`
|
||
- `buildVipOrderBody(values)`
|
||
- `validateVipOrderBody(body)`
|
||
- `normalizeVipOrder(item)`
|
||
- `normalizeVipOrders(data)`
|
||
- `renderVipPackages(data, selectedPackageId)`
|
||
- `renderGenealogyOptions(data)`
|
||
- `renderVipOrders(data)`
|
||
|
||
- [ ] **Step 1: 写失败纯行为测试**
|
||
|
||
使用完整 `VipPackageVo`、`VipOrderVo` 字面量断言:
|
||
|
||
- 套餐要求稳定字符串 `packageId`、非空名称、`packageType=vip/storage`、`durationUnit=permanent/day/month/year`、非负价格、`status=0`;
|
||
- 停用套餐和不安全数字长 ID 不渲染;
|
||
- 家谱选项要求稳定字符串 `genealogyId` 和非空 `genealogyName`;
|
||
- body 只保留 `packageId/genealogyId/payType`,页面默认构造不包含 `payType`;
|
||
- 必须选择有效套餐;可选家谱必须是安全字符串 ID;若显式提供 `payType` 只允许已确认 `wechat`;
|
||
- 订单要求稳定 `orderId/packageId`、订单号、套餐名、非负金额、`payStatus=0/1/2/3`、`status=0/1`;
|
||
- 渲染不出现用户 ID、手机号、原始 JSON,不出现支付/取消/退款按钮。
|
||
|
||
- [ ] **Step 2: 运行 RED**
|
||
|
||
Run: `node --test tests/vip-pages.test.js`
|
||
|
||
Expected: FAIL,模块不存在。
|
||
|
||
- [ ] **Step 3: 最小实现纯函数**
|
||
|
||
支付状态:
|
||
|
||
```js
|
||
{ '0': '待支付', '1': '已支付', '2': '已关闭', '3': '已退款' }
|
||
```
|
||
|
||
套餐类型:
|
||
|
||
```js
|
||
{ vip: '会员套餐', storage: '存储扩容' }
|
||
```
|
||
|
||
套餐和订单金额保留后端字符串语义,不进行浮点运算。
|
||
|
||
- [ ] **Step 4: 运行 GREEN**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
node --test tests/vip-pages.test.js
|
||
node --check public/js/vip-pages.js
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
---
|
||
|
||
### Task 3: 服务中心真实 VIP 闭环
|
||
|
||
**Files:**
|
||
- Modify: `profile-services.html`
|
||
- Modify: `public/js/vip-pages.js`
|
||
- Modify: `tests/vip-pages.test.js`
|
||
- Modify: `tests/pending-pages.test.js`
|
||
- Modify: `tests/pc-scope.test.js`
|
||
|
||
**Interfaces:**
|
||
- Produces: `initVipPage()`、`init()`
|
||
|
||
- [ ] **Step 1: 写失败页面行为测试**
|
||
|
||
断言:
|
||
|
||
- 服务中心不再 pending,加载 `vip-pages.js`;
|
||
- 有套餐列表、只读选中提示、可选家谱下拉、订单表单、刷新订单和订单列表;
|
||
- 不存在 `packageId/genealogyId/orderId` 文本或数字输入;
|
||
- 不存在 `payType` 选择器以及支付、取消、退款、模拟成功按钮;
|
||
- 脚本并行读取套餐、我的家谱和订单;
|
||
- 创建后重读订单并精确匹配提交响应 `orderId`。
|
||
|
||
- [ ] **Step 2: 运行 RED**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
node --test tests/vip-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js
|
||
```
|
||
|
||
Expected: FAIL,服务中心仍为 pending 且允许手填 ID。
|
||
|
||
- [ ] **Step 3: 实现页面初始化**
|
||
|
||
1. 401 跳转登录,403 保留登录态;
|
||
2. `Promise.all([vipPackages(), genealogiesMine(), vipOrders()])` 读取页面数据;
|
||
3. 点击套餐卡保存响应中的字符串 `packageId`;
|
||
4. 家谱下拉只使用 `genealogiesMine()` 选项,空值表示不关联家谱;
|
||
5. 表单提交使用 `writePending` 锁;
|
||
6. 创建响应必须规范化;
|
||
7. 重读 `vipOrders()` 并找到同一 `orderId`;
|
||
8. 刷新订单列表并展示“订单已创建;当前 PC 暂未开放在线支付”。
|
||
|
||
- [ ] **Step 4: 运行 GREEN**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
node --test tests/vip-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js
|
||
node --check public/js/vip-pages.js
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
---
|
||
|
||
### Task 4: 导航、规划和收尾验证
|
||
|
||
**Files:**
|
||
- Modify: `profile.html`
|
||
- Modify: `docs/PC接口对接规划.md`
|
||
- Modify: `tests/stage6-navigation.test.js`
|
||
- Modify: `docs/superpowers/plans/2026-07-29-vip-packages-orders.md`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 1–3 的 VIP 套餐与订单闭环。
|
||
|
||
- [ ] **Step 1: 写失败导航测试**
|
||
|
||
断言个人中心服务入口可进入 `profile-services.html`,服务页真实开放并且所有 VIP 业务都留在该唯一 owner 页面。
|
||
|
||
- [ ] **Step 2: 运行 RED**
|
||
|
||
Run: `node --test tests/stage6-navigation.test.js tests/pending-pages.test.js`
|
||
|
||
Expected: FAIL,服务页仍 pending 或未加载真实脚本。
|
||
|
||
- [ ] **Step 3: 更新规划**
|
||
|
||
记录 `AppVipOrderBody`、`VipPackageVo`、`VipOrderVo` 的字段来源、枚举、隐藏字段、写后重读和支付能力阻断;阶段 7 标记 VIP 批次完成。
|
||
|
||
- [ ] **Step 4: 聚焦和全量验证**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
node --test tests/vip-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js tests/stage6-navigation.test.js
|
||
npm test
|
||
node --check public/js/vip-pages.js
|
||
git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check
|
||
```
|
||
|
||
Expected: 全部 PASS。
|
||
|
||
- [ ] **Step 5: 浏览器只读验证**
|
||
|
||
真实登录态验证套餐、家谱下拉、订单空/有数据态、无伪支付动作和控制台错误。未经用户本轮明确授权,不创建真实订单。
|