Vue2和 vue3 doc、excel、pdf、ppt、txt、图片以及视频等在线预览

Vue2

安装

npm install --save @vue-office/docx @vue-office/excel @vue-office/pdf [email protected]

[email protected]; 其中 @0.14.6 为版本号,可以不加,默认下载最新版。

如果是 vue2.6 版本或以下还需要额外安装 @vue/composition-api

npm install @vue/composition-api

使用

1 doc

<template>
  <div>
    <vue-office-docx
      :src="docx"
      style="height: 75vh"
      @rendered="rendered"
      @error="errorHandler"
    />
  </div>
</template>

<script>
//引入VueOfficeDocx组件
import VueOfficeDocx from "@vue-office/docx";
//引入相关样式
import "@vue-office/docx/lib/index.css";

export default {
  components: {
    VueOfficeDocx,
  },
  props: {
    docx: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/Hadoop2.7.1%E4%BC%AA%E5%88%86%E5%B8%83%E5%BC%8F%E9%9B%86%E7%BE%A4%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3.docx", //设置文档网络地址,可以是相对地址
    },
  },
  data() {
    return {};
  },
  methods: {
    rendered() {
      console.log("渲染完成");
    },
    errorHandler() {
      console.log("渲染失败");
    },
  },
};
</script>

2 excel

<template>
  <vue-office-excel
    :src="excel"
    :options="options"
    @rendered="renderedHandler"
    @error="errorHandler"
    style="height: 75vh"
  />
</template>

<script>
//引入VueOfficeExcel组件
import VueOfficeExcel from "@vue-office/excel";
//引入相关样式
import "@vue-office/excel/lib/index.css";

export default {
  components: {
    VueOfficeExcel,
  },
  props: {
    excel: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/2021%E5%B1%8A%E5%85%A8%E5%9B%BD%E5%90%84%E5%9C%B0%E6%B4%BE%E9%81%A3%E5%9C%B0%E5%9D%80.xlsx", //设置文档地址
    },
  },
  data() {
    return {
      options: {
        xls: true, //预览xlsx文件设置为false 预览xls文件设置为true
        minColLength: 0,
        minRowLength: 0,
        widthOffset: 10,
        heightOffset: 10,
      },
    };
  },
  methods: {
    renderedHandler() {
      console.log("渲染完成");
    },
    errorHandler() {
      console.log("渲染失败");
    },
  },
};
</script>

3 img

<template>
  <div style="display: flex; justify-content: center">
    <el-image :src="imgUrl" :preview-src-list="[imgUrl]"> fit="contain"></el-image>
  </div>
</template>

<script>
export default {
  props: {
    imgUrl: {
      type: String,
      default: "",
    },
  },
  components: {},
  data() {
    return {};
  },
  methods: {
    closeImage() {},
  },
};
</script>

4 pdf

<template>
  <vue-office-pdf :src="pdf" style="height: 75vh" @rendered="rendered" />
</template>

<script>
//引入VueOfficePdf组件
import VueOfficePdf from "@vue-office/pdf";

export default {
  components: {
    VueOfficePdf,
  },
  props: {
    pdf: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/%E6%B7%B1%E5%9C%B3-xx-Java%E9%AB%98%E7%BA%A7.pdf",
    },
  },
  data() {
    return {};
  },
  methods: {
    rendered() {
      console.log("渲染完成");
    },
  },
};
</script>

5 ppt

<template>
  <iframe
    id="iframe1"
    width="100%"
    height="700px"
    frameborder="0"
    border="0"
    marginwidth="0"
    marginheight="0"
    scrolling="no"
    allowtransparency="no"
    :src="'https://view.officeapps.live.com/op/embed.aspx?src=' + fileUrl"
  ></iframe>
</template>

<script>
export default {
  props: {
    fileUrl: {
      type: String,
      default: "",
    },
  },
  components: {},
  data() {
    return {};
  },
  methods: {},
};
</script>

6 txt

<template>
  <div>
    <pre class="pre-txt">{
   
   { txtContent }}</pre>
  </div>
</template>

