wesmiler 2 hafta önce
ebeveyn
işleme
4cb66cbb62

+ 380 - 0
addons/admin/src/views/system/order/refundOrder.vue

@@ -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>

+ 1 - 1
app/Services/Common/OrderService.php

@@ -179,7 +179,7 @@ class OrderService extends BaseService
     public function getInfo($id)
     public function getInfo($id)
     {
     {
         $info = $this->model->where('id', $id)->where('mark', 1)
         $info = $this->model->where('id', $id)->where('mark', 1)
-            ->with(['user', 'orderGoods', 'store','meeting','commissions'])
+            ->with(['user', 'orderGoods', 'store','commissions'])
             ->first();
             ->first();
 
 
         if (!$info) {
         if (!$info) {