323 lines
11 KiB
JavaScript
323 lines
11 KiB
JavaScript
// 转账历史管理
|
||
class TransferHistory {
|
||
constructor() {
|
||
this.storageKey = 'bsc_transfer_history';
|
||
this.history = this.loadHistory();
|
||
this.filteredHistory = [...this.history];
|
||
this.initElements();
|
||
this.bindEvents();
|
||
this.render();
|
||
}
|
||
|
||
initElements() {
|
||
// 筛选器
|
||
this.statusFilter = document.getElementById('statusFilter');
|
||
this.typeFilter = document.getElementById('typeFilter');
|
||
this.searchInput = document.getElementById('searchInput');
|
||
|
||
// 操作按钮
|
||
this.exportBtn = document.getElementById('exportHistory');
|
||
this.clearBtn = document.getElementById('clearHistory');
|
||
|
||
// 统计
|
||
this.totalSuccess = document.getElementById('totalSuccess');
|
||
this.totalFailed = document.getElementById('totalFailed');
|
||
this.totalPending = document.getElementById('totalPending');
|
||
this.totalTransfers = document.getElementById('totalTransfers');
|
||
this.recordCount = document.getElementById('recordCount');
|
||
|
||
// 表格
|
||
this.tableBody = document.getElementById('historyTableBody');
|
||
|
||
// 模态框
|
||
this.detailModal = document.getElementById('detailModal');
|
||
this.closeDetailBtn = document.getElementById('closeDetail');
|
||
this.viewOnBscscanBtn = document.getElementById('viewOnBscscan');
|
||
}
|
||
|
||
bindEvents() {
|
||
// 筛选器事件
|
||
this.statusFilter.addEventListener('change', () => this.applyFilters());
|
||
this.typeFilter.addEventListener('change', () => this.applyFilters());
|
||
this.searchInput.addEventListener('input', () => this.applyFilters());
|
||
|
||
// 操作按钮
|
||
this.exportBtn.addEventListener('click', () => this.exportToCSV());
|
||
this.clearBtn.addEventListener('click', () => this.clearHistory());
|
||
|
||
// 模态框
|
||
this.closeDetailBtn.addEventListener('click', () => this.closeModal());
|
||
this.detailModal.querySelector('.modal-close').addEventListener('click', () => this.closeModal());
|
||
this.detailModal.addEventListener('click', (e) => {
|
||
if (e.target === this.detailModal) this.closeModal();
|
||
});
|
||
}
|
||
|
||
// 从localStorage加载历史记录
|
||
loadHistory() {
|
||
try {
|
||
const data = localStorage.getItem(this.storageKey);
|
||
return data ? JSON.parse(data) : [];
|
||
} catch (error) {
|
||
console.error('加载历史记录失败:', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// 保存历史记录到localStorage
|
||
saveHistory() {
|
||
try {
|
||
localStorage.setItem(this.storageKey, JSON.stringify(this.history));
|
||
} catch (error) {
|
||
console.error('保存历史记录失败:', error);
|
||
showNotification('保存失败', 'error');
|
||
}
|
||
}
|
||
|
||
// 应用筛选
|
||
applyFilters() {
|
||
const status = this.statusFilter.value;
|
||
const type = this.typeFilter.value;
|
||
const search = this.searchInput.value.toLowerCase().trim();
|
||
|
||
this.filteredHistory = this.history.filter(record => {
|
||
// 状态筛选
|
||
if (status !== 'all' && record.status !== status) return false;
|
||
|
||
// 类型筛选
|
||
if (type !== 'all' && record.type !== type) return false;
|
||
|
||
// 搜索筛选
|
||
if (search) {
|
||
const searchableText = [
|
||
record.address,
|
||
record.txHash,
|
||
record.note
|
||
].join(' ').toLowerCase();
|
||
|
||
if (!searchableText.includes(search)) return false;
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
this.render();
|
||
}
|
||
|
||
// 渲染页面
|
||
render() {
|
||
this.renderStats();
|
||
this.renderTable();
|
||
}
|
||
|
||
// 渲染统计数据
|
||
renderStats() {
|
||
const stats = {
|
||
success: 0,
|
||
failed: 0,
|
||
pending: 0,
|
||
total: this.history.length
|
||
};
|
||
|
||
this.history.forEach(record => {
|
||
if (record.status === 'success') stats.success++;
|
||
else if (record.status === 'failed') stats.failed++;
|
||
else if (record.status === 'pending') stats.pending++;
|
||
});
|
||
|
||
this.totalSuccess.textContent = stats.success;
|
||
this.totalFailed.textContent = stats.failed;
|
||
this.totalPending.textContent = stats.pending;
|
||
this.totalTransfers.textContent = stats.total;
|
||
this.recordCount.textContent = this.filteredHistory.length;
|
||
}
|
||
|
||
// 渲染表格
|
||
renderTable() {
|
||
if (this.filteredHistory.length === 0) {
|
||
this.tableBody.innerHTML = `
|
||
<tr class="empty-state">
|
||
<td colspan="8">
|
||
<div class="empty-content">
|
||
<i class="fas fa-inbox"></i>
|
||
<p>${this.history.length === 0 ? '暂无转账记录' : '没有符合条件的记录'}</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
// 按时间倒序排列
|
||
const sortedHistory = [...this.filteredHistory].sort((a, b) => b.timestamp - a.timestamp);
|
||
|
||
this.tableBody.innerHTML = sortedHistory.map(record => `
|
||
<tr>
|
||
<td>${this.formatTime(record.timestamp)}</td>
|
||
<td>
|
||
<span class="type-badge ${record.type}">
|
||
${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || 'Token'}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<span class="address-cell" title="${record.address}">
|
||
${this.formatAddress(record.address)}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<strong>${record.amount}</strong> ${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || ''}
|
||
</td>
|
||
<td>
|
||
<span class="status-badge ${record.status}">
|
||
${this.getStatusText(record.status)}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
${record.txHash ? `
|
||
<a href="https://bscscan.com/tx/${record.txHash}" target="_blank" class="tx-link" title="${record.txHash}">
|
||
${this.formatTxHash(record.txHash)}
|
||
<i class="fas fa-external-link-alt"></i>
|
||
</a>
|
||
` : '-'}
|
||
</td>
|
||
<td>${record.note || '-'}</td>
|
||
<td>
|
||
<button class="btn-icon" onclick="history.showDetail('${record.id}')" title="查看详情">
|
||
<i class="fas fa-eye"></i>
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
// 显示详情模态框
|
||
showDetail(recordId) {
|
||
const record = this.history.find(r => r.id === recordId);
|
||
if (!record) return;
|
||
|
||
document.getElementById('detailTime').textContent = this.formatTime(record.timestamp, true);
|
||
document.getElementById('detailType').textContent = record.type === 'bnb' ? 'BNB' : `${record.tokenSymbol || 'Token'} (${record.tokenAddress})`;
|
||
document.getElementById('detailAddress').textContent = record.address;
|
||
document.getElementById('detailAmount').textContent = `${record.amount} ${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || ''}`;
|
||
document.getElementById('detailStatus').innerHTML = `<span class="status-badge ${record.status}">${this.getStatusText(record.status)}</span>`;
|
||
document.getElementById('detailTxHash').textContent = record.txHash || '-';
|
||
document.getElementById('detailNote').textContent = record.note || '-';
|
||
document.getElementById('detailGas').textContent = record.gasCost ? `${record.gasCost} BNB` : '-';
|
||
|
||
if (record.txHash) {
|
||
this.viewOnBscscanBtn.href = `https://bscscan.com/tx/${record.txHash}`;
|
||
this.viewOnBscscanBtn.style.display = 'inline-flex';
|
||
} else {
|
||
this.viewOnBscscanBtn.style.display = 'none';
|
||
}
|
||
|
||
this.detailModal.style.display = 'flex';
|
||
}
|
||
|
||
// 关闭模态框
|
||
closeModal() {
|
||
this.detailModal.style.display = 'none';
|
||
}
|
||
|
||
// 导出为CSV
|
||
exportToCSV() {
|
||
if (this.filteredHistory.length === 0) {
|
||
showNotification('没有可导出的记录', 'warning');
|
||
return;
|
||
}
|
||
|
||
const headers = ['时间', '类型', '收款地址', '金额', '状态', '交易哈希', '备注', 'Gas费用'];
|
||
const rows = this.filteredHistory.map(record => [
|
||
this.formatTime(record.timestamp, true),
|
||
record.type === 'bnb' ? 'BNB' : record.tokenSymbol || 'Token',
|
||
record.address,
|
||
`${record.amount} ${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || ''}`,
|
||
this.getStatusText(record.status),
|
||
record.txHash || '',
|
||
record.note || '',
|
||
record.gasCost || ''
|
||
]);
|
||
|
||
const csvContent = [headers, ...rows]
|
||
.map(row => row.map(cell => `"${cell}"`).join(','))
|
||
.join('\n');
|
||
|
||
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = `transfer_history_${Date.now()}.csv`;
|
||
link.click();
|
||
|
||
showNotification('导出成功', 'success');
|
||
}
|
||
|
||
// 清空历史记录
|
||
clearHistory() {
|
||
if (this.history.length === 0) {
|
||
showNotification('没有可清空的记录', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (!confirm('确定要清空所有历史记录吗?此操作不可恢复!')) {
|
||
return;
|
||
}
|
||
|
||
this.history = [];
|
||
this.filteredHistory = [];
|
||
this.saveHistory();
|
||
this.render();
|
||
showNotification('历史记录已清空', 'success');
|
||
}
|
||
|
||
// 格式化时间
|
||
formatTime(timestamp, full = false) {
|
||
const date = new Date(timestamp);
|
||
if (full) {
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
});
|
||
}
|
||
return date.toLocaleString('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
|
||
// 格式化地址
|
||
formatAddress(address) {
|
||
if (!address) return '-';
|
||
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||
}
|
||
|
||
// 格式化交易哈希
|
||
formatTxHash(txHash) {
|
||
if (!txHash) return '-';
|
||
return `${txHash.slice(0, 8)}...${txHash.slice(-6)}`;
|
||
}
|
||
|
||
// 获取状态文本
|
||
getStatusText(status) {
|
||
const statusMap = {
|
||
'success': '成功',
|
||
'failed': '失败',
|
||
'pending': '待确认'
|
||
};
|
||
return statusMap[status] || status;
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
let historyManager;
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
historyManager = new TransferHistory();
|
||
// 将实例暴露到全局,供onclick使用
|
||
window.history = historyManager;
|
||
});
|