当前位置: 首页 > news >正文

【TeamFlow】4 团队人员管理系统的实现

项目概述

这是一个基于Rust和Yew框架构建的团队(部门或用户组)与用户管理Web应用,采用现代WebAssembly技术实现高效性能。系统提供完整的团队层级结构管理、用户管理以及团队-用户多对多关系管理功能,具有清晰的数据模型和响应式用户界面。

项目初始化

  • 安装必要工具
cargo install trunk
rustup target add wasm32-unknown-unknown
  • 新建项目
cargo new --lib teambuild
cd teambuild
  • 编辑文档
code .

文件结构

teambuild/
├── Cargo.toml
├── index.html
├── src/
│   ├── lib.rs
│   └── main.css
└── .cargo/└── config.toml (可选)

文件内容

  1. Cargo.toml
[package]
name = "team-manager-yew"
version = "0.1.0"
edition = "2021"[dependencies]
yew = { version = "0.20", features = ["csr"] }
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Document","Element","HtmlInputElement","Window","Storage","console"
]}
gloo-storage = "0.2"
gloo-events = "0.1"
  1. index.html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>团队人员管理系统</title><link data-trunk rel="css" href="src/main.css"/>
</head>
<body></body>
</html>
  1. src/lib.rs
use yew::prelude::*;
use serde::{Serialize, Deserialize};
use gloo_storage::{LocalStorage, Storage};
use web_sys::HtmlInputElement;// 数据结构
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct TeamMember {id: u32,name: String,email: String,department: String,position: String,join_date: String,
}// 团队管理器
struct TeamManager {members: Vec<TeamMember>,next_id: u32,
}impl TeamManager {fn new() -> Self {let mut manager = Self {members: Vec::new(),next_id: 1,};if let Ok(members) = LocalStorage::get::<Vec<TeamMember>>("team_members") {manager.members = members;if let Some(max_id) = manager.members.iter().map(|m| m.id).max() {manager.next_id = max_id + 1;}}manager}fn add_member(&mut self, member: TeamMember) -> bool {let mut member = member;member.id = self.next_id;self.next_id += 1;self.members.push(member);self.save()}fn remove_member(&mut self, id: u32) -> bool {if let Some(index) = self.members.iter().position(|m| m.id == id) {self.members.remove(index);self.save()} else {false}}fn update_member(&mut self, id: u32, new_data: TeamMember) -> bool {if let Some(index) = self.members.iter().position(|m| m.id == id) {self.members[index] = new_data;self.save()} else {false}}fn search_members(&self, query: &str) -> Vec<TeamMember> {let query = query.to_lowercase();self.members.iter().filter(|m| m.name.to_lowercase().contains(&query) ||m.email.to_lowercase().contains(&query) ||m.department.to_lowercase().contains(&query) ||m.position.to_lowercase().contains(&query)).cloned().collect()}fn save(&self) -> bool {LocalStorage::set("team_members", &self.members).is_ok()}
}// 消息类型
enum Msg {AddMember,EditMember(u32),DeleteMember(u32),CancelEdit,UpdateName(String),UpdateEmail(String),UpdateDepartment(String),UpdatePosition(String),UpdateJoinDate(String),Search(String),
}// 主应用组件
struct App {manager: TeamManager,form_data: TeamMember,editing_id: Option<u32>,search_query: String,filtered_members: Vec<TeamMember>,
}impl Component for App {type Message = Msg;type Properties = ();fn create(_ctx: &Context<Self>) -> Self {let form_data = TeamMember {id: 0,name: String::new(),email: String::new(),department: String::new(),position: String::new(),join_date: String::new(),};let manager = TeamManager::new();let filtered_members = manager.members.clone();Self {manager,form_data,editing_id: None,search_query: String::new(),filtered_members,}}fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {match msg {Msg::AddMember => {if self.form_data.name.is_empty() || self.form_data.email.is_empty() || self.form_data.department.is_empty() || self.form_data.position.is_empty() || self.form_data.join_date.is_empty() {return false;}let member = self.form_data.clone();if let Some(id) = self.editing_id {if self.manager.update_member(id, member) {self.reset_form();self.filtered_members = self.manager.search_members(&self.search_query);true} else {false}} else {if self.manager.add_member(member) {self.reset_form();self.filtered_members = self.manager.search_members(&self.search_query);true} else {false}}}Msg::EditMember(id) => {if let Some(member) = self.manager.members.iter().find(|m| m.id == id) {self.form_data = member.clone();self.editing_id = Some(id);true} else {false}}Msg::DeleteMember(id) => {if self.manager.remove_member(id) {self.filtered_members = self.manager.search_members(&self.search_query);true} else {false}}Msg::CancelEdit => {self.reset_form();true}Msg::UpdateName(value) => {self.form_data.name = value;true}Msg::UpdateEmail(value) => {self.form_data.email = value;true}Msg::UpdateDepartment(value) => {self.form_data.department = value;true}Msg::UpdatePosition(value) => {self.form_data.position = value;true}Msg::UpdateJoinDate(value) => {self.form_data.join_date = value;true}Msg::Search(query) => {self.search_query = query;self.filtered_members = self.manager.search_members(&self.search_query);true}}}fn view(&self, ctx: &Context<Self>) -> Html {let link = ctx.link();html! {<div class="app"><h1>{"团队人员管理系统"}</h1><div class="member-form"><h2>{ if self.editing_id.is_some() { "编辑成员" } else { "添加成员" } }</h2><div class="form-group"><label>{"姓名"}</label><input type="text" value={self.form_data.name.clone()} oninput={link.callback(|e: InputEvent| {let input: HtmlInputElement = e.target_unchecked_into();Msg::UpdateName(input.value())})}required=true/></div><div class="form-group"><label>{"邮箱"}</label><input type="email" value={self.form_data.email.clone()} oninput={link.callback(|e: InputEvent| {let input: HtmlInputElement = e.target_unchecked_into();Msg::UpdateEmail(input.value())})}required=true/></div><div class="form-group"><label>{"部门"}</label><select value={self.form_data.department.clone()} onchange={link.callback(|e: Event| {let select: HtmlInputElement = e.target_unchecked_into();Msg::UpdateDepartment(select.value())})}required=true><option value="">{"选择部门"}</option><option value="技术部">{"技术部"}</option><option value="市场部">{"市场部"}</option><option value="人力资源">{"人力资源"}</option><option value="财务部">{"财务部"}</option></select></div><div class="form-group"><label>{"职位"}</label><input type="text" value={self.form_data.position.clone()} oninput={link.callback(|e: InputEvent| {let input: HtmlInputElement = e.target_unchecked_into();Msg::UpdatePosition(input.value())})}required=true/></div><div class="form-group"><label>{"加入日期"}</label><input type="date" value={self.form_data.join_date.clone()} oninput={link.callback(|e: InputEvent| {let input: HtmlInputElement = e.target_unchecked_into();Msg::UpdateJoinDate(input.value())})}required=true/></div><button onclick={link.callback(|_| Msg::AddMember)}>{ if self.editing_id.is_some() { "更新" } else { "添加" } }</button>if self.editing_id.is_some() {<button onclick={link.callback(|_| Msg::CancelEdit)}>{"取消"}</button>}</div><div class="search-box"><h2>{"搜索成员"}</h2><input type="text" placeholder="输入姓名、邮箱、部门或职位..." value={self.search_query.clone()}oninput={link.callback(|e: InputEvent| {let input: HtmlInputElement = e.target_unchecked_into();Msg::Search(input.value())})}/></div><div class="member-list"><h2>{"成员列表"}</h2>if self.filtered_members.is_empty() {<p>{"没有找到成员"}</p>} else {<table><thead><tr><th>{"ID"}</th><th>{"姓名"}</th><th>{"邮箱"}</th><th>{"部门"}</th><th>{"职位"}</th><th>{"加入日期"}</th><th>{"操作"}</th></tr></thead><tbody>{ for self.filtered_members.iter().map(|member| self.view_member(member, link)) }</tbody></table>}</div></div>}}
}impl App {fn reset_form(&mut self) {self.form_data = TeamMember {id: 0,name: String::new(),email: String::new(),department: String::new(),position: String::new(),join_date: String::new(),};self.editing_id = None;}fn view_member(&self, member: &TeamMember, link: &Scope<Self>) -> Html {html! {<tr key={member.id}><td>{member.id}</td><td>{member.name.clone()}</td><td>{member.email.clone()}</td><td>{member.department.clone()}</td><td>{member.position.clone()}</td><td>{member.join_date.clone()}</td><td><button onclick={link.callback(move |_| Msg::EditMember(member.id))}>{"编辑"}</button><button onclick={link.callback(move |_| Msg::DeleteMember(member.id))}>{"删除"}</button></td></tr>}}
}fn main() {yew::start_app::<App>();
}
  1. src/main.css
