首页/文章/javascript

前端实现文件预览 - word、pdf、excel预览

2023-10-08
438315 分钟
...

PDF预览

PDF预览用的是pdfjs-dist这个pdf.js构建库

安装

最好记住自己安装的版本,因为后面解析字体的时候可能会用到

pnpm i pdfjs-dist

使用

  1. 项目中引用 因为我安装的版本是2.6.347,所以我这里用cdn引入需要的工作包版本是2.6.347,最好是引用本地的,因为有些内网没法访问cdn的话会造成异常。
import * as pdfjsLib from 'pdfjs-dist/build/pdf'; 
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.worker.min.js';
  1. 上传组件 这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
<div>      <!-- 上传组件 -->     
  <input type="file" id="fileInput" @change="uploadChange"> 
</div>
  1. 添加一个渲染容器 此时需要一个渲染容器来呈现需要预览的内容,我们添加一个空的div标签,给加上一个id属性,也可以用ref。因为内容需要用到canvas画布,所以我们要v-for循环一个画布,渲染多张,如果需求只是需要渲染一张就不需要v-for循环了,但是此场景下也是兼容一张渲染的。
<div>      <!-- 上传组件 -->     
  <input type="file" id="fileInput" @change="uploadChange">  
  <!-- 预览容器,内容呈现区 -->   
  <div id="canvasCont">           
    <canvas v-for="index in canvasTotalPage" :id="`myVancas${index}`" :key="index"></canvas>   
  </div>  
</div>
  1. 完善change方法
/**  * 上传pdf文件  */ 
function uploadFile() {     
  // 获取到上传文件input组件的dom实例,实际上用ref也行,但是vue2和vue3使用ref的写法上有区别,就不麻烦搞ref了,统一用id了    
  const file = document.getElementById('fileinput').files[0];     
  // FileReader是一个强大的读取文件的api,创建一个FileReader实例来读取文件     
  const reader = new FileReader();    
  // 将文件内容转换为base64编码的url,方便pdfjs-dist插件加载pdf文档     
  reader.readAsDataURL(file);     
  // FileReader读取文件完成后触发,此时拿到base64编码的url了   
  reader.onload = () => {        
    // 直接用atob代码ts会报错,因为网上说这个已经弃用了,但是在代码里面还是能用,不过ts会报错     
    const data = window.atob(reader.result.substring(reader.result.indexOf(',') + 1));      
    // 拿到base64编码的url去加载pdf文档        
    loadPdfData(data);     };
}
  1. 编写loadPdfData函数
/**  * pdf下载以及加载函数(异步)  */ 
const pdfLoadTask = ref(); 
/**  * 引入pdf.js的字体,插件无法解析某些特殊字体,所以需要加载特定字体cmap文件  * 使用cdn的的形式,内网或者没网的时候会失效,建议直接把这个目录下的文件下载到本地,引用本地的资源  */ 
const cmapUrl = ref<string>('https://unpkg.com/pdfjs-dist@2.6.347/cmaps/'); 
/**  * 通过pdfjs-dist插件生产pdf  * @param data base64编码的url  */ 
function loadPdfData(data: string) {   
  // pdfjsLib.getDocument是获取pdf文档的方法,返回的是premise对象,对象包含一些pdf文档的信息以及操作pdf文档的api   
  pdfLoadTask.value = pdfjsLib.getDocument({     
    data: data,        
    cMapUrl: cmapUrl.value,    
    cMapPacked: true,    
  });     
  // 渲染页面,至少一页 
  renderPage(1); 
}
  1. 编写renderPage函数
