Parcourir la source

feat(order-base-info): refactor logistics information display for multiple packages

- Updated the logistics information section to handle multiple packages, allowing for individual tracking details per package.
- Enhanced the popover functionality to show tracking timelines for each package, improving user experience.
- Simplified the display logic for cases with no logistics information, ensuring consistent UI behavior.
ylong il y a 3 semaines
Parent
commit
299d3e437a

+ 100 - 78
src/views/mallOrder/all/components/order-base-info.vue

@@ -112,82 +112,42 @@
 
                 <!-- Right Column: Logistics Info -->
                 <div class="info-column">
-                    <div class="info-item">
-                        <span class="label">发货物流:</span>
-                        <span class="value">{{
-                            detail.expressName || '-'
-                        }}</span>
-                    </div>
-                    <div class="info-item">
-                        <span class="label">发货单号:</span>
-                        <span class="value">{{
-                            detail.waybillCode || '-'
-                        }}</span>
-
-                        <el-popover
-                            placement="bottom"
-                            title="物流动态"
-                            :width="400"
-                            trigger="hover"
-                        >
-                            <template #reference>
-                                <el-button
-                                    link
-                                    type="success"
-                                    class="ml-2"
-                                    v-if="detail.waybillCode"
-                                    >查看</el-button
-                                >
-                            </template>
-                            <el-timeline
-                                style="max-height: 400px; overflow-y: auto"
-                                v-if="
-                                    detail.trackingVoList &&
-                                    detail.trackingVoList.length > 0
-                                "
-                            >
-                                <el-timeline-item
-                                    v-for="(
-                                        activity, index
-                                    ) in detail.trackingVoList"
-                                    :key="index"
-                                    :timestamp="activity.ftime"
-                                    :type="index === 0 ? 'success' : ''"
-                                    :color="index === 0 ? '#0bbd87' : ''"
-                                >
-                                    <div>
-                                        <div
-                                            style="margin-bottom: 5px"
-                                            v-if="activity.status"
-                                        >
-                                            <el-tag
-                                                size="small"
-                                                :type="
-                                                    index === 0
-                                                        ? 'success'
-                                                        : 'info'
-                                                "
-                                                >{{ activity.status }}</el-tag
-                                            >
-                                        </div>
-                                        <div
-                                            style="
-                                                color: #606266;
-                                                font-size: 13px;
-                                                line-height: 1.5;
-                                            "
-                                            >{{ activity.context }}</div
-                                        >
-                                    </div>
-                                </el-timeline-item>
-                            </el-timeline>
-                            <el-empty
-                                v-else
-                                description="暂无物流信息"
-                                :image-size="60"
-                            />
-                        </el-popover>
-                    </div>
+                    <template v-if="expressPackages.length">
+                        <div v-for="(pkg, pkgIndex) in expressPackages" :key="pkgIndex" class="package-block">
+                            <div class="info-item" v-if="expressPackages.length > 1">
+                                <span class="label"></span>
+                                <span class="package-tag">包裹{{ pkgIndex + 1 }}</span>
+                            </div>
+                            <div class="info-item">
+                                <span class="label">发货物流:</span>
+                                <span class="value">{{ pkg.expressName || '-' }}</span>
+                            </div>
+                            <div class="info-item">
+                                <span class="label">发货单号:</span>
+                                <span class="value">{{ pkg.waybillCode || '-' }}</span>
+                                <el-popover v-if="pkg.waybillCode || pkg.tracks.length" placement="bottom" :title="popoverTitle(pkg)" :width="400" trigger="hover">
+                                    <template #reference>
+                                        <el-button link type="success" class="ml-2">查看</el-button>
+                                    </template>
+                                    <el-timeline v-if="pkg.tracks.length" class="track-timeline">
+                                        <el-timeline-item v-for="(activity, index) in pkg.tracks" :key="index" :timestamp="activity.ftime" :type="index === 0 ? 'success' : ''" :color="index === 0 ? '#0bbd87' : ''">
+                                            <div>
+                                                <div style="margin-bottom: 5px" v-if="activity.status">
+                                                    <el-tag size="small" :type="index === 0 ? 'success' : 'info'">{{ activity.status }}</el-tag>
+                                                </div>
+                                                <div style="color: #606266; font-size: 13px; line-height: 1.5">{{ activity.context }}</div>
+                                            </div>
+                                        </el-timeline-item>
+                                    </el-timeline>
+                                    <el-empty v-else description="暂无物流信息" :image-size="60" />
+                                </el-popover>
+                            </div>
+                        </div>
+                    </template>
+                    <template v-else>
+                        <div class="info-item"><span class="label">发货物流:</span><span class="value">-</span></div>
+                        <div class="info-item"><span class="label">发货单号:</span><span class="value">-</span></div>
+                    </template>
 
                     <div class="action-area mt-2" v-if="detail.status >= 3">
                         <el-button type="success" @click="$emit('add-package')"
