docs(gerp): 新增实体选择器标准文档
记录 EntityPicker/MultiEntityPicker 的分层与单一来源、Search 接口数据契约、配置字段(含 treeGroup 与多选暂存)、部门员工关联常量的取值与落库对应、el-tree 自绘场景的 useDeptEmployeeTree,以及新增/改动时必须遵守的约定与变更检查清单。
This commit is contained in:
1 parent
edd4a85f98
commit
3859aa1762
1 file changed
+165
@@ -0,0 +1,165 @@
|
||||
# GERP 实体选择器(EntityPicker)标准 — 设计文档
|
||||
|
||||
> 适用范围:GERP 前端所有「选人 / 选部门 / 选职务 / 选应用实体」控件。
|
||||
> 目的:**一个数据源一份配置**,禁止"功能相同、两套代码"(历史上出过:同一份
|
||||
> 「部门员工关联」配置在后端写了两遍、批量职务弹窗又在前端自己分页拼了一次部门树)。
|
||||
|
||||
---
|
||||
|
||||
## 一、结论速览(先看这个)
|
||||
|
||||
| 场景 | 用什么 | 配置写在哪 |
|
||||
|------|--------|-----------|
|
||||
| 表单里选单个实体(列 `type='entityPicker'`) | `EntityPicker.vue` | 后端列定义 `form_service.py`(`ENTITY_PICKER_MAP` 或就地 `entityPicker` 字段) |
|
||||
| 表单里选多个实体(列 `multiValue=true`,如职务) | `MultiEntityPicker.vue` | 同上 |
|
||||
| 需要"按部门树 → 成员"两级浏览面板 | `EntityPicker` + `config.treeGroup` | 复用 `DEPT_TREE_GROUP`(后端) |
|
||||
| 需要"部门员工关联"(成员 + 所属部门) | `DEPT_EMPLOYEE_PICKER` / `..._BY_USER_ROLE` | 后端 `form_service.py` 常量,勿复制 |
|
||||
| 页面自绘 `el-tree` 勾选(如批量调整职务) | `composables/useDeptEmployeeTree.ts` | 前端 composable(内部调 `DeptEmployeeSearch`) |
|
||||
| 页面里直接用 `EntityPicker` 组件(非列驱动,如菜单编辑器选公司) | `api/entityPickerPresets.ts` 的预设 | 前端预设,勿就地写 api/labelField/valueField |
|
||||
|
||||
---
|
||||
|
||||
## 二、分层与单一来源
|
||||
|
||||
| 层 | 位置 | 职责 |
|
||||
|----|------|------|
|
||||
| 渲染层 | `GERP/src/components/EntityPicker.vue` | 搜索下拉 + 分页加载更多 + 浏览面板 + `treeGroup` 两级面板 + 多选暂存 |
|
||||
| 渲染层 | `GERP/src/components/MultiEntityPicker.vue` | 多值(标签 + 下拉 + 面板多选),支持 `treeGroup` |
|
||||
| 承载层 | `GERP/src/components/CrudFormPanel.vue` | 按列定义渲染表单控件(含 `required` 校验、级联参数透传) |
|
||||
| 承载层 | `GERP/src/components/CrudTable.vue` | 列表页新增/编辑;`picker-multi` → 批量新增 |
|
||||
| 配置层(**权威**) | 后端 `gvsds/apps/gerp/form_service.py` | `ENTITY_PICKER_MAP`(通用实体预设)、`DEPT_TREE_GROUP`、`DEPT_EMPLOYEE_PICKER`、`DEPT_EMPLOYEE_PICKER_BY_USER_ROLE` |
|
||||
| 配置层 | 前端 `GERP/src/api/entityPickerPresets.ts` | 页面直接使用组件时的 6 类实体预设 + `epColumn()` |
|
||||
| 自绘树数据源 | `GERP/src/composables/useDeptEmployeeTree.ts` | `el-tree` 场景的"部门树 → 成员"组装(同源 `DeptEmployeeSearch`) |
|
||||
| 后端数据源 | `gvsds/apps/gerp/views/form_api.py` | `DeptEmployeeSearchView` 等 Search 接口 |
|
||||
|
||||
**为什么配置放后端**:列定义由 `/API/FormAPI/Columns/<Table>` 下发,前端只负责渲染 ——
|
||||
同一张表在列表页、详情页、弹窗里形态天然一致;改一处即全站生效。
|
||||
|
||||
---
|
||||
|
||||
## 三、数据契约(Search 接口)
|
||||
|
||||
### 3.1 响应结构
|
||||
|
||||
```jsonc
|
||||
{ "code": 0, "data": {
|
||||
"list": [ { /* 行 */ } ],
|
||||
"has_more": true,
|
||||
"tree": [ { "uuid": "…", "name": "…", "parentUuid": "…" } ] // 传 tree=1 时返回
|
||||
} }
|
||||
```
|
||||
|
||||
- 分页:`offset`(步长 20,前端"加载更多"/自动拉全)
|
||||
- 关键字:`keyword`(同时匹配多个字段,见各接口 docstring)
|
||||
- 公司过滤:`CompanyUUID`(显式传入强制该公司;不传则回退当前角色公司;无公司上下文返回空列表)
|
||||
- 去重:`Dedup=UserRole`(`DeptEmployeeSearch` 专用:同一"用户-角色对"多部门只留一条)
|
||||
|
||||
### 3.2 接口清单
|
||||
|
||||
| 接口 | 值字段(valueField) | 备注 |
|
||||
|------|---------------------|------|
|
||||
| `/API/FormAPI/UserSearch` | `UserUUID` | 副文本 `UserEmail` |
|
||||
| `/API/FormAPI/RoleSearch` | `RoleUUID` | 支持 `dependsOn=UserUUID` 级联 |
|
||||
| `/API/FormAPI/CompanySearch` | `CompanyUUID` | |
|
||||
| `/API/FormAPI/TenantSearch` | `TenantUUID` | |
|
||||
| `/API/FormAPI/DepartmentSearch` | `DeptUUID` | |
|
||||
| `/API/FormAPI/WorkgroupSearch` | `WorkgroupUUID` | |
|
||||
| `/API/FormAPI/PermissionSearch` | `PermUUID` | 副文本 `PermCode` |
|
||||
| `/API/FormAPI/PositionTemplateSearch` | `PositionTemplateUUID` | |
|
||||
| `/API/FormAPI/PositionSearch` | `PositionUUID` | 带 `tree`(通用部门树),groupField `CategoryUUID` |
|
||||
| `/API/FormAPI/PositionCategorySearch` | `PositionCategoryUUID` | |
|
||||
| `/API/FormAPI/MenuSearch` | `MenuUUID` | |
|
||||
| `/API/FormAPI/ShopSearch` | `ShopUUID` | 本地 7 库无表,预留 |
|
||||
| `/API/FormAPI/UserRoleSearch` | `UserRoleUUID` | 用户-角色对;带 `tree`(部门树) |
|
||||
| `/API/FormAPI/DeptEmployeeSearch` | `UserRoleDepartmentUUID` / `UserRoleUUID` | **部门员工关联**(部门 + 用户-角色对);带 `tree`、`DeptEmployeeLabel`、`RoleName`;支持 `Dedup=UserRole`、`DeptUUID` 过滤 |
|
||||
|
||||
---
|
||||
|
||||
## 四、配置字段(`EntityPickerConfig`)
|
||||
|
||||
定义见 `GERP/src/types/api.ts`。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `api` / `method` | 搜索接口(默认 GET) |
|
||||
| `labelField` / `valueField` | 显示字段 / **取值字段(必须等于落库列)** |
|
||||
| `secondaryField` | 副文本字段(如 `RoleName`、`UserEmail`) |
|
||||
| `placeholder` | 占位文案 |
|
||||
| `fetchOnOpen` / `paged` | 打开即加载前 20 条 / 底部"加载更多"(默认均开启) |
|
||||
| `searchParam` / `resultPath` | 关键字参数名(默认 `keyword`)/ 响应数据路径(默认 `data`) |
|
||||
| `dependsOn` | 级联:请求时附带表单中该字段的当前值(如 `RoleUUID` 依赖 `UserUUID`) |
|
||||
| `dependCompany` | 请求时附带表单 `CompanyUUID`(表单无该字段则不发送,后端回退当前角色公司) |
|
||||
| `treeGroup` | 浏览面板改为"树分组 + 成员"两级结构,见下 |
|
||||
|
||||
### `treeGroup` 子字段
|
||||
|
||||
| 子字段 | 说明 |
|
||||
|--------|------|
|
||||
| `groupField` | 记录上的归组字段(如 `DeptUUID`);配置后前端自动附带 `tree=1` |
|
||||
| `treeField` | 响应中树节点数组字段名(默认 `tree`) |
|
||||
| `ungroupedTitle` | 未归组记录的兜底分组标题(默认"未分组") |
|
||||
| `multiSelect` | 开启后浏览面板出现"多选"开关,成员进暂存区、底部"全部提交"批量返回(`submit-multi` → `CrudTable` 批量新增) |
|
||||
| `roleTypeOptions` | 暂存区成员类型下拉(如 成员/负责人),值随 `submit-multi` 返回写入 `RoleType` |
|
||||
|
||||
---
|
||||
|
||||
## 五、「部门员工关联」选择器(单一来源示例)
|
||||
|
||||
后端常量(`gvsds/apps/gerp/form_service.py` 顶部,含完整注释):
|
||||
|
||||
| 常量 | valueField | 附加行为 | 使用点 |
|
||||
|------|-----------|---------|--------|
|
||||
| `DEPT_TREE_GROUP` | —(公共 treeGroup) | 部门树分组 + 未分配部门兜底 | 下列两处 + `UserRoleDepartment.UserRoleUUID` |
|
||||
| `DEPT_EMPLOYEE_PICKER` | `UserRoleDepartmentUUID` | 单选,`secondaryField=RoleName` | `EmployeeProfile.UserRoleDepartmentUUID`(eHRM 员工档案) |
|
||||
| `DEPT_EMPLOYEE_PICKER_BY_USER_ROLE` | `UserRoleUUID` | `Dedup=UserRole` + 多选暂存 + 成员/负责人 | `UserRoleWorkgroup.UserRoleUUID`(工作组新增成员) |
|
||||
|
||||
**取值与落库的对应关系**(改取值列必须同步后端解析):
|
||||
|
||||
- `UserRoleUUID` → 直接落 `UserRoleWorkgroup.UserRoleUUID`(关系表只存用户-角色对,**不含部门**;部门仅用于选择器分组展示)
|
||||
- `UserRoleDepartmentUUID` → 由 `FormService._ResolveDeptEmployee()` 解析出
|
||||
`UserUUID` / `DeptUUID` / `DeptName` 再落 `EmployeeProfile`
|
||||
|
||||
**唯一性约束**:员工档案一人一份 —— 新增与改绑均由 `form_service.add_record/modify_record` 拦截
|
||||
(前端在已有档案时隐藏新增入口)。
|
||||
|
||||
---
|
||||
|
||||
## 六、`el-tree` 自绘场景
|
||||
|
||||
批量调整职务弹窗(`components/BatchPositionDialog.vue`)需要"树 + 勾选",无法直接复用下拉形态的
|
||||
`EntityPicker`,因此抽 `composables/useDeptEmployeeTree.ts`:
|
||||
|
||||
- 数据源与 `EntityPicker(treeGroup)` **同源**(`/API/FormAPI/DeptEmployeeSearch`)
|
||||
- 分页拉全 → 用服务端 `tree` 组装 `el-tree` 数据 → 成员按 `DeptUUID` 归组,未挂部门进"未分组"
|
||||
- 单部门模式(传 `DeptUUID`,部门详情页)返回单个分组节点
|
||||
- 返回 `{ tree, groupIds }`:`groupIds` 用于提交时过滤掉分组节点,只提交成员 PK
|
||||
- 成员叶子取值列由 `ValueField` 指定(公司/部门列表页 `UserRoleUUID`;部门详情页 `UserRoleDepartmentUUID`)
|
||||
|
||||
对应提交表:`UserRole`(公司页)/ `UserRoleDepartment`(部门页)/ `UserRoleWorkgroup`(工作组页),
|
||||
统一走 `formApi.batchPositions()` → 后端 `FormService.batch_positions`。
|
||||
|
||||
---
|
||||
|
||||
## 七、约定(必须遵守)
|
||||
|
||||
1. **禁止就地复制 `entityPicker` 配置**:能命中 `ENTITY_PICKER_MAP` 或上述部门常量就复用;
|
||||
确有新形态时,先把配置抽成常量(后端)或预设(前端),再在两个以上使用点引用。
|
||||
2. **`valueField` 必须等于落库列**;改取值列要同步后端解析/校验逻辑与唯一性约束。
|
||||
3. **"部门树分组"一律走 `DeptEmployeeSearch` + `DEPT_TREE_GROUP`**,不要另拉 `UserRoleSearch`
|
||||
在前端自己拼树(`UserRoleSearch` 仅用于"用户-角色对"场景)。
|
||||
4. **`el-tree` 自绘场景用 `useDeptEmployeeTree`**,不要自己写分页 + 拼树 + 标签组装。
|
||||
5. **前端预设 `entityPickerPresets.ts` 只在页面直接使用 `EntityPicker` 组件时使用**;
|
||||
列驱动场景一律由后端下发配置。
|
||||
6. 改动任何一处共享配置后,**先 grep 使用点做回归**,再以 `GERP\deploy.ps1`
|
||||
(`vue-tsc -b` + `vite build`)与 `python manage.py check` 作为验收。
|
||||
|
||||
---
|
||||
|
||||
## 八、变更检查清单
|
||||
|
||||
- [ ] 新配置是否复用了既有常量/预设?没有 → 是否应该抽成常量?
|
||||
- [ ] `valueField` 与落库列一致?后端解析/唯一性约束已同步?
|
||||
- [ ] `treeGroup` 是否用了公共 `DEPT_TREE_GROUP`?数据源是否 `DeptEmployeeSearch`?
|
||||
- [ ] 自绘树是否走了 `useDeptEmployeeTree`?
|
||||
- [ ] `grep -rn "DeptEmployeeSearch\|treeGroup\|entityPicker:" GERP/src gvsds/apps/gerp` 是否出现新的重复定义?
|
||||
- [ ] 前端 `deploy.ps1` 全绿;后端 `manage.py check` 0 issue;涉及 Python 改动记得重启服务
|
||||
Reference in new issue
Block a user