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

【TeamFlow】4 团队管理系统

项目概述

基于Rust(Yew框架)和WebAssembly构建的团队管理系统,旨在提供高效的团队组织和成员管理解决方案。系统采用现代Web技术栈,具有高性能、类型安全和跨平台特性。

  1. 核心功能
  • 团队层级管理:支持多级团队结构(父子关系),可视化展示组织架构

  • 成员管理:完整的CRUD操作,支持本地持久化存储

  • 实时搜索:多字段联合搜索,即时反馈结果

  • 响应式UI:适配桌面/移动端,提供一致的用户体验

  1. 技术栈
- 语言: Rust → WebAssembly
- 框架: Yew 0.21
- 样式: 纯CSS
- 工具链: - Trunk (构建工具)- wasm-bindgen (WASM绑定)- gloo-storage (本地存储)
- 辅助库:- Serde (序列化)- UUID (唯一标识)- Validator (表单验证)

环境准备

  1. 安装必要工具
# 安装Trunk构建工具
cargo install trunk# 添加WASM编译目标
rustup target add wasm32-unknown-unknown
  1. 创建新项目
# 创建库项目
cargo new --lib teambuild
cd teambuild# 初始化Git仓库(可选)
git init# 使用VS Code打开项目(可选)
code .

项目结构

team-manager/
├── Cargo.toml    # Rust项目配置
├── index.html     # 应用入口
├── src/
│   ├── lib.rs       # 主应用逻辑和组件
│   ├── models.rs    # 数据模型定义
│   ├── storage.rs   # 数据持久化处理
│   └── main.css     # 全局样式表
└── .cargo/└── config.toml    # WASM优化配置

核心代码实现

  1. 依赖配置(Cargo.toml )
[package]
name = "team-manager"
version = "0.2.0"
edition = "2021"[dependencies]
# Web框架
yew = { version = "0.21", features = ["csr"] }# 序列化
serde = { version = "1.0", features = ["derive"] }# WASM绑定
wasm-bindgen = "0.2"# 唯一标识
uuid = { version = "1.3", features = ["v4", "serde"] }# 表单验证
validator = { version = "3.2", features = ["derive"] }# 本地存储
gloo-storage = "0.2"# Web API绑定
web-sys = { version = "0.3", features = ["Document","Window","HtmlInputElement","Storage"
]}
  1. 数据模型 (src/models.rs)
use serde::{Serialize, Deserialize};
use validator::Validate;
use uuid::Uuid;/// 团队数据结构
#[derive(Serialize, Deserialize, Clone, Validate, Debug)]
pub struct Team {pub id: Uuid,#[validate(length(min = 2, message = "团队名称至少2个字符"))]pub name: String,pub parent_id: Option<Uuid>,  // 父团队ID,None表示顶级团队pub member_ids: Vec<Uuid>     // 团队成员ID列表
}/// 成员数据结构
#[derive(Serialize, Deserialize, Clone, Validate, Debug)]
pub struct Member {pub id: Uuid,#[validate(length(min = 2, max = 50, message = "姓名长度2-50字符"))]pub name: String,#[validate(email(message = "请输入有效邮箱"))]pub email: String,pub position: String,#[validate(length(min = 1, message = "必须选择部门"))]pub department: String,pub join_date: String  // 简化处理,使用字符串格式
}/// 部门枚举
#[derive(Debug)]
pub enum Department {Engineering,Marketing,Hr,Finance
}impl Department {/// 获取所有部门列表(本地化显示)pub fn all() -> Vec<&'static str> {vec!["技术部", "市场部", "人力资源", "财务部"]}
}
  1. 数据存储 (src/storage.rs)
use gloo_storage::{LocalStorage, Storage};
use crate::models::{Team, Member};
use uuid::Uuid;// 本地存储键名常量
const TEAMS_KEY: &str = "team_manager_teams";
const MEMBERS_KEY: &str = "team_manager_members";/// 团队数据存储操作封装
pub struct TeamStorage;impl TeamStorage {/// 加载所有团队数据pub fn load_teams() -> Vec<Team> {LocalStorage::get(TEAMS_KEY).unwrap_or_default()}/// 保存所有团队数据pub fn save_teams(teams: &[Team]) -> Result<(), gloo_storage::errors::StorageError> {LocalStorage::set(TEAMS_KEY, teams)}/// 加载所有成员数据pub fn load_members() -> Vec<Member> {LocalStorage::get(MEMBERS_KEY).unwrap_or_default()}/// 保存所有成员数据pub fn save_members(members: &[Member]) -> Result<(), gloo_storage::errors::StorageError> {LocalStorage::set(MEMBERS_KEY, members)}
}
  1. 主应用逻辑 (src/lib.rs)
