前端上传 pdf 文件 ,前端自己解析出来 生成界面 然后支持编辑

要在前端解析 PDF 文件并生成可编辑界面,我们可以使用 PDF.js 库来解析 PDF 内容,然后将其转换为可编辑的 HTML 元素。
主要特点和工作原理如下:

  1. PDF 解析:
    使用 Mozilla 的 PDF.js 库解析 PDF 文件内容,提取文本信息。这是前端处理 PDF 最常用的库之一,功能强大且稳定。
  2. 用户界面:
    简洁的上传区域,支持点击上传和拖拽上传
    解析进度显示,提升用户体验
    分页导航,方便浏览多页 PDF
    编辑 / 预览模式切换
  3. 编辑功能:
    将 PDF 文本转换为可编辑的 HTML 段落(使用 contenteditable 属性)
    支持基本的文本编辑操作
    保存编辑内容(前端暂存,实际应用中可发送到服务器)
  4. 实现流程:
    用户上传 PDF 文件
    使用 PDF.js 加载并解析 PDF
    将解析出的文本内容转换为可编辑的 HTML 元素
    提供编辑工具和导航功能
    支持保存更改和导出(导出功能在实际应用中需要额外库支持)

要注意的是,前端 PDF 编辑有一些局限性:

  • 复杂的 PDF 布局(如多列、表格)可能无法完美转换
  • 包含图片或复杂图形的 PDF 处理起来比较困难
  • 前端生成 PDF 需要额外的库(如 jsPDF)支持

界面


代码

javascript 复制代码
<!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解析与编辑工具 &copy; 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.js
        const 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或调用后端API
                alert('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') {
                // 将文件设置到fileInput
                const 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>
相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax