Files
zkxw/docs/superpowers/specs/2026-07-17-oa-start-flow-preview-resolved-path-design.md
T
zlwuandCursor 417d6c9c51 docs(oa): 发起审批预览改为钉钉式最终路径规格
条件字段未齐时占位提示,齐后仅展示命中审批链;并在首版预览规格中加交叉引用。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:10:55 +08:00

251 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 发起审批 · 条件可确定后展示最终审批链(钉钉式)
**日期:** 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:
```json
{
"formData": { "days": 5 },
"initiatorSelectUserIds": ["user-id-1"]
}
```
兼容:若短期内保留 GET,则无法可靠传复杂 `formData`;**本期以 POST 为准**,前端切换后可删除 GET 或让 GET 仅支持无条件线性预览。
### 5.2 响应
```json
{
"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 | 初稿:钉钉式待确定占位 + 确定后最终路径;用户确认自选不挡整段预览 |