class BatchTransferApp { constructor() { this.transferData = []; this.transferResults = []; this.isTransferring = false; this.isTokenTransfer = false; this.tokenInfo = null; this.initElements(); this.initEventListeners(); this.initUI(); } initElements() { // 钱包相关 this.connectWalletBtn = document.getElementById('connectWallet'); this.disconnectWalletBtn = document.getElementById('disconnectWallet'); this.walletInfo = document.getElementById('connectedInfo'); this.accountAddress = document.getElementById('accountAddress'); this.walletBalance = document.getElementById('walletBalance'); this.networkInfo = document.getElementById('networkInfo'); // 转账类型 this.transferTypeRadios = document.querySelectorAll('input[name="transferType"]'); this.tokenConfig = document.getElementById('tokenConfig'); this.tokenAddress = document.getElementById('tokenAddress'); this.loadTokenInfoBtn = document.getElementById('loadTokenInfo'); this.tokenInfoElement = document.getElementById('tokenInfo'); this.tokenName = document.getElementById('tokenName'); this.tokenSymbol = document.getElementById('tokenSymbol'); this.tokenBalance = document.getElementById('tokenBalance'); // 上传相关 this.transferInput = document.getElementById('transferInput'); this.parseDataBtn = document.getElementById('parseData'); this.clearInputBtn = document.getElementById('clearInput'); this.dataPreview = document.getElementById('dataPreview'); this.dataTable = document.getElementById('dataTable'); this.dataCount = document.getElementById('dataCount'); this.clearDataBtn = document.getElementById('clearData'); // 汇总信息 this.totalCount = document.getElementById('totalCount'); this.totalAmount = document.getElementById('totalAmount'); this.amountUnit = document.getElementById('amountUnit'); this.estimatedGas = document.getElementById('estimatedGas'); this.totalCost = document.getElementById('totalCost'); // 高级设置 this.delayTime = document.getElementById('delayTime'); this.gasLimit = document.getElementById('gasLimit'); this.gasPrice = document.getElementById('gasPrice'); // 转账按钮 this.startTransferBtn = document.getElementById('startTransfer'); this.stopTransferBtn = document.getElementById('stopTransfer'); // 进度相关 this.progressSection = document.getElementById('progressSection'); this.progressFill = document.getElementById('progressFill'); this.progressInfo = document.getElementById('progressInfo'); this.progressPercent = document.getElementById('progressPercent'); this.successCount = document.getElementById('successCount'); this.failedCount = document.getElementById('failedCount'); this.pendingCount = document.getElementById('pendingCount'); // 日志相关 this.transferLog = document.getElementById('transferLog'); this.clearLogBtn = document.getElementById('clearLog'); // 模态框 this.confirmModal = document.getElementById('confirmModal'); this.confirmCount = document.getElementById('confirmCount'); this.confirmAmount = document.getElementById('confirmAmount'); this.confirmGas = document.getElementById('confirmGas'); this.confirmBalance = document.getElementById('confirmBalance'); this.confirmCheck = document.getElementById('confirmCheck'); this.cancelTransferBtn = document.getElementById('cancelTransfer'); this.executeTransferBtn = document.getElementById('executeTransfer'); this.modalCloseBtn = document.querySelector('.modal-close'); } initEventListeners() { // 钱包连接 this.connectWalletBtn.addEventListener('click', () => this.connectWallet()); this.disconnectWalletBtn.addEventListener('click', () => this.disconnectWallet()); // 转账类型切换 this.transferTypeRadios.forEach(radio => { radio.addEventListener('change', (e) => { if (e.target.checked) { this.onTransferTypeChange(e.target.value); } }); }); // Token相关 this.loadTokenInfoBtn.addEventListener('click', () => this.loadTokenInfo()); this.tokenAddress.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.loadTokenInfo(); } }); // 数据输入 this.parseDataBtn.addEventListener('click', () => this.parseTransferData()); this.clearInputBtn.addEventListener('click', () => this.clearInput()); this.transferInput.addEventListener('keydown', (e) => { // Ctrl/Cmd + Enter 快捷键解析数据 if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { this.parseTransferData(); } }); // 清空数据 if (this.clearDataBtn) { this.clearDataBtn.addEventListener('click', () => this.clearData()); } // 转账控制 this.startTransferBtn.addEventListener('click', () => this.showConfirmModal()); this.stopTransferBtn.addEventListener('click', () => this.stopTransfer()); // 日志控制 this.clearLogBtn.addEventListener('click', () => this.clearLog()); // 配置变化 this.delayTime.addEventListener('change', () => this.updateSummary()); this.gasLimit.addEventListener('change', () => this.updateSummary()); // 模态框 this.confirmCheck.addEventListener('change', (e) => { this.executeTransferBtn.disabled = !e.target.checked; }); this.cancelTransferBtn.addEventListener('click', () => this.hideModal()); this.executeTransferBtn.addEventListener('click', () => this.executeTransfer()); this.modalCloseBtn.addEventListener('click', () => this.hideModal()); // 点击模态框外部关闭 this.confirmModal.addEventListener('click', (e) => { if (e.target === this.confirmModal) { this.hideModal(); } }); } initUI() { this.updateUI(); // 尝试恢复钱包连接 this.restoreWalletConnection(); // 定期更新Gas价格 setInterval(() => { if (window.BatchTransfer.provider) { this.updateGasPrice(); } }, 30000); } async restoreWalletConnection() { try { // 检查是否有保存的钱包连接状态 const wasConnected = localStorage.getItem('walletConnected'); if (wasConnected === 'true' && window.ethereum) { // 尝试自动连接 const accounts = await window.ethereum.request({ method: 'eth_accounts' }); if (accounts && accounts.length > 0) { // 钱包已授权,自动连接 await this.connectWallet(); } } } catch (error) { console.log('无法恢复钱包连接:', error); localStorage.removeItem('walletConnected'); } } async connectWallet() { try { this.addLog('正在连接钱包...', 'info'); const account = await window.BatchTransfer.connectWallet(); // 保存连接状态到localStorage localStorage.setItem('walletConnected', 'true'); // 更新UI this.connectWalletBtn.style.display = 'none'; this.walletInfo.style.display = 'flex'; this.accountAddress.textContent = window.TransferUtils.formatAddress(account); // 获取余额和网络信息 await this.updateWalletInfo(); this.addLog('钱包连接成功', 'success'); } catch (error) { this.addLog(`连接失败: ${error.message}`, 'error'); alert(`连接钱包失败: ${error.message}`); } } async disconnectWallet() { await window.BatchTransfer.disconnectWallet(); // 清除保存的连接状态 localStorage.removeItem('walletConnected'); // 重置UI this.connectWalletBtn.style.display = 'block'; this.walletInfo.style.display = 'none'; // 检查networkInfo元素是否存在(因为我们已经从页面移除了它) if (this.networkInfo) { this.networkInfo.textContent = '未连接'; } this.gasPrice.value = '0'; this.addLog('已断开钱包连接', 'info'); } async updateWalletInfo() { try { // 更新余额 const balance = await window.BatchTransfer.getWalletBalance(); this.walletBalance.textContent = window.TransferUtils.formatNumber(balance, 4); // 更新网络信息(如果元素存在) if (this.networkInfo) { const network = await window.BatchTransfer.getNetworkInfo(); this.networkInfo.textContent = `${network.name} (Chain ID: ${network.chainId})`; } // 更新Gas价格 await this.updateGasPrice(); } catch (error) { console.error('更新钱包信息失败:', error); } } async updateGasPrice() { try { const gasPrice = await window.BatchTransfer.getGasPrice(); console.log('获取到的Gas价格 (Gwei):', gasPrice); this.gasPrice.value = window.TransferUtils.formatNumber(parseFloat(gasPrice), 2); console.log('设置的Gas价格值:', this.gasPrice.value); this.updateSummary(); } catch (error) { console.error('获取Gas价格失败:', error); } } onTransferTypeChange(type) { this.isTokenTransfer = (type === 'token'); if (this.isTokenTransfer) { // 显示Token配置 this.tokenConfig.style.display = 'block'; // 更新Gas Limit默认值 this.gasLimit.value = '65000'; this.amountUnit.textContent = 'Token'; } else { // 隐藏Token配置 this.tokenConfig.style.display = 'none'; this.tokenInfoElement.style.display = 'none'; // 恢复BNB转账的Gas Limit this.gasLimit.value = '21000'; this.amountUnit.textContent = 'BNB'; // 清空token信息 this.tokenInfo = null; } this.updateSummary(); } async loadTokenInfo() { const address = this.tokenAddress.value.trim(); if (!address) { alert('请输入Token合约地址'); return; } if (!window.BatchTransfer.provider) { alert('请先连接钱包'); return; } try { this.addLog('正在加载Token信息...', 'info'); this.loadTokenInfoBtn.disabled = true; this.loadTokenInfoBtn.innerHTML = ' 加载中...'; // 加载Token合约 const tokenInfo = await window.BatchTransfer.loadTokenContract(address); // 获取Token余额 const balance = await window.BatchTransfer.getTokenBalance(); // 更新UI this.tokenName.textContent = tokenInfo.name; this.tokenSymbol.textContent = tokenInfo.symbol; this.tokenBalance.textContent = window.TransferUtils.formatNumber(parseFloat(balance), 4); this.tokenInfoElement.style.display = 'block'; this.tokenInfo = tokenInfo; // 更新金额单位 this.amountUnit.textContent = tokenInfo.symbol; this.addLog(`Token加载成功: ${tokenInfo.name} (${tokenInfo.symbol})`, 'success'); // 更新汇总信息 this.updateSummary(); } catch (error) { this.addLog(`加载Token失败: ${error.message}`, 'error'); alert(`加载Token失败: ${error.message}`); this.tokenInfoElement.style.display = 'none'; } finally { this.loadTokenInfoBtn.disabled = false; this.loadTokenInfoBtn.innerHTML = ' 加载'; } } loadCSVFile(file) { const reader = new FileReader(); reader.onload = (e) => { try { const csvText = e.target.result; this.transferData = window.TransferUtils.parseCSV(csvText); // 验证数据 const errors = window.BatchTransfer.validateTransferData(this.transferData); if (errors.length > 0) { this.addLog('CSV文件解析错误:', 'error'); errors.forEach(error => this.addLog(error, 'error')); alert(`发现${errors.length}个错误,请检查CSV文件格式`); return; } // 显示数据预览 this.dataPreview.style.display = 'block'; // 更新表格显示 this.updateDataTable(); // 更新汇总信息 this.updateSummary(); this.addLog(`成功加载 ${this.transferData.length} 条转账记录`, 'success'); } catch (error) { this.addLog(`解析CSV文件失败: ${error.message}`, 'error'); alert('解析CSV文件失败,请检查文件格式'); } }; reader.onerror = () => { this.addLog('读取文件失败', 'error'); }; reader.readAsText(file); } parseTransferData() { const inputText = this.transferInput.value.trim(); if (!inputText) { alert('请输入转账数据'); return; } try { // 使用相同的CSV解析函数 this.transferData = window.TransferUtils.parseCSV(inputText); // 验证数据 const errors = window.BatchTransfer.validateTransferData(this.transferData); if (errors.length > 0) { this.addLog('数据解析错误:', 'error'); errors.forEach(error => this.addLog(error, 'error')); alert(`发现${errors.length}个错误,请检查数据格式\n\n格式:地址,金额,备注\n示例:0x123...,0.1,用户A`); return; } // 显示数据预览 this.dataPreview.style.display = 'block'; // 更新表格显示 this.updateDataTable(); // 更新汇总信息 this.updateSummary(); this.addLog(`成功解析 ${this.transferData.length} 条转账记录`, 'success'); } catch (error) { this.addLog(`解析数据失败: ${error.message}`, 'error'); alert('解析数据失败,请检查数据格式\n\n格式:地址,金额,备注\n示例:0x123...,0.1,用户A'); } } clearInput() { this.transferInput.value = ''; this.clearData(); } updateDataTable() { const tbody = this.dataTable.querySelector('tbody'); tbody.innerHTML = ''; this.transferData.forEach((item, index) => { const row = document.createElement('tr'); row.innerHTML = ` ${index + 1} ${window.TransferUtils.formatAddress(item.address)} ${item.amount} ${item.note || '-'} `; tbody.appendChild(row); }); this.dataCount.textContent = this.transferData.length; } clearData() { this.transferData = []; this.dataPreview.style.display = 'none'; this.updateSummary(); this.addLog('已清空转账数据', 'info'); } async updateSummary() { if (this.transferData.length === 0) { this.totalCount.textContent = '0'; this.totalAmount.textContent = '0'; this.estimatedGas.textContent = '0'; this.totalCost.textContent = '0'; this.startTransferBtn.disabled = true; return; } // 检查Token转账模式下是否已加载Token信息 if (this.isTokenTransfer && !this.tokenInfo) { this.startTransferBtn.disabled = true; return; } // 计算总金额 const totalAmount = this.transferData.reduce((sum, item) => sum + item.amount, 0); this.totalCount.textContent = this.transferData.length; this.totalAmount.textContent = window.TransferUtils.formatNumber(totalAmount, 4); // 计算Gas费用 if (window.BatchTransfer.provider && this.gasPrice.value && parseFloat(this.gasPrice.value) > 0) { const gasPrice = parseFloat(this.gasPrice.value); const gasLimit = parseInt(this.gasLimit.value); console.log('开始计算Gas费用:'); console.log('- 转账数量:', this.transferData.length); console.log('- Gas价格 (Gwei):', gasPrice); console.log('- Gas限制:', gasLimit); try { const cost = await window.BatchTransfer.calculateTotalCost( this.transferData, gasLimit, gasPrice, this.isTokenTransfer ); console.log('计算结果:'); console.log('- 总Gas费用 (BNB):', cost.totalGasCost); console.log('- 总成本 (BNB):', cost.totalCost); this.estimatedGas.textContent = window.TransferUtils.formatNumber(cost.totalGasCost, 8); this.totalCost.textContent = window.TransferUtils.formatNumber(cost.totalCost, 8); } catch (error) { console.error('计算Gas费用失败:', error); } } else { console.log('无法计算Gas费用:'); console.log('- Provider存在:', !!window.BatchTransfer.provider); console.log('- Gas价格值:', this.gasPrice.value); console.log('- Gas价格>0:', parseFloat(this.gasPrice.value) > 0); } // 启用开始按钮 this.startTransferBtn.disabled = false; } showConfirmModal() { if (!window.BatchTransfer.provider) { alert('请先连接钱包'); return; } if (this.transferData.length === 0) { alert('请先上传转账数据'); return; } // 更新确认信息 this.confirmCount.textContent = this.transferData.length; this.confirmAmount.textContent = this.totalAmount.textContent + ' ' + this.amountUnit.textContent; this.confirmGas.textContent = this.estimatedGas.textContent; this.confirmBalance.textContent = this.walletBalance.textContent; // 重置确认勾选 this.confirmCheck.checked = false; this.executeTransferBtn.disabled = true; // 显示模态框 this.confirmModal.style.display = 'flex'; } hideModal() { this.confirmModal.style.display = 'none'; } async executeTransfer() { this.hideModal(); if (!window.BatchTransfer.provider) { this.addLog('请先连接钱包', 'error'); return; } // Token转账时检查Token余额 if (this.isTokenTransfer) { if (!this.tokenInfo) { this.addLog('请先加载Token信息', 'error'); return; } const tokenBalance = parseFloat(this.tokenBalance.textContent); const requiredAmount = parseFloat(this.totalAmount.textContent); if (tokenBalance < requiredAmount) { this.addLog(`Token余额不足:需要 ${requiredAmount} ${this.tokenInfo.symbol},当前余额 ${tokenBalance} ${this.tokenInfo.symbol}`, 'error'); alert(`Token余额不足,请确保钱包中有足够的${this.tokenInfo.symbol}`); return; } // 检查BNB余额是否足够支付Gas费 const bnbBalance = parseFloat(this.walletBalance.textContent); const requiredGas = parseFloat(this.estimatedGas.textContent); if (bnbBalance < requiredGas) { this.addLog(`BNB余额不足以支付Gas费:需要 ${requiredGas} BNB,当前余额 ${bnbBalance} BNB`, 'error'); alert('BNB余额不足以支付Gas费用'); return; } } else { // BNB转账时检查余额 const balance = parseFloat(this.walletBalance.textContent); const required = parseFloat(this.totalCost.textContent); if (balance < required) { this.addLog(`余额不足:需要 ${required} BNB,当前余额 ${balance} BNB`, 'error'); alert('余额不足,请确保钱包中有足够的BNB支付转账费用'); return; } } // 显示进度区域 this.progressSection.style.display = 'block'; this.progressSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); // 开始转账 this.isTransferring = true; this.startTransferBtn.style.display = 'none'; this.stopTransferBtn.style.display = 'block'; // 重置进度 this.resetProgress(); const transferType = this.isTokenTransfer ? `Token (${this.tokenInfo.symbol})` : 'BNB'; this.addLog(`开始批量转账 ${transferType}...`, 'info'); try { const options = { delay: parseInt(this.delayTime.value), gasLimit: parseInt(this.gasLimit.value), isTokenTransfer: this.isTokenTransfer, onProgress: (progress) => this.updateProgress(progress), onComplete: (results) => this.onTransferComplete(results), onError: (error) => this.onTransferError(error) }; this.transferResults = await window.BatchTransfer.executeBatchTransfer( this.transferData, options ); } catch (error) { this.addLog(`转账过程出错: ${error.message}`, 'error'); this.stopTransfer(); } } stopTransfer() { window.BatchTransfer.stopTransfer(); this.isTransferring = false; this.startTransferBtn.style.display = 'block'; this.stopTransferBtn.style.display = 'none'; this.addLog('转账已停止', 'warning'); } resetProgress() { this.progressFill.style.width = '0%'; this.progressInfo.textContent = '0/0'; this.progressPercent.textContent = '0%'; this.successCount.textContent = '0'; this.failedCount.textContent = '0'; this.pendingCount.textContent = this.transferData.length.toString(); } updateProgress(progress) { const percent = Math.round((progress.current / progress.total) * 100); this.progressFill.style.width = `${percent}%`; this.progressInfo.textContent = `${progress.current}/${progress.total}`; this.progressPercent.textContent = `${percent}%`; this.successCount.textContent = progress.success.toString(); this.failedCount.textContent = progress.failed.toString(); this.pendingCount.textContent = (progress.total - progress.current).toString(); // 更新余额(每5笔更新一次) if (progress.current % 5 === 0) { this.updateWalletInfo(); } } onTransferComplete(results) { this.isTransferring = false; this.startTransferBtn.style.display = 'block'; this.stopTransferBtn.style.display = 'none'; const successCount = results.filter(r => r.success).length; const failedCount = results.filter(r => !r.success).length; this.addLog(`转账完成!成功: ${successCount},失败: ${failedCount}`, 'success'); // 更新钱包余额 this.updateWalletInfo(); // 询问是否导出结果 if (confirm('转账完成,是否导出详细结果?')) { window.TransferUtils.exportToCSV(results); } } onTransferError(error) { this.addLog(`转账过程中出错: ${error.message}`, 'error'); this.isTransferring = false; this.startTransferBtn.style.display = 'block'; this.stopTransferBtn.style.display = 'none'; } addLog(message, type = 'info') { const log = window.TransferUtils.generateLogMessage(type, message); // 移除空状态 const emptyState = this.transferLog.querySelector('.log-empty'); if (emptyState) { emptyState.remove(); } // 添加到日志顶部 this.transferLog.insertAdjacentHTML('afterbegin', log.html); // 限制日志数量 const logItems = this.transferLog.querySelectorAll('.log-item'); if (logItems.length > 100) { logItems[logItems.length - 1].remove(); } } clearLog() { this.transferLog.innerHTML = `

暂无日志

`; } updateUI() { // 初始化UI状态 this.startTransferBtn.disabled = true; this.stopTransferBtn.style.display = 'none'; this.progressSection.style.display = 'none'; this.dataPreview.style.display = 'none'; } } // 应用初始化 document.addEventListener('DOMContentLoaded', () => { window.app = new BatchTransferApp(); });