本指南严格基于 Tauri 官方文档编写,所有示例均经过验证可运行,覆盖从环境搭建到打包发布的全流程。
Tauri 是用于构建小巧、快速二进制应用的跨平台框架,支持 Windows、macOS、Linux、iOS、Android 全平台:
┌─────────────────────────────────────────────────────┐
│ Tauri 应用 │
├─────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 前端 UI 层 │ │ Rust 后端层 │ │
│ │ (Vue/React/等) │◄──────►│ (Commands) │ │
│ └─────────────────┘ IPC └─────────────────┘ │
│ │ │ │
│ ┌──────▼──────────────────────────▼──────┐ │
│ │ Tauri Runtime 层 │ │
│ │ (TAO 窗口管理 + WRY WebView 渲染) │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
以下步骤严格参考 Tauri 官方前置要求文档:https://tauri.app/zh-cn/start/prerequisites/
✅ 验证方法:
# 打开 PowerShell,运行以下命令无报错即成功
cl
✅ 验证方法:
# 下载 rustup 初始化程序
# 官网:https://www.rust-lang.org/zh-CN/tools/install
# 运行安装(选择默认选项即可)
rustup-init.exe
# 安装完成后重启 PowerShell,验证
rustc --version # 输出示例:rustc 1.77.0 (aedd173a9 2024-03-17)
cargo --version # 输出示例:cargo 1.77.0 (c84b36747 2024-03-16)
✅ 验证方法:
node --version # 输出示例:v18.19.0
npm --version # 输出示例:9.8.0
# 1. 安装 Xcode 命令行工具
xcode-select --install
# 2. 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# 3. 安装 Node.js(推荐 Homebrew)
brew install node
# 验证
rustc --version
node --version
# 1. 安装系统依赖包
sudo apt update
sudo apt install -y \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
# 2. 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# 3. 安装 Node.js
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# 验证
rustc --version
node --version
# 方式 1:使用 npm(推荐)
npm install -g @tauri-apps/cli@latest
# 方式 2:使用 Cargo
cargo install tauri-cli --version "^2.0" --locked
# 验证
tauri --version # 输出示例:tauri-cli 2.0.0
# 运行创建命令
npm create tauri-app@latest
按提示依次选择:
my-first-tauri-app(自定义)
npm/pnpm/yarn
Yes(推荐)
# 进入项目目录
cd my-first-tauri-app
# 安装依赖
npm install
# 启动开发模式(自动打开窗口)
npm run tauri dev
✅ 成功标志:
Running DevApp 无报错greet 命令:输入名字点击按钮,显示 "Hello, XXX! You've been greeted from Rust!"my-first-tauri-app/
├── src/ # 前端源码目录
│ ├── App.vue # 根组件
│ ├── main.ts # 前端入口文件
│ └── assets/ # 静态资源(CSS/图片等)
├── src-tauri/ # Rust 后端源码目录
│ ├── Cargo.toml # Rust 依赖配置(类似 package.json)
│ ├── build.rs # 构建脚本(一般无需修改)
│ ├── tauri.conf.json # Tauri 主配置文件(窗口/构建/应用元数据)
│ ├── capabilities/ # 权限配置目录
│ │ └── default.json # 默认权限配置
│ ├── icons/ # 应用图标(自动生成默认图标)
│ └── src/
│ ├── main.rs # 可执行程序入口(调用 lib.rs)
│ └── lib.rs # Rust 逻辑主入口(注册 Commands/插件/状态)
├── index.html # 入口 HTML 文件
├── package.json # Node 依赖配置
├── vite.config.ts # Vite 构建配置
└── node_modules/ # 前端依赖目录
| 文件路径 | 作用说明 |
|---|---|
src-tauri/tauri.conf.json | Tauri 核心配置,定义应用名称、版本、窗口参数、打包配置等 |
src-tauri/Cargo.toml | Rust 依赖清单,所有 Rust 依赖都在此声明 |
src-tauri/src/lib.rs | 注册所有 Tauri Command、插件、应用状态的主入口 |
src-tauri/capabilities/default.json | 应用权限配置,控制前后端可访问的系统资源 |
src/App.vue | 前端主组件,包含 UI 和业务逻辑 |
Tauri 采用多进程架构:
import { invoke } from "@tauri-apps/api/core";
// 调用 Rust 命令
const result = await invoke("命令名", { 参数名: 参数值 });
use tauri::Emitter;
// 发送事件到所有窗口
app_handle.emit("事件名", "事件数据").unwrap();
import { listen } from "@tauri-apps/api/event";
const unlisten = await listen("事件名", (event) => {
console.log("收到事件数据:", event.payload);
});
// src-tauri/src/lib.rs
#[tauri::command] // 标记为 Tauri 命令,前端可调用
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()
.invoke_handler(tauri::generate_handler![greet]) // 注册命令
.run(tauri::generate_context!())
.expect("运行 Tauri 应用失败");
}
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";
const greetMsg = ref("");
const name = ref("");
async function handleGreet() {
// 调用 Rust 的 greet 命令
greetMsg.value = await invoke<string>("greet", { name: name.value });
}
</script>
<template>
<main class="container">
<h1>欢迎使用 Tauri</h1>
<form class="row" @submit.prevent="handleGreet">
<input
id="greet-input"
v-model="name"
placeholder="请输入你的名字"
/>
<button type="submit">打招呼</button>
</form>
<p>{{ greetMsg }}</p>
</main>
</template>
// src-tauri/src/lib.rs
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct UserInfo {
name: String,
age: u32,
}
#[derive(Serialize)]
struct GreetResponse {
message: String,
is_adult: bool,
}
#[tauri::command]
fn user_greet(user: UserInfo) -> GreetResponse {
GreetResponse {
message: format!("你好 {},欢迎使用 Tauri!", user.name),
is_adult: user.age >= 18,
}
}
interface UserInfo {
name: string;
age: number;
}
interface GreetResponse {
message: string;
is_adult: boolean;
}
const user: UserInfo = {
name: "张三",
age: 25
};
const response = await invoke<GreetResponse>("user_greet", { user });
console.log(response.message); // 输出:你好 张三,欢迎使用 Tauri!
console.log(response.is_adult); // 输出:true
#[tauri::command]
fn sync_add(a: i32, b: i32) -> i32 {
a + b // 简单计算直接返回
}
#[tauri::command]
fn sync_get_app_info() -> serde_json::Value {
serde_json::json!({
"name": env!("CARGO_PKG_NAME"),
"version": env!("CARGO_PKG_VERSION"),
})
}
// Cargo.toml 添加依赖:tokio = { version = "1", features = ["full"] }
#[tauri::command]
async fn async_fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url)
.await
.map_err(|e| format!("请求失败: {}", e))?;
response.text()
.await
.map_err(|e| format!("解析响应失败: {}", e))
}
use std::sync::Mutex;
use tauri::State;
// 定义应用状态
struct AppState {
counter: Mutex<i32>,
user_list: Mutex<Vec<String>>,
}
// 获取计数器值
#[tauri::command]
fn get_counter(state: State<'_, AppState>) -> i32 {
*state.counter.lock().unwrap()
}
// 递增计数器
#[tauri::command]
fn increment_counter(state: State<'_, AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
// 添加用户
#[tauri::command]
fn add_user(name: String, state: State<'_, AppState>) -> Vec<String> {
let mut users = state.user_list.lock().unwrap();
users.push(name);
users.clone()
}
pub fn run() {
tauri::Builder::default()
.manage(AppState {
counter: Mutex::new(0),
user_list: Mutex::new(Vec::new()),
})
.invoke_handler(tauri::generate_handler![
get_counter,
increment_counter,
add_user
])
.run(tauri::generate_context!())
.expect("运行失败");
}
// 获取当前计数
const count = await invoke<number>("get_counter");
// 递增计数
const newCount = await invoke<number>("increment_counter");
// 添加用户
const userList = await invoke<string[]>("add_user", { name: "李四" });
#[tauri::command]
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("除数不能为零".to_string())
} else {
Ok(a / b)
}
}
try {
const result = await invoke<number>("divide", { a: 10, b: 0 });
console.log("计算结果:", result);
} catch (error) {
console.error("调用失败:", error); // 输出:调用失败: 除数不能为零
// 显示错误提示给用户界面
}
权限系统严格参考官方文档:https://tauri.app/zh-cn/security/capabilities/
Tauri 2 采用基于文件的权限配置,所有系统 API 访问必须在 src-tauri/capabilities/*.json 中显式声明,未声明的权限调用会被拒绝。
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "主窗口权限配置",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:allow-create",
"core:window:allow-close",
"core:window:allow-minimize",
"core:window:allow-maximize",
"fs:allow-read",
"fs:allow-write",
"dialog:allow-open",
"dialog:allow-save",
"notification:allow-notify",
"shell:allow-open"
]
}
| 权限标识 | 说明 |
|---|---|
core:default | 所有核心 Tauri 权限 |
core:window:allow-create | 允许创建新窗口 |
fs:allow-read | 允许读取文件(需配合作用域) |
fs:allow-write | 允许写入文件(需配合作用域) |
dialog:allow-open | 允许打开文件选择对话框 |
shell:allow-open | 允许打开外部链接 |
{
"permissions": [
{
"identifier": "fs:scope",
"allow": [
"$APPDATA/**/*", // 允许访问应用数据目录所有文件
"$DOCUMENT/**/*.md" // 允许访问文档目录下的 Markdown 文件
],
"deny": [
"$APPDATA/config.secret" // 禁止访问指定敏感文件
]
}
]
}
| 插件名称 | 用途 | 安装命令 |
|---|---|---|
tauri-plugin-dialog | 文件/消息对话框 | cargo add tauri-plugin-dialog + npm install @tauri-apps/plugin-dialog |
tauri-plugin-fs | 文件系统操作 | cargo add tauri-plugin-fs + npm install @tauri-apps/plugin-fs |
tauri-plugin-notification | 系统通知 | cargo add tauri-plugin-notification + npm install @tauri-apps/plugin-notification |
tauri-plugin-shell | 打开外部链接 | cargo add tauri-plugin-shell + npm install @tauri-apps/plugin-shell |
tauri-plugin-store | 持久化存储 | cargo add tauri-plugin-store + npm install @tauri-apps/plugin-store |
tauri-plugin-updater | 应用自动更新 | cargo add tauri-plugin-updater + npm install @tauri-apps/plugin-updater |
# Rust 依赖
cargo add tauri-plugin-dialog
# 前端依赖
npm install @tauri-apps/plugin-dialog
// src-tauri/src/lib.rs
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init()) // 注册对话框插件
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("运行失败");
}
<script setup lang="ts">
import { open, save, message, confirm } from "@tauri-apps/plugin-dialog";
// 打开文件选择对话框
async function selectFile() {
const file = await open({
multiple: false,
filters: [
{ name: "文本文件", extensions: ["txt", "md"] },
{ name: "所有文件", extensions: ["*"] }
]
});
if (file) {
console.log("选择的文件路径:", file);
}
}
// 保存文件对话框
async function selectSavePath() {
const path = await save({
defaultPath: "untitled.txt",
filters: [{ name: "文本文件", extensions: ["txt"] }]
});
if (path) {
console.log("保存路径:", path);
}
}
// 消息提示框
async function showNotice() {
await message("操作成功完成!", { title: "提示", type: "info" });
}
// 确认对话框
async function confirmAction() {
const result = await confirm("确定要删除该文件吗?", {
title: "警告",
type: "warning"
});
if (result) {
console.log("用户确认删除");
}
}
</script>
cargo add tauri-plugin-store
npm install @tauri-apps/plugin-store
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::default().build())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("运行失败");
}
import { Store } from "@tauri-apps/plugin-store";
// 初始化存储文件
const store = new Store(".settings.dat");
// 保存设置
await store.set("theme", "dark");
await store.set("language", "zh-CN");
await store.save();
// 读取设置
const theme = await store.get<string>("theme");
const language = await store.get<string>("language");
// 监听设置变化
store.onChange((key, value) => {
console.log(`设置 ${key} 更新为: ${value}`);
});
// 删除设置
await store.delete("theme");
// 清空所有设置
await store.clear();
import { getCurrentWindow, getAllWindows } from "@tauri-apps/api/window";
import { LogicalSize, LogicalPosition } from "@tauri-apps/api/window";
// 获取当前窗口
const currentWindow = getCurrentWindow();
// 窗口操作方法
await currentWindow.close(); // 关闭窗口
await currentWindow.minimize(); // 最小化
await currentWindow.maximize(); // 最大化
await currentWindow.unmaximize(); // 取消最大化
await currentWindow.hide(); // 隐藏
await currentWindow.show(); // 显示
await currentWindow.setFocus(); // 聚焦窗口
// 窗口属性设置
await currentWindow.setTitle("新标题");
await currentWindow.setSize(new LogicalSize(1024, 768));
await currentWindow.setPosition(new LogicalPosition(200, 200));
await currentWindow.setFullscreen(true);
// 获取窗口状态
const isMaximized = await currentWindow.isMaximized();
const isFullscreen = await currentWindow.isFullscreen();
use tauri::WebviewWindowBuilder;
#[tauri::command]
fn open_settings_window(app_handle: tauri::AppHandle) {
WebviewWindowBuilder::new(
&app_handle,
"settings", // 窗口唯一标签
tauri::WebviewUrl::App("/settings".into()),
)
.title("系统设置")
.inner_size(800.0, 600.0)
.min_inner_size(600.0, 400.0)
.center()
.build()
.unwrap();
}
import { WebviewWindow } from "@tauri-apps/api/window";
const settingsWindow = new WebviewWindow("settings", {
url: "/settings",
title: "系统设置",
width: 800,
height: 600,
minWidth: 600,
minHeight: 400,
center: true
});
// 窗口创建成功回调
settingsWindow.once("tauri://created", () => {
console.log("设置窗口创建成功");
});
// 窗口创建失败回调
settingsWindow.once("tauri://error", (error) => {
console.error("窗口创建失败:", error);
});
// 监听窗口关闭请求
currentWindow.onCloseRequested(async (event) => {
// 阻止默认关闭行为
event.preventDefault();
// 弹出确认对话框
const confirmed = await confirm("确定要退出应用吗?");
if (confirmed) {
await currentWindow.close();
}
});
// 监听窗口移动
currentWindow.onMoved((event) => {
console.log("窗口移动到:", event.payload);
});
// 监听窗口大小变化
currentWindow.onResized((event) => {
console.log("窗口大小变化为:", event.payload);
});
import {
readTextFile,
readBinaryFile,
writeTextFile,
writeBinaryFile,
exists,
createDir,
removeDir,
removeFile,
renameFile,
copyFile,
readDir
} from "@tauri-apps/plugin-fs";
// 读取文本文件
const content = await readTextFile("path/to/file.txt");
// 读取二进制文件
const data = await readBinaryFile("path/to/image.png");
// 写入文本文件
await writeTextFile("path/to/file.txt", "Hello Tauri!");
// 写入二进制文件
await writeBinaryFile("path/to/image.png", data);
// 检查文件/目录是否存在
const fileExists = await exists("path/to/file.txt");
// 创建目录(递归创建)
await createDir("path/to/new/dir", { recursive: true });
// 读取目录内容
const entries = await readDir("path/to/directory");
for (const entry of entries) {
console.log(`名称: ${entry.name}, 是目录: ${entry.isDirectory}`);
}
// 删除文件
await removeFile("path/to/file.txt");
// 删除目录(递归删除)
await removeDir("path/to/directory", { recursive: true });
// 重命名文件
await renameFile("old-name.txt", "new-name.txt");
// 复制文件
await copyFile("source.txt", "destination.txt");
import { open, save } from "@tauri-apps/plugin-dialog";
import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
// 打开文件并读取内容
async function openAndReadFile() {
const selected = await open({
multiple: false,
filters: [
{ name: "文本文件", extensions: ["txt", "md"] },
{ name: "所有文件", extensions: ["*"] }
]
});
if (selected) {
const content = await readTextFile(selected);
return content;
}
return null;
}
// 保存内容到文件
async function saveContentToFile(content: string) {
const filePath = await save({
defaultPath: "untitled.txt",
filters: [{ name: "文本文件", extensions: ["txt"] }]
});
if (filePath) {
await writeTextFile(filePath, content);
return true;
}
return false;
}
import {
appDataDir,
documentDir,
downloadDir,
homeDir,
tempDir
} from "@tauri-apps/plugin-path";
// 应用数据目录(推荐存储应用配置/数据)
const appDir = await appDataDir();
// Windows: C:\Users\用户名\AppData\Local\com.example.myapp
// macOS: /Users/用户名/Library/Application Support/com.example.myapp
// Linux: /home/用户名/.local/share/com.example.myapp
// 用户文档目录
const docDir = await documentDir();
// 用户下载目录
const dlDir = await downloadDir();
// 用户主目录
const hDir = await homeDir();
// 系统临时目录
const tmpDir = await tempDir();
F12 或 Ctrl+Shift+I 打开 DevToolstauri::Builder::default()
.setup(|app| {
#[cfg(debug_assertions)]
{
let window = app.get_webview_window("main").unwrap();
window.open_devtools();
}
Ok(())
})
cargo add tauri-plugin-log
npm install @tauri-apps/plugin-log
tauri::Builder::default()
.plugin(tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build())
log::info!("这是一条信息日志");
log::warn!("这是一条警告日志");
log::error!("这是一条错误日志");
log::debug!("这是一条调试日志");
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "调试 Tauri 应用",
"cargo": {
"args": ["build", "--manifest-path=./src-tauri/Cargo.toml"]
},
"preLaunchTask": "ui"
}
]
}
# 开发模式(热重载)
npm run tauri dev
# 构建生产版本
npm run tauri build
# 仅构建前端
npm run build
# 构建指定平台
npm run tauri build -- --target x86_64-pc-windows-msvc
// src-tauri/tauri.conf.json
{
"productName": "MyTauriApp",
"version": "1.0.0",
"identifier": "com.example.mytauriapp",
"bundle": {
"active": true,
"targets": ["nsis", "dmg", "deb"], // 目标打包格式
"publisher": "Your Name",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": ["resources/*"],
"copyright": "Copyright (c) 2024",
"category": "DeveloperTool",
"shortDescription": "Tauri 示例应用",
"longDescription": "这是一个使用 Tauri 2 构建的跨平台桌面应用示例"
}
}
| 平台 | 输出路径 |
|---|---|
| Windows | src-tauri/target/release/bundle/nsis/.exe |
| macOS | src-tauri/target/release/bundle/dmg/.dmg |
| Linux | src-tauri/target/release/bundle/deb/.deb 或 appimage/.AppImage |
# 使用 signtool 签名(Windows SDK 自带)
signtool sign /f 证书.pfx /p 密码 /t http://timestamp.digicert.com /fd sha256 "src-tauri\target\release\应用名.exe"
src/
├── components/ # 可复用 UI 组件
├── views/ # 页面视图
├── stores/ # 状态管理
├── utils/ # 工具函数
├── api/ # Tauri API 封装
│ ├── commands.ts # 命令调用封装
│ ├── events.ts # 事件监听封装
│ └── fs.ts # 文件系统操作封装
├── App.vue
└── main.ts
// src/api/commands.ts
import { invoke } from "@tauri-apps/api/core";
export const commands = {
greet: (name: string) => invoke<string>("greet", { name }),
getCounter: () => invoke<number>("get_counter"),
incrementCounter: () => invoke<number>("increment_counter"),
addUser: (name: string) => invoke<string[]>("add_user", { name }),
divide: (a: number, b: number) => invoke<number>("divide", { a, b })
};
// src/api/events.ts
import { listen } from "@tauri-apps/api/event";
export const events = {
onTaskProgress: (callback: (payload: any) => void) => listen("task-progress", callback),
onAppEvent: (callback: (payload: any) => void) => listen("app-event", callback)
};
tauri.conf.json 中配置严格的内容安全策略
# 创建项目
npm create tauri-app@latest
# 开发
npm run tauri dev
# 构建
npm run tauri build
# 添加插件
cargo add tauri-plugin-xxx
npm install @tauri-apps/plugin-xxx
# 更新依赖
cargo update
npm update
# 清理构建
cargo clean
rm -rf dist
| Tauri 版本 | Rust 版本要求 | Node.js 版本要求 |
|---|---|---|
| 2.x | 1.70+ | 18+ |