9.0 KiB
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 数据安全
- 始终
tenant_no隔离 +del_flag = 0+app_id = appId - 无全员权限:强制
initiator_user_id = 当前用户 - 有全员权限:不按发起人收窄(仍可用
initiatorName筛选) - 不含草稿(
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,宽度约 1080pxopen({ 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 列表列顺序
- 序号
- 审批 ID
- 流程名称
- 申请人
- 业务字段 × N(N≤6,动态表头)
- 发起时间
- 完成时间
- 审批状态(
el-tag) - 当前节点
- 操作:查看、打印
动态列:按当前页数据的 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. 验收清单
- 发起页卡片「历史记录」在「发起」左侧且始终可见
- 无
oa:start:history:all:仅本人;无「申请人」筛选 - 有该权限:租户内该流程全部;有「申请人」筛选
- 流程名称只读锁定;筛选含审批 ID、状态、发起/完成时间段
- 列序正确;业务字段 ≤6 且在发起时间前
- 默认发起时间倒序;分页正确
- 查看、打印与「我发起的」一致;全员权限可打开他人实例
- 后端无法被前端伪造成全员数据
- 草稿不出现在历史
9. 测试建议
- 后端:权限开/关、时间筛选、
appId必填、previewFields长度、终态finishTime、详情放宽分支 - 前端:弹窗打开/重置、动态列、权限切换后申请人筛选项显隐、打印 loading
10. UI 参考
- Demo:
docs/demos/oa-start-history-demo.html(列序已按「业务字段在发起时间前」;文案「审批 ID」以实现为准同步 Demo) - 筛选/列表模式:任务中心
pending/mine - 打印:
mine/index.vue+exportOaApprovalPdf