记录-html-docs-js避坑指南

news/2024/11/15 3:19:15/

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

我们公司目前在做基于tiptap的在线协同文档,最近需要做导出 pdf、word 需求。

导出 word 文档使用的是html-docx-js-typescript,是用 typescript 重写了一下html-docx-js,可以看到最近的提交记录是 2016 年,貌似已经不维护了,很多 Issues 没人管。

html-docs-js.png

实在找不到其他的 html 转 word 的插件,最后只能使用它来处理,我把我在使用过程中遇到的问题一一列出来,就有了这篇避坑指南。

使用说明

  • 安装

    安装html-docx-js-typescript,同时安装FileSaver用于浏览器端保存文件。

npm install html-docx-js-typescript file-saver --save-dev
npm install @types/html-docx-js @types/file-saver --dev
  • 使用方法

    参考官方示例

使用过程遇到的问题及处理方案

字体加粗不生效、字体背景颜色不生效处理

字体加粗<strong>和标记文本元素<mark>标签需要替换为<b><span>标签

const innerHtml = cloneEle.innerHTML// strong在word中不生效问题.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>')// 背景色不生效问题.replace(/<mark/g, '<span').replace(/<\/mark>/g, '</span>')

h1 - h6 标题高度优化及未同步 word 文档标题

我们文档中的标题对应的 HTML 内容长这样

 需要将内容转换为类似<h1>xxx</h1>这样,不然 word 中编辑时不能对应标题,修改如下:

// 标题高度和字体失效 需要设置lineHeight和fontWeight
const handleLevelStyle = (cloneEle: HTMLElement) => {Array.from({ length: 6 }).forEach((_, index) =>(cloneEle.querySelectorAll(`h${index + 1}`) as unknown as HTMLElement[]).forEach((h) => {h.innerText = (h.children[0] as HTMLElement).innerTexth.style.fontSize = ''}))
}

图片下多出一个白框

Prosemiror-images上传图片后,会在图片后面生成.ProseMirror-separator这个标签,我们在导出时只需要删除它即可。

const removeWhiteBox = (cloneEle: HTMLElement) => {const separators: NodeListOf<Element> = cloneEle.querySelectorAll('.ProseMirror-separator')separators.forEach((separator) =>separator.parentElement?.removeChild(separator))
}

列表 ul、ol

在开始处理之前,先介绍一个插入 DOM 的 API insertAdjacentElement。

在 vue、react 这些框架的盛行,基本上我们已经不会再用到 DOM 操作,不过可以了解一下,万一以后用得到呢。

// 将给定元素element插入到调用的元素的某个位置
element.insertAdjacentElement(position, element)

参数position可以是以下位置

  • 'beforebegin': 插入元素之前,类似 insertBefore
  • 'afterbegin': 插入元素第一个 children 之前,类似 prepend
  • 'beforeend': 插入元素最后一个 children 之后,类似 appendChild
  • 'afterend': 插入元素之后,类似 insertAfter

接着我们看一下列表这部分的修改,由于我们项目功能上的需求,列表是使用 div 标签来改造的,所以需要将 div 标签转为 ul/ol,下面是我的实现

const changeDiv2Ul = (div: HTMLElement | Element, parent?: HTMLElement | Element) => {const kind = div.getAttribute('data-list-kind')const ul = kind === 'ordered' ? document.createElement('ol') : document.createElement('ul')const li = document.createElement('li')// 去除margin 不然在word中会偏移!parent && (ul.style.margin = '0')li.innerHTML = div.innerHTMLul.appendChild(li)parent ? parent.insertAdjacentElement('afterend', ul) : div.insertAdjacentElement('afterend', ul)div.parentElement?.removeChild(div)li.querySelectorAll('.list-marker').forEach((marker) => marker.parentElement?.removeChild(marker))// 内容区域li.querySelectorAll('.list-content').forEach((content) => {const span = document.createElement('span')span.innerHTML = (content.firstChild as HTMLElement).innerHTMLcontent.insertAdjacentElement('beforebegin', span)if (content.querySelectorAll('.prosemirror-flat-list').length) {content.querySelectorAll('.prosemirror-flat-list').forEach((div) => changeDiv2Ul(div, content))}content.parentElement?.removeChild(content)})
}
cloneEle.querySelectorAll('.prosemirror-flat-list').forEach((div) => changeDiv2Ul(div))

复选框 checkbox

复选框 checkbox 的处理,首先考虑的是转为<input type='checkbox' />来处理,结果转完后并没有显示复选框;

接着又想着用 span 标签生成一个方框,<span style='width: 16px;height: 16px...' />,这样总能显示了吧!结果依然不行。

正当我想不到办法的时候,突然灵机一动,可不可以把 word 转成 html 后看看 checkbox 最终会显示成啥样呢?

