Tauri 2.3.1+Leptos 0.7.8开发桌面应用--Sqlite数据库的写入、展示和选择删除
在前期工作的基础上(Tauri2+Leptos开发桌面应用--Sqlite数据库操作_tauri sqlite-CSDN博客),尝试制作产品化学成分录入界面,并展示数据库内容,删除选中的数据。具体效果如下:
一、前端Leptos程序
前端程序主要是实现前端产品录入界面的设计,需要实现:
1. 输入框内输入的数据和日期的合规性检测
2. 定义输入数据的值及信号,实现实时更新
3. 通过invoke调用后台tauri命令,实现数据库的写入,内容展示和删除选中数据项
4. 数据内容展示是通过生成view!视图插入到DIV中实现的,视图内容也是通过定义信号实时更新
5. 为了便于删除选中的数据,需要在展示数据内容时,在每条数据前增加选择的复选框
6. 删除数据后,还要刷新数据的展示
具体代码如下:
use leptos::task::spawn_local;
use leptos::{ev::SubmitEvent, prelude::*};
use leptos_router::hooks::use_navigate;
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 web_sys::HtmlInputElement;#[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;
}//序列化后的变量作为函数invoke(cmd, args: JsValue)的参数,JsValue为序列化格式
#[derive(Serialize, Deserialize)]
struct GreetArgs<'a> {name: &'a str,
}#[derive(Serialize, Deserialize)]
struct InsertArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。username: &'a str,email: &'a str,
}#[derive(Serialize, Deserialize)]
struct OpenArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。title: &'a str, url: &'a str,
}#[derive(Serialize, Deserialize)]
struct UpdateArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。label: &'a str, content: &'a str,
}#[derive(Serialize, Deserialize)]
struct SwitchArgs<'a> { //生命周期标识符 'a 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。label: &'a str,}#[derive(Serialize, Deserialize)]
struct User {id: u16,username: String,email: String,
}#[derive(Serialize, Deserialize)]
struct Pdt {pdt_id:i64,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(Serialize, Deserialize)]
struct PdtArgs {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(Serialize, Deserialize)]
struct WritePdtArgs {product: PdtArgs, // 将 PdtArgs 包装为一个包含 `product` 键的对象
}#[derive(Serialize, Deserialize)]
struct SelectedPdtArgs { // 将invoke调用的参数打包成结构变量再通过json传递,tauri后台invoke函数的参数名称必须根键一致(譬如此处的productlist)productlist: Vec<i64>, // 将Vec<i64>数组包装为一个包含 `productlist` 键的对象,键不能带下划线"_"
}#[component]
pub fn AcidInput() -> impl IntoView { //函数返回IntoView类型,即返回view!宏,函数名App()也是主程序view!宏中的组件名(component name)。//定义产品化学成分输入框值及信号let (pdt_Name, set_pdt_Name) = signal(String::from("产品"));let (Name_error, set_Name_error) = signal(String::new());let (pdt_Si, set_pdt_Si) = signal(0.0);let (Si_error, set_Si_error) = signal(String::new());let (pdt_Al, set_pdt_Al) = signal(0.0);let (Al_error, set_Al_error) = signal(String::new());let (pdt_Ca, set_pdt_Ca) = signal(0.0);let (Ca_error, set_Ca_error) = signal(String::new());let (pdt_Mg, set_pdt_Mg) = signal(0.0);let (Mg_error, set_Mg_error) = signal(String::new());let (pdt_Fe, set_pdt_Fe) = signal(0.0);let (Fe_error, set_Fe_error) = signal(String::new());let (pdt_Ti, set_pdt_Ti) = signal(0.0);let (Ti_error, set_Ti_error) = signal(String::new());let (pdt_Ka, set_pdt_Ka) = signal(0.0);let (Ka_error, set_Ka_error) = signal(String::new());let (pdt_Na, set_pdt_Na) = signal(0.0);let (Na_error, set_Na_error) = signal(String::new());let (pdt_Mn, set_pdt_Mn) = signal(0.0);let (Mn_error, set_Mn_error) = signal(String::new());let now = Local::now().format("%Y-%m-%dT%H:%M").to_string();let (pdt_date, set_pdt_date) = signal(now);let (date_error, set_date_error) = signal(String::new());let (sql_error, set_sql_error) = signal(String::new());//let (div_content, set_div_content) = signal(String::new());//let (div_content, set_div_content) = signal(View::new(()));let (div_content, set_div_content) = signal(view! { <div>{Vec::<View<_>>::new()}</div> });let (selected_items, set_selected_items) = signal::<Vec<i64>>(vec![]);// 创建一个信号来存储 base64 图片数据//let (pic_str, set_pic_str) = signal(String::new());//let (svg_str, set_svg_str) = signal(String::new());let update_pdt = move|ev:Event, set_value:WriteSignal<f64>, set_error:WriteSignal<String>| {match event_target_value(&ev).parse::<f64>(){Ok(num) => {//如果值在范围内,则更新信号if num >= 0.0 && num <= 100.00 {set_value.set(num);set_error.set(String::new());}else{set_error.set("数字必须在0到100之间".to_string());}}Err(_) => {set_error.set("请输入有效的数字".to_string());}}};// 定义日期时间范围let min_datetime = NaiveDateTime::parse_from_str("2011-01-01T00:00", "%Y-%m-%dT%H:%M").unwrap(); // 最小日期时间//let max_datetime = NaiveDateTime::parse_from_str("2023-12-31T18:00", "%Y-%m-%dT%H:%M").unwrap(); // 最大日期时间let update_date = move|ev| {match NaiveDateTime::parse_from_str(&event_target_value(&ev), "%Y-%m-%dT%H:%M") {Ok(parsed_datetime) => {// 检查日期时间是否在范围内if parsed_datetime >= min_datetime {set_pdt_date.set(parsed_datetime.to_string());set_date_error.set(String::new());} else {set_date_error.set(format!("日期时间必须大于{}",min_datetime.format("%Y-%m-%d %H:%M")));}}Err(_) => {set_date_error.set("请输入有效的日期时间(格式:YYYY-MM-DDTHH:MM)".to_string());}}};// 定义名称长度范围let min_length = 3;let max_length = 100;let update_Name = move|ev| {match event_target_value(&ev).parse::<String>(){Ok(name) => {//检查是否为空if name.is_empty() {set_Name_error.set("名称不能为空".to_string());return;};// 检查长度是否在范围内if name.len() < min_length {set_Name_error.set(format!("名称长度不能少于 {} 个字符", min_length));} else if name.len() > max_length {set_Name_error.set(format!("名称长度不能大于 {} 个字符", max_length));}else{set_pdt_Name.set(name.to_string());set_Name_error.set(String::new());}}Err(_) => {set_Name_error.set("请输入有效产品名称!".to_string());}}};let write_pdt_sql = move |ev: SubmitEvent| {ev.prevent_default(); //类似javascript中的Event.preventDefault(),处理<input>字段非常有用spawn_local(async move { //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let pdt_name = pdt_Name.get_untracked();let pdt_si = pdt_Si.get_untracked();let pdt_al = pdt_Al.get_untracked();let pdt_ca = pdt_Ca.get_untracked();let pdt_mg = pdt_Mg.get_untracked();let pdt_fe = pdt_Fe.get_untracked();let pdt_ti = pdt_Ti.get_untracked();let pdt_ka = pdt_Ka.get_untracked();let pdt_na = pdt_Na.get_untracked();let pdt_mn = pdt_Mn.get_untracked();let pdt_date = pdt_date.get_untracked();set_sql_error.set(String::new());let total_chem = pdt_si + pdt_al + pdt_ca + pdt_mg + pdt_fe + pdt_ti + pdt_ka + pdt_na + pdt_mn;if total_chem < 95.0 {set_sql_error.set("所有化学成分总量小于95%,请检查输入数据!".to_string());return;};if total_chem > 105.0 {set_sql_error.set("所有化学成分总量大于105%,请检查输入数据!".to_string());return;};let ca_mg = pdt_ca + pdt_mg;if ca_mg <= 0.0 {set_sql_error.set("CaO和MgO总量不能为零,请检查输入数据!".to_string());return;};let args = WritePdtArgs{product:PdtArgs { pdt_name: pdt_name, pdt_si: pdt_si, pdt_al: pdt_al, pdt_ca: pdt_ca, pdt_mg: pdt_mg, pdt_fe: pdt_fe, pdt_ti: pdt_ti, pdt_ka: pdt_ka, pdt_na: pdt_na, pdt_mn: pdt_mn, pdt_date: pdt_date },};let args_js = serde_wasm_bindgen::to_value(&args).unwrap(); //参数序列化// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/let new_msg = invoke("write_pdt_db", args_js).await.as_string().unwrap(); //使用invoke调用greet命令,greet类似于APIset_sql_error.set(new_msg);});};//处理复选框事件let check_change = move |ev:leptos::ev::Event|{//ev.prevent_default(); spawn_local(async move {let target = event_target::<HtmlInputElement>(&ev);let value_str = target.value(); // 直接获取 value// 将字符串解析为 i64(需处理可能的错误)if let Ok(value) = value_str.parse::<i64>() {set_selected_items.update(|items| {if target.checked() {items.push(value);} else {items.retain(|&x| x != value);}});};});};let receive_pdt_db = move |ev: SubmitEvent| {ev.prevent_default();spawn_local(async move { //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let pdt_js = invoke_without_args("send_pdt_db").await;let pdt_vec: Vec<Pdt> = serde_wasm_bindgen::from_value(pdt_js).map_err(|_| JsValue::from("Deserialization error")).unwrap();let mut receive_msg = String::from("读取数据库ID序列为:[");// 构建日志消息(注意:pdt_vec 已被消耗,需提前克隆或调整逻辑)let pdt_ids: Vec<i64> = pdt_vec.iter().map(|pdt| pdt.pdt_id).collect();for id in pdt_ids {receive_msg += &format!("{}, ", id);}receive_msg += "]";// 动态生成包裹在 div 中的视图let div_views = view! {<div>{pdt_vec.into_iter().map(|pdt| {let pdt_id = pdt.pdt_id;view! {<div style="margin:5px;width:1500px;"><inputtype="checkbox"name="items"value=pdt_id.to_string()prop:checked=move || selected_items.get().contains(&pdt_id)on:change=check_change/><span>// 直接使用 Unicode 下标字符"PdtID: " {pdt_id}",产品名称: " {pdt.pdt_name}",SiO₂: " {pdt.pdt_si} "%"",Al₂O₃: " {pdt.pdt_al} "%"",CaO: " {pdt.pdt_ca} "%"",MgO: " {pdt.pdt_mg} "%"",Fe₂O₃: " {pdt.pdt_fe} "%"",TiO₂: " {pdt.pdt_ti} "%"",K₂O: " {pdt.pdt_ka} "%"",Na₂O: " {pdt.pdt_na} "%"",MnO₂: " {pdt.pdt_mn} "%"",生产日期: " {pdt.pdt_date}</span></div>}}).collect_view()}</div>}; // 关键的类型擦除;// 转换为 View 类型并设置//log!("视图类型: {:?}", std::any::type_name_of_val(&div_views));set_div_content.set(div_views); set_sql_error.set(receive_msg);});};let del_selected_pdt = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move { //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let args = SelectedPdtArgs{productlist:selected_items.get_untracked(),};let args_js = serde_wasm_bindgen::to_value(&args).unwrap(); //参数序列化let new_msg = invoke("del_selected_pdt", args_js).await.as_string().unwrap();set_sql_error.set(new_msg);set_selected_items.set(Vec::<i64>::new());// 删除完成后触发刷新操作receive_pdt_db(ev.clone()); });}; let navigate = use_navigate();let plot_image = move|ev:SubmitEvent| {ev.prevent_default();navigate("/images", Default::default());spawn_local(async move {// 调用 Tauri 的 invoke 方法获取 base64 图片数据let result:String = serde_wasm_bindgen::from_value(invoke_without_args("generate_plot").await).unwrap();//log!("Received Base64 data: {}", result);let mut image = String::new();if result.len() != 0 {// 将 base64 数据存储到信号中image = result;} else {set_sql_error.set("Failed to generate plot".to_string());}// 检查 Base64 数据是否包含前缀let base64_data = if image.starts_with("data:image/png;base64,") {image.trim_start_matches("data:image/png;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/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_div").expect("img_div not found");// 清空 div 内容(避免重复插入)img_div.set_inner_html("");img_div.append_child(&img).expect("Failed to append img");});};view! { //view!宏作为App()函数的返回值返回IntoView类型<main class="container"><h1>"产品化学成分录入"</h1><form id="greet-form" on:submit=write_pdt_sql><div class="pdtinput"><div class="left"> "产品名称:"</div><div class="right"> <input style="width:350px" type="text" minlength="1" maxlength="100" placeholder="请输入产品名称..." value = move || pdt_Name.get() //将信号的值绑定到输入框on:input=move|ev|update_Name(ev) /></div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Name_error}</div></div><div class="pdtinput"><div class="left"> "二氧化硅:"</div><div class="right"> <input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入二氧化硅含量百分数..." value = move || pdt_Si.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev, set_pdt_Si, set_Si_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Si_error}</div></div><div class="pdtinput"><div class="left"> "三氧化二铝:"</div><div class="right"> <input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入三氧化二铝含量百分数..." value = move || pdt_Al.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Al, set_Al_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Al_error}</div></div><div class="pdtinput"><div class="left"> "氧化钙:"</div><div class="right"> <input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入氧化钙含量百分数..." value = move || pdt_Ca.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Ca, set_Ca_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Ca_error}</div></div><div class="pdtinput"><div class="left"> "氧化镁:"</div><div class="right"> <input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入氧化镁含量百分数..." value = move || pdt_Mg.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Mg, set_Mg_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Mg_error}</div></div><div class="pdtinput"><div class="left"> "全铁(TFe):"</div><div class="right"> <input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入全铁(Fe2O3)含量百分数..." value = move || pdt_Fe.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Fe, set_Fe_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Fe_error}</div></div><div class="pdtinput"><div class="left"> "二氧化钛:"</div><div class="right"> <input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入二氧化钛含量百分数..." value = move || pdt_Ti.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Ti, set_Ti_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Ti_error}</div></div><div class="pdtinput"><div class="left"> "氧化钾:"</div><div class="right"><input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入氧化钾含量百分数..." value = move || pdt_Ka.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Ka, set_Ka_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Ka_error}</div></div><div class="pdtinput"><div class="left"> "氧化钠:"</div><div class="right"><input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入氧化钠含量百分数..." value = move || pdt_Na.get() //将信号的值绑定到输入框on:input=move|ev|update_pdt(ev,set_pdt_Na, set_Na_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Na_error}</div></div><div class="pdtinput"><div class="left"> "二氧化锰:"</div><div class="right"><input style="width:320px" type="number" min="0.0" max="100.0" step="0.01" placeholder="请输入氧化锰含量百分数..." value = move || pdt_Mn.get() //将信号的值绑定到输入框on:input=move |ev|update_pdt(ev,set_pdt_Mn, set_Mn_error) />"%"</div></div><div class="errorshow"><div class="left"></div><div class="right red"> {Mn_error}</div></div><div class="pdtinput"><div class="left"> "取样时间:"</div><div class="right"><input style="width:350px" type="datetime" min="2011-01-01T00:00:00"value = move || pdt_date.get() //将信号的值绑定到输入框on:input=update_date /></div></div><div class="errorshow"><div class="left"></div><div class="right red"> {date_error}</div></div><button style="width:300px;" type="submit" id="greet-button">"产品录入"</button></form><p class="red">{move || sql_error.get() }, "选中的项目有:"{move || selected_items.get().iter().map(|x| x.to_string()) // 将 i64 转为 String.collect::<Vec<String>>() // 收集为 Vec<String>.join(", ") // 使用标准库的 join}</p><div class="form-container"><div class="db-window" id="db-item">{move || div_content.get()}</div><div class="btn-window"><form class="row" on:submit=receive_pdt_db><button type="submit" style="margin:10px 5px 10px 5px;" id="get-button" style="margin:0 10px 0 10px;height:35px;" >"读取数据库"</button></form><form class="row" on:submit=del_selected_pdt><button type="submit" style="margin:10px 5px 10px 5px;" id="del-button" style="margin:0 10px 0 10px;height:35px;" >"删除选中项"</button></form></div></div><div><h1>"Plotters in Tauri + Leptos"</h1><form id="img_png" on:submit=plot_image><button type="submit">"Generate PNG Image"</button><p></p><div id="img_div"><imgsrc=""width="600"/></div></form></div></main>}
}
需要注意的是invoke调用,存在两种形式:一种被调用后台tauri命令没有参数,使用invoke_without_args("cmd"),一种是被调用后台tauri命令有参数,使用invoke("cmd", args_js),其中args_js是被序列化处理的自定义结构变量,结构化变量的键值就是tauri调用命令的参数值,且键值不能带下划线"_",tauri后台调用命令的参数名必须键值保持一致。
譬如前端定义的删除选中项的命令del_selected_pdt,调用的是tauri后台的del_selected_pdt命令,其要传递的参数是一个i64的数列,在后台定义del_selected_pdt命令时,其参数名为productlist,具体代码如下:
#[tauri::command]
async fn del_selected_pdt(state: tauri::State<'_, DbState>, productlist:Vec<i64>) -> Result<String, String> {// 参数名productlist必须与前端定义的结构变量SelectedPdtArgs的键值一致let db = &state.db;// 处理空数组的情况if productlist.is_empty() {return Err("删除失败:未提供有效的产品ID".into());}// 生成动态占位符(根据数组长度生成 ?, ?, ?)let placeholders = vec!["?"; productlist.len()].join(", ");let query_str = format!("DELETE FROM products WHERE pdt_id IN ({})",placeholders);// 构建查询并绑定参数let mut query = sqlx::query(&query_str);for id in &productlist {query = query.bind(id);}// 执行删除操作let result = query.execute(db).await.map_err(|e| format!("删除失败: {}", e))?;// 检查实际删除的行数if result.rows_affected() == 0 {return Err("删除失败:未找到匹配的产品".into());}Ok(format!("成功删除 {} 条数据!", result.rows_affected()))}
这样,Leptos前端在自定义结构变量时,键值也必须一致,为productlist,代码如下:
#[derive(Serialize, Deserialize)]
struct SelectedPdtArgs {productlist: Vec<i64>,
}
此处只传递一个参数,所以结构变量只有一个元素,传递几个参数值,结构变量就有几个元素。然后在invoke调用时,对包含所有传递参数的结构变量进行序列化。
let del_selected_pdt = move|ev:SubmitEvent| {ev.prevent_default();spawn_local(async move { //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。let args = SelectedPdtArgs{productlist:selected_items.get_untracked(),};let args_js = serde_wasm_bindgen::to_value(&args).unwrap(); //参数序列化let new_msg = invoke("del_selected_pdt", args_js).await.as_string().unwrap();set_sql_error.set(new_msg);set_selected_items.set(Vec::<i64>::new());// 删除完成后触发刷新操作receive_pdt_db(ev.clone()); });};
二、后台tauri程序
后台tauri程序主要是定义了前端leptos需要调用的命令。具体代码如下:
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::{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
}#[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 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("插入数据成功!"))
}#[tauri::command]
async fn del_selected_pdt(state: tauri::State<'_, DbState>, productlist:Vec<i64>) -> Result<String, String> {// 参数名productlist必须与前端定义的结构变量SelectedPdtArgs的键值一致let db = &state.db;// 处理空数组的情况if productlist.is_empty() {return Err("删除失败:未提供有效的产品ID".into());}// 生成动态占位符(根据数组长度生成 ?, ?, ?)let placeholders = vec!["?"; productlist.len()].join(", ");let query_str = format!("DELETE FROM products WHERE pdt_id IN ({})",placeholders);// 构建查询并绑定参数let mut query = sqlx::query(&query_str);for id in &productlist {query = query.bind(id);}// 执行删除操作let result = query.execute(db).await.map_err(|e| format!("删除失败: {}", e))?;// 检查实际删除的行数if result.rows_affected() == 0 {return Err("删除失败:未找到匹配的产品".into());}Ok(format!("成功删除 {} 条数据!", result.rows_affected()))}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))
}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, send_pdt_db,del_last_pdt,del_selected_pdt,generate_plot]).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程序的时候出错!");
}
至此基本实现数据库的写入(产品化学成分录入),内容展示(产品成分清单展示)和删除选中数据的功能。
相关文章:
Tauri 2.3.1+Leptos 0.7.8开发桌面应用--Sqlite数据库的写入、展示和选择删除
在前期工作的基础上(Tauri2Leptos开发桌面应用--Sqlite数据库操作_tauri sqlite-CSDN博客),尝试制作产品化学成分录入界面,并展示数据库内容,删除选中的数据。具体效果如下: 一、前端Leptos程序 前端程序主…...
MySQL 5.7.43 二进制安装指南:从零开始的高效快速实现安装部署
目录 引言:为什么选择二进制安装? 1 环境准备 1.1 基础环境检查 1.2 系统安全设置 2 系统优化配置 2.1 磁盘调度策略 2.2 系统资源限制 3 mariadb删除 4 依赖包安装 5 MySQL二进制安装包下载 6 MySQL安装部署 6.1 解压安装包 6.2 创建用户以…...
使用U盘安装 ubuntu 系统
1. 准备U 盘制作镜像 1.1 下载 ubuntu iso https://ubuntu.com/download/ 这里有多个版本以供下载,本文选择桌面版。 1.2 下载rufus https://rufus.ie/downloads/ 1.3 以管理员身份运行 rufus 设备选择你用来制作启动项的U盘,不能选错了;点…...
【家政平台开发(42)】筑牢家政平台安全防线:安全测试与漏洞修复指南
本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…...
JavaWeb-04-Web后端基础(SpringBootWeb、HTTP协议、分层解耦、IOC和DI)
目录 一、SpringBootWeb入门 1.1 概述 1.2 入门程序 1.2.1 需求 1.2.2 开发步骤 1.3 入门解析 二、HTTP协议 2.1 HTTP概述 2.1.1 介绍 2.1.2 特点 2.2 HTTP请求协议 2.2.1 介绍 2.2.2 获取请求数据 2.3 HTTP响应协议 2.3.1 格式介绍 2.3.2 响应状态码 2.3…...
随笔 20250413 Elasticsearch 的 term 查询
你这个问题非常经典,来自于 Elasticsearch 的 term 查询是 ✅精确匹配(case-sensitive,大小写敏感)! 🧨 为什么查不到 "World"? 你的查询语句是: GET /movie/_search {&…...
zk(Zookeeper)实现分布式锁
Zookeeper实现分布式锁 1,zk中锁的种类: 读锁:大家都可以读,要想上读锁的前提:之前的锁没有写锁 写锁:只有得到写锁的才能写。要想上写锁的前提是:之前没有任何锁 2,zk如何上读锁 创…...
操作系统简要概述
操作系统是计算机系统的核心软件,它管理和控制计算机硬件与软件资源,为用户提供方便、高效、安全的使用环境。以下是关于操作系统的详细介绍: 一、定义 操作系统(Operating System,简称 OS)是计算机硬件与…...
开漏模式的触发条件和工作状态
MOS管的漏-栅-源三极 漏极开路--开漏 电路整体概述 这是开漏(Open - Drain)电路结构,核心由输出控制模块和一对互补的MOS管(P - MOS和N - MOS)组成。开漏电路的特点是MOS管漏极开路,这种结构常用于需要实现…...
【Java学习笔记】Java第一课,梦开始的地方!!!
目录 1. 基本内容介绍和软件安装 2. 快速入门之第一个程序 hello world 3. 学习方法 基本介绍和软件安装 一、 Java 岗位与应用场景 说明:java 基础也称javaSE 岗位 1. javaEE 软件工程师 电商领域 团购 众筹 sns(社交网络) 教育 金…...
共享内存技术
一、共享内存 共享内存是一种高效的 进程间通信(IPC) 机制,允许多个进程直接访问同一块物理内存区域,无需通过内核缓冲区或文件进行数据拷贝。它通常用于需要 低延迟、高吞吐量 的数据交换场景(如实时系统、高频交易、…...
Lc 大数运算--快速幂 | 统计好数字的数目
快速幂算法是一种高效计算大数幂运算的方法,能将时间复杂度从传统算法的O(n)降低到O(log n)。它的核心思想是 分解指数分治思想,类似生活中的「拆快递」—— 把大包裹拆成小份,分批处理更高效。 一、生活示例:存钱罐的复利计算 假…...
Linux内存管理架构(2)
4.虚拟地址空间布局 4.1虚拟地址空间划分 对于64位处理器,目前不支持完全的64位虚拟地址 1.ARM64内核/用户虚拟地址划分 1. 虚拟地址的最大宽度 最大宽度:虚拟地址的最大宽度是48位。 内核虚拟地址: 在64位地址空间的顶部。高16位全是1。范围…...
图论基础理论
在我看来,想要掌握图的基础应用,仅需要三步走。 什么是图(基本概念)、图的构造(打地基)、图的遍历方式(应用的基础) 只要能OK的掌握这三步、就算图论入门了!࿰…...
最大子序和问题——动态规划/贪心算法解决
目录 一:问题描述 二:解决思路1——动态规划思想 三:C 语言代码实现 四:复杂度分析 五:解决思路2——贪心算法思想 六:具体步骤 七: C语言代码实现 八:复杂度分析 一:问题描述 …...
车载以太网-SOMEIP
文章目录 基本概念SOME/IP的起源与核心定位核心定位设计目标协议栈架构与OSI模型映射报文结构与数据序列化SOME/IP的核心通信机制通信模式分类服务发现协议(SOME/IP-SD)服务发现流程服务质量(QoS)管理SOME/IP在智能汽车中的典型应用SOME/IP测试与验证体系SOME/IP测试环境构…...
DrissionPage详细教程
1. 基本概述 DrissionPage 是一个基于 python 的网页自动化工具。它既能控制浏览器,也能像requests一样收发数据包,更重要的是还能把两者合二为一。因此,简单来说DrissionPage可兼顾浏览器自动化的便利性和 requests 的高效率。 DrissionPa…...
6.1 GitHub亿级数据采集实战:双通道架构+三级容灾设计,破解API限制与反爬难题
GitHub 项目数据获取功能设计与实现 关键词:GitHub API 集成、网页爬虫开发、数据存储设计、定时任务调度、异常处理机制 1. 数据获取架构设计 采用双通道数据采集策略,同时使用 GitHub 官方 API 和网页爬虫技术确保数据完整性: #mermaid-svg-XUg7xhHrzFAozG4J {font-fami…...
LabVIEW 控制电机需注意的关键问题
在自动化控制系统中,LabVIEW 作为图形化编程平台,因其高度可视化、易于集成硬件等优势,被广泛应用于电机控制场景。然而,要实现稳定、精确、高效的电机控制,仅有软件并不足够,还需结合硬件选型、控制逻辑设…...
Linux系统远程操作和程序编译
目录 一、Linux远程终端登录、图形桌面访问、 X图形窗口访问和FTP文件传输操作 1.1 桥接模式 1.2 putty远程登录Ubuntu 1.3 win10远程登录并上传下载文件 1.4 X server仿真软件安装 1.5 树莓派在putty上的远程登录 1.6 使用ftp远程登录并实现文件上传下载 1.7 Linux下的…...
Mac配置开发环境
博主是一名Python后端开发,有时候环境太多 需要配置太多,故做此文章 环境Macbook ,请注意自己的是ARM 还是x86 结构 Vscode/Cursor配置Python debug 配置Debug launch.json {"version": "0.2.0","configuratio…...
LabVIEW配电器自动测试系统
随着航天技术的迅猛发展,航天器供配电系统的结构越来越复杂,对配电器的功能完整性、稳定性和可靠性提出了更高要求。传统人工测试方式难以满足高效率、高精度、可重复的测试需求。本项目开发了一套基于LabVIEW平台的宇航配电器自动测试系统,融…...
生成与强化学习:赋予VLA系统物理行动能力
引言:从“理解世界”到“改变世界” 当机器能够“看懂”图像、“听懂”指令时,一个更根本的挑战浮现:如何让它们像人类一样,将认知转化为精准的物理动作?无论是机械臂抓取杯子,还是自动驾驶汽车紧急避障&a…...
基于Springboot+Mysql的闲一品(含LW+PPT+源码+系统演示视频+安装说明)
系统功能 管理员功能:首页、个人中心、用户管理、零食分类管理、零食信息管理、订单评价管理、系统管理、订单管理。用户功能:首页、个人中心、订单评价管理、我的收藏管理、订单管理。前台首页功能:首页、零食信息、零食资讯、个人中心、后…...
jupyter4.4安装使用
一、chrome谷歌浏览器 1. 安装 1.1 下载地址: 下载地址: https://www.google.cn/intl/zh-CN_ALL/chrome/fallback/ 2 插件markdown-viewer 2.1 下载地址: 下载地址:https://github.com/simov/markdown-viewer/releases 2.2…...
Linux虚拟内存详解
引言 虚拟内存是现代操作系统中的核心概念之一,它为进程提供了一个连续的、独立的地址空间,有效解决了物理内存限制问题,并大大简化了程序开发和执行。本文将深入探讨Linux系统中虚拟内存的工作原理、实现机制以及相关的内存管理技术&#x…...
数据库安装(基于Linux下centos7)(保姆级教程)
前言:笔者有段时间没写博客了,今天笔者要分享新的知识了,那就是数据库,笔者会通过博客系统的且通俗易懂的分享数据库知识,对于想要学习数据库和学习过数据库的老铁复习都是非常有用的,绝对干货满满,那么今天…...
【自动驾驶 机器人】速度规划 |梯形/S型速度曲线
参考文章: (1)【自动驾驶】运动规划丨速度规划丨T型/S型速度曲线 (2)一文教你快速搞懂速度曲线规划之S形曲线(超详细图文推导附件代码) 1 梯形速度曲线 如下图所示梯形速度/加速度/加加速度曲…...
Qt C++内存泄漏排查方法
在Qt C++中排查内存泄漏可以按照以下步骤进行,结合工具使用和代码审查: 1. 使用内存检测工具 Valgrind (Linux/macOS) 安装Valgrind:sudo apt-get install valgrind运行程序并检测内存泄漏:valgrind --leak-check=full ./your_qt_app分析输出结果,定位未释放的内存块。Dr…...
[redis进阶一]redis的持久化(2)AOF篇章
目录 一 为什么有了RDB持久化机制还要有AOF呢 板书介绍具体原因: 编辑二 详细讲解AOF机制 (1)AOF的基本使用 1)板书如下 2)开启AOF机制: 3) AOF工作流程 (2)AOF是否会影响到redis性能 编辑 (3)AOF缓冲区刷新策略 (4)AOF的重写机制 板书如下: 为什么要有这个重写机…...
聊天室项目day4(redis实现验证码期限,实现redis连接池)
1.redis连接池操作和之前所学过的io_context连接池原理一样这里不多赘述,也是创建多个连接,使用时按顺序取出来。 2.知识补充redisConnect()函数建立与 Redis 服务器的非阻塞网络连接,成功返回 redisContext*(连接上下文指针&…...
Redis之分布式锁
面试切入点 锁的分类 单机版同一个JVM虚拟机内,synchronized或者Lock接口分布式多个不同JVM虚拟机,单机的线程锁不再起作用,资源类在不同的服务器之间共享了 一个靠谱分布式锁需要具备的条件与刚需 独占性:onlyOneÿ…...
AF3 ProteinDataset类的__getitem__方法解读
AlphaFold3 protein_dataset 模块 ProteinDataset 类 __getitem__ 方法用于从数据集中获取一个条目,并根据配置对数据进行处理。 源代码: def __getitem__(self, idx):"""Return an entry from the dataset.If a clusters file is provided, then the idx i…...
NLP 梳理02 — 标点符号和大小写
文章目录 一、说明二、为什么文本预处理中需要小写2.1 为什么小写在文本预处理中至关重要?2.2 区分大小写对 NLP 任务的影响 三、删除标点符号及其对 NLP 任务的影响3.1 什么是标点符号?3.2 为什么在文本预处理中删除标点符号?3.3 删除标点符…...
HarmonyOS中的多线程并发机制
目录 多线程并发1. 多线程并发概述2 多线程并发模型3 TaskPool简介4 Worker简介4.1 Woker注意事项4.2 Woker基本用法示例 5. TaskPool和Worker的对比5.1 实现特点对比5.2 适用场景对比 多线程并发 1. 多线程并发概述 并发模型是用来实现不同应用场景中并发任务的编程模型&…...
游戏引擎学习第221天:(实现多层次过场动画)
资产: intro_art.hha 已发布 在下载页面,你会看到一个新的艺术包。你将需要这个艺术包来进行接下来的开发工作。这个艺术包是由一位艺术家精心制作并打包成我们设计的格式,旨在将这些艺术资源直接应用到游戏中。它包含了许多我们会在接下来的直播中使用…...
Python | 在Pandas中按照中值对箱形图排序
箱形图是可视化数据分布的强大工具,因为它们提供了对数据集内的散布、四分位数和离群值的洞察。然而,当处理多个组或类别时,通过特定的测量(如中位数)对箱形图进行排序可以提高清晰度并有助于揭示模式。在本文中&#…...
openapi + knife4j的使用
一、依赖作用与关系 1. springdoc-openapi-starter-webmvc-api • 核心功能: 基于 OpenAPI 3 规范,自动生成 API 文档元数据(JSON 格式),并集成 Spring MVC。 提供Tag Operation、Schema 等注解,支持通过…...
数据结构*包装类泛型
包装类 什么是包装类 在讲基本数据类型的时候,有提到过包装类。 基本数据类型包装类byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean 我们知道:基本数据类型并不是对象,没有对象所具有的方法和属…...
Azure Synapse Dedicated SQL pool里大型表对大型表分批合并数据的策略
Azure Synapse Dedicated SQL pool中大型表的数据通过MERGE INTO语句合并到另一张大型表的时间很长,容易造成运行超时,而有的时候超时的时间是管理设置,由客户控制,无法修改。这种时候为了确保操作可以运行成功,需要将…...
Day81 | 灵神 | 快慢指针 链表的中间结点 环形链表
Day81 | 灵神 | 快慢指针 链表的中间结点 环形链表 876.链表的中间结点 876. 链表的中间结点 - 力扣(LeetCode) 思路: 设置两个指针,一个快指针r一个慢指针l 初始都是头结点 我们要求的是中间节点 所以快指针走两步&#x…...
【DDR 内存学习专栏 1.2 -- DDR Channel 介绍】
文章目录 1. DDR中的通道(Channel)概念1.1 DDR Channel 与 DDRC1.2 DIMM 内存插槽1.3 物理通道的定义1.3.1 多通道的作用 1.4 通道的硬件实现1.5 多核系统的DDR通道分配策略 1. DDR中的通道(Channel)概念 关于 DDR 通道ÿ…...
深入解析xDeepFM:结合压缩交互网络与深度神经网络的推荐系统新突破
今天是周日,我来解读一篇有趣的文章——xDeepFM。这篇文章由 Mao et al. 发表在SIGIR 2019会议。文章提出了一个新的网络模型——压缩交互网络(CIN),用于显式地学习高阶特征交互。通过结合 CIN 和传统的深度神经网络(D…...
Mybatis 中 <mappers> 标签四种配置方式
在MyBatis中,我们可以通过四种不同的方式来配置Mappers标签 : 1. 使用 <package name=""> 批量扫描包 这种方式通过指定一个包名,MyBatis 会自动扫描该包下的所有接口并注册为映射器。 <mappers><package name="com.example.mapper"/&…...
科技赋能记忆共生-郑州
故事背景 故事发生在中国河南郑州的现代城市环境中,这里描绘了人与科技的交融与共生。多样的场景展示了人与自然、历史与未来的互动,通过各种科技手段与古老文化相结合,展现出未来城市的独特魅力。 故事内容 在中国河南郑州,一座科…...
根据开始日期和结束日志统计共有多少天和每天的营业额
controller 重点:根据时间格式接受时间类型参数 DateTimeFormat(pattern "yyyy-MM-dd") LocalDateTime begin, DateTimeFormat(pattern "yyyy-MM-dd") LocalDateTime end) RestController RequestMapping("/admin/report") Slf4…...
LLMs之Agent之A2A:A2A的简介、安装和使用方法、案例应用之详细攻略
LLMs之Agent之A2A:A2A的简介、安装和使用方法、案例应用之详细攻略 目录 相关文章 LLMs之Agent之A2A:《Announcing the Agent2Agent Protocol (A2A)》翻译与解读 LLMs之Agent之A2A:A2A的简介、安装和使用方法、案例应用之详细攻略 A2A协议…...
深入学习OpenCV:第一章简介
本专栏为零基础开发者打造,聚焦OpenCV在Python中的高效应用,用100%代码实践带你玩转图像处理! 从 环境配置到实战项目,内容涵盖: 1️⃣ 基础篇:图像读写、阈值处理、色彩空间转换 2️⃣ 进阶篇ÿ…...
汉诺塔问题——用贪心算法解决
目录 一:起源 二:问题描述 三:规律 三:解决方案 递归算法 四:代码实现 复杂度分析 一:起源 汉诺塔(Tower of Hanoi)问题起源于一个印度的古老传说。在世界中心贝拿勒斯&#…...
【双指针】专题:LeetCode 283题解——移动零
移动零 一、题目链接二、题目三、题目解析四、算法原理两个指针的作用以及三个区间总结 五、与快速排序的联系六、编写代码七、时间复杂度、空间复杂度 一、题目链接 移动零 二、题目 三、题目解析 “保持非零元素的相对顺序”,比如,示例1中非零元素1…...