通过vue-pdf和print-js实现PDF和图片在线预览

发布于:2025-05-24 ⋅ 阅读:(11) ⋅ 点赞:(0)

npm install vue-pdf
npm install print-js

<template>
  <div>
    <!-- PDF 预览模态框 -->
    <a-modal
      :visible="showDialog"
      :footer="null"
      @cancel="handleCancel"
      :width="800"
      :maskClosable="true"
      :keyboard="true"
    >
      <div style="overflow-y: auto; overflow-x: hidden; height: 600px">
        <!-- 使用 sticky 定位打印按钮 -->
        <div style="position: sticky; top: 0; background: white; padding: 0px 0; z-index: 1;">
          <a-button
            shape="round"
            icon="file-pdf"
            @click="handlePrint"
            size="small"
            style="margin-bottom: 10px"
          >打印</a-button
          >
        </div>
        <div id="printFrom">
          <pdf
            v-if="isPdf"
            ref="pdf"
            v-for="item in pageTotal"
            :src="previewFileSrc"
            :key="item"
            :page="item"
          ></pdf>
          <img
            v-else-if="isImage"
            :src="previewImage"
            style="max-width: 100%; max-height: 500px; display: block; margin: 0 auto"
          />
          <div v-else style="text-align: center">
            <p>不支持预览此文件类型</p>
            <a :href="previewFileSrc" download>下载文件</a>
          </div>
        </div>
      </div>
    </a-modal>
  </div>
</template>

<script>
import Vue from "vue";
import { ACCESS_TOKEN } from "@/store/mutation-types";
import { getFileAccessHttpUrl } from "@/api/manage";
import pdf from "vue-pdf";
import printJS from "print-js";

const FILE_TYPE_ALL = "all";
const FILE_TYPE_IMG = "image";
const FILE_TYPE_TXT = "file";

export default {
  name: "AutoFilePreview",
  components: { pdf },
  props: {
    // 是否显示预览
    showDialog: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      fileUrl: null,
      printData: {
        printable: "printFrom",
        header: "",
        ignore: ["no-print"],
      },
      previewImage: "",
      previewFileSrc: "",
      pageTotal: null,
      isImage: false,
      isPdf: false,
    };
  },
  watch: {
    fileUrl: {
      immediate: true,
      handler(newVal) {
        if (newVal) {
          this.previewFileSrc = newVal;
          this.checkFileType();
        }
      },
    },
    showDialog: {
      immediate: true,
      handler(newVal) {
        if (newVal && this.fileUrl) {
          this.previewFileSrc = this.fileUrl;
          this.checkFileType();
        }
      },
    },
  },
  methods: {
    handlePrint() {
      printJS({
        printable: "printFrom",
        type: "html",
        header: "",
        targetStyles: ["*"],
        style: "@page {margin:0 10mm};",
        ignoreElements: ["no-print"],
      });
      this.test();
    },
    async checkFileType() {
      // 重置状态
      this.isImage = false;
      this.isPdf = false;

      // 获取文件类型
      const fileType = this.matchFileType(this.fileUrl);
      if (fileType === "image") {
        this.previewImage = this.fileUrl;
        this.isImage = true;
        this.pageTotal = 1;
      } else if (fileType === "pdf") {
        this.isPdf = true;
        await this.getTotal();
      } else {
        // 其他文件类型直接下载
        this.isImage = false;
        this.isPdf = false;
      }
    },
    async getTotal() {
      try {
        // 多页pdf的src中不能直接使用后端获取的pdf地址
        // 需要使用下述方法的返回值作为url
        const loadingTask = pdf.createLoadingTask(this.previewFileSrc);
        this.previewFileSrc = loadingTask;

        // 获取页码
        const pdfDoc = await loadingTask.promise;
        this.pageTotal = pdfDoc.numPages;
      } catch (error) {
        console.error("PDF加载错误:", error);
        this.$message.error("PDF文件加载失败");
      }
    },
    matchFileType(fileName) {
      // 后缀获取
      let suffix = "";
      // 获取类型结果
      let result = "";

      // 从URL中提取文件名
      const urlParts = fileName.split("/");
      const fullFileName = urlParts[urlParts.length - 1];

      try {
        // 截取文件后缀
        suffix = fullFileName.substr(fullFileName.lastIndexOf(".") + 1, fullFileName.length);
        // 文件后缀转小写,方便匹配
        suffix = suffix.toLowerCase();
      } catch (err) {
        suffix = "";
      }

      // fileName无后缀返回 false
      if (!suffix) {
        result = false;
        return result;
      }

      const fileTypeList = [
        // 图片类型
        { typeName: "image", types: ["png", "jpg", "jpeg", "bmp", "gif"] },
        // 文本类型
        { typeName: "txt", types: ["txt"] },
        // excel类型
        { typeName: "excel", types: ["xls", "xlsx"] },
        { typeName: "word", types: ["doc", "docx"] },
        { typeName: "pdf", types: ["pdf"] },
        { typeName: "ppt", types: ["ppt"] },
        // 视频类型
        { typeName: "video", types: ["mp4", "m2v", "mkv"] },
        // 音频
        { typeName: "radio", types: ["mp3", "wav", "wmv"] },
      ];

      for (let i = 0; i < fileTypeList.length; i++) {
        const fileTypeItem = fileTypeList[i];
        const typeName = fileTypeItem.typeName;
        const types = fileTypeItem.types;

        result = types.some(function(item) {
          return item === suffix;
        });

        if (result) {
          return typeName;
        }
      }

      return "other";
    },
    handleCancel() {
      this.$emit("update:showDialog", false);
    },
    loadFileUrl(url) {
      this.fileUrl = url
    }
  },
};
</script>