于是通过在线 word 转 html将 word 转为 html 后,看到复选框对应的 html 内容为<span style="color:#333333; font-family:'Wingdings 2'; font-size:11pt"></span>,改一下吧。

const span = document.createElement('span')
span.innerHTML = `<span style="color:#333333; font-family:'Wingdings 2'; font-size:11pt"></span>`
marker.insertAdjacentElement('beforebegin', span)
marker.parentElement?.removeChild(marker)

转成 word 后,复选框的选中和取消功能也能正常使用。

附件导出、多维表等 iframe 内容

参考了一下钉钉文档

这样就很好改了,只需要把附件对应的节点内容,改为链接即可。

cloneEle.querySelectorAll('.attachment-node-wrap').forEach((attach) => {const title = `请至One文档查看附件《${attach.getAttribute('name')}》`const anchorId = attach.parentElement?.getAttribute('data-id')const a = document.createElement('a')a.target = '_blank'a.href = `${location.href}&anchor=${anchorId}`a.innerHTML = `<span>${title}</span>`attach.insertAdjacentElement('beforebegin', a)attach.parentElement?.removeChild(attach)
})

未解决的部分

  • 表情无法导出,这个我看了下其他在线协作文档,也有同样的问题。

小结

其实,处理这些问题的方式也是很简单,因为html-docs-js是用html字符串来作为导出文档的输入。如果导出后发现样式不对的情况时,我们只需要去修改html内容即可。

如果有遇到像复选框checkbox这类不知道怎么解决的问题,也可以采用反推,先通过word转html,然后看转为html后的内容,再去修改需要导出的html内容,这也不失为一种解决问题的方式。

以上是我在使用html-docs-js插件时遇到的一些问题及处理方式,如果有遇到同样问题的小伙伴,可以说下你们的处理方式。或者这里没有提到的问题,也欢迎大家补充。

本文转载于:

https://juejin.cn/post/7220244579671916604

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 


http://www.ppmy.cn/news/41085.html

相关文章

ffmpeg的滤镜

FFmpeg 是一款开源的跨平台音视频处理工具&#xff0c;它提供了众多功能强大的滤镜用于视频/音频的加工处理。其中&#xff0c;滤镜(Filter)是 FFmpeg 中一个十分重要且常用的组件&#xff0c;它们可以实现对视频和音频的各种操作和变化&#xff0c;如转码、编解码、剪裁、裁剪…

深入探究Hugging Face中的BertModel类

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

4.9(预习)部分

LoadLibraryA GetProcAddress 编写代码&#xff0c;获取 VirtualAlloc 并且调用成功 #include <windows.h> #include <wchar.h> #include <string.h> #include <stdio.h> #include<libloaderapi.h> int main() {HMODULE hModule0;FARPROC space…

基于springboot的在线考试系统源码数据库论文

目 录 目 录 第一章 概述 1.1研究背景 1.2 开发意义 1.3 研究现状 1.4 研究内容 1.5论文结构 第二章 开发技术介绍 2.1 系统开发平台 2.2 平台开发相关技术 2.2.1 Java技术 2.2.2 mysql数据库介绍 2.2.3 MySQL环境配置 2.2.4 B/S架构 2.2.5 Spr…

ES6 Symbol的介绍与创建

ES6 Symbol的介绍与创建 ES6 引入了一种新的原始数据类型 Symbol&#xff0c;表示独一无二的值。它是JavaScript语言的第七种数据类型。 u &#xff1a;undefined s &#xff1a;string symbol o &#xff1a;object n &#xff1a;null number b &#xff1a;boolean Symbol…

springboot的rest服务配置服务的根路径

如果不配置默认为空&#xff0c;如下是application.yml文件只配置了端口号 server:port: 6868 那么访问时直接访问服务即可 如果配置了rest服务 RestController RequestMapping("/netLicense") public class NetLicenseController {RequestMapping("/getLice…

分布式计算技术(下):Impala、Apache Flink、星环Slipstream

实时计算的发展历史只有十几年&#xff0c;它与基于数据库的计算模型有本质区别&#xff0c;实时计算是固定的计算任务加上流动的数据&#xff0c;而数据库大多是固定的数据和流动的计算任务&#xff0c;因此实时计算平台对数据抽象、延时性、容错性、数据语义等的要求与数据库…

用idea操作hbase数据库,并映射到hive

依赖条件&#xff1a;需要有Hadoop&#xff0c;hive&#xff0c;zookeeper&#xff0c;hbase环境映射&#xff1a;每一个在 Hive 表中的域都存在于 HBase 中&#xff0c;而在 Hive 表中不需要包含所有HBase 中的列。HBase 中的 RowKey 对应到 Hive 中为选择一个域使用 :key 来对…