<template>
  <div id="page">
    <div class="menu-title" id="title-id">
      文本标注
    </div>
    <div class="menu-content text-content" id="contentBox">
      <el-row>
        <el-col :span="20">
          <div class="page-box">
            <el-button type="text" style="cursor: default">总篇幅：{{ result.total }}； 当前篇幅：{{
                qo.pageNo
              }}；
            </el-button>
            <el-button type="text" :disabled="qo.pageNo === 1" @click="queryDataSetView( -1)">上一篇</el-button>
            <el-button type="text" :disabled="qo.pageNo === result.total" @click="queryDataSetView(1)">下一篇
            </el-button>
          </div>
          <div id="con" style="z-index: 35">
            <p id="pBox" v-if="result.records.length > 0"></p>
            <!-- 提示语卡片 -->
            <el-card class="tips-label-box" id="tipsLabelBox" style="left: 0; top: 0" v-show="tipsLabelVisible">
              <div>{{ tipsLabel }}</div>
            </el-card>
            <!-- 编辑标签关系卡 -->
            <el-card class="modify-tag-relation-box" id="modifyTagRelationBox" style="left: 0; top: 0"
                     v-show="modifyTagRelationVisible">
              <el-card class="modify-tag-box" shadow="hover" v-for="(item,index) in modifyTagRelationCmd" :key="index">
                <!--                <div class="title">{{ item.name | relationNameFormatter }}</div>-->
                <div class="tree-box">
                  <div class="left-tree-title">{{ item.edgeName | relationNameFormatter }}</div>
                  <div class="left-tree-icon"></div>
                  <div class="left-tree-content">
                    <div class="non-label">{{ item.sourceTag.text }}</div>
                    <div class="non-label">{{ item.targetTag.text }}</div>
                  </div>
                  <div class="left-tree-btn">
                    <i class="el-icon-delete" @click="removeTagRelation(item, index)" style="cursor: pointer"></i>
                  </div>
                </div>
              </el-card>
            </el-card>
          </div>
        </el-col>
        <el-col :span="4" class="tool-box-parent">
          <div class="tool-box">
            <div class="title back-box">
              <el-button plain style="float: right" @click="backToList">返回列表</el-button>
            </div>
            <div class="title">
              <div class="label-box">标签栏</div>
              <div class="button-box" @click="showEditTagDialog">添加/修改</div>
            </div>
            <div class="relation-box">
              <el-card shadow="hover" v-for="item in edgeList" :key="item.label" v-show="item.edgeName">
                <div class="title">{{ item.label }}</div>
                <div class="non-label">起始本体：{{ item.srcId | nodeNameFormatter }}</div>
                <div class="non-label">终止本体：{{ item.dstId | nodeNameFormatter }}</div>
              </el-card>
            </div>
            <div class="title result-title">
              <div class="label-box">标注结果</div>
              <!--              <div class="button-box" @click="submitSaveRemarkInfo">保存</div>-->
            </div>
            <div class="sign-result-box">
              <el-card shadow="hover" v-for="(item,index) in relationResultList" :key="index">
                <div class="title">{{ item.edgeName | relationNameFormatter }}
                  <i class="el-icon-delete" @click="removeTagRelation(item, index)" style="cursor: pointer;float: right"></i>
                </div>
                <div class="non-label">头实体：{{ item.sourceTag.text }}</div>
                <div class="non-label">尾实体：{{ item.targetTag.text }}</div>
              </el-card>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>
    <!-- 贝塞尔曲线容器 -->
    <svg id="svgBox" v-show="svgVisible" style="width:0;height: 0;">
      <!--        <path id="pathItem" d="M542 340 Q1279 801 2047 394" stroke="rgb(0,150,255)"></path>-->
    </svg>
    <!-- 选择标签卡 -->
    <el-card class="select-type-box" id="selectTypeBox" style="left: 0; top: 0" v-show="selectTagVisible">
      <div class="title">请选择实体类别标签：</div>
      <div class="tag-box">
        <div v-for="item in nodeList" :key="item.label" class="tag-item" v-show="item.vid"
             @click.stop.prevent="confirmTag(item)"
             :style="clickTag.vid === item.vid ? 'color: #bfbfbf;cursor: default;': ''">
          {{ item.label }}
        </div>
      </div>
    </el-card>
    <!-- 选择标签关系卡 -->
    <el-card class="select-relation-box" id="selectRelationBox" style="left: 0; top: 0" v-show="selectRelationVisible">
      <div class="title">请选择实体关系标签：</div>
      <div class="tag-box">
        <div v-for="item in filterEdgeList" :key="item.label" class="tag-item" v-show="item.edgeName"
             @click.stop.prevent="confirmRelation(item)">
          {{ item.label }}
        </div>
        <div v-if="filterEdgeList.length == 0" class="label-box222">暂无关系可供选择</div>
      </div>
    </el-card>
    <!-- 编辑标签和关系弹窗 -->
    <el-dialog
        title="添加关系标签"
        :visible.sync="cmdDialogVisible"
        width="40%">
      <div class="edit-box">
        <div class="title">创建实体类别标签</div>
        <div class="input-box">
          <div class="input-item" v-for="node in nodeList" :key="node.id">
            <el-input v-model="node.label" placeholder="请输入文本标签名称" class="tag-label-input"
                      @change="handleNodeInputChange(node)"></el-input>
            <i class="el-icon-delete" @click="removeNodeOrEdge('node', node)"
               :style="node.canDelete == true ? '' : 'color: #bfbfbf;cursor: default;'"></i>
          </div>
        </div>

        <div class="title">创建关系标签</div>
        <div class="input-box">
          <div class="edge-item" v-for="edge in edgeList" :key="edge.name">
            <el-input v-model="edge.label" placeholder="请输入关系标签名称" class="edge-label-input"
                      @change="handleEdgeInputAndSelectChange(edge)"></el-input>
            <el-select v-model="edge.srcId" placeholder="请指定文本标签" class="edge-node-select"
                       @change="handleEdgeInputAndSelectChange(edge)"
                       :disabled="edge.edgeName != null && edge.edgeName.length > 0">
              <el-option v-for="node in nodeList" :key="node.vid" :label="node.label" :value="node.vid"
                         v-show="node.vid && node.vid.length > 0"></el-option>
            </el-select>
            <el-select v-model="edge.dstId" placeholder="请指定文本标签" class="edge-node-select"
                       @change="handleEdgeInputAndSelectChange(edge)"
                       :disabled="edge.edgeName != null && edge.edgeName.length > 0">
              <el-option v-for="node in nodeList" :key="node.vid" :label="node.label" :value="node.vid"
                         v-show="node.vid && node.vid.length > 0"></el-option>
            </el-select>
            <i class="el-icon-delete" @click="removeNodeOrEdge('edge', edge)"></i>
          </div>
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="cmdDialogVisible = false">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import request from '@/utils/request';
import * as d3 from 'd3';

