Forráskód Böngészése

feat(退款管理): 新增协商退货退款功能并优化退款详情页

refactor(商品管理): 简化价格编辑弹窗代码结构

style: 统一格式化代码缩进和换行
ylong 2 hete
szülő
commit
c22fe0aade

+ 58 - 64
src/views/goods/list/components/edit-price-modal.vue

@@ -1,10 +1,5 @@
 <template>
-    <ele-modal
-        :width="500"
-        title="编辑价格"
-        v-model="visible"
-        @closed="handleClosed"
-    >
+    <ele-modal :width="500" title="编辑价格" v-model="visible" @closed="handleClosed">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
             <el-form-item label="商品标题">
                 <span>{{ rowData?.bookName || '-' }}</span>
@@ -13,81 +8,80 @@
                 <span>{{ rowData?.isbn || '-' }}</span>
             </el-form-item>
             <el-form-item label="一般品售价" prop="productPrice">
-                <el-input-number
-                    v-model="form.productPrice"
-                    :min="0"
-                    :precision="2"
-                    :step="0.1"
-                    style="width: 100%"
-                />
+                <el-input-number v-model="form.productPrice" :min="0" :precision="2" :step="0.1" style="width: 100%" />
             </el-form-item>
         </el-form>
 
         <template #footer>
             <el-button @click="visible = false">取消</el-button>
-            <el-button type="primary" @click="handleSubmit" :loading="loading"
-                >提交</el-button
-            >
+            <el-button type="primary" @click="handleSubmit" :loading="loading">提交</el-button>
         </template>
     </ele-modal>
 </template>
 
 <script setup>
-    import { ref, reactive } from 'vue';
-    import { EleMessage } from 'ele-admin-plus/es';
-    import request from '@/utils/request';
+import { ref, reactive } from 'vue';
+import { EleMessage } from 'ele-admin-plus/es';
+import request from '@/utils/request';
 
-    const emit = defineEmits(['done']);
-    // const visible = defineModel({ type: Boolean });
-    const visible = ref(false);
-    const loading = ref(false);
-    const rowData = ref({});
-    const formRef = ref(null);
+const emit = defineEmits(['done']);
+// const visible = defineModel({ type: Boolean });
+const visible = ref(false);
+const loading = ref(false);
+const rowData = ref({});
+const formRef = ref(null);
 
-    const form = reactive({
-        productPrice: 0
-    });
+const form = reactive({
+    productPrice: 0
+});
+
+const rules = {
+    productPrice: [
+        { required: true, message: '请输入售价', trigger: 'blur' }
+    ]
+};
 
-    const rules = {
-        productPrice: [
-            { required: true, message: '请输入售价', trigger: 'blur' }
-        ]
-    };
+// 打开弹窗
+const open = (row) => {
+    rowData.value = row;
+    form.productPrice = Number(row.productPrice) || 0;
+    visible.value = true;
+};
 
-    // 打开弹窗
-    const open = (row) => {
-        rowData.value = row;
-        form.productPrice = Number(row.productPrice) || 0;
-        visible.value = true;
-    };
+const handleSubmit = () => {
+    formRef.value?.validate(async (valid) => {
+        if (!valid) return;
 
-    const handleSubmit = () => {
-        formRef.value?.validate(async (valid) => {
-            if (!valid) return;
+        loading.value = true;
+        try {
+            request.post('/shop/shopbook/setInfo', {
+                isbn: rowData.value.isbn,
+                productPrice: form.productPrice
+            }).then((res) => {
+                if (res.data.code === 200) {
+                    EleMessage.success('价格更新成功');
+                    visible.value = false;
+                    emit('done');
+                } else {
+                    EleMessage.error(res.data.msg || '更新失败');
+                }
+            });
 
-            loading.value = true;
-            try {
-                await request.post('/shop/shopbook/setInfo', {
-                    isbn: rowData.value.isbn,
-                    productPrice: form.productPrice
-                });
 
-                EleMessage.success('价格更新成功');
-                visible.value = false;
-                emit('done');
-            } catch (e) {
-                EleMessage.error(e.message || '更新失败');
-            } finally {
-                loading.value = false;
-            }
-        });
-    };
 
-    const handleClosed = () => {
-        rowData.value = {};
-        form.productPrice = 0;
-        formRef.value?.resetFields();
-    };
+        } catch (e) {
+            EleMessage.error(e.message || '更新失败');
+        } finally {
+            loading.value = false;
+        }
+    });
+};
+
+const handleClosed = () => {
+    rowData.value = {};
+    form.productPrice = 0;
+    formRef.value?.resetFields();
+};
 
-    defineExpose({ open });
+defineExpose({ open });
 </script>

+ 312 - 0
src/views/mallOrder/refund/components/negotiate-return-dialog.vue

