tauri 提供了两种系统托盘的创建方式,托盘 API 在 JavaScript 和 Rust 中均可用。
通用基本配置(不管是 JavaScript 还是 Rust):
我们需要在 src-tauri/Cargo.toml 中写入包含系统托盘的必要功能 tray-icon,image-png。
[package]
name = "tauri-app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
[lib]
name = "tauri_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
# 为 tauri 的 features 数组里加入 "tray-icon",保存时自动更新依赖。
# "image-png" 是为了支持 Image api 读取文件存在的,详细请参考源码:
# import { Image } from ''@tauri-apps/api/image''; static fromPath 函数的注释
# 如果没有添加 "image-png",则在读取 png 时将会出现该错误:"Uncaught (in promise) expected RGBA image data, found a file path"
tauri = { version = "2", features = ["tray-icon", "image-png"] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
一、使用 JavaScript 创建系统托盘
一. 使用 JavaScript 创建系统托盘:
// 导入系统托盘,
import { TrayIcon } from ''@tauri-apps/api/tray'';
/**
* 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等
*/
const options = {
// icon 的相对路径基于:项目根目录/src-tauri/
icon : "icons/32x32.png",
// 托盘提示,悬浮在托盘图标上可以显示 tauri-app
tooltip: ''tauri-app'',
// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。
menuOnLeftClick: false,
// 托盘菜单,后续创建.....
menu: undefined,
// 托盘图标上事件的处理程序。这个下面需要详细说一下
action: (event) => {}
}
/**
* 创建系统托盘
*/
export async function createTray() {
const tray = await TrayIcon.new(options);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
二. 设置 JavaScript 系统托盘事件
在上面的代码中,我们通过运行 createTray 函数就可以得到一个系统托盘图标。但是光有一个图标还是不够的,所以我们还得有相对应的事件,通过 Ctrl+左键 进入源码部分,我们可以看到 options.action 函数传入了一个 TrayIconEvent 联合类型事件:
export type TrayIconEvent = (TrayIconEventBase<''Click''> & TrayIconClickEvent) | (TrayIconEventBase<''DoubleClick''> & Omit<TrayIconClickEvent, ''buttonState''>) | TrayIconEventBase<''Enter''> | TrayIconEventBase<''Move''> | TrayIconEventBase<''Leave''>;
- 1
在这里,它提供了 Click、DoubleClick、Enter、Move、Leave(单击,双击,鼠标移入,鼠标拖动,鼠标移出)这五个托盘事件。
action 属性函数的 event 参数我们需要使用 switch 或者是 if 把它的 type 筛选出来,在 case 选项中执行我们的事件:
action: (event) => {
switch(event.type) {
case ''Click'':
console.log(''单击事件'');
console.log(''Click event:'', event);
console.log(''Mouse button:'', event.button);
console.log(''Button state:'', event.buttonState);
break;
case ''DoubleClick'':
console.log(''双击事件'');
console.log(''DoubleClick event:'', event);
break;
case ''Enter'':
console.log(''鼠标进入托盘图标'');
console.log(''Mouse entered tray icon area:'', event);
break;
case ''Move'':
console.log(''鼠标移动托盘图标'');
console.log(''Mouse moved over tray icon:'', event);
break;
case ''Leave'':
console.log(''鼠标离开托盘图标'');
console.log(''Mouse left tray icon area:'', event);
break;
default:
console.log(`未定义事件类型: ${event.type}`);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
对于我们来说,常用的也就只有第一个 Click 事件,其他的用的不多,所以别的我也就不说了,大家自行选择就好。
那么接下来,我们来试一试单击托盘图标会怎么样:

看,它单击事件触发了两次,一次是鼠标按下(Button state: Down),一次是鼠标松开(Button state: Up)。 对于我们来说,我们只需要一次就可以,通过简单的 if 判断可以过滤掉一次事件:
if(event.buttonState === ''Down'')
或者
if(event.buttonState === ''Up'')

当然还有一件事,我们多次修改了文件,
vite在多次的重载中难免会造成多个系统托盘图标,但是没关系,重启应用后,我还是一个 o( ̄▽ ̄)o
三. JavaScript 设置窗口关闭后,应用进入后台运行:
其实吧,我们只需要隐藏掉窗口就好,至于怎么隐藏呢,我在这里说一下步骤:
tauri 提供了 import { getCurrentWindow } from ''@tauri-apps/api/window''; api,执行 getCurrentWindow 函数可以获取到一个 Window 当前窗体实例,我们可以通过这个实例的 hide() 函数隐藏窗口。
-
关闭系统自带的拖拽栏:设置
tauri.conf.json属性:app.windows.decorations = false

-
使用
HTML+CSS+JS替代系统拖拽栏,重点是这个关闭按钮得换成hide() -
下一步就是参考我的上一篇文档,看看这两步是怎么做的:tauri 2.0创建项目 https://blog.csdn.net/xiaoyuanbaobao/article/details/143751093
在干完上面的操作后,我们就可以关闭窗口,在创建托盘的页面引入 getCurrentWindow 函数在单击事件中调用它的 show() 方法把进入后台运行的程序窗口打开。
只是把窗口显示出来就够了?当然不是,我们还得在它窗口没隐藏的情况下把窗口置顶聚焦,这才符合我们的程序使用习惯。
-
通过
isVisible方法可以检查窗口是否是可视状态 -
通过
show方法可以把窗口显示出来 -
通过
setFocus方法可以聚焦窗口,让窗口置顶显示 -
通过
unminimize方法可以解除最小化,怎么好像混进去什么奇怪的东西?
这 TM 是个大坑!!!!!!在进入最小化的情况下,聚焦无法将页面置顶,只有解除最小化之后聚焦才是有效的。
这个在rust端api中并没有这个问题,但是在Javascript端api是这样的。!!!需要特别注意!!!
tauri遵循最小权限原则,在运行中可能会碰到如下权限不足的问题:
在src-tauri/capabilities/default.json文件的permissions属性数组中加入这几个权限:
"core:window:allow-set-focus"、"core:window:allow-close、
"core:window:allow-show"、"core:window:allow-is-visible"、
"core:window:allow-unminimize"、"core:window:allow-is-minimized"
写到现在给大家再看看现在的 Tray 代码吧,目前它任然是不完整的,我还没挂托盘菜单 Menu 呢:
// 导入系统托盘
import { TrayIcon } from ''@tauri-apps/api/tray'';
import { getCurrentWindow } from ''@tauri-apps/api/window'';
/**
* 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等
*/
const options = {
// icon 的相对路径基于:项目根目录/src-tauri/
icon : "icons/32x32.png",
// 托盘提示,悬浮在托盘图标上可以显示 tauri-app
tooltip: ''tauri-app'',
// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。
menuOnLeftClick: false,
// 托盘图标上事件的处理程序。
action: (event) => {
// 左键点击事件
if(event.type === ''Click'' && event.button === "Left" && event.buttonState === ''Down'') {
console.log(''单击事件'');
// 显示窗口
winShowFocus();
}
}
}
/**
* 窗口置顶显示
*/
async function winShowFocus() {
// 获取窗体实例
const win = getCurrentWindow();
// 检查窗口是否见,如果不可见则显示出来
if(!(await win.isVisible())) {
win.show();
}else {
// 检查是否处于最小化状态,如果处于最小化状态则解除最小化
if(await win.isMinimized()) {
await win.unminimize();
}
// 窗口置顶
await win.setFocus();
}
}
/**
* 创建系统托盘
*/
export async function createTray() {
const tray = await TrayIcon.new(options);
console.log(tray)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
四. Javascript 前端添加托盘菜单 Menu
创建系统托盘菜单:
import { Menu } from ''@tauri-apps/api/menu'';
/**
* 创建托盘菜单
*/
async function createMenu() {
return await Menu.new({
// items 的显示顺序是倒过来的
items: [
{
id: ''show'',
text: ''显示窗口'',
action: () => {
winShowFocus();
}
},
{
// 菜单 id
id: ''quit'',
// 菜单文本
text: ''退出'',
// 菜单事件处理程序
action: () => {
// 退出应用还需要引入别的依赖
console.log(''退出应用'');
}
}
]
})
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
将托盘菜单添加进系统托盘,修改创建系统托盘的 createTray 函数:
/**
* 创建系统托盘
*/
export async function createTray() {
// 获取 menu
options.menu = await createMenu();
const tray = await TrayIcon.new(options);
console.log(tray)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9

五. JavaScript 退出应用
在窗体化应用中,通常关闭最后一个窗口,就可以退出整个应用:
getCurrentWindow().close();
但是这样可能并不一定保险,我在 getCurrentWindow 并没有发现类似于 app.exit 或者 app.quit 的函数。所以我们又得引入新的东西了:进程 process https://tauri.app/zh-cn/plugin/process/
- 首先使用项目的包管理器来添加依赖:
npm run tauri add process
- 引入
api:import { exit, relaunch } from ‘@tauri-apps/plugin-process’;
在进程管理中,只有两个东西,一个是 exit 退出应用函数,一个是 relaunch 重启应用函数,没了 ಠ_ಠ 。
用法也没啥好说的,直接调用就好:
// 退出应用
await exit(0);
// 重启应用
await relaunch();
六. JavaScript 创建系统托盘最终代码:
createTray 函数是对外导出的,外部调用跑就行,不会真的有人没调这个方法吧?(⓿_⓿)
// 导入系统托盘
import { TrayIcon } from ''@tauri-apps/api/tray'';
// 获取当前窗口
import { getCurrentWindow } from ''@tauri-apps/api/window'';
// 托盘菜单
import { Menu } from ''@tauri-apps/api/menu'';
// 进程管理
import { exit } from ''@tauri-apps/plugin-process'';
/**
* 在这里你可以添加一个托盘菜单,标题,工具提示,事件处理程序等
*/
const options = {
// icon 的相对路径基于:项目根目录/src-tauri/,其他 tauri api 相对路径大抵都是这个套路
icon : "icons/32x32.png",
// 托盘提示,悬浮在托盘图标上可以显示 tauri-app
tooltip: ''tauri-app'',
// 是否在左键点击时显示托盘菜单,默认为 true。当然不能为 true 啦,程序进入后台不得左键点击图标显示窗口啊。
menuOnLeftClick: false,
// 托盘图标上事件的处理程序。
action: (event) => {
// 左键点击事件
if(event.type === ''Click'' && event.button === "Left" && event.buttonState === ''Down'') {
console.log(''单击事件'');
// 显示窗口
winShowFocus();
}
}
}
/**
* 窗口置顶显示
*/
async function winShowFocus() {
// 获取窗体实例
const win = getCurrentWindow();
// 检查窗口是否见,如果不可见则显示出来
if(!(await win.isVisible())) {
win.show();
}else {
// 检查是否处于最小化状态,如果处于最小化状态则解除最小化
if(await win.isMinimized()) {
await win.unminimize();
}
// 窗口置顶
await win.setFocus();
}
}
/**
* 创建托盘菜单
*/
async function createMenu() {
return await Menu.new({
// items 的显示顺序是倒过来的
items: [
{
id: ''show'',
text: ''显示窗口'',
action: () => {
winShowFocus();
}
},
{
// 菜单 id
id: ''quit'',
// 菜单文本
text: ''退出'',
// 菜单项点击事件
action: () => {
// 退出应用
exit(0);
}
}
]
})
}
/**
* 创建系统托盘
*/
export async function createTray() {
// 获取 menu
options.menu = await createMenu();
await TrayIcon.new(options);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
二、在 Rust 创建系统托盘菜单:
其实吧,我觉得 JavaScript api 创建托盘菜单已经足够了,但是由于我 rust 学的一坨屎 (啥也不会!),所以在这里我不会说太多,直接将操作甩这就完了。
1. 在 src-tauri/Cargo.toml 中写入包含系统托盘的必要功能 tray-icon
2. 使用 html+css+Javascript 替换掉系统默认的拖拽栏,详情请参考:tauri 2.0创建项目 https://blog.csdn.net/xiaoyuanbaobao/article/details/143751093
3.Rust 创建系统托盘:
修改 src-tauri/lib.rs 文件,在文件中添加如下内容:
// 导入系统托盘所需的依赖, 导入的全都是
use tauri::{
tray::{
TrayIconBuilder,
MouseButtonState,
MouseButton,
TrayIconEvent
},
menu::{
Menu,
MenuItem
},
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_process::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
// setup 部分是系统托盘
.setup(|app| {
let show_i = MenuItem::with_id(app, "show", "显示", true, None::<&str>)?;
let quit_i = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show_i, &quit_i])?;
// 创建系统托盘
let _tray = TrayIconBuilder::new()
// 添加托盘图标
.icon(app.default_window_icon().unwrap().clone())
// 添加菜单
.menu(&menu)
// 禁用鼠标左键点击图标显示托盘菜单
.menu_on_left_click(false)
// 监听托盘图标发出的鼠标事件
.on_tray_icon_event(|tray, event| match event {
// 左键点击托盘图标显示窗口
TrayIconEvent::Click {
id: _,
position: _,
rect: _,
button: MouseButton::Left,
button_state: MouseButtonState::Up,
} => {
let win = tray
.app_handle()
.get_webview_window("main")
.expect("REASON");
match win.is_visible() {
Ok(visible) if !visible => {
win.show().unwrap();
}
Err(e) => eprintln!("{}", e),
_ => (),
};
// 获取窗口焦点
win.set_focus().unwrap();
}
_ => {}
})
// 监听菜单事件
.on_menu_event(|app, event| match event.id.as_ref() {
"show" => {
let win = app.get_webview_window("main").unwrap();
match win.is_visible() {
Ok(visible) if !visible => {
win.show().unwrap();
}
Err(e) => eprintln!("{}", e),
_ => (),
};
// 获取窗口焦点
win.set_focus().unwrap();
}
"quit" => {
app.exit(0);
}
_ => {}
})
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
在这一大串代码中,.setup 部分是系统托盘部分的代码,注意别忘了粘贴的位置,以及导入依赖。
三、结束
tauri 2.0 创建系统托盘的教程到这里就结束了,让我想想接下来要在官方文档里薅哪一步分下来写 ヽ( ̄ω ̄( ̄ω ̄〃)ゝ