var _this;
export default {
  name: "company",
  data() {
    return {
      ontologyId: null, // 图模型ID
      qo: {
        pageNo: 1,
        pageSize: 1,
        taskId: '',
        taskType: ''
      },
      result: {
        records: [{content: ''}],
        total: 0
      },
      tagResultList: [],
      relationResultList: [],
      selectDom: null,
      nowSource: null,
      nowTarget: null,

      x: 0, // 鼠标x
      y: 0,// 鼠标y
      svgX: 0, // 曲线起点
      svgY: 0, // 曲线终点
      svgVisible: false,
      selectTagVisible: false,
      selectRelationVisible: false,
      isBesselEnd: false,

      nodeList: [],
      nodeMap: {},
      edgeList: [],
      filterEdgeList: [], // 用于根据起止节点然后过滤可选的关系
      newNode: {},
      newEdge: {},
      cmd: {},
      cmdDialogVisible: false,
      tipsLabel: "",
      tipsLabelVisible: false,
      clickTag: {},
      modifyTagRelationVisible: false,
      modifyTagRelationCmd: []
    }
  },
  mounted() {
    _this = this;
    _this.qo.taskId = this.$route.query.taskId;
    _this.qo.taskType = this.$route.query.taskTypeCode;
    _this.ontologyId = this.$route.query.ontologyId;
    if(this.$route.query.sort) {
      _this.qo.pageNo = parseInt(this.$route.query.sort);
    }

    _this.queryRelationData();

    _this.$nextTick(() => {
      // 绘制svg贝塞尔曲线
      let svg = d3.select("#svgBox");
      //添加defs标签
      var defs = svg.append("defs");
      //添加marker标签及其属性
      var arrowMarker = defs.append("marker")
          .attr("id", "arrow")
          .attr("markerUnits", "strokeWidth")
          .attr("markerWidth", 20)
          .attr("markerHeight", 20)
          .attr("viewBox", "0 0 12 12")
          .attr("refX", 6)
          .attr("refY", 6)
          .attr("orient", "auto");
      arrowMarker.append("path")
          .attr("d", "M2,2 L10,6 L2,10 L6,6 L2,2")
          .attr("fill", "rgb(243,144,0)");
      svg.append("path").attr("id", "pathItem")
          .attr("stroke", "rgb(243,144,0)")
          .attr("fill", "rgba(0,0,0,0)")
          .attr("marker-end", "url(#arrow)");
      document.getElementById("page").addEventListener("click", (e) => {
        _this.selectDom = null; // 清空选中的文本
        if (e.target.getAttribute('class') != 'tag-item') {
          if (_this.isBesselEnd || !_this.svgVisible) {
            _this.clearTagSelected();
          }
          _this.selectTagVisible = false;
          _this.selectRelationVisible = false;

          if (e.target.getAttribute('class')) {
            // 点击标注标签，展示标签栏
            if (e.target.getAttribute('class').indexOf('tag-label') >= 0) {
              if (parseInt(e.target.getAttribute("relationnum")) > 0) { // 已存在关系的标签，显示提示语句
                let tag = e.target;
                _this.tipsLabel = "该实体存在关系，不允许改变或删除标注。";
                let tipsBox = document.getElementById("tipsLabelBox");
                tipsBox.style.left = tag.offsetLeft + tag.parentElement.offsetLeft + "px";
                tipsBox.style.top = tag.offsetTop + tag.parentElement.offsetTop + 40 + "px";
                _this.tipsLabelVisible = true;
              } else { // 暂无关系的标签
                _this.clickTag = {
                  domId: e.target.getAttribute("id"),
                  vid: e.target.getAttribute("vid"),
                };
                _this.showSelectTagCard();
              }
            }

            // 点击删除已标注标签按钮
            if (e.target.getAttribute('class').indexOf('close-btn') >= 0) {
              _this.removeTextTag(e.target);
            }
          }

        }
      });
      // 监听文本点击事件，显示标签选择卡片
      document.getElementById("pBox").addEventListener("mouseup", function (e) {
        setTimeout(function () {
          let selection = window.getSelection();
          // 获取选中
          if (selection.toString().length === 0) {
            return false;
          }
          const range = selection.getRangeAt(0);
          _this.selectDom = {
            text: selection.toString(),
            anchorOffset: selection.anchorOffset,
            focusOffset: selection.focusOffset,
            start: {
              node: range.startContainer,
              offset: range.startOffset
            },
            end: {
              node: range.endContainer,
              offset: range.endOffset
            }
          };
          _this.showSelectTagCard();
        }, 100);
      });

      document.onmousemove = function (event) {
        _this.x = event.clientX;
        _this.y = event.clientY;

        if (!_this.isBesselEnd) { // 确认绘制是否结束
          let _x = event.clientX;
          let _y = event.clientY;
          if (_this.svgVisible) {
            // 调整曲线
            let path = document.getElementById("pathItem");

            // 调整容器的长宽和top left,目的是为了让起点始终居于svg的旋转中心点，自适应path的大小
            // 这么做的原因是鼠标需要点击svg背后的元素，如果让svg铺满整个屏幕，就无法监听点击事件
            let svgBox = document.getElementById("svgBox");
            let halfWidth = Math.abs(_x - _this.svgX) - 4;
            let halfHeight = Math.abs(_y - _this.svgY) - 4;

            halfHeight = halfHeight < 10 ? 10 : halfHeight;

            svgBox.style.top = _this.svgY - halfHeight - 25 + "px";
            svgBox.style.left = _this.svgX - halfWidth + "px";
            svgBox.style.width = halfWidth * 2;
            svgBox.style.height = halfHeight * 2;
            // 设置贝塞尔曲线的弧度
            let sizeMin = Math.min(halfWidth, halfHeight);
            let angle = sizeMin < 50 ? (sizeMin / 50 * 10) : 10;

            // 调整贝塞尔曲线终点
            let endX = _x > _this.svgX ? halfWidth * 2 - 5 : 5;
            let endY = _y > _this.svgY ? halfHeight * 2 - 5 : 5;
            let pos = getPosition(halfWidth, halfHeight, endX, endY, angle);
            path.setAttribute("d", "M" + halfWidth + " " + halfHeight + " Q" + pos.x + " " + pos.y + " " + endX + " " + endY);
          }
        }
        // 显示悬浮窗
        if (event.target && event.target.getAttribute("class") && event.target.getAttribute("class").indexOf("tag-label") >= 0) {
          let tag = event.target;
          if (parseInt(tag.getAttribute("relationnum")) == 0) { // 显示提示框
            _this.tipsLabel = "单击标签可进行修改，右键连接关系。";
            let tipsBox = document.getElementById("tipsLabelBox");
            tipsBox.style.left = tag.offsetLeft + tag.parentElement.offsetLeft + "px";
            tipsBox.style.top = tag.offsetTop + tag.parentElement.offsetTop + 25 + "px";
            _this.tipsLabelVisible = true;
          } else { // 显示编辑节点关系的卡片
            let modifyTagRelationBox = document.getElementById("modifyTagRelationBox");
            modifyTagRelationBox.style.left = tag.offsetLeft + tag.parentElement.offsetLeft + "px";
            modifyTagRelationBox.style.top = tag.offsetTop + tag.parentElement.offsetTop + 25 + "px";
            let domId = tag.getAttribute("id");
            let modifyRelationCmd = [];
            _this.relationResultList.forEach(relation => {
              if (relation.sourceTag.domId == domId || relation.targetTag.domId == domId) {
                modifyRelationCmd.push(relation);
              }
            });
            _this.modifyTagRelationCmd = modifyRelationCmd;
            _this.modifyTagRelationVisible = true;
          }
        } else {
          _this.tipsLabelVisible = false;
          if (_this.modifyTagRelationCmd.length > 0) { // 控制编辑关系卡片鼠标移除后关闭显示
            var div = document.getElementById("modifyTagRelationBox");
            let bannerHeight = document.getElementsByClassName("header-box")[0].offsetHeight;
            let menuWidth = document.getElementsByClassName("menu-box")[0].offsetWidth;
            let blockHeight = document.getElementById("title-id").offsetHeight;
            if (div != null) {
              var x = event.clientX - menuWidth;
              var y = event.clientY - bannerHeight - blockHeight;
              // 计算浮动卡片的四个角的坐标，判断鼠标坐标是否移出区域
              var divx1 = div.offsetLeft - 5;
              var divy1 = div.offsetTop - 5;
              var divx2 = div.offsetLeft + div.offsetWidth + 5;
              var divy2 = div.offsetTop + div.offsetHeight + 5;
              if (x < divx1 || x > divx2 || y < divy1 || y > divy2) {
                _this.modifyTagRelationCmd = [];
                _this.modifyTagRelationVisible = false;
              }
            }
          }
        }

      }
      document.onmousedown = function (e) {
        if (e.button == "2" && e && e.target && e.target.getAttribute('class') && e.target.getAttribute('class').indexOf('tag-label') >= 0) { // 右键选择了标签
          _this.svgX = e.clientX;
          _this.svgY = e.clientY;
          if (_this.svgVisible && !_this.isBesselEnd) { // 确定贝塞尔曲线终点(svg可视且曲线未结束)
            // 设置曲线已结束
            _this.isBesselEnd = true;
            _this.nowTarget = {
              text: e.target.getAttribute('text'),
              domId: e.target.getAttribute('id'),
              vid: e.target.getAttribute('vid')
            };
            _this.initHtml();
            _this.showSelectRelationCard();
          } else { // 开始起点贝塞尔曲线
            // 清空所有已选样式的标签
            _this.clearTagSelected();
            setTimeout(function () {
              _this.nowSource = {
                text: e.target.getAttribute('text'),
                domId: e.target.getAttribute('id'),
                vid: e.target.getAttribute('vid')
              }
              _this.selectTagVisible = false;
              _this.selectRelationVisible = false;
              // let svgBox = document.getElementById("svgBox");
              let path = document.getElementById("pathItem");
              path.setAttribute("d", "M0 0 Q0 0 0 0");
              path.setAttribute("stroke-dasharray", "5 5")
              _this.svgVisible = true;
              _this.isBesselEnd = false;
              _this.initHtml();
            }, 100);
          }
          // e.target.setAttribute("class", e.target.getAttribute("class") + " tag-active");
        } else {
          _this.svgVisible = false;
        }
      }

      document.oncontextmenu = function () {
        return false;
      }
    });
  },
  filters: {
    nodeNameFormatter(id) {
      let index = _this.nodeList.findIndex(node => {
        return node.vid === id;
      });
      return index >= 0 ? _this.nodeList[index].label : '-';
    },
    relationNameFormatter(edgeName) {
      // console.log(edgeName);
      let index = _this.edgeList.findIndex(edge => {
        return edge.edgeName === edgeName;
      });
      return index >= 0 ? _this.edgeList[index].label : '-';
    }
  },
  methods: {
    backToList() { // 返回数据集列表
      _this.$router.push({path: '/dataSetManage', query: {}});
    },
    removeTagRelation(item, index) { // 删除标签本体间建立的关系
      _this.modifyTagRelationCmd.splice(index, 1);
      if (_this.modifyTagRelationCmd.length === 0) {
        _this.modifyTagRelationVisible = false;
      }
      let relationResultList = [];
      _this.relationResultList.forEach(row => {
        if (row.edgeName !== item.edgeName || row.targetTag.domId !== item.targetTag.domId || row.sourceTag.domId !== item.sourceTag.domId) {
          relationResultList.push(row);
        }
      });
      _this.relationResultList = relationResultList;
      _this.tagResultList.forEach(tag => {
        if (tag.domId === item.targetTag.domId || tag.domId === item.sourceTag.domId) {
          tag.relationNum -= 1;
        }
      });
      // 保存
      setTimeout(() => {
        _this.submitSaveRemarkInfo();
      }, 200);

      _this.initHtml();
    },
    removeTextTag(dom) { // 删除已标注的标签，只要修改页面静态内容dom
      let spanDom = dom.parentElement;
      let labelDom = spanDom.firstElementChild;
      let id = labelDom.getAttribute("id");
      let _tagResultList = [];
      _this.tagResultList.forEach(tag => {
        if (tag.domId != id) {
          _tagResultList.push(tag);
        }
      });
      _this.tagResultList = _tagResultList;
      _this.initHtml();
    },
    initHtml() { // 初始化文本内容，渲染标签
      let content = _this.result.records[0].content;
      let startIndexMap = {};

      _this.tagResultList.forEach(tag => {
        startIndexMap[tag.startOffset] = tag;
      });
      let resultContent = "";
      for (let i = 0; i < content.length; i++) {
        if (startIndexMap[i]) {
          let tag = startIndexMap[i];
          let label = _this.nodeMap[tag.vid].label;
          resultContent += createTagDom(tag.domId, tag.startOffset, tag.endOffset, label, tag.text, tag.relationNum, tag.vid).outerHTML;
          i += tag.text.length;
        } else {
          resultContent += content[i];
        }
      }
      let pBoxDom = document.getElementById("pBox");
      if (pBoxDom) {
        pBoxDom.innerHTML = resultContent;
      }

    },
    submitSaveRemarkInfo() { // 保存标注信息
      request({
        url: '/es_remark_task/updateremarkinfo',
        method: 'post',
        data: {
          id: _this.result.records[0].id,
          nodes: _this.tagResultList,
          relations: _this.relationResultList
        }
      }).then(res => {
        _this.$message.success("保存成功");
      });
    },
    showEditTagDialog() {
      _this.queryRelationData();
      _this.cmdDialogVisible = true;
    },
    handleNodeInputChange(node) { // 监听标签input输入框的改动，提交保存标签
      _this.submitUpdateNode(node);
    },
    submitUpdateNode(node) { // 提交更新或添加本体标签
      node.ontologyId = _this.ontologyId;
      request({
        url: '/nebula_model/insertmodelvertex',
        method: 'post',
        data: node
      }).then(res => {
        _this.queryRelationData();
        _this.$message.success("保存成功");
      });
    },
    handleEdgeInputAndSelectChange(edge) { // 监听标签关系输入框的改动，提交关系保存
      if (edge.srcId.length === 0 || edge.dstId.length === 0 || edge.label.length === 0) {
        return;
      }
      _this.submitUpdateEdge(edge);
    },
    submitUpdateEdge(edge) { // 提交更新或添加标签关系
      edge.ontologyId = _this.ontologyId;
      request({
        url: '/nebula_model/insertmodeledge',
        method: 'post',
        data: edge
      }).then(res => {
        _this.queryRelationData();
        _this.$message.success("保存成功");
      });
    },
    removeNodeOrEdge(type, item) { // 删除标签或者标签关系
      if (type === "edge") {
        if (!item.edgeName) {
          item.label = "";
          item.srcId = '';
          item.dstId = '';
          return false;
        }
      } else if (type === "node") {
        if (!item.vid) {
          item.label = "";
          return false;
        }
      }
      _this.submitRemoveNodeOrEdge(type, item);
    },
    submitRemoveNodeOrEdge(type, item) { // 提交删除标签或者标签关系
      if (!item.canDelete && type === "node") {
        return;
      }
      this.$confirm('此操作将永久删除选中节点或关系以及已标注的内容, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        if (type === "node") {
          request({
            url: `/nebula_model/deletemodelvertex/${item.vid}`,
            method: 'get',
          }).then(res => {
            _this.queryRelationData();
            _this.$message.success("删除成功");
          });
        } else {
          request({
            url: `/nebula_model/deletemodeledge`,
            method: 'post',
            data: item
          }).then(res => {
            _this.queryRelationData();
            _this.$message.success("删除成功");
          });
        }
      }).catch(() => {
      });
    },
    queryRelationData() { // 查询图空间内标签和标签关系
      request({
        url: `/nebula_model/findallgraphbyontologyid/${_this.ontologyId}`,
        method: 'post',
        data: {}
      }).then(res => {
        let nodes = [], edges = [], nodeMap = {};
        let nodesVids = [];
        res.data.relations.forEach(edge => {
          edges.push({
            edgeName: edge.properties.edgeName,
            label: edge.properties.label,
            srcId: edge.srcId,
            dstId: edge.dstId
          });
          nodesVids.push(edge.srcId);
          nodesVids.push(edge.dstId);
        });

        res.data.nodes.forEach(node => {
          nodes.push(nodeMap[node.vid] = {
            vid: node.vid,
            label: node.properties.label,
            canDelete: !(nodesVids.indexOf(node.vid) >= 0)
          });
        });

        _this.nodeMap = nodeMap;
        _this.nodeList = nodes;
        _this.edgeList = edges;
        _this.addNewNodeOrEdge("node");
        _this.addNewNodeOrEdge("edge");
        _this.queryDataSetView(0);
      });
    },
    addNewNodeOrEdge(type) {// 添加一个新的空标签或者空标签关系
      if (type == "node") {
        // "#bfbfbf"
        _this.nodeList.push({
          id: '',
          edge: '',
          canDelete: true
        });
      } else if (type == "edge") {
        _this.edgeList.push({
          label: '',
          srcId: '',
          dstId: '',
        });
      }
    },
    queryDataSetView(pagePlus) { // 分页查询数据集的一行行文本
      _this.qo.pageNo += pagePlus;
      request({
        url: '/es_remark_task/findbytaskidandtasktype', //all/' + _this.qo.taskId + "/" + _this.qo.taskType,
        method: 'post',
        data: _this.qo
      }).then(res => {
        _this.result.records = res.data.content;
        _this.result.total = res.data.totalElements;

        _this.tagResultList = res.data.content[0].nodes || [];
        _this.relationResultList = res.data.content[0].relations || [];
        _this.initHtml();
      });
    },
    confirmTag({vid, label}) { // 确认打本体标签
      if (_this.selectDom != null && _this.selectDom.text.length > 0) { // 打新标签
        // 最简单的一种情况一行选中
        if (_this.selectDom.start.offset !== _this.selectDom.end.offset) {

          // 以开始选中位置切割一分为2的后半部分
          const lastText = _this.selectDom.start.node.splitText(_this.selectDom.start.offset);
          // 以后半部分按照开始选中到结束选中的偏移量切割，并选择前一部分
          let selectedNode = lastText.splitText(_this.selectDom.end.offset - _this.selectDom.start.offset)
              .previousSibling;
          let startOffset = 0, endOffset = 0;
          let previousTag = selectedNode.previousSibling.previousSibling;
          if (previousTag == null) {
            startOffset = _this.selectDom.start.offset;
            endOffset = _this.selectDom.end.offset;
          } else {
            let offset = parseInt(previousTag.getAttribute("endOffset"));
            startOffset = _this.selectDom.start.offset + offset;
            endOffset = _this.selectDom.end.offset + offset;
          }

          // 创建一个span来包裹，并添加样式
          let randomId = Math.round(Math.random() * 1000000).toString();

          let span = createTagDom(randomId, startOffset, endOffset, label, lastText.textContent, 0, vid);
          selectedNode.parentNode.replaceChild(span, selectedNode);

          // 入库
          _this.tagResultList.push({
            domId: randomId,
            text: lastText.textContent,
            vid: vid,
            relationNum: 0,
            startOffset: startOffset,
            endOffset: endOffset
          });
        }
      } else { // 修改原有标签
        let clickTag = _this.clickTag;
        if (clickTag.vid == vid) {
          return;
        }
        let index = _this.tagResultList.findIndex(tagResult => {
          return tagResult.domId == clickTag.domId;
        });
        _this.tagResultList[index].vid = vid;
        _this.initHtml();
      }
      _this.selectTagVisible = false;
    },
    clearTagSelected() { // 清空高亮选中的标签
      _this.nowTarget = {};
      _this.nowSource = {};
      // _this.initHtml();
      let tagSelected = document.getElementsByClassName("tag-active");
      for (let i = tagSelected.length - 1; i >= 0; i--) {
        tagSelected[i].setAttribute("class", tagSelected[i].getAttribute("class").replace("tag-active", " "));
      }
    },
    confirmRelation({edgeName, label}) { // 确认选择本体间关系
      _this.selectRelationVisible = false;
      // 添加新关系
      _this.relationResultList.push({
        edgeName: edgeName,
        sourceTag: _this.nowSource,
        targetTag: _this.nowTarget
      });
      // 更新节点的关系数量
      _this.tagResultList.forEach(tag => {
        if (tag.domId == _this.nowSource.domId || tag.domId == _this.nowTarget.domId) {
          tag.relationNum += 1;
        }
      });
      _this.initHtml();
      _this.svgVisible = true;

      // 保存
      setTimeout(() => {
        _this.submitSaveRemarkInfo();
      }, 200);
    },
    showSelectTagCard() {// 显示选择本体标签弹窗
      setTimeout(function () {
        _this.selectTagVisible = true;
        _this.$nextTick(() => {
          let box = document.getElementById("selectTypeBox");
          box.style.left = _this.x + "px";
          box.style.top = _this.y + "px";
        });
      }, 10);
    },
    showSelectRelationCard() { // 显示选择本体间关系的弹窗
      setTimeout(function () {
        // 过滤可选择的关系
        let filterEdgeList = [];
        _this.edgeList.forEach(edge => {
          if (_this.nowSource.vid == edge.srcId && _this.nowTarget.vid == edge.dstId) {
            filterEdgeList.push(edge);
          }
        });
        _this.filterEdgeList = filterEdgeList;

        _this.selectRelationVisible = true;
        _this.$nextTick(() => {
          let box = document.getElementById("selectRelationBox");
          box.style.left = _this.x + "px";
          box.style.top = _this.y + "px";
        });
      }, 10);
    }
  }
}

