为什么选择 Tauri × Rust × Vue
Nidalee 的愿景是「高性能、体积小、原生感、安全合规」。Tauri 2 提供跨平台外壳与系统集成,Rust 负责安全高效的本地服务,Vue 3 则让界面表达与迭代更敏捷。
总览架构
- 后端(
src-tauri/):- 以 LCU(League Client API)为核心的模块化服务:
auth/、champ_select/、gameflow/、matches/、perks/、ranked/、summoner/等。 - 每个域包含
commands.rs(暴露 IPC)、service.rs(业务逻辑)、mod.rs(聚合入口),形成清晰的「命令网关 → 领域服务」分层。 - 统一
request.rs与unified_polling.rs:降低重复、控制轮询策略与并发。
- 以 LCU(League Client API)为核心的模块化服务:
- 前端(
src/):- 组合式
composables/(按域):连接、对战流程、选人会话、OP.GG、游戏资源、统一日志等;小而专一、可独立测试。 - 状态(
stores/):核心/功能/UI 三层命名空间;配合pinia-plugin-persistedstate保留关键设置。 - UI:
shadcn-vue+ Tailwind + 自定义主题(OKLCH);复用通用组件与features/功能组件。
- 组合式
Rust 侧领域划分(精简示意):
text
src-tauri/src/lcu/
├── auth/{commands.rs, service.rs}
├── champ_select/{commands.rs, service.rs}
├── gameflow/{commands.rs, service.rs}
├── matches/{commands.rs, service.rs}
├── perks/{commands.rs, service.rs}
├── ranked/{commands.rs, service.rs}
├── summoner/{commands.rs, service.rs}
├── request.rs
└── unified_polling.rs
典型 IPC → 服务的调用链(示例):
rust
// commands.rs
#[tauri::command]
pub async fn get_summoner_summary(app_state: State<'_, AppState>) -> Result<SummonerDto, AppError> {
summoner_service::summary(&app_state.client).await
}
// service.rs
pub async fn summary(client: &LcuClient) -> Result<SummonerDto, AppError> {
let data = client.get("/lol-summoner/v1/current-summoner").await?;
Ok(data)
}
数据流与边界
- IPC 命令仅承载「数据输入/输出」与「限流/鉴权/错误映射」,领域逻辑留在服务层,UI 通过
@tanstack/vue-query管理请求与缓存,useCachedQuery封装了稳定 key 与缓存策略。 - 连接层提供「客户端心跳 + 重连 + 状态广播」,业务只关心「可用与否」而非具体重试细节。
前端以组合式能力封装请求与缓存(示例):
ts
// useSummonerAndMatchUpdater.ts(节选)
import { useQuery } from '@tanstack/vue-query'
export function useSummoner() {
return useQuery({
queryKey: ['summoner'],
queryFn: () => invoke<SummonerDto>('get_summoner_summary'),
staleTime: 60_000,
})
}
生命周期与并发模型:顺畅但可控
- Rust 端:
- 任务使用
tokio驱动;长生命周期的轮询放在tauri::async_runtime::spawn里,使用broadcast通道将事件扇出。 - 资源清理通过
Drop与select!+shutdown信号控制;显式中止任务,避免孤儿任务。
- 任务使用
- 前端:
- 组合式函数
onMounted建立订阅,onUnmounted取消订阅与定时器。 - 多请求并发时以
Promise.allSettled聚合,避免“栅栏效应”;失败个体写入活动日志而不中断整体流程。
- 组合式函数
错误边界与回退:故障可预期、体验可恢复
- IPC 命令返回统一
AppError,前端展示“非阻塞型错误提示”(如 toast),并提供重试。 - 关键路径(如“自动接受匹配”)提供「状态机」式回退:失败后回到可恢复状态,不致于悬挂。
工程化与性能
- 构建拆包(
vite.config.ts→manualChunks):vendor_vue、vendor_pinia、vendor_tanstack、vendor_tailwind、vendor,提高缓存命中与首包稳定性。
- 插件体系:
- 开发:
vite-plugin-vue-devtools、unplugin-auto-import、unplugin-vue-components。 - 生产:
vite-plugin-remove-console、rollup-plugin-visualizer(可选分析)。
- 开发:
- 规范:
- Lint:
oxlint+eslint,格式化prettier;类型边界通过vue-tsc与 Rust 编译器共同保证。 - 前后端类型对齐:
scripts/sync-types.mjs+src-tauri单测输出类型,再同步到前端types/。
- Lint:
多主题与动画策略:高对比、轻干扰
- UI 主题采用 OKLCH 变量方案(
styles/theme-oklch.css),在深浅主题下保持可读性与对比度; - 动画使用
motion-v与轻量化自定义动画工具类,限制在 200–300ms 内,优先“位移动画”,减少重排成本。
功能域拆分的收益
- 变更范围可控:新功能落在单一域(如
game-helper/),修改对其它域影响最小。 - 可观测性:统一的
useActivityLogger记录关键步骤,可挂接 UI 活动面板与日志导出。 - 可测试:域服务可通过命令行/单测注入假数据;前端
composables易于 stub。
安全与合规
- 与官方 LCU API 交互,不注入、不修改内存,不劫持网络,避免外挂风险。
- 敏感信息不持久化,遵循用户协议;提供显式免责声明与非商用 License。
收官
Nidalee 的关键在于「清晰的领域边界 × 统一的数据契约 × 稳健的工程化」。桌面应用也可以拥有现代 Web 的开发效率,同时不妥协于性能与稳定。
进一步阅读:
- 工程化细节与最佳实践:/blog/nidalee-best-practices-engineering