electripper-v2-ui/src/views/bst/order/view/view.vue
2025-06-07 18:01:42 +08:00

521 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container" v-loading="loading">
<el-row :gutter="10">
<el-col :span="18">
<el-card class="mb10">
<el-row :gutter="20" type="flex">
<el-col>
<el-statistic title="骑行费" :value="detail.ridingFee" :precision="2" suffix="元">
<template slot="prefix">
<i class="el-icon-bicycle" style="color: #409EFF"></i>
</template>
</el-statistic>
</el-col>
<el-col>
<el-popover placement="bottom" width="200" trigger="hover">
<div>
<div v-if="detail.creditName">挂账方:{{detail.creditName | dv}}</div>
<div v-if="detail.creditUserName">挂账人:{{detail.creditUserName | dv}}</div>
<div>押金:{{detail.depositFee | fix2 | dv}} 元</div>
</div>
<el-statistic slot="reference" title="押金" :value="detail.depositFee" :precision="2" suffix="元">
<template slot="prefix">
<i class="el-icon-money" style="color: #67C23A"></i>
</template>
<template slot="title">
押金
<el-tag v-if="detail.isCredit" type="success" size="mini">挂账</el-tag>
<i class="el-icon-info" v-else />
</template>
</el-statistic>
</el-popover>
</el-col>
<el-col v-if="detail.ridePayAmount">
<el-statistic title="结算支付" :value="detail.ridePayAmount" :precision="2" suffix="元">
<template slot="prefix">
<i class="el-icon-money" style="color: #67C23A"></i>
</template>
</el-statistic>
</el-col>
<el-col>
<el-popover placement="bottom" width="200" trigger="hover">
<div>
<div>骑行费:{{detail.ridingFee | fix2 | dv}} 元</div>
<div>调度费:{{detail.dispatchFee | fix2 | dv}} 元</div>
<div>管理费:{{detail.manageFee | fix2 | dv}} 元</div>
<div>车损费:{{detail.deductionFee | fix2 | dv}} 元</div>
</div>
<el-statistic slot="reference" :value="detail.totalFee" :precision="2" suffix="元">
<template slot="prefix">
<i class="el-icon-wallet" style="color: #E6A23C"></i>
</template>
<template slot="title">
应收
<el-tag size="mini" type="danger" v-if="detail.priceChanged">改</el-tag>
<i class="el-icon-info" v-else style="color: #909399; margin-left: 4px; cursor: pointer"></i>
</template>
</el-statistic>
</el-popover>
</el-col>
<el-col>
<el-popover placement="bottom" width="200" trigger="hover">
<div>
<template v-if="detail.vipUserId">
<div>优惠券:{{detail.vipName | dv}}</div>
<div>{{VipType.getLabel(detail.vipType)}}{{detail.vipDiscountValue | dv}} {{ VipType.getUnit(detail.vipType) }}</div>
</template>
<div>骑行费优惠:{{detail.ridingDiscount | fix2 | dv}} 元</div>
</div>
<el-statistic slot="reference" :value="detail.totalDiscountAmount" :precision="2" suffix="元">
<template slot="prefix">
<i class="el-icon-discount" style="color: #3c5ae6"></i>
</template>
<template slot="title">
优惠金额
<i class="el-icon-info" style="color: #909399; margin-left: 4px; cursor: pointer"></i>
</template>
</el-statistic>
</el-popover>
</el-col>
<el-col v-if="OrderStatus.finishedList().includes(detail.status)">
<el-popover placement="bottom" width="200" trigger="hover">
<div>
<div>骑行费:{{detail.actualRidingFee | fix2 | dv}} 元</div>
<div>调度费:{{detail.actualDispatchFee | fix2 | dv}} 元</div>
<div>管理费:{{detail.actualManageFee | fix2 | dv}} 元</div>
<div>车损费:{{detail.actualDeductionFee | fix2 | dv}} 元</div>
<br/>
<div>合计:{{detail.actualAmount | fix2 | dv}} 元</div>
<div v-if="detail.depositDeductionAmount != null">押金抵扣:{{detail.depositDeductionAmount | fix2 | dv}} 元</div>
</div>
<el-statistic slot="reference"
title="实收"
:value="detail.actualReceivedAmount"
:precision="2"
suffix="元"
value-style="color: #67C23A">
<template slot="prefix">
<i class="el-icon-success" style="color: #67C23A"></i>
</template>
<template slot="title">
实收 <i class="el-icon-info" style="color: #909399; margin-left: 4px; cursor: pointer"></i>
</template>
</el-statistic>
</el-popover>
</el-col>
<el-col>
<el-statistic
title="总退款"
:value="detail.totalRefundAmount"
:precision="2"
suffix="元"
value-style="color: #F56C6C">
<template slot="prefix">
<i class="el-icon-refresh-left" style="color: #F56C6C"></i>
</template>
</el-statistic>
</el-col>
<el-col>
<el-statistic
title="自动退款"
:value="detail.payAutoRefund"
:precision="2"
suffix="元"
value-style="color: #F56C6C">
<template slot="prefix">
<i class="el-icon-timer" style="color: #F56C6C"></i>
</template>
</el-statistic>
</el-col>
<el-col>
<el-popover placement="bottom" width="200" trigger="hover">
<div>
<div>骑行费:{{detail.ridingRefund | fix2 | dv}} 元</div>
<div>调度费:{{detail.dispatchRefund | fix2 | dv}} 元</div>
<div>管理费:{{detail.manageRefund | fix2 | dv}} 元</div>
<div>车损费:{{detail.deductionRefund | fix2 | dv}} 元</div>
</div>
<el-statistic
slot="reference"
:value="detail.adminRefundAmount"
:precision="2"
suffix="元"
value-style="color: #F56C6C">
<template slot="prefix">
<i class="el-icon-user" style="color: #F56C6C"></i>
</template>
<template slot="title">
人工退款 <i class="el-icon-info" style="color: #909399; margin-left: 4px; cursor: pointer"></i>
</template>
</el-statistic>
</el-popover>
</el-col>
</el-row>
</el-card>
<el-card>
<el-row class="mb10" type="flex" justify="end">
<el-button
size="small"
plain
type="danger"
icon="el-icon-close"
@click="handleEnd(detail)"
v-has-permi="['bst:order:end']"
v-show="OrderStatus.canEnd().includes(detail.status)"
>结束订单</el-button>
<el-button
size="small"
plain
type="warning"
icon="el-icon-wallet"
@click="handleRefund(detail)"
v-has-permi="['bst:order:refund']"
v-show="OrderStatus.canRefund().includes(detail.status)"
>退款</el-button>
<el-button
size="small"
plain
type="warning"
icon="el-icon-s-check"
@click="handleVerify(detail)"
v-has-permi="['bst:order:verify']"
v-show="OrderStatus.canVerify().includes(detail.status)"
>审核</el-button>
<el-button
size="small"
plain
type="warning"
icon="el-icon-wallet"
@click="handleDeduct(detail)"
v-has-permi="['bst:order:deduct']"
v-show="OrderStatus.canDeduct().includes(detail.status)"
>押金抵扣</el-button>
<el-button
size="small"
plain
type="warning"
icon="el-icon-edit-outline"
@click="handleUpdatePrice(detail)"
v-has-permi="['bst:order:updatePrice']"
v-show="OrderStatus.canDeduct().includes(detail.status)"
>改价</el-button>
</el-row>
<collapse-panel :value="true" title="基础信息">
<el-descriptions :column="4" >
<el-descriptions-item label="订单编号">
{{ detail.no | dv}}
<el-link v-clipboard:copy="detail.no" v-clipboard:success="handleCopySuccess" type="primary" icon="el-icon-document"/>
</el-descriptions-item>
<el-descriptions-item label="订单状态">
<dict-tag :options="dict.type.order_status" :value="detail.status" size="small"/>
</el-descriptions-item>
<el-descriptions-item label="运营区">
<area-link :id="detail.areaId" :text="detail.areaName" />
</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detail.createTime | dv}}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ detail.startTime | dv}}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ detail.endTime | dv}}</el-descriptions-item>
<el-descriptions-item label="骑行时长">{{ orderDuration | dv}}</el-descriptions-item>
<el-descriptions-item label="骑行距离">{{ detail.distance / 1000 | fix2 | dv}} 公里</el-descriptions-item>
<el-descriptions-item label="结束原因" v-if="detail.endReason">{{ detail.endReason | dv }}</el-descriptions-item>
<el-descriptions-item label="取消原因" v-if="detail.cancelRemark">{{ detail.cancelRemark | dv }}</el-descriptions-item>
</el-descriptions>
</collapse-panel>
<collapse-panel :value="true" title="套餐信息">
<el-descriptions :column="4" >
<el-descriptions-item label="套餐名称" :span="2">
{{ detail.suitName }}
<dict-tag :options="dict.type.suit_type" :value="detail.suitType" size="mini" style="margin-left: 4px;"/>
<dict-tag :options="dict.type.suit_riding_rule" :value="detail.suitRidingRule" size="mini" style="margin-left: 4px;"/>
<el-tag v-if="detail.suitDepositDeduction" type="success" size="mini" style="margin-left: 4px;">允许抵扣</el-tag>
</el-descriptions-item>
<el-descriptions-item label="免费骑行时间" :span="3">
{{ detail.suitFreeRideTime | dv }} 分钟
</el-descriptions-item>
<el-descriptions-item label="起步规则" :span="3" v-if="SuitRidingRule.START === detail.suitRidingRule">
在{{detail.suitStartRule.startingTime}}{{unitLabel(detail.suitRentalUnit)}}以内,起步价{{detail.suitStartRule.startingPrice}}元;
超出起步时间后,超出的时间每{{detail.suitStartRule.timeoutTime}}{{unitLabel(detail.suitRentalUnit)}}收费{{detail.suitStartRule.timeoutPrice}}元,
不满{{detail.suitStartRule.timeoutTime}}{{unitLabel(detail.suitRentalUnit)}},按{{detail.suitStartRule.timeoutTime}}{{unitLabel(detail.suitRentalUnit)}}计算。
</el-descriptions-item>
<el-descriptions-item label="区间规则" :span="3" v-if="SuitRidingRule.INTERVAL === detail.suitRidingRule">
<template v-if="detail.suitIntervalRule && detail.suitIntervalRule.length > 0">
<div v-for="(rule, index) in detail.suitIntervalRule" :key="index">
<template v-if="index === detail.suitIntervalRule.length - 1">
在{{rule.start}}{{unitLabel(detail.suitRentalUnit)}}之后,
每{{rule.eachUnit}}{{unitLabel(detail.suitRentalUnit)}}收费{{rule.fee}}元;
</template>
<template v-else>
在{{rule.start}}~{{rule.end}}{{unitLabel(detail.suitRentalUnit)}}之间,
每{{rule.eachUnit}}{{unitLabel(detail.suitRentalUnit)}}收费{{rule.fee}}元;
</template>
</div>
</template>
<template v-else>
暂无区间规则
</template>
</el-descriptions-item>
</el-descriptions>
</collapse-panel>
</el-card>
</el-col>
<el-col :span="6">
<el-card>
<collapse-panel :value="true" title="设备信息">
<el-descriptions :column="1" >
<el-descriptions-item label="SN">
<device-link :id="detail.deviceId" :text="detail.deviceSn"/>
</el-descriptions-item>
<el-descriptions-item label="MAC">
<device-link :id="detail.deviceId" :text="detail.deviceMac"/>
</el-descriptions-item>
<el-descriptions-item label="车牌号">
<device-link :id="detail.deviceId" :text="detail.deviceVehicleNum"/>
</el-descriptions-item>
</el-descriptions>
</collapse-panel>
<collapse-panel :value="true" title="用户信息">
<el-descriptions :column="1" >
<el-descriptions-item label="用户">
<user-link :id="detail.userId" :text="detail.userName"/>
</el-descriptions-item>
<el-descriptions-item label="手机">
<user-link :id="detail.userId" :text="detail.userPhone"/>
</el-descriptions-item>
</el-descriptions>
</collapse-panel>
<collapse-panel :value="true" title="支付信息">
<el-descriptions :column="1" >
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.order_pay_type" :value="detail.payType" size="mini"/>
</el-descriptions-item>
<el-descriptions-item label="支付时间">
{{ detail.ridePayTime | dv }}
</el-descriptions-item>
<el-descriptions-item label="押金抵扣" v-if="detail.depositDeductionAmount != null">
{{ detail.depositDeductionAmount | fix2 | dv }} 元
</el-descriptions-item>
</el-descriptions>
</collapse-panel>
</el-card>
</el-col>
</el-row>
<el-card class="box-card" v-if="detail.id" style="margin-top: 10px;">
<el-tabs >
<el-tab-pane lazy label="车辆轨迹" v-if="checkPermi(['bst:locationLog:list'])">
<device-location
:query="{orderId: detail.id, timeRange: orderTimeRange}"
:operQuery="{bizId: detail.id, bizType: LogBizType.ORDER, operTimeRange: []}"
:area-id="detail.areaId"
/>
</el-tab-pane>
<el-tab-pane lazy label="收益信息" v-if="checkPermi(['bst:bonus:list'])">
<bonus :query="{bstId: detail.id, bstType: BonusBstType.ORDER}" />
</el-tab-pane>
<el-tab-pane lazy label="订单车辆" v-if="checkPermi(['bst:orderDevice:list'])">
<order-device :query="{orderId: detail.id}" />
</el-tab-pane>
<el-tab-pane lazy label="支付信息" v-if="checkPermi(['bst:pay:list'])">
<pay :query="{bstId: detail.id, bstTypes: payBstTypes}"/>
</el-tab-pane>
<el-tab-pane lazy label="退款信息" v-if="checkPermi(['bst:refund:list'])">
<refund :query="{payBstId: detail.id, payBstTypes: payBstTypes}"/>
</el-tab-pane>
<el-tab-pane lazy label="命令日志" v-if="checkPermi(['bst:commandLog:list'])">
<command-log :query="{orderId: detail.id}"/>
</el-tab-pane>
<el-tab-pane lazy label="改价记录" v-if="checkPermi(['bst:feeLog:list'])">
<fee-log :query="{orderId: detail.id}"/>
</el-tab-pane>
<el-tab-pane lazy label="操作日志" v-if="checkPermi(['monitor:operlog:list'])">
<operlog :query="{bizId: detail.id, bizType: LogBizType.ORDER}"/>
</el-tab-pane>
</el-tabs>
</el-card>
<order-refund-dialog :id="detail.id" :visible.sync="showRefundDialog" @success="getDetail" />
<order-verify-dialog :id="detail.id" :visible.sync="showVerifyDialog" @success="getDetail" />
<order-deduct-dialog :id="detail.id" :visible.sync="showDeductDialog" @success="getDetail" />
<order-change-price-dialog :id="detail.id" :visible.sync="showUpdatePriceDialog" @success="getDetail" />
</div>
</template>
<script>
import { appCalcOrderFee } from '@/api/app/order'
import { endOrder, getOrder } from '@/api/bst/order'
import AreaLink from '@/components/Business/Area/AreaLink.vue'
import DeviceLink from '@/components/Business/Device/DeviceLink.vue'
import UserLink from '@/components/Business/User/UserLink.vue'
import CollapsePanel from '@/components/CollapsePanel/index.vue'
import { getLastDateTimeEndStr, toDescriptionFromSecond } from '@/utils/date'
import { BonusBstType, LogBizType, OrderStatus, PayBstType, SuitRidingRule, VipType } from '@/utils/enums'
import Bonus from '@/views/bst/bonus/index.vue'
import CommandLog from '@/views/bst/commandLog/index.vue'
import DeviceLocation from '@/views/bst/device/view/components/DeviceLocation.vue'
import FeeLog from '@/views/bst/feeLog/index.vue'
import OrderChangePriceDialog from '@/views/bst/order/components/OrderChangePriceDialog.vue'
import OrderDeductDialog from '@/views/bst/order/components/OrderDeductDialog.vue'
import OrderRefundDialog from '@/views/bst/order/components/OrderRefundDialog.vue'
import OrderVerifyDialog from '@/views/bst/order/components/OrderVerifyDialog.vue'
import { getOrderDuration } from '@/views/bst/order/util'
import OrderDevice from '@/views/bst/orderDevice/index.vue'
import Pay from '@/views/bst/pay/index.vue'
import Refund from '@/views/bst/refund/index.vue'
import Operlog from '@/views/monitor/operlog/index.vue'
export default {
name: 'OrderView',
dicts: [
'order_status',
'device_lock_status',
'suit_type',
'suit_rental_unit',
'suit_riding_rule',
'order_return_mode',
'order_return_type',
'order_pay_type'
],
components: {
CollapsePanel,
OrderDevice,
Pay,
Bonus,
DeviceLocation,
OrderRefundDialog,
OrderVerifyDialog,
Refund,
Operlog,
CommandLog,
DeviceLink,
UserLink,
AreaLink,
OrderDeductDialog,
FeeLog,
OrderChangePriceDialog
},
data() {
return {
VipType,
payBstTypes: [PayBstType.ORDER, PayBstType.ORDER_RIDE],
OrderStatus,
id: null,
detail: {},
loading: false,
SuitRidingRule,
PayBstType,
BonusBstType,
LogBizType,
showRefundDialog: false,
showVerifyDialog: false,
showDeductDialog: false,
showUpdatePriceDialog: false,
}
},
computed: {
orderDuration() {
return toDescriptionFromSecond(getOrderDuration(this.detail)).text;
},
orderTimeRange() {
if (this.detail.startTime && this.detail.endTime) {
return [this.detail.startTime, this.detail.endTime];
}
return [this.detail.startTime, getLastDateTimeEndStr(0)];
}
},
created() {
this.id = this.$route.params.id
this.getDetail()
},
methods: {
handleCopySuccess() {
this.$message.success("复制成功");
},
toDescriptionFromSecond,
getDetail() {
this.loading = true
getOrder(this.id).then(res => {
this.detail = res.data;
}).finally(() => {
this.loading = false
})
},
handleRefund(row) {
this.showRefundDialog = true;
},
handleVerify(row) {
this.showVerifyDialog = true;
},
handleDeduct(row) {
this.showDeductDialog = true;
},
handleUpdatePrice(row) {
this.showUpdatePriceDialog = true;
},
async handleEnd(row) {
this.loading = true;
try {
const calcRes = await appCalcOrderFee({orderId: row.id, checkLocation: false})
let fee = {}
if (calcRes.code === 200 && calcRes.data) {
fee = calcRes.data
}
if (fee.manageFee || fee.dispatchFee) {
this.$confirm(`订单${row.no}存在${fee.dispatchFee ? '调度费' + fee.dispatchFee + '元' : ''}${fee.manageFee ? '管理费' + fee.manageFee + '元' : ''},是否收取?`, '提示', {
confirmButtonText: '收取',
cancelButtonText: '不收取',
type: 'warning'
}).then(() => {
this.doEndOrder(row.id, true);
}).catch(() => {
this.doEndOrder(row.id, false);
});
} else {
this.$confirm(`确定结束订单${row.no}吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.doEndOrder(row.id, false);
});
}
} catch (err) {
this.loading = false;
}
},
doEndOrder(id, needDispatchFee) {
this.loading = true;
endOrder(id, needDispatchFee).then(res => {
this.$message.success("结束成功");
this.getDetail();
}).catch((err) => {
this.loading = false;
})
},
unitLabel(value) {
return this.dict.type.suit_rental_unit.find(item => item.value === value)?.label || value;
},
}
}
</script>
<style scoped>
.red-text {
color: red;
}
.green-text {
color: green;
}
</style>