我又来总结vue+elelmentUI项目中遇到的问题了,话不多说请看下文。
一、功能需求
- 在一个编辑框内根据光标的位置来添加对应的内容,该内容不能重复添加
- 添加完之后可以整体删除该内容块
- 当然这个编辑框是可以在任意位置输入的
二、思路:
-
先写一个编辑框的组件,也可以不封装,看业务需求了
-
点击按钮时获取光标位置,如果没有光标位置则将光标位置一到文本内容的最后面
-
点击按钮追加内容时,要拼接一个标签如span标签,这样可以实现整体删除该内容块
-
拼接完之后,点击追加则获取光标的当前位置。
-
然后该内容就追加进去了
-
其实这个效果就是类似于微信聊天框输入一段文字后,选择某一个位置然后添加表情
-
接下来使用的api: getSelection
-
效果图
三、代码来了,请细细品尝
(1)组件html代码操作
<div class="cursorAppend">
// 编辑框组件
<EditText ref="editText" class="insertData" :contentHtml="contentHtml" @changeHtml="changeHtml"></EditText>
// 要添加的内容
<el-popover
placement="left"
:title="title"
width="300"
v-model="visibleObj.visibleTit"
trigger="click"
@hide="popoverHide">
<span class="divider"></span>
<div class="evtSelectItem"
draggable='true'
v-for="(item, index) in conList"
:class="{evtConActive: evtConIndex == index}" :key="index"
@click="evtSelectItem(index, item)">
{{item.contentName}}
</div>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="visibleObj.visibleTit = false">取消</el-button>
<el-button type="primary" size="mini" @click="visibleObj.visibleTit = false">确定</el-button>
</div>
<i slot="reference" draggable='true' @click="showAddData" class="el-icon-search taskCursor"></i>
</el-popover>
</div>
(2)点击搜索按钮,没有光标时,使追加内容放到后面
// 展示要追加的数据弹框
showAddData() {
// 点击搜索按钮,没有光标时,使追加内容放到后面
let ele = this.editText;
ele.focus();
var range = document.createRange();
range.selectNodeContents(ele);
range.collapse(false);
var sel = window.getSelection();
//判断光标位置,如不需要可删除
if(sel.anchorOffset!=0){
return;
};
sel.removeAllRanges();
sel.addRange(range);
},
主要是下面这个方法,这是我搜索到的方法,进行了一些改动,忘了是哪个链接了,在此借用一下了。
(3)根据光标位置插入内容块,便于删除所添加的内容块
// 根据光标位置插入内容
insertHtmlAtCaret(html, str){
let _this = this;
let dom = this.parseDom(html)[0];
let element = this.editText;
let textNode = document.createTextNode(str)
const doc = element.ownerDocument || element.document
const win = doc.defaultView || doc.parentWindow
const sel = win.getSelection();
let caretOffset = 0;
let range;
if (sel) {
// IE9 and non-IE
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.collapse(false);
range.deleteContents();
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
let el = document.createElement("div");
el.appendChild(dom)
var frag = document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
if (lastNode) {
range = range.cloneRange();
let nodeHtml = document.createTextNode(element.innerText)
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
document.selection.createRange().pasteHTML(dom);
}
},
(4)通过下面的方法将追加的内容字符串转成数据,方便去除重复项以及传给后端,不要传带有span标签的字符串,不然很容易超出字段长度限制
getAddData(contentHtml) {
// console.log('contentHtml', contentHtml)
let con = [];
if (contentHtml) {
let list = contentHtml.split(/<span (.*?)<\/span>/g);
list.forEach(function(e) {
let obj = {};
if (e.indexOf("data-json") > "-1") {
obj.type = "tag";
obj.contentCode = JSON.parse(
e
.replace(/data-json="(.*)" (.*) (.*)/g, "$1")
.replace(/"/g, '"')
);
obj.value = e.replace(/(.*)\[(.*)\](.*)/g, "$2");
} else if (e != "" && e != " ") {
obj.type = "string";
obj.value = e.replace(/ /g, "");
}
if (Object.keys(obj).length) {
con.push(obj);
}
});
}
// console.log('con',con)
return con;
},
parseDom(arg) {
var objE = document.createElement("div");
objE.innerHTML = arg;
return objE.childNodes;
},
(5) 需要转一下节点类型,点击追加时是个字符串所以需要通过该方法parseDom转成node对象,不然就会报以下的错误
结语
我的表演结束,可能过程中有些许不足,希望大家多多指点。