Tauri(2.5.1)+Leptos(0.7.8)开发桌面应用--简单的工作进度管理

发布于:2025-06-06 ⋅ 阅读:(25) ⋅ 点赞:(0)

在前期工作(Tauri(2.5.1)+Leptos(0.7.8)开发桌面应用--程序启动界面_tauri 程序启动画面-CSDN博客)的基础上继续进行自用桌面小程序的开发。为了方便管理工作进度,决定自己造轮子。效果如下:

工作进度管理系统

在编写程序过程中,使用了Roo Code绑定的DeepSeek API 辅助编程,能力确实很强大。

  1. 数据库结构

数据操作详见:Tauri2+Leptos开发桌面应用--Sqlite数据库操作_tauri sqlite-CSDN博客

cd src-tauri
sqlx migrate add create_works_table

大致的数据库结构如下图所示: 

 

 打开数据迁移文件:src-tauri\migrations\xxxxxx_create_works_table.sql文件,修改内容如下:

-- Add migration script here

--强制启用外键约束,此语句确保数据库强制检查外键关系,需在每次数据库连接时重新执行
PRAGMA foreign_keys = ON;

-- 部门表
CREATE TABLE IF NOT EXISTS departments (
    id INTEGER PRIMARY KEY AUTOINCREMENT,   --自增主键
    name TEXT NOT NULL UNIQUE               --唯一部门名称
);

-- 人员表
CREATE TABLE IF NOT EXISTS personnel (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    full_name TEXT NOT NULL,
    department_id INTEGER NOT NULL,         --所属部门ID
    FOREIGN KEY (department_id) REFERENCES departments(id)  --人员必须属于已存在的部门(department_id 外键约束)
);

-- 工作类型表
CREATE TABLE IF NOT EXISTS work_types (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE
);

-- 工作主表(核心字段)
CREATE TABLE IF NOT EXISTS works (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    subject TEXT NOT NULL,
    work_content TEXT NOT NULL,
    start_date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,  -- SQLite 无原生 DATETIME 类型,实际上仍存储为 TEXT 类型,但使用语义化类型名称
    work_type_id INTEGER NOT NULL,  --关联工作类型
    is_completed INTEGER NOT NULL DEFAULT 0 CHECK (is_completed IN (0, 1)),     --is_completed:完成状态标识(0=未完成,1=已完成),CHECK 约束确保只能存储 0 或 1
    FOREIGN KEY (work_type_id) REFERENCES work_types(id)
);

-- 工作-部门关联表(责任部门),多对多关系:一个工作可关联多个部门,一个部门可参与多个工作
CREATE TABLE IF NOT EXISTS work_departments (
    work_id INTEGER NOT NULL,
    department_id INTEGER NOT NULL,
    PRIMARY KEY (work_id, department_id),
    FOREIGN KEY (work_id) REFERENCES works(id) ON DELETE CASCADE,   --当删除工作(work_id外键关联)时,自动删除关联记录
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

-- 工作-人员关联表(责任人),多对对关系
CREATE TABLE IF NOT EXISTS work_personnel (
    work_id INTEGER NOT NULL,
    personnel_id INTEGER NOT NULL,
    is_main_responsible INTEGER NOT NULL DEFAULT 0 CHECK (is_main_responsible IN (0, 1)),   --is_main_responsible:主负责人标识(0=普通负责人,1=主负责人)
    PRIMARY KEY (work_id, personnel_id),
    FOREIGN KEY (work_id) REFERENCES works(id) ON DELETE CASCADE,   ----当删除工作(work_id外键关联)时,自动删除关联记录
    FOREIGN KEY (personnel_id) REFERENCES personnel(id)
);

-- 进度记录表(直接关联工作)
CREATE TABLE IF NOT EXISTS progress_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    work_id INTEGER NOT NULL,
    progress_detail TEXT NOT NULL,
    recorder_id INTEGER NOT NULL,
    record_date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
    FOREIGN KEY (work_id) REFERENCES works(id) ON DELETE CASCADE,
    FOREIGN KEY (recorder_id) REFERENCES personnel(id)
);

-- 索引优化
CREATE INDEX idx_works_completion ON works(is_completed);   --idx_works_completion:加速按完成状态筛选工作
CREATE INDEX idx_work_person_main ON work_personnel(is_main_responsible); --idx_work_person_main:快速查找主负责人
CREATE INDEX idx_progress_timestamp ON progress_records(record_date);   --idx_progress_timestamp:按时间排序进度记录

2. 前端Leptos设计

根据前面的数据结构,工作进度管理界面要具备以下功能:

1. 新建和删除工作部门;

2. 每个部门添加和删除员工;

3. 新建和删除工作状态:进行中或已完成;

4. 新建和删除工作;

5. 每个工作添加或删除进度记录,并可改变工作状态。

前端的src/app.rs文件内容如下:

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 />
        }
    })
}

调用app/app.rs文件的App,在其中使用leptos_router实现标签页功能,具体文件内容如下:

#[warn(unused_imports)]
use leptos::prelude::*;
use leptos_router::components::{Route, Router, Routes};
use leptos_router::path;
mod acidinput;
mod schedule;

use acidinput::*;
use schedule::*;



#[component]
pub fn App() -> impl IntoView {
    view! {
        <Router>
            <nav>
                <a class="nav" href="/">"工作进度表"</a>
                <a class="nav" href="/acidinput">"产品录入"</a>
            </nav>
            <main>
                <Routes fallback=|| "Not found.">
                    // / just has an un-nested "Home"
                    <Route path=path!("/") view= || view! {<WorkSchedule />} />
                    <Route path=path!("/acidinput") view=|| view! {<AcidInput />} />
                </Routes>                
            </main>
        </Router>
    }
}

工作进度表的界面设计放在了schedule.rs文件中,文件内容如下:

use leptos::task::spawn_local;
use leptos::*;
use leptos::{ev::SubmitEvent, prelude::*};
use serde::{Deserialize, Serialize};
use leptos::ev::Event;
use wasm_bindgen::prelude::*;
use web_sys;
use serde_wasm_bindgen;
use web_sys::HtmlInputElement;
use leptos::logging::log;
use chrono::{Local};
use web_sys::{HtmlSelectElement};
use std::rc::Rc;


#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke, catch)]
    async fn invoke_without_args(cmd: &str) -> Result<JsValue, JsValue>;

    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], catch)]
    async fn invoke(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;
}

#[derive(Serialize, Deserialize, Clone, Debug)]
struct Department {
    id:i64,
    name:String,
}

#[derive(Serialize, Deserialize)]
struct DepartmentSend {
    name:String,
}

#[derive(Serialize, Deserialize)]
struct DepartmentArgs {
    department: DepartmentSend,
}

#[derive(Serialize, Deserialize)]
struct SelectedDeptArgs {    // 将invoke调用的参数打包成结构变量再通过json传递,tauri后台invoke函数的参数名称必须根键一致(譬如此处的productlist)
    deptlist: Vec<i64>, // 将Vec<i64>数组包装为一个包含 `productlist` 键的对象,键不能带下划线"_"
}

#[derive(Serialize, Deserialize)]
struct SelectedItemArgs {    // 将invoke调用的参数打包成结构变量再通过json传递,tauri后台invoke函数的参数名称必须根键一致(譬如此处的productlist)
    selectedlist: Vec<i64>, // 将Vec<i64>数组包装为一个包含 `selectedlist` 键的对象,键不能带下划线"_"
}

#[derive(Serialize, Deserialize, Clone, Debug)]
struct Worktype {
    id:i64,
    name:String,
}

#[derive(Serialize, Deserialize)]
struct WorktypeSend {
    name:String,
}

