263 lines
13 KiB
Markdown
263 lines
13 KiB
Markdown
# 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 复用。
|
|
- 页面包含多个模块时定位明确,不依赖隐式顺序。
|
|
- 前端只保留一套有效模块模型和节点枚举。
|
|
- 批量保存、权限刷新、缓存失效和异步切换行为可预测。
|