فهرست منبع

feat(wechat-admin): 订单管理页适配配送履约

E2E Test Bot 3 ماه پیش
والد
کامیت
7ff45d885d
2فایلهای تغییر یافته به همراه201 افزوده شده و 2 حذف شده
  1. 139 1
      cfc-web/src/api/admin.js
  2. 62 1
      cfc-web/src/views/admin/OrderManage.vue

+ 139 - 1
cfc-web/src/api/admin.js

@@ -35,10 +35,148 @@ export function resetUserPassword(id, password) {
   return request({
     url: `/api/admin/users/${id}/reset-password`,
     method: 'post',
-    data: { password }
+    data
+  })
+}
+
+// ========== 商品配送与履约 API ==========
+
+export function vendorConfirmOrder(orderNo) {
+  return request({
+    url: '/api/product/order/vendor-confirm',
+    method: 'post',
+    data: { orderNo }
+  })
+}
+
+export function listDeliveryConfig(params) {
+  return request({
+    url: '/api/product/delivery-config/list',
+    method: 'post',
+    data: params
+  })
+}
+
+export function createPickupLocation(productId, data) {
+  return request({
+    url: '/api/product/pickup-location/create',
+    method: 'post',
+    data: { productId, ...data }
+  })
+}
+
+export function updatePickupLocation(id, data) {
+  return request({
+    url: '/api/product/pickup-location/update',
+    method: 'post',
+    data: { id, ...data }
+  })
+}
+
+export function deletePickupLocation(id) {
+  return request({
+    url: '/api/product/pickup-location/delete',
+    method: 'post',
+    data: { id }
+  })
+}
+
+export function listPickupLocations(productId) {
+  return request({
+    url: '/api/product/pickup-location/list',
+    method: 'post',
+    data: { productId }
+  })
+}
+
+export function createServicePerson(productId, data) {
+  return request({
+    url: '/api/product/service-person/create',
+    method: 'post',
+    data: { productId, ...data }
+  })
+}
+
+export function updateServicePerson(id, data) {
+  return request({
+    url: '/api/product/service-person/update',
+    method: 'post',
+    data: { id, ...data }
+  })
+}
+
+export function deleteServicePerson(id) {
+  return request({
+    url: '/api/product/service-person/delete',
+    method: 'post',
+    data: { id }
   })
 }
 
