PDF文件本地预览解决方案

  • 使用Element UI和PDF.js实现无服务器上传的PDF预览功能


    image.png

    image.png

    image.png

使用的组件

  • npm install element-ui --save
  • vue

如果不自定义,就不用 npm install --save pdfjs-dist

用viewer.html 下载压缩包,在public下解压,页面中使用就可以了


  
<template>
  <div id="app">
    <div class="container">
      <header>
        <h1>PDF文件本地预览解决方案</h1>
        <p>使用Element UI和PDF.js实现无服务器上传的PDF预览功能</p>
      </header>

      <div class="preview-container">
        <div class="card">
          <h2>PDF文件上传</h2>
          <el-upload
            class="upload-area"
            drag
            action="#"
            :auto-upload="false"
            :on-change="handleFileChange"
            :show-file-list="false"
            accept="application/pdf"
          >
            <i class="el-icon-upload upload-icon"></i>
            <div class="el-upload__text">
              将PDF文件拖到此处,或<em>点击上传</em>
            </div>
            <div class="el-upload__tip" slot="tip">
              仅支持PDF文件,且不超过10MB
            </div>
          </el-upload>

          <div v-if="file" class="status-bar">
            <div class="file-info">
              <i class="el-icon-document file-icon"></i>
              <span>{{ file.name }} ({{ formatFileSize(file.size) }})</span>
            </div>
            <el-button type="success" plain @click="previewPDF"
              >预览文件</el-button
            >
          </div>
        </div>

        <div class="card">
          <h2>PDF预览</h2>
          <div v-if="previewUrl" class="preview-frame">
            <iframe
              :src="previewUrl"
              width="100%"
              height="100%"
              frameborder="0"
            ></iframe>
          </div>
          <div v-else class="preview-frame">
            <div
              style="
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100%;
              "
            >
              <div style="text-align: center">
                <i
                  class="el-icon-document"
                  style="font-size: 60px; color: #dcdfe6; margin-bottom: 20px"
                ></i>
                <p style="color: #909399; font-size: 18px">等待上传PDF文件</p>
              </div>
            </div>
          </div>
        </div>
      </div>
<!-- 
      <div class="instructions">
        <h3>技术实现说明</h3>
        <ul>
          <li>使用Element UI的Upload组件实现文件选择功能</li>
          <li>通过URL.createObjectURL生成本地Blob URL实现预览</li>
          <li>采用PDF.js作为PDF渲染引擎,无需服务器支持</li>
          <li>完全本地处理,避免网络请求和代理错误</li>
          <li>支持文件验证(类型、大小)和错误处理</li>
        </ul>
      </div> -->

      <!-- <div class="features">
        <div class="feature-card">
          <i class="el-icon-lock"></i>
          <h3>安全可靠</h3>
          <p>文件仅在浏览器中处理,不会上传到任何服务器</p>
        </div>
        <div class="feature-card">
          <i class="el-icon-thunderbolt"></i>
          <h3>快速预览</h3>
          <p>无需等待上传过程,即时预览PDF文件</p>
        </div>
        <div class="feature-card">
          <i class="el-icon-cpu"></i>
          <h3>零服务器依赖</h3>
          <p>完全在浏览器端实现,无需后端支持</p>
        </div>
      </div> -->

      <footer>
        <p>PDF本地预览解决方案 | 基于Vue.js和Element UI | 无服务器上传</p>
      </footer>
    </div>
  </div>
</template>
  <script>