@@ -0,0 +1,312 @@
+<template>
+    <ele-modal
+        :width="600"
+        v-model="visible"
+        title="与买家协商"
+        :body-style="{ padding: '20px 40px' }"
+    >
+        <div
+            class="mb-4 p-3 bg-emerald-50 text-emerald-600 rounded text-sm flex items-start"
+        >
+            <el-icon class="mr-2 mt-0.5 text-emerald-500"
+                ><WarningFilled
+            /></el-icon>
+            <span
+                >提示:与买家协商退货退款,买家接受后将自动同意退款申请,请选择退货地址</span
+            >
+        </div>
+
+        <el-form
+            :model="form"
+            :rules="rules"
+            ref="formRef"
+            label-width="100px"
+            label-position="right"
+        >
+            <el-form-item label="退款原因:">
+                <span class="text-gray-700">{{ refundReason }}</span>
+            </el-form-item>
+
+            <el-form-item label="退款金额:">
+                <div v-if="!isEditingMoney" class="flex items-center">
+                    <span class="text-gray-700 mr-2"
+                        >¥{{ form.disposeMoney }}</span
+                    >
+                    <el-button link type="success" @click="startEditMoney">
+                        <el-icon class="mr-1"><Edit /></el-icon>修改
+                    </el-button>
+                </div>
+                <div v-else class="flex items-center">
+                    <el-input-number
+                        v-model="form.disposeMoney"
+                        :min="0.01"
+                        :max="maxRefundAmount"
+                        :precision="2"
+                        :step="0.01"
+                        controls-position="right"
+                        style="width: 150px"
+                        class="mr-2"
+                    />
+                    <el-button
+                        link
+                        type="primary"
+                        @click="isEditingMoney = false"
+                        >确认</el-button
+                    >
+                </div>
+            </el-form-item>
+
+            <el-form-item label="退货地址:" prop="receiverAddressId">
+                <el-popover
+                    v-model:visible="addressPickerVisible"
+                    trigger="click"
+                    placement="bottom-start"
+                    :width="520"
+                    popper-class="refund-address-popper"
+                >
+                    <template #reference>
+                        <div
+                            class="w-full flex items-start flex-col cursor-pointer"
+                        >
+                            <div
+                                v-if="selectedAddress"
+                                class="text-gray-700 mb-2 leading-relaxed"
+                            >
+                                {{ selectedAddress.name }},{{
+                                    selectedAddress.mobile
+                                }},{{ selectedAddress.address }}
+                            </div>
+                            <div v-else class="text-gray-400 mb-2">
+                                请选择退货地址
+                            </div>
+                            <el-button link type="success">
+                                <el-icon class="mr-1"><Switch /></el-icon
+                                >切换地址
+                            </el-button>
+                        </div>
+                    </template>
+
+                    <div class="max-h-72 overflow-auto p-2">
+                        <div
+                            v-if="addressLoading"
+                            class="py-6 flex items-center justify-center text-gray-500 text-sm"
+                        >
+                            <el-icon class="is-loading mr-2"
+                                ><Loading
+                            /></el-icon>
+                            加载中
+                        </div>
+                        <div
+                            v-else-if="!addressList.length"
+                            class="py-6 text-center text-gray-400 text-sm"
+                        >
+                            暂无可用退货地址
+                        </div>
+                        <div v-else class="space-y-2">
+                            <div
+                                v-for="item in addressList"
+                                :key="item.id"
+                                class="rounded-md border px-3 py-2 cursor-pointer transition"
+                                :class="
+                                    String(form.receiverAddressId) ===
+                                    String(item.id)
+                                        ? 'border-emerald-400 bg-emerald-50'
+                                        : 'border-gray-100 hover:border-gray-200 hover:bg-gray-50'
+                                "
+                                @click="handleSelectAddress(item)"
+                            >
+                                <div class="flex items-center">
+                                    <div class="font-medium text-gray-900">
+                                        {{ item.name }}
+                                    </div>
+                                    <div class="ml-2 text-sm text-gray-500">
+                                        {{ item.mobile }}
+                                    </div>
+                                    <el-tag
+                                        v-if="
+                                            Number(item.defaultReturnFlag) === 1
+                                        "
+                                        size="small"
+                                        type="success"
+                                        class="ml-2"
+                                        >默认</el-tag
+                                    >
+                                </div>
+                                <div class="text-xs text-gray-500 mt-1">
+                                    {{ item.address }}
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </el-popover>
+            </el-form-item>
+        </el-form>
+
+        <template #footer>
+            <div class="flex justify-center gap-4">
+                <el-button
+                    type="success"
+                    :loading="loading"
+                    @click="handleSubmit"
+                    >提交</el-button
+                >
+                <el-button @click="visible = false">取消</el-button>
+            </div>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref, reactive, computed } from 'vue';
+    import { EleMessage } from 'ele-admin-plus/es';
+    import {
+        Loading,
+        WarningFilled,
+        Edit,
+        Switch
+    } from '@element-plus/icons-vue';
+    import request from '@/utils/request';
+
+    const visible = ref(false);
+    const loading = ref(false);
+    const addressLoading = ref(false);
+    const addressPickerVisible = ref(false);
+    const formRef = ref(null);
+    const currentRefundOrderId = ref('');
+    const refundReason = ref('');
+    const maxRefundAmount = ref(0);
+    const isEditingMoney = ref(false);
+
+    const addressList = ref([]);
+    const selectedAddress = computed(() => {
+        const id = String(form.receiverAddressId || '');
+        if (!id) return null;
+        return addressList.value.find((a) => String(a.id) === id) || null;
+    });
+
+    const form = reactive({
+        receiverAddressId: '',
+        disposeMoney: 0
+    });
+
+    const rules = {
+        receiverAddressId: [
+            { required: true, message: '请选择退货地址', trigger: 'change' }
+        ]
+    };
+
+    const emit = defineEmits(['success']);
+
+    const fetchAddressList = () => {
+        addressLoading.value = true;
+        request
+            .get('/shop/shopAddress/getRefundAddressList')
+            .then((res) => {
+                if (res.data.code === 200) {
+                    addressList.value = res.data.data || [];
+                    if (
+                        addressList.value.length > 0 &&
+                        !form.receiverAddressId
+                    ) {
+                        const defaultAddr = addressList.value.find(
+                            (a) => Number(a.defaultReturnFlag) === 1
+                        );
+                        form.receiverAddressId = defaultAddr
+                            ? defaultAddr.id
+                            : addressList.value[0].id;
+                    }
+                } else {
+                    EleMessage.error(res.data.msg || '获取地址列表失败');
+                }
+            })
+            .catch((e) => {
+                console.error(e);
+                EleMessage.error('获取地址列表失败');
+            })
+            .finally(() => {
+                addressLoading.value = false;
+            });
+    };
+
+    const handleSelectAddress = (item) => {
+        form.receiverAddressId = item.id;
+        addressPickerVisible.value = false;
+        formRef.value?.validateField?.('receiverAddressId');
+    };
+
+    const open = (data) => {
+        currentRefundOrderId.value = data.refundOrderId;
+        refundReason.value = data.refundReason;
+        maxRefundAmount.value = Number(data.refundMoney) || 0;
+
+        form.disposeMoney = maxRefundAmount.value;
+        form.receiverAddressId = '';
+        isEditingMoney.value = false;
+        addressPickerVisible.value = false;
+
+        if (formRef.value) {
+            formRef.value.clearValidate();
+        }
+        fetchAddressList();
+        visible.value = true;
+    };
+
+    const startEditMoney = () => {
+        isEditingMoney.value = true;
+    };
+
+    const handleSubmit = async () => {
+        if (!formRef.value) return;
+
+        await formRef.value.validate((valid) => {
+            if (valid) {
+                if (
+                    form.disposeMoney === undefined ||
+                    form.disposeMoney === null
+                ) {
+                    EleMessage.warning('请输入退款金额');
+                    return;
+                }
+                submitData();
+            }
+        });
+    };
+
+    const submitData = () => {
+        const params = {
+            refundOrderId: currentRefundOrderId.value,
+            disposeMoney: form.disposeMoney,
+            receiverAddressId: form.receiverAddressId
+        };
+
+        loading.value = true;
+        request
+            .post('/shop/shopOrder/refund/disposeRefundType', params)
+            .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(() => {
+                loading.value = false;
+            });
+    };
+
+    defineExpose({
+        open
+    });
+</script>
+
+<style scoped>
+    :deep(.refund-address-popper) {
+        padding: 0;
+    }
+</style>