#[derive(Serialize, Deserialize)]
struct WorktypeArgs {
    worktype: WorktypeSend,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
struct Personnel {
    id:i64,
    full_name:String,
    department_id:i64,
}

#[derive(Serialize, Deserialize)]
struct PersonnelSend {
    full_name:String,
    department_id:i64,
}

#[derive(Serialize, Deserialize)]
struct PersonnelArgs {
    personnel: PersonnelSend,
}

#[derive(Serialize, Deserialize)]
struct WorkArgs {
    work: WorkSend,
}

#[derive(Serialize, Deserialize)]
struct WorkSend {
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
}

#[derive(Serialize, Deserialize, Clone)]
struct Work {
    id: i64,
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
}


#[derive(Serialize, Deserialize)]
struct PersonnelDeptArgs {
    departmentid: i64,
}


#[derive(Serialize, Deserialize)]
struct WorkDeptsArgs {
    workdepts: Vec<WorkDeptsSend>,
}

#[derive(Serialize, Deserialize)]
struct WorkDeptsSend {
    work_id: i64,
    department_id: i64,
}

#[derive(Serialize, Deserialize)]
struct WorkPersonArgs {
    workpersonnels: Vec<WorkPersonSend>,
}

#[derive(Serialize, Deserialize)]
struct WorkPersonSend {
    work_id: i64,
    personnel_id: i64,
    is_main_responsible: i64
}

#[derive(Serialize, Deserialize)]
struct WorkAll {
    id: i64,
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
    work_departments: Vec<WorkDeptsSend>,
    work_personnels: Vec<WorkPersonSend>
}

#[derive(Serialize, Deserialize)]
struct FetchWorkArgs {
    workid: i64,
}

#[derive(Debug, Serialize, Deserialize)]
struct WorkBack {
    id: i64,
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
    work_departments: Vec<Department>,
    work_personnels: Vec<Personnel>,
    responsile_person: Vec<Personnel>
}

#[component]
pub fn WorkSchedule() -> impl IntoView {
    //定义工作部门名称及信号
    let (department_name, set_department_name) = signal(String::new());
    let (department_error, set_department_error) =  signal(String::new());
    let (department_content, set_department_content) = signal(view! { <div>{Vec::<View<_>>::new()}</div> });
    let (selected_depts, set_selected_depts) = signal::<Vec<i64>>(vec![]);
    let (deptsubmit_error, set_deptsubmit_error) =  signal(String::new());
    let (deptdb_msg, set_deptdb_msg) =  signal(String::new());
    let (department_list, set_department_list) =  signal::<Vec<Department>>(vec![]);

    //定义工作类型名称及信号
    let (work_type, set_work_type) = signal(String::new());
    let (worktype_error, set_worktype_error) =  signal(String::new());
    let (worktype_content, set_worktype_content) = signal(view! { <div>{Vec::<View<_>>::new()}</div> });
    let (selected_worktypes, set_selected_worktypes) = signal::<Vec<i64>>(vec![]);
    let (typesubmit_error, set_typesubmit_error) =  signal(String::new());
    let (typedb_msg, set_typedb_msg) =  signal(String::new());
    let (worktype_list, set_worktype_list) = signal::<Vec<Worktype>>(vec![]);


    //定义人员及信号
    let (personnel_name, set_personnel_name) = signal(String::new());
    let (personnel_error, set_personnel_error) =  signal(String::new());
    let (personnel_deptid, set_personnel_deptid) = signal::<i64>(0);
    let (personnel_deptid_error, set_personnel_deptid_error) =  signal(String::new());
    let (personnel_content, set_personnel_content) = signal(view! { <div>{Vec::<View<_>>::new()}</div> });

    let (selected_personnels, set_selected_personnels) = signal::<Vec<i64>>(vec![]);
    let (personnelsubmit_error, set_personnelsubmit_error) =  signal(String::new());
    let (personneldb_msg, set_personneldb_msg) =  signal(String::new());

    //定义工作及信号
    let (work_subject, set_work_subject) = signal(String::new());
    let (subjet_error, set_subject_error) =  signal(String::new());
    let (work_content, set_work_content) = signal(String::new());
    let (workcontent_error, set_workcontent_error) =  signal(String::new());
    let now = Local::now().format("%Y-%m-%dT%H:%M").to_string();
    let (start_date, set_start_date) = signal(now.clone());
    let (work_state, set_work_state) = signal::<i64>(0);
    let (worktype_id, set_worktype_id) = signal::<i64>(0);
    let (typeid_error, set_typeid_error) = signal(String::new());
    let (workview_content, set_workview_content) = signal(view! { <div>{Vec::<View<_>>::new()}</div> });
    let (selected_works, set_selected_works) = signal::<Vec<i64>>(vec![]);
    let (worksubmit_error, set_worksubmit_error) =  signal(String::new());
    let (workdb_msg, set_workdb_msg) =  signal(String::new());
    let (work_depts, set_work_depts) = signal::<Vec<i64>>(vec![]);
    let (work_personnel, set_work_personnel) = signal::<Vec<Personnel>>(vec![]);
    let (work_responsible, set_work_responsible) = signal::<Vec<Personnel>>(vec![]);
    let (personnel_list, set_personnel_list) = signal::<Vec<Personnel>>(vec![]);
    let (dept_personnel_error, set_dept_personnel_error) =  signal(String::new());
    let (work_id, set_work_id) = signal::<i64>(0); // 新增work_id信号
    let (fetch_work_id, set_fetch_work_id) = signal::<i64>(0); // 新增work_id信号
    let (fetch_works_error, set_fetch_works_error) = signal(String::new());
    //let (fetch_work_subject, set_fetch_work_subject) = signal(String::new());
    let (fetch_work_content, set_fetch_work_content) = signal(String::new());
    let (fetch_work_startdate, set_fetch_work_startdate) = signal(String::new());
    let (fetch_worktype_id, set_fetch_worktype_id) =  signal::<i64>(0); 
    let (fetch_work_state, set_fetch_work_state) =  signal::<i64>(0); 
    let (fetch_work_list, set_fetch_work_list) =  signal::<Vec<Work>>(vec![]);
    let (fetch_work_personnels, set_fetch_work_personnels) = signal::<Vec<Personnel>>(vec![]);
    let (fetch_work_depts, set_fetch_work_depts) =  signal::<Vec<Department>>(vec![]);
    let (fetch_work_responsile, set_fetch_work_responsile) = signal::<Vec<Personnel>>(vec![]);
    let (records_date, set_records_date) = signal(now);
    let (progress_content, set_progress_content) = signal(String::new());
    let (progress_recorder, set_progress_recorder) = signal::<i64>(0);
    let (add_record_error, set_add_record_error) = signal(String::new());
    let (fetch_progress_records, set_fetch_progress_records) = signal::<Vec<ProgressRecord>>(vec![]);


    #[derive(Serialize, Deserialize, Clone)]
    struct ProgressRecord {
        id: i64,
        progress_detail: String,
        recorder_id: i64,
        record_date: String,
    }

    #[derive(Serialize, Deserialize)]
    struct ProgressRecordSend {
        work_id: i64,
        progress_detail: String,
        recorder_id: i64,
        record_date: String,
    }

    #[derive(Serialize, Deserialize)]
    struct ProgressRecordArgs {
        progressrecord: ProgressRecordSend,
    }

    #[derive(Serialize, Deserialize)]
    struct SelectedRecord {
        selectedrecord: i64,
    }

    let get_progress_records = move ||{

        spawn_local(async move {
            let args = FetchWorkArgs{workid: fetch_work_id.get_untracked()};
            let args_js = match serde_wasm_bindgen::to_value(&args) {
                Ok(v) => v,
                Err(e) => {
                    set_fetch_works_error.set(format!("参数序列化失败: {}", e));
                    return;
                }
            };
            
            match invoke("send_progress_record", args_js).await {
                Ok(result) => {
                    match serde_wasm_bindgen::from_value::<Vec<ProgressRecord>>(result) {
                        Ok(work) => {
                            set_fetch_progress_records.set(work);
                        }
                        Err(e) => {
                            set_fetch_works_error.set(format!("工作数据反序列化失败: {}", e));
                        }
                    }
                }
                Err(e) => {
                    set_fetch_works_error.set(format!("获取工作详情失败: {:?}", e));
                }
            }
        });
    };

    let write_progress_records = move |ev: SubmitEvent| {
        ev.prevent_default();
        spawn_local(async move {
            let work_id = fetch_work_id.get_untracked();
            if work_id == 0 {
                set_add_record_error.set("请先选择工作".to_string());
                return;
            }

            let progress_detail = progress_content.get_untracked();
            if progress_detail.is_empty() {
                set_add_record_error.set("进度内容不能为空".to_string());
                return;
            }

            let recorder_id = progress_recorder.get_untracked();
            if recorder_id == 0 {
                set_add_record_error.set("请选择记录人".to_string());
                return;
            }

            let args = ProgressRecordArgs {
                progressrecord: ProgressRecordSend {
                    work_id,
                    progress_detail,
                    recorder_id,
                    record_date: records_date.get_untracked(),
                },
            };

            let args_js = match serde_wasm_bindgen::to_value(&args) {
                Ok(v) => v,
                Err(e) => {
                    set_add_record_error.set(format!("参数序列化失败: {}", e));
                    return;
                }
            };

            match invoke("write_progress_record", args_js).await {
                Ok(result) => {
                    if let Some(msg) = result.as_string() {
                        set_add_record_error.set(msg.clone());
                        if msg.contains("SUCCESS") {
                            set_progress_content.set(String::new());
                            // 手动清空contenteditable div的内容
                            if let Some(window) = web_sys::window() {
                                if let Some(document) = window.document() {
                                    if let Some(div) = document.get_element_by_id("progress-content-div") {
                                        div.set_text_content(Some(""));
                                    }
                                }
                            }
                            get_progress_records();
                        }
                    }
                }
                Err(e) => {
                    set_fetch_works_error.set(
                        e.as_string().unwrap_or_else(|| format!("命令调用失败: {:?}", e))
                    );
                }
            }
        });
    };


    // 定义名称长度范围
    let min_length = 3;
    let max_length = 200;

     //处理复选框事件
     let check_change_dept = 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_depts.update(|items| {
                    if target.checked() {               //target.checked与prop:checked不一样, 是浏览器 DOM 的实时状态,用于事件处理
                        items.push(value);
                    } else {
                        items.retain(|&x| x != value);
                    }
                });
            };
        });
    };

    let update_string = move|ev:Event, content:String, set_string:WriteSignal<String>, set_error:WriteSignal<String>| {
        match event_target_value(&ev).parse::<String>(){
            Ok(name) => {
                //检查是否为空
                if name.is_empty() {
                    set_error.set(format!("{}不能为空!", content));
                    return;
                };
                // 检查长度是否在范围内
                if name.len() < min_length {
                    set_error.set(format!("{}长度不能少于 {} 个字符", content, min_length));
                } else if name.len() > max_length {
                    set_error.set(format!("{}长度不能大于 {} 个字符", content, max_length));
                }else{
                    set_string.set(name.to_string());
                    set_error.set(String::new());
                }
            }
            Err(_) => {
                set_error.set("请输入有效字符串!".to_string());
            }
        }
    };

    let get_department_db = move |ev: SubmitEvent| {
        ev.prevent_default();
        spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。
            let dept_js = match invoke_without_args("send_department_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_deptdb_msg.set(format!("获取部门数据失败: {:?}", e));
                    return;
                }
            };
            let dept_vec: Vec<Department> = match serde_wasm_bindgen::from_value(dept_js) {
                Ok(vec) => vec,
                Err(e) => {
                    log!("反序列化部门数据失败: {:?}", e);
                    set_deptdb_msg.set(format!("反序列化部门数据失败: {}", e));
                    return;
                }
            };

            // 动态生成包裹在 div 中的视图
            let div_views = view! {
                    <div>
                        {dept_vec.into_iter().map(|dept| {
                            let dept_id = dept.id;
                            view! {
                                <div style="margin:5px;width:1500px;">
                                    <input
                                        type="checkbox"
                                        name="items"
                                        value=dept_id.to_string()
                                        prop:checked=move || selected_depts.get().contains(&dept_id)     //Leptos 的状态绑定,用于确保界面最终与数据同步。
                                        on:change=check_change_dept      //用户操作 → 更新 target.checked → 触发事件check_change → 更新状态 → prop:checked 驱动视图更新。
                                    />
                                    <span>
                                        "部门ID: " {dept_id}
                                        ",部门名称: " {dept.name}
                                    </span>
                                </div>
                            }
                        }).collect_view()}
                    </div>
                }; // 关键的类型擦除;

            // 转换为 View 类型并设置
            //log!("视图类型: {:?}", std::any::type_name_of_val(&div_views));
            set_department_content.set(div_views); 
        });
    };

    let del_selected_items = move|ev:SubmitEvent, selected_items:ReadSignal<Vec<i64>>,
                            set_selected_items:WriteSignal<Vec<i64>>, cmd_invoke:String, set_error:WriteSignal<String>, refresh:Box<dyn Fn(SubmitEvent)>| {
        ev.prevent_default();
        spawn_local(async move {
            set_error.set(String::new());
            let args = SelectedItemArgs{
                selectedlist:selected_items.get_untracked(),
            };
            let args_js = serde_wasm_bindgen::to_value(&args).unwrap();
            let new_msg = match invoke(&cmd_invoke, args_js).await {
                Ok(val) => val.as_string().unwrap_or_else(|| "未知错误".to_string()),
                Err(e) => format!("调用命令失败: {:?}", e)
            };
            set_error.set(new_msg.clone());
            set_selected_items.set(Vec::<i64>::new());
            // 确保删除操作成功完成后再刷新
            if new_msg.contains("SUCCESS") {
                refresh(ev);
            }
        });
    };

    // 修改 write_dept_sql 中的调用逻辑
    let write_dept_sql = move |ev: SubmitEvent| {
        ev.prevent_default();           //类似javascript中的Event.preventDefault(),处理<input>字段非常有用
        spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。
            let dept_name = department_name.get_untracked();
            set_deptsubmit_error.set(String::new());
            // 检查长度是否在范围内
            if dept_name.len() < min_length {
                set_deptsubmit_error.set(format!("部门名称长度不能少于 {} 个字符", min_length));
                return;
            } 
            if dept_name.len() > max_length {
                set_deptsubmit_error.set(format!("部门名称长度不能大于 {} 个字符", max_length));
                return;
            }
            let args = DepartmentArgs{
                department:DepartmentSend { name: dept_name } ,
            };
            let args_js = serde_wasm_bindgen::to_value(&args).unwrap();   //参数序列化
            // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
            let result = match invoke("write_department_db", args_js).await {
                Ok(result) => result,
                Err(e) => {
                    let err_str = e.as_string().unwrap_or_else(|| format!("{:?}", e));
                    set_deptsubmit_error.set(err_str.clone());
                    //log!("调用后端命令失败: {}", err_str);
                    return;
                }
            };

            if let Some(msg) = result.as_string() {
                set_deptsubmit_error.set(msg.clone());
                if msg.contains("SUCCESS") {
                    get_department_db(ev.clone());
                }
            } else {
                let err_msg = format!("ERROR: 无法解析的返回格式: {:?}", result);
                set_deptsubmit_error.set(err_msg.clone());
                log!("{}", err_msg);
            }
        });
    };


     //处理复选框事件
     let check_change_type = 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_worktypes.update(|items| {
                    if target.checked() {               //target.checked与prop:checked不一样, 是浏览器 DOM 的实时状态,用于事件处理
                        items.push(value);
                    } else {
                        items.retain(|&x| x != value);
                    }
                });
            };
        });
    };



    let get_worktype_db = move |ev: SubmitEvent| {
        ev.prevent_default();
        spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。
            let type_js = match invoke_without_args("send_worktype_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_typedb_msg.set(format!("获取工作类型数据失败: {:?}", e));
                    return;
                }
            };
            let type_vec: Vec<Worktype> = match serde_wasm_bindgen::from_value(type_js) {
                Ok(vec) => vec,
                Err(e) => {
                    //log!("反序列化工作类型数据失败: {:?}", e);
                    set_typedb_msg.set(format!("反序列化工作类型数据失败: {}", e));
                    return;
                }
            };

            // 动态生成包裹在 div 中的视图
            let div_views = view! {
                    <div>
                        {type_vec.into_iter().map(|worktype| {
                            let type_id = worktype.id;
                            view! {
                                <div style="margin:5px;width:1500px;">
                                    <input
                                        type="checkbox"
                                        name="items"
                                        value=type_id.to_string()
                                        prop:checked=move || selected_worktypes.get().contains(&type_id)     //Leptos 的状态绑定,用于确保界面最终与数据同步。
                                        on:change=check_change_type      //用户操作 → 更新 target.checked → 触发事件check_change → 更新状态 → prop:checked 驱动视图更新。
                                    />
                                    <span>
                                        "类型ID: " {type_id}
                                        ",工作类型: " {worktype.name}
                                    </span>
                                </div>
                            }
                        }).collect_view()}
                    </div>
                }; // 关键的类型擦除;

            // 转换为 View 类型并设置
            //log!("视图类型: {:?}", std::any::type_name_of_val(&div_views));
            set_worktype_content.set(div_views); 
        });
    };


    let write_type_sql = move |ev: SubmitEvent| {
        ev.prevent_default();           //类似javascript中的Event.preventDefault(),处理<input>字段非常有用
        spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。
            let type_name = work_type.get_untracked();
            set_typesubmit_error.set(String::new());
            // 检查长度是否在范围内
            if type_name.len() < min_length {
                set_typesubmit_error.set(format!("工作类型长度不能少于 {} 个字符", min_length));
                return;
            } 
            if type_name.len() > max_length {
                set_typesubmit_error.set(format!("工作类型长度不能大于 {} 个字符", max_length));
                return;
            }
            let args = WorktypeArgs{
                worktype:WorktypeSend { name: type_name } ,
            };
            let args_js = serde_wasm_bindgen::to_value(&args).unwrap();   //参数序列化
            // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
            let result = match invoke("write_worktype_db", args_js).await {
                Ok(result) => result,
                Err(e) => {
                    let err_str = e.as_string().unwrap_or_else(|| format!("{:?}", e));
                    set_typesubmit_error.set(err_str.clone());
                    //log!("调用后端命令失败: {}", err_str);
                    return;
                }
            };

            if let Some(msg) = result.as_string() {
                set_typesubmit_error.set(msg.clone());
                if msg.contains("SUCCESS") {
                    get_worktype_db(ev.clone());
                }
            } else {
                let err_msg = format!("ERROR: 无法解析的返回格式: {:?}", result);
                set_typesubmit_error.set(err_msg.clone());
                log!("{}", err_msg);
            }
        });
    };



    let get_department_list = move || {
        spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。
            let dept_js = match invoke_without_args("send_department_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_personnel_deptid_error.set(format!("获取部门数据失败: {:?}", e));
                    return;
                }
            };
            let dept_vec: Vec<Department> = match serde_wasm_bindgen::from_value(dept_js) {
                Ok(vec) => vec,
                Err(e) => {
                    log!("反序列化部门数据失败: {:?}", e);
                    set_personnel_deptid_error.set(format!("反序列化部门数据失败: {}", e));
                    return;
                }
            };
            if dept_vec.clone().len() == 0 {
                set_personnel_deptid_error.set(format!("部门数据为空,请先录入工作部门"));
                return;
            }
            else{
                set_department_list.set(dept_vec);
            }
        });
    };


    let get_worktype_list = move || {
        spawn_local(async move {
            let type_js = match invoke_without_args("send_worktype_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_typeid_error.set(format!("获取工作类型数据失败: {:?}", e));
                    return;
                }
            };
            let type_vec: Vec<Worktype> = match serde_wasm_bindgen::from_value(type_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_typeid_error.set(format!("反序列化工作类型数据失败: {}", e));
                    return;
                }
            };
            if type_vec.clone().len() == 0 {
                set_typeid_error.set(format!("工作类型数据为空,请先录入工作类型"));
                return;
            }
            else{
                set_worktype_list.set(type_vec);
            }
        });
    };


    let check_change_work = move |ev: leptos::ev::Event| {
        spawn_local(async move {
            let target = event_target::<HtmlInputElement>(&ev);
            let value_str = target.value();
            if let Ok(value) = value_str.parse::<i64>() {
                set_selected_works.update(|items| {
                    if target.checked() {
                        items.push(value);
                    } else {
                        items.retain(|&x| x != value);
                    }
                });
            };
        });
    };

    let get_work_db = move |ev: SubmitEvent| {
        ev.prevent_default();
        spawn_local(async move {
            let work_js = match invoke_without_args("send_work_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_workdb_msg.set(format!("获取工作数据失败: {:?}", e));
                    return;
                }
            };
            let work_vec: Vec<WorkAll> = match serde_wasm_bindgen::from_value(work_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_workdb_msg.set(format!("反序列化工作数据失败: {}", e));
                    return;
                }
            };

            // 获取工作类型列表用于显示
            let type_js = match invoke_without_args("send_worktype_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_workdb_msg.set(format!("获取工作类型数据失败: {:?}", e));
                    return;
                }
            };
            let type_vec: Vec<Worktype> = match serde_wasm_bindgen::from_value(type_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_workdb_msg.set(format!("反序列化工作类型数据失败: {}", e));
                    return;
                }
            };
            set_worktype_list.set(type_vec.clone());

            if type_vec.is_empty() {
                set_typedb_msg.set(format!("获取的工作类型列表为空!"));
                return;
            }

            // 获取部门列表用于显示部门名称
            let dept_js = match invoke_without_args("send_department_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_workdb_msg.set(format!("获取部门数据失败: {:?}", e));
                    return;
                }
            };
            let dept_vec: Vec<Department> = match serde_wasm_bindgen::from_value(dept_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_workdb_msg.set(format!("反序列化部门数据失败: {}", e));
                    return;
                }
            };

            // 获取人员列表用于显示人员名称
            let personnel_js = match invoke_without_args("send_personnel_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_workdb_msg.set(format!("获取人员数据失败: {:?}", e));
                    return;
                }
            };
            let personnel_vec: Vec<Personnel> = match serde_wasm_bindgen::from_value(personnel_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_workdb_msg.set(format!("反序列化人员数据失败: {}", e));
                    return;
                }
            };

            // 创建类型名称信号
            let type_name_signal = move |type_id: i64| {
                type_vec.iter()
                    .find(|t| t.id == type_id)
                    .map(|t| t.name.clone())
                    .unwrap_or_else(|| "未知类型".to_string())
            };

            // 创建部门名称信号
            let dept_name_signal = move |dept_id: i64| {
                dept_vec.iter()
                    .find(|d| d.id == dept_id)
                    .map(|d| d.name.clone())
                    .unwrap_or_else(|| "未知部门".to_string())
            };

            // 创建人员名称信号
            let personnel_name_signal = move |person_id: i64| {
                personnel_vec.iter()
                    .find(|p| p.id == person_id)
                    .map(|p| p.full_name.clone())
                    .unwrap_or_else(|| "未知人员".to_string())
            };

            // 动态生成工作列表视图
            let div_views = view! {
                <div>
                    {work_vec.into_iter().map(|work| {
                        let work_id = work.id;
                        let work_type = type_name_signal(work.work_type_id);
                        
                        // 获取关联部门名称
                        let dept_names: Vec<String> = work.work_departments.iter()
                            .map(|wd| dept_name_signal(wd.department_id))
                            .collect();
                            
                        // 获取关联人员名称
                        let personnel_names: Vec<String> = work.work_personnels.iter()
                            .map(|wp| {
                                let name = personnel_name_signal(wp.personnel_id);
                                if wp.is_main_responsible == 1 {
                                    format!("{} (负责人)", name)
                                } else {
                                    name
                                }
                            })
                            .collect();

                        view! {
                            <div style="margin:5px;width:1500px;border:1px solid #ccc;padding:5px;">
                                <div>
                                    <input
                                        type="checkbox"
                                        name="items"
                                        value=work_id.to_string()
                                        prop:checked=move || selected_works.get().contains(&work_id)
                                        on:change=check_change_work
                                    />
                                    <span>
                                        "工作ID: " {work_id}
                                        ",标题: " {work.subject}
                                        ",类型: " {work_type}
                                        ",状态: " {if work.is_completed == 1 { "已完成" } else { "未完成" }}
                                        ",开始时间: " {work.start_date}
                                    </span>
                                </div>
                                <div style="margin-left:20px;margin-top:5px;">
                                    <div>"责任部门: " {dept_names.join(", ")}</div>
                                    <div>"参与人员: " {personnel_names.join(", ")}</div>
                                </div>
                            </div>
                        }
                    }).collect_view()}
                </div>
            };

            set_workview_content.set(div_views);
        });
    };

    let write_work_sql = move |ev: SubmitEvent| {
        ev.prevent_default();
        spawn_local(async move {
            let subject = work_subject.get_untracked();
            let content = work_content.get_untracked();
            let start_date = start_date.get_untracked();
            let work_type_id = worktype_id.get_untracked();
            let is_completed = work_state.get_untracked();

            set_worksubmit_error.set(String::new());

            // 验证输入
            if subject.len() < 3 {
                set_subject_error.set("工作标题长度不能少于3个字符".to_string());
                return;
            }
            if content.len() < 10 {
                set_workcontent_error.set("工作内容长度不能少于10个字符".to_string());
                return;
            }
            if work_type_id == 0 {
                set_worksubmit_error.set("请选择工作类型".to_string());
                return;
            }

            // 获取工作类型列表用于显示
            let type_js = match invoke_without_args("send_worktype_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_worksubmit_error.set(format!("获取工作类型数据失败: {:?}", e));
                    return;
                }
            };
            let type_vec: Vec<Worktype> = match serde_wasm_bindgen::from_value(type_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_worksubmit_error.set(format!("反序列化工作类型数据失败: {}", e));
                    return;
                }
            };
            set_worktype_list.set(type_vec.clone());

            if type_vec.is_empty() {
                set_worksubmit_error.set(format!("获取的工作类型列表为空!"));
                return;
            }

 
            if !type_vec.iter().any(|worktype| worktype.id == work_type_id) {
                set_worksubmit_error.set(format!("工作类型ID {} 不存在于工作类型列表中!",work_type_id));
                return;
            }

            if work_depts.get_untracked().len() == 0 {
                set_worksubmit_error.set(format!("工作责任部门列表为空,请选择!"));
                return;
            }

            if work_personnel.get_untracked().len() == 0 {
                set_worksubmit_error.set(format!("工作参与人员列表为空,请选择!"));
                return;
            }

            if work_responsible.get_untracked().len() == 0 {
                set_worksubmit_error.set(format!("该工作负责人为空,请选择!"));
                return;
            }


            let args = WorkArgs {
                work: WorkSend {
                    subject,                //正常为:subject:subject, key和value一致时,只写一个即可
                    work_content: content,
                    start_date,
                    work_type_id,
                    is_completed,
                },
            };
            //写入工作数据库,返回工作ID
            set_work_id.set(0);
            // 序列化参数
            let args_js = match serde_wasm_bindgen::to_value(&args) {
                Ok(v) => v,
                Err(e) => {
                    set_worksubmit_error.set(format!("参数序列化失败: {}", e));
                    return;
                }
            };

            // 调用后端命令
            let result = match invoke("write_work_db", args_js).await {
                Ok(v) => v,
                Err(e) => {
                    set_worksubmit_error.set(
                        e.as_string().unwrap_or_else(|| format!("命令调用失败: {:?}", e))
                    );
                    return;
                }
            };

            // 解析返回结果
            // 先解析为JsValue,然后手动转换为i64
            match result.as_f64() {
                Some(id) => {
                    set_work_id.set(id as i64);
                }
                None => {
                    set_worksubmit_error.set("无法解析工作ID".to_string());
                    return;
                }
            }
            
            log!("成功录入的工作任务的ID为:{}", work_id.get_untracked());
            
            // 确保work_id有效
            let work_id_val = work_id.get_untracked();
            if work_id_val == 0 {
                set_worksubmit_error.set("ERROR: 无效的工作ID".to_string());
                return;
            }

            // 创建工作责任部门参数
            let work_depts_args = WorkDeptsArgs {
                workdepts: work_depts.get_untracked().into_iter().map(|dept_id| {
                    WorkDeptsSend {
                        work_id: work_id_val,
                        department_id: dept_id
                    }
                }).collect()
            };

            // 创建工作参与人员参数
            let work_person_args = WorkPersonArgs {
                workpersonnels: work_personnel.get_untracked().into_iter().map(|person| {
                    WorkPersonSend {
                        work_id: work_id_val,
                        personnel_id: person.id,
                        is_main_responsible: if work_responsible.get_untracked().iter().any(|p| p.id == person.id) {
                            1
                        } else {
                            0
                        }
                    }
                }).collect()
            };

            // 调用写入工作责任部门的命令
            let work_depts_js = serde_wasm_bindgen::to_value(&work_depts_args).unwrap();
            match invoke("write_work_depts_db", work_depts_js).await {
                Ok(result) => {
                    if let Some(msg) = result.as_string() {
                        if !msg.contains("SUCCESS") {
                            set_worksubmit_error.set(msg);
                            return;
                        }
                    }
                }
                Err(e) => {
                    let err_str = e.as_string().unwrap_or_else(|| format!("{:?}", e));
                    set_worksubmit_error.set(err_str);
                    return;
                }
            };

            // 调用写入工作参与人员的命令
            let work_person_js = serde_wasm_bindgen::to_value(&work_person_args).unwrap();
            match invoke("write_work_personnel_db", work_person_js).await {
                Ok(result) => {
                    if let Some(msg) = result.as_string() {
                        if !msg.contains("SUCCESS") {
                            set_worksubmit_error.set(msg);
                            //log!("写入工作参与人员出错:{}",msg);
                            return;
                        }
                    }
                }
                Err(e) => {
                    let err_str = e.as_string().unwrap_or_else(|| format!("{:?}", e));
                    set_worksubmit_error.set(err_str);
                    //log!("写入工作参与人员出错:{}",err_str);
                    return;
                }
            };
        get_work_db(ev);
        });
    };

    let get_personnel_db = move |ev: SubmitEvent| {
        ev.prevent_default();
        spawn_local(async move {
            // 先获取部门列表
            let dept_js = match invoke_without_args("send_department_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_personneldb_msg.set(format!("获取部门数据失败: {:?}", e));
                    return;
                }
            };
            let dept_vec: Vec<Department> = match serde_wasm_bindgen::from_value(dept_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_personneldb_msg.set(format!("反序列化部门数据失败: {}", e));
                    return;
                }
            };
            set_department_list.set(dept_vec.clone());

            if dept_vec.is_empty() {
                set_personneldb_msg.set(format!("获取的部门列表为空!"));
                return;
            }

            // 然后获取人员列表
            let personnel_js = match invoke_without_args("send_personnel_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_personneldb_msg.set(format!("获取人员数据失败: {:?}", e));
                    return;
                }
            };
            let personnel_vec: Vec<Personnel> = match serde_wasm_bindgen::from_value(personnel_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_personneldb_msg.set(format!("反序列化人员数据失败: {}", e));
                    return;
                }
            };

            // 创建部门名称信号
            let dept_name_signal = move |dept_id: i64| {
                department_list.with_untracked(|depts| {
                    depts.iter()
                        .find(|d| d.id == dept_id)
                        .map(|d| d.name.clone())
                        .unwrap_or_else(|| {
                            log!("找不到部门ID: {}", dept_id);
                            "未知部门".to_string()
                        })
                })
            };

            // 动态生成包裹在 div 中的视图
            let div_views = view! {
                    <div>
                        {personnel_vec.into_iter().map(|personnel| {
                            let personnel_id = personnel.id;
                            let dept_name = dept_name_signal(personnel.department_id);
                            view! {
                                <div style="margin:5px;width:1500px;">
                                    <input
                                        type="checkbox"
                                        name="items"
                                        value=personnel_id.to_string()
                                        prop:checked=move || selected_personnels.get().contains(&personnel_id)
                                        on:change=move |ev| {
                                            let target = event_target::<HtmlInputElement>(&ev);
                                            if let Ok(value) = target.value().parse::<i64>() {
                                                set_selected_personnels.update(|items| {
                                                    if target.checked() {
                                                        items.push(value);
                                                    } else {
                                                        items.retain(|&x| x != value);
                                                    }
                                                });
                                            }
                                        }
                                    />
                                    <span>
                                        "员工ID: " {personnel_id}
                                        ",员工姓名: " {personnel.full_name}
                                        ", 所属部门:" {dept_name}
                                    </span>
                                </div>
                            }
                        }).collect_view()}
                    </div>
                };

            // 转换为 View 类型并设置
            //log!("视图类型: {:?}", std::any::type_name_of_val(&div_views));
            set_personnel_content.set(div_views); 
        });
    };

    let write_personnel_sql = move |ev: SubmitEvent| {
        ev.prevent_default();           //类似javascript中的Event.preventDefault(),处理<input>字段非常有用
        spawn_local(async move {                //使用Leptos的spawn_local创建一个本地线程(local_thread)Future, 提供一个异步move闭包。
            let personnel_name = personnel_name.get_untracked();
            set_personnelsubmit_error.set(String::new());
            // 检查长度是否在范围内
            if personnel_name.len() < 2 {
                set_personnelsubmit_error.set(format!("员工姓名长度不能少于两个字符"));
                return;
            } 
            if personnel_name.len() > 50 {
                set_personnelsubmit_error.set(format!("员工姓名长度不能大于50个字符"));
                return;
            }
            if personnel_deptid.get_untracked() == 0 {
                set_personnelsubmit_error.set(format!("未选择所属部门!"));
                return;
            }

            //确认部门ID是否存在
            // 先获取部门列表
            let dept_js = match invoke_without_args("send_department_db").await {
                Ok(val) => val,
                Err(e) => {
                    set_personnelsubmit_error.set(format!("获取部门数据失败: {:?}", e));
                    return;
                }
            };
            let dept_vec: Vec<Department> = match serde_wasm_bindgen::from_value(dept_js) {
                Ok(vec) => vec,
                Err(e) => {
                    set_personnelsubmit_error.set(format!("反序列化部门数据失败: {}", e));
                    return;
                }
            };
            set_department_list.set(dept_vec.clone());

            if dept_vec.is_empty() {
                set_personnelsubmit_error.set(format!("获取的部门列表为空!"));
                return;
            }

            let dept_id = personnel_deptid.get_untracked();
            
            if !dept_vec.iter().any(|dept| dept.id == dept_id) {
                set_personnelsubmit_error.set(format!("部门ID {} 不存在于部门列表中!", dept_id));
                return;
            }

            let args = PersonnelArgs{
                personnel:PersonnelSend { full_name: personnel_name, department_id: personnel_deptid.get_untracked()} ,
            };
            let args_js = serde_wasm_bindgen::to_value(&args).unwrap();   //参数序列化
            // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
            let result = match invoke("write_personnel_db", args_js).await {
                Ok(result) => result,
                Err(e) => {
                    let err_str = e.as_string().unwrap_or_else(|| format!("{:?}", e));
                    set_personnelsubmit_error.set(err_str);
                    return;
                }
            };

            if let Some(msg) = result.as_string() {
                set_personnelsubmit_error.set(msg.clone());
                if msg.contains("SUCCESS") {
                    get_personnel_db(ev);
                }
            } else {
                let err_msg = format!("ERROR: 无法解析的返回格式: {:?}", result);
                set_personnelsubmit_error.set(err_msg);
            }
        });
    };

    view! {                                              //view!宏作为App()函数的返回值返回IntoView类型
        <main class="container">
        <h1>"---------※工作进度管理系统※---------"</h1>
        <div class="pdtinput">
            <div class="left"  style=";margin-top:10px;margin-bottom: 10px;">"工作标题:"</div>
            <div class="right">
                <select style="width:450px;font-size: 1em;"
                    on:focus=move |_| {
                        spawn_local(async move {
                            match invoke_without_args("send_work_list").await {
                                Ok(work_js) => {
                                    let works = serde_wasm_bindgen::from_value::<Vec<Work>>(work_js)
                                        .unwrap_or_else(|e| {
                                            set_fetch_works_error.set(format!("工作列表反序列化失败: {}", e));
                                            vec![]
                                        });
                                    set_fetch_work_list.set(works);
                                }
                                Err(e) => {
                                    set_fetch_works_error.set(format!("获取工作列表失败: {:?}", e));
                                }
                            }
                        });
                    }
                    on:change=move |ev| {
                        get_worktype_list();
                        let value = event_target_value(&ev);
                        if let Ok(id) = value.parse::<i64>() {
                            set_fetch_work_id.set(id);
                            set_fetch_works_error.set(String::new());
                            spawn_local(async move {
                                let args = FetchWorkArgs{workid: fetch_work_id.get_untracked()};
                                let args_js = match serde_wasm_bindgen::to_value(&args) {
                                    Ok(v) => v,
                                    Err(e) => {
                                        set_fetch_works_error.set(format!("参数序列化失败: {}", e));
                                        return;
                                    }
                                };
                                
                                match invoke("send_one_work", args_js).await {
                                    Ok(result) => {
                                        match serde_wasm_bindgen::from_value::<WorkBack>(result) {
                                            Ok(work) => {
                                                set_fetch_work_startdate.set(work.start_date);
                                                set_fetch_work_content.set(work.work_content);
                                                set_fetch_work_state.set(work.is_completed);
                                                set_fetch_worktype_id.set(work.work_type_id);
                                                set_fetch_work_depts.set(work.work_departments);
                                                set_fetch_work_personnels.set(work.work_personnels);
                                                set_fetch_work_responsile.set(work.responsile_person);
                                            }
                                            Err(e) => {
                                                set_fetch_works_error.set(format!("工作数据反序列化失败: {}", e));
                                            }
                                        }
                                    }
                                    Err(e) => {
                                        set_fetch_works_error.set(format!("获取工作详情失败: {:?}", e));
                                    }
                                }
                            });
                        }
                        get_progress_records();
                    }
                >
                    <option value="" disabled selected>"请选择工作"</option>
                    {move || {
                        fetch_work_list.get()
                            .iter()
                            .map(|work| view! {
                                <option value={work.id.to_string()}>
                                    {work.subject.clone()}
                                </option>
                            })
                            .collect_view()
                    }}
                </select>
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"启动时间:"</div>
            <div class="right">
                {move || fetch_work_startdate.get()}
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"工作主要内容:"</div>
            <div class="right">
                {move || fetch_work_content.get()}
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"工作类型:"</div>
            <div class="right">
                {move || {
                   let worktype_id = fetch_worktype_id.get();
                   worktype_list.with(|worktypes| {
                       worktypes.iter()
                           .find(|wt| wt.id == worktype_id)
                           .map(|wt| wt.name.clone())
                           .unwrap_or_default()
                   })
               }}
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"工作状态:"</div>
            <div class="right">
                <Show when=move || fetch_work_id.get() != 0>
                    {move || if fetch_work_state.get() == 1 { "已完成" } else { "进行中" }}
                    <button
                        style="margin:0px 15px 0px 15px;height:35px;vertical-align:middle;padding:5px 10px;"
                        on:click=move |_| {
                            spawn_local(async move {
                                let args = FetchWorkArgs{workid: fetch_work_id.get_untracked()};
                                let args_js = match serde_wasm_bindgen::to_value(&args) {
                                    Ok(v) => v,
                                    Err(e) => {
                                        set_fetch_works_error.set(format!("序列化参数失败: {}", e));
                                        return;
                                    }
                                };
                                
                                match invoke("change_work_state", args_js).await {
                                    Ok(result) => {
                                        if let Some(status) = result.as_f64() {
                                            if status == 1.0 {
                                                set_fetch_work_state.set(1);
                                            } else {
                                                set_fetch_work_state.set(0);
                                            }
                                        }
                                    }
                                    Err(e) => {
                                        set_fetch_works_error.set(format!("调用change_work_state失败: {:?}", e));
                                    }
                                }
                            });
                        }
                    >
                        "改变工作状态"
                    </button>
                </Show>
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"参与部门:"</div>
            <div class="right">
                {move || {
                    fetch_work_depts.get()
                        .iter()
                        .map(|dept| view! {
                            <div style="margin:5px;border:1px solid #ccc;padding:5px;">
                                {dept.name.clone()}
                            </div>
                        })
                        .collect_view()
                }}
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"负责人:"</div>
            <div class="right" style="display: flex; flex-wrap: wrap; gap: 10px;">
                {move || {
                   fetch_work_responsile.get()
                       .iter()
                       .map(|p| view! {
                           <div style="flex: 1 0 43%;margin:5px;background-color:rgb(235, 89, 128);">
                               {p.full_name.clone()}
                           </div>
                       })
                       .collect_view()
                }}
            </div>
        </div>
    
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"参与人员:"</div>
            <div class="right">
                {move || {
                    let responsile_ids: Vec<i64> = fetch_work_responsile.get()
                        .iter()
                        .map(|p| p.id)
                        .collect();
                    view! {
                        <div style="display: flex; flex-wrap: wrap; gap: 10px;">
                            {fetch_work_personnels.get()
                                .iter()
                                .filter(|p| !responsile_ids.contains(&p.id))
                                .map(|p| view! {
                                    <div style="flex: 1 0 30%;background-color:rgb(13, 200, 225);">
                                        {p.full_name.clone()}
                                    </div>
                                })
                                .collect_view()}
                        </div>
                    }
                }}
            </div>
        </div>
                
        <div class="errorshow">
            <div class="left"></div>
            <div class="right red">
                {fetch_works_error}
            </div>
        </div>
        
    <Show when=move || (fetch_work_id.get() != 0 && fetch_work_state.get() == 0)>
    <h2>"---------※添加新的工作进度记录※---------"</h2>
    <form  id="records-form" on:submit=write_progress_records>
        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"新的工作进度:"</div>
            <div class="right">
                <div id="progress-content-div"
                contenteditable="true"
                style="width:420px; min-height:100px; border:1px solid #ccc; padding:5px;"
                on:input=move |ev| {
                    let target = event_target::<web_sys::HtmlDivElement>(&ev);
                    set_progress_content.set(target.inner_text());
                }
                ></div>
            </div>
        </div>

        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"添加时间:"</div>
            <div class="right">
                <input
                    type="datetime-local"
                    value=move || records_date.get()
                    on:input=move |ev| {
                        let value = event_target_value(&ev);
                        set_records_date.set(value);
                    }
                />
            </div>
        </div>

        <div class="pdtinput" style="background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style=";margin-top:10px;margin-bottom: 10px;">"记录人:"</div>
            <div class="right">
                <select
                    style="width:350px;font-size:1em"
                    on:change=move |ev| {
                        let value = event_target_value(&ev);
                        if let Ok(id) = value.parse::<i64>() {
                            set_progress_recorder.set(id);
                        }
                    }
                >
                    <option value="" disabled selected>"请选择记录人"</option>
                    {move || fetch_work_personnels.get().into_iter().map(|person| {
                        view! {
                            <option value={person.id.to_string()}>
                                {person.full_name.clone()}
                            </option>
                        }
                    }).collect_view()}
                </select>
            </div>
        </div>
        <div class="errorshow">
        <div class="left"></div>
        <div class="right red"> 
            {add_record_error}
        </div>
    </div>
        <button style="width:300px;" type="submit" id="dept-button">"新建工作进度记录"</button>
    </form>
    </Show>
        

        <div class="pdtinput" style="width:800px;background-color:rgb(182, 239, 245);margin-top:10px;margin-bottom: 10px;">
            <div class="left" style="margin-top:10px;margin-bottom: 10px;background-color:rgb(77, 192, 204);">"进度记录历史:"</div>
            <div class="right" style="width:670px;">
                {move || {
                    let mut records = fetch_progress_records.get();
                    // 按记录日期排序
                    // 按记录日期降序排序(最新记录在前)
                    // 使用b.cmp(&a)实现降序,a.cmp(&b)则是升序
                    records.sort_by(|a, b| b.record_date.cmp(&a.record_date));
                    view! {
                        <div style="margin-top:10px;width:660px;">
                            {records.into_iter().map(|record| {
                                view! {
                                    <div style="display:flex; width:660px; margin:5px;background-color:rgb(77, 192, 204);">
                                        <div style="flex:1; border:1px solid #ccc; padding:5px;">
                                            <div style="font-weight:bold;">{record.record_date}</div>
                                            <div>{record.progress_detail}</div>
                                            <div style="text-align:right;font-style:italic;">
                                                {move || {
                                                    let person = fetch_work_personnels.get()
                                                        .iter()
                                                        .find(|p| p.id == record.recorder_id)
                                                        .map(|p| p.full_name.clone())
                                                        .unwrap_or_else(|| format!("未知人员(ID: {})", record.recorder_id));
                                                    format!("记录人: {}", person)
                                                }}
                                            </div>
                                        </div>
                                        <div style="width:100px; display:flex; align-items:center; justify-content:center;">
                                            <button
                                                style="width:80px;"
                                                on:click=move |_| {
                                                    spawn_local(async move {
                                                        let args = SelectedRecord {
                                                            selectedrecord: record.id,
                                                        };
                                                        let args_js = serde_wasm_bindgen::to_value(&args).unwrap();
                                                        match invoke("del_progress_record", args_js).await {
                                                            Ok(result) => {
                                                                if let Some(msg) = result.as_string() {
                                                                    if msg.contains("SUCCESS") {
                                                                        get_progress_records();
                                                                    }
                                                                }
                                                            }
                                                            Err(e) => {
                                                                set_fetch_works_error.set(
                                                                    e.as_string().unwrap_or_else(|| format!("删除记录失败: {:?}", e))
                                                                );
                                                            }
                                                        }
                                                    });
                                                }
                                            >"删除"</button>
                                        </div>
                                    </div>
                                }
                            }).collect_view()}
                        </div>
                    }
                }}
            </div>
        </div>
        <p></p>

        <h1>"---------※工作管理系统※---------"</h1>
        <form  id="work-form" on:submit=write_work_sql>
            <div class="pdtinput">
                <div class="left"> "工作标题:"</div>
                <div class="right">
                    <input style="width:420px" type="text" minlength="3" maxlength="150" placeholder="请输入工作标题..."
                        value = move || work_subject.get()
                        on:input=move|ev|update_string(ev, "工作标题".to_string(), set_work_subject, set_subject_error) />
                </div>
            </div>
            <div class="errorshow">
                <div class="left"></div>
                <div class="right red">
                    {subjet_error}
                </div>
            </div>
    
            <div class="pdtinput">
                <div class="left"> "工作内容:"</div>
                <div class="right">
                    <div
                        contenteditable="true"
                        style="width:420px; min-height:100px; border:1px solid #ccc; padding:5px;"
                        on:input=move |ev| {
                            let target = event_target::<web_sys::HtmlDivElement>(&ev);
                            set_work_content.set(target.inner_text());
                        }
                    ></div>
                </div>
            </div>
            <div class="errorshow">
                <div class="left"></div>
                <div class="right red">
                    {workcontent_error}
                </div>
            </div>
    
            <div class="pdtinput">
                <div class="left"> "开始时间:"</div>
                <div class="right">
                    <input
                        type="datetime-local"
                        value=move || start_date.get()
                        on:input=move |ev| {
                            let value = event_target_value(&ev);
                            set_start_date.set(value);
                        }
                    />
                </div>
            </div>
    
            <div class="pdtinput">
                <div class="left" style="margin-top:7px;margin-bottom: 7px;"> "工作状态:"</div>
                <div class="right">
                    <input
                        type="checkbox"
                        prop:checked=move || work_state.get() == 1
                        on:change=move |ev| {
                            let target = event_target::<HtmlInputElement>(&ev);
                            set_work_state.set(if target.checked() { 1 } else { 0 });
                        }
                    />
                    <span>"已完成"</span>
                </div>
            </div>
    
            <div class="pdtinput">
                <div class="left" style="margin-top:7px;margin-bottom: 7px;"> "工作类型:"</div>
                <div class="right">
                    <select style="width:350px;margin-top:10px;margin-bottom: 10px;"
                        on:focus=move |_| {
                            get_worktype_list();
                        }
                        on:change=move |ev| {
                            let value = event_target_value(&ev);
                            match value.parse::<i64>() {
                                Ok(id) => {
                                    set_worktype_id.set(id);
                                    set_typeid_error.set(String::new());
                                }
                                Err(_) => {
                                    set_typeid_error.set(format!("请重新选择工作类型!"));
                                }
                            }
                        }
                    >
                        <option value="" disabled selected>"请选择工作类型"</option>
                        {move || worktype_list.get().into_iter().map(|worktype| {
                            view! {
                                <option value={worktype.id.to_string()}>
                                    {worktype.name}
                                </option>
                            }
                        }).collect_view()}
                    </select>
                </div>
            </div>
            <div class="errorshow">
                <div class="left"></div>
                <div class="right red">
                    {typeid_error}
                </div>
            </div>
    
    
            // 责任人员备选目录
            <div class="pdtinput">
                <div class="left">"工作部门:"</div>
                <div class="right">
                    <select id="dept-select" style="width:350px;margin-top:10px;margin-bottom: 10px;"
                        on:focus=move |_| {
                            get_department_list();
                        }
                        on:change=move |ev| {
                            set_dept_personnel_error.set(String::new());
                            let select = event_target::<HtmlSelectElement>(&ev);
                            if let Some(selected_value) = select.value().parse::<i64>().ok() {
                                spawn_local(async move {
                                    let args = PersonnelDeptArgs{
                                        departmentid: selected_value
                                    };
                                    let args_js = serde_wasm_bindgen::to_value(&args).unwrap();
                                    let result = invoke("get_personnel_by_department", args_js).await;
                                    match result {
                                        Ok(val) => {
                                            let personnel: Vec<Personnel> = serde_wasm_bindgen::from_value(val).unwrap();
                                            set_personnel_list.set(personnel);
                                        }
                                        Err(e) => {
                                            set_dept_personnel_error.set(format!("获取人员失败: {:?}", e));
                                        }
                                    }
                                });
                            }
                        }
                    >
                        <option value="" disabled selected>"请选择责任部门"</option>
                        {move || department_list.get().into_iter().map(|dept| {
                            view! {
                                <option value={dept.id.to_string()}>
                                    {dept.name}
                                </option>
                            }
                        }).collect_view()}
                    </select>
                </div>
            </div>
    
            <div class="errorshow">
                <div class="left"></div>
                <div class="right red">
                    {dept_personnel_error}
                </div>
            </div>
    
            <div class="pdtinput">
                <div class="left" style="margin-top:10px;margin-bottom: 10px;">"部门人员:"</div>
                <div class="right">
                    <div style="display: flex; flex-wrap: wrap; gap: 5px;">
                        {move || personnel_list.get().into_iter().map(|person| {
                            view! {
                                <div style="flex: 1 0 30%; min-width: 30px; margin: 5px;">
                                    <input
                                        type="checkbox"
                                        value={person.id.to_string()}
                                        prop:checked=move || work_personnel.get().iter().any(|p| p.id == person.id)
                                        on:change=move |ev| {
                                            let target = event_target::<HtmlInputElement>(&ev);
                                            if let Some(person) = personnel_list.get_untracked().iter().find(|p| p.id.to_string() == target.value()).cloned() {
                                                    if target.checked() {
                                                        set_work_personnel.update(|ids| ids.push(person.clone()));
                                                        // 添加部门ID到work_depts
                                                        set_work_depts.update(|depts| {
                                                            if !depts.contains(&person.department_id) {
                                                                depts.push(person.department_id);
                                                            }
                                                        });
                                                    } else {
                                                        set_work_personnel.update(|ids| ids.retain(|p| p.id != person.id));
                                                        // 检查是否需要从work_depts中移除部门ID
                                                        set_work_depts.update(|depts| {
                                                            let work_personnel = work_personnel.get_untracked();
                                                            if !work_personnel.iter().any(|p| p.department_id == person.department_id) {
                                                                depts.retain(|&did| did != person.department_id);
                                                            }
                                                        });
                                                    }
                                                }
                                            }
                                    />
                                    {person.full_name}
                                </div>
                            }
                            }).collect_view()
                        }
                    </div>
                </div>
            </div>
    
            <div class="pdtinput" style="background-color:rgb(182, 239, 245);">
                <div class="left" style="margin-top:10px;margin-bottom:10px;">"责任部门:"</div>
                <div class="right">
                    {move || {
                        // 获取所有涉及的部门
                        let departments = work_depts.get().clone();
    
                        view! {
                            // 部门显示区域
                            <div style="margin-bottom: 10px;">
                                {departments.into_iter().map(move |dept| {
                                    let dept_id = dept;
                                    let dept_name = department_list.get_untracked()
                                        .iter()
                                        .find(|d| d.id == dept_id)
                                        .map(|d| d.name.clone())
                                        .unwrap_or_else(|| "未知部门".to_string());
                                    view! {
                                        <div style="margin:5px;border:1px solid #ccc;padding:5px;;background-color:rgb(225, 168, 13)">
                                            <span>{dept_name}</span>
                                        </div>
                                    }
                                }).collect_view()}
                            </div>
                        }}
                    }
                </div>
            </div>
            <div class="pdtinput"  style="background-color:rgb(182, 239, 245);">
                <div class="left">
                "项目参与人员:"<br/>
                "(请勾选负责人)"
                </div>
                <div class="right">
                    // 人员显示区域
                    {move || {
                        // 获取所有涉及的部门
                        view! {
                            <div style="display: flex; flex-wrap: wrap; gap: 5px;">
                                {move || {
                                    {move || {
                                        let personnel = work_personnel.get().clone();
                                        personnel.into_iter().map(|person| {
                                            let person_rc = Rc::new(person);
                                            let person_id = person_rc.id;
                                            let full_name = person_rc.full_name.clone();
                                            let person_clone = person_rc.clone();
                                            view! {
                                                <div style="flex: 1 0 40%; min-width:40px;margin:5px;border:1px solid #ccc;padding:5px;background-color:rgb(13, 200, 225);">
                                                    {full_name}
                                                    <input
                                                        type="checkbox"
                                                        prop:checked=move || work_responsible.with(|r| r.iter().any(|p| p.id == person_id))
                                                        on:change=move |ev| {
                                                            let target = event_target::<HtmlInputElement>(&ev);
                                                            if target.checked() {
                                                                set_work_responsible.update(|personnel| personnel.push((*person_clone).clone()));
                                                            } else {
                                                                set_work_responsible.update(|personnel| personnel.retain(|p| p.id != person_id));
                                                            }
                                                        }
                                                    />
                                        </div>
                                    }
                                }).collect_view()}
                                }
                            }}
                            </div>
                        }
                    }}
                </div>
            </div>
    
    
            <div class="pdtinput" style="background-color:rgb(182, 239, 245);">
                <div class="left" style="margin-top: 10px; margin-bottom: 10px;">"项目负责人:"</div>
                <div class="right" >
                    // 人员显示区域
                    {move || {
                        // 获取所有涉及的部门
                        view! {
                            <div style="display: flex; flex-wrap: wrap; gap: 5px;">
                                {move || {
                                    {move || {
                                        let res_person = work_responsible.get().clone();
                                        res_person.into_iter().map(|person| {
                                            let person_rc = Rc::new(person);
                                            let full_name = person_rc.full_name.clone();
                                            view! {
                                                <div style="flex: 1 0 40%; min-width: 40px; margin:5px;border:1px solid #ccc;padding:5px;background-color:rgb(235, 89, 128);">
                                                    <span>{full_name}</span>
                                                </div>
                                    }
                                }).collect_view()}
                                }
                            }}
                            </div>
                        }
                    }}
                </div>
            </div>
    
    
    
            <div class="errorshow">
                <div class="left"></div>
                <div class="right red">
                    {worksubmit_error}
                </div>
            </div>
    
            <button style="width:300px;" type="submit" id="work-button">"添加新工作"</button>
        </form>
        <p></p>
        <div class="errorshow">
            <div class="left"></div>
            <div class="right red">
                {workdb_msg}
            </div>
        </div>
        <div class="form-container">
            <div class="db-window" id="work-item">{move || workview_content.get()}</div>
            <div class="btn-window">
                <form class="row" on:submit=get_work_db>
                    <button type="submit" style="margin:10px 5px 10px 5px;" id="get-button">"读取工作列表"</button>
                </form>
                <form class="row" on:submit=move|ev|{del_selected_items(ev, selected_works, set_selected_works, String::from("del_work_item"), set_workdb_msg, Box::new(get_work_db))}>
                    <button type="submit" style="margin:10px 5px 10px 5px;" id="del-button">"删除选中项"</button>
                </form>
            </div>
        </div>

        <p></p>
        <h1>"---------※工作部门管理※---------"</h1>
                <form  id="dept-form" on:submit=write_dept_sql>
                    <div class="pdtinput">
                        <div class="left"> "部门名称:"</div>
                        <div class="right"> 
                            <input style="width:420px" type="text" minlength="3" maxlength="150" placeholder="请输入部门名称..." 
                                value = move || department_name.get()  //将信号的值绑定到输入框
                                on:input=move|ev|update_string(ev, "部门名称".to_string(), set_department_name, set_department_error) />
                        </div>
                    </div>
                    <div class="errorshow">
                        <div class="left"></div>
                        <div class="right red"> 
                            {department_error}
                        </div>
                    </div>

                    <div class="errorshow">
                        <div class="left"></div>
                        <div class="right red"> 
                            {deptsubmit_error}
                        </div>
                    </div>

                    <button style="width:300px;" type="submit" id="dept-button">"新建工作部门"</button>
                </form>
                <p></p>
                <div class="errorshow">
                    <div class="left"></div>
                    <div class="right red"> 
                        {deptdb_msg}
                    </div>
                </div>
                <div class="form-container">
                <div class="db-window" id="department-item">{move || department_content.get()}</div>
                <div class="btn-window">
                    <form class="row" on:submit=get_department_db>
                        <button type="submit" id="get-button" style="margin:10px 5px 10px 5px;height:45px;" >"读取数据库"</button>
                    </form>
                    <form class="row" on:submit=move|ev|{del_selected_items(ev, selected_depts, set_selected_depts, String::from("del_department_item"), set_deptdb_msg, Box::new(get_department_db))}>
                        <button type="submit" style="margin:10px 5px 10px 5px;height:45px;" id="del-button" >"删除选中项"</button>
                    </form>
                </div>
            </div>

            <h1>"---------※工作类型管理※---------"</h1>
            <form  id="type-form" on:submit=write_type_sql>
                <div class="pdtinput">
                    <div class="left"> "工作类型:"</div>
                    <div class="right"> 
                        <input style="width:420px" type="text" minlength="3" maxlength="150" placeholder="请输入部门名称..." 
                            value = move || work_type.get()  //将信号的值绑定到输入框
                            on:input=move|ev|update_string(ev, "工作类型".to_string(), set_work_type, set_worktype_error) />
                    </div>
                </div>
                <div class="errorshow">
                    <div class="left"></div>
                    <div class="right red"> 
                        {worktype_error}
                    </div>
                </div>

                <div class="errorshow">
                    <div class="left"></div>
                    <div class="right red"> 
                        {typesubmit_error}
                    </div>
                </div>

                <button style="width:300px;" type="submit" id="type-button">"新建工作类型"</button>
        </form>
        <p></p>
        <div class="errorshow">
            <div class="left"></div>
            <div class="right red"> 
                {typedb_msg}
            </div>
        </div>
        <div class="form-container">
        <div class="db-window" id="worktype-item">{move || worktype_content.get()}</div>
        <div class="btn-window">
            <form class="row" on:submit=get_worktype_db>
                <button type="submit" style="margin:10px 5px 10px 5px;height:45px;" id="get-button" >"读取数据库"</button>
            </form>
            <form class="row" on:submit=move|ev|{del_selected_items(ev, selected_worktypes, set_selected_worktypes, String::from("del_worktype_item"), set_typedb_msg, Box::new(get_worktype_db))}>
                <button type="submit" style="margin:10px 5px 10px 5px;height:45px;" id="del-button">"删除选中项"</button>
            </form>
        </div>
    </div>

    <h1>"---------※部门人员管理※---------"</h1>
    <form  id="personnel-form" on:submit=write_personnel_sql>
        <div class="pdtinput">
            <div class="left"> "员工姓名:"</div>
            <div class="right"> 
                <input style="width:420px" type="text" minlength="2" maxlength="50" placeholder="请输入部门员工全名..." 
                    value = move || personnel_name.get()  //将信号的值绑定到输入框
                    on:input=move|ev|update_string(ev, "员工全名".to_string(), set_personnel_name, set_personnel_error) />
            </div>
        </div>
        <div class="errorshow">
            <div class="left"></div>
            <div class="right red"> 
                {personnel_error}
            </div>
        </div>

        <div class="pdtinput">
            <div class="left" style="margin-top:7px;margin-bottom: 7px;"> "所属部门:"</div>
            <div class="right"> 
                <select style="width:350px;margin-top:10px;margin-bottom: 10px;"
                    on:focus=move |_| {
                        get_department_list();
                    }
                    on:change=move |ev| {
                        let value = event_target_value(&ev);
                        match value.parse::<i64>() {
                            Ok(id) => {
                                set_personnel_deptid.set(id);
                                set_personnel_deptid_error.set(String::new());
                            }
                            Err(_) => {
                                set_personnel_deptid_error.set(format!("请重新选择工作部门!"));
                            }
                        }
                    }
                >
                    <option value="" disabled selected>"请选择部门"</option>
                    {move || department_list.get().into_iter().map(|dept| {
                        view! {
                            <option value={dept.id.to_string()}>
                                {dept.name}
                            </option>
                        }
                    }).collect_view()}
                </select>
            </div>
        </div>
        <div class="errorshow">
            <div class="left"></div>
            <div class="right red"> 
                {personnel_deptid_error}
            </div>
        </div>

        <div class="errorshow">
            <div class="left"></div>
            <div class="right red"> 
                {personnelsubmit_error}
            </div>
        </div>

        <button style="width:300px;" type="submit" id="type-button">"添加部门员工"</button>
    </form>
    <p></p>
    <div class="errorshow">
        <div class="left"></div>
        <div class="right red"> 
            {personneldb_msg}
        </div>
    </div>
    <div class="form-container">
    <div class="db-window" id="personnel-item">{move || personnel_content.get()}</div>
    <div class="btn-window">
        <form class="row" on:submit=get_personnel_db>
            <button type="submit" style="margin:10px 5px 10px 5px;height:45px;" id="get-button" >"读取员工名单"</button>
        </form>
        <form class="row" on:submit=move|ev|{del_selected_items(ev, selected_personnels, set_selected_personnels, String::from("del_personnel_item"), set_personneldb_msg, Box::new(get_personnel_db))}>
            <button type="submit" style="margin:10px 5px 10px 5px;height:45px;" id="del-button" >"删除选中项"</button>
        </form>
    </div>
    </div>

    </main>
    }
}

