chore: apply cargo fmt across workspace and update docs

- Run cargo fmt on all Rust crates for consistent formatting
- Update CLAUDE.md with WASM plugin commands and dev.ps1 instructions
- Update wiki: add WASM plugin architecture, rewrite dev environment docs
- Minor frontend cleanup (unused imports)
This commit is contained in:
iven
2026-04-15 00:49:20 +08:00
parent e16c1a85d7
commit 9568dd7875
113 changed files with 4355 additions and 937 deletions

View File

@@ -2,5 +2,5 @@ pub mod dictionary;
pub mod dictionary_item;
pub mod menu;
pub mod menu_role;
pub mod setting;
pub mod numbering_rule;
pub mod setting;

View File

@@ -22,9 +22,7 @@ pub enum ConfigError {
impl From<sea_orm::TransactionError<ConfigError>> for ConfigError {
fn from(err: sea_orm::TransactionError<ConfigError>) -> Self {
match err {
sea_orm::TransactionError::Connection(err) => {
ConfigError::Validation(err.to_string())
}
sea_orm::TransactionError::Connection(err) => ConfigError::Validation(err.to_string()),
sea_orm::TransactionError::Transaction(inner) => inner,
}
}

View File

@@ -97,14 +97,8 @@ where
{
require_permission(&ctx, "dictionary.update")?;
let dictionary = DictionaryService::update(
id,
ctx.tenant_id,
ctx.user_id,
&req,
&state.db,
)
.await?;
let dictionary =
DictionaryService::update(id, ctx.tenant_id, ctx.user_id, &req, &state.db).await?;
Ok(Json(ApiResponse::ok(dictionary)))
}
@@ -185,14 +179,8 @@ where
req.validate()
.map_err(|e| AppError::Validation(e.to_string()))?;
let item = DictionaryService::add_item(
dict_id,
ctx.tenant_id,
ctx.user_id,
&req,
&state.db,
)
.await?;
let item =
DictionaryService::add_item(dict_id, ctx.tenant_id, ctx.user_id, &req, &state.db).await?;
Ok(Json(ApiResponse::ok(item)))
}
@@ -214,20 +202,12 @@ where
require_permission(&ctx, "dictionary.update")?;
// 验证 item_id 属于 dict_id
let item = DictionaryService::update_item(
item_id,
ctx.tenant_id,
ctx.user_id,
&req,
&state.db,
)
.await?;
let item = DictionaryService::update_item(item_id, ctx.tenant_id, ctx.user_id, &req, &state.db)
.await?;
// 确保 item 属于指定的 dictionary
if item.dictionary_id != dict_id {
return Err(AppError::Validation(
"字典项不属于指定的字典".to_string(),
));
return Err(AppError::Validation("字典项不属于指定的字典".to_string()));
}
Ok(Json(ApiResponse::ok(item)))

View File

@@ -30,14 +30,9 @@ where
page_size: Some(100),
};
let (settings, _total) = SettingService::list_by_scope(
"platform",
&None,
ctx.tenant_id,
&pagination,
&state.db,
)
.await?;
let (settings, _total) =
SettingService::list_by_scope("platform", &None, ctx.tenant_id, &pagination, &state.db)
.await?;
let languages: Vec<LanguageResp> = settings
.into_iter()
@@ -83,7 +78,7 @@ where
SettingService::set(
SetSettingParams {
key,
key: key.clone(),
scope: "platform".to_string(),
scope_id: None,
value,
@@ -96,9 +91,20 @@ where
)
.await?;
// 从返回的 SettingResp 中读取实际值
let updated = SettingService::get(&key, "platform", &None, ctx.tenant_id, &state.db).await?;
// 尝试从 value 中提取 name否则用 code 作为默认名称
let name = updated
.setting_value
.get("name")
.and_then(|v| v.as_str())
.unwrap_or(&code)
.to_string();
Ok(JsonResponse(ApiResponse::ok(LanguageResp {
code,
name: String::new(),
name,
is_active: req.is_active,
})))
}

View File