export default {
  components: {

  },
  data() {
    return {
      file: null,
      previewUrl: null,
      previousBlobUrl: null,
    };
  },
  methods: {
    handleFileChange(file) {
      // 验证文件类型
      if (file.raw.type !== "application/pdf") {
        this.$message.error("请上传PDF格式的文件");
        return;
      }

      // 验证文件大小 (10MB)
      if (file.raw.size > 20 * 1024 * 1024) {
        this.$message.error("文件大小不能超过10MB");
        return;
      }

      // 清理之前的文件引用
      this.revokePreviousBlob();

      this.file = file.raw;
      this.$message.success("文件已选择,点击预览按钮查看");
    },

    previewPDF() {
      if (!this.file) {
        this.$message.warning("请先选择PDF文件");
        return;
      }

      this.revokePreviousBlob();

      try {
        // 生成Blob URL
        const blobUrl = URL.createObjectURL(this.file);
        this.previousBlobUrl = blobUrl;

        // 创建PDF.js预览URL
        const viewerPath = "/pdfjs2/web/viewer.html";
        const encodedUrl = encodeURIComponent(blobUrl);
        this.previewUrl = `${viewerPath}?file=${encodedUrl}`;

        this.$message.success("PDF预览已加载");
      } catch (error) {
        console.error("预览失败:", error);
        this.$message.error("预览失败: " + error.message);
      }
    },

    revokePreviousBlob() {
      if (this.previousBlobUrl) {
        URL.revokeObjectURL(this.previousBlobUrl);
        this.previousBlobUrl = null;
      }
    },

    formatFileSize(bytes) {
      if (bytes === 0) return "0 Bytes";
      const k = 1024;
      const sizes = ["Bytes", "KB", "MB", "GB"];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
    },
  },
  beforeDestroy() {
    this.revokePreviousBlob();
  },
};
</script>



<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: "Helvetica Neue", Arial, sans-serif;
}
body {
  background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
  min-height: 100vh;
  padding: 20px;
}
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 30px;
  background-color: white;
  border-radius: 15px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
header {
  text-align: center;
  margin-bottom: 40px;
}
header h1 {
  color: #2c3e50;
  font-size: 2.5rem;
  margin-bottom: 10px;
}
header p {
  color: #7f8c8d;
  font-size: 1.1rem;
}
.card {
  background: white;
  border-radius: 12px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
  padding: 25px;
  margin-bottom: 30px;
  transition: transform 0.3s ease;
}
.card:hover {
  transform: translateY(-5px);
}
.card h2 {
  color: #3498db;
  margin-bottom: 20px;
  padding-bottom: 10px;
  border-bottom: 2px solid #f0f5ff;
}
.preview-container {
  display: flex;
  flex-direction: column;
  gap: 30px;
}
.upload-area {
  border: 2px dashed #dcdfe6;
  border-radius: 10px;
  padding: 40px 20px;
  text-align: center;
  background-color: #fafafa;
  cursor: pointer;
  transition: all 0.3s;
}
.upload-area:hover {
  border-color: #409eff;
  background-color: #f0f7ff;
}
.upload-icon {
  font-size: 50px;
  color: #409eff;
  margin-bottom: 15px;
}
.preview-frame {
  height: 70vh;
  width: 100%;
  border: 1px solid #ebeef5;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  background-color: #f8f9fa;
}
.instructions {
  background-color: #f8f9fa;
  border-left: 4px solid #3498db;
  padding: 15px;
  margin-top: 20px;
  border-radius: 0 8px 8px 0;
}
.instructions h3 {
  color: #2c3e50;
  margin-bottom: 10px;
}
.instructions ul {
  padding-left: 20px;
  color: #555;
}
.instructions li {
  margin-bottom: 8px;
  line-height: 1.5;
}
footer {
  text-align: center;
  margin-top: 40px;
  color: #7f8c8d;
  font-size: 0.9rem;
}
.status-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #eaf6ff;
  padding: 10px 15px;
  border-radius: 6px;
  margin-top: 15px;
  font-size: 14px;
}
.file-info {
  display: flex;
  align-items: center;
  gap: 10px;
}
.file-icon {
  color: #e74c3c;
  font-size: 20px;
}
.features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin-top: 30px;
}
.feature-card {
  background: #f8f9fa;
  border-radius: 10px;
  padding: 20px;
  text-align: center;
  border: 1px solid #eaeaea;
}
.feature-card i {
  font-size: 36px;
  color: #3498db;
  margin-bottom: 15px;
}
.feature-card h3 {
  margin-bottom: 10px;
  color: #2c3e50;
}


