跳到主要内容

对话框

Tauri 提供了原生系统对话框,包括文件选择、保存和消息提示等。

启用对话框权限

src-tauri/capabilities/default.json 中添加:

{
"permissions": [
"dialog:default",
"dialog:allow-open",
"dialog:allow-save",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm"
]
}

文件对话框

打开文件

前端代码:

import { open } from "@tauri-apps/api/dialog";

// 基础用法 - 选择单个文件
async function selectFile() {
const selected = await open({
multiple: false,
directory: false
});

if (selected) {
console.log("选择的文件:", selected);
// selected 是文件路径字符串
}
}

// 选择多个文件
async function selectMultipleFiles() {
const selected = await open({
multiple: true,
directory: false
});

if (selected && Array.isArray(selected)) {
selected.forEach(path => console.log(path));
}
}

// 选择目录
async function selectDirectory() {
const selected = await open({
directory: true,
multiple: false
});

if (selected) {
console.log("选择的目录:", selected);
}
}

带过滤器的文件选择:

async function selectImage() {
const selected = await open({
filters: [
{
name: "图片",
extensions: ["png", "jpg", "jpeg", "gif", "webp"]
},
{
name: "所有文件",
extensions: ["*"]
}
]
});

return selected;
}

async function selectDocument() {
const selected = await open({
filters: [
{
name: "文档",
extensions: ["pdf", "doc", "docx", "txt"]
}
]
});

return selected;
}

设置默认路径:

import { documentDir } from "@tauri-apps/api/path";

async function selectWithDefaultPath() {
const defaultPath = await documentDir();

const selected = await open({
defaultPath: defaultPath,
filters: [
{ name: "文本文件", extensions: ["txt"] }
]
});

return selected;
}

保存文件

import { save } from "@tauri-apps/api/dialog";

async function saveFile() {
const filePath = await save({
filters: [
{
name: "文本文件",
extensions: ["txt"]
},
{
name: "Markdown",
extensions: ["md"]
}
],
defaultPath: "untitled.txt"
});

if (filePath) {
console.log("保存到:", filePath);
// 使用 fs API 写入文件
}
}

消息对话框

消息提示

import { message } from "@tauri-apps/api/dialog";

async function showMessage() {
await message("操作成功完成", {
title: "提示",
type: "info" // "info" | "warning" | "error"
});
}

async function showError() {
await message("文件读取失败,请检查文件权限", {
title: "错误",
type: "error"
});
}

确认对话框

import { confirm } from "@tauri-apps/api/dialog";

async function confirmDelete() {
const confirmed = await confirm("确定要删除此文件吗?此操作不可撤销。", {
title: "确认删除",
type: "warning",
okLabel: "删除",
cancelLabel: "取消"
});

if (confirmed) {
// 执行删除操作
console.log("用户确认删除");
} else {
console.log("用户取消删除");
}
}

询问对话框

import { ask } from "@tauri-apps/api/dialog";

async function askSaveChanges() {
const answer = await ask("文件有未保存的更改,是否保存?", {
title: "保存更改",
type: "info",
okLabel: "保存",
cancelLabel: "不保存"
});

if (answer === true) {
// 用户点击"保存"
await saveFile();
} else if (answer === false) {
// 用户点击"不保存"
discardChanges();
}
// 如果用户关闭对话框,answer 为 null
}

Rust 端使用对话框

打开文件对话框

use tauri::dialog::FileDialogBuilder;

#[tauri::command]
async fn open_file_dialog(app: tauri::AppHandle) -> Result<Option<String>, String> {
let (tx, rx) = tokio::sync::oneshot::channel();

FileDialogBuilder::new(app)
.add_filter("图片", &["png", "jpg", "jpeg"])
.add_filter("所有文件", &["*"])
.pick_file(move |path| {
let _ = tx.send(path.map(|p| p.to_string_lossy().to_string()));
});

rx.await
.map_err(|e| e.to_string())
}

保存文件对话框

use tauri::dialog::FileDialogBuilder;

#[tauri::command]
async fn save_file_dialog(app: tauri::AppHandle) -> Result<Option<String>, String> {
let (tx, rx) = tokio::sync::oneshot::channel();

FileDialogBuilder::new(app)
.add_filter("文本文件", &["txt"])
.set_file_name("untitled.txt")
.save_file(move |path| {
let _ = tx.send(path.map(|p| p.to_string_lossy().to_string()));
});

rx.await
.map_err(|e| e.to_string())
}

