usePDF.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // composables/usePDF.js
  2. import { ref } from 'vue'
  3. import html2pdf from 'html2pdf.js'
  4. import { PDFDocument, rgb } from 'pdf-lib'
  5. import fontkit from '@pdf-lib/fontkit' // 关键:导入适配版fontkit
  6. export function usePDF() {
  7. const loading = ref(false)
  8. const error = ref(null)
  9. // 基础HTML转PDF(单页)
  10. const generateSinglePDF = async (element, options = {}) => {
  11. const defaultOptions = {
  12. margin: [10, 10, 10, 10],
  13. image: { type: 'jpeg', quality: 0.98 },
  14. html2canvas: {
  15. scale: 2,
  16. letterRendering: true,
  17. useCORS: true
  18. },
  19. jsPDF: {
  20. unit: 'mm',
  21. format: 'a4',
  22. orientation: 'portrait'
  23. },
  24. filename: 'temp.pdf',
  25. save: false
  26. }
  27. const mergedOptions = { ...defaultOptions, ...options }
  28. const pdfBlob = await html2pdf().set(mergedOptions).from(element).output('blob')
  29. return pdfBlob
  30. }
  31. // 加载中文字体(修复fontkit注册问题)
  32. const loadChineseFont = async (pdfDoc) => {
  33. // 1. 注册fontkit到PDFDocument(核心修复)
  34. pdfDoc.registerFontkit(fontkit)
  35. // 2. 加载中文字体文件(思源黑体,确保路径正确)
  36. const fontUrl = '/SourceHanSansSC-Regular.otf'
  37. // try {
  38. const fontResponse = await fetch(fontUrl)
  39. if (!fontResponse.ok) throw new Error('字体文件加载失败')
  40. const fontArrayBuffer = await fontResponse.arrayBuffer()
  41. // 3. 嵌入中文字体
  42. const font = await pdfDoc.embedFont(fontArrayBuffer)
  43. return font
  44. // } catch (err) {
  45. // console.error('字体加载失败:', err)
  46. // throw new Error('中文字体加载失败,请检查字体文件路径')
  47. // }
  48. }
  49. // 物理分页生成PDF(最终完整版)
  50. const generateWithPagination = async (pageElements, options = {}) => {
  51. loading.value = true
  52. error.value = null
  53. try {
  54. const { filename = '中文分页文档.pdf' } = options
  55. // 1. 创建PDF文档实例
  56. const finalPdfDoc = await PDFDocument.create()
  57. // 2. 加载中文字体
  58. const chineseFont = await loadChineseFont(finalPdfDoc)
  59. // 3. 遍历所有分页节点,逐个生成并合并页面
  60. for (let i = 0; i < pageElements.length; i++) {
  61. const element = pageElements[i]
  62. if (!element) continue // 跳过空节点
  63. // 生成当前页的PDF Blob
  64. const pageBlob = await generateSinglePDF(element, options)
  65. const pageArrayBuffer = await new Response(pageBlob).arrayBuffer()
  66. // 加载临时PDF并复制页面
  67. const pagePdfDoc = await PDFDocument.load(pageArrayBuffer)
  68. const [copiedPage] = await finalPdfDoc.copyPages(pagePdfDoc, [0])
  69. // 绘制中文页码(支持中文渲染)
  70. const { width, height } = copiedPage.getSize()
  71. copiedPage.drawText(`第 ${i + 1} 页 / 共 ${pageElements.length} 页`, {
  72. x: width / 2 - 60, // 微调位置适配中文字符
  73. y: 12, // 页码在页面底部
  74. size: 12, // 字体大小
  75. font: chineseFont, // 使用嵌入的中文字体
  76. color: rgb(0.3, 0.3, 0.3) // 灰色页码
  77. })
  78. // 添加到最终PDF(物理分页核心)
  79. finalPdfDoc.addPage(copiedPage)
  80. }
  81. // 4. 生成并下载PDF
  82. const finalPdfBytes = await finalPdfDoc.save()
  83. const finalPdfBlob = new Blob([finalPdfBytes], { type: 'application/pdf' })
  84. // 原生下载(无需file-saver)
  85. const downloadLink = document.createElement('a')
  86. downloadLink.href = URL.createObjectURL(finalPdfBlob)
  87. downloadLink.download = filename
  88. downloadLink.click()
  89. URL.revokeObjectURL(downloadLink.href)
  90. return true
  91. } catch (err) {
  92. error.value = `PDF生成失败:${err.message}`
  93. console.error('分页PDF生成错误:', err)
  94. return false
  95. } finally {
  96. loading.value = false
  97. }
  98. }
  99. // 原有HTML转PDF方法(保持兼容)
  100. const generateFromHTML = async (element, options = {}) => {
  101. loading.value = true
  102. error.value = null
  103. try {
  104. const defaultOptions = {
  105. margin: [10, 10, 10, 10],
  106. filename: 'document.pdf',
  107. image: { type: 'jpeg', quality: 0.98 },
  108. html2canvas: { scale: 2, letterRendering: true, useCORS: true },
  109. jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
  110. }
  111. const mergedOptions = { ...defaultOptions, ...options }
  112. await html2pdf().set(mergedOptions).from(element).save()
  113. return true
  114. } catch (err) {
  115. error.value = err.message
  116. console.error('PDF生成失败:', err)
  117. return false
  118. } finally {
  119. loading.value = false
  120. }
  121. }
  122. return {
  123. loading,
  124. error,
  125. generateFromHTML,
  126. generateWithPagination // 物理分页核心方法
  127. }
  128. }