在之前工作(Tauri2+Leptos开发桌面应用--新建窗口、自定义菜单和多页面切换_tauri实现多窗口-CSDN博客)的基础上继续尝试绘制图形、制作GIF动画和mp4视频
绘制图形主要尝试了两种方式,一种是调用python使用matplotlib模块绘制图形,一种是使用纯Rust开发的图形库Plotters来绘制图形,包括png、svg格式的图片,gif格式的动画。制作mp4视频主要是使用Plotters结合video_rs来完成的。
上述功能都是写成Tauri后台命令,通过前端Leptos调用显示实现的。
一、Leptos前端
为了方便,在主窗口单独新建了一个页面用于绘图练习。
1. main.rs文件
rust">mod app;use app::*;
use leptos::prelude::*;//打开trunk serve --open 以开始开发您的应用程序。 Trunk 服务器将在文件更改时重新加载您的应用程序,从而使开发相对无缝。fn main() {console_error_panic_hook::set_once(); //浏览器中运行 WASM 代码发生 panic 时可以获得一个实际的 Rust 堆栈跟踪,其中包括 Rust 源代码中的一行。mount_to_body(|| {view! {<App />}})
}
main.rs文件中使用了模块app,app.rs文件主要为导航页面。
2. app.rs文件
rust">#[warn(unused_imports)]
use leptos::prelude::*;
use leptos_router::components::{ParentRoute, Route, Router, Routes, Outlet, A};
use leptos_router::path;
use leptos_router::hooks::use_params_map;mod about;
mod practice;
mod acidinput;
mod images;use about::*;
use practice::*;
use acidinput::*;
use images::*;#[component]
pub fn App() -> impl IntoView {view! {<Router><nav><a class="nav" href="/">"产品录入"</a><a class="nav" href="/practice">"练习1"</a><a class="nav" href="/about">"练习2"</a><a class="nav" href="/images">"图形练习"</a><a class="nav" href="/embeddedpage">"嵌入HTML"</a><a class="nav" href="/contacts">"联系我们"</a><a class="nav" href="/embeddedweb">"嵌入网页"</a></nav><main><Routes fallback=|| "Not found.">// / just has an un-nested "Home"<Route path=path!("/") view=|| view! {<AcidInput />} /><Route path=path!("/practice") view=|| view! {<Practice initial_value = 8 />}/><Route path=path!("/about") view=|| view! {<About />}/><Route path=path!("/images") view=|| view! {<ImagesPage />}/><Route path=path!("/embeddedpage") view=EmbeddedPage/>//<Route path=path!("/embeddedweb") view=EmbeddedWeb/><ParentRoute path=path!("/contacts") view=ContactList>// if no id specified, fall back<ParentRoute path=path!(":id") view=ContactInfo><Route path=path!("") view=|| view! {<div class="tab">"(Contact Info)"</div>}/><Route path=path!("conversations") view=|| view! {<div class="tab">"(Conversations)"</div>}/></ParentRoute>// if no id specified, fall back<Route path=path!("") view=|| view! {<div class="select-user">"Select a user to view contact info."</div>}/></ParentRoute></Routes> </main></Router>}
}#[component]
fn EmbeddedPage() -> impl IntoView {view! {<h1>"嵌入HTML文件"</h1><iframesrc="public/about/insert.html"width="100%"height="500px"style="border:none;"></iframe>}
}#[component]
fn EmbeddedWeb() -> impl IntoView {view! {<h1>"嵌入网站"</h1><iframesrc="https://sina.com.cn"width="100%"height="600px"style="border:none;"></iframe>}
}#[component]
fn ContactList() -> impl IntoView {view! {<div class="contact-list">// here's our contact list component itself<h3>"Contacts"</h3><div class="contact-list-contacts"><A href="alice">"Alice"</A><A href="bob">"Bob"</A><A href="steve">"Steve"</A></div>// <Outlet/> will show the nested child route// we can position this outlet wherever we want// within the layout<Outlet/></div>}
}#[component]
fn ContactInfo() -> impl IntoView {// we can access the :id param reactively with `use_params_map`let params = use_params_map();let id = move || params.read().get("id").unwrap_or_default();// imagine we're loading data from an API herelet name = move || match id().as_str() {"alice" => "Alice","bob" => "Bob","steve" => "Steve",_ => "User not found.",};view! {<h4>{name}</h4><div class="contact-info"><div class="tabs"><A href="" exact=true>"Contact Info"</A><A href="conversations">"Conversations"</A></div>// <Outlet/> here is the tabs that are nested// underneath the /contacts/:id route<Outlet/></div>}
}
页面效果如下:
其中/images路径的内容主要通过app/images.rs来实现。
3. app/images.rs文件
rust">use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos::ev::SubmitEvent;
//use serde::{Deserialize, Serialize};
//use leptos::ev::Event;
use wasm_bindgen::prelude::*;
//use chrono::{Local, NaiveDateTime};
use leptos::web_sys::{Blob, Url};
use web_sys::BlobPropertyBag;
use js_sys::{Array, Uint8Array};
use base64::engine::general_purpose::STANDARD; // 引入 STANDARD Engine
use base64::Engine; // 引入 Engine trait
use leptos::logging::log;#[wasm_bindgen]
extern "C" {#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke)]async fn invoke_without_args(cmd: &str) -> JsValue;#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])] //Tauri API 将会存储在 window.__TAURI__ 变量中,并通过 wasm-bindgen 导入。async fn invoke(cmd: &str, args: JsValue) -> JsValue;
}#[component]
pub fn ImagesPage() -> impl IntoView { //函数返回IntoView类型,即返回view!宏,函数名App()也是主程序view!宏中的组件名(component name)。let (img_error, set_img_error) = signal(String::new());let (video_src, set_video_src) = signal(String::new());let plot_svg_image = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("generate_svg_curve").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_img_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀let base64_data = if image.starts_with("data:image/svg+xml;base64,") {image.trim_start_matches("data:image/svg+xml;base64,").to_string()} else {image};// 将 Base64 字符串解码为二进制数据let binary_data = STANDARD.decode(&base64_data).expect("Failed to decode Base64");// 将二进制数据转换为 js_sys::Uint8Arraylet uint8_array = Uint8Array::from(&binary_data[..]);// 创建 Bloblet options = BlobPropertyBag::new();options.set_type("image/svg+xml");let blob = Blob::new_with_u8_array_sequence_and_options(&Array::of1(&uint8_array),&options,).expect("Failed to create Blob");// 生成图片 URLlet image_url = Url::create_object_url_with_blob(&blob).expect("Failed to create URL");// 打印生成的 URL,用于调试//log!("Generated Blob URL: {}", image_url);// 动态创建 <img> 元素let img = document().create_element("img").expect("Failed to create img element");img.set_attribute("src", &image_url).expect("Failed to set src");img.set_attribute("alt", "Plot").expect("Failed to set alt");// 设置宽度(例如 300px),高度会自动缩放img.set_attribute("width", "600").expect("Failed to set width");// 将 <img> 插入到 DOM 中let img_div = document().get_element_by_id("img_svg").expect("img_div not found");// 清空 div 内容(避免重复插入)img_div.set_inner_html("");img_div.append_child(&img).expect("Failed to append img");});};let python_plot = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("python_plot").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_img_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀data:image/png;base64let base64_data = if image.starts_with("data:image/png;base64,") {image.trim_start_matches("data:image/png;base64,").to_string()} else {image};// 去除多余的换行符和空格let base64_data = base64_data.trim().to_string();// 将 Base64 字符串解码为二进制数据let binary_data = STANDARD.decode(&base64_data).expect("Failed to decode Base64");// 将二进制数据转换为 js_sys::Uint8Arraylet uint8_array = Uint8Array::from(&binary_data[..]);// 创建 Bloblet options = BlobPropertyBag::new();options.set_type("image/png");let blob = Blob::new_with_u8_array_sequence_and_options(&Array::of1(&uint8_array),&options,).expect("Failed to create Blob");// 生成图片 URLlet image_url = Url::create_object_url_with_blob(&blob).expect("Failed to create URL");// 打印生成的 URL,用于调试log!("Generated Blob URL: {}", image_url);// 动态创建 <img> 元素let img = document().create_element("img").expect("Failed to create img element");img.set_attribute("src", &image_url).expect("Failed to set src");img.set_attribute("alt", "Plot").expect("Failed to set alt");// 设置宽度(例如 300px),高度会自动缩放img.set_attribute("width", "600").expect("Failed to set width");// 将 <img> 插入到 DOM 中let img_div = document().get_element_by_id("img_python").expect("img_div not found");// 清空 div 内容(避免重复插入)img_div.set_inner_html("");img_div.append_child(&img).expect("Failed to append img");});};let plot_gif_image = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("generate_gif_curve").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_img_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀let base64_data = if image.starts_with("data:image/gif;base64,") {image.trim_start_matches("data:image/gif;base64,").to_string()} else {image};// 将 Base64 字符串解码为二进制数据let binary_data = STANDARD.decode(&base64_data).expect("Failed to decode Base64");// 将二进制数据转换为 js_sys::Uint8Arraylet uint8_array = Uint8Array::from(&binary_data[..]);// 创建 Bloblet options = BlobPropertyBag::new();options.set_type("image/gif");let blob = Blob::new_with_u8_array_sequence_and_options(&Array::of1(&uint8_array),&options,).expect("Failed to create Blob");// 生成图片 URLlet image_url = Url::create_object_url_with_blob(&blob).expect("Failed to create URL");// 打印生成的 URL,用于调试//log!("Generated Blob URL: {}", image_url);// 动态创建 <img> 元素let img = document().create_element("img").expect("Failed to create img element");img.set_attribute("src", &image_url).expect("Failed to set src");img.set_attribute("alt", "Plot").expect("Failed to set alt");// 设置宽度(例如 300px),高度会自动缩放img.set_attribute("width", "600").expect("Failed to set width");// 将 <img> 插入到 DOM 中let img_div = document().get_element_by_id("gif_div").expect("gif_div not found");// 清空 div 内容(避免重复插入)img_div.set_inner_html("");img_div.append_child(&img).expect("Failed to append img");});};let plot_3d_image = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("generate_3d_surface").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_img_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀let base64_data = if image.starts_with("data:image/svg+xml;base64,") {image.trim_start_matches("data:image/svg+xml;base64,").to_string()} else {image};// 将 Base64 字符串解码为二进制数据let binary_data = STANDARD.decode(&base64_data).expect("Failed to decode Base64");// 将二进制数据转换为 js_sys::Uint8Arraylet uint8_array = Uint8Array::from(&binary_data[..]);// 创建 Bloblet options = BlobPropertyBag::new();options.set_type("image/svg+xml");let blob = Blob::new_with_u8_array_sequence_and_options(&Array::of1(&uint8_array),&options,).expect("Failed to create Blob");// 生成图片 URLlet image_url = Url::create_object_url_with_blob(&blob).expect("Failed to create URL");// 打印生成的 URL,用于调试//log!("Generated Blob URL: {}", image_url);// 动态创建 <img> 元素let img = document().create_element("img").expect("Failed to create img element");img.set_attribute("src", &image_url).expect("Failed to set src");img.set_attribute("alt", "Plot").expect("Failed to set alt");// 设置宽度(例如 300px),高度会自动缩放img.set_attribute("width", "600").expect("Failed to set width");// 将 <img> 插入到 DOM 中let img_div = document().get_element_by_id("3d_div").expect("img_3d not found");// 清空 div 内容(避免重复插入)img_div.set_inner_html("");img_div.append_child(&img).expect("Failed to append img");});};let plot_gif_3d = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("generate_gif_3d").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_img_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀let base64_data = if image.starts_with("data:image/gif;base64,") {image.trim_start_matches("data:image/gif;base64,").to_string()} else {image};// 将 Base64 字符串解码为二进制数据let binary_data = STANDARD.decode(&base64_data).expect("Failed to decode Base64");// 将二进制数据转换为 js_sys::Uint8Arraylet uint8_array = Uint8Array::from(&binary_data[..]);// 创建 Bloblet options = BlobPropertyBag::new();options.set_type("image/gif");let blob = Blob::new_with_u8_array_sequence_and_options(&Array::of1(&uint8_array),&options,).expect("Failed to create Blob");// 生成图片 URLlet image_url = Url::create_object_url_with_blob(&blob).expect("Failed to create URL");// 打印生成的 URL,用于调试//log!("Generated Blob URL: {}", image_url);// 动态创建 <img> 元素let img = document().create_element("img").expect("Failed to create img element");img.set_attribute("src", &image_url).expect("Failed to set src");img.set_attribute("alt", "Plot").expect("Failed to set alt");// 设置宽度(例如 300px),高度会自动缩放img.set_attribute("width", "600").expect("Failed to set width");// 将 <img> 插入到 DOM 中let img_div = document().get_element_by_id("gif_3d").expect("gif_3d not found");// 清空 div 内容(避免重复插入)img_div.set_inner_html("");img_div.append_child(&img).expect("Failed to append img");});};let plot_mp4_3d = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("generate_mp4_3d").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_img_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀let base64_data = if image.starts_with("data:video/mp4;base64,") {image.trim_start_matches("data:video/mp4;base64,").to_string()} else {image};// 将 Base64 字符串解码为二进制数据let binary_data = STANDARD.decode(&base64_data).expect("Failed to decode Base64");// 将二进制数据转换为 js_sys::Uint8Arraylet uint8_array = Uint8Array::from(&binary_data[..]);// 创建 Bloblet options = BlobPropertyBag::new();options.set_type("video/mp4");let blob = Blob::new_with_u8_array_sequence_and_options(&Array::of1(&uint8_array),&options,).expect("Failed to create Blob");// 生成视频 URLlet video_url = Url::create_object_url_with_blob(&blob).expect("Failed to create URL");// 打印生成的 URL,用于调试//log!("Generated Blob URL: {}", video_url);set_video_src.set(video_url); });};view! {<main class="container"><h1>"Images Page"</h1><div><p class="red">{move || img_error.get() }</p><p>"Generate PNG Image"</p><div id="img_div"><imgsrc=""width="600"/></div><form id="form_svg" on:submit=plot_svg_image><button type="submit">"Generate SVG Image"</button><p></p><div id="img_svg"><imgsrc=""width="600"/></div></form><form id="form_python" on:submit=python_plot><button type="submit">"Generate By Python"</button><p></p><div id="img_python"><imgsrc=""width="600"/></div></form><form id="form_gif" on:submit=plot_gif_image><button type="submit">"Generate GIF"</button><p></p><div id="gif_div"><imgsrc=""width="600"/></div></form><form id="form_3d" on:submit=plot_3d_image><button type="submit">"Generate 3D Surface"</button><p></p><div id="3d_div"><imgsrc=""width="600"/></div></form><form id="form_gif_3d" on:submit=plot_gif_3d><button type="submit">"Generate 3D GIF"</button><p></p><div id="gif_3d"><imgsrc=""width="600"/></div></form><form on:submit=plot_mp4_3d><button type="submit">"Generate Video"</button><div id="mp4_3d"><videosrc=video_srccontrols=truewidth="600"style="margin-top: 20px;">Your browser does not support the video tag.</video></div></form></div></main>}
}
该文件中定义了所有绘图功能的显示,其中生成png图片是由另外页面按钮调用切换到该页面后显示的,页面效果如下:
4. 绘图效果
(1)绘制SVG格式图片
(2)调用python的Matplotlib绘制图形
python代码被放在src-tauri\resources目录下,文件名plot.py,内容如下:
python"># -*_ coding: utf-8 -*-import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib as mpl
import base64
from io import BytesIOmpl.rcParams.update({"font.family": "sans-serif","font.sans-serif": "SimHei", "axes.unicode_minus":False})n = 101
R = 12
r = 5u, v=np.meshgrid(np.linspace(0, 2*np.pi, n), np.linspace(0, 2*np.pi, n))x = (R - r*np.sin(v))*np.sin(u)
y = (R - r*np.sin(v))*np.cos(u)
z = r*np.cos(v)fig = plt.figure(figsize=(13, 10))
ax3d = plt.axes(projection='3d')#设置画布透明
fig.patch.set_alpha(0)
#设置绘图区域透明
ax3d.patch.set_alpha(0.0)
#设置边框透明
for spine in ax3d.spines.values():spine.set_alpha(0)ax3d.set_xlabel("X")
ax3d.set_ylabel("Y")
ax3d.set_zlabel("Z")
ax3d.set_zlim(-15, 15)
ax3d.set_xlim(-15, 15)
ax3d.set_ylim(-15, 15)
ax3d.zaxis.set_major_locator(LinearLocator(10))
ax3d.zaxis.set_major_formatter(FormatStrFormatter('%.01f'))plt.tick_params(labelsize=10)surf = ax3d.plot_surface(x, y, z, cmap=plt.cm.Spectral)ax3d.set_title("四次元百宝袋", fontsize=96, bbox={'facecolor': 'gold', 'alpha':0.8, 'pad': 10})ax3d.view_init(30, 45)plt.tight_layout()# 将图像保存为 Base64
buf = BytesIO()
plt.savefig(buf, dpi=400, format='png', transparent=True, facecolor = 'none')
buf.seek(0)
image_base64 = base64.b64encode(buf.read()).decode('utf-8')
# 打印 Base64 字符串(确保没有换行符)
print(f"data:image/png;base64,{image_base64}", end="")
为了便于程序编译定位到plot.py文件,需要修改tauri.conf.json文件的"beforeBuildCommand",添加"resources":。
"build": {"beforeDevCommand": "trunk serve","devUrl": "http://localhost:1420","beforeBuildCommand": "trunk build && xcopy /E /I src-tauri\\resources src-tauri\\target\\release\\resources","frontendDist": "../dist"},"bundle": {"active": true,"targets": "all","icon": ["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],"resources":["resources/plot.py"]}
(3)绘制GIF动画
(4)绘制SVG格式的三维立体图形
然后制作了响应的GIF动画(文件太大,不能上传)
(5)制作mp4视频
mp4视频是通过Plotters结合video_rs来完成的,Windows下需要预装LLVM和FFMpeg。视频效果如下:
Plotters结合video_rs生成mp4视频
二、Tauri后台绘图命令
lib.rs文件内容如下,里面包括一些前期工作写的命令,供前端Leptos调用。
rust">use full_palette::PURPLE;
use futures::TryStreamExt;
use plotters::prelude::*;
use std::path::Path;
use sqlx::{migrate::MigrateDatabase, prelude::FromRow, sqlite::SqlitePoolOptions, Pool, Sqlite};
//use tauri::{App, Manager, WebviewWindowBuilder, Emitter};
use tauri::{menu::{CheckMenuItem, Menu, MenuItem, Submenu}, App, Emitter, Listener, Manager, WebviewWindowBuilder};
use serde::{Deserialize, Serialize};
type Db = Pool<Sqlite>;
use image::{ExtendedColorType, ImageBuffer, ImageEncoder, Rgba, DynamicImage, RgbImage};
use image::codecs::png::PngEncoder; // 引入 PngEncoder
use std::process::Command;
use std::env;struct DbState {db: Db,
}async fn setup_db(app: &App) -> Db {let mut path = app.path().app_data_dir().expect("获取程序数据文件夹路径失败!");match std::fs::create_dir_all(path.clone()) {Ok(_) => {}Err(err) => {panic!("创建文件夹错误:{}", err);}};//C:\Users\<user_name>\AppData\Roaming\com.mynewapp.app\db.sqlite path.push("db.sqlite");Sqlite::create_database(format!("sqlite:{}", path.to_str().expect("文件夹路径不能为空!")).as_str(),).await.expect("创建数据库失败!");let db = SqlitePoolOptions::new().connect(path.to_str().unwrap()).await.unwrap();//创建迁移文件位于./migrations/文件夹下 //cd src-tauri//sqlx migrate add create_users_tablesqlx::migrate!("./migrations/").run(&db).await.unwrap();db
}// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {format!("你好, {}!Rust向你问候了!", name)
}#[derive(Debug, Serialize, Deserialize, FromRow)]
struct User {id: u16,username: String,email: String,
}#[derive(Debug, Serialize, Deserialize, FromRow)]
struct UserId {id: u16,
}#[derive(Debug, Serialize, Deserialize, FromRow)]
struct ProductId {pdt_id: i64,
}#[derive(Serialize, Deserialize)]
struct Product {pdt_name:String,pdt_si:f64,pdt_al:f64,pdt_ca:f64,pdt_mg:f64,pdt_fe:f64,pdt_ti:f64,pdt_ka:f64,pdt_na:f64,pdt_mn:f64,pdt_date:String,
}#[derive(Debug, Serialize, Deserialize, FromRow)]
struct Pdt {pdt_id:i64, //sqlx 会将 SQLite 的 INTEGER 类型映射为 i64(64 位有符号整数)pdt_name:String,pdt_si:f64,pdt_al:f64,pdt_ca:f64,pdt_mg:f64,pdt_fe:f64,pdt_ti:f64,pdt_ka:f64,pdt_na:f64,pdt_mn:f64,pdt_date:String,
}#[tauri::command]
async fn get_db_value(state: tauri::State<'_, DbState>, window: tauri::Window) -> Result<String, String> {let db = &state.db;let query_result:Vec<User> = sqlx::query_as::<_, User>( //查询数据以特定的格式输出"SELECT * FROM users").fetch(db).try_collect().await.unwrap();let mut div_content = String::new();for user in query_result.iter(){div_content += &format!(r###"<p><input type="checkbox" name="items" value="{}"> ID:{},姓名:{},邮箱:{}</p>"###, user.id, user.id, user.username, user.email);} // 获取当前窗口let current_window = window.get_webview_window("main").unwrap();let script = &format!("document.getElementById('db-item').innerHTML = '{}';",div_content);current_window.eval(script).unwrap();Ok(String::from("数据库读取成功!"))
}#[tauri::command]
async fn send_db_item(state: tauri::State<'_, DbState>) -> Result<Vec<User>, String> {let db = &state.db;let query_result:Vec<User> = sqlx::query_as::<_, User>( //查询数据以特定的格式输出"SELECT * FROM users").fetch(db).try_collect().await.unwrap();Ok(query_result)
}#[tauri::command]
async fn send_pdt_db(state: tauri::State<'_, DbState>) -> Result<Vec<Pdt>, String> {let db = &state.db;let query_result:Vec<Pdt> = sqlx::query_as::<_, Pdt>( //查询数据以特定的格式输出"SELECT * FROM products").fetch(db).try_collect().await.unwrap();Ok(query_result)
}#[tauri::command]
async fn write_pdt_db(state: tauri::State<'_, DbState>, product:Product) -> Result<String, String> {let db = &state.db;sqlx::query("INSERT INTO products (pdt_name, pdt_si, pdt_al, pdt_ca, pdt_mg, pdt_fe, pdt_ti, pdt_ka, pdt_na, pdt_mn, pdt_date) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)").bind(product.pdt_name).bind(product.pdt_si).bind(product.pdt_al).bind(product.pdt_ca).bind(product.pdt_mg).bind(product.pdt_fe).bind(product.pdt_ti).bind(product.pdt_ka).bind(product.pdt_na).bind(product.pdt_mn).bind(product.pdt_date).execute(db).await.map_err(|e| format!("数据库插入项目错误: {}", e))?;Ok(String::from("插入数据成功!"))
}//use tauri::Error;
#[tauri::command]
async fn insert_db_item(state: tauri::State<'_, DbState>, username: &str, email: Option<&str>) -> Result<String, String> {let db = &state.db;email.unwrap_or("not set yet"); //email类型为Option<&str>,其结果为None或者Some(&str)sqlx::query("INSERT INTO users (username, email) VALUES (?1, ?2)").bind(username).bind(email).execute(db).await.map_err(|e| format!("数据库插入项目错误: {}", e))?;Ok(String::from("插入数据成功!"))
}#[tauri::command]
async fn update_user(state: tauri::State<'_, DbState>, user: User) -> Result<(), String> {let db = &state.db;sqlx::query("UPDATE users SET username = ?1, email = ?2 WHERE id = ?3").bind(user.username).bind(user.email).bind(user.id).execute(db).await.map_err(|e| format!("不能更新user:{}", e))?;Ok(())
}#[tauri::command]
async fn del_last_user(state: tauri::State<'_, DbState>) -> Result<String, String> {let db = &state.db;let last_id:UserId = sqlx::query_as::<_,UserId>("SELECT id FROM users ORDER BY id DESC LIMIT 1").fetch_one(db).await.unwrap();sqlx::query("DELETE FROM users WHERE id = ?1").bind(last_id.id).execute(db).await.map_err(|e| format!("could not delete last user: {}", e))?;Ok(String::from("最后一条数据删除成功!"))
}#[tauri::command]
async fn del_last_pdt(state: tauri::State<'_, DbState>) -> Result<String, String> {let db = &state.db;let last_id:ProductId = sqlx::query_as::<_,ProductId>("SELECT pdt_id FROM products ORDER BY pdt_id DESC LIMIT 1").fetch_one(db).await.unwrap();sqlx::query("DELETE FROM products WHERE pdt_id = ?1").bind(last_id.pdt_id).execute(db).await.map_err(|e| format!("could not delete last product: {}", e))?;Ok(String::from("最后一条数据删除成功!"))
}#[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 = '</br><h1>Hello from dynamically injected content!</h1>';")// .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 close_window(app: tauri::AppHandle) -> Result<(), String>{if let Some(window) = app.get_webview_window("about"){window.close().unwrap();}Ok(())
}#[tauri::command]
async fn show_window(app: tauri::AppHandle) -> Result<(), String>{if let Some(window) = app.get_webview_window("about"){window.show().unwrap();}Ok(())
}#[tauri::command]
async fn close_main_window(app: tauri::AppHandle) -> Result<(), String>{if let Some(window) = app.get_webview_window("main"){window.close().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(())
}use base64::engine::general_purpose::STANDARD;
use base64::Engine;// 生成图表并返回 Base64 编码的 PNG 图片
#[tauri::command]
async fn generate_plot() -> Result<String, String> {// 创建一个缓冲区,大小为 800x600 的 RGBA 图像let mut buffer = vec![0; 800 * 600 * 3]; // 800x600 图像,每个像素 3 字节(RGB){// 使用缓冲区创建 BitMapBackendlet root = BitMapBackend::with_buffer(&mut buffer, (800, 600)).into_drawing_area();root.fill(&WHITE).map_err(|e| e.to_string())?;// 定义绘图区域let mut chart = ChartBuilder::on(&root).caption("Sine Curve", ("sans-serif", 50).into_font()).build_cartesian_2d(-10.0..10.0, -1.5..1.5) // X 轴范围:-10 到 10,Y 轴范围:-1.5 到 1.5.map_err(|e| e.to_string())?;// 绘制正弦曲线chart.draw_series(LineSeries::new((-100..=100).map(|x| {let x_val = x as f64 * 0.1; // 将 x 转换为浮点数(x_val, x_val.sin()) // 计算正弦值}),&RED, // 使用红色绘制曲线)).map_err(|e| e.to_string())?;// 将图表写入缓冲区root.present().map_err(|e| e.to_string())?;} // 这里 `root` 离开作用域,释放对 `buffer` 的可变借用// 将 RGB 数据转换为 RGBA 数据(添加 Alpha 通道)let mut rgba_buffer = Vec::with_capacity(800 * 600 * 4);for pixel in buffer.chunks(3) {// 判断是否为背景色(RGB 值为 (255, 255, 255))let is_background = pixel[0] == 255 && pixel[1] == 255 && pixel[2] == 255;// 设置 Alpha 通道的值let alpha = if is_background {0 // 背景部分完全透明} else {255 // 其他部分完全不透明};rgba_buffer.extend_from_slice(&[pixel[0], pixel[1], pixel[2], alpha]); // 添加 Alpha 通道}// 将缓冲区的 RGBA 数据转换为 PNG 格式let image_buffer: ImageBuffer<Rgba<u8>, _> =ImageBuffer::from_raw(800, 600, rgba_buffer).ok_or("Failed to create image buffer")?;// 直接保存图片,检查是否乱码//image_buffer.save("output.png").map_err(|e| e.to_string())?;// 将 PNG 数据编码为 Base64let mut png_data = Vec::new();let encoder = PngEncoder::new(&mut png_data);encoder.write_image(&image_buffer.to_vec(),800,600,ExtendedColorType::Rgba8,).map_err(|e| e.to_string())?;// 将图片数据转换为 Base64 编码的字符串let base64_data = STANDARD.encode(&png_data);//use std::fs::File;//use std::io::Write;// 创建或打开文件//let file_path = "output.txt"; // 输出文件路径//let mut file = File::create(file_path).unwrap();// 将 base64_data 写入文件//file.write_all(base64_data.as_bytes()).unwrap();// 返回 Base64 编码的图片数据Ok(format!("data:image/png;base64,{}", base64_data))
}// 绘制正弦曲线并返回 Base64 编码的 SVG 数据
#[tauri::command]
fn generate_svg_curve() -> Result<String, String> {// 创建一个字符串缓冲区,用于存储 SVG 数据let mut buffer = String::new();{// 使用缓冲区创建 SVGBackendlet root = SVGBackend::with_string(&mut buffer, (800, 800)).into_drawing_area();// 设置背景为透明root.fill(&TRANSPARENT).map_err(|e| e.to_string()).unwrap();// 创建字体描述,设置粗体和斜体//let font = FontDesc::new(FontFamily::Name("微软雅黑"), 50.0, FontStyle::Normal).style(FontStyle::Bold);let font = ("微软雅黑", 50).into_font().style(FontStyle::Bold); // 设置粗体// 将 FontDesc 转换为 TextStylelet yhfont = font.into_text_style(&root) //.transform(FontTransform::Rotate180).color(&RED);// 定义绘图区域let mut chart = ChartBuilder::on(&root).caption("一元二次方程", yhfont) //.caption("正弦曲线", ("微软雅黑", 50).into_font()).margin(10).x_label_area_size(50).y_label_area_size(50).build_cartesian_2d(-4f32..4f32, 0f32..20f32) // X 轴范围:-10 到 10,Y 轴范围:-1到 10.map_err(|e| e.to_string())?;//chart.configure_mesh().draw().map_err(|e| e.to_string())?;// 设置标签的字体大小chart.configure_mesh().x_labels(5) //X 轴主要标签数量.y_labels(5).x_label_style(("sans-serif", 20).into_font()).y_label_style(("sans-serif", 20).into_font()).x_label_formatter(&|x| format!("{:.1}", x)) // 格式化 X 轴标签.y_label_formatter(&|y| format!("{:.1}", y)) // 格式化 Y 轴标签//.disable_y_mesh() // 隐藏 Y 轴网格线,.disable_x_mesh() // 隐藏 X 轴网格线.light_line_style(&BLACK.mix(0.1)) // 设置次要网格线样式.bold_line_style(&BLACK.mix(0.3)) // 设置主要网格线样式.axis_style(&BLACK) // 设置坐标轴本身的样式.draw().map_err(|e| e.to_string())?;// 绘制二次方程chart.draw_series(DashedLineSeries::new((-200..=200).map(|x| x as f32/50.0).map(|x|(x,x*x)),10, /* size = length of dash */5, /* size = spacing of dash */ShapeStyle {color: RED.to_rgba(),filled: false,stroke_width: 3,},)).map_err(|e| e.to_string())?.label("一元二次方程").legend(|(x, y)| PathElement::new(vec![(x-20, y), (x-5, y)], &RED));//.legend(|(x,y)| Rectangle::new([(x - 20, y + 1), (x, y)], BLACK));chart.configure_series_labels().position(SeriesLabelPosition::UpperRight).margin(25).legend_area_size(10).border_style(&BLUE).background_style(&PURPLE.mix(0.8)).label_font(("微软雅黑", 20)).draw().map_err(|e| e.to_string())?;// 将图表写入缓冲区root.present().map_err(|e| e.to_string())?;}// 将 SVG 数据转换为 Base64 编码的字符串let base64_data = STANDARD.encode(&buffer);// 返回 Base64 编码的 SVG 数据Ok(format!("data:image/svg+xml;base64,{}", base64_data))
}fn pdf(x: f64, y: f64) -> f64 {const SDX: f64 = 0.1;const SDY: f64 = 0.1;const A: f64 = 5.0;let x = x / 10.0;let y = y / 10.0;A * (-x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp()
}// 绘制正弦曲线并返回 Base64 编码的 SVG 数据
use uuid::Uuid;
use tauri::path::BaseDirectory;
use std::fs::read;#[tauri::command]
fn generate_gif_curve(app: tauri::AppHandle) -> Result<String, String> {// Generate a unique file name using UUIDlet file_name = format!("{}.gif", Uuid::new_v4());// Get the app data directorylet app_data_dir = app.path().resolve(&file_name, BaseDirectory::AppData).expect("Failed to resolve app data directory");// Create the directory if it doesn't existif let Some(parent) = app_data_dir.parent() {std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;}{// 使用缓冲区创建 gifBackendlet root = BitMapBackend::gif(&app_data_dir, (600, 400), 100).unwrap().into_drawing_area();// for pitch in 0..157 {for pitch in 0..50 {root.fill(&WHITE).map_err(|e| e.to_string())?;let mut chart = ChartBuilder::on(&root).caption("2D Gaussian PDF", ("sans-serif", 20)).build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0).map_err(|e| e.to_string())?;chart.with_projection(|mut p| {p.pitch = 1.57 - (1.57 - pitch as f64 / 50.0).abs();p.scale = 0.7;p.into_matrix() // build the projection matrix});chart.configure_axes().light_grid_style(BLACK.mix(0.15)).max_light_lines(3).draw().map_err(|e| e.to_string())?;chart.draw_series(SurfaceSeries::xoz((-15..=15).map(|x| x as f64 / 5.0),(-15..=15).map(|x| x as f64 / 5.0),pdf,).style_func(&|&v| (VulcanoHSL::get_color(v / 5.0)).into()),).map_err(|e| e.to_string())?;root.present().map_err(|e| e.to_string())?;}// 将图表写入缓冲区root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir");}//println!("Gif file has been saved to {:?}", app_data_dir);// 读取文件内容let file_data = read(app_data_dir).map_err(|e| e.to_string())?;// 将文件内容转换为 Base64let base64_data = STANDARD.encode(&file_data);// 返回 Base64 编码的图片数据Ok(format!("data:image/gif;base64,{}", base64_data))
}#[tauri::command]
fn python_plot(app: tauri::AppHandle) -> Result<String, String> {let resource_path = app.path().resolve("resources/plot.py", BaseDirectory::Resource) // 解析资源文件路径.expect("Failed to resolve resource");// 调用 Python 脚本let output = Command::new("E:/python_envs/eric7/python.exe").arg(resource_path) // Python 脚本路径.output().map_err(|e| e.to_string())?;// 调用打包后的 Python 可执行文件/*let output = Command::new("E:/Rust_Program/tauri-app/acid-index/src-tauri/dist/plot.exe").output().map_err(|e| e.to_string())?;*/// 检查 Python 脚本是否成功运行if output.status.success() {// 获取 Python 脚本的输出(Base64 图像数据)let image_data = String::from_utf8(output.stdout).map_err(|e| e.to_string())?;// 去除多余的换行符let image_data = image_data.trim().to_string();Ok(image_data)} else {// 获取 Python 脚本的错误输出let error_message = String::from_utf8(output.stderr).map_err(|e| e.to_string())?;Err(error_message)}
}fn sin_sqrt(x: f64, y: f64) -> f64 {const SQT: f64 = 3.0;//let x = x / 10.0;//let y = y / 10.0;SQT * ((x * x + y * y ).sqrt()).sin().exp()
}#[tauri::command]
fn generate_3d_surface() -> Result<String, String> {// 创建一个字符串缓冲区,用于存储 SVG 数据let mut buffer = String::new();{// 使用缓冲区创建 SVGBackendlet root = SVGBackend::with_string(&mut buffer, (800, 800)).into_drawing_area();// 设置背景为透明root.fill(&TRANSPARENT).map_err(|e| e.to_string()).unwrap();// 创建字体描述,设置粗体和斜体//let font = FontDesc::new(FontFamily::Name("微软雅黑"), 50.0, FontStyle::Normal).style(FontStyle::Bold);let font = ("微软雅黑", 50).into_font().style(FontStyle::Bold); // 设置粗体// 将 FontDesc 转换为 TextStylelet yhfont = font.into_text_style(&root) //.transform(FontTransform::Rotate180).color(&RED);// 定义绘图区域let mut chart = ChartBuilder::on(&root).caption("三维曲面", yhfont) //.caption("正弦曲线", ("微软雅黑", 50).into_font()).margin(10).build_cartesian_3d(-6.0..6.0, -0.0..10.0, -6.0..6.0).map_err(|e| e.to_string())?;// 通过闭包配置投影矩阵chart.with_projection(|mut p| {p.yaw = 0.5; // 设置偏航角(绕 Y 轴旋转)p.pitch = 0.5; // 设置俯仰角p.scale = 0.75; // 设置缩放比例p.into_matrix() // 返回 ProjectionMatrix});chart.configure_axes().light_grid_style(BLACK.mix(0.15)).max_light_lines(3).draw().map_err(|e| e.to_string())?;chart.draw_series(SurfaceSeries::xoz((-30..=30).map(|x| x as f64 / 5.0),(-30..=30).map(|x| x as f64 / 5.0),sin_sqrt,).style_func(&|&v| (VulcanoHSL::get_color(v / 8.0)).into()),).map_err(|e| e.to_string())?;root.present().map_err(|e| e.to_string())?;}// 将 SVG 数据转换为 Base64 编码的字符串let base64_data = STANDARD.encode(&buffer);// 返回 Base64 编码的 SVG 数据Ok(format!("data:image/svg+xml;base64,{}", base64_data))
}#[tauri::command]
fn generate_gif_3d(app: tauri::AppHandle) -> Result<String, String> {// Generate a unique file name using UUIDlet file_name = format!("{}.gif", Uuid::new_v4());// Get the app data directorylet app_data_dir = app.path().resolve(&file_name, BaseDirectory::AppData).expect("Failed to resolve app data directory");// Create the directory if it doesn't existif let Some(parent) = app_data_dir.parent() {std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;}{// 使用缓冲区创建 gifBackendlet root = BitMapBackend::gif(&app_data_dir, (600, 620), 25).unwrap().into_drawing_area();// 创建字体描述,设置粗体和斜体//let font = FontDesc::new(FontFamily::Name("微软雅黑"), 50.0, FontStyle::Normal).style(FontStyle::Bold);let font = ("微软雅黑", 50).into_font().style(FontStyle::Bold); // 设置粗体// 将 FontDesc 转换为 TextStylelet yhfont = font.into_text_style(&root) //.transform(FontTransform::Rotate180).color(&RED);for yaw in 0..180 {root.fill(&WHITE.mix(0.3)).map_err(|e| e.to_string())?;// 定义绘图区域let mut chart = ChartBuilder::on(&root).caption("三维曲面", yhfont.clone()) //.caption("正弦曲线", ("微软雅黑", 50).into_font()).margin(0).build_cartesian_3d(-6.0..6.0, -0.0..10.0, -6.0..6.0).map_err(|e| e.to_string())?;// 通过闭包配置投影矩阵chart.with_projection(|mut p| {p.yaw = yaw as f64/100.00; // 设置偏航角(绕 Y 轴旋转)p.pitch = 0.5; // 设置俯仰角p.scale = 0.75; // 设置缩放比例p.into_matrix() // 返回 ProjectionMatrix});chart.configure_axes().light_grid_style(BLACK.mix(0.15)).max_light_lines(3).draw().map_err(|e| e.to_string())?;chart.draw_series(SurfaceSeries::xoz((-30..=30).map(|x| x as f64 / 5.0),(-30..=30).map(|x| x as f64 / 5.0),sin_sqrt,).style_func(&|&v| (VulcanoHSL::get_color(v / (8.0 - (yaw as f64/4.0)%6.0).abs())).into()),).map_err(|e| e.to_string())?;root.present().map_err(|e| e.to_string())?;}// 将图表写入缓冲区root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir");}//println!("Gif file has been saved to {:?}", app_data_dir);// 读取文件内容let file_data = read(app_data_dir).map_err(|e| e.to_string())?;// 将文件内容转换为 Base64let base64_data = STANDARD.encode(&file_data);// 返回 Base64 编码的图片数据Ok(format!("data:image/gif;base64,{}", base64_data))
}use video_rs::encode::{Encoder, Settings};
use video_rs::time::Time;
use ndarray::Array3;
#[tauri::command]
fn generate_mp4_3d(app: tauri::AppHandle) -> Result<String, String> {// Generate a unique file name using UUIDlet file_name = format!("{}.mp4", Uuid::new_v4());// Get the app data directorylet app_data_dir = app.path().resolve(&file_name, BaseDirectory::AppData).expect("Failed to resolve app data directory");// Create the directory if it doesn't existif let Some(parent) = app_data_dir.parent() {std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;}// Initialize video encoderlet settings = Settings::preset_h264_yuv420p(600, 620, false); let mut encoder = Encoder::new(app_data_dir.as_ref() as &Path, settings).map_err(|e| e.to_string())?;let frame_rate = 25; // 帧率let frame_duration_sec = 1.0 / frame_rate as f64; // 每帧的持续时间(秒)for yaw in 0..180 {// Create a buffer to hold the frame imagelet mut buffer = vec![0; 600 * 620 * 3];{let root = BitMapBackend::with_buffer(&mut buffer, (600, 620)).into_drawing_area();root.fill(&WHITE).map_err(|e| e.to_string())?;let font = ("微软雅黑", 50).into_font().style(FontStyle::Bold); // 设置粗体// 将 FontDesc 转换为 TextStylelet yhfont = font.into_text_style(&root) //.transform(FontTransform::Rotate180).color(&RED);// 定义绘图区域let mut chart = ChartBuilder::on(&root).caption("三维曲面", yhfont.clone()) //.caption("正弦曲线", ("微软雅黑", 50).into_font()).margin(0).build_cartesian_3d(-6.0..6.0, -0.0..10.0, -6.0..6.0).map_err(|e| e.to_string())?;// 通过闭包配置投影矩阵chart.with_projection(|mut p| {p.yaw = yaw as f64/100.00; // 设置偏航角(绕 Y 轴旋转)p.pitch = 0.5; // 设置俯仰角p.scale = 0.75; // 设置缩放比例p.into_matrix() // 返回 ProjectionMatrix});chart.configure_axes().light_grid_style(BLACK.mix(0.15)).max_light_lines(3).draw().map_err(|e| e.to_string())?;chart.draw_series(SurfaceSeries::xoz((-30..=30).map(|x| x as f64 / 5.0),(-30..=30).map(|x| x as f64 / 5.0),sin_sqrt,).style_func(&|&v| (VulcanoHSL::get_color(v / (8.0 - (yaw as f64/4.0)%6.0).abs())).into()),).map_err(|e| e.to_string())?;}// Convert the buffer to an imagelet image = RgbImage::from_raw(600, 620, buffer).ok_or("Failed to create image from buffer")?;let dynamic_image = DynamicImage::ImageRgb8(image);// Convert DynamicImage to ndarraylet rgb_image = dynamic_image.to_rgb8(); // 转换为 RGB 图像let array = Array3::from_shape_fn((rgb_image.height() as usize, rgb_image.width() as usize, 3), |(y, x, c)| {rgb_image.get_pixel(x as u32, y as u32)[c]});// Calculate PTS for the current framelet pts = Time::from_secs_f64(frame_duration_sec * yaw as f64);// Encode the frame into the videoencoder.encode(&array, pts).map_err(|e| e.to_string())?;}// Finish encodingencoder.finish().map_err(|e| e.to_string())?;// Read the video file and encode it as Base64let file_data = std::fs::read(&app_data_dir).map_err(|e| e.to_string())?;let base64_data = STANDARD.encode(&file_data);// Return the Base64-encoded video dataOk(format!("data:video/mp4;base64,{}", base64_data))
}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,close_main_window,show_window,insert_div,emit_event,write_pdt_db,send_pdt_db,del_last_pdt,generate_plot,generate_svg_curve,python_plot,generate_gif_curve,generate_3d_surface,generate_gif_3d,generate_mp4_3d]).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程序的时候出错!");
}
所有的图片和视频都是加密成base64字符串传递给前端的。
三、其它配置
主要是涉及一些依赖关系,src-tauri/Cargo.toml文件内容如下:
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio"] }
tokio = { version ="1", features = ["full"] }
futures = "0.3.31"
log = "0.4.22"
chrono = "0.4.39"
plotters = { version = "0.3.7", features = [] }
base64 = "0.22.1"
image = "0.25.5"
uuid = {version = "1.12.0", features = ["v4"] }
video-rs = { version = "0.10", features = ["ndarray"] }
ndarray = "0.16"
至此,基本上实现了Tauri+Leptos后台图形的绘制及其动画视频的制作,并供前端调用展示。