<template>
  <div id="topologyWrapper">
    <el-container style="margin:0px; padding:0px; height: 100%" >
      <!--侧边按钮 el-icon-thumb-->
      <el-aside width="40px" class="tool-aside">
        <div class="tool-box">
          <el-row>
            <el-tooltip popper-class="tooltipBackground" effect="dark" :content="$t('DrawTopologyComponent.translate')" placement="left">
              <el-button size = 'mini' class="btn-zoom active" type="primary"  @click="changeToZoom" plain circle><i class="fa fa-hand-pointer-o"></i></el-button>
            </el-tooltip>
          </el-row>
          <br/>
          <el-row>
            <el-tooltip popper-class="tooltipBackground" effect="dark" :content="$t('DrawTopologyComponent.areaSelection')" placement="left">
              <el-button size = 'mini'  type="primary" class="btn-select" @click="changeToSelect" plain circle><i class="fa fa-square-o "></i></el-button>
            </el-tooltip>
          </el-row>
          <br/>
          <el-row>
            <el-tooltip popper-class="tooltipBackground" :content="$t('DrawTopologyComponent.trafficMonitoring')" placement="left" v-if="showSDNCModel">
              <el-switch
                v-model="isMonitor"
                active-value="是"
                inactive-value="否"
                @change="changeMonitorState(isMonitor)">
              </el-switch>
            </el-tooltip>
          </el-row>

        </div>
      </el-aside>
      <!-- 主拓扑图-->
      <el-main ref="elmain" style="padding: 0px auto;overflow: hidden;" width="calc(100% - 40px)" height="100%" >
        <el-container style="margin:0px; padding:0px; height: 100%" >
          <el-aside :width="topologySvgWidth" height="100%" style = "align:middle;overflow: hidden">
            <!-- topology svg -->
            <svg width="100%" height="100%" id="svgWrapper" class="fullScreenOut"></svg>
          </el-aside>
          <el-main :width ="SDNCServerSvgWidth" height="100%" style = "align:middle;overflow: hidden" v-if="showSDNCModel">
            <!-- server svg 服务器不再显示-->
<!--            <ServerShowInHome-->
<!--              :alarmLevelConfig="alarmLevelConfig"-->
<!--              :commands="commands"-->
<!--              :topologyCollection = "topologyNodeList"-->
<!--              v-on:showDeviceOfController="showDeviceInGroup">-->
<!--            </ServerShowInHome>-->
          </el-main>
        </el-container>
      </el-main>
    </el-container>
    <div id="topologyTooltipID" :class="topologyTipClass" ref="topologyTooltipRef">
      <p v-if = "subnetOrDevice === 1">
        <strong class="name" style="display: block">{{$t('hoverSubnet.subnetName')+ ': ' + hoverSubnetMessage.subnetName}}<br/></strong>
        <strong class="name" style="display: block"><br/></strong>
        <strong class="name" style="display: block">{{$t('hoverSubnet.domain')+ ': '+ hoverSubnetMessage.domain}}<br/></strong>
        <strong class="name" style="display: block"><br/></strong>
      </p>
      <p v-else-if="subnetOrDevice === 0">
        <strong class="name" style="display: block" >{{$t('hoverDevice.displayName') + ': '+ hoverDeviceMessage.displayName}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.deviceIP') + ': '+ hoverDeviceMessage.ipAddress}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.deviceBussIP') + ': '+ hoverDeviceMessage.deviceBussIp}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.station') + ': ' + hoverDeviceMessage.station}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.deviceType') + ': ' + hoverDeviceMessage.deviceType}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.deviceModel') + ': ' + hoverDeviceMessage.deviceModel}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.deviceVersion') + ': ' + hoverDeviceMessage.deviceVersion}}<br/></strong>
        <strong class="name" style="display: block">{{$t('hoverDevice.startTime') + ': ' + hoverDeviceMessage.startTime}}<br/></strong>
      </p>
      <div v-if = "subnetOrDevice === 1">
        <div class="icon-circle-subnet" style="background-color:#0000FF;float: left"></div><label style="float: left">{{hoverSubnetMessage.alarmCount}}</label>
        <div class="icon-circle-subnet" style="background-color:#FF0000;float: left"></div><label style="float: left">{{hoverSubnetMessage.urgentAlarm}}</label>
        <div class="icon-circle-subnet" style="background-color:#FFA500;float: left"></div><label style="float: left">{{hoverSubnetMessage.seriousAlarm}}</label>
        <div class="icon-circle-subnet" style="background-color:#FFFF00;float: left"></div><label style="float: left">{{hoverSubnetMessage.waringAlarm}}</label>
      </div>
      <div v-else-if="subnetOrDevice === 0">
        <div class="icon-circle-subnet" style="background-color:#0000FF;float: left"></div><label style="float: left">{{hoverDeviceMessage.alarmCount}}</label>
        <div class="icon-circle-subnet" style="background-color:#FF0000;float: left"></div><label style="float: left">{{hoverDeviceMessage.urgentAlarm}}</label>
        <div class="icon-circle-subnet" style="background-color:#FFA500;float: left"></div><label style="float: left">{{hoverDeviceMessage.seriousAlarm}}</label>
        <div class="icon-circle-subnet" style="background-color:#FFFF00;float: left"></div><label style="float: left">{{hoverDeviceMessage.waringAlarm}}</label>
      </div>
    </div>
    <div id="topologyLinkID" :class="topologyLinkClass" ref="topologyLinkRef">
        <p v-if = "subnetOrDevice === 1">
        <strong class="name" style="display: block">{{hoverSubnetMessage.startStation+ '- ' + hoverSubnetMessage.endStation}}<br/></strong>
        <strong class="name" style="display: block"><br/></strong>
        <strong class="name" style="display: block">{{$t('hoverSubnet.linkNum')+ ': '+ hoverSubnetMessage.linkNum}}<br/></strong>
        <strong class="name" style="display: block"><br/></strong>
        </p>
        <p v-else-if="subnetOrDevice === 0">
          <strong class="name" style="display: block">{{$t('hoverDevice.linkName')+ ': ' + hoverDeviceMessage.linkName}}<br/></strong>
          <strong class="name" style="display: block">{{$t('hoverDevice.startStation')+ ': ' + hoverDeviceMessage.startStation+ ': ' + hoverDeviceMessage.startPortName}}<br/></strong>
          <strong class="name" style="display: block">{{$t('hoverDevice.endStation')+ ': ' + hoverDeviceMessage.endStation+ ': ' + hoverDeviceMessage.endPortName}}<br/></strong>
        </p>
        <div>
          <div class="icon-circle-subnet" style="background-color:#0000FF;float: left"></div><label style="float: left">{{hoverLinkMessage.alarmCount}}</label>
          <div class="icon-circle-subnet" style="background-color:#FF0000;float: left"></div><label style="float: left">{{hoverLinkMessage.urgentAlarm}}</label>
          <div class="icon-circle-subnet" style="background-color:#FFA500;float: left"></div><label style="float: left">{{hoverLinkMessage.seriousAlarm}}</label>
          <div class="icon-circle-subnet" style="background-color:#FFFF00;float: left"></div><label style="float: left">{{hoverLinkMessage.waringAlarm}}</label>
        </div>
      </div>

    <!-- 右键单击网元 菜单栏 -->
    <v-contextmenu ref="deviceContextmenu" id="nodeMenu">
      <v-contextmenu-item v-for="command in contextCommands" :key="command.key" @click="command.execute" :disabled="command.disabled">{{command.name}}</v-contextmenu-item>
    </v-contextmenu>

    <!-- 右键单击链路 菜单栏 -->
    <v-contextmenu ref="linkContextmenu" id="linkMenu">
      <v-contextmenu-item v-for="command in contextCommands" :key="command.key" @click="command.execute" :disabled="command.disabled">{{command.name}}</v-contextmenu-item>
    </v-contextmenu>

    <v-contextmenu ref="blankContextmenu" id="blankMenu">
      <v-contextmenu-item v-for="command in contextCommands" :key="command.key" @click="command.execute" :disabled="command.disabled">{{command.name}}</v-contextmenu-item>
    </v-contextmenu>
    <CommandExecuteContext v-bind:commands="commandContext"></CommandExecuteContext>
  </div>
</template>

<script>
import AlarmManager from '../../domain/models/AlarmManager'
import DeviceCommands from '@/commands/DeviceCommands'
import TopologyManager from '../../domain/models/TopologyManager'
import SubnetManager from '../../domain/models/SubnetManager'
import { EventBus } from '../../domain/EventBus'
import ServerShowInHome from './ServerShowInHome'
import CommandExecuteContext from '@/commands/CommandExecuteContext'
import HelperUtil from '../../utils/HelperUtil'
let d3 = require('d3')
let svg = '', g = '', svgZoom = '', brushFunction = '', brush = ''
let curThis = ''
// var chartTooltip = d3

export default {
  name: 'DrawTopologyComponent',
  props: {
    // 命令
    commands: {},
    alarmCollection: null,
    drawGraphSizeConfig: null, // 画图的基本规格配置
    alarmLevelConfig: null, // 告警的颜色配置
    topologyCollection: null, // 带status的设备集合
    linkCollection: null, // 带status的链路集合
    subnetCollection: null, // 子网集合
    curClickSubnetId: null, // 当前点击的子网ID
    curClickDeviceId: null, // 当前点击的设备ID
    deviceCollection: null// 设备列表
    // serverCollection:null,//服务器列表
  },
  data: function () {
    return {
      deviceList: {
        'deviceKey': '',
        'uuid': '',
        'hostName': '',
        'displayName': '',
        'deviceTypeKey': '',
        'deviceModel': '',
        'subnetKey': '',
        'ipAddress': '',
        'bussIpAddress': '',
        'mac': '',
        'manageType': 0,
        'bussManageType': 0,
        'snmpVersion': 0,
        'snmpPort': 161,
        'trapPort': 162,
        'snmpGroup': 'publicCrscd',
        'ipmiUserName': '',
        'ipmiPassword': '',
        'deviceStatus': 0,
        'deviceVersion': '',
        'deviceDesc': '',
        'startTime': '',
        'firmwareKey': '',
        'firmwareFileName': '',
        'station': '',
        'locationName': '',
        'brand': '',
        'manufacturerLiaison': '',
        'manufacturerPhone': '',
        'manager': '',
        'managerPhone': '',
        'delFlag': 0,
        'modifyTime': ''
      },
      links: {
        'linkKey': '',
        'linkName': '',
        'startDeviceKey': '',
        'startPortName': '',
        'endDeviceKey': '',
        'endPortName': '',
        'linkStatus': 0,
        'linkType': 0,
        'delFlag': 0,
        'modifyTime': ''

      },
      commandContext: {},
      topologySvgWidth: '100%',
      SDNCServerSvgWidth: '15%',
      showSDNCModel: false, // 是否展示主页面右侧的服务器显示区域
      isMonitor: '否', // 流量监控按钮的显示值
      topologyTipClass: 'topologyTooltip hidden',
      topologyLinkClass: 'topologyLink hidden',

      timeout: null,

      subnetOrDeviceShow: -1,
      currentSubnetKey: '',

      // 定位偏移<g>所用到的记录<g>的当前位置
      groupLeft: 0,
      groupTop: 0,
      scaleK: 1,

      selectDevicesList: [], // 选择多个设备，加入列表中
      contextCommands: [], // 操作命令
      selectDevice: '',
      selectLink: '',

      topologyNodeList: this.topologyCollection, // 带status的设备集合
      topologyNodeShowList: this.topologyCollection,
      linkList: this.linkCollection, // 带status的链路集合
      alarmColorConfig: this.alarmLevelConfig, // 告警的颜色、图片和声音配置
      drawSvgSizeConfig: this.drawGraphSizeConfig, // 画图的基本规格配置
      subnetList: this.subnetCollection, // 子网集合
      clickSubnetId: this.curClickSubnetId, // 当前点击的子网ID(考虑置空)
      clickDeviceId: this.curClickDeviceId, // 当前点击的设备ID
      newLinkList: null, // 处理后的链路数据

      // 拓扑图路由视图的假数据：
      newRouteLinkList: [],

      isLinkList: 0, // 控制使用哪种链路集合的标志,0---拓扑图连接集合；1---路由试图连接集合

      deviceMenuTop: -1, // 设备菜单的上边距
      deviceMenuLeft: -1, // 设备菜单的侧边距
      deviceMenuEl: '', // 设备菜单元素的id
      deviceMenuShow: false, // 设备菜单的显示

      topologyTop: -1, // 设备菜单的上边距
      topologyLeft: -1, // 设备菜单的侧边距

      topologyLinkTop: -1,
      topologyLinkLeft: -1,

      currentDragKey: '',
      doubleClickSubnetId: '',

      subnetOrDevice: -1,
      hoverSubnetMessage: {
        subnetName: '',
        domain: '',
        subnetAlarmList: [],
        alarmCount: '',
        urgentAlarm: '',
        seriousAlarm: '',
        waringAlarm: '',
        startStation: '',
        endStation: '',
        linkNum: ''

      },

      hoverDeviceMessage: {
        displayName: '',
        ipAddress: '',
        deviceBussIp: '',
        station: '',
        deviceType: '',
        deviceModel: '',
        deviceVersion: '',
        startTime: '',
        elementAlarmList: [],
        alarmCount: '',
        urgentAlarm: '',
        seriousAlarm: '',
        waringAlarm: '',
        linkName: '',
        startStation: '',
        startPortName: '',
        endStation: '',
        endPortName: ''
      },

      hoverLinkMessage: {
        linkAlarmList: [],
        alarmCount: '',
        urgentAlarm: '',
        seriousAlarm: '',
        waringAlarm: ''
      },
      // topologyCollection改变了，但当前在其它tab页，无法尽心渲染，做个标记，等回到当前页面，再进行绘制
      dataChangeFromOtherTabs: false
    }
  },

  // 引用子组件
  components: {
    ServerShowInHome,
    CommandExecuteContext
  },

  methods: {

    /**
    * @Description  ：父组件调用svg的id，实现导出图片
    * @author       : yjz
    * @param        :
    * @return       :
    * @exception    :
    * @date         :
    */
    getSvgId () {
      let topologySvgId = document.getElementById('svgWrapper')
      return topologySvgId
    },

    /**
     * @Description  ：创建SVG画布，视图群组g，等..
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    createSvg () {
      // 创建svg画布
      svg = d3.select('#svgWrapper')
        .attr('width', curThis.drawGraphSizeConfig.svgWidth)
        .attr('height', curThis.drawGraphSizeConfig.svgHeight)

      // 当前可视化窗口的大小以及中心点
      let topologySvgWidth = document.getElementById('topologyWrapper').offsetWidth
      let topologySvgHeight = document.getElementById('topologyWrapper').offsetHeight

      g = svg.append('g').attr('id', 'viewGroup')// 创建视图群组g

      // let backgroundGroup = g.append('g').attr('id', 'backgroundGroup');// 创建背景群组backgroundGroup
      //
      // backgroundGroup.append('image').attr('class', 'background-image')// 创建image标签
      //   .attr('width', topologySvgWidth)
      //   .attr('height', topologySvgHeight)
      //   .attr('preserveAspectRatio', 'none')// 此属性控制是否强制缩放，none代表不强制缩放，这样上面设置的宽高就会生效。否则他会强制给你按照原始图片的宽高比例进行缩放，导致宽高不生效。
      //   .attr('xlink:href', './static/whiteBackground.png');

      g.append('path').attr('id', 'checkPath')// 此处在group里面建立一个虚线框，代表点击选中的网元节点。

      brush = g.append('g').attr('class', 'brush')// 创建刷子Group
    },

    /**
     * @Description  ：根据当前系统的模式，控制SDNC独有的显示区域是否显示。
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         :
     */
    isSDNCModel () {
      if (this.$store.state.systemMode === 'TRAD') {
        this.showSDNCModel = false
        this.topologySvgWidth = '100%'
        this.SDNCServerSvgWidth = '0%'
      } else if (this.$store.state.systemMode === 'SDNC') {
        this.showSDNCModel = true
        this.topologySvgWidth = '85%'
        this.SDNCServerSvgWidth = '15%'
      }
    },

    /**
     * @Description  ：添加缩放事件
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    addZoomToSvg () {
      // 创建zoom函数，实现缩放和平移
      svgZoom = d3.zoom()
        .scaleExtent([0.8, 3])// 设置缩放的范围
        .on('zoom', function () { // 和drag类似，设置缩放监听事件，"start" "zoom" "end" 三个状态。
          curThis.$options.methods.zoom()
        })
      // svg调用zoom函数,并取消双击事件
      svg.call(svgZoom).on('dblclick.zoom', () => {})
    },
    // 缩小
    zoomOutFuc () {
      if (curThis.scaleK <= 0.8) {
        return
      }
      curThis.scaleK = curThis.scaleK - 0.5
      if (curThis.scaleK < 0.8) {
        curThis.scaleK = 0.8
      }

      svg.call(svgZoom.transform, d3.zoomIdentity.translate(curThis.groupLeft, curThis.groupTop).scale(curThis.scaleK))
    },
    // 放大
    zoomInFuc () {
      if (curThis.scaleK >= 3) {
        return
      }
      curThis.scaleK = curThis.scaleK + 0.5

      svg.call(svgZoom.transform, d3.zoomIdentity.translate(curThis.groupLeft, curThis.groupTop).scale(curThis.scaleK))
    },
    // 还原
    reset () {
      curThis.scaleK = 1
      svgZoom.scaleTo(svg, curThis.scaleK)
    },

    /**
     * @Description  ：添加刷子事件
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    addBrushToSvg () {
      brushFunction = d3.brush()
        .on('brush', this.brushed)
        .on('end', this.endBrushed)
    },

    /**
     * @Description  ：添加缩放的缩放状态的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    zoom () {
      document.getElementById('nodeMenu').style.display = 'none'
      document.getElementById('linkMenu').style.display = 'none'
      document.getElementById('blankMenu').style.display = 'none'
      curThis.groupLeft = d3.event.transform.x // 左偏移量，就是<g>的左边界距离显示区域的距离
      curThis.groupTop = d3.event.transform.y // 上偏移量,就是<g>的上边界距离显示区域的距离
      curThis.scaleK = d3.event.transform.k // 记录scale的倍数
      return g.attr('transform', 'translate(' + curThis.groupLeft + ',' + curThis.groupTop + ') scale(' + curThis.scaleK + ')')
    },

    /**
     * @Description  ：渲染视图的函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    renderView () {
      this.drawLink() // 绘制连接
      this.drawNode() // 绘制节点
    },

    /**
     * @Description  ：处理连接数据，向domain层的topology发起请求
     *                  将每两个节点之间的多条连线放到一个一维数组。并将这些一维数组放到一个二维数组里。
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    handleLinkList: function () {
      let topologyMgr = TopologyManager(curThis.topologyNodeShowList)
      curThis.newLinkList = topologyMgr.getNewLinkList(curThis.linkList)
    },

    handleSubnetListToTopology: function () {
      let subnetMgr = SubnetManager(curThis.subnetCollection)
      subnetMgr.subnetCollectionTotopology(curThis.topologyCollection)
    },

    /**
     * @Description  ：绘制连接
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date          : 2019/04/14
     */
    drawLink () {
      this.handleLinkList()
      g.selectAll('.link').remove()
      let linkUpdate = g.selectAll('.link').data(curThis.newLinkList)// 返回数据和dom元素的交集
      linkUpdate.exit().remove()// 返回多出的dom集合并处理
      let linkEnter = linkUpdate.enter().insert('g', '.node').classed('link', true)// 返回多出的数据集合并处理

      linkUpdate = linkUpdate.merge(linkEnter)// 将enter 合并 到 update 里面去，使得更新包含第一次创建。

      // 为二维数组中的每一个一维数组绘制连线。
      linkUpdate.each(function (d) {
        curThis.$options.methods.drawPath(d, d3.select(this))
      })
      // linkEnter.on('mouseover', p => {
      //   // d3.event.sourceEvent.stopPropagation();
      //   d3.select(".topologyTooltip").classed("hidden", true);
      //   clearTimeout(curThis.timeout)
      //   curThis.topologyLinkTop = d3.event.y;
      //   curThis.topologyLinkLeft = d3.event.x + window.scrollX;
      //   curThis.timeout = setTimeout(() => {
      //     if (document.getElementById('linkMenu').style.display === 'block' || (document.getElementById('nodeMenu').style.display === 'block' && document.getElementById('nodeMenu').style.visibility === 'visible')){
      //       return
      //     }
      //     const contextTopologyWidth = window.getComputedStyle(this.$refs.topologyLinkRef).width;
      //     const contextTopologyHeight = window.getComputedStyle(this.$refs.topologyLinkRef).height;
      //     if (parseInt(contextTopologyHeight) + curThis.topologyLinkTop >= window.innerHeight) {
      //       curThis.topologyLinkTop -= parseInt(contextTopologyHeight)
      //     }
      //     if (parseInt(contextTopologyWidth) + curThis.topologyLinkLeft >= window.innerWidth) {
      //       curThis.topologyLinkLeft -= parseInt(contextTopologyWidth)
      //     }
      //
      //     // 将浮层位置设置为鼠标位置
      //     var topologyLink = d3
      //       .select(".topologyLink")
      //       .style('left', (curThis.topologyLinkLeft - document.getElementById('topologyWrapper').getBoundingClientRect().left) + 'px')
      //       .style('top', (curThis.topologyLinkTop - document.getElementById('topologyWrapper').getBoundingClientRect().top) + 'px')
      //     // 更新浮层内容
      //     // chartTooltip.select(".name").text(p.name);
      //     // 移除浮层hidden样式，展示浮层
      //     let alarmMgr = AlarmManager(curThis.alarmCollection);
      //     curThis.hoverLinkMessage.linkAlarmList =alarmMgr.findAlarmByElement(p.elementKey)
      //     let  AlarmLevelList = alarmMgr.getNumberByAlarmLevel(curThis.hoverLinkMessage.linkAlarmList);
      //     curThis.hoverLinkMessage.waringAlarm = AlarmLevelList[0];
      //     curThis.hoverLinkMessage.seriousAlarm = AlarmLevelList[1];
      //     curThis.hoverLinkMessage.urgentAlarm = AlarmLevelList[2];
      //     curThis.hoverLinkMessage.alarmCount = AlarmLevelList[3];
      //     if(p[0].linkType === 0){
      //       curThis.subnetOrDevice = 0;
      //       curThis.hoverDeviceMessage.startStation = curThis.linkCollection[p[0].linkKey].source.topologyName;
      //       curThis.hoverDeviceMessage.startPortName = curThis.linkCollection[p[0].linkKey].startPortName;
      //       curThis.hoverDeviceMessage.endStation = curThis.linkCollection[p[0].linkKey].target.topologyName;
      //       curThis.hoverDeviceMessage.endPortName = curThis.linkCollection[p[0].linkKey].endPortName;
      //       curThis.hoverDeviceMessage.linkName = curThis.linkCollection[p[0].linkKey].linkName;
      //     }else {
      //       curThis.subnetOrDevice = 1;
      //       curThis.hoverSubnetMessage.startStation = curThis.linkCollection[p[0].linkKey].source.topologyName;
      //       curThis.hoverSubnetMessage.endStation = curThis.linkCollection[p[0].linkKey].target.topologyName;
      //       curThis.hoverSubnetMessage.linkNum = Object.values(p).length;
      //     }
      //     topologyLink.classed("hidden", false);
      //   }, 400);
      // })
      //   .on('mouseout', d => {
      //     // d3.event.sourceEvent.stopPropagation();
      //     clearTimeout(curThis.timeout);
      //     d3.select(".topologyLink").classed("hidden", true);
      //   });
    },

    /**
     * @Description  ：处理每两个节点之间的一组连线，可能包含多条
     * @author       :  yjz
     * @param        :  两个参数，第一个参数是二维数组中的每一项，也就是每两个节点之间的一组连线，是一个一维数组。
     *                           第二个参数是选择的当前数据代表的对象，类为link的g元素
     * @return       :
     * @exception    :
     * @date         : 2018/01/10
     */
    drawPath: function (d, object) {
      // 新建一个数组，存放坐标重新计算好的一组连线。
      let paths = []
      // 处理传过来的数据，重新计算好坐标组成一个新的数组，并进行后续的绘制。
      curThis.$options.methods.handleManyPath(d, paths)
      // 返回每条连线的path和data的选择集
      let path = object.selectAll('.path').data(paths, function (d) { return d.linkKey })
      // 清楚多余的dom element
      path.exit().remove()
      // 每条连线的path和text存放在一个g里
      let pathEnter = path.enter().append('g')
        .attr('class', 'path')
        .on('mouseover', curThis.$options.methods.handleLinkMouseover)
        .on('mouseout', curThis.$options.methods.handleLinkMouseout)
        .on('contextmenu', curThis.$options.methods.handleTopologyLinkContextmenu)

      // 为每条连线创建容器，当鼠标移入时，控制其透明度
      pathEnter.append('path')
        .attr('class', 'linkWrapper')
        .attr('id', p => {
          return 'linkWrapper' + p.linkKey
        })
      // 为每条连线创建path,并给该path赋一个id，提供给textpath使用
      pathEnter.append('path')
        .attr('class', 'path-data')
        .attr('id', p => {
          return 'textPath' + p.linkKey
        })

      // 为每条连线创建text 和 textpath
      pathEnter.append('text')
        .attr('class', 'path-text')
        .style('font-size', '12px')
        .style('font-family', 'Si  mSun')
        .append('textPath')
        .attr('class', 'textPath')
        .attr('xlink:href', p => {
          return '#textPath' + p.linkKey
        })

      // 合并第一次创建为更新
      path = path.merge(pathEnter)

      // 绘制容器
      path.select('.linkWrapper')
        .attr('d', p => curThis.$options.methods.getPathData(p))
        .style('stroke', p => curThis.$options.methods.getLinkColor(p))
        .style('stroke-width', '6')
        .style('fill', 'none')
        .style('stroke-linecap', 'round')
        .style('opacity', 0.4)
        .style('display', 'none')

      // 更新连线path,绘制连线
      path.select('.path-data')
        .attr('d', p => curThis.$options.methods.getPathData(p))
        .style('stroke', p => curThis.$options.methods.getLinkColor(p))
        .style('stroke-width', '2')
        .style('fill', 'none')
        .style('stroke-linecap', 'round')
        .style('opacity', 1)
        .style('stroke-dasharray', function (p) {
          // 此处做判断，若连接线为发现的连接，则使用虚线，状态码使用3
          if (p.status === '3') {
            return '2,4'
          }
        })

      // 此处更新的是textpath 的属性，使得文字依附在path上
      path.select('.path-text')
        .attr('x', curThis.$options.methods.handleTopologyLinkTextX)// x指定文字最左侧坐标位置，根据连线两端节点的坐标，勾股出连线的长度l，让文字最左侧位于连线的中间，就是让x = l/2;
        .attr('dy', -3)
        .attr('text-anchor', 'middle')
        .attr('rotate', curThis.$options.methods.handleTopologyLinkRotate)
        .select('.textPath')
        .text(curThis.$options.methods.handleTopologyLinkTextDirection)
    },

    /**
     * @Description  ：处理拓扑图中连接的文字方向的旋转
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/10
     */
    handleTopologyLinkRotate (p) {
      return p.sourceAxis[0] < p.targetAxis[0] ? 0 : 180
    },

    /**
     * @Description  ：处理拓扑图中连接的文字的最左侧坐标的位置
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/10
     */
    handleTopologyLinkTextX (p) {
      // x指定文字最左侧坐标位置，根据连线两端节点的坐标，勾股出连线的长度l，让文字最左侧位于连线的中间，就是让x = l/2;
      let x = p.targetAxis[0] - p.sourceAxis[0], y = p.targetAxis[1] - p.sourceAxis[1]
      let l = Math.sqrt(x * x + y * y)
      return l / 2
    },

    /**
     * @Description  ：处理拓扑图中连接的文字的顺序方向，保证文字正常显示
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/10
     */
    handleTopologyLinkTextDirection (p) {
      // 为进行旋转的时候，文字方向是正常；当进行旋转之后，文字就反序显示了，需要将文字转换为顺序。
      if (p.sourceAxis[0] < p.targetAxis[0]) {
        return p.linkName
      }
      return p.linkName.split('').reverse().join('')
    },

    /**
    * @Description  ：处理拓扑图中连接的右键事件
    * @author       : yjz
    * @param        :
    * @return       :
    * @exception    :
    * @date         : 2019/04/15
    */
    handleTopologyLinkContextmenu (p) {
      d3.select('.topologyLink').classed('hidden', true)
      if (curThis.isLinkList === 0) {
        curThis.selectLink = curThis.linkList[p.linkKey]
        curThis.contextCommands = []
        curThis.contextCommands = DeviceCommands.generateCommandsForLink(curThis.selectLink, curThis.commands)
        d3.select('#linkMenu')
          .style('left', (d3.event.x + window.scrollX) + 'px')
          .style('top', (d3.event.y) + 'px')
          .style('display', 'block')
      } else if (curThis.isLinkList === 1) {
        // 无操作
      }
    },

    /**
     * @Description  ：处理拓扑图中连接的移入事件
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/15
     */
    handleLinkMouseover (p) {
      d3.select('#linkWrapper' + p.linkKey)
        .style('display', 'block')
      var topologyLink = d3
        .select(".topologyLink")
        .style('left', (d3.event.x)-200 + 'px')
        .style('top', (d3.event.y)-200 + 'px')
      let alarmMgr = AlarmManager(curThis.alarmCollection);
      curThis.hoverLinkMessage.linkAlarmList =alarmMgr.findAlarmByElement(p.linkKey)
      let  AlarmLevelList = alarmMgr.getNumberByAlarmLevel(curThis.hoverLinkMessage.linkAlarmList);
      curThis.hoverLinkMessage.waringAlarm = AlarmLevelList[0];
      curThis.hoverLinkMessage.seriousAlarm = AlarmLevelList[1];
      curThis.hoverLinkMessage.urgentAlarm = AlarmLevelList[2];
      curThis.hoverLinkMessage.alarmCount = AlarmLevelList[3];
      curThis.subnetOrDevice = 0;
      curThis.hoverDeviceMessage.startStation = curThis.linkCollection[p.linkKey].source.topologyName;
      curThis.hoverDeviceMessage.startPortName = curThis.linkCollection[p.linkKey].startPortName;
      curThis.hoverDeviceMessage.endStation = curThis.linkCollection[p.linkKey].target.topologyName;
      curThis.hoverDeviceMessage.endPortName = curThis.linkCollection[p.linkKey].endPortName;
      curThis.hoverDeviceMessage.linkName = curThis.linkCollection[p.linkKey].linkName;
      topologyLink.classed("hidden", false);
    },

    /**
     * @Description  ：处理拓扑图中连接的移出事件
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/15
     */
    handleLinkMouseout (p) {
      d3.select('#linkWrapper' + p.linkKey)
        .style('display', 'none')
      d3.select('.topologyLink').classed('hidden', true)
    },

    /**
     * @Description  ：根据链路的二维数组，转化为一个一维数组，同时使得相同两个设备之间的多条链路不会重合，错开显示
     * @author       : yjz
     * @param        : 二维数组中的每一项，也就是每两个节点之间的一组连线，是一个一维数组
     * @return       :
     * @exception    :
     * @date         :
     */
    handleManyPath: function (d, paths) {
      let same = d[0]
      let source = same.startDeviceKey
      let target = same.endDeviceKey
      for (let i = 0; i < d.length; ++i) {
        let path = {}
        let sourceAxis = [d[i].source.horizontalAxis, d[i].source.verticalAxis], targetAxis = [d[i].target.horizontalAxis, d[i].target.verticalAxis]

        path.sameIndex = (i + 1)
        path.sameTotal = d.length
        path.sameTotalHalf = (path.sameTotal / 2)
        path.sameUneven = ((path.sameTotal % 2) !== 0)
        path.sameMiddleLink = ((path.sameUneven === true) && (Math.ceil(path.sameTotalHalf) === path.sameIndex))
        path.sameLowerHalf = (path.sameIndex <= path.sameTotalHalf)
        path.sameArcDirection = path.sameLowerHalf ? 0 : 1
        path.sameIndexCorrected = path.sameLowerHalf ? path.sameIndex : (path.sameIndex - Math.ceil(path.sameTotalHalf))
        path.maxSameHalf = Math.floor(d.length / 2)
        path.sourceAxis = sourceAxis
        path.targetAxis = targetAxis
        path.linkKey = d[i].linkKey
        if (d[i].startDeviceKey === target && d[i].endDeviceKey === source && path.sameTotal > 1) {
          path.sameArcDirection = path.sameArcDirection === 0 ? 1 : 0
        }

        if (curThis.isLinkList === 0) {
          path.linkName = d[i].linkName
          path.status = d[i].status
          // paths.push({'sourceAxis': sourceAxis, 'targetAxis': targetAxis, 'linkKey': d[i].linkKey, 'linkName': d[i].linkName, 'status': d[i].status})
        } else if (curThis.isLinkList === 1) {
          // TODO:有了真是数据以后，使用真的bandwidthUsed字段充当linkName。bandwidthMax 字段暂时无用。
          path.linkName = d[i].bandwidthUsed
          path.status = '100'
          // paths.push({'sourceAxis': sourceAxis, 'targetAxis': targetAxis, 'linkKey': d[i].linkKey, 'linkName': d[i].bandwidthUsed, 'status': '100'})
        }
        paths.push(path)
      }
    },

    /**
     * @Description  ：得到当前path 'd'属性的值，绘制连线。
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2018/01/10
     */
    getPathData: function (p) {
      // let M = 'M' + (p.sourceAxis[0] + 16) + ',' + (p.sourceAxis[1] + 16);
      // let L = 'L' + (p.targetAxis[0] + 16) + ',' + (p.targetAxis[1] + 16);
      // let pathData = M + '' + L + 'Z';
      // return pathData
      //

      var dx = p.targetAxis[0] - p.sourceAxis[0],
        dy = p.targetAxis[1] - p.sourceAxis[1],
        dr = Math.sqrt(dx * dx + dy * dy),
        unevenCorrection = (p.sameUneven ? 0 : 0.5),
        arc = ((dr * p.maxSameHalf) / (p.sameIndexCorrected - unevenCorrection))
      if (p.sameMiddleLink) {
        arc = 0
      }
      let result = 'M' + (p.sourceAxis[0] + 16) + ',' + (p.sourceAxis[1] + 16) + 'A' + arc + ',' + arc + ' 0 0,' + p.sameArcDirection + ' ' + (p.targetAxis[0] + 16) + ',' + (p.targetAxis[1] + 16)
      return result
    },

    /**
     * @Description  ：获取连线的颜色,读取传过来的配置
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    getLinkColor: function (p) {
      if (curThis.isLinkList === 0) {
        let colorValue = curThis.alarmLevelConfig[p.status]['alarmColor']
        return colorValue
      } else if (curThis.isLinkList === 1) {
        // TODO:根据前台配置的带宽---颜色映射决定连接颜色。
        return '#000000'
      }
    },

    /**
     * @Description  ：对两个数组是否相同进行判断
     * @author       : yjz
     * @param        :两个数组a,b 数组里面存的是对象object
     * @return       :Boolean
     * @exception    :
     * @date         : 2018/01/08
     */
    equalArray: function (a, b) {
      // 判断数组的长度
      if (a.length !== b.length) {
        return false
      } else {
        // 循环遍历数组的值进行比较
        for (let i = 0; i < a.length; i++) {
          if (JSON.stringify(a[i]) !== JSON.stringify(b[i])) {
            return false
          }
        }
        return true
      }
    },

    timeToShow (d) {
      var year = d.slice(0, 4)
      var month = d.slice(4, 6)
      var day = d.slice(6, 8)
      var hour = d.slice(9, 11)
      var min = d.slice(11, 13)
      var sec = d.slice(13, 15)
      return year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + sec
    },

    EnglishToChinese (d) {
      const name = {
        '*': '其他',
        CLIENT: '终端',
        SERVER: '服务器',
        SWITCH: '交换机',
        ROUTERSWITCH: '路由交换机',
        ROUTER: '路由器',
        OTHER: '其他',
        FIREWALL: '防火墙',
        STORAGE: '存储设备',
        HANDHELDTERMINAL: 'HANDHELDTERMINAL',
        CAMERA: 'CAMERA',
        PCTERMINAL: 'PCTERMINAL',
        DISPATCHDESK: 'DISPATCHDESK',
        CPE: 'CPE',
        '5GBBU': '5GBBU',
        '5GC': '5GC'
      }
      return name[d]
    },

    /**
     * @Description  ：绘制节点
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    drawNode () {
      // 画布偏移
      console.log(Object.values(curThis.topologyNodeShowList))
      if (curThis.groupLeft === 0 && curThis.groupTop === 0 && Object.values(curThis.topologyNodeShowList).length > 0) {
        curThis.groupLeft = Object.values(curThis.topologyNodeShowList)[0].horizontalOffset
        curThis.groupTop = Object.values(curThis.topologyNodeShowList)[0].verticalOffset
      }

      let topologySvgWidth = (document.getElementById('topologyWrapper').offsetWidth - document.getElementsByClassName('tool-aside')[0].offsetWidth) / 2
      let topologySvgHeight = document.getElementById('topologyWrapper').offsetHeight / 2
      // 返回数据和dom元素的交集
      let g1Update = g.selectAll('.node').data(Object.values(curThis.topologyNodeShowList), function (d) {
        if (d.new !== undefined && d.new) {
          if (d.horizontalAxis === 0 && d.verticalAxis === 0) {
            d.horizontalAxis = topologySvgWidth - curThis.groupLeft
            d.verticalAxis = topologySvgHeight - curThis.groupTop
            d.verticalAxis = topologySvgHeight - curThis.groupTop
          }
        }
        return d.topologyKey
      })

      // 返回多出的dom集合并处理
      g1Update.exit().remove()

      // 返回多出的数据集合并处理

      let g1Enter = g1Update.enter().append('g')
        .style('cursor', 'pointer')
        .classed('node', true)// 注意，此处就是之前一直创建图标准的病因，并未给g1一个类，以至于，选择集为空，每次都是enter()一个新的集合。和上面g.selectAll一一对应。
        .on('tick', this.$options.methods.tick())
        .on('contextmenu', curThis.$options.methods.handleTopologyDeviceContextmenu)
        .on('click', curThis.$options.methods.handleTopologyDeviceClick)
        .on('dblclick', curThis.$options.methods.handleDoubleClickSubnet)
        .on('mouseover', p => {
          // d3.event.sourceEvent.stopPropagation();
          clearTimeout(curThis.timeout) // 首先清除
          curThis.topologyTop = d3.event.y
          curThis.topologyLeft = d3.event.x
          curThis.timeout = setTimeout(() => {
            if ((document.getElementById('nodeMenu').style.display === 'block' && document.getElementById('nodeMenu').style.visibility === 'visible') || document.getElementById('linkMenu').style.display === 'block') {
              return
            }
            const contextTopologyWidth = window.getComputedStyle(this.$refs.topologyTooltipRef).width
            const contextTopologyHeight = window.getComputedStyle(this.$refs.topologyTooltipRef).height
            if (parseInt(contextTopologyHeight) + curThis.topologyTop >= window.innerHeight) {
              curThis.topologyTop -= parseInt(contextTopologyHeight)
            }
            if (parseInt(contextTopologyWidth) + curThis.topologyLeft >= window.innerWidth) {
              curThis.topologyLeft -= parseInt(contextTopologyWidth)
            }

            var topologyTooltip = d3

              .select('.topologyTooltip')
              .style('left', (curThis.topologyLeft - document.getElementById('topologyWrapper').getBoundingClientRect().left) + 'px')
              .style('top', (curThis.topologyTop - document.getElementById('topologyWrapper').getBoundingClientRect().top) + 'px')

            // 更新浮层内容
            // chartTooltip.select(".name").text(p.name);
            let alarmMgr = AlarmManager(curThis.alarmCollection)
            if (p.nodeType === 1) {
              curThis.subnetOrDevice = 1
              curThis.hoverSubnetMessage.subnetAlarmList = alarmMgr.findAlarmBySubnet(curThis.subnetCollection[p.belongSubnet].deviceList)
              let AlarmLevelList = alarmMgr.getNumberByAlarmLevel(curThis.hoverSubnetMessage.subnetAlarmList)
              curThis.hoverSubnetMessage.waringAlarm = AlarmLevelList[0]
              curThis.hoverSubnetMessage.seriousAlarm = AlarmLevelList[1]
              curThis.hoverSubnetMessage.urgentAlarm = AlarmLevelList[2]
              curThis.hoverSubnetMessage.alarmCount = AlarmLevelList[3]
              curThis.hoverSubnetMessage.subnetName = curThis.subnetCollection[p.belongSubnet].subnetName
              curThis.hoverSubnetMessage.domain = curThis.subnetCollection[p.belongSubnet].domain
            } else {
              curThis.subnetOrDevice = 0
              curThis.hoverDeviceMessage.displayName = curThis.deviceCollection[p.elementKey].displayName
              curThis.hoverDeviceMessage.ipAddress = curThis.deviceCollection[p.elementKey].ipAddress
              curThis.hoverDeviceMessage.deviceBussIp = curThis.deviceCollection[p.elementKey].bussIpAddress
              curThis.hoverDeviceMessage.station = curThis.deviceCollection[p.elementKey].station
              curThis.hoverDeviceMessage.deviceType = this.EnglishToChinese(curThis.deviceCollection[p.elementKey].deviceTypeKey)
              curThis.hoverDeviceMessage.deviceModel = curThis.deviceCollection[p.elementKey].deviceModel
              curThis.hoverDeviceMessage.deviceVersion = curThis.deviceCollection[p.elementKey].deviceVersion
              curThis.hoverDeviceMessage.startTime = this.timeToShow(curThis.deviceCollection[p.elementKey].startTime)
              curThis.hoverDeviceMessage.elementAlarmList = alarmMgr.findAlarmByElement(p.elementKey)
              let AlarmLevelList = alarmMgr.getNumberByAlarmLevel(curThis.hoverDeviceMessage.elementAlarmList)
              curThis.hoverDeviceMessage.waringAlarm = AlarmLevelList[0]
              curThis.hoverDeviceMessage.seriousAlarm = AlarmLevelList[1]
              curThis.hoverDeviceMessage.urgentAlarm = AlarmLevelList[2]
              curThis.hoverDeviceMessage.alarmCount = AlarmLevelList[3]
            }
            // 移除浮层hidden样式，展示浮层
            topologyTooltip.classed('hidden', false)
          }, 400) // 设置你要的时间
        })
        .on('mouseout', d => {
          // d3.event.sourceEvent.stopPropagation();
          clearTimeout(curThis.timeout)
          d3.select('.topologyTooltip').classed('hidden', true)
        })
      // 调用拖拽函数
      g1Enter.call(curThis.$options.methods.addDragEvent())
      // 为多出数据集合添加网元图标
      g1Enter.append('g').attr('class', 'node-image')
      // 为多出的数据集合添加网元名称
      g1Enter.append('text').attr('class', 'node-text')

      // 此处是d3最牛逼的代码，v4新特性，将g1Enter合并到g1Update，使得第一次创建也属于更新里面。
      g1Update = g1Update.merge(g1Enter)

      // 更新网元名称
      g1Update.select('.node-text')
        .attr('dx', 15)
        .attr('dy', 45)
        .style('font-size', '12px')
        .style('text-anchor', 'middle')
        .style('font-family', 'SimSun')
        .text(function (d) {
          return d.topologyName
        })
      g1Update.selectAll('.node-image').nodes().forEach(n => {
        d3.xml(this.selectDeviceImagePth(n.__data__)).mimeType('image/svg+xml').get((error, xml) => {
          if (error) throw error
          for (let i = 0; i < n.childNodes.length; i++) {
            n.removeChild(n.childNodes[i])
          }
          n.append(xml.documentElement)
          n.childNodes[0].setAttribute('width', this.drawGraphSizeConfig.imageWidth)
          n.childNodes[0].setAttribute('height', this.drawGraphSizeConfig.imageHeight)
          n.childNodes[0].style.fill = this.getTopoColor(n.__data__.nodeType, n.__data__.elementKey)
        })
      })

      d3.select('#checkPath')
        .style('display', 'none')
      curThis.$emit('update:curClickDeviceId', '')
      curThis.$emit('update:curClickSubnetId', '')
      svg.call(svgZoom.transform, d3.zoomIdentity.translate(curThis.groupLeft, curThis.groupTop).scale(curThis.scaleK))
    },

    /**
     * @Description  ：选择拓扑图中的设备图片地址
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/15
     */
    selectDeviceImagePth (d) {
      if (d.nodeType === 1) {
        return `./static/子网.svg`
      } else {
        if (curThis.deviceCollection[d.elementKey].deviceTypeKey === '*' || curThis.deviceCollection[d.elementKey].deviceTypeKey === 'other') {
          return `./static/OTHER.svg`
        } else {
          return `./static/${curThis.deviceCollection[d.elementKey].deviceTypeKey}.svg`
        }
      }
    },

    getTopoColor (nodeType, elementKey) {
      if (nodeType === 0) {
        var index = curThis.deviceCollection[elementKey].status
      } else {
        var index = curThis.subnetCollection[elementKey].status
      }
      return curThis.alarmLevelConfig[index].alarmColor
    },
    handleDisa: function (val, device) {
      let alarmList = []
      alarmList = Object.values(curThis.alarmCollection)
      for (var i = 0; i < alarmList.length; i++) {
        if (alarmList[i].elementKey === device.deviceKey && alarmList[i].alarmCode === 'A_DEVICE_STATUS') {
          val = 1
          break
        }
      }
      return val
    },

    /**
     * @Description  ：处理拓扑图中设备的右键事件
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/15
     */
    handleTopologyDeviceContextmenu (d) {
      d3.select('.topologyTooltip').classed('hidden', true)
      /** 判断当前设备是否在list里面，在就显示多选菜单；否则就显示单选菜单。 */
      let index = curThis.selectDevicesList.indexOf(d.elementKey)
      if (d.nodeType === 0) {
        if (index === -1) { // 选中单个设备的时候的右键响应函数
          let enableLinkList = []// 需要加入规划的链路集合
          let disableFlag = 0// 链路加入规划 这一项是否disable的标识；0--disable/1--enable
          // if (curThis.deviceCollection[d.elementKey].deviceStatus !== 2) { // 此时当前设备状态为已激活
          //   let linkMgr = LinkManager(curThis.linkList)
          //   enableLinkList = linkMgr.findLinkByDeviceKey(curThis.deviceCollection, d.elementKey)
          //   if (enableLinkList.length !== 0) { // 此时链路集合里面有链路
          //     disableFlag = 1
          //   }
          // }
          curThis.selectDevice = curThis.deviceCollection[d.elementKey]
          disableFlag = HelperUtil.handleDisa(disableFlag, curThis.selectDevice, curThis.alarmCollection)
          curThis.contextCommands = []
          curThis.contextCommands = DeviceCommands.generateCommandsForDevice(curThis.selectDevice, curThis.commands, disableFlag, enableLinkList)
          curThis.deviceMenuEl = 'nodeMenu' // 记录当前点击的设备的位置数据
          curThis.deviceMenuTop = d3.event.y
          curThis.deviceMenuLeft = d3.event.x + window.scrollX
          curThis.deviceMenuShow = true
        } else { // 选中多个设备的时候的右键响应函数
          let deviceList = []
          for (let i = 0; i < curThis.selectDevicesList.length; i++) {
            deviceList.push(curThis.deviceCollection[curThis.selectDevicesList[i]])
          }
          curThis.contextCommands = []
          curThis.contextCommands = DeviceCommands.generateCommandsForDeviceList(deviceList, curThis.commands)
          curThis.deviceMenuEl = 'nodeMenu' // 记录当前点击的设备的位置数据
          curThis.deviceMenuTop = d3.event.y
          curThis.deviceMenuLeft = d3.event.x + window.scrollX
          curThis.deviceMenuShow = true
        }
      } else if (d.nodeType === 1) {
        // 子网右键
        let subnetMgr = SubnetManager(curThis.subnetCollection)
        let selectSubnet = subnetMgr.findSubnetByKey(d.elementKey)
        let flag = 0 // 0是子网有设备，1没有设备
        if (d.deviceList.length === 0) {
          flag = 1
        }
        curThis.contextCommands = []
        // curThis.contextCommands = SubnetCommands.generateCommandsForSubnet(selectSubnet, curThis.commands, flag)
        curThis.deviceMenuEl = 'nodeMenu' // 记录当前点击的设备的位置数据
        curThis.deviceMenuTop = d3.event.y
        curThis.deviceMenuLeft = d3.event.x + window.scrollX
        curThis.deviceMenuShow = true
      }
    },
    /**
     * 处理拓扑图中双击节点事件，显示子网视图
     */
    handleDoubleClickSubnet (d) {
      d3.select('.topologyTooltip').classed('hidden', true)
      // 子网才有双击事件
      if (d.nodeType === 1) {
        // 链接跳转
        if (Object.keys(curThis.subnetList).includes(d.elementKey)) {
          if (curThis.subnetList[d.elementKey].manageType === 1) {
            if (!(curThis.subnetList[d.elementKey].linkAddress === null || curThis.subnetList[d.elementKey].linkAddress === undefined || curThis.subnetList[d.elementKey].linkAddress === '')) {
              // 跳转的地址
              window.open(curThis.subnetList[d.elementKey].linkAddress + '?userName=' + window.sessionStorage.getItem('userName') + '&passWord=' + window.sessionStorage.getItem('passWord') + '&id=' + d.elementKey, '_blank')
              // window.open('http://localhost:8081/login' + '?userName=' + window.sessionStorage.getItem('userName') + '&passWord=' + window.sessionStorage.getItem('passWord') + '&id=' + d.elementKey, '_blank');
            }
            return
          }
        }
        if (Object.keys(curThis.topologyNodeList).includes(d.elementKey)) {
          if (curThis.doubleClickSubnetId === d.elementKey) {
            return
          }
          curThis.doubleClickSubnetId = d.elementKey
          curThis.topologyNodeShowList = curThis.topologyNodeList[d.elementKey].deviceList
          curThis.groupTop = 0
          curThis.groupLeft = 0
          // 现在显示网元
          curThis.subnetOrDeviceShow = 0
          curThis.currentSubnetKey = d.elementKey
          // 调用点击事件的是拓扑图节点的双击，而不是代码触发
          if (d.topologyKey !== undefined && d.topologyKey !== null) {
            let data = {
              key: curThis.doubleClickSubnetId,
              type: d.nodeType
            }
            EventBus.$emit('i-got-doubleClickSubnetId', data)
          }
          // 删除框选
          d3.select('#checkPath')
            .style('display', 'none')
          if (curThis.$route.path === '/TheLayout/main') {
            if (!d.isLinkTo) {
              svg.call(svgZoom.transform, d3.zoomIdentity.translate(0, 0).scale(1))
            }
            curThis.renderView()
          } else {
            // 当前在其它tab页，拓扑图tab页也需要侧边栏行为作出响应，在此进行标记
            curThis.dataChangeFromOtherTabs = true
          }
        }
      }
    },
    /**
     * 显示全局拓扑图
     */
    showGlobalView () {
      curThis.doubleClickSubnetId = ''
      curThis.topologyNodeShowList = curThis.topologyNodeList
      curThis.groupTop = 0
      curThis.groupLeft = 0
      curThis.subnetOrDeviceShow = 1
      let data = {
        key: curThis.doubleClickSubnetId,
        type: 1
      }
      EventBus.$emit('i-got-doubleClickSubnetId', data)
      // 删除框选
      d3.select('#checkPath')
        .style('display', 'none')
      svg.call(svgZoom.transform, d3.zoomIdentity.translate(0, 0).scale(1))
      curThis.renderView()
    },
    /**
     * @Description  ：控制拓扑图中设备的右键菜单显示
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/06/05
     */
    showDeviceMenu () {
      d3.select('#' + curThis.deviceMenuEl)
        .style('left', (curThis.deviceMenuLeft) + 'px')
        .style('top', (curThis.deviceMenuTop) + 'px')
        .style('visibility', 'visible')
    },

    /**
     * @Description  ：处理拓扑图中设备的点击事件
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/15
     */
    handleTopologyDeviceClick (d) {
      console.log('node')
      console.log(d)
      if (d3.event.ctrlKey) { // 返回值是一个布尔型的值，代表当前是 点击 || ctrl + 点击
        if (d.nodeType === 0) {
          curThis.clickDeviceId = '' // 清空记录点击节点的变量
        } else if (d.nodeType === 1) {
          curThis.clickSubnetId = '' // 清空记录点击节点的变量
        }
        let startXAxis = d.horizontalAxis // 当前点击设备的x坐标
        let startYAxis = d.verticalAxis // 当前点击设备的y坐标
        let key = d.elementKey // 当前点击设备的key

        let addFlag = 1// 是否添加标记，1代表添加，0代表不添加
        if (curThis.selectDevicesList.length === 0) { // 第一次添加的设备
          curThis.selectDevicesList.push(d.elementKey) // 把选中的设备加入到一个list里面
          curThis.showTopoNodeOneByOne(startXAxis, startYAxis, key) // 依次标识 ctrl+click 选中设备
        } else { // 以后添加的设备，每一个都判断是否重复，如果重复，addFlag置为0
          for (let i in curThis.selectDevicesList) {
            if (curThis.selectDevicesList[i] === d.elementKey) {
              addFlag = 0
              curThis.selectDevicesList.splice(i, 1) // 将数据从数组中清除。
              d3.select('#checkPaths' + d.elementKey).remove() // 框选删除
              break
            }
          }
          if (addFlag === 1) { // 判断标志位，addFlag是否为1，决定是否添加
            curThis.selectDevicesList.push(d.elementKey) // 把选中的设备加入到一个list里面
            curThis.showTopoNodeOneByOne(startXAxis, startYAxis, key) // 依次标识 ctrl+click 选中设备
          }
        }
      } else { // 当前点击设备的响应
        if (d.nodeType === 0) {
          curThis.clickDeviceId = d.elementKey
          curThis.selectOneDevice(curThis.clickDeviceId) // 标识选中的设备
        } else if (d.nodeType === 1) {
          curThis.clickSubnetId = d.elementKey
          curThis.selectOneDevice(curThis.clickSubnetId)
        }
      }
      if (d.nodeType === 0) {
        EventBus.$emit('i-got-clickDeviceId', curThis.clickDeviceId) // 通过全局事件总线将点击的设备传送给顶层组件
      } else if (d.nodeType === 1) {
        EventBus.$emit('i-got-clickSubnetId', curThis.clickSubnetId)
      }
      clearTimeout(curThis.timeout)
      d3.select('.topologyTooltip').classed('hidden', true)
    },

    /**
    * @Description  ：增加拖拽事件。
    * @author       : yjz
    * @param        :
    * @return       :
    * @exception    :
    * @date         : 2019/04/15
    */
    addDragEvent () {
      let dragEvent = d3.drag()
        .on('start', curThis.$options.methods.dragStart)
        .on('drag', curThis.$options.methods.dragMove)
        .on('end', curThis.$options.methods.dragEnd)
      return dragEvent
    },

    /**
     * @Description  ：增加拖拽事件。
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/15
     */
    dragStart (d) {
      clearTimeout(curThis.timeout)
      d3.select('.topologyTooltip').classed('hidden', true)
      // document.getElementById("checkPath").style.display = "none";
      document.getElementById('nodeMenu').style.display = 'none'
      document.getElementById('linkMenu').style.display = 'none'
      document.getElementById('blankMenu').style.display = 'none'
    },

    dragEnd (d) {
      clearTimeout(curThis.timeout)
      d3.select('.topologyTooltip').classed('hidden', true)
      curThis.currentDragKey = '' // 置空拖拽设备记录变量
    },
    /**
     * @Description  ：位置更新函数。
     *                  要充分理解tick的作用，搞清楚什么时候调用，调用tick是为了什么。更新的到底是哪些元素的位置。
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2018/12/22
     */
    tick: function () {
      // 更新连接位置
      g.selectAll('g.link')
        .each(function (d) {
          curThis.$options.methods.drawPath(d, d3.select(this))
        })

      // 更新网元及名称位置
      g.selectAll('.node')
        .attr('transform', function (d) {
          return 'translate(' + d.horizontalAxis + ',' + d.verticalAxis + ')'
        })

      // 更新拖拽设备的标注框
      if (curThis.currentDragKey !== '') { // 当前有拖拽设备才执行以下代码
        g.select('#checkPaths' + curThis.currentDragKey) // 更新多选设备框
          .attr('d', function () {
            let startXAxis = curThis.topologyNodeShowList[curThis.currentDragKey].horizontalAxis
            let startYAxis = curThis.topologyNodeShowList[curThis.currentDragKey].verticalAxis
            return 'M' + (startXAxis - 2) + ' ' + (startYAxis - 2) + ' ' + 'h36 v36 h-36 Z'
          })
        g.select('#checkPath') // 更新单选设备框
          .attr('d', function () {
            let startXAxis = curThis.topologyNodeShowList[curThis.currentDragKey].horizontalAxis
            let startYAxis = curThis.topologyNodeShowList[curThis.currentDragKey].verticalAxis
            return 'M' + (startXAxis - 2) + ' ' + (startYAxis - 2) + ' ' + 'h36 v36 h-36 Z'
          })
      }
      curThis.currentDragKey = '' // 置空拖拽设备记录变量
    },

    /**
     * @Description  ：拖拽事件中拖拽行为的响应函数，并调用tick()进行位置的更新。
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2018/12/22
     */
    dragMove: function (d) {
      clearTimeout(curThis.timeout)
      d3.select('.topologyTooltip').classed('hidden', true)
      curThis.currentDragKey = d.elementKey // 记录下当前拖拽的设备的id
      d3.select(curThis.$el)
        .attr('transform', function () {
          d.horizontalAxis += d3.event.dx
          d.verticalAxis += d3.event.dy
          return 'translate(' + d.horizontalAxis + ',' + d.verticalAxis + ')'
        })
      d3.select(curThis.$el).on('tick', curThis.$options.methods.tick)
      curThis.$options.methods.tick()
    },

    /**
     * @Description  ：标识拓扑图中的单一设备
     * @author       :  yjz
     * @param        :  拓扑中需要标记的设备的坐标
     * @return       :
     * @exception    :
     * @date         : 2019/04/04
     */
    selectOneDevice: function (id) {
      let selectedDevice = curThis.topologyNodeShowList[id]
      if (selectedDevice === undefined) {
        return
      }
      let startXAxis = selectedDevice.horizontalAxis
      let startYAxis = selectedDevice.verticalAxis
      d3.select('#checkPath')
        .attr('d', 'M' + (startXAxis - 2) + ' ' + (startYAxis - 2) + ' ' + 'h36 v36 h-36 Z')
        .style('display', 'block')
    },

    /**
     * @Description  ：定位偏移函数。
     * @author       : yjz
     * @param        :
     * @return
     * @exception    :
     * @date         : 2019/1/14
     */
    clickDeviceTranslate: function (id) {
      if (this.topologyNodeShowList[id] === undefined) {
        return
      }
      // 被选中设备的x/y坐标
      let x = this.topologyNodeShowList[id].horizontalAxis + 16
      let y = this.topologyNodeShowList[id].verticalAxis + 16

      // 被选中设备相对于svg左上角的的x/y坐标
      let xForSvg = curThis.scaleK * x + curThis.groupLeft
      let yForSvg = curThis.scaleK * y + curThis.groupTop

      // 当前可视化窗口的大小以及中心点
      let topologySvgWidth = document.getElementById('topologyWrapper').offsetWidth - document.getElementsByClassName('tool-aside')[0].offsetWidth
      let topologySvgHeight = document.getElementById('topologyWrapper').offsetHeight

      let visualWidth = topologySvgWidth
      let visualHeight = topologySvgHeight
      let visualCenterX = visualWidth / 2
      let visualCenterY = visualHeight / 2

      // 判断，当被点击设备的相对坐标不在可视窗口内时，进行偏移
      if (xForSvg < 0 || xForSvg > visualWidth || yForSvg < 0 || yForSvg > visualHeight) {
        curThis.groupLeft += (visualCenterX - xForSvg)
        curThis.groupTop += (visualCenterY - yForSvg)
        svg.call(svgZoom.transform, d3.zoomIdentity.translate(curThis.groupLeft, curThis.groupTop).scale(curThis.scaleK))
      }
    },

    /**
     * @Description  ：处理关联设备的显示和偏移。
     * @author       : yjz
     * @param        :
     * @return
     * @exception    :
     * @date         : 2019/1/14
     */
    handleDeviceShowAndTranslate (id) {
      this.selectOneDevice(id)
      this.clickDeviceTranslate(id)
    },

    /**
     * @Description  ：获取子网下的设备集合
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    showBelongDevices: function () {
      // 1.构建一个Submanger，参数是subList
      let subMgr = SubnetManager(this.subnetCollection)
      // 2.调用Submanger里面的Sub下属设备集合。参数是clickSubId
      let belongDeviceList = subMgr.findSubnetDeviceByKey(this.clickSubnetId)
      // 3.根据返回的集合，遍历，然后画框标识
      curThis.$options.methods.showDeviceInGroup(belongDeviceList)
    },

    /**
     * @Description  ：批量标注子网/控制器下的设备
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    showDeviceInGroup (showList) {
      for (let deviceKey in showList) {
        let startXAxis = curThis.topologyNodeShowList[deviceKey].horizontalAxis
        let startYAxis = curThis.topologyNodeShowList[deviceKey].verticalAxis
        let key = curThis.topologyNodeShowList[deviceKey].elementKey
        // TODO:此处现在是可以选择多个子网，标注他们下面的设备；如果以后只能选择和标注一个子网下的设备，此处判断list长度是否大于1，先清空，在添加。
        if (curThis.selectDevicesList.length === 0) {
          curThis.selectDevicesList.push(key)
          curThis.showTopoNodeOneByOne(startXAxis, startYAxis, key)
        } else {
          let index = curThis.selectDevicesList.indexOf(key)
          if (index === -1) { // 判断当前被选元素是否已经存在list中
            curThis.selectDevicesList.push(key)
            curThis.showTopoNodeOneByOne(startXAxis, startYAxis, key)
          }
        }
      }
    },

    /**
     * @Description  ：给拓扑节点创建标注框（不区分设备和子网）
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/04/14
     */
    showTopoNodeOneByOne (startXAxis, startYAxis, key) {
      g.append('path').attr('class', 'checkPaths')
        .attr('id', () => {
          return 'checkPaths' + key
        })
        .attr('d', 'M' + (startXAxis - 2) + ' ' + (startYAxis - 2) + ' ' + 'h36 v36 h-36 Z')
        .style('display', 'block')
    },

    /**
     * @Description  ：刷子监听brush事件的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    brushed () {
      if (brush.empty()) { return }
      brush.style('opacity', 1)// 显示刷子
    },

    /**
     * @Description  ：刷子监听end事件的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    endBrushed () {
      let selection = d3.event.selection// 刷子覆盖区域
      if (selection) { // 如果该区域不为空
        for (let elementKey in this.topologyNodeShowList) {
          let x = this.topologyNodeShowList[elementKey].horizontalAxis
          let y = this.topologyNodeShowList[elementKey].verticalAxis
          let key = this.topologyNodeShowList[elementKey].elementKey

          if (selection[0][0] <= x && x < selection[1][0] &&
            selection[0][1] <= y && y < selection[1][1]) {
            if (this.selectDevicesList.length === 0) {
              this.selectDevicesList.push(key)
              this.showTopoNodeOneByOne(x, y, key)
            } else {
              let index = this.selectDevicesList.indexOf(key)
              if (index === -1) { // 判断当前被选元素是否已经存在list中
                this.selectDevicesList.push(key)
                this.showTopoNodeOneByOne(x, y, key)
              }
            }
          }
        }
      } else { return }
      brush.style('opacity', 0)
      // brush.select(".brush").call(brushFunction.move, null)
      d3.select('.selection').attr('width', 0).attr('height', 0)
    },

    /**
     * @Description  ：左侧工具栏点击刷子按钮的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    changeToSelect () {
      document.getElementsByClassName('btn-select')[0].classList.add('active')
      document.getElementsByClassName('btn-zoom')[0].classList.remove('active')
      svg.on('.zoom', null)// 屏蔽缩放操作
      // 当前可视化窗口的大小以及中心点
      let topologySvgWidth = document.getElementById('topologyWrapper').offsetWidth
      let topologySvgHeight = document.getElementById('topologyWrapper').offsetHeight
      /**
       * 更改之前，采用获取所有坐标极值制定刷子作用域
       */
      // let extents = this.searchMaxAndMinPosition();//获取到拓扑节点坐标的最大最小
      // let X0 = extents.min[0] - 100, Y0 = extents.min[1] - 100, X1 = extents.max[0] + 100, Y1 = extents.max[1] + 100;
      /**
       * 更改之后，采用将可视区域制定为刷子作用域
       */
      let x0 = 0 - curThis.groupLeft
      let y0 = 0 - curThis.groupTop
      let x1 = topologySvgWidth - curThis.groupLeft
      let y1 = topologySvgHeight - curThis.groupTop
      brushFunction.extent([[x0, y0], [x1, y1]])// 设置刷子的作用域
      brush.call(brushFunction)// 调用刷子函数
      brush.style('display', '')
    },

    /**
     * @Description  ：左侧工具栏点击移动按钮的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    changeToZoom (e) {
      document.getElementsByClassName('btn-zoom')[0].classList.add('active')
      document.getElementsByClassName('btn-select')[0].classList.remove('active')
      brush.style('display', 'none')// 屏蔽刷子操作
      brushFunction.on('.brush', null)
      svg.call(svgZoom).on('dblclick.zoom', () => {})// 调用缩放函数
    },

    /**
     * @Description  ：左侧工具栏点击全屏按钮的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    fullScreenIn () {
      // TODO:全屏以后的缩放倍数以及左上角的点有待优化。
      document.getElementById('topologyWrapper').classList.add('fullScreenIn')
      let element = document.getElementById('svgWrapper')
      element.classList.add('fullScreenIn')
      element.classList.remove('fullScreenOut')
    },
    fullScreenOut () {
      document.getElementById('nodeMenu').style.display = 'none'
      document.getElementById('linkMenu').style.display = 'none'
      document.getElementById('blankMenu').style.display = 'none'
      document.getElementById('topologyWrapper').classList.remove('fullScreenIn')
      let element = document.getElementById('svgWrapper')
      if (element.classList.contains('fullScreenIn')) {
        element.classList.remove('fullScreenIn')
        element.classList.add('fullScreenOut')
      }
    },

    /**
     * @Description  ：辅助函数，寻找一个数组的最大最小值
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/09
     */
    searchMax (arr) {
      return Math.max.apply(null, arr)
    },
    searchMin (arr) {
      return Math.min.apply(null, arr)
    },

    /**
     * @Description  ：左侧工具栏的实时监控按钮的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/05/10
     */
    async changeMonitorState (isMonitor) {
      /** 打开监控按钮改动地方记录表
       *1.handleTopologyLinkContextmenu/ 处理连接右键，
       *2.handleManyPath / 处理链路数组数据重新赋字段的时候。
       *3.getLinkColor / 处理链路颜色。
       * */
      let monitoring = false// 传给顶层组件的值，代表监控正在打开。
      if (isMonitor === '是') { // 打开监控进行的回掉函数
        monitoring = true
        // 改变链路集合标志位，通过watch监听变化，实现render,重绘拓扑。
        curThis.isLinkList = 1
      } else if (isMonitor === '否') { // 关闭监控进行的回掉函数
        monitoring = false
        curThis.isLinkList = 0
      }
      EventBus.$emit('i-got-isMonitor', monitoring)// 通过全局事件总线将按钮的开关传送给顶层组件
    },

    /**
     * @Description  ：click的全局监听响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/06/06
     */
    nativeClick (event) {
      d3.select('.topologyTooltip').classed('hidden', true)
      if (this.$route.path !== '/TheLayout/main') return
      document.getElementById('nodeMenu').style.display = 'none'
      document.getElementById('linkMenu').style.display = 'none'
      document.getElementById('blankMenu').style.display = 'none'
      if (event.ctrlKey) { // 此时是多选设备模式
        document.getElementById('checkPath').style.display = 'none' // 单选框消除
        curThis.clickDeviceId = '' // 清空id
        EventBus.$emit('i-got-clickDeviceId', curThis.clickDeviceId)// 通过全局事件总线将点击的设备传送给顶层组件
      } else { // 此时是单选设备模式
        d3.selectAll('.checkPaths').remove() // 将批量标注的框进行移除
        curThis.selectDevicesList = [] // 清空列表
        // 图标左键 不处理
        // if (event.target.className.animVal !== 'node') { // 当前点击的不是设备
        //   let box = document.getElementById('alarmDisplay');
        //   if (box !== null && !box.contains(event.target)) { // 当前点击的不是下面的告警栏
        //     document.getElementById('checkPath').style.display = 'none'; // 单选框消除
        //     curThis.clickDeviceId = ''; // 清空id
        //     EventBus.$emit('i-got-clickDeviceId', curThis.clickDeviceId)// 通过全局事件总线将点击的设备传送给顶层组件
        //   }
        // }
      }
    },

    /**
     * @Description  ：contextmenu的全局监听响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/06/06
     */
    nativeContextmenu (event) {
      if (this.$route.path !== '/TheLayout/main') return
      // 图标右键
      if (curThis.contextCommands.length === 0) {
        document.getElementById('nodeMenu').style.display = 'none' // 屏蔽设备菜单
        document.getElementById('blankMenu').style.display = 'none' // 屏蔽设备菜单
        document.getElementById('blankMenu').style.display = 'none'
        d3.selectAll('.checkPaths').remove() // 将批量标注的框进行移除
        curThis.selectDevicesList = [] // 清空列表
      }
      // 链路右键
      if (event.target.className.animVal === 'textPath' || event.target.className.animVal === 'path' || event.target.className.animVal === 'path-data' || event.target.className.animVal === 'path-text' || event.target.className.animVal === 'linkWrapper') {
        // 显示链路右键
      } else {
        document.getElementById('linkMenu').style.display = 'none'
        document.getElementById('blankMenu').style.display = 'none'
      }
      if (event.target.id === 'svgWrapper') {
        if (curThis.doubleClickSubnetId !== '') {
          curThis.contextCommands = []
          curThis.contextCommands.push(curThis.createDeviceCommands)
          curThis.contextCommands.push(curThis.createLinkCommands)
          let menu = document.getElementById('blankMenu')
          let yy = document.body.clientHeight - event.clientY
          let xx = document.body.clientWidth - event.clientX
          console.log(event.clientX + ' ' + event.clientY)
          if (yy < 120) {
            menu.style.top = event.clientY - 100 + 'px'
          } else {
            menu.style.top = event.clientY + 'px'
          }
          if (xx < 200) {
            menu.style.left = event.clientX - 100 + 'px'
          } else {
            menu.style.left = event.clientX + 'px'
          }
          menu.style.display = 'block'
        }
      }
    },

    /**
     * @Description  ：侧边栏左键的响应函数
     * @author       : yjz
     * @param        :
     * @return       :
     * @exception    :
     * @date         : 2019/06/06
     */
    handleClickSidebar (clickSidebar) {
      if (clickSidebar) {
        // curThis.$options.methods.nativeClick(event);
        document.getElementById('nodeMenu').style.display = 'none'
        document.getElementById('linkMenu').style.display = 'none'
        document.getElementById('blankMenu').style.display = 'none'
      }
    },

    /**
     * 获得本地最新的拓扑集合,用于保存
     */
    getLocalNewestTopologyCollection () {
      // todo 给每个节点绑定偏移量数据
      for (let iter in curThis.topologyNodeShowList) {
        curThis.topologyNodeShowList[iter].horizontalOffset = curThis.groupLeft
        curThis.topologyNodeShowList[iter].verticalOffset = curThis.groupTop
      }
      return JSON.parse(JSON.stringify(this.topologyNodeShowList))
    }
  },

  computed: {
    createDeviceCommands () {
      return DeviceCommands.blankCreateAndCopyDeviceCommand(this.$t('DeviceConfigurationManagement.createDeviceInfo'), curThis.deviceList, curThis.commandContext)
    },
    createLinkCommands () {
      return DeviceCommands.createAndCopyLinkCommand(this.$t('LinkConfigurationManagement.createLinkInfo'), curThis.links, curThis.commandContext)
    }
  },
  watch: {
    // 监听prop，更新本地缓存数据
    topologyCollection: {
      handler (newVal, OldVal) { // 目前的逻辑中，当前组件并不会直接对list进行修改，所以只修监听到传过来的props有修改，此时重新渲染视图
        this.topologyNodeList = newVal
        if (curThis.doubleClickSubnetId === '') {
          this.topologyNodeShowList = this.topologyNodeList
        } else {
          this.topologyNodeShowList = this.topologyNodeList[curThis.doubleClickSubnetId].deviceList
        }
        console.log('topology 发生更新')
        if (curThis.$route.path === '/TheLayout/main') {
          this.drawNode()
        } else {
          curThis.dataChangeFromOtherTabs = true
        }
      },
      deep: true
    },

    // 监听prop，更新本地缓存数据
    linkCollection: {
      handler (newVal, OldVal) {
        this.linkList = newVal
        console.log('link 发生更新')
        this.drawLink()
      },
      deep: true
    },
    // 监听prop，更新本地缓存数据
    curClickDeviceId: {
      handler (newVal, OldVal) {
        this.clickDeviceId = newVal
        if (!(this.clickDeviceId === '' || this.clickDeviceId === null)) {
          this.handleDeviceShowAndTranslate(this.clickDeviceId)
        }
      }
    },

    // 监听prop，更新本地缓存数据
    curClickSubnetId: {
      handler (newVal, OldVal) {
        this.clickSubnetId = newVal
        this.handleDeviceShowAndTranslate(this.clickSubnetId)
      }
    },

    // 监听prop，更新本地缓存数据
    alarmLevelConfig: {
      handler (newVal, OldVal) {
        this.alarmColorConfig = newVal
        console.log('alarm 发生更新')
        this.renderView()
      },
      deep: true
    },

    alarmCollection: {
      handler (newVal, OldVal) {
        this.alarmCollection = newVal
        console.log('alarm 发生更新')
      },
      deep: true
    },

    // 监听prop，更新本地缓存数据
    isLinkList: {
      handler (newVal, OldVal) {
        // todo 此时该变量无用，和业务无关
        curThis.isLinkList = newVal
        console.log('is link 发生更新')
        this.renderView()
      },
      deep: true
    },
    // 双击打开的子网id，也就是当前拓扑图所属子网的id,若是全局视图，则id为''
    doubleClickSubnetId: {
      handler (newVal, oldVal) {
        curThis.$emit('update:doubleClickSubnetId', newVal)
      },
      deep: true
    },
    '$route' (to, from) {
      if (to.path === '/TheLayout/main') {
        // 从其它tab页转到拓扑图tab页，先判断拓扑图是否需要响应之前侧边栏的行为，为true，则需要响应，(数据已经变了，还未绘制)进行绘制，响应之后置为false
        if (curThis.dataChangeFromOtherTabs) {
          curThis.renderView()
          this.handleDeviceShowAndTranslate(curThis.clickDeviceId)
          curThis.dataChangeFromOtherTabs = false
        }
      }
      // 设备右键跳转到实时性能tab页，要右键菜单消失
      if (to.path === '/TheLayout/RtPerformance') {
        document.getElementById('nodeMenu').style.display = 'none'
        document.getElementById('linkMenu').style.display = 'none'
        document.getElementById('blankMenu').style.display = 'none'
      }
    }

  },
  // 无法获取 data中的数据、methods中的方法
  beforeCreate () {},
  // 常用的生命周期，可以调用methods中的方法、改变data中的数据
  created () {
    // 将this对象进行转换，避免出现对象未定义错误
    curThis = this
    let time = new Date()
    console.log('拓扑created============' + time.getMinutes() + 'm' + time.getSeconds() + 's' + time.getMilliseconds() + 'ms')
  },
  // 在挂载开始之前被调用
  beforeMount () {
  },
  // 此时，vue实例已经挂载到页面中，可以获取到el中的DOM元素，进行DOM操作
  mounted () {
    let time = new Date()
    console.log('拓扑mounted============' + time.getMinutes() + 'm' + time.getSeconds() + 's' + time.getMilliseconds() + 'ms')

    // curThis.topologyNodeShowList = curThis.topologyNodeList
    let mode = this.$store.state.systemMode
    /**
     * 设置点击监听事件，实现左键点击，菜单/选择框消失。
     * 1.获取到所有网元节点集合的长度
     * 2.监听鼠标左键，
     * 3.循环所有网元节点集合，判断当前右键点击的元素是否是网元节点中的一个，是将flag置为true。
     * 4.当遍历结束所有的网元节点，flag均为false的时候，隐藏菜单/选择框。
     */
    document.addEventListener('click', this.nativeClick)

    /**
     * 设置右键监听事件，实现右键其他区域，菜单消失。
     * 1.获取到所有网元节点集合的长度
     * 2.监听鼠标右键，
     * 3.循环所有网元节点集合，判断当前右键点击的元素是否是网元节点中的一个，是将flag置为true。
     * 4.当遍历结束所有的网元节点，flag均为false的时候，隐藏菜单。
     *
     */
    document.addEventListener('contextmenu', this.nativeContextmenu)

    EventBus.$on('i-click-sidebar', this.handleClickSidebar)

    // 1.创建SVG画布、创建视图群组g、创建标识框
    this.createSvg()
    this.isSDNCModel()
    // 2.为SVG添加缩放以及刷子
    this.addZoomToSvg()
    this.addBrushToSvg()
    let time1 = new Date()
    console.log('拓扑mounted 创建画布刷子============' + time1.getMinutes() + 'm' + time1.getSeconds() + 's' + time1.getMilliseconds() + 'ms')

    // 3.渲染视图
    let subnetId = HelperUtil.getDesCookie('subnetId')
    let subnets = Object.values(curThis.subnetCollection)
    let isExist = subnets.find((n) => {
      return n.subnetKey === subnetId
    })
    if (subnetId !== '' && isExist) {
      let subnet = {
        nodeType: 1,
        elementKey: subnetId,
        isLinkTo: true // 是否是链接跳转过来的
      }
      curThis.handleDoubleClickSubnet(subnet)
    } else {
      this.renderView()
    }
    // 4.处理linkObject,使得其属性中有两端的deviceObject
    // 5.处理linkList,使得其成为二维数组
    // 6.绘制连接
    // 7.绘制设备
    // 8.标识单一设备

    // TODO：将text 的一些样式抽离出来

    // 双击子网节点的处理
    EventBus.$on('i-got-doubleClickSubnet', curThis.handleDoubleClickSubnet)
    document.addEventListener('keydown', function (e) {
      if (e.which == 27) {
        curThis.fullScreenOut()
      }
    }, false)
  },
  // 运行期间
  beforeUpdate () {},
  updated () {
    console.log('vue 生命周期，数据更新')
    if (curThis.deviceMenuEl !== '') {
      /** 此处进行设备菜单的边界检测以及显示 */
      d3.select('#' + curThis.deviceMenuEl)
        .style('left', 0)
        .style('top', 0)
        .style('display', 'block')
        .style('visibility', 'hidden')
      let el = document.getElementById(curThis.deviceMenuEl)
      const contextmenuWidth = el.offsetWidth
      const contextmenuHeight = el.offsetHeight
      if (contextmenuHeight + curThis.deviceMenuTop >= window.innerHeight) {
        curThis.deviceMenuTop -= contextmenuHeight
      }
      if (contextmenuWidth + curThis.deviceMenuLeft >= window.innerWidth) {
        curThis.deviceMenuLeft -= contextmenuWidth
      }
      if (this.deviceMenuShow) { // 当所选择的是设备右键，才会显示菜单。
        this.showDeviceMenu()
        this.deviceMenuShow = false // 将标志位置空
      }
    }
  },
  // 销毁期间
  beforeDestroy () {},
  destroyed () {}

}
</script>