+export function listServicePersons(productId) {
+  return request({
+    url: '/api/product/service-person/list',
+    method: 'post',
+    data: { productId }
+  })
+}
+
+export function createOnlineSlot(productId, data) {
+  return request({
+    url: '/api/product/online-slot/create',
+    method: 'post',
+    data: { productId, ...data }
+  })
+}
+
+export function updateOnlineSlot(id, data) {
+  return request({
+    url: '/api/product/online-slot/update',
+    method: 'post',
+    data: { id, ...data }
+  })
+}
+
+export function deleteOnlineSlot(id) {
+  return request({
+    url: '/api/product/online-slot/delete',
+    method: 'post',
+    data: { id }
+  })
+}
+
+export function listOnlineSlots(productId) {
+  return request({
+    url: '/api/product/online-slot/list',
+    method: 'post',
+    data: { productId }
+  })
+}
+
+export function createPickupTimeSlot(locationId, data) {
+  return request({
+    url: '/api/product/pickup-time-slot/create',
+    method: 'post',
+    data: { locationId, ...data }
+  })
+}
+
+export function updatePickupTimeSlot(id, data) {
+  return request({
+    url: '/api/product/pickup-time-slot/update',
+    method: 'post',
+    data: { id, ...data }
+  })
+}
+
+export function deletePickupTimeSlot(id) {
+  return request({
+    url: '/api/product/pickup-time-slot/delete',
+    method: 'post',
+    data: { id }
+  })
+}
+
 // 家庭管理
 export function getFamilyList(params) {
   return request({

+ 62 - 1
cfc-web/src/views/admin/OrderManage.vue

@@ -52,6 +52,12 @@
           <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
         </template>
       </el-table-column>
+      <el-table-column label="履约状态" width="120">
+        <template slot-scope="{ row }">
+          <el-tag v-if="row.fulfillStatus" :type="fulfillType(row.fulfillStatus)" size="mini">{{ fulfillLabel(row.fulfillStatus) }}</el-tag>
+          <span v-else style="color:#999;">-</span>
+        </template>
+      </el-table-column>
       <el-table-column prop="paymentMethod" label="支付方式" width="100">
         <template slot-scope="{ row }">
           {{ payMethodLabel(row.paymentMethod) }}
@@ -70,6 +76,7 @@
               更多<i class="el-icon-arrow-down el-icon--right"></i>
             </el-button>
             <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item v-if="row.status === 'paid'" command="vendor-confirm" icon="el-icon-check">确认发货</el-dropdown-item>
               <el-dropdown-item v-if="row.status === 'paid'" command="ship" icon="el-icon-s-cooperation">发货</el-dropdown-item>
               <el-dropdown-item v-if="row.status === 'paid' || row.status === 'refunding'" command="close" icon="el-icon-circle-close">关闭</el-dropdown-item>
               <el-dropdown-item v-if="row.status === 'refunding'" command="refund" icon="el-icon-warning-outline">退款审核</el-dropdown-item>
@@ -110,6 +117,16 @@
           <el-descriptions-item label="支付时间">{{ detail.paidAt ? formatTime(detail.paidAt) : '-' }}</el-descriptions-item>
           <el-descriptions-item label="买家备注">{{ detail.remark || '-' }}</el-descriptions-item>
           <el-descriptions-item label="下单时间">{{ formatTime(detail.createdAt) }}</el-descriptions-item>
+          <el-descriptions-item label="配送方式" v-if="detail.deliveryMethod">
+            {{ deliveryMethodLabel(detail.deliveryMethod) }}
+          </el-descriptions-item>
+          <el-descriptions-item label="履约状态" v-if="detail.fulfillStatus">
+            <el-tag :type="fulfillType(detail.fulfillStatus)" size="mini">{{ fulfillLabel(detail.fulfillStatus) }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="自取地点" v-if="detail.pickupLocationSnapshot">{{ getJsonField(detail.pickupLocationSnapshot, 'name') }}</el-descriptions-item>
+          <el-descriptions-item label="服务者" v-if="detail.servicePersonSnapshot">{{ getJsonField(detail.servicePersonSnapshot, 'name') }}</el-descriptions-item>
+          <el-descriptions-item label="确认时间" v-if="detail.fulfillConfirmedAt">{{ formatTime(detail.fulfillConfirmedAt) }}</el-descriptions-item>
+          <el-descriptions-item label="收货时间" v-if="detail.receivedAt">{{ formatTime(detail.receivedAt) }}</el-descriptions-item>
         </el-descriptions>
       </template>
       <span slot="footer">
@@ -162,7 +179,7 @@
 </template>
 
 <script>
-import { getOrderList, shipOrder, closeOrder, approveRefund, rejectRefund } from '@/api/admin'
+import { getOrderList, shipOrder, closeOrder, approveRefund, rejectRefund, vendorConfirmOrder } from '@/api/admin'
 
 export default {
   name: 'OrderManage',
@@ -273,6 +290,18 @@ export default {
         }
       }
     },
+    async handleVendorConfirm(row) {
+      try {
+        await this.$confirm('确认供应商已发货/履约完成?', '提示', { type: 'info' })
+        await vendorConfirmOrder(row.orderNo)
+        this.$message.success('已确认发货')
+        this.loadList()
+      } catch (e) {
+        if (e !== 'cancel') {
+          this.$message.error(e.message || '操作失败')
+        }
+      }
+    },
     showRefundDialog(row) {
       this.refundTarget = row
       this.refundRejectReason = ''
@@ -325,6 +354,24 @@ export default {
       var map = { wechat: '微信支付', balance: '余额支付', points: '积分兑换', test: '测试支付' }
       return map[method] || method || '-'
     },
+    fulfillLabel(status) {
+      var map = {
+        'pending': '待履约',
+        'confirmed_by_vendor': '已确认',
+        'received_by_user': '已收货',
+        'completed': '已完成'
+      }
+      return map[status] || status
+    },
+    fulfillType(status) {
+      var map = {
+        'pending': 'warning',
+        'confirmed_by_vendor': 'primary',
+        'received_by_user': 'success',
+        'completed': 'success'
+      }
+      return map[status] || 'info'
+    },
     typeLabel(type) {
       var map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
       return map[type] || type || '-'
@@ -333,6 +380,19 @@ export default {
       if (cents == null) return '-'
       return '¥' + (cents / 100).toFixed(2)
     },
+    deliveryMethodLabel(method) {
+      var map = { 1: '快递配送', 2: '到店自提', 3: '快递/自提', 4: '线上服务', 5: '线上预约' }
+      return map[method] || '快递'
+    },
+    getJsonField(snapshot, field) {
+      if (!snapshot) return '-'
+      try {
+        var obj = JSON.parse(snapshot)
+        return obj[field] || '-'
+      } catch (e) {
+        return snapshot
+      }
+    },
     formatTime(t) {
       if (!t) return '-'
       return t.replace ? t.replace('T', ' ').substring(0, 19) : t
@@ -340,6 +400,7 @@ export default {
     handleActionCmd(row, cmd) {
       switch (cmd) {
         case 'ship': this.showShipDialog(row); break;
+        case 'vendor-confirm': this.handleVendorConfirm(row); break;
         case 'close': this.handleClose(row); break;
         case 'refund': this.showRefundDialog(row); break;
         case 'view-pending-refund': this.$router.push('/pending-refund'); break;