Files
SMANAGE/docs/entity_picker_standard.md
TermiNexus fd56a9e4ff feat(gerp): 部门新增成员改用部门员工关联选择器
UserRoleDepartment 表单人员输入统一为 DEPT_EMPLOYEE_PICKER(与 eHRM/工作组同源单一配置):新增虚拟列置顶,用户/角色列改为纯列表展示,DeptUUID 表单隐藏(上下文优先,否则取选择器解析的部门),删除就地写的 UserRoleSearch 选择器;add_record/modify_record 支持 UserRoleDepartmentUUID 解析 UserRoleUUID 并拒绝选到别的员工。
2026-10-01 14:16:18 +08:00

11 KiB
Raw Permalink Blame History

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。


七、约定(必须遵守)

  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 改动记得重启服务