<script>
import axios from "axios";
export default {
  components: {},
  props: {
    fileUrl: {
      type: String,
      default: "https://example.com/your-txt-file.txt",
    },
  },
  data() {
    return {
      txtContent: "",
    };
  },
  mounted() {
    this.fetchTxtFile();
  },
  methods: {
    fetchTxtFile() {
      axios
        .get(this.fileUrl)
        .then((response) => {
          this.txtContent = response.data;
        })
        .catch((error) => {
          console.error("获取txt文件失败:", error);
        });
    },
  },
};
</script>
<style scoped>
.pre-txt {
  font-size: 12px;
  padding: 0;
  width: 100%;
  max-height: 70vh;
  min-height: 70vh;
  margin: 0;
  background: #f0f0f0;
  line-height: 20px; /* 行距 */
  overflow: auto; /* 超出宽度出现滑动条 */
  overflow-y: auto; /* 作用是隐藏IE的右侧滑动条 */
}
</style>

7 video

<template>
  <div
    class="m-video"
    :class="{ 'u-video-hover': !hidden }"
    :style="`width: ${veoWidth}; height: ${veoHeight};`"
  >
    <video
      ref="veoRef"
      class="u-video"
      :style="`object-fit: ${zoom};`"
      :src="src"
      :poster="veoPoster"
      :autoplay="autoplay"
      :controls="!originPlay && controls"
      :loop="loop"
      :muted="autoplay || muted"
      :preload="preload"
      crossorigin="anonymous"
      @loadeddata="poster ? () => false : getPoster()"
      @pause="showPlay ? onPause() : () => false"
      @playing="showPlay ? onPlaying() : () => false"
      @click.prevent.once="onPlay"
      v-bind="$attrs"
    >
      您的浏览器不支持video标签。
    </video>
    <svg
      v-show="originPlay || showPlay"
      class="u-play"
      :class="{ hidden: hidden }"
      :style="`width: ${playWidth}px; height: ${playWidth}px;`"
      viewBox="0 0 24 24"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        stroke-width="1.5"
        d="M4.75 6.75C4.75 5.64543 5.64543 4.75 6.75 4.75H17.25C18.3546 4.75 19.25 5.64543 19.25 6.75V17.25C19.25 18.3546 18.3546 19.25 17.25 19.25H6.75C5.64543 19.25 4.75 18.3546 4.75 17.25V6.75Z"
      ></path>
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        stroke-width="1.5"
        d="M15.25 12L9.75 8.75V15.25L15.25 12Z"
      ></path>
    </svg>
  </div>