// 生成本体标签dom
function createTagDom(id, startOffset, endOffset, label, textContent, relationNum, vid) {
  const span = document.createElement("span");
  span.setAttribute("class", "label-select");
  span.setAttribute("endOffset", endOffset.toString());
  span.setAttribute("startOffset", startOffset.toString());
  span.append(textContent);
  const labelDom = document.createElement("label");
  let _className = "tag-label";
  if (parseInt(relationNum) > 0) {
    _className += " has-relation";
  }
  if ((_this.nowTarget && _this.nowTarget.domId == id) || (_this.nowSource && _this.nowSource.domId == id)) {
    _className += " tag-active";
  }
  labelDom.setAttribute("class", _className);
  labelDom.setAttribute("text", textContent);
  labelDom.setAttribute("label", label);
  labelDom.setAttribute("relationnum", relationNum);
  labelDom.setAttribute("id", id);
  labelDom.setAttribute("vid", vid);
  labelDom.innerText = parseInt(relationNum) > 0 ? (label + "(" + relationNum + ")") : label;

  const i = document.createElement("i");
  i.setAttribute("class", parseInt(relationNum) > 0 ? ("el-icon-error close-btn not-show") : ("el-icon-error close-btn"));
  span.appendChild(labelDom);
  span.appendChild(i);

  return span;
}

