Pārlūkot izejas kodu

feat(售后订单): 新增售后订单管理页面及相关组件

实现售后订单管理功能,包括:
- 新增售后订单列表页面,包含统计卡片、搜索表单、状态筛选和分页功能
- 添加退款表格头部组件,支持全选和部分选中状态
- 实现退款项组件,展示商品信息、退款金额、买家信息等
- 新增搜索表单组件,支持多种条件筛选
- 添加推送短信弹窗组件,支持短信类型选择和发送记录查看
- 优化页面样式和交互体验
ylong 2 dienas atpakaļ
vecāks
revīzija
3b0861bef5

+ 52 - 92
src/views/mallOrder/all/components/page-search.vue

@@ -1,79 +1,61 @@
 <template>
-    <ele-card :body-style="{ paddingBottom: '0' }">
-        <el-form :model="where" label-width="110px" @keyup.enter="search">
-            <el-row :gutter="15">
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="订单编号">
-                        <el-input v-model="where.orderId" placeholder="请输入" clearable />
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="收件人姓名">
-                        <el-input v-model="where.receiverName" placeholder="请输入" clearable />
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="收件人手机号">
-                        <el-input v-model="where.receiverPhone" placeholder="请输入" clearable />
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="发货物流单号">
-                        <el-input v-model="where.logisticsNo" placeholder="请输入" clearable />
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="退货物流单号">
-                        <el-input v-model="where.returnLogisticsNo" placeholder="请输入" clearable />
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="全部订单状态">
-                        <el-select v-model="where.status" placeholder="请选择" clearable>
-                            <el-option v-for="item in statusDicts" :key="item.dictValue" :label="item.dictLabel"
-                                :value="item.dictValue" />
-                        </el-select>
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="搜索备注关键字">
-                        <el-input v-model="where.keyword" placeholder="请输入" clearable />
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="催发货订单">
-                        <el-select v-model="where.urgeStatus" placeholder="请选择" clearable>
-                            <el-option label="催发货" value="1" />
-                        </el-select>
-                    </el-form-item>
-                </el-col>
-                <el-col :lg="6" :md="12">
-                    <el-form-item label="下单时间">
-                        <el-date-picker v-model="dateRange" type="daterange" range-separator="到"
-                            start-placeholder="开始时间" end-placeholder="结束时间" value-format="YYYY-MM-DD"
-                            style="width: 100%" @change="handleDateChange" />
-                    </el-form-item>
-                </el-col>
-
-                <el-col :lg="6" :md="12" class="mb-4">
-                    <el-button type="primary" @click="search">查询</el-button>
-                    <el-button @click="reset">重置</el-button>
-                </el-col>
-            </el-row>
-        </el-form>
+    <ele-card :body-style="{ paddingBottom: '8px' }">
+        <ProSearch
+            :items="formItems"
+            ref="searchRef"
+            @search="search"
+            :initKeys="initKeys"
+        />
     </ele-card>
 </template>
 
 <script setup>
 import { reactive, ref } from 'vue';
-import { useDictData } from '@/utils/use-dict-data';
+import ProSearch from '@/components/CommonPage/ProSearch2.vue';
 
 const emit = defineEmits(['search']);
+const searchRef = ref(null);
 
-// 字典
-const [statusDicts] = useDictData(['order_status']);
+const formItems = reactive([
+    { type: 'input', label: '订单编号', prop: 'orderId' },
+    { type: 'input', label: '收件人姓名', prop: 'receiverName' },
+    { type: 'input', label: '收件人手机号', prop: 'receiverPhone' },
+    { type: 'input', label: '发货物流单号', prop: 'logisticsNo' },
+    { type: 'input', label: '退货物流单号', prop: 'returnLogisticsNo' },
+    { 
+        type: 'dictSelect', 
+        label: '全部订单状态', 
+        prop: 'status', 
+        props: { code: 'order_status' } 
+    },
+    { type: 'input', label: '搜索备注关键字', prop: 'keyword' },
+    {
+        type: 'select',
+        label: '催发货订单',
+        prop: 'urgeStatus',
+        options: [
+            { label: '催发货', value: '1' }
+        ]
+    },
+    {
+        type: 'daterange',
+        label: '下单时间',
+        prop: 'dateRange',
+        keys: ['startTime', 'endTime'],
+        props: {
+            format: 'YYYY-MM-DD',
+            valueFormat: 'YYYY-MM-DD',
+            onChange: (val) => {
+                searchRef.value?.setData({
+                    startTime: val && val.length > 0 ? val[0] : '',
+                    endTime: val && val.length > 0 ? val[1] : ''
+                });
+            }
+        }
+    }
+]);
 
