feat(config): add system configuration module (Phase 3)
Implement the complete erp-config crate with: - Data dictionaries (CRUD + items management) - Dynamic menus (tree structure with role filtering) - System settings (hierarchical: platform > tenant > org > user) - Numbering rules (concurrency-safe via PostgreSQL advisory_lock) - Theme and language configuration (via settings store) - 6 database migrations (dictionaries, menus, settings, numbering_rules) - Frontend Settings page with 5 tabs (dictionary, menu, numbering, settings, theme) Refactor: move RBAC functions (require_permission) from erp-auth to erp-core to avoid cross-module dependencies. Add 20 new seed permissions for config module operations.
This commit is contained in:
101
crates/erp-config/src/handler/language_handler.rs
Normal file
101
crates/erp-config/src/handler/language_handler.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, State};
|
||||
use axum::response::Json as JsonResponse;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, Pagination, TenantContext};
|
||||
|
||||
use crate::config_state::ConfigState;
|
||||
use crate::dto::{LanguageResp, UpdateLanguageReq};
|
||||
use crate::service::setting_service::SettingService;
|
||||
|
||||
/// GET /api/v1/languages
|
||||
///
|
||||
/// 获取当前租户的语言配置列表。
|
||||
/// 查询 scope 为 "platform" 的设置,过滤 key 以 "language." 开头的记录。
|
||||
/// 需要 `language.list` 权限。
|
||||
pub async fn list_languages<S>(
|
||||
State(state): State<ConfigState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
) -> Result<JsonResponse<ApiResponse<Vec<LanguageResp>>>, AppError>
|
||||
where
|
||||
ConfigState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "language.list")?;
|
||||
|
||||
let pagination = Pagination {
|
||||
page: Some(1),
|
||||
page_size: Some(100),
|
||||
};
|
||||
|
||||
let (settings, _total) = SettingService::list_by_scope(
|
||||
"platform",
|
||||
&None,
|
||||
ctx.tenant_id,
|
||||
&pagination,
|
||||
&state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let languages: Vec<LanguageResp> = settings
|
||||
.into_iter()
|
||||
.filter(|s| s.setting_key.starts_with("language."))
|
||||
.filter_map(|s| {
|
||||
let code = s.setting_key.strip_prefix("language.")?.to_string();
|
||||
let name = code.clone(); // 默认使用 code 作为名称
|
||||
let is_active = s
|
||||
.setting_value
|
||||
.get("is_active")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(true);
|
||||
Some(LanguageResp {
|
||||
code,
|
||||
name,
|
||||
is_active,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(JsonResponse(ApiResponse::ok(languages)))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/languages/:code
|
||||
///
|
||||
/// 更新指定语言配置的激活状态。
|
||||
/// 语言配置存储在 settings 表中,key 为 "language.{code}",scope 为 "platform"。
|
||||
/// 需要 `language.update` 权限。
|
||||
pub async fn update_language<S>(
|
||||
State(state): State<ConfigState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(code): Path<String>,
|
||||
Json(req): Json<UpdateLanguageReq>,
|
||||
) -> Result<JsonResponse<ApiResponse<LanguageResp>>, AppError>
|
||||
where
|
||||
ConfigState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "language.update")?;
|
||||
|
||||
let key = format!("language.{}", code);
|
||||
let value = serde_json::json!({"is_active": req.is_active});
|
||||
|
||||
SettingService::set(
|
||||
&key,
|
||||
"platform",
|
||||
&None,
|
||||
value,
|
||||
ctx.tenant_id,
|
||||
ctx.user_id,
|
||||
&state.db,
|
||||
&state.event_bus,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(JsonResponse(ApiResponse::ok(LanguageResp {
|
||||
code,
|
||||
name: String::new(),
|
||||
is_active: req.is_active,
|
||||
})))
|
||||
}
|
||||
Reference in New Issue
Block a user