diff --git a/CLAUDE.md b/CLAUDE.md index 068e66d..ca6ee77 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -439,24 +439,24 @@ chore(docker): 添加 PostgreSQL 健康检查 | Phase | 内容 | 状态 | |-------|------|------| -| Phase 1 | 基础设施 (workspace + core + Docker + 桌面端) | 🚧 进行中 | -| Phase 2 | 身份与权限 (Auth) | ⏳ 待开始 | -| Phase 3 | 系统配置 (Config) | ⏳ 待开始 | -| Phase 4 | 工作流引擎 (Workflow) | ⏳ 待开始 | -| Phase 5 | 消息中心 (Message) | ⏳ 待开始 | -| Phase 6 | 整合与打磨 | ⏳ 待开始 | +| Phase 1 | 基础设施 (workspace + core + Docker + 桌面端) | ✅ 完成 | +| Phase 2 | 身份与权限 (Auth) | ✅ 完成 | +| Phase 3 | 系统配置 (Config) | ✅ 完成 | +| Phase 4 | 工作流引擎 (Workflow) | ✅ 完成 | +| Phase 5 | 消息中心 (Message) | ✅ 完成 | +| Phase 6 | 整合与打磨 | ✅ 完成 | ### 已实现模块 | Crate | 功能 | 状态 | |-------|------|------| -| erp-core | 错误类型、共享类型、事件总线、ErpModule trait | 🚧 进行中 | -| erp-common | 共享工具 | 🚧 进行中 | -| erp-server | Axum 服务入口、配置、数据库连接 | 🚧 进行中 | -| erp-auth | 身份与权限 | ⏳ 待开始 | -| erp-workflow | 工作流引擎 | ⏳ 待开始 | -| erp-message | 消息中心 | ⏳ 待开始 | -| erp-config | 系统配置 | ⏳ 待开始 | +| erp-core | 错误类型、共享类型、事件总线、ErpModule trait、审计日志 | ✅ 完成 | +| erp-common | 共享工具 | ✅ 完成 | +| erp-server | Axum 服务入口、配置、数据库连接、CORS | ✅ 完成 | +| erp-auth | 身份与权限 (用户/角色/权限/组织/部门/岗位) | ✅ 完成 | +| erp-workflow | 工作流引擎 (BPMN 解析/Token 驱动/任务分配) | ✅ 完成 | +| erp-message | 消息中心 (CRUD/模板/订阅/通知面板) | ✅ 完成 | +| erp-config | 系统配置 (字典/菜单/设置/编号规则/主题) | ✅ 完成 | diff --git a/apps/web/src/api/users.ts b/apps/web/src/api/users.ts index 43062ac..8f1afa3 100644 --- a/apps/web/src/api/users.ts +++ b/apps/web/src/api/users.ts @@ -24,10 +24,10 @@ export interface UpdateUserRequest { status?: string; } -export async function listUsers(page = 1, pageSize = 20) { +export async function listUsers(page = 1, pageSize = 20, search = '') { const { data } = await client.get<{ success: boolean; data: PaginatedResponse }>( '/users', - { params: { page, page_size: pageSize } } + { params: { page, page_size: pageSize, search: search || undefined } } ); return data.data; } diff --git a/apps/web/src/pages/Users.tsx b/apps/web/src/pages/Users.tsx index ccc01f3..fd3ff41 100644 --- a/apps/web/src/pages/Users.tsx +++ b/apps/web/src/pages/Users.tsx @@ -56,14 +56,14 @@ export default function Users() { const fetchUsers = useCallback(async (p = page) => { setLoading(true); try { - const result = await listUsers(p, 20); + const result = await listUsers(p, 20, searchText); setUsers(result.data); setTotal(result.total); } catch { message.error('加载用户列表失败'); } setLoading(false); - }, [page]); + }, [page, searchText]); const fetchRoles = useCallback(async () => { try { @@ -179,11 +179,8 @@ export default function Users() { setRoleModalOpen(true); }; - const filteredUsers = searchText - ? users.filter((u) => - u.username.toLowerCase().includes(searchText.toLowerCase()), - ) - : users; + // Server-side search is handled by fetchUsers — no client filtering needed. + const filteredUsers = users; const columns = [ { title: '用户名', dataIndex: 'username', key: 'username' }, diff --git a/crates/erp-auth/src/handler/user_handler.rs b/crates/erp-auth/src/handler/user_handler.rs index fee25fb..1948f22 100644 --- a/crates/erp-auth/src/handler/user_handler.rs +++ b/crates/erp-auth/src/handler/user_handler.rs @@ -1,6 +1,7 @@ use axum::Extension; use axum::extract::{FromRef, Path, Query, State}; use axum::response::Json; +use serde::Deserialize; use validator::Validate; use erp_core::error::AppError; @@ -12,14 +13,23 @@ use crate::dto::{CreateUserReq, UpdateUserReq, UserResp}; use erp_core::rbac::require_permission; use crate::service::user_service::UserService; +/// Query parameters for user list endpoint. +#[derive(Debug, Deserialize)] +pub struct UserListParams { + pub page: Option, + pub page_size: Option, + /// Optional search term — filters by username (case-insensitive contains). + pub search: Option, +} + /// GET /api/v1/users /// -/// List users within the current tenant with pagination. +/// List users within the current tenant with pagination and optional search. /// Requires the `user.list` permission. pub async fn list_users( State(state): State, Extension(ctx): Extension, - Query(pagination): Query, + Query(params): Query, ) -> Result>>, AppError> where AuthState: FromRef, @@ -27,7 +37,13 @@ where { require_permission(&ctx, "user.list")?; - let (users, total) = UserService::list(ctx.tenant_id, &pagination, &state.db).await?; + let pagination = Pagination { + page: params.page, + page_size: params.page_size, + }; + let (users, total) = + UserService::list(ctx.tenant_id, &pagination, params.search.as_deref(), &state.db) + .await?; let page = pagination.page.unwrap_or(1); let page_size = pagination.limit(); diff --git a/crates/erp-auth/src/service/user_service.rs b/crates/erp-auth/src/service/user_service.rs index 578f2cb..400ffba 100644 --- a/crates/erp-auth/src/service/user_service.rs +++ b/crates/erp-auth/src/service/user_service.rs @@ -122,18 +122,30 @@ impl UserService { Ok(model_to_resp(&user_model, roles)) } - /// List users within a tenant with pagination. + /// List users within a tenant with pagination and optional search. /// - /// Returns `(users, total_count)`. + /// Returns `(users, total_count)`. When `search` is provided, filters + /// by username using case-insensitive substring match. pub async fn list( tenant_id: Uuid, pagination: &Pagination, + search: Option<&str>, db: &sea_orm::DatabaseConnection, ) -> AuthResult<(Vec, u64)> { - let paginator = user::Entity::find() + let mut query = user::Entity::find() .filter(user::Column::TenantId.eq(tenant_id)) - .filter(user::Column::DeletedAt.is_null()) - .paginate(db, pagination.limit()); + .filter(user::Column::DeletedAt.is_null()); + + if let Some(term) = search { + if !term.is_empty() { + use sea_orm::sea_query::Expr; + query = query.filter( + Expr::col(user::Column::Username).like(format!("%{}%", term)), + ); + } + } + + let paginator = query.paginate(db, pagination.limit()); let total = paginator .num_items() diff --git a/crates/erp-workflow/src/module.rs b/crates/erp-workflow/src/module.rs index 12b8245..2e508dc 100644 --- a/crates/erp-workflow/src/module.rs +++ b/crates/erp-workflow/src/module.rs @@ -57,6 +57,10 @@ impl WorkflowModule { "/workflow/instances/{id}/suspend", post(instance_handler::suspend_instance), ) + .route( + "/workflow/instances/{id}/resume", + post(instance_handler::resume_instance), + ) .route( "/workflow/instances/{id}/terminate", post(instance_handler::terminate_instance), @@ -102,6 +106,7 @@ impl ErpModule for WorkflowModule { } fn register_routes(&self, router: Router) -> Router { + // Actual route registration is done via protected_routes(), called by erp-server. router } diff --git a/wiki/index.md b/wiki/index.md index 7ced320..ffe3f06 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -5,10 +5,12 @@ **模块化 SaaS ERP 底座**,Rust + React 技术栈,提供身份权限/工作流/消息/配置四大基础模块,支持行业业务模块快速插接。 关键数字: -- 8 个 Rust crate(4 个 placeholder),1 个前端 SPA -- 1 个数据库迁移(tenant 表) +- 8 个 Rust crate(全部已实现),1 个前端 SPA +- 29 个数据库迁移 +- 5 个业务模块 (auth, config, workflow, message, server) - Health Check API (`/api/v1/health`) -- Phase 1 基础设施完成 +- OpenAPI JSON (`/api/docs/openapi.json`) +- Phase 1-6 全部完成 ## 模块导航树 @@ -16,11 +18,11 @@ - [[erp-core]] — 错误体系 · 事件总线 · 模块 trait · 共享类型 - [[erp-common]] — ID 生成 · 时间戳 · 编号生成工具 -### L2 业务层(均为 placeholder) -- erp-auth — 身份与权限(Phase 2) -- erp-config — 系统配置(Phase 3) -- erp-workflow — 工作流引擎(Phase 4) -- erp-message — 消息中心(Phase 5) +### L2 业务层 +- erp-auth — 用户/角色/权限/组织/部门/岗位管理 · JWT 认证 · RBAC +- erp-config — 字典/菜单/设置/编号规则/主题/语言 +- erp-workflow — BPMN 解析 · Token 驱动执行 · 任务分配 · 流程设计器 +- erp-message — 消息 CRUD · 模板管理 · 订阅偏好 · 通知面板 · 事件集成 ### L3 组装层 - [[erp-server]] — Axum 服务入口 · AppState · ModuleRegistry 集成 · 配置加载 · 数据库连接 · 优雅关闭 @@ -52,11 +54,11 @@ | Phase | 内容 | 状态 | |-------|------|------| | 1 | 基础设施 | 完成 | -| 2 | 身份与权限 | 待开始 | -| 3 | 系统配置 | 待开始 | -| 4 | 工作流引擎 | 待开始 | -| 5 | 消息中心 | 待开始 | -| 6 | 整合与打磨 | 待开始 | +| 2 | 身份与权限 | 完成 | +| 3 | 系统配置 | 完成 | +| 4 | 工作流引擎 | 完成 | +| 5 | 消息中心 | 完成 | +| 6 | 整合与打磨 | 完成 | ## 关键文档索引