Skip to content

选人组件升级:能力探测的分层 PeoplePicker(搜索优先默认 + 组织树/人群渐进增强) #2112

Closed

Description

@baozhoutao

背景 / 问题

当前的「选人」能力是把通用 LookupField 套在 sys_user 上实现的(UserField 只是把 reference 锁死为 sys_user、display_field 设为 name 的薄包装,见 packages/fields/src/widgets/UserField.tsx)。也就是说——选的时候和普通 lookup 没区别(同一个搜索弹窗 + RecordPickerDialog),只有表格只读单元格用 UserCellRenderer 渲染成头像。

这套在小团队够用,但面向真实组织(几百~上千人)时体验明显不足:

  1. 只有"搜索 + 平铺列表",没有组织维度——用户记不全名字就抓瞎,缺部门/团队这个导航入口。
  2. 多选体验弱——逐个勾选,没有"已选区"实时回显、不能整组选、勾错要回列表里找。
  3. 消歧信息不足——同名的人在列表里只有名字,缺部门/邮箱等二级标识。
  4. 头像依赖 $expand——拿不到展开对象时 UserCellRenderer 退化成纯 ID 文本(见 packages/fields/src/index.tsx 的 UserCellRenderer)。
  5. 无人事语义——离职/停用过滤、"最近联系人""我的下属"等都没有。

模式调研结论

  • 左组织树 + 右成员 + 穿梭框 是钉钉/企业微信/飞书等「组织通讯录」产品的中国特色形态。
  • 西方企业软件(Salesforce / Workday / ServiceNow)几乎不用树浏览,而是 搜索优先 + 声明式候选过滤(Lookup Filter) + 命名人群(Public Group / Queue / Team)——用抽象让你"几乎不需要从几千人里手挑个人"。
  • 关键约束:ObjectUI 是通用开发工具,不是每个客户都启用组织结构,因此选人器不能硬依赖 sys_business_unit 被启用/填充。

方案:能力探测的分层选人器(PeoplePicker)

把"搜索优先"作为默认主形态,组织树和选人群作为 capability-detected 的渐进增强,运行时探测对象是否启用,没启用就根本不渲染对应 UI——对未开组织功能的客户零负担。

档位 触发条件 形态 对标
Tier 0 搜索版(默认) 永远可用,零配置 type-ahead + 最近联系人 + 富信息行(头像/部门/邮箱)+ 已选区 chips Salesforce lookup
Tier 1 + 组织树 探测到 sys_business_unit 存在且有数据 左树点亮(SplitPaneGrid + ObjectTree),点节点过滤右列表 钉钉/飞书
Tier 2 + 选人群 探测到 sys_team 启用 可直接选 Team/Group,不展开成员 SF Public Group/Queue

复用现有积木(不重造轮子)

  • 分栏 + 拖拽:packages/plugin-grid SplitPaneGrid
  • 左树:packages/plugin-tree ObjectTree(sys_business_unit 已自带 org_chart 的 type:'tree' 视图,parentField: 'parent_business_unit_id',可直接复用)
  • 右列表查询/分页/多选内核:从 packages/fields/src/widgets/RecordPickerDialog.tsx 抽出复用
  • 最近联系人:已有的 recentLookups

数据模型(已确认,平台对象)

源自 framework-stagefield-typed/packages/platform-objects/src/identity/:

  • sys_business_unit — 部门/组织树,父字段 parent_business_unit_id,带 kind/active/manager_user_id。
  • sys_business_unit_member — 用户↔部门多对多 junction(is_primary、role_in_business_unit、effective-dated,支持矩阵组织)。
  • sys_user.primary_business_unit_id — 平台已为本场景冗余的字段,注释原文:"Lets a user-lookup filter candidates by business unit without traversing the membership junction"。→ 右列表按部门筛人走它即可,无需碰 junction。
  • sys_team / sys_team_member — 扁平协作分组(= SF Public Group),Tier 2 的人群来源。
  • sys_user 可用展示字段:name(searchable)、email(searchable)、image(头像)、manager_id;无 job_title → 富信息行副标题用 email + 部门名;离职过滤用 banned。

作者侧 opt-in(草案)

Field.user({
  multiple: true,
  picker: 'org-tree',          // 缺省 'search' = 现状;探测不到组织对象时自动降级回搜索
  org: { object: 'sys_business_unit', userOrgField: 'primary_business_unit_id', cascade: false },
  display: { subtitle: ['primary_business_unit_id.name', 'email'], avatarField: 'image' },
  filters: { banned: { $ne: true } },
})

待定决策

  • 右列表显示"主属成员"还是"全部成员(含矩阵)"? 建议默认主属(走 primary_business_unit_id,简单快),cascade(选父含子)+ 矩阵全成员(走 junction)作为 P2 开关。

落地顺序

  • Step 1 从 RecordPickerDialog 抽出 useRecordQuery(查询/分页/多选状态),现有 dialog 切过去验证不回归。
  • Step 2 SelectionTray + 富信息行 → 单栏多选选人器(= Tier 0,零依赖、可独立上线)。
  • Step 3 加能力探测 + 左树(ObjectTree + SplitPaneGrid)→ Tier 1,仅对启用组织的客户点亮。
  • Step 4 字段接线:picker 元数据 → widget registry,user/owner 按配置切换。
  • Step 5(可选) 选 Team/Group → Tier 2。

