--- url: https://v2.tauri.app/ description: Tauri 是一个用于构建跨平台桌面和移动应用的框架。它使用 Web 技术构建前端界面,使用 Rust 构建后端逻辑,生成体积小、性能高的原生应用程序。支持 Windows、macOS、Linux、iOS 和 Android 平台。 --- # Tauri v2 完整文档 ## 简介 Tauri 是一个用于构建跨平台应用程序的现代框架,主要特点: - **跨平台支持**: Windows、macOS、Linux、iOS、Android - **体积小**: 使用系统 WebView,应用体积可小至几 MB - **高性能**: Rust 后端提供原生性能 - **安全性**: 基于权限的安全模型,最小化攻击面 - **灵活前端**: 支持任何前端框架 (React, Vue, Svelte, Solid 等) - **丰富插件**: 33+ 官方插件覆盖常见功能 ## 快速开始 ### 创建新项目 ```bash # 使用 npm npm create tauri-app@latest # 使用 pnpm pnpm create tauri-app # 使用 yarn yarn create tauri-app # 使用 cargo cargo create-tauri-app # 使用 shell (Linux/macOS) sh <(curl https://create.tauri.app/sh) # 使用 PowerShell (Windows) irm https://create.tauri.app/ps | iex ``` ### 项目结构 ``` my-tauri-app/ ├── src/ # 前端源码 │ ├── main.ts │ └── App.vue ├── src-tauri/ # Rust 后端 │ ├── src/ │ │ ├── lib.rs # 主入口 │ │ └── main.rs │ ├── capabilities/ # 权限配置 │ │ └── default.json │ ├── icons/ # 应用图标 │ ├── Cargo.toml # Rust 依赖 │ └── tauri.conf.json # Tauri 配置 ├── package.json └── vite.config.ts ``` ### 开发和构建命令 ```bash # 启动开发服务器 npm run tauri dev # 构建生产版本 npm run tauri build # 添加插件 npm run tauri add # 初始化 Android npm run tauri android init # 初始化 iOS npm run tauri ios init ``` ## 核心配置 (tauri.conf.json) ### 基本配置 ```json { "$schema": "https://schema.tauri.app/config/2", "productName": "my-app", "version": "1.0.0", "identifier": "com.example.myapp", "build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:5173", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" }, "app": { "windows": [ { "title": "My App", "width": 800, "height": 600, "resizable": true, "fullscreen": false, "center": true } ], "security": { "csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost" } }, "bundle": { "active": true, "icon": [ "icons/32x32.png", "icons/128x128.png", "icons/icon.icns", "icons/icon.ico" ], "targets": "all" }, "plugins": {} } ``` ### 窗口配置选项 ```json { "app": { "windows": [ { "label": "main", "title": "主窗口", "width": 1024, "height": 768, "minWidth": 400, "minHeight": 300, "maxWidth": 1920, "maxHeight": 1080, "resizable": true, "fullscreen": false, "center": true, "x": 100, "y": 100, "visible": true, "decorations": true, "transparent": false, "alwaysOnTop": false, "skipTaskbar": false, "focus": true, "url": "index.html" } ] } } ``` ### 构建配置 ```json { "build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:5173", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" } } ``` ### 打包配置 ```json { "bundle": { "active": true, "targets": ["deb", "rpm", "appimage", "dmg", "nsis", "msi"], "icon": ["icons/icon.png", "icons/icon.icns", "icons/icon.ico"], "identifier": "com.example.myapp", "publisher": "My Company", "category": "Utility", "shortDescription": "A short description", "longDescription": "A longer description of the app", "copyright": "Copyright 2024", "license": "MIT", "windows": { "certificateThumbprint": null, "timestampUrl": null, "webviewInstallMode": { "type": "downloadBootstrapper" } }, "macOS": { "minimumSystemVersion": "10.13", "signingIdentity": null, "providerShortName": null, "entitlements": null }, "linux": { "appimage": { "bundleMediaFramework": false } } } } ``` ## Rust 后端开发 ### 主入口 (lib.rs) ```rust // src-tauri/src/lib.rs use tauri::Manager; #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![greet]) .setup(|app| { // 初始化代码 Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ### 定义命令 (Commands) ```rust // 基本命令 #[tauri::command] fn simple_command() { println!("I was invoked from JavaScript!"); } // 带参数的命令 #[tauri::command] fn command_with_message(message: String) -> String { format!("Received: {}", message) } // 带多个参数的命令 #[tauri::command] fn command_with_object(name: String, age: u32) -> String { format!("{} is {} years old", name, age) } // 异步命令 #[tauri::command] async fn async_command() -> Result { // 模拟异步操作 tokio::time::sleep(std::time::Duration::from_secs(1)).await; Ok("Done!".to_string()) } // 返回 Result 的命令 #[tauri::command] fn fallible_command() -> Result { if true { Ok("Success!".to_string()) } else { Err("Something went wrong".to_string()) } } // 注册命令 fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ simple_command, command_with_message, command_with_object, async_command, fallible_command ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ### 访问 AppHandle 和 Window ```rust use tauri::{AppHandle, Manager, WebviewWindow}; #[tauri::command] fn command_with_app_handle(app: AppHandle) { // 访问应用句柄 let app_dir = app.path().app_data_dir().unwrap(); println!("App data dir: {:?}", app_dir); } #[tauri::command] fn command_with_window(window: WebviewWindow) { // 操作窗口 window.set_title("New Title").unwrap(); window.center().unwrap(); } #[tauri::command] async fn create_window(app: AppHandle) { let _webview = tauri::WebviewWindowBuilder::new( &app, "new-window", tauri::WebviewUrl::App("index.html".into()) ) .title("New Window") .inner_size(800.0, 600.0) .build() .unwrap(); } ``` ### 状态管理 ```rust use std::sync::Mutex; use tauri::State; // 定义状态结构 struct AppState { counter: u32, name: String, } // 使用 Mutex 包装可变状态 struct Counter(Mutex); #[tauri::command] fn get_count(state: State) -> u32 { *state.0.lock().unwrap() } #[tauri::command] fn increment(state: State) -> u32 { let mut count = state.0.lock().unwrap(); *count += 1; *count } #[tauri::command] fn get_app_state(state: State>) -> String { let state = state.lock().unwrap(); format!("{}: {}", state.name, state.counter) } fn main() { tauri::Builder::default() .manage(Counter(Mutex::new(0))) .manage(Mutex::new(AppState { counter: 0, name: "My App".to_string(), })) .invoke_handler(tauri::generate_handler![ get_count, increment, get_app_state ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ### 从 Rust 发送事件到前端 ```rust use tauri::{AppHandle, Emitter, Manager}; // 发送全局事件 #[tauri::command] fn emit_event(app: AppHandle) { app.emit("my-event", "Hello from Rust!").unwrap(); } // 发送到特定窗口 #[tauri::command] fn emit_to_window(app: AppHandle) { app.emit_to("main", "window-event", serde_json::json!({ "message": "Hello", "count": 42 })).unwrap(); } // 使用 Channel 进行流式通信 use tauri::ipc::Channel; #[derive(Clone, serde::Serialize)] struct ProgressPayload { progress: u32, message: String, } #[tauri::command] async fn download_file(on_progress: Channel) { for i in 0..=100 { on_progress.send(ProgressPayload { progress: i, message: format!("Downloading... {}%", i), }).unwrap(); tokio::time::sleep(std::time::Duration::from_millis(50)).await; } } ``` ### 监听前端事件 ```rust use tauri::Listener; fn main() { tauri::Builder::default() .setup(|app| { let handle = app.handle().clone(); // 监听事件 app.listen("frontend-event", move |event| { println!("Received event: {:?}", event.payload()); }); Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ## 前端 API ### 调用 Rust 命令 ```typescript import { invoke } from '@tauri-apps/api/core'; // 基本调用 async function callSimpleCommand() { await invoke('simple_command'); } // 带参数调用 async function callWithMessage() { const result = await invoke('command_with_message', { message: 'Hello from frontend' }); console.log(result); } // 带对象参数 async function callWithObject() { const result = await invoke('command_with_object', { name: 'Alice', age: 30 }); console.log(result); } // 错误处理 async function callFallibleCommand() { try { const result = await invoke('fallible_command'); console.log('Success:', result); } catch (error) { console.error('Error:', error); } } ``` ### 事件系统 ```typescript import { emit, listen, once, emitTo } from '@tauri-apps/api/event'; // 监听事件 const unlisten = await listen('my-event', (event) => { console.log('Received:', event.payload); }); // 监听一次 await once('one-time-event', (event) => { console.log('Received once:', event.payload); }); // 发送事件 await emit('frontend-event', { message: 'Hello from frontend' }); // 发送到特定窗口 await emitTo('main', 'targeted-event', { data: 'some data' }); // 清理监听器 unlisten(); ``` ### 使用 Channel 接收流式数据 ```typescript import { invoke, Channel } from '@tauri-apps/api/core'; interface ProgressPayload { progress: number; message: string; } async function downloadWithProgress() { const onProgress = new Channel(); onProgress.onmessage = (payload) => { console.log(`Progress: ${payload.progress}% - ${payload.message}`); }; await invoke('download_file', { onProgress }); } ``` ### 窗口管理 ```typescript import { getCurrentWebviewWindow, WebviewWindow } from '@tauri-apps/api/webviewWindow'; // 获取当前窗口 const currentWindow = getCurrentWebviewWindow(); // 窗口操作 await currentWindow.setTitle('New Title'); await currentWindow.center(); await currentWindow.setSize({ width: 800, height: 600 }); await currentWindow.setPosition({ x: 100, y: 100 }); await currentWindow.setFullscreen(true); await currentWindow.minimize(); await currentWindow.maximize(); await currentWindow.unmaximize(); await currentWindow.show(); await currentWindow.hide(); await currentWindow.close(); // 窗口状态查询 const isMaximized = await currentWindow.isMaximized(); const isMinimized = await currentWindow.isMinimized(); const isFullscreen = await currentWindow.isFullscreen(); const isFocused = await currentWindow.isFocused(); const isVisible = await currentWindow.isVisible(); // 创建新窗口 const newWindow = new WebviewWindow('new-window', { url: '/settings', title: 'Settings', width: 600, height: 400, center: true, resizable: true, }); // 监听窗口事件 await currentWindow.onResized((size) => { console.log('Window resized:', size.payload); }); await currentWindow.onMoved((position) => { console.log('Window moved:', position.payload); }); await currentWindow.onCloseRequested((event) => { console.log('Close requested'); // event.preventDefault(); // 阻止关闭 }); await currentWindow.onFocusChanged((focused) => { console.log('Focus changed:', focused.payload); }); ``` ## 安全与权限 ### Capabilities 配置 ```json // src-tauri/capabilities/default.json { "$schema": "https://schemas.tauri.app/config/2/capability", "identifier": "default", "description": "Default capabilities for the main window", "windows": ["main"], "permissions": [ "core:default", "opener:default", "fs:default", "dialog:default", "shell:allow-open" ] } ``` ### 平台特定权限 ```json { "identifier": "desktop-only", "windows": ["main"], "platforms": ["linux", "macOS", "windows"], "permissions": [ "global-shortcut:allow-register", "global-shortcut:allow-unregister" ] } ``` ### 细粒度权限控制 ```json { "identifier": "restricted-fs", "windows": ["main"], "permissions": [ { "identifier": "fs:allow-read-text-file", "allow": [ { "path": "$APPDATA/**" }, { "path": "$RESOURCE/**" } ] }, { "identifier": "fs:allow-write-text-file", "allow": [ { "path": "$APPDATA/**" } ] } ] } ``` ### 远程 URL 权限 ```json { "identifier": "remote-access", "windows": ["main"], "remote": { "urls": ["https://*.example.com"] }, "permissions": [ "http:default" ] } ``` ## 官方插件 ### 文件系统 (fs) ```bash npm run tauri add fs ``` ```typescript import { readTextFile, writeTextFile, readDir, createDir, removeFile, removeDir, exists, BaseDirectory } from '@tauri-apps/plugin-fs'; // 读取文件 const content = await readTextFile('config.json', { baseDir: BaseDirectory.AppConfig }); // 写入文件 await writeTextFile('data.json', JSON.stringify({ key: 'value' }), { baseDir: BaseDirectory.AppData }); // 读取目录 const entries = await readDir('documents', { baseDir: BaseDirectory.Home, recursive: true }); // 创建目录 await createDir('my-app/data', { baseDir: BaseDirectory.AppData, recursive: true }); // 检查文件存在 const fileExists = await exists('config.json', { baseDir: BaseDirectory.AppConfig }); // 删除文件 await removeFile('temp.txt', { baseDir: BaseDirectory.Temp }); // 监听文件变化 import { watch } from '@tauri-apps/plugin-fs'; const stopWatching = await watch( 'config.json', (event) => { console.log('File changed:', event); }, { baseDir: BaseDirectory.AppConfig } ); // 停止监听 stopWatching(); ``` ### 对话框 (dialog) ```bash npm run tauri add dialog ``` ```typescript import { open, save, message, ask, confirm } from '@tauri-apps/plugin-dialog'; // 打开文件选择 const selected = await open({ multiple: false, directory: false, filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif'] }] }); // 打开多个文件 const files = await open({ multiple: true, filters: [{ name: 'Documents', extensions: ['pdf', 'doc', 'docx'] }] }); // 选择目录 const dir = await open({ directory: true }); // 保存文件对话框 const savePath = await save({ defaultPath: 'document.txt', filters: [{ name: 'Text', extensions: ['txt'] }] }); // 消息对话框 await message('Operation completed successfully!', { title: 'Success', kind: 'info' // 'info' | 'warning' | 'error' }); // 询问对话框 const answer = await ask('Are you sure you want to delete?', { title: 'Confirm Delete', kind: 'warning' }); // 确认对话框 const confirmed = await confirm('Do you want to proceed?', { title: 'Confirmation' }); ``` ### HTTP 客户端 (http) ```bash npm run tauri add http ``` ```typescript import { fetch } from '@tauri-apps/plugin-http'; // GET 请求 const response = await fetch('https://api.example.com/data'); const data = await response.json(); // POST 请求 const postResponse = await fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John', email: 'john@example.com' }) }); // 带认证的请求 const authResponse = await fetch('https://api.example.com/protected', { headers: { 'Authorization': 'Bearer token123' } }); ``` 权限配置: ```json { "permissions": [ { "identifier": "http:default", "allow": [ { "url": "https://api.example.com/*" } ] } ] } ``` ### 存储 (store) ```bash npm run tauri add store ``` ```typescript import { load, LazyStore } from '@tauri-apps/plugin-store'; // 加载存储 const store = await load('settings.json', { autoSave: true }); // 设置值 await store.set('theme', 'dark'); await store.set('user', { name: 'John', age: 30 }); // 获取值 const theme = await store.get('theme'); const user = await store.get<{ name: string; age: number }>('user'); // 检查键是否存在 const hasTheme = await store.has('theme'); // 删除键 await store.delete('theme'); // 获取所有键 const keys = await store.keys(); // 获取所有值 const values = await store.values(); // 获取所有条目 const entries = await store.entries(); // 清空存储 await store.clear(); // 手动保存 await store.save(); // 使用 LazyStore (延迟加载) const lazyStore = new LazyStore('config.json'); await lazyStore.set('key', 'value'); ``` ### Shell (shell) ```bash npm run tauri add shell ``` ```typescript import { Command } from '@tauri-apps/plugin-shell'; // 执行命令 const output = await Command.create('echo', ['Hello, World!']).execute(); console.log('stdout:', output.stdout); console.log('stderr:', output.stderr); console.log('code:', output.code); // 使用 shell 执行 const shellOutput = await Command.create('exec-sh', ['-c', 'ls -la']).execute(); // 生成子进程并获取流输出 const command = Command.create('long-running-process'); command.on('close', (data) => { console.log('Process finished with code:', data.code); }); command.on('error', (error) => { console.error('Error:', error); }); command.stdout.on('data', (line) => { console.log('stdout:', line); }); command.stderr.on('data', (line) => { console.error('stderr:', line); }); const child = await command.spawn(); // 写入 stdin await child.write('input data\n'); // 终止进程 await child.kill(); ``` ### 通知 (notification) ```bash npm run tauri add notification ``` ```typescript import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification'; // 检查权限 let permissionGranted = await isPermissionGranted(); // 请求权限 if (!permissionGranted) { const permission = await requestPermission(); permissionGranted = permission === 'granted'; } // 发送通知 if (permissionGranted) { sendNotification({ title: 'Tauri App', body: 'This is a notification!', icon: 'icons/icon.png' }); } // 带动作的通知 (移动端) import { registerActionTypes, onAction } from '@tauri-apps/plugin-notification'; await registerActionTypes([ { id: 'reply', actions: [ { id: 'reply-action', title: 'Reply', input: true, inputPlaceholder: 'Type your reply...' } ] } ]); await onAction((notification) => { console.log('Action:', notification.actionId); console.log('Input:', notification.inputValue); }); ``` ### 剪贴板 (clipboard-manager) ```bash npm run tauri add clipboard-manager ``` ```typescript import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager'; // 写入剪贴板 await writeText('Hello, clipboard!'); // 读取剪贴板 const text = await readText(); console.log('Clipboard content:', text); ``` ### 全局快捷键 (global-shortcut) ```bash npm run tauri add global-shortcut ``` ```typescript import { register, unregister, unregisterAll, isRegistered } from '@tauri-apps/plugin-global-shortcut'; // 注册快捷键 await register('CommandOrControl+Shift+C', () => { console.log('Shortcut triggered!'); }); // 检查是否已注册 const registered = await isRegistered('CommandOrControl+Shift+C'); // 注销快捷键 await unregister('CommandOrControl+Shift+C'); // 注销所有快捷键 await unregisterAll(); ``` ### Opener (opener) ```bash npm run tauri add opener ``` ```typescript import { open, reveal } from '@tauri-apps/plugin-opener'; // 打开 URL await open('https://tauri.app'); // 打开文件 (使用默认应用) await open('/path/to/document.pdf'); // 在文件管理器中显示 await reveal('/path/to/file.txt'); ``` ### 自动启动 (autostart) ```bash npm run tauri add autostart ``` ```typescript import { enable, disable, isEnabled } from '@tauri-apps/plugin-autostart'; // 启用开机自启 await enable(); // 禁用开机自启 await disable(); // 检查状态 const enabled = await isEnabled(); ``` ## 菜单系统 ### JavaScript 菜单 ```typescript import { Menu, MenuItem, Submenu, PredefinedMenuItem, CheckMenuItem } from '@tauri-apps/api/menu'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; // 创建菜单项 const copyItem = await MenuItem.new({ id: 'copy', text: 'Copy', accelerator: 'CommandOrControl+C', action: () => console.log('Copy clicked') }); const pasteItem = await MenuItem.new({ id: 'paste', text: 'Paste', accelerator: 'CommandOrControl+V', action: () => console.log('Paste clicked') }); // 创建子菜单 const editSubmenu = await Submenu.new({ text: 'Edit', items: [copyItem, pasteItem] }); // 使用预定义菜单项 const separator = await PredefinedMenuItem.new({ item: 'Separator' }); const quit = await PredefinedMenuItem.new({ item: 'Quit' }); // 创建复选菜单项 const darkMode = await CheckMenuItem.new({ id: 'dark-mode', text: 'Dark Mode', checked: true, action: (item) => console.log('Dark mode:', item.isChecked) }); // 创建完整菜单 const menu = await Menu.new({ items: [editSubmenu] }); // 设置窗口菜单 const window = getCurrentWebviewWindow(); await window.setMenu(menu); ``` ### Rust 菜单 ```rust use tauri::menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder}; fn main() { tauri::Builder::default() .setup(|app| { let menu = MenuBuilder::new(app) .item(&SubmenuBuilder::new(app, "File") .text("new", "New") .text("open", "Open") .separator() .text("quit", "Quit") .build()?) .item(&SubmenuBuilder::new(app, "Edit") .copy() .paste() .cut() .build()?) .build()?; app.set_menu(menu)?; Ok(()) }) .on_menu_event(|app, event| { match event.id().as_ref() { "new" => println!("New clicked"), "open" => println!("Open clicked"), "quit" => app.exit(0), _ => {} } }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ## 系统托盘 ### Rust 系统托盘 ```rust // Cargo.toml 需要启用 feature // tauri = { version = "2", features = ["tray-icon"] } use tauri::{ menu::{Menu, MenuItem}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, Manager, }; fn main() { tauri::Builder::default() .setup(|app| { // 创建托盘菜单 let quit = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; let show = MenuItem::with_id(app, "show", "Show", true, None::<&str>)?; let menu = Menu::with_items(app, &[&show, &quit])?; // 创建托盘图标 let _tray = TrayIconBuilder::new() .icon(app.default_window_icon().unwrap().clone()) .menu(&menu) .menu_on_left_click(false) .on_menu_event(|app, event| match event.id.as_ref() { "quit" => app.exit(0), "show" => { if let Some(window) = app.get_webview_window("main") { window.show().unwrap(); window.set_focus().unwrap(); } } _ => {} }) .on_tray_icon_event(|tray, event| { if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event { let app = tray.app_handle(); if let Some(window) = app.get_webview_window("main") { window.show().unwrap(); window.set_focus().unwrap(); } } }) .build(app)?; Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ### JavaScript 系统托盘 ```typescript import { TrayIcon, Menu, MenuItem } from '@tauri-apps/api/tray'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; // 创建托盘菜单 const showItem = await MenuItem.new({ id: 'show', text: 'Show', action: async () => { const window = getCurrentWebviewWindow(); await window.show(); await window.setFocus(); } }); const quitItem = await MenuItem.new({ id: 'quit', text: 'Quit', action: () => { // 退出应用 } }); const menu = await Menu.new({ items: [showItem, quitItem] }); // 创建托盘图标 const tray = await TrayIcon.new({ icon: 'icons/icon.png', menu, menuOnLeftClick: false, action: async (event) => { if (event.type === 'Click') { const window = getCurrentWebviewWindow(); await window.show(); await window.setFocus(); } } }); ``` ## 嵌入外部二进制 (Sidecar) ### 配置 ```json // tauri.conf.json { "bundle": { "externalBin": [ "binaries/my-sidecar" ] } } ``` 需要为每个平台提供正确后缀的二进制文件: - `binaries/my-sidecar-x86_64-pc-windows-msvc.exe` - `binaries/my-sidecar-x86_64-unknown-linux-gnu` - `binaries/my-sidecar-aarch64-apple-darwin` - `binaries/my-sidecar-x86_64-apple-darwin` ### Rust 调用 ```rust use tauri_plugin_shell::ShellExt; #[tauri::command] async fn run_sidecar(app: tauri::AppHandle) -> Result { let sidecar_command = app .shell() .sidecar("my-sidecar") .map_err(|e| e.to_string())?; let output = sidecar_command .args(["--arg1", "value1"]) .output() .await .map_err(|e| e.to_string())?; Ok(String::from_utf8_lossy(&output.stdout).to_string()) } ``` ### JavaScript 调用 ```typescript import { Command } from '@tauri-apps/plugin-shell'; const command = Command.sidecar('binaries/my-sidecar', ['--arg1', 'value1']); const output = await command.execute(); console.log('Output:', output.stdout); ``` ## 移动端开发 ### iOS 配置 ```bash # 添加 iOS 目标 rustup target add aarch64-apple-ios rustup target add x86_64-apple-ios rustup target add aarch64-apple-ios-sim # 初始化 iOS 项目 npm run tauri ios init # 开发模式 npm run tauri ios dev # 构建 npm run tauri ios build ``` ### Android 配置 ```bash # 添加 Android 目标 rustup target add aarch64-linux-android rustup target add armv7-linux-androideabi rustup target add i686-linux-android rustup target add x86_64-linux-android # 设置环境变量 export JAVA_HOME=/path/to/java export ANDROID_HOME=/path/to/android/sdk export NDK_HOME=$ANDROID_HOME/ndk/ # 初始化 Android 项目 npm run tauri android init # 开发模式 npm run tauri android dev # 构建 npm run tauri android build ``` ### 平台检测 ```typescript import { platform } from '@tauri-apps/plugin-os'; const currentPlatform = await platform(); // 'linux' | 'macos' | 'ios' | 'freebsd' | 'dragonfly' | 'netbsd' | 'openbsd' | 'solaris' | 'android' | 'windows' const isMobile = ['ios', 'android'].includes(currentPlatform); const isDesktop = !isMobile; ``` ### 条件编译 (Rust) ```rust #[cfg(target_os = "android")] fn android_only() { // Android 专用代码 } #[cfg(target_os = "ios")] fn ios_only() { // iOS 专用代码 } #[cfg(any(target_os = "android", target_os = "ios"))] fn mobile_only() { // 移动端专用代码 } #[cfg(not(any(target_os = "android", target_os = "ios")))] fn desktop_only() { // 桌面端专用代码 } ``` ## 插件开发 ### 创建插件 ```bash npx @tauri-apps/cli plugin new my-plugin ``` ### 插件结构 ``` tauri-plugin-my-plugin/ ├── src/ │ ├── lib.rs # 插件入口 │ ├── commands.rs # 命令定义 │ ├── error.rs # 错误类型 │ └── models.rs # 数据模型 ├── permissions/ # 权限定义 │ ├── default.toml │ └── schemas/ ├── guest-js/ # JavaScript API │ └── index.ts ├── Cargo.toml └── package.json ``` ### 插件入口 (lib.rs) ```rust use tauri::{ plugin::{Builder, TauriPlugin}, Manager, Runtime, }; mod commands; mod error; pub use error::{Error, Result}; pub fn init() -> TauriPlugin { Builder::new("my-plugin") .invoke_handler(tauri::generate_handler![ commands::do_something ]) .setup(|app, api| { // 插件初始化 Ok(()) }) .on_navigation(|webview, url| { // 导航事件处理 true }) .on_event(|app, event| { // 事件处理 }) .build() } ``` ### 命令定义 (commands.rs) ```rust use tauri::{command, AppHandle, Runtime}; #[command] pub async fn do_something( app: AppHandle, input: String, ) -> Result { Ok(format!("Processed: {}", input)) } ``` ### JavaScript API (guest-js/index.ts) ```typescript import { invoke } from '@tauri-apps/api/core'; export async function doSomething(input: string): Promise { return await invoke('plugin:my-plugin|do_something', { input }); } ``` ### 权限定义 (permissions/default.toml) ```toml [default] description = "Default permissions for the my-plugin plugin" permissions = ["allow-do-something"] [[permission]] identifier = "allow-do-something" description = "Allows the do_something command" commands.allow = ["do_something"] ``` ## 构建和分发 ### 构建命令 ```bash # 构建所有目标 npm run tauri build # 构建特定目标 npm run tauri build -- --target x86_64-pc-windows-msvc npm run tauri build -- --target aarch64-apple-darwin npm run tauri build -- --target x86_64-unknown-linux-gnu # 调试构建 npm run tauri build -- --debug # 指定 bundle 类型 npm run tauri build -- --bundles deb,appimage npm run tauri build -- --bundles dmg npm run tauri build -- --bundles nsis,msi ``` ### 代码签名 #### macOS ```bash # 设置环境变量 export APPLE_CERTIFICATE="base64_encoded_certificate" export APPLE_CERTIFICATE_PASSWORD="password" export APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name" export APPLE_ID="your@email.com" export APPLE_PASSWORD="app-specific-password" export APPLE_TEAM_ID="your_team_id" # 构建并签名 npm run tauri build ``` #### Windows ```bash # 设置环境变量 export TAURI_SIGNING_PRIVATE_KEY="path/to/private-key.pem" export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="password" # 构建 npm run tauri build ``` ### 自动更新 ```json // tauri.conf.json { "plugins": { "updater": { "active": true, "endpoints": [ "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}" ], "pubkey": "your_public_key_here" } } } ``` ```typescript import { check } from '@tauri-apps/plugin-updater'; import { relaunch } from '@tauri-apps/plugin-process'; async function checkForUpdates() { const update = await check(); if (update) { console.log(`Update available: ${update.version}`); await update.downloadAndInstall(); await relaunch(); } } ``` ## 常用模式 ### 单实例应用 ```bash npm run tauri add single-instance ``` ```rust // lib.rs tauri::Builder::default() .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { // 处理第二个实例的参数 println!("Second instance with args: {:?}", argv); // 聚焦主窗口 if let Some(window) = app.get_webview_window("main") { window.show().unwrap(); window.set_focus().unwrap(); } })) ``` ### 深度链接 ```bash npm run tauri add deep-link ``` ```json // tauri.conf.json { "plugins": { "deep-link": { "desktop": { "schemes": ["myapp"] } } } } ``` ```typescript import { onOpenUrl } from '@tauri-apps/plugin-deep-link'; await onOpenUrl((urls) => { console.log('Opened with URL:', urls); // 处理 myapp://some/path }); ``` ### 窗口状态持久化 ```bash npm run tauri add window-state ``` ```rust // lib.rs tauri::Builder::default() .plugin(tauri_plugin_window_state::Builder::default().build()) ``` 自动保存和恢复窗口位置、大小、最大化状态等。 ## 调试 ### 开发者工具 ```rust // 在开发模式下自动打开 #[cfg(debug_assertions)] window.open_devtools(); ``` ### 日志 ```bash npm run tauri add log ``` ```rust use log::{info, warn, error, debug}; #[tauri::command] fn my_command() { info!("This is an info message"); warn!("This is a warning"); error!("This is an error"); debug!("This is a debug message"); } ``` ```typescript import { info, warn, error, debug } from '@tauri-apps/plugin-log'; await info('Info message'); await warn('Warning message'); await error('Error message'); await debug('Debug message'); ``` ### 环境变量 ```bash # 启用 Tauri 日志 RUST_LOG=debug npm run tauri dev # 启用详细构建输出 TAURI_DEBUG=1 npm run tauri build ``` ## 最佳实践 ### 项目结构建议 ``` my-tauri-app/ ├── src/ # 前端代码 │ ├── components/ │ ├── pages/ │ ├── stores/ │ ├── utils/ │ └── main.ts ├── src-tauri/ │ ├── src/ │ │ ├── commands/ # 按功能分组的命令 │ │ │ ├── mod.rs │ │ │ ├── file.rs │ │ │ └── system.rs │ │ ├── models/ # 数据模型 │ │ ├── services/ # 业务逻辑 │ │ ├── lib.rs │ │ └── main.rs │ ├── capabilities/ │ ├── icons/ │ └── tauri.conf.json └── package.json ``` ### 错误处理 ```rust // 定义错误类型 #[derive(Debug, thiserror::Error)] pub enum AppError { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Database error: {0}")] Database(String), #[error("Validation error: {0}")] Validation(String), } // 实现 Serialize 以便返回给前端 impl serde::Serialize for AppError { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { serializer.serialize_str(self.to_string().as_ref()) } } // 在命令中使用 #[tauri::command] fn may_fail() -> Result { // ... } ``` ### 安全建议 1. **最小权限原则**: 只授予应用需要的权限 2. **验证用户输入**: 在 Rust 端验证所有来自前端的数据 3. **使用作用域**: 限制文件系统和 shell 访问的范围 4. **保护敏感数据**: 使用 stronghold 插件存储密钥 5. **更新依赖**: 定期更新 Tauri 和其他依赖 ### 性能优化 1. **懒加载**: 使用动态导入减少初始加载时间 2. **异步命令**: 长时间操作使用异步命令 3. **批量操作**: 合并多个小操作为单个命令 4. **资源嵌入**: 使用 `$RESOURCE` 目录嵌入静态资源 5. **图片优化**: 压缩应用图标和资源图片