<style lang="less" scoped>
/* 可以根据需要添加样式 */
</style>

父组件调用

 <a-button
  :ghost="true"
   type="primary"
   icon="eye"
   size="small"
   @click="showFilePreview(text)">
   预览
 </a-button>
<auto-file-preview ref="autoFilePreview" :show-dialog.sync="showPreview"></auto-file-preview>
showFilePreview(url){
  this.showPreview = true
  this.$refs.autoFilePreview.loadFileUrl(this.getImgView(url))
}

show-dialog是否展示true,false

效果:
在这里插入图片描述
在这里插入图片描述

进阶版,支持word,excel,音频,视频预览,组件使用方式相同

先安装所需依赖:
npm install xlsx mammoth

<template>
  <div>
    <a-modal
      :visible="showDialog"
      :footer="null"
      @cancel="handleCancel"
      :width="800"
      :maskClosable="true"
      :keyboard="true"
    >
      <div style="overflow-y: auto; overflow-x: hidden; height: 600px">
        <div style="position: sticky; top: 0; background: white; padding: 10px 0; z-index: 1;">
          <a-button
            shape="round"
            icon="file-pdf"
            @click="handlePrint"
            size="small"
            style="margin-bottom: 10px"
          >打印</a-button>
        </div>
        <div id="printFrom">
          <pdf
            v-if="isPdf"
            ref="pdf"
            v-for="item in pageTotal"
            :src="previewFileSrc"
            :key="item"
            :page="item"
          ></pdf>
          <img
            v-else-if="isImage"
            :src="previewImage"
            style="max-width: 100%; max-height: 500px; display: block; margin: 0 auto"
          />
          <pre v-else-if="isText" style="white-space: pre-wrap; word-wrap: break-word;">
            {{ textContent }}
          </pre>
          <table v-else-if="isExcel">
            <tr v-for="(row, index) in excelData" :key="index">
              <td v-for="(value, key) in row" :key="key">{{ value }}</td>
            </tr>
          </table>
          <pre v-else-if="isWord" style="white-space: pre-wrap; word-wrap: break-word;">
            {{ wordContent }}
          </pre>
          <video
            v-else-if="isVideo"
            controls
            style="max-width: 100%; display: block; margin: 0 auto"
          >
            <source :src="previewFileSrc" :type="getVideoType(previewFileSrc)" />
            您的浏览器不支持视频标签。
          </video>
          <audio
            v-else-if="isAudio"
            controls
            style="display: block; margin: 0 auto"
          >
            <source :src="previewFileSrc" :type="getAudioType(previewFileSrc)" />
            您的浏览器不支持音频标签。
          </audio>
          <div v-else-if="isUnsupported" style="text-align: center">
            <p>不支持预览此文件类型</p>
            <a :href="previewFileSrc" download>下载文件</a>
          </div>
        </div>
      </div>
    </a-modal>
  </div>
</template>

<script>
import pdf from "vue-pdf";
import printJS from "print-js";
import * as XLSX from "xlsx";
import mammoth from "mammoth";