</template>
<script>
export default {
  name: "Video",
  props: {
    src: {
      // 视频文件url,必传,支持网络地址 https 和相对地址 require('@/assets/files/Bao.mp4')
      type: String,
      required: true,
      default: "",
    },
    poster: {
      // 视频封面url,支持网络地址 https 和相对地址 require('@/assets/images/Bao.jpg')
      type: String,
      default: "",
    },
    second: {
      // 在未设置封面时,自动截取视频第 second 秒对应帧作为视频封面
      type: Number,
      default: 0.5,
    },
    width: {
      // 视频播放器宽度,单位 px
      type: [String, Number],
      default: 800,
    },
    height: {
      // 视频播放器高度,单位 px
      type: [String, Number],
      default: 450,
    },
    /*
      参考 MDN 自动播放指南:https://developer.mozilla.org/zh-CN/docs/Web/Media/Autoplay_guide
      Autoplay 功能
      据新政策,媒体内容将在满足以下至少一个的条件下自动播放:
      1.音频被静音或其音量设置为 0
      2.用户和网页已有交互行为(包括点击、触摸、按下某个键等等)
      3.网站已被列入白名单;如果浏览器确定用户经常与媒体互动,这可能会自动发生,也可能通过首选项或其他用户界面功能手动发生
      4.自动播放策略应用到<iframe>或者其文档上
      autoplay:由于目前在最新版的Chrome浏览器(以及所有以Chromium为内核的浏览器)中,
      已不再允许自动播放音频和视频。就算你为video或audio标签设置了autoplay属性也一样不能自动播放!
      解决方法:设置视频 autoplay 时,视频必须设置为静音 muted: true 即可实现自动播放,
      然后用户可以使用控制栏开启声音,类似某宝商品自动播放的宣传视频逻辑
    */
    autoplay: {
      // 视频就绪后是否马上播放,优先级高于 preload
      type: Boolean,
      default: false,
    },
    controls: {
      // 是否向用户显示控件,比如进度条,全屏等
      type: Boolean,
      default: true,
    },
    loop: {
      // 视频播放完成后,是否循环播放
      type: Boolean,
      default: false,
    },
    muted: {
      // 是否静音
      type: Boolean,
      default: false,
    },
    preload: {
      // 是否在页面加载后载入视频,如果设置了autoplay属性,则preload将被忽略;
      type: String,
      default: "metadata", // auto:一旦页面加载,则开始加载视频; metadata:当页面加载后仅加载视频的元数据 none:页面加载后不应加载视频
    },
    showPlay: {
      // 播放暂停时是否显示播放器中间的暂停图标
      type: Boolean,
      default: true,
    },
    playWidth: {
      // 中间播放暂停按钮的边长
      type: Number,
      default: 96,
    },
    zoom: {
      // video的poster默认图片和视频内容缩放规则
      type: String,
      default: "contain", // none:(默认)保存原有内容,不进行缩放; fill:不保持原有比例,内容拉伸填充整个内容容器; contain:保存原有比例,内容以包含方式缩放; cover:保存原有比例,内容以覆盖方式缩放
    },
  },
  data() {
    return {
      veoPoster: this.poster,
      originPlay: true,
      hidden: false,
    };
  },
  computed: {
    veoWidth() {
      return '100%';
    },
    veoHeight() {
      if (typeof this.height === "number") {
        return this.height + "px";
      }
      return this.height;
    },
  },
  mounted() {
    if (this.autoplay) {
      this.hidden = true;
      this.originPlay = false;
    }
    /*
      自定义设置播放速度,经测试:
      在vue2中需设置:this.$refs.veoRef.playbackRate = 2
      在vue3中需设置:veo.value.defaultPlaybackRate = 2
    */
    // this.$refs.veoRef.playbackRate = 2
  },
  methods: {
    /*
      loadedmetadata 事件在元数据(metadata)被加载完成后触发
      loadeddata 事件在媒体当前播放位置的视频帧(通常是第一帧)加载完成后触发
        若在移动/平板设备的浏览器设置中开启了流量节省(data-saver)模式,该事件则不会被触发。
      preload 为 none 时不会触发
    */
    getPoster() {
      // 在未设置封面时,自动获取视频0.5s对应帧作为视频封面
      // 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图
      this.$refs.veoRef.currentTime = this.second;
      // 创建canvas元素
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      // canvas画图
      canvas.width = this.$refs.veoRef.videoWidth;
      canvas.height = this.$refs.veoRef.videoHeight;
      ctx.drawImage(this.$refs.veoRef, 0, 0, canvas.width, canvas.height);
      // 把canvas转成base64编码格式
      this.veoPoster = canvas.toDataURL("image/png");
    },
    onPlay() {
      if (this.originPlay) {
        this.$refs.veoRef.currentTime = 0;
        this.originPlay = false;
      }
      if (this.autoplay) {
        this.$refs.veoRef.pause();
      } else {
        this.hidden = true;
        this.$refs.veoRef.play();
      }
    },
    onPause() {
      this.hidden = false;
    },
    onPlaying() {
      this.hidden = true;
    },
  },
};
</script>
<style scoped>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
.m-video {
  display: inline-block;
  position: relative;
  background: #000;
  cursor: pointer;
}

.u-play {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  fill: none;
  color: #fff;
  pointer-events: none;
  opacity: 0.7;
  transition: opacity 0.3s;
}
.hidden {
  opacity: 0;
}
.u-video {
  display: inline-block;
  width: 100%;
  height: 100%;
  vertical-align: bottom;
}
.u-video-hover {
}
</style>

主页面

<template>
  <el-dialog
    title="文件预览"
    :visible.sync="open"
    width="50vw"
    :before-close="handleClose"
    :close-on-click-modal="true"
  >
    <DOC :docx="url" v-if="componentToUse === 'DOC'"></DOC>
    <PDF :pdf="url" v-if="componentToUse === 'PDF'"></PDF>
    <EXCEL :excel="url" v-if="componentToUse === 'EXCEL'"></EXCEL>
    <TXT :fileUrl="url" v-if="componentToUse === 'TXT'"></TXT>
    <IMG :imgUrl="url" v-if="componentToUse === 'IMG'"></IMG>
    <PPT :fileUrl="url" v-if="componentToUse === 'PPT'"></PPT>
    <!-- <span slot="footer" class="dialog-footer">
      <el-button @click="handleClose" size="small">关 闭</el-button>
    </span> -->
    <div v-if="componentToUse === 'WZ'">不支持的文件类型</div>
  </el-dialog>