建议先交付 Step 1–2(Tier 0):老外也认、对所有客户都有价值、不依赖组织功能;组织树纯加分。

Activity

  1. os-zhuang commented on Jun 30, 2026

    @os-zhuang
    Contributor

    落地顺序收敛建议:第一版只做 Tier 0(搜索优先),把"搜索做强到能替代组织树"作为重点

    核对了 framework 的数据模型、并调研了中外主流选人器之后,建议把 v1 收敛为纯搜索版,组织树/公司多组织维度全部留到 v2 按需再上。理由:中国之所以离不开组织树,根因是"重名 + 搜索弱";只要把搜索做强(拼音 + 富信息行消歧),就直接顶替了组织树"导航 + 消歧"的核心价值,对中西客户都受用,且不依赖任何尚未拍板的多组织模型。

    v1 范围(Tier 0,可独立上线)

    • Step 1 — 从 RecordPickerDialog 抽出 useRecordQuery(查询/分页/多选状态),现有 dialog 切过去验证不回归(纯重构)。
    • Step 2 — 搜索增强 + SelectionTray + 富信息行。

    v1 搜索增强要点(这些才让搜索足以替代树)

    要素 作用
    拼音 / 首字母 / 工号 / 手机号搜索 中文消歧刚需——重名率高,纯名字搜不动(这是"完善搜索"的核心)
    富信息行(头像 + 部门 + 邮箱) 行内消歧,顶替组织树的"二级标识";部门只读 sys_user.primary_business_unit_id 展示字段,无需公司维度
    最近联系人 / 常用 recentLookups 已有,大多数选人是重复选那几个
    穿梭框式已选区(SelectionTray) 多选实时回显、可删
    过滤(离职 banned 等) 候选干净

    补充:UserCellRenderer 当前不加载头像图片、只渲染首字母(packages/fields/src/index.tsx),富信息行的真实头像走 sys_user.image 是净新增能力,不涉及 $expand 退化。

    v1 明确推迟(全部属于"树那一档",不阻塞 v1)

    • 组织树(SplitPaneGrid + ObjectTree)、能力探测 / 树开关
    • 公司/法人多组织维度 → 见 framework#2483(独立推进,v1 零依赖)
    • "上级看下级" / 组织数据权限裁剪(企业版,开源版 fail-closed 到 own)
    • cascade 整组选

    保留的架构钩子(避免 v2 返工)

    useRecordQuery 与 SelectionTray 做成可复用内核,v2 的树档右列表 + 已选区直接复用——树是"接线",不是"重做"。


    v2+ 组织树档的设计结论(先记录,供将来)

    这些是讨论中已澄清、但 v1 不实现的点,记录在此备查:

    • 三层 scope 必须分清(原 issue 未区分,易踩坑):
      • 可达上限 = 整个公司森林(单租户集团内跨公司可见,由 RLS 自动框定);
      • 树根 = sys_business_unit 中 parent_business_unit_id IS NULL AND active(可能多根 → 虚拟超级根);
      • 初始焦点 = 当前用户主公司 → 主属 BU(primary_business_unit_id 往上爬父链)。
    • 不要把树根钉死在用户部门——否则集团财务无法跨子公司选人;主部门只是 focus,非边界。
    • root-agnostic + 兜底:无公司用户(无 BU membership)→ 无 focus,退回全租户搜索,不报错。
    • 公司层 = 来自 sys_company 的虚拟节点(framework#2483);公司本身有股权/并表层级(parent_company_id),与 BU 组织树正交。
    • 选岗位 / 角色(运行时解析成人) = Tier 2 的扩展,对标中国流程选人(审批节点选"岗位/角色"而非具体人)。
    • 中国选人器形态参考:左树 + 右穿梭框 + 拼音搜索 + 整组选 + Tab 化入口(组织架构/最近/我的下属/常用)。

    关联

    • framework#2483 — 分拆 sys_company 维度(v1 不依赖,v2 树档的公司层来源)

    Generated by Claude Code

  2. os-zhuang commented on Jun 30, 2026

    @os-zhuang
    Contributor

    v1 进一步简化:采用"租户-per-子公司",公司/组织树跨公司档暂缓

    架构决定:每个公司 = 一个租户(隔离靠现有多租户 RLS),集团级查看走超管。对选人器的影响:

    • Tier-1 简化为"本租户人员目录 +(可选)本子公司自己的 BU 树"——之前讨论的公司森林根 / 主公司 focus / 上级看下级 / 跨公司候选裁剪 这些全部从 v1 移除(它们随 framework#2483 一并暂缓);
    • 租户内选人没有跨公司 scope,组织树就是本子公司一棵普通 BU 树(无虚拟公司根、无 root-agnostic 兜底);
    • 跨子公司选人是少数集团级流程,走超管/集团上下文,不在 v1;
    • 搜索版内核不变:useRecordQuery 抽取 + 富信息行(头像/部门/邮箱)+ SelectionTray 穿梭框 + recents + 过滤;
    • 拼音搜索仍按 framework#2486 透明点亮。

    即:v1 = 租户内、搜索优先的人员选择器;组织树(若做)退化为"本子公司 BU 树",无任何跨公司复杂度。

    framework#2483(租户内公司维度)已暂缓;将来出现复杂集团需求时,再回来加"组织树 + 跨公司档"。本帖上方那条评论里记录的三层 scope / 虚拟根等设计,保留备将来之用。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions