vue手写多对多关联图,连线用leader-line

这篇具有很好参考价值的文章主要介绍了vue手写多对多关联图,连线用leader-line。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

效果如图

vue手写多对多关联图,连线用leader-line,VUE,vue.js,elementui,javascript

 鼠标滑动效果

vue手写多对多关联图,连线用leader-line,VUE,vue.js,elementui,javascript

关联性效果

vue手写多对多关联图,连线用leader-line,VUE,vue.js,elementui,javascript 

vue手写多对多关联图,连线用leader-line,VUE,vue.js,elementui,javascript 

<template >
  <div class="main" ref="predecessor">
    <div class="search">
      <div class="search-item">
        <div class="search-item-label">
          部门
        </div>
        <Treeselect v-model="dept"
                                        :options="deptOptions"
                                        show-count
                                        placeholder="请选择部门"
                                        @change="changeDept()"
                            />
        <!-- <el-cascader v-model="dept" clearable placeholder="请选择部门" >
          <el-option v-for="item in deptOptions" :key="item.id" :label="item.label" :value="item.id">
          </el-option>
        </el-cascader> -->
      </div>
      <div class="search-item">
        <div class="search-item-label">
          周期
        </div>
        <el-cascader v-model="TypeSelectValue" :options="cycleTypeSelect" :props="{
          checkStrictly: true,
          expandTrigger: 'hover'
        }" @change="handleChange" />
      </div>
    </div>
    <div class="main-predecessor">
      <div v-for="(item, index) in predecessorList" :key="'father' + index" class="father-predecessor">
        <div v-for="(itm, idx) in item" :key="itm.id">
          <div v-if="itm.status === 0 && itm.display === true" :id="itm.id" class="children-predecessor-big"
            @mouseenter="enter(index, idx)" @mouseleave="leave()"
            :style="changeCardList.indexOf(itm.id) === -1 ? '' : 'background:rgba(206, 210, 232, 0.87)'"
            @click="clickDownsize(index, idx)">
            <div class="caption">{{ itm.okrUserOrDept }}</div>
            <Vptip :content="itm.okrOContent" :width="'100%'" style="max-width: 500px;margin-bottom: 10px;">
              <span class="O-list" :style="changeOList.indexOf(itm.id) === -1 ? '' : 'color:#8198fe;font-weight: 700;'">O:
                {{
                  `${itm.okrOContent}` }}</span>
            </Vptip>
            <div v-for="(im, indx) in itm.okrKrConfigList">
              <Vptip :content="im.okrKrContent" :width="'100%'" style="max-width: 500px;">
                <span class="kr-list"
                  :style="changeKrList.indexOf(im.id) === -1 ? '' : 'color:#8198fe;font-weight: 700;'">
                  KR{{ indx + 1 }}: {{ `${im.okrKrContent}` }}
                </span>
              </Vptip>
            </div>
            <div class="button">
              <el-button type="text" class="open" v-if="itm.isHeader && !itm.buttonDisplay" :disabled="timeout !== 0"
                @click.stop="commencementAll(index, itm.id, idx)" icon="el-icon-caret-bottom" />
              <el-button type="text" class="open" v-if="itm.isHeader && itm.buttonDisplay" :disabled="timeout !== 0"
                @click.stop="implicitAll(index, itm.id, idx)" icon="el-icon-caret-top" />
            </div>
          </div>
          <div v-if="itm.status === 1 && itm.display === true" :id="itm.id" class="children-predecessor-middle"
            @mouseenter="enter(index, idx)" @mouseleave="leave()"
            :style="changeCardList.indexOf(itm.id) === -1 ? '' : 'background:rgba(206, 210, 232, 0.87)'"
            @click="clickDownsize(index, idx)">
            <div class="caption">{{ itm.okrUserOrDept }}</div>
            <Vptip :content="itm.okrOContent" :width="'100%'" style="max-width: 500px;">
              <span class="O-list" :style="changeOList.indexOf(itm.id) === -1 ? '' : 'color:#8198fe;font-weight: 700;'">O:
                {{
                  `${itm.okrOContent}` }}</span>
            </Vptip>
            <div class="button">
              <el-button type="text" class="open" v-if="itm.isHeader && !itm.buttonDisplay" :disabled="timeout !== 0"
                @click.stop="commencementAll(index, itm.id, idx)" icon="el-icon-caret-bottom" />
              <el-button type="text" class="open" v-if="itm.isHeader && itm.buttonDisplay" :disabled="timeout !== 0"
                @click.stop="implicitAll(index, itm.id, idx)" icon="el-icon-caret-top" />
            </div>
          </div>
          <div v-if="itm.status === 2 && itm.display === true" :id="itm.id" class="children-predecessor-small"
            @mouseenter="enter(index, idx)" @mouseleave="leave()"
            :style="changeCardList.indexOf(itm.id) === -1 ? '' : 'background:rgba(206, 210, 232, 0.87)'"
            @click="clickDownsize(index, idx)">
            <div class="caption">{{ itm.okrUserOrDept }}</div>
            <div class="button">
              <el-button type="text" class="open" v-if="itm.isHeader && !itm.buttonDisplay" :disabled="timeout !== 0"
                @click.stop="commencementAll(index, itm.id, idx)" icon="el-icon-caret-bottom" />
              <el-button type="text" class="open" v-if="itm.isHeader && itm.buttonDisplay" :disabled="timeout !== 0"
                @click.stop="implicitAll(index, itm.id, idx)" icon="el-icon-caret-top" />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script >
import { deptTreeSelect } from '@/api/system/user'
import okrConstant from '@/utils/okr/okrConstant'
import { getOkrPredecessor } from '@/api/okr/okrPredecessor'
import LeaderLine from 'leader-line'
import Vptip from "@/components/vptip" // 自定义Tooltip 文字提示
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
export default {
  name: 'okrPredecessor',
  components: {
    Vptip,Treeselect
  },
  data() {
    return {
      // 级联选择器下拉框
      selectOKrForm: {
        // 年份搜索值
        okrCycleYear: okrConstant.yearDecade()[0].value,
        // 季度搜索值
        okrCycleQuarter: okrConstant.currentMonthIsQuarter(),
        // 月份搜索值
        okrCycleMonth: '',
      },
      TypeSelectValue: okrConstant.currentMonthIsQuarter(),
      cycleTypeSelect: okrConstant.yearDecade(),
      // 部门列表
      deptOptions: [],
      // 部门数据
      dept: undefined,
      // 控制按钮的倒计时
      timeout: 0,
      // 线条的数组
      lineList: {},
      // 接口返回的循环数据
      predecessorListAll: [],
      // 继承的渲染数据
      predecessorList: [
        // [
        //   {
        //     id: 0,
        //     // 展开还是缩小状态
        //     status: 0,
        //     // 能否展开或者缩小下一级
        //     isHeader: true,
        //     // 目前在显示还是隐藏
        //     display: true,
        //     // 当前按钮是展开还是隐藏
        //     buttonDisplay: true,
        //     // 父级相关的id合集
        //     fatherUserId: []
        //   }
        // ]
      ],
      // 所有的线条
      allLineList: [],
      // 应该画出来的线
      drawLineList: [],
      // 隐藏的数组list
      displayList: [],
      // 变色的卡片ID
      changeCardList: [],
      // 变色的KRID
      changeKrList: [],
      // 变色的OID
      changeOList: [],
      // 变色的线段
      changeDrawLineList: []
    }
  },
  watch:{
    dept(){
      this.search()
    }
  },
  // 注册监听
  mounted() {
    // 获取部门列表
    this.getDept()
    window.addEventListener('resize', this.getScroll)
    // 监听div滚动事件
    let scrollView = this.$refs.predecessor
    scrollView.addEventListener("scroll", this.positionLine);
    document.body.style.overflow = 'hidden'
    this.search()
  },
  // 销毁前
  beforeDestroy() {
    document.body.style.overflow = ''
    // 销毁线条
    this.removeLines()
  },
  // 销毁监听,防止内存泄露
  destroyed() {
    window.removeEventListener('resize', this.getScroll)
  },
  methods: {
    // 获取部门列表
    getDept() {
      deptTreeSelect().then(response => {
        this.deptOptions = response.data[0].children
      })
    },
    // 鼠标移出事件(将O和KR两个数组归0)
    leave() {
      // 变色的KRID
      this.changeKrList = []
      // 变色的OID
      this.changeOList = []
      // 变色的卡片ID
      this.changeCardList = []
      // 线段颜色恢复
      this.resumption()
      // 特殊线段归0
      this.changeDrawLineList = []
    },
    // 鼠标移入事件
    enter(index, idx) {
      // 变色的KRID
      this.changeKrList = []
      // 变色的OID
      this.changeOList = []
      // 变色的卡片ID
      this.changeCardList = []
      // 获取到第一层DIV之后首先对上下紧挨的关系层进行循环
      // 寻找相关的父级
      this.findFatherList(index, idx)
      // 寻找相关的子集
      this.findChildrenList(index, idx)
      // 先将他本身的数据push进去
      if (this.predecessorList[index][idx].okrKrConfigList) {
        for (let i = 0; i < this.predecessorList[index][idx].okrKrConfigList.length; i++) {
          this.changeKrList.push(this.predecessorList[index][idx].okrKrConfigList[i].id)
        }
      }
      this.changeOList.push(this.predecessorList[index][idx].id)
      this.changeCardList.push(this.predecessorList[index][idx].id)
      // 获取线段变化线段
      this.getChangeLine()
      // 更改相关线段
      this.changeLineColor()
    },
    // 获取相关需要更改线段
    getChangeLine() {
      this.changeDrawLineList = []
      // 判断开头结尾都在变色的卡片之中
      for (let j = 0; j < this.drawLineList.length; j++) {
        if (this.changeCardList.indexOf(Number(this.drawLineList[j].start)) !== -1 && this.changeCardList.indexOf(Number(this.drawLineList[j].end)) !== -1) {
          this.changeDrawLineList.push(this.drawLineList[j])
        }
      }
    },
    // 恢复相关线段颜色
    resumption() {
      for (let i = 0; i < this.changeDrawLineList.length; i++) {
        this.twiceGetLine(this.changeDrawLineList[i].start, this.changeDrawLineList[i].end, i)
      }
    },
    // 更改相关线段颜色
    changeLineColor() {
      for (let i = 0; i < this.changeDrawLineList.length; i++) {
        this.changeGetLine(this.changeDrawLineList[i].start, this.changeDrawLineList[i].end, i)
      }
    },
    // 寻找相关的子集
    findChildrenList(index, idx) {
      // 对所有的线段进行遍历(因为只有O能够继承,所以子集直接往下找,只要有就是要变色)
      for (let i = 0; i < this.drawLineList.length; i++) {
        // 寻找到以此为开头的数据
        if (this.drawLineList[i].start === String(this.predecessorList[index][idx].id)) {
          // 对页面数据进行遍历寻找出此ID的位置,为后续获取KR做准备
          for (let x = 0; x < this.predecessorList.length; x++) {
            if (this.predecessorList[x]) {
              for (let y = 0; y < this.predecessorList[x].length; y++) {
                // 寻找到线段结束点位的内容
                if (String(this.predecessorList[x][y].id) === this.drawLineList[i].end) {
                  // 变色的OID
                  this.changeOList.push(this.predecessorList[x][y].id)
                  // 变色的卡片ID
                  this.changeCardList.push(this.predecessorList[x][y].id)
                  //  对KRLIST进行遍历,渲染KRLIST
                  for (let z = 0; z < this.predecessorList[x][y].okrKrConfigList.length; z++) {
                    // 将所有相关kr进行push
                    this.changeKrList.push(this.predecessorList[x][y].okrKrConfigList[z].id)
                  }
                  // 删除递归根节点功能
                  // this.findChildrenList(x, y)
                }
              }
            }
          }
        }
      }
    },
    // 寻找相关的父级
    findFatherList(index, idx) {
      let fatherList = this.predecessorList[index][idx].extendsKrs
      // 对FATHERlIST进行遍历
      for (let i = 0; i < fatherList.length; i++) {
        // 只要是存在的Oid卡片就跟随变色(需要两点之间还存在连线关系)
        for (let j = 0; j < this.drawLineList.length; j++) {
          if (this.drawLineList[j].start === String(fatherList[i].okrId) && this.drawLineList[j].end === String(this.predecessorList[index][idx].id)) {
            this.changeCardList.push(fatherList[i].okrId)
            // 如果继承的是KR(O不变色,但是KR需要变色)
            if (fatherList[i].krId !== null) {
              this.changeKrList.push(fatherList[i].krId)
            } else {
              // 如果继承的是O,那么OKR全部变色,并且继续寻找上一层的OKR
              // 将变色Opush进入O的数组
              this.changeOList.push(fatherList[i].okrId)
              // 将变色O所对应的KR全部push进入数组(遍历寻找对应O)
              for (let x = 0; x < this.predecessorList.length; x++) {
                if (this.predecessorList[x]) {
                  for (let y = 0; y < this.predecessorList[x].length; y++) {
                    // 找出对应的ID
                    if (fatherList[i].okrId === this.predecessorList[x][y].id) {
                      // 对ID中的OKR进行循环push
                      for (let z = 0; z < this.predecessorList[x][y].okrKrConfigList.length; z++) {
                        // 将所有相关kr进行push
                        this.changeKrList.push(this.predecessorList[x][y].okrKrConfigList[z].id)
                      }
                      // 寻找完O之后,将后生成的O继续进行上层寻找循环
                      // 删除递归根节点功能
                      // this.findFatherList(x, y)
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    // 级联改变方法
    handleChange(value) {
      this.selectOKrForm.okrCycleYear = ''
      this.selectOKrForm.okrCycleQuarter = ''
      this.selectOKrForm.okrCycleMonth = ''
      value.forEach((element, index) => {
        switch (index) {
          case 0: this.selectOKrForm.okrCycleYear = element; break
          case 1: this.selectOKrForm.okrCycleQuarter = element; break
          case 2: this.selectOKrForm.okrCycleMonth = element; break
        }
      })
      this.search()
    },
    // 搜索
    search() {
      let params = {
        okrUserDept: this.dept,
        okrCycleYear: this.selectOKrForm.okrCycleYear,
        okrCycleQuarter: this.selectOKrForm.okrCycleQuarter,
        okrCycleMonth: this.selectOKrForm.okrCycleMonth
      }
      // 跑接口之前销毁所有线条
      this.removeLines()
      // 获取继承关系详细数据
      getOkrPredecessor(params).then(res => {
        // 所有的线条
        this.allLineList = []
        // 应该画出来的线
        this.drawLineList = []
        // 页面渲染数据
        this.predecessorList = []
        this.predecessorListAll = res.data
        // 递归逻辑
        for (let i = 0; i < this.predecessorListAll.length; i++) {
          for (let j = 0; j < this.predecessorListAll[i].inheritedToOkrId.length; j++) {
            let level = this.predecessorListAll[i].level
            let fatherUserId = []
            // 获取fatherList
            for (let x = 0; x < this.predecessorListAll[i].inheritedToOkrId[j].extendsKrs.length; x++) {
              if (fatherUserId.indexOf(this.predecessorListAll[i].inheritedToOkrId[j].extendsKrs[x].okrId) === -1) {
                fatherUserId.push(this.predecessorListAll[i].inheritedToOkrId[j].extendsKrs[x].okrId)
              }
            }
            // 如果此层级还不存在先重置层级
            if (!this.predecessorList[level]) {
              this.predecessorList[level] = []
            }
            this.predecessorList[level].push(this.predecessorListAll[i].inheritedToOkrId[j])
            this.predecessorList[level][this.predecessorList[level].length - 1].fatherUserId = fatherUserId
            this.predecessorList[level][this.predecessorList[level].length - 1].status = 2
            this.predecessorList[level][this.predecessorList[level].length - 1].isHeader = false
            this.predecessorList[level][this.predecessorList[level].length - 1].display = true
            this.predecessorList[level][this.predecessorList[level].length - 1].buttonDisplay = true
          }
        }
        // 寻找父子关系的连线
        this.findLineList()
        // 更改header属性判断是否可以当标头
        this.whetherIsHeader()
        // 刷新数组变化导致的数据更改
        this.$forceUpdate()
        // 画线(页面加载有一个动画,需要等动画完毕再开始加载线条保证线条的稳定性)
        setTimeout(() => { this.drawLine() }, 500)
      })
    },
    // 判断是否可以当标头,是否含有展开收起属性
    whetherIsHeader() {
      // 对二维数组进行遍历
      for (let x = 0; x < this.predecessorList.length; x++) {
        // 判断此层级中是否有内容
        if (this.predecessorList[x]) {
          for (let y = 0; y < this.predecessorList[x].length; y++) {
            // 对当前所有连线进行遍历
            for (let i = 0; i < this.allLineList.length; i++) {
              // 如果存在以此ID为开头的线段,证明为header,isHeader属性为true
              if (String(this.predecessorList[x][y].id) === this.allLineList[i].start) {
                this.predecessorList[x][y].isHeader = true
              }
            }
          }
        }
      }
    },
    // 寻找父子关系的连线
    findLineList() {
      // 对二维数组进行循环
      for (let i = 0; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            // 对父级进行循环
            if (this.predecessorList[i][j].fatherUserId) {
              for (let x = 0; x < this.predecessorList[i][j].fatherUserId.length; x++) {
                if (this.existent(this.predecessorList[i][j].fatherUserId[x], this.predecessorList[i][j].id)) {
                  this.allLineList.push(
                    {
                      start: String(this.predecessorList[i][j].fatherUserId[x]),
                      end: String(this.predecessorList[i][j].id)
                    }
                  )
                }
              }
            }
          }
        }

      }
      // 将数据同步到渲染的线段中,默认全部显示
      this.drawLineList = JSON.parse(JSON.stringify(this.allLineList))
    },
    // 验证是否存在该条数据
    existent(start, end) {
      // 对二维数组进行遍历
      for (let i = 0; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            if (this.predecessorList[i][j].id === start) {
              // 如果开始符合,看结束是否存在
              for (let x = 0; x < this.predecessorList.length; x++) {
                if (this.predecessorList[x]) {
                  for (let y = 0; y < this.predecessorList[x].length; y++) {
                    if (this.predecessorList[x][y].id === end) {
                      return true
                    }
                  }
                }
              }
            }
          }
        }
      }
      return false
    },
    // 点击展开
    // 准备开始展开递归将所有关联的数据进行处理
    commencementAll(index, id, idx) {
      this.timeout = 1
      // 防抖
      setTimeout(() => {
        this.timeout = 0
      }, 500)
      // 更改按钮
      let changeIndex
      for (let i = 0; i < this.predecessorList[index].length; i++) {
        if (this.predecessorList[index][i].id === id) {
          changeIndex = i
        }
      }
      // 更改按钮状态
      this.predecessorList[index][changeIndex].buttonDisplay = true
      // 目前已经确认隐藏的id标题
      // 把跟原标题有关的数据全都找出来再进行删除
      let oldIdList = this.displayList
      //  所有可以改回状态的ID
      let deleteIdList = [id]
      // 新的ID集合
      let newIdList = []
      // 进行遍历寻找不需要删除的数组
      for (let i = index; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            if (this.arbitrarily(deleteIdList, this.predecessorList[i][j].fatherUserId)) {
              deleteIdList.push(this.predecessorList[i][j].id)
            }
          }
        }

      }
      //  生成新数组
      for (let i = 0; i < oldIdList.length; i++) {
        let haveId = true
        for (let j = 0; j < deleteIdList.length; j++) {
          if (deleteIdList[j] === oldIdList[i]) {
            haveId = false
          }
        }
        if (haveId) {
          newIdList.push(oldIdList[i])
        }
      }
      // 当前禁止当头的数据 this.displayList
      this.displayList = [...newIdList]
      // 更改页面
      this.commencement(index, id)
      // 更改页面显示
      this.enter(index, idx)
    },
    // 点击收起
    implicitAll(index, id, idx) {
      this.timeout = 1
      // 防抖
      setTimeout(() => {
        this.timeout = 0
      }, 1000)
      // 更改按钮
      let changeIndex
      for (let i = 0; i < this.predecessorList[index].length; i++) {
        if (this.predecessorList[index][i].id === id) {
          changeIndex = i
        }
      }
      // 更改按钮状态
      this.predecessorList[index][changeIndex].buttonDisplay = false
      // 寻找子集
      this.findImplicit(index, id)
      // 更改页面
      this.implicit(index, id)
      // 更改页面显示
      this.enter(index, idx)
    },
    // 展开隐藏后续内容
    commencement(index, id) {
      // 操作线条
      // 销毁线条
      this.removeLines()
      // 线条数组清零
      let newDrawLineList = []
      // 对目前所有线条进行循环
      for (let i = 0; i < this.allLineList.length; i++) {
        let remain = false
        for (let j = 0; j < this.displayList.length; j++) {
          if (this.allLineList[i].start === String(this.displayList[j])) {
            remain = true
          }
        }
        if (remain === false) {
          newDrawLineList.push(this.allLineList[i])
        }
      }
      // 操作卡片
      // 寻找各个子集,并且观察子集是不是只有这一个fatherUserId
      for (let i = index; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            // 如果先前的隐藏那么展开后按钮状态为打开
            if (this.predecessorList[i][j].display === false) {
              this.predecessorList[i][j].buttonDisplay = true
            }
            // 只有从无到有的才进行回显
            this.predecessorList[i][j].display = true
            if (this.including(this.displayList, this.predecessorList[i][j].fatherUserId)) {
              this.predecessorList[i][j].display = false
            }
          }
        }

      }
      // 验证画线数据结尾是否实际存在
      let trueLineList = []
      for (let i = 0; i < newDrawLineList.length; i++) {
        if (this.trueDiv(newDrawLineList[i].end)) {
          trueLineList.push(newDrawLineList[i])
        }
      }
      // 新的画线数据
      this.drawLineList = trueLineList
      // 重新画线
      setTimeout(() => { this.drawLine() }, 50)
    },
    // 隐藏后续内容
    implicit(index, id) {
      // 操作线条
      // 销毁线条
      this.removeLines()
      // 线条数组清零
      let newDrawLineList = []
      // 对目前所有线条进行循环
      for (let i = 0; i < this.allLineList.length; i++) {
        let remain = false
        for (let j = 0; j < this.displayList.length; j++) {
          if (this.allLineList[i].start === String(this.displayList[j])) {
            remain = true
          }
        }
        if (remain === false) {
          newDrawLineList.push(this.allLineList[i])
        }
      }
      // 操作卡片
      // 寻找各个子集,并且观察子集是不是只有这一个fatherUserId
      for (let i = index; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            if (this.including(this.displayList, this.predecessorList[i][j].fatherUserId)) {
              this.predecessorList[i][j].display = false
            }
          }
        }

      }
      // 验证画线数据结尾是否实际存在
      let trueLineList = []
      for (let i = 0; i < newDrawLineList.length; i++) {
        if (this.trueDiv(newDrawLineList[i].end)) {
          trueLineList.push(newDrawLineList[i])
        }
      }
      // 新的画线数据
      this.drawLineList = trueLineList
      // 重新画线
      setTimeout(() => { this.drawLine() }, 50)
    },
    // div节点是否是实际存在的
    trueDiv(id) {
      for (let i = 0; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            if (String(this.predecessorList[i][j].id) === id && this.predecessorList[i][j].display === true) {
              return true
            }
          }
        }
      }
      return false
    },
    // 寻找子集
    findImplicit(index, id) {
      // 目前已经确认隐藏的id标题
      this.displayList.push(id)
      // 进行遍历寻找
      for (let i = index; i < this.predecessorList.length; i++) {
        if (this.predecessorList[i]) {
          for (let j = 0; j < this.predecessorList[i].length; j++) {
            if (this.including(this.displayList, this.predecessorList[i][j].fatherUserId)) {
              this.displayList.push(this.predecessorList[i][j].id)
            }
          }
        }
      }
      let newArr = [];
      for (let i = 0; i < this.displayList.length; i++) {
        if (!newArr.includes(this.displayList[i])) {
          newArr.push(this.displayList[i])
        }
      }
      // 隐藏的数组数据: this.displayList
      this.displayList = [...newArr]
    },
    // 判断子数组中是否存在任意包含关系,不需要全等,只要有一个就行
    arbitrarily(list, fatherUserId) {
      let x = 0
      for (let i = 0; i < list.length; i++) {
        for (let j = 0; j < fatherUserId.length; j++) {
          if (list[i] === fatherUserId[j]) {
            x++
          }
        }
      }
      if (x !== 0) {
        return true
      }
      return false
    },
    // 判断子数组中是否存在包含关系
    including(list, fatherUserId) {
      let x = 0
      for (let i = 0; i < list.length; i++) {
        for (let j = 0; j < fatherUserId.length; j++) {
          if (list[i] === fatherUserId[j]) {
            x++
          }
        }
      }
      if (x === fatherUserId.length && fatherUserId.length !== 0) {
        return true
      }
      return false
    },
    // 重新定位各线
    positionLine() {
      for (let i = 0; i < this.drawLineList.length; i++) {
        let nameLine = this.drawLineList[i].start + '' + this.drawLineList[i].end
        // 判断线段是否真实存在
        if (this.lineList[nameLine] !== undefined) {
          this.lineList[nameLine].position()
        }
      }
    },
    // 开始画线
    drawLine() {
      for (let i = 0; i < this.drawLineList.length; i++) {
        this.getLine(this.drawLineList[i].start, this.drawLineList[i].end, i)
      }
    },
    // 点击展开缩小重新定位线条
    hideLines() {
      for (let i = 0; i < this.drawLineList.length; i++) {
        let nameLine = this.drawLineList[i].start + '' + this.drawLineList[i].end
        this.lineList[nameLine].position()
      }
    },
    // 隐藏线条
    hideLine(start, end) {
      let nameLine = start + end
      this.lineList[nameLine].hide()
    },
    // 销毁线条
    removeLines() {
      for (let i = 0; i < this.drawLineList.length; i++) {
        let nameLine = this.drawLineList[i].start + '' + this.drawLineList[i].end
        this.lineList[nameLine].remove()
      }
    },
    // 点击展开
    clickDownsize(index, idx) {
      // 卡片三种状态互相切换
      if (this.predecessorList[index][idx].status === 0) {
        this.predecessorList[index][idx].status = 1
      } else if (this.predecessorList[index][idx].status === 1) {
        this.predecessorList[index][idx].status = 2
      } else {
        this.predecessorList[index][idx].status = 0
      }
      let predecessor = JSON.parse(JSON.stringify(this.predecessorList))
      this.predecessorList = []
      this.predecessorList = JSON.parse(JSON.stringify(predecessor))
      setTimeout(() => { this.positionLine() }, 50)
    },
    // 画线
    changeGetLine(start, end, i) {
      let lineStart = document.getElementById(start)
      let lineEnd = document.getElementById(end)
      let styleOption = {
        color: 'red', // 指引线颜色
        endPlug: '', // 指引线结束点的样式 hand,disc
        size: 2, // 线条尺寸
        startSocket: 'bottom', //在指引线开始的地方从元素左侧开始
        endSocket: 'top', //在指引线开始的地方从元素右侧结束
        // hide: true, // 绘制时隐藏,默认为false,在初始化时可能会出现闪烁的线条
        // startPlugColor: '#ff3792', // 渐变色开始色
        // endPlugColor: '#fff386', // 渐变色结束色
        gradient: false, // 使用渐变色
        outLineColor: 'blue',
        path: 'fluid', // straight,arc,fluid,magnet,grid
        // dash: {
        //   // 虚线样式
        //   animation: true // 让线条滚动起来
        // },
        hide: true,
      }
      let nameLine = start + end
      this.lineList[nameLine].setOptions(styleOption)
      /** 显示效果
       *  draw 绘制线条
       *  fade 淡入
       *  none 无效果,即直接显示
       */
      // let showEffectName = 'draw'
      // // 动画参数
      // let animOptions = {
      //   // duration: 1000, //持续时长
      //   // timing: 'ease-in' // 动画函数
      // }
      // this.lineList[nameLine].show()
      // this.lineList[nameLine].position()
    },
    // 恢复线段
    twiceGetLine(start, end, i) {
      let lineStart = document.getElementById(start)
      let lineEnd = document.getElementById(end)
      let styleOption = {
        color: '#6a6a6a', // 指引线颜色
        endPlug: '', // 指引线结束点的样式 hand,disc
        size: 2, // 线条尺寸
        startSocket: 'bottom', //在指引线开始的地方从元素左侧开始
        endSocket: 'top', //在指引线开始的地方从元素右侧结束
        // hide: true, // 绘制时隐藏,默认为false,在初始化时可能会出现闪烁的线条
        // startPlugColor: '#ff3792', // 渐变色开始色
        // endPlugColor: '#fff386', // 渐变色结束色
        gradient: false, // 使用渐变色
        outLineColor: '#6a6a6a',
        path: 'fluid', // straight,arc,fluid,magnet,grid
        // dash: {
        //   // 虚线样式
        //   animation: true // 让线条滚动起来
        // },
        hide: true,
      }
      let nameLine = start + end
      this.lineList[nameLine].setOptions(styleOption)
    },
    // 画线
    getLine(start, end, i) {
      let lineStart = document.getElementById(start)
      let lineEnd = document.getElementById(end)
      let styleOption = {
        color: '#6a6a6a', // 指引线颜色
        endPlug: '', // 指引线结束点的样式 hand,disc
        size: 2, // 线条尺寸
        startSocket: 'bottom', //在指引线开始的地方从元素左侧开始
        endSocket: 'top', //在指引线开始的地方从元素右侧结束
        // hide: true, // 绘制时隐藏,默认为false,在初始化时可能会出现闪烁的线条
        // startPlugColor: '#ff3792', // 渐变色开始色
        // endPlugColor: '#fff386', // 渐变色结束色
        gradient: false, // 使用渐变色
        outLineColor: '#6a6a6a',
        path: 'fluid', // straight,arc,fluid,magnet,grid
        // dash: {
        //   // 虚线样式
        //   animation: true // 让线条滚动起来
        // },
        hide: true,
      }
      let nameLine = start + end
      this.$set(this.lineList, `${nameLine}`, null)
      this.lineList[nameLine] = new LeaderLine(lineStart, lineEnd, styleOption)
      /** 显示效果
       *  draw 绘制线条
       *  fade 淡入
       *  none 无效果,即直接显示
       */
      // let showEffectName = 'draw'
      // // 动画参数
      // let animOptions = {
      //   // duration: 1000, //持续时长
      //   // timing: 'ease-in' // 动画函数
      // }
      this.lineList[nameLine].show()
      this.lineList[nameLine].position()
    },
  }
}
</script>
<style>
.leader-line {
  z-index: -1
}
</style>
<style lang="scss" scoped >
.search-item-label {
  width: 30%;
  font-size: 14px;
  color: #333;
  font-weight: 600;
}