3. 后端Tauri命令

 对数据库的读写、删除、更新操作主要是通过前端Leptos调用(invoke)后台Tauri命令完成的,在schedule.rs中需要对invoke调用传递的参数和返回的数据的格式进行规定。

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = invoke, catch)]
    async fn invoke_without_args(cmd: &str) -> Result<JsValue, JsValue>;

    #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], catch)]
    async fn invoke(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;
}
/*
在tauri后台,命令:fn function()-> Result<String, String>
使用match处理Reslut输出,Ok()及Err()输出如下:
match ().await
    {
        Ok(_) => Ok(String::from("SUCCESS! 插入数据成功!")),
        Err(e) => {
            let err_msg = e.to_string();
            if err_msg.contains("UNIQUE constraint failed") {
                Err(format!("ERROR! 部门名称 '{}' 已存在!!", department.name))
            } else {
                Err(format!("数据库错误: {}", err_msg))
            }
        }
    }

在前端leptos的invoke调用中,同样通过match来处理调用后台命令返回的Ok和Err信息:
let result = match invoke("write_department_db", args_js).await {
                Ok(result) => result,
                Err(e) => {
                    let err_str = e.as_string().unwrap_or_else(|| format!("{:?}", e));
                    set_deptdb_msg.set(err_str.clone());
                    //log!("调用后端命令失败: {}", err_str);
                    return;
                }
            };

            if let Some(msg) = result.as_string() {
                set_deptdb_msg.set(msg.clone());
                if msg.contains("SUCCESS") {
                    get_department_db(ev.clone());
                }
            } else {
                let err_msg = format!("ERROR: 无法解析的返回格式: {:?}", result);
                set_deptdb_msg.set(err_msg.clone());
                log!("{}", err_msg);
            }
*/

