likes
comments
collection
share

项目实现|前端预览 docx、pdf、xlsx 文件

作者站长头像
站长
· 阅读数 26

前端预览 docx、pdf、xlsx 文件

作者:Kvon

准备暂时学习一下前端预览文件的功能

预览 docx

预览 docx 遇到的最大的问题是,我本来是想将 docx 文件放到我的本地,然后去读取,但是使用很多方法都没有读取到,初步分析原因是浏览器的安全性考虑吧,文档需要放在服务器端,服务器返回给前端,前端再去处理,所以我取了一个巧,使用的是 el-upload 组件。

插件:npm i docx-preview

这个插件提供了一个 renderAsync 函数,可以将 blob 数据渲染到界面上

上代码:这是公共部分,其中 docx、xlsx 文件都渲染在 docxRef 这个 div 中,pdf 需要借助 canvas,渲染 pdf 部分会介绍。

<template>
  <div>
    <el-upload
      class="upload-demo"
      drag
      ref="upload"
      action="https://jsonplaceholder.typicode.com/posts/"
      :auto-upload="false"
      :on-change="onUploadChange"
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">
        将文件拖到此处,或
        <em>点击上传</em>
      </div>
    </el-upload>
    <div id="luckysheet" class="docxRef" ref="docxRef"></div>
  </div>
</template>

关于 docx 的设置

const optionDocx = {
	className: "docx", // 默认和文档样式类的类名/前缀
	inWrapper: true, // 启用围绕文档内容渲染包装器
	ignoreWidth: false, // 禁止页面渲染宽度
	ignoreHeight: false, // 禁止页面渲染高度
	ignoreFonts: false, // 禁止字体渲染
	breakPages: true, // 在分页符上启用分页
	ignoreLastRenderedPageBreak: true, //禁用lastRenderedPageBreak元素的分页
	experimental: false, //启用实验性功能(制表符停止计算)
	trimXmlDeclaration: true, //如果为真,xml声明将在解析之前从xml文档中删除
	debug: false, // 启用额外的日志记录
};

处理 docx 的文件

    preDocx(file) {
      let blob = new Blob([file], { type: 'text/xml' })
      renderAsync(blob, this.$refs.docxRef, null, optionDocx)
    },

预览的效果:

项目实现|前端预览 docx、pdf、xlsx 文件

这个插件是有一定的缺点的,就是不能处理分页,其实也不是不能处理分页,听我细说

如果你的 word 是通过空格加的分页,那是不能识别的,不过如果你是通过手动分页符设置的分页,那可以识别到分页的。optionDocx 中有配置 breakPages: true, // 在分页符上启用分页

预览 xlsx

预览 xlsx 尝试了 2 种插件

xlsx

npm i xlsx

渲染代码,但是这个代码只是读取了数据,没有保留样式

    preXlsx(file) {
      let blob = new Blob([file], { type: 'text/xml' })
      const reader = new FileReader()
      reader.readAsArrayBuffer(blob)
      reader.onload = (event) => {
        // 读取ArrayBuffer数据变成Uint8Array
        const data = new Uint8Array(event.target.result)
        // 这里的data里面的类型和后面的type类型要对应
        const workbook = xlsx.read(data, { type: 'array' })
        // 工作表名称
        const sheetNames = workbook.SheetNames
        const worksheet = workbook.Sheets[sheetNames[0]]
        const html = xlsx.utils.sheet_to_html(worksheet)
        document.getElementsByClassName('docxRef')[0].innerHTML = html
      }
    },

项目实现|前端预览 docx、pdf、xlsx 文件

如果想要做的好看,还要借助 handsontable 这个插件,懒得去写了,有兴趣的可以了解一下

luckysheet

luckysheet 是第二种方式

npm i luckysheet

npm i luckyexcel

渲染代码:

    preLuckySheet(file) {
      LuckyExcel.transformExcelToLucky(
        file,
        function (exportJson, luckysheetfile) {
          // 获得转化后的表格数据后,使用luckysheet初始化,或者更新已有的luckysheet工作簿
          // 注:luckysheet需要引入依赖包和初始化表格容器才可以使用
          console.log(exportJson)
          luckysheet.create({
            container: 'luckysheet', // luckysheet is the container id
            data: exportJson.sheets,
            title: exportJson.info.name,
            userInfo: exportJson.info.name.creator,
            lang: 'zh', // 设定表格语言
            showinfobar: false, //是否显示顶部信息栏
            showtoolbar: false, //是否显示顶部工具栏
            sheetFormulaBar: false //是否显示公式栏
          })
          // this.createExcel()
        },
        function (err) {
          console.error('Import failed. Is your fail a valid xlsx?')
        }
      )
    },

此处需要注意的是,需要先把luckysheet 的源码下载到本地,然后使用npm run build打包一下,再把 dist 包中文件,复制到当前项目的 public 目录下。

项目实现|前端预览 docx、pdf、xlsx 文件

