chuangte_bike_newxcx/page_shanghu/offlinePay/audit.vue

473 lines
11 KiB
Vue
Raw Normal View History

2026-06-02 16:35:58 +08:00
<template>
<view class="page">
<u-navbar title="线下支付审核" :border-bottom="false" :background="bgc" title-color="#2E4975" title-size="36"
back-icon-color="#2E4975" height="45"></u-navbar>
<scroll-view scroll-y class="page-scroll">
<view class="scroll-inner" :class="{ 'scroll-inner--with-bar': detail && detail.id && canVerify }">
<view v-if="loading" class="loading-wrap">
<u-loading mode="circle" size="48" color="#4C97E7"></u-loading>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="detail && detail.id" class="content">
<view class="amount-card">
<view class="amount-label">支付金额</view>
<view class="amount-value">{{ displayCurrencySymbol }}{{ formatAmount(detail.amount) }}</view>
<view class="amount-status">
<text class="status-tag" :class="statusClass">{{ statusLabel }}</text>
</view>
</view>
<view class="card">
<view class="card-title">申请信息</view>
<view class="info-row">
<text class="label">申请单号</text>
<text class="value">{{ detail.no }}</text>
</view>
<view class="info-row">
<text class="label">支付单号</text>
<text class="value">{{ detail.payNo || '-' }}</text>
</view>
<view class="info-row">
<text class="label">申请用户</text>
<text class="value">{{ detail.userName || '-' }}{{ detail.userPhone ? ' (' + detail.userPhone + ')' : '' }}</text>
</view>
<view class="info-row">
<text class="label">申请时间</text>
<text class="value">{{ detail.createTime || '-' }}</text>
</view>
<view class="info-row" v-if="detail.reason">
<text class="label">支付说明</text>
<text class="value">{{ detail.reason }}</text>
</view>
<view class="info-row">
<text class="label">区域</text>
<text class="value">{{ detail.areaName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">应用</text>
<text class="value">{{ detail.appName || '-' }}</text>
</view>
</view>
<view class="card" v-if="detail.status && !canVerify">
<view class="card-title">审核结果</view>
<view class="info-row">
<text class="label">审核时间</text>
<text class="value">{{ detail.verifyTime || '-' }}</text>
</view>
<view class="info-row">
<text class="label">审核人</text>
<text class="value">{{ detail.verifyUserName || '-' }}</text>
</view>
<view class="info-row" v-if="detail.verifyRemark">
<text class="label">审核备注</text>
<text class="value">{{ detail.verifyRemark }}</text>
</view>
</view>
<view class="remark-wrap" v-if="canVerify">
<text class="remark-label">审核备注</text>
<textarea
v-model="form.remark"
class="remark-input"
placeholder="请输入审核备注(选填)"
placeholder-style="color:#C7CDD3"
maxlength="500"
/>
</view>
</view>
<view v-else class="empty-wrap">
<text class="empty-text">{{ failMsg || '线下支付申请加载失败' }}</text>
<view class="empty-btn" hover-class="app-tap-hover" @click="loadDetail">重新加载</view>
</view>
</view>
</scroll-view>
<view class="bottom-actions" v-if="detail && detail.id && canVerify">
<view class="btn reject-btn" :class="{ disabled: submitLoading }" hover-class="app-tap-hover" @click="handleSubmit(false)">
<text>驳回</text>
</view>
<view class="btn pass-btn" :class="{ disabled: submitLoading }" hover-class="app-tap-hover" @click="handleSubmit(true)">
<text>{{ submitLoading ? '提交中...' : '通过审核' }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: { backgroundColor: '#EEF3FA' },
applyNo: '',
payNo: '',
loading: true,
failMsg: '',
detail: null,
form: {
remark: ''
},
submitLoading: false,
auditResultEmitted: false,
eventChannel: null
}
},
computed: {
displayCurrencySymbol() {
const d = this.detail || {}
return d.currencySymbol != null && d.currencySymbol !== '' ? d.currencySymbol : ''
},
canVerify() {
return this.detail && this.detail.status === 'WAIT_AUDIT'
},
statusLabel() {
if (!this.detail || !this.detail.status) return ''
const map = {
WAIT_AUDIT: '待审核',
PASSED: '已通过',
REJECTED: '已驳回',
CANCELED: '已取消'
}
return map[this.detail.status] || this.detail.status
},
statusClass() {
if (!this.detail || !this.detail.status) return ''
const map = {
WAIT_AUDIT: 'status-wait',
PASSED: 'status-passed',
REJECTED: 'status-rejected',
CANCELED: 'status-canceled'
}
return map[this.detail.status] || ''
}
},
onLoad(options) {
this.applyNo = options.no ? decodeURIComponent(options.no) : ''
this.payNo = options.payNo ? decodeURIComponent(options.payNo) : ''
if (this.getOpenerEventChannel) {
this.eventChannel = this.getOpenerEventChannel()
}
this.loadDetail()
},
onUnload() {
if (this.auditResultEmitted) return
const status = this.detail && this.detail.status
if (status === 'REJECTED') {
this.emitAuditResult({ pass: false, rejected: true, payNo: this.payNo || '' })
} else if (status === 'WAIT_AUDIT') {
this.emitAuditResult({ pass: false, canceled: true, payNo: this.payNo || '' })
}
},
methods: {
emitAuditResult(payload) {
if (this.auditResultEmitted) return
this.auditResultEmitted = true
const data = payload || {}
try {
if (this.eventChannel && this.eventChannel.emit) {
this.eventChannel.emit('offlineAuditResult', data)
}
} catch (e) {}
uni.setStorageSync('agentOrderOfflineResult', data)
},
loadDetail() {
if (!this.applyNo) {
this.failMsg = '缺少线下支付申请单号'
this.loading = false
return
}
this.loading = true
this.failMsg = ''
const query = `no=${encodeURIComponent(this.applyNo)}&pageNum=1&pageSize=1`
this.$u.get(`/bst/offlinePay/list?${query}`).then(res => {
const first = res.rows && res.rows[0]
if (res.code === 200 && first) {
this.detail = first
} else {
this.detail = null
this.failMsg = res.msg || '线下支付申请加载失败'
}
}).catch(err => {
this.detail = null
this.failMsg = (err && err.msg) || '线下支付申请加载失败'
}).finally(() => {
this.loading = false
})
},
formatAmount(val) {
if (val === null || val === undefined || val === '') return '0.00'
const n = Number(val)
return isNaN(n) ? '0.00' : n.toFixed(2)
},
handleSubmit(pass) {
if (this.submitLoading || !this.detail) return
const tip = pass ? '确定通过此线下支付申请吗?' : '确定驳回此线下支付申请吗?'
uni.showModal({
title: '确认操作',
content: tip,
success: (modalRes) => {
if (!modalRes.confirm) return
this.verifyOfflinePay(pass)
}
})
},
verifyOfflinePay(pass) {
this.submitLoading = true
this.$u.put('/bst/offlinePay/verify', {
id: this.detail.id,
pass,
remark: this.form.remark || undefined
}).then(res => {
if (res.code === 200) {
uni.showToast({ title: '审核成功', icon: 'success', duration: 1200 })
const payNo = this.payNo || (this.detail && this.detail.payNo) || ''
if (pass) {
this.emitAuditResult({ pass: true, payNo })
} else {
this.emitAuditResult({ pass: false, rejected: true, payNo })
}
setTimeout(() => { uni.navigateBack() }, 1200)
} else {
uni.showToast({ title: res.msg || '审核失败', icon: 'none', duration: 2500 })
}
}).catch(err => {
uni.showToast({ title: (err && err.msg) || '审核失败', icon: 'none', duration: 2500 })
}).finally(() => {
this.submitLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
$primary: #4C97E7;
$primary-deep: #3A7EDB;
$ink: #2E4975;
page {
background-color: #EEF3FA;
}
.page {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #EEF3FA;
box-sizing: border-box;
}
.page-scroll {
flex: 1;
height: 0;
width: 100%;
}
.scroll-inner {
padding: 24rpx 32rpx 32rpx;
box-sizing: border-box;
}
.scroll-inner--with-bar {
padding-bottom: calc(32rpx + 120rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(32rpx + 120rpx + env(safe-area-inset-bottom));
}
.loading-wrap {
padding: 100rpx 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.loading-text {
margin-top: 18rpx;
font-size: 26rpx;
color: #8B98AA;
}
.content {
padding: 0;
}
.amount-card {
background: linear-gradient(135deg, $primary, $primary-deep);
border-radius: 28rpx;
padding: 48rpx 32rpx;
margin-bottom: 24rpx;
text-align: center;
box-shadow: 0 18rpx 40rpx rgba(76, 151, 231, 0.22);
}
.amount-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.78);
margin-bottom: 12rpx;
}
.amount-value {
font-size: 60rpx;
font-weight: 700;
color: #fff;
letter-spacing: 2rpx;
}
.amount-status {
margin-top: 20rpx;
}
.status-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8rpx 24rpx;
border-radius: 999rpx;
font-size: 24rpx;
background: rgba(255, 255, 255, 0.2);
color: #fff;
}
.status-wait {
color: #FFE58F;
}
.status-passed {
color: #B7EB8F;
}
.status-rejected {
color: #FFA39E;
}
.status-canceled {
color: rgba(255, 255, 255, 0.72);
}
.card,
.remark-wrap {
background: #fff;
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 8rpx 28rpx rgba(46, 73, 117, 0.06);
}
.card-title {
font-size: 30rpx;
font-weight: 700;
color: $ink;
margin-bottom: 24rpx;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
font-size: 28rpx;
line-height: 1.5;
}
.info-row:last-child {
margin-bottom: 0;
}
.label {
width: 160rpx;
color: #8B98AA;
flex-shrink: 0;
}
.value {
flex: 1;
color: #2F3A4A;
word-break: break-all;
}
.remark-label {
display: block;
font-size: 28rpx;
color: #5D6B7D;
margin-bottom: 16rpx;
}
.remark-input {
width: 100%;
min-height: 180rpx;
padding: 20rpx;
border: 1rpx solid #E6ECF3;
border-radius: 16rpx;
font-size: 28rpx;
color: #2F3A4A;
box-sizing: border-box;
background: #F8FAFD;
}
.empty-wrap {
padding: 120rpx 40rpx;
text-align: center;
}
.empty-text {
display: block;
font-size: 28rpx;
color: #8B98AA;
margin-bottom: 28rpx;
}
.empty-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 72rpx;
padding: 0 36rpx;
border-radius: 999rpx;
background: $primary;
color: #fff;
font-size: 28rpx;
}
.bottom-actions {
flex-shrink: 0;
display: flex;
gap: 24rpx;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -8rpx 28rpx rgba(46, 73, 117, 0.08);
box-sizing: border-box;
}
.btn {
flex: 1;
height: 92rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 700;
}
.reject-btn {
background: #FFF2F0;
color: #F04438;
}
.pass-btn {
background: linear-gradient(135deg, $primary, $primary-deep);
color: #fff;
box-shadow: 0 14rpx 28rpx rgba(76, 151, 231, 0.24);
}
.disabled {
opacity: 0.6;
pointer-events: none;
}
</style>