传递给后台命令的参数首先要转换成结构体,然后其转换成JsValue格式后,传递给后台命令,后台命令返回的值也是JsValue格式,也需要格式转换。具体例子如下:

#[derive(Serialize, Deserialize)]
struct FetchWorkArgs {
    workid: i64,
}


......
        spawn_local(async move {
            let args = FetchWorkArgs{workid: fetch_work_id.get_untracked()};
            let args_js = match serde_wasm_bindgen::to_value(&args) {
                Ok(v) => v,
                Err(e) => {
                    set_fetch_works_error.set(format!("参数序列化失败: {}", e));
                    return;
                }
            };
            
            match invoke("send_progress_record", args_js).await {
                Ok(result) => {
                    match serde_wasm_bindgen::from_value::<Vec<ProgressRecord>>(result) {
                        Ok(work) => {
                            set_fetch_progress_records.set(work);
                        }
                        Err(e) => {
                            set_fetch_works_error.set(format!("工作数据反序列化失败: {}", e));
                        }
                    }
                }
                Err(e) => {
                    set_fetch_works_error.set(format!("获取工作详情失败: {:?}", e));
                }
            }
        });

而对应的后台send_progress_record命令如下,其中参数workid是与Leptos传递的参数结构体的键workid保持一直的,且不能有下划线等符号。