@@ -220,8 +180,7 @@
 
 <script setup>
     import { Lightning } from '@element-plus/icons-vue';
-    import { EleMessage } from 'ele-admin-plus/es';
-    import { ref } from 'vue';
+    import { computed } from 'vue';
     import wxIcon from '@/assets/wx.png';
     import alipayIcon from '@/assets/alipay.png';
     import { formatPhone } from '@/utils/common';
@@ -236,6 +195,50 @@
 
     defineEmits(['add-package', 'edit-receiver']);
 
+    const isTrackNode = (item) => {
+        if (!item || typeof item !== 'object') return false;
+        const hasTrack = item.context || item.ftime || item.status;
+        const hasPackage = item.waybillCode || item.expressName || Array.isArray(item.trackingVoList);
+        return Boolean(hasTrack) && !hasPackage;
+    };
+    const isPackageNode = (item) => {
+        if (!item || typeof item !== 'object') return false;
+        return Boolean(item.waybillCode || item.expressName || Array.isArray(item.trackingVoList));
+    };
+    const expressPackages = computed(() => {
+        const detail = props.detail || {};
+        const list = Array.isArray(detail.trackingVoList) ? detail.trackingVoList : [];
+        const packages = list.filter(isPackageNode).map((item) => ({
+            expressName: item.expressName || '',
+            waybillCode: item.waybillCode || '',
+            tracks: Array.isArray(item.trackingVoList) ? item.trackingVoList : []
+        }));
+        const flatTracks = list.filter(isTrackNode);
+        const legacy = {
+            expressName: detail.expressName || '',
+            waybillCode: detail.waybillCode || '',
+            tracks: flatTracks
+        };
+        const hasLegacy = legacy.expressName || legacy.waybillCode || flatTracks.length;
+        const duplicated = packages.some((pkg) => {
+            if (legacy.waybillCode && pkg.waybillCode) return pkg.waybillCode === legacy.waybillCode;
+            return legacy.expressName && pkg.expressName === legacy.expressName && !legacy.waybillCode && !pkg.waybillCode;
+        });
+        if (hasLegacy && duplicated) {
+            const hit = packages.find((pkg) => legacy.waybillCode && pkg.waybillCode === legacy.waybillCode) || packages[0];
+            if (hit && !hit.tracks.length && flatTracks.length) hit.tracks = flatTracks;
+        } else if (hasLegacy) {
+            packages.unshift(legacy);
+        }
+        return packages.filter((pkg) => pkg.expressName || pkg.waybillCode || pkg.tracks.length);
+    });
+    const popoverTitle = (pkg) => {
+        if (expressPackages.value.length > 1) {
+            return ((pkg.expressName || '物流') + ' ' + (pkg.waybillCode || '')).trim();
+        }
+        return '物流动态';
+    };
+
     const getOutOfStockText = (value) => {
         const map = {
             1: '缺货时电话与我沟通',
@@ -283,4 +286,23 @@
     .action-area {
         margin-top: auto;
     }
+
+    .popover-package + .popover-package {
+        margin-top: 12px;
+        padding-top: 12px;
+        border-top: 1px solid #ebeef5;
+    }
+
+    .popover-package-title {
+        margin-bottom: 8px;
+        color: #303133;
+        font-weight: 600;
+    }
+
+    .package-tag { color: #67c23a; font-size: 12px; }
+    .package-block + .package-block { margin-top: 4px; }
+    .track-timeline {
+        max-height: 400px;
+        overflow-y: auto;
+    }
 </style>

+ 24 - 27
src/views/mallOrder/refund/components/refund-detail-dialog.vue

@@ -103,13 +103,13 @@
                         <span class="label">要求:</span>
                         <span class="value">{{
                             getRefundTypeText(form.refundType)
-                        }}</span>
+                            }}</span>
                     </div>
                     <div class="info-item">
                         <span class="label">货物状态:</span>
                         <span class="value">{{
                             getShopStatusText(form.shopStatus)
-                        }}</span>
+                            }}</span>
                     </div>
                     <div class="info-item">
                         <span class="label">买家留言:</span>
@@ -179,7 +179,7 @@ activity, index
                                 <div class="text-sm font-bold truncate-2-lines">{{ prod.bookName }}</div>
                                 <div class="text-xs text-gray-500 mt-1">{{
                                     prod.isbn
-                                }}</div>
+                                    }}</div>
                                 <div class="text-xs text-gray-500 mt-1">{{ prod.payPrice }} × {{ prod.num }}</div>
                             </div>
                         </div>
@@ -212,7 +212,7 @@ activity, index
                         <span class="label">订单编号:</span>
                         <span class="value text-blue-500">{{
                             form.originOrderId
-                        }}</span>
+                            }}</span>
                         <el-icon class="copy-icon" @click="handleCopy(form.originOrderId)">
                             <CopyDocument />
                         </el-icon>
@@ -234,10 +234,9 @@ activity, index
                                         @click="handleCopy(item.originWaybillCode)">
                                         <CopyDocument />
                                     </el-icon>
