121 lines
7.2 KiB
Markdown
121 lines
7.2 KiB
Markdown
---
|
||
name: 模块管理树节点点击懒加载
|
||
overview: 将模块管理从“挂载时一次性加载全部表”改为“挂载时仅加载树(b_module),点击树节点时才懒加载该模块的字段/权限/翻译/用户分配数据”。
|
||
todos:
|
||
- id: split-load
|
||
content: 拆分 loadAll 为 loadModules 与 loadModuleDetail,新增翻译条件过滤器
|
||
status: pending
|
||
- id: wire-lazy
|
||
content: 将树选中/刷新、onMounted、保存/删除后刷新接入懒加载,保留 draft 与级联逻辑
|
||
status: pending
|
||
dependencies:
|
||
- split-load
|
||
- id: verify-scopes
|
||
content: 回归校验新建/取消/保存/删除/刷新在各场景下数据正确且不触发全量加载
|
||
status: pending
|
||
dependencies:
|
||
- wire-lazy
|
||
---
|
||
|
||
## 用户需求
|
||
|
||
模块管理页面进入时,不要立即把所有模块的全部数据一次性拉取下来;应当在用户点击(或右键)左侧树节点时,才加载该节点对应模块的详情数据。
|
||
|
||
## 产品概述
|
||
|
||
模块管理采用「左侧模块树 + 右侧详情」布局。当前实现在页面挂载时即通过 `loadAll()` 一次性并发加载 6 张表(b_module / b_module_field / b_module_power / b_i18n / b_user_module / b_user_power)的全部数据并缓存,再按选中节点从内存过滤。需要改为:挂载时只加载树所需的 b_module;选中某个节点时才按该模块懒加载其字段、权限、翻译及用户分配数据。
|
||
|
||
## 核心特性
|
||
|
||
- 页面挂载仅加载模块树(b_module),用于渲染树、上级模块选项与编码重复校验。
|
||
- 点击/右键树节点时才加载该模块的字段定义、操作权限、多语言翻译、用户模块/权限分配,并构建编辑草稿。
|
||
- 保存、删除、刷新后只刷新树与「当前选中模块」的详情,不再全量加载。
|
||
- 新建模块、取消编辑保持原交互;保存/删除的级联逻辑在懒加载下依然正确。
|
||
|
||
## 技术栈
|
||
|
||
- 前端:Vue 3 `<script setup>` + Vite + antdv-next + Pinia(沿用现有项目栈)
|
||
- 数据访问:复用通用端点 `loadDataApi(viewName, searchCondition, orderBy)`(POST `/api/data/loaddata`),不新增后端 URL
|
||
- 复用现有组件 `ModuleTreePanel` / `ModuleBasicInfoPanel` / `ModuleFieldDefinitionPanel` / `ModulePowerPanel`,仅调整 `index.vue` 的加载编排
|
||
|
||
## 实现方案
|
||
|
||
### 策略
|
||
|
||
将当前的 `loadAll()` 拆分为「树加载」与「模块详情懒加载」两步:
|
||
|
||
1. `loadModules()`:仅 `loadDataApi("b_module", null, "b_xh ASC")` 填充 `data.modules`(树、上级选项、编码校验所需)。
|
||
2. `loadModuleDetail(moduleCode)`:先定位 `data.modules` 中的模块,再按 `b_module_id` 条件并发加载 `b_module_field`、`b_module_power`、`b_i18n`(按模块编码过滤)、`b_user_module`;随后以已加载的权限 id 列表为条件加载 `b_user_power`。加载完成后调用现有 `selectModule(moduleCode)` 从已加载的 `data` 构建 `draft`。
|
||
3. 树节点 `@select` / 右键 `@select` 改为调用 `loadModuleDetail`;`@refresh`、保存后、删除后统一复用 `loadModules()` +(必要时)`loadModuleDetail(selectedKey)`。
|
||
|
||
### 关键技术决策
|
||
|
||
- **翻译按模块过滤**:多语言键规则为 `module.<code>`、`field.<code>.<字段>`、`power.<code>.<权限>`。用条件 `b_key = 'module.<code>' OR b_key LIKE 'field.<code>.%' OR b_key LIKE 'power.<code>.%'` 在后端过滤,避免一次性加载全量 b_i18n。`.%` 后缀确保不会被 `m_page` 误匹配到 `m_page_x` 等。code 来自数据库且为小写字母数字下划线,仍做单引号转义防御。
|
||
- **用户分配按当前模块加载**:`b_user_module` 用 `b_module_id = <id>` 过滤;`b_user_power` 依赖权限 id,故在 powers 加载完成后用 `b_power_id IN (...)` 二次加载。保证 `saveModule` 的权限回收与 `deleteModule` 的级联删除逻辑数据完整。
|
||
- **保留草稿构建逻辑**:`selectModule` 仅负责从已加载的 `data`(fields/powers/translations)按模块过滤并构建 `draft`,不含网络请求,最小化改动面;`resetDraft` 直接复用它从已加载数据重建。
|
||
- **不回归**:`parentOptions`/`directoryParentOptions`/`validate()` 依赖 `data.modules`(已加载);新建、取消、保存、删除交互保持不变。
|
||
|
||
### 性能与可靠性
|
||
|
||
- 挂载请求由 6 个并发降为 1 个(b_module),点击节点时按模块加载 4~5 个按需查询,随模块规模线性而非全量。
|
||
- `loading` 复用现有 `a-spin` 同时驱动树刷新按钮与右侧工作区,加载期间提供反馈。
|
||
- 对 `handleSelect` 增加 `loading` 守卫,避免并发重复加载。
|
||
- bigint 的 `b_module_id`/`b_power_id` 以字符串拼接条件(与原 `diffRows` 中 `String(item.b_id)` 处理一致),避免精度问题。
|
||
|
||
## 实现注意
|
||
|
||
- 仅修改 `fms-vue/src/views/module-management/index.vue`,不触碰 `api.js`、通用端点、全局组件与其他页面。
|
||
- `data` 响应式结构(modules/fields/powers/translations/userModules/userPowers)保持不变;`clearDraft` 仍负责清空选中态与详情。
|
||
- 删除当前模块后 `selectedKey` 对应的模块已从 `data.modules` 移除,应走 `clearDraft` 分支;删除非当前模块则重新加载当前详情。
|
||
- 新建模块期间刷新:只刷新树(loadModules),不要因 `selectedKey` 不存在而清空新建草稿。
|
||
|
||
## 架构设计
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[onMounted] --> B[loadModules 仅 b_module]
|
||
B --> C[渲染模块树]
|
||
D[点击/右键树节点] --> E[loadModuleDetail 按模块懒加载]
|
||
E --> F[并发: b_module_field / b_module_power / b_i18n / b_user_module]
|
||
F --> G[按权限id加载 b_user_power]
|
||
G --> H[selectModule 构建 draft]
|
||
H --> I[右侧展示详情]
|
||
J[保存/删除/刷新] --> B
|
||
J --> K{有选中且非新建?}
|
||
K -->|是| E
|
||
K -->|否| L[clearDraft]
|
||
```
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
fms-vue/src/views/module-management/
|
||
└── index.vue # [MODIFY] 模块管理主视图。拆分 loadAll 为 loadModules(仅加载 b_module 树数据)与
|
||
# loadModuleDetail(按选中模块懒加载字段/权限/翻译/用户分配并构建 draft);
|
||
# 新增 translationSearchCondition 按模块编码过滤 b_i18n;
|
||
# 树 @select/@refresh、onMounted、saveModule/deleteModule 末尾统一调用新加载函数;
|
||
# 保留 selectModule/resetDraft/clearDraft 与级联保存删除逻辑。
|
||
```
|
||
|
||
## 关键代码结构
|
||
|
||
```js
|
||
// 按模块编码过滤多语言键(精确前缀,避免误匹配)
|
||
function translationSearchCondition(code) {
|
||
const c = String(code).replace(/'/g, "''");
|
||
return `b_key = 'module.${c}' OR b_key LIKE 'field.${c}.%' OR b_key LIKE 'power.${c}.%'`;
|
||
}
|
||
|
||
// 树加载:仅 b_module
|
||
async function loadModules() { /* loadDataApi("b_module", null, "b_xh ASC") -> data.modules */ }
|
||
|
||
// 详情懒加载:选中节点时按模块加载字段/权限/翻译/用户分配
|
||
async function loadModuleDetail(moduleCode) {
|
||
const module = data.modules.find((i) => i.b_code === moduleCode);
|
||
if (!module) return;
|
||
// 并发加载 b_module_field(b_module_id=id)、b_module_power(b_module_id=id)、
|
||
// b_i18n(translationSearchCondition(code))、b_user_module(b_module_id=id)
|
||
// 再以权限 id 列表加载 b_user_power(b_power_id IN (...))
|
||
// 写入 data 后调用 selectModule(moduleCode)
|
||
}
|
||
``` |