#[tauri::command]
async fn send_progress_record(state: tauri::State<'_, DbState>, workid:i64) -> Result<Vec<ProgressRecord>, String> {
    let db = &state.db;

    let records: Vec<ProgressRecord> = sqlx::query_as::<_, ProgressRecord>(
        "SELECT id, progress_detail, recorder_id, record_date
         FROM progress_records
         WHERE work_id = ?
         ORDER BY record_date ASC"
    )
    .bind(workid)
    .fetch_all(db)
    .await
    .map_err(|e| format!("查询进度记录失败: {}", e))?;

    Ok(records)
}

所有后台命令均放在src-tauri\src\lib.rs文件中,具体内容如下:

use std::io::Write;
use futures::TryStreamExt;
use plotters::prelude::*;
use sqlx::{migrate::MigrateDatabase, prelude::FromRow, sqlite::SqlitePoolOptions, Pool, Sqlite};
//use tauri::{App, Manager, WebviewWindowBuilder, Emitter};
use tauri::{App, Emitter, Manager};
use serde::{Deserialize, Serialize};
type Db = Pool<Sqlite>;
use std::process::Command;
use std::env;


struct DbState {
    db: Db,
}

mod tray;       //导入tray.rs模块
mod mymenu;     //导入mynemu.rs模块
use mymenu::{create_menu, handle_menu_event};

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_table
    sqlx::migrate!("./migrations/").run(&db).await.unwrap();
 
    db
}


// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
async fn show_splashscreen_window(app: tauri::AppHandle) {
    if let Some(splashscreen) = app.get_webview_window("splashscreen") {
        splashscreen.show().unwrap();
    }
}
#[tauri::command]
async fn close_splashscreen(app: tauri::AppHandle) {
    // 获取主窗口
    let main_window = app.get_webview_window("main").unwrap();
    
    // 延迟创建菜单并附加到窗口
    let menu = create_menu(&app).unwrap();
    main_window.set_menu(menu).unwrap();
    main_window.on_menu_event(move |window, event| handle_menu_event(window, event));

    if let Some(splashscreen) = app.get_webview_window("splashscreen") {
        splashscreen.close().unwrap();
    }
   
    // 显示主窗口
    main_window.show().unwrap();
}


//导航到指定页面
#[tauri::command]
async fn navigate_to(app: tauri::AppHandle, path: String) -> Result<(), String> {
    if let Some(window) = app.get_webview_window("main") {
        window.emit("navigate", path).map_err(|e| e.to_string())?;      //window.emit_to(label, event, content)向特定label页面发送event
    }
    Ok(())
}



#[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(Clone, Serialize, Deserialize)]
    struct DataPoint {
        x: f64,
        y: f64,
    }

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

#[derive(Debug, Serialize, Deserialize, FromRow)]
struct Department {
    id:i64,
    name:String,
}

#[derive(Serialize, Deserialize)]
struct DepartmentSend {
    name:String,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
struct Worktype {
    id:i64,
    name:String,
}

#[derive(Serialize, Deserialize)]
struct WorktypeSend {
    name:String,
}


#[derive(Debug, Serialize, Deserialize, FromRow, Clone)]
struct Personnel {
    id:i64,
    full_name:String,
    department_id:i64,
}

#[derive(Serialize, Deserialize)]
struct PersonnelSend {
    full_name:String,
    department_id:i64,
}

#[derive(Serialize, Deserialize,  FromRow)]
struct ProgressRecord {
    id: i64,
    progress_detail: String,
    recorder_id: i64,
    record_date: String,
}

#[derive(Serialize, Deserialize,  FromRow)]
struct ProgressRecordSend {
    work_id: i64,
    progress_detail: String,
    recorder_id: i64,
    record_date: String,
}

#[tauri::command]
async fn get_personnel_by_department(
    state: tauri::State<'_, DbState>,
    departmentid: i64
) -> Result<Vec<Personnel>, String> {
    let db = &state.db;
    
    if departmentid <= 0 {
        return Ok(Vec::new());
    }

    let query_result:Vec<Personnel> = sqlx::query_as::<_, Personnel>(
        "SELECT id, full_name, department_id FROM personnel WHERE department_id = ?1"
    )
    .bind(&departmentid)
    .fetch(db)
    .try_collect()
    .await
    .map_err(|e| format!("查询人员失败: {}", e))?;

    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("插入数据成功!"))
}



#[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_selected_pdt(state: tauri::State<'_, DbState>, productlist:Vec<i64>) -> Result<String, 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!(
        "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(String::from("删除失败:未找到匹配的产品"));
    }

    Ok(format!("成功删除 {} 条数据!", result.rows_affected()))

}

#[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)

}

#[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(())
}

use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use tauri::path::BaseDirectory;


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

#[tauri::command]
fn python_acid_plot(app: tauri::AppHandle, productdata: Vec<Pdt>) -> Result<String, String> {
    use std::collections::HashMap;
    
    let resource_path = app.path().resolve("resources/views.py", BaseDirectory::Resource)
        .expect("Failed to resolve resource");

    // 将Pdt结构体转换为HashMap
    let data: Vec<HashMap<&str, serde_json::Value>> = productdata.iter().map(|pdt| {
        let mut map = HashMap::new();
        map.insert("pdt_id", serde_json::json!(pdt.pdt_id));
        map.insert("pdt_name", serde_json::json!(pdt.pdt_name));
        map.insert("pdt_si", serde_json::json!(pdt.pdt_si));
        map.insert("pdt_al", serde_json::json!(pdt.pdt_al));
        map.insert("pdt_ca", serde_json::json!(pdt.pdt_ca));
        map.insert("pdt_mg", serde_json::json!(pdt.pdt_mg));
        map.insert("pdt_fe", serde_json::json!(pdt.pdt_fe));
        map.insert("pdt_ti", serde_json::json!(pdt.pdt_ti));
        map.insert("pdt_ka", serde_json::json!(pdt.pdt_ka));
        map.insert("pdt_na", serde_json::json!(pdt.pdt_na));
        map.insert("pdt_mn", serde_json::json!(pdt.pdt_mn));
        map.insert("pdt_date", serde_json::json!(pdt.pdt_date));
        map
    }).collect();

    
    // 将HashMap序列化为JSON字符串
    // 添加调试日志
    //println!("Input data to Python script: {:?}", data);

    let input_data = serde_json::to_string(&data)
        .map_err(|e| e.to_string())?;

    // 添加调试日志
    //println!("JSON input data: {}", input_data);

    // 创建Python进程并将数据通过标准输入传递
    let mut command = Command::new("E:/python_envs/eric7/python.exe")
        .arg(resource_path)
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .map_err(|e| e.to_string())?;

    // 将JSON数据写入Python进程的标准输入
    if let Some(stdin) = command.stdin.as_mut() {
        Write::write_all(stdin, input_data.as_bytes())
            .map_err(|e| e.to_string())?;
    }

    // 等待命令完成并获取输出
    let output = command.wait_with_output()
        .map_err(|e| e.to_string())?;

    if output.status.success() {
        let image_data = String::from_utf8(output.stdout)
            .map_err(|e| e.to_string())?
            .trim()
            .to_string();
        Ok(image_data)
    } else {
        let error_message = String::from_utf8(output.stderr)
            .map_err(|e| e.to_string())?;
        Err(error_message)
    }
}

