Jelajahi Sumber

feat(checkout): 切换地址系统从 consignee 到 user_address

checkout 页面改用 user_address 系统,支持省市区三级联动选择器、默认地址管理。身份证等购买信息字段保持独立,由 ProductPurchaseFieldService 按商品配置显示。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Xiaogang Liao 1 bulan lalu
induk
melakukan
d8df8b7686
2 mengubah file dengan 63 tambahan dan 54 penghapusan
  1. 53 54
      cfc-frontend/pages/shop/checkout/checkout.vue
  2. 10 0
      cfc-frontend/utils/api.js

+ 53 - 54
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -4,22 +4,22 @@
       <text class="loading-text">加载中...</text>
     </view>
     <scroll-view v-else scroll-y class="content">
-      <!-- Consignee Section -->
-      <view class="section address-section" @click="openConsigneePicker">
-        <view v-if="selectedConsignee" class="address-content">
+      <!-- Address Section -->
+      <view class="section address-section" @click="openAddressPicker">
+        <view v-if="selectedAddress" class="address-content">
           <view class="address-top">
-            <text class="receiver-name">{{ selectedConsignee.name }}</text>
-            <text class="receiver-phone">{{ formatPhone(selectedConsignee.phone) }}</text>
-            <text v-if="selectedConsignee.isDefault === 1" class="default-badge">默认</text>
+            <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 v-if="selectedConsignee.idCard" class="address-detail">身份证: {{ maskField(selectedConsignee.idCard) }}</text>
-          <text v-if="selectedConsignee.ethnicity" class="address-detail">民族: {{ selectedConsignee.ethnicity }}</text>
-          <text v-if="selectedConsignee.bloodType" class="address-detail">血型: {{ selectedConsignee.bloodType }}</text>
+          <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="goAddConsignee">
+        <view v-else class="address-empty" @click.stop="goAddAddress">
           <text class="address-empty-icon">📍</text>
-          <text class="address-empty-text">请选择收货</text>
+          <text class="address-empty-text">请选择收货地址</text>
           <text class="address-arrow">›</text>
         </view>
       </view>
@@ -193,17 +193,16 @@
 
 <script>
 import config from '@/config.js'
