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>

效果大概是这样

 

posted @ 2022-03-05 12:07  魔狼再世  阅读(994)  评论(0编辑  收藏  举报