# Checkout 地址迁移实施计划 > **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:** 将 checkout 页面从 `consignees` 系统切换到 `user_address` 系统,身份证等购买信息字段保持独立。 **Architecture:** checkout.vue 调用 `/api/user/address/list` 替代 `/api/consignee/list`,地址选择跳转到 `address/address.vue`,新增/编辑走 `address-edit/address-edit.vue`。后端无改动。 **Tech Stack:** uni-app Vue 2, WeChat mini-program, Spring Boot 2.7.18 ## Global Constraints - 小程序禁止可选链 `?.`(用 `&&` 替代) - 小程序禁止 CSS Grid(用 flexbox) - Vue 2 Options API,禁止 Composition API - 所有接口统一 `@PostMapping`,前端 `method: 'POST'` - 响应格式:`uni.request` + `Authorization: Bearer {token}` --- ### Task 1: 添加地址 API 函数到 api.js **Files:** - Modify: `cfc-frontend/utils/api.js` **Interfaces:** - Consumes: 无 - Produces: `addressList()`, `addressDetail(id)`, `addressSave(data)`, `addressDelete(id)`, `addressSetDefault(id)` - [ ] **Step 1: 在 api.js 末尾添加地址 API 函数** 找到 `consigneeDelete` 函数附近,在 `getProductRequiredFields` 之前添加以下函数: ```javascript // ===== 地址管理 ===== export const addressList = () => request('/api/user/address/list', 'POST', {}) export const addressDetail = (id) => request('/api/user/address/get', 'POST', { id }) export const addressSave = (data) => { var url = data.id ? '/api/user/address/update' : '/api/user/address/create' return request(url, 'POST', data) } export const addressDelete = (id) => request('/api/user/address/delete', 'POST', { id }) export const addressSetDefault = (id) => request('/api/user/address/setDefault', 'POST', { id }) ``` - [ ] **Step 2: 验证文件修改** ```bash grep -n 'addressList\|addressDetail\|addressSave\|addressDelete\|addressSetDefault' cfc-frontend/utils/api.js ``` --- ### Task 2: 修改 checkout.vue — 切换为地址系统 **Files:** - Modify: `cfc-frontend/pages/shop/checkout/checkout.vue` **Interfaces:** - Consumes: `addressList()` from api.js - Produces: 提交订单时传 `addressId` 代替 `consigneeId` - [ ] **Step 1: 修改 import,替换 consigneeList 为 addressList** ```javascript // 替换这一行: import { getCouponList, consigneeList, getProductRequiredFields, getMyMembership } from '../../../utils/api.js' // 改为: import { getCouponList, addressList, getProductRequiredFields, getMyMembership } from '../../../utils/api.js' ``` - [ ] **Step 2: 修改 data(),替换 consignee 相关字段为 address 字段** ```javascript // 在 data() 中,替换: selectedConsignee: null, consigneeList: [], // 改为: selectedAddress: null, addressList: [], // 同时移除: showConsigneePicker: false, ``` - [ ] **Step 3: 修改模板 — 地址卡片区域** 找到地址卡片区域(约第 8-25 行),替换为: ```html {{ selectedAddress.receiverName }} {{ formatPhone(selectedAddress.phone) }} 默认 {{ selectedAddress.province }}{{ selectedAddress.city }}{{ selectedAddress.district }}{{ selectedAddress.street }} › 📍 请选择收货地址 › ``` - [ ] **Step 4: 替换 `loadDefaultConsignee` 方法为 `loadDefaultAddress`** ```javascript loadDefaultAddress() { this.loading = true var that = this addressList().then(function(res) { that.loading = false if (res.code === 200) { var list = res.data || [] that.addressList = list for (var i = 0; i < list.length; i++) { if (list[i].isDefault === 1) { that.selectedAddress = list[i] break } } if (!that.selectedAddress && list.length > 0) { that.selectedAddress = list[0] } } }).catch(function(e) { that.loading = false console.error(e) }) } ``` - [ ] **Step 5: 替换 `openConsigneePicker` 方法为 `openAddressPicker`** ```javascript openAddressPicker() { uni.navigateTo({ url: '/pages/shop/address/address?selectMode=1' }) } ``` - [ ] **Step 6: 替换 `goAddConsignee` 方法为 `goAddAddress`** ```javascript goAddAddress() { uni.navigateTo({ url: '/pages/shop/address-edit/address-edit' }) } ``` - [ ] **Step 7: 替换 `autoFillPurchaseForm` 中的 `selectedConsignee` 引用** ```javascript autoFillPurchaseForm() { if (!this.selectedAddress || this.requiredFields.length === 0) return // ... 其余逻辑不变,只是 selectedConsignee → selectedAddress } ``` - [ ] **Step 8: 修改 `onLoad` 中的调用** ```javascript // 替换: this.loadDefaultConsignee() // 改为: this.loadDefaultAddress() ``` - [ ] **Step 9: 添加 `onShow` 生命周期 — 返回时重新加载地址列表** ```javascript onShow() { if (this.addressList.length > 0) { // 从 address-edit 或 address 页面返回后重新加载 this.loadDefaultAddress() } } ``` - [ ] **Step 10: 修改 `onSubmit` — 验证和提交参数** ```javascript // 验证部分: if (!this.selectedAddress) { uni.showToast({ title: '请选择收货地址', icon: 'none' }) return } // 提交参数部分: data: { items: orderItems, remark: this.remark, addressId: that.selectedAddress.id, purchaseInfo: that.purchaseForm, pointsUsed: that.pointsUsed } ``` - [ ] **Step 11: 移除不再使用的 `consigneeList` 相关代码** 检查并移除: - `showConsigneePicker` 相关状态 - 任何 `consigneeList` 引用 - 任何 `consignee` 相关的 picker 弹窗 --- ### Task 3: 完善 address/address.vue 选择模式回传 **Files:** - Modify: `cfc-frontend/pages/shop/address/address.vue` **Interfaces:** - Consumes: 无 - Produces: 通过 `prevPage.selectedAddress` 回传地址数据 - [ ] **Step 1: 确认 `onSelect` 方法回传完整数据** 当前 `onSelect` 方法已存在(约第 105-113 行),数据来源于 `addressList` 加载的 `UserAddress` 对象,已有 `id`, `receiverName`, `phone`, `province`, `city`, `district`, `street`, `isDefault` 字段。确认 `onSelect` 直接回传 `item` 对象即可: ```javascript onSelect(item) { if (this.selectMode) { var pages = getCurrentPages() var prevPage = pages[pages.length - 2] if (prevPage) { prevPage.selectedAddress = item } uni.navigateBack() } } ``` 无需修改。 --- ### Task 4: 验证构建 **Files:** - 无 - [ ] **Step 1: 运行构建验证** ```bash cd cfc-frontend && npm run build:mp-weixin ``` 预期:构建成功,无报错。 - [ ] **Step 2: 检查 checkout.vue 中所有 `selectedConsignee` 和 `consigneeList` 引用是否已清理** ```bash grep -n 'selectedConsignee\|consigneeList\|consignee' cfc-frontend/pages/shop/checkout/checkout.vue | head -20 ``` 预期:仅保留 `consignee` 相关的行不超过 2 行(如注释或无关引用)。