Browse Source

feat(订单管理): 为待发货订单添加手工发货功能

新增手工发货对话框组件,允许管理员为状态为2(待发货)的订单填写物流信息并发货。在订单项组件中增加了“手工发货”按钮,点击后打开发货弹窗,可填写单号、选择快递公司并支持多包裹发货。发货成功后刷新订单列表数据。
ylong 1 tháng trước cách đây
mục cha
commit
fde2a07ddf

+ 163 - 0
src/views/mallOrder/all/components/manual-delivery-dialog.vue

@@ -0,0 +1,163 @@
+<template>
+    <ele-modal :width="800" v-model="visible" title="发货" @closed="handleClosed">
+        <div class="delivery-container" v-loading="loading">
+            <div class="section-title font-bold mb-3">填写包裹信息</div>
+            <div class="packages-list">
+                <div v-for="(pkg, index) in expressList" :key="index" class="package-item flex items-start gap-3 mb-4">
+                    <el-input v-model="pkg.waybillCode" placeholder="请输入物流单号" class="w-64" clearable />
+                    <dict-data code="shop_order_express_name" v-model="pkg.expressName" type="select" placeholder="请选择快递公司"
+                        class="w-48" />
+                    <el-button v-if="expressList.length > 1" type="danger" link @click="removePackage(index)"
+                        class="mt-1">删除</el-button>
+                </div>
+            </div>
+
+            <el-button @click="addPackage" plain class="mb-4">
+                <el-icon class="mr-1">
+                    <Plus />
+                </el-icon> 添加包裹
+            </el-button>
+            <div class="text-xs text-gray-400 mb-4">如需拆分为多个包裹发货,请先添加包裹</div>
+
+            <div class="order-info-card mb-4 border border-gray-200 rounded">
+                <div class="receiver-info bg-gray-50 p-3 text-sm text-gray-700 border-b border-gray-200">
+                    {{ orderData.receiverName }}, {{ formatPhone(orderData.receiverMobile) }}, {{
+                        orderData.receiverProvince }}{{ orderData.receiverCity }}{{ orderData.receiverCounty }}{{
+                        orderData.receiverAddress }}
+                </div>
+
+
+                <div
+                    class="order-id-bar bg-blue-50 p-2 text-sm text-gray-700 border-b border-gray-200 flex justify-between items-center">
+                    <div>
+                        订单号:{{ orderData.orderId }}
+                    </div>
+                </div>
+                <div class="product-list p-3">
+                    <div v-for="(product, idx) in orderData.detailList" :key="idx"
+                        class="product-item flex items-center mb-3 last:mb-0">
+                        <el-image :src="product.cover" class="w-12 h-12 rounded mr-3 flex-shrink-0" fit="cover" />
+                        <div class="flex-1 min-w-0 mr-4">
+                            <div class="text-sm font-medium truncate">{{ product.bookName }} {{ product.isbn }}</div>
+                        </div>
+                        <div class="w-20 text-center text-sm text-gray-500">¥ {{ product.price }}</div>
+                        <div class="w-16 text-center text-sm text-red-500">{{ product.num }}件</div>
+                        <div class="w-24 text-center text-sm text-gray-500">待发货</div>
+                    </div>
+                </div>
+            </div>
+
+
+        </div>
+
+        <template #footer>
+            <el-button @click="visible = false">返回</el-button>
+            <el-button type="primary" @click="handleSubmit" :loading="submitLoading">发货</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref, reactive } from 'vue';
+    import { Plus } from '@element-plus/icons-vue';
+    import { EleMessage } from 'ele-admin-plus/es';
+    import request from '@/utils/request';
+    import { formatPhone } from '@/utils/common';
+
+    const visible = ref(false);
+    const loading = ref(false);
+    const submitLoading = ref(false);
+    const orderData = ref({});
+    const expressList = ref([
+        { waybillCode: '', expressName: '' }
+    ]);
+
+    const emit = defineEmits(['success']);
+
+    const open = (order) => {
+        orderData.value = order;
+        expressList.value = [{ waybillCode: '', expressName: '' }];
+        visible.value = true;
+    };
+
+    const handleClosed = () => {
+        orderData.value = {};
+        expressList.value = [{ waybillCode: '', expressName: '' }];
+    };
+
+    const addPackage = () => {
+        expressList.value.push({ waybillCode: '', expressName: '' });
+    };
+
+    const removePackage = (index) => {
+        expressList.value.splice(index, 1);
+    };
+
+    const handleSubmit = () => {
+        // Validate
+        const validList = expressList.value.filter(item => item.waybillCode && item.expressName);
+        if (validList.length === 0) {
+            EleMessage.warning('请至少填写一个完整的包裹信息(物流单号和快递公司)');
+            return;
+        }
+
+        for (let i = 0; i < expressList.length; i++) {
+            const item = expressList.value[i];
+            if ((item.waybillCode && !item.expressName) || (!item.waybillCode && item.expressName)) {
+                EleMessage.warning(`第 ${i + 1} 个包裹信息不完整`);
+                return;
+            }
+        }
+
+        submitLoading.value = true;
+        request.post('/shop/shopOrder/sendPackage', {
+            orderId: orderData.value.orderId,
+            expressList: validList
+        }).then(res => {
+            if (res.data.code === 200) {
+                EleMessage.success('发货成功');
+                visible.value = false;
+                emit('success');
+            } else {
+                EleMessage.error(res.data.msg || '发货失败');
+            }
+        }).catch(e => {
+            console.error(e);
+            EleMessage.error('网络错误');
+        }).finally(() => {
+            submitLoading.value = false;
+        });
+    };
+
+    defineExpose({ open });
+</script>
+
+<style scoped>
+    .border-gray-200 {
+        border-color: #ebeef5;
+    }
+
+    .bg-gray-50 {
+        background-color: #f9fafc;
+    }
+
+    .bg-blue-50 {
+        background-color: #ecf5ff;
+    }
+
+    .text-gray-700 {
+        color: #606266;
+    }
+
+    .text-gray-500 {
+        color: #909399;
+    }
+
+    .text-gray-400 {
+        color: #c0c4cc;
+    }
+
+    .text-red-500 {
+        color: #f56c6c;
+    }
+</style>

