前言
在项目的开发中产品提出了一个需求 我要实现自定义的告警模板,模板可以输入也可以插入或者拖拽按钮到指定的位置。我着简单我给你搞.....
第一版
这不简单我直接插入不就可以了吗
用textarea 配合焦点的位置 直接插入即可 简单就放了
啥垃圾东西 谁看的懂 我要文本里面展示的按钮的名称
第二版
我这苦想呀 想到了h5中的可编辑属性contenteditable 不就可以实现元素的可编辑 里面的按钮 咱们给他整成标签不就行了 再插入到对应的位置即可。又遇到了 插入元素后焦点丢失了等问题 最后实现了需求。
<!-- eslint-disable no-irregular-whitespace -->
<template>
<div>
<el-button v-for="item in optionList" size="small" style="background: rgba(92,133,200,0.08);color:black;font-size: 14px;
font-family: PingFang SC, PingFang SC-Regular;color: rgba(0,0,0,0.85);" :key="item.value"
@click="AddExtractedValue(item.label)" :draggable="true" @dragstart.native="handleDragStart(item.label)">
{{ item.label }}
</el-button>
<p contenteditable="true" class="w-textarea_input" id="bjContent" @dragover="handleDragOver" @drop="handleDrop"
@blur="updateData"></p>
</div>
</template>
<script>
export default {
props: {
optionList: {
type: Array,
default: () => {
return []
}
},
value: {
type: String,
default: ''
}
},
data() {
return {
bjContent: null,
idList: [],
range: null,
};
},
mounted() {
this.idList = [];
this.bjContent = document.getElementById("bjContent");
if (this.value) {
let text = this.value;
let idList = [];
let textDataLength = this.value.split('').filter(item => item == '$').length;
for (let i = 0; i < textDataLength; i++) {
this.optionList.forEach((item) => {
if (text.includes(item.value)) {
let id = this.guid();
idList.push(id);
text = text.replace(
item.value,
`<span contenteditable="false" class="w-textarea_tag" id="${id}" style="position:relative;border:1px dashed #a9d3ff;border-radius:4px;padding:8px 14px;margin:0 4px;background:rgba(121,200,255,0.26) ">
<span>${item.label}</span>
<span id="${id}span" style="color:#2E94FF;cursor:pointer;font-size:12px;position:absolute;margin-top:8px;" class='el-icon-close' ></span>
</span>
<span contenteditable=true id='jkq${id}' style="line-height:3"></span>
`
);
}
});
}
let span = document.createElement("span");
span.innerHTML = text;
this.bjContent.appendChild(span);
this.idList = idList;
this.$nextTick(() => {
idList.forEach(item => {
document.querySelector(`#jkq${item}`).innerHTML = " ";
})
})
}
},
watch: {
idList(val) {
if (!val) return false
this.idList.forEach((item) => {
let ele = document.getElementById(`${item}span`);
if (!ele) return false
ele.addEventListener("click", () => {
document.getElementById(`${item}`).remove()
});
});
},
},
methods: {
AddExtractedValue(insertTxt) {
let id = this.guid();
this.idList.push(id);
let html = `<span contenteditable="false" class="w-textarea_tag" id="${id}" style="position:relative;border:1px dashed #a9d3ff;border-radius:4px;padding:8px 14px;margin:0 4px;background:rgba(121,200,255,0.26) ">
<span>${insertTxt}</span>
<span id="${id}span" style="color:#2E94FF;cursor:pointer;font-size:12px;position:absolute;margin-top:8px" class='el-icon-close' ></span>
</span>
<span contenteditable=true id='jkq${id}' style="line-height:3"></span>
`;
this.insertNode(html);
if (!document.querySelector(`#jkq${id}`)) return false
document.querySelector(`#jkq${id}`).innerHTML = " ";
},
guid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
},
insertNode(html) {
if (this.bjContent != undefined) {
var sel;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
var tag = document.createElement("span");
tag.setAttribute("contenteditable", true);
tag.innerHTML = html;
var frag = document.createDocumentFragment(),
node,
lastNode;
while ((node = tag.firstChild)) {
lastNode = frag.appendChild(node);
}
this.range.insertNode(frag);
if (lastNode) {
this.range.insertNode(lastNode);
sel.removeAllRanges();
sel.addRange(this.range);
sel.collapseToEnd();
}
this.bjContent.focus();
}
}
}
},
save() {
let test = this.bjContent.innerText.replace(/\s/g, '')
this.optionList.forEach(item => {
test = test.replaceAll(item.label, item.value)
})
},
handleDragStart(label) {
this.bjContent.focus();
event.dataTransfer.setData("text/plain", label);
},
handleDragOver(event) {
event.preventDefault();
},
updateData() {
let list = this.bjContent.innerText;
this.$emit("changeInnerText", list);
//获取当前的选取
this.range = window.getSelection().getRangeAt(0);
},
handleDrop(event) {
event.preventDefault();
const label = event.dataTransfer.getData("text/plain");
let id = this.guid();
this.idList.push(id);
let html = `<span contenteditable="false" class="w-textarea_tag" id="${id}" style="position:relative;border:1px dashed #a9d3ff;border-radius:4px;padding:8px 14px;margin:0 4px;background:rgba(121,200,255,0.26) ">
<span>${label}</span>
<span id="${id}span" style="color:#2E94FF;cursor:pointer;font-size:12px;position:absolute;margin-top:8px" class='el-icon-close' ></span>
</span>
<span contenteditable=true id='jkq${id}' style="line-height:3"></span>
`;
this.insertNode(html);
if (!document.querySelector(`#jkq${id}`)) return false
document.querySelector(`#jkq${id}`).innerHTML = " ";
this.updateData();
},
},
}
</script>
<style scoped lang="scss">
.w-textarea_input {
height: 200px;
width: 100%;
background: #ffffff;
border: 1px solid #eaeef3;
border-radius: 4px;
padding: 8px;
overflow: auto;
text-align: left;
word-break: keep-all
}
</style>
知识点: 1.contenteditable 2.window.getSelection 3.window.getSelection().getRangeAt(0)