在线预览 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);
        // response 是接口返回的二进制文件
        console.log('response::: ', response);
       // 如果后端返回没有设置文件 MIME 类型
       // 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 并在新窗口或对话框中展示,确保了良好的用户体验和较高的预览质量。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB笔记02-MongoDB基本常用命令

文章目录 一、前言二、数据库操作2.1 选择和创建数据库2.2 数据库的删除 3 集合操作3.1 集合的显式创建3.2 集合的隐式创建3.3 集合的删除 四、文档基本CRUD4.1 文档的插入4.1.1 单个文档插入4.1.2 批量插入 4.2 文档的基本查询4.2.1 查询所有4.2.2 投影查询&#xff08;Projec…

对称二叉树(力扣101)

题目如下: 思路 对于这道题, 我会采用递归的解法. 看着对称的二叉树, 写下判断对称的条件, 再进入递归即可. 值得注意的是, 代码中会有两个函数, 第一个是isSymmetric,第二个是judge. 因为这里会考虑到一种特殊情况, 那就是 二叉树的根结点(最上面的那个),它会单独用…

基于SSM的社区物业管理系统+LW参考示例

1.项目介绍 系统角色&#xff1a;管理员、业主&#xff08;普通用户&#xff09;功能模块&#xff1a;管理员&#xff08;用户管理、二手置换管理、报修管理、缴费管理、公告管理&#xff09;、普通用户&#xff08;登录注册、二手置换、生活缴费、信息采集、报事报修&#xf…

【pycharm jupyter】远程开发 启动报错

报错信息 upyter server process exited with code 1 ServerApp] A _jupyter_server_extension_points function was not found in jupyter_lsp. Instead, a _jupyter_server_extension_paths function was found and will be used for now. This function name will be depre…

软件设计师-上午题-12、13 软件工程(11分)

软件工程题号一般为17-19和29-36题&#xff0c;分值一般为11分。 目录 1 软件过程 1.1 CMM(能力成熟度模型) 1.1.1 真题 1.2 CMMI(能力成熟度模型集成) 1.2.1 真题 2 软件过程模型 2.1 瀑布模型 2.2 V模型 2.2.1 真题 2.3 增量模型 2.3.1 真题 2.4 演化模型 2.5 …

基于springboot得高校评教教师工作量管理系统设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

vue3 ref对象的width改变了,并不会直接去更新视图,但是触发obj.width++是可以正常更新视图的简单处理方法

1、服务器返回的是一个对象Obj&#xff0c;结构如下&#xff1a; {id: 999,width: 100,height:500, } 2、定义一个变量const objRef ref(); 3、视图&#xff1a; 4、这样是可以触发更新的&#xff1a; 说明&#xff1a;直接去更新这个width值&#xff0c;会自动触发这个div…

“立桩升量”,大智慧+通达信精品公式,上涨途中抓大趋势 源码无限制

使用技巧 立桩升量是指一只股票在上升途中&#xff0c;在相对低位放出一根阶段性的大量&#xff08;超越和覆盖掉前期的量&#xff09;&#xff0c;成交量呈现阶段性放大趋势&#xff0c;同时股票价格处于上涨趋势&#xff0c;以上两点缺一不可。特别强调的是&#xff0c;立桩…

Linux系统的文件系统和日志和管理

文件系统 stat命令 查看文件系统可用的inode号 平时涉及到的环境 inode号和文件名分离&#xff0c;使得linux会出现一下几个现象 模拟inode号耗尽的情况 分别创建ext4文件系统的sdb2和xfs文件系统的sdb3 ext4 因为inode号用完而导致无法创建文件 硬盘还有空间&#xff0c;但i…

SQL Server身份验证模式

SQL Server是一个广泛使用的关系数据库管理系统&#xff0c;通常使用两种身份验证模式&#xff1a;Windows身份验证和SQL Server身份验证。理解这些身份验证方式的概念与更改方式的操作&#xff0c;对于数据库管理员和开发者至关重要。本文将详细介绍身份验证方式的概念以及如何…

SpringBoot+FileBeat+ELK8.x版本收集日志