<style>
  /** css 属性 控制全屏时候的默认样式*/
  :-webkit-full-screen {
    background: #fff;
    width: 100%;
    height: 100%;
  }
  /** css 属性 控制全屏时候的默认样式*/
  :-moz-full-screen {
    background: #fff;
    width: 100%;
    height: 100%;
  }
  /** css 属性 控制全屏时候的默认样式*/
  :-ms-fullscreen {
    /* properties */
  }
  /** css 属性 控制全屏时候的默认样式*/
  :fullscreen { /* spec */
    /* properties */
  }
  /** css 属性 控制全屏时候的默认样式*/
  /* deeper elements */
  :-webkit-full-screen svgWrapper {
    width: 100%;
    height: 100%;
    background: #fff;
  }

  /** button 按钮的tooltip 样式*/
  .top {
    text-align: center;
  }

  .left {
    float: left;
    width: 60px;
  }

  .right {
    float: right;
    width: 60px;
  }

  .bottom {
    clear: both;
    text-align: center;
  }

  .left .el-tooltip__popper,
  .right .el-tooltip__popper {
    padding: 8px 10px;
  }

  /**  自己修改提示框的背景和箭头颜色 */
  .el-tooltip__popper[x-placement^=left] .popper__arrow{
    border-left-color: #5fa4e8;
  }
  .el-tooltip__popper[x-placement^=left] .popper__arrow:after {
    border-left-color: #5fa4e8;
  }
  .tooltipBackground{
    background: #5fa4e8 !important;
  }
  .tool-aside{
    display: flex;
    justify-content:center;
    align-items:Center;
    align:middle;
    overflow: hidden;
    background:#f0f9eb
  }
  .tool-box{
    width: 100%;
    overflow: hidden;
  }
  .tool-box .el-button.active{
    background: #409EFF;
    border-color: #409EFF;
    color: #FFF;
  }
  #checkPath {
    stroke: #BEBEBE;
    stroke-width:2;
    fill: none;
  }
  .checkPaths {
    stroke: #BEBEBE;
    stroke-width:2;
    fill: none;
  }
  .topologyTooltip {
    position: absolute;
    z-index: 2000;
    width: 240px;
    height: auto;
    padding: 10px;
    box-sizing: border-box;
    background-color: white;
    border-radius: 5px;
    box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4);
    pointer-events: none;
  }
  .topologyTooltip.hidden {
    display: none;
  }
  .topologyTooltip p {
    margin: 0;
    font-size: 14px;
    line-height: 20px;
    word-wrap: break-word;
    text-align:left;
  }
  #nodeMenu {
	z-index: 2000;
	}
  .topologyLink {
    position: absolute;
    z-index: 2000;
    width: 200px;
    height: auto;
    padding: 10px;
    box-sizing: border-box;
    background-color: white;
    border-radius: 5px;
    box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4);
    pointer-events: none;
  }
  .topologyLink p {
    margin: 0;
    font-size: 14px;
    line-height: 20px;
    word-wrap: break-word;
    text-align:left;
  }
  .topologyLink.hidden {
    display: none;
  }
  #svgWrapper{
    background-color: #fff;
    user-select: none;
  }
  #svgWrapper.fullScreenIn {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1999;
  }
  #svgWrapper.fullScreenOut {
    position: relative;
    top: 0;
    /*z-index: 1999;*/
  }
  #topologyWrapper.fullScreenIn {
    position: fixed;
    z-index: 1999;
    top: 0px;
    left: 0px;
    height: 100%;
    width: 100%;
  }
</style>
