保姆级教程:用Vue3把wangeditor内容一键导出为高清PDF(附完整代码)

张开发
2026/4/4 4:34:53 15 分钟阅读
保姆级教程:用Vue3把wangeditor内容一键导出为高清PDF(附完整代码)
Vue3实战零基础构建富文本PDF导出组件附工程化解决方案在Web开发中将富文本内容导出为PDF是常见的业务需求。本文将带您从零开始用Vue3构建一个可复用的PDF导出组件解决实际开发中的分页截断、背景色异常等问题。不同于简单的代码片段演示我们将采用工程化思维封装成即插即用的Composable函数和组件。1. 环境准备与依赖选型1.1 初始化Vue3项目使用Vite快速搭建开发环境npm create vitelatest vue3-pdf-export --template vue-ts cd vue3-pdf-export npm install1.2 核心依赖安装执行以下命令安装必要依赖npm install wangeditor html2canvas jspdf types/html2canvas types/jspdf -D各依赖作用说明依赖名称版本范围功能描述wangeditor^5.x轻量级富文本编辑器html2canvas^1.4.x将HTML元素转为Canvas图像jspdf^2.5.x生成PDF文档的核心库提示建议锁定版本号以避免API变更导致的兼容性问题2. 富文本编辑器集成2.1 基础编辑器实现创建src/components/RichEditor.vuetemplate div classeditor-container div refeditorRef classeditor/div /div /template script setup langts import { ref, onMounted, defineExpose } from vue import WangEditor from wangeditor const editorRef refHTMLElement() const editor refWangEditor() const content ref() onMounted(() { editor.value new WangEditor(editorRef.value) editor.value.config.onchange (newHtml: string) { content.value newHtml } editor.value.create() }) defineExpose({ getContent: () content.value }) /script style scoped .editor { border: 1px solid #ddd; min-height: 300px; } /style2.2 编辑器功能扩展为提升用户体验建议添加以下配置editor.value.config { ...editor.value.config, highlight: true, // 启用代码高亮 pasteFilterStyle: false, // 保留粘贴样式 customAlert: (msg: string) { console.warn(Editor Warning:, msg) } }3. PDF导出核心逻辑实现3.1 创建转换工具函数新建src/utils/pdfExport.tsimport html2canvas from html2canvas import { jsPDF } from jspdf const A4_WIDTH 595.28 const A4_HEIGHT 841.89 export async function exportToPdf(html: string, filename: string) { // 创建临时容器 const container document.createElement(div) container.style.width ${A4_WIDTH}px container.style.padding 20px container.style.position absolute container.style.left -9999px container.innerHTML html document.body.appendChild(container) try { const canvas await html2canvas(container, { scale: 2, useCORS: true, logging: false, backgroundColor: #FFFFFF }) const pdf new jsPDF(p, pt, a4) const imgData canvas.toDataURL(image/png) const imgWidth A4_WIDTH const imgHeight (canvas.height * imgWidth) / canvas.width // 分页处理 let heightLeft imgHeight let position 0 pdf.addImage(imgData, PNG, 0, position, imgWidth, imgHeight) heightLeft - A4_HEIGHT while (heightLeft 0) { position heightLeft - imgHeight pdf.addPage() pdf.addImage(imgData, PNG, 0, position, imgWidth, imgHeight) heightLeft - A4_HEIGHT } pdf.save(${filename}.pdf) } finally { document.body.removeChild(container) } }3.2 常见问题解决方案问题1黑色背景// 解决方案显式设置背景色 html2canvas(element, { backgroundColor: #FFFFFF })问题2文字截断// 解决方案调整scale参数并优化分页逻辑 const canvas await html2canvas(element, { scale: 2, // 提高渲染精度 windowWidth: 1200 // 扩大渲染窗口 })4. 完整组件集成方案4.1 创建PDF导出按钮组件src/components/PdfExportButton.vuetemplate button clickhandleExport :disabledloading classpdf-export-btn {{ loading ? 生成中... : 导出PDF }} /button /template script setup langts import { ref } from vue import { exportToPdf } from /utils/pdfExport const props defineProps{ content: string fileName?: string }() const loading ref(false) const handleExport async () { try { loading.value true await exportToPdf( props.content, props.fileName || document-${new Date().getTime()} ) } finally { loading.value false } } /script style scoped .pdf-export-btn { padding: 8px 16px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } .pdf-export-btn:disabled { background: #cccccc; } /style4.2 组合式函数封装创建可复用的Composable// src/composables/usePdfExport.ts import { exportToPdf } from /utils/pdfExport export function usePdfExport() { const exportAsPdf async (html: string, filename: string) { try { await exportToPdf(html, filename) return true } catch (error) { console.error(PDF导出失败:, error) return false } } return { exportAsPdf } }5. 项目实战应用5.1 页面集成示例src/views/DocumentEditor.vuetemplate div classdocument-editor RichEditor refeditor / PdfExportButton :contenteditorContent fileName我的文档 clickhandleExport / /div /template script setup langts import { ref } from vue import RichEditor from /components/RichEditor.vue import PdfExportButton from /components/PdfExportButton.vue const editor refInstanceTypetypeof RichEditor() const editorContent ref() const handleExport async () { if (editor.value) { editorContent.value editor.value.getContent() } } /script5.2 性能优化建议延迟加载PDF库const exportToPdf async () { const { jsPDF } await import(jspdf) // 使用动态导入的jsPDF }缓存Canvas数据let cachedCanvas: HTMLCanvasElement | null null function getCanvasCache() { if (!cachedCanvas) { cachedCanvas document.createElement(canvas) } return cachedCanvas }Web Worker支持// 创建worker.js self.importScripts(https://cdn.jsdelivr.net/npm/jspdflatest/dist/jspdf.umd.min.js) self.onmessage async (e) { const { html, filename } e.data // 在worker中执行转换逻辑 }6. 进阶功能扩展6.1 添加页眉页脚修改exportToPdf函数function addHeaderFooter(pdf: jsPDF, pageNumber: number) { pdf.setFontSize(10) pdf.setTextColor(150) pdf.text( 页眉 - ${new Date().toLocaleDateString()}, A4_WIDTH / 2, 20, { align: center } ) pdf.text( 第 ${pageNumber} 页, A4_WIDTH - 40, A4_HEIGHT - 20 ) }6.2 支持自定义样式扩展API接口interface PdfOptions { margin?: number header?: string footer?: string styles?: string } export async function exportToPdf( html: string, filename: string, options?: PdfOptions ) { // 应用自定义样式 if (options?.styles) { const style document.createElement(style) style.innerHTML options.styles container.appendChild(style) } // ...其余逻辑 }6.3 多语言支持创建i18n配置文件// src/locales/pdfExport.ts export const pdfExportTexts { zh-CN: { exporting: 正在生成PDF..., success: 导出成功, error: 导出失败 }, en-US: { exporting: Generating PDF..., success: Export succeeded, error: Export failed } }在组件中使用script setup import { useI18n } from vue-i18n import { pdfExportTexts } from /locales/pdfExport const { t } useI18n() const localeTexts computed(() pdfExportTexts[t.locale.value]) /script7. 测试与调试技巧7.1 单元测试配置安装测试依赖npm install vitest vue/test-utils happy-dom -D示例测试用例// tests/utils/pdfExport.test.ts import { describe, it, expect } from vitest import { exportToPdf } from /utils/pdfExport describe(PDF导出功能, () { it(应正确处理空内容, async () { const result await exportToPdf(, test) expect(result).toBeUndefined() }) it(应生成包含文本的PDF, async () { const mockHtml div测试内容/div await expect(exportToPdf(mockHtml, test)).resolves.not.toThrow() }) })7.2 常见问题排查问题图片不显示解决方案确保图片支持CORS或使用base64内联图片问题特殊字体缺失/* 在PDF容器中添加字体声明 */ font-face { font-family: PdfFont; src: local(SimSun), local(Songti SC); }问题分页位置不当// 调整分页检测逻辑 const shouldBreak (yPos: number) { return yPos A4_HEIGHT - 50 // 预留50pt页边距 }8. 工程化封装建议8.1 发布为独立npm包package.json关键配置{ name: vue3-pdf-export, version: 1.0.0, main: dist/index.umd.js, module: dist/index.es.js, types: dist/index.d.ts, files: [dist], peerDependencies: { vue: ^3.0.0 } }8.2 自动生成类型定义使用vite插件配置// vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import dts from vite-plugin-dts export default defineConfig({ plugins: [ vue(), dts({ insertTypesEntry: true, include: [src/components] }) ], build: { lib: { entry: src/index.ts, formats: [es, umd] } } })8.3 示例项目集成创建演示工程mkdir examples cd examples npm create vitelatest demo --template vue-ts在示例项目中测试组件script setup import { PdfExport } from vue3-pdf-export const content ref(h1测试文档/h1p这是演示内容/p) /script

更多文章