// 获取贝塞尔曲线的切线等腰交点
function getPosition(x1, y1, x2, y2, angle) {
  var PI = Math.PI;
  // 两点间的x轴夹角弧度
  var xAngle = Math.atan2((y2 - y1), (x2 - x1));
  // 转为角度
  xAngle = 360 * xAngle / (2 * PI);
  // 两点间的长度
  var L = Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
  // 计算等腰三角形斜边长度
  var L2 = L / 2 / Math.cos(angle * 2 * PI / 360);

  // 判断求的是上切角还是下切角(例如终点在起点右上角，设定为下弧线，即y2 < y1 且 x2 > x1)
  let flag = x2 < x1 ? (y1 < y2) : (y1 > y2);
  var val = {};
  if (flag) {
    val['x'] = x1 + Math.round(L2 * Math.cos((xAngle + angle) * 2 * PI / 360));
    val['y'] = y1 + Math.round(L2 * Math.sin((xAngle + angle) * 2 * PI / 360));
  } else {
    val['x'] = x1 + Math.round(L2 * Math.cos((xAngle - angle) * 2 * PI / 360));
    val['y'] = y1 + Math.round(L2 * Math.sin((xAngle - angle) * 2 * PI / 360));
  }
  return val;
}
</script>

<style lang="scss">

