2026-07-30-oa-person-select-design.md 10 KB

OA「人员」控件设计文档

日期: 2026-07-30
范围: web-admin 审批设计器步骤 2(表单设计)+ 步骤 3(条件分支)+ 发起/详情/打印运行时;backend 发布白名单、发起校验、条件匹配
关联: docs/superpowers/specs/2026-07-27-oa-dept-select-design.md;docs/superpowers/specs/2026-07-15-oa-condition-from-form-design.md;docs/superpowers/specs/2026-07-30-oa-money-field-design.md
状态: 已实现


1. 背景与目标

OA 审批「2 表单设计」需提供可拖拽的 人员 控件:从本租户系统用户表选择一人或多名人员;展示与交互对齐已落地的 部门 控件。勾选「参与流程条件分支」后,可在「3 流程设计」中配置钉钉式「包含」条件(右侧可选多人,任一命中即通过)。

1.1 产品结论(已确认)

项 结论
展示/交互 对齐「部门」:PC 下拉 + 窄屏抽屉;多选 tag 在选择框内
选择能力 默认可多选;设计器可改为单选;可配 max
数据源 sys_user(fetchUserList,status=1 启用用户);租户隔离走现有 admin API
存值 单选 { id, name };多选同结构数组(name 为展示名快照)
条件运算符 仅「包含」(contains);不提供等于/不等于
包含语义 条件右侧可多选人;表单人员 id 集合 ∩ 条件 id 集合 非空 即命中(OR)
实现路径 自定义 FormCreate type=person(四层扩展,同部门/金额)

1.2 非目标

  • 等于 / 不等于 运算符
  • 按部门树筛人(可后续加)
  • 用本控件结果直接改写审批人解析(仍用现有 assigneeType 策略)
  • 本期小程序运行时渲染器(Schema/form_data 须可映射)
  • 行业通讯录 / 外部联系人

2. 架构

对齐 FormCreate / FcDesigner 四层扩展:

层 内容
Runtime OaPersonSelect.vue:v-model 为对象或对象数组
FormCreate 组件注册 formCreate.component('person', …)
DragRule menu: 'main';label: '人员';props.multiple 默认 true
双侧注册 主应用 + oa-fc-host 均调用 setupOaPersonSelect
[设计器 DragRule] → form_schema_json.type = "person"
        ↓
[OaPersonSelect] ← fetchUserList(status=1)
        ↓
form_data[field] = { id, name } | { id, name }[]
        ↓
[条件] useInCondition → op=contains, value=string[] (user ids)
        ↓
[引擎] id 集合交集非空 → 命中分支
[详情/打印] 优先展示 name 快照

3. Schema 与属性

3.1 规则形态(示例)

{
  "type": "person",
  "field": "Fxxxx",
  "title": "协同人",
  "info": "",
  "$required": false,
  "useInCondition": false,
  "props": {
    "multiple": true,
    "max": null,
    "useInCondition": false
  }
}

3.2 设计器可配属性

属性 说明
标题 / 提示信息 / 必填 与现有控件一致
允许多选 Switch,默认 true
最大可选数 可选;仅多选时生效;不填不限制
参与流程条件分支 基础配置全局开关(person 加入 CONDITION_ELIGIBLE_TYPES 后可见);勾选后强制必填

4. form_data 契约(锁定)

type OaPersonValueItem = {
  id: string   // sys_user.id (UUID)
  name: string // 提交时展示名快照:优先 nickName,空则 userNo
}

// 单选(props.multiple === false)
type PersonFormValueSingle = OaPersonValueItem | null

// 多选(props.multiple === true,默认)
type PersonFormValueMulti = OaPersonValueItem[]

约束:

  • id、name 均为非空字符串。
  • 禁止与 multiple 不一致的标量/对象混用。
  • 用户改名后,历史实例 SHALL 仍展示提交时的 name 快照。
  • 后端发起校验 SHALL 校验值形态;MAY 校验 id 对应当前租户启用用户(建议校验,失败返回中文错误)。

5. 运行时交互

5.1 PC(视口宽度 ≥ 768)

  • el-select:filterable、clearable;multiple 时多选,已选以 select 内 tag 展示(对齐部门 tree-select 多选表现)。
  • 选项 label:nickName(空则 userNo);可附带次要信息区分重名,写入 name 的仍为主展示名。

5.2 窄屏(< 768)

  • 触发按钮 + 底部 el-drawer(仿部门)。
  • 抽屉内:搜索 + 单选点选 / 多选勾选;底栏取消(Close)/ 确定(Check)。

5.3 只读

  • 单选:展示 name;空为 —。
  • 多选:姓名用「、」拼接。

5.4 超限

  • max 有值且超限:前端 ElMessage.warning,不写入;后端校验兜底。

5.5 数据加载

  • fetchUserList({ status: '1', page, pageSize }) 分页拉全量(与发起页 / 流程设计器选人一致)。
  • 组件内维护 id→name 映射,将 select 的 id 还原为 { id, name }。

5.6 设计器「设备切换」与「预览」兼容

FormCreate 在 oa-fc-host 中有两处预览相关能力,人员控件 SHALL 同时可用:

