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

前言

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

PDF预览

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

安装

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

css 复制代码
pnpm i pdfjs-dist

使用

  1. 项目中引用 因为我安装的版本是2.6.347,所以我这里用cdn引入需要的工作包版本是2.6.347,最好是引用本地的,因为有些内网没法访问cdn的话会造成异常。
js 复制代码
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事件方便测试使用效果
xml 复制代码
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
  </div>
  1. 添加一个渲染容器 此时需要一个渲染容器来呈现需要预览的内容,我们添加一个空的div标签,给加上一个id属性,也可以用ref。因为内容需要用到canvas画布,所以我们要v-for循环一个画布,渲染多张,如果需求只是需要渲染一张就不需要v-for循环了,但是此场景下也是兼容一张渲染的。
xml 复制代码
  <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方法
scss 复制代码
/**
 * 上传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函数
php 复制代码
/**
 * 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函数
ini 复制代码
/**
 * 需要绘制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,对象形式
属性名 类型 默认值 描述
className string docx 默认的文档样式类的类名/前缀
inWrapper boolean true 启用围绕文档内容的包装器渲染
ignoreWidth boolean false 禁用页面呈现宽度
ignoreHeight boolean false 禁用页面呈现高度
ignoreFonts boolean false 禁用字体渲染
breakPages boolean true 在遇到分页符时启用分页
ignoreLastRenderedPageBreak boolean true 禁用lastRenderedPageBreak标签的分页
experimental boolean false 启用实验功能(制表位计算)
trimXmlDeclaration boolean true xml声明 如果true 在文档解析之前移除
useBase64URL boolean false 是否转化为base64编码的url
useMathMLPolyfill boolean false 是否包括用于chrome、edge等的MathML多填充
renderChanges boolean false 启用文档更改的实验性渲染(插入/删除)
renderHeaders boolean true 启用头部渲染
renderFooters boolean true 启用底部渲染
renderFootnotes boolean true 渲染脚注
renderEndnotes boolean true 渲染尾注
debug boolean false 启用额外的日子记录

简单的使用的话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,对象形式
属性名 类型 默认值 描述
styleMap string/array 控制Word样式到HTML的映射
includeEmbeddedStyleMap boolean true 是否包含嵌入的样式映射
includeDefaultStyleMap boolean true styleMap中传递的样式映射是否与默认样式映射相结合
convertImage boolean true 图像是否被转换为<img>元素,源代码包含在src属性中
ignoreEmptyParagraphs boolean true 是否忽略空段落
idPrefix string 一个字符串,用于在任何生成的ID(例如书签、脚注和尾注使用的ID)前加前缀。默认为空字符串
transformDocument function 如果设置了此函数,则此函数将应用于转换为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预览

安装

css 复制代码
pnpm i xlsx

使用

  1. 项目中引用
js 复制代码
import * as XLSL from 'xlsx'
  1. 上传组件 这时候要简单的写一个上传组件,给组件加上id以及change事件方便测试使用效果
xml 复制代码
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
  </div>
  1. 添加一个渲染容器
xml 复制代码
  <div> 
    <!-- 上传组件 -->
    <input type="file" id="fileInput" @change="uploadChange">
    <!-- 预览容器,内容呈现区 vHtml作为插入内容-->
    <div v-html="vHtml"></div>
  </div>
  1. 完善change方法
csharp 复制代码
/**
 * 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额外加自定义样式,比如边框、边距啥的

xml 复制代码
<style>
table {
    border: 1px solid black;
}
th {
    border: 1px solid black;
}
td {
    border: 1px solid black;
}

</style>

效果演示

加border样式

默认样式

完整代码

xml 复制代码
<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
css 复制代码
pnpm i ant-design-vue
  1. main.ts引入
javascript 复制代码
import Antd from 'ant-design-vue';
createApp(App).use(Antd).mount('#app')

效果演示

使用完整代码

xml 复制代码
<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>
相关推荐
℘团子এ3 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z8 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
星星会笑滴12 分钟前
vue+node+Express+xlsx+emements-plus实现导入excel,并且将数据保存到数据库
vue.js·excel·express
前端百草阁32 分钟前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜32 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40433 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish33 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple34 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five35 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序35 分钟前
vue3 封装request请求
java·前端·typescript·vue