diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6cabbdf..b1390f4 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -216,29 +216,112 @@ async fn process_upx(options: UpxOptions) -> Result { 100 }; + // 过滤并格式化输出信息 + let upx_output = format_upx_output(&stdout, &stderr); + let result = format!( - "操作成功!\n输出: {}\n原始大小: {}\n处理后大小: {}\n压缩率: {}%\n\nUPX 输出:\n{}{}", + "操作成功!\n输出: {}\n原始大小: {}\n处理后大小: {}\n压缩率: {}%{}", output_file_clone, format_bytes(original_size_clone), format_bytes(output_size), ratio, - stdout, - stderr + upx_output ); Ok(result) } else { - Err(format!("UPX 执行失败:\n{}{}", stdout, stderr)) + // 解析并优化错误信息 + let error_msg = parse_upx_error(&stdout, &stderr); + Err(error_msg) } } - Err(e) => { - Err(format!("执行 UPX 命令失败: {}", e)) - } + Err(e) => Err(format!("执行 UPX 命令失败: {}", e)), } }) .await .map_err(|e| format!("任务执行错误: {}", e))? } +// 格式化 UPX 成功输出信息 +fn format_upx_output(stdout: &str, stderr: &str) -> String { + let combined = format!("{}{}", stdout, stderr); + + // 过滤掉不需要的行 + let output_lines: Vec<&str> = combined + .lines() + .filter(|line| { + let l = line.trim(); + !l.is_empty() + && !l.starts_with("---") + && !l.starts_with("File size") + && !l.starts_with("Ratio") + && !l.starts_with("Format") + && !l.starts_with("Name") + && !l.starts_with("Ultimate Packer") + && !l.starts_with("Copyright") + && !l.starts_with("UPX ") + }) + .collect(); + + if output_lines.is_empty() { + return String::new(); + } + + format!("\n\nUPX 输出:\n{}", output_lines.join("\n")) +} + +// 解析并优化 UPX 错误信息 +fn parse_upx_error(stdout: &str, stderr: &str) -> String { + let combined = format!("{}{}", stdout, stderr); + + // 常见错误模式匹配 + if combined.contains("AlreadyPackedException") || combined.contains("already packed") { + return "[错误] 文件已经被 UPX 加壳过了\n\n解决方案:\n - 如果要重新压缩,请先使用「脱壳解压」功能\n - 或者选择其他未加壳的文件".to_string(); + } + + if combined.contains("NotPackedException") || combined.contains("not packed") { + return "[错误] 文件未被 UPX 加壳,无法脱壳\n\n解决方案:\n - 请确认文件是否使用 UPX 加壳\n - 或者选择「加壳压缩」功能".to_string(); + } + + if combined.contains("CantPackException") { + return "[错误] 无法压缩此文件\n\n可能原因:\n - 文件格式不支持\n - 文件已损坏\n - 文件受保护(尝试启用「强制压缩」选项)".to_string(); + } + + if combined.contains("OverlayException") { + return "[错误] 文件包含附加数据(Overlay)\n\n解决方案:\n - 某些文件在末尾附加了额外数据\n - 尝试启用「强制压缩」选项\n - 或使用其他工具移除附加数据".to_string(); + } + + if combined.contains("IOException") || combined.contains("can't open") { + return "[错误] 文件访问失败\n\n可能原因:\n - 文件被其他程序占用\n - 文件权限不足\n - 文件路径包含特殊字符".to_string(); + } + + if combined.contains("NotCompressibleException") { + return "[错误] 文件无法压缩\n\n可能原因:\n - 文件已经高度压缩\n - 压缩后反而会变大\n - UPX 自动跳过了此文件".to_string(); + } + + // 如果没有匹配到特定错误,返回简化的原始信息 + let error_lines: Vec<&str> = combined + .lines() + .filter(|line| { + let l = line.trim(); + !l.is_empty() + && !l.starts_with("---") + && !l.starts_with("File size") + && !l.starts_with("Ratio") + && !l.starts_with("Format") + && !l.starts_with("Name") + }) + .collect(); + + if error_lines.is_empty() { + return "[错误] UPX 处理失败\n\n请检查文件是否正常,或尝试其他选项".to_string(); + } + + format!( + "[错误] UPX 处理失败\n\n错误信息:\n{}", + error_lines.join("\n") + ) +} + // 格式化字节大小 fn format_bytes(bytes: u64) -> String { const KB: u64 = 1024; diff --git a/ui/css/style.css b/ui/css/style.css index 29c707c..3438c47 100644 --- a/ui/css/style.css +++ b/ui/css/style.css @@ -111,15 +111,30 @@ body { .log-error { color: #ef4444; + background: rgba(239, 68, 68, 0.1); + padding: 8px 12px; + border-radius: 4px; + border-left: 3px solid #ef4444; + margin: 8px 0; } .log-warning { color: #f59e0b; + background: rgba(245, 158, 11, 0.1); + padding: 8px 12px; + border-radius: 4px; + border-left: 3px solid #f59e0b; + margin: 8px 0; } .log-success { color: #10b981; font-weight: 600; + background: rgba(16, 185, 129, 0.1); + padding: 8px 12px; + border-radius: 4px; + border-left: 3px solid #10b981; + margin: 8px 0; } /* 按钮动画 */ diff --git a/ui/index.html b/ui/index.html index 7b010f8..d5ef856 100644 --- a/ui/index.html +++ b/ui/index.html @@ -6,6 +6,8 @@ UPX GUI + + diff --git a/ui/js/main.js b/ui/js/main.js index f4f231c..1c5f729 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -59,30 +59,42 @@ document.addEventListener('DOMContentLoaded', async () => { await appWindow.show(); }, 100); - // 监听窗口大小变化,更新按钮位置缓存 + // 监听窗口大小变化,更新按钮位置缓存(使用防抖优化) + let resizeTimer; window.addEventListener('resize', () => { - cachedButtonRects = null; // 清除缓存,下次使用时重新计算 - }); + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + cachedButtonRects = null; // 清除缓存,下次使用时重新计算 + }, 150); + }, { passive: true }); }); // 屏蔽刷新快捷键 function preventRefresh() { + // 使用 Map 提高查找效率 + const blockedKeys = new Set(['F5']); + document.addEventListener('keydown', (e) => { - // F5 或 Ctrl+R 或 Ctrl+Shift+R 刷新 - if (e.key === 'F5' || - (e.ctrlKey && e.key === 'r') || - (e.ctrlKey && e.shiftKey && e.key === 'R')) { + // F5 刷新 + if (blockedKeys.has(e.key)) { e.preventDefault(); addLog('刷新功能已禁用', 'warning'); return false; } - + + // Ctrl+R 或 Ctrl+Shift+R 刷新 + if (e.ctrlKey && e.key.toLowerCase() === 'r') { + e.preventDefault(); + addLog('刷新功能已禁用', 'warning'); + return false; + } + // Ctrl+W 关闭窗口 - if (e.ctrlKey && e.key === 'w') { + if (e.ctrlKey && e.key.toLowerCase() === 'w') { e.preventDefault(); return false; } - }); + }, { passive: false }); } // 窗口控制 @@ -256,20 +268,22 @@ async function processBatchFiles(files, mode) { addLog('没有找到可处理的文件', 'warning'); return; } - - addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info'); - addLog(`批量处理模式`, 'info'); - addLog(`找到 ${files.length} 个文件`, 'info'); - addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info'); - + + addLog(`批量处理模式 - 找到 ${files.length} 个文件`, 'info'); + let successCount = 0; let failCount = 0; - + // 并行处理,每次最多5个文件 const batchSize = 5; for (let i = 0; i < files.length; i += batchSize) { const batch = files.slice(i, Math.min(i + batchSize, files.length)); - + const batchStart = i + 1; + const batchEnd = Math.min(i + batchSize, files.length); + + // 显示进度 + addLog(`处理进度: ${batchStart}-${batchEnd}/${files.length}`, 'info'); + await Promise.all(batch.map(async (file) => { try { if (mode === 'compress') { @@ -283,12 +297,12 @@ async function processBatchFiles(files, mode) { failCount++; } })); + + // 让出主线程,避免阻塞 UI + await new Promise(resolve => setTimeout(resolve, 0)); } - - addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info', true); - addLog(`批量处理完成!`, 'success', true); - addLog(`成功: ${successCount} 个,失败: ${failCount} 个`, 'info', true); - addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info', true); + + addLog(`批量处理完成! 成功: ${successCount} 个,失败: ${failCount} 个`, 'success', true); } // 判断路径是否为文件夹并扫描 @@ -399,18 +413,20 @@ function getDropTarget(position) { // 为元素设置视觉反馈 function setupVisualFeedback(element) { - ['dragenter', 'dragover'].forEach(eventName => { - element.addEventListener(eventName, (e) => { - e.preventDefault(); - element.classList.add('drag-over'); - }, false); - }); + const handleDragEnter = (e) => { + e.preventDefault(); + element.classList.add('drag-over'); + }; - ['dragleave', 'drop'].forEach(eventName => { - element.addEventListener(eventName, () => { - element.classList.remove('drag-over'); - }, false); - }); + const handleDragLeave = () => { + element.classList.remove('drag-over'); + }; + + // 使用被动事件监听器优化性能 + element.addEventListener('dragenter', handleDragEnter, { passive: false }); + element.addEventListener('dragover', handleDragEnter, { passive: false }); + element.addEventListener('dragleave', handleDragLeave, { passive: true }); + element.addEventListener('drop', handleDragLeave, { passive: true }); } // 处理加壳压缩 @@ -551,20 +567,54 @@ async function processUpx(mode, inputFile, outputFile) { if (ultraBruteCheckbox.checked) { addLog('已启用极限压缩模式', 'info'); } - + if (forceCompressCheckbox.checked) { addLog('已启用强制压缩模式', 'warning'); } addLog(`开始${mode === 'compress' ? '加壳压缩' : '脱壳解压'}...`, 'info'); - + const result = await invoke('process_upx', { options }); - - addLog(result, 'success'); - addLog('操作完成!', 'success', true); - + + // 分行显示结果信息 + const lines = result.split('\n'); + lines.forEach((line) => { + if (line.trim()) { + // 根据内容判断日志类型 + if (line.includes('操作成功') || line.includes('操作完成')) { + addLog(line, 'success', true); + } else if (line.includes('输出:') || line.includes('大小:') || line.includes('压缩率:')) { + addLog(line, 'success'); + } else if (line.includes('UPX 输出:')) { + addLog(line, 'info'); + } else if (line.includes('扫描') || line.includes('检测')) { + addLog(line, 'warning'); + } else { + addLog(line, 'info'); + } + } + }); + } catch (error) { - addLog(`处理失败: ${error}`, 'error'); + // 格式化错误信息,保留换行和特殊字符 + const errorMsg = String(error); + + // 分行显示错误信息,保持格式 + const lines = errorMsg.split('\n'); + lines.forEach((line, index) => { + if (line.trim()) { + // 错误标题使用 error,解决方案等使用 warning + if (line.includes('[错误]')) { + addLog(line, 'error'); + } else if (line.includes('解决方案:') || line.includes('可能原因:')) { + addLog(line, 'warning'); + } else if (line.trim().startsWith('-')) { + addLog(line, 'info'); + } else { + addLog(line, index === 0 ? 'error' : 'warning'); + } + } + }); } } @@ -579,6 +629,9 @@ async function handleRefreshIcon() { } } +// 日志初始化标志 +let logInitialized = false; + // 添加日志 function addLog(message, type = 'info', highlight = false) { const logLine = document.createElement('div'); @@ -593,15 +646,18 @@ function addLog(message, type = 'info', highlight = false) { logLine.textContent = `[${timestamp}] ${message}`; - // 清除初始提示 - if (logOutput.querySelector('.text-gray-500')) { + // 清除初始提示(只执行一次) + if (!logInitialized) { logOutput.innerHTML = ''; + logInitialized = true; } logOutput.appendChild(logLine); - // 自动滚动到底部 - logOutput.scrollTop = logOutput.scrollHeight; + // 使用 requestAnimationFrame 优化滚动性能 + requestAnimationFrame(() => { + logOutput.scrollTop = logOutput.scrollHeight; + }); } // 保存当前配置