feat(oa): export mobile PDF via upload and WeChat preview navigate

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-13 10:57:44 +08:00
1 parent bf22b29d80
commit 8ac1eb3d40
1 file changed
+94 -50
@@ -5,6 +5,8 @@
*/
import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'
import { uploadAttachment, fetchAttachmentDownloadUrl } from '../../api/oa.js'
import { attachmentFullUrl } from '../../utils/attachment.js'
import { formatDeptDisplay } from '../kit/oa-dept-value.js'
import { formatPersonDisplay } from '../kit/oa-person-value.js'
import { formatIdNameDisplay } from '../../utils/id-name-value.js'
@@ -871,68 +873,110 @@ function isWeChatMiniWebView() {
return false
}
function navigateToHostPdfPreview({ url, fileName }) {
const q =
'url=' +
encodeURIComponent(url) +
'&name=' +
encodeURIComponent(fileName || 'approval.pdf')
const path = '/pages/oa/pdf-preview/index?' + q
try {
if (typeof wx !== 'undefined' && wx.miniProgram?.navigateTo) {
wx.miniProgram.navigateTo({ url: path })
return true
}
} catch {
/* ignore */
}
try {
if (typeof uni !== 'undefined' && uni.webView?.navigateTo) {
uni.webView.navigateTo({ url: path })
return true
}
} catch {
/* ignore */
}
return false
}
/**
* 打开可打印 HTML 并调用 window.print()。
* @returns {Promise<{ ok: boolean, message?: string }>}
* @returns {Promise<{ ok: boolean, message?: string, mode?: 'preview' | 'download' }>}
*/
export async function exportOaApprovalPdf(detail, opts = {}) {
if (!detail) {
return { ok: false, message: '暂无审批详情,无法打印' }
return { ok: false, message: '暂无审批详情,无法导出 PDF' }
}
const model = buildOaPrintModel(detail, {
printerName: opts.printerName || ''
})
const html = buildPrintHtml(model)
// 微信小程序 web-view 内系统打印能力普遍不可用
if (isWeChatMiniWebView()) {
return {
ok: false,
message: '微信内暂不支持系统打印,请用手机浏览器打开或到电脑端导出'
}
}
let frame = null
let blob
let filename
try {
frame = document.createElement('iframe')
frame.setAttribute('title', 'oa-print')
frame.style.cssText =
'position:fixed;right:0;bottom:0;width:0;height:0;border:0;opacity:0;pointer-events:none;'
document.body.appendChild(frame)
const doc = frame.contentDocument || frame.contentWindow?.document
if (!doc || !frame.contentWindow) {
throw new Error('print frame unavailable')
}
doc.open()
doc.write(html)
doc.close()
await new Promise((r) => setTimeout(r, 80))
const win = frame.contentWindow
if (typeof win.print !== 'function') {
throw new Error('print not supported')
}
win.focus()
win.print()
return { ok: true }
;({ blob, filename } = await generateOaApprovalPdfBlob(detail, opts))
} catch (e) {
return {
ok: false,
message:
(e && e.message) ||
'当前环境无法调起打印,请用手机浏览器打开或到电脑端导出'
message: (e && e.message) || '生成 PDF 失败'
}
} finally {
if (frame) {
setTimeout(() => {
try {
frame.remove()
} catch {
/* ignore */
}
if (isWeChatMiniWebView()) {
try {
const file = new File([blob], filename, { type: 'application/pdf' })
const res = await uploadAttachment(file)
const fileUrl = String(res?.fileUrl || res?.data?.fileUrl || '').trim()
if (!fileUrl) {
return { ok: false, message: '上传 PDF 失败:无文件地址' }
}
let openUrl = ''
try {
const dl = await fetchAttachmentDownloadUrl({
fileUrl,
fileName: filename
})
openUrl = String(dl?.downloadUrl || dl?.data?.downloadUrl || '').trim()
} catch {
/* fall through */
}
if (!openUrl) {
openUrl = attachmentFullUrl(fileUrl) || fileUrl
}
if (!/^https?:\/\//i.test(openUrl)) {
return { ok: false, message: '无法打开 PDF:下载地址无效' }
}
const navigated = navigateToHostPdfPreview({
url: openUrl,
fileName: filename
})
if (!navigated) {
return {
ok: false,
message: '无法跳转预览页,请升级微信后重试'
}
}, 1500)
}
return { ok: true, mode: 'preview' }
} catch (e) {
return {
ok: false,
message: (e && e.message) || '上传或打开 PDF 失败'
}
}
}
try {
const href = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = href
a.download = filename
document.body.appendChild(a)
a.click()
a.remove()
setTimeout(() => URL.revokeObjectURL(href), 2000)
return { ok: true, mode: 'download' }
} catch (e) {
return {
ok: false,
message: (e && e.message) || '下载 PDF 失败'
}
}
}