/* 浅色主题覆盖样式 */
::v-ddep :root {
  --primary-color: #f0f0f0;
  --secondary-color: #ffffff;
  --text-color: #333333;
  --border-color: #e0e0e0;
  --hover-color: #e6f7ff;
}

/* PDF.js v1.10.100 浅色主题样式 */

/* 基础样式 */
/* body {
  background-color: #f5f7fa !important;
  color: #333 !important;
  font-family: Arial, sans-serif !important;
} */
::v-deep #toolbarSideba{
    background-color: #ffffff !important;
  border-bottom: 1px solid #e1e4e8 !important;
}
::v-deep #sidebarContainer{
    background-color: #ffffff !important;
  border-bottom: 1px solid #e1e4e8 !important;
}

/* 工具栏 */
#toolbar {
  background-color: #ffffff !important;
  border-bottom: 1px solid #e1e4e8 !important;
}

.toolbarButton {
  color: #586069 !important;
  background-color: transparent !important;
  border: none !important;
}

.toolbarButton:hover {
  background-color: #f1f5f9 !important;
  color: #2d3748 !important;
  border-radius: 4px !important;
}

.toolbarButton[disabled] {
  color: #d1d5da !important;
}

/* 缩放控制 */
#scaleSelectContainer .dropdown {
  background-color: #ffffff !important;
  border: 1px solid #e1e4e8 !important;
  color: #333 !important;
}

/* 侧边栏 */
#sidebar {
  background-color: #ffffff !important;
  border-right: 1px solid #e1e4e8 !important;
}

#sidebarContent {
  background-color: #ffffff !important;
}

/* 缩略图视图 */
#thumbs {
  background-color: #ffffff !important;
}

.thumb {
  border: 1px solid transparent !important;
}

.thumb.selected {
  border-color: #4299e1 !important;
  background-color: #ebf8ff !important;
}

/* 大纲视图 */
#outline {
  background-color: #ffffff !important;
}

.outlineItem {
  color: #333 !important;
}

.outlineItem:hover {
  background-color: #f1f5f9 !important;
}

.outlineItem.active {
  color: #2b6cb0 !important;
  background-color: #ebf8ff !important;
}

/* 文档容器 */
#viewer {
  background-color: #e9ecef !important;
}

.page {
  background-color: #ffffff !important;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) !important;
  margin: 10px auto !important;
}

.page:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
}

/* 搜索面板 */
#findbar {
  background-color: #ffffff !important;
  border-top: 1px solid #e1e4e8 !important;
}

#findInput {
  background-color: #f9fafb !important;
  border: 1px solid #e1e4e8 !important;
  color: #333 !important;
}

.findbarButton {
  color: #586069 !important;
  background-color: transparent !important;
}

.findbarButton:hover {
  background-color: #f1f5f9 !important;
}

/* 上下文菜单 */
.contextMenu {
  background-color: #ffffff !important;
  border: 1px solid #e1e4e8 !important;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1) !important;
}

.contextMenuItem {
  color: #333 !important;
}

.contextMenuItem:hover {
  background-color: #f1f5f9 !important;
  color: #2d3748 !important;
}

/* 加载指示器 */
#loadingBox {
  background-color: rgba(255, 255, 255, 0.9) !important;
}

#loadingIndicator {
  color: #4a5568 !important;
}

/* 错误消息 */
#errorMessage {
  background-color: #fff5f5 !important;
  color: #742a2a !important;
  border: 1px solid #ffe3e3 !important;
}

/* 打印对话框 */
.printDialog {
  background-color: #ffffff !important;
  border: 1px solid #e1e4e8 !important;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
}

.printDialogTitle {
  color: #333 !important;
  border-bottom: 1px solid #e1e4e8 !important;
}

.printDialogButton {
  background-color: #f5f5f5 !important;
  color: #333 !important;
  border: 1px solid #ddd !important;
}

.printDialogButton:hover {
  background-color: #e9e9e9 !important;
}

/* 文本层样式 */
.textLayer {
  color: #1a202c !important;
}

/* 链接样式 */
a {
  color: #2b6cb0 !important;
}

a:hover {
  color: #1a365d !important;
}
    
</style>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容