UserRoleDepartment 表单人员输入统一为 DEPT_EMPLOYEE_PICKER(与 eHRM/工作组同源单一配置):新增虚拟列置顶,用户/角色列改为纯列表展示,DeptUUID 表单隐藏(上下文优先,否则取选择器解析的部门),删除就地写的 UserRoleSearch 选择器;add_record/modify_record 支持 UserRoleDepartmentUUID 解析 UserRoleUUID 并拒绝选到别的员工。
11 KiB
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 响应结构
{ "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。
七、约定(必须遵守)
- 禁止就地复制
entityPicker配置:能命中ENTITY_PICKER_MAP或上述部门常量就复用; 确有新形态时,先把配置抽成常量(后端)或预设(前端),再在两个以上使用点引用。 valueField必须等于落库列;改取值列要同步后端解析/校验逻辑与唯一性约束。- "部门树分组"一律走
DeptEmployeeSearch+DEPT_TREE_GROUP,不要另拉UserRoleSearch在前端自己拼树(UserRoleSearch仅用于"用户-角色对"场景)。 el-tree自绘场景用useDeptEmployeeTree,不要自己写分页 + 拼树 + 标签组装。- 前端预设
entityPickerPresets.ts只在页面直接使用EntityPicker组件时使用; 列驱动场景一律由后端下发配置。 - 改动任何一处共享配置后,先 grep 使用点做回归,再以
GERP\deploy.ps1(vue-tsc -b+vite build)与python manage.py check作为验收。 - 浏览面板标题用
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 check0 issue;涉及 Python 改动记得重启服务