首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

基于Tauri2+Vue3搭建桌面端程序|tauri2+vite5多窗口|消息提醒|托盘闪烁

编程知识
2024年09月16日 23:45

基于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()
        }
    })
}

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 = 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开发多窗口实践,自定义托盘图标消息提醒,右键菜单的一些简单分享,功能还是比较粗糙,主要是为了实现功能思路,希望以上分享对大家有所帮助哈!

 

From:https://www.cnblogs.com/xiaoyan2017/p/18416811
本文地址: http://shuzixingkong.net/article/2070
0评论
提交 加载更多评论
其他文章 Java读取寄存器数据的方法
本文简要介绍了在Java中直接读取硬件寄存器(如CPU寄存器、I/O端口等)通常不是一个直接的任务,因为Java设计之初就是为了跨平台的安全性和易用性,它并不直接提供访问底层硬件的API。不过,在嵌入式系统、工业控制或需要直接与硬件交互的特定场景中,可能会使用JNI(Java Native Inte
LeetCode题集-4 - 寻找两个有序数组的中位数,图文并茂,六种解法,万字讲解
题目:给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (m+n)) 。 作为目前遇到的第一个困难级别题目,我感觉这题还是挺难的,研究了三天总算研究明白了,下面就给大家分享一下这题的几种
LeetCode题集-4 - 寻找两个有序数组的中位数,图文并茂,六种解法,万字讲解 LeetCode题集-4 - 寻找两个有序数组的中位数,图文并茂,六种解法,万字讲解 LeetCode题集-4 - 寻找两个有序数组的中位数,图文并茂,六种解法,万字讲解
Go runtime 调度器精讲(十):异步抢占
原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 前面介绍了运行时间过长和系统调用引起的抢占,它们都属于协作式抢占。本讲会介绍基于信号的真抢占式调度。 在介绍真抢占式调度之前看下 Go 的两种抢占式调度器: 抢占式调度器 - Go 1.2 至今 基于协作的抢占式调度器 - Go 1.2 - G
mongo集群同步数据异常,手动同步节点副本数据
转载请注明出处: 数据同步方案 当副本集节点的复制进程落后太多,以至于主节点覆盖了该节点尚未复制的 oplog 条目时,副本集节点就会变为“陈旧”。节点跟不上,就会变得“陈旧”。出现这种情况时,必须删除副本集节点的数据,然后执行初始同步,从而完全重新同步该节点。 MongoDB 提供了两种执行初始同
mongo集群同步数据异常,手动同步节点副本数据
ComfyUI 基础教程(五) —— 应用 IP-Adapter 实现图像风格迁移
中秋假期,又可以玩玩 AI 了。前面介绍了 ComfyUI 的 Lora 模型以及 ControlNet,本文介绍另一个非常重要且使用的节点,IP-Adapter。 一、 IP-Adapter 概念 1.1 IPAdapter 的介绍 IP-Adapter 的是腾讯 ailab 实验室发布的一个 S
ComfyUI 基础教程(五) —— 应用 IP-Adapter 实现图像风格迁移 ComfyUI 基础教程(五) —— 应用 IP-Adapter 实现图像风格迁移 ComfyUI 基础教程(五) —— 应用 IP-Adapter 实现图像风格迁移
Angular 18+ 高级教程 – 国际化 Internationalization i18n
介绍 先讲讲名词。 Internationalization 的缩写是 i18n,中文叫国际化。 Globalization 是 Internationalization 的同义词,都是指国际化。 Localization 的缩写是 l10n,中文叫本地化。 i18n vs l10n 一个国际化,一
Angular 18+ 高级教程 – 国际化 Internationalization i18n Angular 18+ 高级教程 – 国际化 Internationalization i18n Angular 18+ 高级教程 – 国际化 Internationalization i18n
Spring框架漏洞总结
目录SpEL注入攻击Spring H2 Database Console未授权访问Spring Security OAuth2远程命令执行漏洞(CVE-2016-4977)Spring WebFlow远程代码执行漏洞(CVE-2017-4971)Spring Data Rest远程命令执行漏洞(CV
Spring框架漏洞总结 Spring框架漏洞总结 Spring框架漏洞总结
C++11 线程同步接口std::condition_variable和std::future的简单使用
std::condition_variable 条件变量std::condition_variable有wait和notify接口用于线程间的同步。如下图所示,Thread 2阻塞在wait接口,Thread 1通过notify接口通知Thread 2继续执行。 具体参见示例代码: #include
C++11 线程同步接口std::condition_variable和std::future的简单使用 C++11 线程同步接口std::condition_variable和std::future的简单使用 C++11 线程同步接口std::condition_variable和std::future的简单使用