最近一种在捣鼓 Tauri 集成 Vue3 技术开发桌面端应用实践,tauri 实现创建多窗口,窗口之间通讯功能。

开始正文之前,先来了解下 tauri 结合 vue3.js 快速创建项目。

tauri 在 github 上star高达53K+,而且呈快速增长趋势。相比electron构建应用更具优势。

分别用 Tauri 和 Electron 打包测试一个 todo list 程序。

Electron打包体积  69  M,Tauri打包体积才只有  7.5 M。

Tauri 构建的桌面应用体积远远比 Electron 构建的小得多。因为它放弃了体积庞大的 Chromium 内核和Nodejs,tauri前端集成了 webview,后端使用 Rust。而且 Tauri 构建应用还提供了诸多初始化程序模板,比如原生 JavaScript、Vue2/3、React、Svelte.js、SvelteKit 等。

准备工作

首先您需要安装 Rust 及其他系统依赖。

  • "C++ 生成工具" 和 Windows 10 SDK。
  • Tauri 需要 WebView2 才能在 Windows 上呈现网页内容,所以您必须先安装 WebView2。
  • Rust

具体操作,请前往 https://tauri.app/zh/v1/guides/getting-started/prerequisites 来按步骤操作。

  • 创建 tauri 初始化项目

具体的前端框架模板,大家根据实际情况选择。

npm create tauri-app

  • 开发/构建打包

tauri dev  tauri build

非常简单的几步就能快速搭建 vue3+tauri 桌面端模板。接下来就能顺利的开发了。

tauri 也提供了如下几种常用创建多窗口的方法。

  • tauri.conf.json
{
"tauri": {
"windows": [
{
"label": "external",
"title": "Tauri App",
"url": "https://tauri.app"
},
{
"label": "local",
"title": "Tauri",
"url": "home.html"
}
]
}
}
  • src-tauri/src/main.rs
tauri::Builder::default()
.setup(|app| {
let docs_window = tauri::WindowBuilder::new(
app,
"external", /* the unique window label */
tauri::WindowUrl::External("https://tauri.app/".parse().unwrap())
).build()?;
let local_window = tauri::WindowBuilder::new(
app,
"local",
tauri::WindowUrl::App("index.html".into())
).build()?;
Ok(())
})
  • 通过前端 JS 创建窗口。
import { WebviewWindow } from '@tauri-apps/api/window'
const webview = new WebviewWindow('main_win', {
url: '/home',
}) webview.once('tauri://created', function () {
// webview window successfully created
})
webview.once('tauri://error', function (e) {
// an error happened creating the webview window
})

具体详细的介绍,大家可以去官网查看,文档都有非常详细的讲解。

https://tauri.app/zh/v1/guides/features/multiwindow

上面介绍的方法比较适用于一些简单的窗口,对于一些复杂多开窗口,还得封装一个窗口创建器,直接通过传入参数快速生成窗体。

createWin({
label: 'Home',
title: '主页',
url: '/home',
width: 800,
height: 600,
})

新建一个 windows 文件夹,用来封装窗口及调用窗口。

