You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP无输入框上传PDF:浏览器标注后自动上传至upload.php

实现标注后PDF存入localStorage并自动上传至upload.php

方案一:上传多页标注图片(基于现有canvas渲染逻辑)

你的现有代码是将每一页PDF渲染为带标注的图片,我们可以调整逻辑,先收集所有页面的图片数据存入localStorage,再转成文件格式上传到后端。

前端修改代码

// 生成标注后数据并完成上传
async function generateAndUploadAnnotatedPages() {
  const pageDataUrls = [];
  const totalPages = pdfDoc.numPages;

  // 按顺序渲染所有带标注的页面
  for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
    const page = await pdfDoc.getPage(pageNum);
    const viewport = page.getViewport({ scale: 1.5 });
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = viewport.width;
    canvas.height = viewport.height;

    const renderContext = {
      canvasContext: context,
      viewport: viewport
    };

    // 渲染PDF页面并等待完成
    await page.render(renderContext).promise;
    // 渲染当前页的标注
    renderAnnotationsOnCanvas(context, pageNum);
    // 将canvas转为base64格式的图片数据
    const imageData = canvas.toDataURL('image/png');
    pageDataUrls.push(imageData);
  }

  // 存入localStorage(注意:localStorage单条数据上限约5MB,大PDF可能超出)
  localStorage.setItem('annotatedPDFPages', JSON.stringify(pageDataUrls));

  // 从localStorage取出数据并准备上传
  const storedPages = JSON.parse(localStorage.getItem('annotatedPDFPages'));
  const formData = new FormData();

  // 将每个页面的base64数据转为Blob,添加到FormData
  storedPages.forEach((dataUrl, index) => {
    const blob = dataURLToBlob(dataUrl);
    formData.append(`pdf_pages[${index}]`, blob, `page_${index + 1}.png`);
  });

  // 发送上传请求
  try {
    const response = await fetch('upload.php', {
      method: 'POST',
      body: formData
    });

    if (response.ok) {
      console.log('标注页面上传成功');
      // 上传完成后可清除localStorage中的数据
      localStorage.removeItem('annotatedPDFPages');
    } else {
      console.error('上传请求失败');
    }
  } catch (error) {
    console.error('上传过程出错:', error);
  }
}

// 辅助函数:将base64格式的dataURL转为Blob
function dataURLToBlob(dataUrl) {
  const arr = dataUrl.split(',');
  const mime = arr[0].match(/:(.*?);/)[1];
  const bstr = atob(arr[1]);
  const u8arr = new Uint8Array(bstr.length);
  
  for (let i = 0; i < bstr.length; i++) {
    u8arr[i] = bstr.charCodeAt(i);
  }
  
  return new Blob([u8arr], { type: mime });
}

方案二:合并为单PDF文件上传(更贴合需求)

如果需要上传完整的PDF文件而非单页图片,可以使用jsPDF库将带标注的页面图片合并为一个PDF文件,再进行存储和上传。

前端代码(需引入jsPDF)

// 先在页面引入jsPDF库:<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
async function generateAndUploadAnnotatedPDF() {
  const { jsPDF } = window.jspdf;
  // 初始化A4尺寸的PDF文档
  const pdf = new jsPDF('p', 'mm', 'a4');
  const totalPages = pdfDoc.numPages;

  for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
    const page = await pdfDoc.getPage(pageNum);
    const viewport = page.getViewport({ scale: 1.5 });
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = viewport.width;
    canvas.height = viewport.height;

    const renderContext = {
      canvasContext: context,
      viewport: viewport
    };

    await page.render(renderContext).promise;
    renderAnnotationsOnCanvas(context, pageNum);

    // 将canvas图片添加到PDF文档
    const imgData = canvas.toDataURL('image/png');
    const pageWidth = pdf.internal.pageSize.getWidth();
    // 按比例计算图片高度,适配A4宽度
    const pageHeight = (canvas.height * pageWidth) / canvas.width;

    // 第一页无需新增,后续页面添加新页
    if (pageNum > 1) {
      pdf.addPage();
    }
    pdf.addImage(imgData, 'PNG', 0, 0, pageWidth, pageHeight);
  }

  // 将生成的PDF转为Blob格式
  const pdfBlob = pdf.output('blob');
  
  // 将Blob转为base64存入localStorage(Blob无法直接存入)
  const reader = new FileReader();
  reader.onload = function(e) {
    const pdfBase64 = e.target.result;
    localStorage.setItem('annotatedPDF', pdfBase64);

    // 准备FormData上传
    const formData = new FormData();
    formData.append('annotated_pdf', pdfBlob, 'annotated.pdf');

    // 发送上传请求
    fetch('upload.php', {
      method: 'POST',
      body: formData
    })
    .then(response => {
      if (response.ok) {
        console.log('标注PDF上传成功');
        localStorage.removeItem('annotatedPDF');
      } else {
        console.error('上传失败');
      }
    })
    .catch(error => console.error('上传错误:', error));
  };
  reader.readAsDataURL(pdfBlob);
}

后端upload.php示例

<?php
// 确保上传目录存在
$uploadDir = 'uploads/';
if (!file_exists($uploadDir)) {
  mkdir($uploadDir, 0777, true);
}

// 处理单PDF文件上传(对应方案二)
if (isset($_FILES['annotated_pdf'])) {
  $targetFile = $uploadDir . basename($_FILES['annotated_pdf']['name']);
  if (move_uploaded_file($_FILES['annotated_pdf']['tmp_name'], $targetFile)) {
    echo "PDF文件上传成功";
  } else {
    echo "PDF上传失败";
  }
}

// 处理多页图片上传(对应方案一)
if (isset($_FILES['pdf_pages'])) {
  foreach ($_FILES['pdf_pages']['tmp_name'] as $index => $tmpName) {
    $fileName = $uploadDir . 'page_' . ($index + 1) . '.png';
    move_uploaded_file($tmpName, $fileName);
  }
  echo "所有页面图片上传成功";
}
?>

关键注意事项

  • localStorage存储限制:localStorage单条数据上限约5MB,base64格式会比原文件大30%左右,大尺寸/多页PDF可能超出限制,这种情况建议使用IndexedDB替代。
  • 异步处理顺序:原代码的for循环未处理异步渲染的顺序问题,使用async/await可以保证页面按顺序渲染和收集。
  • 文件上传格式:使用FormData传递文件是前端上传文件的标准方式,后端可通过$_FILES全局变量获取上传的文件。

内容的提问来源于stack exchange,提问作者Dhruv Pandey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:44:59