From b3eaf4b4bddad4f6348864a0120ffe7fbfba4234 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 15 Jul 2026 17:40:12 +0800 Subject: [PATCH] docs: add OA approval PDF export design (DingTalk-style stamp v4). Locks frontend html2canvas/jspdf flow, list entry points, and stamp mapping before implementation. Co-authored-by: Cursor --- ...2026-07-15-oa-approval-print-pdf-design.md | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-15-oa-approval-print-pdf-design.md diff --git a/docs/superpowers/specs/2026-07-15-oa-approval-print-pdf-design.md b/docs/superpowers/specs/2026-07-15-oa-approval-print-pdf-design.md new file mode 100644 index 00000000..8d1cffac --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-oa-approval-print-pdf-design.md @@ -0,0 +1,167 @@ +# 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`(与现有「查看」一致) | + +按钮规则: + +- 文案「打印」,前置 `` +- 任意流程状态均可打印(进行中也盖「审批中」章) +- 点击后 `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): + +```html +
+
+
+
+
OA审批
+
已通过
+
approval
+
+``` + +要点: + +- 粗外圈 + 上下内弧;上弧「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 }) + → 离屏挂载 + → 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 组件做打印内容(表格用原生 ``) + +## 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`