2026-07-29-checkout-address-migration.md 7.4 KB

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 之前添加以下函数:

// ===== 地址管理 =====
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: 验证文件修改

    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

    // 替换这一行:
    import { getCouponList, consigneeList, getProductRequiredFields, getMyMembership } from '../../../utils/api.js'
    // 改为:
    import { getCouponList, addressList, getProductRequiredFields, getMyMembership } from '../../../utils/api.js'
    
  • [ ] Step 2: 修改 data(),替换 consignee 相关字段为 address 字段

    // 在 data() 中,替换:
    selectedConsignee: null,
    consigneeList: [],
    // 改为:
    selectedAddress: null,
    addressList: [],
    
    // 同时移除:
    showConsigneePicker: false,
    
  • [ ] Step 3: 修改模板 — 地址卡片区域

找到地址卡片区域(约第 8-25 行),替换为:

<!-- Address Section -->
<view class="section address-section" @click="openAddressPicker">
  <view v-if="selectedAddress" class="address-content">
    <view class="address-top">
      <text class="receiver-name">{{ selectedAddress.receiverName }}</text>
      <text class="receiver-phone">{{ formatPhone(selectedAddress.phone) }}</text>
      <text v-if="selectedAddress.isDefault === 1" class="default-badge">默认</text>
    </view>
    <text class="address-detail">
      {{ selectedAddress.province }}{{ selectedAddress.city }}{{ selectedAddress.district }}{{ selectedAddress.street }}
    </text>
    <text class="address-arrow">›</text>
  </view>
  <view v-else class="address-empty" @click.stop="goAddAddress">
    <text class="address-empty-icon">📍</text>
    <text class="address-empty-text">请选择收货地址</text>
    <text class="address-arrow">›</text>
  </view>
</view>
  • [ ] Step 4: 替换 loadDefaultConsignee 方法为 loadDefaultAddress

    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

    openAddressPicker() {
    uni.navigateTo({ url: '/pages/shop/address/address?selectMode=1' })
    }
    
  • [ ] Step 6: 替换 goAddConsignee 方法为 goAddAddress

    goAddAddress() {
    uni.navigateTo({ url: '/pages/shop/address-edit/address-edit' })
    }
    
  • [ ] Step 7: 替换 autoFillPurchaseForm 中的 selectedConsignee 引用

    autoFillPurchaseForm() {
    if (!this.selectedAddress || this.requiredFields.length === 0) return
    // ... 其余逻辑不变,只是 selectedConsignee → selectedAddress
    }
    
  • [ ] Step 8: 修改 onLoad 中的调用

    // 替换:
    this.loadDefaultConsignee()
    // 改为:
    this.loadDefaultAddress()
    
  • [ ] Step 9: 添加 onShow 生命周期 — 返回时重新加载地址列表

    onShow() {
    if (this.addressList.length > 0) {
    // 从 address-edit 或 address 页面返回后重新加载
    this.loadDefaultAddress()
    }
    }
    
  • [ ] Step 10: 修改 onSubmit — 验证和提交参数

    // 验证部分:
    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 对象即可:

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: 运行构建验证

    cd cfc-frontend && npm run build:mp-weixin
    

预期:构建成功,无报错。

  • [ ] Step 2: 检查 checkout.vue 中所有 selectedConsigneeconsigneeList 引用是否已清理

    grep -n 'selectedConsignee\|consigneeList\|consignee' cfc-frontend/pages/shop/checkout/checkout.vue | head -20
    

预期:仅保留 consignee 相关的行不超过 2 行(如注释或无关引用)。