/**
* @desc 窗口容器
* @author: YXY Q:282310962
* @time 2022.10
*/ import { WebviewWindow, appWindow, getAll, getCurrent } from '@tauri-apps/api/window'
import { relaunch, exit } from '@tauri-apps/api/process'
import { emit, listen } from '@tauri-apps/api/event' import { setWin } from './actions' // 系统参数配置
export const windowConfig = {
label: null, // 窗口唯一label
title: '', // 窗口标题
url: '', // 路由地址url
width: 900, // 窗口宽度
height: 640, // 窗口高度
minWidth: null, // 窗口最小宽度
minHeight: null, // 窗口最小高度
x: null, // 窗口相对于屏幕左侧坐标
y: null, // 窗口相对于屏幕顶端坐标
center: true, // 窗口居中显示
resizable: true, // 是否支持缩放
maximized: false, // 最大化窗口
decorations: false, // 窗口是否无边框及导航条
alwaysOnTop: false, // 置顶窗口
} class Windows {
constructor() {
this.mainWin = null
} // 获取窗口
getWin(label) {
return WebviewWindow.getByLabel(label)
} // 获取全部窗口
getAllWin() {
return getAll()
} // 创建新窗口
async createWin(options) {
const args = Object.assign({}, windowConfig, options) // 判断窗口是否存在
const existWin = getAll().find(w => w.label == args.label)
if(existWin) {
if(existWin.label.indexOf('main') == -1) {
await existWin?.unminimize()
await existWin?.setFocus()
return
}
await existWin?.close()
} // 创建窗口对象
let win = new WebviewWindow(args.label, args) // 是否最大化
if(args.maximized && args.resizable) {
win.maximize()
} // 窗口创建完毕/失败
win.once('tauri://created', async() => {
console.log('window create success!')
...
}) win.once('tauri://error', async() => {
console.log('window create error!')
})
} // 开启主进程监听事件
async listen() {
// 创建新窗体
await listen('win-create', (event) => {
console.log(event)
this.createWin(JSON.parse(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-exit', async(event) => {
setWin('logout')
await exit()
}) // 重启应用
await listen('win-relaunch', async(event) => {
await relaunch()
}) // 主/渲染进程传参
await listen('win-setdata', async(event) => {
await emit('win-postdata', JSON.parse(event.payload))
})
}
} export default Windows

actions.js进行一些调用处理。

/**
* 处理渲染器进程到主进程的异步通信
*/ import { WebviewWindow } from '@tauri-apps/api/window'
import { emit } from '@tauri-apps/api/event' /**
* @desc 创建新窗口
*/
export async function createWin(args) {
await emit('win-create', args)
} /**
* @desc 获取窗口
* @param args {string}
*/
export async function getWin(label) {
return await WebviewWindow.getByLabel(label)
} /**
* @desc 设置窗口
* @param type {string} 'show'|'hide'|'close'|'min'|'max'|'max2min'|'exit'|'relaunch'
*/
export async function setWin(type) {
await emit('win-' + type)
} /**
* @desc 登录窗口
*/
export async function loginWin() {
await createWin({
label: 'Login',
title: '登录',
url: '/login',
width: 320,
height: 420,
resizable: false,
alwaysOnTop: true,
})
} // ...

在需要调用创建窗口的.vue页面,引入actions.js文件。

import { loginWin, createWin } from '@/windows/actions'

const createManageWin = async() => {
createWin({
label: 'Manage',
title: '管理页面',
url: '/manage',
width: 600,
height: 450,
minWidth: 300,
minHeight: 200
})
} const createAboutWin = async() => {
createWin({
label: 'About',
title: '关于页面',
url: '/about',
width: 500,
height: 500,
resizable: false,
alwaysOnTop: true
})
}

一些注意点

  • 创建系统托盘图标

use tauri::{
AppHandle, Manager,
CustomMenuItem, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu
}; // 托盘菜单
pub fn menu() -> SystemTray {
let quit = CustomMenuItem::new("quit".to_string(), "Quit");
let show = CustomMenuItem::new("show".to_string(), "Show");
let hide = CustomMenuItem::new("hide".to_string(), "Hide");
let change_ico = CustomMenuItem::new("change_ico".to_string(), "Change Icon");
let tray_menu = SystemTrayMenu::new()
.add_submenu(SystemTraySubmenu::new(
"Language", // 语言菜单
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("lang_english".to_string(), "English"))
.add_item(CustomMenuItem::new("lang_zh_CN".to_string(), "简体中文"))
.add_item(CustomMenuItem::new("lang_zh_HK".to_string(), "繁体中文")),
))
.add_native_item(SystemTrayMenuItem::Separator) // 分割线
.add_item(change_ico)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(hide)
.add_item(show)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(quit); SystemTray::new().with_menu(tray_menu)
} // 托盘事件
pub fn handler(app: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::LeftClick {
position: _,
size: _,
..
} => {
println!("点击左键");
}
SystemTrayEvent::RightClick {
position: _,
size: _,
..
} => {
println!("点击右键");
}
SystemTrayEvent::DoubleClick {
position: _,
size: _,
..
} => {
println!("双击");
}
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"change_ico" => { // 更新托盘图标
app.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../icons/new.png").to_vec()
))
.unwrap();
}
lang if lang.contains("lang_") => { // 选择语言,匹配 id 前缀包含 `lang_` 的事件
Lang::new(
app,
id, // 点击菜单的 id
vec![
Lang {
name: "English",
id: "lang_english",
},
Lang {
name: "繁体中文",
id: "lang_zh_HK",
},
Lang {
name: "简体中文",
id: "lang_zh_CN",
},
],
);
}
"hide" => {
// let window = app.get_window("main").unwrap();
// window.show().unwrap();
println!("点击隐藏");
}
"show" => {
println!("点击显示");
}
"quit" => {
println!("点击退出");
std::process::exit(0);
}
_ => {}
},
_ => {}
}
} struct Lang<'a> {
name: &'a str,
id: &'a str,
} impl Lang<'static> {
fn new(app: &AppHandle, id: String, langs: Vec<Lang>) {
// 获取点击的菜单项
langs.iter().for_each(|lang| {
let handle = app.tray_handle().get_item(lang.id);
if lang.id.to_string() == id.as_str() {
// 设置菜单名称
handle.set_title(format!(" {}", lang.name)).unwrap();
// 还可以使用 `set_selected`、`set_enabled` 和 `set_native_image`(仅限 macOS)
handle.set_selected(true).unwrap();
} else {
handle.set_title(lang.name).unwrap();
handle.set_selected(false).unwrap();
}
});
}
}

