Tauri 2.3.1+Leptos 0.7.8开发桌面应用--Sqlite数据库选中数据的表格输出

在前期工作的基础上(Tauri 2.3.1+Leptos 0.7.8开发桌面应用--Sqlite数据库的写入、展示和选择删除_tauri leptos sqlite 选择删除-CSDN博客),实现将选中的数据实时用表格展示出来,效果如下:

1. 后台invoke调用命令

Tauri后台lib.rs文件中send_seleted_pdt命令的代码如下: 

#[tauri::command]
async fn send_selected_pdt(state: tauri::State<'_, DbState>, productlist:Vec<i64>) -> Result<Vec<Pdt>, String> {
    // 参数名productlist必须与前端定义的结构变量SelectedPdtArgs的键值一致
    let db = &state.db;

    // 处理空数组的情况
    if productlist.is_empty() {
        return Err(String::from("读取失败:未提供有效的产品ID"));
    }

    // 生成动态占位符(根据数组长度生成 ?, ?, ?)
    let placeholders = vec!["?"; productlist.len()].join(", ");

    let query_str = format!(
        "SELECT * FROM products WHERE pdt_id IN ({})",
        placeholders
    );

    // 构建查询并绑定参数
    let mut query = sqlx::query_as::<_, Pdt>(&query_str);
    for id in &productlist {
        query = query.bind(id);
    }

    // 执行读取操作
    let query_result = query
        .fetch_all(db)
        .await
        .map_err(|e| format!("查询失败: {}", e))?;
    
    Ok(query_result)

}

 命令的返回格式为 Result<Vec<Pdt>, String>,Pdt为结构变量,定义如下:

#[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,
}

然后还需对send_selected_pdt命令进行注册:

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![
            send_selected_pdt
            ])
        .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程序的时候出错!");
}

2. Leptos前端调用并使用表格展示

首先要定义相应的信号,然后在展示的所有数据前面添加复选框,定义复选框选中事件,根据选中数据的Pdt_id列表,实时读取数据库的产品数据,并通过表格展示。

信号定义、复选框选中事件函数check_change、读取数据库所有数据库按钮事件函数receive_pdt_db代码如下:

#[component]
pub fn AcidInput() -> impl IntoView {         //函数返回IntoView类型,即返回view!宏,函数名App()也是主程序view!宏中的组件名(component name)。

    let (div_content, set_div_content) = signal(view! { <div>{Vec::<View<_>>::new()}</div> });
    let (selected_items, set_selected_items) = signal::<Vec<i64>>(vec![]);
    let (selected_pdt_data, set_selected_pdt_data) = signal::<Vec<Pdt>>(vec![]);
    let (data, set_data) = signal::<Vec<DataPoint>>(vec![]);

     //处理复选框事件,选中的同时展示在表格中
     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() {               //target.checked与prop:checked不一样, 是浏览器 DOM 的实时状态,用于事件处理
                        items.push(value);
                    } else {
                        items.retain(|&x| x != value);
                    }
                });
            };
            //接收读取的数据
            log!("选中的数据列表: {:?}", selected_items.get_untracked());
            if selected_items.get_untracked().len() != 0 {
                let args = SelectedPdtArgs{
                    productlist:selected_items.get_untracked(),
                };
                let args_js = serde_wasm_bindgen::to_value(&args).unwrap();   //参数序列化
                let pdt_js = invoke("send_selected_pdt", args_js).await;
                let pdt_vec: Vec<Pdt> = serde_wasm_bindgen::from_value(pdt_js).map_err(|_| JsValue::from("Deserialization error")).unwrap();
                set_selected_pdt_data.set(pdt_vec);
                //log!("返回的产品数据: {:?}", selected_pdt_data.get_untracked());
            }else{
                set_selected_pdt_data.set(vec![]);
            }
        });
    };

    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;">
                                    <input
                                        type="checkbox"
                                        name="items"
                                        value=pdt_id.to_string()
                                        prop:checked=move || selected_items.get().contains(&pdt_id)     //Leptos 的状态绑定,用于确保界面最终与数据同步。
                                        on:change=check_change      //用户操作 → 更新 target.checked → 触发事件check_change → 更新状态 → prop:checked 驱动视图更新。
                                    />
                                    <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);
          
        });
    };
};