+ 473 - 327
src/views/mallOrder/refund/components/refund-detail-dialog.vue

@@ -1,6 +1,11 @@
 <template>
-    <ele-modal :width="1200" v-model="visible" title="退款详情" fullscreen
-        :body-style="{ padding: '0 20px', height: 'calc(100vh - 120px)' }">
+    <ele-modal
+        :width="1200"
+        v-model="visible"
+        title="退款详情"
+        fullscreen
+        :body-style="{ padding: '0 20px', height: 'calc(100vh - 120px)' }"
+    >
         <div v-loading="loading" class="h-full flex flex-col">
             <!-- 顶部状态栏 -->
             <div class="status-bar mb-6">
@@ -11,24 +16,42 @@
                         </div>
 
                         <!-- 倒计时组件 -->
-                        <div v-if="deadline > 0" class="flex items-center text-red-500 text-sm mb-3">
+                        <div
+                            v-if="deadline > 0"
+                            class="flex items-center text-red-500 text-sm mb-3"
+                        >
                             <span class="mr-2">剩余</span>
-                            <el-countdown format="DD [天] HH [时] mm [分]" :value="deadline" :value-style="{ fontSize: '14px', color: '#ef4444', lineHeight: '1' }" />
+                            <el-countdown
+                                format="DD [天] HH [时] mm [分]"
+                                :value="deadline"
+                                :value-style="{
+                                    fontSize: '14px',
+                                    color: '#ef4444',
+                                    lineHeight: '1'
+                                }"
+                            />
                         </div>
 
                         <div class="text-sm text-gray-500 space-y-1">
-                            <template v-if="['1', '3'].includes(String(form.status))">
+                            <template
+                                v-if="['1', '3'].includes(String(form.status))"
+                            >
                                 <div>{{
                                     isRefundOnly
                                         ? '请及时联系买家协商退款事宜'
                                         : '请及时联系买家协商退货事宜'
                                 }}</div>
-                                <div v-if="form.refundType == 1">该退款为卖家原因退款,若您同意买家退货,退货运费将由您承担。</div>
+                                <div v-if="form.refundType == 1"
+                                    >该退款为卖家原因退款,若您同意买家退货,退货运费将由您承担。</div
+                                >
                             </template>
 
-                            <template v-else-if="
-                                String(form.status) === '7' && String(form.refundType) === '1'
-                            ">
+                            <template
+                                v-else-if="
+                                    String(form.status) === '7' &&
+                                    String(form.refundType) === '1'
+                                "
+                            >
                                 <div>买家已发货,请及时验货并处理退款</div>
                             </template>
 
@@ -38,25 +61,35 @@
 
                             <template v-else-if="String(form.status) === '2'">
                                 <div>买家修改退款申请后,需要您重新处理</div>
-                                <div>如果买家超时未响应,退款申请将自动关闭</div>
+                                <div
+                                    >如果买家超时未响应,退款申请将自动关闭</div
+                                >
                             </template>
 
                             <template v-else-if="String(form.status) === '9'">
                                 <div>退款成功时间:{{ form.finishTime }}</div>
-                                <div>退款金额:¥
+                                <div
+                                    >退款金额:¥
                                     {{
                                         form.refundMoneyFinal ||
                                         form.refundMoney
-                                    }}元</div>
-                                <div>退款规则:符合未发货秒退
-                                </div>
+                                    }}元</div
+                                >
+                                <div>退款规则:符合未发货秒退 </div>
                             </template>
                         </div>
                     </div>
                     <div style="min-width: 600px" class="pt-2">
