vue 中 wangeditor 格式刷功能
去年我刚到公司的时候,旁边同事问我wangeditor怎么实现格式刷功能,我找了很多资料勉勉强强实现了一个,一直想写个文章记录下,拖到现在才写,真是老了呢。
新建一个js文件,暂且命名为formatBrush.js,直接上代码:
1 // 参考资料 2 // https://www.jianshu.com/p/13dca2711b6e 3 // https://juejin.cn/post/6844903737161433102 4 5 import wangEditor from "wangeditor"; 6 7 const { BtnMenu } = wangEditor; 8 // 第一,菜单 class ,Button 菜单继承 BtnMenu class 9 class FormatBrushMenu extends BtnMenu { 10 constructor(editor) { 11 // data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述 12 // 图表直接用的element-ui的图标,请自行调整 13 const $elem = wangEditor.$( 14 `<div class="w-e-menu el-icon-s-open" data-title="格式刷"> 15 </div>` 16 ); 17 super($elem, editor); 18 const me = this; 19 me.editor = editor; 20 // 监听编辑器鼠标释放事件 21 editor.$textElem.on("mouseup", () => { 22 // 如果格式刷功能出于激活状态 23 if (me._active) { 24 // 延迟执行,避免获取不到正确的元素 25 setTimeout(() => { 26 // 复制格式刷样式 27 pasteStyle(editor); 28 // 取消格式刷激活样式 29 me.unActive(); 30 }, 100); 31 } 32 }); 33 } 34 // 菜单点击事件 35 clickHandler() { 36 const me = this; 37 const editor = me.editor; 38 if (me._active) { 39 // 已经在激活状态时取消激活 40 me.unActive(); 41 // 清空格式刷样式数据 42 editor.copyStyleList = [] 43 } else { 44 // 没有选中则终端 45 if (editor.selection.isSelectionEmpty()) return; 46 // 激活按钮 47 me.active(); 48 // 获取格式刷样式 49 const domToParse = 50 editor.selection.getSelectionContainerElem().elems[0]; 51 const copyStyleList = parseDom(domToParse); 52 // 保存格式刷样式 53 editor.copyStyleList = copyStyleList; 54 } 55 } 56 tryChangeActive() { } 57 } 58 59 // 菜单 key ,各个菜单不能重复 60 const menuKey = "formatBrush"; 61 62 // 注册菜单 63 wangEditor.registerMenu(menuKey, FormatBrushMenu); 64 65 // 复制选中dom的样式 66 function parseDom(dom) { 67 let targetDom = null; 68 const nodeArray = []; 69 70 getTargetDom(dom); 71 72 getAllStyle(targetDom); 73 74 function getTargetDom(dom) { 75 for (const i of dom.childNodes) { 76 if (i.nodeType === 3 && i.nodeValue && i.nodeValue.trim() !== "") { 77 targetDom = dom; 78 return; 79 } 80 } 81 getTargetDom(dom.children[0]); 82 } 83 84 function getAllStyle(dom) { 85 if (!dom) return; 86 const tagName = dom.tagName.toLowerCase(); 87 if (tagName === "p") { 88 nodeArray.push({ 89 tagName: "span", 90 attributes: Array.from(dom.attributes).map((i) => { 91 return { 92 name: i.name, 93 value: i.value 94 }; 95 }) 96 }); 97 return; 98 } else { 99 nodeArray.push({ 100 tagName: tagName, 101 attributes: Array.from(dom.attributes).map((i) => { 102 return { 103 name: i.name, 104 value: i.value 105 }; 106 }) 107 }); 108 getAllStyle(dom.parentNode); 109 } 110 } 111 return nodeArray; 112 } 113 114 function addStyle(text, nodeArray) { 115 let currentNode = null; 116 nodeArray.forEach((ele, index) => { 117 const node = document.createElement(ele.tagName); 118 for (const attr of ele.attributes) { 119 node.setAttribute(attr.name, attr.value); 120 } 121 if (index === 0) { 122 node.innerText = text; 123 currentNode = node; 124 } else { 125 node.appendChild(currentNode); 126 currentNode = node; 127 } 128 }); 129 return currentNode; 130 } 131 132 // 粘贴 133 function pasteStyle(editor) { 134 // 获取格式刷保存的样式 135 const copyStyleList = editor.copyStyleList; 136 // 有样式说明格式刷被激活 137 if (copyStyleList) { 138 // 获取当前选中内容 139 // 如果没选中也会执行,再次使用需要重新激活格式刷功能 140 const text = editor.selection.getSelectionText(); 141 const targetDom = addStyle(text, copyStyleList); 142 editor.cmd.do("insertHTML", targetDom.outerHTML); 143 // 清空格式刷样式 144 editor.copyStyleList = null; 145 } 146 }
vue组件代码如下,基于ts(没用ts的自己改改吧,话说vue3这种写法就不行了呀,组合式api还是好用):
1 <template> 2 <div class="editor-el"> 3 <div></div> 4 <el-input v-show="false" v-model="value"></el-input> 5 </div> 6 </template> 7 8 <script lang="ts"> 9 // 引入 wangEditor 10 import wangEditor from "wangeditor"; 11 import { Component, Vue, Model, Emit, Watch } from "vue-property-decorator"; 12 import "./formatBrush"; 13 @Component({}) 14 export default class servicesNoticeEdit extends Vue { 15 // v-model绑定值 16 @Model("valuechange", { type: String }) value!: String; 17 @Emit("valuechange") setValue() {} 18 19 @Watch("value", { immediate: true }) updateValue(v: string) { 20 if (v) { 21 const me = this as any; 22 if (me.isUp) { 23 // 这里通过标识符避免重复触发事件 24 me.isUp = false; 25 } else { 26 me.$nextTick(() => { 27 // 重新设置编辑器内容 28 me.editor.txt.html(v); 29 }); 30 } 31 } 32 } 33 editor: any = null; 34 // 标识是否正在设置数据,避免重复触发事件 35 isUp: Boolean = false; 36 mounted() { 37 const me = this as any, 38 // eslint-disable-next-line new-cap 39 editor = new wangEditor(me.$el.firstChild) as any; 40 // zIndex配置小点,避免影响其他组件 41 editor.config.zIndex = 100; 42 // 配置 onchange 回调函数,将数据同步到 vue 中 43 editor.config.onchange = (newHtml: string) => { 44 me.isUp = true; 45 me.setValue(newHtml); 46 }; 47 48 // 创建编辑器 49 editor.create(); 50 me.editor = editor; 51 } 52 beforeDestroy() { 53 // 调用销毁 API 对当前编辑器实例进行销毁 54 this.editor.destroy(); 55 this.editor = null; 56 } 57 } 58 </script> 59 <style scoped lang="scss"> 60 ::v-deep.editor-el { 61 // 格式刷功能激活图标样式 62 .w-e-menu.w-e-active { 63 color: red; 64 } 65 } 66 </style>
效果大概是这样