
老后臺(tái)管理系統(tǒng)里業(yè)務(wù)方最?lèi)?ài)提的一個(gè)需求就是Excel數(shù)據(jù)粘貼到網(wǎng)頁(yè)端的UEditor編輯框還要把邊框、底色、合并單元格都帶過(guò)來(lái)。很多人問(wèn)怎么配置ueditor才能支持這個(gè)操作。說(shuō)實(shí)話(huà)這問(wèn)題在社區(qū)里被問(wèn)過(guò)無(wú)數(shù)次官方文檔對(duì)Excel粘貼卻講得很少。UEditor作為一款經(jīng)典的富文本編輯器剪貼板處理邏輯默認(rèn)偏保守粘貼過(guò)來(lái)的一整張Excel表格經(jīng)常變成堆在一起的純文本或者表格進(jìn)來(lái)了但樣式被過(guò)濾得干干凈凈。這篇文章就把“ueditor配置Excel數(shù)據(jù)粘貼”這件事從頭到尾拆一遍重點(diǎn)落在配置項(xiàng)、過(guò)濾規(guī)則和常見(jiàn)坑上適合維護(hù)傳統(tǒng)后臺(tái)系統(tǒng)、或者還在用UEditor的老項(xiàng)目前端參考。1. 先把原理弄清楚Excel粘貼到編輯器到底發(fā)生了什么1.1 剪貼板里不只有純文本Excel中選中一片區(qū)域按CtrlC復(fù)制到剪貼板的內(nèi)容不一定只有文字。Windows下剪貼板會(huì)同時(shí)存好幾種格式純文本、帶制表符的CSV、HTML片段還可能有OLE富文本數(shù)據(jù)。瀏覽器執(zhí)行粘貼事件時(shí)contenteditable區(qū)域會(huì)優(yōu)先讀取HTML/富文本格式的數(shù)據(jù)因?yàn)橹挥羞@樣才能把“表格”這種結(jié)構(gòu)還原出來(lái)。UEditor的核心編輯區(qū)是一個(gè)iframe里的contenteditable區(qū)域所以當(dāng)用戶(hù)按下CtrlV時(shí)瀏覽器把剪貼板里的HTML片段插入進(jìn)去。Excel生成的HTML片段一般是一整段以table開(kāi)頭的結(jié)構(gòu)包含tr、td甚至還會(huì)帶上注釋語(yǔ)法。在Chrome里這個(gè)HTML通常還帶著inline style比如寬度、背景色、邊框樣式。聽(tīng)上去很美好但UEditor在接收這段HTML之后還會(huì)疊加好幾層自定義處理大部分“貼上就丟”的情況就發(fā)生在這個(gè)過(guò)程中。1.2 默認(rèn)情況下表格為什么總是“丟”UEditor對(duì)粘貼內(nèi)容不是直接接受的它默認(rèn)會(huì)走一次“清洗”流程beforepaste事件觸發(fā)UEditor拿到剪貼板的HTML根據(jù)pasteplain配置判斷是否要轉(zhuǎn)純文本對(duì)HTML做標(biāo)簽與屬性過(guò)濾filterInput移除敏感標(biāo)簽和不在白名單里的屬性最后再插入到編輯器DOM中。問(wèn)題就出在第2和第3步。如果ueditor.config.js里把pasteplain設(shè)成了true粘貼Excel必然變成純文本表格結(jié)構(gòu)全部丟失。就算pasteplain是false第3步的過(guò)濾規(guī)則如果不放行table/tr/td/th以及border/width/style這些屬性表格結(jié)構(gòu)即使沒(méi)被拆散也會(huì)被剝成一堆沒(méi)有邊框、沒(méi)有寬度的素表格。還要留意老版本一個(gè)容易誤踩的配置retainOnlyPastedText。這個(gè)參數(shù)在部分1.4.x版本或改造后的UEditor擴(kuò)展中用于控制粘貼時(shí)是否只保留文本。如果被誤開(kāi)和pasteplain效果類(lèi)似Excel內(nèi)容過(guò)來(lái)后同樣只剩純文本。排查時(shí)如果pasteplain已經(jīng)是false就要順手把這個(gè)也檢查一下。1.3 配置前先明確目標(biāo)效果動(dòng)手改配置之前建議先和業(yè)務(wù)方把“粘貼后要保留到什么程度”確認(rèn)清楚不然很容易返工。通常有三個(gè)檔次只要表格結(jié)構(gòu)粘貼后能分列、分行邊框樣式無(wú)所謂。這個(gè)最容易放行標(biāo)簽即可。保留基礎(chǔ)樣式要有邊框、背景色、合并單元格效果。需要在過(guò)濾白名單里保留style等屬性。完全還原Excel樣式包括字體、行高、列寬、顏色、條件格式。這個(gè)不建議通過(guò)粘貼配置解決因?yàn)閁Editor的編輯區(qū)不是Excel渲染引擎硬摳最終效果也達(dá)不到反而會(huì)拖慢編輯器。確定目標(biāo)后再去看配置項(xiàng)就能對(duì)號(hào)入座。多數(shù)后臺(tái)系統(tǒng)只需要第二個(gè)檔次也就是“保留表格結(jié)構(gòu)和基礎(chǔ)樣式”下面章節(jié)的配置也主要圍繞這個(gè)目標(biāo)展開(kāi)。2. 核心配置項(xiàng)拆解從 pasteplain 到過(guò)濾白名單2.1 第一個(gè)要檢查的開(kāi)關(guān)這個(gè)配置很容易被忽略。很多老項(xiàng)目從模板里抄配置時(shí)會(huì)寫(xiě)一行pasteplain: true意思就是“粘貼一律按純文本處理”。業(yè)務(wù)方之前吐槽“Excel粘貼不了”八成是這一個(gè)開(kāi)關(guān)的原因。需要在ueditor.config.js里確認(rèn)window.UEDITOR_CONFIG { // 是否啟用純文本粘貼。true表示任何粘貼內(nèi)容都會(huì)去掉格式 pasteplain: false };也可以在初始化時(shí)覆蓋var ue UE.getEditor(editor, { pasteplain: false });判斷是否生效最簡(jiǎn)單的方法復(fù)制一段帶加粗的文本粘貼后看strong標(biāo)簽還在不在。如果粗體還在說(shuō)明編輯器對(duì)粘貼的富文本做了保留如果成了一片沒(méi)有格式的純文本那pasteplain大概率被設(shè)置成了true或者在頁(yè)面其它JS里被動(dòng)態(tài)改過(guò)。toolbar里的pasteplain按鈕也要注意。UEditor默認(rèn)工具欄里如果放了“純文本粘貼”按鈕用戶(hù)在編輯器里點(diǎn)一下會(huì)動(dòng)態(tài)切換粘貼模式。這個(gè)切換結(jié)果不會(huì)改ueditor.config.js里的原值而是通過(guò)編輯器內(nèi)部execCommand(pasteplain)切換。所以用戶(hù)側(cè)操作造成的狀態(tài)排查時(shí)也要考慮進(jìn)去。2.2 給表格標(biāo)簽和屬性開(kāi)放行名單UEditor為了安全在ueditor.all.js里維護(hù)了一套xssFilterRules本質(zhì)是一個(gè)HTML標(biāo)簽屬性的白名單。粘貼進(jìn)來(lái)的HTML會(huì)經(jīng)過(guò)白名單過(guò)濾不在名單里的標(biāo)簽或?qū)傩詴?huì)被剝掉。這個(gè)環(huán)節(jié)是Excel表格樣式丟失的主要區(qū)域。默認(rèn)白名單里通常有table和td這些標(biāo)簽但屬性未必齊全。例如table上常見(jiàn)的border、cellpadding、cellspacingtd上的rowspan、colspan、align、valign幾乎所有元素上的style屬性默認(rèn)可能被過(guò)濾。配置思路不是把xssFilterRules整體關(guān)掉那是拿安全換方便在管理后臺(tái)里非常不可取。正確做法是給白名單補(bǔ)齊需要用到的屬性。假如你用的是1.4.x版本可以直接在初始化時(shí)這樣補(bǔ)UE.getEditor(editor, { pasteplain: false, xssFilterRules: { allowList: { table: [border, cellpadding, cellspacing, width, height, style], caption: [align, style], tr: [align, valign, style], td: [width, height, colspan, rowspan, align, valign, style], th: [width, height, colspan, rowspan, align, valign, style] } } });注意不同小版本的UEditor里xssFilterRules內(nèi)部字段名可能有差異有的版本是allowList有的版本是whiteList。動(dòng)手前先打開(kāi)你工程里的ueditor.all.js確認(rèn)一下實(shí)際字段名再用對(duì)應(yīng)名字覆蓋避免配置無(wú)效。另外還有一組filterTxtRules主要用于粘貼純文本時(shí)的字符級(jí)清洗和Excel表格樣式關(guān)系不大。排查時(shí)可以看一眼是否被改造過(guò)但正常情況下不需要?jiǎng)铀?.3 初始化參數(shù)、全局配置和動(dòng)態(tài)改法UEditor有兩層配置來(lái)源。第一層是ueditor.config.js里默認(rèn)導(dǎo)出的window.UEDITOR_CONFIG第二層是初始化時(shí)傳入的配置對(duì)象比如UE.getEditor(editor, { ... })。初始化參數(shù)會(huì)在內(nèi)部合并并覆蓋默認(rèn)配置所以在頁(yè)面里針對(duì)單個(gè)編輯器做差異化粘貼配置放在初始化參數(shù)里是最穩(wěn)的。有些情況下編輯器已經(jīng)初始化完不方便再加參數(shù)這時(shí)候可以用兩種方式動(dòng)態(tài)干預(yù)var ue UE.getEditor(editor, { pasteplain: false }); ue.ready(function() { // 運(yùn)行時(shí)再改配置 ue.setOpt({ pasteplain: false }); }); // 監(jiān)聽(tīng)粘貼事件做自定義處理 ue.addListener(beforepaste, function(type, html) { // 這里可以拿到即將插入的HTML做二次加工 });beforepaste事件是后期做定制最常用的入口也是后面清洗Excel樣式的主要戰(zhàn)場(chǎng)。把核心配置項(xiàng)理一張表排查時(shí)對(duì)照看很方便配置項(xiàng)作用常見(jiàn)錯(cuò)誤配置pasteplain是否默認(rèn)純文本粘貼為true時(shí)Excel粘貼必然變純文本xssFilterRules粘貼HTML的標(biāo)簽/屬性白名單缺style、border、colspan導(dǎo)致樣式和結(jié)構(gòu)丟失autoClearEmptyNode是否自動(dòng)清理空標(biāo)簽節(jié)點(diǎn)為true時(shí)空的td被刪除表格列對(duì)不齊initialStyle/ 頁(yè)面CSS編輯區(qū)默認(rèn)樣式不配置時(shí)Excel粘貼表格觀感不可控3. 實(shí)操把Excel數(shù)據(jù)粘貼功能完整配置起來(lái)3.1 最小可用配置從復(fù)制Excel到粘貼出表格下面這份配置我在實(shí)際項(xiàng)目里驗(yàn)證過(guò)可以直接對(duì)照著改。var ue UE.getEditor(editor, { pasteplain: false, // 保留表格結(jié)構(gòu)和基礎(chǔ)屬性 xssFilterRules: { allowList: { table: [border, cellpadding, cellspacing, width, style], tr: [align, valign, style], td: [width, height, colspan, rowspan, align, valign, style], th: [width, height, colspan, rowspan, align, valign, style] } }, // 避免粘貼后自動(dòng)清除空節(jié)點(diǎn) autoClearEmptyNode: false });這里特別提一下autoClearEmptyNode。UEditor默認(rèn)會(huì)清理內(nèi)容為空的節(jié)點(diǎn)但一個(gè)正常的Excel表格里經(jīng)常有空白單元格對(duì)應(yīng)的就是td/td。如果開(kāi)了空節(jié)點(diǎn)清理這些空格子會(huì)被直接刪除導(dǎo)致整個(gè)表格形狀移位。業(yè)務(wù)方反饋“粘貼后列對(duì)不齊”很多時(shí)候不是粘貼邏輯問(wèn)題而是這里把空td吞了。改完配置后做一次基礎(chǔ)驗(yàn)證打開(kāi)Excel復(fù)制一個(gè)3行4列的表格回到編輯器頁(yè)面按CtrlV。正常效果應(yīng)該是編輯區(qū)內(nèi)出現(xiàn)一個(gè)邊框可見(jiàn)的表格單元格可以點(diǎn)擊編輯行和列基本與Excel一致。3.2 處理Excel粘貼后遺留的Office私有樣式第一次配置成功的人下一個(gè)動(dòng)作往往是放大瀏覽器看效果然后發(fā)現(xiàn)表格上帶了很多奇怪的行內(nèi)樣式這些樣式大多來(lái)自Excel生成的HTML。比如td styleborder:solid #ccc 1.0pt;padding:5.0pt 5.0pt 5.0pt 5.0pt;mso-border-alt:solid windowtext .5pt;mso-padding-alt:0cm 5.4pt 0cm 5.4ptmso-開(kāi)頭的樣式是Office特有的瀏覽器不認(rèn)識(shí)還會(huì)讓保存后的HTML變得很長(zhǎng)。建議在beforepaste階段做一次輕量清洗把mso-私有屬性去掉同時(shí)把Excel給td加的一堆內(nèi)邊距統(tǒng)一成自己的樣式ue.addListener(beforepaste, function(type, html) { if (!html || !html.html) return; // 去掉 Office 私有樣式前綴屬性 html.html html.html.replace(/mso-[a-zA-Z0-9-]:([^;]);?/g, ); // 把Excel默認(rèn)的0.5pt邊框統(tǒng)一替換為常用1px邊框 html.html html.html.replace(/border:[^;]*;?/g, border:1px solid #ccc;); });注意beforepaste事件里的html參數(shù)是一個(gè)對(duì)象結(jié)構(gòu)在不同小版本里不完全一樣常見(jiàn)的處理是直接操作html.html。如果你用的版本拿不到html.html可以打印一下html對(duì)象看結(jié)構(gòu)或者改為監(jiān)聽(tīng)afterpaste在HTML插入后再用editor.body做DOM遍歷同樣能達(dá)到效果。但清洗放在beforepaste的好處是編輯器內(nèi)部還會(huì)再走一遍過(guò)濾不會(huì)把你處理過(guò)的內(nèi)容再加工一次。3.3 讓粘貼表格的顯示樣式可控Excel自帶的行內(nèi)樣式經(jīng)常會(huì)干擾頁(yè)面原有風(fēng)格所以工程里我更建議“粘貼后統(tǒng)一走一套表格CSS”。在ueditor.config.js里有initialStyle可以給編輯區(qū)域?qū)懭胱远x樣式或者直接在當(dāng)前頁(yè)面的樣式中覆蓋編輯區(qū)table/* 給編輯區(qū)里的表格一個(gè)默認(rèn)觀感 */ .edui-body-content table { border-collapse: collapse; width: 100%; } .edui-body-content table td, .edui-body-content table th { border: 1px solid #ddd; padding: 6px 8px; }配合上一步把Excel行內(nèi)樣式清掉表格就會(huì)按這套CSS渲染而不是各家Excel表格各長(zhǎng)各的樣。要是業(yè)務(wù)方要求保留合并單元格和背景色那就在beforepaste清洗時(shí)只清mso-*不要把background-color和colspan也剝掉。3.4 從粘貼到入庫(kù)的完整鏈路驗(yàn)證配置完以后不要只在編輯頁(yè)看到表格顯示正常就收工要回到“保存-取數(shù)-回顯”的完整鏈路里驗(yàn)證一遍。點(diǎn)保存時(shí)UEditor的getContent()會(huì)把編輯區(qū)HTML取出來(lái)Excel粘貼進(jìn)來(lái)的表格結(jié)構(gòu)會(huì)原樣輸出里面可能帶著style屬性。后端入庫(kù)前注意做一次HTML轉(zhuǎn)義和XSS過(guò)濾不要相信前端UEditor的過(guò)濾能擋住所有攻擊。尤其是帶著style的標(biāo)簽后端還是要按自己的安全策略再過(guò)一遍。回顯時(shí)把數(shù)據(jù)庫(kù)內(nèi)容直接塞回UEditor的初始內(nèi)容或者編輯時(shí)重新setContent()表格通常能正常顯示。如果發(fā)現(xiàn)回顯后表格越過(guò)編輯區(qū)域、寬度失控多半是保存的HTML里沒(méi)有外層容器約束檢查一下table的width是否被設(shè)置成了固定像素值必要時(shí)在getContent之后再統(tǒng)一替換為百分比。這一步驗(yàn)證很值得做。很多項(xiàng)目粘貼時(shí)看著正常一到數(shù)據(jù)回顯就不對(duì)最后查出問(wèn)題是線(xiàn)上代碼里的ueditor.config.js流傳版本和本地不一樣粘貼過(guò)濾規(guī)則根本沒(méi)有生效。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 粘貼Excel后全部變成純文本先查這四處看到這個(gè)現(xiàn)象按優(yōu)先級(jí)排查看編輯器工具欄上的“純文本粘貼”按鈕是否處于激活狀態(tài)。如果是再點(diǎn)一次解除。檢查ueditor.config.js里的pasteplain是否為false??错?yè)面里是否在編輯器初始化后又執(zhí)行過(guò)editor.execCommand(pasteplain)。如果都不是檢查是否有全局監(jiān)聽(tīng)把粘貼事件攔截了比如某些前端框架的防粘貼擴(kuò)展。這四處都查完基本能定位問(wèn)題。最隱蔽的是第三種有些項(xiàng)目里為了“統(tǒng)一編輯器行為”會(huì)在公共初始化腳本里強(qiáng)制開(kāi)啟純文本粘貼后來(lái)某個(gè)頁(yè)面需要Excel粘貼就在業(yè)務(wù)側(cè)寫(xiě)了pasteplain: false但公共腳本在初始化后又被調(diào)了一次把配置覆蓋回去了。4.2 表格結(jié)構(gòu)在但邊框和背景色沒(méi)了怎么辦多半是過(guò)濾白名單里沒(méi)有放行style或者放行了但清洗正則把background-color也一并清了。處理方式在xssFilterRules中給table/td/th/tr補(bǔ)上style屬性。如果你自己寫(xiě)了替換邊框的正則檢查正則是否過(guò)于激進(jìn)比如把整個(gè)style值都刪了。建議清洗時(shí)只針對(duì)mso-*不要用“把style整體刪掉”這種一刀切辦法。還有一個(gè)經(jīng)驗(yàn)Excel粘貼出來(lái)的表格邊框通常寫(xiě)在td上而不是table上。所以如果業(yè)務(wù)方反饋“只有外框、單元格之間沒(méi)有線(xiàn)”優(yōu)先檢查td的style是否被過(guò)濾而不是盯著table的border屬性。4.3 粘貼的表格把頁(yè)面撐破了寬度怎么控制Excel生成的HTML里table上經(jīng)常帶著固定總寬度比如width1000或者stylewidth:100.0pt這個(gè)寬度在小屏電腦上會(huì)直接撐破編輯區(qū)。建議在beforepaste里把table的width統(tǒng)一處理成百分比或者限制最大值ue.addListener(beforepaste, function(type, html) { if (!html || !html.html) return; html.html html.html.replace(/table([^]*)/g, function(match, attrs) { attrs attrs.replace(/width[^]*/g, ); attrs attrs.replace(/style([^]*)/g, function(s, style) { style style.replace(/width:\s*\d(pt|px)/g, width:100%); return style style ; }); return table attrs ; }); });這樣處理之后表格寬度跟著編輯區(qū)走不會(huì)再看Excel原始列寬。要注意的是單元格的寬度也要做類(lèi)似處理否則表格總寬度收住了個(gè)別td的固定像素寬仍然會(huì)把內(nèi)容擠變形。4.4 瀏覽器差異Firefox粘貼Excel效果不對(duì)怎么辦Excel粘貼在不同瀏覽器里的呈現(xiàn)差異很大。Chrome表現(xiàn)最直接粘貼出來(lái)的HTML基本可用Firefox對(duì)剪貼板富文本的支持弱一些用戶(hù)在Firefox里粘貼Excel有時(shí)只能拿到帶制表符的純文本表現(xiàn)為“看起來(lái)沒(méi)分列、全擠在一行”。這種情況不是UEditor配置能完全兜住的常見(jiàn)做法是引導(dǎo)用戶(hù)使用Chrome或者在粘貼后識(shí)別到內(nèi)容里沒(méi)有table但出現(xiàn)了多個(gè)Tab符自動(dòng)做一次\t到表格單元格的轉(zhuǎn)換ue.addListener(beforepaste, function(type, html) { var text html.html || ; if (text.indexOf(table) -1 text.indexOf(\t) -1) { // 按制表符拆行拆列拼一個(gè)簡(jiǎn)易表格 } });這個(gè)方案可以用來(lái)兜底Firefox用戶(hù)的體驗(yàn)但要注意如果用戶(hù)粘貼的是普通文本里面恰好有Tab符號(hào)可能會(huì)被誤判。建議加上確認(rèn)邏輯或者只對(duì)明確指定來(lái)源的粘貼內(nèi)容做轉(zhuǎn)換。4.5 二次編輯時(shí)表格樣式被重排別慌排查過(guò)濾白名單有的業(yè)務(wù)方會(huì)要求粘貼進(jìn)來(lái)的表格后續(xù)再次進(jìn)入編輯模式繼續(xù)改。UEditor在setContent()回填內(nèi)容時(shí)會(huì)走一次自己的過(guò)濾規(guī)則如果之前存入數(shù)據(jù)庫(kù)的HTML里包含了不在白名單里的屬性二次保存時(shí)這些屬性可能被剝掉。解決思路是回顯后如果發(fā)現(xiàn)表格樣式和第一次保存時(shí)不一致就把過(guò)濾白名單里缺的屬性補(bǔ)上再重新驗(yàn)證“保存-回顯-再保存”的完整往返過(guò)程。只在前端繞過(guò)過(guò)濾而不動(dòng)白名單下次編輯還會(huì)遇到同樣問(wèn)題。在我經(jīng)手的幾個(gè)老項(xiàng)目里最省心的做法其實(shí)是“粘貼時(shí)盡量保留結(jié)構(gòu)樣式交給編輯區(qū)CSS接管”。因?yàn)镋xcel的mso私有樣式真的很難控與其和它較勁不如只保留合并單元格、對(duì)齊這些關(guān)鍵信息邊框和底色讓頁(yè)面自己統(tǒng)一。這樣配置一次后面業(yè)務(wù)方不管貼什么樣的Excel都不會(huì)亂維護(hù)成本也低。