目标:实现一个输入框,可以输入变量与常规文字,组成一个计算公式,同时,变量要求高亮直观
HTML:
<div ref="formulaBoxRef" class="formulaBox" contenteditable="true" :v-html="formula" @blur="getFormula">
CSS:
.formulaBox {
padding: 10px 15px;
border: 1px solid #DCDFE6;
border-radius: 4px;
width: 100%;
caret-color: #3d7ecf;
line-height: 18px;
min-height: 34px;
}
这里只展示核心逻辑,其余代码以伪代码形式展示:
//项目框架:vue2
data(){
return{
saveRange:null,
formula:''
}
}
//在选择变量之前,记录光标之前所处的range
this.saveRange = range
//获取到变量名paraname后,调用addTag方法添加至输入框
this.addTag(paraname)
addTag(data) {
//对于有空格的样式字符串,建议直接写到html字符串中,在外面用变量接收的话,在实际使用中,style的双引号遇到空格会直接中断
let tag = `<input style="display:inline-block;height:18px;line-height:18px;background-color:#004ba6;color:white;padding:0 5px;border:1px solid #DCDFE6;border-radius:4px;text-align:center;margin:0 5px;" type="button" value=${data}>`
this.pasteHtmlAtCaret(tag)
},
pasteHtmlAtCaret(html) {
let sel, range
sel = window.getSelection()
if (sel && sel.rangeCount === 0 && this.savedRange !== null) sel.addRange(this.savedRange); // 保留光标在文字中间插入的最后位置
if (sel && sel.rangeCount) range = sel.getRangeAt(0);
if (['', null, undefined].includes(range)) {
// 如果div没有光标,则在div内容末尾插入
range = this.keepCursorEnd(true).getRangeAt(0);
}else{
const contentRange = document.createRange()
contentRange.selectNode(this.$refs.formulaBoxRef)
// 对比range,检查光标是否在输入范围内
const compareStart = range.compareBoundaryPoints(Range.START_TO_START, contentRange)
const compareEnd = range.compareBoundaryPoints(Range.END_TO_END, contentRange)
const compare = compareStart !== -1 && compareEnd !== 1
if (!compare) range = this.keepCursorEnd(true).getRangeAt(0);
}
let input = range.createContextualFragment(html);
// 记录插入input之后的最后节点位置,将光标位置重新定位到插入变量后的位置
let lastNode = input.lastChild;
range.insertNode(input)
if (lastNode) { // 如果有最后的节点
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
},
/**
* 将光标重新定位到内容最后
* isReturn 是否要将range实例返回
* */
keepCursorEnd(isReturn) {
this.$refs.formulaBoxRef.focus();
let sel = window.getSelection(); // 创建range
sel.selectAllChildren(this.$refs.formulaBoxRef); // range 选择obj下所有子内容
sel.collapseToEnd(); // 光标移至最后
if (isReturn) return sel;
},
//插入input标签形式的变量后,需要得到只包含文本形式的字符串
//div失焦后,将字符串中的input标签体字符串替换为对应的value
async getFormula(e){
let innerHTML = e.target.innerHTML
//匹配input标签形式的变量
const regex = /<input([^>]+)>/g;
const matchArr = innerHTML.toString().match(regex)
//没有变量,说明全是常规文本
if(!matchArr){
this.form.evaluationFormula = innerHTML
return
}
let result = innerHTML
matchArr.forEach(item => {
//匹配input标签的value值
const variableName = item.match(/value="([^"]+)"/)[1]
const variable = result.replace(item, variableName)
result = variable
})
// console.log('result', result);
this.form.evaluationFormula = result //这里的result就是我们需要的文本形式的字符串
},