条件字段未齐时占位提示,齐后仅展示命中审批链;并在首版预览规格中加交叉引用。 Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
发起审批 · 条件可确定后展示最终审批链(钉钉式)
日期: 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」与「默认」),并标注「实际路径以提交时表单为准」。用户无法在提交前看到将实际走的那一条链,与钉钉「填齐必填后再出流程」的体验不一致。
目标
- 条件相关表单字段未齐时:底部不展开支路,仅提示「必填信息填写后,流程将自动显示」。
- 字段齐且可匹配后:只展示命中路径上的节点,并解析审批人/抄送人(与提交后引擎行为一致)。
- 无条件分支的线性流程:打开弹窗即可展示完整链(行为不变)。
非目标
- 全展 + 命中支路高亮(备选路径仍可见)。
- 条件规则依赖「已解析出的审批人是谁」(条件仍只认表单字段)。
- 改造审批详情页进度轴。
- 在流程节点卡片内点选自选审批人(自选仍用弹窗顶部选择器)。
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 / 默认」双支路。
- 填齐条件字段后,底部自动变为单路径时间轴,节点与人员与按该表单提交后的引擎路径一致。
- 修改条件字段导致走另一支时,预览链随之切换。
- 无条件分支的应用:行为与改前一致,可立即看到审批人。
- 仅缺自选审批人、条件已齐:仍显示最终链,自选节点可无人 chip。
- 遮罩无 blur、弹窗仍为
dialog-form-layout;预览区块视觉延续 Fresh Mint / 现有OaFlowPreview风格。
10. 分阶段
| 阶段 | 内容 |
|---|---|
| P0(本期) | POST + resolved 门闩 + 单路预览 + 占位文案 + 前端防抖 |
| P1(可选) | 匹配失败专用文案;从 when 公式提取字段的更完整解析;mockup HTML 更新 |
11. 修订记录
| 日期 | 说明 |
|---|---|
| 2026-07-17 | 初稿:钉钉式待确定占位 + 确定后最终路径;用户确认自选不挡整段预览 |