Files
zkxw/docs/superpowers/specs/2026-07-15-oa-approval-print-pdf-design.md
T
2026-07-23 18:01:11 +08:00

7.3 KiB
Raw Blame History

OA 审批单前端导出 PDF 设计

日期:2026-07-15
范围:web-admin · OA 审批「我发起的」「抄送我的」
状态:已确认(印章对齐 brainstorm Demo 第 4 版)

1. 目标

在「我发起的」「抄送我的」列表操作列增加「打印」按钮:点击后拉取审批实例详情,在前端用钉钉风格 HTML 模板渲染,经 html2canvas + jspdf 直接下载 PDF(不弹出系统打印对话框)。

2. 非目标

  • 不新增后端 PDF 生成接口(本期纯前端)
  • 不导出附件二进制内容;附件行仅展示完整 http(s) URL
  • 不展示扫码二维码
  • 不修改审批详情页布局(可后续复用同一导出工具,本期只接线列表)

3. 交互

页面 文件 行为
我发起的 web-admin/src/views/oa-approval/mine/index.vue 操作列增加「打印」;实例 id 取 row.id
抄送我的 web-admin/src/views/oa-approval/cc/index.vue 同上;实例 id 取 row.instanceId(与现有「查看」一致)

按钮规则:

  • 文案「打印」,前置 <el-icon><Printer /></el-icon>
  • 任意流程状态均可打印(进行中也盖「审批中」章)
  • 点击后 loading 防重复;失败 ElMessage.error

文件名:{发起人姓名}提交的{流程名称}.pdf(缺省则用「审批单」)。

4. PDF 版式(对齐钉钉样本 + 产品约束)

页面结构自上而下:

  1. 标题:流程名称居中(appName 优先,否则 title)
  2. 元信息行(表格上方,无二维码)
    • 左:公司名固定 安徽中科兴皖医学研究有限公司
    • 右邻:创建时间:YYYY-MM-DD(取实例 createTime 的日期部分)
  3. 主表(两列:标签 | 值)
    • 固定前几行:审批编号、创建人、创建人部门
    • 随后按表单 schema 顺序展开字段(一行一项)
    • 附件字段:每个 URL 单独一行展示完整地址(可换行)
    • 最后一行:审批流程
  4. 印章:绝对定位于主表容器右上角(见 §5)
  5. 页脚元信息:打印时间(导出瞬间)、打印人(当前登录用户昵称)
  6. 水印(可选但本期要做):以打印人姓名斜向淡色重复铺底(仿钉钉)
  7. 页码:第 N 页(多页时 jspdf 切片后写入)

纸张:A4 竖向,内容宽约 178mm(左右边距约 16mm)。

4.1 表格行数据来源

行 来源
审批编号 detail.id
创建人 detail.initiatorUserName
创建人部门 detail.initiatorDeptName;若详情暂无该字段,前端尽量用已有扩展字段,否则显示 -(本期不强制改后端,可在实现时若接口已有则用)
动态表单行 formSchemaJson.rule 字段 title/label + formDataJson[field]
附件 schema 中类型为 upload/attachment 的字段;值为 URL 字符串或 URL 数组,逗号分隔 URL 按条拆分
审批流程 detail.progress(与详情页 OaProgressChain 同源)

审批流程单元格内每条:

  • 展示:【节点名】 处理人 状态文案,右侧时间;有意见则次行展示 意见:…
  • 抄送节点:抄送姓名1,姓名2 + 时间
  • 状态文案与详情页 statusText 映射一致(已同意 / 已拒绝 / 已打回等)
  • 打回:仅展示审批人节点上的「已打回」+ 意见;不得再输出独立终态行「已打回」(与详情进度一致)

金额类字段若原始值为数字,可原样输出;不强制本期做中文大写金额(钉钉有则更好,YAGNI:无现成工具则跳过)。

5. 印章(锁定:Demo 第 4 版)