</template>

<script>
import DOC from "./doc/index.vue";
import PDF from "./pdf/index.vue";
import EXCEL from "./excel/index.vue";
import TXT from "./txt/index.vue";
import IMG from "./img/index.vue";
import PPT from "./ppt/index.vue";

export default {
  components: {
    DOC,
    PDF,
    EXCEL,
    TXT,
    IMG,
    PPT,
  },
  props: {
    open: {
      type: Boolean,
      default: false,
    },
    url: {
      type: String,
      default:
        "http://qncdn.qkongtao.cn/lib/teamadmin/files/Hadoop2.7.1%E4%BC%AA%E5%88%86%E5%B8%83%E5%BC%8F%E9%9B%86%E7%BE%A4%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3.docx", //设置文档网络地址,可以是相对地址
    },
  },
  data() {
    return {
      componentToUse: null, // 用于存储要使用的组件
    };
  },
  watch: {
    url() {
      // 当 url 改变时,重新判断文件类型并设置组件
      this.determineComponentType();
    },
  },
  created() {
    // 在组件创建时判断文件类型并设置组件
    this.determineComponentType();
  },
  methods: {
    rendered() {
      console.log("渲染完成");
    },
    handleClose() {
      this.$emit("update:open", false);
    },
    determineComponentType() {
      const fileExtension = this.url.split(".").pop().toLowerCase();
      switch (fileExtension) {
        case "docx":
          this.componentToUse = "DOC";
          break;
        case "pdf":
          this.componentToUse = "PDF";
          break;
        case "xlsx":
        case "xls":
          this.componentToUse = "EXCEL";
          break;
        case "txt":
        case "html":
        case "vue":
        case "js":
        case "json":
          this.componentToUse = "TXT";
          break;
        case "png":
        case "jpg":
        case "jpeg":
          this.componentToUse = "IMG";
          break;
        case "ppt":
        case "pptx":
          this.componentToUse = "PPT";
          break;
        default:
          console.error("不支持的文件类型");
          this.componentToUse = "WZ";
      }
    },
  },
};
</script>

应用实例

建议在main.js中增加全局组件引用

import FileView from '@/components/FileView'
Vue.component('FileView',FileView)
我的使用场景
<FileView :open.sync="fileViewOpen" :url="fileUrl" v-if="fileViewOpen" />

vue3

Vue3实现文件预览word、pdf、excel、图片以及txt文件
由于工作需要,需要对上传的各种不同文件类型进行预览
使用的插件是vue-office 插件地址: vue-office

vue-office简介
vue-office是一个支持多种文件(docx、.xlsx、pdf)预览的vue组件库,支持vue2和vue3。
目标是成为使用最简单,功能最强大的文件预览库

// 安装演示   vue-demi(此包是针对vue2、vue3项目兼容转换配合使用的)
#docx文档预览组件
npm install @vue-office/docx vue-demi

#excel文档预览组件
npm install @vue-office/excel vue-demi

#pdf文档预览组件
npm install @vue-office/pdf vue-demi
如果是vue2.6版本或以下还需要额外安装 @vue/composition-api
npm install @vue/composition-api

word预览

docx、xlsx、pdf三种文件的预览方式几乎一致,我们先以docx文档的预览为例,介绍下组件用法。
docx的预览如下: 此处进行封装组件,父组件传参方便使用
<template>
    <vue-office-docx
        :src="docx"
        style="height: 100vh;"
        @rendered="renderedHandler"
        @error="errorHandler"
    />
</template>

<script setup>
  import {  ref } from "vue";
//引入VueOfficeDocx组件
import VueOfficeDocx from '@vue-office/docx'
//引入相关样式
import '@vue-office/docx/lib/index.css'

const docx=ref("http://static.shanhuxueyuan.com/test6.docx") //设置文档网络地址,可以是相对地址
// docx作为参数通过父组件传参
const renderedHandler=()=>{
  console.log("渲染完成")
}
const errorHandler=()=>{
  console.log("渲染失败")
}

