# 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: 浏览器只读验证** 真实登录态验证套餐、家谱下拉、订单空/有数据态、无伪支付动作和控制台错误。未经用户本轮明确授权,不创建真实订单。