-const where = reactive({
+const initKeys = reactive({
     orderId: '',
     receiverName: '',
     receiverPhone: '',
@@ -86,31 +68,9 @@ const where = reactive({
     endTime: ''
 });
 
-const dateRange = ref([]);
-
-const handleDateChange = (val) => {
-    if (val && val.length === 2) {
-        where.startTime = val[0];
-        where.endTime = val[1];
-    } else {
-        where.startTime = '';
-        where.endTime = '';
-    }
-};
-
-const search = () => {
-    emit('search', { ...where });
-};
-
-const reset = () => {
-    Object.keys(where).forEach(key => where[key] = '');
-    dateRange.value = [];
-    search();
+const search = (data) => {
+    let params = JSON.parse(JSON.stringify(data));
+    delete params.dateRange;
+    emit('search', params);
 };
 </script>
-
-<style scoped>
-.mb-4 {
-    margin-bottom: 16px;
-}
-</style>

+ 81 - 0
src/views/mallOrder/refund/components/page-search.vue

@@ -0,0 +1,81 @@
+<template>
+    <ele-card :body-style="{ paddingBottom: '8px' }">
+        <ProSearch
+            :items="formItems"
+            ref="searchRef"
+            @search="search"
+            :initKeys="initKeys"
+        />
+    </ele-card>
+</template>
+
+<script setup>
+import { reactive, ref } from 'vue';
+import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+
+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: 'select',
+        label: '全部售后状态',
+        prop: 'afterSalesStatus',
+        options: [
+            { label: '退款成功', value: 'success' },
+            { label: '退款中', value: 'processing' },
+            { label: '商家处理中', value: 'merchant_processing' },
+            { label: '商家待收货', value: 'merchant_receiving' },
+            { label: '退款关闭', value: 'closed' }
+        ]
+    },
+    { type: 'input', label: '退款编号', prop: 'refundId' },
+    {
+        type: 'select',
+        label: '售后类型',
+        prop: 'afterSalesType',
+        options: [
+            { label: '仅退款', value: 'only_refund' },
+            { label: '退货退款', value: 'return_refund' }
+        ]
+    },
+    {
+        type: 'daterange',
+        label: '申请时间',
+        prop: 'dateRange',
+        keys: ['startTime', 'endTime'],
+        props: {
+            format: 'YYYY-MM-DD',
+            valueFormat: 'YYYY-MM-DD',
+            onChange: (val) => {
+                searchRef.value?.setData({
+                    startTime: val && val.length > 0 ? val[0] : '',
+                    endTime: val && val.length > 0 ? val[1] : ''
+                });
+            }
+        }
+    }
+]);
+
+const initKeys = reactive({
+    orderId: '',
+    receiverPhone: '',
+    logisticsNo: '',
+    returnLogisticsNo: '',
+    afterSalesStatus: '',
+    refundId: '',
+    afterSalesType: '',
+    startTime: '',
+    endTime: ''
+});
+
+const search = (data) => {
+    let params = JSON.parse(JSON.stringify(data));
+    delete params.dateRange;
+    emit('search', params);
+};
+</script>

+ 209 - 0
src/views/mallOrder/refund/components/push-sms-dialog.vue

@@ -0,0 +1,209 @@
+<template>
+    <ele-modal
+        form
+        :width="800"
+        v-model="visible"
+        title="推送短信"
+        @open="handleOpen"
+    >
+        <el-form
+            ref="formRef"
+            :model="form"
+            :rules="rules"
+            label-width="80px"
+            @submit.prevent=""
+        >
+            <el-form-item label="短信类型" prop="type">
+                <el-radio-group v-model="form.type" @change="handleChangeType">
+                    <el-radio
+                        :value="item.type"
+                        v-for="item in smsContentList"
+                        :key="item.type"
+                        >{{ item.typeName }}</el-radio
+                    >
+                </el-radio-group>
+            </el-form-item>
+            <el-form-item label="短信预览" prop="remark">
+                <el-input
+                    :rows="4"
+                    type="textarea"
+                    v-model="form.remark"
+                    disabled
+                    placeholder="请输入短信预览"
+                />
+            </el-form-item>
+            <el-form-item label="推送历史" prop="records">
+                <ele-data-table
+                    row-key="id"
+                    :columns="columns"
+                    :data="smsRecords"
+                    height="200px"
+                    :show-overflow-tooltip="true"
+                />
+            </el-form-item>
+        </el-form>
+
+        <template #footer>
+            <el-button @click="handleCancel">关闭</el-button>
+            <el-button type="primary" @click="handleSubmit">确定</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance } from 'vue';
+import { EleMessage } from 'ele-admin-plus/es';
+
+const { proxy } = getCurrentInstance();
+const emit = defineEmits(['success']);
+
+/** 弹窗是否打开 */
+const visible = defineModel({ type: Boolean });
+
+/** 表单引用 */
+const formRef = ref();
+
+/** SMS记录数据 */
+const smsRecords = ref([]);
+
+/** 短信内容列表 */
+const smsContentList = ref([]);
+
+const form = reactive({
+    orderId: null,
+    type: '',
+    remark: ''
+});
+
+/** 短信类型变化 */
+const handleChangeType = (val) => {
+    const found = smsContentList.value.find((item) => item.type === val);
+    if (found) {
+        form.remark = found.smsContent;
+    }
+};
+
+/** 表单验证规则 */
+const rules = reactive({
+    type: [
+        {
+            required: true,
+            message: '请选择短信类型',
+            trigger: 'change'
+        }
+    ],
+    remark: [
+        {
+            required: true,
+            message: '请输入短信预览',
+            type: 'string',
+            trigger: 'blur'
+        }
+    ]
+});
+
+// 表格列配置
+const columns = reactive([
+    { label: '发送人', prop: 'createName', width: 100, align: 'center' },
+    { label: '发送内容', prop: 'smsContent', align: 'center', minWidth: 200 },
+    { label: '发送时间', prop: 'createTime', width: 160, align: 'center' },
+    {
+        label: '发送状态',
+        prop: 'status',
+        width: 100,
+        align: 'center',
+        formatter: (row) => (row.status == 1 ? '成功' : '失败')
+    }
+]);
+
+/** 关闭弹窗 */
+const handleCancel = () => {
+    visible.value = false;
+    resetForm();
+};
+
+/** 重置表单 */
+const resetForm = () => {
+    form.orderId = null;
+    form.type = '';
+    form.remark = '';
+    formRef.value?.resetFields();
+    smsRecords.value = [];
+};
+
+/** 弹窗打开事件 */
+const handleOpen = (row) => {
+    resetForm();
+    // 模拟获取数据,实际开发中可以使用 row.orderId 调用接口
+    if (row) {
+        form.orderId = row.orderId || 123;
+        getSmsLogInfo(form.orderId);
+    } else {
+        // 如果没有 row 传入,也可以默认加载 mock 数据方便预览
+        getSmsLogInfo(123);
+    }
+    visible.value = true;
+};
+
+/** 获取短信记录信息 (Mock) */
+const getSmsLogInfo = (orderId) => {
+    // 模拟 API 请求延迟
+    setTimeout(() => {
+        // Mock data
+        smsContentList.value = [
+            { type: 'urge', typeName: '催发货提醒', smsContent: '亲,您的订单已发货,请注意查收。' },
+            { type: 'receive', typeName: '收货提醒', smsContent: '亲,您的宝贝已送达,请确认收货。' },
+            { type: 'good_review', typeName: '好评提醒', smsContent: '亲,如果您对宝贝满意,请给个好评哦~' }
+        ];
+
+        smsRecords.value = [
+            {
+                id: 1,
+                createName: '管理员',
+                smsContent: '亲,您的订单已发货,请注意查收。',
+                createTime: '2023-10-27 10:00:00',
+                status: 1
+            },
+            {
+                id: 2,
+                createName: '客服小美',
+                smsContent: '亲,您的宝贝已送达,请确认收货。',
+                createTime: '2023-10-28 14:30:00',
+                status: 1
+            }
+        ];
+    }, 100);
+};
+
+/** 提交表单 */
+const handleSubmit = () => {
+    formRef.value?.validate(async (valid) => {
+        if (!valid) return;
+        
+        // 模拟提交
+        setTimeout(() => {
+            EleMessage.success('短信发送成功');
+            // 添加一条记录到 mock 列表,模拟实时更新
+            smsRecords.value.unshift({
+                id: Date.now(),
+                createName: '当前用户',
+                smsContent: form.remark,
+                createTime: new Date().toLocaleString(),
+                status: 1
+            });
+            // 这里的业务逻辑是发送后不一定马上关闭,或者可以关闭
+            // 参考 send-SMS.vue 是关闭的
+            handleCancel();
+            emit('success');
+        }, 500);
+    });
+};
+
+defineExpose({
+    handleOpen
+});
+</script>
+
+<style scoped>
+/* 可以在这里添加样式微调 */
+</style>

+ 210 - 0
src/views/mallOrder/refund/components/refund-item.vue

@@ -0,0 +1,210 @@
+<template>
+    <div class="refund-item">
+        <!-- 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>
+        </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>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 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>
+
+            <!-- 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>
+                </div>
+            </div>
+
+            <!-- Reason (Flex 1) -->
+            <div class="col-merged col-reason">
+                <div>{{ item.refundReason }}</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>
+                </div>
+                <div class="logistics-row">
+                    <span>买家退货: {{ item.logistics.buyerReturned ? '运输中' : '未发货' }}</span>
+                    <el-button link type="primary" size="small">[查看]</el-button>
+                </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>
+
+            <!-- Action (Flex 1) -->
+            <div class="col-merged col-action">
+                <div class="action-btn text-green-500" @click="$emit('push-sms', item)">[推送短信]</div>
+                <div class="action-btn text-orange-500" @click="$emit('send-packet', item)">[发红包]</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { CopyDocument } from '@element-plus/icons-vue';
+import { EleMessage } from 'ele-admin-plus/es';
+import { useClipboard } from '@vueuse/core';
+
+const props = defineProps({
+    item: {
+        type: Object,
+        required: true
+    }
+});
+
+defineEmits(['push-sms', 'send-packet']);
+
+const { copy } = useClipboard();
+
+const handleCopy = async (text) => {
+    try {
+        await copy(text);
+        EleMessage.success('复制成功');
+    } catch (e) {
+        EleMessage.error('复制失败');
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.refund-item {
+    border: 1px solid #ebeef5;
+    margin-bottom: 15px;
+    background: #fff;
+
+    .refund-header {
+        background-color: #f5f7fa;
+        padding: 8px 15px;
+        font-size: 13px;
+        display: flex;
+        align-items: center;
+        border-bottom: 1px solid #ebeef5;
+    }
+
+    .refund-body {
+        display: flex;
+        align-items: stretch;
+
+        .col-product {
+            flex: 3;
+            padding: 15px;
+            
+            .product-info {
+                display: flex;
+                
+                .product-img {
+                    width: 80px;
+                    height: 80px;
+                    margin-right: 15px;
+                    border-radius: 4px;
+                    border: 1px solid #eee;
+                }
+
+                .product-detail {
+                    font-size: 13px;
+                    
+                    .product-title {
+                        font-weight: 500;
+                        margin-bottom: 5px;
+                        color: #333;
+                    }
+                    
+                    .product-isbn, .product-code {
+                        color: #666;
+                        margin-bottom: 3px;
+                        font-size: 12px;
+                        
+                        .link {
+                            color: #409eff;
+                        }
+                    }
+                    
+                    .product-price {
+                        margin-top: 5px;
+                        color: #333;
+                    }
+                }
+            }
+        }
+
+        .col-merged {
+            border-left: 1px solid #ebeef5;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            padding: 10px;
+            text-align: center;
+            font-size: 13px;
+            
+            &.col-amount { flex: 1; }
+            &.col-buyer { flex: 1; }
+            &.col-reason { flex: 1; }
+            &.col-logistics { 
+                flex: 1.5; 
+                align-items: flex-start;
+                padding-left: 15px;
+                
+                .logistics-row {
+                    display: flex;
+                    align-items: center;
+                    margin-bottom: 4px;
+                    color: #666;
+                    font-size: 12px;
+                }
+            }
+            &.col-status { flex: 1; }
+            &.col-action { flex: 1; }
+        }
+    }
+    
+    .action-btn {
+        cursor: pointer;
+        margin-bottom: 5px;
+        font-size: 13px;
+        
+        &:hover {
+            opacity: 0.8;
+        }
+    }
+}
+</style>

+ 87 - 0
src/views/mallOrder/refund/components/refund-table-header.vue

@@ -0,0 +1,87 @@
+<template>
+    <div class="refund-table-header">
+        <div class="col-product">
+            <el-checkbox :model-value="checkAll" :indeterminate="isIndeterminate" @change="handleChange">
+                全选
+            </el-checkbox>
+            <span class="ml-4">商品信息</span>
+        </div>
+        <div class="col-amount">实收金额/退款金额</div>
+        <div class="col-buyer">买家</div>
+        <div class="col-reason">售后原因</div>
+        <div class="col-logistics">物流状态</div>
+        <div class="col-status">售后状态</div>
+        <div class="col-action">操作</div>
+    </div>
+</template>
+
+<script setup>
+defineProps({
+    checkAll: {
+        type: Boolean,
+        default: false
+    },
+    isIndeterminate: {
+        type: Boolean,
+        default: false
+    }
+});
+
+const emit = defineEmits(['update:checkAll', 'change']);
+
+const handleChange = (val) => {
+    emit('update:checkAll', val);
+    emit('change', val);
+};
+</script>
+
+<style lang="scss" scoped>
+.refund-table-header {
+    display: flex;
+    background-color: #f5f7fa;
+    padding: 10px 0;
+    border: 1px solid #ebeef5;
+    border-bottom: none;
+    font-size: 13px;
+    font-weight: bold;
+    color: #909399;
+
+    div {
+        text-align: center;
+        padding: 0 10px;
+    }
+
+    .col-product {
+        flex: 3;
+        text-align: left;
+        padding-left: 16px;
+        display: flex;
+        align-items: center;
+        font-size: 14px;
+    }
+
+    .col-amount {
+        flex: 1;
+    }
+
+    .col-buyer {
+        flex: 1;
+    }
+
+    .col-reason {
+        flex: 1;
+    }
+
+    .col-logistics {
+        flex: 1.5;
+    }
+
+    .col-status {
+        flex: 1;
+    }
+
+    .col-action {
+        flex: 1;
+    }
+}
+</style>

+ 228 - 0
src/views/mallOrder/refund/index.vue

@@ -0,0 +1,228 @@
+<template>
+    <ele-page>
+        <!-- Stats Header -->
+        <div class="stats-container">
+            <el-row :gutter="15">
+                <el-col :span="4">
+                    <el-card shadow="never" class="stat-card bg-gray-50" :body-style="{ padding: '10px' }">
+                        <div class="stat-title">退款处理时长</div>
+                        <div class="stat-value">6.35h</div>
+                    </el-card>
+                </el-col>
+                <el-col :span="4">
+                    <el-card shadow="never" class="stat-card bg-gray-50" :body-style="{ padding: '10px' }">
+                        <div class="stat-title">待平台处理</div>
+                        <div class="stat-value">10</div>
+                    </el-card>
+                </el-col>
+                <el-col :span="4">
+                    <el-card shadow="never" class="stat-card bg-gray-50" :body-style="{ padding: '10px' }">
+                        <div class="stat-title">退款待处理</div>
+                        <div class="stat-value">10</div>
+                    </el-card>
+                </el-col>
+                <el-col :span="4">
+                    <el-card shadow="never" class="stat-card bg-gray-50" :body-style="{ padding: '10px' }">
+                        <div class="stat-title">待平台收货</div>
+                        <div class="stat-value">10</div>
+                    </el-card>
+                </el-col>
+                <el-col :span="4">
+                    <el-card shadow="never" class="stat-card bg-gray-50" :body-style="{ padding: '10px' }">
+                        <div class="stat-title">24小时内即将追期</div>
+                        <div class="stat-value">10</div>
+                    </el-card>
+                </el-col>
+                <el-col :span="4">
+                    <el-card shadow="never" class="stat-card bg-gray-50" :body-style="{ padding: '10px' }">
+                        <div class="stat-title">待买家处理</div>
+                        <div class="stat-value">10</div>
+                    </el-card>
+                </el-col>
+            </el-row>
+        </div>
+
+        <!-- Search -->
+        <page-search @search="handleSearch" />
+
+        <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-tabs>
+
+            <!-- Table Header -->
+            <div class="mt-2">
+                <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>
+
+            <!-- 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"
+                />
+            </div>
+        </ele-card>
+
+        <!-- Dialogs -->
+        <push-sms-dialog ref="pushSmsDialogRef" @success="handleSearch" />
+    </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('发红包功能暂未实现');
+};
+</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;
+    }
+}
+</style>