1

初始需求是返回说话人数组,每一项有头像,昵称,时间和说话文本,但是鼠标选中只需要说话文本的集合,而且可以跨多行选中。

  1. 因为我只要说话文本,所以将说话文本用p标签包起来,其它的元素用div。
    后端返回的文本是一长串字符串,前端需要处理将每一个字分割,然后用span标签包起来。

image.png

  1. 利用mouseup方法,当鼠标指针移动到元素上方,并松开鼠标左键时,会发生 mouseup 事件。
  2. 在mouseup事件里面使用window.getSelection()属性
// Range就是通过鼠标或者键盘或者代码选中的一段内容,这段内容浏览器会将其封装成一个Range对象
const range = window.getSelection().getRangeAt(0);
docFragment,获取鼠标选中的所有DOM元素
const docFragment = range.cloneContents();
// 用户选择多行文字的时候
const parentP = docFragment.querySelectorAll("p");
// 用户选择一行文字,但是文字是多个的时候
const childSpan = docFragment.querySelectorAll("span");
// 用户选择一行文字,并且文字只有一个的时候
const oneText = [range.startContainer.parentElement];
  1. 因为在开发过程中,发现当用户选择一行文字,并且文字只有一个的时候,parentP和childSpan数组是空的。当用户选择一行文字,但是文字是多个的时候parentP数组是空的。所以做了这三个判断。

image.png
image.png
image.png
image.png

  1. 以用户选中多行文字为例:循环parentP,因为parentP是类数组,所以需要用Array.from(parentP)转义一下。
    目的是为了拿到parentP数组对象中dataset里面的值,然后处理成我想要的格式:
          [
            {
              "parentId": "111",
              "childId": [
                {
                  "id": "0",
                  "val": "字"
                },
                {
                  "id": "1",
                  "val": "段"
                }
              ]
            },
            {
              "parentId": "222",
              "childId": [
                {
                  "id": "0",
                  "val": "我"
                },
                {
                  "id": "1",
                  "val": "刚"
                }
              ]
            }
          ]

image.png

  1. 点击确认选中的时候,将选中的文字高亮。this.selectText能够拿到选中的纯文本

image.png

  1. 下面是完整代码:
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>vue开发:利用window.getSelection()实现多行文字选中高亮,只针对需要的标签</title>
  <!--引入 element-ui 的样式,-->
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
  <!-- 引入element 的组件库-->
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    #app {
      padding: 50px;
      width: 100vw;
      height: 100vh;
      box-sizing: border-box;
    }

    .el-textarea__inner {
      width: 500px;
    }

    .labelbutton {
      box-sizing: border-box;
      position: absolute;
      z-index: 1;
      height: 44px;
      left: 30%;
      top: 30%;
      background: #ffffff;
      border: 1px solid #e5e7ef;
      box-shadow: 0px 8px 24px rgba(18, 19, 20, 0.08);
      border-radius: 3px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 0 10px;
      cursor: pointer;
    }

    span.highlightActive {
      background: red;
    }
  </style>
</head>

