Tauri2+Leptos开发桌面应用--新建窗口、自定义菜单和多页面切换

继续在之前工作(Tauri2+Leptos开发桌面应用--Sqlite数据库操作-CSDN博客)的基础上尝试新建窗口、自定义主窗口和新建窗口菜单、实现多页面切换。

1. 新建窗口

Tauri2+Leptos新建窗口有三种方法:

(1) 在src-tauri/tauri.conf.json中定义

使用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
    }
  },
(2) 在src-tauri/src/lib.rs中创建

在主程序的.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程序的时候出错!");
}
(3) 通过Invoke调用新建弹出窗口

前面两种方法新建的窗口都是随程序启动而打开的,日常为实现某部分功能,需要弹出窗口。

第一步:通过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程序的时候出错!"); }
第二步:Leptos前端invoke调用

在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!{

            
} }
第三步:创建public/about/about.html文件

文件内容如下,关闭按钮通过invoke调用后端命令close_window实现关闭窗口




    
    
    New Window
    


    

欢迎来到Tauri应用程序


在这里更新内容......

最终效果如下:

Tauri2+Leptos开发桌面应用--新建窗口、自定义菜单和多页面切换_第1张图片

2. 自定义窗口菜单

前面在新建窗口中,定义了新窗口菜单,下面来自定义主窗口菜单。

 (1) 新建菜单文件

在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,Error> {
    let quit_i = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
    //let show_i = MenuItem::with_id(app, "show", "显示", true, None::<&str>)?;
    let hide_i = MenuItem::with_id(app, "hide", "隐藏", true, None::<&str>)?;
    let china_i = MenuItem::with_id(app, "china", "中华人民共和国", true, None::<&str>)?;
    let people_i = MenuItem::with_id(app, "people", "伟大的中国人民", true, None::<&str>)?;
    let greet_i=Submenu::with_id_and_items(app, "greetone", "问候", true, &[&china_i, &people_i])?;
    // 分割线
    let menu = Menu::with_items(app, &[&quit_i, &hide_i, &greet_i])?;
    Ok(menu)
}

pub fn handle_menu_event(window: &tauri::Window, event:MenuEvent) {
    match event.id.as_ref() {
        "quit" => {
            let _ = window.close();
        },
        "hide" => {
            let window = window.get_webview_window("main").unwrap();
            let _ = window.hide();
        },
        "china" => {
            let window = window.get_webview_window("main").unwrap();
            // 在webview中执行JavaScript代码来设置input元素的值
            let _ = window.show();
            let script = format!(
                "
                var input = document.getElementById('greet-input');\
                input.value = '{}';\
                var event = new Event('input', {{bubbles:true}});\
                input.dispatchEvent(event);\
                document.getElementById('greet-button').click();\
                ",
                String::from("中国人民共和国")
            );
            window.eval(&script).unwrap();
        },
        "people" => {
            let window = window.get_webview_window("main").unwrap();                             
            // 在webview中执行JavaScript代码来设置input元素的值
            let _ = window.show();
            let script = format!(
                "
                var input = document.getElementById('greet-input');\
                input.value = '{}';\
                var event = new Event('input', {{bubbles:true}});\
                input.dispatchEvent(event);\
                document.getElementById('greet-button').click();\
                ",
                String::from("伟大的中国人民")
            );
            window.eval(&script).unwrap();
        },
        // Add more events here
        _ => {}
    }
}

(2) 设置主窗口菜单

在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程序的时候出错!");
}

3. 多页面切换

前端使用Leptos_router实现多页面切换

(1) Cargo.toml添加Leptos_router依赖
[dependencies]
leptos = { version = "0.7.2", features = ["csr"] }
leptos_router = { version = "0.7.2", features = [] }
(2) app.rs设置多页面

多页面类似于浏览器的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开发桌面应用--新建窗口、自定义菜单和多页面切换_第2张图片

Tauri2+Leptos开发桌面应用--新建窗口、自定义菜单和多页面切换_第3张图片

至此,实现了Tauri2+Leptos应用程序新建窗口,设置新建窗口和主窗口菜单,通过invoke调用和自定义event事件实时对窗口内容的修改,使用Leptos_router实现主窗口多页面显示。

Tauri2+Leptos开发桌面应用--新建窗口、自定义菜单和多页面切换_第4张图片 

你可能感兴趣的:(rust,visual,studio,code,前端)