export default {
  name: "AutoFilePreview",
  components: { pdf },
  props: {
    showDialog: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      fileUrl: null,
      previewImage: "",
      previewFileSrc: "",
      pageTotal: null,
      isImage: false,
      isPdf: false,
      isText: false,
      isExcel: false,
      isWord: false,
      isVideo: false,
      isAudio: false,
      isUnsupported: false,
      textContent: "",
      excelData: [],
      wordContent: "",
    };
  },
  watch: {
    fileUrl: {
      immediate: true,
      handler(newVal) {
        if (newVal) {
          this.previewFileSrc = newVal;
          this.checkFileType();
        }
      },
    },
    showDialog: {
      immediate: true,
      handler(newVal) {
        if (newVal && this.fileUrl) {
          this.previewFileSrc = this.fileUrl;
          this.checkFileType();
        }
      },
    },
  },
  methods: {
    handlePrint() {
      printJS({
        printable: "printFrom",
        type: "html",
        header: "",
        targetStyles: ["*"],
        style: "@page {margin:0 10mm};",
        ignoreElements: ["no-print"],
      });
    },
    async checkFileType() {
      this.isImage = false;
      this.isPdf = false;
      this.isText = false;
      this.isExcel = false;
      this.isWord = false;
      this.isVideo = false;
      this.isAudio = false;
      this.isUnsupported = false;

      const fileType = this.matchFileType(this.fileUrl);

      if (fileType === "image") {
        this.previewImage = this.fileUrl;
        this.isImage = true;
        this.pageTotal = 1;
      } else if (fileType === "pdf") {
        this.isPdf = true;
        await this.getTotal();
      } else if (fileType === "txt") {
        await this.fetchTextFile();
        this.isText = true;
      } else if (fileType === "excel") {
        await this.fetchExcelFile();
        this.isExcel = true;
      } else if (fileType === "word") {
        await this.fetchWordFile();
        this.isWord = true;
      } else if (fileType === "video") {
        this.isVideo = true;
      } else if (fileType === "audio") {
        this.isAudio = true;
      } else {
        this.isUnsupported = true;
      }
    },
    async getTotal() {
      try {
        const loadingTask = pdf.createLoadingTask(this.previewFileSrc);
        const pdfDoc = await loadingTask.promise;
        this.pageTotal = pdfDoc.numPages;
      } catch (error) {
        console.error("PDF加载错误:", error);
        this.$message.error("PDF文件加载失败");
      }
    },
    matchFileType(fileName) {
      let suffix = "";
      try {
        suffix = fileName.split(".").pop().toLowerCase();
      } catch (e) {
        suffix = "";
      }
      const fileTypeList = [
        { typeName: "image", types: ["png", "jpg", "jpeg", "bmp", "gif"] },
        { typeName: "pdf", types: ["pdf"] },
        { typeName: "txt", types: ["txt"] },
        { typeName: "excel", types: ["xls", "xlsx"] },
        { typeName: "word", types: ["doc", "docx"] },
        { typeName: "video", types: ["mp4", "webm", "ogg", "mov", "avi"] },
        { typeName: "audio", types: ["mp3", "wav", "ogg", "aac"] },
      ];

      for (const fileTypeItem of fileTypeList) {
        if (fileTypeItem.types.includes(suffix)) {
          return fileTypeItem.typeName;
        }
      }

      return "other";
    },
    async fetchTextFile() {
      try {
        const response = await fetch(this.fileUrl);
        this.textContent = await response.text();
      } catch (error) {
        console.error("加载文本文件失败:", error);
        this.$message.error("文件加载失败");
      }
    },
    async fetchExcelFile() {
      try {
        const response = await fetch(this.fileUrl);
        const arrayBuffer = await response.arrayBuffer();
        const workbook = XLSX.read(arrayBuffer, { type: "array" });
        const firstSheetName = workbook.SheetNames[0];
        const worksheet = workbook.Sheets[firstSheetName];
        this.excelData = XLSX.utils.sheet_to_json(worksheet);
      } catch (error) {
        console.error("加载 Excel 文件失败:", error);
        this.$message.error("文件加载失败");
      }
    },
    async fetchWordFile() {
      try {
        const response = await fetch(this.fileUrl);
        const arrayBuffer = await response.arrayBuffer();
        const result = await mammoth.extractRawText({ arrayBuffer });
        this.wordContent = result.value;
      } catch (error) {
        console.error("加载 Word 文件失败:", error);
        this.$message.error("文件加载失败");
      }
    },
    getVideoType(url) {
      let suffix = "";
      try {
        suffix = url.split(".").pop().toLowerCase();
      } catch (e) {
        suffix = "";
      }
      const videoTypes = {
        mp4: "video/mp4",
        webm: "video/webm",
        ogg: "video/ogg",
        mov: "video/quicktime",
        avi: "video/x-msvideo",
      };
      return videoTypes[suffix] || "video/mp4";
    },
    getAudioType(url) {
      let suffix = "";
      try {
        suffix = url.split(".").pop().toLowerCase();
      } catch (e) {
        suffix = "";
      }
      const audioTypes = {
        mp3: "audio/mpeg",
        wav: "audio/wav",
        ogg: "audio/ogg",
        aac: "audio/aac",
      };
      return audioTypes[suffix] || "audio/mpeg";
    },
    handleCancel() {
      this.$emit("update:showDialog", false);
    },
    loadFileUrl(url) {
      this.fileUrl = url;
    },
  },
};
</script>

<style lang="less" scoped>
/* 可以根据需要添加样式 */
</style>

网站公告

今日签到

点亮在社区的每一天
去签到