# FMS 前端元数据运行时重构计划 ## 1. 目标 本次重构以内部 ERP 快速开发为目标,将模块配置、运行时解析、列表编辑和页面组合能力集中到前端公共层。业务开发优先通过配置和组合完成,只有特殊业务流程编写页面代码。 预期结果: ```text 普通数据页面 配置 Module + Profile 后直接使用公共页面 组合业务页面 配置 PageNode,并通过公共运行时加载多个模块 特殊业务页面 复用公共运行时,只补充业务事件和业务流程 模块配置维护 统一在模块管理中完成,不修改后端 ``` ## 2. 硬约束 - 不兼容旧系统、旧模块模型和旧业务数据。 - 日常开发和运行期间不修改、不重启后端。 - 不新增业务 Controller、Service 或专用接口。 - 不修改 `/data/loaddata`、`/data/page`、`/data/saveobjt`、`/data/nextid`、`/data/describe` 的协议和处理逻辑。 - 所有多表保存继续通过一次 `/data/saveobjt` 在同一事务中提交。 - SQL Server `bigint` 在前端始终按字符串处理。 - 数据库约束负责主外键、唯一性、类型和树结构的最终完整性。 - 前端负责运行时解析、配置编排、交互校验、保存请求生成和错误定位。 - 后端重启不作为前端功能开发、配置调整或页面接入的前置步骤。 ## 3. 保留的核心模型 | 模型 | 稳定职责 | | --- | --- | | `Module` | 可复用资源及公共属性 | | `FunctionNode` | 菜单目录和页面入口 | | `PageNode` | 页面内部结构和模块引用 | | `Profile` | 同一数据模块的不同使用方式 | | `Field` | 模块级字段定义 | | `Capability` | Profile 级列表、表单、查询等配置及 Module 级操作 | 字段继续属于 Module;列表、表单、查询、导入和导出继续属于 Profile。不得把这些配置重新合并为一张大表。 当前阶段正式支持 `page` 和 `data` 模块。`report`、`dashboard`、`workflow` 只保留数据库契约,在对应运行能力完成前不作为常规配置入口。 ## 4. 目标前端结构 ```text src/ module-runtime/ constants.js 元数据表名、视图名、节点类型和能力编码 runtimeResolver.js Module/Profile/PageNode 统一解析 configLoader.js 字段及 Profile 配置统一加载 conditionBuilder.js 固定条件、查询条件和页面参数组合 saveRequestBuilder.js inserts/updates/deletes 请求生成与排序 fieldRegistry.js 字段控件和渲染器注册 actionRegistry.js 通用操作和页面操作注册 useModuleRuntime.js 面向页面的统一组合入口 useModuleDataSource.js 分页、刷新、选择和查询状态 useModuleEditor.js 新增、编辑、删除、临时 ID 和批量保存 ``` 目录名可在实施时结合现有结构调整,但职责边界不得重新散落到业务页面。 ## 5. 公共运行时契约 ### 5.1 模块定位 公共运行时支持以下三种明确定位方式: 1. `moduleId`:使用该模块的默认 Profile 2. `profileId`:精确定位 Profile 并反查所属 Module 3. `pageNodeId` 同时提供 `moduleId + profileId` 时只用于校验两者归属关系。新页面不再使用 `moduleCode + profileCode` 定位,也不再通过页面路由后取“第一个 `module_ref`”作为隐式模块。一个页面存在多个模块引用时,必须通过 `pageNodeId` 明确定位。 ### 5.2 运行时结果 `useModuleRuntime` 对调用方提供统一对象: ```text module 模块及数据源 profile 当前使用方式 fields 启用字段 listConfig 列表配置 formConfig 表单配置 queryConfig 查询配置 actions 操作目录及有效权限 groups 布局分组 runtimeKey 当前配置唯一键 loading/error 加载状态 reload() 重新加载配置 ``` 业务页面不再自行查询 `s_module_field*`、`s_module_profile` 或 `v_s_module_runtime`。 ### 5.3 扩展点 通用页面通过明确扩展点承载差异: - `fixedCondition` - `beforeLoad` - `afterLoad` - `beforeSave` - `afterSave` - `rowActions` - `actionHandlers` - 表格和表单插槽 扩展点只处理业务差异,不重新实现分页、字段解析、脏数据和批量保存。 ## 6. 实施阶段 ### 阶段 0:基线和旧设计清理 状态:`completed` 实际结果:旧设计文档已标记为历史方案;公共常量已建立;`otherdata` 只使用正式的 `group/module_ref` 节点类型。 - 以 `新数据库结构.md` 为唯一模块模型规范。 - 将旧的 `s_module_group`、`s_option_source`、旧节点类型和编码型 i18n 设计标记为历史方案。 - 建立前端元数据常量,统一 `directory/entry`、`group/tab/section/module_ref` 等枚举。 - 修正 `otherdata` 中 `category/group`、`module/module_ref` 的混合判断。 - 记录当前普通列表、可编辑列表、其他资料、用户管理、登录日志和多语言页面的行为基线。 验收:代码和有效文档中不再存在会参与运行的旧模型定义。 ### 阶段 1:抽取运行时解析和配置加载 状态:`completed` 实际结果:已建立 ID 优先的公共解析、配置加载、并发版本控制和会话缓存;多模块页面必须使用 `pageNodeId`。 - 从 `FmsModuleListPage.vue` 抽取模块定位、Profile 定位和配置加载逻辑。 - 建立 `useModuleRuntime`,统一 ID 字符串转换、加载并发控制和错误状态。 - 建立显式页面节点解析规则,删除“第一个数据节点”约定。 - 保持现有 API 请求数量和运行行为,阶段内不调整页面视觉结构。 - 为同一运行时键增加会话级缓存;模块管理保存后可以定向失效缓存。 验收:普通页面可通过 `moduleId`、`profileId`、`pageNodeId` 或唯一页面引用定位;同一页面可通过不同 `pageNodeId` 明确加载多个数据模块。 ### 阶段 2:抽取数据源和编辑器 状态:`completed` 实际结果:分页查询、查询状态、临时 ID、脏行、批量保存、批量删除和拖拽排序已从页面组件抽离;一次保存仍只调用一次 `saveobjt`。 - 从 `FmsModuleListPage.vue` 抽取分页、查询、排序和刷新状态到 `useModuleDataSource`。 - 抽取新增、编辑、删除、临时 ID、自动编码和保存请求生成到 `useModuleEditor`。 - 建立统一 `saveRequestBuilder`,负责表级变更合并及删除、更新、插入顺序。 - 一次用户保存只发起一次 `/data/saveobjt`。 - 保留 `FmsModuleTable`、`FmsQueryToolbar` 和现有页面插槽契约。 验收:`FmsModuleListPage.vue` 主要负责界面组合,不再同时承担元数据加载、数据访问和保存算法。 ### 阶段 3:控件和操作注册中心 状态:`completed` 实际结果:字段注册中心已覆盖输入、长文本、数字、金额、日期、日期时间、布尔和选择控件;数字编辑使用字符串模式保护 bigint;操作注册中心已提供通用操作和页面 handler 扩展。 - 建立字段控件注册中心,收敛输入、数字、日期、布尔、选择和自定义渲染器映射。 - 建立操作注册中心,统一 `create/update/delete/export` 等通用行为。 - 特殊业务操作由页面注册 handler,不修改公共运行时主体。 - 注册项使用稳定 key;未知 key 明确报错并指出模块、Profile 和字段。 - 表格行内重型控件继续按需挂载,任意时刻只保留当前编辑实例。 验收:新增一种字段控件或通用操作只修改注册中心及对应实现,不修改所有业务页面。 ### 阶段 4:模块管理提效 状态:`completed` 实际结果:创建数据模块自动创建默认 Profile;同步字段自动补齐列表和表单配置;Profile 可完整复制;模块管理已增加实时完整性检查和列表预览;常规新建入口只开放 `page/data`。 - 创建数据模块时自动创建 `default` Profile。 - 同步字段后,新字段自动生成默认列表和表单配置;失效字段只停用。 - 支持复制 Profile,并完整复制分组、列表、表单和查询配置。 - 模块管理增加配置预览和完整性检查结果,但不展示底层保存顺序。 - 常规入口只开放 `page` 和 `data`;预留模块类型放入明确的高级区域或隐藏。 - 保存后定向刷新运行时缓存、菜单和权限数据,不刷新整个应用。 验收:技术人员可以完成“创建模块、同步字段、调整配置、加入页面、立即使用”的完整流程。 ### 阶段 5:页面编排和业务页面接入 状态:`completed` 实际结果:`contact`、`othercompany`、`user`、`login-log`、`i18n-type` 已接入公共运行时;`otherdata` 通过 `pageNodeId` 加载所选模块引用,不再按模块编码定位运行时。 - 页面组件通过 `pageNodeId` 或明确的 `moduleId/profileId` 加载数据模块。 - `otherdata` 使用统一 PageNode 枚举和运行时解析,不再维护独立模块解析逻辑。 - `contact`、`othercompany`、`user`、`login-log`、`i18n-type` 按风险从低到高接入公共运行时。 - 普通页面只保留模块声明和必要插槽;特殊页面只保留业务流程。 - 不为了接入公共运行时修改后端或重启后端。 验收:新增普通数据页面只需要配置 Module/Profile 和一个轻量页面入口。 ### 阶段 6:清理与稳定性验证 状态:`in_progress` 已完成:公共运行时代码已通过 Node 语法检查、定向格式检查和 Vite 生产构建;异步请求均使用运行时键或版本号丢弃过期结果;bigint ID 和数字编辑按字符串处理。 待完成:连接实际运行数据后回归普通列表、可编辑列表、`otherdata`、多模块页面和机构切换,并确认每个路由页面只有一个启用的数据模块引用。 - 删除已无调用方的重复加载、条件组合和保存函数。 - 删除旧枚举适配、旧模块解析路径和演示数据。 - 检查所有 bigint 比较、Map key、选择值和请求参数均按字符串处理。 - 检查模块切换、路由切换和机构切换时不会应用过期异步结果。 - 检查大列表滚动、编辑器按需挂载和批量保存请求数量。 - 执行 `pnpm fmt:check`;除非用户明确要求,不执行构建、不启动或重启后端。 验收:公共运行时成为唯一模块解析入口,业务页面没有重复实现模块配置加载。 ## 7. 优先改造文件 | 优先级 | 文件/目录 | 处理 | | --- | --- | --- | | P0 | `src/components/fms-module-list/FmsModuleListPage.vue` | 拆分运行时、数据源和编辑逻辑 | | P0 | `src/views/base/otherdata/index.vue` | 统一 PageNode 枚举和模块定位 | | P0 | `src/views/module-management/composables/useModuleResources.js` | 复用请求构造、缓存失效和字段同步能力 | | P1 | `src/views/module-management/composables/usePageComposition.js` | 使用统一节点规则和运行时定位 | | P1 | `src/views/module-management/composables/useFunctionStructure.js` | 使用统一节点常量和树校验 | | P1 | `src/stores/permissions.js` | 与运行时共享模块、节点和操作索引 | | P2 | 使用 `FmsModuleListPage` 的业务页面 | 逐页删除重复配置加载和保存逻辑 | ## 8. 性能和运行连续性 - 元数据按 `orgId + moduleId + profileId` 缓存,避免同一页面重复请求。 - 模块配置保存后只失效相关缓存,不清空所有模块缓存。 - 页面或机构切换使用版本号丢弃过期请求结果。 - 列表业务数据始终分页;不因元数据重构增加全表读取。 - 多个元数据请求可并行加载,但不得在行循环中发起请求。 - 批量编辑只生成一次保存请求,不逐行调用接口。 - 前端变更通过 Vite 热更新生效;实施期间不重启后端。 ## 9. 非目标 - 不建立传统业务 Controller/Service/DAO 分层。 - 不为每个模块生成后端代码。 - 不实现旧模块表兼容、双写或旧数据转换。 - 不在本次重构实现报表引擎、仪表盘设计器或工作流引擎。 - 不重写 `fms-table`、文件组件或已有手写业务流程。 - 不一次性改造所有业务页面;公共能力稳定后逐页接入。 ## 10. 执行纪律 - 每个阶段单独完成、验证和交付,不跨阶段进行大范围改写。 - 开始一个阶段前记录受影响页面和当前行为。 - 公共组件改动后优先回归普通列表、可编辑列表和 `otherdata`。 - 发现通用接口无法满足需求时停止该项,说明缺口后再决定是否允许修改后端。 - 每阶段完成后更新本文件状态和实际结果,未通过验收不得进入下一阶段。 ## 11. 完成标准 满足以下条件后,本计划完成: - 技术人员可以不修改后端完成数据模块创建、字段同步、Profile 配置和页面挂载。 - 开发人员可以通过公共组件或 composable 接入模块,不重复编写元数据查询和保存流程。 - 普通模块新增和配置调整不需要后端重启。 - 同一数据模块可被多个页面和多个 Profile 复用。 - 页面包含多个模块时定位明确,不依赖隐式顺序。 - 前端只保留一套有效模块模型和节点枚举。 - 批量保存、权限刷新、缓存失效和异步切换行为可预测。