diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 379aa79..0000000 --- a/.eslintignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules/ -src-tauri/ -upx/ -icons/ -img/ -*.min.js -*.min.css -dist/ -target/ diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 743c877..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": ["html"], - "rules": { - "indent": ["error", 4], - "quotes": ["error", "single", { "avoidEscape": true }], - "semi": ["error", "never"], - "no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], - "no-constant-condition": ["warn", { "checkLoops": false }], - "no-console": "off" - }, - "globals": { - "invoke": "readonly", - "open": "readonly", - "save": "readonly", - "getCurrentWindow": "readonly", - "listen": "readonly" - } -} diff --git a/CLAUDE.md b/CLAUDE.md index 94a1573..f7103a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to Claude Code (claude.ai/code) when working in this codebase. ## 项目概述 @@ -16,6 +16,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **拖放支持**:支持文件/文件夹拖放到窗口,自动判断操作区域 - **多线程批处理**:根据 CPU 核心数动态调整并发数 - **配置持久化**:配置保存在程序目录的 `upx_gui_config.json` +- **便携版支持**:使用 `include_bytes!` 嵌入 UPX,运行时释放到临时目录 ## 常用命令 @@ -28,41 +29,60 @@ cargo tauri build # 编译产物位置 # src-tauri/target/release/bundle/ -# 生成 MSI 和 NSIS 两种安装包 +# 生成 MSI、NSIS 和 Portable 三种格式 # 前端代码检查 npm run lint # 检查代码问题 npm run lint:fix # 自动修复 npm run format # 格式化代码 npm run check # 完整检查 + +# Tailwind CSS 构建 +npm run build:css # 编译并压缩 Tailwind CSS +npm run watch:css # 监听模式编译 Tailwind CSS + +# 完整构建(包含便携版复制) +npm run build # 编译 Tauri 并复制便携版到 bundle 目录 ``` ## 代码结构 ### 前端 (ui/) -- `index.html` - 主界面,包含双操作按钮布局 -- `js/main.js` - 前端逻辑,约 740 行,包含: - - 拖放检测与区域判断 +- `index.html` - 主界面,包含双操作按钮布局、设置弹窗、更新弹窗 +- `js/main.js` - 前端逻辑(约 900 行),包含: + - 拖放检测与区域判断(通过按钮位置缓存 `cachedButtonRects`) - 动态批处理(基于 CPU 核心数) - 配置的保存/加载 - - 日志系统(带数量限制和自动清理) -- `css/style.css` - 主样式 -- `css/tailwind-custom.css` - Tailwind 自定义 + - 日志系统(带数量限制和自动清理,最大 1000 条) + - 更新检查与下载 +- `css/style.css` - 主样式,包含 shadcn/ui CSS 变量、自定义按钮、弹窗动画 +- `css/main.css` - Tailwind 入口文件(需编译) ### 后端 (src-tauri/src/) -- `main.rs` - 唯一的 Rust 源文件,包含所有 Tauri commands: - - `process_upx` - 执行压缩/解压 - - `scan_folder` - 递归扫描 exe/dll 文件 - - `get_upx_version` - 获取 UPX 版本 - - `refresh_icon_cache` - 刷新 Windows 图标缓存 - - `check_update` - 检查 GitHub 最新版本 - - `save_config` / `load_config` - 配置持久化 +- `main.rs` - 唯一的 Rust 源文件(约 500 行),采用模块化结构: + - **数据结构定义**:`UpxOptions`、`ScanFolderOptions`、`AppConfig`、`UpdateInfo` + - **路径解析**:`get_upx_path()` 多级查找(安装版 → 开发版 → 便携版嵌入资源) + - **命令构建辅助**:`build_compress_args()`、`build_decompress_args()` + - **输出处理**:`filter_output_lines()`、`parse_upx_error()` 智能过滤和错误解析 + - **Tauri Commands**: + - `process_upx` - 执行压缩/解压(支持 tokio 阻塞任务) + - `scan_folder` - 递归扫描 exe/dll 文件 + - `get_upx_version` - 获取 UPX 版本 + - `refresh_icon_cache` - 刷新 Windows 图标缓存 + - `check_update` / `download_and_install` - 检查和下载 GitHub 更新 + - `save_config` / `load_config` - 配置持久化 + +### 构建配置 +- `src-tauri/tauri.conf.json` - Tauri 配置,定义窗口属性、权限、打包设置 +- `src-tauri/Cargo.toml` - Rust 依赖,release profile 优化(strip、lto、opt-level=z) +- `.github/workflows/release.yml` - GitHub Actions 自动发布,生成 MSI、NSIS、Portable 三种安装包 ## 重要细节 -### UPX 路径解析 -开发环境:`../upx/upx.exe` -打包后:`_up_/upx/upx.exe`(相对于程序目录) +### UPX 路径解析(三级查找) +1. **安装版**:`程序目录/_up_/upx/upx.exe` +2. **开发版**:`../upx/upx.exe`(相对于项目根目录) +3. **便携版**:从嵌入的 `EMBEDDED_UPX` 释放到 `%TEMP%/upx-gui-portable/upx.exe` ### 编码处理 UPX 在 Windows 上输出 GBK 编码,使用 `encoding_rs::GBK` 转换为 UTF-8 @@ -73,10 +93,24 @@ batchSize = Math.max(2, Math.min(cpuCores * 2, 16)) ``` 最小 2,最大 16,默认为 CPU 核心数的 2 倍 +### 日志系统 +- 最大日志条数:1000 条 +- 超出时一次删除 200 条(批量删除优化性能) +- 支持不同日志类型:`info`、`error`、`warning`、`success`、`hint` +- 使用 `requestAnimationFrame` 优化滚动性能 + ### Tauri 2.0 特性 - 使用 `window.__TAURI__` 全局对象(需启用 `withGlobalTauri: true`) - 命令调用:`invoke('command_name', { options })` - 拖放事件:`listen('tauri://drag-drop', handler)` +- 权限通过 `capabilities` 定义(`main-capability`) + +### 错误处理模式 +后端使用模式匹配解析 UPX 输出,提供中文错误提示和解决方案: +- `AlreadyPackedException` - 文件已加壳 +- `NotPackedException` - 文件未加壳 +- `CantPackException` - 无法压缩 +- `OverlayException` - 附加数据冲突 ## 代码规范 @@ -93,3 +127,19 @@ batchSize = Math.max(2, Math.min(cpuCores * 2, 16)) ### 后端 (Rust) - 使用 `cargo fmt` 格式化 - 使用 `cargo clippy` 检查代码质量 +- Release profile 已优化:`strip = true`、`lto = true`、`opt-level = "z"` + +### 界面风格 +- 基于 shadcn/ui New York 风格,使用 neutral 基色 +- 自定义字体:`YASLant`(回退到系统字体) +- 禁用右键菜单、F5 刷新、Ctrl+W 等浏览器默认行为 + +## 版本发布 + +1. 修改 `src-tauri/Cargo.toml` 和 `package.json` 中的版本号 +2. 推送 tag:`git tag v1.x.x && git push origin v1.x.x` +3. GitHub Actions 自动构建并发布 Release +4. 生成三种安装包: + - `UPX-Tools-{version}-x64.msi` - MSI 安装包 + - `UPX-Tools-{version}-x64-setup.exe` - NSIS 安装包 + - `UPX-Tools-{version}-x64-portable.exe` - 便携版(单文件,内嵌 UPX) diff --git a/README.md b/README.md index 5f71024..c0e17f2 100644 --- a/README.md +++ b/README.md @@ -148,10 +148,11 @@ UPX-Tools/ │ └── js/ # JavaScript 代码 ├── src-tauri/ # Rust 后端 │ ├── src/ # Rust 源码 +│ ├── Cargo.toml # Rust 依赖配置 │ └── tauri.conf.json # Tauri 配置 ├── upx/ # UPX 工具 │ └── upx.exe # UPX 可执行文件 -├── .eslintrc.json # ESLint 配置 +├── eslint.config.js # ESLint v9 配置 ├── .prettierrc.json # Prettier 配置 └── package.json # npm 依赖配置 ``` diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..2dc43fb --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,46 @@ +import js from '@eslint/js' +import html from 'eslint-plugin-html' +import globals from 'globals' + +export default [ + js.configs.recommended, + { + files: ['ui/**/*.{js,html}'], + plugins: { + html, + }, + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.browser, + invoke: 'readonly', + open: 'readonly', + save: 'readonly', + getCurrentWindow: 'readonly', + listen: 'readonly', + }, + }, + rules: { + indent: ['error', 4], + quotes: ['error', 'single', { avoidEscape: true }], + semi: ['error', 'never'], + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + 'no-constant-condition': ['warn', { checkLoops: false }], + 'no-console': 'off', + }, + }, + { + ignores: [ + 'node_modules/**', + 'src-tauri/**', + 'upx/**', + 'icons/**', + 'img/**', + '*.min.js', + '*.min.css', + 'dist/**', + 'target/**', + ], + }, +] diff --git a/package.json b/package.json index 35df75f..ee62ca9 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "post-build": "powershell -Command \"New-Item -Path 'src-tauri/target/release/bundle/Portable' -ItemType Directory -Force; Copy-Item 'src-tauri/target/release/UPX-Tools.exe' 'src-tauri/target/release/bundle/Portable/'\"" }, "devDependencies": { + "@eslint/js": "^9.39.2", "autoprefixer": "^10.4.23", - "eslint": "^8.57.1", + "eslint": "^9.39.2", "eslint-plugin-html": "^8.1.1", + "globals": "^17.2.0", "postcss": "^8.5.6", "postcss-cli": "^11.0.1", "prettier": "^3.3.3", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 727f327..6c816a6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -171,9 +171,9 @@ checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -728,9 +728,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" @@ -2861,7 +2861,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3842,7 +3842,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5122,18 +5122,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.35" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.35" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", @@ -5202,6 +5202,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 09230d0..6fa4ab6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -501,7 +501,6 @@ fn refresh_icon_cache_internal() { } #[tauri::command] -#[allow(dead_code)] async fn refresh_icon_cache() -> Result<(), String> { tokio::task::spawn_blocking(move || { #[cfg(target_os = "windows")] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 97df5d0..6186fff 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,10 +14,10 @@ { "label": "main", "title": "UPX-Tools", - "width": 1000, - "height": 800, - "minWidth": 900, - "minHeight": 700, + "width": 640, + "height": 600, + "minWidth": 640, + "minHeight": 600, "resizable": true, "fullscreen": false, "center": true, diff --git a/ui/css/style.css b/ui/css/style.css index a92db9f..39db273 100644 --- a/ui/css/style.css +++ b/ui/css/style.css @@ -99,52 +99,45 @@ body { /* 滚动条样式 */ ::-webkit-scrollbar { - width: 8px; - height: 8px; + width: 4px; + height: 4px; } ::-webkit-scrollbar-track { - background: hsl(var(--muted)); + background: transparent; } ::-webkit-scrollbar-thumb { - background: hsl(var(--muted-foreground) / 0.3); - border-radius: var(--radius); + background: hsl(var(--border)); + border-radius: 2px; } ::-webkit-scrollbar-thumb:hover { - background: hsl(var(--muted-foreground) / 0.5); + background: hsl(var(--muted-foreground) / 0.3); } /* 日志输出区域 */ #log-output { font-family: 'YASLant', 'Consolas', 'Monaco', 'Courier New', monospace; - overflow: overlay; + overflow: auto; } /* 日志输出区域滚动条 */ #log-output::-webkit-scrollbar { - width: 6px; + width: 4px; } #log-output::-webkit-scrollbar-track { background: transparent; - border-radius: var(--radius); } #log-output::-webkit-scrollbar-thumb { - background: hsl(0 0% 40% / 0.4); - border-radius: var(--radius); + background: hsl(var(--muted-foreground) / 0.2); + border-radius: 2px; } #log-output::-webkit-scrollbar-thumb:hover { - background: hsl(0 0% 50% / 0.6); -} - -/* 日志高亮样式 */ -#log-output .log-highlight { - color: #ffffff; - font-weight: 500; + background: hsl(var(--muted-foreground) / 0.4); } /* 日志行样式 */ @@ -158,24 +151,24 @@ body { } .log-info { - color: #a1a1aa; + color: hsl(var(--muted-foreground)); } .log-error { - color: #f87171; + color: hsl(var(--destructive)); } .log-warning { - color: #fbbf24; + color: #f59e0b; } .log-success { - color: #4ade80; + color: hsl(var(--primary)); font-weight: 500; } .log-hint { - color: #71717a; + color: hsl(var(--muted-foreground)); padding-left: 16px; } @@ -246,6 +239,12 @@ body { color: hsl(var(--accent-foreground)); } +/* 清空按钮 hover 特效 */ +#clear-log-btn:hover { + color: hsl(var(--destructive)); + background-color: hsl(var(--destructive) / 0.1); +} + /* 卡片样式 */ .card { background-color: hsl(var(--card)); diff --git a/ui/index.html b/ui/index.html index d80a369..fbed7e3 100644 --- a/ui/index.html +++ b/ui/index.html @@ -135,9 +135,9 @@ -
+
-
+
+
+
-
等待操作...
+
等待操作...
diff --git a/ui/js/main.js b/ui/js/main.js index 66418e0..c8c311f 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -25,6 +25,7 @@ let compressBtn, includeSubfoldersCheckbox, forceCompressCheckbox, logOutput, + clearLogBtn, settingsModal, settingsBtn, closeSettingsBtn, @@ -48,6 +49,7 @@ function initDOMElements() { includeSubfoldersCheckbox = $('include-subfolders') forceCompressCheckbox = $('force-compress') logOutput = $('log-output') + clearLogBtn = $('clear-log-btn') settingsModal = $('settings-modal') settingsBtn = $('settings-btn') closeSettingsBtn = $('close-settings') @@ -152,6 +154,7 @@ function initOperationButtons() { settingsBtn.addEventListener('click', showSettingsModal) closeSettingsBtn.addEventListener('click', handleCloseSettings) settingsModal.addEventListener('click', handleModalBackdropClick) + clearLogBtn.addEventListener('click', handleClearLog) compressBtn.addEventListener('click', () => handleOperationClick('compress')) decompressBtn.addEventListener('click', () => handleOperationClick('decompress')) @@ -576,6 +579,11 @@ async function handleRefreshIcon() { } } +// 清空日志 +function handleClearLog() { + logOutput.innerHTML = '
日志已清空
' +} + // 更新弹窗相关元素(延迟初始化) let updateModal, updateVersion,