执行摘要
Tauri v2 是一个基于 Rust 构建的跨平台桌面应用框架,它将 Web 前端技术与 Rust 后端相结合,提供了体积小、速度快、安全性高的应用开发解决方案。本文档收集并整理了 Tauri v2 的完整实战代码示例,涵盖项目初始化配置、Rust 命令定义、前端交互、事件系统、插件开发、窗口管理、文件系统操作以及 HTTP 请求处理等核心使用场景。所有示例均来源于官方文档、GitHub 仓库及经过验证的开发者教程,确保代码的正确性和可运行性。
一、项目初始化与配置
1.1 项目创建
Tauri v2 项目的创建可以通过多种包管理器完成。创建过程中会提示选择前端框架(如 Vue、React、Svelte 或纯 HTML)以及 TypeScript 支持选项。创建完成后,项目会包含前端源码目录和 src-tauri Rust 后端目录两个主要部分[1]。
使用 pnpm 创建项目的命令如下:
pnpm create tauri-app
其他包管理器的创建命令包括 npm create tauri-app@latest、yarn create tauri-app、cargo create-tauri-app 等[2]。
1.2 tauri.conf.json 基础配置
tauri.conf.json 是 Tauri 应用程序的核心配置文件,位于 src-tauri 目录中。以下是一个完整的基础配置示例:
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm build",
"devPath": "http://localhost:1420",
"distDir": "../dist",
"devtools": true
},
"package": {
"productName": "My Tauri App",
"version": "1.0.0"
},
"tauri": {
"windows": [
{
"title": "My Tauri Application",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false,
"decorations": true,
"center": true
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.myapp.example",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
该配置文件定义了构建命令、开发服务器路径、窗口属性以及打包设置。其中 devtools 选项在开发阶段应设置为 true 以便于调试[3]。
1.3 Cargo.toml 依赖配置
Rust 后端的依赖管理通过 src-tauri/Cargo.toml 文件完成。以下是集成常用插件的依赖配置:
[package]
name = "my-tauri-app"
version = "1.0.0"
description = "A Tauri Application"
authors = ["Developer"]
edition = "2021"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-fs = "2"
tauri-plugin-http = "2"
tauri-plugin-shell = "2"
tauri-plugin-os = "2"
tauri-plugin-process = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
Tauri v2 采用插件化架构,文件系统、HTTP 请求、Shell 命令等功能都需要通过相应的插件来实现[4]。
1.4 平台特定配置
Tauri 支持平台特定的配置文件,包括 tauri.linux.conf.json、tauri.windows.conf.json 和 tauri.macos.conf.json。这些文件会与主配置文件合并,允许开发者为不同平台定制配置[3]。
1.5 权限与能力系统
Tauri v2 引入了基于能力的权限管理系统。权限配置文件位于 src-tauri/capabilities/ 目录中,每个窗口可以拥有独立的能力配置。以下是一个完整的能力配置示例:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Main window capabilities",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:default",
"core:window:allow-minimize",
"core:window:allow-maximize",
"core:window:allow-close",
"core:window:allow-set-title",
"core:window:allow-start-dragging",
"fs:default",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
"http:default",
{
"identifier": "http:allow-fetch",
"allow": [
{ "url": "https://*.api.example.com" },
{ "url": "https://jsonplaceholder.typicode.com" }
]
},
"shell:allow-open",
"os:default"
]
}
权限系统支持精细的作用域控制,可以限制对特定路径或域名的访问。变量如 $HOME、$APPDATA、$APPCACHE 等可用于路径配置[5]。
二、Rust 后端命令定义
2.1 基本命令定义
Tauri 的命令系统允许前端 JavaScript 调用 Rust 函数。命令通过 #[tauri::command] 属性宏定义,并使用 #[tauri::command] 注解的函数必须放在 src-tauri/src/lib.rs 文件中(非 main.rs)[6]。
以下是最基本的命令定义示例:
#[tauri::command]
fn my_custom_command() {
println!("I was invoked from JavaScript!");
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
2.2 命令模块化
当命令较多时,建议将命令分离到单独的文件或模块中:
// src-tauri/src/commands.rs
#[tauri::command]
pub fn command_a() -> String {
"Command A result".into()
}
#[tauri::command]
pub fn command_b(value: String) -> Result<String, String> {
if value.is_empty() {
Err("Value cannot be empty".into())
} else {
Ok(format!("Received: {}", value))
}
}
// src-tauri/src/lib.rs
mod commands;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
commands::command_a,
commands::command_b
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
2.3 带参数的命令
命令可以接受参数,参数类型支持 Rust 的基本类型和实现了 serde::Deserialize 的自定义类型:
#[tauri::command]
fn greet(name: String, age: u32) -> String {
format!("Hello, {}! You are {} years old.", name, age)
}
// 使用 snake_case 重命名规则
#[tauri::command(rename_all = "snake_case")]
fn process_data(my_value: String, another_field: bool) -> String {
format!("Value: {}, Field: {}", my_value, another_field)
}
2.4 返回数据与错误处理
命令可以返回结果,也可以返回错误。以下是完整的错误处理模式:
use serde::Serialize;
// 基本错误处理
#[tauri::command]
fn login(user: String, password: String) -> Result<String, String> {
if user == "admin" && password == "admin123" {
Ok("login_success".to_string())
} else {
Err("Invalid credentials".to_string())
}
}
// 使用 thiserror 库定义自定义错误类型
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(String),
}
impl Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[tauri::command]
fn read_file_content(path: String) -> Result<String, AppError> {
std::fs::read_to_string(&path).map_err(AppError::from)
}
2.5 异步命令
异步命令使用 async fn 语法,需要注意借用规则:
use tokio::time::{sleep, Duration};
// 方式一:使用 String 替代 &str 避免借用问题
#[tauri::command]
async fn async_operation(value: String) -> String {
sleep(Duration::from_secs(1)).await;
format!("Processed: {}", value)
}
// 方式二:返回 Result
#[tauri::command]
async fn async_with_result(url: String) -> Result<String, String> {
// 模拟异步操作
sleep(Duration::from_millis(100)).await;
Ok(format!("Fetched from: {}", url))
}
2.6 访问状态管理
Tauri 提供状态管理功能,可以在应用生命周期内共享数据:
use std::sync::Mutex;
struct AppState {
counter: Mutex<i32>,
config: Mutex<AppConfig>,
}
#[derive(Debug, Clone, serde::Serialize)]
struct AppConfig {
theme: String,
language: String,
}
#[tauri::command]
fn increment_counter(state: tauri::State<'_, AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
#[tauri::command]
fn get_config(state: tauri::State<'_, AppState>) -> AppConfig {
state.config.lock().unwrap().clone()
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let app_state = AppState {
counter: Mutex::new(0),
config: Mutex::new(AppConfig {
theme: "dark".to_string(),
language: "en".to_string(),
}),
};
tauri::Builder::default()
.manage(app_state)
.invoke_handler(tauri::generate_handler![
increment_counter,
get_config
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
2.7 访问 WebviewWindow 和 AppHandle
命令可以接收 WebviewWindow 或 AppHandle 参数以执行窗口操作或访问应用级功能:
#[tauri::command]
async fn open_settings_window(app_handle: tauri::AppHandle) -> Result<(), String> {
use tauri::{WebviewUrl, WebviewWindowBuilder};
WebviewWindowBuilder::new(
&app_handle,
"settings",
WebviewUrl::App("settings.html".into()),
)
.title("Settings")
.inner_size(600.0, 400.0)
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
fn get_window_label(window: tauri::Window) -> String {
window.label().to_string()
}
#[tauri::command]
async fn send_notification(app_handle: tauri::AppHandle, title: String, body: String) {
use tauri::Manager;
if let Some(window) = app_handle.get_webview_window("main") {
let _ = window.emit("notification", serde_json::json!({
"title": title,
"body": body
}));
}
}
2.8 使用 Channel 进行流式数据传输
Channel 允许从 Rust 端向前端发送流式数据:
use tauri::{AppHandle, ipc::Channel};
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(tag = "event", content = "data")]
enum DownloadEvent {
Started { url: String, size: u64 },
Progress { bytes_downloaded: u64 },
Finished { success: bool },
}
#[tauri::command]
async fn download_file(
app: AppHandle,
url: String,
on_progress: Channel<DownloadEvent>,
) -> Result<(), String> {
let total_size: u64 = 1000;
on_progress
.send(DownloadEvent::Started {
url: url.clone(),
size: total_size,
})
.map_err(|e| e.to_string())?;
for i in (0..total_size).step_by(100) {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
on_progress
.send(DownloadEvent::Progress {
bytes_downloaded: i,
})
.map_err(|e| e.to_string())?;
}
on_progress
.send(DownloadEvent::Finished { success: true })
.map_err(|e| e.to_string())?;
Ok(())
}
三、前端调用 Rust 代码
3.1 使用 npm 包调用命令
Tauri 2.x 推荐使用 @tauri-apps/api 包中的 invoke 函数来调用 Rust 命令:
npm install @tauri-apps/api
基础调用方式如下:
import { invoke } from '@tauri-apps/api/core';
// 无参数命令
async function callBasicCommand() {
await invoke('my_custom_command');
}
// 带参数命令
async function callWithArgs() {
const result = await invoke('greet', { name: 'Alice', age: 30 });
console.log(result);
}
// 返回复杂对象
async function getData() {
const data = await invoke('get_config');
console.log(data.theme, data.language);
}
3.2 TypeScript 类型支持
可以为命令定义 TypeScript 类型以获得完整的类型提示:
interface CustomResponse {
message: string;
otherVal: number;
}
interface ErrorKind {
kind: 'io' | 'utf8';
message: string;
}
async function fetchData(): Promise<CustomResponse> {
try {
const response = await invoke<CustomResponse>('my_custom_command', {
number: 42,
});
return response;
} catch (error) {
const typedError = error as ErrorKind;
console.error(`Error (${typedError.kind}): ${typedError.message}`);
throw error;
}
}
3.3 错误处理
前端可以通过 Promise 的 catch 方法处理命令返回的错误:
async function login(username, password) {
try {
const result = await invoke('login', {
user: username,
password: password
});
console.log('Login result:', result);
return result;
} catch (error) {
console.error('Login failed:', error);
return null;
}
}
3.4 调用异步命令
异步命令的调用方式与普通命令相同,invoke 会自动等待异步操作完成:
async function performAsyncOperation() {
try {
const result = await invoke('async_operation', {
value: 'test data'
});
console.log('Async result:', result);
} catch (error) {
console.error('Async operation failed:', error);
}
}
3.5 使用 Global Tauri(可选)
如果不想使用 npm 包,可以在 tauri.conf.json 中启用全局 Tauri:
{
"tauri": {
"app": {
"withGlobalTauri": true
}
}
}
然后使用全局对象调用命令:
// 使用全局脚本
const invoke = window.__TAURI__.invoke;
await invoke('my_custom_command');
3.6 原始请求调用
对于需要发送原始字节数据的场景,Tauri 2 支持原始请求调用:
const data = new Uint8Array([1, 2, 3, 4, 5]);
await invoke('upload', data, {
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/octet-stream'
}
});
四、事件系统
4.1 从 Rust 发送全局事件
Tauri 的事件系统允许 Rust 后端向所有前端监听器广播消息:
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn download(app: AppHandle, url: String) {
// 发送初始事件
app.emit("download-started", &url).unwrap();
// 发送进度事件
for progress in [10, 30, 50, 70, 90, 100] {
app.emit("download-progress", progress).unwrap();
std::thread::sleep(std::time::Duration::from_millis(200));
}
// 发送完成事件
app.emit("download-finished", &url).unwrap();
}
4.2 发送事件到特定 Webview
使用 emit_to 方法可以将事件发送到特定窗口:
use tauri::{AppHandle, Emitter};
#[tauri::command]
fn login(app: AppHandle, user: String, password: String) {
let result = if user == "admin" && password == "admin" {
"success"
} else {
"failed"
};
// 只向 login 窗口发送结果
app.emit_to("login", "login-result", result).unwrap();
}
4.3 使用事件过滤器
emit_filter 允许根据条件选择目标窗口:
use tauri::{AppHandle, Emitter, EventTarget};
#[tauri::command]
fn broadcast_to_main(app: AppHandle, message: String) {
app.emit_filter("broadcast", message, |target| {
match target {
EventTarget::WebviewWindow { label } => {
label == "main" || label == "dashboard"
},
_ => false,
}
}).unwrap();
}
4.4 前端监听全局事件
JavaScript 端使用 listen 函数监听事件:
import { listen } from '@tauri-apps/api/event';
interface DownloadStarted {
url: string;
downloadId: number;
contentLength: number;
}
// 监听下载开始事件
const unlisten = await listen<DownloadStarted>('download-started', (event) => {
console.log(`开始下载: ${event.payload.url}`);
console.log(`文件大小: ${event.payload.contentLength} bytes`);
});
// 监听进度更新
await listen<number>('download-progress', (event) => {
updateProgressBar(event.payload);
});
// 监听下载完成
await listen<string>('download-finished', (event) => {
console.log(`下载完成: ${event.payload}`);
showNotification('Download complete!');
});
// 停止监听
unlisten();
4.5 前端监听特定窗口事件
获取特定窗口实例并监听其事件:
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
const appWindow = getCurrentWebviewWindow();
// 监听来自该窗口的事件
appWindow.listen<string>('login-result', (event) => {
if (event.payload === 'success') {
navigateToDashboard();
} else {
showError('Login failed');
}
});
4.6 监听一次事件
使用 once 函数监听仅触发一次的事件:
import { once } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
// 全局事件监听一次
once('app-ready', (event) => {
console.log('App is ready!');
});
// 特定窗口事件监听一次
const appWindow = getCurrentWebviewWindow();
appWindow.once('initial-data-loaded', () => {
renderApp();
});
4.7 从 Rust 端监听事件
在 Rust 端也可以监听前端发送的事件:
use tauri::{Listener, Manager};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
// 监听全局事件
app.listen("my-event", |event| {
println!("Received global event: {:?}", event.payload());
});
// 监听特定窗口事件
if let Some(window) = app.get_webview_window("main") {
window.listen("frontend-event", |event| {
println!("Frontend event: {:?}", event.payload());
});
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
4.8 停止监听
可以通过 unlisten 函数停止事件监听:
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('status-update', (event) => {
console.log('Status:', event.payload);
});
// 稍后停止监听
setTimeout(() => {
unlisten();
}, 5000);
五、插件开发
5.1 插件项目结构
Tauri 插件拥有独立的目录结构,包含 Rust 实现和 JavaScript API:
tauri-plugin-[name]/
├── src/
│ ├── lib.rs # 插件入口和初始化
│ ├── commands.rs # 命令定义
│ ├── desktop.rs # 桌面平台实现
│ ├── mobile.rs # 移动平台实现
│ ├── error.rs # 错误类型定义
│ └── models.rs # 共享数据结构
├── permissions/ # 权限定义文件
├── guest-js/ # JavaScript API 源码
├── dist-js/ # 编译后的 JavaScript
├── android/ # Android 原生代码
├── ios/ # iOS 原生代码
├── Cargo.toml
└── package.json
5.2 插件初始化
插件通过 Builder 模式初始化:
// src/lib.rs
use tauri::plugin::{Builder, Runtime, TauriPlugin};
use serde::Deserialize;
#[derive(Deserialize)]
struct PluginConfig {
timeout: usize,
}
pub fn init<R: Runtime>() -> TauriPlugin<R, PluginConfig> {
Builder::<R, PluginConfig>::new("my-plugin")
.setup(|app, api| {
let timeout = api.config().timeout;
println!("Plugin initialized with timeout: {}", timeout);
Ok(())
})
.build()
}
5.3 生命周期钩子
插件支持多种生命周期钩子:
use tauri::{Manager, plugin::Builder, RunEvent};
Builder::new("my-plugin")
// 插件初始化
.setup(|app, _api| {
app.manage(MyState::default());
Ok(())
})
// WebView 导航拦截
.on_navigation(|window, url| {
println!("Window {} navigating to {}", window.label(), url);
url.scheme() != "blocked" // 返回 false 阻止导航
})
// WebView 创建完成
.on_webview_ready(|window| {
window.listen("content-loaded", |_| {
println!("Content loaded in window");
});
})
// 事件循环事件
.on_event(|app, event| {
match event {
RunEvent::ExitRequested { api, .. } => {
// 阻止退出
api.prevent_exit();
}
RunEvent::Exit => {
// 清理资源
println!("App is exiting");
}
_ => {}
}
})
// 插件销毁
.on_drop(|app| {
println!("Plugin destroyed");
})
5.4 定义命令
在插件中定义可从调用的命令:
// src/commands.rs
use tauri::{command, AppHandle, Runtime, Window, ipc::Channel};
#[command]
pub async fn start_server<R: Runtime>(
app: AppHandle<R>,
port: u16,
on_event: Channel,
) -> Result<(), String> {
// 实现服务器启动逻辑
on_event.send("server-started").map_err(|e| e.to_string())?;
// 发送定期心跳
for i in 0..10 {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
on_event.send(format!("heartbeat-{}", i)).map_err(|e| e.to_string())?;
}
Ok(())
}
5.5 注册命令和 JavaScript API
// src/lib.rs
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::<R>::new("my-plugin")
.invoke_handler(tauri::generate_handler![
commands::start_server
])
.build()
}
// guest-js/index.ts
import { invoke, Channel } from '@tauri-apps/api/core';
export async function startServer(
port: number,
onEventHandler: (event: string) => void
): Promise<void> {
const onEvent = new Channel<string>();
onEvent.onmessage = onEventHandler;
await invoke('plugin:my-plugin|start_server', { port, onEvent });
}
5.6 权限定义
插件通过 TOML 文件定义权限:
# permissions/start-server.toml
"$schema" = "../gen/schemas/schema.json"
[[permission]]
identifier = "allow-start-server"
description = "Enables the start_server command"
commands.allow = ["start_server"]
[[permission]]
identifier = "deny-start-server"
description = "Denies the start_server command"
commands.deny = ["start_server"]
作用域权限定义:
# permissions/spawn-process.toml
[[permission]]
identifier = "allow-spawn"
description = "Allows spawning processes"
[[permission.scope.allow]]
binary = "node"
args = ["--version"]
[[permission.scope.deny]]
binary = "rm"
5.7 在应用中使用插件
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(my_plugin::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
5.8 自动生成权限
通过构建脚本自动生成权限:
// build.rs
const COMMANDS: &[&str] = &["upload", "download"];
fn main() {
tauri_plugin::Builder::new(COMMANDS).build();
}
这会自动生成 allow-upload、deny-upload、allow-download、deny-download 等权限。
六、窗口管理
6.1 窗口配置
通过 tauri.conf.json 配置窗口属性:
{
"tauri": {
"windows": [
{
"label": "main",
"title": "My Application",
"width": 1024,
"height": 768,
"minWidth": 640,
"minHeight": 480,
"resizable": true,
"fullscreen": false,
"decorations": true,
"transparent": false,
"center": true,
"x": 100,
"y": 100,
"visible": true,
"focus": true
}
]
}
}
6.2 运行时创建窗口
使用 WebviewWindowBuilder 在运行时创建新窗口:
use tauri::{WebviewUrl, WebviewWindowBuilder, Manager};
#[tauri::command]
async fn open_settings(app: tauri::AppHandle) -> Result<(), String> {
WebviewWindowBuilder::new(
&app,
"settings", // 唯一标签
WebviewUrl::App("settings.html".into()),
)
.title("Settings")
.inner_size(600.0, 400.0)
.resizable(true)
.center()
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
6.3 自定义标题栏
实现自定义标题栏需要禁用原生窗口装饰:
{
"tauri": {
"windows": [
{
"decorations": false,
"transparent": true
}
]
}
}
HTML 结构:
<div class="titlebar" data-tauri-drag-region>
<div class="title">My App</div>
<div class="controls">
<button id="btn-minimize">
<svg><!-- 最小化图标 --></svg>
</button>
<button id="btn-maximize">
<svg><!-- 最大化图标 --></svg>
</button>
<button id="btn-close">
<svg><!-- 关闭图标 --></svg>
</button>
</div>
</div>
CSS 样式:
.titlebar {
height: 32px;
background: #2d3748;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
}
.titlebar button {
width: 46px;
height: 32px;
border: none;
background: transparent;
color: white;
cursor: pointer;
}
.titlebar button:hover {
background: rgba(255, 255, 255, 0.1);
}
[data-tauri-drag-region] {
flex: 1;
height: 100%;
cursor: move;
}
JavaScript 控制逻辑:
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
document.getElementById('btn-minimize')?.addEventListener('click', () => {
appWindow.minimize();
});
document.getElementById('btn-maximize')?.addEventListener('click', async () => {
const isMaximized = await appWindow.isMaximized();
if (isMaximized) {
appWindow.unmaximize();
} else {
appWindow.maximize();
}
});
document.getElementById('btn-close')?.addEventListener('click', () => {
appWindow.close();
});
6.4 窗口操作
完整的窗口操作 API:
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
// 最小化
await appWindow.minimize();
// 最大化
await appWindow.maximize();
// 恢复窗口
await appWindow.unmaximize();
// 关闭窗口
await appWindow.close();
// 设置窗口标题
await appWindow.setTitle('New Title');
// 获取窗口是否最大化
const isMax = await appWindow.isMaximized();
// 获取窗口是否最小化
const isMin = await appWindow.isMinimized();
// 设置焦点
await appWindow.setFocus();
// 设置全屏
await appWindow.setFullscreen(true);
// 检查是否全屏
const isFullscreen = await appWindow.isFullscreen();
// 设置窗口始终在最前
await appWindow.setAlwaysOnTop(true);
// 开始拖动窗口
await appWindow.startDragging();
6.5 多窗口管理
创建和管理多个窗口:
use tauri::{AppHandle, Manager, Emitter};
#[tauri::command]
async fn open_window(app: AppHandle, label: String, url: String) -> Result<(), String> {
if app.get_webview_window(&label).is_some() {
// 窗口已存在,聚焦
if let Some(window) = app.get_webview_window(&label) {
window.set_focus().map_err(|e| e.to_string())?;
}
return Ok(());
}
// 创建新窗口
tauri::WebviewWindowBuilder::new(&app, &label, url.parse().unwrap())
.title(&label)
.inner_size(800.0, 600.0)
.center()
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
async fn close_window(app: AppHandle, label: String) -> Result<(), String> {
if let Some(window) = app.get_webview_window(&label) {
window.close().map_err(|e| e.to_string())?;
}
Ok(())
}
#[tauri::command]
async fn list_windows(app: AppHandle) -> Vec<String> {
app.webview_windows()
.keys()
.cloned()
.collect()
}
6.6 窗口事件监听
监听窗口状态变化:
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
// 监听窗口大小变化
appWindow.onResized(async ({ payload }) => {
const size = await appWindow.innerSize();
console.log(`Window resized to ${size.width}x${size.height}`);
});
// 监听窗口移动
appWindow.onMoved(async ({ payload }) => {
const position = await appWindow.outerPosition();
console.log(`Window moved to ${position.x}, ${position.y}`);
});
// 监听窗口关闭请求
appWindow.onCloseRequested(async (event) => {
const shouldClose = confirm('Are you sure you want to close?');
if (!shouldClose) {
event.preventClose();
}
});
// 监听窗口焦点变化
appWindow.onFocusChanged(({ payload: focused }) => {
console.log(`Window focus: ${focused}`);
});
6.7 macOS 透明标题栏
针对 macOS 平台的特殊配置:
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let win = WebviewWindowBuilder::new(
app,
"main",
WebviewUrl::default(),
)
.title("My App")
.inner_size(800.0, 600.0);
#[cfg(target_os = "macos")]
let win = win.title_bar_style(TitleBarStyle::Transparent);
win.build().unwrap();
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
七、文件系统操作
7.1 插件安装与初始化
Tauri 的文件系统功能通过 tauri-plugin-fs 提供:
cargo add tauri-plugin-fs
在 Rust 端初始化:
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
7.2 权限配置
在 capabilities 中配置文件系统权限:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "fs-capability",
"windows": ["main"],
"permissions": [
"fs:default",
{
"identifier": "fs:allow-read-text-file",
"allow": [
{ "path": "$APPDATA/*" },
{ "path": "$HOME/test.txt" }
]
},
{
"identifier": "fs:allow-write-text-file",
"allow": [
{ "path": "$APPDATA" },
{ "path": "$APPDATA/**" }
]
}
]
}
7.3 读取文本文件
import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
// 使用基础目录读取
async function loadConfig() {
const content = await readTextFile('config.json', {
baseDir: BaseDirectory.AppConfig,
});
return JSON.parse(content);
}
// 使用路径 API 组合完整路径
import { homeDir, join } from '@tauri-apps/plugin-path';
async function readUserFile() {
const home = await homeDir();
const filePath = await join(home, 'documents', 'notes.txt');
const content = await readTextFile(filePath);
return content;
}
7.4 写入文本文件
import { writeTextFile, BaseDirectory, mkdir, exists } from '@tauri-apps/plugin-fs';
async function saveData(data) {
const dir = 'myapp';
const file = 'data.json';
// 确保目录存在
if (!(await exists(dir, { baseDir: BaseDirectory.AppData }))) {
await mkdir(dir, { baseDir: BaseDirectory.AppData, recursive: true });
}
// 写入文件
await writeTextFile(
`${dir}/${file}`,
JSON.stringify(data, null, 2),
{ baseDir: BaseDirectory.AppData }
);
}
7.5 二进制文件操作
import { readFile, writeFile, BaseDirectory } from '@tauri-apps/plugin-fs';
// 读取二进制文件(如图片)
async function loadImage() {
const bytes = await readFile('icon.png', {
baseDir: BaseDirectory.Resource,
});
return bytes;
}
// 写入二进制文件
async function saveImage(data) {
const bytes = new Uint8Array(data);
await writeFile('output.png', bytes, {
baseDir: BaseDirectory.Picture,
});
}
7.6 目录操作
import {
readDir,
mkdir,
remove,
exists,
BaseDirectory,
} from '@tauri-apps/plugin-fs';
// 读取目录
async function listDirectory() {
const entries = await readDir('projects', {
baseDir: BaseDirectory.Home,
});
for (const entry of entries) {
console.log(`${entry.isDirectory ? '[DIR]' : '[FILE]'} ${entry.name}`);
}
}
// 创建目录
async function createProject(name) {
const projectPath = `projects/${name}`;
if (!(await exists(projectPath, { baseDir: BaseDirectory.Home }))) {
await mkdir(projectPath, {
baseDir: BaseDirectory.Home,
recursive: true,
});
}
}
// 删除目录
async function deleteProject(name) {
const projectPath = `projects/${name}`;
if (await exists(projectPath, { baseDir: BaseDirectory.Home })) {
await remove(projectPath, {
baseDir: BaseDirectory.Home,
recursive: true,
});
}
}
7.7 文件操作
import {
copyFile,
rename,
truncate,
stat,
exists,
remove,
BaseDirectory,
} from '@tauri-apps/plugin-fs';
// 复制文件
async function backupFile() {
await copyFile('data.db', 'data.db.bak', {
fromPathBaseDir: BaseDirectory.AppData,
toPathBaseDir: BaseDirectory.AppData,
});
}
// 重命名文件
async function renameFile(oldName, newName) {
await rename(oldName, newName, {
fromPathBaseDir: BaseDirectory.AppData,
toPathBaseDir: BaseDirectory.AppData,
});
}
// 获取文件元数据
async function getFileInfo(filename) {
const metadata = await stat(filename, {
baseDir: BaseDirectory.AppData,
});
console.log('Size:', metadata.size);
console.log('Created:', metadata.created);
console.log('Modified:', metadata.modified);
console.log('Is Directory:', metadata.isDirectory);
}
// 截断文件
async function clearLogFile() {
await truncate('app.log', 0, {
baseDir: BaseDirectory.AppLog,
});
}
// 删除文件
async function deleteFile(filename) {
if (await exists(filename, { baseDir: BaseDirectory.AppData })) {
await remove(filename, {
baseDir: BaseDirectory.AppData,
});
}
}
7.8 监视文件变化
需要启用 watch 功能:
# Cargo.toml
[dependencies]
tauri-plugin-fs = { version = "2", features = ["watch"] }
import { watch, watchImmediate, BaseDirectory } from '@tauri-apps/plugin-fs';
// 防抖监视(适合文件编辑场景)
async function watchConfigFile() {
await watch(
'config.json',
(event) => {
console.log('Event type:', event.type);
console.log('Paths:', event.paths);
},
{
baseDir: BaseDirectory.AppConfig,
delayMs: 500,
}
);
}
// 即时监视(适合日志文件等实时更新场景)
async function watchLogFile() {
await watchImmediate(
'app.log',
(event) => {
console.log('Log file changed:', event);
},
{
baseDir: BaseDirectory.AppLog,
recursive: true,
}
);
}
7.9 使用 open 和 create 函数
import { open, create, BaseDirectory } from '@tauri-apps/plugin-fs';
// 以写模式打开文件
async function appendToFile() {
const file = await open('notes.txt', {
write: true,
append: true,
baseDir: BaseDirectory.Document,
});
await file.write(new TextEncoder().encode('New content\n'));
await file.close();
}
// 使用 create 创建新文件
async function createNewFile() {
const file = await create('newfile.txt', {
baseDir: BaseDirectory.Desktop,
});
await file.write(new TextEncoder().encode('Hello, World!'));
await file.close();
}
// 以只读模式打开并读取
async function readFileWithOpen() {
const file = await open('report.pdf', {
read: true,
baseDir: BaseDirectory.Document,
});
const stat = await file.stat();
const buffer = new Uint8Array(stat.size);
await file.read(buffer);
await file.close();
return buffer;
}
八、HTTP 请求处理
8.1 插件安装与初始化
HTTP 功能通过 tauri-plugin-http 提供:
cargo add tauri-plugin-http
Rust 端初始化:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_http::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
安装 JavaScript 包:
npm install @tauri-apps/plugin-http
8.2 权限配置
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "http-capability",
"windows": ["main"],
"permissions": [
"http:default",
{
"identifier": "http:allow-fetch",
"allow": [
{ "url": "https://api.example.com" },
{ "url": "https://*.github.com" },
{ "url": "https://jsonplaceholder.typicode.com" }
]
}
]
}
8.3 基础 GET 请求
import { fetch } from '@tauri-apps/plugin-http';
async function getUsers() {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'GET',
});
if (response.ok) {
const users = await response.json();
return users;
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
8.4 POST 请求
async function createUser(userData) {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
return await response.json();
}
8.5 带认证的请求
async function fetchWithAuth() {
const response = await fetch('https://api.example.com/protected', {
method: 'GET',
headers: {
'Authorization': 'Bearer your-token-here',
'Accept': 'application/json',
},
});
if (response.status === 401) {
// Token 过期,需要刷新
await refreshToken();
return fetchWithAuth();
}
return await response.json();
}
8.6 处理不同响应类型
async function handleVariousResponses() {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
});
const contentType = response.headers.get('content-type');
if (contentType.includes('application/json')) {
return await response.json();
} else if (contentType.includes('text/')) {
return await response.text();
} else if (contentType.includes('application/octet-stream')) {
return await response.arrayBuffer();
} else {
// 处理其他类型
return await response.blob();
}
}
8.7 文件上传
async function uploadFile(file, uploadUrl) {
const formData = new FormData();
formData.append('file', file);
formData.append('filename', file.name);
const response = await fetch(uploadUrl, {
method: 'POST',
body: formData,
});
return await response.json();
}
8.8 Rust 端使用 reqwest
在 Rust 端可以直接使用 reqwest 库:
use tauri_plugin_http::reqwest;
#[tauri::command]
async fn fetch_api_data() -> Result<String, String> {
let response = reqwest::get("https://api.example.com/data")
.await
.map_err(|e| e.to_string())?;
let text = response.text()
.await
.map_err(|e| e.to_string())?;
Ok(text)
}
8.9 处理网络错误
async function robustFetch(url, options = {}) {
const maxRetries = 3;
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
lastError = error;
console.warn(`Attempt ${i + 1} failed:`, error);
if (i < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, 1000 * (i + 1))
);
}
}
}
throw new Error(`All attempts failed: ${lastError.message}`);
}
8.10 启用不安全请求头
某些请求头(如 Authorization)在默认情况下被禁止。如需启用,需要在 Cargo.toml 中启用 unsafe-headers 特性:
[dependencies]
tauri-plugin-http = { version = "2", features = ["unsafe-headers"] }
九、总结与最佳实践
9.1 项目结构建议
一个成熟的 Tauri v2 项目应该采用清晰的模块化结构。前端代码组织应遵循所选框架的最佳实践,Rust 后端建议按功能模块划分,将命令、事件处理、插件初始化等分离到独立文件中。
9.2 安全考虑
Tauri v2 的权限系统是保障应用安全的关键。开发者应遵循最小权限原则,仅授予应用实际需要的权限。路径访问应限制在必要的目录范围内,网络请求应明确限定允许的域名列表。所有用户输入都应在 Rust 后端进行验证和清理。
9.3 性能优化建议
异步命令应优先使用 async fn 语法以避免阻塞事件循环。大量数据传输推荐使用 Channel 而非直接返回值。文件操作应考虑批量处理以减少 I/O 次数。窗口创建和销毁应谨慎管理,避免频繁创建销毁导致的资源消耗。
9.4 跨平台兼容性
虽然 Tauri 抽象了大部分平台差异,但某些功能(如透明标题栏、文件路径格式)仍需要平台特定处理。建议使用条件编译处理平台相关代码,并通过检测运行时环境提供合适的回退方案。