713 lines
26 KiB
JavaScript
713 lines
26 KiB
JavaScript
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 = '<i class="fas fa-spinner fa-spin"></i> 加载中...';
|
||
|
||
// 加载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 = '<i class="fas fa-sync"></i> 加载';
|
||
}
|
||
}
|
||
|
||
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 = `
|
||
<td>${index + 1}</td>
|
||
<td title="${item.address}">${window.TransferUtils.formatAddress(item.address)}</td>
|
||
<td>${item.amount}</td>
|
||
<td>${item.note || '-'}</td>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="log-empty">
|
||
<i class="fas fa-info-circle"></i>
|
||
<p>暂无日志</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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();
|
||
});
|