前端在线预览之pdf、word、excel预览

前言

公司最近有这么个需求,在线预览pdf、excel、word功能,正好有点时间,稍微整理记录一下。

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;
}

完整代码

<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.gif

多页(数据结构一本书)

多页其实滚动条特别长,注意看右边的滚动条
pdf-multiple.gif

PDF预览加水印

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

实现代码

// 将数据渲染到画布上
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;
}

预览加水印完整代码

<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>

效果实现

单张水印

pdf-single-water.gif

多张水印

pdf-multiple-water.gif

word预览

word预览介绍两种解决方案

docx-preview

安装
pnpm i docx-preview
使用
  1. 项目中引用
import { renderAsync } from 'docx-preview';
  1. 上传组件
    这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
  </div>
  1. 添加一个渲染容器
    此时需要一个渲染容器来呈现需要预览的内容,我们添加一个空的div标签,给加上一个id属性,也可以用ref。
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
    <!-- 预览容器,内容呈现区 -->
    <div id="preview"></div>
  </div>
  1. 完善change方法
/**
 * 文件上传事件
 */
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都可以省略

完整代码
<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.gif

分页

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

mammoth

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

安装
pnpm i mammoth
使用
  1. 项目中引用
import mammoth from 'mammoth'
  1. 上传组件
    这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
  </div>
  1. 添加一个渲染容器
    此时需要一个渲染容器来呈现需要预览的内容,我们添加一个空的div标签,给加上一个id属性,也可以用ref。
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
    <!-- 预览容器,内容呈现区 vHtml作为插入内容-->
    <div id="preview" 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 = () => {
    // 读取完成,调用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文件读取的文档
完整代码
<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>
效果演示

word-moma.gif

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样式

xlsx.gif

默认样式

xlsx-style.gif

完整代码

<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')
效果演示

ant-table.gif

使用完整代码


<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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值