视觉权威:.superpowers/brainstorm/*/content/dingtalk-pdf-v4-dingtalk-stamp.html 中的 .oa-stamp。

结构(DOM):

<div class="oa-stamp ok" aria-label="已通过">
  <div class="outer"></div>
  <div class="arc-top"></div>
  <div class="arc-bottom"></div>
  <div class="label-top">OA审批</div>
  <div class="main">已通过</div>
  <div class="label-bottom">approval</div>
</div>

要点:

  • 粗外圈 + 上下内弧;上弧「OA审批」、下弧「approval」
  • 中心文案大号粗体(「已通过」约 32px;三字状态用 .chars-3 略缩小)
  • 颜色 #3ec9a8 系薄荷绿;opacity ~0.88;rotate(-18deg)
  • 半透明,表格线可透过

5.1 状态映射

detail.status class 中心文案
completed / approved / done ok 已通过
running / pending run 审批中
rejected rej 已拒绝
returned ret 已打回
cancelled can 已撤销
其他 run 审批中

所有状态均盖章(非仅已通过)。

6. 前端架构

6.1 新增模块

路径 职责
web-admin/src/components/oa-approval/OaApprovalPrintSheet.vue 钉钉风格打印 DOM(标题、表、印章、水印、页脚);props 接收已组装的 printModel
web-admin/src/utils/oa-approval-print.ts 组装 printModel、挂载离屏节点、调用 html2canvas/jspdf、触发下载、清理
(可选)web-admin/src/utils/oa-approval-print-fields.ts 从 schema + formData 抽出打印行,保持 print.ts 精简

6.2 导出流程

列表点击「打印」
  → fetchOaInstanceDetail(id)
  → buildOaPrintModel(detail, { printerName, printTime })
  → 离屏挂载 <OaApprovalPrintSheet />
  → html2canvas(el, { scale: 2, useCORS: true, backgroundColor: '#ffffff' })
  → 将 canvas 按 A4 高度切片写入 jsPDF
  → pdf.save(filename)
  → 卸载离屏节点

依赖(web-admin):新增 html2canvas、jspdf(pnpm add)。

6.3 分页策略

  • 单张长 canvas:按 A4 像素高度切片多页
  • 页码:导出后用 jspdf 在每页底部居中写「第 n 页 / 共 m 页」或「第 n 页」(与钉钉「第 1 页」一致即可)
  • 表格不被「保证不切断单行」本期不做复杂编排;接受简单切片(已知局限,可后续优化)

6.4 离屏渲染注意

  • 容器 position:fixed; left:-99999px; top:0; 或同等不可见但可布局的尺寸(固定 A4 宽度 px)
  • 印章、水印使用纯 CSS/DOM(第 4 版),避免 html2canvas 对复杂滤镜的兼容问题
  • 不依赖 Element Plus 组件做打印内容(表格用原生 <table>)

7. 错误与边界

  • 详情接口失败:提示「加载审批单失败」
  • schema 缺失:仅输出审批编号/创建人/部门 + 审批流程 + 原始 formDataJson 键值兜底
  • 导出异常:提示「导出 PDF 失败」
  • 进行中实例:仍可导出,印章为「审批中」

8. 测试要点

  1. 「我发起的」已完成实例:可下载 PDF,印章「已通过」,字段与审批流正确
  2. 「抄送我的」:用 instanceId 能导出
  3. 进行中 / 已拒绝 / 已打回 / 已撤销:印章文案与颜色正确
  4. 含附件 URL:完整 URL 出现在附件行
  5. 长表单多页:能多页 PDF,页脚打印人/时间正确
  6. 无系统打印对话框弹起

9. 参考

  • 钉钉样本 PDF:用户提供的付款单 / 请假 / 物品采购
  • Demo:dingtalk-pdf-v4-dingtalk-stamp.html(印章锁定版)
  • 详情 API:GET /web/admin/oa-approval/instances/{id} → fetchOaInstanceDetail
  • 进度:progress 字段,同 OaProgressChain