#[tauri::command]
fn plotters_acid_rust(productdata: Vec<Pdt>) -> Result<String, String> {
    // 参数列表
    let para_list = [
        [1375.76, 122.29, 1.06247, 1.57233, 1.61648, 1.44738, 1.92899, 1.47337],
        [1272.64, 117.64, 1.05336, 1.42246, 1.48036, 1.51099, 1.86207, 1.36590],
        [1192.44, 112.99, 1.03567, 1.27336, 1.43136, 1.41448, 1.65966, 1.20929]
    ];
    
    // 使用SVG后端实现抗锯齿
    let mut svg_buffer = String::new();
    {
        let root = SVGBackend::with_string(&mut svg_buffer, (2400, 2000)).into_drawing_area();
        root.fill(&WHITE).map_err(|e| e.to_string())?;

        let mut ymax = vec![0.0; productdata.len()];
        // 存储每个产品的t0, b1, b0值
        let mut params = Vec::with_capacity(productdata.len());
        
        // 处理每个产品数据 - 第一次循环计算并存储参数
        for (idx, pdt) in productdata.iter().enumerate() {
            // 计算T1, T2, T3
            let mut t = [0.0; 3];
            for (i, para) in para_list.iter().enumerate() {
                let [a, b0, b1, b2, b3, b4, b5, b6] = para;
                
                // 计算温度参数公式
                let numerator = b0 - pdt.pdt_si - b1 * pdt.pdt_al;
                let denominator = b2 * pdt.pdt_ca
                    + b3 * pdt.pdt_mg
                    + b4 * (pdt.pdt_na + pdt.pdt_ka)
                    + b5 * pdt.pdt_fe * 2.0 / 3.0 * 71.8444 * 2.0 / 159.6882
                    + b6 * pdt.pdt_fe / 3.0;
                
                t[i] = a * (numerator / denominator);
            }
            
            // 计算T0, B1, B0并存储
            let t0 = (t[0] * t[1] + t[1] * t[2] - 2.0 * t[0] * t[2]) / (t[0] - 2.0 * t[1] + t[2]);
            let b1 = (t[0] + t0) * (t[1] + t0) / (t[0] - t[1]) / 2.0;
            let b0 = 1.5 - b1 / (t[0] + t0);
            params.push((t0, b1, b0));
            
            // 计算当前产品的ymax并存储
            let x_min = 1300.0;
            let exponent = b0 + b1 / (x_min + t0);
            ymax[idx] = (exponent * std::f64::consts::LN_10).exp() / 10.0;
         }
        // 计算ymax的最大值并向上取偶
        let max_value = *ymax.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
        let max_even = ((max_value.ceil() as i32 + 1) & !1) as f64;

        // 创建图表
        let mut chart = ChartBuilder::on(&root)
            .caption("岩矿棉温粘曲线", ("微软雅黑", 96).into_font()) // 字体放大到140
            .margin(80)  // 增大边距
            .x_label_area_size(120)  // 增大X轴标签区域
            .y_label_area_size(120)  // 增大Y轴标签区域
            .build_cartesian_2d(1300f64..1600f64, 0f64..max_even)
            .map_err(|e| e.to_string())?;
            
        // 配置网格
        chart.configure_mesh()
            .x_labels(16)
            .y_labels((0..=max_even as usize).count()) // 根据Y轴范围设置标签数量
            .x_desc("温度T/℃")
            .y_desc("动力粘度η/Pa·s")
            .x_label_style(("微软雅黑", 48).into_font()) // 字体放大到80
            .y_label_style(("微软雅黑", 48).into_font())
            .light_line_style(BLACK.mix(0.15))
            .bold_line_style({
                let style = BLACK.mix(0.5).stroke_width(2);
                style
            }) // 加粗刻度线
            .x_label_formatter(&|x| format!("{:.0}", x))    //标签格式,小数位数0
            .y_label_formatter(&|y| format!("{:.0}", y))
            .draw()
            .map_err(|e| e.to_string())?;

        // 手动绘制红色网格线
        let drawing_area = chart.plotting_area();
        for y in [2.0, 5.0] {
            if y <= max_even {
                drawing_area.draw(&PathElement::new(
                    vec![(1300.0, y), (1600.0, y)],
                    ShapeStyle {
                        color: RED.to_rgba(),
                        filled: false,
                        stroke_width: 5,
                    }
                )).map_err(|e| e.to_string())?;
            }
        }
            

        
        // 处理每个产品数据 - 第二次循环使用存储的参数
        for (idx, pdt) in productdata.iter().enumerate() {
            // 从存储的参数中获取t0, b1, b0
            let (t0, b1, b0) = params[idx];
            
            // 绘制曲线
            let color = Palette99::pick(idx);
            // 使用PathElement绘制更平滑的曲线
            let points: Vec<_> = (1300000..1600000).step_by(1).map(|x| {
                let x_val = x as f64 / 1000.0;
                let exponent = b0 + b1 / (x_val + t0);
                let y_val = (exponent * std::f64::consts::LN_10).exp() / 10.0;
                (x_val, y_val)
            }).collect();
            
            chart.draw_series(vec![PathElement::new(
                points,
                ShapeStyle {
                    color: color.to_rgba(),
                    filled: false,
                    stroke_width: 8,
                }
            )])
                .map_err(|e| e.to_string())?
                .label(&pdt.pdt_name)
                .legend(move |(x, y)| {
                    Rectangle::new([(x - 35, y - 5), (x + 35, y + 5)], color.filled())  // 将图例线段宽度从20px增加到70px
                });
        }
        
       
       
        // 绘制图例
        let legend_bg_style = WHITE.mix(0.8);
        let legend_border_style = BLACK.stroke_width(4);  // 边框调整为5px
        
        chart.configure_series_labels()
            .position(SeriesLabelPosition::UpperRight)
            .background_style(legend_bg_style)
            .border_style(legend_border_style)
            .label_font(("微软雅黑", 48))  // 字体调整为72pt
            .margin(75)  // 边距调整为75px
            .legend_area_size(90)  // 图例区调整为90px
            .draw()
            .map_err(|e| e.to_string())?;

        // 将图表写入缓冲区
        root.present().map_err(|e| e.to_string())?;
    }
    
    // 将 SVG 数据转换为 Base64 编码的字符串
    let base64_data = STANDARD.encode(&svg_buffer);

    // 返回 Base64 编码的 SVG 数据
    Ok(format!("data:image/svg+xml;base64,{}", base64_data))
}

#[tauri::command]
async fn send_department_db(state: tauri::State<'_, DbState>) -> Result<Vec<Department>, String> {
    let db = &state.db;
    let query_result:Vec<Department> = sqlx::query_as::<_, Department>(       //查询数据以特定的格式输出
        "SELECT * FROM departments"
        )
        .fetch(db)
        .try_collect()
        .await.unwrap();
    Ok(query_result)
}

#[tauri::command]
async fn write_department_db(state: tauri::State<'_, DbState>, department:DepartmentSend) -> Result<String, String> {
    let db = &state.db;

    match sqlx::query("INSERT INTO departments (name) VALUES (?1)")
        .bind(&department.name)
        .execute(db)
        .await
    {
        Ok(_) => Ok(String::from("SUCCESS! 插入数据成功!")),
        Err(e) => {
            let err_msg = e.to_string();
            if err_msg.contains("UNIQUE constraint failed") {
                Err(format!("ERROR! 部门名称 '{}' 已存在!!", department.name))
            } else {
                Err(format!("数据库错误: {}", err_msg))
            }
        }
    }
}

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

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

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

    let query_str = format!(
        "DELETE FROM departments WHERE id IN ({})",
        placeholders
    );

    // 构建查询并绑定参数
    let mut query = sqlx::query(&query_str);
    for id in &selectedlist {
        query = query.bind(id);
    }

    // 执行删除操作
    let result = query
        .execute(db)
        .await
        .map_err(|e| format!("删除失败: {}", e))?;

    // 检查实际删除的行数
    if result.rows_affected() == 0 {
        return Err(String::from("删除失败:未找到匹配的产品"));
    }

    Ok(format!("SUCCESS! 成功删除 {} 条数据!", result.rows_affected()))

}

#[tauri::command]
async fn send_worktype_db(state: tauri::State<'_, DbState>) -> Result<Vec<Worktype>, String> {
    let db = &state.db;
    let query_result:Vec<Worktype> = sqlx::query_as::<_, Worktype>(       //查询数据以特定的格式输出
        "SELECT * FROM work_types"
        )
        .fetch(db)
        .try_collect()
        .await.unwrap();
    Ok(query_result)
}

#[tauri::command]
async fn write_worktype_db(state: tauri::State<'_, DbState>, worktype:WorktypeSend) -> Result<String, String> {
    let db = &state.db;

    match sqlx::query("INSERT INTO work_types (name) VALUES (?1)")
        .bind(&worktype.name)
        .execute(db)
        .await
    {
        Ok(_) => Ok(String::from("SUCCESS! 插入数据成功!")),
        Err(e) => {
            let err_msg = e.to_string();
            if err_msg.contains("UNIQUE constraint failed") {
                Err(format!("ERROR! 工作类型 '{}' 已存在!!", worktype.name))
            } else {
                Err(format!("数据库错误: {}", err_msg))
            }
        }
    }
}

#[tauri::command]
async fn write_progress_record(state: tauri::State<'_, DbState>, progressrecord: ProgressRecordSend) -> Result<String, String> {
    let db = &state.db;

    // 检查工作是否已完成
    let is_completed: i64 = sqlx::query_scalar(
        "SELECT is_completed FROM works WHERE id = ?"
    )
    .bind(&progressrecord.work_id)
    .fetch_one(db)
    .await
    .map_err(|e| format!("ERROR! 查询工作状态失败: {}", e))?;

    if is_completed == 1 {
        return Err("ERROR! 该工作已完成,不能再添加进度记录".to_string());
    }

    sqlx::query(
        "INSERT INTO progress_records (work_id, progress_detail, recorder_id, record_date)
        VALUES (?1, ?2, ?3, ?4)"
    )
    .bind(&progressrecord.work_id)
    .bind(&progressrecord.progress_detail)
    .bind(&progressrecord.recorder_id)
    .bind(&progressrecord.record_date)
    .execute(db)
    .await
    .map_err(|e| format!("ERROR! 进度记录失败: {}", e))?;

    Ok("SUCCESS! 工作进度记录已保存!".to_string())
}


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

    // 处理空数组的情况
    if selectedlist.is_empty() {
        return Err(String::from("删除失败:未提供有效的ID清单"));
    }

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

    let query_str = format!(
        "DELETE FROM work_types WHERE id IN ({})",
        placeholders
    );

    // 构建查询并绑定参数
    let mut query = sqlx::query(&query_str);
    for id in &selectedlist {
        query = query.bind(id);
    }

    // 执行删除操作
    let result = query
        .execute(db)
        .await
        .map_err(|e| format!("删除失败: {}", e))?;

    // 检查实际删除的行数
    if result.rows_affected() == 0 {
        return Err(String::from("删除失败:未找到匹配的工作类型"));
    }

    Ok(format!("SUCCESS! 成功删除 {} 种工作类型!", result.rows_affected()))

}

#[tauri::command]
async fn write_personnel_db(state: tauri::State<'_, DbState>, personnel:PersonnelSend) -> Result<String, String> {
    let db = &state.db;

    match sqlx::query("INSERT INTO personnel (full_name, department_id) VALUES (?1,?2)")
        .bind(&personnel.full_name)
        .bind(&personnel.department_id)
        .execute(db)
        .await
    {
        Ok(_) => Ok(format!("SUCCESS! 员工({})已成功录入系统!", &personnel.full_name)),
        Err(e) => {
            let err_msg = e.to_string();
            Err(format!("数据库写入错误: {}", err_msg))

        }
    }
}

#[tauri::command]
async fn send_personnel_db(state: tauri::State<'_, DbState>) -> Result<Vec<Personnel>, String> {
    let db = &state.db;
    let query_result:Vec<Personnel> = sqlx::query_as::<_, Personnel>(       //查询数据以特定的格式输出
        "SELECT * FROM personnel"
        )
        .fetch(db)
        .try_collect()
        .await.unwrap();
    Ok(query_result)
}

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

    // 处理空数组的情况
    if selectedlist.is_empty() {
        return Err(String::from("删除失败:未提供有效员工ID清单"));
    }

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

    let query_str = format!(
        "DELETE FROM personnel WHERE id IN ({})",
        placeholders
    );

    // 构建查询并绑定参数
    let mut query = sqlx::query(&query_str);
    for id in &selectedlist {
        query = query.bind(id);
    }

    // 执行删除操作
    let result = query
        .execute(db)
        .await
        .map_err(|e| format!("删除失败: {}", e))?;

    // 检查实际删除的行数
    if result.rows_affected() == 0 {
        return Err(String::from("删除失败:未找到匹配的岗位员工"));
    }

    Ok(format!("SUCCESS! 成功删除 {} 个岗位员工!", result.rows_affected()))

}

