2025-10-20 10:15:13 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="funds-page">
|
|
|
|
|
|
<h3 class="title" style="margin-bottom: 18px; text-align: left;">资金流水</h3>
|
|
|
|
|
|
<div class="tabs-card">
|
|
|
|
|
|
<el-tabs v-model="active" @tab-click="handleTab">
|
|
|
|
|
|
<el-tab-pane label="充值记录" name="recharge">
|
|
|
|
|
|
<div class="list-wrap">
|
|
|
|
|
|
<div class="list-header">
|
|
|
|
|
|
<span class="list-title">全部充值 ({{ rechargeRows.length }})</span>
|
|
|
|
|
|
<el-button type="primary" size="small" @click="loadRecharge">刷新</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="record-list" v-loading="loading.recharge">
|
|
|
|
|
|
<div v-for="(row, idx) in rechargeRows" :key="getRowKey(row, idx)" class="record-item" :class="statusClass(row.status)" @click="toggleExpand('recharge', row, idx)">
|
|
|
|
|
|
<div class="item-main">
|
|
|
|
|
|
<div class="item-left">
|
2025-11-14 16:17:36 +08:00
|
|
|
|
<div class="amount">
|
|
|
|
|
|
<el-tooltip
|
|
|
|
|
|
v-if="formatAmount(row.amount, row.fromSymbol).truncated"
|
|
|
|
|
|
:content="`${formatAmount(row.amount, row.fromSymbol).full} ${(row.fromSymbol || 'USDT').toUpperCase()}`"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
+ {{ formatAmount(row.amount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
|
|
|
|
|
<i class="el-icon-more amount-more"></i>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
<span v-else>
|
|
|
|
|
|
+ {{ formatAmount(row.amount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<div class="chain">{{ formatChain(row.fromChain) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="item-right">
|
|
|
|
|
|
<div class="status"><el-tag :type="getRechargeStatusType(row.status)" size="small">{{ getRechargeStatusText(row.status) }}</el-tag></div>
|
|
|
|
|
|
<div class="time">{{ formatFullTime(row.createTime) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-show="isExpanded('recharge', row, idx)" class="expand-panel">
|
|
|
|
|
|
<div class="expand-grid">
|
2025-10-31 14:09:58 +08:00
|
|
|
|
<div class="expand-item">
|
|
|
|
|
|
<span class="label">充值地址</span>
|
|
|
|
|
|
<div class="value value-row">
|
|
|
|
|
|
<span class="mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress }}</span>
|
|
|
|
|
|
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.fromAddress, '充值地址')">复制</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="expand-item" v-if="row.txHash">
|
|
|
|
|
|
<span class="label">交易哈希</span>
|
|
|
|
|
|
<div class="value value-row">
|
|
|
|
|
|
<span class="mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span>
|
|
|
|
|
|
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.txHash, '交易哈希')">复制</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="!rechargeRows.length" class="empty">暂无充值记录</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
<el-tab-pane label="提现记录" name="withdraw">
|
|
|
|
|
|
<div class="list-wrap">
|
|
|
|
|
|
<div class="list-header">
|
|
|
|
|
|
<span class="list-title">全部提现 ({{ withdrawRows.length }})</span>
|
|
|
|
|
|
<el-button type="primary" size="small" @click="loadWithdraw">刷新</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="record-list" v-loading="loading.withdraw">
|
|
|
|
|
|
<div v-for="(row, idx) in withdrawRows" :key="getRowKey(row, idx)" class="record-item" :class="statusClass(row.status)" @click="toggleExpand('withdraw', row, idx)">
|
|
|
|
|
|
<div class="item-main">
|
|
|
|
|
|
<div class="item-left">
|
2025-11-14 16:17:36 +08:00
|
|
|
|
<div class="amount">
|
|
|
|
|
|
<el-tooltip
|
|
|
|
|
|
v-if="formatAmount(row.amount, row.toSymbol).truncated"
|
|
|
|
|
|
:content="`${formatAmount(row.amount, row.toSymbol).full} ${(row.toSymbol || 'USDT').toUpperCase()}`"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
- {{ formatAmount(row.amount, row.toSymbol).text }} {{ (row.toSymbol || 'USDT').toUpperCase() }}
|
|
|
|
|
|
<i class="el-icon-more amount-more"></i>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
<span v-else>
|
|
|
|
|
|
- {{ formatAmount(row.amount, row.toSymbol).text }} {{ (row.toSymbol || 'USDT').toUpperCase() }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<div class="chain">{{ formatChain(row.toChain) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="item-right">
|
|
|
|
|
|
<div class="status"><el-tag :type="getWithdrawStatusType(row.status)" size="small">{{ getWithdrawStatusText(row.status) }}</el-tag></div>
|
|
|
|
|
|
<div class="time">{{ formatFullTime(row.createTime) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-show="isExpanded('withdraw', row, idx)" class="expand-panel">
|
|
|
|
|
|
<div class="expand-grid">
|
2025-10-31 14:09:58 +08:00
|
|
|
|
<div class="expand-item">
|
|
|
|
|
|
<span class="label">收款地址</span>
|
|
|
|
|
|
<div class="value value-row">
|
|
|
|
|
|
<span class="mono-ellipsis" :title="row.toAddress">{{ row.toAddress }}</span>
|
|
|
|
|
|
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.toAddress, '收款地址')">复制</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="expand-item" v-if="row.txHash">
|
|
|
|
|
|
<span class="label">交易哈希</span>
|
|
|
|
|
|
<div class="value value-row">
|
|
|
|
|
|
<span class="mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span>
|
|
|
|
|
|
<el-button type="text" size="mini" icon="el-icon-document-copy" @click.stop="handleCopy(row.txHash, '交易哈希')">复制</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="!withdrawRows.length" class="empty">暂无提现记录</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
<el-tab-pane label="消费记录" name="consume">
|
|
|
|
|
|
<div class="list-wrap">
|
|
|
|
|
|
<div class="list-header">
|
|
|
|
|
|
<span class="list-title">全部消费 ({{ consumeRows.length }})</span>
|
|
|
|
|
|
<el-button type="primary" size="small" @click="loadConsume">刷新</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="record-list" v-loading="loading.consume">
|
|
|
|
|
|
<div v-for="(row, idx) in consumeRows" :key="getRowKey(row, idx)" class="record-item" :class="statusClass(row.status)" @click="toggleExpand('consume', row, idx)">
|
|
|
|
|
|
<div class="item-main">
|
|
|
|
|
|
<div class="item-left">
|
2025-11-14 16:17:36 +08:00
|
|
|
|
<div class="amount">
|
|
|
|
|
|
<el-tooltip
|
|
|
|
|
|
v-if="formatAmount(row.realAmount, row.fromSymbol).truncated"
|
|
|
|
|
|
:content="`${formatAmount(row.realAmount, row.fromSymbol).full} ${(row.fromSymbol || 'USDT').toUpperCase()}`"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
- {{ formatAmount(row.realAmount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
|
|
|
|
|
<i class="el-icon-more amount-more"></i>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
<span v-else>
|
|
|
|
|
|
- {{ formatAmount(row.realAmount, row.fromSymbol).text }} {{ (row.fromSymbol || 'USDT').toUpperCase() }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2025-10-20 10:15:13 +08:00
|
|
|
|
<div class="chain">{{ formatChain(row.fromChain) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="item-right">
|
|
|
|
|
|
<div class="status"><el-tag :type="getPayStatusType(row.status)" size="small">{{ getPayStatusText(row.status) }}</el-tag></div>
|
|
|
|
|
|
<div class="time">{{ formatFullTime(row.createTime || row.time) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-show="isExpanded('consume', row, idx)" class="expand-panel">
|
|
|
|
|
|
<div class="expand-grid">
|
|
|
|
|
|
<div class="expand-item"><span class="label">订单号</span><span class="value mono">{{ row.orderId || '' }}</span></div>
|
|
|
|
|
|
<div class="expand-item"><span class="label">支付地址</span><span class="value mono-ellipsis" :title="row.fromAddress">{{ row.fromAddress || '' }}</span></div>
|
|
|
|
|
|
<div class="expand-item"><span class="label">收款地址</span><span class="value mono-ellipsis" :title="row.toAddress">{{ row.toAddress || '' }}</span></div>
|
|
|
|
|
|
<div class="expand-item" v-if="row.txHash"><span class="label">交易哈希</span><span class="value mono-ellipsis" :title="row.txHash">{{ row.txHash }}</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="!consumeRows.length" class="empty">暂无消费记录</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-row>
|
|
|
|
|
|
<el-col :span="24" style="display: flex; justify-content: center">
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
style="margin: 0 auto; margin-top: 10px"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
:current-page.sync="currentPage"
|
|
|
|
|
|
:page-sizes="pageSizes"
|
|
|
|
|
|
:page-size="pagination.pageSize"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
>
|
|
|
|
|
|
</el-pagination>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { transactionRecord } from '../../api/wallet'
|
2025-11-14 16:17:36 +08:00
|
|
|
|
import { truncateAmountByCoin } from '../../utils/amount'
|
2025-10-20 10:15:13 +08:00
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'AccountFundsFlow',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
active: 'recharge',
|
|
|
|
|
|
loading: { recharge: false, withdraw: false, consume: false },
|
|
|
|
|
|
rechargeRows: [
|
|
|
|
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
// createTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// amount: 100,
|
|
|
|
|
|
// fromAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// fromChain: 'tron',
|
|
|
|
|
|
// fromSymbol: 'USDT',
|
|
|
|
|
|
// status: 2,
|
|
|
|
|
|
// id: 1,
|
|
|
|
|
|
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
|
|
|
|
// updateTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// createTime: '2024-01-15 14:30:25',
|
2025-10-31 14:09:58 +08:00
|
|
|
|
// amount: 100.656578965,
|
2025-10-20 10:15:13 +08:00
|
|
|
|
// fromAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// fromChain: 'tron',
|
|
|
|
|
|
// fromSymbol: 'USDT',
|
|
|
|
|
|
// status: 2,
|
|
|
|
|
|
// id: 1,
|
|
|
|
|
|
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
|
|
|
|
// updateTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// },
|
|
|
|
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
withdrawRows: [
|
|
|
|
|
|
// {
|
|
|
|
|
|
// createTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// amount: 100,
|
|
|
|
|
|
// toAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// toChain: 'tron',
|
|
|
|
|
|
// toSymbol: 'USDT',
|
|
|
|
|
|
// status: 2,
|
|
|
|
|
|
// id: 1,
|
|
|
|
|
|
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
|
|
|
|
// updateTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
|
|
|
|
// createTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// amount: 100,
|
|
|
|
|
|
// toAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// toChain: 'tron',
|
|
|
|
|
|
// toSymbol: 'USDT',
|
|
|
|
|
|
// status: 2,
|
|
|
|
|
|
// id: 1,
|
|
|
|
|
|
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
|
|
|
|
// updateTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// },
|
|
|
|
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
consumeRows: [
|
|
|
|
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
// orderId: '1234567890',
|
|
|
|
|
|
// fromAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// fromChain: 'tron',
|
|
|
|
|
|
// fromSymbol: 'USDT',
|
|
|
|
|
|
// createTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// amount: 100,
|
|
|
|
|
|
// toAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// toChain: 'tron',
|
|
|
|
|
|
// toSymbol: 'USDT',
|
|
|
|
|
|
// status: 2,
|
|
|
|
|
|
// id: 1,
|
|
|
|
|
|
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
|
|
|
|
// updateTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// orderId: '1234567890',
|
|
|
|
|
|
// fromAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// fromChain: 'tron',
|
|
|
|
|
|
// fromSymbol: 'USDT',
|
|
|
|
|
|
// createTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// realAmount: 100, //后端告知只有消费记录金额用这个字段
|
|
|
|
|
|
// toAddress: 'djdddksfhsfj',
|
|
|
|
|
|
// toChain: 'tron',
|
|
|
|
|
|
// toSymbol: 'USDT',
|
|
|
|
|
|
// status: 2,
|
|
|
|
|
|
// id: 1,
|
|
|
|
|
|
// txHash: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
|
|
|
|
// updateTime: '2024-01-15 14:30:25',
|
|
|
|
|
|
// },
|
|
|
|
|
|
],
|
|
|
|
|
|
// 手风琴展开集合
|
|
|
|
|
|
expandedKeys: new Set(),
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
pageSizes: [10, 20, 50],
|
|
|
|
|
|
currentPage: 1,
|
|
|
|
|
|
// 分页和筛选参数
|
|
|
|
|
|
pagination: {
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
|
status: 2,
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
const tab = (this.$route && this.$route.query && this.$route.query.tab) || 'recharge'
|
|
|
|
|
|
if (['recharge', 'withdraw', 'consume'].includes(tab)) this.active = tab
|
|
|
|
|
|
// 初始化按当前 Tab 的状态加载列表
|
|
|
|
|
|
this.pagination.status = this.getStatusByTab(this.active)
|
|
|
|
|
|
this.loadList()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2025-11-14 16:17:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 金额格式化(不补0、不四舍五入)
|
|
|
|
|
|
*/
|
|
|
|
|
|
formatAmount(value, coin) {
|
|
|
|
|
|
return truncateAmountByCoin(value, coin)
|
|
|
|
|
|
},
|
2025-10-20 10:15:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 处理 Tab 切换:清空展开状态,确保手风琴行为
|
|
|
|
|
|
* @param {any} pane - 当前 pane(Element UI 传入)
|
|
|
|
|
|
* @param {Event} evt - 事件对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleTab(pane, evt) {
|
|
|
|
|
|
this.expandedKeys.clear()
|
|
|
|
|
|
// 触发视图刷新
|
|
|
|
|
|
this.expandedKeys = new Set(this.expandedKeys)
|
|
|
|
|
|
// 按 Tab 切换刷新对应数据
|
|
|
|
|
|
const tabName = (pane && pane.name) || this.active
|
|
|
|
|
|
this.pagination.status = this.getStatusByTab(tabName)
|
|
|
|
|
|
this.pagination.pageNum = 1
|
|
|
|
|
|
this.currentPage = 1
|
|
|
|
|
|
this.loadList()
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成唯一键,优先使用后端提供的稳定字段,其次回退到索引
|
|
|
|
|
|
* @param {Record<string, any>} row - 当前行数据
|
|
|
|
|
|
* @param {number} [idx] - v-for 提供的索引
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
getRowKey(row, idx) {
|
|
|
|
|
|
const indexPart = idx != null ? `#${idx}` : ''
|
|
|
|
|
|
if (!row) return String(idx != null ? idx : '')
|
|
|
|
|
|
const stable = row.__key || row.id || row.txHash || row.orderId || `${row.createTime || ''}-${row.updateTime || ''}`
|
|
|
|
|
|
// 始终附加索引,避免重复 id/txHash 导致多条记录共用同一键
|
|
|
|
|
|
if (stable == null || stable === '') return String(idx != null ? idx : '')
|
|
|
|
|
|
return `${String(stable)}${indexPart}`
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断当前行是否展开(手风琴:全局仅允许一个展开)
|
|
|
|
|
|
* @param {string} type - 列表类型:recharge/withdraw/consume
|
|
|
|
|
|
* @param {Record<string, any>} row - 当前行数据
|
|
|
|
|
|
* @param {number} [idx] - 行索引
|
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
|
*/
|
|
|
|
|
|
isExpanded(type, row, idx) {
|
|
|
|
|
|
const key = `${type}-${this.getRowKey(row, idx)}`
|
|
|
|
|
|
return this.expandedKeys.has(key)
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换展开(手风琴):若点击同一行则收起,否则清空后仅展开当前行
|
|
|
|
|
|
* @param {string} type - 列表类型
|
|
|
|
|
|
* @param {Record<string, any>} row - 当前行数据
|
|
|
|
|
|
* @param {number} [idx] - 行索引
|
|
|
|
|
|
*/
|
|
|
|
|
|
toggleExpand(type, row, idx) {
|
|
|
|
|
|
const key = `${type}-${this.getRowKey(row, idx)}`
|
|
|
|
|
|
if (this.expandedKeys.has(key)) {
|
|
|
|
|
|
this.expandedKeys.clear()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.expandedKeys.clear()
|
|
|
|
|
|
this.expandedKeys.add(key)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 触发视图刷新
|
|
|
|
|
|
this.expandedKeys = new Set(this.expandedKeys)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 统一加载:根据 pagination.status 请求并填充对应列表
|
|
|
|
|
|
async loadList() {
|
|
|
|
|
|
const status = Number(this.pagination.status)
|
|
|
|
|
|
const typeKey = this.getTypeKeyByStatus(status)
|
|
|
|
|
|
if (!typeKey) return
|
|
|
|
|
|
this.loading[typeKey] = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await transactionRecord({
|
|
|
|
|
|
pageNum: this.pagination.pageNum,
|
|
|
|
|
|
pageSize: this.pagination.pageSize,
|
|
|
|
|
|
status
|
|
|
|
|
|
})
|
|
|
|
|
|
const rows = res?.rows || res?.data?.rows || []
|
|
|
|
|
|
this.total = res?.total || res?.data?.total || (Array.isArray(rows) ? rows.length : 0)
|
|
|
|
|
|
const mapped = (Array.isArray(rows) ? rows : []).map((r, i) => ({
|
|
|
|
|
|
...r,
|
|
|
|
|
|
__key: r.id || r.txHash || r.orderId || `${i}`
|
|
|
|
|
|
}))
|
|
|
|
|
|
if (status === 2) this.rechargeRows = mapped
|
|
|
|
|
|
else if (status === 1) this.withdrawRows = mapped
|
|
|
|
|
|
else this.consumeRows = mapped
|
|
|
|
|
|
// 刷新时收起展开
|
|
|
|
|
|
this.expandedKeys.clear()
|
|
|
|
|
|
this.expandedKeys = new Set(this.expandedKeys)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading[typeKey] = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 包装:根据传入状态加载,并同步 Tab/分页重置
|
|
|
|
|
|
* @param {0|1|2} status - 0 支付,1 提现,2 充值
|
|
|
|
|
|
*/
|
|
|
|
|
|
loadByStatus(status) {
|
|
|
|
|
|
this.pagination.status = status
|
|
|
|
|
|
this.active = this.getTabByStatus(status)
|
|
|
|
|
|
this.pagination.pageNum = 1
|
|
|
|
|
|
this.currentPage = 1
|
|
|
|
|
|
return this.loadList()
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容现有按钮点击
|
|
|
|
|
|
loadRecharge() { return this.loadByStatus(2) },
|
|
|
|
|
|
loadWithdraw() { return this.loadByStatus(1) },
|
|
|
|
|
|
loadConsume() { return this.loadByStatus(0) },
|
|
|
|
|
|
|
|
|
|
|
|
statusClass(status) { return { 0: 'failed', 1: 'success', 2: 'pending' }[status] || 'neutral' },
|
|
|
|
|
|
getRechargeStatusType(s) { return ({ 0: 'danger', 1: 'success', 2: 'warning' })[s] || 'info' },
|
|
|
|
|
|
getRechargeStatusText(s) { return ({ 0: '充值失败', 1: '充值成功', 2: '充值中', 3: '证书校验失败' })[s] || '未知' },
|
|
|
|
|
|
getWithdrawStatusType(s) { return ({ 0: 'danger', 1: 'success', 2: 'warning' })[s] || 'info' },
|
|
|
|
|
|
getWithdrawStatusText(s) { return ({ 0: '提现失败', 1: '提现成功', 2: '提现中', 3: '证书校验失败' })[s] || '未知' },
|
|
|
|
|
|
getPayStatusType(s) { return ({ 0: 'danger', 1: 'success', 2: 'warning', 3: 'danger' })[s] || 'info' },
|
|
|
|
|
|
getPayStatusText(s) { return ({ 0: '支付失败', 1: '支付成功', 2: '待校验', 3: '证书校验失败' })[s] || '未知' },
|
|
|
|
|
|
|
2025-10-31 14:09:58 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将链名称标准化为大写简称
|
|
|
|
|
|
* @param {string} chain - 后端返回的链标识,如 tron/eth/ethereum/bsc/polygon
|
|
|
|
|
|
* @returns {string} 大写显示,如 TRON/ETH/BSC/POLYGON
|
|
|
|
|
|
*/
|
2025-10-20 10:15:13 +08:00
|
|
|
|
formatChain(chain) {
|
2025-10-31 14:09:58 +08:00
|
|
|
|
if (!chain) return ''
|
|
|
|
|
|
const key = String(chain).toLowerCase()
|
|
|
|
|
|
const map = { tron: 'TRON', trx: 'TRON', eth: 'ETH', ethereum: 'ETH', bsc: 'BSC', polygon: 'POLYGON', matic: 'POLYGON' }
|
|
|
|
|
|
return (map[key] || String(chain)).toUpperCase()
|
2025-10-20 10:15:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
formatFullTime(time) { if (!time) return ''; try { return new Date(time).toLocaleString('zh-CN') } catch (e) { return String(time) } },
|
|
|
|
|
|
formatTime(time) { return this.formatFullTime(time) },
|
|
|
|
|
|
formatTrunc(value, decimals = 2) {
|
|
|
|
|
|
const num = Number(value)
|
|
|
|
|
|
if (!Number.isFinite(num)) return '0'
|
|
|
|
|
|
const d = Math.max(0, Number(decimals) || 0)
|
|
|
|
|
|
const factor = Math.pow(10, d)
|
|
|
|
|
|
const truncated = Math.trunc(num * factor) / factor
|
|
|
|
|
|
const str = String(truncated)
|
|
|
|
|
|
if (d === 0) return str
|
|
|
|
|
|
const [intPart, decPart = ''] = str.split('.')
|
|
|
|
|
|
const padded = decPart.padEnd(d, '0')
|
|
|
|
|
|
return `${intPart}.${padded}`
|
|
|
|
|
|
},
|
2025-10-31 14:09:58 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 金额显示:保留最多6位小数,直接截断不四舍五入;不补尾随0;始终返回非负字符串
|
|
|
|
|
|
* @param {number|string} value
|
|
|
|
|
|
* @returns {string}
|
|
|
|
|
|
*/
|
2025-11-14 16:17:36 +08:00
|
|
|
|
// 删除旧的 formatDec6,统一使用 formatAmount
|
2025-10-20 10:15:13 +08:00
|
|
|
|
handleSizeChange(val) {
|
|
|
|
|
|
console.log(`每页 ${val} 条`);
|
|
|
|
|
|
this.pagination.pageSize = val;
|
|
|
|
|
|
this.pagination.pageNum = 1;
|
|
|
|
|
|
this.currentPage = 1;
|
|
|
|
|
|
this.loadList();
|
|
|
|
|
|
},
|
|
|
|
|
|
handleCurrentChange(val) {
|
|
|
|
|
|
console.log(`当前页: ${val}`);
|
|
|
|
|
|
this.pagination.pageNum = val;
|
|
|
|
|
|
this.loadList();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-10-31 14:09:58 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 复制文本到剪贴板
|
|
|
|
|
|
* @param {string} text - 需要复制的内容
|
|
|
|
|
|
* @param {string} [label] - 语义标签,用于提示文案
|
|
|
|
|
|
*/
|
|
|
|
|
|
async handleCopy(text, label = '内容') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const value = String(text || '')
|
|
|
|
|
|
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
|
|
|
|
await navigator.clipboard.writeText(value)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const ta = document.createElement('textarea')
|
|
|
|
|
|
ta.value = value
|
|
|
|
|
|
ta.style.position = 'fixed'
|
|
|
|
|
|
ta.style.left = '-9999px'
|
|
|
|
|
|
document.body.appendChild(ta)
|
|
|
|
|
|
ta.focus()
|
|
|
|
|
|
ta.select()
|
|
|
|
|
|
document.execCommand('copy')
|
|
|
|
|
|
document.body.removeChild(ta)
|
|
|
|
|
|
}
|
|
|
|
|
|
this.$message.success(`${label}已复制`)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
this.$message.error('复制失败,请手动选择复制')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-10-20 10:15:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* Tab 名称转状态码
|
|
|
|
|
|
* @param {string} tabName - 'recharge' | 'withdraw' | 'consume'
|
|
|
|
|
|
* @returns {0|1|2}
|
|
|
|
|
|
*/
|
|
|
|
|
|
getStatusByTab(tabName) {
|
|
|
|
|
|
if (tabName === 'recharge') return 2
|
|
|
|
|
|
if (tabName === 'withdraw') return 1
|
|
|
|
|
|
return 0
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 状态码转 Tab 名称
|
|
|
|
|
|
* @param {number} status - 0|1|2
|
|
|
|
|
|
* @returns {'recharge'|'withdraw'|'consume'}
|
|
|
|
|
|
*/
|
|
|
|
|
|
getTabByStatus(status) {
|
|
|
|
|
|
if (Number(status) === 2) return 'recharge'
|
|
|
|
|
|
if (Number(status) === 1) return 'withdraw'
|
|
|
|
|
|
return 'consume'
|
|
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 状态码转 loading/列表 key
|
|
|
|
|
|
* @param {number} status - 0|1|2
|
|
|
|
|
|
* @returns {'recharge'|'withdraw'|'consume'|''}
|
|
|
|
|
|
*/
|
|
|
|
|
|
getTypeKeyByStatus(status) {
|
|
|
|
|
|
if (Number(status) === 2) return 'recharge'
|
|
|
|
|
|
if (Number(status) === 1) return 'withdraw'
|
|
|
|
|
|
if (Number(status) === 0) return 'consume'
|
|
|
|
|
|
return ''
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.funds-page { padding: 8px; }
|
|
|
|
|
|
.tabs-card { background: #fff; border: 1px solid #eee; border-radius: 10px; padding: 12px; }
|
|
|
|
|
|
.list-wrap { padding: 6px 0; }
|
|
|
|
|
|
.list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
|
|
|
|
|
|
.list-title { font-size: 14px; font-weight: 600; color: #333; }
|
|
|
|
|
|
.record-list { display: flex; flex-direction: column; gap: 10px; max-height: 62vh; overflow-y: auto; }
|
|
|
|
|
|
.record-item { background: #f8f9fa; border-radius: 8px; padding: 12px; border: 1px solid transparent; transition: all .15s ease; }
|
|
|
|
|
|
.record-item:hover { background: #eef2f7; border-color: #409eff; box-shadow: 0 4px 12px rgba(64,158,255,.12); transform: translateY(-1px); }
|
|
|
|
|
|
.record-item.pending { border-left: 4px solid #e6a23c; }
|
|
|
|
|
|
.record-item.success { border-left: 4px solid #67c23a; }
|
|
|
|
|
|
.record-item.failed { border-left: 4px solid #f56c6c; }
|
|
|
|
|
|
.item-main { display: flex; justify-content: space-between; align-items: center; }
|
|
|
|
|
|
.item-left .amount { font-size: 16px; font-weight: 700; color: #111; margin-bottom: 2px; }
|
|
|
|
|
|
.item-left .chain { font-size: 12px; color: #666; }
|
|
|
|
|
|
.item-right { text-align: right; }
|
|
|
|
|
|
.status { margin-bottom: 2px; }
|
|
|
|
|
|
.time { font-size: 12px; color: #999; }
|
|
|
|
|
|
.expand-panel { background: #fff; border: 1px dashed #e5e7eb; border-radius: 8px; padding: 10px; margin-top: 8px; }
|
|
|
|
|
|
.expand-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 24px; }
|
|
|
|
|
|
.expand-item { display: grid; grid-template-columns: 80px 1fr; gap: 6px; align-items: center; }
|
|
|
|
|
|
.label { color: #666; font-size: 13px; text-align: right; }
|
|
|
|
|
|
.value { color: #333; font-size: 13px; text-align: left; }
|
2025-10-31 14:09:58 +08:00
|
|
|
|
.value-row { display: inline-flex; align-items: center; gap: 6px; }
|
2025-10-20 10:15:13 +08:00
|
|
|
|
.mono { font-family: "Monaco", "Menlo", monospace; }
|
|
|
|
|
|
.mono-ellipsis { font-family: "Monaco", "Menlo", monospace; max-width: 480px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
.empty { text-align: center; color: #999; padding: 20px 0; }
|
2025-11-14 16:17:36 +08:00
|
|
|
|
.amount-more { font-size: 12px; color: #94a3b8; margin-left: 4px; }
|
2025-10-20 10:15:13 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|