富文本編輯器html內(nèi)容轉(zhuǎn)word:html-docs-js避坑指南

前言

我們公司目前在做基于tiptap的在線協(xié)同文檔术吗,最近需要做導(dǎo)出 pdf、word 需求珊拼。

導(dǎo)出 word 文檔使用的是html-docx-js-typescript础拨,是用 typescript 重寫了一下html-docx-js瘩绒,可以看到最近的提交記錄是 2016 年,貌似已經(jīng)不維護(hù)了,很多 Issues 沒人管。

html-docs-js.png

實(shí)在找不到其他的 html 轉(zhuǎn) word 的插件吐句,最后只能使用它來處理,我把我在使用過程中遇到的問題一一列出來店读,就有了這篇避坑指南。

使用說明

  • 安裝

    安裝html-docx-js-typescript攀芯,同時(shí)安裝FileSaver用于瀏覽器端保存文件屯断。

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

    參考官方示例

使用過程遇到的問題及處理方案

字體加粗不生效、字體背景顏色不生效處理

字體加粗<strong>和標(biāo)記文本元素<mark>標(biāo)簽需要替換為<b><span>標(biāo)簽

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

h1 - h6 標(biāo)題高度優(yōu)化及未同步 word 文檔標(biāo)題

我們文檔中的標(biāo)題對應(yīng)的 HTML 內(nèi)容長這樣

docs-h-style.png

需要將內(nèi)容轉(zhuǎn)換為類似<h1>xxx</h1>這樣侣诺,不然 word 中編輯時(shí)不能對應(yīng)標(biāo)題殖演,修改如下:

// 標(biāo)題高度和字體失效 需要設(shè)置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 = ''
    })
  )
}

圖片下多出一個(gè)白框

Prosemiror-images上傳圖片后,會在圖片后面生成.ProseMirror-separator這個(gè)標(biāo)簽年鸳,我們在導(dǎo)出時(shí)只需要刪除它即可趴久。

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

列表 ul、ol

在開始處理之前搔确,先介紹一個(gè)插入 DOM 的 API insertAdjacentElement彼棍。

在 vue、react 這些框架的盛行膳算,基本上我們已經(jīng)不會再用到 DOM 操作座硕,不過可以了解一下,萬一以后用得到呢涕蜂。

// 將給定元素element插入到調(diào)用的元素的某個(gè)位置
element.insertAdjacentElement(position, element)

參數(shù)position可以是以下位置

  • 'beforebegin': 插入元素之前华匾,類似 insertBefore
  • 'afterbegin': 插入元素第一個(gè) children 之前,類似 prepend
  • 'beforeend': 插入元素最后一個(gè) children 之后机隙,類似 appendChild
  • 'afterend': 插入元素之后蜘拉,類似 insertAfter

接著我們看一下列表這部分的修改,由于我們項(xiàng)目功能上的需求有鹿,列表是使用 div 標(biāo)簽來改造的旭旭,所以需要將 div 標(biāo)簽轉(zhuǎn)為 ul/ol,下面是我的實(shí)現(xiàn)

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))

  // 內(nèi)容區(qū)域
  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))

復(fù)選框 checkbox

復(fù)選框 checkbox 的處理印颤,首先考慮的是轉(zhuǎn)為<input type='checkbox' />來處理您机,結(jié)果轉(zhuǎn)完后并沒有顯示復(fù)選框;
接著又想著用 span 標(biāo)簽生成一個(gè)方框,<span style='width: 16px;height: 16px...' />际看,這樣總能顯示了吧咸产!結(jié)果依然不行。

正當(dāng)我想不到辦法的時(shí)候仲闽,突然靈機(jī)一動脑溢,可不可以把 word 轉(zhuǎn)成 html 后看看 checkbox 最終會顯示成啥樣呢?

于是通過在線 word 轉(zhuǎn) html將 word 轉(zhuǎn)為 html 后赖欣,看到復(fù)選框?qū)?yīng)的 html 內(nèi)容為<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)

轉(zhuǎn)成 word 后顶吮,復(fù)選框的選中和取消功能也能正常使用社牲。

附件導(dǎo)出、多維表等 iframe 內(nèi)容

參考了一下釘釘文檔


dingding-fujian.png

這樣就很好改了悴了,只需要把附件對應(yīng)的節(jié)點(diǎn)內(nèi)容搏恤,改為鏈接即可。

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)
})

未解決的部分

  • 表情無法導(dǎo)出湃交,這個(gè)我看了下其他在線協(xié)作文檔熟空,也有同樣的問題。

小結(jié)

其實(shí)搞莺,處理這些問題的方式也是很簡單息罗,因?yàn)閔tml-docs-js是用html字符串來作為導(dǎo)出文檔的輸入。如果導(dǎo)出后發(fā)現(xiàn)樣式不對的情況時(shí)才沧,我們只需要去修改html內(nèi)容即可迈喉。
如果有遇到像復(fù)選框checkbox這類不知道怎么解決的問題,也可以采用反推温圆,先通過word轉(zhuǎn)html弊添,然后看轉(zhuǎn)為html后的內(nèi)容,再去修改需要導(dǎo)出的html內(nèi)容捌木,這也不失為一種解決問題的方式油坝。
以上是我在使用html-docs-js插件時(shí)遇到的一些問題及處理方式,如果有遇到同樣問題的小伙伴刨裆,可以說下你們的處理方式澈圈。或者這里沒有提到的問題帆啃,也歡迎大家補(bǔ)充瞬女。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市努潘,隨后出現(xiàn)的幾起案子诽偷,更是在濱河造成了極大的恐慌坤学,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件报慕,死亡現(xiàn)場離奇詭異深浮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)眠冈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門飞苇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜗顽,你說我怎么就攤上這事布卡。” “怎么了雇盖?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵忿等,是天一觀的道長。 經(jīng)常有香客問我崔挖,道長这弧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任虚汛,我火速辦了婚禮,結(jié)果婚禮上皇帮,老公的妹妹穿的比我還像新娘卷哩。我一直安慰自己,他們只是感情好属拾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布将谊。 她就那樣靜靜地躺著,像睡著了一般渐白。 火紅的嫁衣襯著肌膚如雪尊浓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天纯衍,我揣著相機(jī)與錄音栋齿,去河邊找鬼。 笑死襟诸,一個(gè)胖子當(dāng)著我的面吹牛瓦堵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歌亲,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼菇用,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了陷揪?” 一聲冷哼從身側(cè)響起惋鸥,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤杂穷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后卦绣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耐量,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年迎卤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拴鸵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜗搔,死狀恐怖劲藐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情樟凄,我是刑警寧澤聘芜,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站缝龄,受9級特大地震影響汰现,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叔壤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一瞎饲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炼绘,春花似錦嗅战、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脚曾,卻和暖如春东且,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背本讥。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工珊泳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拷沸。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓旨椒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親堵漱。 傳聞我的和親對象是個(gè)殘疾皇子综慎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355