Files
oil/admin-vue/src/views/partners/order/modules/RefundForm.vue
T
vipnub fafd3e2c16 1、合作商余额查询
2、分油账户司机可用额度
2026-10-20 23:21:10 +08:00

242 lines
8.7 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>
<a-modal
title="订单退款"
:width="760"
:visible="visible"
:footer="null"
:maskClosable="false"
@cancel="handleCancel"
>
<a-spin :spinning="isLoading">
<div class="info">
<p>油卡编号:{{ record.oilCardNo }}</p>
<p>司机:{{ record.driver_name || '-' }}&nbsp;&nbsp;{{ record.mobile }}<a-tag v-if="record.source === 'mall'" class="ml-10">小程序订单</a-tag></p>
<p>订单金额:¥{{ record.total_price }}&nbsp;&nbsp;已加油:¥{{ record.used_amount || '0.00' }}({{ record.used_num || 0 }} 次)</p>
<p>
退款金额:<b class="amount">¥{{ refundAmount }}</b>
<span v-if="status === 2">(实际退款)</span>
<span v-else>(剩余未加油金额,固定不可修改)</span>
</p>
<p>当前状态:<a-tag :color="statusColor">{{ statusText }}</a-tag></p>
<p v-if="record.refund_text" class="sub">{{ record.refund_text }}</p>
</div>
<a-alert v-if="(status === 0 || status === 3) && Number(refundAmount) <= 0" type="warning" show-icon class="block" message="订单金额已全部加油,没有可退金额" />
<!-- 可发起:未退款 / 退款失败,且还有剩余未加油金额 -->
<template v-if="(status === 0 || status === 3) && Number(refundAmount) > 0">
<a-alert type="info" show-icon class="block">
<div slot="message">
一张订单可以分多次加油,<b>退款金额固定为剩余未加油金额</b>,已加油的部分不退,每单只能成功退一次。
<br>退款会先用企油通 7.17 查询司机支付凭证,再用 7.16 把凭证额度调低退款金额;成功后{{ backText }}。
<br>额度在凭证上按司机累计,若凭证可用余额不足退款金额(被该司机其他订单用掉),会直接拒绝。
</div>
</a-alert>
<a-textarea v-model="reason" :rows="2" :maxLength="100" placeholder="退款原因(选填,会写入企油通调额记录)" />
<div class="btns">
<a-button type="primary" :loading="isLoading" @click="handleRefund">确认退款</a-button>
</div>
</template>
<!-- 待人工确认 -->
<template v-if="status === 4">
<a-alert type="warning" show-icon class="block">
<div slot="message">
调额请求已发给企油通但没有拿到明确结果。企油通调额接口<b>没有幂等保护</b>,重复发起会被重复扣减,所以这里不允许直接重试。
<br>请先点「向企油通取证」:系统会查司机钱包流水(找退款单号)和凭证额度变化自动判定;判定不了再人工核对后提交。
</div>
</a-alert>
<div class="btns">
<a-button type="primary" :loading="isLoading" @click="handleConfirm">向企油通取证</a-button>
</div>
<a-divider orientation="left">人工判定</a-divider>
<a-radio-group v-model="resolveResult">
<a-radio value="success">企油通已调低额度(退款成功)</a-radio>
<a-radio value="failed">企油通未调额(退款失败,可重新发起)</a-radio>
</a-radio-group>
<a-textarea v-model="resolveRemark" class="mt-10" :rows="2" :maxLength="100" placeholder="判定依据(必填),如:已在企油通后台核对凭证 xxx 总额度由 300 变为 200" />
<div class="btns">
<a-button :loading="isLoading" @click="handleResolve">提交人工判定</a-button>
</div>
</template>
<a-divider orientation="left">退款流水</a-divider>
<a-table
size="small"
rowKey="refund_id"
:columns="columns"
:dataSource="logs"
:pagination="false"
>
<span slot="status" slot-scope="text, row">
<a-tag :color="logColor(row.status)">{{ row.status_text }}</a-tag>
</span>
</a-table>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/partners/order'
const STATUS_TEXT = { 0: '未退款', 1: '退款中', 2: '已退款', 3: '退款失败', 4: '待人工确认' }
const STATUS_COLOR = { 1: 'blue', 2: 'green', 3: 'red', 4: 'orange' }
export default {
data () {
return {
visible: false,
isLoading: false,
record: {},
reason: '',
resolveResult: 'success',
resolveRemark: '',
logs: [],
columns: [
{ title: '退款单号', dataIndex: 'refund_no' },
{ title: '状态', scopedSlots: { customRender: 'status' } },
{ title: '凭证', dataIndex: 'quota_id' },
{ title: '调额前可用', dataIndex: 'available_before' },
{ title: '说明', dataIndex: 'message' },
{ title: '操作人', dataIndex: 'operator' },
{ title: '时间', dataIndex: 'create_time_text' }
]
}
},
computed: {
status () {
return Number(this.record.refund_status || 0)
},
statusText () {
return STATUS_TEXT[this.status] || '-'
},
statusColor () {
return STATUS_COLOR[this.status] || ''
},
// 退款成功后钱退到哪里:合作商单回补合作商余额,小程序单退回下单用户余额
backText () {
return this.record.source === 'mall'
? '把钱退回下单用户的账户余额(按实付金额折算),并同步订单状态到用户端'
: '原路回补合作商余额,并推送油卡变动通知'
},
// 已退款显示实际退款金额;否则为剩余未加油金额(后端同口径计算)
refundAmount () {
if (this.status === 2) {
return Number(this.record.refund_amount || 0).toFixed(2)
}
const remain = Number(this.record.total_price || 0) - Number(this.record.used_amount || 0)
return Math.max(0, remain).toFixed(2)
}
},
methods: {
show (record) {
this.visible = true
this.record = { ...record }
this.reason = ''
this.resolveResult = 'success'
this.resolveRemark = ''
this.loadLogs()
},
handleCancel () {
this.visible = false
},
loadLogs () {
Api.refundLog({ orderId: this.record.order_id }).then(result => {
this.logs = result.data.list
// 流水状态码与订单退款状态一致:用最新一条同步弹窗状态,失败 / 转人工后界面跟着切换
if (this.logs.length) {
this.record = { ...this.record, refund_status: Number(this.logs[0].status), refund_text: this.logs[0].message }
}
})
},
logColor (status) {
return STATUS_COLOR[Number(status)] || ''
},
// 退款 / 取证 / 判定之后统一刷新:弹窗里的状态 + 父列表
afterAction (result, status) {
this.$message.success(result.message, 2)
if (status !== undefined) {
this.record = { ...this.record, refund_status: status, refund_text: result.message }
}
this.loadLogs()
this.$emit('handleSubmit')
},
// 业务失败时后端返回错误,request 拦截器已弹出提示;这里仍需刷新状态(可能已变成失败/待确认)
afterError () {
this.loadLogs()
this.$emit('handleSubmit')
},
handleRefund () {
this.$confirm({
title: '确认退款 ¥' + this.refundAmount + '?',
content: '退还剩余未加油金额:将调低该司机企油通凭证额度,成功后回补合作商余额。',
onOk: () => {
this.isLoading = true
return Api.refund({ orderId: this.record.order_id, reason: this.reason })
.then(result => this.afterAction(result, 2))
.catch(() => this.afterError())
.finally(() => {
this.isLoading = false
})
}
})
},
handleConfirm () {
this.isLoading = true
Api.refundConfirm({ orderId: this.record.order_id })
.then(result => this.afterAction(result, result.data.refundStatus))
.catch(() => this.afterError())
.finally(() => {
this.isLoading = false
})
},
handleResolve () {
if (!this.resolveRemark.trim()) {
this.$message.warning('请填写判定依据')
return
}
this.isLoading = true
Api.refundResolve({ orderId: this.record.order_id, result: this.resolveResult, remark: this.resolveRemark })
.then(result => this.afterAction(result, result.data.refundStatus))
.catch(() => this.afterError())
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.info p {
margin-bottom: 6px;
}
.amount {
color: #f5222d;
}
.sub {
color: #999;
font-size: 12px;
}
.block {
margin: 12px 0;
}
.btns {
margin-top: 12px;
text-align: right;
}
.mt-10 {
margin-top: 10px;
}
</style>