batch-bsc-sender/history.js
2026-01-24 22:22:11 +08:00

323 lines
11 KiB
JavaScript
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.

// 转账历史管理
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;
});