前端实现简单富文本输入框及校验四则运算表达式是否合法
一、实现思路:
- 富文本框实现
contentEditable:contentEditable属性非常强大,很多富文本编辑器都是依赖该属性实现,通过将html标签的contentEditable设置为true可将任意标签设置为可编辑标签,在标签内输入任意html文档。
文本编辑器:当然也有现成的富文本编辑器本身就在带@呼出列表的功能,但是存在两个问题:一是富文本编辑器太重,我们的需求只需要到富文本编辑器中的很少一部分功能;二是富文本编辑器存在一个问题(至少wangedit),编辑器内部是通过伪元素的方式实现退格删除整个标签,但是没有解决退格删除标签时导致光标输入光标定位不准确问题,在网上查阅了很多实现方案后,在Stack Overflow上找到另外一种方式实现标签的效果,那就是使用input标签来替代伪元素。将input标签的type设置为button,再修改样式去掉背景边框等,即可实现标签效果,退格删除也会删除整个input元素。
2. 校验四则运算的合法性
最初是希望通过正则表达式来校验公式的合法性,但是由于规则过于复杂,最终选了使用js的eval函数,利用eval函数解析和执行代码的功能,将式子传入进去,如果执行成功,则表示式子合法,如果报语法错误,则意味着公式不合法。
二、代码片段
<div ref="editor" :contentEditable="true" @keydown="enterEv($event)" @click="onClickEditor" @input="inputChange($event)" @οnpaste="() => false"/>
监听键盘事:
// keydown事件 enterEv(e) { if (e.key === '$') { // 如果是$,阻止输入,弹出选择弹窗 e.preventDefault(); this.setRecordCoordinates(); // 保存当前光标的坐标,选择之后要插入到当前位置 this.showSelectPop(); // 展示选择弹窗 } else { const mathReg = /^[0-9.+\-*/() ]|(Backspace)|(ArrowLeft)|(ArrowRight)|(ArrowDown)|(ArrowUp)|(Shift)+$/; if (!mathReg.test(e.key)) { e.preventDefault(); } } },
获取当前光标位置:
// 获取当前光标坐标 setRecordCoordinates() { try { // getSelection() 返回一个 Selection 对象,表示用户选择的文本范围或光标的当前位置。 const selection = window.getSelection(); this.lightPosition = { range: selection.getRangeAt(0), // 返回range对象 selection: selection, }; } catch (error) { console.log(error, '获取光标位置失败'); } },
选择插入的项之后,生成对应的节点并插入到保存的光标位置:
(这里需要注意,在进行点击选择某一项的时候,当前光标会自动定位到该位置,所以我们需要取出上一步保存的位置,将光标恢复到该位置,然后插入生成的input节点)
createSelectElement({name, uuid}) { const {range, selection} = this.lightPosition; // 生成需要显示的内容 const inputNode = document.createElement('input'); inputNode.value = `\${${name}}`; // $的文本信息 inputNode.type = 'button'; inputNode.dataset.id = uuid; // 用户ID、为后续解析富文本提供 // spanNodeFirst.contentEditable = false // 当设置为false时,富文本会把成功文本视为一个节点。 inputNode.className = 'tag'; const frag = document.createDocumentFragment(); frag.appendChild(inputNode); if (range) { range.insertNode(frag); } this.showPop = false; /* 1. 点击选择插入内容时selection会重新设置range,为了重新将光标定位到插入内容后方,需要清除range,启用已保存的range, 2. 由于原先range为未选状态,设置rang的位置被插入的内容所替换,所以重置range后插入内容会处于选择状态,调用setRecordCoordinates取消当前选区,并把光标定位在原选区的最末尾处。 3. 更新存储的光标信息 */ this.$nextTick(() => { selection.removeAllRanges(); // 移除当前光标 selection.addRange(range); // 还原光标位置 selection.collapseToEnd(); // addRange之后光标处于选中状态,需要将光标移动至最末端 this.setRecordCoordinates(); // 更新存储的光标信息 this.inputChange(); }); },
每次点击事件也要更新光标位置:
// 点击事件的触发函数,每次点击获取更新坐标 onClickEditor() { this.setRecordCoordinates(); },
弹窗代码:
弹窗实现比较简单,只需要在选择项之后调用createSelectElement函数生成节点并关闭自身即可。
公式校验:
把最终得到的式子中的变量替换为数字1,然后传入eval函数
// 校验公式合法性,利用eval函数能够执行计算式子的特性去校验 const REG = /(\$\{.+?\})/g; const strList = str.split(REG); const newList = strList.map(str => { return REG.test(str) ? 'a' : str; }); try { const res = eval('let a = 1;' + newList.join('')); if (res === Infinity) { this.$hMessage.warning('请检查计算公式是否正确'); return false; } } catch (e) { this.$hMessage.warning('请检查计算公式是否正确'); return false; }