@@ -142,8 +142,7 @@ where
role_ids: item.role_ids.clone(),
version,
};
MenuService::update(id, ctx.tenant_id, ctx.user_id, &update_req, &state.db)
.await?;
MenuService::update(id, ctx.tenant_id, ctx.user_id, &update_req, &state.db).await?;
}
None => {
let create_req = CreateMenuReq {

View File

@@ -91,8 +91,7 @@ where
{
require_permission(&ctx, "numbering.update")?;
let rule =
NumberingService::update(id, ctx.tenant_id, ctx.user_id, &req, &state.db).await?;
let rule = NumberingService::update(id, ctx.tenant_id, ctx.user_id, &req, &state.db).await?;
Ok(Json(ApiResponse::ok(rule)))
}

View File

@@ -25,8 +25,7 @@ where
{
require_permission(&ctx, "theme.read")?;
let setting =
SettingService::get("theme", "tenant", &None, ctx.tenant_id, &state.db).await?;
let setting = SettingService::get("theme", "tenant", &None, ctx.tenant_id, &state.db).await?;
let theme: ThemeResp = serde_json::from_value(setting.setting_value)
.map_err(|e| AppError::Validation(format!("主题配置解析失败: {e}")))?;

View File

@@ -50,8 +50,7 @@ impl ConfigModule {
)
.route(
"/config/dictionaries/{dict_id}/items/{item_id}",
put(dictionary_handler::update_item)
.delete(dictionary_handler::delete_item),
put(dictionary_handler::update_item).delete(dictionary_handler::delete_item),
)
// Menu routes
.route(
@@ -62,8 +61,7 @@ impl ConfigModule {
)
.route(
"/config/menus/{id}",
put(menu_handler::update_menu)
.delete(menu_handler::delete_menu),
put(menu_handler::update_menu).delete(menu_handler::delete_menu),
)
// Setting routes
.route(
@@ -93,10 +91,7 @@ impl ConfigModule {
get(theme_handler::get_theme).put(theme_handler::update_theme),
)
// Language routes
.route(
"/config/languages",
get(language_handler::list_languages),
)
.route("/config/languages", get(language_handler::list_languages))
.route(
"/config/languages/{code}",
put(language_handler::update_language),

View File

@@ -1,7 +1,5 @@
use chrono::Utc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set};
use uuid::Uuid;
use crate::dto::{DictionaryItemResp, DictionaryResp};
@@ -133,15 +131,25 @@ impl DictionaryService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"dictionary.created",
tenant_id,
serde_json::json!({ "dictionary_id": id, "code": code }),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"dictionary.created",
tenant_id,
serde_json::json!({ "dictionary_id": id, "code": code }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "dictionary.create", "dictionary")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"dictionary.create",
"dictionary",
)
.with_resource_id(id),
db,
)
.await;
@@ -198,8 +206,13 @@ impl DictionaryService {
let items = Self::fetch_items(updated.id, tenant_id, db).await?;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "dictionary.update", "dictionary")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"dictionary.update",
"dictionary",
)
.with_resource_id(id),
db,
)
.await;
@@ -244,15 +257,25 @@ impl DictionaryService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"dictionary.deleted",
tenant_id,
serde_json::json!({ "dictionary_id": id }),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"dictionary.deleted",
tenant_id,
serde_json::json!({ "dictionary_id": id }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "dictionary.delete", "dictionary")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"dictionary.delete",
"dictionary",
)
.with_resource_id(id),
db,
)
.await;
@@ -315,8 +338,13 @@ impl DictionaryService {
.map_err(|e| ConfigError::Validation(e.to_string()))?;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "dictionary_item.create", "dictionary_item")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"dictionary_item.create",
"dictionary_item",
)
.with_resource_id(id),
db,
)
.await;
@@ -376,8 +404,13 @@ impl DictionaryService {
.map_err(|e| ConfigError::Validation(e.to_string()))?;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "dictionary_item.update", "dictionary_item")
.with_resource_id(item_id),
AuditLog::new(
tenant_id,
Some(operator_id),
"dictionary_item.update",
"dictionary_item",
)
.with_resource_id(item_id),
db,
)
.await;
@@ -423,8 +456,13 @@ impl DictionaryService {
.map_err(|e| ConfigError::Validation(e.to_string()))?;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "dictionary_item.delete", "dictionary_item")
.with_resource_id(item_id),
AuditLog::new(
tenant_id,
Some(operator_id),
"dictionary_item.delete",
"dictionary_item",
)
.with_resource_id(item_id),
db,
)
.await;

