use axum::Extension; use axum::extract::{FromRef, Json, Path, State}; use axum::response::Json as JsonResponse; use validator::Validate; use erp_core::error::AppError; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, TenantContext}; use uuid::Uuid; use crate::config_state::ConfigState; use crate::dto::{BatchSaveMenusReq, CreateMenuReq, MenuResp}; use crate::service::menu_service::MenuService; /// GET /api/v1/config/menus /// /// 获取当前租户下当前用户角色可见的菜单树。 pub async fn get_menus( State(state): State, Extension(ctx): Extension, ) -> Result>>, AppError> where ConfigState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "menu.list")?; let role_ids: Vec = ctx .roles .iter() .filter_map(|r| Uuid::parse_str(r).ok()) .collect(); let menus = MenuService::get_menu_tree(ctx.tenant_id, &role_ids, &state.db).await?; Ok(JsonResponse(ApiResponse::ok(menus))) } /// POST /api/v1/config/menus /// /// 创建单个菜单项。 pub async fn create_menu( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where ConfigState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "menu.update")?; req.validate() .map_err(|e| AppError::Validation(e.to_string()))?; let resp = MenuService::create( ctx.tenant_id, ctx.user_id, &req, &state.db, &state.event_bus, ) .await?; Ok(JsonResponse(ApiResponse::ok(resp))) } /// PUT /api/v1/config/menus/{id} /// /// 更新单个菜单项。 pub async fn update_menu( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where ConfigState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "menu.update")?; let resp = MenuService::update(id, ctx.tenant_id, ctx.user_id, &req, &state.db).await?; Ok(JsonResponse(ApiResponse::ok(resp))) } /// DELETE /api/v1/config/menus/{id} /// /// 软删除单个菜单项。需要请求体包含 version 字段用于乐观锁校验。 pub async fn delete_menu( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where ConfigState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "menu.update")?; MenuService::delete( id, ctx.tenant_id, ctx.user_id, req.version, &state.db, &state.event_bus, ) .await?; Ok(JsonResponse(ApiResponse::ok(()))) } /// PUT /api/v1/config/menus/batch /// /// 批量保存菜单列表。 pub async fn batch_save_menus( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where ConfigState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "menu.update")?; req.validate() .map_err(|e| AppError::Validation(e.to_string()))?; for item in &req.menus { match item.id { Some(id) => { let version = item.version.unwrap_or(0); let update_req = crate::dto::UpdateMenuReq { title: Some(item.title.clone()), path: item.path.clone(), icon: item.icon.clone(), sort_order: item.sort_order, visible: item.visible, permission: item.permission.clone(), role_ids: item.role_ids.clone(), version, }; MenuService::update(id, ctx.tenant_id, ctx.user_id, &update_req, &state.db).await?; } None => { let create_req = CreateMenuReq { parent_id: item.parent_id, title: item.title.clone(), path: item.path.clone(), icon: item.icon.clone(), sort_order: item.sort_order, visible: item.visible, menu_type: item.menu_type.clone(), permission: item.permission.clone(), role_ids: item.role_ids.clone(), }; MenuService::create( ctx.tenant_id, ctx.user_id, &create_req, &state.db, &state.event_bus, ) .await?; } } } Ok(JsonResponse(ApiResponse { success: true, data: None, message: Some("菜单批量保存成功".to_string()), })) } /// 删除菜单的乐观锁版本号请求体。 #[derive(Debug, serde::Deserialize)] pub struct DeleteMenuVersionReq { pub version: i32, }