代理修改

This commit is contained in:
2026-07-18 17:46:21 +08:00
parent 2d9a8ddef6
commit e84958e0fc
71 changed files with 1221 additions and 134 deletions
+133
View File
@@ -357,3 +357,136 @@ appStore.toggleModule(moduleId, enabled)
- **瀑布流布局避免卡片等高撑开**:使用 `columns-1 md:columns-2 gap-4 [&>*]:mb-4 [&>*]:break-inside-avoid` 替代 `grid`,让卡片按内容高度自然排列。
- **模块禁用清理系统状态**:模块 `onDisable` 钩子应清理系统级副作用(如系统代理注册表项),避免模块停用后遗留导致系统异常。
- **应用退出清理**:在 `lib.rs``quit_app` 命令和托盘退出事件中都要调用 `cleanup_on_exit` + `stop_all`,确保任何退出路径都清理干净。
## 近期重要变更(影响其他模块开发)
以下是最近几次改动中确立的约定和模式,开发新模块时需遵循。
### 后端:子进程启动必须隐藏控制台窗口
`process_manager.rs` 暴露了公共函数 `setup_creation_flags(cmd: &mut Command)`Windows 上会设置 `CREATE_NO_WINDOW` 标志。
**所有用 `std::process::Command` 启动外部程序的地方都必须调用它**,否则会弹出黑色控制台窗口(即使程序是后台运行)。包括:
- `ProcessManager::start()` 启动 mihomo 等内核
- `ProcessManager::check_and_cleanup()` 崩溃重启
- 任何调用 `mihomo.exe -v``aria2c --version` 等查询版本的场景
- 未来下载器模块启动 aria2 时同样适用
跨平台:非 Windows 平台该函数为空实现,无需条件编译。
```rust
use crate::process_manager::setup_creation_flags;
let mut cmd = std::process::Command::new(&path);
cmd.arg("-v");
setup_creation_flags(&mut cmd); // 必须调用
cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
```
### 后端:长时间 wait() 不要阻塞主线程
`ProcessManager::stop()``child.kill()` 是快速的,但 `child.wait()` 可能阻塞几十到几百毫秒。**wait() 已移到后台线程**
```rust
thread::spawn(move || {
let _ = entry.child.wait();
});
```
新模块若有类似的"终止外部进程"逻辑,也应遵循此模式,避免 Tauri 命令阻塞导致前端卡顿。
### 后端:内核/二进制下载用流式 + 事件推送
`mihomo_manager.rs``install_kernel` 确立了"下载二进制资源"的标准模式,未来下载器模块下载 aria2 内核时应复用:
- **流式下载**`reqwest::Response::bytes_stream()` + `futures_util::StreamExt`,避免大文件一次性读入内存
- **进度事件**:通过 `app.emit("xxx-install-progress", progress)` 推送,事件载荷结构参考 `InstallProgress`
- **事件节流**:仅在百分比变化 ≥1% 时 emit,避免事件轰炸
- **zip 解压**:用 `zip` crate(纯 Rust),不要用 PowerShell `Expand-Archive`(有执行策略问题)
- **文件名匹配**:解压后用 `find_exe_in_dir` 查找 `.exe`(zip 内文件名可能含版本号,不是固定名字),找到后重命名为标准名
### 后端:GitHub API rate limit 规避
`check_kernel_update` 采用 **API 优先 + 重定向回退** 策略:
1. 先调 `api.github.com/.../releases/latest`(能拿完整资产列表,命名变化时更健壮)
2. 失败(403 rate limit / 网络错误)时回退到访问 `github.com/.../releases/latest`,从 302 重定向的最终 URL 提取版本号,按稳定命名规则构造下载 URL
**新模块若需要查 GitHub 最新版本,应复用此模式**,不要直接调 API(未认证 60次/小时/IP 极易超限)。
### 后端:资产命名规则适配
mihomo v1.19+ 改了 Windows 资产命名,按 CPU 微架构分级:
- 旧:`mihomo-windows-amd64-vX.X.X.zip`(已废弃)
- 新:`mihomo-windows-amd64-v3-vX.X.X.zip`v1/v2/v3 对应 CPU level
`fetch_latest_via_api` 中的匹配优先级:v3 标准 > v3-go124 > v3-go123 > v3 其他 > v2 > v1 > 旧命名。若未来其他内核也有类似分级,参考此优先级策略。
### 前端:Transition 内的 v-if/v-else 必须加 key
**这是 dev 模式的坑**`<Transition mode="out-in">` 内的 `v-if`/`v-else` 分支如果缺 `:key`Vue 3.5.x dev 模式下会触发 `__vnode` 写入竞态,导致:
- 控制台报错 `Cannot set properties of null (setting '__vnode')`
- vnode 树损坏,所有事件派发失效(按钮点击没反应)
- **build 模式不报错**(生产构建剥除了 `__vnode` instrumentation),容易漏掉
**约定**`<Transition>` 内所有分支(v-if/v-else-if/v-else)都必须加 `:key`,即使是原生 div 也要加。
```vue
<Transition name="fade" mode="out-in">
<ComponentA v-if="cond" key="a" />
<div v-else key="empty">占位</div>
</Transition>
```
`ModuleContainer.vue``ProxyModule.vue` 的 Progress 区块已修复,新模块开发时注意。
### 前端:模块内 Tabs 顶部固定模式
模块根容器用 `h-full overflow-hidden flex flex-col`Tabs 用 `flex-1 min-h-0 flex flex-col`TabsList 加 `shrink-0`TabsContent 加 `flex-1 min-h-0 overflow-y-auto`
```vue
<div class="h-full p-6 overflow-hidden flex flex-col">
<Tabs v-model="tab" class="flex-1 min-h-0 flex flex-col">
<TabsList class="shrink-0">...</TabsList>
<TabsContent value="x" class="flex-1 min-h-0 overflow-y-auto">...</TabsContent>
</Tabs>
</div>
```
这样 TabsList 固定在顶部,只有 TabsContent 滚动。`min-h-0` 是 flex 子元素 overflow 生效的关键,不能省。
### 前端:Tauri 事件监听需在 store 中管理生命周期
`proxyStore.ts``installKernel` 确立了模式:
- 监听在方法调用时注册,`finally` 块中取消
- 用模块级变量保存 `UnlistenFn`,避免重复注册
- 错误事件由后端保证 emit(前端不重复弹 toast,统一由 watch 处理)
```typescript
let progressUnlisten: UnlistenFn | null = null
const installKernel = async () => {
if (!progressUnlisten) {
progressUnlisten = await listen<Progress>('xxx-progress', (e) => {
progress.value = e.payload
})
}
try {
await invoke('xxx_command')
} finally {
if (progressUnlisten) {
progressUnlisten()
progressUnlisten = null
}
}
}
```
### 前端:窗口隐藏前 blur 焦点
TitleBar 的关闭按钮实际是 `hide()` 到托盘。webview 快速隐藏时浏览器 `mouseleave` 可能不触发,导致从托盘恢复后按钮仍显示 hover 高亮。
**修复**`hide()` 前调用 `document.activeElement.blur()`,并监听 `onFocusChanged` 在窗口重新获得焦点时再 blur 一次。新模块若有类似的"隐藏窗口"操作(如全局快捷键隐藏),同样需要 blur。