feat(oa): generate mobile OA approval PDF via html2canvas and jspdf
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
505bf29fdd
commit
bf22b29d80
3 files changed
+391
-46
No files matched your search
Generated
+235
@@ -9,6 +9,8 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@form-create/vant": "^3.2.14",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^4.2.1",
|
||||
"vant": "^4.9.21",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
@@ -51,6 +53,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.8",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz",
|
||||
@@ -928,6 +939,26 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@types/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@vant/popperjs": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz",
|
||||
@@ -1061,6 +1092,59 @@
|
||||
"integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz",
|
||||
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/raf": "^3.4.0",
|
||||
"core-js": "^3.8.3",
|
||||
"raf": "^3.4.1",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.50.0",
|
||||
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.50.0.tgz",
|
||||
"integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -1073,6 +1157,16 @@
|
||||
"integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optional": true,
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
|
||||
@@ -1133,6 +1227,17 @@
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-png": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/fast-png/-/fast-png-6.4.0.tgz",
|
||||
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/pako": "^2.0.3",
|
||||
"iobuffer": "^5.3.2",
|
||||
"pako": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
|
||||
@@ -1151,6 +1256,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.3.tgz",
|
||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -1166,6 +1277,42 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iobuffer": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/iobuffer/-/iobuffer-5.4.0.tgz",
|
||||
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-4.2.1.tgz",
|
||||
"integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"fast-png": "^6.2.0",
|
||||
"fflate": "^0.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvg": "^3.0.11",
|
||||
"core-js": "^3.6.0",
|
||||
"dompurify": "^3.3.1",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1193,6 +1340,29 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/pako/-/pako-2.2.0.tgz",
|
||||
"integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1241,6 +1411,33 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/rgbcolor": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/rgbcolor/-/rgbcolor-1.0.1.tgz",
|
||||
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
|
||||
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.62.4",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.4.tgz",
|
||||
@@ -1296,6 +1493,35 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackblur-canvas": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-pathdata": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
@@ -1313,6 +1539,15 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/vant": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmmirror.com/vant/-/vant-4.10.0.tgz",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@form-create/vant": "^3.2.14",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^4.2.1",
|
||||
"vant": "^4.9.21",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* OA 审批单移动端打印(S6)
|
||||
* 对齐 web-admin `oa-approval-print.ts` 字段/流程结构;优先 window.print(),无 jspdf 依赖。
|
||||
* 对齐 web-admin `oa-approval-print.ts` 字段/流程结构;支持 html2canvas + jsPDF 导出。
|
||||
* Spec: docs/superpowers/specs/2026-08-12-mobile-oa-task-centers-design.md §6.5
|
||||
*/
|
||||
import html2canvas from 'html2canvas'
|
||||
import { jsPDF } from 'jspdf'
|
||||
import { formatDeptDisplay } from '../kit/oa-dept-value.js'
|
||||
import { formatPersonDisplay } from '../kit/oa-person-value.js'
|
||||
import { formatIdNameDisplay } from '../../utils/id-name-value.js'
|
||||
@@ -13,6 +15,7 @@ import {
|
||||
import { toRmbUpper } from '../../utils/rmb-upper.js'
|
||||
import { OA_STATUS_TEXT } from '../status-label.js'
|
||||
import { resolveStamp } from '../oa-status-stamp.js'
|
||||
import { buildOaPrintFilename } from './oa-print-filename.js'
|
||||
|
||||
export { resolveStamp }
|
||||
|
||||
@@ -597,42 +600,8 @@ function flowCellHtml(f) {
|
||||
return html
|
||||
}
|
||||
|
||||
function buildPrintHtml(model) {
|
||||
const stampChars3 = model.stampText.length >= 3 ? ' is-chars3' : ''
|
||||
const wmItems = Array.from({ length: 24 }, (_, i) => {
|
||||
const row = Math.floor(i / 3)
|
||||
const col = i % 3
|
||||
const top = 40 + row * 100
|
||||
const left = -20 + col * 240 + (row % 2) * 40
|
||||
return `<span class="oa-print-wm__item" style="top:${top}px;left:${left}px">${escapeHtml(model.watermarkName)} ${escapeHtml(model.watermarkName)}</span>`
|
||||
}).join('')
|
||||
|
||||
let rowsHtml = ''
|
||||
for (const row of model.rows) {
|
||||
rowsHtml += `<tr><th>${escapeHtml(row.label)}</th><td>${cellInnerHtml(row)}</td></tr>`
|
||||
}
|
||||
|
||||
let flowHtml = ''
|
||||
if (model.flowItems.length) {
|
||||
model.flowItems.forEach((f, fi) => {
|
||||
flowHtml += '<tr class="oa-print-flow">'
|
||||
if (fi === 0) {
|
||||
flowHtml += `<th class="oa-print-flow__label" rowspan="${model.flowItems.length}">审批流程</th>`
|
||||
}
|
||||
flowHtml += `<td class="oa-print-flow__cell">${flowCellHtml(f)}</td></tr>`
|
||||
})
|
||||
} else {
|
||||
flowHtml =
|
||||
'<tr class="oa-print-flow"><th class="oa-print-flow__label">审批流程</th><td class="oa-print-flow__cell"><span class="oa-print-text">-</span></td></tr>'
|
||||
}
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>${escapeHtml(model.title)}</title>
|
||||
<style>
|
||||
function buildPrintStylesContent() {
|
||||
return `
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: #fff; color: #1f2329;
|
||||
font-family: "PingFang SC","Microsoft YaHei","Helvetica Neue",Arial,sans-serif; }
|
||||
@@ -702,15 +671,39 @@ function buildPrintHtml(model) {
|
||||
line-height: 1; color: currentColor; }
|
||||
.oa-stamp .main.is-chars3 { font-size: 26px; letter-spacing: 1px; }
|
||||
.oa-print-footer { display: flex; flex-wrap: wrap; justify-content: space-between;
|
||||
gap: 8px; margin-top: 16px; font-size: 12px; color: #646464; }
|
||||
@media print {
|
||||
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
.oa-print-page { max-width: none; padding: 12mm 14mm; }
|
||||
gap: 8px; margin-top: 16px; font-size: 12px; color: #646464; }`
|
||||
}
|
||||
|
||||
function buildPrintPageContent(model) {
|
||||
const stampChars3 = model.stampText.length >= 3 ? ' is-chars3' : ''
|
||||
const wmItems = Array.from({ length: 24 }, (_, i) => {
|
||||
const row = Math.floor(i / 3)
|
||||
const col = i % 3
|
||||
const top = 40 + row * 100
|
||||
const left = -20 + col * 240 + (row % 2) * 40
|
||||
return `<span class="oa-print-wm__item" style="top:${top}px;left:${left}px">${escapeHtml(model.watermarkName)} ${escapeHtml(model.watermarkName)}</span>`
|
||||
}).join('')
|
||||
|
||||
let rowsHtml = ''
|
||||
for (const row of model.rows) {
|
||||
rowsHtml += `<tr><th>${escapeHtml(row.label)}</th><td>${cellInnerHtml(row)}</td></tr>`
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="oa-print-page">
|
||||
|
||||
let flowHtml = ''
|
||||
if (model.flowItems.length) {
|
||||
model.flowItems.forEach((f, fi) => {
|
||||
flowHtml += '<tr class="oa-print-flow">'
|
||||
if (fi === 0) {
|
||||
flowHtml += `<th class="oa-print-flow__label" rowspan="${model.flowItems.length}">审批流程</th>`
|
||||
}
|
||||
flowHtml += `<td class="oa-print-flow__cell">${flowCellHtml(f)}</td></tr>`
|
||||
})
|
||||
} else {
|
||||
flowHtml =
|
||||
'<tr class="oa-print-flow"><th class="oa-print-flow__label">审批流程</th><td class="oa-print-flow__cell"><span class="oa-print-text">-</span></td></tr>'
|
||||
}
|
||||
|
||||
return `<div class="oa-print-page">
|
||||
<div class="oa-print-wm" aria-hidden="true">${wmItems}</div>
|
||||
<div class="oa-print-inner">
|
||||
<h1 class="oa-print-title">${escapeHtml(model.title)}</h1>
|
||||
@@ -736,11 +729,126 @@ function buildPrintHtml(model) {
|
||||
<span>打印人:${escapeHtml(model.printerName)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
export function buildPrintBodyHtml(model) {
|
||||
return `<style>${buildPrintStylesContent()}</style>${buildPrintPageContent(model)}`
|
||||
}
|
||||
|
||||
function buildPrintHtml(model) {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>${escapeHtml(model.title)}</title>
|
||||
<style>${buildPrintStylesContent()}</style>
|
||||
</head>
|
||||
<body>
|
||||
${buildPrintPageContent(model)}
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
/** 用 canvas 绘制中文页码(jsPDF 内置字体不支持汉字,会乱码) */
|
||||
function renderPageLabelCanvas(text) {
|
||||
const font = '20px "PingFang SC", "Microsoft YaHei", "Heiti SC", sans-serif'
|
||||
const measure = document.createElement('canvas')
|
||||
const mctx = measure.getContext('2d')
|
||||
if (!mctx) {
|
||||
const empty = document.createElement('canvas')
|
||||
empty.width = 1
|
||||
empty.height = 1
|
||||
return empty
|
||||
}
|
||||
mctx.font = font
|
||||
const tw = Math.ceil(mctx.measureText(text).width) + 8
|
||||
const th = 28
|
||||
const scale = 2
|
||||
const c = document.createElement('canvas')
|
||||
c.width = tw * scale
|
||||
c.height = th * scale
|
||||
const ctx = c.getContext('2d')
|
||||
if (!ctx) return c
|
||||
ctx.scale(scale, scale)
|
||||
ctx.clearRect(0, 0, tw, th)
|
||||
ctx.font = font
|
||||
ctx.fillStyle = '#646464'
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillText(text, 4, th / 2)
|
||||
return c
|
||||
}
|
||||
|
||||
async function canvasToPdfBlob(canvas) {
|
||||
const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' })
|
||||
const pageWidth = pdf.internal.pageSize.getWidth()
|
||||
const pageHeight = pdf.internal.pageSize.getHeight()
|
||||
const imgWidth = pageWidth
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width
|
||||
|
||||
const EPS_MM = 1.5
|
||||
const totalPages = Math.min(40, Math.max(1, Math.ceil((imgHeight - EPS_MM) / pageHeight)))
|
||||
|
||||
const pageData = canvas.toDataURL('image/jpeg', 0.95)
|
||||
const labelMmH = 4.5
|
||||
|
||||
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
|
||||
if (pageIndex > 0) pdf.addPage()
|
||||
const position = -pageIndex * pageHeight
|
||||
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
|
||||
|
||||
const label = `第${pageIndex + 1}页`
|
||||
const labelCanvas = renderPageLabelCanvas(label)
|
||||
if (labelCanvas.width > 1) {
|
||||
const labelMmW = (labelCanvas.width / labelCanvas.height) * labelMmH
|
||||
pdf.addImage(
|
||||
labelCanvas.toDataURL('image/png'),
|
||||
'PNG',
|
||||
(pageWidth - labelMmW) / 2,
|
||||
pageHeight - 10,
|
||||
labelMmW,
|
||||
labelMmH
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return pdf.output('blob')
|
||||
}
|
||||
|
||||
export async function generateOaApprovalPdfBlob(detail, opts = {}) {
|
||||
if (!detail) throw new Error('暂无审批详情,无法导出')
|
||||
const model = buildOaPrintModel(detail, {
|
||||
printerName: opts.printerName || ''
|
||||
})
|
||||
const filename = buildOaPrintFilename(detail)
|
||||
const host = document.createElement('div')
|
||||
host.setAttribute('data-oa-print-host', '1')
|
||||
host.style.cssText =
|
||||
'position:fixed;left:-100000px;top:0;width:794px;z-index:-1;opacity:1;pointer-events:none;'
|
||||
document.body.appendChild(host)
|
||||
host.innerHTML = buildPrintBodyHtml(model)
|
||||
try {
|
||||
await new Promise((r) => requestAnimationFrame(() => r()))
|
||||
await new Promise((r) => setTimeout(r, 50))
|
||||
const el = host.querySelector('.oa-print-page')
|
||||
if (!el) throw new Error('print sheet missing')
|
||||
const canvas = await html2canvas(el, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#ffffff',
|
||||
logging: false,
|
||||
windowWidth: el.scrollWidth,
|
||||
windowHeight: el.scrollHeight
|
||||
})
|
||||
const blob = await canvasToPdfBlob(canvas)
|
||||
return { blob, filename }
|
||||
} finally {
|
||||
host.remove()
|
||||
}
|
||||
}
|
||||
|
||||
function isWeChatMiniWebView() {
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.__wxjs_environment === 'miniprogram') {
|
||||
|
||||
Reference in new issue
Block a user