-                        <el-steps :active="currentStep" align-center finish-status="success" :process-status="processStatus">
+                        <el-steps
+                            :active="currentStep"
+                            align-center
+                            finish-status="success"
+                            :process-status="processStatus"
+                        >
                             <template v-if="isRefundOnly">
-                                <el-step :title="`买家申请${getRefundTypeText(form.refundType)}`" />
+                                <el-step
+                                    :title="`买家申请${getRefundTypeText(form.refundType)}`"
+                                />
                                 <el-step title="卖家处理退款申请" />
                                 <el-step title="退款完毕" />
                             </template>
@@ -79,15 +112,14 @@
                         <span class="label">退款金额:</span>
                         <span class="value text-red-500 font-bold">
                             ¥{{ form.refundMoney }}
-                            <span v-if="form.refundMoneyFinal" class="text-xs text-gray-500 ml-1">
+                            <span
+                                v-if="form.refundMoneyFinal"
+                                class="text-xs text-gray-500 ml-1"
+                            >
                                 (实退: ¥{{ form.refundMoneyFinal }})
                             </span>
                         </span>
                     </div>
-                    <div class="info-item">
-                        <span class="label">申请件数:</span>
-                        <span class="value">{{ form.totalNum }}</span>
-                    </div>
                     <div class="info-item">
                         <span class="label">退款原因:</span>
                         <span class="value">{{ form.refundReason }}</span>
@@ -96,13 +128,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>
@@ -111,24 +143,42 @@
                     <div class="info-item">
                         <span class="label">退款编号:</span>
                         <span class="value">{{ form.refundOrderId }}</span>
-                        <el-icon class="copy-icon" @click="handleCopy(form.refundOrderId)">
+                        <el-icon
+                            class="copy-icon"
+                            @click="handleCopy(form.refundOrderId)"
+                        >
                             <CopyDocument />
                         </el-icon>
                     </div>
                 </div>
 
                 <!-- 中间:交易信息 -->
-                <div class="section transaction-info flex-[1.2] overflow-y-auto">
+                <div
+                    class="section transaction-info flex-[1.2] overflow-y-auto"
+                >
                     <div class="section-title">交易信息</div>
                     <div class="product-list mb-4">
-                        <div v-for="(prod, idx) in form.detailList" :key="idx" class="product-item flex mb-2">
-                            <el-image :src="prod.cover" class="w-16 h-16 rounded mr-2" fit="cover" />
+                        <div
+                            v-for="(prod, idx) in form.detailList"
+                            :key="idx"
+                            class="product-item flex mb-2"
+                        >
+                            <el-image
+                                :src="prod.cover"
+                                class="w-16 h-16 rounded mr-2"
+                                fit="cover"
+                            />
                             <div class="flex-1">
-                                <div class="text-sm font-bold truncate-2-lines">{{ prod.bookName }}</div>
+                                <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 class="text-xs text-gray-500 mt-1">{{ prod.payPrice }} × {{ prod.num }}</div>
+                                }}</div>
+                                <div class="text-xs text-gray-500 mt-1"
+                                    >{{ prod.payPrice }} × {{ prod.num }}</div
+                                >
                             </div>
                         </div>
                     </div>
@@ -136,21 +186,35 @@
                     <div class="info-item">
                         <span class="label">买家:</span>
                         <div class="flex items-center">
-                            <el-avatar :size="20" :src="form.avatar" class="mr-2" />
-                            <span class="value text-blue-500">{{ form.userNick }}/{{ form.userId }}</span>
+                            <el-avatar
+                                :size="20"
+                                :src="form.avatar"
+                                class="mr-2"
+                            />
+                            <span class="value text-blue-500"
+                                >{{ form.userNick }}/{{ form.userId }}</span
+                            >
                         </div>
                     </div>
                     <div class="info-item">
                         <span class="label">申请退款:</span>
-                        <span class="value">¥{{ form.refundMoney || '0.00' }}</span>
+                        <span class="value"
+                            >¥{{ form.refundMoney || '0.00' }}</span
+                        >
                     </div>
                     <div class="info-item">
                         <span class="label">邮费:</span>
-                        <span class="value">¥{{ form.expressMoney || '0.00' }}</span>
+                        <span class="value"
+                            >¥{{ form.expressMoney || '0.00' }}</span
+                        >
                     </div>
                     <div class="info-item">
                         <span class="label">订单标签:</span>
-                        <dict-data code="shop_refund_status" v-model="form.status" type="tag" />
+                        <dict-data
+                            code="shop_refund_status"
+                            v-model="form.status"
+                            type="tag"
+                        />
                     </div>
                     <div class="info-item">
                         <span class="label">成交时间:</span>
@@ -160,8 +224,11 @@
                         <span class="label">订单编号:</span>
                         <span class="value text-blue-500">{{
                             form.originOrderId
-                            }}</span>
-                        <el-icon class="copy-icon" @click="handleCopy(form.originOrderId)">
+                        }}</span>
+                        <el-icon
+                            class="copy-icon"
+                            @click="handleCopy(form.originOrderId)"
+                        >
                             <CopyDocument />
                         </el-icon>
                     </div>
@@ -171,7 +238,10 @@
                         <div v-if="form.waybillCode">
                             <div class="text-sm mb-1">
                                 {{ form.expressName }} {{ form.waybillCode }}
-                                <el-icon class="copy-icon" @click="handleCopy(form.waybillCode)">
+                                <el-icon
+                                    class="copy-icon"
+                                    @click="handleCopy(form.waybillCode)"
+                                >
                                     <CopyDocument />
                                 </el-icon>
                             </div>
@@ -179,49 +249,84 @@
                                 您已在云南财大南苑荟华1栋菜鸟驿站完成取件,感谢使用菜鸟驿站,期待再次为您服务。
                             </div>
                         </div>