html {
  overflow: hidden;
}

.label-box222 {
  padding: 15px 0;
}

.modify-tag-box {


  margin-bottom: 15px;

  .el-card__body {
    padding: 5px;

    .title {
      height: 25px;
      line-height: 25px;
    }
  }

  .tree-box {
    overflow: hidden;
    height: 50px;

    .left-tree-title {
      width: 27%;
      height: 20px;
      border: 2px solid #afafaf;
      margin-top: 12px;
      line-height: 20px;
      text-align: center;
    }

    .left-tree-icon {
      width: 15%;
      height: 49px;
      background: url("../../assets/image/icon/shuxingtu.png") no-repeat center/contain;
    }

    .left-tree-content {
      width: 45%;

      .non-label {
        height: 25px;
        line-height: 25px;
        padding-left: 10px;
      }
    }

    .left-tree-btn {
      width: 8%;
      padding-top: 10px;

      i {
        font-size: 18px;
      }
    }
  }
}


.tree-box > div {
  float: left;
}

.page-box {
  width: calc(100% - 50px);
  height: 45px;
  text-align: right;
  padding: 0 25px;
}

.edit-box {
  .title {
    font-size: 18px;
    font-weight: bold;
    padding-bottom: 15px;
  }

  .input-box {
    overflow: hidden;
    margin-bottom: 25px;

    .input-item {
      width: 50%;
      float: left;
      margin-bottom: 15px;

      .tag-label-input {
        width: 85%;
        margin-right: 15px;
      }


      .el-icon-delete {
        font-size: 20px;
        cursor: pointer;
      }
    }

    .edge-item {
      width: 100%;
      margin-bottom: 15px;

      .edge-label-input {
        width: 30%;
        margin-right: 15px;
      }

      .edge-node-select {
        width: 30%;
        margin-right: 15px;
      }

      .el-icon-delete {
        font-size: 20px;
        cursor: pointer;
      }


    }
  }
}

