日期: 2026-07-23
范围:
web-admin /#/system-manage/oa-approval/start 发起弹窗web-admin OaFlowPreview(审批流程预览时间轴)backend 发起 / 流程预览 / 引擎推进 / 进度未达节点解析关联:
docs/superpowers/specs/2026-07-15-oa-start-flow-preview-design.md(首版预览;自选曾用顶部选择器)docs/superpowers/specs/2026-07-17-oa-start-flow-preview-resolved-path-design.md(条件未齐占位 / 齐后最终路径)docs/superpowers/specs/2026-07-14-oa-approval-dingtalk-design.md §5.3(initiator_select 解析)关系: 本规格取代上述两份预览规格中「自选仍用弹窗顶部选择器 / 节点内点选为非目标」的约定;条件门闩、最终路径、卡片视觉仍沿用 2026-07-17。
assigneeType = initiator_select(发起人自选)。initiatorSelectUserIds。people 为空。advance_from 不传自选人列表;若后续节点仍是 initiator_select,无法正确建任务。进度「未到达」自选节点也只能从已建任务反推,无法覆盖尚未到达的节点。initiator_select 的审批 / 抄送节点按钉钉方式用 ➕ 按节点选人。initiatorSelectUserIds(本期从 Body/类型中删除;多余字段由 Pydantic 忽略,不做双读双写)。resubmit_instance(已废弃)。| 项 | 结论 |
|---|---|
| 选人粒度 | 按节点各自选人(对齐钉钉) |
| UI 位置 | 「审批流程」时间轴节点内;去掉顶部全局选择器 |
| 自选审批节点 | 必选 ≥1 人,否则不可提交 |
| 自选抄送节点 | 可不选;空则不生成抄送任务 |
| 未选占位文案 | 审批:「请选择审批人」;抄送:「请选择抄送人」 |
| 多人 | 节点内可多选;chip 可删;右侧保留 ➕ 继续加 |
| 与条件预览 | 路径未确定(resolved=false)时不展示可点选节点;路径确定后再选 |
| 自选是否挡整段预览 | 否(沿用 2026-07-17:路径齐即可出链,自选人可后补) |
form-create)不再出现顶部「自选审批人」el-form-item。
resolved=true 且 assigneeType=initiator_select)| 区域 | 行为 |
|---|---|
| 左侧 | 与现有一致:节点名、签批/抄送徽章、meta(可为「发起人自选」) |
| 中间人员区 | 已选:人员 chip(头字 + 姓名);未选:灰色占位文案 |
| 右侧 | 方形 ➕ 按钮;点击打开选人弹窗(租户内启用用户,可多选、可搜索) |
| 删除 | chip 上可移除该人;移除后若该节点映射为空则恢复占位文案 |
非 initiator_select 节点保持只读(现有解析出的 chip,无 ➕)。
自选节点的 chip / 占位以前端本地 initiatorSelectByNode 为展示源(结合已加载的用户昵称);flow-preview 仍传入 map,主要用于非自选节点解析及与后端预览一致,不阻塞本地 chip 即时更新。
对当前预览 steps 中每个 type=approval 且 assigneeType=initiator_select 的节点:若映射中该 nodeId 人数为 0,拦截并提示(如「请为「{节点名}」选择审批人」)。
抄送自选不校验人数。
表单校验、条件路径未确定时的提交失败逻辑保持现有行为。
flow-preview。people 与 chip 一致。新增(camelCase 对外):
initiatorSelectByNode?: Record<string, string[]>
// key = 流程节点 id(flow tree node.id)
// value = 用户 id 列表(去重、保序)
涉及:
| 接口 | 变更 |
|---|---|
POST .../templates/{appId}/flow-preview |
Body 用 initiatorSelectByNode 替换 initiatorSelectUserIds |
POST .../instances(发起) |
同上 |
OaResubmitBody(若仍暴露) |
同上,仅保持 schema 一致;运行时仍废弃 |
废弃:initiatorSelectUserIds: string[]。
steps[] 形状不变;对自选节点:
people 为解析后的 { id, nickName }[]people: [],meta 可为「发起人自选」,resolveError 为空(不因未选报错)assigneeType === 'initiator_select' 决定是否渲染 ➕ / 占位(后端须稳定返回该字段,现已有)oa_instance 新增列:
| 列 | 类型 | 说明 |
|---|---|---|
initiator_select_json |
TEXT / NULL |
JSON 对象:{ "<nodeId>": ["userId", ...] } |
initiatorSelectByNode(JSON);推进时按当前节点 id 取值。docs/sql/ 增加 patch;模型 OaInstance 同步字段。将原「整单一份 initiator_select_user_ids」改为「按当前节点 id 从 map 取 list」:
| 节点类型 | map 中为空或缺失 |
|---|---|
approval + initiator_select |
抛错(可读文案:发起人未选择审批人 / 指定节点) |
cc + initiator_select |
不抛错;不创建抄送任务,继续向后推进 |
其他 assigneeType |
行为不变 |
调用链需贯穿:
start_instance → advance_from → _create_tasks_for_node → resolve_assigneesadvance_from:从实例 initiator_select_json 读取 map,不再依赖请求入参preview_start_flow / _walk_preview_nodesbuild_progress_items 未达节点人员:优先实例持久化 map;再回退已有 resolve_source=initiator_select 任务(兼容旧数据)resolve_assignees 仍接收「当前节点的 userId 列表」;由 advance_from / _create_tasks_for_node 按 nodeId 从 map 取出后再传入。抄送且列表为空时不调用 resolve_assignees,直接跳过建任务。
| 文件 | 改动 |
|---|---|
web-admin/src/views/oa-approval/start/index.vue |
删除顶部自选表单;维护 initiatorSelectByNode;提交/预览传 map;校验审批自选 |
web-admin/src/components/oa-approval/OaFlowPreview.vue |
自选节点:占位文案、chip、➕、emit 选人/删人;非自选只读 |
web-admin/src/api/oaApproval.ts |
类型与请求字段替换 |
| 选人弹窗 | 复用现有用户列表加载(发起页已有 loadUsers / fetchUserList),可用 el-dialog + el-select multiple 或独立小组件 |
样式:Fresh Mint;➕ 按钮对齐钉钉位置(卡片行右侧),无 backdrop blur;不引入小程序色值。
| 文件 | 改动 |
|---|---|
docs/sql/patch_2026-07-23_oa_instance_initiator_select_json.sql |
加列 |
backend/app/models/models.py |
OaInstance.initiator_select_json |
backend/app/api/oa_approval.py |
Body 字段替换 |
backend/app/services/oa_engine.py |
map 贯穿 start / advance / preview / progress;CC 空跳过 |
backend/app/services/oa_assignee.py |
按传入 list 解析;空 list 时由调用方决定是否调用(审批调用前校验,抄送跳过) |
backend/tests/ |
按节点 map、审批必填、抄送可空、持久化后二次推进 |
| 日期 | 说明 |
|---|---|
| 2026-07-23 | 初稿:钉钉式节点内自选;按节点 map;审批必填 / 抄送可空;实例持久化 |