diff --git a/docs/superpowers/specs/2026-07-30-oa-person-select-design.md b/docs/superpowers/specs/2026-07-30-oa-person-select-design.md new file mode 100644 index 00000000..cad78467 --- /dev/null +++ b/docs/superpowers/specs/2026-07-30-oa-person-select-design.md @@ -0,0 +1,261 @@ +# 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 规则形态(示例) + +```json +{ + "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` 契约(锁定) + +```ts +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 }`。 + +--- + +## 6. 条件分支 + +### 6.1 资格 + +- 前端 `CONDITION_ELIGIBLE_TYPES` 与后端 `oa_publish.CONDITION_ELIGIBLE_TYPES` **均加入** `person`。 +- 勾选 `useInCondition` 后出现在条件候选列表;发布前强制 `$required` / `required`(现有归一化逻辑)。 + +### 6.2 规则形态 + +```ts +// 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 +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 +- [ ] 发起页可选人并正确提交 `{ id, name }` / 数组 +- [ ] 勾选参与条件后,流程条件仅「包含」,右侧可多选人,任一命中走对应分支 +- [ ] 详情与打印展示姓名;历史快照不随用户改名变化 +- [ ] 未知控件发布校验通过;既有金额/部门行为无回归