为什么选择 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