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.
12 KiB
Phase 3: 系统配置模块 — 实施计划
Context
Phase 1(基础设施)和 Phase 2(身份与权限)已完成。Phase 3 需要实现系统配置模块(erp-config),提供数据字典、动态菜单、系统参数、编号规则、i18n 框架和主题自定义能力。当前 erp-config 仅为 placeholder。
前置重构:将 RBAC 移至 erp-core
问题: require_permission 在 erp-auth/src/middleware/rbac.rs 中,但只依赖 erp-core 的 TenantContext 和 AppError。erp-config 不能依赖 erp-auth(架构铁律:业务 crate 禁止直接依赖)。
方案:
- 将
erp-auth/src/middleware/rbac.rs→erp-core/src/rbac.rs erp-core/src/lib.rs添加pub mod rbac;- 更新
erp-auth所有 handler 的 import:crate::middleware::rbac→erp_core::rbac erp-auth/src/middleware/mod.rs移除pub mod rbac;
Task 1: erp-config 骨架 + ConfigState
目标: 创建可编译的最小 crate,注册到 erp-server。
创建/修改文件:
- 修改:
crates/erp-config/Cargo.toml— 添加完整依赖 - 修改:
crates/erp-config/src/lib.rs— 模块声明 + re-export - 创建:
crates/erp-config/src/config_state.rs— ConfigState { db, event_bus } - 创建:
crates/erp-config/src/error.rs— ConfigError 枚举 - 创建:
crates/erp-config/src/module.rs— ConfigModule + 空路由 - 创建:
crates/erp-config/src/dto.rs— 占位 - 创建:
crates/erp-config/src/entity/mod.rs— 占位 - 创建:
crates/erp-config/src/service/mod.rs— 占位 - 创建:
crates/erp-config/src/handler/mod.rs— 占位 - 修改:
crates/erp-server/Cargo.toml— 添加 erp-config 依赖 - 修改:
crates/erp-server/src/state.rs— 添加 FromRef for ConfigState - 修改:
crates/erp-server/src/main.rs— 注册 ConfigModule
验证: cargo check 通过
Task 2: 数据库迁移(6 张表)
目标: 创建所有配置模块表。
创建文件(crates/erp-server/migration/src/):
m20260412_000012_create_dictionaries.rs— 字典分类表m20260412_000013_create_dictionary_items.rs— 字典项表m20260412_000014_create_menus.rs— 菜单树形表m20260412_000015_create_menu_roles.rs— 菜单-角色关联表m20260412_000016_create_settings.rs— 分层键值配置表m20260412_000017_create_numbering_rules.rs— 编号规则表- 修改:
lib.rs— 注册新迁移
表结构:
dictionaries
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | 租户 ID |
| name | string NOT NULL | 显示名称 |
| code | string NOT NULL | 字典键(如 gender) |
| description | text NULL | 说明 |
| + 标准审计字段 | ||
| 唯一索引: | (tenant_id, code) WHERE deleted_at IS NULL |
dictionary_items
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| dictionary_id | uuid NOT NULL | FK → dictionaries |
| label | string NOT NULL | 显示标签 |
| value | string NOT NULL | 存储值 |
| sort_order | int DEFAULT 0 | 排序 |
| color | string NULL | 颜色标签 |
| + 标准审计字段 | ||
| 唯一索引: | (dictionary_id, value) WHERE deleted_at IS NULL |
menus(树形自引用)
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| parent_id | uuid NULL | 自引用 |
| title | string NOT NULL | 菜单标题 |
| path | string NULL | 前端路由 |
| icon | string NULL | 图标名 |
| sort_order | int DEFAULT 0 | |
| visible | bool DEFAULT true | |
| menu_type | string DEFAULT 'page' | page/link/button |
| permission | string NULL | 所需权限码 |
| + 标准审计字段 |
menu_roles(复合主键)
| 列 | 类型 | 说明 |
|---|---|---|
| menu_id | uuid NOT NULL | PK 组成 |
| role_id | uuid NOT NULL | PK 组成 |
| tenant_id | uuid NOT NULL | |
| + 标准审计字段 | ||
| 唯一索引: | (menu_id, role_id) WHERE deleted_at IS NULL |
settings(分层配置)
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| scope | string NOT NULL | platform/tenant/org/user |
| scope_id | uuid NULL | 平台=NULL, 租户=tenant_id 等 |
| setting_key | string NOT NULL | |
| setting_value | jsonb DEFAULT '{}' | |
| + 标准审计字段 | ||
| 唯一索引: | (scope, scope_id, setting_key) WHERE deleted_at IS NULL |
numbering_rules
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| name | string NOT NULL | 规则名称 |
| code | string NOT NULL | 唯一编码(如 INV) |
| prefix | string DEFAULT '' | 前缀 |
| date_format | string NULL | 如 %Y%m%d |
| seq_length | int DEFAULT 4 | 序列位数 |
| seq_start | int DEFAULT 1 | 起始值 |
| seq_current | bigint DEFAULT 0 | 当前序列 |
| separator | string DEFAULT '-' | 分隔符 |
| reset_cycle | string DEFAULT 'never' | never/daily/monthly/yearly |
| last_reset_date | date NULL | |
| + 标准审计字段 | ||
| 唯一索引: | (tenant_id, code) WHERE deleted_at IS NULL |
验证: cargo run -p erp-server 启动后 \dt 可见新表
Task 3: SeaORM Entity
目标: 为 6 张表创建 Entity 定义。
创建文件(crates/erp-config/src/entity/):
mod.rs— 导出所有实体dictionary.rs— Dictionary Entitydictionary_item.rs— DictionaryItem Entitymenu.rs— Menu Entitymenu_role.rs— MenuRole Entity(复合主键模式,参考 role_permission)setting.rs— Setting Entitynumbering_rule.rs— NumberingRule Entity
模式: 参考 erp-auth/src/entity/role.rs,包含 Relation 和 Related 实现。
验证: cargo check 通过
Task 4: DTO 定义
目标: 定义所有配置端点的请求/响应类型。
修改文件: crates/erp-config/src/dto.rs
包含:
- 字典 DTO:DictionaryResp, DictionaryItemResp, CreateDictionaryReq, UpdateDictionaryReq, CreateDictionaryItemReq, UpdateDictionaryItemReq
- 菜单 DTO:MenuResp(含 children 递归), CreateMenuReq, UpdateMenuReq, BatchSaveMenuReq
- 设置 DTO:SettingResp, UpdateSettingReq
- 编号规则 DTO:NumberingRuleResp, CreateNumberingRuleReq, UpdateNumberingRuleReq, GenerateNumberResp
- 主题 DTO:ThemeResp, UpdateThemeReq(委托 settings 存储)
- 语言 DTO:LanguageResp, UpdateLanguageReq(委托 settings 存储)
验证: cargo check 通过
Task 5: Service — 字典 + 系统参数
创建文件(crates/erp-config/src/service/):
dictionary_service.rs— CRUD + 字典项管理 + 按 code 查询setting_service.rs— 分层读取(User>Org>Tenant>Platform)+ 写入
关键逻辑:
- SettingService::get 实现分层覆盖查找
- SettingService::set 使用 upsert 语义(INSERT ON CONFLICT UPDATE)
- DictionaryService 遵循 RoleService 的无状态模式
Task 6: Service — 菜单 + 编号规则
创建文件(crates/erp-config/src/service/):
menu_service.rs— 菜单树构建 + 角色过滤 + 批量保存numbering_service.rs— 编号规则 CRUD + generate_number
关键逻辑:
- MenuService::get_menu_tree — 按 role_ids 过滤 menu_role,HashMap 分组构建树(参考 OrgService 的 build_org_tree)
- NumberingService::generate_number — PostgreSQL advisory_lock + 事务内序列递增:
生成格式:
SELECT pg_advisory_xact_lock(hashtext($1), $2::int) -- $1 = rule_code, $2 = hash(tenant_id){prefix}{separator}{date}{separator}{seq_padded}
Task 7: Handler 层
创建文件(crates/erp-config/src/handler/):
dictionary_handler.rs— 5 个端点menu_handler.rs— 2 个端点setting_handler.rs— 2 个端点numbering_handler.rs— 4 个端点(含 generate)theme_handler.rs— 2 个端点(委托 SettingService)language_handler.rs— 2 个端点(委托 SettingService)
端点映射:
GET/POST /config/dictionaries
PUT/DELETE /config/dictionaries/{id}
GET/PUT /config/menus
GET/PUT /config/settings/{key}
GET/POST /config/numbering-rules
PUT /config/numbering-rules/{id}
POST /config/numbering-rules/{id}/generate
GET/PUT /config/themes
GET /config/languages
PUT /config/languages/{code}
Task 8: 模块注册 + 种子数据
修改文件:
crates/erp-config/src/module.rs— 填充真实路由crates/erp-auth/src/service/seed.rs— 添加配置模块权限(dictionary/menu/setting/numbering/theme/language)crates/erp-server/src/main.rs— 确认路由集成
新增种子权限(17 个):
- dictionary: create/read/update/delete/list
- menu: read/update
- setting: read/update
- numbering: create/read/update/generate
- theme: read/update
- language: read/update
验证: cargo check + cargo test 通过,服务器启动后 Swagger UI 可见新端点
Task 9: 前端 API 层 + Settings 页面框架
创建文件(apps/web/src/):
api/dictionaries.ts— 字典 CRUD APIapi/menus.ts— 菜单 APIapi/settings.ts— 设置 APIapi/numberingRules.ts— 编号规则 APIpages/Settings.tsx— Tabs 壳页面
修改文件:
App.tsx— 替换 settings 占位组件
Tabs 结构: 数据字典 | 菜单配置 | 编号规则 | 系统参数 | 主题设置
Task 10: 前端设置子页面
创建文件(apps/web/src/pages/settings/):
DictionaryManager.tsx— Table + 展开行显示字典项,参考 Roles.tsxMenuConfig.tsx— Tree + 编辑表单,参考 Organizations.tsxNumberingRules.tsx— Table + Modal CRUD + 生成按钮SystemSettings.tsx— 键值编辑列表ThemeSettings.tsx— 颜色选择器 + 表单
修改文件:
Settings.tsx— 导入子组件MainLayout.tsx— 更新设置菜单图标
验证: pnpm dev 启动,访问 /settings 各 tab 可正常交互
依赖图
前置重构(RBAC→erp-core)
|
Task 1(骨架)
|
Task 2(迁移)→ Task 3(Entity)→ Task 4(DTO)
|
+----------------+----------------+
| |
Task 5(字典+设置 Service) Task 6(菜单+编号 Service)
| |
+----------------+----------------+
|
Task 7(Handler)
|
Task 8(集成+种子)
|
Task 9(前端API+壳)
|
Task 10(前端页面)
验证清单
cargo check全 workspace 通过cargo test --workspace全部通过- Docker 环境正常启动
- 所有迁移可正/反向执行
- API 端点可通过 Swagger UI 测试
- 前端 /settings 页面各 Tab 正常工作
- 所有代码已提交
关键参考文件
| 用途 | 文件路径 |
|---|---|
| Service 模式 | crates/erp-auth/src/service/role_service.rs |
| Handler 模式 | crates/erp-auth/src/handler/role_handler.rs |
| 树构建模式 | crates/erp-auth/src/service/org_service.rs |
| 迁移模式 | crates/erp-server/migration/src/m20260411_000005_create_roles.rs |
| State 桥接 | crates/erp-server/src/state.rs |
| 复合主键 Entity | crates/erp-auth/src/entity/role_permission.rs |
| 前端 Table CRUD | apps/web/src/pages/Roles.tsx |
| 前端树形展示 | apps/web/src/pages/Organizations.tsx |
| RBAC 工具函数 | crates/erp-auth/src/middleware/rbac.rs(待迁移到 erp-core) |