View File

@@ -1,9 +1,7 @@
use std::collections::HashMap;
use chrono::Utc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
use uuid::Uuid;
use crate::dto::{CreateMenuReq, MenuResp};
@@ -58,19 +56,13 @@ impl MenuService {
// 3. 按 parent_id 分组构建 HashMap
let filtered: Vec<&menu::Model> = match &visible_menu_ids {
Some(ids) => all_menus
.iter()
.filter(|m| ids.contains(&m.id))
.collect(),
Some(ids) => all_menus.iter().filter(|m| ids.contains(&m.id)).collect(),
None => all_menus.iter().collect(),
};
let mut children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
for m in &filtered {
children_map
.entry(m.parent_id)
.or_default()
.push(*m);
children_map.entry(m.parent_id).or_default().push(*m);
}
// 4. 递归构建树形结构(从 parent_id == None 的根节点开始)
@@ -152,15 +144,19 @@ impl MenuService {
Self::assign_roles(id, role_ids, tenant_id, operator_id, db).await?;
}
event_bus.publish(erp_core::events::DomainEvent::new(
"menu.created",
tenant_id,
serde_json::json!({ "menu_id": id, "title": req.title }),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"menu.created",
tenant_id,
serde_json::json!({ "menu_id": id, "title": req.title }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "menu.create", "menu")
.with_resource_id(id),
AuditLog::new(tenant_id, Some(operator_id), "menu.create", "menu").with_resource_id(id),
db,
)
.await;
@@ -235,8 +231,7 @@ impl MenuService {
}
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "menu.update", "menu")
.with_resource_id(id),
AuditLog::new(tenant_id, Some(operator_id), "menu.update", "menu").with_resource_id(id),
db,
)
.await;
@@ -285,15 +280,19 @@ impl MenuService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"menu.deleted",
tenant_id,
serde_json::json!({ "menu_id": id }),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"menu.deleted",
tenant_id,
serde_json::json!({ "menu_id": id }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "menu.delete", "menu")
.with_resource_id(id),
AuditLog::new(tenant_id, Some(operator_id), "menu.delete", "menu").with_resource_id(id),
db,
)
.await;
@@ -370,10 +369,7 @@ impl MenuService {
nodes
.iter()
.map(|m| {
let children = children_map
.get(&Some(m.id))
.cloned()
.unwrap_or_default();
let children = children_map.get(&Some(m.id)).cloned().unwrap_or_default();
MenuResp {
id: m.id,
parent_id: m.parent_id,

View File

@@ -1,7 +1,7 @@
use chrono::{Datelike, NaiveDate, Utc};
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set,
Statement, ConnectionTrait, DatabaseBackend, TransactionTrait,
ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseBackend, EntityTrait, PaginatorTrait,
QueryFilter, Set, Statement, TransactionTrait,
};
use uuid::Uuid;
@@ -41,10 +41,7 @@ impl NumberingService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
let resps: Vec<NumberingRuleResp> = models
.iter()
.map(Self::model_to_resp)
.collect();
let resps: Vec<NumberingRuleResp> = models.iter().map(Self::model_to_resp).collect();
Ok((resps, total))
}
@@ -89,7 +86,10 @@ impl NumberingService {
seq_start: Set(seq_start),
seq_current: Set(seq_start as i64),
separator: Set(req.separator.clone().unwrap_or_else(|| "-".to_string())),
reset_cycle: Set(req.reset_cycle.clone().unwrap_or_else(|| "never".to_string())),
reset_cycle: Set(req
.reset_cycle
.clone()
.unwrap_or_else(|| "never".to_string())),
last_reset_date: Set(Some(Utc::now().date_naive())),
created_at: Set(now),
updated_at: Set(now),
@@ -103,15 +103,25 @@ impl NumberingService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"numbering_rule.created",
tenant_id,
serde_json::json!({ "rule_id": id, "code": req.code }),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"numbering_rule.created",
tenant_id,
serde_json::json!({ "rule_id": id, "code": req.code }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "numbering_rule.create", "numbering_rule")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"numbering_rule.create",
"numbering_rule",
)
.with_resource_id(id),
db,
)
.await;
@@ -126,7 +136,10 @@ impl NumberingService {
seq_start,
seq_current: seq_start as i64,
separator: req.separator.clone().unwrap_or_else(|| "-".to_string()),
reset_cycle: req.reset_cycle.clone().unwrap_or_else(|| "never".to_string()),
reset_cycle: req
.reset_cycle
.clone()
.unwrap_or_else(|| "never".to_string()),
last_reset_date: Some(Utc::now().date_naive().to_string()),
version: 1,
})
@@ -181,8 +194,13 @@ impl NumberingService {
.map_err(|e| ConfigError::Validation(e.to_string()))?;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "numbering_rule.update", "numbering_rule")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"numbering_rule.update",
"numbering_rule",
)
.with_resource_id(id),
db,
)
.await;
@@ -219,15 +237,25 @@ impl NumberingService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"numbering_rule.deleted",
tenant_id,
serde_json::json!({ "rule_id": id }),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"numbering_rule.deleted",
tenant_id,
serde_json::json!({ "rule_id": id }),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "numbering_rule.delete", "numbering_rule")
.with_resource_id(id),
AuditLog::new(
tenant_id,
Some(operator_id),
"numbering_rule.delete",
"numbering_rule",
)
.with_resource_id(id),
db,
)
.await;

