Vue3实现各种附件预览

12,661 阅读2分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

前言

最近开发了一个建筑相关的移动端项目,其中有各种附件预览的功能。通过各种尝试,总结出了一些常用的文件预览方式。本文介绍vue3常用文件(pdf、word、excel、txt)的预览。

一、pdf预览

本文使用的pdf预览是通过 pdfjs-dist 插件,在 Vue3 使用的时候还是有一些坑。

  • 首先通过 npm 安装插件
npm install --save pdfjs-dist
  • 然后在页面中引入
const PDF = require('pdfjs-dist')
  • 启动项目之后就会报错如下:

报错

仔细查看报错信息,是插件中使用了 es11 的语法 ?. 然而当项目启动,插件中的代码没有经过编译,导致项目不能启动。解决方案是在项目中配置webpack 针对于这个插件进行编译。

修改项目根目录下 vue.config.js ,如果没有就创建一个,在文件中增加如下代码:

// vue.config.js
module.exports = {
  // ...
  chainWebpack: config => {
     // ...
     config.module.rule('pdfjs-dist').test({
      test: /\.js$/,
      include: path.join(__dirname, 'node_modules/pdfjs-dist')
    }).use('babel-loader').loader('babel-loader').options({
      presets: ['@babel/preset-env'],
      plugins: ['@babel/plugin-proposal-optional-chaining']
    })
  }
}
  • 之后的错误

image-20210813113307256

需要在引入 pdfjs-dist 之后配置 workerSrc ,但是引入 pdfjs-dist/build/pdf.worker.entry 之后浏览器还是有个警告:Warning: Setting up fake worker. ,经过各种原因查找,最终找到了一句描述:pdf.worker.js必须位于自己的文件中(而不是与pdf.js捆绑在一起)。否则它不能在服务工作线程中运行。

解决方式:将 pdfjs-dist/build/pdf.worker.js 复制一份放到项目 public 目录下。

  • pdf解析组件代码

    通过 pdfjs-dist 加载pdf文件,获取到总页数将 canvas 遍历到页面上,然后编写一个递归函数通过 getPage 解析 pdf 的每一页将pdf内容渲染到 canvas 。以下示例就是封装了一个解析pdf组件,通过传递文件链接全屏渲染pdf。

<template>
  <div class="pdf">
    <template v-for="item in pageNum" :key="item">
      <canvas :id="`pdf-canvas-${item}`" class="pdf-page" />
    </template>
  </div>
</template>
<script>
import { reactive, toRefs, nextTick, watchEffect } from 'vue'
const PDF = require('pdfjs-dist')
PDF.GlobalWorkerOptions.workerSrc = '/pdf.worker.js'
export default {
  name: 'DisplayPdf',
  props: {
    url: {
      type: String,
      default: ''
    }
  },
  setup (props, { emit }) {
    const state = reactive({
      pageNum: 0,
      pdfCtx: null
    })
    watchEffect(() => {
      if (props.url) {
        resolvePdf(props.url)
      }
    })
    const resolvePdf = (url) => {
      const loadingTask = PDF.getDocument(url)
      loadingTask.promise.then(pdf => {
        state.pdfCtx = pdf
        state.pageNum = pdf.numPages
        nextTick(() => {
          renderPdf()
        })
      })
    }
    const renderPdf = (num = 1) => {
      state.pdfCtx.getPage(num).then(page => {
        const canvas = document.getElementById(`pdf-canvas-${num}`)
        const ctx = canvas.getContext('2d')
        const viewport = page.getViewport({ scale: 1 })
        // 画布大小,默认值是width:300px,height:150px
        canvas.height = viewport.height
        canvas.width = viewport.width
        // 画布的dom大小, 设置移动端,宽度设置铺满整个屏幕
        const clientWidth = document.body.clientWidth
        canvas.style.width = clientWidth + 'px'
        // 根据pdf每页的宽高比例设置canvas的高度
        canvas.style.height = clientWidth * (viewport.height / viewport.width) + 'px'
        page.render({
          canvasContext: ctx,
          viewport
        })
        if (num < state.pageNum) {
          renderPdf(num + 1)
        } else {
          emit('onRendered')
        }
      })
    }
    return {
      ...toRefs(state)
    }
  }
}
</script>

二、txt文件预览

txt 的文件预览就比较简单了,因为没有样式,我们直接读取文件的内容,展示到页面即可。

需要注意的是,如果需要正常显示 txt 文件中的换行符需要在文本容器上加上样式:white-space: pre-wrap; ,或者将内容中所有的换行符替换成 <br> 使用 v-html 显示内容。

<template>
  <div class="txt" style="white-space: pre-wrap;">{{ txtContent }}</div>
</template>
<script>
import { ref } from 'vue'
import axios from 'axios'
export default {
  name: 'PreviewTxt',
  setup () {
    const txtContent = ref('')
    const url = '/demo.txt'
    axios.get(url, {
      responseType: 'text'
    }).then(res => {
      txtContent.value = res.data
    })
    return {
      txtContent
    }
  }
}
</script>

三、word预览

本文中使用的是 mammoth.js ,会忽略复杂样式,只是实现了简单内容预览,使用方式也很简单,通过http请求获取文件的 ArrayBuffer ,再使用 mammoth.convertToHtml 将内容转换成 html。如果文件能够外网访问并且机密性不高,还是推荐使用 http://view.officeapps.live.com/op/view.aspx?src=文档url 的方式预览。

// npm install mammoth --save
<template>
  <div class="word-container" v-html="doc" />
</template>
<script>
import { watchEffect, ref } from 'vue'
import axios from 'axios'
import mammoth from 'mammoth'
export default {
  name: 'DisplayDocx',
  props: {
    url: {
      type: String,
      default: ''
    }
  },
  setup (props) {
    const doc = ref('')
    const resolveDocx = async (url) => {
      const { data } = await axios.get(url, { responseType: 'arraybuffer' })
      const res = await mammoth.convertToHtml({ arrayBuffer: data })
      doc.value = res.value
    }
    watchEffect(() => {
      if (props.url) resolveDocx(props.url)
    })
    return { doc }
  }
}
</script>

四、excel预览

excel预览是使用 xsls 插件,读取文件的某个 Sheet 中的内容,然后把内容转换成 json 我们自己用json渲染表格。

npm install xsls --save

读取出json内容如下,默认以第一行为 key。

image-20210813174732460

也可以通过 sheet_to_json 第二个参数改变,如果传入 { header: 1 } 则返回每行为文件中的一行组成数组

image-20210813175209162

示例代码

import xlsx from 'xlsx'
import axios from 'axios'
export default {
  name: 'PreviewExcel',
  async setup () {
    const url = '/demo.xlsx'
    const { data } = await axios.get(url, { responseType: 'arraybuffer' })
    const ctx = xlsx.read(data, { type: 'array' })
    const result = xlsx.utils.sheet_to_json(ctx.Sheets[ctx.SheetNames[0]], { header: 1 })
    console.log(result)
  }
}