share-space-vue/src/views/system/order/order_detail.vue
2025-01-27 00:20:30 +08:00

525 lines
16 KiB
Vue

<template>
<div class="app-container">
<div class="detail-container">
<el-card class="box-card" shadow="hover">
<!-- 标题和操作按钮 -->
<div class="card-header">
<div class="header-title">
<span class="title">订单详情</span>
<dict-tag :options="dict.type.ss_order_status" :value="order.status"/>
</div>
<div class="operation-buttons">
<el-button
type="primary"
icon="el-icon-card"
size="small"
@click="handleRefund"
v-has-permi="['system:room:refund']"
>退款</el-button>
<el-button
type="danger"
icon="el-icon-close"
size="small"
@click="handleClose"
v-has-permi="['system:room:close']"
>结束订单</el-button>
</div>
</div>
<!-- 左侧详情信息 -->
<el-row :span="24">
<el-col :span="20">
<div class="detail-left">
<!-- 所有info-section -->
<div class="info-sections">
<!-- 基本信息 -->
<div class="info-section">
<h3>
<i class="el-icon-info"></i>
基本信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="订单号">{{ order.orderNo }}</el-descriptions-item>
<el-descriptions-item label="用户">
<router-link :to="`/user/detail/${order.userId}`" class="link-type">
{{ order.userName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="预约时间">
<template v-if="order.reserveStartTime && order.reserveEndTime">
{{ parseTime(order.reserveStartTime) }} 至 {{ parseTime(order.reserveEndTime) }}
</template>
<template v-else>
--
</template>
</el-descriptions-item>
<el-descriptions-item label="房间/设施">
<router-link :to="`/system/roomDetail/index/${order.roomId}`" class="link-type">
{{ order.roomName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="店铺">
<router-link :to="`/system/storeDetail/index/${order.storeId}`" class="link-type">
{{ order.storeName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="大门">
<router-link
v-if="order.gateSn"
:to="`/system/deviceDetail/index/${order.gateId}`"
class="link-type">
{{ order.gateSn }}
</router-link>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 套餐信息 -->
<div class="info-section">
<h3>
<i class="el-icon-goods"></i>
套餐信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="收费模式">
<dict-tag :options="dict.type.ss_fee_rule_mode" :value="order.mode"/>
</el-descriptions-item>
<el-descriptions-item label="套餐名称">{{ order.ruleName }}</el-descriptions-item>
<el-descriptions-item label="套餐时长">{{ order.duration }} 小时</el-descriptions-item>
</el-descriptions>
</div>
<!-- 商户信息 -->
<div class="info-section">
<h3>
<i class="el-icon-office-building"></i>
商户信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="商户名称">
<router-link :to="`/user/detail/${order.merchantId}`" class="link-type">
{{ order.merchantName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="商户ID">{{ order.merchantId }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 支付信息 -->
<div class="info-section">
<h3>
<i class="el-icon-money"></i>
支付信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.ss_pay_type" :value="order.payType"/>
</el-descriptions-item>
<el-descriptions-item label="支付状态">
<dict-tag :options="dict.type.et_order_pay_status" :value="order.paid"/>
</el-descriptions-item>
<el-descriptions-item label="支付时间">
{{ parseTime(order.payTime) }}
</el-descriptions-item>
<el-descriptions-item label="订单金额">{{ order.totalFee | dv }} 元</el-descriptions-item>
<el-descriptions-item label="实付金额">{{ order.payFee | dv }} 元</el-descriptions-item>
<el-descriptions-item label="手续费">{{ order.handlingCharge | dv }} 元</el-descriptions-item>
<el-descriptions-item label="平台服务费">{{ order.platformServiceFee | dv }} 元</el-descriptions-item>
<el-descriptions-item label="押金扣除">{{ order.depositDeduction | dv }} 元</el-descriptions-item>
<el-descriptions-item label="扣除金额">{{ order.deductionAmount | dv }} 元</el-descriptions-item>
</el-descriptions>
</div>
</div>
</div>
</el-col>
<el-col :span="4">
<!-- 右侧操作履历 -->
<div class="detail-right">
<div class="info-sections history-section">
<h3>
<i class="el-icon-time" style="color: #E6A23C;"></i>
操作履历
</h3>
<el-timeline class="padding0">
<el-timeline-item
v-for="activity in order.orderOpers"
:key="activity.operId"
:type="getTimelineItemType(activity.operType)"
:timestamp="activity.createTime"
:color="getTimelineItemColor(activity.operType)"
>
<div class="timeline-content">
<span class="operation-type" :style="{ color: getTimelineItemColor(activity.operType) }">
{{ dict.type.ss_order_oper_type[activity.operType].label }}
</span>
<el-tooltip placement="right" effect="light" popper-class="operation-tooltip">
<div slot="content">
<div>{{ activity.details }}</div>
<div v-if="activity.beforeAmount !== activity.afterAmount">
金额变更: {{ activity.beforeAmount }}元 -> {{ activity.afterAmount }}元
</div>
<div>操作人: {{ activity.operPhone }}</div>
</div>
<i
class="el-icon-info tooltip-icon"
:style="{ color: getTimelineItemColor(activity.operType) }"
></i>
</el-tooltip>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</el-col>
</el-row>
</el-card>
<!-- 分账明细标签页 -->
<el-card class="box-card detail-tabs" shadow="hover">
<el-tabs v-model="activeTab">
<el-tab-pane label="分账明细" name="detail" :lazy="true">
<detail v-if="order.orderNo != null"
:query="{
orderNo: order.orderNo
}"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
<!-- 退款弹窗 -->
<refund-dialog :show.sync="showRefund" :pay-id="order.payId" :amount="order.payFee" @success="getDetail(orderId)" />
</div>
</template>
<script>
import { getOrder } from "@/api/system/order";
import { parseTime } from '@/utils/ruoyi'
import Detail from '@/views/system/detail/index.vue'
import RefundDialog from './components/RefundDialog'
import Log from "@/views/system/commandLog/index.vue";
export default {
name: "OrderDetail",
dicts: ['ss_order_status', 'ss_pay_type', 'et_order_pay_status', 'ss_fee_rule_mode', 'ss_order_oper_type'],
components: {
Log,
RefundDialog,
Detail
},
data() {
return {
// 订单信息
order: {},
// 退款弹窗显示状态
showRefund: false,
// 当前激活的标签页
activeTab: 'detail',
// 订单ID
orderId: null,
// 域名
domain: '',
}
},
created() {
this.orderId = this.$route.params.orderId;
this.getDetail(this.orderId);
},
methods: {
/** 获取订单详情 */
async getDetail(orderId) {
try {
const response = await getOrder(orderId);
this.order = response.data;
} catch (error) {
console.error('获取订单详情失败:', error);
this.$message.error('获取订单详情失败');
}
},
/** 处理退款 */
handleRefund() {
this.showRefund = true;
},
/** 处理结束订单 */
handleClose() {
this.$confirm(`确定结束订单【${this.order.orderNo}】吗?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// closeBill({billId: this.order.billId}).then(res => {
// if (res.code === 200) {
// this.$message.success("操作成功");
// this.getDetail(this.order.orderId);
// }
// })
}).catch(() => {})
},
/** 获取时间轴节点的类型 */
getTimelineItemType(operType) {
const typeMap = {
'1': 'primary', // 下单
'2': 'success', // 支付
'3': 'warning', // 更换包间
'4': 'info', // 结束订单
'6': 'warning', // 退款
'7': 'danger', // 用户取消
'8': 'danger' // 系统自动取消
};
return typeMap[operType] || 'info';
},
/** 获取时间轴节点的颜色 */
getTimelineItemColor(operType) {
const colorMap = {
'1': '#409EFF', // 下单 - 蓝色
'2': '#67C23A', // 支付 - 绿色
'3': '#E6A23C', // 更换包间 - 黄色
'4': '#909399', // 结束订单 - 灰色
'6': '#E6A23C', // 退款 - 黄色
'7': '#F56C6C', // 用户取消 - 红色
'8': '#F56C6C' // 系统自动取消 - 红色
};
return colorMap[operType] || '#909399';
},
/** 获取操作图标 */
getOperationIcon(operType) {
const iconMap = {
'1': 'el-icon-plus', // 下单 - 加号图标
'2': 'el-icon-check', // 支付 - 对勾图标
'3': 'el-icon-refresh', // 更换包间 - 刷新图标
'4': 'el-icon-circle-close', // 结束订单 - 关闭圆圈图标
'6': 'el-icon-back', // 退款 - 返回图标
'7': 'el-icon-close', // 用户取消 - 关闭图标
'8': 'el-icon-warning' // 系统自动取消 - 警告图标
};
return iconMap[operType] || 'el-icon-info';
},
/** 获取提示内容 */
getTooltipContent(activity) {
let content = [];
// 添加操作详情
if (activity.details) {
content.push(activity.details);
}
// 添加金额变更信息
if (activity.beforeAmount !== activity.afterAmount) {
content.push(`金额变更: ${activity.beforeAmount}元 -> ${activity.afterAmount}`);
}
// 添加操作人信息
if (activity.operPhone) {
content.push(`操作人: ${activity.operPhone}`);
}
return content.join('\n');
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
padding: 24px;
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
.box-card {
margin-bottom: 24px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #ebeef5;
.header-title {
display: flex;
align-items: center;
gap: 12px;
.title {
font-size: 18px;
font-weight: bold;
color: #303133;
}
}
}
.operation-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.info-sections {
padding: 0 20px;
background-color: #fff;
border-radius: 4px;
}
.info-section {
padding: 20px 0;
background-color: #fff;
border-radius: 4px;
& + .info-section {
border-top: 1px solid #ebeef5;
}
h3 {
margin: 0 0 16px;
color: #303133;
font-size: 16px;
font-weight: 500;
display: flex;
align-items: center;
i {
margin-right: 8px;
font-size: 18px;
color: #409EFF;
}
}
}
.detail-tabs {
margin-top: 24px;
}
.link-type {
color: #1890ff;
font-size: 14px;
font-weight: 500;
&:hover {
text-decoration: underline;
}
}
:deep(.el-descriptions) {
.el-descriptions-item__label {
background-color: #f5f7fa;
font-weight: 500;
min-width: 90px;
}
.el-descriptions-item__content {
padding: 12px 16px;
}
}
.detail-container {
width: 100%;
min-height: calc(100vh - 132px);
}
.detail-left {
.box-card {
margin-bottom: 0;
}
}
.detail-right {
.history-section {
h3 {
margin-bottom: 20px;
}
}
}
.padding0{
padding: 0 !important;
}
.history-section {
background-color: #fff;
border-radius: 4px;
:deep(.el-timeline) {
padding: 20px;
margin: 0 -20px;
.el-timeline-item__content {
.timeline-content {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
.operation-type {
font-size: 14px;
font-weight: 500;
}
.tooltip-icon {
font-size: 16px;
cursor: help;
opacity: 0.8;
transition: opacity 0.2s;
display: inline-block;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
&:hover {
opacity: 1;
}
}
}
.el-timeline-item__timestamp {
color: #909399;
font-size: 12px;
line-height: 1.4;
padding-top: 4px;
}
.el-timeline-item__node {
background-color: #fff;
border: 2px solid currentColor;
}
.el-timeline-item__tail {
border-left: 2px solid #e4e7ed;
}
}
}
/* 添加全局样式 */
:deep(.operation-tooltip) {
.el-tooltip__popper {
max-width: 300px;
line-height: 1.5;
div {
margin: 4px 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
}
</style>