消息对话框

use tauri::dialog::MessageDialogBuilder;
use tauri::dialog::MessageDialogKind;

#[tauri::command]
async fn show_info_dialog(app: tauri::AppHandle) {
MessageDialogBuilder::new(
app,
"提示",
"操作成功完成"
)
.kind(MessageDialogKind::Info)
.show(|result| {
println!("对话框关闭");
});
}

#[tauri::command]
async fn confirm_action(app: tauri::AppHandle) -> Result<bool, String> {
let (tx, rx) = tokio::sync::oneshot::channel();

MessageDialogBuilder::new(
app,
"确认",
"确定要执行此操作吗?"
)
.kind(MessageDialogKind::Warning)
.buttons(tauri::dialog::MessageDialogButtons::OkCancel)
.show(move |result| {
let _ = tx.send(result);
});

rx.await
.map_err(|e| e.to_string())
}

实战示例

完整的文件打开和保存流程

import { open, save } from "@tauri-apps/api/dialog";
import { readTextFile, writeTextFile } from "@tauri-apps/api/fs";
import { confirm } from "@tauri-apps/api/dialog";

class FileManager {
private currentFile: string | null = null;
private content: string = "";
private modified: boolean = false;

// 打开文件
async openFile(): Promise<boolean> {
// 如果有未保存的更改,先询问
if (this.modified) {
const shouldProceed = await this.confirmDiscard();
if (!shouldProceed) return false;
}

const selected = await open({
filters: [
{ name: "文本文件", extensions: ["txt", "md"] },
{ name: "所有文件", extensions: ["*"] }
],
multiple: false
});

if (!selected || Array.isArray(selected)) return false;

try {
this.content = await readTextFile(selected);
this.currentFile = selected;
this.modified = false;
return true;
} catch (error) {
await message(`无法打开文件: ${error}`, { type: "error" });
return false;
}
}

// 保存文件
async saveFile(): Promise<boolean> {
if (!this.currentFile) {
return this.saveAs();
}

try {
await writeTextFile(this.currentFile, this.content);
this.modified = false;
return true;
} catch (error) {
await message(`保存失败: ${error}`, { type: "error" });
return false;
}
}

// 另存为
async saveAs(): Promise<boolean> {
const filePath = await save({
filters: [
{ name: "文本文件", extensions: ["txt"] },
{ name: "Markdown", extensions: ["md"] }
],
defaultPath: this.currentFile || "untitled.txt"
});

if (!filePath) return false;

try {
await writeTextFile(filePath, this.content);
this.currentFile = filePath;
this.modified = false;
return true;
} catch (error) {
await message(`保存失败: ${error}`, { type: "error" });
return false;
}
}

// 确认放弃更改
private async confirmDiscard(): Promise<boolean> {
const answer = await confirm(
"当前文件有未保存的更改,是否放弃?",
{ title: "未保存的更改", type: "warning" }
);
return answer;
}

// 更新内容
setContent(newContent: string) {
if (newContent !== this.content) {
this.content = newContent;
this.modified = true;
}
}

getContent(): string {
return this.content;
}

isModified(): boolean {
return this.modified;
}

getCurrentFile(): string | null {
return this.currentFile;
}
}

export const fileManager = new FileManager();

批量文件处理

import { open } from "@tauri-apps/api/dialog";
import { readDir, readFile } from "@tauri-apps/api/fs";
import { join } from "@tauri-apps/api/path";

async function batchProcessImages() {
// 选择目录
const directory = await open({
directory: true,
multiple: false
});

if (!directory || Array.isArray(directory)) return;

// 读取目录内容
const entries = await readDir(directory);

const imageFiles = entries.filter(entry => {
const ext = entry.name?.toLowerCase();
return ext?.endsWith('.png') ||
ext?.endsWith('.jpg') ||
ext?.endsWith('.jpeg');
});

console.log(`找到 ${imageFiles.length} 个图片文件`);

// 处理每个图片
for (const file of imageFiles) {
if (file.name) {
const filePath = await join(directory, file.name);
console.log("处理:", filePath);
// 执行图片处理...
}
}
}

最佳实践

  1. 始终检查返回值:用户可能取消对话框
  2. 设置合适的过滤器:帮助用户找到正确的文件类型
  3. 提供默认路径:提升用户体验
  4. 处理错误情况:文件操作可能失败

下一步