Files
zkxw/docs/superpowers/specs/2026-08-28-oa-start-history-design.md
T
2026-08-28 10:56:24 +08:00

9.0 KiB
Raw Blame History

OA 发起审批 · 流程历史记录设计

模块: OA 审批 · 发起审批
日期: 2026-08-28
状态: 待实现
UI Demo: docs/demos/oa-start-history-demo.html
决策记录:

  • 实现方案: 方案 A(弹窗 + 新列表 API + 菜单权限字符)
  • 按钮始终显示;全员数据靠 oa:start:history:all 控制
  • 审批 ID = 实例 id;完成时间 = 终态 update_time;不改表
  • 业务字段固定取表单 schema 前 6 个,列位置在「发起时间」之前
  • 操作:查看 + 打印(对齐「我发起的」)

1. 背景与目标

OA 审批已具备完整工作流。客户希望在「发起审批」页面每张流程卡片的「发起」按钮左侧增加「历史记录」,弹窗展示该流程的发起记录(默认按发起时间倒序),支持筛选与分页,并按权限决定看本人还是看租户内全员。

成功标准:

  • 发起页卡片常显「历史记录」,打开弹窗后可检索、分页浏览该流程实例
  • 无全员权限仅见本人;有 oa:start:history:all 可见本租户该流程全部,并显示「申请人」筛选
  • 列表含标准字段 + 最多 6 个业务字段;支持查看、打印
  • 后端强制鉴权,前端无法伪造成全员数据

2. 范围

2.1 在范围内

项 说明
入口 web-admin/src/views/oa-approval/start/index.vue 流程卡片
弹窗 新组件 OaAppHistoryDialog.vue
API GET /web/admin/oa-approval/instances/history
权限 按钮级菜单 perms = oa:start:history:all + SQL 种子
详情可见性 有全员历史权限时可打开本租户任意该流程实例详情(打印同源)

2.2 非目标

  • 移动端入口
  • 独立菜单列表页
  • 新增审批编号发号字段或独立 finish_time 列
  • 可配置「列表摘要字段」
  • 「复制并重新发起」
  • 草稿箱记录进入历史

3. 权限模型

角色/权限 「历史记录」按钮 列表数据范围 「申请人」筛选
能进发起审批页、无 oa:start:history:all 显示 仅本人发起 不显示
拥有 oa:start:history:all 显示 本租户该 appId 全部实例 显示
超级管理员 / menuPermAll 显示 同全员 显示
  • 权限字符:oa:start:history:all
  • 挂载方式:sys_menu 按钮级节点,建议挂在「发起审批」菜单下;交付 docs/sql/ 种子 SQL,可由角色勾选
  • 前端:useMenuPerm().hasMenuPerm('oa:start:history:all') 仅控制 UI(申请人筛选项等)
  • 后端:user_has_menu_perm 强制收窄数据;忽略客户端伪造的「全员」意图

4. API 设计

4.1 端点

GET /web/admin/oa-approval/instances/history

鉴权:管理端登录用户(与发起页一致);数据范围由权限字符决定。

4.2 查询参数

参数 必填 说明
appId 是 锁定流程;缺失返回 400
instanceId 否 审批 ID,对 oa_instance.id 做包含匹配(LIKE %value%)
instanceStatus 否 running / completed / rejected / returned / cancelled
initiatorName 否 申请人昵称模糊;仅全员权限生效,无权限时忽略
applyTimeFrom / applyTimeTo 否 发起时间,YYYY-MM-DD,过滤 create_time
finishTimeFrom / finishTimeTo 否 完成时间,YYYY-MM-DD;见 §4.4
page / pageSize 否 分页;默认排序 create_time DESC

4.3 响应

{
  "list": [ /* 行对象 */ ],
  "total": 0,
  "canViewAll": true
}

行对象要点(在现有 _enrich_list_rows / _instance_to_dict 摘要之上扩展):

字段 说明
id 审批 ID(列表列「审批 ID」)
appName / title 流程名称
initiatorUserName 申请人
applyTime 发起时间(create_time)
finishTime 完成时间;非终态为 null
status 审批状态
currentNodeName 等 与任务列表 enrich 一致
previewFields 最多 6 项 { label, value },按该实例锁定版本 form_schema_json 取前 6 个可摘要字段

另:可将 previewFields 上限从现有任务列表的 3 参数化为 limit;历史接口传 6,既有 tasks/{kind} 保持 3 以免影响其它页。

4.4 完成时间规则