/**  * 需要绘制pdf的总页数  */ const canvasTotalPage = ref<number>(1);  /**  * 渲染指定页码的pdf文档  * @param num 指定页码  */ function renderPage(num: number) {     // 异步函数结束后返回pdf的基本信息以及一些api,是一个对象     pdfLoadTask.value.promise.then((pdf: any) => {         // 记录一下总页数,多页的情况,每页都需要新建一个画布         canvasTotalPage.value = pdf.numPages;          // 通过调用pdf对象的getPage方法,将指定的页码传入,可以获取传入页码的引用         pdf.getPage(num).then((page: any) => {             // 获取canvas的DOM对象             const canvas: any = document.getElementById(`myVancas${num}`);              // 获取canvas的渲染上下文(包含canvas的引用以及绘图功能)             const ctx = canvas.getContext('2d');              // 获取页面的像素比率             const ratio = getRatio(ctx);              // 页面的视口宽度,也就是元素的可视宽度             // 不太理解用offsetWidth,可以看下这篇文章https://zhuanlan.zhihu.com/p/603633893             const viewWidth = document.getElementById('canvasCont').offsetWidth;              // pdf文档的宽度             const pdfWidth = page.view[2];              // 根据视口的宽度/pdf文档宽度得到缩放比             const scale = viewWidth / pdfWidth;              // 获取pdf文档的缩放后基本信息             const viewport = page.getViewport({ scale });              // 画布的宽高需要根据实际像素调整,避免出现模糊的情况             canvas.width = viewport.width * ratio;             canvas.height = viewport.height * ratio;              // 准备page.render()函数需要的参数             const renderContext = {                 canvasContext: ctx,                 viewport: viewport,             };              // 将数据渲染到画布上             page.render(renderContext)                           // 多页pdf的情况             if (num < pdf.numPages) {                 renderPage(num + 1);             }         });     }); }  /**  * 获取页面的比率  * @param ctx canvas绘图环境的上下文  */ function getRatio(ctx: any) {     // window对象中的属性devicePixelRatio,这个属性决定了浏览器会用多少实际的像素来渲染一个像素(也可以理解为css像素)     // 至于为什么要获取这个,不同设备的像素比不同,当这个像素比为2时,会用2个实际像素来渲染一个css像素,就相当于放大了视图一倍,会导致屏幕显示模糊     const dpr = window.devicePixelRatio || 1;     // canvas的context上下文中也存在一些关于像素比的属性,逻辑是canvas是用几个像素来存储画布信息     // webkit、moz、ms、o这些代码的是不同浏览器之间的内核识别码     const bsr =         ctx.webkitBackingStorePixelRatio ||         ctx.mozBackingStorePixelRatio ||         ctx.msBackingStorePixelRatio ||         ctx.oBackingStorePixelRatio ||         ctx.backingStorePixelRatio ||         1;      return dpr / bsr; }

完整代码

ini

复制代码

``<template>     <div>         <input id="fileinput" type="file" @change="uploadFile" />         <div id="canvasCont">             <canvas v-for="index in canvasTotalPage" :id="`myVancas${index}`" :key="index"></canvas>         </div>     </div> </template>  <script setup lang="ts"> import { ref } from 'vue';  import * as pdfjsLib from 'pdfjs-dist/build/pdf'; pdfjsLib.GlobalWorkerOptions.workerSrc =     'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.worker.min.js';  /**  * pdf下载以及加载函数(异步)  */ const pdfLoadTask = ref();  /**  * 需要绘制pdf的总页数  */ const canvasTotalPage = ref<number>(1);  /**  * 引入pdf.js的字体,插件无法解析某些特殊字体,所以需要加载特定字体cmap文件  * 使用cdn的的形式,内网或者没网的时候会失效,建议直接把这个目录下的文件下载到本地,引用本地的资源  */ const cmapUrl = ref<string>('https://unpkg.com/pdfjs-dist@2.6.347/cmaps/');  /**  * 上传pdf文件  */ function uploadFile() {     // 获取到上传文件input组件的dom实例,实际上用ref也行,但是vue2和vue3使用ref的写法上有区别,就不麻烦搞ref了,统一用id了     const file = document.getElementById('fileinput').files[0];      // FileReader是一个强大的读取文件的api,创建一个FileReader实例来读取文件     const reader = new FileReader();      // 将文件内容转换为base64编码的url,方便pdfjs-dist插件加载pdf文档     reader.readAsDataURL(file);      // FileReader读取文件完成后触发,此时拿到base64编码的url了     reader.onload = () => {         // 直接用atob代码ts会报错,因为网上说这个已经弃用了,但是在代码里面还是能用,不过ts会报错         const data = window.atob(reader.result.substring(reader.result.indexOf(',') + 1));         // 拿到base64编码的url去加载pdf文档         loadPdfData(data);     }; }  /**  * 通过pdfjs-dist插件生产pdf  * @param data base64编码的url  */ function loadPdfData(data: string) {     // pdfjsLib.getDocument是获取pdf文档的方法,返回的是premise对象,对象包含一些pdf文档的信息以及操作pdf文档的api     pdfLoadTask.value = pdfjsLib.getDocument({         data: data,         cMapUrl: cmapUrl.value,         cMapPacked: true,     });      // 渲染页面,至少一页     renderPage(1); }  /**  * 渲染指定页码的pdf文档  * @param num 指定页码  */ function renderPage(num: number) {     // 异步函数结束后返回pdf的基本信息以及一些api,是一个对象     pdfLoadTask.value.promise.then((pdf: any) => {         // 记录一下总页数,多页的情况,每页都需要新建一个画布         canvasTotalPage.value = pdf.numPages;          // 通过调用pdf对象的getPage方法,将指定的页码传入,可以获取传入页码的引用         pdf.getPage(num).then((page: any) => {             // 获取canvas的DOM对象             const canvas: any = document.getElementById(`myVancas${num}`);              // 获取canvas的渲染上下文(包含canvas的引用以及绘图功能)             const ctx = canvas.getContext('2d');              // 获取页面的像素比率             const ratio = getRatio(ctx);              // 页面的视口宽度,也就是元素的可视宽度             // 不太理解用offsetWidth,可以看下这篇文章https://zhuanlan.zhihu.com/p/603633893             const viewWidth = document.getElementById('canvasCont').offsetWidth;              // pdf文档的宽度,不懂为啥用page.view[2]的话,可以打印一下page看看page返回的具体是啥信息             const pdfWidth = page.view[2];              // 根据视口的宽度/pdf文档宽度得到缩放比             const scale = viewWidth / pdfWidth;              // 获取pdf文档的缩放后基本信息             const viewport = page.getViewport({ scale });              // 画布的宽高需要根据实际像素调整,避免出现模糊的情况             canvas.width = viewport.width * ratio;             canvas.height = viewport.height * ratio;              // 准备page.render()函数需要的参数             const renderContext = {                 canvasContext: ctx,                 viewport: viewport,             };              // 将数据渲染到画布上             page.render(renderContext)                           // 多页pdf的情况             if (num < pdf.numPages) {                 renderPage(num + 1);             }         });     }); }  /**  * 获取页面的比率  * @param ctx canvas绘图环境的上下文  */ function getRatio(ctx: any) {     // window对象中的属性devicePixelRatio,这个属性决定了浏览器会用多少实际的像素来渲染一个像素(也可以理解为css像素)     // 至于为什么要获取这个,不同设备的像素比不同,当这个像素比为2时,会用2个实际像素来渲染一个css像素,就相当于放大了视图一倍,会导致屏幕显示模糊     const dpr = window.devicePixelRatio || 1;     // canvas的context上下文中也存在一些关于像素比的属性,逻辑是canvas是用几个像素来存储画布信息     // webkit、moz、ms、o这些代码的是不同浏览器之间的内核识别码     const bsr =         ctx.webkitBackingStorePixelRatio ||         ctx.mozBackingStorePixelRatio ||         ctx.msBackingStorePixelRatio ||         ctx.oBackingStorePixelRatio ||         ctx.backingStorePixelRatio ||         1;      return dpr / bsr; } </script>``

效果演示

单页

多页(数据结构一本书)

多页其实滚动条特别长,注意看右边的滚动条

PDF预览加水印

加水印比较简单,在PDF预览代码的page.render函数回调中创建一个画布水印,加到具体页面中。

实现代码

ini

复制代码

``// 将数据渲染到画布上 page.render(renderContext).promise.then(() => {     // 添加水印     addWatermark(num, canvas.width, canvas.height); });  /**  * 在画布上添加水印  * @param num 画布索引  */ function addWatermark(num: number, width: number, height: number) {     const canvas: any = document.getElementById(`myVancas${num}`);     const ctx = canvas.getContext('2d');      // 方法在指定的方向内重复指定的元素,元素可以是图片、视频,或者其他 <canvas> 元素,被重复的元素可用于绘制/填充矩形、圆形或线条等等。     const pattern = ctx.createPattern(initWatermark(), 'repeat');      // 创建矩形     ctx.rect(0, 0, width, height);      // fillStyle 属性设置或返回用于填充绘画的颜色、渐变或模式,也可以设置pattern。     ctx.fillStyle = pattern;      // 当前的图像     ctx.fill(); }  /**  * 初始化水印元素  */ function initWatermark() {     const canvas = document.createElement('canvas');     canvas.width = 200;     canvas.height = 200;     const ctx: any = canvas.getContext('2d');     ctx.rotate((45 * Math.PI) / 180);     ctx.font = '15px Verdana';     ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';     ctx.fillText('我是水印', 30, 30);     return canvas; }``

预览加水印完整代码

ini

复制代码

``<template>     <div>         <input id="fileinput" type="file" @change="uploadFile" />         <div id="canvasCont">             <canvas v-for="index in canvasTotalPage" :id="`myVancas${index}`" :key="index"></canvas>         </div>     </div> </template>  <script setup lang="ts"> import { ref } from 'vue';  import * as pdfjsLib from 'pdfjs-dist/build/pdf'; pdfjsLib.GlobalWorkerOptions.workerSrc =     'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.worker.min.js';  /**  * pdf下载以及加载函数(异步)  */ const pdfLoadTask = ref();  /**  * 需要绘制pdf的总页数  */ const canvasTotalPage = ref<number>(1);  /**  * 引入pdf.js的字体,插件无法解析某些特殊字体,所以需要加载特定字体cmap文件  * 使用cdn的的形式,内网或者没网的时候会失效,建议直接把这个目录下的文件下载到本地,引用本地的资源  */ const cmapUrl = ref<string>('https://unpkg.com/pdfjs-dist@2.6.347/cmaps/');  /**  * 上传pdf文件  */ function uploadFile() {     // 获取到上传文件input组件的dom实例,实际上用ref也行,但是vue2和vue3使用ref的写法上有区别,就不麻烦搞ref了,统一用id了     const file = document.getElementById('fileinput').files[0];      // FileReader是一个强大的读取文件的api,创建一个FileReader实例来读取文件     const reader = new FileReader();      // 将文件内容转换为base64编码的url,方便pdfjs-dist插件加载pdf文档     reader.readAsDataURL(file);      // FileReader读取文件完成后触发,此时拿到base64编码的url了     reader.onload = () => {         // 直接用atob代码ts会报错,因为网上说这个已经弃用了,但是在代码里面还是能用,不过ts会报错         const data = window.atob(reader.result.substring(reader.result.indexOf(',') + 1));         // 拿到base64编码的url去加载pdf文档         loadPdfData(data);     }; }  /**  * 通过pdfjs-dist插件生产pdf  * @param data base64编码的url  */ function loadPdfData(data: string) {     // pdfjsLib.getDocument是获取pdf文档的方法,返回的是premise对象,对象包含一些pdf文档的信息以及操作pdf文档的api     pdfLoadTask.value = pdfjsLib.getDocument({         data: data,         cMapUrl: cmapUrl.value,         cMapPacked: true,     });      // 渲染页面,至少一页     renderPage(1); }  /**  * 渲染指定页码的pdf文档  * @param num 指定页码  */ function renderPage(num: number) {     // 异步函数结束后返回pdf的基本信息以及一些api,是一个对象     pdfLoadTask.value.promise.then((pdf: any) => {         // 记录一下总页数,多页的情况,每页都需要新建一个画布         canvasTotalPage.value = pdf.numPages;          // 通过调用pdf对象的getPage方法,将指定的页码传入,可以获取传入页码的引用         pdf.getPage(num).then((page: any) => {             // 获取canvas的DOM对象             const canvas: any = document.getElementById(`myVancas${num}`);              // 获取canvas的渲染上下文(包含canvas的引用以及绘图功能)             const ctx = canvas.getContext('2d');              // 获取页面的像素比率             const ratio = getRatio(ctx);              // 页面的视口宽度,也就是元素的可视宽度             // 不太理解用offsetWidth,可以看下这篇文章https://zhuanlan.zhihu.com/p/603633893             const viewWidth = document.getElementById('canvasCont').offsetWidth;              // pdf文档的宽度,不懂为啥用page.view[2]的话,可以打印一下page看看page返回的具体是啥信息             const pdfWidth = page.view[2];              // 根据视口的宽度/pdf文档宽度得到缩放比             const scale = viewWidth / pdfWidth;              // 获取pdf文档的缩放后基本信息             const viewport = page.getViewport({ scale });              // 画布的宽高需要根据实际像素调整,避免出现模糊的情况             canvas.width = viewport.width * ratio;             canvas.height = viewport.height * ratio;              // 准备page.render()函数需要的参数             const renderContext = {                 canvasContext: ctx,                 viewport: viewport,             };              // 将数据渲染到画布上             page.render(renderContext).promise.then(() => {                 // 添加水印                 addWatermark(num, canvas.width, canvas.height);                               });                           // 多页pdf的情况             if (num < pdf.numPages) {                 renderPage(num + 1);             }         });     }); }  /**  * 在画布上添加水印  * @param num 画布索引  */ function addWatermark(num: number, width: number, height: number) {     const canvas: any = document.getElementById(`myVancas${num}`);     const ctx = canvas.getContext('2d');      // 方法在指定的方向内重复指定的元素,元素可以是图片、视频,或者其他 <canvas> 元素,被重复的元素可用于绘制/填充矩形、圆形或线条等等。     const pattern = ctx.createPattern(initWatermark(), 'repeat');      // 创建矩形     ctx.rect(0, 0, width, height);      // fillStyle 属性设置或返回用于填充绘画的颜色、渐变或模式,也可以设置pattern。     ctx.fillStyle = pattern;      // 当前的图像     ctx.fill(); }  /**  * 初始化水印元素  */ function initWatermark() {     const canvas = document.createElement('canvas');     canvas.width = 200;     canvas.height = 200;     const ctx: any = canvas.getContext('2d');     ctx.rotate((45 * Math.PI) / 180);     ctx.font = '15px Verdana';     ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';     ctx.fillText('我是水印', 30, 30);     return canvas; }  /**  * 获取页面的比率  * @param ctx canvas绘图环境的上下文  */ function getRatio(ctx: any) {     // window对象中的属性devicePixelRatio,这个属性决定了浏览器会用多少实际的像素来渲染一个像素(也可以理解为css像素)     // 至于为什么要获取这个,不同设备的像素比不同,当这个像素比为2时,会用2个实际像素来渲染一个css像素,就相当于放大了视图一倍,会导致屏幕显示模糊     const dpr = window.devicePixelRatio || 1;     // canvas的context上下文中也存在一些关于像素比的属性,逻辑是canvas是用几个像素来存储画布信息     // webkit、moz、ms、o这些代码的是不同浏览器之间的内核识别码     const bsr =         ctx.webkitBackingStorePixelRatio ||         ctx.mozBackingStorePixelRatio ||         ctx.msBackingStorePixelRatio ||         ctx.oBackingStorePixelRatio ||         ctx.backingStorePixelRatio ||         1;      return dpr / bsr; } </script>``

效果实现

单张水印

多张水印

word预览

word预览介绍两种解决方案

docx-preview

安装

css

复制代码

`pnpm i docx-preview`

使用

  1. 项目中引用
js

复制代码

`import { renderAsync } from 'docx-preview';`
  1. 上传组件 这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
xml

复制代码

  `<div>      <!-- 上传组件 -->     <input type="file" id="fileInput" @change="uploadChange">   </div>`
  1. 添加一个渲染容器 此时需要一个渲染容器来呈现需要预览的内容,我们添加一个空的div标签,给加上一个id属性,也可以用ref。
xml

复制代码

  `<div>      <!-- 上传组件 -->     <input type="file" id="fileInput" @change="uploadChange">     <!-- 预览容器,内容呈现区 -->     <div id="preview"></div>   </div>`
  1. 完善change方法
javascript

复制代码

`/**  * 文件上传事件  */ function uploadChange(  ){   const fileInputDom = document.getElementById('fileInput')    const previewDom = document.getElementById('preview')    renderAsync(fileInputDom.files[0], previewDom) }`

renderAsync

我们一般都是通过调用renderAsync这个异步函数来触发文件的预览的,他有4个参数 分别是:

  1. document 类型可以是Blob | ArrayBuffer | Uint8Array
  2. bodyContainer 用来呈现内容的html对象
  3. styleContainer 给内容加样式
  4. options 一些自定义api,对象形式
属性名类型默认值描述
classNamestringdocx默认的文档样式类的类名/前缀
inWrapperbooleantrue启用围绕文档内容的包装器渲染
ignoreWidthbooleanfalse禁用页面呈现宽度
ignoreHeightbooleanfalse禁用页面呈现高度
ignoreFontsbooleanfalse禁用字体渲染
breakPagesbooleantrue在遇到分页符时启用分页
ignoreLastRenderedPageBreakbooleantrue禁用lastRenderedPageBreak标签的分页
experimentalbooleanfalse启用实验功能(制表位计算)
trimXmlDeclarationbooleantruexml声明 如果true 在文档解析之前移除
useBase64URLbooleanfalse是否转化为base64编码的url
useMathMLPolyfillbooleanfalse是否包括用于chrome、edge等的MathML多填充
renderChangesbooleanfalse启用文档更改的实验性渲染(插入/删除)
renderHeadersbooleantrue启用头部渲染
renderFootersbooleantrue启用底部渲染
renderFootnotesbooleantrue渲染脚注
renderEndnotesbooleantrue渲染尾注
debugbooleanfalse启用额外的日子记录

简单的使用的话styleContainer和options都可以省略

完整代码

vue.js

复制代码

`<template>   <div>      <input type="file" id="fileInput" @change="uploadChange">     <div id="preview"></div>   </div> </template> <script setup lang="ts"> import { renderAsync } from 'docx-preview'; /**  * 文件上传事件  */ function uploadChange(){   const fileInputDom = document.getElementById('fileInput')    const previewDom = document.getElementById('preview')   renderAsync(fileInputDom.files[0], previewDom) } </script>`

效果演示

目前没发现能自动和word一样分页,如果word不加分页符,自动全部合并成一页

不分页
分页

分页要在word加分隔符并且options的breakPages需要设置为true

mammoth

看下一下mammoth,它的样式调起来好像比较麻烦。

安装

css

复制代码

`pnpm i mammoth`

使用

  1. 项目中引用
js

复制代码

`import mammoth from 'mammoth'`
  1. 上传组件 这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
xml

复制代码

  `<div>      <!-- 上传组件 -->     <input type="file" id="fileInput" @change="uploadChange">   </div>`
  1. 添加一个渲染容器 此时需要一个渲染容器来呈现需要预览的内容,我们添加一个空的div标签,给加上一个id属性,也可以用ref。
xml

复制代码

  `<div>      <!-- 上传组件 -->     <input type="file" id="fileInput" @change="uploadChange">     <!-- 预览容器,内容呈现区 vHtml作为插入内容-->     <div id="preview" v-html="vHtml"></div>   </div>`
  1. 完善change方法
javascript

复制代码

`/**  * v-html  */ const vHtml = ref<string>('')  /**  * 文件上传  */ function uploadChange(  ){   // 读取上传后的文件   const file= document.getElementById('fileInput').files[0]    // 创建FileReader实例   const reader = new FileReader()    // 指定读取的内容是ArrayBuffer类型的数据   reader.readAsArrayBuffer(file)    reader.onload = () => {     // 读取完成,调用mammoth的convertToHtml函数获取解析到的数据     mammoth.convertToHtml({arrayBuffer: reader.result as ArrayBuffer}).then((resultObject)=>{       vHtml.value = resultObject.value     })   } }`

convertToHtml

我们一般都是通过调用mammoth的convertToHtml这个异步函数来触发word文件的解析,他有2个参数 分别是:

  1. input 类型可以是path | ArrayBuffer | buffer
  2. options 一些自定义api,对象形式
属性名类型默认值描述
styleMapstring/array控制Word样式到HTML的映射
includeEmbeddedStyleMapbooleantrue是否包含嵌入的样式映射
includeDefaultStyleMapbooleantruestyleMap中传递的样式映射是否与默认样式映射相结合
convertImagebooleantrue图像是否被转换为<img>元素,源代码包含在src属性中
ignoreEmptyParagraphsbooleantrue是否忽略空段落
idPrefixstring一个字符串,用于在任何生成的ID(例如书签、脚注和尾注使用的ID)前加前缀。默认为空字符串
transformDocumentfunction如果设置了此函数,则此函数将应用于转换为HTML之前从docx文件读取的文档

完整代码

xml

复制代码

`<template>   <div>      <input type="file" id="fileInput" @change="uploadChange">     <div id="preview" v-html="vHtml"></div>   </div> </template> <script setup lang="ts">  import mammoth from 'mammoth' import { ref } from 'vue';  /**  * v-html  */ const vHtml = ref<string>('')  /**  * 文件上传  */ function uploadChange(  ){   // 读取上传后的文件   const file= document.getElementById('fileInput').files[0]    // 创建FileReader实例   const reader = new FileReader()    // 指定读取的内容是ArrayBuffer类型的数据   reader.readAsArrayBuffer(file)    reader.onload = () => {     // 读取完成,调用mammoth的convertToHtml函数获取解析到的数据     mammoth.convertToHtml({arrayBuffer: reader.result as ArrayBuffer}).then((resultObject)=>{       vHtml.value = resultObject.value     })   } }     </script>`

效果演示

excel预览

安装

pnpm i xlsx

使用

  1. 项目中引用
import * as XLSL from 'xlsx'
  1. 上传组件 这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
<div>      <!-- 上传组件 -->     <input type="file" id="fileInput" @change="uploadChange">   </div>
  1. 添加一个渲染容器
<div>      <!-- 上传组件 -->     <input type="file" id="fileInput" @change="uploadChange">     <!-- 预览容器,内容呈现区 vHtml作为插入内容-->     <div v-html="vHtml"></div>   </div>
  1. 完善change方法

/**  * v-html  */ const vHtml = ref<string>('')  /**  * 文件上传  */ function uploadChange(){   // 读取上传后的文件   const file= document.getElementById('fileInput').files[0]    // 创建FileReader实例   const reader = new FileReader()    // 指定读取的内容是ArrayBuffer类型的数据   reader.readAsArrayBuffer(file)    reader.onload = () => {     // 读取完成,调用XLSL的read函数获取解析上传的xlsx     let workbook =  XLSL.read(reader.result,{type:'array'})     // 一般都只有一个sheet,取第一个sheet的名称,方便取到sheet的数据     let sheetNames = workbook.SheetNames[0]      // 根据sheet的名称获取sheet数据解析成html     let html = XLSL.utils.sheet_to_html(workbook.Sheets[sheetNames])      // 将渲染容器替换成解析出来的html标签渲染     vHtml.value =  html   } }

此时渲染出来的html是没有样式的table,需要自己给table加样式,我们需要给table额外加自定义样式,比如边框、边距啥的

<style> 
table {  
  border: 1px solid black;
} 
th {     
  border: 1px solid black;
} 
td {  
  border: 1px solid black; 
} 
</style>

效果演示

加border样式

默认样式

完整代码

<template>   <div>      <input type="file" id="fileInput" @change="uploadChange">     <div v-html="vHtml"></div>   </div> </template> <script setup lang="ts">  import * as XLSL from 'xlsx' import { ref } from 'vue'; /**  *  v-html  */ const vHtml = ref<string>('')  /**  * 文件上传  */ function uploadChange(  ){   // 读取上传后的文件   const file= document.getElementById('fileInput').files[0]    // 创建FileReader实例   const reader = new FileReader()    // 指定读取的内容是ArrayBuffer类型的数据   reader.readAsArrayBuffer(file)    reader.onload = () => {     // 读取完成,调用XLSL的read函数获取解析上传的xlsx     let workbook =  XLSL.read(reader.result,{type:'array'})     // 一般都只有一个sheet,取第一个sheet的名称,方便取到sheet的数据     let sheetNames = workbook.SheetNames[0]      // 根据sheet的名称获取sheet数据解析成html     let html  = XLSL.utils.sheet_to_html(workbook.Sheets[sheetNames])           // 将渲染容器替换成解析出来的html标签渲染     vHtml.value =  html   } }  </script> <!-- <style> table {     border: 1px solid black; }  th {     border: 1px solid black; } td {     border: 1px solid black; } </style> -->

建议输出json

建议输出成json格式的,这样的话,可以只拿到数据,然后放到自己想要的组件中。输出的数据是对象数组形式,数组中的对象的key就是excel的第一行。引入一个ant-design-vue的table,将数据解析出来看看

引入ant-design-vue table

  1. 安装ant-design-vue
pnpm i ant-design-vue
  1. main.ts引入
import Antd from 'ant-design-vue'; createApp(App).use(Antd).mount('#app')

效果演示

使用完整代码

<template>     <div>        <input type="file" id="fileInput" @change="uploadChange">       <a-table :dataSource="dataSource" :columns="columns" bordered />  </div>   </template>   <script setup lang="ts">   import * as XLSL from 'xlsx'   import { ref } from 'vue';       interface rowType {     [key:string]: string   }       /**    *  table columns    */   const columns = ref<rowType[]>([])           /**    *  table dataSource    */    const dataSource = ref<rowType[]>([])       /**    * 文件上传    */   function uploadChange(  ){     // 读取上传后的文件     const file= document.getElementById('fileInput').files[0]         // 创建FileReader实例     const reader = new FileReader()         // 指定读取的内容是ArrayBuffer类型的数据     reader.readAsArrayBuffer(file)         reader.onload = () => {       // 读取完成,调用XLSL的read函数获取解析上传的xlsx       let workbook =  XLSL.read(reader.result,{type:'array'})       // 一般都只有一个sheet,取第一个sheet的名称,方便取到sheet的数据       let sheetNames = workbook.SheetNames[0]           // 根据sheet的名称获取sheet数据解析成json       let jsonData  = XLSL.utils.sheet_to_json(workbook.Sheets[sheetNames]) as rowType[]        // 取excel第一行当表头       Object.keys(jsonData[0]).forEach((item: string)=>{         columns.value.push({           title: item,           dataIndex: item         })       })           // 其他行都当做数据       jsonData.forEach((item: rowType)=>{         let obj:rowType = {}                   Object.keys(item).map((key:string)=>{           obj[key] = item[key]         })             dataSource.value.push(obj)       })      }   }       </script>

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2026-02-24
navigator.sendBeacon全指南
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户 关闭页面 、 刷新 或 跳转路由 时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。 但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— 。 一、 痛点与传统方案的挣扎 场景还原 当用户点击关闭按钮时,浏览器会触发生命周期事件( 或 )。如果我们直接使用普通的异步 AJAX ( 或 ) 发送请求,浏览...
学习
javascript2025-11-02
理解浏览器事件系统,从用户点击到事件对象的完整旅程
深入理解浏览器事件系统:从用户点击到事件对象的完整旅程 “当我点击页面按钮时,背后发生了什么?为什么回调函数能收到一个包含丰富信息的event对象?今天,让我们一起揭开浏览器事件系统的神秘面纱。” 一个令人困惑的现象 作为前端开发者,我们每天都在写这样的代码: 这段代码如此熟悉,以至于我们很少停下来思考:​ ​这个 对象到底从哪里来?它为什么能知道点击的精确坐标?为什么能识别是哪个元素被点击了?​ ​ 更神奇的是,当我们手动创建事件时:...
学习
javascript2025-10-01
实现大文件上传全流程详解
在日常开发中,大文件上传是个绕不开的坎——动辄几百 MB 甚至 GB 级的文件,直接上传不仅容易超时,还会让用户体验大打折扣。最近我用 Vue+Express 实现了一套完整的大文件上传方案,支持分片上传、断点续传、秒传和手动中。 一、先看效果:我们要实现什么? 先上核心功能清单,确保大家明确目标,知道我们要解决哪些实际问题: 大文件分片上传 :将文件切成固定大小的小片段分批上传,避免单次请求超时 秒传 :服务器已存在完整文件时,直接返...
学习
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-15
一张 8K 海报差点把首屏拖垮
你给后台管理系统加了一个「企业风采」模块,运营同学一口气上传了 200 张 8K 宣传海报。首屏直接飙到 8.3 s,LCP 红得发紫。 老板一句「能不能像朋友圈那样滑到哪看到哪?」——于是你把懒加载重新翻出来折腾了一轮。 解决方案:三条技术路线,你全踩了一遍 1\. 最偷懒:原生 一行代码就能跑,浏览器帮你搞定。 🔍 关键决策点 2020 年后现代浏览器全覆盖,IE 全军覆没。 必须写死 ,否则 CLS 会抖成 PPT。 适用场景...
学习
javascript2025-09-10
🚀 Web Worker让你的应用丝滑
🌟 引言 在日常的前端开发中,你是否遇到过这样的困扰: 大数据处理时页面卡死 :处理几万条数据时,页面直接卡成PPT,用户点击毫无反应 复杂计算阻塞UI :图片处理、数据分析等计算密集型任务让整个应用假死 文件上传/下载卡顿 :大文件操作时,其他功能完全无法使用 实时数据处理性能差 :WebSocket接收大量数据时,页面渲染严重滞后 今天分享6个Web Worker的核心技巧,让你的应用告别卡顿,用户体验丝滑如德芙! 💡 核心技巧...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录