创建托盘图标,默认图标文件在src-tauri/icons目录下。如果想使用自定义的.ico图标,可通过tauri.cong.json文件配置。

"systemTray": {
"iconPath": "icons/tray.ico",
"iconAsTemplate": true,
"menuOnLeftClick": false
}

如果setIcon报错,则需要在 src-tauri/src/Cargo.toml 中配置 icon-icoicon-png

  • tauri 配置自定义拖拽区域。

当创建窗口的时候配置了 decorations: false  则会不显示窗口边框及顶部导航栏。

此时在需要拖动元素上加一个  data-tauri-drag-region 属性,即可实现自定义区域拖动窗口功能。这个功能有些类似 electron 中自定义拖拽 -webkit-app-region: drag

不过点击窗口右键,会出现系统菜单。这样显得应用不够原生,可以简单的通过禁用右键菜单来屏蔽功能。

export function disableWinMenu() {
document.addEventListener('contextmenu', e => e.preventDefault())
}
disableWinMenu()

好了,基于 tauri+vue3 构建多窗口桌面应用就分享到这里。希望对大家有丢丢帮助哈~~

最后附上一个 vue3+electron 仿macOs桌面UI系统

https://www.cnblogs.com/xiaoyan2017/p/14926338.html

基于tauri+vue3.x多开窗口|Tauri创建多窗体实践的更多相关文章

  1. 手把手带你实现基于 Vite+Vue3 的在线Excel表格系统

    今天,葡萄带你了解如何基于Vite+Vue3实现一套纯前端在线表格系统. 在正式开始项目介绍之前,首先咱们首先来介绍一下Vite和Vue3. Vue3 2020年09月18日Vue.js 3.0发布, ...

  2. 002-Spring4 快速入门-项目搭建、基于注解的开发bean,Bean创建和装配、基于注解的开发bean,Bean初始化销毁、Bean装配,注解、Bean依赖注入

    一.项目搭建 1.项目创建 eclipse→project explorer→new→Project→Maven Project 默认配置即可创建项目 2.spring配置 <dependenc ...

  3. 基于JavaFX图形界面演示的迷宫创建与路径寻找

    事情的起因是收到了一位网友的请求,他的java课设需要设计实现迷宫相关的程序--如标题概括. 我这边不方便透露相关信息,就只把任务要求写出来. 演示视频指路: 视频过审后就更新链接 完整代码链接: 网 ...

  4. 如何开发一款基于 Vite+Vue3 的在线表格系统(上)

    今天,葡萄带你了解如何基于Vite+Vue3实现一套纯前端在线表格系统. 在正式开始项目介绍之前,首先咱们首先来介绍一下Vite和Vue3. Vue3 Vue是什么?大多前端开发者对这个词已毫不陌生了 ...

  5. 基于Python玩转人工智能最火框架 TensorFlow应用实践✍✍✍

    基于Python玩转人工智能最火框架  TensorFlow应用实践 随着 TensorFlow 在研究及产品中的应用日益广泛,很多开发者及研究者都希望能深入学习这一深度学习框架.而在昨天机器之心发起 ...

  6. 从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

    本文由马蜂窝技术团队电商交易基础平台研发工程师"Anti Walker"原创分享. 一.引言 即时通讯(IM)功能对于电商平台来说非常重要,特别是旅游电商. 从商品复杂性来看,一个 ...

  7. 基于消息队列 RocketMQ 的大型分布式应用上云最佳实践

    作者|绍舒 审核&校对:岁月.佳佳 编辑&排版:雯燕 前言 消息队列是分布式互联网架构的重要基础设施,在以下场景都有着重要的应用: 应用解耦 削峰填谷 异步通知 分布式事务 大数据处理 ...

  8. 基于 Angularjs&Node.js 云编辑器架构设计及开发实践

    基于 Angularjs&Node.js 云编辑器架构设计及开发实践 一.产品背景 二.总体架构 1. 前端架构 a.前端层次 b.核心基础模块设计 c.业务模块设计 2. Node.js端设 ...

  9. WPF换肤之一:创建圆角窗体

    原文:WPF换肤之一:创建圆角窗体 我们都期望自己的软件能够有一套看上去很吸引人眼球的外衣,使得别人看上去既专业又有美感.这个系列就带领着大家一步一步的讲解如何设计出一套自己的WPF的窗体皮肤,如果文 ...

