记录-html-docs-js避坑指南
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
我们公司目前在做基于tiptap的在线协同文档,最近需要做导出 pdf、word 需求。
导出 word 文档使用的是html-docx-js-typescript,是用 typescript 重写了一下html-docx-js,可以看到最近的提交记录是 2016 年,貌似已经不维护了,很多 Issues 没人管。
实在找不到其他的 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).innerText
h.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.innerHTML
ul.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).innerHTML
content.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
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录-html-docs-js避坑指南的更多相关文章
- js避坑历险记
代码改变世界,世界改变码农,码农改变代码! 我就是我,我就是一个码农的武林. 前方JS巨坑出没,请注意集中力! 巨坑1:js精度问题 前段时间去一家物流公司面试,做了一个js题,印象尤为深刻: var ...
- Harmony OS 开发避坑指南——源码下载和编译
Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...
- 今天 1024,为了不 996,Lombok 用起来以及避坑指南
Lombok简介.使用.工作原理.优缺点 Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中,Lombok 提供了一组有用的注解,用来消除 Java 类中的大量样板代码. 目录 L ...
- electron 编译 sqlite3避坑指南---尾部链接有已经编译成功的sqlite3
electron 编译 sqlite3避坑指南(尾部链接有已经编译成功的sqlite3) sqlite很好用,不需要安装,使用electron开发桌面程序,sqlite自然是存储数据的不二之选,奈何编 ...
- CEF避坑指南(一)——下载并编译第一个示例
CEF即Chromium Embedded Framework,Chrome浏览器嵌入式框架.它提供了接口供程序员们把Chrome放到自己的程序中.许多大型公司,如网易.腾讯都开始使用CEF进行前端开 ...
- Canal v1.1.4版本避坑指南
前提 在忍耐了很久之后,忍不住爆发了,在掘金发了条沸点(下班时发的): 这是一个令人悲伤的故事,这条情感爆发的沸点好像被屏蔽了,另外小水渠(Canal意为水道.管道)上线一段时间,不出坑的时候风平浪静 ...
- Linux下Python3.6的安装及避坑指南
Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装Python3之前,需要将这些依赖库先行安装好. yum -y install zlib-dev ...
- Hive改表结构的两个坑|避坑指南
Hive在大数据中可能是数据工程师使用的最多的组件,常见的数据仓库一般都是基于Hive搭建的,在使用Hive时候,遇到了两个奇怪的现象,今天给大家聊一下,以后遇到此类问题知道如何避坑! 坑一:改变字段 ...
- Android连接远程数据库的避坑指南
Android连接远程数据库的避坑指南 今天用Android Studio连接数据库时候,写了个测试连接的按钮,然后连接的时候报错了,报错信息: 2021-09-07 22:45:20.433 705 ...
- .NET AsyncLocal 避坑指南
目录 AsyncLocal 用法简介 AsyncLocal 实现原理 AsyncLocal 的坑 AsyncLocal 的避坑指南 HttpContextAccessor 的实现原理 AsyncLoc ...
随机推荐
- [Java]HashMap与ConcurrentHashMap的一些总结
HashMap与ConcurrentHashMap的一些总结 HashMap底层数据结构 JDK7:数组+链表 JDK8:数组+链表+红黑树 JDK8中的HashMap什么时候将链表转为红黑树? 当发 ...
- 源码剖析Spring依赖注入:今天你还不会,你就输了
在之前的讲解中,我乐意将源码拿出来并粘贴在文章中,让大家看一下.然而,我最近意识到这样做不仅会占用很多篇幅,而且实际作用很小,因为大部分人不会花太多时间去阅读源码. 因此,从今天开始,我将采取以下几个 ...
- Js中的位操作符
Js中的位操作符 JavaScript的数字类型为双精度IEEE 754 64位浮点类型,但是在位运算中位运算符用于32位的数字上, 任何的数字操作都将转为32位, 运算结果再转化为Js数字类型. 描 ...
- postgresql常见开发技巧
1.数据类型 名字 描述 bigint 有符号 8 字节整数 bigserial 自增八字节整数 bit [ (n) ] 定长位串 bit varying [ (n) ] 变长位串 boolean 逻 ...
- vscode添加自定义html片段
最近在学vue,用的是微软的vscode 开发工具. 很不错,赞一下微软.里面包含了众多插件大家可以各取所需. 另外有一项实用的功能,User Snippets 用户自定义代码段, 对于那些需要重复编 ...
- SpringBoot中Redis的基础使用
基础使用 首先引入依赖 <!-- redis依赖--> <dependency> <groupId>org.springframework.boot</gro ...
- Qt实用技巧:QCustomPlot做北斗GPS显示绝对位置运动轨迹和相对位置运动轨迹图的时,使图按照输入点顺序连曲线
需求 使用QCustomPlot绘制多个目标的北斗运行轨迹图,包括累计绝对位置图和记录时刻的相对位置图. 当前绘制存在问题: 交付客户前,公司内部自测流程发现的问题. 实际预期效果为: ...
- 文件IO操作开发笔记(一):使用Qt的QFile对磁盘文件存储进行性能测试以及测试工具
前言 在做到个别项目对日志要求较高,要求并行写入的数据较多,尽管写入数据的线程放在子线程,仍然会造成界面程序的假死(实际上Qt还是在跑,只是磁盘消耗超过瓶颈,造成假死(注意:控制台还能看到打印输出 ...
- 【LeetCode贪心#08】根据身高重建队列(还是涉及处理两个维度的信息)
根据身高重建队列 力扣题目链接(opens new window) 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序).每个 people[i] = [hi ...
- 【Azure 应用服务】Azure Function App在部署时候遇见 503 ServiceUnavailable
问题描述 在VS Code中编写好 Azure Function App代码后,通过 func azure functionapp publish 部署失败,抛出 503 Service Unava ...
