基于tauri2+vite5+vue3封装多窗口实践|自定义消息提醒|托盘右键菜单及图标闪烁
这段时间一直在捣鼓最新版Tauri2.x整合Vite5搭建桌面端多开窗体应用实践。tauri2.0相较于1.0版本api有了比较多的更改,而且tauri2支持创建android/ios应用。至于具体的api变更,大家可以去官网查阅文档资料。
https://v2.tauri.app/start/migrate/from-tauri-1/
版本信息
"@tauri-apps/api": ">=2.0.0-rc.0", "@tauri-apps/cli": ">=2.0.0-rc.0", "vue": "^3.3.4", "vite": "^5.3.1"
创建tauri2+vue3项目模板
官网提供了多种方式创建tauri2+vue3项目。
// 创建项目模板 yarn create tauri-app --rc // 进入项目目录 cd tauri-app // 安装依赖 yarn // 运行项目 yarn tauri dev
内置了多种热门前端框架模板可供选择。
// 运行到桌面端 yarn tauri dev // 初始化android yarn tauri android init // 运行到android yarn tauri android dev
至此一个简单的tauri2+vue3项目模板就搭建好了。
tauri2封装多窗口管理
通过封装一个tauri多窗口类,只需传入配置参数,即可快速创建一个新窗体,简化调用方式。
createWin({label: 'manage',title: '管理页面',url: '/manage',width: 960,height: 750,center: false,x: 320,y: 500,resizable: false,alwaysOnTop: true, })
/*** @desc Tauri2多窗口封装管理* @author: Andy QQ:282310962* @time 2024.9*/import { getAllWindows, getCurrentWindow } from '@tauri-apps/api/window' import { WebviewWindow, getAllWebviewWindows, getCurrentWebviewWindow} from '@tauri-apps/api/webviewWindow' import { relaunch, exit } from '@tauri-apps/plugin-process' import { emit, listen } from '@tauri-apps/api/event'import { setWin } from './actions'const appWindow = getCurrentWindow()// 创建窗口参数配置 export const windowConfig = {label: null, // 窗口唯一labeltitle: '', // 窗口标题url: '', // 路由地址urlwidth: 1000, // 窗口宽度height: 640, // 窗口高度minWidth: null, // 窗口最小宽度minHeight: null, // 窗口最小高度x: null, // 窗口相对于屏幕左侧坐标y: null, // 窗口相对于屏幕顶端坐标center: true, // 窗口居中显示resizable: true, // 是否支持缩放maximized: false, // 最大化窗口decorations: false, // 窗口是否装饰边框及导航条alwaysOnTop: false, // 置顶窗口dragDropEnabled: false, // 禁止系统拖放visible: false, // 隐藏窗口// ... }class Windows {constructor() {// 主窗口this.mainWin = null}// 创建新窗口 async createWin(options) {console.log('-=-=-=-=-=开始创建窗口')const args = Object.assign({}, windowConfig, options)// 判断窗口是否存在const existWin = await this.getWin(args.label)if(existWin) {console.log('窗口已存在>>', existWin)// ... }// 创建窗口对象const win = new WebviewWindow(args.label, args)// 窗口创建完毕/失败win.once('tauri://created', async() => {console.log('tauri://created')// 是否主窗口if(args.label.indexOf('main') > -1) {// ... }// 是否最大化if(args.maximized && args.resizable) {console.log('is-maximized')await win.maximize()}})win.once('tauri://error', async(error) => {console.log('window create error!', error)})}// 获取窗口 async getWin(label) {return await WebviewWindow.getByLabel(label)}// 获取全部窗口 async getAllWin() {// return getAll()return await getAllWindows()}// 开启主进程监听事件 async listen() {console.log('——+——+——+——+——+开始监听窗口')// 创建新窗体await listen('win-create', (event) => {console.log(event)this.createWin(event.payload)})// 显示窗体await listen('win-show', async(event) => {if(appWindow.label.indexOf('main') == -1) returnawait appWindow.show()await appWindow.unminimize()await appWindow.setFocus()})// 隐藏窗体await listen('win-hide', async(event) => {if(appWindow.label.indexOf('main') == -1) returnawait appWindow.hide()})// 关闭窗体await listen('win-close', async(event) => {await appWindow.close()})// ... } }export default Windows
actions.js封装一些调用方法。
import { emit } from '@tauri-apps/api/event'/*** @desc 创建新窗口* @param args {object} {label: 'new', url: '/new', width: 500, height: 300, ...}*/export async function createWin(args) {await emit('win-create', args) }// .../*** @desc 登录窗口*/export async function loginWin() {await createWin({label: 'main_login',title: '登录',url: '/login',width: 400,height: 320,resizable: false,alwaysOnTop: true}) }export async function mainWin() {await createWin({label: 'main',title: 'TAURI-WINDOWMANAGER',url: '/',width: 800,height: 600,minWidth: 500,minHeight: 360,}) }export async function aboutWindow() {await createWin({label: 'about',title: '关于',url: '/about',width: 450,height: 360,}) }
tauri2创建系统托盘图标|托盘闪烁消息提醒|托盘右键菜单
tauri2创建系统托盘图标,实现类似QQ消息提醒,自定义托盘右键菜单。
在src-tauri/src目录下,新建一个tray.rs托盘文件。
use tauri::{tray::{MouseButton, TrayIconBuilder, TrayIconEvent}, Emitter, Manager, Runtime }; use std::thread::{sleep}; use std::time::Duration;pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {let _ = TrayIconBuilder::with_id("tray").tooltip("tauri").icon(app.default_window_icon().unwrap().clone()).on_tray_icon_event(|tray, event| match event {TrayIconEvent::Click {id: _,position,rect: _,button,button_state: _,} => match button {MouseButton::Left {} => {// ... }MouseButton::Right {} => {tray.app_handle().emit("tray_contextmenu", position).unwrap();}_ => {}},TrayIconEvent::Enter {id: _,position,rect: _,} => {tray.app_handle().emit("tray_mouseenter", position).unwrap();}TrayIconEvent::Leave {id: _,position,rect: _,} => {// sleep(Duration::from_millis(500));tray.app_handle().emit("tray_mouseleave", position).unwrap();}_ => {}}).build(app);Ok(()) }
在lib.rs中引入托盘配置。
// ... mod tray;#[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() {tauri::Builder::default()// ....setup(|app| {#[cfg(all(desktop))]{let handle = app.handle();tray::create_tray(handle)?;}Ok(())}).invoke_handler(tauri::generate_handler![greet]).run(tauri::generate_context!()).expect("error while running tauri application"); }
- 托盘消息提醒
新建一个msg新窗口,通过获取鼠标滑过托盘图标的position坐标给到msg窗口x,y参数
import { WebviewWindow } from '@tauri-apps/api/webviewWindow' import { emit, listen } from '@tauri-apps/api/event' import { LogicalPosition } from '@tauri-apps/api/window'export let messageBoxWindowWidth = 280 export let messageBoxWindowHeight = 100export default async function CreateMsgBox() {console.log('start create msgbox...')let webview = new WebviewWindow("msgbox", {url: "/msg",title: "消息通知",width: messageBoxWindowWidth,height: messageBoxWindowHeight,skipTaskbar: true,decorations: false,center: false,resizable: false,alwaysOnTop: true,focus: true,x: window.screen.width + 50,y: window.screen.height + 50,visible: false})// 托盘消息事件await webview.listen('tauri://window-created', async () => {console.log('msgbox create')})await webview.listen('tauri://blur', async () => {console.log('msgbox blur')const win = await WebviewWindow.getByLabel('msgbox')await win.hide()})await webview.listen('tauri://error', async(error) => {console.log('msgbox error!', error)})// 监听托盘事件let trayEnterListen = listen('tray_mouseenter', async (event) => {// console.log(event) const win = await WebviewWindow.getByLabel('msgbox')if(!win) returnlet position = event.payloadif(win) {await win.setAlwaysOnTop(true)await win.setFocus()await win.setPosition(new LogicalPosition(position.x - messageBoxWindowWidth / 2, window.screen.availHeight - messageBoxWindowHeight))await win.show()}})let trayLeaveListen = listen('tray_mouseleave', async (event) => {console.log(event)const win = await WebviewWindow.getByLabel('msgbox')await win.hide()}) }
设置托盘图标闪烁 flashTray(true) 和取消闪烁 flashTray(false)
<script setup>// ... const flashTimer = ref(false)const flashTray = async(bool) => {let flag = trueif(bool) {TrayIcon.getById('tray').then(async(res) => {clearInterval(flashTimer.value)flashTimer.value = setInterval(() => {if(flag) {res.setIcon(null)}else {// 支持把自定义图标放在默认icons文件夹,通过如下方式设置图标// res.setIcon('icons/msg.png')// 支持把自定义图标放在自定义文件夹tray,需要配置tauri.conf.json参数 "bundle": {"resources": ["tray"]}res.setIcon('tray/msg.png')}flag = !flag}, 500)})}else {clearInterval(flashTimer.value)let tray = await TrayIcon.getById("tray")tray.setIcon('icons/icon.png')}} </script>
或者放在自定义文件夹。
如果放在自定义文件夹tray,则需要配置tauri.conf.json文件resources字段。
"bundle": {..."resources": ["tray"] },
- 托盘右键菜单
其实窗口原理和消息提醒差不多。
import { ref } from 'vue' import { WebviewWindow } from '@tauri-apps/api/webviewWindow' import { emit, listen } from '@tauri-apps/api/event' import { PhysicalPosition, LogicalPosition } from '@tauri-apps/api/window' import { TrayIcon } from '@tauri-apps/api/tray' import { invoke } from '@tauri-apps/api/core'export let menuBoxWindowWidth = 150 export let menuBoxWindowHeight = JSON.parse(localStorage.getItem('logged')) ? 320 : 45export default async function CreateTraymenu() {console.log('start create traymenu...')let webview = new WebviewWindow("traymenu", {url: "/menu",title: "消息通知",width: menuBoxWindowWidth,height: menuBoxWindowHeight,skipTaskbar: true,decorations: false,center: false,resizable: false,alwaysOnTop: true,focus: true,x: window.screen.width + 50,y: window.screen.height + 50,visible: false})// 托盘消息事件await webview.listen('tauri://window-created', async () => {console.log('traymenu create')})await webview.listen('tauri://blur', async () => {console.log('traymenu blur')const win = await WebviewWindow.getByLabel('traymenu')await win.hide()})await webview.listen('tauri://error', async(error) => {console.log('traymenu error!', error)})// 监听托盘事件let trayEnterListen = listen('tray_contextmenu', async (event) => {console.log(event)const win = await WebviewWindow.getByLabel('traymenu')if(!win) returnlet position = event.payloadif(win) {await win.setAlwaysOnTop(true)await win.setFocus()await win.setPosition(new LogicalPosition(position.x, position.y - menuBoxWindowHeight))await win.show()}}) }
Msg/index.vue模板
<!--托盘右键菜单--> <script setup> import { ref } from 'vue' import { WebviewWindow } from "@tauri-apps/api/webviewWindow" import { TrayIcon } from '@tauri-apps/api/tray' import { invoke } from '@tauri-apps/api/core'const logged = JSON.parse(localStorage.getItem('logged'))const handleMainShow = async () => {const traywin = await WebviewWindow.getByLabel('traymenu')await traywin.hide()const homewin = await WebviewWindow.getByLabel('main')await homewin.show()await homewin.unminimize()await homewin.setFocus()}const flashTimer = ref(false)const flashTray = async(bool) => {let flag = trueif(bool) {TrayIcon.getById('tray').then(async(res) => {clearInterval(flashTimer.value)flashTimer.value = setInterval(() => {if(flag) {res.setIcon(null)}else {// res.setIcon(defaultIcon)// 支持把自定义图标放在默认icons文件夹,通过如下方式设置图标// res.setIcon('icons/msg.png')// 支持把自定义图标放在自定义文件夹tray,需要配置tauri.conf.json参数 "bundle": {"resources": ["tray"]} res.setIcon('tray/msg.png')}flag = !flag}, 500)})}else {clearInterval(flashTimer.value)let tray = await TrayIcon.getById("tray")tray.setIcon('icons/icon.png')}} </script><template><div v-if="logged" class="traymenu"><p class="item">😍 我在线上</p><p class="item">😎 隐身</p><p class="item">😏 离开</p><p class="item">😱 忙碌</p><p class="item">关闭所有声音</p><p class="item" @click="flashTray(true)">开启图标闪烁</p><p class="item" @click="flashTray(false)">关闭图标闪烁</p><p class="item" @click="handleMainShow">👀 打开主面板</p><p class="item">💍 退出</p></div><div v-else class="traymenu"><p class="item">💍 退出</p></div> </template>
综上就是tauri2+vue3开发多窗口实践,自定义托盘图标消息提醒,右键菜单的一些简单分享,功能还是比较粗糙,主要是为了实现功能思路,希望以上分享对大家有所帮助哈!