在线预览 Word 文档

引言

随着互联网技术的发展,Web 应用越来越复杂,用户对在线办公的需求也日益增加。在许多业务场景中,能够直接在浏览器中预览 Word 文档是一个非常实用的功能。这不仅可以提高用户体验,还能减少用户操作步骤,提升效率。

实现原理

1. 后端服务

假设后端服务已经提供了两个 API 接口:

getFilesList: 获取文件列表。
previewFile: 获取指定文件的内容。

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');const app = express();// 定义文件夹路径
const mergedDir = path.join(__dirname, 'merged');// 获取文件列表
app.get('/getFilesList', (req, res) => {fs.readdir(mergedDir, (err, files) => {if (err) {return res.status(500).json({ error: '无法读取文件夹' });}// 获取每个文件的详细信息const fileInfos = files.map(file => {const filePath = path.join(mergedDir, file);const stats = fs.statSync(filePath);return {fileName: file,size: stats.size,upTime: stats.mtime,isFile: stats.isFile()};});let resContent = {code: 200,data: fileInfos || [],message: '查询成功'}res.json(resContent);});
});// 文件预览接口
app.get('/download', (req, res) => {const { fileName } = req.query;const filePath = path.join(mergedDir, fileName);fs.access(filePath, fs.constants.F_OK, (err) => {if (err) {return res.status(404).json({ error: '文件不存在' });}const stats = fs.statSync(filePath);if (stats.isFile()) {const contentType = getContentType(fileName);res.setHeader('Content-Type', contentType);// 对 fileName 进行编码const encodedFileName = encodeURIComponent(fileName);res.setHeader('Content-Disposition', `inline; filename=${encodedFileName}`);fs.createReadStream(filePath).pipe(res);} else {res.status(400).json({ error: '不是一个文件' });}});
});// 获取文件的 MIME 类型
function getContentType(fileName) {const ext = path.extname(fileName).toLowerCase();switch (ext) {case '.js':return 'application/javascript';case '.json':return 'application/json';case '.html':return 'text/html';case '.css':return 'text/css';case '.txt':return 'text/plain';case '.png':return 'image/png';case '.jpg':case '.jpeg':return 'image/jpeg';case '.gif':return 'image/gif';case '.pdf':return 'application/pdf';case '.doc':return 'application/msword';case '.docx':return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';case '.ppt':return 'application/vnd.ms-powerpoint';case '.pptx':return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';case '.xlsx':return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';default:return 'application/octet-stream';}
}// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`);
});

2. 前端页面

前端页面使用 Vue.js 和 Element UI 组件库来实现文件列表展示和预览功能。

文件列表展示

  • 在组件的 created 生命周期钩子中调用 getFileName 方法,从后端获取文件列表。

  • 使用 timeStampToString 工具函数将时间戳转换为可读的日期格式。

  • 将处理后的数据赋值给 tableData,并在表格中展示。

async getFileName() {const { code, data: resData } = await getFilesList(this.form);if (code === 200) {resData.forEach(item => {item.upTime = timeStampToString(item.upTime);});this.tableData = resData;}
}

预览 Word 文档

  • 当用户点击“预览”按钮时,触发 previewDocx 方法。

  • 该方法接收当前行的数据对象 { fileName },并调用 previewFile API 获取文件内容。

async previewDocx({ fileName }) {try {const response = await previewFile(fileName);const reader = new FileReader();reader.onload = async (event) => {const arrayBuffer = event.target.result;const result = await mammoth.convertToHtml({ arrayBuffer });const htmlContent = result.value;// 创建一个新的窗口const newWindow = window.open('', '_blank');// 在新窗口中写入 HTML 内容newWindow.document.write(`<!DOCTYPE html><html><head><title>Word 文档预览</title><meta charset="UTF-8"><style>/* 添加一些样式以改善预览效果 */body {font-family: Arial, sans-serif;margin: 20px;}</style></head><body>${htmlContent}</body></html>`);// 确保新窗口的内容加载完成newWindow.document.close();};reader.readAsArrayBuffer(response);} catch (err) {console.error('Failed to preview file', err);}
}

使用 Mammoth 库文件转换

  • mammoth 是一个将 Word 文档(.docx)转换为 HTML 的库。
  • 在 reader.onload 回调中,使用 mammoth.convertToHtml 方法将文件的二进制数据(ArrayBuffer)转换为 HTML 字符串。
const result = await mammoth.convertToHtml({ arrayBuffer });
const htmlContent = result.value;

创建新窗口预览 word 文档

  • 使用 window.open 方法创建一个新的浏览器窗口。

  • 在新窗口中写入转换后的 HTML 内容,并添加一些基本的样式以改善预览效果。

const newWindow = window.open('', '_blank');
newWindow.document.write(`<!DOCTYPE html><html><head><title>Word 文档预览</title><meta charset="UTF-8"><style>body {font-family: Arial, sans-serif;margin: 20px;}</style></head><body>${htmlContent}</body></html>
`);
newWindow.document.close();

缺点:使用 mammoth 将 Word 文档转换为 HTML 时,会丢失 Word 文档中的样式,导致渲染的页面与原 word 文档差异很大。

转换后的 HTML 与原 Word 文档对比如下
在这里插入图片描述

解决办法: 使用 docx-preview 预览 Word 文档

使用 docx-preview 预览 Word 文档

介绍

docx-preview 是一个用于在浏览器中预览 Word 文档(.docx)的 JavaScript 库。它提供了简单易用的 API 来渲染 Word 文档。

安装

npm install docx-preview
# 或者
yarn add docx-preview

主要方法

1. renderAsync

参数:

  • document:Word 文档的二进制数据,可以是 BlobArrayBufferstring 类型。

  • bodyContainer:DOM 元素,用于放置渲染后的文档内容。

  • styleContainer: DOM 元素,用于渲染文档的样式、编号、字体等。如果设置为 null,则使用 bodyContainer

  • options:一个对象,包含以下可选属性:

    • className: string (默认值: "docx"),用于生成默认和文档样式的类名前缀。

    • inWrapper: boolean (默认值: true),是否启用围绕文档内容的包装器,如果启用,文档内容将被包裹在一个额外的容器中。

    • ignoreWidth: boolean (默认值: false),是否禁用页面宽度的渲染,如果启用,页面宽度将不会被应用到渲染的 HTML 中。

    • ignoreHeight: boolean (默认值: false),是否禁用页面高度的渲染,如果启用,页面高度将不会被应用到渲染的 HTML 中。

    • ignoreFonts: boolean (默认值: false),是否禁用字体的渲染,如果启用,文档中的自定义字体将不会被加载和应用。

    • breakPages: boolean (默认值: true),是否在页面中断处进行分页,如果启用,页面中断(如分页符)将被正确处理。

    • ignoreLastRenderedPageBreak: boolean (默认值: true),是否禁用最后一个渲染的页面中断,如果启用,最后一个页面中断将不会被处理。

    • experimental: boolean (默认值: false),是否启用实验性功能(如制表位计算),启用后,可以使用一些尚未完全稳定的高级功能。

    • trimXmlDeclaration: boolean (默认值: true),是否在解析 XML 文档之前移除 XML 声明,如果启用,XML 声明将被移除,以避免解析问题。

    • useBase64URL: boolean (默认值: false),是否将图像、字体等资源转换为 Base64 URL,如果启用,资源将被嵌入到 HTML 中,而不是使用 URL.createObjectURL。

    • renderChanges: boolean (默认值: false),是否启用实验性的文档更改渲染(如插入和删除),启用后,文档中的更改标记将被渲染。

    • renderHeaders: boolean (默认值: true),是否启用页眉的渲染,如果启用,文档中的页眉将被正确渲染。

    • renderFooters: boolean (默认值: true),是否启用页脚的渲染,如果启用,文档中的页脚将被正确渲染。

    • renderFootnotes: boolean (默认值: true),是否启用脚注的渲染,如果启用,文档中的脚注将被正确渲染。

    • renderEndnotes: boolean (默认值: true),是否启用尾注的渲染,如果启用,文档中的尾注将被正确渲染。

    • renderComments: boolean (默认值: false),是否启用实验性的评论渲染,启用后,文档中的评论将被渲染。

    • debug: boolean (默认值: false),是否启用额外的调试日志,启用后,将在控制台输出更多的调试信息,有助于问题排查。

示例

import { renderAsync } from 'your-render-library';const documentBlob = /* 获取 Word 文档的 Blob 数据 */;
const bodyContainer = document.getElementById('body-container');
const styleContainer = document.getElementById('style-container');const options = {className: 'docx',inWrapper: true,ignoreWidth: false,ignoreHeight: false,ignoreFonts: false,breakPages: true,ignoreLastRenderedPageBreak: true,experimental: false,trimXmlDeclaration: true,useBase64URL: false,renderChanges: false,renderHeaders: true,renderFooters: true,renderFootnotes: true,renderEndnotes: true,renderComments: false,debug: false
};renderAsync(documentBlob, bodyContainer, styleContainer, options).then((wordDocument) => {console.log('Document rendered successfully:', wordDocument);}).catch((error) => {console.error('Error rendering document:', error);});

预览 Word 文档示例代码

<template><div class="table"><el-table :data="tableData" header-align="center" border style="width: 100%"><el-table-column align="center" type="index" width="60" label="序号"></el-table-column><el-table-column align="center" prop="fileName" label="文件名" /><el-table-column align="center" prop="upTime" label="上传日期" width="200" /><el-table-column align="center" fixed="right" label="操作" width="120"><template slot-scope="scope"><el-button @click="previewDocx(scope.row)" type="text">预览</el-button></template></el-table-column></el-table><el-dialog width="68%" :visible.sync="isShow" :before-close="close" class="doxc_dialog" :show-close="false":close-on-press-escape="true"><div class="doxc_con" ref="doxc_con"></div></el-dialog></div>
</template><script>
import { previewFile } from "@/api/file";
import { timeStampToString } from "@/utils/utils";
import { getFilesList } from "@/api/file";
import { renderAsync } from 'docx-preview';
export default {name: "fileTable",data() {return {pageSizes: [10, 20, 50, 100],form: {pageSize: 10,pageNum: 1,},total: 0,tableData: [],isShow: false,}},created() {this.getFileName();},methods: {// 获取文件列表async getFileName() {const { code, data: resData } = await getFilesList(this.form);console.log('code, data::: ', code, resData);if (code === 200) {resData.forEach(item => {item.upTime = timeStampToString(item.upTime);});this.tableData = resData;}},// 预览 DOCX 文件async previewDocx({ fileName }) {try {const response = await previewFile(fileName);console.log('response::: ', response);let blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });this.isShow = true;// 使用 dialog 组件,需要弹窗完成后再渲染内容this.$nextTick(async () => {// 渲染预览await renderAsync(response,this.$refs.doxc_con,);});} catch (err) {console.error('Failed to preview file', err);}},close() {this.isShow = false;},}
}
</script><style lang="scss" scoped>
.table {width: 600px;
}.pagination {float: right;margin-top: 20px;
}::v-deep .doxc_dialog .el-dialog__body {padding: 0 !important;
}::v-deep .doxc_dialog .el-dialog {margin-top: 5vh !important;width: 595.3pt !important;
}::v-deep .doxc_dialog .el-dialog__header {display: none;
}::v-deep .doxc_dialog .docx-wrapper {padding: 0 !important;
}
</style>

实现效果

在这里插入图片描述

总结

本文介绍了如何在 Web 应用中实现 Word 文档的预览功能。后端使用 Express 框架提供文件列表和文件内容的 API 接口,前端使用 Vue.js 和 Element UI 组件库展示文件列表并实现预览功能。通过 mammothdocx-preview 库将 Word 文档转换为 HTML 并在新窗口或对话框中展示,确保了良好的用户体验和较高的预览质量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/59222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ 优先算法 —— 查找总价格为目标值的两个商品(双指针)

目录 题目 &#xff1a;查找总价格为目标值的两个商品 1. 题目解析 2. 算法原理 Ⅰ 暴力枚举 Ⅱ 双指针算法 3. 代码实现 暴力枚举 双指针算法 题目 &#xff1a;查找总价格为目标值的两个商品 1. 题目解析 题目截图&#xff1a; 这道题的一个关键的地方&#xff0c;它先…

【操作系统】基于环形队列的生产消费模型

这篇博客的重点在于代码实现&#xff0c;理论部分请看CSDN 一、单生产单消费 1.环形队列的实现 单生产单消费的情况下&#xff0c;我们只需要维护生产者和消费者之间的互斥和同步关系即可 将环形队列封装成一个类&#xff1a;首先给出整体框架&#xff0c;接着会说明每一个…

【Android】Activity组件通信

文章目录 1.使用Intent传递数据2.使用Bundle传递复杂数据3.startActivityForResult 和 onActivityResult4.使用ViewModel共享数据 在Android中&#xff0c;Activity之间的通信是一个常见且重要的任务。以下是一些常用的方法来实现Activity之间的数据传递和通信&#xff1a; 1.使…

如何在Linux环境中的Qt项目中使用ActiveMQ-CPP

文章目录 代码1&#xff1a;消费者代码2&#xff1a;生成者 之前在Linux下的qt程序中使用activeMQ的时候也是用了很多时间去研究&#xff0c;本来想的是好好记录一下&#xff0c;但是当时顾着写代码。很多细节也不想再去走一遍了。大概写一下怎么使用就行了。注意&#xff1a;一…

Qt QCheckBox、QPushButton和QRadioButton详解

QCheckBox&#xff08;复选框&#xff09; 功能&#xff1a;QCheckBox用于创建一个复选框控件&#xff0c;允许用户从多个选项中选择多个。 属性&#xff1a; checkable&#xff1a;决定复选框是否可以被选中或取消选中。checked&#xff1a;表示复选框当前的选中状态&#…

6、显卡品牌分类介绍:技嘉 - 计算机硬件品牌系列文章

技嘉科技是一家以主板、‌显卡在业界缔造无以撼动的地位的科技公司&#xff0c;‌其核心理念是「‌技术创新、‌质量稳定」‌的高标准。‌技嘉专注于关键技术研发&#xff0c;‌其经营范围涵盖家用、‌商用、‌电竞等多元科技领域。‌通过应用突破性的专利技术&#xff0c;‌技…

自编以e为底的指数函数exp,性能接近标准库函数

算法描述&#xff1a; (1). 先做自变量x的范围检查&#xff0c;对于双精度浮点数&#xff0c;自变量不能超出(-1022ln2, 1024ln2)(-708.39, 709.78)&#xff0c;否则exp(x)会溢出。对于单精度浮点数&#xff0c;自变量不能超出(-126ln2, 128ln2)(-87.33, 88.72). 自己使用此函数…

es安装拼音分词后Kibana出现内存错误

出现错误 今天在安装es的拼音分词器&#xff0c;并重启es容器后&#xff0c;登录Kibana无法使用&#xff0c;查询日志发现如下报错 Waiting until all Elasticsearch nodes are compatible with Kibana before starting saved objects migrations... | typelog timestamp2024…

前端react面试基础知识(II)

这些问题涵盖了 React 的很多核心概念和实际应用场景。下面是针对每个问题的详细回答&#xff1a; 1. **React 项目中&#xff0c;如何动态改变组件的 class 来切换样式?** 可以通过条件判断或者状态&#xff08;state&#xff09;来动态改变组件的 class。例如&#xff0c;使…

Day 42 || 完全背包、518. 零钱兑换 II 、 377. 组合总和 Ⅳ、70. 爬楼梯 (进阶)

完全背包 题目链接&#xff1a;卡码网第52题 思路&#xff1a;和之前01背包一样&#xff0c;但是物品可以无限放置&#xff0c;所以之前二维数组中的背包容量是倒序遍历的&#xff0c;现在可以正序遍历即可重复放入。 import java.util.Scanner; public class Main {public …

数据结构-二叉树中的递归

目录 前言 简单手撕二叉树 二叉树节点的求解 二叉树叶子节点的求解 二叉树高度 二叉树第K层节点的个数 二叉树查找值为X的节点 结束语 前言 在这里说声抱歉&#xff0c;好久没更新数据结构了&#xff0c;二叉树的相关内容还没有更新完&#xff0c;是小编的失职&#xff…

在基于AWS EC2的云端k8s环境中 搭建开发基础设施

中间件下载使用helm,这里部署的都是单机版的 aws-ebs-storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata:name: aws-ebs-storageclass provisioner: kubernetes.io/aws-ebs parameters:type: gp2 # 选择合适的 EBS 类型&#xff0c;如 gp2、io1…

Apache Calcite - 查询优化之自定义优化规则

RelOptRule简介 为了自定义优化规则&#xff0c;我们需要继承RelOptRule类。org.apache.calcite.plan.RelOptRule 是 Apache Calcite 中的一个抽象类&#xff0c;用于定义优化规则。优化规则是用于匹配查询计划中的特定模式&#xff0c;并将其转换为更优化的形式的逻辑。通过继…

2024网鼎杯青龙组wp:Crypto1

题目 附件内容如下 from Crypto.Util.number import * from secret import flag from Cryptodome.PublicKey import RSAp getPrime(512) q getPrime(512) n p * q d getPrime(299) e inverse(d,(p-1)*(q-1)) m bytes_to_long(flag) c pow(m,e,n) hint1 p >> (51…

Python 单元测试中的 Mocking 与 Stubbing:提高测试效率的关键技术

在软件开发过程中&#xff0c;单元测试是确保代码质量的重要环节。为了实现高效的单元测试&#xff0c;我们常常需要隔离待测试的代码与其外部依赖。这时候&#xff0c;Mocking&#xff08;模拟&#xff09;和 Stubbing&#xff08;桩&#xff09;技术就显得尤为重要。这两种技…

Golang | Leetcode Golang题解之第528题按权重随机选择

题目&#xff1a; 题解&#xff1a; type Solution struct {pre []int }func Constructor(w []int) Solution {for i : 1; i < len(w); i {w[i] w[i-1]}return Solution{w} }func (s *Solution) PickIndex() int {x : rand.Intn(s.pre[len(s.pre)-1]) 1return sort.Searc…

3D打印机 屏幕的固定挂钩断后的一次自己修复经历

引子 3D打印机的屏幕固定挂钩断了 这次确实不知道咋断的&#xff0c;这可咋办呢&#xff0c;到网上看了一下&#xff0c;一个屏幕要2佰多&#xff0c;有些小贵&#xff0c;要不就自己修修吧&#xff0c;打个挂钩按上&#xff0c;说干就干。 正文 freecad的设计图如下【其中各…

PHP合成图片,生成海报图,poster-editor使用说明

之前写过一篇使用Grafika插件生成海报图的文章&#xff0c;但是当我再次使用时&#xff0c;却发生了错误&#xff0c;回看Grafika文档&#xff0c;发现很久没更新了&#xff0c;不兼容新版的GD&#xff0c;所以改用了intervention/image插件来生成海报图。 但是后来需要对海报…

Java基于微信小程序的美食推荐系统(附源码,文档)

博主介绍&#xff1a;✌程序猿徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Linux的IP网路命令: 用于显示和操作网络接口(网络设备)的命令ip link详解

目录 一、概述 二、用法 1、基本语法 2、常用选项 3、常用参数 4、获取帮助 三、示例 1. 显示所有网络接口的信息 &#xff08;1&#xff09;命令 &#xff08;2&#xff09;输出示例 &#xff08;3&#xff09;实际操作 2. 启动网络接口 3. 停止网络接口 4. 更改…