-                                    <el-popover placement="bottom" title="物流动态" :width="400" trigger="hover">
+                                    <el-popover v-if="item.originWaybillCode" placement="bottom" title="物流动态" :width="400" trigger="hover">
                                         <template #reference>
-                                            <el-button link type="success" class="ml-2"
-                                                v-if="item.originWaybillCode">查看</el-button>
+                                            <el-button link type="success" class="ml-2">查看</el-button>
                                         </template>
                                         <el-timeline style="
                                                 max-height: 400px;
@@ -307,14 +306,14 @@ activity, index
                                     }}</span>
                                     <span class="time text-xs text-gray-400">{{
                                         activity.createTime
-                                    }}</span>
+                                        }}</span>
                                 </div>
                                 <div class="main-content text-sm text-gray-600">
                                     <div v-if="activity.title" class="font-bold mb-1 text-gray-900">{{ activity.title }}
                                     </div>
                                     <div class="whitespace-pre-wrap mb-2">{{
                                         activity.content
-                                    }}</div>
+                                        }}</div>
 
                                     <div v-if="
                                         activity.imgList &&
@@ -338,7 +337,8 @@ activity, index
                     · 如果您逾期未处理申请,视作同意买家申请。<br />
                 </template>
                 <template v-else>
-                    · 如果您同意,请点击“同意退货”,将正确退货地址发给买家。<br />
+                    · 如果您同意退货,请点击“同意退货”,将正确退货地址发给买家。<br />
+                    · 买家退回商品后,请点击“同意退款”,退款将退到买家账户。<br />
                     · 如果您拒绝,买家可以申请客服介入。<br />
                     ·
                     如果您逾期未处理申请,视作同意买家申请。系统会自动将当前交易的退货地址发给买家<br />
@@ -349,12 +349,10 @@ activity, index
         <template #footer>
             <div class="flex justify-center gap-4">
                 <el-button @click="handleRemark">备注</el-button>
-                <el-button @click="handleRefuse">{{
-                    isRefundOnly ? '拒绝退款申请' : '拒绝退货申请'
-                }}</el-button>
-                <el-button @click="handleAgree">{{
-                    isRefundOnly ? '同意退款' : '同意退货'
-                }}</el-button>
+                <el-button v-if="!isRefundOnly" @click="handleRefuse">拒绝退货申请</el-button>
+                <el-button @click="handleRefuse">拒绝退款申请</el-button>
+                <el-button v-if="!isRefundOnly" @click="handleAgreeReturn">同意退货</el-button>
+                <el-button @click="handleAgreeRefund">同意退款</el-button>
                 <el-button v-if="isPlatformConfirmReceipt" type="primary"
                     @click="handleConfirmReceiptRefund">确认收货,同意退款</el-button>
                 <el-button v-if="isRefundOnly" type="primary" @click="handleExpressIntercept"
@@ -365,7 +363,7 @@ activity, index
                     ">与买家协商</el-button>
             </div>
         </template>
-        <negotiation-apply-dialog v-model="negotiationDialogVisible" :refund-order-id="form.refundOrderId"
+        <negotiation-apply-dialog v-model="negotiationDialogVisible" :refund-order-id="form.refundOrderId || ''"
             :refund-type="Number(form.refundType)" :max-refund-amount="Number(form.refundMoney) || 0"
             @success="handleNegotiationSuccess" />
         <refuse-dialog ref="refuseDialogRef" @success="handleNegotiationSuccess" />
@@ -408,8 +406,10 @@ const remarkRef = ref(null);
 
 const deadline = ref(0);
 
+// 0-极速退款 2-仅退款 3-缺货退款:直接退款
+// 1-退货退款:先走退货,退回后仍可退款
 const isRefundOnly = computed(() =>
-    ['0', "1", '2', '3','4'].includes(String(form.value.refundType))
+    ['0', '2', '3'].includes(String(form.value.refundType))
 );
 
 // 进度条状态
@@ -635,24 +635,21 @@ const handleRefuse = () => {
     }
 };
 
-const handleAgree = () => {
-    if (isRefundOnly.value) {
-        confirmRefundDialogRef.value?.open(
-            form.value.refundOrderId,
-            form.value.refundMoneyFinal || form.value.refundMoney
-        );
-        return;
-    }
+const handleAgreeReturn = () => {
     agreeDialogRef.value?.open(form.value.refundOrderId);
 };
 
-const handleConfirmReceiptRefund = () => {
+const handleAgreeRefund = () => {
     confirmRefundDialogRef.value?.open(
         form.value.refundOrderId,
         form.value.refundMoneyFinal || form.value.refundMoney
     );
 };
 
+const handleConfirmReceiptRefund = () => {
+    handleAgreeRefund();
+};
+
 const handleLeaveMessage = () => {
     if (leaveMessageDialogRef.value) {
         leaveMessageDialogRef.value.open(form.value.refundOrderId);