其中,结构变量Pdt定义如下:

#[derive(Serialize, Deserialize, Clone, Debug)]
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,
}

views!视图中表格部分内容如下:

<div style="flex: 1;">
     <PdtTable data=move || selected_pdt_data.get() 
          x_bg_color="red-100"   // 对应 --red-100-bg
          x_text_color="red-800" // 对应 --red-800-text
     />
</div>

其中调用了视图命令PdtTable,具体代码如下:

#[component]
fn PdtTable<F>(
    data: F,
    // 颜色参数(变量)用于设置 CSS 变量 (对应 styles.css 中定义的变量)
    #[prop(default = "blue-100")]  // 对应 --blue-100-bg
    x_bg_color: &'static str,
    #[prop(default = "blue-800")]  // 对应 --blue-800-text
    x_text_color: &'static str,
    #[prop(default = "green-100")] // 对应 --green-100-bg
    y_bg_color: &'static str,
    #[prop(default = "green-800")] // 对应 --green-800-text
    y_text_color: &'static str,
) -> impl IntoView
where
    F: Fn() -> Vec<Pdt> + 'static + Send + Sync + Clone,
{
    view! {
        <table class="table-container" style="border-collapse: collapse; border: 3px double black;">
            <thead>
                <tr>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>ID</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>产品名称</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"SiO2"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"Al2O3"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"CaO"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"MgO"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"Fe2O3"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"TiO2"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"K2O"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"Na2O"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>"MnO2"</th>
                    <th class="table-header-cell" style:background-color=move || format!("var(--{}-bg)", x_bg_color) style:color=move || format!("var(--{}-text)", x_text_color)>生产日期</th>
                </tr>
            </thead>
            <tbody>
                {move || data().into_iter().map(|pdt| view! {
                    <tr>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{pdt.pdt_id}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{pdt.pdt_name}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_si)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_al)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_ca)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_mg)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_fe)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_ti)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_ka)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_na)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{format!("{:.2}", pdt.pdt_mn)}</td>
                        <td class="table-data-cell" style:background-color=move || format!("var(--{}-bg)", y_bg_color) style:color=move || format!("var(--{}-text)", y_text_color)>{pdt.pdt_date}</td>
                    </tr>
                }).collect_view()}
            </tbody>
        </table>
    }
}

需要强调的是:Leptos 的响应式系统要求组件闭包能在多线程环境中安全传递,所以<PdtTable data= move || selected_pdt_data.get() />中闭包要实现线程安全约束(Send + Sync)。具体在程序中为泛型F添加线程安全约束:Send + Sync,为了便于后面克隆,添加Clone约束。

另外在PdtTable中定义了颜色参数(变量)用于设置 CSS 变量,以及用的自定义class,需要在styles.css中添加如下内容:

  .table-container {
    margin-left: auto;
    margin-right: auto;
    border-collapse: collapse;
    border: 3px double #d1d5db;
  }
  
  .table-header-cell {
    position: sticky;
    left: 0;
    background-color: #e5e7eb;
    padding: 0.5rem 1rem;
    border: 1px solid #d1d5db;
  }
  
  .table-data-cell {
    padding: 0.5rem 1rem;
    min-width: 20px;
    border: 1px solid #d1d5db;
  }
  
  :root {
    --blue-100-bg: #dbeafe;
    --blue-800-text: #1e40af;
    --red-100-bg: #fee2e2;
    --red-800-text: #991b1b;
    --green-100-bg: #dcfce7;
    --green-800-text: #166534;
  }

至此,基本就完成了将数据库选中数据实时用表格显示出来。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值