UserRoleDepartment 表单人员输入统一为 DEPT_EMPLOYEE_PICKER(与 eHRM/工作组同源单一配置):新增虚拟列置顶,用户/角色列改为纯列表展示,DeptUUID 表单隐藏(上下文优先,否则取选择器解析的部门),删除就地写的 UserRoleSearch 选择器;add_record/modify_record 支持 UserRoleDepartmentUUID 解析 UserRoleUUID 并拒绝选到别的员工。
178 lines
11 KiB
Markdown
178 lines
11 KiB
Markdown
# 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` / `DEPT_EMPLOYEE_PICKER_MULTI` | 后端 `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_MULTI` |
|
||
| 配置层 | 前端 `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) | 部门树分组 + 未分配部门兜底 | 被下面两个常量引用(勿单独就地使用) |
|
||
| `DEPT_EMPLOYEE_PICKER` | `UserRoleDepartmentUUID` | 单选 | `EmployeeProfile.UserRoleDepartmentUUID`(eHRM 员工档案)、`UserRoleDepartment.UserRoleDepartmentUUID`(部门新增成员,虚拟列) |
|
||
| `DEPT_EMPLOYEE_PICKER_MULTI` | `UserRoleDepartmentUUID` | 多选暂存 + 成员/负责人类型 | `UserRoleWorkgroup.UserRoleDepartmentUUID`(工作组新增成员,虚拟列) |
|
||
|
||
**三个使用点取值一致**(都是「部门员工关联」= 部门 + 该用户在本公司的用户-角色对),
|
||
区别只在单/多选;落库时由 `FormService._ResolveDeptEmployee()` **统一解析**(唯一实现):
|
||
|
||
- `EmployeeProfile`:取 `UserUUID` / `DeptUUID` / `DeptName` 三列落库(常量 `_PROFILE_DEPT_EMPLOYEE_KEYS` 声明)
|
||
- `UserRoleWorkgroup`:取 `UserRoleUUID` 落库到关系列(关系表只存用户-角色对,**部门仅用于选择/分组展示**);
|
||
表单里该关系列 `hideInForm`,由虚拟列 `UserRoleDepartmentUUID` 作为唯一人员选择入口
|
||
- `UserRoleDepartment`:取 `UserRoleUUID` 落库;`DeptUUID` **表单上下文优先**(部门详情页 = 当前部门),
|
||
无上下文时取选择器解析出的部门(员工详情页新增"部门职务"即此场景);
|
||
员工详情页上下文里 `UserUUID` 由 query 注入,选择器选到别的员工会被后端拒绝
|
||
(`所选「部门员工关联」不属于当前员工`)。两张关联表的 `UserUUID`/`RoleUUID` 列一律
|
||
`editable=False`(纯列表展示),人员输入只有选择器一个入口
|
||
|
||
> 已知取舍:选择器数据源是 `UserRoleDepartment`,因此**尚未挂靠任何部门的"用户-角色对"
|
||
> 选不到**。公司员工列表(`UserRole`)新增/编辑时若填了部门会同步建主关联,正常路径不受影响。
|
||
|
||
**唯一性约束**:员工档案一人一份 —— 新增与改绑均由 `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` 作为验收。
|
||
7. **浏览面板标题用 `browseTitle`**(`EntityPicker` / `MultiEntityPicker` 各有一份):占位文案
|
||
已以「请选择 / 选择」开头时不再加前缀,避免出现"选择选择部门员工关联…"。面板统一
|
||
`width="680px"`、列表 `min-height: 420px / max-height: 560px`,两组件保持一致。
|
||
|
||
---
|
||
|
||
## 八、变更检查清单
|
||
|
||
- [ ] 新配置是否复用了既有常量/预设?没有 → 是否应该抽成常量?
|
||
- [ ] `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 改动记得重启服务
|