.search-item {
  width: 40%;
  display: flex;
  align-items: center;
  margin-right: 5%;
}
.vue-treeselect{
  width: 80%;
}
.search {
  margin-left: 20px;
  margin-top: 20px;
  width: 500px;
  display: flex;
  justify-content: space-between;

  .el-button+.el-button {
    margin-left: 0px;
  }
}

.main {
  height: calc(100vh - 84px);
  overflow-x: scroll;
  overflow-y: scroll;
}

.main-predecessor {
  min-width: 1100px;
  width: fit-content;
  min-height: calc(100vh - 94px);

}

.father-predecessor {
  display: flex;
  justify-content: space-around;
  align-items: flex-start;
}

.children-predecessor-big {
  width: 250px;
  height: 200px;
  border: 1px solid #f8f8f8;
  margin: 60px 10px;
  border-radius: 6px;
  box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
  background-color: rgba(255, 255, 255, 0.95);
  position: relative;

}

.children-predecessor-middle {
  width: 200px;
  height: 80px;
  border: 1px solid #f8f8f8;
  margin: 60px 10px;
  border-radius: 6px;
  box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
  background-color: rgba(255, 255, 255, 0.95);
  position: relative;
}

.children-predecessor-small {
  width: 130px;
  height: 50px;
  border: 1px solid #f8f8f8;
  margin: 60px 10px;
  border-radius: 6px;
  box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
  background-color: rgba(255, 255, 255, 0.95);
  position: relative;
}