再把打包后的文件引入到 public 下的 index.html 中,才能完成 luckysheet 的使用

      <link rel="stylesheet" href="./luckysheet/plugins/css/pluginsCss.css" />
      <link rel="stylesheet" href="./luckysheet/plugins/plugins.css" />
      <link rel="stylesheet" href="./luckysheet/css/luckysheet.css" />
      <link rel="stylesheet" href="./luckysheet/assets/iconfont/iconfont.css" />
      <script src="./luckysheet/plugins/js/plugin.js"></script>
      <script src="./luckysheet/luckysheet.umd.js"></script>

项目实现|前端预览 docx、pdf、xlsx 文件

预览 pdf

插件

npm i pdfjs-dist@2.0.402

这个插件安装对 node 版本是有要求的,如果没有指定版本,安装需要 node>=18 我安装的这个 2.x 对应的版本是 14.x

渲染代码: 其中,pdf 是通过 canvas 渲染出来的,所以公共代码部分,有改动,在上面也提到了,详见下面的改动,canvas对应pdf的总页数

    <div id="luckysheet" class="docxRef" ref="docxRef">
      <canvas
        :id="'canvas' + pageIndex"
        v-for="pageIndex in pdfPages"
        :key="pageIndex"
      ></canvas>
    </div>
    prePDF(file) {
      const reader = new FileReader()
      reader.readAsArrayBuffer(file)
      // onload该事件在读取操作完成时触发
      reader.onload = () => {
        console.log('pdf文件', reader.result)
        pdfjs
          .getDocument({ data: new Uint8Array(reader.result) })
          .then((pdfDoc) => {
            this.pdfFile = pdfDoc
            this.pdfPages = pdfDoc.numPages
            // 渲染是需要时间的,要等待渲染结束
            this.timer = setTimeout(() => {
              this.renderPage(pdfDoc.numPages)
            }, 100)
          })
      }
    },
    renderPage(nums) {
      for (let item = 1; item <= nums; item++) {
        // 获取页面canvas节点
        let canvas = document.getElementById(`canvas${item}`)
        // 获取上下文
        const ctx = canvas.getContext('2d')
        // 获取每一页的内容
        this.pdfFile.getPage(item).then((page) => {
          // 文件页面的视图 1倍
          const viewport = page.getViewport(1.0)
          // pdf不清晰有一个重要的原因是默认dpi为96,然后网页一般使用dpi为72。
          var CSS_UNITS = 96.0 / 72.0
          canvas.height = Math.floor(viewport.height * CSS_UNITS) // 放大了
          canvas.width = Math.floor(viewport.width * CSS_UNITS) // 放大了
          const renderContext = {
            transform: [CSS_UNITS, 0, 0, CSS_UNITS, 0, 0],
            canvasContext: ctx,
            viewport: viewport
          }
          // 渲染页面内容:参数是canvas画布上下文,以及文件视图
          page.render(renderContext)
        })
      }
    }

项目实现|前端预览 docx、pdf、xlsx 文件

pdf这里遇到了清晰度不够的问题,去查了一下是dpi导致的,所以做了一点优化

完整代码

<!--
 * @Author: Kvon
 * @Date: 2023-10-09 15:55:30
 * @Description: 
-->
<template>
  <div>
    <el-upload
      class="upload-demo"
      drag
      ref="upload"
      action="https://jsonplaceholder.typicode.com/posts/"
      :auto-upload="false"
      :on-change="onUploadChange"
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">
        将文件拖到此处,或
        <em>点击上传</em>
      </div>
    </el-upload>
    <div id="luckysheet" class="docxRef" ref="docxRef">
      <canvas
        :id="'canvas' + pageIndex"
        v-for="pageIndex in pdfPages"
        :key="pageIndex"
      ></canvas>
    </div>
  </div>
</template>