<body>
  <div @mouseup.stop="pitchOnFun($event)" id="app">
    <ul>
      <li v-for="(item, index) in detail" :key="index">
        <div>
          昵称:{{ item.nickname }}
        </div>
        <p :data-id="item.id">
          <span :class="{highlightActive: textItem.highlightActive }" v-for="(textItem, textIndex) in item.text2"
            :key="textIndex" :data-id="textIndex" :data-val="textItem.val" :data-parentid="item.id">{{ textItem.val
            }}</span>
        </p>
      </li>
    </ul>
    <div @click.stop="selectFun" class="labelbutton" v-show="highlightDialog"
      :style="{ left: layerX + 'px', top: layerY + 'px' }">
      确定选中
    </div>

    <br>
    <br>
    <br>
    <br>
    <br>
    获取选中的纯文本:
    <br>
    {{selectText}}
  </div>
  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          // 弹层坐标
          layerX: 0,
          layerY: 0,
          highlightDialog: false,
          selectedText: [],
          selectText: '',
          // 模拟数据是从后端返回
          detail: [
            {
              "text": "字段,是说明我再创建一个联系人。",
              "nickname": "宝贝",
              id: '111',
            },
            {
              "text": "我刚刚创建了一个供应商,然后它是一个自定义类型,我要把它做成一个细。",
              "nickname": "宝贝",
              id: '222',
            },
            {
              "text": "表,怎么弄?我的供应商,拍摄系表,在这选到我的供应商。",
              "nickname": "宝贝",
              id: '333',
            }
          ],

        }
      },
      created() {
        this.detail = this.detail.map((item) => {
          // 前端将每一句话分割成每一个字
          item.text2 = item.text.split("").map((item, index) => {
            const obj = {};
            obj.val = item;
            return obj;
          });
          return item
        });
      },
      mounted() {
        document.addEventListener("click", this.unhighlightPopupHideFun);
      },
      beforeUnmount() {
        window.removeEventListener("click", this.unhighlightPopupHideFun);
      },
      methods: {
        // 关闭取消高亮弹出层
        unhighlightPopupHideFun() {
          this.unhighlightPopup = false;
        },

        pitchOnFun(event) {
          setTimeout(() => {
            // console.log(event, "event");
            // console.log(window.getSelection().toString(), "选中的文本");
            if (!window.getSelection().toString()) {
              //选中文本后出现标注列表弹框
              this.highlightDialog = false;
              return;
            };
            // 当前选中的集合
            this.selectedText = [];
            this.layerX = event.clientX;
            this.layerY = event.clientY + 10;
            const range = window.getSelection().getRangeAt(0);
            const docFragment = range.cloneContents();
            const parentP = docFragment.querySelectorAll("p");
            const childSpan = docFragment.querySelectorAll("span");
            const oneText = [range.startContainer.parentElement];
            this.selectText = "";
            // 选择多行文字
            if (parentP.length) {
              Array.from(parentP).forEach((item) => {
                const childArr = Array.from(item.children).map((item2) => {
                  return { id: item2.dataset.id, val: item2.dataset.val };
                });
                if (item.dataset.id && childArr.length) {
                  this.selectedText.push({
                    parentId: item.dataset.id,
                    childId: childArr,
                  });
                }
              });
            } else {
              let childArr = [];
              let parentId = "";
              // 选择一行文字'
              if (Array.from(childSpan).length) {
                childArr = Array.from(childSpan).map((item2) => {
                  return { id: item2.dataset.id, val: item2.dataset.val };
                });
                parentId = childSpan[0].dataset.parentid;
              } else {
                // 一行文字只选择了一个字
                childArr = [{ id: oneText[0].dataset.id, val: oneText[0].dataset.val }];
                parentId = oneText[0].dataset.parentid;
              }
              if (parentId && childArr.length) {
                this.selectedText = [
                  {
                    parentId: parentId,
                    childId: childArr,
                  },
                ];
              }
            }
            if (!this.selectedText.length) {
              return;
            } else {
              // 当选中文本是用户说话内容的时候,弹出标签列表
              this.highlightDialog = true;
            }
            // 获取选中的纯文本
            if (docFragment.querySelectorAll("span").length) {
              this.selectText = Array.from(docFragment.querySelectorAll("span"))
                .map((item) => {
                  return item.innerHTML;
                })
                .join("");
            } else {
              this.selectText = range.startContainer.parentElement.innerHTML;
            }
          }, 50);
        },
        selectFun() {
          console.log(this.selectedText, '选中的文字集合')
          // 先取消所有高亮
          this.detail.forEach((item, index) => {
            item.text2.forEach((item3, index3) => {
              if (item3.highlightActive) {
                this.$set(item3, "highlightActive", false);
              }
            });
          });

          this.detail.forEach((item, index) => {
            this.selectedText.forEach((item2, index2) => {
              // 父集id相同
              if (item.id == item2.parentId) {
                item.text2.forEach((item3, index3) => {
                  item2.childId.forEach((item4, index4) => {
                    // 子集下标相同
                    if (index3 == item4.id) {
                      this.$set(item3, "highlightActive", true);
                    }
                  });
                });
              }
            });
          });
        },
      }
    })
  </script>
</body>

</html>

我的一个道姑朋友
80 声望4 粉丝

星光不问赶路人,岁月不负有心人。