Files
webs/power_leasing/src/views/account/withdrawalHistory.vue
2025-09-26 16:40:38 +08:00

969 lines
24 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>
<div class="withdrawal-history-container">
<!-- 页面标题 -->
<div class="page-header">
<h1 class="page-title">提现记录</h1>
<p class="page-subtitle">查看您的提现申请和交易状态</p>
</div>
<!-- 状态标签页 -->
<div class="tab-container">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="提现中" name="pending">
<div class="tab-content">
<div class="list-header">
<span class="list-title">提现中 ({{ total}})</span>
<el-button type="primary" size="small" @click="refreshData">
<i class="el-icon-refresh"></i>
刷新
</el-button>
</div>
<div class="withdrawal-list" v-loading="loading">
<div
v-for="item in pendingWithdrawals"
:key="item.id"
class="withdrawal-item pending"
@click="showDetail(item)"
>
<div class="item-main">
<div class="item-left">
<div class="amount">{{ item.amount }} {{ item.toSymbol || 'USDT' }}</div>
<div class="chain">{{ getChainName(item.toChain) }}</div>
</div>
<div class="item-right">
<div class="status pending-status">
<i class="el-icon-loading"></i>
{{ getStatusText(item.status) }}
</div>
<div class="time">{{ formatTime(item.createTime) }}</div>
</div>
</div>
<div class="item-footer">
<div class="footer-left">
<span class="address">{{ formatAddress(item.toAddress) }}</span>
<span class="tx-hash" v-if="item.txHash">
<i class="el-icon-link"></i>
{{ formatAddress(item.txHash) }}
</span>
</div>
<i class="el-icon-arrow-right"></i>
</div>
</div>
<div v-if="pendingWithdrawals.length === 0" class="empty-state">
<i class="el-icon-document"></i>
<p>暂无提现中的记录</p>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="提现成功" name="success">
<div class="tab-content">
<div class="list-header">
<span class="list-title">提现成功 ({{total }})</span>
<el-button type="primary" size="small" @click="refreshData">
<i class="el-icon-refresh"></i>
刷新
</el-button>
</div>
<div class="withdrawal-list" v-loading="loading">
<div
v-for="item in successWithdrawals"
:key="item.id"
class="withdrawal-item success"
@click="showDetail(item)"
>
<div class="item-main">
<div class="item-left">
<div class="amount">{{ item.amount }} {{ item.toSymbol || 'USDT' }}</div>
<div class="chain">{{ getChainName(item.toChain) }}</div>
</div>
<div class="item-right">
<div class="status success-status">
<i class="el-icon-check"></i>
{{ getStatusText(item.status) }}
</div>
<div class="time">{{ formatTime(item.createTime) }}</div>
</div>
</div>
<div class="item-footer">
<div class="footer-left">
<span class="address">{{ formatAddress(item.toAddress) }}</span>
<span class="tx-hash" v-if="item.txHash">
<i class="el-icon-link"></i>
{{ formatAddress(item.txHash) }}
</span>
</div>
<i class="el-icon-arrow-right"></i>
</div>
</div>
<div v-if="successWithdrawals.length === 0" class="empty-state">
<i class="el-icon-document"></i>
<p>暂无提现成功的记录</p>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="提现失败" name="failed">
<div class="tab-content">
<div class="list-header">
<span class="list-title">提现失败 ({{ total }})</span>
<el-button type="primary" size="small" @click="refreshData">
<i class="el-icon-refresh"></i>
刷新
</el-button>
</div>
<div class="withdrawal-list" v-loading="loading">
<div
v-for="item in failedWithdrawals"
:key="item.id"
class="withdrawal-item failed"
@click="showDetail(item)"
>
<div class="item-main">
<div class="item-left">
<div class="amount">{{ item.amount }} {{ item.toSymbol || 'USDT' }}</div>
<div class="chain">{{ getChainName(item.toChain) }}</div>
</div>
<div class="item-right">
<div class="status failed-status">
<i class="el-icon-close"></i>
{{ getStatusText(item.status) }}
</div>
<div class="time">{{ formatTime(item.createTime) }}</div>
</div>
</div>
<div class="item-footer">
<div class="footer-left">
<span class="address">{{ formatAddress(item.toAddress) }}</span>
<span class="tx-hash" v-if="item.txHash">
<i class="el-icon-link"></i>
{{ formatAddress(item.txHash) }}
</span>
</div>
<i class="el-icon-arrow-right"></i>
</div>
</div>
<div v-if="failedWithdrawals.length === 0" class="empty-state">
<i class="el-icon-document"></i>
<p>暂无提现失败的记录</p>
</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>
<!-- 详情弹窗 -->
<el-dialog
title="提现详情"
:visible.sync="detailDialogVisible"
width="600px"
@close="closeDetail"
>
<div v-if="selectedItem" class="detail-content">
<!-- 基本信息 -->
<div class="detail-section">
<h3 class="section-title">基本信息</h3>
<div class="detail-list">
<div class="detail-row">
<span class="detail-label">提现ID</span>
<span class="detail-value">{{ selectedItem.id }}</span>
</div>
<div class="detail-row">
<span class="detail-label">提现金额</span>
<span class="detail-value amount">{{ selectedItem.amount }} {{ selectedItem.toSymbol || 'USDT' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">区块链网络</span>
<span class="detail-value">{{ getChainName(selectedItem.toChain) }}</span>
</div>
<div class="detail-row">
<span class="detail-label">提现状态</span>
<span class="detail-value">
<el-tag :type="getStatusType(selectedItem.status)">
{{ getStatusText(selectedItem.status) }}
</el-tag>
</span>
</div>
</div>
</div>
<!-- 地址信息 -->
<div class="detail-section">
<h3 class="section-title">地址信息</h3>
<div class="detail-list">
<div class="detail-row">
<span class="detail-label">收款地址</span>
<div class="address-container">
<span class="detail-value address">{{ selectedItem.toAddress }}</span>
<el-button
type="text"
size="small"
@click="copyAddress(selectedItem.toAddress)"
>
复制
</el-button>
</div>
</div>
<!-- v-if="selectedItem.txHash" -->
<div class="detail-row">
<span class="detail-label">交易哈希</span>
<div class="address-container">
<span class="detail-value address">{{ selectedItem.txHash }}</span>
<el-button
v-if="selectedItem.txHash"
type="text"
size="small"
@click="copyAddress(selectedItem.txHash)"
>
复制
</el-button>
<!-- <el-button
type="text"
size="small"
@click="viewOnExplorer(selectedItem.txHash, selectedItem.toChain)"
v-if="selectedItem.txHash"
>
查看
</el-button> -->
</div>
</div>
</div>
</div>
<!-- 时间信息 -->
<div class="detail-section">
<h3 class="section-title">时间信息</h3>
<div class="detail-list">
<div class="detail-row">
<span class="detail-label">提现时间</span>
<span class="detail-value">{{ formatFullTime(selectedItem.createTime) }}</span>
</div>
<div class="detail-row" v-if="selectedItem.updateTime">
<span class="detail-label">完成时间</span>
<span class="detail-value">{{ formatFullTime(selectedItem.updateTime) }}</span>
</div>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="closeDetail">关闭</el-button>
<!-- <el-button type="primary" @click="refreshData" v-if="selectedItem && selectedItem.status === 2">
刷新状态
</el-button> -->
</div>
</el-dialog>
</div>
</template>
<script>
import { balanceWithdrawList } from '../../api/wallet'
export default {
name: 'WithdrawalHistory',
data() {
return {
activeTab: 'pending',
detailDialogVisible: false,
selectedItem: null,
// 提现记录数据
withdrawalRecords: [
// {
// id: 1,
// amount: 100,
// toSymbol: 'USDT',
// toChain: 'tron',
// status: 2,
// createTime: '2024-01-15 14:30:25',
// toAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
// updateTime: '2024-01-15 14:30:25',
// fromAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
// fromChain: 'tron',
// fromSymbol: 'USDT',
// },
// {
// id: 2,
// amount: 100,
// toSymbol: 'USDT',
// toChain: 'tron',
// status: 1,
// createTime: '2024-01-15 14:30:25',
// toAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
// updateTime: '2024-01-15 14:30:25',
// fromAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
// fromChain: 'tron',
// fromSymbol: 'USDT',
// },
// {
// id: 3,
// amount: 100,
// toSymbol: 'USDT',
// toChain: 'tron',
// status: 0,
// createTime: '2024-01-15 14:30:25',
// toAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
// updateTime: '2024-01-15 14:30:25',
// fromAddress: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
// fromChain: 'tron',
// fromSymbol: 'USDT',
// },
],
// 分页和筛选参数
pagination: {
pageNum: 1,
pageSize: 10,
totalPage: 0
},
// 加载状态
loading: false,
// 状态筛选
statusFilter: '',
total: 0,
pageSizes: [10, 20, 50],
currentPage: 1,
}
},
computed: {
/**
* 提现中的记录 (status = 2)
*/
pendingWithdrawals() {
return this.withdrawalRecords.filter(item => item.status === 2)
},
/**
* 提现成功的记录 (status = 1)
*/
successWithdrawals() {
return this.withdrawalRecords.filter(item => item.status === 1)
},
/**
* 提现失败的记录 (status = 0)
*/
failedWithdrawals() {
return this.withdrawalRecords.filter(item => item.status === 0)
}
},
mounted() {
// 设置默认选中提现中状态
this.activeTab = 'pending'
this.statusFilter = 2
this.loadWithdrawalRecords()
},
methods: {
/**
* 获取提现记录列表
* @param {Object} params - 请求参数
* @param {number} params.pageNum - 当前页码默认为1
* @param {number} params.pageSize - 每页条数默认为20
* @param {number} params.status - 记录状态0-提现失败1-提现成功2-提现中
*/
async fetchBalanceWithdrawList(params = {}) {
try {
// 设置默认参数
const requestParams = {
pageNum: 1,
pageSize: 20,
...params
}
console.log('获取提现记录参数:', requestParams)
const res = await balanceWithdrawList(requestParams)
if (res && (res.code === 0 || res.code === 200)) {
// 更新提现记录数据
this.withdrawalRecords = res.rows || []
// 更新分页信息
this.pagination.totalPage = res.totalPage || 0
this.total = res.total || 0
console.log('提现记录获取成功:', {
records: this.withdrawalRecords,
pagination: this.pagination
})
} else {
this.$message({
message: res?.msg || '获取提现记录失败',
type: 'error',
showClose: true
})
}
} catch (error) {
console.error('获取提现记录失败:', error)
}
},
/**
* 加载提现记录
*/
async loadWithdrawalRecords() {
this.loading = true
try {
const params = {
pageNum: this.pagination.pageNum,
pageSize: this.pagination.pageSize
}
// 如果选择了状态筛选,添加状态参数
if (this.statusFilter !== '') {
params.status = this.statusFilter
}
await this.fetchBalanceWithdrawList(params)
} finally {
this.loading = false
}
},
/**
* 标签页切换
*/
handleTabClick(tab) {
this.activeTab = tab.name
// 根据标签页设置状态筛选
if (tab.name === 'pending') {
this.statusFilter = 2 // 提现中
} else if (tab.name === 'success') {
this.statusFilter = 1 // 提现成功
} else if (tab.name === 'failed') {
this.statusFilter = 0 // 提现失败
}
this.currentPage = 1;
this.pagination.pageSize = 10;
// 重置分页并重新加载数据
this.pagination.pageNum = 1
this.loadWithdrawalRecords()
},
/**
* 显示详情
*/
showDetail(item) {
this.selectedItem = item
this.detailDialogVisible = true
},
/**
* 关闭详情
*/
closeDetail() {
this.detailDialogVisible = false
this.selectedItem = null
},
/**
* 获取链名称
*/
getChainName(chain) {
const chainNames = {
tron: 'Tron (TRC20)',
ethereum: 'Ethereum (ERC20)',
bsc: 'BSC (BEP20)',
polygon: 'Polygon (MATIC)'
}
return chainNames[chain] || chain
},
/**
* 获取状态类型
*/
getStatusType(status) {
const statusTypeMap = {
0: 'danger', // 提现失败
1: 'success', // 提现成功
2: 'warning' // 提现中
}
return statusTypeMap[status] || 'info'
},
/**
* 格式化地址
*/
formatAddress(address) {
if (!address) return ''
return address.length > 20 ? `${address.slice(0, 10)}...${address.slice(-10)}` : address
},
/**
* 格式化时间
*/
formatTime(timeStr) {
if (!timeStr) return ''
const date = new Date(timeStr)
const now = new Date()
const diff = now - date
if (diff < 60000) { // 1分钟内
return '刚刚'
} else if (diff < 3600000) { // 1小时内
return `${Math.floor(diff / 60000)}分钟前`
} else if (diff < 86400000) { // 1天内
return `${Math.floor(diff / 3600000)}小时前`
} else {
return date.toLocaleDateString()
}
},
/**
* 格式化完整时间
*/
formatFullTime(timeStr) {
if (!timeStr) return ''
return new Date(timeStr).toLocaleString('zh-CN')
},
/**
* 复制地址
*/
copyAddress(address) {
if (navigator.clipboard) {
navigator.clipboard.writeText(address).then(() => {
this.$message.success('地址已复制到剪贴板')
}).catch(() => {
this.fallbackCopyAddress(address)
})
} else {
this.fallbackCopyAddress(address)
}
},
/**
* 备用复制方法
*/
fallbackCopyAddress(address) {
const textArea = document.createElement('textarea')
textArea.value = address
document.body.appendChild(textArea)
textArea.select()
try {
document.execCommand('copy')
this.$message.success('地址已复制到剪贴板')
} catch (err) {
this.$message.error('复制失败,请手动复制')
}
document.body.removeChild(textArea)
},
/**
* 在浏览器中查看交易
*/
viewOnExplorer(txHash, chain) {
const explorers = {
tron: `https://tronscan.org/#/transaction/${txHash}`,
ethereum: `https://etherscan.io/tx/${txHash}`,
bsc: `https://bscscan.com/tx/${txHash}`,
polygon: `https://polygonscan.com/tx/${txHash}`
}
const url = explorers[chain]
if (url) {
window.open(url, '_blank')
} else {
this.$message.error('不支持的区块链网络')
}
},
/**
* 刷新数据
*/
refreshData() {
this.loadWithdrawalRecords()
},
/**
* 格式化提现状态显示
*/
getStatusText(status) {
const statusMap = {
0: '提现失败',
1: '提现成功',
2: '提现中'
}
return statusMap[status] || '未知状态'
},
handleSizeChange(val) {
console.log(`每页 ${val}`);
this.pagination.pageSize = val;
this.pagination.pageNum = 1;
this.currentPage = 1;
this.loadWithdrawalRecords();
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.pagination.pageNum = val;
this.loadWithdrawalRecords();
},
}
}
</script>
<style scoped>
/* 页面容器 */
.withdrawal-history-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 页面标题 */
.page-header {
margin-bottom: 24px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #333;
margin: 0 0 8px 0;
}
.page-subtitle {
font-size: 14px;
color: #666;
margin: 0;
}
/* 标签页容器 */
.tab-container {
background: white;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
padding: 18px;
}
.tab-content {
padding: 20px;
}
/* 列表头部 */
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.list-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
/* 提现列表 */
.withdrawal-list {
display: flex;
flex-direction: column;
gap: 12px;
height: 400px;
overflow-y: auto;
}
.withdrawal-item {
background: #f8f9fa;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid transparent;
}
.withdrawal-item:hover {
background: #e9ecef;
border-color: #409eff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
}
.withdrawal-item.pending {
border-left: 4px solid #e6a23c;
}
.withdrawal-item.success {
border-left: 4px solid #67c23a;
}
.withdrawal-item.failed {
border-left: 4px solid #f56c6c;
}
.item-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.item-left .amount {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.item-left .chain {
font-size: 12px;
color: #666;
}
.item-right {
text-align: right;
}
.status {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
}
.pending-status {
color: #e6a23c;
}
.success-status {
color: #67c23a;
}
.failed-status {
color: #f56c6c;
}
.time {
font-size: 12px;
color: #999;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-left {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.address {
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
color: #666;
text-align: left;
}
.tx-hash {
display: flex;
align-items: center;
gap: 4px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 11px;
color: #409eff;
background: rgba(64, 158, 255, 0.1);
padding: 2px 6px;
border-radius: 4px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tx-hash i {
font-size: 10px;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
display: block;
}
.empty-state p {
margin: 0;
font-size: 14px;
}
/* 详情弹窗样式 */
.detail-content {
max-height: 500px;
overflow-y: auto;
}
.detail-section {
margin-bottom: 24px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0 0 12px 0;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.detail-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.detail-row {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 0;
border-bottom: 1px solid #f5f5f5;
}
.detail-row:last-child {
border-bottom: none;
}
.detail-label {
font-size: 14px;
color: #666;
font-weight: 500;
min-width: 80px;
flex-shrink: 0;
text-align: right;
}
.detail-value {
font-size: 14px;
color: #333;
flex: 1;
word-break: break-all;
text-align: left;
}
.detail-value.amount {
font-weight: 600;
font-family: 'Monaco', 'Menlo', monospace;
color: #e74c3c;
}
.detail-value.address {
font-family: 'Monaco', 'Menlo', monospace;
word-break: break-all;
}
.address-container {
display: flex;
align-items: center;
gap: 8px;
}
.address-container .detail-value {
flex: 1;
word-break: break-all;
}
/* 响应式设计 */
@media (max-width: 768px) {
.withdrawal-history-container {
padding: 16px;
}
.page-title {
font-size: 24px;
}
.detail-row {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.detail-label {
min-width: auto;
}
.item-main {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.item-right {
text-align: left;
}
}
/* 滚动条样式 */
.detail-content::-webkit-scrollbar {
width: 6px;
}
.detail-content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.detail-content::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.detail-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>