-                        <div v-else class="text-sm text-gray-400">暂无物流信息</div>
+                        <div v-else class="text-sm text-gray-400"
+                            >暂无物流信息</div
+                        >
                     </div>
                 </div>
 
                 <!-- 右侧:协商历史 -->
-                <div class="section history-info flex-[1.5] flex flex-col overflow-hidden">
-                    <div class="flex justify-between items-center mb-4 flex-shrink-0">
+                <div
+                    class="section history-info flex-[1.5] flex flex-col overflow-hidden"
+                >
+                    <div
+                        class="flex justify-between items-center mb-4 flex-shrink-0"
+                    >
                         <div class="section-title mb-0">协商历史</div>
-                        <el-button link type="primary" @click="handleLeaveMessage">我要留言</el-button>
+                        <el-button
+                            link
+                            type="primary"
+                            @click="handleLeaveMessage"
+                            >我要留言</el-button
+                        >
                     </div>
 
                     <div class="history-list flex-1 overflow-y-auto pr-2">
-                        <div v-for="(activity, index) in historyList" :key="index"
-                            class="history-item flex mb-4 pb-4 border-b border-gray-100 last:border-0">
+                        <div
+                            v-for="(activity, index) in historyList"
+                            :key="index"
+                            class="history-item flex mb-4 pb-4 border-b border-gray-100 last:border-0"
+                        >
                             <div class="avatar-wrapper mr-3">
-                                <el-avatar :size="40" :src="activity.imgPath" shape="square" class="rounded-lg" />
+                                <el-avatar
+                                    :size="40"
+                                    :src="activity.imgPath"
+                                    shape="square"
+                                    class="rounded-lg"
+                                />
                             </div>
                             <div class="content-wrapper flex-1">
-                                <div class="header flex justify-between items-start mb-1">
-                                    <span class="name font-bold text-gray-800">{{
-                                        activity.userName ||
-                                        (activity.userType === '2'
-                                            ? '客服'
-                                            : '用户')
-                                    }}</span>
+                                <div
+                                    class="header flex justify-between items-start mb-1"
+                                >
+                                    <span
+                                        class="name font-bold text-gray-800"
+                                        >{{
+                                            activity.userName ||
+                                            (activity.userType === '2'
+                                                ? '客服'
+                                                : '用户')
+                                        }}</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
+                                        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 &&
-                                        activity.imgList.length
-                                    " class="mt-2 flex flex-wrap gap-2">
-                                        <el-image v-for="(img, i) in activity.imgList" :key="i" :src="img"
+                                    <div
+                                        v-if="
+                                            activity.imgList &&
+                                            activity.imgList.length
+                                        "
+                                        class="mt-2 flex flex-wrap gap-2"
+                                    >
+                                        <el-image
+                                            v-for="(img, i) in activity.imgList"
+                                            :key="i"
+                                            :src="img"
                                             class="w-20 h-20 rounded border border-gray-200"
-                                            :preview-src-list="activity.imgList" fit="cover" />
+                                            :preview-src-list="activity.imgList"
+                                            fit="cover"
+                                        />
                                     </div>
                                 </div>
                             </div>
@@ -247,316 +352,357 @@
 
         <template #footer>
             <div class="flex justify-center gap-4">
-                <el-button :disabled="!canOperate && !['4', '5'].includes(String(form.status))">备注</el-button>
+                <el-button
+                    :disabled="
+                        !canOperate && !['4', '5'].includes(String(form.status))
+                    "
+                    >备注</el-button
+                >
                 <el-button @click="handleRefuse" :disabled="!canOperate">{{
                     isRefundOnly ? '拒绝退款申请' : '拒绝退货申请'
-                    }}</el-button>
+                }}</el-button>
                 <el-button @click="handleAgree" :disabled="!canOperate">{{
                     isRefundOnly ? '同意退款' : '同意退货'
-                    }}</el-button>
-                <el-button type="primary" @click="handleNegotiate" :disabled="!canOperate && !['4', '5'].includes(String(form.status))">与买家协商</el-button>
+                }}</el-button>
+                <el-button
+                    v-if="isRefundOnly"
+                    type="primary"
+                    @click="handleNegotiateReturn"
+                    :disabled="
+                        !canOperate && !['4', '5'].includes(String(form.status))
+                    "
+                    >协商退货退款</el-button
+                >
+                <el-button
+                    type="primary"
+                    @click="handleNegotiate"
+                    :disabled="
+                        !canOperate && !['4', '5'].includes(String(form.status))
+                    "
+                    >与买家协商</el-button
+                >
             </div>
         </template>
-        <negotiation-apply-dialog v-model="negotiationDialogVisible" :refund-order-id="form.refundOrderId"
-            :max-refund-amount="Number(form.refundMoney) || 0" @success="handleNegotiationSuccess" />
-        <refuse-dialog ref="refuseDialogRef" @success="handleNegotiationSuccess" />
-        <agree-dialog ref="agreeDialogRef" @success="handleNegotiationSuccess" />
-        <confirm-refund-dialog ref="confirmRefundDialogRef" @success="handleNegotiationSuccess" />
+        <negotiation-apply-dialog
+            v-model="negotiationDialogVisible"
+            :refund-order-id="form.refundOrderId"
+            :max-refund-amount="Number(form.refundMoney) || 0"
+            @success="handleNegotiationSuccess"
+        />
+        <refuse-dialog
+            ref="refuseDialogRef"
+            @success="handleNegotiationSuccess"
+        />
+        <agree-dialog
+            ref="agreeDialogRef"
+            @success="handleNegotiationSuccess"
+        />
+        <confirm-refund-dialog
+            ref="confirmRefundDialogRef"
+            @success="handleNegotiationSuccess"
+        />
+        <negotiate-return-dialog
+            ref="negotiateReturnDialogRef"
+            @success="handleNegotiationSuccess"
+        />
     </ele-modal>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