一、准备环境 1、ElasticSearch&#xff1a;8.1.0 2、FileBeat&#xff1a;8.1.0 3、Kibana&#xff1a;8.1.0 4、logstach&#xff1a;8.1.0 本次统一版本&#xff1a;8.1.0,4个组件&#xff0c;划分目录&#xff0c;保持版本一致。 说明&#xff1a;elasticsearch和kib…

tiktok付费广告效果说明经验分享

付费ROAS&#xff08;站内&#xff09; 付费ROAS&#xff08;Return On Advertising Spend&#xff09;是指归因于广告的付费事件所产生的总广告支出回报率。这是衡量广告效果的关键指标之一&#xff0c;能够反映广告投入与收益之间的比例关系。 计算公式&#xff1a; ROAS(流…

vue中实现列表无缝动态滚动

要想实现列表的动态无缝滚动&#xff0c;这里推荐两款组件&#xff0c;vue-seamless-scroll和vue3-seamless-scroll&#xff0c;组件的用法也非常简单&#xff0c;以下是使用方式。 vue2 vue2版本使用vue-seamless-scroll vue-seamless-scroll文档https://chenxuan0000.gith…

A Consistent Dual-MRC Framework for Emotion-cause Pair Extraction——论文阅读笔记

前言 这是我第一次向同学院同年级的学生和老师们汇报的第一篇论文,于2022年发表在TOIS上,属于CCF A类,主要内容是将MRC应用到情感原因对抽取中。 论文链接:用于情绪-原因对提取的一致双 MRC 框架 |信息系统上的 ACM Transactions 这里我就不放上我自己翻译的中文版还有我…

【docker】5. 背景知识(了解)

Docker 是什么 Docker 本质 Docker 本质其实是 LXC 之类的增强版&#xff0c;它本身不是容器&#xff0c;而是容器的易用工具。容器是 linux 内核中的技术&#xff0c;Docker 只是把这种技术在使用上简易普及了。Docker 在早期的版本其核心就是 LXC 的二次封装发行版。 Docke…

Pr 视频效果:ASC CDL

视频效果/颜色校正/ASC CDL Color Correction/ASC CDL ASC CDL ASC CDL效果通过对红、绿、蓝三个原色通道的独立调整&#xff0c;实现对图像色彩的精确控制。在此基础上&#xff0c;还可用于调整处理后图像的整体饱和度。 ◆ ◆ ◆ 效果选项说明 斜率 Slope、偏移 Offset和功…

linux之调度管理(1)-调度器的初始化

一、linux 启动内核时的第一个进程 init_task linux 进程的鼻祖 0,1,2&#xff0c;我在其他文章中&#xff0c;有具体讲解&#xff0c;链接是&#xff1a; linux 之0号进程、1号进程、2号进程_linux下0号进程 swap-CSDN博客。 在这里就不具体展开了。请看上面的文章详解。 当…

雷池社区版7.1新版本自定义NGINX配置分析

简单介绍雷池&#xff0c;是一款简单好用, 效果突出的 Web 应用防火墙(WAF)&#xff0c;可以保护 Web 服务不受黑客攻击。 雷池通过阻断流向 Web 服务的恶意 HTTP 流量来保护 Web 服务。雷池作为反向代理接入网络&#xff0c;通过在 Web 服务前部署雷池&#xff0c;可在 Web 服…

基于 AIGC 的糖尿病健康管理平台

项目介绍 Healthoney为一款以大语言模型为核心&#xff0c;通过分析用户的各项信息为用户生成定制化的饮食运动计划&#xff0c;提高糖尿病患者生活质量的智能健康管理平台。通过集成体重趋势预测、健康数据分析、食物热量查询、饮食计划个性化定制等功能&#xff0c;帮助用户…

qt QTableWidgetItem详解

1、概述 QTableWidgetItem 是 Qt 框架中的一个类&#xff0c;专门用于在 QTableWidget&#xff08;一个基于项的表格视图&#xff09;中表示单个单元格的内容。QTableWidget 继承自 QAbstractItemView&#xff0c;而 QTableWidgetItem 则作为表格中的一个单元格项&#xff0c;…