对话框
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);
// 执行图片处理...
}
}
}
最佳实践
- 始终检查返回值:用户可能取消对话框
- 设置合适的过滤器:帮助用户找到正确的文件类型
- 提供默认路径:提升用户体验
- 处理错误情况:文件操作可能失败