View File

@@ -1,7 +1,5 @@
use chrono::Utc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set};
use uuid::Uuid;
use crate::dto::SettingResp;
@@ -46,9 +44,7 @@ impl SettingService {
db: &sea_orm::DatabaseConnection,
) -> ConfigResult<SettingResp> {
// 1. Try exact match
if let Some(resp) =
Self::find_exact(key, scope, scope_id, tenant_id, db).await?
{
if let Some(resp) = Self::find_exact(key, scope, scope_id, tenant_id, db).await? {
return Ok(resp);
}
@@ -81,12 +77,18 @@ impl SettingService {
event_bus: &EventBus,
) -> ConfigResult<SettingResp> {
// Look for an existing non-deleted record
let existing = setting::Entity::find()
let mut query = setting::Entity::find()
.filter(setting::Column::TenantId.eq(tenant_id))
.filter(setting::Column::Scope.eq(&params.scope))
.filter(setting::Column::ScopeId.eq(params.scope_id))
.filter(setting::Column::SettingKey.eq(&params.key))
.filter(setting::Column::DeletedAt.is_null())
.filter(setting::Column::DeletedAt.is_null());
query = match params.scope_id {
Some(id) => query.filter(setting::Column::ScopeId.eq(id)),
None => query.filter(setting::Column::ScopeId.is_null()),
};
let existing = query
.one(db)
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
@@ -94,7 +96,9 @@ impl SettingService {
if let Some(model) = existing {
// Update existing record — 乐观锁校验
let next_version = match params.version {
Some(v) => check_version(v, model.version).map_err(|_| ConfigError::VersionMismatch)?,
Some(v) => {
check_version(v, model.version).map_err(|_| ConfigError::VersionMismatch)?
}
None => model.version + 1,
};
@@ -109,15 +113,20 @@ impl SettingService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"setting.updated",
tenant_id,
serde_json::json!({
"setting_id": updated.id,
"key": params.key,
"scope": params.scope,
}),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"setting.updated",
tenant_id,
serde_json::json!({
"setting_id": updated.id,
"key": params.key,
"scope": params.scope,
}),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "setting.upsert", "setting")
@@ -150,15 +159,20 @@ impl SettingService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
event_bus.publish(erp_core::events::DomainEvent::new(
"setting.created",
tenant_id,
serde_json::json!({
"setting_id": id,
"key": params.key,
"scope": params.scope,
}),
), db).await;
event_bus
.publish(
erp_core::events::DomainEvent::new(
"setting.created",
tenant_id,
serde_json::json!({
"setting_id": id,
"key": params.key,
"scope": params.scope,
}),
),
db,
)
.await;
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "setting.upsert", "setting")
@@ -179,12 +193,17 @@ impl SettingService {
pagination: &Pagination,
db: &sea_orm::DatabaseConnection,
) -> ConfigResult<(Vec<SettingResp>, u64)> {
let paginator = setting::Entity::find()
let mut query = setting::Entity::find()
.filter(setting::Column::TenantId.eq(tenant_id))
.filter(setting::Column::Scope.eq(scope))
.filter(setting::Column::ScopeId.eq(*scope_id))
.filter(setting::Column::DeletedAt.is_null())
.paginate(db, pagination.limit());
.filter(setting::Column::DeletedAt.is_null());
query = match scope_id {
Some(id) => query.filter(setting::Column::ScopeId.eq(*id)),
None => query.filter(setting::Column::ScopeId.is_null()),
};
let paginator = query.paginate(db, pagination.limit());
let total = paginator
.num_items()
@@ -197,8 +216,7 @@ impl SettingService {
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
let resps: Vec<SettingResp> =
models.iter().map(Self::model_to_resp).collect();
let resps: Vec<SettingResp> = models.iter().map(Self::model_to_resp).collect();
Ok((resps, total))
}
@@ -214,20 +232,23 @@ impl SettingService {
version: i32,
db: &sea_orm::DatabaseConnection,
) -> ConfigResult<()> {
let model = setting::Entity::find()
let mut query = setting::Entity::find()
.filter(setting::Column::TenantId.eq(tenant_id))
.filter(setting::Column::Scope.eq(scope))
.filter(setting::Column::ScopeId.eq(*scope_id))
.filter(setting::Column::SettingKey.eq(key))
.filter(setting::Column::DeletedAt.is_null())
.filter(setting::Column::DeletedAt.is_null());
query = match scope_id {
Some(id) => query.filter(setting::Column::ScopeId.eq(*id)),
None => query.filter(setting::Column::ScopeId.is_null()),
};
let model = query
.one(db)
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?
.ok_or_else(|| {
ConfigError::NotFound(format!(
"设置 '{}' 在 '{}' 作用域下不存在",
key, scope
))
ConfigError::NotFound(format!("设置 '{}' 在 '{}' 作用域下不存在", key, scope))
})?;
let next_version =
@@ -264,12 +285,19 @@ impl SettingService {
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
) -> ConfigResult<Option<SettingResp>> {
let model = setting::Entity::find()
let mut query = setting::Entity::find()
.filter(setting::Column::TenantId.eq(tenant_id))
.filter(setting::Column::Scope.eq(scope))
.filter(setting::Column::ScopeId.eq(*scope_id))
.filter(setting::Column::SettingKey.eq(key))
.filter(setting::Column::DeletedAt.is_null())
.filter(setting::Column::DeletedAt.is_null());
// SQL 中 `= NULL` 永远返回 false必须用 IS NULL 匹配 NULL 值
query = match scope_id {
Some(id) => query.filter(setting::Column::ScopeId.eq(*id)),
None => query.filter(setting::Column::ScopeId.is_null()),
};
let model = query
.one(db)
.await
.map_err(|e| ConfigError::Validation(e.to_string()))?;
@@ -301,9 +329,7 @@ impl SettingService {
(SCOPE_TENANT.to_string(), Some(tenant_id)),
(SCOPE_PLATFORM.to_string(), None),
]),
SCOPE_TENANT => {
Ok(vec![(SCOPE_PLATFORM.to_string(), None)])
}
SCOPE_TENANT => Ok(vec![(SCOPE_PLATFORM.to_string(), None)]),
SCOPE_PLATFORM => Ok(vec![]),
_ => Err(ConfigError::Validation(format!(
"不支持的作用域类型: '{}'",