核心库为:snapDOM、html-to-image、jsPDF
处理流程为DOM元素=>image=>pdf
封装工具函数为:
import { jsPDF as JsPDF } from 'jspdf'
import { snapdom } from '@zumer/snapdom'
import { toJpeg } from 'html-to-image'
/**
* 单页截图工具
* @param {HTMLElement|string} target - DOM元素或选择器
* @param {object} options - 配置项
* @param {number} [options.scale=1] - 缩放比例
* @param {boolean} [options.preview=false] - 是否预览截图(默认关闭)
* @param {boolean} [options.downLoad=false] - 是否下载截图(默认关闭)
* @param {string} [options.documentName=''] - 文档名称
* @returns - 返回PNG的Base64字符串
*/
export const capturePageAsPNG = async(target, options = {}) => {
const { scale = 1, preview = false, downLoad = false, documentName = '' } = options
const el = typeof target === 'string' ? document.querySelector(target) : target
if (!el) throw new Error('无效的DOM元素')
await new Promise(resolve => setTimeout(resolve, 50))
const snapshot = await snapdom(el, { scale, compress: false, embedFonts: true })
const imgBase64 = await snapshot.toPng()
if (preview) {
const img = new Image()
img.src = imgBase64
img.style.maxWidth = '100%'
document.body.appendChild(img)
}
if (downLoad) {
await snapshot.download({ format: 'jpg', filename: `${documentName}` })
}
return imgBase64 // 返回单页PNG的Base64
}
/**
* 纸张尺寸映射表(300dpi分辨率)
* 键:纸张类型
* 值:包含宽高尺寸的对象(单位:像素)
*/
const jpegPaperSizeMap = {
a4: { width: 2480, height: 3508 }, // A4尺寸:210mm×297mm → 2480×3508像素
a5: { width: 1748, height: 2480 }, // A5尺寸:148mm×210mm → 1748×2480像素
letter: { width: 2550, height: 3300 } // 美国信纸尺寸:8.5×11英寸 → 2550×3300像素
}
/**
* 将HTML元素转换为JPEG格式的Base64图片
* @param {string|HTMLElement} target - DOM元素或CSS选择器字符串
* @param {Object} options - 配置选项
* @param {number} [options.quality=1] - 图片质量(0-1)
* @param {string} [options.paperType='a4'] - 纸张类型('a4'|'a5'|'letter')
* @param {boolean} [options.preview=false] - 是否在页面预览生成的图片
* @param {boolean} [options.downLoad=false] - 是否自动下载图片
* @param {string} [options.documentName=''] - 下载时的文件名
* @returns {Promise<string|null>} 返回JPEG的Base64字符串或null(失败时)
*/
export const convertHtmlToJpegBase64 = async(
target, // 支持选择器字符串或DOM元素
options = {}
) => {
const {
quality = 1,
paperType = 'a4',
preview = false,
downLoad = false,
documentName = ''
} = options
try {
const el = typeof target === 'string' ? document.querySelector(target) : target
if (!el) throw new Error('无效的DOM元素')
const { width: targetWidth, height: targetHeight } = jpegPaperSizeMap[paperType]
const realWidth = el.scrollWidth
const realHeight = el.scrollHeight
let scale = 1
let extraStyle = {}
// 超出页面尺寸才缩放
if (realHeight > targetHeight || realWidth > targetWidth) {
scale = Math.min(targetWidth / realWidth, targetHeight / realHeight)
extraStyle = {
transform: `scale(${scale})`,
transformOrigin: 'top left'
}
}
// 添加微小延迟确保样式应用
await new Promise(resolve => setTimeout(resolve, 50))
const imgBase64 = await toJpeg(el, {
backgroundColor: '#FFF',
width: realWidth,
height: realHeight,
style: extraStyle,
quality
})
// 预览功能
if (preview) {
const img = new Image()
img.src = imgBase64
img.style.maxWidth = '100%'
document.body.appendChild(img)
}
// 下载功能
if (downLoad) {
const link = document.createElement('a')
link.href = imgBase64
link.download = documentName || 'document.jpg'
link.click()
}
return imgBase64
} catch (e) {
console.error('转换HTML为JPEG出错:', e)
return null
}
}
/**
* 将多页PNG合并为PDF
* @param {string[]} pngBase64Array - 多页PNG的Base64数组
* @param {object} options - PDF配置
* @param {string} [options.title='PDF'] - PDF标题
* @param {'a3'|'a4'} [options.paperType='a4'] - 纸张类型
* @param {'p'|'l'} [options.orientation] - 强制方向(默认自动选择)
* @param {boolean} [options.download=false] - 是否自动下载PDF
* @param {string} [options.filename=''] - 下载的文件名
* @returns {string} - PDF的Base64字符串
*/
export const convertPNGsToPDF = (pngBase64Array, options = {}) => {
const { title = 'PDF', paperType = 'a4', orientation, download = false, filename = '' } = options
if (!pngBase64Array?.length) throw new Error('PNG数组不能为空')
// 自动决定方向:A3横向,A4纵向(可被orientation覆盖)
const finalOrientation = orientation || (paperType === 'a3' ? 'l' : 'p')
const pdf = new JsPDF(finalOrientation, 'mm', paperType)
const pageSize = pdf.internal.pageSize
const pageWidth = pageSize.getWidth() - 20 // 左右留白
const pageHeight = pageSize.getHeight() - 20 // 上下留白
pdf.setProperties({ title })
pngBase64Array.forEach((pngBase64, index) => {
if (index > 0) pdf.addPage()
pdf.addImage(pngBase64, 'PNG', 10, 10, pageWidth, pageHeight)
})
// 新增下载控制
if (download) {
downloadPDF(pdf, filename || `${options.title || 'document'}`)
}
return pdf.output('datauristring')
}
/**
* 下载PDF文件
* @param {string|Blob|jsPDF} pdfSource - PDF数据源(Base64字符串/Blob对象/jsPDF实例)
* @param {string} [filename='document.pdf'] - 下载的文件名
*/
export const downloadPDF = (pdfSource, filename = 'document.pdf') => {
// 确保文件名以.pdf结尾
if (!filename.toLowerCase().endsWith('.pdf')) {
filename += '.pdf'
}
let blob
if (typeof pdfSource === 'string') {
// 处理Base64字符串
const byteString = atob(pdfSource.split(',')[1])
const ab = new ArrayBuffer(byteString.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
blob = new Blob([ab], { type: 'application/pdf' })
} else if (pdfSource instanceof Blob) {
// 直接使用Blob对象
blob = pdfSource
} else if (pdfSource?.output) {
// 处理jsPDF实例
blob = pdfSource.output('blob')
} else {
throw new Error('不支持的PDF数据源类型')
}
// 创建下载链接
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
// 清理
setTimeout(() => {
document.body.removeChild(a)
URL.revokeObjectURL(url)
}, 100)
}
辅助工具函数DOM稳定检测器:
/**
* 等待DOM稳定(Vue 2兼容版)
* @param {string} selector - 选择器
* @param {number} [timeout=1000] - 超时时间(ms)
* @returns {Promise<void>}
*/
export const waitForDOMStable = function(selector, timeout) {
timeout = timeout || 1000 // 默认超时1秒
return new Promise(function(resolve) {
const targetNode = document.querySelector(selector)
if (!targetNode) return resolve()
let timer
const observer = new MutationObserver(function() {
clearTimeout(timer)
timer = setTimeout(function() {
observer.disconnect()
resolve()
}, 200) // 100ms内无变化视为稳定
})
observer.observe(targetNode, {
childList: true,
subtree: true,
attributes: true,
characterData: true
})
// 超时保护
setTimeout(function() {
observer.disconnect()
resolve()
}, timeout)
})
}
2645

被折叠的 条评论
为什么被折叠?



