前端上传 pdf 文件 ,前端自己解析出来 生成界面 然后支持编辑
要在前端解析 PDF 文件并生成可编辑界面,我们可以使用 PDF.js 库来解析 PDF 内容,然后将其转换为可编辑的 HTML 元素。
主要特点和工作原理如下:
- PDF 解析:
使用 Mozilla 的 PDF.js 库解析 PDF 文件内容,提取文本信息。这是前端处理 PDF 最常用的库之一,功能强大且稳定。 - 用户界面:
简洁的上传区域,支持点击上传和拖拽上传
解析进度显示,提升用户体验
分页导航,方便浏览多页 PDF
编辑 / 预览模式切换 - 编辑功能:
将 PDF 文本转换为可编辑的 HTML 段落(使用 contenteditable 属性)
支持基本的文本编辑操作
保存编辑内容(前端暂存,实际应用中可发送到服务器) - 实现流程:
用户上传 PDF 文件
使用 PDF.js 加载并解析 PDF
将解析出的文本内容转换为可编辑的 HTML 元素
提供编辑工具和导航功能
支持保存更改和导出(导出功能在实际应用中需要额外库支持)
要注意的是,前端 PDF 编辑有一些局限性:
- 复杂的 PDF 布局(如多列、表格)可能无法完美转换
- 包含图片或复杂图形的 PDF 处理起来比较困难
- 前端生成 PDF 需要额外的库(如 jsPDF)支持
界面
代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>PDF解析与编辑工具</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',neutral: '#6B7280',light: '#F3F4F6',dark: '#1F2937'},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.transition-height {transition: max-height 0.3s ease-out;}.editable-content [contenteditable="true"]:focus {outline: 2px solid #3B82F6;border-radius: 2px;background-color: rgba(59, 130, 246, 0.05);}}</style>
</head>
<body class="bg-gray-50 font-sans"><!-- 顶部导航栏 --><header class="bg-white shadow-sm sticky top-0 z-50"><div class="container mx-auto px-4 py-4 flex justify-between items-center"><div class="flex items-center space-x-2"><i class="fa fa-file-pdf-o text-red-500 text-2xl"></i><h1 class="text-xl font-bold text-dark">PDF解析与编辑工具</h1></div><div class="flex space-x-3"><button id="saveBtn" class="bg-secondary hover:bg-green-600 text-white px-4 py-2 rounded-md flex items-center transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" disabled><i class="fa fa-save mr-2"></i>保存</button><button id="downloadBtn" class="bg-primary hover:bg-blue-600 text-white px-4 py-2 rounded-md flex items-center transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" disabled><i class="fa fa-download mr-2"></i>导出PDF</button></div></div></header><main class="container mx-auto px-4 py-8"><!-- 文件上传区域 --><section id="uploadSection" class="mb-8"><div class="bg-white rounded-lg shadow-md p-8 text-center"><label for="fileInput" class="cursor-pointer"><div class="border-2 border-dashed border-neutral rounded-lg p-10 transition-colors duration-200 hover:border-primary"><i class="fa fa-cloud-upload text-5xl text-primary mb-4"></i><h2 class="text-xl font-semibold mb-2">上传PDF文件</h2><p class="text-neutral mb-4">点击或拖拽文件到此处上传</p><p class="text-sm text-neutral">支持的格式: PDF</p><input id="fileInput" type="file" accept=".pdf" class="hidden"></div></label><div id="fileInfo" class="mt-4 hidden"><div class="flex items-center justify-center p-3 bg-light rounded-md"><i class="fa fa-file-pdf-o text-red-500 mr-2"></i><span id="fileName" class="mr-2"></span><button id="removeFile" class="text-neutral hover:text-red-500 transition-colors"><i class="fa fa-times"></i></button></div></div></div></section><!-- 解析进度 --><section id="progressSection" class="mb-8 hidden"><div class="bg-white rounded-lg shadow-md p-6"><h2 class="text-lg font-semibold mb-4">正在解析PDF文件...</h2><div class="w-full bg-gray-200 rounded-full h-2.5"><div id="progressBar" class="bg-primary h-2.5 rounded-full" style="width: 0%"></div></div><p id="progressText" class="text-sm text-neutral mt-2">准备中...</p></div></section><!-- 编辑区域 --><section id="editorSection" class="hidden"><div class="bg-white rounded-lg shadow-md p-6 mb-6"><div class="flex justify-between items-center mb-6"><h2 class="text-xl font-semibold">PDF内容编辑</h2><div class="flex space-x-2"><button id="editModeBtn" class="bg-primary hover:bg-blue-600 text-white px-3 py-1 rounded text-sm transition-colors"><i class="fa fa-pencil mr-1"></i>编辑模式</button><button id="previewModeBtn" class="bg-neutral hover:bg-gray-600 text-white px-3 py-1 rounded text-sm transition-colors"><i class="fa fa-eye mr-1"></i>预览模式</button></div></div><div id="pdfEditor" class="editable-content min-h-[500px]"><!-- PDF内容将在这里显示 --></div></div></section><!-- 页面导航 --><section id="pageNavigation" class="flex justify-center mt-6 hidden"><div class="flex items-center space-x-4"><button id="prevPage" class="bg-white border border-neutral rounded-md px-3 py-1 hover:bg-light transition-colors disabled:opacity-50 disabled:cursor-not-allowed"><i class="fa fa-chevron-left"></i></button><div id="pageIndicator" class="text-neutral">第 1 页 / 共 0 页</div><button id="nextPage" class="bg-white border border-neutral rounded-md px-3 py-1 hover:bg-light transition-colors disabled:opacity-50 disabled:cursor-not-allowed"><i class="fa fa-chevron-right"></i></button></div></section></main><footer class="bg-dark text-white py-6 mt-12"><div class="container mx-auto px-4 text-center"><p>PDF解析与编辑工具 © 2025年7月16日</p><p class="text-sm text-gray-400 mt-1">使用PDF.js和Tailwind CSS构建</p></div></footer><script>// 全局变量let pdfDoc = null;let currentPage = 1;let totalPages = 0;let isEditMode = true;let pdfData = null;// DOM元素const fileInput = document.getElementById('fileInput');const fileInfo = document.getElementById('fileInfo');const fileName = document.getElementById('fileName');const removeFile = document.getElementById('removeFile');const uploadSection = document.getElementById('uploadSection');const progressSection = document.getElementById('progressSection');const progressBar = document.getElementById('progressBar');const progressText = document.getElementById('progressText');const editorSection = document.getElementById('editorSection');const pdfEditor = document.getElementById('pdfEditor');const pageNavigation = document.getElementById('pageNavigation');const pageIndicator = document.getElementById('pageIndicator');const prevPageBtn = document.getElementById('prevPage');const nextPageBtn = document.getElementById('nextPage');const editModeBtn = document.getElementById('editModeBtn');const previewModeBtn = document.getElementById('previewModeBtn');const saveBtn = document.getElementById('saveBtn');const downloadBtn = document.getElementById('downloadBtn');// 初始化PDF.jsconst pdfjsLib = window['pdfjs-dist/build/pdf'];pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';// 事件监听fileInput.addEventListener('change', handleFileUpload);removeFile.addEventListener('click', removeSelectedFile);prevPageBtn.addEventListener('click', goToPreviousPage);nextPageBtn.addEventListener('click', goToNextPage);editModeBtn.addEventListener('click', enableEditMode);previewModeBtn.addEventListener('click', enablePreviewMode);saveBtn.addEventListener('click', saveChanges);downloadBtn.addEventListener('click', downloadAsPDF);// 处理文件上传function handleFileUpload(event) {const file = event.target.files[0];if (!file) return;// 显示文件信息fileName.textContent = file.name;fileInfo.classList.remove('hidden');uploadSection.classList.add('opacity-50');// 准备解析const fileReader = new FileReader();fileReader.onload = function() {pdfData = new Uint8Array(this.result);loadPDF(pdfData);};fileReader.readAsArrayBuffer(file);}// 移除选中的文件function removeSelectedFile() {fileInput.value = '';fileInfo.classList.add('hidden');uploadSection.classList.remove('opacity-50');resetPDFState();}// 重置PDF状态function resetPDFState() {pdfDoc = null;currentPage = 1;totalPages = 0;pdfData = null;progressSection.classList.add('hidden');editorSection.classList.add('hidden');pageNavigation.classList.add('hidden');saveBtn.disabled = true;downloadBtn.disabled = true;}// 加载PDF文件function loadPDF(data) {progressSection.classList.remove('hidden');progressBar.style.width = '0%';progressText.textContent = '正在加载PDF...';pdfjsLib.getDocument(data).promise.then(function(pdf) {pdfDoc = pdf;totalPages = pdf.numPages;progressBar.style.width = '30%';progressText.textContent = '解析PDF内容...';updatePageIndicator();renderPage(currentPage);// 显示编辑区域和导航editorSection.classList.remove('hidden');pageNavigation.classList.remove('hidden');saveBtn.disabled = false;downloadBtn.disabled = false;}).catch(function(error) {console.error('加载PDF时出错:', error);progressText.textContent = `加载失败: ${error.message}`;});}// 渲染指定页面function renderPage(pageNum) {if (!pdfDoc) return;pdfDoc.getPage(pageNum).then(function(page) {// 获取页面内容return page.getTextContent().then(function(textContent) {// 更新进度const progress = 30 + Math.round((pageNum / totalPages) * 70);progressBar.style.width = `${progress}%`;progressText.textContent = `正在处理第 ${pageNum} 页 / 共 ${totalPages} 页`;// 清空编辑器pdfEditor.innerHTML = '';// 创建页面容器const pageContainer = document.createElement('div');pageContainer.className = 'pdf-page p-8 border border-gray-200 rounded-lg shadow-sm';pageContainer.dataset.page = pageNum;// 处理文本内容let lastY = null;let paragraph = document.createElement('p');paragraph.className = 'mb-4 leading-relaxed';paragraph.contentEditable = isEditMode;textContent.items.forEach(function(item) {// 当Y坐标变化较大时,创建新段落if (lastY !== null && Math.abs(item.transform[5] - lastY) > 15) {pageContainer.appendChild(paragraph);paragraph = document.createElement('p');paragraph.className = 'mb-4 leading-relaxed';paragraph.contentEditable = isEditMode;}const span = document.createElement('span');span.textContent = item.str;paragraph.appendChild(span);lastY = item.transform[5];});// 添加最后一个段落if (paragraph.children.length > 0) {pageContainer.appendChild(paragraph);}// 如果页面没有文本内容if (textContent.items.length === 0) {const emptyMsg = document.createElement('p');emptyMsg.className = 'text-neutral italic text-center py-8';emptyMsg.textContent = '此页面没有可编辑的文本内容。可能包含图像或其他非文本元素。';pageContainer.appendChild(emptyMsg);}// 添加到编辑器pdfEditor.appendChild(pageContainer);// 更新按钮状态updateNavigationButtons();// 如果是最后一页,隐藏进度if (pageNum === totalPages) {setTimeout(() => {progressSection.classList.add('hidden');}, 500);}});}).catch(function(error) {console.error('渲染页面时出错:', error);pdfEditor.innerHTML = `<p class="text-red-500">渲染页面时出错: ${error.message}</p>`;});}// 更新页码指示器function updatePageIndicator() {pageIndicator.textContent = `第 ${currentPage} 页 / 共 ${totalPages} 页`;}// 更新导航按钮状态function updateNavigationButtons() {prevPageBtn.disabled = currentPage <= 1;nextPageBtn.disabled = currentPage >= totalPages;}// 上一页function goToPreviousPage() {if (currentPage > 1) {currentPage--;renderPage(currentPage);updatePageIndicator();}}// 下一页function goToNextPage() {if (currentPage < totalPages) {currentPage++;renderPage(currentPage);updatePageIndicator();}}// 启用编辑模式function enableEditMode() {isEditMode = true;editModeBtn.classList.remove('bg-neutral', 'hover:bg-gray-600');editModeBtn.classList.add('bg-primary', 'hover:bg-blue-600');previewModeBtn.classList.remove('bg-primary', 'hover:bg-blue-600');previewModeBtn.classList.add('bg-neutral', 'hover:bg-gray-600');// 使所有段落可编辑document.querySelectorAll('#pdfEditor [contenteditable]').forEach(el => {el.contentEditable = true;});}// 启用预览模式function enablePreviewMode() {isEditMode = false;previewModeBtn.classList.remove('bg-neutral', 'hover:bg-gray-600');previewModeBtn.classList.add('bg-primary', 'hover:bg-blue-600');editModeBtn.classList.remove('bg-primary', 'hover:bg-blue-600');editModeBtn.classList.add('bg-neutral', 'hover:bg-gray-600');// 使所有段落不可编辑document.querySelectorAll('#pdfEditor [contenteditable]').forEach(el => {el.contentEditable = false;});}// 保存更改(在实际应用中,这里会将数据发送到服务器)function saveChanges() {// 获取所有页面的内容const pagesContent = [];document.querySelectorAll('.pdf-page').forEach(pageEl => {const pageNum = parseInt(pageEl.dataset.page);const textContent = pageEl.innerText;pagesContent.push({page: pageNum,content: textContent});});// 显示保存成功提示const originalText = saveBtn.innerHTML;saveBtn.innerHTML = '<i class="fa fa-check mr-2"></i>已保存';saveBtn.classList.remove('bg-secondary');saveBtn.classList.add('bg-green-600');setTimeout(() => {saveBtn.innerHTML = originalText;saveBtn.classList.remove('bg-green-600');saveBtn.classList.add('bg-secondary');}, 2000);// 在实际应用中,这里会发送数据到服务器console.log('保存的PDF内容:', pagesContent);}// 下载为PDF(实际应用中需要后端支持或使用jsPDF等库)function downloadAsPDF() {// 显示加载状态const originalText = downloadBtn.innerHTML;downloadBtn.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i>处理中...';downloadBtn.disabled = true;// 模拟PDF生成过程setTimeout(() => {// 这里仅做演示,实际应用中需要使用专门的库如jsPDF或调用后端APIalert('PDF导出功能在实际应用中需要额外的库或后端支持。');// 恢复按钮状态downloadBtn.innerHTML = originalText;downloadBtn.disabled = false;}, 1500);}// 支持拖拽上传const dropArea = document.querySelector('#uploadSection .border-dashed');['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {dropArea.addEventListener(eventName, preventDefaults, false);});function preventDefaults(e) {e.preventDefault();e.stopPropagation();}['dragenter', 'dragover'].forEach(eventName => {dropArea.addEventListener(eventName, highlight, false);});['dragleave', 'drop'].forEach(eventName => {dropArea.addEventListener(eventName, unhighlight, false);});function highlight() {dropArea.classList.add('border-primary', 'bg-blue-50');}function unhighlight() {dropArea.classList.remove('border-primary', 'bg-blue-50');}dropArea.addEventListener('drop', handleDrop, false);function handleDrop(e) {const dt = e.dataTransfer;const file = dt.files[0];if (file && file.type === 'application/pdf') {// 将文件设置到fileInputconst dataTransfer = new DataTransfer();dataTransfer.items.add(file);fileInput.files = dataTransfer.files;// 触发change事件const event = new Event('change', { bubbles: true });fileInput.dispatchEvent(event);} else {alert('请上传PDF格式的文件');}}</script>
</body>
</html>