|
|
@@ -0,0 +1,380 @@
|
|
|
+<template>
|
|
|
+ <div class="ele-body">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <div slot="header" class="clearfix">
|
|
|
+ <span style="font-size: 18px; font-weight: bold;">
|
|
|
+ <i class="el-icon-money"></i> 退款订单管理
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 提示信息 -->
|
|
|
+ <el-alert title="退款订单说明" type="warning" :closable="false" style="margin-bottom: 15px;">
|
|
|
+ <div slot>
|
|
|
+ <p>退款订单是指用户申请退款的订单</p>
|
|
|
+ <p>包括不想要了、商品不符、价格问题等需要退款处理的订单</p>
|
|
|
+ </div>
|
|
|
+ </el-alert>
|
|
|
+
|
|
|
+ <!-- 退款状态tabs -->
|
|
|
+ <el-tabs v-model="activeRefundStatus" @tab-click="handleTabClick" style="margin-top: 15px;">
|
|
|
+ <el-tab-pane v-for="item in refundStatusTabs" :key="item.value" :name="item.value">
|
|
|
+ <span slot="label"><i :class="item.icon"></i> {{ item.label }}</span>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
+ <!-- 搜索表单 -->
|
|
|
+ <el-form style="margin-top: 15px;" :model="table.where" label-width="90px" class="ele-form-search"
|
|
|
+ @keyup.enter.native="$refs.table.reload()" @submit.native.prevent>
|
|
|
+ <el-row :gutter="15">
|
|
|
+ <el-col :md="8" :sm="12">
|
|
|
+ <el-form-item label="关键词:">
|
|
|
+ <el-input v-model="table.where.keyword" placeholder="订单号/商品名称" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :md="10" :sm="12">
|
|
|
+ <div class="ele-form-actions">
|
|
|
+ <el-button type="primary" @click="$refs.table.reload()" icon="el-icon-search"
|
|
|
+ class="ele-btn-icon">查询</el-button>
|
|
|
+ <el-button @click="handleReset()">重置</el-button>
|
|
|
+ <el-button type="warning" icon="el-icon-download" @click="handleExport"
|
|
|
+ v-if="permission.includes('sys:refundOrder:index')">导出</el-button>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <ele-data-table ref="table" :config="table" :choose.sync="choose" height="calc(100vh - 450px)"
|
|
|
+ highlight-current-row>
|
|
|
+ <template>
|
|
|
+ <el-table-column type="selection" width="45" align="center" fixed="left" />
|
|
|
+ <el-table-column prop="id" label="ID" width="60" align="center" fixed="left" />
|
|
|
+ <el-table-column prop="order_no" label="订单号" min-width="180" />
|
|
|
+ <el-table-column prop="user" label="下单用户" min-width="120">
|
|
|
+ <template slot-scope="{row}">
|
|
|
+ <span>{{ row.user ? row.user.realname || row.user.nickname : '' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="goods" label="商品信息" min-width="200">
|
|
|
+ <template slot-scope="{row}">
|
|
|
+ <div v-if="row.goods" style="display:flex;align-items:center;">
|
|
|
+ <el-image :src="row.goods.thumb" style="width:50px;height:50px;margin-right:10px;" fit="cover" />
|
|
|
+ <span>{{ row.goods.goods_name }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="pay_total" label="退款金额" width="100" align="center">
|
|
|
+ <template slot-scope="{row}">
|
|
|
+ <span style="color: #f56c6c; font-weight: bold;">¥{{ row.pay_total }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="after_realname" label="联系人" width="100" align="center" />
|
|
|
+ <el-table-column prop="after_phone" label="联系电话" width="120" align="center" />
|
|
|
+ <el-table-column prop="status" label="状态" width="160" align="center">
|
|
|
+ <template slot-scope="{row}">
|
|
|
+ <div style="display: flex; flex-direction: column; gap: 5px; align-items: center;">
|
|
|
+ <div style="display: flex; align-items: center; gap: 5px;">
|
|
|
+ <span style="font-size: 12px; color: #909399;">订单:</span>
|
|
|
+ <el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
|
|
|
+ </div>
|
|
|
+ <div v-if="row.refund_status == 3" style="display: flex; align-items: center; gap: 5px;">
|
|
|
+ <span style="font-size: 12px; color: #909399;">退款:</span>
|
|
|
+ <el-tag type="warning" size="mini">
|
|
|
+ <i class="el-icon-warning"></i> 待审核
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div v-if="row.refund_status == 2" style="display: flex; align-items: center; gap: 5px;">
|
|
|
+ <span style="font-size: 12px; color: #909399;">退款:</span>
|
|
|
+ <el-tag type="primary" size="mini">
|
|
|
+ <i class="el-icon-s-order"></i> 已审核
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div v-if="row.refund_status == 1" style="display: flex; align-items: center; gap: 5px;">
|
|
|
+ <span style="font-size: 12px; color: #909399;">退款:</span>
|
|
|
+ <el-tag type="success" size="mini">
|
|
|
+ <i class="el-icon-circle-check"></i> 已退款
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div v-if="row.refund_status == 4" style="display: flex; align-items: center; gap: 5px;">
|
|
|
+ <span style="font-size: 12px; color: #909399;">退款:</span>
|
|
|
+ <el-tag type="danger" size="mini">
|
|
|
+ <i class="el-icon-circle-close"></i> 已驳回
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="create_time" label="申请时间" min-width="160" align="center" />
|
|
|
+ <el-table-column label="操作" width="250px" align="center" :resizable="false" fixed="right">
|
|
|
+ <template slot-scope="{row}">
|
|
|
+ <el-link @click="viewDetail(row)" icon="el-icon-view" type="primary" :underline="false">查看</el-link>
|
|
|
+
|
|
|
+ <!-- 待审核:同意、拒绝 -->
|
|
|
+ <el-link @click="handleAgree(row)" icon="el-icon-check" type="success" :underline="false"
|
|
|
+ v-if="row.refund_status == 3 && permission.includes('sys:refundOrder:status')"
|
|
|
+ class="ele-action">同意</el-link>
|
|
|
+ <el-link @click="handleReject(row)" icon="el-icon-close" type="danger" :underline="false"
|
|
|
+ v-if="row.refund_status == 3 && permission.includes('sys:refundOrder:status')"
|
|
|
+ class="ele-action">驳回</el-link>
|
|
|
+
|
|
|
+ <!-- 已审核:确认退款 -->
|
|
|
+ <el-link @click="handleConfirmRefund(row)" icon="el-icon-money" type="warning" :underline="false"
|
|
|
+ v-if="row.refund_status == 2 && permission.includes('sys:refundOrder:status')"
|
|
|
+ class="ele-action">确认退款</el-link>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </template>
|
|
|
+ </ele-data-table>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 订单详情弹窗 -->
|
|
|
+ <el-dialog title="退款订单详情" :visible.sync="detailVisible" width="85%" top="3vh" :close-on-click-modal="false"
|
|
|
+ custom-class="order-detail-dialog">
|
|
|
+ <div v-loading="detailLoading" class="order-detail-content">
|
|
|
+ <!-- 退款申请信息 -->
|
|
|
+ <el-alert title="退款申请信息" type="error" :closable="false" style="margin-bottom: 20px;">
|
|
|
+ <div>
|
|
|
+ <p><strong>退款金额:</strong><span style="color: #f56c6c; font-size: 18px; font-weight: bold;">¥{{
|
|
|
+ orderInfo.pay_total }}</span></p>
|
|
|
+ <p><strong>联系人:</strong>{{ orderInfo.after_realname }}</p>
|
|
|
+ <p><strong>联系电话:</strong>{{ orderInfo.after_phone }}</p>
|
|
|
+ <p><strong>退款原因:</strong>{{ orderInfo.after_remark }}</p>
|
|
|
+ <p v-if="orderInfo.refund_remark"><strong>处理备注:</strong>{{ orderInfo.refund_remark }}</p>
|
|
|
+ </div>
|
|
|
+ </el-alert>
|
|
|
+
|
|
|
+ <p style="text-align: center; color: #909399;">详细订单信息请查看订单管理</p>
|
|
|
+ </div>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="detailVisible = false">关闭</el-button>
|
|
|
+ <el-button type="success" @click="handleAgreeDetail"
|
|
|
+ v-if="orderInfo.refund_status == 3 && permission.includes('sys:refundOrder:status')">
|
|
|
+ 同意退款
|
|
|
+ </el-button>
|
|
|
+ <el-button type="danger" @click="handleRejectDetail"
|
|
|
+ v-if="orderInfo.refund_status == 3 && permission.includes('sys:refundOrder:status')">
|
|
|
+ 驳回退款
|
|
|
+ </el-button>
|
|
|
+ <el-button type="warning" @click="handleConfirmRefundDetail"
|
|
|
+ v-if="orderInfo.refund_status == 2 && permission.includes('sys:refundOrder:status')">
|
|
|
+ 确认退款
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { mapGetters } from "vuex";
|
|
|
+export default {
|
|
|
+ name: "RefundOrder",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ table: {
|
|
|
+ url: '/order/index',
|
|
|
+ where: {
|
|
|
+ after_type: 2, // 退款类型
|
|
|
+ refund_status: 0 // 默认全部
|
|
|
+ }
|
|
|
+ },
|
|
|
+ choose: [],
|
|
|
+ refundStatusTabs: [
|
|
|
+ { value: '0', label: '全部', icon: 'el-icon-s-grid' },
|
|
|
+ { value: '3', label: '待审核', icon: 'el-icon-warning' },
|
|
|
+ { value: '2', label: '已审核', icon: 'el-icon-s-order' },
|
|
|
+ { value: '1', label: '已退款', icon: 'el-icon-circle-check' },
|
|
|
+ { value: '4', label: '已驳回', icon: 'el-icon-circle-close' },
|
|
|
+ ],
|
|
|
+ activeRefundStatus: '0',
|
|
|
+ detailVisible: false,
|
|
|
+ detailLoading: false,
|
|
|
+ orderInfo: {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ ...mapGetters(["permission"]),
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ handleTabClick(tab) {
|
|
|
+ this.activeRefundStatus = tab.name;
|
|
|
+ this.table.where.refund_status = parseInt(tab.name);
|
|
|
+ this.$refs.table.reload();
|
|
|
+ },
|
|
|
+ handleReset() {
|
|
|
+ this.activeRefundStatus = '0';
|
|
|
+ this.table.where = { after_type: 2, refund_status: 0 };
|
|
|
+ this.$refs.table.reload();
|
|
|
+ },
|
|
|
+ viewDetail(row) {
|
|
|
+ this.detailVisible = true;
|
|
|
+ this.loadOrderDetail(row.id);
|
|
|
+ },
|
|
|
+ loadOrderDetail(id) {
|
|
|
+ this.detailLoading = true;
|
|
|
+ this.$http.get('/order/info', { params: { id } }).then(res => {
|
|
|
+ this.detailLoading = false;
|
|
|
+ if (res.data.code === 0) {
|
|
|
+ this.orderInfo = res.data.data;
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.data.msg);
|
|
|
+ }
|
|
|
+ }).catch(e => {
|
|
|
+ this.detailLoading = false;
|
|
|
+ this.$message.error(e.message);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleAgree(row) {
|
|
|
+ this.$confirm('同意退款后,退款状态将变为"已审核",需要再次确认退款才会完成退款流程。确定要同意吗?', '同意退款', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ closeOnClickModal: false
|
|
|
+ }).then(() => {
|
|
|
+ this.$prompt('请输入审核备注', '同意退款', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ inputPlaceholder: '请输入审核备注(选填)',
|
|
|
+ closeOnClickModal: false
|
|
|
+ }).then(({ value }) => {
|
|
|
+ const loading = this.$loading({ lock: true });
|
|
|
+ this.$http.post('/order/agreeRefund', {
|
|
|
+ id: row.id,
|
|
|
+ refund_remark: value || '退款申请已通过,待确认退款'
|
|
|
+ }).then(res => {
|
|
|
+ loading.close();
|
|
|
+ if (res.data.code === 0) {
|
|
|
+ this.$message.success(res.data.msg);
|
|
|
+ this.$refs.table.reload();
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.data.msg);
|
|
|
+ }
|
|
|
+ }).catch(e => {
|
|
|
+ loading.close();
|
|
|
+ this.$message.error(e.message);
|
|
|
+ });
|
|
|
+ }).catch(() => { });
|
|
|
+ }).catch(() => { });
|
|
|
+ },
|
|
|
+ handleConfirmRefund(row) {
|
|
|
+ this.$prompt(`请输入退款金额(订单金额:¥${row.pay_total})`, '确认退款', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ inputPattern: /^(0|[1-9]\d*)(\.\d{1,2})?$/,
|
|
|
+ inputErrorMessage: '请输入正确的金额格式(最多两位小数)',
|
|
|
+ inputPlaceholder: '请输入退款金额',
|
|
|
+ inputValue: row.pay_total,
|
|
|
+ closeOnClickModal: false
|
|
|
+ }).then(({ value }) => {
|
|
|
+ const refundAmount = parseFloat(value);
|
|
|
+ if (refundAmount <= 0) {
|
|
|
+ this.$message.error('退款金额必须大于0');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (refundAmount > parseFloat(row.pay_total)) {
|
|
|
+ this.$message.error('退款金额不能超过订单金额');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const loading = this.$loading({ lock: true });
|
|
|
+ this.$http.post('/order/confirmRefund', {
|
|
|
+ id: row.id,
|
|
|
+ refund_amount: refundAmount
|
|
|
+ }).then(res => {
|
|
|
+ loading.close();
|
|
|
+ if (res.data.code === 0) {
|
|
|
+ this.$message.success(res.data.msg);
|
|
|
+ this.$refs.table.reload();
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.data.msg);
|
|
|
+ }
|
|
|
+ }).catch(e => {
|
|
|
+ loading.close();
|
|
|
+ this.$message.error(e.message);
|
|
|
+ });
|
|
|
+ }).catch(() => { });
|
|
|
+ },
|
|
|
+ handleReject(row) {
|
|
|
+ this.$prompt('请输入驳回原因', '驳回退款', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ inputPattern: /.+/,
|
|
|
+ inputErrorMessage: '请输入驳回原因',
|
|
|
+ closeOnClickModal: false
|
|
|
+ }).then(({ value }) => {
|
|
|
+ const loading = this.$loading({ lock: true });
|
|
|
+ this.$http.post('/order/rejectRefund', {
|
|
|
+ id: row.id,
|
|
|
+ refund_remark: value
|
|
|
+ }).then(res => {
|
|
|
+ loading.close();
|
|
|
+ if (res.data.code === 0) {
|
|
|
+ this.$message.success(res.data.msg);
|
|
|
+ this.$refs.table.reload();
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.data.msg);
|
|
|
+ }
|
|
|
+ }).catch(e => {
|
|
|
+ loading.close();
|
|
|
+ this.$message.error(e.message);
|
|
|
+ });
|
|
|
+ }).catch(() => { });
|
|
|
+ },
|
|
|
+ handleAgreeDetail() {
|
|
|
+ this.handleAgree(this.orderInfo);
|
|
|
+ },
|
|
|
+ handleRejectDetail() {
|
|
|
+ this.handleReject(this.orderInfo);
|
|
|
+ },
|
|
|
+ handleConfirmRefundDetail() {
|
|
|
+ this.handleConfirmRefund(this.orderInfo);
|
|
|
+ },
|
|
|
+ handleExport() {
|
|
|
+ this.$confirm('确定要导出当前筛选条件下的退款订单数据吗?', '提示', {
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ const loading = this.$loading({ lock: true, text: '正在导出...' });
|
|
|
+ const params = { ...this.table.where };
|
|
|
+ Object.keys(params).forEach(key => {
|
|
|
+ if (params[key] === undefined || params[key] === null || params[key] === '') {
|
|
|
+ delete params[key];
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$http.get('/order/export', {
|
|
|
+ params: params,
|
|
|
+ responseType: 'blob'
|
|
|
+ }).then(res => {
|
|
|
+ loading.close();
|
|
|
+ const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = window.URL.createObjectURL(blob);
|
|
|
+ link.download = `退款订单_${new Date().getTime()}.xlsx`;
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ window.URL.revokeObjectURL(link.href);
|
|
|
+ this.$message.success('导出成功');
|
|
|
+ }).catch(e => {
|
|
|
+ loading.close();
|
|
|
+ this.$message.error(e.message || '导出失败');
|
|
|
+ });
|
|
|
+ }).catch(() => { });
|
|
|
+ },
|
|
|
+ getStatusText(status) {
|
|
|
+ const map = { 1: '待付款', 2: '已付款', 3: '已发货', 4: '已完成' };
|
|
|
+ return map[status] || '未知';
|
|
|
+ },
|
|
|
+ getStatusType(status) {
|
|
|
+ const map = { 1: 'info', 2: 'warning', 3: 'primary', 4: 'success' };
|
|
|
+ return map[status] || '';
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.order-detail-content {
|
|
|
+ max-height: 70vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+</style>
|