|
|
<template>
|
|
|
<view class="wrap">
|
|
|
<u-navbar
|
|
|
title="退款详情"
|
|
|
:is-back="true"
|
|
|
back-icon-color="#fff"
|
|
|
:background="{ background: '#1479ff' }"
|
|
|
title-color="#fff"
|
|
|
:border-bottom="false"
|
|
|
:custom-back="goBack"
|
|
|
></u-navbar>
|
|
|
<view class="b-border"></view>
|
|
|
|
|
|
<view v-if="loading" class="state-tip">加载中…</view>
|
|
|
<view v-else-if="!refundDetail.id" class="state-tip">未找到退款记录</view>
|
|
|
|
|
|
<scroll-view v-else scroll-y class="scroll" :style="{ paddingBottom: actionBarPad }">
|
|
|
<view class="order-info">
|
|
|
<view class="section-title">退款信息</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">退款编号</text>
|
|
|
<text class="value">{{ refundDetail.no }}</text>
|
|
|
</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">状态</text>
|
|
|
<text class="value">{{ refundStatusText(refundDetail.status) }}</text>
|
|
|
</view>
|
|
|
<view class="info-item" v-if="refundDetail.apply_reason">
|
|
|
<text class="label">退款理由</text>
|
|
|
<text class="value">{{ refundDetail.apply_reason }}</text>
|
|
|
</view>
|
|
|
<view class="info-item" v-if="showRefundResult">
|
|
|
<text class="label">处理说明</text>
|
|
|
<text class="value">{{ displayRefundResult }}</text>
|
|
|
</view>
|
|
|
<view class="info-item" v-if="refundDetail.created_at">
|
|
|
<text class="label">申请时间</text>
|
|
|
<text class="value">{{ refundDetail.created_at }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="order-info" v-if="orderRef && orderRef.id">
|
|
|
<view class="section-title">关联订单</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">订单号</text>
|
|
|
<text class="value">{{ orderRef.no }}</text>
|
|
|
</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">服务项目</text>
|
|
|
<text class="value">{{ orderRef.accompany_product ? orderRef.accompany_product.name : '' }}</text>
|
|
|
</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">服务时间</text>
|
|
|
<text class="value">{{ orderRef.time }}</text>
|
|
|
</view>
|
|
|
<view class="info-item" v-if="Number(orderRef.type) === 2">
|
|
|
<text class="label">服务地址</text>
|
|
|
<text class="value">{{ homeCareServiceAddress(orderRef) }}</text>
|
|
|
</view>
|
|
|
<view class="info-item" v-else>
|
|
|
<text class="label">就诊医院</text>
|
|
|
<text class="value">{{ orderRef.hospital ? orderRef.hospital.name : '' }}</text>
|
|
|
</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">被服务人</text>
|
|
|
<text class="value">{{ orderRef.user_archive ? orderRef.user_archive.name : '' }}</text>
|
|
|
</view>
|
|
|
<view class="info-item">
|
|
|
<text class="label">订单金额</text>
|
|
|
<text class="value">¥{{ orderRef.price }}</text>
|
|
|
</view>
|
|
|
<view class="info-item" v-if="orderRef.paid_at">
|
|
|
<text class="label">支付时间</text>
|
|
|
<text class="value">{{ orderRef.paid_at }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="order-info" v-if="orderLogs.length > 0">
|
|
|
<view class="section-title">订单日志</view>
|
|
|
<view class="log-entry" v-for="(log, idx) in orderLogs" :key="log.id != null ? log.id : idx">
|
|
|
<text class="log-meta">{{ formatLogTime(log) }}</text>
|
|
|
<text class="log-operator">{{ formatLogOperator(log) }}</text>
|
|
|
<text class="log-text">{{ log.remark || '—' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
|
|
|
<view v-if="showActionBar" class="action-bar">
|
|
|
<view class="bar-btn-wrap">
|
|
|
<u-button type="primary" shape="circle" :custom-style="approveStyle" @click="onApprove">同意退款</u-button>
|
|
|
</view>
|
|
|
<view class="bar-btn-wrap">
|
|
|
<u-button shape="circle" type="default" :custom-style="rejectStyle" @click="onReject">驳回</u-button>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { homeCareServiceAddress } from '@/common/homeCareAddress.js'
|
|
|
import { parseApiRecord, refundStatusText } from '@/common/refundApply.js'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
refundId: '',
|
|
|
detailSource: 'staff',
|
|
|
loading: true,
|
|
|
refundDetail: {},
|
|
|
approveStyle: {
|
|
|
background: '#1479ff',
|
|
|
color: '#fff',
|
|
|
fontSize: '30rpx',
|
|
|
height: '88rpx',
|
|
|
width: '100%'
|
|
|
},
|
|
|
rejectStyle: {
|
|
|
background: '#969da7',
|
|
|
color: '#fff',
|
|
|
fontSize: '30rpx',
|
|
|
height: '88rpx',
|
|
|
width: '100%'
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
orderRef() {
|
|
|
return this.refundDetail.order || null
|
|
|
},
|
|
|
orderLogs() {
|
|
|
const o = this.orderRef
|
|
|
if (!o || typeof o !== 'object') return []
|
|
|
const raw = o.accompany_order_log || o.accompanyOrderLog
|
|
|
if (!Array.isArray(raw)) return []
|
|
|
return [...raw].sort((a, b) => {
|
|
|
const ta = new Date(a.created_at || 0).getTime()
|
|
|
const tb = new Date(b.created_at || 0).getTime()
|
|
|
return tb - ta
|
|
|
})
|
|
|
},
|
|
|
showActionBar() {
|
|
|
return !this.loading && Number(this.refundDetail.status) === 0
|
|
|
},
|
|
|
actionBarPad() {
|
|
|
return this.showActionBar ? '180rpx' : '40rpx'
|
|
|
},
|
|
|
showRefundResult() {
|
|
|
const st = Number(this.refundDetail.status)
|
|
|
if (st === 1 || !this.refundDetail.result) return false
|
|
|
return st === 2
|
|
|
},
|
|
|
displayRefundResult() {
|
|
|
const raw = String(this.refundDetail.result || '').trim()
|
|
|
if (!raw) return ''
|
|
|
if (raw.startsWith('{')) {
|
|
|
try {
|
|
|
const obj = JSON.parse(raw)
|
|
|
if (obj.summary) return String(obj.summary)
|
|
|
} catch (e) {}
|
|
|
return '退款失败,请联系管理员'
|
|
|
}
|
|
|
return raw
|
|
|
}
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
this.refundId = options.id || ''
|
|
|
this.detailSource = options.source === 'operator' ? 'operator' : 'staff'
|
|
|
if (this.refundId) {
|
|
|
this.fetchDetail()
|
|
|
} else {
|
|
|
this.loading = false
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
homeCareServiceAddress,
|
|
|
goBack() {
|
|
|
uni.navigateBack({ delta: 1 })
|
|
|
},
|
|
|
normalizePayload(res) {
|
|
|
let payload = res
|
|
|
if (
|
|
|
payload &&
|
|
|
typeof payload.data !== 'undefined' &&
|
|
|
payload.data !== null &&
|
|
|
payload.errcode === undefined
|
|
|
) {
|
|
|
payload = payload.data
|
|
|
}
|
|
|
return payload
|
|
|
},
|
|
|
async fetchDetail() {
|
|
|
this.loading = true
|
|
|
try {
|
|
|
let res = null
|
|
|
if (this.detailSource === 'operator') {
|
|
|
res = await this.$u.api.operatorAccompanyRefundShow({
|
|
|
id: this.refundId,
|
|
|
'show_relation[0]': 'order',
|
|
|
'show_relation[1]': 'order.userArchive',
|
|
|
'show_relation[2]': 'order.accompanyProduct',
|
|
|
'show_relation[3]': 'order.hospital',
|
|
|
'show_relation[4]': 'order.accompanyOrderLog'
|
|
|
})
|
|
|
} else {
|
|
|
res = await this.$u.api.staffRefundShow({
|
|
|
id: this.refundId,
|
|
|
'show_relation[0]': 'order',
|
|
|
'show_relation[1]': 'order.userArchive',
|
|
|
'show_relation[2]': 'order.accompanyProduct',
|
|
|
'show_relation[3]': 'order.hospital',
|
|
|
'show_relation[4]': 'order.accompanyOrderLog'
|
|
|
})
|
|
|
}
|
|
|
if (res === false) {
|
|
|
this.refundDetail = {}
|
|
|
this.loading = false
|
|
|
return
|
|
|
}
|
|
|
this.refundDetail = this.normalizePayload(res) || {}
|
|
|
} catch (e) {
|
|
|
this.refundDetail = {}
|
|
|
uni.showToast({ title: this.apiErrorMessage(e, '加载失败'), icon: 'none' })
|
|
|
} finally {
|
|
|
this.loading = false
|
|
|
}
|
|
|
},
|
|
|
apiErrorMessage(e, fallback) {
|
|
|
const m = e && (e.msg || e.message || (e.data && (e.data.msg || e.data.message)))
|
|
|
if (m && String(m).trim()) return String(m).slice(0, 60)
|
|
|
return fallback || '操作失败'
|
|
|
},
|
|
|
refundStatusText(s) {
|
|
|
return refundStatusText(s)
|
|
|
},
|
|
|
formatLogTime(log) {
|
|
|
if (!log) return ''
|
|
|
return log.created_at || log.updated_at || ''
|
|
|
},
|
|
|
formatLogOperator(log) {
|
|
|
if (!log) return '操作人:—'
|
|
|
const name = log.operator_name && String(log.operator_name).trim()
|
|
|
? String(log.operator_name).trim()
|
|
|
: ''
|
|
|
if (!name) return '操作人:—'
|
|
|
const typeLabels = {
|
|
|
worker: '工作人员',
|
|
|
nurse: '护工',
|
|
|
admin: '运营',
|
|
|
user: '用户',
|
|
|
system: '系统'
|
|
|
}
|
|
|
let t = log.operator_type ? (typeLabels[log.operator_type] || log.operator_type) : ''
|
|
|
if (log.operator_type === 'system' && name === '微信支付') {
|
|
|
t = '微信支付'
|
|
|
}
|
|
|
return t ? `操作人:${name}(${t})` : `操作人:${name}`
|
|
|
},
|
|
|
onApprove() {
|
|
|
uni.showModal({
|
|
|
title: '确认同意退款?',
|
|
|
content: '将按规则发起微信退款',
|
|
|
success: async (r) => {
|
|
|
if (!r.confirm) return
|
|
|
try {
|
|
|
let res = null
|
|
|
if (this.detailSource === 'operator') {
|
|
|
res = await this.$u.api.operatorAccompanyRefundSave({ id: this.refundDetail.id, status: 1 })
|
|
|
} else {
|
|
|
res = await this.$u.api.staffRefundProcess({ id: this.refundDetail.id, action: 'approve' })
|
|
|
}
|
|
|
if (res === false || parseApiRecord(res) == null) {
|
|
|
return
|
|
|
}
|
|
|
await this.fetchDetail()
|
|
|
const st = Number(this.refundDetail.status)
|
|
|
if (st === 1) {
|
|
|
uni.showToast({ title: '退款成功', icon: 'success' })
|
|
|
} else if (st === 2) {
|
|
|
uni.showToast({ title: '退款失败,请查看处理说明', icon: 'none' })
|
|
|
} else {
|
|
|
uni.showToast({ title: '处理未完成,请稍后重试', icon: 'none' })
|
|
|
}
|
|
|
} catch (e) {
|
|
|
uni.showToast({ title: this.apiErrorMessage(e, '操作失败'), icon: 'none' })
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
onReject() {
|
|
|
uni.showModal({
|
|
|
title: '确认驳回该退款?',
|
|
|
content: '驳回后用户可重新沟通或再次申请',
|
|
|
success: async (r) => {
|
|
|
if (!r.confirm) return
|
|
|
try {
|
|
|
let res = null
|
|
|
if (this.detailSource === 'operator') {
|
|
|
res = await this.$u.api.operatorAccompanyRefundSave({ id: this.refundDetail.id, status: 2 })
|
|
|
} else {
|
|
|
res = await this.$u.api.staffRefundProcess({ id: this.refundDetail.id, action: 'reject' })
|
|
|
}
|
|
|
if (res === false || parseApiRecord(res) == null) {
|
|
|
return
|
|
|
}
|
|
|
await this.fetchDetail()
|
|
|
if (Number(this.refundDetail.status) === 2) {
|
|
|
uni.showToast({ title: '已驳回', icon: 'success' })
|
|
|
}
|
|
|
} catch (e) {
|
|
|
uni.showToast({ title: this.apiErrorMessage(e, '操作失败'), icon: 'none' })
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.wrap {
|
|
|
min-height: 100vh;
|
|
|
background: #f5f5f5;
|
|
|
}
|
|
|
|
|
|
.b-border {
|
|
|
width: 100%;
|
|
|
height: 30rpx;
|
|
|
border-radius: 0 0 120rpx 120rpx;
|
|
|
background-color: #1479ff;
|
|
|
}
|
|
|
|
|
|
.state-tip {
|
|
|
text-align: center;
|
|
|
color: #999;
|
|
|
padding: 80rpx 30rpx;
|
|
|
font-size: 28rpx;
|
|
|
}
|
|
|
|
|
|
.scroll {
|
|
|
height: calc(100vh - 200rpx);
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
.order-info {
|
|
|
background: #fff;
|
|
|
margin: 24rpx 24rpx 0;
|
|
|
border-radius: 16rpx;
|
|
|
padding: 24rpx 28rpx 32rpx;
|
|
|
box-shadow: 0 4rpx 16rpx #e6eaf1;
|
|
|
}
|
|
|
|
|
|
.section-title {
|
|
|
font-size: 30rpx;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
margin-bottom: 20rpx;
|
|
|
}
|
|
|
|
|
|
.info-item {
|
|
|
display: flex;
|
|
|
padding: 16rpx 0;
|
|
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
|
|
|
|
|
&:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
|
|
|
.label {
|
|
|
width: 200rpx;
|
|
|
flex-shrink: 0;
|
|
|
font-size: 28rpx;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
.value {
|
|
|
flex: 1;
|
|
|
font-size: 28rpx;
|
|
|
color: #333;
|
|
|
word-break: break-all;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.log-entry {
|
|
|
padding: 24rpx 0;
|
|
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
|
|
|
|
|
&:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.log-meta {
|
|
|
display: block;
|
|
|
font-size: 24rpx;
|
|
|
color: #999;
|
|
|
margin-bottom: 12rpx;
|
|
|
}
|
|
|
|
|
|
.log-operator {
|
|
|
display: block;
|
|
|
font-size: 24rpx;
|
|
|
color: #666;
|
|
|
margin-bottom: 12rpx;
|
|
|
}
|
|
|
|
|
|
.log-text {
|
|
|
display: block;
|
|
|
font-size: 28rpx;
|
|
|
color: #333;
|
|
|
line-height: 1.5;
|
|
|
}
|
|
|
|
|
|
.action-bar {
|
|
|
position: fixed;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
padding: 20rpx 24rpx;
|
|
|
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
|
|
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
|
background: #fff;
|
|
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
display: flex;
|
|
|
gap: 24rpx;
|
|
|
align-items: center;
|
|
|
z-index: 100;
|
|
|
|
|
|
.bar-btn-wrap {
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
</style>
|