docs(oa): 发起审批预览改为钉钉式最终路径规格

条件字段未齐时占位提示,齐后仅展示命中审批链;并在首版预览规格中加交叉引用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-17 16:10:55 +08:00
1 parent a4fd31f254
commit 417d6c9c51
2 files changed
+254

No files matched your search

@@ -4,6 +4,10 @@
**范围:** `web-admin` `/#/system-manage/oa-approval/start` 发起弹窗
**Demo:** `docs/mockups/oa-approval-start-flow-preview-demo.html`(已定稿样式)
> **修订(2026-07-17):** 条件分支展示已改为钉钉式「未齐占位 / 齐后最终路径」,见
> `docs/superpowers/specs/2026-07-17-oa-start-flow-preview-resolved-path-design.md`。
> 下文「全部支路展开」与「非目标:条件随表单实时裁剪」以新规格为准。
## 目标
发起人填单时,在表单下方展示审批链预览:节点名称 + 签批方式 + 配置说明 + 解析出的具体人员,样式对齐 Demo(灰色圆点时间轴、单行紧凑卡片)。
@@ -0,0 +1,250 @@
# 发起审批 · 条件可确定后展示最终审批链(钉钉式)
**日期:** 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 | 初稿:钉钉式待确定占位 + 确定后最终路径;用户确认自选不挡整段预览 |