文本标记 - 用户从网页上选择文本高亮
在上一篇文章 文本标记 - 高亮标记多个指定位置的文本 中, 说明了如何高亮后台返回的指定位置的多个数据. 现在解释下一个需求: 用户在高亮后台返回的数据的基础上, 从页面上选择文本高亮, 并且在表格中显示文本
上篇文章说到高亮的效果: 一般都是给指定的文本包裹一个标签 ( 如 ), 在给这个标签设置一个样式 ( 比如添加背景色 ), 这是实现这个需求的基础.
ps: 如果只有用户从网页上选择文本的需求, 可以考虑用 web-highlighter
插件, 比起自己手写方法来要简便一些.
首先介绍一下这个需求的逻辑:
点这个知识抽取的按钮, 然后左边文本高亮, 右面的表格填充 (实体这一列为只读状态), (有多个表格, 采取了不同的高亮样式)
点击这个按钮, 对应的高亮消失, 表格变空
3. 用户从左边文本选择一段, 像这样; 再点击这个对钩, 就会填充, 然后获取它对应的下标起始位置, 保存下来, 并且将选中的文本高亮
这里的难点就是, 因为已经标记了后台返回的那些文本, 然后这些高亮文本都被包裹了span标签. 这时候获取的下标就不是从整个文本对应的下标0开始的了, 因为整个文本被span标签分成了很多部分, 用户标记的时候, 获取的这个下标只是这个分段里的下标, 但是我们要传给后端相对于整篇文本的下标, 这就是难点.
通过对不同位置的selection对象的研究 , 发现了以下几个不同点:
普通文本的
a) parentNode是 <div>...</div>
b) previousSibling是前一个已经标记的元素节点, 元素节点的nodeType=1, className可以看到该元素节点的类名; nextSibling也是已标记的元素节点
已标记文本的
a) parentNode是当前标签, eg. <span>
纯电动汽车</span>
b) previousSibling 和 nextSibling都是null
判断是否是开头的普通文本:
a) parentNode是整个div
后台返回的某个高亮本文对象:
思路
首先通过baseNode的parentNode是div还是span判断是普通文本还是标记文本,
如果是普通文本, 通过previousSibling寻找前面的元素节点:
a) 如果返回null => 说明是开头的元素, 直接用 开始位置: baseOffset 结束位置: baseOffset+length-1 b) 如果返回某个元素节点 => 通过className判断是table1还是table2里的元素 (项目中不只有右边一个表格, 这几个表格中使用了不同的高亮样式), 然后 前兄弟节点的end+1+baseOffset 就是用户选择的selection相对于整个文本的开始位置, 结束位置可以是: 开始位置+选中文本.length-1 3) 如果是标记文本, 根据它的className判断是table1还是table2中的元素, 获取对应的start. 开始位置: start+baseOffset 结束位置: 开始位置+选中文本.length-1
代码
// 知识抽取按钮 knowExtract() { if (this.isEdit) { this.$alert("请先保存文本"); } else { const _key = this.extract_key; console.log(_key); knowiExtract(_key).then((res) => { console.log(res); if (res.status == 0) { // 知识抽取成功 this.$message.success(res.message); // 表1数据渲染 this.tableData1 = res.entity; console.log(this.tableData1); // 表2数据渲染 // this.tableData2 = res.attribute // 表1文本高亮 this.highlightEntity(); } else { this.$message.error("知识抽取失败"); } }); } }, // 表1 文本高亮 highlightEntity() { console.log("文本高亮"); let text = this.fixedText let textList = text.split(""); // 表1中的实体高亮 this.tableData1.forEach((item) => { let startNum = item.start; let endNum = item.end + 1; console.log(startNum,endNum) if (startNum == endNum - 1) { textList[startNum] = `<span>${textList[startNum]}</span>`; } else { textList[startNum] = `<span>${textList[startNum]}`; textList[endNum - 1] = `${textList[endNum - 1]}</span>`; } }); // 表2 中的文本高亮 this.text = textList.join(""); }, // 表1 实体高亮初始化 (编辑按钮) initEntiExtract(i) { this.$message.warning("请从前向后选择文本内容") console.log("实体高亮初始化" + i); this.tableData1[i].entity = "" this.tableData1[i].start = "" this.tableData1[i].end = "" console.log(this.tableData1) this.highlightEntity() this.selection = window.getSelection(); }, // 表1 实体抽取 (√按钮) entityExtract(i) { console.log(this.selection); let newEntiry = this.selection.toString(); // 保证起始位置小于结束位置 (用户倒选的操作)(未完成) // 获取截取开头的baseNode let startNode = this.selection.baseNode // 根据parentNode是div还是span判断是普通文本还是标记文本 let startParent = startNode.parentNode.nodeName console.log("parentNode 父节点") console.log(startParent) if (startParent == "DIV") { // 普通文本, 通过previousSibling寻找前面的元素节点 let preSib = startNode.previousSibling console.log("前兄弟节点") console.log(preSib) if (!preSib) { console.log("前兄弟节点不存在") // 如果返回null => 说明是开头的元素, 直接用开始位置: baseOffset,结束位置: baseOffset+length-1 this.tableData1[i].start = this.selection.baseOffset this.tableData1[i].end = this.selection.baseOffset + newEntiry.length - 1 console.log("起始位置") console.log(this.tableData1[i].start, this.tableData1[i].end) } else { let preSibClass = preSib.className console.log("前兄弟节点的className") console.log(preSibClass) if (preSibClass == "EntityHighlight") { // 表1元素 // 寻找前兄弟节点的end位置 let preSibEnd = 0 for(let k=0;k<this.tableData1.length;k++){ if(this.tableData1[k].entity == preSib.innerText){ console.log(preSib.innerText) console.log(this.tableData1[k].end) preSibEnd = this.tableData1[k].end } } console.log("前兄弟节点的end位置") console.log(preSibEnd) this.tableData1[i].start = preSibEnd + 1 + this.selection.baseOffset this.tableData1[i].end = this.tableData1[i].start + newEntiry.length - 1 console.log("起始位置") console.log(this.tableData1[i].start, this.tableData1[i].end) } } } else if (startParent == "SPAN") { // 标记文本, 根据它的className判断是table1还是table2中的元素 let nodeClass = startNode.className console.log(nodeClass) if (nodeClass == "EntityHighlight"){ // 表1元素 // 获取起始位置 for(let j=0;j<this.tableData1.length;j++) { if( this.tableData1[j].entity = startNode.nodeValue) { this.tableData1[i].start = this.tableData1[j].start + this.selection.baseOffset this.tableData1[i].end = this.tableData1[i].start + this.selection.length - 1 } } } } // 给table1中相应的entity赋值 this.tableData1[i].entity = newEntiry; console.log(this.tableData1) this.highlightEntity() },
作者:Charonmomo
链接:https://juejin.cn/post/7032194743366860813