基於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, // 視窗唯一label title: '', // 視窗標題 url: '', // 路由地址url width: 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) return await appWindow.show() await appWindow.unminimize() await appWindow.setFocus() }) // 隱藏窗體 await listen('win-hide', async(event) => { if(appWindow.label.indexOf('main') == -1) return await 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 = 100 export 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) return let position = event.payload if(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 = true if(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 : 45 export 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) return let position = event.payload if(win) { await win.setAlwaysOnTop(true) await win.setFocus() await win.setPosition(new LogicalPosition(position.x, position.y - menuBoxWindowHeight)) await win.show() } }) }
<!--托盤右鍵選單--> <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 = true if(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開發多視窗實踐,自定義托盤圖示訊息提醒,右鍵選單的一些簡單分享,效果還是比較粗糙,主要是為了實現功能思路,希望以上分享對大家有所幫助哈!