# 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 禁止直接依赖)。 **方案:** 1. 将 `erp-auth/src/middleware/rbac.rs` → `erp-core/src/rbac.rs` 2. `erp-core/src/lib.rs` 添加 `pub mod rbac;` 3. 更新 `erp-auth` 所有 handler 的 import:`crate::middleware::rbac` → `erp_core::rbac` 4. `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 Entity - `dictionary_item.rs` — DictionaryItem Entity - `menu.rs` — Menu Entity - `menu_role.rs` — MenuRole Entity(复合主键模式,参考 role_permission) - `setting.rs` — Setting Entity - `numbering_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 + 事务内序列递增: ```sql 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 API - `api/menus.ts` — 菜单 API - `api/settings.ts` — 设置 API - `api/numberingRules.ts` — 编号规则 API - `pages/Settings.tsx` — Tabs 壳页面 **修改文件:** - `App.tsx` — 替换 settings 占位组件 **Tabs 结构:** 数据字典 | 菜单配置 | 编号规则 | 系统参数 | 主题设置 --- ## Task 10: 前端设置子页面 **创建文件(`apps/web/src/pages/settings/`):** - `DictionaryManager.tsx` — Table + 展开行显示字典项,参考 Roles.tsx - `MenuConfig.tsx` — Tree + 编辑表单,参考 Organizations.tsx - `NumberingRules.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) |