excel预览

excel以及pdf同理,以下代码演示进行简化
//excel
<template>
    <vue-office-excel
        :src="excel"
        style="height: 100vh;"
    />
</template>

<script setup>
//引入VueOfficeExcel组件
import VueOfficeExcel from '@vue-office/excel'
//引入相关样式
import '@vue-office/excel/lib/index.css'
const excel=ref("http://static.shanhuxueyuan.com/demo/excel.xlsx")
</script>

pdf预览

//pdf
<vue-office-pdf 
        :src="pdf"
    />

<script setup>
//引入VueOfficePdf组件
import VueOfficePdf from '@vue-office/pdf'
const pdf=ref("http://static.shanhuxueyuan.com/test.pdf")
</script>

vue 使用 js-preview 方法完成预览

以上的安装的包不需要删除,还以word文件预览进行代码演示
安装依赖库 npm i @js-preview/docx
以上的安装的包不需要删除,还以word文件预览进行代码演示
安装依赖库 npm i @js-preview/docx

 word预览

<template>
  <div id="docx"></div>
</template> 

<script setup>
import {  ref,onMounted } from "vue";
import jsPreviewDocx from "@js-preview/docx";
import "@js-preview/docx/lib/index.css";

const docx = ref("http://static.shanhuxueyuan.com/test6.docx");
onMounted(() => { // setup下先再获取到dom节点之后再进行挂载
  const myDocxPreviewer = jsPreviewDocx.init(document.getElementById("docx"));
  //传递要预览的文件地址即可
  myDocxPreviewer
    .preview(
      docx.value
    )
    .then((res) => {
       console.log("预览完成");
    })
    .catch((e) => {
      console.log("预览失败", e);
    });
});

</script>

 excel预览

excel文件预览
npm i @js-preview/excel

<template>
  <div id="excel"></div>
</template> 

<script setup>
import {  ref,onMounted } from "vue";
import jsPreviewExcel from "@js-preview/excel";
import '@js-preview/excel/lib/index.css';

const excel = ref("http://static.shanhuxueyuan.com/demo/excel.xlsx");
onMounted(() => { // setup下先再获取到dom节点之后再进行挂载
  const myExcelPreviewer = jsPreviewExcel.init(document.getElementById('excel'));
myExcelPreviewer.preview(excel.value).then(res=>{
    console.log('预览完成');
}).catch(e=>{
    console.log('预览失败', e);
    });
});

pdf预览

pdf文件预览
npm i @js-preview/pdf

<template>
  <div id="pdf"></div>
</template> 

<script setup>
import {  ref,onMounted } from "vue";
import jsPreviewPdf from "@js-preview/pdf";

const pdf = ref("http://static.shanhuxueyuan.com/test.pdf");
onMounted(() => { // setup下先再获取到dom节点之后再进行挂载
  const myPdfPreviewer = jsPreviewPdf.init(document.getElementById('pdf'));
myPdfPreviewer.preview(pdf.value).then(res=>{
    console.log('预览完成');
}).catch(e=>{
    console.log('预览失败', e);
    });
});

 txt预览

txt文件的预览前端可以直接针对文件进行解读
安装axios包 npm i @axios
<template>
  <div class="txt" style="white-space: pre-wrap">
    {
   
   { textContent }}
  </div>
</template> 

<script setup>


import axios from "axios"; 
import { ref,onMounted } from "vue";

const textContent = ref("");

const transformData = (data) => {
      return new Promise((resolve) => {
        let reader = new FileReader();
        reader.readAsText(data, "GBK");//这里如果出现乱码可改成reader.readAsText(data, "")试试
        reader.onload = () => {
          resolve(reader.result);
        };
      });
    };
    axios
      .get(sourcePdf.value, {
        responseType: "blob",
        transformResponse: [
          async function (data) {
            return await transformData(data);
          },
        ],
      })
      .then((res) => {
        res.data.then((data) => {
          textContent.value = data;// 编译好的txt重新赋值给textContent
        });
      });

图片预览

图片预览直接img标签就够用了,后端返回数据流或者接口地址可以直接进行访问的
<img style="width: 300px" :src="url" alt="图片加载失败" />

url为图片的动态地址/数据流,这里比较简单自己可以找合适的方法进行处理