-import { getCouponList, consigneeList, getProductRequiredFields, getMyMembership } from '../../../utils/api.js'
+import { getCouponList, addressList, getProductRequiredFields, getMyMembership } from '../../../utils/api.js'
 
 export default {
   data() {
     return {
       items: [],
-      selectedConsignee: null,
+      selectedAddress: null,
       requiredFields: [],       // 需要的购买信息字段
       purchaseForm: {},         // 用户填写的购买信息
-      consigneeList: [],
-      showConsigneePicker: false,
+      addressList: [],
       freight: 0,
       remark: '',
       loading: false,
@@ -274,11 +273,15 @@ export default {
       this.calcAmount()
     }
     this.checkMemberStatus()
-    this.loadDefaultConsignee()
+    this.loadDefaultAddress()
     this.loadRequiredFields()
     this.loadCoupons()
     this.loadUserPoints()
   },
+  onShow() {
+    // 从地址选择/编辑页返回后重新加载地址列表
+    this.loadDefaultAddress()
+  },
   methods: {
     calcAmount() {
       var total = 0
@@ -327,23 +330,36 @@ export default {
       if (!minSpend || minSpend <= 0) return '无门槛'
       return '满' + (minSpend / 100).toFixed(2) + '元可用'
     },
-    async loadDefaultConsignee() {
+    async loadDefaultAddress() {
       this.loading = true
       try {
-        var res = await consigneeList()
+        var res = await addressList()
         if (res.code === 200) {
           var list = res.data || []
-          this.consigneeList = list
-          for (var i = 0; i < list.length; i++) {
-            if (list[i].isDefault === 1) {
-              this.selectedConsignee = list[i]
-              this.autoFillPurchaseForm()
-              break
+          this.addressList = list
+          // 如果当前已有选中地址,保持选中状态
+          if (this.selectedAddress) {
+            var stillExists = false
+            for (var k = 0; k < list.length; k++) {
+              if (list[k].id === this.selectedAddress.id) {
+                stillExists = true
+                break
+              }
+            }
+            if (!stillExists) {
+              this.selectedAddress = null
             }
           }
-          if (!this.selectedConsignee && list.length > 0) {
-            this.selectedConsignee = list[0]
-            this.autoFillPurchaseForm()
+          if (!this.selectedAddress) {
+            for (var i = 0; i < list.length; i++) {
+              if (list[i].isDefault === 1) {
+                this.selectedAddress = list[i]
+                break
+              }
+            }
+            if (!this.selectedAddress && list.length > 0) {
+              this.selectedAddress = list[0]
+            }
           }
         }
       } catch (e) {
@@ -366,7 +382,7 @@ export default {
       }
     },
     autoFillPurchaseForm() {
-      if (!this.selectedConsignee || this.requiredFields.length === 0) return
+      if (!this.selectedAddress || this.requiredFields.length === 0) return
       var form = {}
       for (var i = 0; i < this.requiredFields.length; i++) {
         var field = this.requiredFields[i]
@@ -378,22 +394,12 @@ export default {
       }
       this.purchaseForm = form
     },
-    openConsigneePicker() {
-      var that = this
-      if (this.consigneeList.length === 0) {
-        uni.showToast({ title: '暂无收货人,请先添加', icon: 'none' })
+    openAddressPicker() {
+      if (this.addressList.length === 0) {
+        uni.showToast({ title: '暂无地址,请先添加', icon: 'none' })
         return
       }
-      var items = this.consigneeList.map(function(c) {
-        return (c.name || '') + ' ' + (c.phone || '')
-      })
-      uni.showActionSheet({
-        itemList: items,
-        success: function(res) {
-          that.selectedConsignee = that.consigneeList[res.tapIndex]
-          that.autoFillPurchaseForm()
-        }
-      })
+      uni.navigateTo({ url: '/pages/shop/address/address?selectMode=1' })
     },
     checkMemberStatus() {
       var that = this
@@ -406,8 +412,8 @@ export default {
     goUpgrade() {
       uni.navigateTo({ url: '/pages/membership/upgrade' })
     },
-    goAddConsignee() {
-      uni.navigateTo({ url: '/pages/shop/consignee-edit/consignee-edit' })
+    goAddAddress() {
+      uni.navigateTo({ url: '/pages/shop/address-edit/address-edit' })
     },
     loadUserPoints() {
       var that = this
@@ -427,8 +433,8 @@ export default {
       })
     },
     onSubmit() {
-      if (!this.selectedConsignee) {
-        uni.showToast({ title: '请选择收货', icon: 'none' })
+      if (!this.selectedAddress) {
+        uni.showToast({ title: '请选择收货地址', icon: 'none' })
         return
       }
       if (this.items.length === 0) {
@@ -454,7 +460,7 @@ export default {
         data: {
           items: orderItems,
           remark: this.remark,
-          consigneeId: that.selectedConsignee.id,
+          addressId: that.selectedAddress.id,
           purchaseInfo: that.purchaseForm,
           pointsUsed: that.pointsUsed
         },
@@ -495,13 +501,6 @@ export default {
         return phone.substr(0, 3) + '****' + phone.substr(7)
       }
       return phone
-    },
-    maskField(val) {
-      if (!val) return ''
-      if (val.length >= 8) {
-        return val.substr(0, 4) + '****' + val.substr(val.length - 4)
-      }
-      return val
     }
   }
 }

+ 10 - 0
cfc-frontend/utils/api.js

@@ -2008,6 +2008,16 @@ export const consigneeDetail = (id) => request('/api/consignee/detail', 'POST',
 export const consigneeSave = (data) => request('/api/consignee/save', 'POST', data)
 export const consigneeDelete = (id) => request('/api/consignee/delete', 'POST', { id })
 
+// ===== 地址管理 =====
+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 })
+
 // ===== 商品购买字段 =====
 export const getProductRequiredFields = (productId) => request('/api/product/purchase_fields/required', 'POST', { productId })