chore: 添加 ESLint 和 Prettier 代码格式化配置
- 新增 ESLint 和 Prettier 配置文件 - 配置代码规范:4空格缩进、单引号、无分号 - 更新文档:README.md 和 CLAUDE.md - 格式化所有前端代码 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d585477def
commit
b24bdc5891
12 files changed
+1275
-614
No files matched your search
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
src-tauri/
|
||||
upx/
|
||||
icons/
|
||||
img/
|
||||
*.min.js
|
||||
*.min.css
|
||||
dist/
|
||||
target/
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ target
|
||||
.claude/
|
||||
*-cwd
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
package-lock.json
|
||||
dist/
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
node_modules/
|
||||
src-tauri/
|
||||
upx/
|
||||
icons/
|
||||
img/
|
||||
*.min.js
|
||||
*.min.css
|
||||
dist/
|
||||
target/
|
||||
package-lock.json
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -29,6 +29,12 @@ cargo tauri build
|
||||
# 编译产物位置
|
||||
# src-tauri/target/release/bundle/
|
||||
# 生成 MSI 和 NSIS 两种安装包
|
||||
|
||||
# 前端代码检查
|
||||
npm run lint # 检查代码问题
|
||||
npm run lint:fix # 自动修复
|
||||
npm run format # 格式化代码
|
||||
npm run check # 完整检查
|
||||
```
|
||||
|
||||
## 代码结构
|
||||
@@ -70,3 +76,19 @@ batchSize = Math.max(2, Math.min(cpuCores * 2, 16))
|
||||
- 使用 `window.__TAURI__` 全局对象(需启用 `withGlobalTauri: true`)
|
||||
- 命令调用:`invoke('command_name', { options })`
|
||||
- 拖放事件:`listen('tauri://drag-drop', handler)`
|
||||
|
||||
## 代码规范
|
||||
|
||||
### 前端 (JavaScript/HTML/CSS)
|
||||
- **缩进**:4 空格
|
||||
- **字符串**:单引号优先
|
||||
- **分号**:不使用分号结尾
|
||||
- **命名**:
|
||||
- 常量:`UPPER_SNAKE_CASE`
|
||||
- 函数/变量:`camelCase`
|
||||
- DOM ID:`kebab-case`
|
||||
- **修改前端代码后必须运行**:`npm run format`
|
||||
|
||||
### 后端 (Rust)
|
||||
- 使用 `cargo fmt` 格式化
|
||||
- 使用 `cargo clippy` 检查代码质量
|
||||
@@ -76,6 +76,9 @@
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
# 安装前端开发依赖(ESLint + Prettier)
|
||||
npm install
|
||||
|
||||
# 安装 Tauri CLI
|
||||
cargo install tauri-cli
|
||||
|
||||
@@ -83,6 +86,32 @@ cargo install tauri-cli
|
||||
npm install -g @tauri-apps/cli
|
||||
```
|
||||
|
||||
### 代码检查与格式化
|
||||
|
||||
项目使用 ESLint 和 Prettier 进行代码质量控制:
|
||||
|
||||
```bash
|
||||
# 检查代码问题
|
||||
npm run lint
|
||||
|
||||
# 自动修复代码问题
|
||||
npm run lint:fix
|
||||
|
||||
# 格式化代码
|
||||
npm run format
|
||||
|
||||
# 检查代码格式
|
||||
npm run format:check
|
||||
|
||||
# 完整检查(lint + format)
|
||||
npm run check
|
||||
```
|
||||
|
||||
**代码规范**:
|
||||
- 缩进:4 空格
|
||||
- 字符串:单引号
|
||||
- 结尾:无分号
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
@@ -94,8 +123,11 @@ UPX-GUI/
|
||||
├── src-tauri/ # Rust 后端
|
||||
│ ├── src/ # Rust 源码
|
||||
│ └── tauri.conf.json # Tauri 配置
|
||||
└── upx/ # UPX 工具
|
||||
└── upx.exe # UPX 可执行文件
|
||||
├── upx/ # UPX 工具
|
||||
│ └── upx.exe # UPX 可执行文件
|
||||
├── .eslintrc.json # ESLint 配置
|
||||
├── .prettierrc.json # Prettier 配置
|
||||
└── package.json # npm 依赖配置
|
||||
```
|
||||
|
||||
## 编译方法
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "upx-gui",
|
||||
"version": "1.0.0",
|
||||
"description": "UPX 可视化加壳/脱壳工具",
|
||||
"scripts": {
|
||||
"lint": "eslint \"ui/**/*.{js,html}\" --max-warnings 0",
|
||||
"lint:fix": "eslint \"ui/**/*.{js,html}\" --fix",
|
||||
"format": "prettier --write \"ui/**/*.{js,html,css}\"",
|
||||
"format:check": "prettier --check \"ui/**/*.{js,html,css}\"",
|
||||
"check": "npm run lint && npm run format:check"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-plugin-html": "^8.1.1",
|
||||
"prettier": "^3.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
+27
-14
@@ -23,15 +23,27 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'HarmonyOS Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
|
||||
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family:
|
||||
'HarmonyOS Sans SC',
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
'Roboto',
|
||||
'Oxygen',
|
||||
'Ubuntu',
|
||||
'Cantarell',
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* 呼吸灯效果 */
|
||||
@keyframes breathing {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
@@ -168,23 +180,24 @@ button:active {
|
||||
}
|
||||
|
||||
/* 输入框聚焦效果 */
|
||||
input:focus, select:focus {
|
||||
input:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 自定义复选框 */
|
||||
input[type="checkbox"] {
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:focus {
|
||||
input[type='checkbox']:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* 滑动条样式 */
|
||||
input[type="range"].slider {
|
||||
input[type='range'].slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
@@ -196,12 +209,12 @@ input[type="range"].slider {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input[type="range"].slider:hover {
|
||||
input[type='range'].slider:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 滑块样式 - Webkit浏览器 */
|
||||
input[type="range"].slider::-webkit-slider-thumb {
|
||||
input[type='range'].slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
@@ -214,18 +227,18 @@ input[type="range"].slider::-webkit-slider-thumb {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input[type="range"].slider::-webkit-slider-thumb:hover {
|
||||
input[type='range'].slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
input[type="range"].slider::-webkit-slider-thumb:active {
|
||||
input[type='range'].slider::-webkit-slider-thumb:active {
|
||||
transform: scale(1.05);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
/* 滑块样式 - Firefox */
|
||||
input[type="range"].slider::-moz-range-thumb {
|
||||
input[type='range'].slider::-moz-range-thumb {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: white;
|
||||
@@ -236,12 +249,12 @@ input[type="range"].slider::-moz-range-thumb {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input[type="range"].slider::-moz-range-thumb:hover {
|
||||
input[type='range'].slider::-moz-range-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
input[type="range"].slider::-moz-range-thumb:active {
|
||||
input[type='range'].slider::-moz-range-thumb:active {
|
||||
transform: scale(1.05);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
+472
-161
@@ -2,7 +2,9 @@
|
||||
/* 仅包含项目中实际使用的类,优化加载性能 */
|
||||
|
||||
/* Reset & Base */
|
||||
*, ::before, ::after {
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
@@ -10,236 +12,545 @@
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.h-screen { height: 100vh; }
|
||||
.h-10 { height: 2.5rem; }
|
||||
.h-9 { height: 2.25rem; }
|
||||
.h-4 { height: 1rem; }
|
||||
.h-2 { height: 0.5rem; }
|
||||
.h-full { height: 100%; }
|
||||
.h-\[calc\(100vh-2\.5rem\)\] { height: calc(100vh - 2.5rem); }
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
.h-9 {
|
||||
height: 2.25rem;
|
||||
}
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
.h-2 {
|
||||
height: 0.5rem;
|
||||
}
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.h-\[calc\(100vh-2\.5rem\)\] {
|
||||
height: calc(100vh - 2.5rem);
|
||||
}
|
||||
|
||||
.w-full { width: 100%; }
|
||||
.w-16 { width: 4rem; }
|
||||
.w-9 { width: 2.25rem; }
|
||||
.w-4 { width: 1rem; }
|
||||
.w-2 { width: 0.5rem; }
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
}
|
||||
.w-9 {
|
||||
width: 2.25rem;
|
||||
}
|
||||
.w-4 {
|
||||
width: 1rem;
|
||||
}
|
||||
.w-2 {
|
||||
width: 0.5rem;
|
||||
}
|
||||
|
||||
.min-h-0 { min-height: 0; }
|
||||
.min-w-0 { min-width: 0; }
|
||||
.max-w-\[700px\] { max-width: 700px; }
|
||||
.min-h-0 {
|
||||
min-height: 0;
|
||||
}
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
.max-w-\[700px\] {
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
/* Flexbox */
|
||||
.flex { display: flex; }
|
||||
.flex-1 { flex: 1 1 0%; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.flex-shrink-0 { flex-shrink: 0; }
|
||||
.items-center { align-items: center; }
|
||||
.items-start { align-items: flex-start; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-center { justify-content: center; }
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.grid { display: grid; }
|
||||
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.col-span-2 { grid-column: span 2 / span 2; }
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
|
||||
/* Spacing */
|
||||
.gap-3 { gap: 0.75rem; }
|
||||
.gap-6 { gap: 1.5rem; }
|
||||
.space-x-1 > :not([hidden]) ~ :not([hidden]) { margin-left: 0.25rem; }
|
||||
.space-x-2\.5 > :not([hidden]) ~ :not([hidden]) { margin-left: 0.625rem; }
|
||||
.space-x-3 > :not([hidden]) ~ :not([hidden]) { margin-left: 0.75rem; }
|
||||
.space-y-5 > :not([hidden]) ~ :not([hidden]) { margin-top: 1.25rem; }
|
||||
.gap-3 {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.gap-6 {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.space-x-1 > :not([hidden]) ~ :not([hidden]) {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.space-x-2\.5 > :not([hidden]) ~ :not([hidden]) {
|
||||
margin-left: 0.625rem;
|
||||
}
|
||||
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
.space-y-5 > :not([hidden]) ~ :not([hidden]) {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.p-3 { padding: 0.75rem; }
|
||||
.p-4 { padding: 1rem; }
|
||||
.p-6 { padding: 1.5rem; }
|
||||
.px-1 { padding-left: 0.25rem; padding-right: 0.25rem; }
|
||||
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
||||
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
||||
.py-2\.5 { padding-top: 0.625rem; padding-bottom: 0.625rem; }
|
||||
.py-8 { padding-top: 2rem; padding-bottom: 2rem; }
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.px-1 {
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.px-6 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
.py-2\.5 {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
}
|
||||
.py-8 {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.m-0 { margin: 0; }
|
||||
.mx-4 { margin-left: 1rem; margin-right: 1rem; }
|
||||
.mx-auto { margin-left: auto; margin-right: auto; }
|
||||
.mt-0\.5 { margin-top: 0.125rem; }
|
||||
.mb-2 { margin-bottom: 0.5rem; }
|
||||
.mb-3 { margin-bottom: 0.75rem; }
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mb-5 { margin-bottom: 1.25rem; }
|
||||
.mb-6 { margin-bottom: 1.5rem; }
|
||||
.mt-6 { margin-top: 1.5rem; }
|
||||
.pt-5 { padding-top: 1.25rem; }
|
||||
.m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
.mx-4 {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.mt-0\.5 {
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.pt-5 {
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
|
||||
/* Position */
|
||||
.relative { position: relative; }
|
||||
.fixed { position: fixed; }
|
||||
.inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
|
||||
.z-50 { z-index: 50; }
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
.inset-0 {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* Display */
|
||||
.hidden { display: none; }
|
||||
.block { display: block; }
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Overflow */
|
||||
.overflow-hidden { overflow: hidden; }
|
||||
.overflow-y-auto { overflow-y: auto; }
|
||||
.overflow-x-hidden { overflow-x: hidden; }
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.overflow-x-hidden {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Colors - Background */
|
||||
.bg-transparent { background-color: transparent; }
|
||||
.bg-white { background-color: rgb(255 255 255); }
|
||||
.bg-gray-50 { background-color: rgb(249 250 251); }
|
||||
.bg-gray-100 { background-color: rgb(243 244 246); }
|
||||
.bg-gray-200 { background-color: rgb(229 231 235); }
|
||||
.bg-gray-900 { background-color: rgb(17 24 39); }
|
||||
.bg-blue-50 { background-color: rgb(239 246 255); }
|
||||
.bg-blue-500 { background-color: rgb(59 130 246); }
|
||||
.bg-red-50 { background-color: rgb(254 242 242); }
|
||||
.bg-black { background-color: rgb(0 0 0); }
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: rgb(255 255 255);
|
||||
}
|
||||
.bg-gray-50 {
|
||||
background-color: rgb(249 250 251);
|
||||
}
|
||||
.bg-gray-100 {
|
||||
background-color: rgb(243 244 246);
|
||||
}
|
||||
.bg-gray-200 {
|
||||
background-color: rgb(229 231 235);
|
||||
}
|
||||
.bg-gray-900 {
|
||||
background-color: rgb(17 24 39);
|
||||
}
|
||||
.bg-blue-50 {
|
||||
background-color: rgb(239 246 255);
|
||||
}
|
||||
.bg-blue-500 {
|
||||
background-color: rgb(59 130 246);
|
||||
}
|
||||
.bg-red-50 {
|
||||
background-color: rgb(254 242 242);
|
||||
}
|
||||
.bg-black {
|
||||
background-color: rgb(0 0 0);
|
||||
}
|
||||
|
||||
.bg-opacity-50 { --tw-bg-opacity: 0.5; }
|
||||
.bg-opacity-50 { background-color: rgb(0 0 0 / var(--tw-bg-opacity)); }
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
.bg-opacity-50 {
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
/* Colors - Text */
|
||||
.text-gray-400 { color: rgb(156 163 175); }
|
||||
.text-gray-500 { color: rgb(107 114 128); }
|
||||
.text-gray-600 { color: rgb(75 85 99); }
|
||||
.text-gray-700 { color: rgb(55 65 81); }
|
||||
.text-gray-800 { color: rgb(31 41 55); }
|
||||
.text-blue-600 { color: rgb(37 99 235); }
|
||||
.text-blue-700 { color: rgb(29 78 216); }
|
||||
.text-green-400 { color: rgb(74 222 128); }
|
||||
.text-green-600 { color: rgb(22 163 74); }
|
||||
.text-green-700 { color: rgb(21 128 61); }
|
||||
.text-white { color: rgb(255 255 255); }
|
||||
.text-gray-400 {
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
.text-gray-500 {
|
||||
color: rgb(107 114 128);
|
||||
}
|
||||
.text-gray-600 {
|
||||
color: rgb(75 85 99);
|
||||
}
|
||||
.text-gray-700 {
|
||||
color: rgb(55 65 81);
|
||||
}
|
||||
.text-gray-800 {
|
||||
color: rgb(31 41 55);
|
||||
}
|
||||
.text-blue-600 {
|
||||
color: rgb(37 99 235);
|
||||
}
|
||||
.text-blue-700 {
|
||||
color: rgb(29 78 216);
|
||||
}
|
||||
.text-green-400 {
|
||||
color: rgb(74 222 128);
|
||||
}
|
||||
.text-green-600 {
|
||||
color: rgb(22 163 74);
|
||||
}
|
||||
.text-green-700 {
|
||||
color: rgb(21 128 61);
|
||||
}
|
||||
.text-white {
|
||||
color: rgb(255 255 255);
|
||||
}
|
||||
|
||||
/* Colors - Border */
|
||||
.border { border-width: 1px; }
|
||||
.border-2 { border-width: 2px; }
|
||||
.border-b { border-bottom-width: 1px; }
|
||||
.border-t { border-top-width: 1px; }
|
||||
.border-gray-200 { border-color: rgb(229 231 235); }
|
||||
.border-gray-300 { border-color: rgb(209 213 219); }
|
||||
.border-blue-300 { border-color: rgb(147 197 253); }
|
||||
.border-blue-500 { border-color: rgb(59 130 246); }
|
||||
.border-green-500 { border-color: rgb(34 197 94); }
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
.border-b {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
.border-t {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
.border-gray-200 {
|
||||
border-color: rgb(229 231 235);
|
||||
}
|
||||
.border-gray-300 {
|
||||
border-color: rgb(209 213 219);
|
||||
}
|
||||
.border-blue-300 {
|
||||
border-color: rgb(147 197 253);
|
||||
}
|
||||
.border-blue-500 {
|
||||
border-color: rgb(59 130 246);
|
||||
}
|
||||
.border-green-500 {
|
||||
border-color: rgb(34 197 94);
|
||||
}
|
||||
|
||||
/* Border Radius */
|
||||
.rounded { border-radius: 0.25rem; }
|
||||
.rounded-lg { border-radius: 0.5rem; }
|
||||
.rounded-full { border-radius: 9999px; }
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
/* Shadow */
|
||||
.shadow-md { box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); }
|
||||
.shadow-lg { box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); }
|
||||
.shadow-xl { box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); }
|
||||
.shadow-2xl { box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); }
|
||||
.shadow-md {
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgb(0 0 0 / 0.1),
|
||||
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
.shadow-lg {
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
.shadow-xl {
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgb(0 0 0 / 0.1),
|
||||
0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
.shadow-2xl {
|
||||
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.text-xs { font-size: 0.75rem; line-height: 1rem; }
|
||||
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
|
||||
.text-base { font-size: 1rem; line-height: 1.5rem; }
|
||||
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
|
||||
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.font-medium { font-weight: 500; }
|
||||
.font-semibold { font-weight: 600; }
|
||||
.font-bold { font-weight: 700; }
|
||||
.font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-mono {
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
/* Appearance */
|
||||
.appearance-none { appearance: none; }
|
||||
.appearance-none {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* Cursor */
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Select */
|
||||
.select-none { user-select: none; }
|
||||
.select-none {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Transform */
|
||||
.transform { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); }
|
||||
.hover\:-translate-y-1:hover { --tw-translate-y: -0.25rem; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); }
|
||||
.transform {
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
|
||||
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
|
||||
scaleY(var(--tw-scale-y));
|
||||
}
|
||||
.hover\:-translate-y-1:hover {
|
||||
--tw-translate-y: -0.25rem;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
|
||||
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
|
||||
scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
/* Transition */
|
||||
.transition-all { transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
|
||||
.transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
|
||||
.duration-200 { transition-duration: 200ms; }
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.transition-colors {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
/* Gradient */
|
||||
.bg-gradient-to-br { background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); }
|
||||
.from-blue-50 { --tw-gradient-from: #eff6ff; --tw-gradient-to: rgb(239 246 255 / 0); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
|
||||
.from-blue-100 { --tw-gradient-from: #dbeafe; --tw-gradient-to: rgb(219 234 254 / 0); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
|
||||
.from-green-50 { --tw-gradient-from: #f0fdf4; --tw-gradient-to: rgb(240 253 244 / 0); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
|
||||
.from-green-100 { --tw-gradient-from: #dcfce7; --tw-gradient-to: rgb(220 252 231 / 0); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
|
||||
.to-blue-100 { --tw-gradient-to: #dbeafe; }
|
||||
.to-blue-200 { --tw-gradient-to: #bfdbfe; }
|
||||
.to-green-100 { --tw-gradient-to: #dcfce7; }
|
||||
.to-green-200 { --tw-gradient-to: #bbf7d0; }
|
||||
.bg-gradient-to-br {
|
||||
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
|
||||
}
|
||||
.from-blue-50 {
|
||||
--tw-gradient-from: #eff6ff;
|
||||
--tw-gradient-to: rgb(239 246 255 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-blue-100 {
|
||||
--tw-gradient-from: #dbeafe;
|
||||
--tw-gradient-to: rgb(219 234 254 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-green-50 {
|
||||
--tw-gradient-from: #f0fdf4;
|
||||
--tw-gradient-to: rgb(240 253 244 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.from-green-100 {
|
||||
--tw-gradient-from: #dcfce7;
|
||||
--tw-gradient-to: rgb(220 252 231 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
.to-blue-100 {
|
||||
--tw-gradient-to: #dbeafe;
|
||||
}
|
||||
.to-blue-200 {
|
||||
--tw-gradient-to: #bfdbfe;
|
||||
}
|
||||
.to-green-100 {
|
||||
--tw-gradient-to: #dcfce7;
|
||||
}
|
||||
.to-green-200 {
|
||||
--tw-gradient-to: #bbf7d0;
|
||||
}
|
||||
|
||||
/* Hover States - Background */
|
||||
.hover\:bg-gray-100:hover {
|
||||
background-color: rgb(243 244 246) !important;
|
||||
.hover\:bg-gray-100:hover {
|
||||
background-color: rgb(243 244 246) !important;
|
||||
}
|
||||
.hover\:bg-red-50:hover {
|
||||
background-color: rgb(254 242 242) !important;
|
||||
.hover\:bg-red-50:hover {
|
||||
background-color: rgb(254 242 242) !important;
|
||||
}
|
||||
.hover\:bg-blue-50:hover {
|
||||
background-color: rgb(239 246 255) !important;
|
||||
.hover\:bg-blue-50:hover {
|
||||
background-color: rgb(239 246 255) !important;
|
||||
}
|
||||
.hover\:bg-blue-600:hover {
|
||||
background-color: rgb(37 99 235) !important;
|
||||
.hover\:bg-blue-600:hover {
|
||||
background-color: rgb(37 99 235) !important;
|
||||
}
|
||||
|
||||
/* Hover States - Border */
|
||||
.hover\:border-blue-300:hover {
|
||||
border-color: rgb(147 197 253) !important;
|
||||
.hover\:border-blue-300:hover {
|
||||
border-color: rgb(147 197 253) !important;
|
||||
}
|
||||
|
||||
/* Hover States - Text */
|
||||
.hover\:text-blue-600:hover {
|
||||
color: rgb(37 99 235) !important;
|
||||
.hover\:text-blue-600:hover {
|
||||
color: rgb(37 99 235) !important;
|
||||
}
|
||||
.hover\:text-red-600:hover {
|
||||
color: rgb(220 38 38) !important;
|
||||
.hover\:text-red-600:hover {
|
||||
color: rgb(220 38 38) !important;
|
||||
}
|
||||
|
||||
/* Hover States - Shadow */
|
||||
.hover\:shadow-xl:hover {
|
||||
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) !important;
|
||||
.hover\:shadow-xl:hover {
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgb(0 0 0 / 0.1),
|
||||
0 8px 10px -6px rgb(0 0 0 / 0.1) !important;
|
||||
}
|
||||
.hover\:shadow-lg:hover {
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1) !important;
|
||||
.hover\:shadow-lg:hover {
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(0 0 0 / 0.1) !important;
|
||||
}
|
||||
|
||||
/* Hover States - Gradient */
|
||||
.hover\:from-blue-100:hover {
|
||||
--tw-gradient-from: #dbeafe !important;
|
||||
--tw-gradient-to: rgb(219 234 254 / 0) !important;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
|
||||
.hover\:from-blue-100:hover {
|
||||
--tw-gradient-from: #dbeafe !important;
|
||||
--tw-gradient-to: rgb(219 234 254 / 0) !important;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
|
||||
}
|
||||
.hover\:from-green-100:hover {
|
||||
--tw-gradient-from: #dcfce7 !important;
|
||||
--tw-gradient-to: rgb(220 252 231 / 0) !important;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
|
||||
.hover\:from-green-100:hover {
|
||||
--tw-gradient-from: #dcfce7 !important;
|
||||
--tw-gradient-to: rgb(220 252 231 / 0) !important;
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
|
||||
}
|
||||
.hover\:to-blue-200:hover {
|
||||
--tw-gradient-to: #bfdbfe !important;
|
||||
.hover\:to-blue-200:hover {
|
||||
--tw-gradient-to: #bfdbfe !important;
|
||||
}
|
||||
.hover\:to-green-200:hover {
|
||||
--tw-gradient-to: #bbf7d0 !important;
|
||||
.hover\:to-green-200:hover {
|
||||
--tw-gradient-to: #bbf7d0 !important;
|
||||
}
|
||||
|
||||
/* Active States */
|
||||
.active\:bg-blue-700:active { background-color: rgb(29 78 216); }
|
||||
.active\:bg-blue-700:active {
|
||||
background-color: rgb(29 78 216);
|
||||
}
|
||||
|
||||
/* Focus States */
|
||||
.focus\:ring-2:focus { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); }
|
||||
.focus\:ring-blue-500:focus { --tw-ring-color: rgb(59 130 246); }
|
||||
.focus\:ring-2:focus {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
|
||||
var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width))
|
||||
var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
.focus\:ring-blue-500:focus {
|
||||
--tw-ring-color: rgb(59 130 246);
|
||||
}
|
||||
|
||||
/* Group Hover */
|
||||
.group:hover .group-hover\:text-red-600 { color: rgb(220 38 38); }
|
||||
.group:hover .group-hover\:text-red-600 {
|
||||
color: rgb(220 38 38);
|
||||
}
|
||||
|
||||
/* Drop Zone */
|
||||
.drop-zone { transition: all 0.3s ease; position: relative; }
|
||||
.drop-zone {
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
+349
-157
@@ -1,172 +1,364 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UPX GUI</title>
|
||||
<!-- 本地化 Tailwind CSS - 仅包含使用的类,优化性能 -->
|
||||
<link rel="stylesheet" href="css/tailwind-custom.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<!-- 预加载关键资源 -->
|
||||
<link rel="preload" href="js/main.js" as="script">
|
||||
</head>
|
||||
<body class="h-screen overflow-hidden bg-gray-50" oncontextmenu="return false;">
|
||||
<!-- 标题栏 -->
|
||||
<div id="titlebar" class="h-10 bg-white border-b border-gray-200 flex items-center justify-between px-4">
|
||||
<div data-tauri-drag-region class="flex items-center space-x-3 flex-1 h-full">
|
||||
<div class="w-2 h-2 rounded-full bg-blue-500 breathing-light"></div>
|
||||
<span id="app-title" class="text-gray-700 text-xs font-medium select-none cursor-pointer hover:text-blue-600 transition-colors" title="访问 GitHub 仓库">UPX-加壳脱壳</span>
|
||||
<span id="upx-version" class="text-gray-400 text-xs select-none"></span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button id="refresh-icon-btn" class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;" title="刷新图标缓存">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="settings-btn" class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;" title="设置">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="titlebar-minimize" class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="titlebar-maximize" class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="titlebar-close" class="w-9 h-9 bg-transparent hover:bg-red-50 flex items-center justify-center transition-all duration-200 group" style="border-radius: 0.3rem;">
|
||||
<svg class="w-4 h-4 text-gray-600 group-hover:text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="h-[calc(100vh-2.5rem)] flex flex-col p-6">
|
||||
<!-- 操作选择 -->
|
||||
<div class="bg-white shadow-lg p-6 border border-gray-200 mb-6" style="border-radius: 0.3rem;">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<button id="compress-btn" class="drop-zone group relative py-8 px-6 border-2 border-blue-500 bg-gradient-to-br from-blue-50 to-blue-100 hover:from-blue-100 hover:to-blue-200 transition-all shadow-md hover:shadow-xl transform hover:-translate-y-1" style="border-radius: 0.3rem;">
|
||||
<svg class="w-16 h-16 mx-auto mb-3 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>UPX GUI</title>
|
||||
<!-- 本地化 Tailwind CSS - 仅包含使用的类,优化性能 -->
|
||||
<link rel="stylesheet" href="css/tailwind-custom.css" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<!-- 预加载关键资源 -->
|
||||
<link rel="preload" href="js/main.js" as="script" />
|
||||
</head>
|
||||
<body class="h-screen overflow-hidden bg-gray-50" oncontextmenu="return false">
|
||||
<!-- 标题栏 -->
|
||||
<div
|
||||
id="titlebar"
|
||||
class="h-10 bg-white border-b border-gray-200 flex items-center justify-between px-4"
|
||||
>
|
||||
<div data-tauri-drag-region class="flex items-center space-x-3 flex-1 h-full">
|
||||
<div class="w-2 h-2 rounded-full bg-blue-500 breathing-light"></div>
|
||||
<span
|
||||
id="app-title"
|
||||
class="text-gray-700 text-xs font-medium select-none cursor-pointer hover:text-blue-600 transition-colors"
|
||||
title="访问 GitHub 仓库"
|
||||
>
|
||||
UPX-加壳脱壳
|
||||
</span>
|
||||
<span id="upx-version" class="text-gray-400 text-xs select-none"></span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button
|
||||
id="refresh-icon-btn"
|
||||
class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200"
|
||||
style="border-radius: 0.3rem"
|
||||
title="刷新图标缓存"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-xl text-blue-700 mb-2">加壳压缩</div>
|
||||
<div class="text-sm text-blue-600">点击选择或拖放文件/文件夹到此处</div>
|
||||
</button>
|
||||
<button id="decompress-btn" class="drop-zone group relative py-8 px-6 border-2 border-green-500 bg-gradient-to-br from-green-50 to-green-100 hover:from-green-100 hover:to-green-200 transition-all shadow-md hover:shadow-xl transform hover:-translate-y-1" style="border-radius: 0.3rem;">
|
||||
<svg class="w-16 h-16 mx-auto mb-3 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"/>
|
||||
<button
|
||||
id="settings-btn"
|
||||
class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200"
|
||||
style="border-radius: 0.3rem"
|
||||
title="设置"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
id="titlebar-minimize"
|
||||
class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M20 12H4"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
id="titlebar-maximize"
|
||||
class="w-9 h-9 bg-transparent hover:bg-gray-100 flex items-center justify-center transition-all duration-200"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
id="titlebar-close"
|
||||
class="w-9 h-9 bg-transparent hover:bg-red-50 flex items-center justify-center transition-all duration-200 group"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-600 group-hover:text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
></path>
|
||||
</svg>
|
||||
<div class="text-xl text-green-700 mb-2">脱壳解压</div>
|
||||
<div class="text-sm text-green-600">点击选择或拖放文件/文件夹到此处</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 日志输出 -->
|
||||
<div class="bg-white shadow-lg p-6 border border-gray-200 flex-1 flex flex-col min-h-0" style="border-radius: 0.3rem;">
|
||||
<h2 class="text-lg font-bold text-gray-800 mb-4">执行日志</h2>
|
||||
<div id="log-output" class="flex-1 bg-gray-900 text-green-400 font-mono text-sm p-4 overflow-y-auto overflow-x-hidden" style="border-radius: 0.3rem;">
|
||||
<div class="text-gray-500">等待操作...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 全局设置弹窗 -->
|
||||
<div id="settings-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white shadow-2xl p-6 w-full mx-4 transform transition-all" style="border-radius: 0.5rem; max-width: 700px;">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-5">全局设置</h2>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- 压缩级别 -->
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<label class="text-sm font-semibold text-gray-700">压缩级别</label>
|
||||
<span id="level-display" class="text-base font-bold text-blue-600">级别 9</span>
|
||||
</div>
|
||||
|
||||
<div class="relative mb-2">
|
||||
<input type="range" id="compression-level" min="1" max="10" value="9"
|
||||
class="w-full h-2 bg-gray-200 appearance-none cursor-pointer slider" style="border-radius: 0.3rem;">
|
||||
</div>
|
||||
|
||||
<!-- 级别标记 -->
|
||||
<div class="flex justify-between px-1 mb-2">
|
||||
<span class="text-xs text-gray-400">1</span>
|
||||
<span class="text-xs text-gray-400">2</span>
|
||||
<span class="text-xs text-gray-400">3</span>
|
||||
<span class="text-xs text-gray-400">4</span>
|
||||
<span class="text-xs text-gray-400">5</span>
|
||||
<span class="text-xs text-gray-400">6</span>
|
||||
<span class="text-xs text-gray-400">7</span>
|
||||
<span class="text-xs text-gray-400">8</span>
|
||||
<span class="text-xs text-gray-400">9</span>
|
||||
<span class="text-xs text-gray-600 font-semibold">best</span>
|
||||
</div>
|
||||
|
||||
<p id="level-description" class="text-xs text-gray-500 text-center">推荐级别,平衡速度和压缩率</p>
|
||||
</div>
|
||||
|
||||
<!-- 选项列表 - 双列布局 -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg">
|
||||
<input type="checkbox" id="overwrite" checked class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">覆盖原文件</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">直接替换原文件,不创建新文件</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg">
|
||||
<input type="checkbox" id="backup" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">备份原文件</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">保留 .bak 备份文件</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg">
|
||||
<input type="checkbox" id="ultra-brute" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">极限压缩</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">尝试所有压缩方法,速度较慢但压缩率最高</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg">
|
||||
<input type="checkbox" id="include-subfolders" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">包含子文件夹</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">处理文件夹时,递归处理所有子目录</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg col-span-2">
|
||||
<input type="checkbox" id="force-compress" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0">
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">强制压缩</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">强制处理受保护的文件(如 GUARD_CF),可能移除安全特性</p>
|
||||
</div>
|
||||
</label>
|
||||
<!-- 主内容区 -->
|
||||
<div class="h-[calc(100vh-2.5rem)] flex flex-col p-6">
|
||||
<!-- 操作选择 -->
|
||||
<div
|
||||
class="bg-white shadow-lg p-6 border border-gray-200 mb-6"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<button
|
||||
id="compress-btn"
|
||||
class="drop-zone group relative py-8 px-6 border-2 border-blue-500 bg-gradient-to-br from-blue-50 to-blue-100 hover:from-blue-100 hover:to-blue-200 transition-all shadow-md hover:shadow-xl transform hover:-translate-y-1"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<svg
|
||||
class="w-16 h-16 mx-auto mb-3 text-blue-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-xl text-blue-700 mb-2">加壳压缩</div>
|
||||
<div class="text-sm text-blue-600">点击选择或拖放文件/文件夹到此处</div>
|
||||
</button>
|
||||
<button
|
||||
id="decompress-btn"
|
||||
class="drop-zone group relative py-8 px-6 border-2 border-green-500 bg-gradient-to-br from-green-50 to-green-100 hover:from-green-100 hover:to-green-200 transition-all shadow-md hover:shadow-xl transform hover:-translate-y-1"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<svg
|
||||
class="w-16 h-16 mx-auto mb-3 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-xl text-green-700 mb-2">脱壳解压</div>
|
||||
<div class="text-sm text-green-600">点击选择或拖放文件/文件夹到此处</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-6 pt-5 border-t border-gray-200">
|
||||
<button id="close-settings" class="w-full py-2.5 px-6 bg-blue-500 text-white text-sm font-semibold hover:bg-blue-600 active:bg-blue-700 transition-colors shadow-md hover:shadow-lg rounded-lg">
|
||||
保存设置
|
||||
</button>
|
||||
<!-- 日志输出 -->
|
||||
<div
|
||||
class="bg-white shadow-lg p-6 border border-gray-200 flex-1 flex flex-col min-h-0"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<h2 class="text-lg font-bold text-gray-800 mb-4">执行日志</h2>
|
||||
<div
|
||||
id="log-output"
|
||||
class="flex-1 bg-gray-900 text-green-400 font-mono text-sm p-4 overflow-y-auto overflow-x-hidden"
|
||||
style="border-radius: 0.3rem"
|
||||
>
|
||||
<div class="text-gray-500">等待操作...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
<!-- 全局设置弹窗 -->
|
||||
<div
|
||||
id="settings-modal"
|
||||
class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||
>
|
||||
<div
|
||||
class="bg-white shadow-2xl p-6 w-full mx-4 transform transition-all"
|
||||
style="border-radius: 0.5rem; max-width: 700px"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-5">全局设置</h2>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- 压缩级别 -->
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<label class="text-sm font-semibold text-gray-700">压缩级别</label>
|
||||
<span id="level-display" class="text-base font-bold text-blue-600">
|
||||
级别 9
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative mb-2">
|
||||
<input
|
||||
type="range"
|
||||
id="compression-level"
|
||||
min="1"
|
||||
max="10"
|
||||
value="9"
|
||||
class="w-full h-2 bg-gray-200 appearance-none cursor-pointer slider"
|
||||
style="border-radius: 0.3rem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 级别标记 -->
|
||||
<div class="flex justify-between px-1 mb-2">
|
||||
<span class="text-xs text-gray-400">1</span>
|
||||
<span class="text-xs text-gray-400">2</span>
|
||||
<span class="text-xs text-gray-400">3</span>
|
||||
<span class="text-xs text-gray-400">4</span>
|
||||
<span class="text-xs text-gray-400">5</span>
|
||||
<span class="text-xs text-gray-400">6</span>
|
||||
<span class="text-xs text-gray-400">7</span>
|
||||
<span class="text-xs text-gray-400">8</span>
|
||||
<span class="text-xs text-gray-400">9</span>
|
||||
<span class="text-xs text-gray-600 font-semibold">best</span>
|
||||
</div>
|
||||
|
||||
<p id="level-description" class="text-xs text-gray-500 text-center">
|
||||
推荐级别,平衡速度和压缩率
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 选项列表 - 双列布局 -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label
|
||||
class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="overwrite"
|
||||
checked
|
||||
class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">
|
||||
覆盖原文件
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
直接替换原文件,不创建新文件
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="backup"
|
||||
class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">
|
||||
备份原文件
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">保留 .bak 备份文件</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="ultra-brute"
|
||||
class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">
|
||||
极限压缩
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
尝试所有压缩方法,速度较慢但压缩率最高
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="include-subfolders"
|
||||
class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">
|
||||
包含子文件夹
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
处理文件夹时,递归处理所有子目录
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="flex items-start space-x-2.5 cursor-pointer p-3 border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-all rounded-lg col-span-2"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="force-compress"
|
||||
class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="text-sm font-semibold text-gray-800 block">
|
||||
强制压缩
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
强制处理受保护的文件(如 GUARD_CF),可能移除安全特性
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-6 pt-5 border-t border-gray-200">
|
||||
<button
|
||||
id="close-settings"
|
||||
class="w-full py-2.5 px-6 bg-blue-500 text-white text-sm font-semibold hover:bg-blue-600 active:bg-blue-700 transition-colors shadow-md hover:shadow-lg rounded-lg"
|
||||
>
|
||||
保存设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+292
-280
@@ -1,182 +1,190 @@
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { open, save } = window.__TAURI__.dialog;
|
||||
const { getCurrentWindow } = window.__TAURI__.window;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
const { invoke } = window.__TAURI__.core
|
||||
const { open, save } = window.__TAURI__.dialog
|
||||
const { getCurrentWindow } = window.__TAURI__.window
|
||||
const { listen } = window.__TAURI__.event
|
||||
|
||||
const appWindow = getCurrentWindow();
|
||||
const appWindow = getCurrentWindow()
|
||||
|
||||
const PERFORMANCE_CONFIG = {
|
||||
cpuCores: navigator.hardwareConcurrency || 4,
|
||||
batchSize: null
|
||||
};
|
||||
batchSize: null,
|
||||
}
|
||||
|
||||
function calculateOptimalBatchSize() {
|
||||
const optimal = Math.max(2, Math.min(PERFORMANCE_CONFIG.cpuCores * 2, 16));
|
||||
PERFORMANCE_CONFIG.batchSize = optimal;
|
||||
return optimal;
|
||||
const optimal = Math.max(2, Math.min(PERFORMANCE_CONFIG.cpuCores * 2, 16))
|
||||
PERFORMANCE_CONFIG.batchSize = optimal
|
||||
return optimal
|
||||
}
|
||||
|
||||
// DOM 元素
|
||||
const compressBtn = document.getElementById('compress-btn');
|
||||
const decompressBtn = document.getElementById('decompress-btn');
|
||||
const compressionLevel = document.getElementById('compression-level');
|
||||
const levelDisplay = document.getElementById('level-display');
|
||||
const levelDescription = document.getElementById('level-description');
|
||||
const overwriteCheckbox = document.getElementById('overwrite');
|
||||
const backupCheckbox = document.getElementById('backup');
|
||||
const ultraBruteCheckbox = document.getElementById('ultra-brute');
|
||||
const includeSubfoldersCheckbox = document.getElementById('include-subfolders');
|
||||
const forceCompressCheckbox = document.getElementById('force-compress');
|
||||
const logOutput = document.getElementById('log-output');
|
||||
const settingsModal = document.getElementById('settings-modal');
|
||||
const settingsBtn = document.getElementById('settings-btn');
|
||||
const closeSettingsBtn = document.getElementById('close-settings');
|
||||
const refreshIconBtn = document.getElementById('refresh-icon-btn');
|
||||
const appTitle = document.getElementById('app-title');
|
||||
const compressBtn = document.getElementById('compress-btn')
|
||||
const decompressBtn = document.getElementById('decompress-btn')
|
||||
const compressionLevel = document.getElementById('compression-level')
|
||||
const levelDisplay = document.getElementById('level-display')
|
||||
const levelDescription = document.getElementById('level-description')
|
||||
const overwriteCheckbox = document.getElementById('overwrite')
|
||||
const backupCheckbox = document.getElementById('backup')
|
||||
const ultraBruteCheckbox = document.getElementById('ultra-brute')
|
||||
const includeSubfoldersCheckbox = document.getElementById('include-subfolders')
|
||||
const forceCompressCheckbox = document.getElementById('force-compress')
|
||||
const logOutput = document.getElementById('log-output')
|
||||
const settingsModal = document.getElementById('settings-modal')
|
||||
const settingsBtn = document.getElementById('settings-btn')
|
||||
const closeSettingsBtn = document.getElementById('close-settings')
|
||||
const refreshIconBtn = document.getElementById('refresh-icon-btn')
|
||||
const appTitle = document.getElementById('app-title')
|
||||
|
||||
// 窗口控制按钮
|
||||
const minimizeBtn = document.getElementById('titlebar-minimize');
|
||||
const maximizeBtn = document.getElementById('titlebar-maximize');
|
||||
const closeBtn = document.getElementById('titlebar-close');
|
||||
const minimizeBtn = document.getElementById('titlebar-minimize')
|
||||
const maximizeBtn = document.getElementById('titlebar-maximize')
|
||||
const closeBtn = document.getElementById('titlebar-close')
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// 初始化性能配置
|
||||
calculateOptimalBatchSize();
|
||||
|
||||
initWindowControls();
|
||||
initOperationButtons();
|
||||
initCompressionLevelSlider();
|
||||
initTitleClick();
|
||||
preventRefresh();
|
||||
await setupDragAndDrop();
|
||||
calculateOptimalBatchSize()
|
||||
|
||||
initWindowControls()
|
||||
initOperationButtons()
|
||||
initCompressionLevelSlider()
|
||||
initTitleClick()
|
||||
preventRefresh()
|
||||
await setupDragAndDrop()
|
||||
|
||||
// 加载保存的配置
|
||||
await loadSavedConfig();
|
||||
await loadSavedConfig()
|
||||
|
||||
// 获取并显示UPX版本
|
||||
try {
|
||||
const version = await invoke('get_upx_version');
|
||||
const version = await invoke('get_upx_version')
|
||||
// 在标题栏显示版本
|
||||
const versionElement = document.getElementById('upx-version');
|
||||
const versionElement = document.getElementById('upx-version')
|
||||
if (versionElement) {
|
||||
versionElement.textContent = `- ${version}`;
|
||||
versionElement.textContent = `- ${version}`
|
||||
}
|
||||
addLog(`UPX GUI 已就绪 - ${version}`, 'info');
|
||||
addLog(`UPX GUI 已就绪 - ${version}`, 'info')
|
||||
} catch (error) {
|
||||
addLog('UPX GUI 已就绪 - 请选择操作', 'info');
|
||||
addLog('UPX GUI 已就绪 - 请选择操作', 'info')
|
||||
}
|
||||
|
||||
// 页面加载完成后显示窗口,避免白屏
|
||||
setTimeout(async () => {
|
||||
await appWindow.show();
|
||||
}, 100);
|
||||
await appWindow.show()
|
||||
}, 100)
|
||||
|
||||
// 监听窗口大小变化,更新按钮位置缓存(使用防抖优化)
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => {
|
||||
cachedButtonRects = null; // 清除缓存,下次使用时重新计算
|
||||
}, 150);
|
||||
}, { passive: true });
|
||||
});
|
||||
let resizeTimer
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
() => {
|
||||
clearTimeout(resizeTimer)
|
||||
resizeTimer = setTimeout(() => {
|
||||
cachedButtonRects = null // 清除缓存,下次使用时重新计算
|
||||
}, 150)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
})
|
||||
|
||||
function preventRefresh() {
|
||||
const BLOCKED_COMBINATIONS = [
|
||||
{ key: 'F5', message: '刷新功能已禁用' },
|
||||
{ ctrlKey: true, key: 'r', message: '刷新功能已禁用' },
|
||||
{ ctrlKey: true, key: 'w', message: null }
|
||||
];
|
||||
{ ctrlKey: true, key: 'w', message: null },
|
||||
]
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
for (const combo of BLOCKED_COMBINATIONS) {
|
||||
const isMatch = combo.ctrlKey
|
||||
? e.ctrlKey && e.key.toLowerCase() === combo.key
|
||||
: e.key === combo.key;
|
||||
document.addEventListener(
|
||||
'keydown',
|
||||
(e) => {
|
||||
for (const combo of BLOCKED_COMBINATIONS) {
|
||||
const isMatch = combo.ctrlKey
|
||||
? e.ctrlKey && e.key.toLowerCase() === combo.key
|
||||
: e.key === combo.key
|
||||
|
||||
if (isMatch) {
|
||||
e.preventDefault();
|
||||
if (combo.message) addLog(combo.message, 'warning');
|
||||
return false;
|
||||
if (isMatch) {
|
||||
e.preventDefault()
|
||||
if (combo.message) addLog(combo.message, 'warning')
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { passive: false });
|
||||
},
|
||||
{ passive: false }
|
||||
)
|
||||
}
|
||||
|
||||
// 窗口控制
|
||||
function initWindowControls() {
|
||||
minimizeBtn.addEventListener('click', () => {
|
||||
appWindow.minimize();
|
||||
});
|
||||
appWindow.minimize()
|
||||
})
|
||||
|
||||
maximizeBtn.addEventListener('click', () => {
|
||||
appWindow.toggleMaximize();
|
||||
});
|
||||
appWindow.toggleMaximize()
|
||||
})
|
||||
|
||||
closeBtn.addEventListener('click', () => {
|
||||
appWindow.close();
|
||||
});
|
||||
appWindow.close()
|
||||
})
|
||||
}
|
||||
|
||||
// 标题点击事件
|
||||
function initTitleClick() {
|
||||
appTitle.addEventListener('click', async (e) => {
|
||||
// 阻止事件冒泡,避免触发拖动
|
||||
e.stopPropagation();
|
||||
e.stopPropagation()
|
||||
|
||||
try {
|
||||
// 使用 Tauri 的 shell 插件打开 URL
|
||||
const { open } = window.__TAURI__.shell;
|
||||
await open('https://github.com/Y-ASLant/UPX-GUI');
|
||||
addLog('已在浏览器中打开 GitHub 仓库', 'info');
|
||||
const { open } = window.__TAURI__.shell
|
||||
await open('https://github.com/Y-ASLant/UPX-GUI')
|
||||
addLog('已在浏览器中打开 GitHub 仓库', 'info')
|
||||
} catch (error) {
|
||||
addLog(`打开链接失败: ${error}`, 'error');
|
||||
addLog(`打开链接失败: ${error}`, 'error')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function initOperationButtons() {
|
||||
refreshIconBtn.addEventListener('click', handleRefreshIcon);
|
||||
settingsBtn.addEventListener('click', showSettingsModal);
|
||||
closeSettingsBtn.addEventListener('click', handleCloseSettings);
|
||||
settingsModal.addEventListener('click', handleModalBackdropClick);
|
||||
refreshIconBtn.addEventListener('click', handleRefreshIcon)
|
||||
settingsBtn.addEventListener('click', showSettingsModal)
|
||||
closeSettingsBtn.addEventListener('click', handleCloseSettings)
|
||||
settingsModal.addEventListener('click', handleModalBackdropClick)
|
||||
|
||||
compressBtn.addEventListener('click', () => handleOperationClick('compress'));
|
||||
decompressBtn.addEventListener('click', () => handleOperationClick('decompress'));
|
||||
compressBtn.addEventListener('click', () => handleOperationClick('compress'))
|
||||
decompressBtn.addEventListener('click', () => handleOperationClick('decompress'))
|
||||
}
|
||||
|
||||
async function handleCloseSettings() {
|
||||
await saveCurrentConfig();
|
||||
hideSettingsModal();
|
||||
addLog('设置已保存', 'success');
|
||||
await saveCurrentConfig()
|
||||
hideSettingsModal()
|
||||
addLog('设置已保存', 'success')
|
||||
}
|
||||
|
||||
async function handleModalBackdropClick(e) {
|
||||
if (e.target === settingsModal) {
|
||||
await saveCurrentConfig();
|
||||
hideSettingsModal();
|
||||
await saveCurrentConfig()
|
||||
hideSettingsModal()
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOperationClick(mode) {
|
||||
const modeName = mode === 'compress' ? '加壳压缩' : '脱壳解压';
|
||||
const processFile = mode === 'compress' ? handleCompressWithFile : handleDecompressWithFile;
|
||||
const selectFile = mode === 'compress' ? handleCompress : handleDecompress;
|
||||
const modeName = mode === 'compress' ? '加壳压缩' : '脱壳解压'
|
||||
const processFile = mode === 'compress' ? handleCompressWithFile : handleDecompressWithFile
|
||||
const selectFile = mode === 'compress' ? handleCompress : handleDecompress
|
||||
|
||||
if (window.droppedFiles?.length > 0) {
|
||||
const files = window.droppedFiles;
|
||||
window.droppedFiles = null;
|
||||
addLog(`开始批量${modeName}...`, 'info');
|
||||
await processBatchFiles(files, mode);
|
||||
const files = window.droppedFiles
|
||||
window.droppedFiles = null
|
||||
addLog(`开始批量${modeName}...`, 'info')
|
||||
await processBatchFiles(files, mode)
|
||||
} else if (window.droppedFile) {
|
||||
const filePath = window.droppedFile;
|
||||
window.droppedFile = null;
|
||||
addLog(`开始${modeName}...`, 'info');
|
||||
await processFile(filePath);
|
||||
const filePath = window.droppedFile
|
||||
window.droppedFile = null
|
||||
addLog(`开始${modeName}...`, 'info')
|
||||
await processFile(filePath)
|
||||
} else {
|
||||
addLog(`选择文件进行${modeName === '加壳压缩' ? '加壳' : '脱壳'}...`, 'info');
|
||||
await selectFile();
|
||||
addLog(`选择文件进行${modeName === '加壳压缩' ? '加壳' : '脱壳'}...`, 'info')
|
||||
await selectFile()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,36 +199,36 @@ const LEVEL_DESCRIPTIONS = {
|
||||
7: '较高压缩率',
|
||||
8: '高压缩率',
|
||||
9: '推荐级别,平衡速度和压缩率',
|
||||
10: '极致压缩,速度最慢'
|
||||
};
|
||||
10: '极致压缩,速度最慢',
|
||||
}
|
||||
|
||||
// 更新压缩级别显示
|
||||
function updateLevelDisplay(value) {
|
||||
const level = parseInt(value);
|
||||
levelDisplay.textContent = level === 10 ? '级别 best' : `级别 ${level}`;
|
||||
levelDescription.textContent = LEVEL_DESCRIPTIONS[level] || '';
|
||||
const level = parseInt(value)
|
||||
levelDisplay.textContent = level === 10 ? '级别 best' : `级别 ${level}`
|
||||
levelDescription.textContent = LEVEL_DESCRIPTIONS[level] || ''
|
||||
}
|
||||
|
||||
// 初始化压缩级别滑动条
|
||||
function initCompressionLevelSlider() {
|
||||
updateLevelDisplay(compressionLevel.value);
|
||||
compressionLevel.addEventListener('input', (e) => updateLevelDisplay(e.target.value));
|
||||
updateLevelDisplay(compressionLevel.value)
|
||||
compressionLevel.addEventListener('input', (e) => updateLevelDisplay(e.target.value))
|
||||
}
|
||||
|
||||
// 获取当前压缩级别值
|
||||
function getCompressionLevel() {
|
||||
const value = parseInt(compressionLevel.value);
|
||||
return value === 10 ? 'best' : value.toString();
|
||||
const value = parseInt(compressionLevel.value)
|
||||
return value === 10 ? 'best' : value.toString()
|
||||
}
|
||||
|
||||
// 显示设置弹窗
|
||||
function showSettingsModal() {
|
||||
settingsModal.classList.remove('hidden');
|
||||
settingsModal.classList.remove('hidden')
|
||||
}
|
||||
|
||||
// 隐藏设置弹窗
|
||||
function hideSettingsModal() {
|
||||
settingsModal.classList.add('hidden');
|
||||
settingsModal.classList.add('hidden')
|
||||
}
|
||||
|
||||
// 扫描文件夹获取所有exe和dll文件
|
||||
@@ -229,245 +237,253 @@ async function scanFolder(folderPath, includeSubfolders) {
|
||||
const files = await invoke('scan_folder', {
|
||||
options: {
|
||||
folder_path: folderPath,
|
||||
include_subfolders: includeSubfolders
|
||||
}
|
||||
});
|
||||
return files;
|
||||
include_subfolders: includeSubfolders,
|
||||
},
|
||||
})
|
||||
return files
|
||||
} catch (error) {
|
||||
addLog(`扫描文件夹失败: ${error}`, 'error');
|
||||
return [];
|
||||
addLog(`扫描文件夹失败: ${error}`, 'error')
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function processBatchFiles(files, mode) {
|
||||
if (files.length === 0) {
|
||||
addLog('没有找到可处理的文件', 'warning');
|
||||
return;
|
||||
addLog('没有找到可处理的文件', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
addLog(`批量处理模式 - 找到 ${files.length} 个文件`, 'info');
|
||||
addLog(`使用 ${PERFORMANCE_CONFIG.batchSize} 并发处理(CPU核心: ${PERFORMANCE_CONFIG.cpuCores})`, 'info');
|
||||
addLog(`批量处理模式 - 找到 ${files.length} 个文件`, 'info')
|
||||
addLog(
|
||||
`使用 ${PERFORMANCE_CONFIG.batchSize} 并发处理(CPU核心: ${PERFORMANCE_CONFIG.cpuCores})`,
|
||||
'info'
|
||||
)
|
||||
|
||||
const handler = mode === 'compress' ? handleCompressWithFile : handleDecompressWithFile;
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const batchSize = PERFORMANCE_CONFIG.batchSize;
|
||||
const handler = mode === 'compress' ? handleCompressWithFile : handleDecompressWithFile
|
||||
let successCount = 0
|
||||
let failCount = 0
|
||||
const batchSize = PERFORMANCE_CONFIG.batchSize
|
||||
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, Math.min(i + batchSize, files.length));
|
||||
addLog(`处理进度: ${i + 1}-${Math.min(i + batchSize, files.length)}/${files.length}`, 'info');
|
||||
const batch = files.slice(i, Math.min(i + batchSize, files.length))
|
||||
addLog(
|
||||
`处理进度: ${i + 1}-${Math.min(i + batchSize, files.length)}/${files.length}`,
|
||||
'info'
|
||||
)
|
||||
|
||||
await Promise.all(batch.map(async (file) => {
|
||||
try {
|
||||
await handler(file);
|
||||
successCount++;
|
||||
} catch {
|
||||
addLog(`处理失败: ${file}`, 'error');
|
||||
failCount++;
|
||||
}
|
||||
}));
|
||||
await Promise.all(
|
||||
batch.map(async (file) => {
|
||||
try {
|
||||
await handler(file)
|
||||
successCount++
|
||||
} catch {
|
||||
addLog(`处理失败: ${file}`, 'error')
|
||||
failCount++
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
}
|
||||
|
||||
addLog(`批量处理完成! 成功: ${successCount} 个,失败: ${failCount} 个`, 'success', true);
|
||||
addLog(`批量处理完成! 成功: ${successCount} 个,失败: ${failCount} 个`, 'success', true)
|
||||
}
|
||||
|
||||
async function checkAndScanPath(path) {
|
||||
const files = await scanFolder(path, includeSubfoldersCheckbox.checked);
|
||||
const files = await scanFolder(path, includeSubfoldersCheckbox.checked)
|
||||
|
||||
if (files.length > 0) {
|
||||
addLog(`扫描文件夹: ${path} (找到 ${files.length} 个文件)`, 'info');
|
||||
return files;
|
||||
addLog(`扫描文件夹: ${path} (找到 ${files.length} 个文件)`, 'info')
|
||||
return files
|
||||
}
|
||||
|
||||
const extension = path.toLowerCase();
|
||||
const extension = path.toLowerCase()
|
||||
if (extension.endsWith('.exe') || extension.endsWith('.dll')) {
|
||||
return [path];
|
||||
return [path]
|
||||
}
|
||||
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
async function setupDragAndDrop() {
|
||||
await listen('tauri://drag-drop', handleDragDrop);
|
||||
setupVisualFeedback(compressBtn);
|
||||
setupVisualFeedback(decompressBtn);
|
||||
await listen('tauri://drag-drop', handleDragDrop)
|
||||
setupVisualFeedback(compressBtn)
|
||||
setupVisualFeedback(decompressBtn)
|
||||
}
|
||||
|
||||
async function handleDragDrop(event) {
|
||||
const { paths, position } = event.payload;
|
||||
const { paths, position } = event.payload
|
||||
|
||||
if (!paths?.length) return;
|
||||
if (!paths?.length) return
|
||||
|
||||
const allFiles = await collectFiles(paths);
|
||||
const allFiles = await collectFiles(paths)
|
||||
|
||||
if (allFiles.length === 0) {
|
||||
addLog('未找到 .exe 或 .dll 文件', 'warning');
|
||||
return;
|
||||
addLog('未找到 .exe 或 .dll 文件', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
await processDropByTarget(allFiles, position);
|
||||
await processDropByTarget(allFiles, position)
|
||||
}
|
||||
|
||||
async function collectFiles(paths) {
|
||||
const allFiles = [];
|
||||
const allFiles = []
|
||||
for (const path of paths) {
|
||||
const files = await checkAndScanPath(path);
|
||||
allFiles.push(...files);
|
||||
const files = await checkAndScanPath(path)
|
||||
allFiles.push(...files)
|
||||
}
|
||||
return allFiles;
|
||||
return allFiles
|
||||
}
|
||||
|
||||
async function processDropByTarget(files, position) {
|
||||
const dropTarget = getDropTarget(position);
|
||||
const dropTarget = getDropTarget(position)
|
||||
|
||||
if (dropTarget === 'compress') {
|
||||
addLog('检测到拖放至加壳区域', 'info');
|
||||
await processBatchFiles(files, 'compress');
|
||||
addLog('检测到拖放至加壳区域', 'info')
|
||||
await processBatchFiles(files, 'compress')
|
||||
} else if (dropTarget === 'decompress') {
|
||||
addLog('检测到拖放至脱壳区域', 'info');
|
||||
await processBatchFiles(files, 'decompress');
|
||||
addLog('检测到拖放至脱壳区域', 'info')
|
||||
await processBatchFiles(files, 'decompress')
|
||||
} else {
|
||||
storeFilesForLater(files);
|
||||
storeFilesForLater(files)
|
||||
}
|
||||
}
|
||||
|
||||
function storeFilesForLater(files) {
|
||||
if (files.length === 1) {
|
||||
addLog('请点击"加壳压缩"或"脱壳解压"按钮', 'info');
|
||||
window.droppedFile = files[0];
|
||||
addLog('请点击"加壳压缩"或"脱壳解压"按钮', 'info')
|
||||
window.droppedFile = files[0]
|
||||
} else {
|
||||
addLog(`已选择 ${files.length} 个文件,请点击操作按钮`, 'info');
|
||||
window.droppedFiles = files;
|
||||
addLog(`已选择 ${files.length} 个文件,请点击操作按钮`, 'info')
|
||||
window.droppedFiles = files
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存按钮位置信息
|
||||
let cachedButtonRects = null;
|
||||
let cachedButtonRects = null
|
||||
|
||||
// 更新按钮位置缓存
|
||||
function updateButtonRectsCache() {
|
||||
cachedButtonRects = {
|
||||
compress: compressBtn.getBoundingClientRect(),
|
||||
decompress: decompressBtn.getBoundingClientRect()
|
||||
};
|
||||
decompress: decompressBtn.getBoundingClientRect(),
|
||||
}
|
||||
}
|
||||
|
||||
function isPointInRect(x, y, rect) {
|
||||
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
|
||||
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
|
||||
}
|
||||
|
||||
function getDropTarget(position) {
|
||||
if (!position) return null;
|
||||
if (!position) return null
|
||||
|
||||
if (!cachedButtonRects) {
|
||||
updateButtonRectsCache();
|
||||
updateButtonRectsCache()
|
||||
}
|
||||
|
||||
const { x, y } = position;
|
||||
const { x, y } = position
|
||||
|
||||
if (isPointInRect(x, y, cachedButtonRects.compress)) {
|
||||
return 'compress';
|
||||
return 'compress'
|
||||
}
|
||||
|
||||
if (isPointInRect(x, y, cachedButtonRects.decompress)) {
|
||||
return 'decompress';
|
||||
return 'decompress'
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
// 为元素设置视觉反馈
|
||||
function setupVisualFeedback(element) {
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
element.classList.add('drag-over');
|
||||
};
|
||||
e.preventDefault()
|
||||
element.classList.add('drag-over')
|
||||
}
|
||||
|
||||
const handleDragLeave = () => {
|
||||
element.classList.remove('drag-over');
|
||||
};
|
||||
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 });
|
||||
element.addEventListener('dragenter', handleDragEnter, { passive: false })
|
||||
element.addEventListener('dragover', handleDragEnter, { passive: false })
|
||||
element.addEventListener('dragleave', handleDragLeave, { passive: true })
|
||||
element.addEventListener('drop', handleDragLeave, { passive: true })
|
||||
}
|
||||
|
||||
async function handleCompress() {
|
||||
await handleFileSelect('compress');
|
||||
await handleFileSelect('compress')
|
||||
}
|
||||
|
||||
async function handleDecompress() {
|
||||
await handleFileSelect('decompress');
|
||||
await handleFileSelect('decompress')
|
||||
}
|
||||
|
||||
async function handleFileSelect(mode) {
|
||||
try {
|
||||
const selected = await open({
|
||||
multiple: true,
|
||||
filters: [{ name: '可执行文件', extensions: ['exe', 'dll'] }]
|
||||
});
|
||||
filters: [{ name: '可执行文件', extensions: ['exe', 'dll'] }],
|
||||
})
|
||||
|
||||
if (!selected || (Array.isArray(selected) && selected.length === 0)) {
|
||||
addLog('未选择文件', 'warning');
|
||||
return;
|
||||
addLog('未选择文件', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
const files = Array.isArray(selected) ? selected : [selected];
|
||||
const files = Array.isArray(selected) ? selected : [selected]
|
||||
|
||||
if (files.length === 1) {
|
||||
addLog(`选择文件: ${files[0]}`, 'info');
|
||||
const handler = mode === 'compress' ? handleCompressWithFile : handleDecompressWithFile;
|
||||
await handler(files[0]);
|
||||
addLog(`选择文件: ${files[0]}`, 'info')
|
||||
const handler = mode === 'compress' ? handleCompressWithFile : handleDecompressWithFile
|
||||
await handler(files[0])
|
||||
} else {
|
||||
addLog(`选择了 ${files.length} 个文件`, 'info');
|
||||
await processBatchFiles(files, mode);
|
||||
addLog(`选择了 ${files.length} 个文件`, 'info')
|
||||
await processBatchFiles(files, mode)
|
||||
}
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
addLog(`操作失败: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCompressWithFile(inputFile) {
|
||||
try {
|
||||
let outputFile;
|
||||
let outputFile
|
||||
|
||||
if (overwriteCheckbox.checked) {
|
||||
outputFile = inputFile;
|
||||
addLog('将覆盖原文件', 'info');
|
||||
outputFile = inputFile
|
||||
addLog('将覆盖原文件', 'info')
|
||||
} else {
|
||||
const ext = inputFile.substring(inputFile.lastIndexOf('.'));
|
||||
const baseName = inputFile.substring(0, inputFile.lastIndexOf('.'));
|
||||
const defaultOutput = `${baseName}_packed${ext}`;
|
||||
const ext = inputFile.substring(inputFile.lastIndexOf('.'))
|
||||
const baseName = inputFile.substring(0, inputFile.lastIndexOf('.'))
|
||||
const defaultOutput = `${baseName}_packed${ext}`
|
||||
|
||||
outputFile = await save({
|
||||
filters: [{ name: '可执行文件', extensions: ['exe', 'dll'] }],
|
||||
defaultPath: defaultOutput
|
||||
});
|
||||
defaultPath: defaultOutput,
|
||||
})
|
||||
|
||||
if (!outputFile) {
|
||||
addLog('未选择输出位置', 'warning');
|
||||
return;
|
||||
addLog('未选择输出位置', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
addLog(`输出文件: ${outputFile}`, 'info');
|
||||
addLog(`输出文件: ${outputFile}`, 'info')
|
||||
}
|
||||
|
||||
await processUpx('compress', inputFile, outputFile);
|
||||
await processUpx('compress', inputFile, outputFile)
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
addLog(`操作失败: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDecompressWithFile(inputFile) {
|
||||
try {
|
||||
addLog('将覆盖原文件', 'info');
|
||||
await processUpx('decompress', inputFile, inputFile);
|
||||
addLog('将覆盖原文件', 'info')
|
||||
await processUpx('decompress', inputFile, inputFile)
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
addLog(`操作失败: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,25 +496,24 @@ async function processUpx(mode, inputFile, outputFile) {
|
||||
compression_level: getCompressionLevel(),
|
||||
backup: backupCheckbox.checked,
|
||||
ultra_brute: ultraBruteCheckbox.checked,
|
||||
force: forceCompressCheckbox.checked
|
||||
};
|
||||
force: forceCompressCheckbox.checked,
|
||||
}
|
||||
|
||||
if (ultraBruteCheckbox.checked) {
|
||||
addLog('已启用极限压缩模式', 'info');
|
||||
addLog('已启用极限压缩模式', 'info')
|
||||
}
|
||||
|
||||
if (forceCompressCheckbox.checked) {
|
||||
addLog('已启用强制压缩模式', 'warning');
|
||||
addLog('已启用强制压缩模式', 'warning')
|
||||
}
|
||||
|
||||
const actionName = mode === 'compress' ? '加壳压缩' : '脱壳解压';
|
||||
addLog(`开始${actionName}...`, 'info');
|
||||
|
||||
const result = await invoke('process_upx', { options });
|
||||
parseProcessResult(result);
|
||||
const actionName = mode === 'compress' ? '加壳压缩' : '脱壳解压'
|
||||
addLog(`开始${actionName}...`, 'info')
|
||||
|
||||
const result = await invoke('process_upx', { options })
|
||||
parseProcessResult(result)
|
||||
} catch (error) {
|
||||
parseProcessError(String(error));
|
||||
parseProcessError(String(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,101 +522,99 @@ function parseProcessResult(result) {
|
||||
{ patterns: ['操作成功', '操作完成'], type: 'success', highlight: true },
|
||||
{ patterns: ['输出:', '大小:', '压缩率:'], type: 'success', highlight: false },
|
||||
{ patterns: ['UPX 输出:'], type: 'info', highlight: false },
|
||||
{ patterns: ['扫描', '检测'], type: 'warning', highlight: false }
|
||||
];
|
||||
{ patterns: ['扫描', '检测'], type: 'warning', highlight: false },
|
||||
]
|
||||
|
||||
result.split('\n').forEach((line) => {
|
||||
if (!line.trim()) return;
|
||||
if (!line.trim()) return
|
||||
|
||||
const match = LOG_PATTERNS.find(({ patterns }) =>
|
||||
patterns.some(p => line.includes(p))
|
||||
);
|
||||
const match = LOG_PATTERNS.find(({ patterns }) => patterns.some((p) => line.includes(p)))
|
||||
|
||||
if (match) {
|
||||
addLog(line, match.type, match.highlight);
|
||||
addLog(line, match.type, match.highlight)
|
||||
} else {
|
||||
addLog(line, 'info');
|
||||
addLog(line, 'info')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function parseProcessError(errorMsg) {
|
||||
const ERROR_PATTERNS = [
|
||||
{ test: (s) => s.includes('[错误]'), type: 'error' },
|
||||
{ test: (s) => s.includes('解决方案:') || s.includes('可能原因:'), type: 'warning' },
|
||||
{ test: (s) => s.trim().startsWith('-'), type: 'info' }
|
||||
];
|
||||
{ test: (s) => s.trim().startsWith('-'), type: 'info' },
|
||||
]
|
||||
|
||||
errorMsg.split('\n').forEach((line, index) => {
|
||||
if (!line.trim()) return;
|
||||
if (!line.trim()) return
|
||||
|
||||
const match = ERROR_PATTERNS.find(({ test }) => test(line));
|
||||
const match = ERROR_PATTERNS.find(({ test }) => test(line))
|
||||
|
||||
if (match) {
|
||||
addLog(line, match.type);
|
||||
addLog(line, match.type)
|
||||
} else {
|
||||
addLog(line, index === 0 ? 'error' : 'warning');
|
||||
addLog(line, index === 0 ? 'error' : 'warning')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新图标缓存
|
||||
async function handleRefreshIcon() {
|
||||
try {
|
||||
addLog('正在刷新图标缓存...', 'info');
|
||||
await invoke('refresh_icon_cache');
|
||||
addLog('图标缓存刷新完成', 'success');
|
||||
addLog('正在刷新图标缓存...', 'info')
|
||||
await invoke('refresh_icon_cache')
|
||||
addLog('图标缓存刷新完成', 'success')
|
||||
} catch (error) {
|
||||
addLog(`刷新失败: ${error}`, 'error');
|
||||
addLog(`刷新失败: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 日志初始化标志
|
||||
let logInitialized = false;
|
||||
let logInitialized = false
|
||||
|
||||
// 日志管理配置
|
||||
const LOG_CONFIG = {
|
||||
MAX_LOGS: 1000, // 最大日志条数
|
||||
TRIM_COUNT: 200 // 超出时一次删除的条数
|
||||
};
|
||||
MAX_LOGS: 1000, // 最大日志条数
|
||||
TRIM_COUNT: 200, // 超出时一次删除的条数
|
||||
}
|
||||
|
||||
// 添加日志
|
||||
function addLog(message, type = 'info', highlight = false) {
|
||||
const logLine = document.createElement('div');
|
||||
logLine.className = `log-line log-${type} fade-in${highlight ? ' log-highlight' : ''}`;
|
||||
const logLine = document.createElement('div')
|
||||
logLine.className = `log-line log-${type} fade-in${highlight ? ' log-highlight' : ''}`
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
second: '2-digit',
|
||||
})
|
||||
|
||||
logLine.textContent = `[${timestamp}] ${message}`;
|
||||
logLine.textContent = `[${timestamp}] ${message}`
|
||||
|
||||
// 清除初始提示(只执行一次)
|
||||
if (!logInitialized) {
|
||||
logOutput.innerHTML = '';
|
||||
logInitialized = true;
|
||||
logOutput.innerHTML = ''
|
||||
logInitialized = true
|
||||
}
|
||||
|
||||
logOutput.appendChild(logLine);
|
||||
logOutput.appendChild(logLine)
|
||||
|
||||
// 日志数量管理:超过最大值时删除旧日志
|
||||
const logCount = logOutput.children.length;
|
||||
const logCount = logOutput.children.length
|
||||
if (logCount > LOG_CONFIG.MAX_LOGS) {
|
||||
// 批量删除旧日志以提升性能
|
||||
for (let i = 0; i < LOG_CONFIG.TRIM_COUNT; i++) {
|
||||
if (logOutput.firstChild) {
|
||||
logOutput.removeChild(logOutput.firstChild);
|
||||
logOutput.removeChild(logOutput.firstChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 requestAnimationFrame 优化滚动性能
|
||||
requestAnimationFrame(() => {
|
||||
logOutput.scrollTop = logOutput.scrollHeight;
|
||||
});
|
||||
logOutput.scrollTop = logOutput.scrollHeight
|
||||
})
|
||||
}
|
||||
|
||||
async function saveCurrentConfig() {
|
||||
@@ -612,33 +625,32 @@ async function saveCurrentConfig() {
|
||||
backup: backupCheckbox.checked,
|
||||
ultra_brute: ultraBruteCheckbox.checked,
|
||||
include_subfolders: includeSubfoldersCheckbox.checked,
|
||||
force_compress: forceCompressCheckbox.checked
|
||||
};
|
||||
force_compress: forceCompressCheckbox.checked,
|
||||
}
|
||||
|
||||
await invoke('save_config', { config });
|
||||
await invoke('save_config', { config })
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error);
|
||||
console.error('保存配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function applyConfigToUI(config) {
|
||||
compressionLevel.value = config.compression_level;
|
||||
overwriteCheckbox.checked = config.overwrite;
|
||||
backupCheckbox.checked = config.backup;
|
||||
ultraBruteCheckbox.checked = config.ultra_brute;
|
||||
includeSubfoldersCheckbox.checked = config.include_subfolders;
|
||||
forceCompressCheckbox.checked = config.force_compress;
|
||||
updateLevelDisplay(config.compression_level);
|
||||
compressionLevel.value = config.compression_level
|
||||
overwriteCheckbox.checked = config.overwrite
|
||||
backupCheckbox.checked = config.backup
|
||||
ultraBruteCheckbox.checked = config.ultra_brute
|
||||
includeSubfoldersCheckbox.checked = config.include_subfolders
|
||||
forceCompressCheckbox.checked = config.force_compress
|
||||
updateLevelDisplay(config.compression_level)
|
||||
}
|
||||
|
||||
async function loadSavedConfig() {
|
||||
try {
|
||||
const config = await invoke('load_config');
|
||||
applyConfigToUI(config);
|
||||
addLog('已加载上次保存的配置', 'info');
|
||||
const config = await invoke('load_config')
|
||||
applyConfigToUI(config)
|
||||
addLog('已加载上次保存的配置', 'info')
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
addLog('使用默认配置', 'info');
|
||||
console.error('加载配置失败:', error)
|
||||
addLog('使用默认配置', 'info')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user