352 lines
13 KiB
Vue
352 lines
13 KiB
Vue
<template>
|
||
<el-card shadow="never" class="balance-log">
|
||
<div slot="header" class="flex justify-between items-center">
|
||
<span>余额记录</span>
|
||
<div class="flex justify-end items-center">
|
||
<div class="query-date mr-20">
|
||
<span class="query-date-label">查询日期:</span>
|
||
<el-date-picker
|
||
v-model="queryDate"
|
||
type="date"
|
||
size="small"
|
||
value-format="yyyyMMdd"
|
||
format="yyyy-MM-dd"
|
||
:clearable="false"
|
||
:editable="false"
|
||
placeholder="选择日期"
|
||
@change="onQueryDateChange">
|
||
</el-date-picker>
|
||
</div>
|
||
<div class="balance-text mr-20">当前余额:<span class="amount">{{ balanceLoading ? '加载中...' : formatBalance(balance) }}</span></div>
|
||
<el-button type="primary" size="small" :loading="withdrawLoading" @click="openWithdrawDialog">提现</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table
|
||
v-loading="loading"
|
||
:data="list"
|
||
stripe
|
||
style="width: 100%">
|
||
<el-table-column prop="reqJournalNo" label="订单号" min-width="220">
|
||
<template slot-scope="{ row }">
|
||
{{ row.reqJournalNo || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="订单金额" min-width="140">
|
||
<template slot-scope="{ row }">
|
||
<span class="price">¥{{ formatFen(row.orderAmt) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="订单状态" min-width="140">
|
||
<template slot-scope="{ row }">
|
||
{{ orderStatusText(row.orderStatus) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="errMsg" label="失败原因" min-width="220">
|
||
<template slot-scope="{ row }">
|
||
{{ row.errMsg || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="付款方" min-width="180">
|
||
<template slot-scope="{ row }">
|
||
{{ row.payerName || row.payerAcctNo || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="mt-20 text-center" v-if="totalPages > 1">
|
||
<el-pagination
|
||
background
|
||
layout="prev, pager, next"
|
||
:current-page="pageIndex"
|
||
:page-size="pageSize"
|
||
:total="totalPages"
|
||
@current-change="getList">
|
||
</el-pagination>
|
||
</div>
|
||
|
||
<el-dialog
|
||
title="余额提现"
|
||
:visible.sync="dialogVisible"
|
||
width="420px"
|
||
append-to-body>
|
||
<el-form label-width="90px">
|
||
<el-form-item label="企业用户号">
|
||
<span>{{ merNo || '无企业用户号' }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="提现金额">
|
||
<el-input v-model.trim="withdrawForm.amount" placeholder="请输入提现金额">
|
||
<template slot="append">元</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="withdrawLoading" @click="submitWithdraw">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</el-card>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: "balance-log",
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
list: [],
|
||
pageIndex: 1,
|
||
pageSize: 10,
|
||
// 查询日期默认当天且不允许清空。
|
||
queryDate: '',
|
||
// 后端返回的是记录总数,供 el-pagination 使用。
|
||
totalPages: 0,
|
||
balance: null,
|
||
balanceLoading: false,
|
||
dialogVisible: false,
|
||
withdrawLoading: false,
|
||
withdrawForm: {
|
||
amount: ''
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
// 企业用户号用于查询实时余额及银联流水状态。
|
||
merNo() {
|
||
return vk.getVuex('$user.userInfo.merNo')
|
||
}
|
||
},
|
||
mounted() {
|
||
this.queryDate = this.getToday()
|
||
this.requestMerchantBalance()
|
||
this.getList()
|
||
},
|
||
methods: {
|
||
/**
|
||
* 查询当前商户实时余额。
|
||
* 客户端云函数内部调用 balanceUtil.requestMerchantBalance,并将金额统一转换为元。
|
||
*/
|
||
async requestMerchantBalance() {
|
||
if (!this.merNo) {
|
||
this.balance = null
|
||
return
|
||
}
|
||
|
||
this.balanceLoading = true
|
||
try {
|
||
const res = await vk.callFunction({
|
||
url: 'client/userBalanceLog/kh/getMerchantBalance',
|
||
data: {
|
||
accNo: this.merNo
|
||
}
|
||
})
|
||
const balance = this.pickFirstDefined([
|
||
this.pickResponseValue(res, 'balance'),
|
||
this.pickResponseValue(res, 'merchantBalance'),
|
||
this.pickResponseValue(res, 'canPayAmt')
|
||
])
|
||
if (balance === undefined) {
|
||
throw new Error((res && (res.msg || res.message)) || '未获取到余额')
|
||
}
|
||
// getInfo 已将 canPayAmt 从分转换为元;仅在直接返回 canPayAmt 时做分转元。
|
||
const canPayAmt = this.pickResponseValue(res, 'canPayAmt')
|
||
this.balance = canPayAmt !== undefined ? Number(canPayAmt || 0) / 100 : Number(balance || 0)
|
||
} catch (err) {
|
||
this.balance = null
|
||
this.$message.error((err && err.message) || '余额获取失败')
|
||
} finally {
|
||
this.balanceLoading = false
|
||
}
|
||
},
|
||
async getList(pageIndex = 1) {
|
||
this.loading = true
|
||
try {
|
||
// 由服务端从当前登录用户身份取得 sellerUid,客户端只传分页和日期。
|
||
const queryDate = this.queryDate || this.getToday()
|
||
const res = await vk.callFunction({
|
||
url: 'client/userBalanceLog/kh/getList',
|
||
data: {
|
||
queryDate,
|
||
pageIndex,
|
||
pageSize: this.pageSize
|
||
}
|
||
})
|
||
|
||
const code = Number(res && res.code)
|
||
if (code !== 0 && code !== 200 && res && (res.msg || res.message)) {
|
||
throw new Error(res.msg || res.message)
|
||
}
|
||
|
||
const rows = this.pickResponseValue(res, 'rows')
|
||
this.list = Array.isArray(rows) ? rows : []
|
||
this.pageIndex = Number(this.pickResponseValue(res, 'pageIndex') || pageIndex) || pageIndex
|
||
this.totalPages = Number(this.pickResponseValue(res, 'total') || 0)
|
||
} catch (err) {
|
||
this.list = []
|
||
this.totalPages = 0
|
||
this.$message.error((err && err.message) || '余额记录获取失败')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
onQueryDateChange() {
|
||
// 日期切换后从第一页开始查询,日期选择器不可清空;异常情况下恢复当天。
|
||
if (!this.queryDate) {
|
||
this.queryDate = this.getToday()
|
||
}
|
||
this.getList(1)
|
||
},
|
||
pickFirstDefined(values) {
|
||
return values.find(value => value !== undefined && value !== null && value !== '')
|
||
},
|
||
pickResponseValue(res, key) {
|
||
if (!res || typeof res !== 'object') return undefined
|
||
return this.pickFirstDefined([
|
||
res[key],
|
||
res.data && typeof res.data === 'object' && !Array.isArray(res.data) ? res.data[key] : undefined,
|
||
res.info && typeof res.info === 'object' && !Array.isArray(res.info) ? res.info[key] : undefined,
|
||
res.content && typeof res.content === 'object' && !Array.isArray(res.content) ? res.content[key] : undefined
|
||
])
|
||
},
|
||
getToday() {
|
||
const date = new Date()
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
return `${year}${month}${day}`
|
||
},
|
||
formatFen(value) {
|
||
const num = Number(value)
|
||
if (!Number.isFinite(num)) return '0.00'
|
||
return (num / 100).toFixed(2)
|
||
},
|
||
orderStatusText(status) {
|
||
const statusMap = {
|
||
0: '处理中',
|
||
1: '处理成功',
|
||
2: '处理失败',
|
||
3: '退票',
|
||
4: '对账'
|
||
}
|
||
return statusMap[Number(status)] || status || '-'
|
||
},
|
||
formatPrice(price) {
|
||
const num = Number(price)
|
||
if (Number.isNaN(num)) return price || '0.00'
|
||
return num.toFixed(2)
|
||
},
|
||
formatBalance(balance) {
|
||
return balance === null || balance === undefined ? '--' : `¥${this.formatPrice(balance)}`
|
||
},
|
||
openWithdrawDialog() {
|
||
if (!this.merNo) {
|
||
this.$message.error('无商户号,无法提现')
|
||
return
|
||
}
|
||
this.withdrawForm.amount = ''
|
||
this.dialogVisible = true
|
||
},
|
||
async submitWithdraw() {
|
||
if (!this.merNo) {
|
||
this.$message.error('无商户号,无法提现')
|
||
return
|
||
}
|
||
if (!this.withdrawForm.amount) {
|
||
this.$message.error('请输入提现金额')
|
||
return
|
||
}
|
||
if (!/^\d+(\.\d{1,2})?$/.test(this.withdrawForm.amount)) {
|
||
this.$message.error('提现金额最多保留两位小数')
|
||
return
|
||
}
|
||
const amount = Number(this.withdrawForm.amount)
|
||
if (Number.isNaN(amount) || amount <= 0) {
|
||
this.$message.error('提现金额必须大于0')
|
||
return
|
||
}
|
||
if (this.balance === null || amount > Number(this.balance || 0)) {
|
||
this.$message.error('提现金额不能大于当前余额')
|
||
return
|
||
}
|
||
|
||
this.withdrawLoading = true
|
||
try {
|
||
const res = await vk.request({
|
||
method: "POST",
|
||
url: 'http://jjx-union-pay.gxwebsoft.com/api/union-pay-jjx/transByAmount',
|
||
header: {
|
||
"content-type": "application/json; charset=utf-8",
|
||
},
|
||
data: {
|
||
accNo: this.merNo,
|
||
amount
|
||
}
|
||
})
|
||
|
||
const code = Number(res && res.code)
|
||
const success = res && (
|
||
code === 0 ||
|
||
code === 200 ||
|
||
res.success === true ||
|
||
(typeof res.code === 'undefined' && !res.msg && !res.message)
|
||
)
|
||
|
||
if (!success) {
|
||
throw new Error(
|
||
(res && (res.msg || res.message)) ||
|
||
(res && res.content && (res.content.msg || res.content.message)) ||
|
||
'提现失败'
|
||
)
|
||
}
|
||
|
||
this.$message.success('提现申请已提交')
|
||
this.dialogVisible = false
|
||
this.withdrawForm.amount = ''
|
||
await this.requestMerchantBalance()
|
||
await this.getList(this.pageIndex)
|
||
} catch (err) {
|
||
this.$message.error((err && err.message) || '提现失败')
|
||
} finally {
|
||
this.withdrawLoading = false
|
||
}
|
||
},
|
||
refreshUserInfo() {
|
||
return new Promise((resolve) => {
|
||
vk.userCenter.getCurrentUserInfo({
|
||
success: () => {
|
||
resolve()
|
||
},
|
||
fail: () => {
|
||
resolve()
|
||
}
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.balance-log {
|
||
.query-date {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.query-date-label {
|
||
color: #666;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.balance-text {
|
||
color: #666;
|
||
}
|
||
|
||
.amount,
|
||
.price {
|
||
color: #bf2b28;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
</style>
|