Files
jjx-web/pages/user/components/balance-log.vue
T
2026-10-08 18:18:21 +08:00

352 lines
13 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>
<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>