這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 我們公司目前在做基於tiptap的線上協同文檔,最近需要做導出 pdf、word 需求。 導出 word 文檔使用的是html-docx-js-typescript,是用 typescript 重寫了一下html-docx-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
插件時遇到的一些問題及處理方式,如果有遇到同樣問題的小伙伴,可以說下你們的處理方式。或者這裡沒有提到的問題,也歡迎大家補充。