.app {font-family: Arial, sans-serif;max-width: 1000px;margin: 0 auto;padding: 20px;
}.member-form, .search-box, .member-list {margin-bottom: 30px;padding: 20px;background: #f9f9f9;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}.form-group {margin-bottom: 15px;
}label {display: block;margin-bottom: 5px;font-weight: bold;color: #333;
}input, select {width: 100%;padding: 10px;box-sizing: border-box;border: 1px solid #ddd;border-radius: 4px;font-size: 16px;
}input:focus, select:focus {outline: none;border-color: #4CAF50;
}button {padding: 10px 15px;background: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;font-size: 16px;transition: background 0.3s;
}button:hover {background: #45a049;
}button:disabled {background: #cccccc;cursor: not-allowed;
}table {width: 100%;border-collapse: collapse;margin-top: 20px;font-size: 14px;
}th, td {border: 1px solid #ddd;padding: 12px;text-align: left;
}th {background-color: #4CAF50;color: white;
}tr:nth-child(even) {background-color: #f2f2f2;
}tr:hover {background-color: #e9e9e9;
}.search-box input {width: 100%;padding: 10px;font-size: 16px;
}h1, h2 {color: #333;
}h1 {text-align: center;margin-bottom: 30px;color: #4CAF50;
}
  1. .cargo/config.toml (可选)
