前端预览 docx、pdf、xlsx 文件
作者:Kvon
准备暂时学习一下前端预览文件的功能
预览 docx
预览 docx 遇到的最大的问题是,我本来是想将 docx 文件放到我的本地,然后去读取,但是使用很多方法都没有读取到,初步分析原因是浏览器的安全性考虑吧,文档需要放在服务器端,服务器返回给前端,前端再去处理,所以我取了一个巧,使用的是 el-upload 组件。
插件:npm i docx-preview
这个插件提供了一个 renderAsync 函数,可以将 blob 数据渲染到界面上
上代码:这是公共部分,其中 docx、xlsx 文件都渲染在 docxRef 这个 div 中,pdf 需要借助 canvas,渲染 pdf 部分会介绍。
js
<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 的设置
js
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 的文件
js
preDocx(file) {
let blob = new Blob([file], { type: 'text/xml' })
renderAsync(blob, this.$refs.docxRef, null, optionDocx)
},
预览的效果:
这个插件是有一定的缺点的,就是不能处理分页,其实也不是不能处理分页,听我细说
如果你的 word 是通过空格加的分页,那是不能识别的,不过如果你是通过手动分页符设置的分页,那可以识别到分页的。optionDocx 中有配置 breakPages: true, // 在分页符上启用分页
预览 xlsx
预览 xlsx 尝试了 2 种插件
xlsx
npm i xlsx
渲染代码,但是这个代码只是读取了数据,没有保留样式
js
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
}
},
如果想要做的好看,还要借助 handsontable 这个插件,懒得去写了,有兴趣的可以了解一下
luckysheet
luckysheet 是第二种方式
npm i luckysheet
npm i luckyexcel
渲染代码:
js
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 目录下。
再把打包后的文件引入到 public 下的 index.html 中,才能完成 luckysheet 的使用
js
<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>
预览 pdf
插件
npm i pdfjs-dist@2.0.402
这个插件安装对 node 版本是有要求的,如果没有指定版本,安装需要 node>=18 我安装的这个 2.x 对应的版本是 14.x
渲染代码: 其中,pdf 是通过 canvas 渲染出来的,所以公共代码部分,有改动,在上面也提到了,详见下面的改动,canvas对应pdf的总页数
js
<div id="luckysheet" class="docxRef" ref="docxRef">
<canvas
:id="'canvas' + pageIndex"
v-for="pageIndex in pdfPages"
:key="pageIndex"
></canvas>
</div>
js
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)
})
}
}
pdf这里遇到了清晰度不够的问题,去查了一下是dpi导致的,所以做了一点优化
完整代码
js
<!--
* @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>
参考
Vue2预览xlsx文件------插件LuckySheet和LuckyExcel
【Vue-Element】el-upload将文件转为Blob类型
vue中使用docx-preview插件预览word文档(后端express)