2026-07-17-oa-start-flow-preview-resolved-path-design.md 10 KB

发起审批 · 条件可确定后展示最终审批链(钉钉式)

日期: 2026-07-17
范围: web-admin /#/system-manage/oa-approval/start 发起弹窗底部「审批流程」;backend preview_start_flow / flow-preview API
关联:

  • docs/superpowers/specs/2026-07-15-oa-start-flow-preview-design.md(首版方案 B:条件全展)
  • docs/superpowers/specs/2026-07-15-oa-condition-from-form-design.md(条件字段来源与必填约定)
  • backend/app/services/oa_engine.py — _match_condition_group、preview_start_flow

关系: 本规格取代首版中「条件分支全部支路展开」与非目标「条件随表单实时裁剪」;人员解析、卡片样式、线性流程展示仍沿用 2026-07-15 规格。


1. 背景与目标

现状问题

含条件分支的流程在发起预览中并列展示所有支路(如「条件1」与「默认」),并标注「实际路径以提交时表单为准」。用户无法在提交前看到将实际走的那一条链,与钉钉「填齐必填后再出流程」的体验不一致。

目标

  1. 条件相关表单字段未齐时:底部不展开支路,仅提示「必填信息填写后,流程将自动显示」。
  2. 字段齐且可匹配后:只展示命中路径上的节点,并解析审批人/抄送人(与提交后引擎行为一致)。
  3. 无条件分支的线性流程:打开弹窗即可展示完整链(行为不变)。

非目标

  • 全展 + 命中支路高亮(备选路径仍可见)。
  • 条件规则依赖「已解析出的审批人是谁」(条件仍只认表单字段)。
  • 改造审批详情页进度轴。
  • 在流程节点卡片内点选自选审批人(自选仍用弹窗顶部选择器)。

2. 产品约定(已确认)

项 结论
未确定时 UI 钉钉式占位文案,不展开条件1/默认等支路
占位文案 「必填信息填写后,流程将自动显示」
已确定时 UI 仅最终路径时间轴(节点名 + 签批方式 + meta + 人员 chip)
「可确定」依据 流程树中条件分支所引用的表单字段在 formData 中均已有有效值
发起人自选 不作为整段预览的门闩;路径已确定即出链,未选人时该节点 people 为空
与提交一致性 分支匹配复用 _match_condition_group(form_data),与 advance_from 同源
刷新时机 表单值变化防抖刷新;自选人变化立即/短防抖刷新

3. 状态机

                    ┌─────────────────────┐
                    │ 打开发起弹窗 / 改表单 │
                    └──────────┬──────────┘
                               ▼
                    流程树是否含 condition?
                     /                    \
                   否                      是
                    │                      │
                    ▼                      ▼
              【已确定】            条件引用字段是否均有值?
              展示完整链             /              \
                                 否                是
                                  │                │
                                  ▼                ▼
                            【待确定】        【已确定】
                            仅占位文案      只走命中支路 + 解析人
状态 resolved 前端展示
待确定 false 标题「审批流程」+ 占位文案;steps 为空或忽略
已确定 true 标题 + 时间轴 steps(可含右侧 hint,如「按当前发起人解析审批人」)

加载中:沿用现有 v-loading,不闪占位与时间轴之间的错误态。


4. 「可确定」判定细则

4.1 收集条件字段

从发布版 flow_tree_json 根节点递归:

  • 对每个 type=condition 节点,遍历 conditionGroups;
  • 跳过默认分支(isDefault 或无 rules/when 的空规则组);
  • 从非默认组的 rules[] 收集 field;若仅有历史 when 字符串,用与引擎一致的方式解析出 {field} 占位符(若无法解析,视为该组不增加门闩字段,匹配仍走引擎)。

去重后得到集合 conditionFields。

4.2 字段「有值」

对每个 field,取 formData[field]:

  • null / undefined / ''(trim 后)→ 未填;
  • 数组:长度为 0 → 未填;
  • 数字 0、布尔 false → 视为已填(合法取值)。

任一 conditionFields 未填 → resolved=false。

无 condition 节点,或 conditionFields 为空(仅有默认支)→ 直接 resolved=true,按主链(或默认支)预览。

4.3 匹配后行走

resolved=true 时:

  • 遇到 condition:调用 _match_condition_group(groups, form_data),仅递归命中组的 child,不再把其它组写入 steps;
  • 不再输出 type=branch 的「条件1 / 默认」占位步(最终链上无需分支标题卡,与钉钉「直接出后续审批人」一致);
  • 条件节点汇合后的 child(若有)继续行走;
  • approval / cc / start / end 人员解析逻辑与现网 preview_start_flow 一致。

若字段齐但 _match_condition_group 返回 None(异常配置):resolved=false,占位文案可改为「流程条件无法匹配,请检查表单或联系管理员」(可选;首版可仍用统一占位句,并在 resolveError 级日志/接口字段暴露)。