+ 2 - 1
src/views/mallOrder/all/components/order-item.vue

@@ -79,6 +79,7 @@
                 <el-button link type="primary" @click="$emit('view-detail', order)">[查看详情]</el-button>
             </div>
             <div class="col-merged col-action">
+                <el-button v-if="order.status == 2" link type="primary" @click="$emit('manual-delivery', order)">[手工发货]</el-button>
                 <el-button link type="primary" @click="$emit('push-sms', order)">[推送短信]</el-button>
                 <el-button link type="warning" @click="$emit('refund', order)">[缺货退款]</el-button>
                 <el-button link type="primary" @click="$emit('view-log', order)">[订单日志]</el-button>
@@ -108,7 +109,7 @@
         }
     });
 
-    defineEmits(['view-detail', 'push-sms', 'refund', 'view-log', 'add-remark']);
+    defineEmits(['view-detail', 'push-sms', 'refund', 'view-log', 'add-remark', 'manual-delivery']);
 
     const { copy } = useClipboard();
 

+ 8 - 1
src/views/mallOrder/all/index.vue

@@ -16,7 +16,7 @@
             <div class="order-list" v-loading="loading">
                 <div v-if="list.length === 0" class="empty-text">暂无数据</div>
                 <order-item v-for="order in list" :key="order.orderId" :order="order" @view-detail="openDetail"
-                    @push-sms="openSms" @refund="openRefund" @view-log="openLog" @add-remark="openRemark" />
+                    @push-sms="openSms" @refund="openRefund" @view-log="openLog" @add-remark="openRemark" @manual-delivery="openManualDelivery" />
             </div>
 
             <!-- Pagination -->
@@ -34,6 +34,7 @@
         <add-package-dialog ref="packageRef" />
         <order-log ref="logRef" />
         <order-remarks ref="remarkRef" @refresh="fetchData" />
+        <manual-delivery-dialog ref="manualDeliveryRef" @success="fetchData" />
     </ele-page>
 </template>
 
@@ -48,6 +49,7 @@
     import OrderTableHeader from './components/order-table-header.vue';
     import OrderItem from './components/order-item.vue';
     import OrderRemarks from './components/order-remarks.vue';
+    import ManualDeliveryDialog from './components/manual-delivery-dialog.vue';
     import request from '@/utils/request';
     import { useDictData } from '@/utils/use-dict-data';
 
@@ -71,6 +73,7 @@
     const packageRef = ref(null);
     const logRef = ref(null);
     const remarkRef = ref(null);
+    const manualDeliveryRef = ref(null);
 
     const fetchData = () => {
         loading.value = true;
@@ -137,6 +140,10 @@
         remarkRef.value?.open(row.orderId);
     };
 
+    const openManualDelivery = (row) => {
+        manualDeliveryRef.value?.open(row);
+    };
+
     onMounted(() => {
         fetchData();
     });