继续在之前工作(Tauri2+Leptos开发桌面应用--Sqlite数据库操作-CSDN博客)的基础上尝试新建窗口、自定义主窗口和新建窗口菜单、实现多页面切换。
Tauri2+Leptos新建窗口有三种方法:
使用create-tauri-app新建应用就会自动通过tauri.conf.json中的"app"{"windows"[{ },{ } ]}创建一个label为main的主窗口。自己可以照着格式再添加其它窗口,label为窗口的唯一标识。
"app": {
"withGlobalTauri": true,
"windows": [ {
"title": "主窗口",
"fullscreen": false,
"resizable": true,
"width": 840,
"height": 720,
"label":"main",
"alwaysOnTop": false
},
{
"title": "关于",
"fullscreen": false,
"resizable": true,
"width": 600,
"height": 400,
"label":"about",
"alwaysOnTop": false
}
],
"security": {
"csp": null
}
},
在主程序的.setup()中创建新窗口,同时还自定义了一个菜单,大致格式如下:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet,
get_db_value,
insert_db_item,
update_user,
del_last_user,
send_db_item,
open_new_window,
close_window,
show_window,
insert_div,
emit_event
])
.menu(|app|{create_menu(app)})
.setup(|app| {
let handle = app.handle();
let save_menu_item = MenuItem::new(handle, "保存", true, None::<&str>)?;
let menu = Menu::with_items(handle, &[
&Submenu::with_items(handle, "文件", true, &[
&save_menu_item,
])?,
])?;
let about_window = WebviewWindowBuilder::new(
app,
"about",
//WebviewUrl::External("https://scybbd.com".parse().unwrap()),
WebviewUrl::App("index.html".into()))
.menu(menu)
.on_menu_event(move|window, event|{
if event.id == save_menu_item.id(){
//定义菜单事件
}
})
.title("关于程序")
.inner_size(840.0,720.0)
.position(20.0, 10.0)
.build()
.unwrap();
#[cfg(all(desktop))]
{
let handle = app.handle();
tray::create_tray(handle)?; //设置app系统托盘
}
tauri::async_runtime::block_on(async move {
let db = setup_db(&app).await; //setup_db(&app:&mut App)返回读写的数据库对象
app.manage(DbState { db }); //通过app.manage(DbState{db})把数据库对象传递给state:tauri::State<'_, DbState>
});
Ok(())
})
.run(tauri::generate_context!())
.expect("运行Tauri程序的时候出错!");
}
前面两种方法新建的窗口都是随程序启动而打开的,日常为实现某部分功能,需要弹出窗口。
在src-tauri/src/lib.rs文件中,新建#[tauri::command] open_new_window函数
a. 通过WebviewWindowBuilder::new()新建窗口,调用已经编辑好的本地html文件。
b. 通过.menu(menu)自定义弹出窗口菜单
c. 通过window.on_menu_event定义菜单事件
d. 定义insert_div函数用于前端调用,修改新窗口内容
f. 定义emit_event函数用于从主窗口向其新窗口发送自定义event,主窗口中invoke调用触发自定义update_content事件,后端对自定义事件进行监听,并通过执行javascript脚本修改窗口内容(过程有点绕)。
g. 定义关闭新窗口命令。
window.clone().listen("update-content", move |event| {
let script = &format!("document.getElementById('insert_div').innerHTML = '{}';",event.payload());
window.eval(script).unwrap();
});
具体代码如下:
use tauri::{menu::{CheckMenuItem, Menu, MenuItem, Submenu}, App, Emitter, Listener, Manager, WebviewWindowBuilder};
#[tauri::command]
async fn open_new_window(app: tauri::AppHandle, title:String, url:String) -> Result<(), String>{
let main_window = app.get_webview_window("main").unwrap();
let toggle = MenuItem::with_id(&app, "quit", "退出", true, None::<&str>).unwrap();
let submenu_1 = CheckMenuItem::with_id(&app, "check_me", "显示窗口",true, true, None::<&str>).unwrap();
let submenu_2 =CheckMenuItem::with_id(&app, "check_you", "隐藏窗口",true, false, None::<&str>).unwrap();
let check_menu = Submenu::with_id_and_items(&app, "check_one", "选择", true, &[&submenu_1, &submenu_2]).unwrap();
let menu = Menu::with_items(&app, &[&toggle, &check_menu]).unwrap();
let window = WebviewWindowBuilder::new(
&app,
"about",
tauri::WebviewUrl::App(url.into()))
.parent(&main_window).unwrap()
.always_on_top(false)
.title(&title)
.inner_size(800.0, 600.0)
.center()
.menu(menu)
.build()
.unwrap();
window.on_menu_event(move|app, event| {
match event.id.as_ref() {
"quit" => {
let _ = app.close();
},
"check_you" => {
let _ = app.hide();
},
"check_me" => {
let _ = app.show();
},
_ => {}
}
});
// 动态注入内容
//window
// .eval("document.getElementById('insert_div').innerHTML = 'Hello from dynamically injected content!
';")
// .expect("Failed to inject content");
window.show().unwrap();
// 监听主窗口发送的事件
window.clone().listen("update-content", move |event| {
let script = &format!("document.getElementById('insert_div').innerHTML = '{}';",event.payload());
window.eval(script).unwrap();
});
Ok(())
}
#[tauri::command]
async fn insert_div(app: tauri::AppHandle, label:String, content:String) -> Result<(), String>{
if let Some(window) = app.get_webview_window(&label){
window
.eval(&format!("document.getElementById('insert_div').innerHTML = '{}';",content))
.expect("Failed to inject content");
}
Ok(())
}
//从主窗口向其它窗口发送event
#[tauri::command]
async fn emit_event(app: tauri::AppHandle, label:String, content:String) -> Result<(), String>{
if let Some(window) = app.get_webview_window(&label){
window
.emit_to(&label, "update-content", content) //.emit_to(target, event, content)
.unwrap();
}
Ok(())
}
#[tauri::command]
async fn close_window(app: tauri::AppHandle) -> Result<(), String>{
if let Some(window) = app.get_webview_window("about"){
window.close().unwrap();
}
Ok(())
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet,
get_db_value,
insert_db_item,
update_user,
del_last_user,
send_db_item,
open_new_window,
close_window,
show_window,
insert_div,
emit_event
])
.menu(|app|{create_menu(app)})
.setup(|app| {
Ok(())
})
.run(tauri::generate_context!())
.expect("运行Tauri程序的时候出错!");
}
在src/app.rs中,创建信号来打开新建窗口,新窗口打开本地已经创建的public/about/about.html文件。
#[derive(Serialize, Deserialize)]
struct OpenArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。
title: &'a str,
url: &'a str,
}
#[derive(Serialize, Deserialize)]
struct InsertArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。
username: &'a str,
email: &'a str,
}
#[derive(Serialize, Deserialize)]
struct UpdateArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。
label: &'a str,
content: &'a str,
}
#[component]
pub fn App() -> impl IntoView {
......
//触发打开新窗口
let open_new_window = move|title:String, url:String|{
spawn_local(async move {
let args = serde_wasm_bindgen::to_value(&OpenArgs { title: &title, url: &url}).unwrap(); //参数序列化
//调用Tauri命令
invoke("open_new_window", args).await.as_string().unwrap();
});
};
//触发关闭窗口
let close_window = move|_|{
spawn_local(async move {
//调用Tauri命令
invoke_without_args("close_window").await.as_string().unwrap();
})};
//创建自定义事件,通过后端监听来给窗口更新内容
let event_update = move|label:String, content:String|{
spawn_local(async move {
let args = serde_wasm_bindgen::to_value(&UpdateArgs { label: &label, content: &content}).unwrap(); //参数序列化
//调用Tauri命令
invoke("emit_event", args).await.as_string().unwrap();
});
};
//给新窗口插入内容
let update_div = move|label:String, content:String|{
spawn_local(async move {
let args = serde_wasm_bindgen::to_value(&UpdateArgs { label: &label, content: &content}).unwrap(); //参数序列化
//调用Tauri命令
invoke("insert_div", args).await.as_string().unwrap();
});
};
......
view!{
}
}
文件内容如下,关闭按钮通过invoke调用后端命令close_window实现关闭窗口
New Window
欢迎来到Tauri应用程序
在这里更新内容......
最终效果如下:
前面在新建窗口中,定义了新窗口菜单,下面来自定义主窗口菜单。
在src-tuari/src目录下新建mymenu.rs文件,将自定义菜单和菜单事件函数handle_menu_event全部放在一个文件中,具体内容如下:
use tauri::{
menu::{Menu, MenuItem, Submenu, MenuEvent}, Manager,Runtime,Error,
};
pub fn create_menu(app: &tauri::AppHandle) -> Result
在src-tauri/src/lib.rs中导入模块mymenu.rs,然后在主程序.setup()中设置。
mod tray; //导入tray.rs模块
mod mymenu; //导入mynemu.rs模块
use mymenu::{create_menu, handle_menu_event};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet,
get_db_value,
insert_db_item,
update_user,
del_last_user,
send_db_item,
open_new_window,
close_window,
show_window,
insert_div,
emit_event
])
.menu(|app|{create_menu(app)})
.setup(|app| {
let main_window = app.get_webview_window("main").unwrap();
main_window.on_menu_event(move |window, event| handle_menu_event(window, event));
#[cfg(all(desktop))]
{
let handle = app.handle();
tray::create_tray(handle)?; //设置app系统托盘
}
tauri::async_runtime::block_on(async move {
let db = setup_db(&app).await; //setup_db(&app:&mut App)返回读写的数据库对象
app.manage(DbState { db }); //通过app.manage(DbState{db})把数据库对象传递给state:tauri::State<'_, DbState>
});
Ok(())
})
.run(tauri::generate_context!())
.expect("运行Tauri程序的时候出错!");
}
前端使用Leptos_router实现多页面切换
[dependencies]
leptos = { version = "0.7.2", features = ["csr"] }
leptos_router = { version = "0.7.2", features = [] }
多页面类似于浏览器的tab,可以:
a. 调用Leptos模块中定义的view!宏(自定义函数返回IntoView类型)
b. 嵌入自定义的HTML文件
c. 嵌入网站页面
d. 自定义#[component](返回view!宏)
#[warn(unused_imports)]
use leptos::prelude::*;
use leptos_router::components::{Outlet, Route, Router, Routes, A};
use leptos_router::hooks::use_params_map;
use leptos_router::path;
mod about;
mod acid;
use about::*;
use acid::*;
#[component]
pub fn App() -> impl IntoView {
view! {
}/>
}/>
}
}
#[component]
fn EmbeddedPage() -> impl IntoView {
view! {
"嵌入HTML文件"
}
}
#[component]
fn EmbeddedWeb() -> impl IntoView {
view! {
"嵌入网站"
}
}
页面效果如下:
至此,实现了Tauri2+Leptos应用程序新建窗口,设置新建窗口和主窗口菜单,通过invoke调用和自定义event事件实时对窗口内容的修改,使用Leptos_router实现主窗口多页面显示。