#[derive(Debug, Serialize, Deserialize, FromRow)]
struct Work {
    id: i64,
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
}

#[derive(Serialize, Deserialize)]
struct WorkSend {
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
struct WorkDeptsSend {
    work_id: i64,
    department_id: i64,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
struct WorkPersonSend {
    work_id: i64,
    personnel_id: i64,
    is_main_responsible: i64
}

#[derive(Debug, Serialize, Deserialize)]
struct WorkAll {
    id: i64,
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
    work_departments: Vec<WorkDeptsSend>,
    work_personnels: Vec<WorkPersonSend>
}

#[derive(Debug, Serialize, Deserialize)]
struct WorkBack {
    id: i64,
    subject: String,
    work_content: String,
    start_date: String,
    work_type_id: i64,
    is_completed: i64,
    work_departments: Vec<Department>,
    work_personnels: Vec<Personnel>,
    responsile_person: Vec<Personnel>
}

#[tauri::command]
async fn send_work_db(state: tauri::State<'_, DbState>) -> Result<Vec<WorkAll>, String> {
    let db = &state.db;
    
    // 查询works表获取工作基本信息
    let works: Vec<Work> = sqlx::query_as::<_, Work>(
        "SELECT * FROM works"
    )
    .fetch(db)
    .try_collect()
    .await
    .map_err(|e| format!("查询工作数据失败: {}", e))?;

    let mut result = Vec::new();

    for work in works {
        // 查询关联部门
        let departments: Vec<WorkDeptsSend> = sqlx::query_as::<_, WorkDeptsSend>(
            "SELECT work_id, department_id FROM work_departments WHERE work_id = ?"
        )
        .bind(work.id)
        .fetch(db)
        .try_collect()
        .await
        .map_err(|e| format!("查询工作关联部门失败: {}", e))?;

        // 查询关联人员
        let personnels: Vec<WorkPersonSend> = sqlx::query_as::<_, WorkPersonSend>(
            "SELECT work_id, personnel_id, is_main_responsible FROM work_personnel WHERE work_id = ?"
        )
        .bind(work.id)
        .fetch(db)
        .try_collect()
        .await
        .map_err(|e| format!("查询工作关联人员失败: {}", e))?;

        result.push(WorkAll {
            id: work.id,
            subject: work.subject,
            work_content: work.work_content,
            start_date: work.start_date,
            work_type_id: work.work_type_id,
            is_completed: work.is_completed,
            work_departments: departments,
            work_personnels: personnels
        });
    }

    Ok(result)
}


#[tauri::command]
async fn send_work_list(state: tauri::State<'_, DbState>) -> Result<Vec<Work>, String> {
    let db = &state.db;
    
    // 查询works表获取工作基本信息
    let works: Vec<Work> = sqlx::query_as::<_, Work>(
        "SELECT * FROM works"
    )
    .fetch(db)
    .try_collect()
    .await
    .map_err(|e| format!("查询工作数据失败: {}", e))?;

    Ok(works)
}

#[tauri::command]
async fn send_one_work(state: tauri::State<'_, DbState>, workid:i64) -> Result<WorkBack, String> {
    let db = &state.db;
    
    // 开始事务
    let mut tx = db.begin().await
        .map_err(|e| format!("事务开始失败: {}", e))?;

    // 查询works表获取工作基本信息
    let work: Work = sqlx::query_as::<_, Work>(
        "SELECT * FROM works WHERE id = ?"
    )
    .bind(workid)
    .fetch_one(&mut *tx)
    .await
    .map_err(|e| format!("查询工作数据失败: {}", e))?;

    // 查询关联部门ID
    let department_ids: Vec<WorkDeptsSend> = sqlx::query_as::<_, WorkDeptsSend>(
        "SELECT work_id, department_id FROM work_departments WHERE work_id = ?"
    )
    .bind(workid)
    .fetch_all(&mut *tx)
    .await
    .map_err(|e| format!("查询工作关联部门失败: {}", e))?;

    // 查询部门详细信息
    let mut work_departments = Vec::new();
    for dept in &department_ids {
        let department: Department = sqlx::query_as::<_, Department>(
            "SELECT * FROM departments WHERE id = ?"
        )
        .bind(dept.department_id)
        .fetch_one(&mut *tx)
        .await
        .map_err(|e| format!("查询部门详细信息失败: {}", e))?;
        work_departments.push(department);
    }

    // 查询关联人员ID
    let personnel_ids: Vec<WorkPersonSend> = sqlx::query_as::<_, WorkPersonSend>(
        "SELECT work_id, personnel_id, is_main_responsible FROM work_personnel WHERE work_id = ?"
    )
    .bind(workid)
    .fetch_all(&mut *tx)
    .await
    .map_err(|e| format!("查询工作关联人员失败: {}", e))?;

    // 查询人员详细信息
    let mut work_personnels = Vec::new();
    let mut responsile_person = Vec::new();
    for person in &personnel_ids {
        let personnel: Personnel = sqlx::query_as::<_, Personnel>(
            "SELECT * FROM personnel WHERE id = ?"
        )
        .bind(person.personnel_id)
        .fetch_one(&mut *tx)
        .await
        .map_err(|e| format!("查询人员详细信息失败: {}", e))?;
        
        work_personnels.push(personnel.clone());
        
        if person.is_main_responsible == 1 {
            responsile_person.push(personnel);
        }
    }


    // 提交事务
    tx.commit().await
        .map_err(|e| format!("事务提交失败: {}", e))?;

    let result = WorkBack {
        id: work.id,
        subject: work.subject,
        work_content: work.work_content,
        start_date: work.start_date,
        work_type_id: work.work_type_id,
        is_completed: work.is_completed,
        work_departments,
        work_personnels,
        responsile_person
    };

    Ok(result)
}

#[tauri::command]
async fn send_progress_record(state: tauri::State<'_, DbState>, workid:i64) -> Result<Vec<ProgressRecord>, String> {
    let db = &state.db;

    let records: Vec<ProgressRecord> = sqlx::query_as::<_, ProgressRecord>(
        "SELECT id, progress_detail, recorder_id, record_date
         FROM progress_records
         WHERE work_id = ?
         ORDER BY record_date ASC"
    )
    .bind(workid)
    .fetch_all(db)
    .await
    .map_err(|e| format!("查询进度记录失败: {}", e))?;

    Ok(records)
}

#[tauri::command]
async fn del_progress_record(state: tauri::State<'_, DbState>, selectedrecord: i64) -> Result<String, String> {
    let db = &state.db;

    if selectedrecord <= 0 {
        return Err(String::from("删除失败:未提供有效的记录ID"));
    }

    let result = sqlx::query(
        "DELETE FROM progress_records WHERE id = ?"
    )
    .bind(selectedrecord)
    .execute(db)
    .await
    .map_err(|e| format!("删除失败: {}", e))?;

    if result.rows_affected() == 0 {
        return Err(String::from("删除失败:未找到匹配的记录"));
    }

    Ok(format!("SUCCESS! 成功删除进度记录!"))
}

#[tauri::command]
async fn change_work_state(state: tauri::State<'_, DbState>, workid: i64) -> Result<i64, String> {
    let db = &state.db;

    if workid <= 0 {
        return Err("ERROR! 无效的工作ID".to_string());
    }

    // 获取当前状态
    let current_state: i64 = sqlx::query_scalar(
        "SELECT is_completed FROM works WHERE id = ?"
    )
    .bind(workid)
    .fetch_one(db)
    .await
    .map_err(|e| format!("ERROR! 查询工作状态失败: {}", e))?;

    // 切换状态
    let new_state = if current_state == 1 { 0 } else { 1 };

    // 更新状态
    sqlx::query(
        "UPDATE works SET is_completed = ? WHERE id = ?"
    )
    .bind(new_state)
    .bind(workid)
    .execute(db)
    .await
    .map_err(|e| format!("ERROR! 更新工作状态失败: {}", e))?;

    Ok(new_state)
}

#[tauri::command]
async fn write_work_db(state: tauri::State<'_, DbState>, work: WorkSend) -> Result<i64, String> {
    let db = &state.db;

    sqlx::query(
        "INSERT INTO works (subject, work_content, start_date, work_type_id, is_completed)
        VALUES (?1, ?2, ?3, ?4, ?5)"
    )
    .bind(&work.subject)
    .bind(&work.work_content)
    .bind(&work.start_date)
    .bind(&work.work_type_id)
    .bind(&work.is_completed)
    .execute(db)
    .await
    .map_err(|e| format!("数据库写入错误: {}", e))?;

    // 获取最后插入的ID - 查询works表中的最大id并确保返回整数
    let id: i64 = sqlx::query_scalar("SELECT CAST(MAX(id) AS INTEGER) FROM works")
        .fetch_one(db)
        .await
        .map_err(|e| format!("获取ID失败: {}", e))?;

    Ok(id)
}

#[tauri::command]
async fn del_work_item(state: tauri::State<'_, DbState>, selectedlist: Vec<i64>) -> Result<String, String> {
    let db = &state.db;

    if selectedlist.is_empty() {
        return Err(String::from("删除失败:未提供有效工作ID清单"));
    }

    let placeholders = vec!["?"; selectedlist.len()].join(", ");
    let query_str = format!("DELETE FROM works WHERE id IN ({})", placeholders);

    let mut query = sqlx::query(&query_str);
    for id in &selectedlist {
        query = query.bind(id);
    }

    let result = query
        .execute(db)
        .await
        .map_err(|e| format!("删除失败: {}", e))?;

    if result.rows_affected() == 0 {
        return Err(String::from("删除失败:未找到匹配的工作"));
    }

    Ok(format!("SUCCESS! 成功删除 {} 个工作!", result.rows_affected()))
}


#[tauri::command]
async fn write_work_depts_db(state: tauri::State<'_, DbState>, workdepts: Vec<WorkDeptsSend>) -> Result<String, String> {
    let db = &state.db;

    if workdepts.is_empty() {
        return Err("ERROR! 未提供有效的部门关联数据".to_string());
    }

    // 开始事务
    let mut tx = db.begin().await
        .map_err(|e| format!("ERROR! 事务开始失败: {}", e))?;

    // 批量插入
    for workdept in &workdepts {
        match sqlx::query(
            "INSERT INTO work_departments (work_id, department_id) VALUES (?1, ?2)"
        )
        .bind(workdept.work_id)
        .bind(workdept.department_id)
        .execute(&mut *tx)
        .await {
            Ok(_) => (),
            Err(e) => {
                if let Err(rollback_err) = tx.rollback().await {
                    return Err(format!("ERROR! 关联部门失败: {}, 回滚失败: {}", e, rollback_err));
                }
                return Err(format!("ERROR! 关联部门失败: {}", e));
            }
        }
    }

    // 提交事务
    tx.commit().await
        .map_err(|e| format!("ERROR! 事务提交失败: {}", e))?;

    Ok(format!("SUCCESS! 成功关联 {} 个工作与部门!", workdepts.len()))
}

#[tauri::command]
async fn write_work_personnel_db(state: tauri::State<'_, DbState>, workpersonnels: Vec<WorkPersonSend>) -> Result<String, String> {
    let db = &state.db;

    if workpersonnels.is_empty() {
        return Err("ERROR! 未提供有效的人员关联数据".to_string());
    }

    // 开始事务
    let mut tx = match db.begin().await {
        Ok(tx) => tx,
        Err(e) => return Err(format!("ERROR! 事务开始失败: {}", e)),
    };

    // 批量插入
    for workperson in &workpersonnels {
        if let Err(e) = sqlx::query(
            "INSERT INTO work_personnel (work_id, personnel_id, is_main_responsible) VALUES (?1, ?2, ?3)"
        )
        .bind(workperson.work_id)
        .bind(workperson.personnel_id)
        .bind(workperson.is_main_responsible)
        .execute(&mut *tx)
        .await {
            if let Err(rollback_err) = tx.rollback().await {
                return Err(format!("ERROR! 关联人员失败: {}, 回滚失败: {}", e, rollback_err));
            }
            return Err(format!("ERROR! 关联人员失败: {}", e));
        }
    }

    // 提交事务
    match tx.commit().await {
        Ok(_) => Ok(format!("SUCCESS! 成功关联 {} 个人员!", workpersonnels.len())),
        Err(e) => Err(format!("ERROR! 事务提交失败: {}", e)),
    }
}




#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![
            show_splashscreen_window,
            close_splashscreen,
            navigate_to,
            update_user,
            close_main_window,
            write_pdt_db,
            send_pdt_db,
            del_selected_pdt,
             python_plot,
            python_acid_plot,
            plotters_acid_rust,
            send_selected_pdt,
            send_department_db,
            write_department_db,
            del_department_item,
            send_worktype_db,
            write_worktype_db,
            del_worktype_item,
            write_personnel_db,
            send_personnel_db,
            del_personnel_item,
            write_work_db,
            send_work_db,
            send_one_work,
            send_work_list,
            del_work_item,
            get_personnel_by_department,
            write_work_depts_db,
            write_work_personnel_db,
            write_progress_record,
            send_progress_record,
            del_progress_record,
            change_work_state
            ])
        .setup(|app| {
            #[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程序的时候出错!");
}

 至此,工作进度管理桌面小程序基本完成。


网站公告

今日签到

点亮在社区的每一天
去签到