-import { EleMessage } from 'ele-admin-plus/es';
-import { CopyDocument } from '@element-plus/icons-vue';
-import { useClipboard } from '@vueuse/core';
-import request from '@/utils/request';
-import NegotiationApplyDialog from './negotiation-apply-dialog.vue';
-import RefuseDialog from './refuse-dialog.vue';
-import AgreeDialog from './agree-dialog.vue';
-import ConfirmRefundDialog from './confirm-refund-dialog.vue';
-
-const visible = defineModel({ type: Boolean });
-const loading = ref(false);
-const form = ref({});
-const historyList = ref([]);
-const { copy } = useClipboard();
-const negotiationDialogVisible = ref(false);
-const refuseDialogRef = ref(null);
-const agreeDialogRef = ref(null);
-const confirmRefundDialogRef = ref(null);
-
-const deadline = ref(0);
-
-const isRefundOnly = computed(() => ['0', '2'].includes(String(form.value.refundType)));
-
-const refundCount = computed(() => {
-    if (form.value.totalNum) return form.value.totalNum;
-    if (!form.value.detailList) return 0;
-    return form.value.detailList.reduce(
-        (sum, item) => sum + (Number(item.refundNum) || 0),
-        0
+    import { ref, computed } from 'vue';
+    import { EleMessage } from 'ele-admin-plus/es';
+    import { CopyDocument } from '@element-plus/icons-vue';
+    import { useClipboard } from '@vueuse/core';
+    import request from '@/utils/request';
+    import NegotiationApplyDialog from './negotiation-apply-dialog.vue';
+    import RefuseDialog from './refuse-dialog.vue';
+    import AgreeDialog from './agree-dialog.vue';
+    import ConfirmRefundDialog from './confirm-refund-dialog.vue';
+    import NegotiateReturnDialog from './negotiate-return-dialog.vue';
+
+    const visible = defineModel({ type: Boolean });
+    const loading = ref(false);
+    const form = ref({});
+    const historyList = ref([]);
+    const { copy } = useClipboard();
+    const negotiationDialogVisible = ref(false);
+    const refuseDialogRef = ref(null);
+    const agreeDialogRef = ref(null);
+    const confirmRefundDialogRef = ref(null);
+    const negotiateReturnDialogRef = ref(null);
+
+    const deadline = ref(0);
+
+    const isRefundOnly = computed(() =>
+        ['0', '2', '3'].includes(String(form.value.refundType))
     );
-});
 
-// 进度条状态
-const processStatus = computed(() => {
-    const status = Number(form.value.status);
-    if ([5, 6].includes(status)) {
-        return 'error';
-    }
-    return 'process';
-});
+    // 进度条状态
+    const processStatus = computed(() => {
+        const status = Number(form.value.status);
+        if ([5, 6].includes(status)) {
+            return 'error';
+        }
+        return 'process';
+    });
+
+    // 当前步骤
+    const currentStep = computed(() => {
+        const status = Number(form.value.status);
+        if (isRefundOnly.value) {
+            // 1:申请, 2,3:协商中, 5:驳回, 6:超时关闭 -> 停在第2步(卖家处理)
+            if ([1, 2, 3, 5, 6].includes(status)) return 1;
+            // 4:审核通过, 7:买家已发货, 8:确认收货 -> 停在第3步(退款完毕前)
+            if ([4, 7, 8].includes(status)) return 2;
+            // 9:退款成功 -> 全绿(第3步完成)
+            if (status === 9) return 3;
+            return 1;
+        }
 
-// 当前步骤
-const currentStep = computed(() => {
-    const status = Number(form.value.status);
-    if (isRefundOnly.value) {
+        // 退货退款
         // 1:申请, 2,3:协商中, 5:驳回, 6:超时关闭 -> 停在第2步(卖家处理)
         if ([1, 2, 3, 5, 6].includes(status)) return 1;
-        // 4:审核通过, 7:买家已发货, 8:确认收货 -> 停在第3步(退款完毕前)
-        if ([4, 7, 8].includes(status)) return 2;
-        // 9:退款成功 -> 全绿(第3步完成)
-        if (status === 9) return 3;
+        // 4:审核通过(商家同意退货,等待买家退货) -> 停在第3步(买家退货)
+        if (status === 4) return 2;
+        // 7:买家已发货, 8:确认收货 -> 停在第4步(退款完毕前)
+        if ([7, 8].includes(status)) return 3;
+        // 9:退款成功 -> 全绿(第4步完成)
+        if (status === 9) return 4;
         return 1;
-    }
-
-    // 退货退款
-    // 1:申请, 2,3:协商中, 5:驳回, 6:超时关闭 -> 停在第2步(卖家处理)
-    if ([1, 2, 3, 5, 6].includes(status)) return 1;
-    // 4:审核通过(商家同意退货,等待买家退货) -> 停在第3步(买家退货)
-    if (status === 4) return 2;
-    // 7:买家已发货, 8:确认收货 -> 停在第4步(退款完毕前)
-    if ([7, 8].includes(status)) return 3;
-    // 9:退款成功 -> 全绿(第4步完成)
-    if (status === 9) return 4;
-    return 1;
-});
-
-// 是否可操作主流程(拒绝、同意)
-const canOperate = computed(() => {
-    return ['1', '3'].includes(String(form.value.status));
-});
-
-const handleOpen = (row) => {
-    if (row && row.refundOrderId) {
-        visible.value = true;
-        loading.value = true;
-
-        request
-            .get(`/shop/shopOrder/getRefundInfo/${row.refundOrderId}`)
-            .then((res) => {
-                if (res.data.code === 200) {
-                    const data = res.data.data || {};
-                    form.value = data;
-                    if (!form.value.orderTime) {
-                        form.value.orderTime = form.value.createTime;
-                    }
-                    if (data.restSecond) {
-                        deadline.value = Date.now() + data.restSecond * 1000;
-                    } else {
-                        deadline.value = 0;
-                    }
-                    // 处理协商历史数据
-                    if (
-                        data.complaintsLogList &&
-                        data.complaintsLogList.length
-                    ) {
-                        historyList.value = data.complaintsLogList;
+    });
+
+    // 是否可操作主流程(拒绝、同意)
+    const canOperate = computed(() => {
+        return ['1', '3'].includes(String(form.value.status));
+    });
+
+    const handleOpen = (row) => {
+        if (row && row.refundOrderId) {
+            visible.value = true;
+            loading.value = true;
+
+            request
+                .get(`/shop/shopOrder/getRefundInfo/${row.refundOrderId}`)
+                .then((res) => {
+                    if (res.data.code === 200) {
+                        const data = res.data.data || {};
+                        form.value = data;
+                        if (!form.value.orderTime) {
+                            form.value.orderTime = form.value.createTime;
+                        }
+                        if (data.restSecond) {
+                            deadline.value =
+                                Date.now() + data.restSecond * 1000;
+                        } else {
+                            deadline.value = 0;
+                        }
+                        // 处理协商历史数据
+                        if (
+                            data.complaintsLogList &&
+                            data.complaintsLogList.length
+                        ) {
+                            historyList.value = data.complaintsLogList;
+                        } else {
+                            historyList.value = [];
+                        }
                     } else {
-                        historyList.value = [];
+                        EleMessage.error(res.data.msg || '获取详情失败');
+                        fallbackToRow(row);
                     }
-                } else {
-                    EleMessage.error(res.data.msg || '获取详情失败');
+                })
+                .catch((e) => {
+                    console.error(e);
+                    EleMessage.error('获取详情失败');
                     fallbackToRow(row);
-                }
-            })
-            .catch((e) => {
-                console.error(e);
-                EleMessage.error('获取详情失败');
-                fallbackToRow(row);
-            })
-            .finally(() => {
-                loading.value = false;
-            });
-    } else if (row) {
-        visible.value = true;
-        fallbackToRow(row);
-    }
-};
+                })
+                .finally(() => {
+                    loading.value = false;
+                });
+        } else if (row) {
+            visible.value = true;
+            fallbackToRow(row);
+        }
+    };
 
-const fallbackToRow = (row) => {
-    form.value = JSON.parse(JSON.stringify(row));
-    if (!form.value.orderTime) {
-        form.value.orderTime = form.value.createTime;
-    }
-    if (form.value.restSecond) {
-        deadline.value = Date.now() + form.value.restSecond * 1000;
-    } else {
-        deadline.value = 0;
-    }
-    historyList.value = [];
-
-    // Fallback时也添加初始申请记录
-    const startEvent = {
-        createTime: form.value.createTime,
-        userName: form.value.userNick,
-        userType: '1',
-        imgPath: form.value.avatar || '',
-        title: '发起了退款申请',
-        content: `货物状态:${getShopStatusText(form.value.shopStatus)}\n原因:${form.value.refundReason}\n金额:¥${form.value.refundMoney}\n说明:${form.value.description || '无'}`,
-        imgList: []
+    const fallbackToRow = (row) => {
+        form.value = JSON.parse(JSON.stringify(row));
+        if (!form.value.orderTime) {
+            form.value.orderTime = form.value.createTime;
+        }
+        if (form.value.restSecond) {
+            deadline.value = Date.now() + form.value.restSecond * 1000;
+        } else {
+            deadline.value = 0;
+        }
+        historyList.value = [];
+
+        // Fallback时也添加初始申请记录
+        const startEvent = {
+            createTime: form.value.createTime,
+            userName: form.value.userNick,
+            userType: '1',
+            imgPath: form.value.avatar || '',
+            title: '发起了退款申请',
+            content: `货物状态:${getShopStatusText(form.value.shopStatus)}\n原因:${form.value.refundReason}\n金额:¥${form.value.refundMoney}\n说明:${form.value.description || '无'}`,
+            imgList: []
+        };
+        historyList.value.push(startEvent);
     };
-    historyList.value.push(startEvent);
-};
-
-const handleCopy = async (text) => {
-    try {
-        await copy(text);
-        EleMessage.success('复制成功');
-    } catch (e) {
-        EleMessage.error('复制失败');
-    }
-};
-
-const mockHistory = (row) => {
-    // 移除模拟数据
-};
-
-const getRefundTypeText = (type) => {
-    const map = { 0: '极速退款', 1: '退货退款', 2: '仅退款' };
-    return map[type] || type;
-};
-
-const getStatusText = (status) => {
-    const key = String(status);
-    const map = {
-        1: '申请退款',
-        2: '协商中待用户确认',
-        3: '协商中待商家确认',
-        4: '审核通过',
-        5: '审核驳回',
-        6: '超时关闭',
-        7: '买家已发货',
-        8: '确认收货',
-        9: '退款成功'
+
+    const handleCopy = async (text) => {
+        try {
+            await copy(text);
+            EleMessage.success('复制成功');
+        } catch (e) {
+            EleMessage.error('复制失败');
+        }
     };
-    return map[key] || status;
-};
 
-const getShopStatusText = (status) => {
-    const map = { 1: '未收到货', 2: '已收到货' };
-    return map[status] || '-';
-};
+    const getRefundTypeText = (type) => {
+        const map = {
+            0: '极速退款',
+            1: '退货退款',
+            2: '仅退款',
+            3: '缺货退款'
+        };
+        return map[type] || type;
+    };
 
-const handleNegotiate = () => {
-    negotiationDialogVisible.value = true;
-};
+    const getStatusText = (status) => {
+        const key = String(status);
+        const map = {
+            1: '申请退款',
+            2: '协商中待用户确认',
+            3: '协商中待商家确认',
+            4: '审核通过',
+            5: '审核驳回',
+            6: '超时关闭',
+            7: '买家已发货',
+            8: '确认收货',
+            9: '退款成功'
+        };
+        return map[key] || status;
+    };
 
-const handleNegotiationSuccess = () => {
-    handleOpen(form.value);
-};
+    const getShopStatusText = (status) => {
+        const map = { 1: '未收到货', 2: '已收到货' };
+        return map[status] || '-';
+    };
 
-const handleRefuse = () => {
-    if (refuseDialogRef.value) {
-        refuseDialogRef.value.open(form.value.refundOrderId);
-    }
-};
-
-const handleAgree = () => {
-    if (isRefundOnly.value) {
-        confirmRefundDialogRef.value?.open(
-            form.value.refundOrderId,
-            form.value.refundMoneyFinal || form.value.refundMoney
-        );
-        return;
-    }
-    agreeDialogRef.value?.open(form.value.refundOrderId);
-};
+    const handleNegotiate = () => {
+        negotiationDialogVisible.value = true;
+    };
 
-const handleLeaveMessage = () => {
-    EleMessage.info('点击了我要留言');
-};
+    const handleNegotiateReturn = () => {
+        if (negotiateReturnDialogRef.value) {
+            negotiateReturnDialogRef.value.open(form.value);
+        }
+    };
 
-defineExpose({
-    handleOpen
-});
-</script>
+    const handleNegotiationSuccess = () => {
+        handleOpen(form.value);
+    };
 
-<style scoped lang="scss">
-.status-bar {
-    background: #fff;
-    padding: 15px;
-    border-bottom: 1px solid #eee;
-}
+    const handleRefuse = () => {
+        if (refuseDialogRef.value) {
+            refuseDialogRef.value.open(form.value.refundOrderId);
+        }
+    };
 
-.detail-container {
-    gap: 20px;
+    const handleAgree = () => {
+        if (isRefundOnly.value) {
+            confirmRefundDialogRef.value?.open(
+                form.value.refundOrderId,
+                form.value.refundMoneyFinal || form.value.refundMoney
+            );
+            return;
+        }
+        agreeDialogRef.value?.open(form.value.refundOrderId);
+    };
 
-    .section {
-        background: #fff;
+    const handleLeaveMessage = () => {
+        EleMessage.info('点击了我要留言');
+    };
 
-        .section-title {
-            font-size: 16px;
-            font-weight: bold;
-            margin-bottom: 15px;
-            color: #333;
-            padding-left: 10px;
-            border-left: 3px solid #409eff;
-            line-height: 1;
-        }
+    defineExpose({
+        handleOpen
+    });
+</script>
 
-        .info-item {
-            margin-bottom: 10px;
-            font-size: 13px;
-            display: flex;
+<style scoped lang="scss">
+    .status-bar {
+        background: #fff;
+        padding: 15px;
+        border-bottom: 1px solid #eee;
+    }
 
-            .label {
-                color: #999;
-                width: 70px;
-                flex-shrink: 0;
-            }
+    .detail-container {
+        gap: 20px;
+
+        .section {
+            background: #fff;
 
-            .value {
+            .section-title {
+                font-size: 16px;
+                font-weight: bold;
+                margin-bottom: 15px;
                 color: #333;
-                flex: 1;
-                word-break: break-all;
+                padding-left: 10px;
+                border-left: 3px solid #409eff;
+                line-height: 1;
             }
 
-            .copy-icon {
-                cursor: pointer;
-                color: #409eff;
-                margin-left: 5px;
-                font-size: 14px;
+            .info-item {
+                margin-bottom: 10px;
+                font-size: 13px;
+                display: flex;
+
+                .label {
+                    color: #999;
+                    width: 70px;
+                    flex-shrink: 0;
+                }
+
+                .value {
+                    color: #333;
+                    flex: 1;
+                    word-break: break-all;
+                }
+
+                .copy-icon {
+                    cursor: pointer;
+                    color: #409eff;
+                    margin-left: 5px;
+                    font-size: 14px;
+                }
             }
         }
-    }
 
-    .transaction-info {
-        padding: 0 10px;
-        border-left: 1px solid #eee;
-        border-right: 1px solid #eee;
-    }
+        .transaction-info {
+            padding: 0 10px;
+            border-left: 1px solid #eee;
+            border-right: 1px solid #eee;
+        }
 
-    .history-info {
-        padding-left: 10px;
+        .history-info {
+            padding-left: 10px;
+        }
     }
-}
 
-.history-card {
-    background: #f9f9f9;
-    padding: 10px;
-    border-radius: 4px;
+    .history-card {
+        background: #f9f9f9;
+        padding: 10px;
+        border-radius: 4px;
+
+        .detail-rows {
+            line-height: 1.6;
+        }
+    }
 
-    .detail-rows {
-        line-height: 1.6;
+    .truncate-2-lines {
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        -webkit-box-orient: vertical;
+        overflow: hidden;
     }
-}
-
-.truncate-2-lines {
-    display: -webkit-box;
-    -webkit-line-clamp: 2;
-    -webkit-box-orient: vertical;
-    overflow: hidden;
-}
 </style>