5. API

5.1 变更

将

GET /api/admin/oa-approval/templates/{appId}/flow-preview?initiatorSelectUserIds=

改为(推荐):

POST /api/admin/oa-approval/templates/{appId}/flow-preview

Body:

{
  "formData": { "days": 5 },
  "initiatorSelectUserIds": ["user-id-1"]
}

兼容:若短期内保留 GET,则无法可靠传复杂 formData;本期以 POST 为准,前端切换后可删除 GET 或让 GET 仅支持无条件线性预览。

5.2 响应

{
  "resolved": true,
  "message": "",
  "steps": [
    {
      "id": "n_start",
      "type": "start",
      "name": "发起人",
      "signMode": "",
      "assigneeType": "",
      "meta": "",
      "people": [{ "id": "...", "nickName": "系统管理员" }],
      "resolveError": ""
    }
  ]
}
字段 说明
resolved false 时前端只显示 message(或默认占位句),不渲染时间轴
message resolved=false 时默认「必填信息填写后,流程将自动显示」;可覆盖异常文案
steps resolved=true 时为最终路径;false 时建议 []

单步结构与现有 OaFlowPreviewStep 一致。


6. 前端

6.1 组件

  • OaFlowPreview.vue:增加 resolved(或由父级根据接口决定);resolved===false 时展示占位区块(样式对齐 Fresh Mint:浅底、居中/左对齐 muted 文案),隐藏 chain。
  • start/index.vue:
    • loadFlowPreview 改为 POST,传入当前 formData 与自选人;
    • watch(formData, …, { deep: true }) + debounce(建议 300ms);
    • 保留对 initiatorSelectUserIds 的 watch;
    • 打开弹窗、详情加载完成后拉一次预览。

6.2 文案与 hint

  • 待确定:主文案固定为「必填信息填写后,流程将自动显示」;标题旁原 hint「按当前发起人解析审批人」可不显示。
  • 已确定:可保留 hint。

6.3 API 模块

web-admin/src/api/oaApproval.ts — fetchOaFlowPreview(appId, { formData, initiatorSelectUserIds }) 改为 POST;类型增加 resolved、message。


7. 后端

7.1 主要改动

  • oa_approval.py:新增/替换为 POST handler,解析 body。
  • oa_engine.preview_start_flow:增加 form_data 参数;实现字段收集、resolved 门闩、条件单路行走。
  • 抽取 _collect_condition_fields(root) -> List[str]、_form_value_present(form_data, field) -> bool,便于单测。
  • 删除或停用「全展 + meta=条件分支(实际路径以提交时表单为准)」分支路径(仅 resolved=false 时根本不 walk 条件子树亦可:直接返回空 steps)。

7.2 测试建议

  • 无条件:始终 resolved=true,steps 含 start→approval→end。
  • 有条件、字段空:resolved=false,steps=[],message 为占位句。
  • 有条件、字段满足条件1:仅条件1子树节点出现,无默认支、无 branch 占位步。
  • 有条件、字段走默认:仅默认子树。
  • 自选人未选但字段齐:resolved=true,initiator_select 节点 people 为空。

8. 主要文件

文件 职责
backend/app/services/oa_engine.py preview_start_flow 门闩 + 单路 walk
backend/app/api/oa_approval.py POST flow-preview
backend/tests/… 预览解析单测(新建或挂现有 oa 测试)
web-admin/src/api/oaApproval.ts POST 客户端
web-admin/src/components/oa-approval/OaFlowPreview.vue 待确定占位 UI
web-admin/src/views/oa-approval/start/index.vue formData 防抖刷新
docs/superpowers/specs/2026-07-15-oa-start-flow-preview-design.md 文首增加「条件展示见 2026-07-17 规格」指向(实现时可补一行)

9. 验收标准

  1. 含条件分支的应用:打开发起弹窗且条件字段未填时,底部仅见「必填信息填写后,流程将自动显示」,不出现「条件1 / 默认」双支路。
  2. 填齐条件字段后,底部自动变为单路径时间轴,节点与人员与按该表单提交后的引擎路径一致。
  3. 修改条件字段导致走另一支时,预览链随之切换。
  4. 无条件分支的应用:行为与改前一致,可立即看到审批人。
  5. 仅缺自选审批人、条件已齐:仍显示最终链,自选节点可无人 chip。
  6. 遮罩无 blur、弹窗仍为 dialog-form-layout;预览区块视觉延续 Fresh Mint / 现有 OaFlowPreview 风格。

10. 分阶段

阶段 内容
P0(本期) POST + resolved 门闩 + 单路预览 + 占位文案 + 前端防抖
P1(可选) 匹配失败专用文案;从 when 公式提取字段的更完整解析;mockup HTML 更新

11. 修订记录

日期 说明
2026-07-17 初稿:钉钉式待确定占位 + 确定后最终路径;用户确认自选不挡整段预览