mod models;
mod storage;use yew::prelude::*;
use uuid::Uuid;
use validator::Validate;
use web_sys::HtmlInputElement;
use crate::{models::{Member, Team, Department},storage::TeamStorage
};/// 应用状态
struct App {teams: Vec<Team>,members: Vec<Member>,form: MemberForm,search_query: String,selected_team: Option<Uuid>,editing_member: Option<Uuid>
}/// 成员表单数据
#[derive(Default, Validate)]
struct MemberForm {#[validate(length(min = 2, max = 50))]name: String,#[validate(email)]email: String,department: String,position: String,join_date: String
}/// 应用消息枚举
enum Msg {AddMember,EditMember(Uuid),DeleteMember(Uuid),UpdateName(String),UpdateEmail(String),UpdateDepartment(String),UpdatePosition(String),UpdateJoinDate(String),Search(String),SelectTeam(Uuid),SaveAll,CancelEdit
}impl Component for App {type Message = Msg;type Properties = ();fn create(_ctx: &Context<Self>) -> Self {Self {teams: TeamStorage::load_teams(),members: TeamStorage::load_members(),form: MemberForm::default(),search_query: String::new(),selected_team: None,editing_member: None}}fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {match msg {Msg::AddMember => {if self.form.validate().is_err() {return false;}let member = Member {id: Uuid::new_v4(),name: self.form.name.clone(),email: self.form.email.clone(),department: self.form.department.clone(),position: self.form.position.clone(),join_date: self.form.join_date.clone()};self.members.push(member);TeamStorage::save_members(&self.members).unwrap();self.form = MemberForm::default();  // 重置表单true}Msg::EditMember(id) => {if let Some(member) = self.members.iter().find(|m| m.id == id) {self.form.name = member.name.clone();self.form.email = member.email.clone();self.form.department = member.department.clone();self.form.position = member.position.clone();self.form.join_date = member.join_date.clone();self.editing_member = Some(id);}true}Msg::DeleteMember(id) => {self.members.retain(|m| m.id != id);TeamStorage::save_members(&self.members).unwrap();true}// 其他消息处理..._ => false}}fn view(&self, ctx: &Context<Self>) -> Html {html! {<div class="app"><TeamTree teams={self.teams.clone()} on_select={ctx.link().callback(Msg::SelectTeam)} /><MemberForm form={self.form.clone()}is_editing={self.editing_member.is_some()}on_submit={ctx.link().callback(|_| Msg::AddMember)}on_cancel={ctx.link().callback(|_| Msg::CancelEdit)}on_name_change={ctx.link().callback(Msg::UpdateName)}on_email_change={ctx.link().callback(Msg::UpdateEmail)}on_department_change={ctx.link().callback(Msg::UpdateDepartment)}on_position_change={ctx.link().callback(Msg::UpdatePosition)}on_join_date_change={ctx.link().callback(Msg::UpdateJoinDate)}/><MemberList members={self.filtered_members()}on_edit={ctx.link().callback(Msg::EditMember)}on_delete={ctx.link().callback(Msg::DeleteMember)}/></div>}}
}/// 团队树组件
#[derive(Properties, PartialEq)]
struct TeamTreeProps {teams: Vec<Team>,on_select: Callback<Uuid>
}#[function_component]
fn TeamTree(props: &TeamTreeProps) -> Html {// 团队树渲染逻辑html! {<div class="team-tree"><h2>{"团队结构"}</h2><ul>{for props.teams.iter().map(|team| {html! {<li key={team.id.to_string()}><button onclick={props.on_select.reform(move |_| team.id)}>{&team.name}</button></li>}})}</ul></div>}
}/// 成员表单组件
#[derive(Properties, PartialEq)]
struct MemberFormProps {form: MemberForm,is_editing: bool,on_submit: Callback<()>,on_cancel: Callback<()>,on_name_change: Callback<String>,on_email_change: Callback<String>,on_department_change: Callback<String>,on_position_change: Callback<String>,on_join_date_change: Callback<String>,
}#[function_component]
fn MemberForm(props: &MemberFormProps) -> Html {// 表单渲染逻辑html! {<div class="member-form"><h2>{if props.is_editing { "编辑成员" } else { "添加成员" }}</h2><form onsubmit={props.on_submit.reform(|e: SubmitEvent| e.prevent_default())}>{/* 表单字段实现... */}<div class="form-actions"><button type="submit">{"保存"}</button>{if props.is_ing {<button type="button" onclick={props.on_cancel.reform(|_| ())}>{"取消"}</button>}}</div></form></div>}
}/// 成员列表组件
#[derive(Properties, PartialEq)]
struct MemberListProps {members: Vec<Member>,on_edit: Callback<Uuid>,on_delete: Callback<Uuid>,
}#[function_component]
fn MemberList(props: &MemberListProps) -> Html {html! {<div class="member-list"><h2>{"成员列表"}</h2><table><thead><tr><th>{"姓名"}</th><th>{"邮箱"}</th><th>{"部门"}</th><th>{"职位"}</th><th>{"操作"}</th></tr></thead><tbody>{for props.members.iter().map(|member| {html! {<tr key={member.id.to_string()}><td>{&member.name}</td><td>{&member.email}</td><td>{&member.department}</td><td>{&member.position}</td><td><button onclick={props.on_edit.reform(move |_| member.id)}>{"编辑"}</button><button onclick={props.on_delete.reform(move |_| member.id)}>{"删除"}</button></td></tr>}})}</tbody></table></div>}
}/// 应用入口
fn main() {yew::Renderer::<App>::new().render();
}
  1. 样式表 (src/main.css)
/* 全局变量 */
:root {--primary-color: #4CAF50;--error-color: #f44336;--border-color: #ddd;--hover-color: #f5f5f5;
}/* 主布局 */
.app {display: grid;grid-template-columns: 300px 1fr;gap: 20px;max-width: 1200px;margin: 0 auto;padding: 20px;font-family: Arial, sans-serif;
}/* 表单样式 */
.member-form {background: white;padding: 20px;border-radius: 5px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}.member-form input, 
.member-form select {width: 100%;padding: 8px;margin: 5px 0 15px;border: 1px solid var(--border-color);border-radius: 4px;
}.member-form button {background-color: var(--primary-color);color: white;padding: 10px 15px;border: none;border-radius: 4px;cursor: pointer;margin-right: 10px;
}/* 错误提示 */
.form-error {color: var(--error-color);font-size: 0.8em;margin-top: -10px;margin-bottom: 10px;
}/* 团队树 */
.team-tree {background: white;padding: 20px;border-radius: 5px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}.team-tree ul {list-style: none;padding-left: 0;
}.team-tree li {margin: 5px 0;
}.team-tree button {background: none;border: none;cursor: pointer;text-align: left;width: 100%;padding: 8px;
}.team-tree button:hover {background-color: var(--hover-color);
}/* 成员列表 */
.member-list {background: white;padding: 20px;border-radius: 5px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);grid-column: span 2;
}.member-list table {width: 100%;border-collapse: collapse;
}.member-list th, 
.member-list td {padding: 12px;text-align: left;border-bottom: 1px solid var(--border-color);
}.member-list tr:hover {background-color: var(--hover-color);
}.member-list button {padding: 5px 10px;margin-right: 5px;cursor: pointer;
}/* 响应式设计 */
@media (max-width: 768px) {.app {grid-template-columns: 1fr;}.team-tree {margin-bottom: 20px;}
}
  1. 入口HTML (index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>团队管理系统</title><link data-trunk rel="copy-dir" href="assets/"><link data-trunk rel="css" href="src/main.css">
</head>
<body><!-- WASM加载脚本 --><script type="module">import init from "/wasm.js";init();</script><!-- 加载动画 --><div id="loading" style="position: fixed;top: 0;left: 0;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background: white;z-index: 9999;"><div>加载中...</div></div><!-- 应用挂载点 --><div id="app"></div><script>// WASM加载完成后隐藏加载动画window.addEventListener('load', () => {document.getElementById('loading').style.display = 'none';});</script>
</body>
</html>
  1. WASM优化配置 (.cargo/config.toml)
[target.wasm32-unknown-unknown]
# 优化级别
rustflags = ["-C", "opt-level=z",       # 最小体积优化"-C", "link-arg=-s",       # 压缩输出"-C", "link-arg=-O3",      # 高级优化"-C", "link-arg=--stack-first"  # 优化内存布局
][build]
# 默认编译目标
target = "wasm32-unknown-unknown"

开发与构建

  1. 开发模式
# 启动开发服务器并自动打开浏览器
trunk serve --open# 或者指定端口
trunk serve --port 3000 --open

开发服务器支持热重载,代码修改后会自动重新编译并刷新浏览器。

  1. 生产构建
# 设置优化标志
export RUSTFLAGS="-C opt-level=z"# 执行生产构建
trunk build --release

构建产物将输出到 dist 目录,包含:

  • 优化后的WASM二进制

  • 压缩的JavaScript胶水代码

  • 处理后的HTML和CSS

  1. 测试与质量保证
# 运行单元测试
cargo test# WASM测试覆盖率(需要tarpaulin)
cargo tarpaulin --target wasm32-unknown-unknown# 代码格式化检查
cargo fmt -- --check# 代码lint检查
cargo clippy --target wasm32-unknown-unknown

功能特点详解

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

    • 表单验证确保数据完整性

    • 自动生成唯一ID

    • 即时保存到本地存储

  • 编辑成员:

    • 双击成员或点击编辑按钮进入编辑模式

    • 保留原始数据直到确认修改

    • 实时验证编辑内容

  • 删除成员:

    • 确认对话框防止误删

    • 自动从所属团队中移除

    • 即时更新存储

  1. 团队管理
  • 层级结构:

    • 可视化树形展示

    • 支持拖拽调整团队关系(待实现)

    • 自动维护父子关系

  • 成员分配:

    • 拖拽成员到团队(待实现)

    • 批量操作支持

    • 成员计数显示

  1. 数据持久化
  • 自动保存:

    • 任何修改都会立即保存

    • 使用localStorage作为后端

    • 可扩展为IndexedDB或远程API

  • 数据恢复:

    • 启动时自动加载

    • 版本兼容性处理

    • 损坏数据检测

  1. 搜索功能
  • 多字段搜索:

    • 姓名、邮箱、部门、职位联合搜索

    • 实时显示结果

    • 高亮匹配内容

  • 筛选器:

    • 按团队筛选

    • 按部门筛选

    • 组合条件查询

  1. 表单验证
  • 即时反馈:

    • 输入时实时验证

    • 明确错误提示

    • 防止无效提交

  • 验证规则:

    • 必填字段

    • 邮箱格式

    • 长度限制

    • 自定义规则

  1. 响应式设计
  • 布局适应:

    • 桌面端多列布局

    • 移动端单列布局

    • 可调节的侧边栏

  • 交互优化:

    • 触摸友好的控件

    • 适当的点击区域

    • 键盘导航支持

部署指南

静态服务器部署
  1. 构建生产版本:
trunk build --release
  1. 将 dist 目录内容上传到任何静态文件服务器:
  • Nginx

  • Apache

  • GitHub Pages

  • Netlify/Vercel

Docker部署
  1. 创建Dockerfile:
FROM nginx:alpine
COPY dist /usr/share/nginx/html
EXPOSE 80
  1. 构建并运行:
docker build -t team-manager .
docker run -p 8080:80 team-manager

未来扩展

  1. 后端集成:
  • REST API支持

  • 用户认证

  • 数据同步

  1. 高级功能:
  • 团队权限管理

  • 成员导入/导出

  • 数据统计图表

  1. 性能优化:
  • 虚拟滚动长列表

  • WASM多线程

  • 代码分割

这个完善的团队管理系统展示了如何使用Rust和Yew框架构建功能丰富的Web应用,具有类型安全、高性能和良好的用户体验。


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

相关文章:

  • 2.1 基于委托的异步编程方法
  • 2020 年 7 月大学英语四级考试真题(组合卷)——解析版
  • 计算机视觉cv2入门之视频处理
  • 硬件工程师笔记——电子器件汇总大全
  • AI书籍大模型微调-基于亮数据获取垂直数据集
  • 【Rust 精进之路之第11篇-借用·实践】切片 (Slices):安全、高效地引用集合的一部分
  • 车载测试用例开发-如何平衡用例覆盖度和测试效率的方法论
  • Linux学习——TCP
  • 【Flutter】使用LiveKit和Flutter构建实时视频聊天应用
  • LicheeRV Nano 与Ubuntu官方risc-v 镜像混合
  • [OpenGL]使用OpenGL实现基于物理的渲染模型PBR(下)
  • kotlin知识体系(六) : Flow核心概念与与操作符指南
  • (mac)Grafana监控系统之监控Linux的Redis
  • 【Rust 精进之路之第13篇-生命周期·进阶】省略规则与静态生命周期 (`‘static`)
  • 【SpringBoot】99、SpringBoot中整合RabbitMQ实现重试功能
  • Linux 生产者消费者模型
  • (done) 吴恩达版提示词工程 1. 引言 (Base LLM 和 Instruction Tuned LLM)
  • C++:详解命名空间
  • 【Rust 精进之路之第14篇-结构体 Struct】定义、实例化与方法:封装数据与行为
  • 【TeamFlow】4 团队人员管理系统的实现