<script>
import { renderAsync } from 'docx-preview'
import xlsx from 'xlsx'
import LuckyExcel from 'luckyexcel'
import * as pdfjs from 'pdfjs-dist'
import workerSrc from 'pdfjs-dist/build/pdf.worker.entry'
pdfjs.GlobalWorkerOptions.workerSrc = workerSrc
const optionDocx = {
  className: 'docx', // 默认和文档样式类的类名/前缀
  inWrapper: true, // 启用围绕文档内容渲染包装器
  ignoreWidth: false, // 禁止页面渲染宽度
  ignoreHeight: false, // 禁止页面渲染高度
  ignoreFonts: false, // 禁止字体渲染
  breakPages: true, // 在分页符上启用分页
  ignoreLastRenderedPageBreak: true, //禁用lastRenderedPageBreak元素的分页
  experimental: false, //启用实验性功能(制表符停止计算)
  trimXmlDeclaration: true, //如果为真,xml声明将在解析之前从xml文档中删除
  debug: false // 启用额外的日志记录
}
export default {
  name: 'Preview',
  data() {
    return {
      pdfFile: null,
      pdfPages: 0,
      timer: null
    }
  },
  methods: {
    onUploadChange(file) {
      const _file = file.raw
      let _type = _file.name.split('.')[1]
      if (_type === 'docx') {
        this.preDocx(_file)
      } else if (_type === 'xlsx') {
        // 使用xlsx预览
        // this.preXlsx(_file)
        // 使用luckyexcel+luckysheet进行预览
        this.preLuckySheet(_file)
      } else if (_type === 'pdf') {
        this.prePDF(_file)
      } else {
        console.error('Import failed. Is your fail a valid file?')
      }
    },
    /**
     * @Author: Kvon
     * @Date: 2023-10-11 10:51:51
     * @Description: 预览docx
     * @param {*} file
     */
    preDocx(file) {
      let blob = new Blob([file], { type: 'text/xml' })
      renderAsync(blob, this.$refs.docxRef, null, optionDocx)
    },
    /**
     * @Author: Kvon
     * @Date: 2023-10-11 10:51:23
     * @Description: xlsx预览xlsx文件
     * @param {*} file
     */
    preXlsx(file) {
      let blob = new Blob([file], { type: 'text/xml' })
      const reader = new FileReader()
      reader.readAsArrayBuffer(blob)
      reader.onload = (event) => {
        // 读取ArrayBuffer数据变成Uint8Array
        const data = new Uint8Array(event.target.result)
        // 这里的data里面的类型和后面的type类型要对应
        const workbook = xlsx.read(data, { type: 'array' })
        // 工作表名称
        const sheetNames = workbook.SheetNames
        const worksheet = workbook.Sheets[sheetNames[0]]
        const html = xlsx.utils.sheet_to_html(worksheet)
        document.getElementsByClassName('docxRef')[0].innerHTML = html
      }
    },
    /**
     * @Author: Kvon
     * @Date: 2023-10-11 10:50:46
     * @Description: LuckySheet预览xlsx文件
     * @param {*} file
     */
    preLuckySheet(file) {
      LuckyExcel.transformExcelToLucky(
        file,
        function (exportJson, luckysheetfile) {
          // 获得转化后的表格数据后,使用luckysheet初始化,或者更新已有的luckysheet工作簿
          // 注:luckysheet需要引入依赖包和初始化表格容器才可以使用
          console.log(exportJson)
          luckysheet.create({
            container: 'luckysheet', // luckysheet is the container id
            data: exportJson.sheets,
            title: exportJson.info.name,
            userInfo: exportJson.info.name.creator,
            lang: 'zh', // 设定表格语言
            showinfobar: false, //是否显示顶部信息栏
            showtoolbar: false, //是否显示顶部工具栏
            sheetFormulaBar: false //是否显示公式栏
          })
          // this.createExcel()
        },
        function (err) {
          console.error('Import failed. Is your fail a valid xlsx?')
        }
      )
    },
    /**
     * @Author: Kvon
     * @Date: 2023-10-11 10:50:12
     * @Description: 处理pdf多页情况
     * @param {*} file
     */
    prePDF(file) {
      const reader = new FileReader()
      reader.readAsArrayBuffer(file)
      // onload该事件在读取操作完成时触发
      reader.onload = () => {
        console.log('pdf文件', reader.result)
        pdfjs
          .getDocument({ data: new Uint8Array(reader.result) })
          .then((pdfDoc) => {
            this.pdfFile = pdfDoc
            this.pdfPages = pdfDoc.numPages
            // 渲染是需要时间的,要等待渲染结束
            this.timer = setTimeout(() => {
              this.renderPage(pdfDoc.numPages)
            }, 100)
          })
      }
    },
    renderPage(nums) {
      for (let item = 1; item <= nums; item++) {
        // 获取页面canvas节点
        let canvas = document.getElementById(`canvas${item}`)
        // 获取上下文
        const ctx = canvas.getContext('2d')
        // 获取每一页的内容
        this.pdfFile.getPage(item).then((page) => {
          // 文件页面的视图 1倍
          const viewport = page.getViewport(1.0)
          // pdf不清晰有一个重要的原因是默认dpi为96,然后网页一般使用dpi为72。
          var CSS_UNITS = 96.0 / 72.0
          canvas.height = Math.floor(viewport.height * CSS_UNITS) // 放大了
          canvas.width = Math.floor(viewport.width * CSS_UNITS) // 放大了
          const renderContext = {
            transform: [CSS_UNITS, 0, 0, CSS_UNITS, 0, 0],
            canvasContext: ctx,
            viewport: viewport
          }
          // 渲染页面内容:参数是canvas画布上下文,以及文件视图
          page.render(renderContext)
        })
      }
    }
  },
  destroyed() {
    console.log('清除定时器')
    clearTimeout(this.timer)
    this.timer = null
  }
}
</script>

<style>
.docxRef {
  width: 100%;
  height: 500px;
}
</style>

参考

@vue-office/excel

【Vue-Element】el-upload将文件转为Blob类型

vue中使用docx-preview插件预览word文档(后端express)

vue2使用pdfjs-dist批量在页面渲染pdf

pdf.js图像不清晰的问题