终态集合(有 finishTime):completed、rejected、cancelled
非终态(finishTime = null):running、returned

  • 展示:终态 → update_time;否则空
  • 筛选:仅当 status IN (completed, rejected, cancelled) 且 update_time 落在 [finishTimeFrom, finishTimeTo] 日界内

4.5 数据安全

  1. 始终 tenant_no 隔离 + del_flag = 0 + app_id = appId
  2. 无全员权限:强制 initiator_user_id = 当前用户
  3. 有全员权限:不按发起人收窄(仍可用 initiatorName 筛选)
  4. 不含草稿(oa_instance_draft 不参与)

4.6 实例详情可见性(连带)

现有 GET /instances/{id} 若仅允许发起人/审批人/抄送人查看,则全员历史用户无法「查看/打印」他人单据。

本期约定:调用方拥有 oa:start:history:all 且实例属于同租户时,允许读取该实例详情(打印走同一详情接口)。实现落在详情鉴权分支,避免仅放开列表却打不开详情。


5. 前端设计

5.1 发起页卡片

文件:web-admin/src/views/oa-approval/start/index.vue

[历史记录]  [发起]
  • 「历史记录」在「发起」左侧;@click.stop 打开历史弹窗
  • 始终渲染,不按权限隐藏

5.2 弹窗组件

文件:web-admin/src/components/oa-approval/OaAppHistoryDialog.vue

  • dialog-form-layout + append-to-body,宽度约 1080px
  • open({ id, appName }):锁定 appId,标题 历史记录 · {appName}
  • 打开时重置筛选并加载第一页
  • 无 backdrop blur

5.3 筛选区

字段 行为
流程名称 只读预填,不可改
审批状态 下拉,枚举同任务中心
审批 ID 文本
发起时间 daterange,YYYY-MM-DD
完成时间 daterange,YYYY-MM-DD
申请人 仅 canViewAll / hasMenuPerm('oa:start:history:all') 时显示

操作:重置、搜索(可与任务中心一致做防抖即时检索,非必须;至少支持显式搜索/重置)。

5.4 列表列顺序

  1. 序号
  2. 审批 ID
  3. 流程名称
  4. 申请人
  5. 业务字段 × N(N≤6,动态表头)
  6. 发起时间
  7. 完成时间
  8. 审批状态(el-tag)
  9. 当前节点
  10. 操作:查看、打印

动态列:按当前页数据的 previewFields 标签对齐;不足 6 个则少列;缺值显示 -。

5.5 操作

与 views/oa-approval/mine/index.vue 对齐:

  • 查看:跳转 /system-manage/oa-approval/instances/:id(或项目现有详情打开方式)
  • 打印:fetchOaInstanceDetail + exportOaApprovalPdf,带 loading 防重复

5.6 API 封装

web-admin/src/api/oaApproval.ts 新增 fetchOaInstanceHistory(params)。


6. 后端实现要点

位置 变更
app/api/oa_approval.py 注册 history 路由
app/services/oa_engine.py list_instance_history;复用/扩展 _apply_instance_list_filters、_enrich_list_rows(preview limit=6,计算 finishTime)
app/core/menu_guard.py 使用已有 user_has_menu_perm
docs/sql/ 插入按钮菜单种子(perms=oa:start:history:all)

表结构:不改 oa_instance。


7. 边界情况

场景 处理
无权限带 initiatorName 忽略
无权限搜他人审批 ID 无结果
表单字段少于 6 个 有几列展示几列
多版本 schema 每行按实例锁定版本取前 6 字段
appId 非法 400
空列表 「暂无历史记录」

8. 验收清单

  1. 发起页卡片「历史记录」在「发起」左侧且始终可见
  2. 无 oa:start:history:all:仅本人;无「申请人」筛选
  3. 有该权限:租户内该流程全部;有「申请人」筛选
  4. 流程名称只读锁定;筛选含审批 ID、状态、发起/完成时间段
  5. 列序正确;业务字段 ≤6 且在发起时间前
  6. 默认发起时间倒序;分页正确
  7. 查看、打印与「我发起的」一致;全员权限可打开他人实例
  8. 后端无法被前端伪造成全员数据
  9. 草稿不出现在历史

9. 测试建议

  • 后端:权限开/关、时间筛选、appId 必填、previewFields 长度、终态 finishTime、详情放宽分支
  • 前端:弹窗打开/重置、动态列、权限切换后申请人筛选项显隐、打印 loading

10. UI 参考

  • Demo:docs/demos/oa-start-history-demo.html(列序已按「业务字段在发起时间前」;文案「审批 ID」以实现为准同步 Demo)
  • 筛选/列表模式:任务中心 pending / mine
  • 打印:mine/index.vue + exportOaApprovalPdf