From 41a0dc8bd6fe342d7800edf3f7e71428073851c4 Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 17 Apr 2026 10:45:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(plugin):=20=E5=AE=9E=E4=BD=93=E7=BA=A7=20d?= =?UTF-8?q?ata=5Fscope=20+=20scope=5Frole=20+=20data=5Fscope=5Flevels=20?= =?UTF-8?q?=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PluginEntity 新增 data_scope: Option 字段,控制是否启用行级数据权限 - PluginField 新增 scope_role: Option 字段,标记数据权限的"所有者"字段 - PluginPermission 新增 data_scope_levels: Option> 字段,声明支持的数据范围等级 - 更新 default_for_field() 测试辅助和 dynamic_table.rs 中的 PluginEntity 构造 - 新增 parse_entity_with_data_scope 和 parse_permission_with_data_scope_levels 测试 --- crates/erp-plugin/src/dynamic_table.rs | 2 + crates/erp-plugin/src/manifest.rs | 51 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/crates/erp-plugin/src/dynamic_table.rs b/crates/erp-plugin/src/dynamic_table.rs index 1dd4bf7..7fd5010 100644 --- a/crates/erp-plugin/src/dynamic_table.rs +++ b/crates/erp-plugin/src/dynamic_table.rs @@ -1019,6 +1019,7 @@ mod tests { ], indexes: vec![], relations: vec![], + data_scope: None, }; let sql = DynamicTableManager::build_create_table_sql("erp_crm", &entity); @@ -1060,6 +1061,7 @@ mod tests { }], indexes: vec![], relations: vec![], + data_scope: None, }; let sql = DynamicTableManager::build_create_table_sql("erp_crm", &entity); diff --git a/crates/erp-plugin/src/manifest.rs b/crates/erp-plugin/src/manifest.rs index 56d6d0e..740eb2b 100644 --- a/crates/erp-plugin/src/manifest.rs +++ b/crates/erp-plugin/src/manifest.rs @@ -45,6 +45,8 @@ pub struct PluginEntity { pub indexes: Vec, #[serde(default)] pub relations: Vec, + #[serde(default)] + pub data_scope: Option, // 是否启用行级数据权限 } /// 字段校验规则 @@ -79,6 +81,8 @@ pub struct PluginField { pub validation: Option, // 字段校验规则 #[serde(default)] pub no_cycle: Option, // 禁止循环引用 + #[serde(default)] + pub scope_role: Option, // 标记为数据权限的"所有者"字段 } /// 字段类型 @@ -145,6 +149,7 @@ impl PluginField { ref_entity: None, validation: None, no_cycle: None, + scope_role: None, } } } @@ -272,6 +277,8 @@ pub struct PluginPermission { pub name: String, #[serde(default)] pub description: String, + #[serde(default)] + pub data_scope_levels: Option>, // 支持的数据范围等级 } /// 从 TOML 字符串解析插件清单 @@ -746,4 +753,48 @@ on_delete = "cascade" assert_eq!(entity.relations[0].foreign_key, "customer_id"); assert!(matches!(entity.relations[0].on_delete, OnDeleteStrategy::Cascade)); } + + #[test] + fn parse_entity_with_data_scope() { + let toml = r#" +[metadata] +id = "test" +name = "Test" +version = "0.1.0" + +[schema] +[[schema.entities]] +name = "customer" +display_name = "客户" +data_scope = true + +[[schema.entities.fields]] +name = "owner_id" +field_type = "uuid" +display_name = "负责人" +scope_role = "owner" +"#; + let manifest = parse_manifest(toml).unwrap(); + let entity = &manifest.schema.unwrap().entities[0]; + assert_eq!(entity.data_scope, Some(true)); + assert_eq!(entity.fields[0].scope_role.as_deref(), Some("owner")); + } + + #[test] + fn parse_permission_with_data_scope_levels() { + let toml = r#" +[metadata] +id = "test" +name = "Test" +version = "0.1.0" + +[[permissions]] +code = "customer.list" +name = "查看客户" +data_scope_levels = ["self", "department", "department_tree", "all"] +"#; + let manifest = parse_manifest(toml).unwrap(); + let perm = &manifest.permissions.unwrap()[0]; + assert_eq!(perm.data_scope_levels.as_ref().unwrap().len(), 4); + } }