.text-content {
  padding: 0;
}

.el-row {
  height: 100%;

  .el-col {
    height: 100%;
  }
}

.tool-box-parent {
  border-left: 1px solid #eee;
}

.tool-box > div {
  padding-left: 15px;
  padding-right: 15px;
}

.tool-box > .title {
  padding-top: 10px;
  padding-bottom: 5px;
}

.tool-box {
  height: 100%;

  .title {
    height: 30px;
    line-height: 20px;
    font-size: 14px;
    font-weight: bolder;
    border-bottom: 1px solid #eee;
    margin-bottom: 5px;

    .label-box, .button-box {
      width: 50%;
      float: left;
    }

    .label-box {
      font-size: 18px;
    }

    .button-box {
      text-align: right;
      cursor: pointer;
    }
  }

  .back-box {
    padding-top: 10px;
    padding-bottom: 20px;
  }

  .result-title {
    border-top: 1px solid #eee;
    padding-top: 25px;
  }

  .relation-box {
    padding-bottom: 25px;
  }

  .relation-box, .sign-result-box {
    height: 40%;
    overflow-y: auto;

    .el-card {
      margin-top: 20px;

      .el-card__body {
        padding: 15px 15px 5px 15px;
      }

      .non-label {
        height: 30px;
        line-height: 30px;
      }
    }
  }
}