场景 行为约束
顶部 PC / Pad / Mobile 设备切换 Mobile 时画布 ._fc-m-drag.mobile 固定约 400px 宽。iframe 视口仍可能 ≥768,不能仅依赖 matchMedia 才可用。PC 路径的 el-select SHALL 全宽、min-width:0,多选 tag 可折叠/换行,禁止横向撑破画布。
工具栏 预览 按钮 弹窗内 ViewForm 渲染同一套已注册 person 组件;setupOaPersonSelect 必须在 oa-fc-host 注册,否则预览空白。抽屉(若触发)append-to-body。
发起页 流程预览 填写人员后 form_data 进入 fetchOaFlowPreview;条件 contains 由引擎集合匹配驱动分支展示。

窄屏抽屉(matchMedia max-width: 767px)仍保留,用于真实窄 viewport / 真机,与部门一致。

全局运行时样式:oa-fc-runtime.scss 将 .oa-person-select 与 .oa-dept-select 并列,保证列宽内不溢出。


6. 条件分支

6.1 资格

  • 前端 CONDITION_ELIGIBLE_TYPES 与后端 oa_publish.CONDITION_ELIGIBLE_TYPES 均加入 person。
  • 勾选 useInCondition 后出现在条件候选列表;发布前强制 $required / required(现有归一化逻辑)。

6.2 规则形态

// ConditionOp 扩展
type ConditionOp = '>' | '>=' | '<' | '<=' | '==' | '!=' | 'contains'

interface ConditionRule {
  field: string
  op: 'contains'  // person 字段仅允许此 op
  value: string[] // 用户 id 列表,长度 ≥ 1
}

6.3 命中语义

formIds = extract_user_ids(form_data[field])  // 单对象或数组 → Set<id>
condIds = Set(rule.value)
hit = |formIds ∩ condIds| > 0

类比钉钉审批「发起人属于这些人」:条件侧多人,任一出现在表单已选人员中即命中。

6.4 条件编辑器 UI

  • 选中 person 字段:运算符固定为「包含」(可只读展示)。
  • 右侧:人员多选(可搜),value 为 string[]。
  • 预览:{标题} 包含 张三、李四。
  • 发布/保存流程时:contains 且 value 为空 → 拒绝。

6.5 引擎实现要点

  • SHALL NOT 将人员对象塞进现有 {field} == 'x' 公式字符串做比较。
  • 在 _match_condition_group(或等价路径)对 op === 'contains' 走专用集合判断;从 form 值中抽取 id(兼容单对象与数组)。
  • 其他字段的 == / 数值比较逻辑保持不变。

7. 文件改动清单

7.1 前端(拟新增)

路径 职责
web-admin/src/components/oa-approval/OaPersonSelect.vue 运行时控件
web-admin/src/utils/oa-person-value.ts normalize / 展示
web-admin/src/utils/oa-person-select-rule.ts DragRule
web-admin/src/utils/oa-form-create-person.ts 注册入口

7.2 前端(拟修改)

路径 职责
web-admin/src/main.ts、oa-fc-host/main.ts setupOaPersonSelect
oa-condition-fields.ts 合格类型 + contains + opsForFieldType
flow-tree/types.ts ConditionOp / ConditionRule.value
ConditionRuleEditor.vue person 右侧多选
utils/oa-approval-print.ts 等 person 打印展示

7.3 后端

路径 职责
app/services/oa_publish.py FILLABLE_TYPES / CONDITION_ELIGIBLE_TYPES 加入 person
app/services/oa_person_select.py(新建) form_data 值形态校验(仿 oa_dept_select)
app/services/oa_engine.py contains 集合匹配
app/services/oa_related_approval.py 摘要格式化(仿 dept)
发起/提交入口 挂接 person 校验

8. 错误处理

场景 行为
用户列表加载失败 选项空;可提示;不阻断设计器
超 max 前端拦截 + 后端校验
条件 contains 且 value 空 发布/保存流程拒绝
值形态非法 发起返回中文错误列表

9. 测试要点

  • 单选/多选存值与回显;窄屏抽屉确认写入。
  • useInCondition 对 person 可见;勾选后必填。
  • 条件:表单选 A,条件 [A,B] → 命中;条件仅 [B] 且表单无 B → 不命中。
  • 发布含 person 的应用不报未知控件。
  • 打印 / 关联审批摘要显示姓名快照。
  • 回归:金额数值条件、部门存值与打印不受影响。

10. 实现顺序建议

  1. 值工具 + 后端 FILLABLE/CONDITION 白名单 + person 校验骨架
  2. OaPersonSelect + DragRule + 双侧注册
  3. 条件类型/contains + ConditionRuleEditor 人员多选
  4. 引擎集合匹配 + 单测
  5. 打印/关联审批格式化 + 端到端手测

11. 验收标准

  • 设计器左侧可拖入「人员」,可配单多选与 max
  • Mobile 设备画布与「预览」弹窗中人员控件可正常选择且不横向溢出
  • 发起页可选人并正确提交 { id, name } / 数组;流程预览随 contains 条件切换分支
  • 勾选参与条件后,流程条件仅「包含」,右侧可多选人,任一命中走对应分支
  • 详情与打印展示姓名;历史快照不随用户改名变化
  • 未知控件发布校验通过;既有金额/部门行为无回归