实现效果:通过 鼠标松开事件highlight监听选中文本,文本选中后可以通过window.getSelection()拿到选中的文本数据;同时可以通过鼠标松开时的位置,调用this.setBoxPosition(e.pageX, e.pageY)事件计算想要弹出的标签弹窗的位置。标签弹窗中给每一个标签添加点击事件labelmenuchose(id, item)(标签弹窗的html代码没有贴出,可以根据自身需要自行定义),点击选择标签后触发事件labelmenuchose(id, item),[id是我在点击标签时传入的时间戳,准备用来给标注span标签和i标签的id选择器命名用的,保证他们id的一致性,为了后面的删除]。点击事件触发后,在点击事件内部,对选中文本右边添加标注,整个标注内容用span标签包裹,选中文本背景色变成标签背景色的百分之二十五,单击标签,可以对标签进行修改,点击删除按钮实现删除功能
html部分 后端传入文本,前端拿到文本通过v-html进行渲染
<template>
<div
id="annotateContent"
style="text-align: left; font-size: 16px; padding: 20px"
type="textarea"
v-html="textMgs.content"
@mouseup.stop="highlight($event)"
v-if="textrender"
></div>
</template>
js部分
<script>
import $ from "jquery";
export default {
data() {
return {
textMgs: {
content: "",
detailId: null,
},
textrender: true,
sel: null,
range: null,
labeldialog: false,
editbutton: false,//已标注在文本的标签单击后会把editbutton变成true,然后再次弹出标签选择弹框,可以重新选择标签把旧的标签替换掉,这个代码忘写了,算了,但那不重要,没有标签修改功能的直接走else就可以了
}
},
methods:{
// 选中标注文本
highlight(e) {
if (!window.getSelection().toString() || this.labels.length == 0) {
this.labeldialog = false; //选中文本后出现标注列表弹框
return;
}
this.sel = window.getSelection();
this.range = this.sel.getRangeAt(0);
// 判断选中文本中是否包含子元素 如果需要阻止重叠标注可使用
// let span = document.querySelectorAll(".highlight ");
// for (let i = 0; i < span.length; i++) {
// if (this.sel.containsNode(span[i])) {
// this.labeldialog = false;
// return;
// }
// }
//计算选中文本在屏幕中的位置,然后弹出标签列表弹窗
this.setBoxPosition(e.pageX, e.pageY);
},
// 计算标注标签栏弹出的位置
setBoxPosition(X, Y) {
var labellist = document.querySelector(".labelbutton2");
const maxHeight = document.body.clientHeight;
let height = Y + 10;
if (maxHeight < height + 248) {
height = height - 268;
}
labellist.style.cssText = `height:248px;overflow:auto;width:200px;position:fixed;left:${X + 10}px;top:${height}px;background:#fff;box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);border-radius: 4px`;
//弹出标签列表
this.labeldialog = true;
},
// 选择标注标签
labelmenuchose(id, item) {
// 修改标签
if (this.editbutton) { //这个是标注后单击标签,实现修改标签,不需要可以直接走else
let changeid = this.editbutton_spanid;
let annotateContent =
document.getElementById(changeid).parentNode.parentNode.parentNode;
let span = document.getElementById(changeid).parentNode.parentNode;
span.removeChild(document.getElementById(changeid).parentNode);
const newspan = document.createElement("span");
newspan.className = "highlight";
let delicon = document.createElement("i");
delicon.setAttribute("id", item.tagId + "-" + new Date().getTime());
// // 删除标签
delicon.addEventListener("click", (a) => {
this.deleteById(a.currentTarget.id);
this.delMarking.visible = false; });
delicon.setAttribute("class", "el-icon-close");
delicon.setAttribute("style", this.deliconStyle());
let color = item.tagColor + "40";
newspan.innerHTML = `<em style="background:${color};font-style: normal;">${span.innerText.trim()}</em><button style="border:1px solid #ccc;position:relative;vertical-align: super;background:${
item.tagColor
};color:#fff">${item.tagName}</button>`;
newspan.lastChild.appendChild(delicon);
annotateContent.insertBefore(newspan, span);
annotateContent.removeChild(span);
this.labeldialog = false;
this.editbutton = false;
this.editbutton_spanid = null;
} else {
// 添加标签
if (!this.textchose) { //如果选中文本为空,直接返回
return;
}
this.addMarking(id, item, this.sel, this.range);//标注时间
var labellist = document.querySelector(".labelbutton2");
labellist.style.cssText = `position:fixed;left:-999px;top:-999px;background:#fff`;
this.labeldialog = false;
this.highLightTableMsg(
$("#annotateContent").html(),
);
}
},
// 在文本中生成标注
addMarking(id, item, sel, range) {
// 限制标注内嵌套标注
let startContainer = range.startContainer;
const span = document.createElement("span");
span.className = "highlight";
let delicon = document.createElement("i");
delicon.setAttribute("id", id);
// 删除标签
delicon.addEventListener("click", (a) => {
// deleteById(id);
this.labeldialog = false;
this.deleteById(id);
this.delMarking.visible = false;
a.stopPropagation();
});
delicon.setAttribute("class", "el-icon-close");
delicon.setAttribute("style", this.deliconStyle());
try{
range.surroundContents(span); //把指定节点插入选区的起始位置,然后把指定节点的内容替换为选区的内容。
}catch(e){
window.getSelection().removeAllRanges();
this.sel = null;
this.range = null;
this.textchose = null;
this.labeldialog = false;
return;
}
let color = item.tagColor + "40";
span.innerHTML = `<em style="background:${color};font-style: normal;">${span.innerHTML}</em><button style="border:1px solid #fff;background:#fff;padding: 0 2px;border-radius: 2px;position:relative;vertical-align: super;background:${item.tagColor}!important;color:#fff">${item.tagName}</button>`;
span.lastChild.appendChild(delicon);
window.getSelection().removeAllRanges();
this.sel = null;
this.range = null;
this.textchose = null;
},
}
}
</script>
如果还需要删除标记功能,需要在上面的methods插入下面的方法
// 删除标记
deleteById(id) {
let annotateContent =
document.getElementById(id).parentNode.parentNode.parentNode;
let span = document.getElementById(id).parentNode.parentNode;
let button = document.getElementById(id).parentNode;
let emtext = "";
if (span.firstChild.getElementsByTagName("span").length > 0) { //判断标注内是否还嵌套有标注,有走if,没有走else(其实这里只需要if就可以了,写的时候可以把代码简化一下)
let kk = span.firstChild.innerHTML
let newSpan = document.createElement('span');
newSpan.className = "tagReplace";
span.removeChild(button);
annotateContent.insertBefore(newSpan, span);
annotateContent.removeChild(span);
annotateContent.innerHTML = annotateContent.innerHTML.replace('<span class="tagReplace"></span>', kk);
} else {
emtext = span.firstChild.innerText.trim();
span.removeChild(button);
let textNode = document.createTextNode(emtext);
annotateContent.insertBefore(textNode, span);
annotateContent.removeChild(span);
}
window.getSelection().removeAllRanges();
this.labeldialog = false;
},
因为我这边保存是直接把整个文本返回给后端,再次获取已标注的结果的话,也是后端直接把一整个文本返回给我的,所以为了保证渲染完已标注过的文本还能正常实现删除功能、标签修改功能,需要在文本获取成功后再次添加上删除的点击事件
// 给已标注文件添加点击事件
addLabelDel() {
let del = document.querySelectorAll("#annotateContent span button i");
// let button = document.querySelectorAll("#annotateContent span button")
$("#annotateContent").on("click", "span button", (e) => {
//通过事件委托完成,有效
if (this.labels.length > 0) {
this.editbutton = true;
this.setBoxPosition(e.pageX, e.pageY);
this.editbutton_spanid = e.currentTarget.lastElementChild.id;
//操作this
// ...
}
});
for (var i = 0; i < del.length; i++) {
del[i].addEventListener("click", (a) => {
this.labeldialog = false;
this.deleteById(a.currentTarget.id);
a.stopPropagation();
});
}
},