#pBox {
  padding: 0 25px;
  font-size: 18px;
  line-height: 45px;
  margin-top: 0;
  text-indent: 2em;
  text-align: justify;
}

#svgBox {
  position: absolute;
  top: 0;
  width: calc(100% - 50px);
  height: calc(100% - 50px);
  margin-top: 25px;
}

.tag-label {
  color: rgb(26, 115, 232);
  background: rgb(224, 238, 254);
  padding: 5px 10px;
  margin: 0 5px;
  border-radius: 3px;
  cursor: pointer;
}

.tag-label.tag-active {
  color: #f39000 !important;
  border: 1px solid #f39000 !important;
  background-color: rgba(245, 161, 65, .15) !important;
}

#con {
  font-size: 16px;
  line-height: 35px;
}

.label-select {
  color: #1a73e8;
  font-weight: bold;
  position: relative;

  .close-btn {
    display: none;
    position: absolute;
    right: -10px;
    top: -15px;
    color: #4c4c4c;
    font-size: 18px;
    cursor: pointer;
  }
}

.label-select:hover {
  .close-btn {
    display: inline-block;
  }

  .close-btn.not-show {
    display: none;
  }
}

.tips-label-box {
  position: absolute;
  width: 320px;
  font-size: 14px;
  left: 0;
  top: 0;
}

.modify-tag-relation-box {
  position: absolute;
  width: 320px;
  font-size: 14px;
  left: 0;
  top: 0;
}

.select-type-box, .select-relation-box {
  position: absolute;
  width: 185px;
  left: 0;
  top: 0;

  .el-card__body {
    padding: 0;
  }

  .title {
    height: 30px;
    line-height: 30px;
    color: rgb(153, 153, 153);
    padding: 0px 0px 0px 10px;
    text-align: left;
    border-bottom: 1px solid rgb(246, 246, 246);
  }

  .tag-box {
    max-height: 168px;
    overflow: auto;
    padding: 0px 10px;
    cursor: pointer;

    .tag-item {
      height: 32px;
      line-height: 32px;
      word-break: keep-all;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      color: #333;
    }
  }
}
</style>