.button {
  position: absolute;
  text-align: center;
  bottom: -10px;
  left: 0;
  width: 100%;
  height: 20px;

}

.caption {
  text-align: center;
  line-height: 30px;
}

.icon {
  cursor: pointer;
  float: right;
  font-size: 20px;
  line-height: 30px;
  color: #158BBB;
  margin-right: 10px;
}

::v-deep .open {
  padding: 0 !important;
  border: 1px solid #b4b4b4;
  box-shadow: rgba(135, 138, 141, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
  background-color: rgba(255, 255, 255, 0.95);
  border-radius: 50%;
}

.O-list {
  margin-left: 10px;
  font-size: 18px;
}

.kr-list {
  margin-left: 10px;
  font-size: 16px;
}

::v-deep .small-select {
  .el-input__inner {
    width: 120px;
  }
}
</style>

 文章来源地址https://www.toymoban.com/news/detail-642576.html

到了这里,关于vue手写多对多关联图,连线用leader-line的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JPA实现多对多关系

    本文已收录于专栏 《Java》   多对多关系是指两个实体之间存在多对多的关联关系。在数据库中,多对多关系无法直接表示,需要通过中间表来实现。   举个例子,假设有两个实体类:学生(Student)和课程(Course)。一个学生可以选择多门课程,而一门课程也可以被多

    2024年02月17日
    浏览(40)
  • EFCore-13 多对多关系配置

    学生与老师的关系,是一个常见的多对多的关系模型,一个老师有多个学生,一个学生也可能有多个老师。关系模型图如下所示: 学生1的老师为老师1、老师2 学生2的老师为老师2、老师3 学生3的老师为老师1、老师2、老师3 下面用程序实现这样的关系模型。 新建一个控制台应

    2024年02月12日
    浏览(42)
  • MySQL表设计思路(一对多、多对多...)

    要开始单独负责需求了,捋一捋表设计的思路。 字符串类型 varchar:即variable char ,可边长度的字符串,会根据实际数据的长度动态分配空间,以节省空间,如varchar(10)存jack,则只给4字节 char:定长字符串,最大255 字符 。不论实际数据长度都以定长空间存储,使用不当容易浪

    2024年02月07日
    浏览(40)
  • hibernate 一对一 一对多 多对多

    User 实体类 Address 实体类 测试 User实体类 Vlog实体类 测试 测试 mappedby : 属性指向实体关联表的拥有者,声明在被拥有者。 简单说就是另一边定义了关联规则,这边不用再定义一遍了,直接引用就行。 @JoinColumn : 外键列 在一对一中 @JoinColumn 声明在那个实体类中,生成数据库表

    2024年02月13日
    浏览(50)
  • Kurento多对多webrtc会议搭建测试

    环境ubuntu18.04 KMS版本6.13.0 多对多通信demo7.0.0 KMS运行起来后,通过运行它的一个个demo,来实现不同的功能,它的demo很多如下: https://github.com/Kurento 里面有一对一,多对多,还有一些特效的demo。这里运行的是多对多通话的demo。 它的KMS可以通过apt方式安装,然后拉下来每个d

    2024年02月08日
    浏览(47)
  • queryWrapper处理一对一,一对多,多对多

    是的,定义一个 BankUser 对象时,通常需要在其内部定义一个 BankCard 字段来表示其与 bank_card 表的关联关系。 例如,在 BankUser 类中定义一个 BankCard 对象作为其属性:```java ``` 然后,在查询 BankUser 对象时,需要使用 LEFT JOIN 将 bank_user 和 bank_card 表进行关联,并使用 select 方法指

    2024年02月04日
    浏览(50)
  • MySQL-多表设计-一对一&多对多

    案例:用户 与身份证信息 的关系 关系:一对一关系, 多用于单表拆分 ,将一张表的基础字段放在一张表中,其它字段放在另一张表中,以提高操作效率 实现: 在任意一方加入外键,关联另一方的主键,并且设计外键为唯一的(unique) 具体代码及运行结果如下:   上述的

    2024年02月16日
    浏览(48)
  • 杨中科 .NETCORE EFCORE第七部分 一对一,多对多

    1、builder.HasOne(o =o.Delivery).WithOne(d=d.Order).HasForeignKey(d=dOrderId); 2、测试插入和获取数据 示例 新建 Order 新建 Delivery DeliveryConfig OrderConfig 执行 迁移命令 查看数据库 测试数据插入 运行查看数据 1、多对多:老师一学生 2、EF Core 5.0开始,才正式支持多对多 3、需要中间表,举例数据

    2024年01月17日
    浏览(45)
  • python#django数据库一对一/一对多/多对多

    搭建 # 一对一 class   TestUser(models.Model):     username=models.CharField(max_length=32)     password = models.CharField(max_length=32) class TestInfo(models.Model):     mick_name=models.CharField(max_length=32)     user=models.OneToOneField(to=TestUser,on_delete=models.CASCADE()#on_delete 删除的模式 CASCADE 级联删除 让后执行数

    2024年02月14日
    浏览(78)
  • websocket 局域网 webrtc 一对一 多对多 视频通话 的示例

    基本介绍 WebRTC(Web Real-Time Communications)是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第

    2024年04月28日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包