随机推荐

  1. C# 从补码中获取有符号数的实际数值

    C# 从补码中获取有符号数的实际数值 原理 计算机存储数据时,默认是存储数据的补码.有符号的数粗存在符号位(最高位). 这里就会提到原码.反码.补码的概念. 原码:用符号位和数值表示带符号数,正数的符 ...

  2. postgresql逻辑备份工具pg_dump和pg_resotre学习

    (一)pg_dump备份 pg提供了pg_dump和pg_dumpall命令进行数据库的备份,pg_dumpall是将整个pg集群转储到一个脚本文件中,而pg_dump命令可以选择一个数据库或者部分表 ...

  3. 一颗完整意义的LPWAN SOC无线通信芯片——ASR6601

    ASR6601是完整意义的LPWAN SOC无线通信芯片,该芯片集成了LORA射频收发器.调制解调器和32位RISC MCU.MCU采用cortex M4,频率48mhz.LORA射频收发器从150 ...

  4. Git 01 介绍

    参考源 https://www.bilibili.com/video/BV1FE411P7B3?spm_id_from=333.999.0.0 版本 本文章基于 Git 2.35.1.2 版本控制 版 ...

  5. RocketMQ保姆级教程

    大家好,我是三友~~ 上周花了一点时间从头到尾.从无到有地搭建了一套RocketMQ的环境,觉得还挺easy的,所以就写篇文章分享给大家. 整篇文章可以大致分为三个部分,第一部分属于一些核心概念和工作 ...

  6. CCF NOI Online 2021 提高组 T3 岛屿探险(CDQ 分治,Trie 树)

    题面 凇睦是一个喜欢探险的女孩子,这天她到一片海域上来探险了. 在这片海域上一共有 n 座岛屿排成一排,标号为 1, 2, 3, . . . , n.每座岛屿有两个权值,分别为劳累度 ai 和有趣度 ...

  7. javaee相关基础

    2020-2-28 java 学习 开始学习javaee了 瞎跳着看 今日内容 web相关概念 web服务器软件:Tomcat Servlet入门学习 web概念 软件架构 C/S:客户端/服务器端 ...

  8. 【Vue学习笔记】—— vuex的语法 { }

    学习笔记 作者:o_Ming vuex Vuex ++ state ++ (用于存储全局数据) 组件访问 state 中的全局数据的方式1: this.$store.state.全局数据 组件访问 s ...

  9. JS作用域、变量提升和闭包

    作用域 作用域可以理解为JS引擎执行代码的时候,查找变量的规则. 从确定变量访问范围的阶段的角度,可以分为2类,词法作用域和动态作用域.js是词法作用域. 从变量查找的范围的角度,可以分为3类,全局作 ...

  10. 视频结构化 AI 推理流程

    「视频结构化」是一种 AI 落地的工程化实现,目的是把 AI 模型推理流程能够一般化.它输入视频,输出结构化数据,将结果给到业务系统去形成某些行业的解决方案. 换个角度,如果你想用摄像头来实现某些智能 ...