瀏覽代碼

feat(退款管理): 对接后端退款列表接口并更新搜索组件

- 将页面搜索表单字段改为与后端接口一致的参数名
- 将静态数据替换为从 `/shop/shopOrder/refundPagelist` 接口获取的动态数据
- 更新状态筛选标签页的值和显示文本以匹配后端枚举
- 在退款项组件中添加数据映射函数以正确显示后端返回的字段
- 添加空数据状态显示和查看详情功能占位
ylong 1 周之前
父節點
當前提交
67d3ea1abe

+ 33 - 31
src/views/mallOrder/refund/components/page-search.vue

@@ -17,44 +17,46 @@ const emit = defineEmits(['search']);
 const searchRef = ref(null);
 
 const formItems = reactive([
-    { type: 'input', label: '订单编号', prop: 'orderId' },
-    { type: 'input', label: '收件人手机号', prop: 'receiverPhone' },
-    { type: 'input', label: '发货物流单号', prop: 'logisticsNo' },
-    { type: 'input', label: '退货物流单号', prop: 'returnLogisticsNo' },
+    { type: 'input', label: '原订单ID', prop: 'originOrderId' },
+    { type: 'input', label: '退回订单ID', prop: 'refundOrderId' },
+    { type: 'input', label: '用户ID', prop: 'userId' },
+    { type: 'input', label: '寄件人手机号', prop: 'sendMobile' },
+    { type: 'input', label: '退回运单号', prop: 'refundWaybillCode' },
     {
         type: 'select',
-        label: '全部售后状态',
-        prop: 'afterSalesStatus',
+        label: '状态',
+        prop: 'status',
         options: [
-            { label: '退款成功', value: 'success' },
-            { label: '退款中', value: 'processing' },
-            { label: '商家处理中', value: 'merchant_processing' },
-            { label: '商家待收货', value: 'merchant_receiving' },
-            { label: '退款关闭', value: 'closed' }
+            { label: '申请退款', value: 1 },
+            { label: '审核通过', value: 2 },
+            { label: '审核驳回', value: 3 },
+            { label: '超时关闭', value: 4 },
+            { label: '卖家已发货', value: 5 },
+            { label: '已完成', value: 6 }
         ]
     },
-    { type: 'input', label: '退款编号', prop: 'refundId' },
     {
         type: 'select',
-        label: '售后类型',
-        prop: 'afterSalesType',
+        label: '退款类型',
+        prop: 'refundType',
         options: [
-            { label: '仅退款', value: 'only_refund' },
-            { label: '退货退款', value: 'return_refund' }
+            { label: '极速退款', value: '0' },
+            { label: '退货退款', value: '1' },
+            { label: '仅退款', value: '2' }
         ]
     },
     {
         type: 'daterange',
-        label: '申请时间',
+        label: '创建时间',
         prop: 'dateRange',
-        keys: ['startTime', 'endTime'],
+        keys: ['createTimeStart', 'createTimeEnd'],
         props: {
-            format: 'YYYY-MM-DD',
-            valueFormat: 'YYYY-MM-DD',
+            format: 'YYYY-MM-DD HH:mm:ss',
+            valueFormat: 'YYYY-MM-DD HH:mm:ss',
             onChange: (val) => {
                 searchRef.value?.setData({
-                    startTime: val && val.length > 0 ? val[0] : '',
-                    endTime: val && val.length > 0 ? val[1] : ''
+                    createTimeStart: val && val.length > 0 ? val[0] : '',
+                    createTimeEnd: val && val.length > 0 ? val[1] : ''
                 });
             }
         }
@@ -62,15 +64,15 @@ const formItems = reactive([
 ]);
 
 const initKeys = reactive({
-    orderId: '',
-    receiverPhone: '',
-    logisticsNo: '',
-    returnLogisticsNo: '',
-    afterSalesStatus: '',
-    refundId: '',
-    afterSalesType: '',
-    startTime: '',
-    endTime: ''
+    originOrderId: '',
+    refundOrderId: '',
+    userId: '',
+    sendMobile: '',
+    refundWaybillCode: '',
+    status: '',
+    refundType: '',
+    createTimeStart: '',
+    createTimeEnd: ''
 });
 
 const search = (data) => {

+ 153 - 71
src/views/mallOrder/refund/components/refund-item.vue

@@ -3,31 +3,36 @@
         <!-- Header -->
         <div class="refund-header">
             <el-checkbox v-model="item.checked" style="margin-right:12px" />
-            <el-tag type="success" size="small" class="mr-2" effect="plain">退货退款</el-tag>
-            <span class="mr-4 text-gray-600">订单编号: {{ item.orderId }}</span>
-            <span class="mr-4 text-gray-600">退款编号: {{ item.refundId }}</span>
-            <span class="mr-4 text-gray-600">申请时间: {{ item.applyTime }}</span>
+            <el-tag type="success" size="small" class="mr-2" effect="plain">
+                {{ getRefundTypeText(item.refundType) }}
+            </el-tag>
+            <span class="mr-4 text-gray-600">退款编号: {{ item.refundOrderId }}</span>
+            <span class="mr-4 text-gray-600">原订单编号: {{ item.originOrderId }}</span>
+            <span class="mr-4 text-gray-600">申请时间: {{ item.createTime }}</span>
+            <span class="mr-4 text-gray-600" v-if="item.cancelStatus == 2">
+                <el-tag type="info" size="small">已撤销</el-tag>
+            </span>
         </div>
 
         <!-- Body -->
         <div class="refund-body">
             <!-- Product Info (Flex 3) -->
-            <div class="col-product">
-                <div class="product-info">
-                    <el-image :src="item.product.image" class="product-img" fit="cover" />
-                    <div class="product-detail">
-                        <div class="product-title">{{ item.product.title }}</div>
-                        <div class="product-isbn">
-                            ISBN: <span class="link">{{ item.product.isbn }}</span>
-                            <el-icon class="cursor-pointer ml-1" @click="handleCopy(item.product.isbn)">
-                                <CopyDocument />
-                            </el-icon>
-                            <span class="ml-2">(品相: {{ item.product.condition }})</span>
-                        </div>
-                        <div class="product-code">商家编码: {{ item.product.code }}</div>
-                        <div class="product-price">
-                            <span class="mr-2">x{{ item.product.qty }}</span>
-                            <span>¥ {{ item.product.price }}</span>
+            <div class="col-product-wrapper">
+                <div v-for="(product, idx) in item.detailList" :key="idx" class="product-row">
+                    <div class="product-info">
+                        <el-image :src="product.cover" class="product-img" fit="cover" />
+                        <div class="product-detail">
+                            <div class="product-title">{{ product.bookName }}</div>
+                            <div class="product-isbn">
+                                ISBN: <span class="link">{{ product.isbn }}</span>
+                                <el-icon class="cursor-pointer ml-1" @click="handleCopy(product.isbn)">
+                                    <CopyDocument />
+                                </el-icon>
+                            </div>
+                            <div class="product-price">
+                                <span class="mr-2">退回数量: {{ product.refundNum }}</span>
+                                <span>单价: ¥ {{ product.price }}</span>
+                            </div>
                         </div>
                     </div>
                 </div>
@@ -35,39 +40,51 @@
 
             <!-- Refund Amount (Flex 1) -->
             <div class="col-merged col-amount">
-                <div class="amount">¥ {{ item.refundAmount }}</div>
-                <div class="original-price text-gray-400">¥ {{ item.product.price }}</div>
+                <div class="amount">申请: ¥ {{ item.refundMoney }}</div>
+                <div class="final-amount text-green-500" v-if="item.refundMoneyFinal">实退: ¥ {{ item.refundMoneyFinal }}</div>
+                <div class="money-status text-xs text-gray-400 mt-1">
+                    {{ getMoneyStatusText(item.moneyStatus) }}
+                </div>
             </div>
 
             <!-- Buyer (Flex 1) -->
             <div class="col-merged col-buyer">
                 <div class="buyer-info">
-                    <el-avatar :size="30" :src="item.buyer.avatar" />
-                    <div class="buyer-name">{{ item.buyer.name }}</div>
+                    <el-avatar :size="30" :src="item.avatar" />
+                    <div class="buyer-name">{{ item.userNick }}</div>
+                    <div class="buyer-id text-xs text-gray-400">ID: {{ item.userId }}</div>
                 </div>
             </div>
 
             <!-- Reason (Flex 1) -->
             <div class="col-merged col-reason">
-                <div>{{ item.refundReason }}</div>
+                <div class="font-bold">{{ item.refundReason }}</div>
+                <div class="text-xs text-gray-500 mt-1">货物状态: {{ getShopStatusText(item.shopStatus) }}</div>
             </div>
 
             <!-- Logistics (Flex 1.5) -->
             <div class="col-merged col-logistics">
                 <div class="logistics-row">
-                    <span>商家发货: {{ item.logistics.merchantShipped ? '已发货' : '未发货' }}</span>
-                    <el-button link type="primary" size="small">[查看]</el-button>
+                    <span class="font-bold">{{ getSendTypeText(item.sendType) }}</span>
                 </div>
-                <div class="logistics-row">
-                    <span>买家退货: {{ item.logistics.buyerReturned ? '运输中' : '未发货' }}</span>
-                    <el-button link type="primary" size="small">[查看]</el-button>
+                <div class="logistics-row" v-if="item.expressName">
+                    <span>{{ item.expressName }}</span>
+                </div>
+                <div class="logistics-row" v-if="item.waybillCode">
+                    <span>{{ item.waybillCode }}</span>
+                    <el-icon class="cursor-pointer ml-1" @click="handleCopy(item.waybillCode)">
+                        <CopyDocument />
+                    </el-icon>
+                </div>
+                <div class="logistics-row" v-if="item.pickupCode">
+                    <span>取件码: {{ item.pickupCode }}</span>
                 </div>
             </div>
 
             <!-- Status (Flex 1) -->
             <div class="col-merged col-status">
-                <div class="text-red-500 font-bold mb-1">{{ item.status }}</div>
-                <el-button link type="primary" size="small">[查看详情]</el-button>
+                <div class="text-red-500 font-bold mb-1">{{ getStatusText(item.status) }}</div>
+                <el-button link type="primary" size="small" @click="$emit('view-detail', item)">[查看详情]</el-button>
             </div>
 
             <!-- Action (Flex 1) -->
@@ -91,7 +108,7 @@ const props = defineProps({
     }
 });
 
-defineEmits(['push-sms', 'send-packet']);
+defineEmits(['push-sms', 'send-packet', 'view-detail']);
 
 const { copy } = useClipboard();
 
@@ -103,6 +120,53 @@ const handleCopy = async (text) => {
         EleMessage.error('复制失败');
     }
 };
+
+const getRefundTypeText = (type) => {
+    const map = {
+        '0': '极速退款',
+        '1': '退货退款',
+        '2': '仅退款'
+    };
+    return map[type] || type;
+};
+
+const getStatusText = (status) => {
+    const map = {
+        '1': '申请退款',
+        '2': '审核通过',
+        '3': '审核驳回',
+        '4': '超时关闭',
+        '5': '卖家已发货',
+        '6': '已完成'
+    };
+    return map[status] || status;
+};
+
+const getShopStatusText = (status) => {
+    const map = {
+        '1': '未收到货',
+        '2': '已收到货'
+    };
+    return map[status] || '-';
+};
+
+const getSendTypeText = (type) => {
+    const map = {
+        '1': '上门取件',
+        '2': '寄件点自寄',
+        '3': '自行寄回'
+    };
+    return map[type] || '-';
+};
+
+const getMoneyStatusText = (status) => {
+    const map = {
+        '1': '未退还',
+        '2': '已退还',
+        '3': '已到账'
+    };
+    return map[status] || '未退还';
+};
 </script>
 
 <style lang="scss" scoped>
@@ -124,50 +188,64 @@ const handleCopy = async (text) => {
         display: flex;
         align-items: stretch;
 
-        .col-product {
+        .col-product-wrapper {
             flex: 3;
-            padding: 15px;
-            
-            .product-info {
+            display: flex;
+            flex-direction: column;
+            border-right: 1px solid #ebeef5;
+
+            .product-row {
                 display: flex;
-                
-                .product-img {
-                    width: 80px;
-                    height: 80px;
-                    margin-right: 15px;
-                    border-radius: 4px;
-                    border: 1px solid #eee;
+                padding: 15px;
+                border-bottom: 1px solid #ebeef5;
+
+                &:last-child {
+                    border-bottom: none;
                 }
 
-                .product-detail {
-                    font-size: 13px;
-                    
-                    .product-title {
-                        font-weight: 500;
-                        margin-bottom: 5px;
-                        color: #333;
+                .product-info {
+                    display: flex;
+                    width: 100%;
+
+                    .product-img {
+                        width: 80px;
+                        height: 80px;
+                        margin-right: 15px;
+                        border-radius: 4px;
+                        border: 1px solid #eee;
                     }
-                    
-                    .product-isbn, .product-code {
-                        color: #666;
-                        margin-bottom: 3px;
-                        font-size: 12px;
-                        
-                        .link {
-                            color: #409eff;
+
+                    .product-detail {
+                        font-size: 13px;
+                        flex: 1;
+
+                        .product-title {
+                            font-weight: 500;
+                            margin-bottom: 5px;
+                            color: #333;
+                        }
+
+                        .product-isbn {
+                            color: #666;
+                            margin-bottom: 3px;
+                            font-size: 12px;
+
+                            .link {
+                                color: #409eff;
+                            }
+                        }
+
+                        .product-price {
+                            margin-top: 5px;
+                            color: #333;
                         }
-                    }
-                    
-                    .product-price {
-                        margin-top: 5px;
-                        color: #333;
                     }
                 }
             }
         }
 
         .col-merged {
-            border-left: 1px solid #ebeef5;
+            border-right: 1px solid #ebeef5;
             display: flex;
             flex-direction: column;
             justify-content: center;
@@ -175,15 +253,19 @@ const handleCopy = async (text) => {
             padding: 10px;
             text-align: center;
             font-size: 13px;
-            
+
+            &:last-child {
+                border-right: none;
+            }
+
             &.col-amount { flex: 1; }
             &.col-buyer { flex: 1; }
             &.col-reason { flex: 1; }
-            &.col-logistics { 
-                flex: 1.5; 
+            &.col-logistics {
+                flex: 1.5;
                 align-items: flex-start;
                 padding-left: 15px;
-                
+
                 .logistics-row {
                     display: flex;
                     align-items: center;
@@ -196,12 +278,12 @@ const handleCopy = async (text) => {
             &.col-action { flex: 1; }
         }
     }
-    
+
     .action-btn {
         cursor: pointer;
         margin-bottom: 5px;
         font-size: 13px;
-        
+
         &:hover {
             opacity: 0.8;
         }

+ 124 - 153
src/views/mallOrder/refund/index.vue

@@ -48,181 +48,152 @@
         <ele-card :body-style="{ padding: '10px' }" class="mt-4">
             <!-- Tabs -->
             <el-tabs v-model="activeTab" @tab-click="handleTabClick">
-                <el-tab-pane label="全部售后状态" name="all" />
-                <el-tab-pane label="进行中的订单" name="processing" />
-                <el-tab-pane label="待商家处理" name="merchant_pending" />
-                <el-tab-pane label="待商家收货" name="merchant_receiving" />
-                <el-tab-pane label="待买家发货" name="buyer_shipping" />
-                <el-tab-pane label="已拒绝退款" name="rejected" />
-                <el-tab-pane label="退款成功" name="success" />
-                <el-tab-pane label="退款取消" name="cancelled" />
+                <el-tab-pane label="全部" name="" />
+                <el-tab-pane label="申请退款" name="1" />
+                <el-tab-pane label="审核通过" name="2" />
+                <el-tab-pane label="审核驳回" name="3" />
+                <el-tab-pane label="超时关闭" name="4" />
+                <el-tab-pane label="卖家已发货" name="5" />
+                <el-tab-pane label="已完成" name="6" />
             </el-tabs>
 
             <!-- Table Header -->
             <div class="mt-2">
-                <refund-table-header
-                    v-model:checkAll="checkAll"
-                    :is-indeterminate="isIndeterminate"
-                    @change="handleCheckAllChange"
-                />
+                <refund-table-header v-model:checkAll="checkAll" :is-indeterminate="isIndeterminate"
+                    @change="handleCheckAllChange" />
             </div>
 
             <!-- List -->
             <div class="refund-list" v-loading="loading">
-                <refund-item
-                    v-for="(item, index) in list"
-                    :key="index"
-                    :item="item"
-                    @push-sms="handlePushSms"
-                    @send-packet="handleSendPacket"
-                />
+                <div v-if="list.length === 0" class="empty-text text-center py-10 text-gray-400">暂无数据</div>
+                <refund-item v-for="(item, index) in list" :key="index" :item="item" @push-sms="handlePushSms"
+                    @send-packet="handleSendPacket" @view-detail="handleViewDetail" />
             </div>
 
             <!-- Pagination -->
             <div class="pagination-wrapper mt-4 flex justify-end">
-                <el-pagination
-                    v-model:current-page="pageParams.page"
-                    v-model:page-size="pageParams.limit"
-                    :total="total"
-                    layout="total, sizes, prev, pager, next, jumper"
-                    @size-change="handleSizeChange"
-                    @current-change="handlePageChange"
-                />
+                <el-pagination v-model:current-page="pageParams.pageNum" v-model:page-size="pageParams.pageSize"
+                    :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
+                    @current-change="handlePageChange" />
             </div>
         </ele-card>
 
         <!-- Dialogs -->
-        <push-sms-dialog ref="pushSmsDialogRef" @success="handleSearch" />
+        <push-sms-dialog ref="pushSmsDialogRef" @success="fetchData" />
     </ele-page>
 </template>
 
 <script setup>
-import { ref, reactive } from 'vue';
-import { EleMessage } from 'ele-admin-plus/es';
-import PageSearch from './components/page-search.vue';
-import RefundTableHeader from './components/refund-table-header.vue';
-import RefundItem from './components/refund-item.vue';
-import PushSmsDialog from './components/push-sms-dialog.vue';
-
-const activeTab = ref('all');
-const loading = ref(false);
-const total = ref(10);
-const pageParams = reactive({
-    page: 1,
-    limit: 10
-});
-
-const checkAll = ref(false);
-const isIndeterminate = ref(false);
-
-const list = ref([
-    {
-        checked: false,
-        orderId: '4066879393210936038',
-        refundId: '4066879393210936038',
-        applyTime: '2024-10-04 12:01:09',
-        product: {
-            image: 'https://img14.360buyimg.com/n0/jfs/t1/157997/3/36676/122176/65e975a0F98822998/5023348143493720.jpg',
-            title: '毛泽东思想和中国特色社会主义理论体系概论(2023年版)',
-            isbn: '9787040599012',
-            condition: '中等',
-            code: 'wk-725154',
-            qty: 1,
-            price: 2.9
-        },
-        refundAmount: 2.9,
-        buyer: {
-            avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
-            name: '书嗨4654645'
-        },
-        refundReason: '不喜欢,效果不好',
-        logistics: {
-            merchantShipped: true,
-            buyerReturned: true
-        },
-        status: '退款成功'
-    },
-    {
-        checked: false,
-        orderId: '4066879393210936038',
-        refundId: '4066879393210936038',
-        applyTime: '2024-10-04 12:01:09',
-        product: {
-            image: 'https://img14.360buyimg.com/n0/jfs/t1/157997/3/36676/122176/65e975a0F98822998/5023348143493720.jpg',
-            title: '毛泽东思想和中国特色社会主义理论体系概论(2023年版)',
-            isbn: '9787040599012',
-            condition: '中等',
-            code: 'wk-725154',
-            qty: 1,
-            price: 2.9
-        },
-        refundAmount: 2.9,
-        buyer: {
-            avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
-            name: '书嗨4654645'
-        },
-        refundReason: '不喜欢,效果不好',
-        logistics: {
-            merchantShipped: true,
-            buyerReturned: true
-        },
-        status: '同意退款'
-    }
-]);
-
-const pushSmsDialogRef = ref(null);
-
-const handleSearch = (where) => {
-    loading.value = true;
-    setTimeout(() => {
-        loading.value = false;
-        EleMessage.success('查询成功');
-    }, 500);
-};
-
-const handleTabClick = (tab) => {
-    handleSearch();
-};
-
-const handleSizeChange = (val) => {
-    pageParams.limit = val;
-    handleSearch();
-};
-
-const handlePageChange = (val) => {
-    pageParams.page = val;
-    handleSearch();
-};
-
-const handleCheckAllChange = (val) => {
-    list.value.forEach(item => item.checked = val);
-    isIndeterminate.value = false;
-};
-
-const handlePushSms = (item) => {
-    pushSmsDialogRef.value?.handleOpen(item);
-};
-
-const handleSendPacket = (item) => {
-    EleMessage.warning('发红包功能暂未实现');
-};
+    import { ref, reactive, onMounted } from 'vue';
+    import { EleMessage } from 'ele-admin-plus/es';
+    import request from '@/utils/request';
+    import PageSearch from './components/page-search.vue';
+    import RefundTableHeader from './components/refund-table-header.vue';
+    import RefundItem from './components/refund-item.vue';
+    import PushSmsDialog from './components/push-sms-dialog.vue';
+
+    const activeTab = ref('');
+    const loading = ref(false);
+    const total = ref(0);
+    const pageParams = reactive({
+        pageNum: 1,
+        pageSize: 10
+    });
+    const searchParams = ref({});
+
+    const checkAll = ref(false);
+    const isIndeterminate = ref(false);
+
+    const list = ref([]);
+
+    const pushSmsDialogRef = ref(null);
+
+    const fetchData = () => {
+        loading.value = true;
+        const params = {
+            ...pageParams,
+            ...searchParams.value,
+            status: activeTab.value
+        };
+
+        request.get('/shop/shopOrder/refundPagelist', { params })
+            .then(res => {
+                const data = res.data;
+                list.value = data.rows || [];
+                total.value = data.total || 0;
+                // Reset check status
+                checkAll.value = false;
+                isIndeterminate.value = false;
+            })
+            .catch(e => {
+                console.error(e);
+                EleMessage.error('获取退款列表失败');
+            })
+            .finally(() => {
+                loading.value = false;
+            });
+    };
+
+    const handleSearch = (where) => {
+        pageParams.pageNum = 1;
+        searchParams.value = where;
+        fetchData();
+    };
+
+    const handleTabClick = ({ props }) => {
+        console.log(props);
+        activeTab.value = props.name;
+        pageParams.pageNum = 1;
+        fetchData();
+    };
+
+    const handleSizeChange = (val) => {
+        pageParams.pageSize = val;
+        fetchData();
+    };
+
+    const handlePageChange = (val) => {
+        pageParams.pageNum = val;
+        fetchData();
+    };
+
+    const handleCheckAllChange = (val) => {
+        list.value.forEach(item => item.checked = val);
+        isIndeterminate.value = false;
+    };
+
+    const handlePushSms = (item) => {
+        pushSmsDialogRef.value?.handleOpen(item);
+    };
+
+    const handleSendPacket = (item) => {
+        EleMessage.warning('发红包功能暂未实现');
+    };
+
+    const handleViewDetail = (item) => {
+        EleMessage.warning('详情页暂未实现');
+    };
+
+    onMounted(() => {
+        fetchData();
+    });
 </script>
 
 <style scoped lang="scss">
-.stat-card {
-    text-align: center;
-    border: none;
-    
-    .stat-title {
-        font-size: 13px;
-        color: #606266;
-        font-weight: bold;
-    }
-    
-    .stat-value {
-        font-size: 24px;
-        font-weight: bold;
-        color: #303133;
+    .stat-card {
+        text-align: center;
+        border: none;
+
+        .stat-title {
+            font-size: 13px;
+            color: #606266;
+            font-weight: bold;
+        }
+
+        .stat-value {
+            font-size: 24px;
+            font-weight: bold;
+            color: #303133;
+        }
     }
-}
 </style>