[target.wasm32-unknown-unknown]
rustflags = ["-C", "opt-level=z","-C", "link-arg=-s","-C", "link-arg=-O3","-C", "link-arg=-flto"
]

运行开发服务器:

trunk serve

访问 http://localhost:8080 使用应用

功能特点

  1. 成员管理:
  • 添加新成员

  • 编辑现有成员

  • 删除成员

  1. 数据持久化:
  • 使用浏览器 localStorage 自动保存数据

  • 关闭页面后数据不会丢失

  1. 实时搜索:
  • 按姓名、邮箱、部门或职位搜索

  • 输入时即时显示结果

  1. 表单验证:
  • 必填字段验证

  • 邮箱格式验证

  1. 响应式设计:
  • 适配不同屏幕尺寸

  • 现代化的UI界面

这个完整的Rust实现展示了如何使用Yew框架构建功能完善的Web应用,所有代码都是纯Rust编写,无需JavaScript知识,大大降低了学习门槛。


http://www.mrgr.cn/news/99104.html

相关文章:

  • 【Rust 精进之路之第6篇-流程之舞】控制流:`if/else`, `loop`, `while`, `for` 与模式匹配初窥
  • 【Rust 精进之路之第15篇-枚举 Enum】定义、变体与数据关联:表达多种可能性
  • 【Rust 精进之路之第4篇-数据基石·上】标量类型:整数、浮点数、布尔与字符的精妙之处
  • 【Rust 精进之路之第10篇-借用·规则】引用 (``, `mut`):安全、高效地访问数据
  • 【Rust 精进之路之第2篇-初体验】安装、配置与 Hello Cargo:踏出 Rust 开发第一步
  • uniapp-商城-29-vuex 关于系统状态的管理
  • VSCode 扩展离线下载方法
  • 【Python图像处理入门】Python读取图像的5种方式指南(从入门到入土)
  • 【更新完毕】2025泰迪杯数据挖掘竞赛A题数学建模思路代码文章教学:竞赛论文初步筛选系统
  • uniapp-商城-27-vuex 使用流程
  • 6.QT-常用控件-QWidget|windowTitle|windowIcon|qrc机制|windowOpacity|cursor(C++)
  • C++ AVL树
  • MySQL+Redis实战教程:从Docker安装部署到自动化备份与数据恢复20250418
  • Linux笔记---动静态库(原理篇)
  • QML Label 组件
  • QT6(24)4.1界面组件概述:基础类QWidget 的属性 sizePolicy(组件默认的布局属性)。4.2布局管理:把容器组件与布局组件结合在一起使用,举例设置组件伸缩因子 stretch
  • 小白服务器开发-socket网络编程
  • 2026《数据结构》考研复习笔记一(C++基础知识)
  • MCP(2)架构篇:深入理解MCP的设计架构
  • 7.QT-常用控件-QWidget|font|toolTip|focusPolicy|styleSheet(C++)