<template>
  <div class="topology">
    <!-- toolbar -->
    <div v-if="toolbarShow && graphMode === 'edit'">
      <toolbar-edit></toolbar-edit>
    </div>
    <!-- container -->
    <div class="container" :class="graphBg" @contextmenu.prevent>
      <!-- item-pannel -->
      <div v-if="graphMode === 'edit'" class="left">
        <div class="top-nav-confg">
          <div class="top-img">
            <div width="10px" height="10px"
                 class="node-type"
                 v-for="nodeType in nodeTypeList"
                 :key="nodeType.id">
              <img
                :src="imgSrc"
                :alt="nodeType.tableName+':'+nodeType.tableComment"
                :title="nodeType.tableName+':'+nodeType.tableComment"
                draggable="true"
                @dragstart="dragstartHandler($event, nodeType)"
                @dragend="dragendHandler"
              />
              <p style="
                           text-align: center;
                           color: #272727;
                           display: block;
                           overflow: hidden;
                           white-space: nowrap;
                           text-overflow: ellipsis;
                           width: 60px;">{{ nodeType.tableComment }}</p>
            </div>
          </div>
        </div>

        <!--        <div-->
        <!--          class="node-type"-->
        <!--          v-for="nodeType in nodeTypeList"-->
        <!--          :key="nodeType.id"-->
        <!--        >-->
        <!--          <img-->
        <!--            :src="imgSrc"-->
        <!--            :alt="nodeType.tableName+':'+nodeType.tableComment"-->
        <!--            :title="nodeType.tableName+':'+nodeType.tableComment"-->
        <!--            draggable="true"-->
        <!--            @dragstart="dragstartHandler($event, nodeType)"-->
        <!--            @dragend="dragendHandler"-->
        <!--          />-->
        <!--        </div>-->
      </div>
      <!-- graph-container -->
      <div
        class="center graph-container"
        :style="{'width': graphMode === 'edit' ? '76%': '100%'}"
        ref="graphContainer"
      >
        <div
          id="mount-topology"
          @dragenter="dragenterHandler"
          @dragover="dragoverHandler"
          @drop="dropHandler"
        >
        </div>
      </div>
      <!-- graph-pannel -->
      <div v-if="graphMode === 'edit'" class="right">
        <div class="detail-pannel">
          <div v-if="currentFocus === 'node'">
            <div class="pannel-title">节点</div>
            <div class="block-container">
              <span>节点标签</span>
              <el-input v-model="selectedNodeParams.label" size="mini"/>
              <!--              <input class="params-input" type="text" autocomplete="off" v-model="selectedNodeParams.label" />-->
              <div v-for="(value, key) in nodeAppConfig" :key="key">
                <span>{{ value }}</span>
                <el-input v-model="selectedNodeParams.appConfig[key]" size="mini"/>
              </div>
            </div>
          </div>
          <div v-else-if="currentFocus === 'edge'">
            <div class="pannel-title">连线</div>
            <div class="block-container">
              <!--              <span>连线标签</span>-->
              <!--              <el-input v-model="selectedEdgeParams.label" size="mini"/>-->
              <div class="input-item">
                <span>查询方式</span>
                <div class="input-select">
                  <el-select v-model="selectedEdgeParams.appConfig.associated"
                             @change="associatedChange"
                             size="mini"
                  >
                    <el-option label="left join" value="left"/>
                    <el-option label="right join" value="right"/>
                    <el-option label="inner join" value="inner"/>
                  </el-select>
                </div>
              </div>
              <div class="input-item">
                <span>主表字段</span>
                <div class="input-select">
                  <el-select v-model="selectedEdgeParams.appConfig.tableComment" @change="tableCommentChang"
                             size="mini">
                    <el-option
                      v-for="(item ,index) in selectedEdgeParams.sourceColumn"
                      :key="index"
                      :label="item.columnName+':'+item.columnComment"
                      :value="item.columnName"
                    />
                  </el-select>
                </div>
              </div>
              <div class="input-item">
                <span>关联字段</span>
                <div class="input-select">
                  <el-select v-model="selectedEdgeParams.appConfig.relComment" @change="relCommentChange" size="mini">
                    <el-option
                      v-for="(item ,index) in selectedEdgeParams.targetColumn"
                      :key="index"
                      :label="item.columnName+':'+item.columnComment"
                      :value="item.columnName"
                    />
                  </el-select>
                </div>
              </div>
              <!--              <input class="params-input" type="text" autocomplete="off" v-model="selectedEdgeParams.label" />-->
              <!--              <div v-for="(value, key) in edgeAppConfig" :key="key">-->
              <!--                <span>{{ value }}</span>-->
              <!--                <el-input v-model="selectedEdgeParams.appConfig[key]" size="mini"/>-->
              <!--&lt;!&ndash;                <input class="params-input" type="text" autocomplete="off"&ndash;&gt;-->
              <!--&lt;!&ndash;                       v-model="selectedEdgeParams.appConfig[key]" />&ndash;&gt;-->
              <!--              </div>-->
            </div>
          </div>
          <div v-else-if="currentFocus === 'C'">
            C
          </div>
          <div v-else>
            <div class="pannel-title">画布</div>
            <div class="block-container">
              <cc-checkbox @change="enableGridHandler">网格对齐</cc-checkbox>
            </div>
          </div>
        </div>
        <!-- navigator-pannel -->
        <div class="navigator-pannel">
          <div class="pannel-title">导航器</div>
          <div class="navigator" ref="navigator">
            <div id="g6-minimap"></div>
          </div>
        </div>
      </div>
    </div>
    <cc-loading v-if="loading" loading-text="自动布局中..."></cc-loading>
    <div class="right-menu" ref="rightMenu" v-show="rightMenuShow" @contextmenu.prevent>
      <ul v-if="currentFocus === 'node' && selectedNodes.length > 1">
        <li @click="alignHandler('horizontal')">水平对齐</li>
        <li @click="alignHandler('vertical')">垂直对齐</li>
        <li @click="addGroup">创建分组</li>
      </ul>
      <ul v-else-if="currentFocus === 'group'">
        <li @click="removeGroup">移除分组</li>
      </ul>
    </div>
    <div>
      <el-dialog :title="selectedNodeParams.label" width="1200px"
                 :visible.sync="tableColumnEditOpen"
                 @close="closeEditColumns">
        <el-table
          :data="selectedNodeParams.columns"
          max-height="650px"
          style="width: 100%">
          <el-table-column width="120px" prop="columnName" label="字段名称"/>
          <el-table-column
            prop="key"
            width="150px"
            label="字段别名">
            <template slot-scope="scope">
              <el-input v-model="scope.row.columnComment" placeholder="请输入列别名"/>
            </template>
          </el-table-column>
          <el-table-column
            prop="isUse"
            width="50px"
            label="是否使用">
            <template slot-scope="scope">
              <el-checkbox :true-label="1" :false-label="0" v-model="scope.row.isUse"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column
            prop="isQuery"
            width="50px"
            label="是否查询">
            <template slot-scope="scope">
              <el-checkbox :true-label="1" :false-label="0" v-model="scope.row.isQuery"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column
            prop="key"
            width="150px"
            label="条件描述">
            <template slot-scope="scope">
              <el-input v-model="scope.row.ucName" placeholder="请输入条件描述"/>
            </template>
          </el-table-column>
          <el-table-column label="条件">
            <template slot-scope="scope">
              <el-select v-model="scope.row.ucCon" @change="ucTypeChang(scope.$index,scope.row)">
                <el-option label="=" value="EQ"/>
                <el-option label="!=" value="NE"/>
                <el-option label=">" value="GT"/>
                <el-option label=">=" value="GTE"/>
                <el-option label="<" value="LT"/>
                <el-option label="<=" value="LTE"/>
                <el-option label="LIKE" value="LIKE"/>
                <el-option label="BETWEEN" value="BETWEEN"/>
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="显示类型">
            <template slot-scope="scope">
              <el-select v-model="scope.row.ucType" @change="ucTypeChang(scope.$index,scope.row)">
                <el-option label="文本框" value="input"/>
                <el-option label="日期控件" value="datetime"/>
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="模拟数据">
            <template slot-scope="scope">
              <el-input v-if="scope.row.type == 1" placeholder="请输入模拟数据" v-model="scope.row.ucMock"></el-input>
              <div v-else-if="scope.row.type == 2">
                <el-row :gutter="15">
                  <el-col :span="12">
                    <el-input placeholder="开始值" v-model="scope.row.ucMock.begin"></el-input>
                  </el-col>
                  <el-col :span="12">
                    <el-input placeholder="结束值" v-model="scope.row.ucMock.end"></el-input>
                  </el-col>
                </el-row>
              </div>
              <el-date-picker
                v-else-if="scope.row.type ==3"
                v-model="scope.row.ucMock"
                type="date"
                format="yyyy-MM-dd"
                value-format="yyyy-MM-dd"
                placeholder="选择日期时间">
              </el-date-picker>
              <el-date-picker v-else-if="scope.row.type ==4"
                              v-model="scope.row.ucMock"
                              size="small"
                              style="width: 240px"
                              value-format="yyyy-MM-dd"
                              type="daterange"
                              range-separator="-"
                              start-placeholder="开始日期"
                              end-placeholder="结束日期"
              ></el-date-picker>
            </template>
          </el-table-column>
        </el-table>
      </el-dialog>
    </div>
  </div>
</template>

<script>
import G6 from '@antv/g6'

import {Loading, Checkbox} from '../../elements'
import ToolbarEdit from './toolbar-edit'
import registerEdge from './edge'
import Node from './node'
import Behavior from './behavior'
import config from './config'
import theme from './theme'
import initGraph from './graph'
import utils from './utils'

registerEdge(G6)
Node.register(G6)
Behavior.register(G6)

export default {
  name: 'Topology',
  components: {
    'cc-checkbox': Checkbox,
    'cc-loading': Loading,
    'toolbar-edit': ToolbarEdit
  },
  props: {
    nodeTypeList: {
      type: Array,
      default: () => {
        return []
      }
    },
    relationalMap: {
      type: Array,
      default: () => {
        return {}
      }
    },
    nodeAppConfig: {
      type: Object,
      default: () => {
        return {}
      }
    },
    edgeAppConfig: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  data() {
    return {
      topId: null,
      graphBg: 'default-style',
      toolbarShow: true,
      rightMenuShow: false,
      imgSrc: 'data:image/png;base64, ',
      graphData: {
        nodes: [],
        edges: []
      },
      // 表格Column设置弹窗开关
      tableColumnEditOpen:false,
      loading: false,
      clientWidth: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
      clientHeight: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
      //todo 设置线段的选择 er图中只有一种线
      edgeShapeList: [
        // { guid: "top-line", label: "直线", class: "iconfont icon-flow-line" },
        // { guid: "dice-er-edge", label: "连线", class: "iconfont icon-flow-line" },
        // { guid: "top-brokenline", label: "折线", class: "iconfont icon-flow-broken" },
        // { guid: "top-polyline", label: "多段线", class: "iconfont icon-flow-broken" },
        // { guid: "top-cubic", label: "曲线", class: "iconfont icon-flow-curve" }
      ],
      graph: null,
      grid: null,
      minimap: null,
      //todo 修改进去时页面状态
      graphMode: 'edit',
      //todo 默认使用连线
      currentEdgeShape: {
        guid: 'top-cubic',
        label: '曲线'
      },
      currentFocus: 'canvas',
      selectedNode: null,
      selectedNodeParams: {
        label: '',
        appConfig: this.nodeAppConfig
      },
      selectedNodeParamsTimeout: null,
      selectedEdge: null,
      selectedEdgeParams: {
        label: '',
        appConfig: this.edgeAppConfig
      },
      selectedEdgeParamsTimeout: null,
      selectedGroupId: null,
      zoomValue: 1,
      nodesInClipboard: [],
      historyIndex: 0,
      undoCount: 0,
      onresizeTimeout: null,
      pasteCount: 0
    }
  },
  computed: {
    disableUndo: function () {
      return this.historyIndex === 0 || this.historyIndex - (this.undoCount + 1) < 0
    },
    disableRedo: function () {
      return this.historyIndex === 0 || this.historyIndex === 10 || this.undoCount < 1
    },
    disableCopy: function () {
      return this.selectedNodes.length === 0
    },
    disablePaste: function () {
      return this.nodesInClipboard.length === 0
    },
    disableDelete: function () {
      return this.selectedNodes.length === 0 && this.selectedEdges.length === 0
    },
    selectedNodes: function () {
      let self = this
      let graph = self.graph
      if (graph && !graph.destroyed) {
        return graph.findAllByState('node', 'selected')
      } else {
        return []
      }
    },
    selectedEdges: function () {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        return graph.findAllByState('edge', 'selected')
      } else {
        return []
      }
    }
  },
  watch: {
    layoutType() {
      this.initTopo(this.graphData)
    },
    selectedNodeParams: {
      deep: true,
      handler: function (newVal, oldVal) {
        let selectedNodeModel = this.selectedNode.getModel()
        if (utils.isObjectValueEqual(selectedNodeModel.appConfig, newVal.appConfig) && selectedNodeModel.label === newVal.label) {
          return
        }
        // 实时监听input值的变化，停止输入300ms后执行update，而不是时时update
        clearTimeout(this.selectedNodeParamsTimeout)
        this.selectedNodeParamsTimeout = setTimeout(() => {
          selectedNodeModel.label = newVal.label
          selectedNodeModel.appConfig = newVal.appConfig
          selectedNodeModel.columns = newVal.columns
          try {
            this.selectedNode.update(selectedNodeModel)
          } catch (e) {

          }

        }, 300)
      }
    },
    selectedEdgeParams: {
      deep: true,
      handler: function (newVal, oldVal) {
        let selectedEdgeModel = this.selectedEdge.getModel()
        if (utils.isObjectValueEqual(selectedEdgeModel.appConfig, newVal.appConfig) && selectedEdgeModel.label === newVal.label) {
          return
        }
        // 实时监听input值的变化，停止输入300ms后执行update，而不是时时update
        clearTimeout(this.selectedEdgeParamsTimeout)
        this.selectedEdgeParamsTimeout = setTimeout(() => {
          let selectedEdgeModel = this.selectedEdge.getModel()
          selectedEdgeModel.label = newVal.label
          selectedEdgeModel.appConfig = newVal.appConfig
          // selectedEdgeModel.labelCfg.position = 'middle'
          try {
            this.selectedEdge.update(selectedEdgeModel)
          } catch (e) {
          }
          console.log(selectedEdgeModel)
        }, 300)
      }
    }
  },
  created() {
  },
  mounted() {
    Behavior.obj.dragAddEdge.sendThis(this)
    Behavior.obj.dragEventEdit.sendThis(this)
    Behavior.obj.keyupEventEdit.sendThis(this)
    Behavior.obj.hoverEventEdit.sendThis(this)
    Behavior.obj.clickErNode.sendThis(this)
    Behavior.obj.clickErEdge.sendThis(this)

    this.clearHistoryData()
    // this.initTopo(this.graphData)
    // this.autoZoomHandler()
    window.onresize = () => {
      return (() => {
        this.onresizeHandler()
      })()
    }
  },
  beforeRouteUpdate(to, from, next) {
    this.clearHistoryData()
    next()
  },
  beforeRouteLeave(to, from, next) {
    this.clearHistoryData()
    next()
  },
  beforeDestroy() {
    this.clearHistoryData()
  },
  methods: {
    //todo
    ucTypeChang(index, row) {
      if (row.ucType == 'input' && row.ucCon != 'BETWEEN') {
        this.selectedNodeParams.columns[index].type = 1
        if (typeof (this.selectedNodeParams.columns[index].ucMock) != 'string') {
          this.selectedNodeParams.columns[index].ucMock = ''
        }
      } else if (row.ucType == 'input' && row.ucCon == 'BETWEEN') {
        this.selectedNodeParams.columns[index].type = 2
        this.selectedNodeParams.columns[index].ucMock = {begin: '', end: ''}
      } else if (row.ucType == 'datetime' && row.ucCon != 'BETWEEN') {
        this.selectedNodeParams.columns[index].type = 3
        this.selectedNodeParams.columns[index].ucMock = ''
      } else if (row.ucType == 'datetime' && row.ucCon == 'BETWEEN') {
        this.selectedNodeParams.columns[index].type = 4
        this.selectedNodeParams.columns[index].ucMock = []
      }
    },


    closeEditColumns() {
      this.tableColumnEditOpen = false
    },
    // 修改字段信息
    editColumns() {
      this.tableColumnEditOpen = true

    },
    // 连线的三个选择框触发实践
    tableCommentChang(e) {
      if (this.selectedEdgeParams.relationalItem.mainKey !== e) {
        this.selectedEdgeParams.appConfig.tableComment = null
        this.msgError("主表关联字段为:" + this.selectedEdgeParams.relationalItem.mainKey)
      }
    },
    // 关联关系判定
    associatedChange(e) {
      this.selectedEdgeParams.label = null
      let type = this.selectedEdgeParams.relationalItem.type
      let start = true
      if (e === 'left') {
        console.log('left')
        if (type.indexOf('1') === -1 && type.indexOf('2') === -1) {
          this.msgError("查询方式有误")
          start = false
        }
      } else if (e === 'right') {
        console.log(type.indexOf('3'))
        if (type.indexOf('3') === -1) {
          this.msgError("查询方式有误")
          start = false
        }
      } else if (e === 'inner') {
        console.log(type.indexOf('1'))
        if (type.indexOf('1') === -1) {
          this.msgError("查询方式有误")
          start = false
        }
      }
      if (start) {
        this.selectedEdgeParams.label = this.selectedEdgeParams.appConfig.associated + ' join'
      } else {
        this.selectedEdgeParams.appConfig.associated = null
      }
    },
    relCommentChange(e) {
      if (this.selectedEdgeParams.relationalItem.childKey !== e) {
        this.selectedEdgeParams.appConfig.relComment = null
        this.msgError("字表关联字段为:" + this.selectedEdgeParams.relationalItem.mainKey)
      }
    },

    openFullScreenLoading() {
      this.loading = true
    },
    closeFullScreenLoading() {
      let self = this
      self.$nextTick(() => {
        self.loading = false
      })
    },
    dragstartHandler(event, nodeType) {
      event.dataTransfer.setData('text', JSON.stringify(nodeType))
    },
    dragenterHandler(event) {
      event.preventDefault()
    },
    dragoverHandler(event) {
      event.preventDefault()
    },
    dropHandler(event) {
      let nodeTypeStr = event.dataTransfer.getData('text')
      let nodeType = JSON.parse(nodeTypeStr)
      let clientX = event.clientX
      let clientY = event.clientY
      this.addNode(clientX, clientY, nodeType)
    },
    dragendHandler() {
    },
    initTopo(graphData) {
      let self = this
      if (self.graph) {
        self.graph.destroy()
      }
      // 【预览模式】 - 封装的节点展开/收缩交互
      G6.registerBehavior('my-collapse-expand', {
        getEvents() {
          return {
            'node:click': 'onNodeClick'
          }
        },
        onNodeClick(event) {
          let clickNode = event.item
          this.sourceNodeIds = [clickNode._cfg.id]
          if (clickNode.hasState('collapse')) {
            // 节点已收缩, 需要展开
            let visible = true
            this.collapseOrExpand(clickNode, visible)
            clickNode.clearStates('collapse')
          } else {
            // 节点未收缩, 需要收缩
            let visible = false
            this.collapseOrExpand(clickNode, visible)
            clickNode.setState('collapse', true)
          }
          self.graph.paint()
        },
        collapseOrExpand(sourceNode, visible) {
          let outEdges = sourceNode.getOutEdges()
          for (let i = 0; i < outEdges.length; i++) {
            let targetNode = outEdges[i].getTarget()
            let targetNodeId = targetNode._cfg.id
            if (!this.sourceNodeIds.includes(targetNodeId)) {
              targetNode.changeVisibility(visible)
              // 如果一个节点隐藏/显示了，那么它关联的所有边都隐藏
              let relationEdges = targetNode.getEdges()
              for (let i = 0; i < relationEdges.length; i++) {
                relationEdges[i].changeVisibility(visible)
              }
              this.sourceNodeIds.push(targetNodeId)
              // 递归, 该节点的下属节点继续隐藏
              if (targetNode.getOutEdges().length > 0) {
                this.collapseOrExpand(targetNode, visible)
              }
            }
          }
        }
      })

      // 图画布的定义
      let graphContainer = self.$refs.graphContainer
      let graphWidth = graphContainer.clientWidth
      let graphHeight = graphContainer.clientHeight
      // Plugins
      let plugins = []
      let modes = {
        default: [
          'drag-canvas',
          'drag-node',
          {
            type: 'click-select',
            trigger: 'ctrl',
            multiple: true
          }
        ],
        //添加预览状态下的Behavior
        preview: [
          'drag-canvas',
          'zoom-canvas',
          // "drag-node",
          // 'click-select',
          {
            type: 'tooltip',
            formatText(model) {
              return model.description || model.label
            }
          },
          {
            type: 'edge-tooltip',
            formatText(model) {
              return model.description || 'source:' + self.graph.findById(model.source).getModel().label + ' target:' + self.graph.findById(model.target).getModel().label
            }
          }
          // 自定义Behavior
          // 'my-collapse-expand'
        ],
        //添加编辑模式下的Behavior
        edit: [
          'drag-node',
          'drag-canvas',
          {
            type: 'click-select',
            // trigger: 'ctrl', // TODO... 疑似官方bug，ctrl无效
            multiple: true
          },
          // 多选 ctrl＋鼠标左键
          {
            type: 'brush-select',
            trigger: 'ctrl',
            includeEdges: false
          },
          'right-click-node',
          'right-click-edge',
          // Group Behavior
          // 'drag-group',
          // 'collapse-expand-group',
          // 'drag-node-with-group',
          // 自定义Behavior
          //悬浮交互
          'hover-event-edit',
          //键盘事件的交互
          'keyup-event-edit',
          //通过拖拽节点上的锚点添加连线
          'drag-add-edge',
          //鼠标拖动节点的交互
          'drag-event-edit',
          //图点击事件
          'click-er-node',
          'click-er-edge'
        ],
        addEdge: [
          'drag-canvas'
          // 自定义Behavior
          // "click-add-edge"
        ],
        multiSelect: [
          {
            type: 'brush-select',
            trigger: 'drag',
            onSelect() {
              this.graph.setMode('edit')
              window.document.getElementById('multi-select').style.backgroundColor = 'transparent'
            }
          }
        ]
      }
      if (self.graphMode === 'edit') {
        let navigator = self.$refs.navigator
        let minimapWidth = navigator ? navigator.clientWidth : 160
        let minimapHeight = navigator ? navigator.clientHeight : 120
        minimapWidth = minimapWidth > 160 ? minimapWidth : 120
        minimapHeight = minimapHeight > 160 ? minimapHeight : 120
        let minimap = new G6.Minimap({
          size: [minimapWidth, minimapHeight],
          container: 'g6-minimap',
          type: 'default'
        })
        plugins.push(minimap)
      }
      /* 生成图 */
      if (self.layoutType === 'force') {
        /* 力导布局: force-layout */
        self.graph = initGraph.forceLayoutGraph(G6, {
          plugins: plugins,
          container: 'mount-topology',
          width: graphWidth,
          height: graphHeight,
          modes: modes,
          graphData: graphData
        })
      } else {
        /* 默认布局: 自由布局 */
        self.graph = initGraph.commonGraph(G6, {
          plugins: plugins,
          container: 'mount-topology',
          width: graphWidth,
          height: graphHeight,
          modes: modes,
          graphData: graphData
        })
      }
      self.graph.$C = config
      self.graph.setMode(self.graphMode)
      self.graph.refresh()
      self.autoZoomHandler()
    },
    /* Deprecated method: 早期用d3-force手写的自动布局 */
    autoLayoutHandler() {
      let self = this
      // 数据获取
      let graphData = self.getGraphData()
      if (self.graph && !self.graph.destroyed) {
        self.graph.destroy()
      }
      let graphContainer = self.$refs.graphContainer
      let graphWidth = graphContainer.clientWidth
      let graphHeight = graphContainer.clientHeight
      self.openFullScreenLoading()
      let promise = new Promise((resolve) => {
        initGraph.forceLayoutGraph(resolve, G6, {
          container: 'mount-topology',
          width: graphWidth,
          height: graphHeight,
          graphData: graphData
        })
      })
      promise.then(graph => {
        self.graph = graph
        self.closeFullScreenLoading()
        self.initTopo(self.getGraphData())
      })
    },
    /* 自动布局 */
    forceLayoutHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        this.openFullScreenLoading()
        graph.updateLayout({
          type: 'force',
          center: [200, 200],
          preventOverlap: true,
          linkDistance: 150,
          nodeStrength: -200,
          onLayoutEnd: () => {
            this.closeFullScreenLoading()
          }
        })
      }
    },
    /* 环形布局 - 相关逻辑未完成 */
    circularLayoutHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.updateLayout({
          type: 'circular',
          center: [200, 200],
          radius: 200,
          clockwise: false,
          ordering: 'topology',
          angleRatio: 1
        })
      }
    },
    /* 辐射状布局 - 相关逻辑未完成，且拓扑图不需要 */
    radialLayoutHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.updateLayout({
          type: 'circular',
          center: [200, 200],
          linkDistance: 50,
          maxIteration: 1000,
          unitRadius: 100,
          preventOverlap: true
        })
      }
    },
    changeEdgeShapeHandler(edgeShape) {
      this.graph.$C.edge.type = edgeShape

      // this.currentEdgeShape = edgeShape;
      //
      // this.graph.$C.edge.type = this.currentEdgeShape["guid"];
    },
    // 返回
    reBack() {
      this.$router.replace('/tool/er-top')
    },
    undoHandler() {
      if (this.historyIndex > 0 && this.historyIndex - (this.undoCount + 1) >= 0) {
        this.undoCount += 1
        let key = `graph_history_${this.historyIndex - this.undoCount}`
        let historyData = this.getHistoryData(key)
        this.changeGraphData(JSON.parse(historyData))
        this.refreshGraph()
      }
    },
    redoHandler() {
      if (this.undoCount > 0) {
        let key = `graph_history_${this.historyIndex - this.undoCount + 1}`
        let historyData = this.getHistoryData(key)
        this.changeGraphData(JSON.parse(historyData))
        this.undoCount -= 1
        this.refreshGraph()
      }
    },
    copyHandler() {
      this.nodesInClipboard = this.selectedNodes
      this.pasteCount = 0
    },

    pasteHandler() {
      this.pasteCount += 1 // 连续paste次数统计
      let graph = this.graph
      let nodesInClipboard = this.nodesInClipboard
      if (graph && !graph.destroyed && nodesInClipboard.length > 0) {

        // ************** 记录【粘贴】前的数据状态 start **************
        let historyData = JSON.stringify(graph.save())
        let key = `graph_history_${this.historyIndex}`
        this.addHistoryData(key, historyData)
        // ************** 记录【粘贴】前的数据状态 end **************

        // 开始粘贴
        for (let i = 0; i < nodesInClipboard.length; i++) {
          let node = nodesInClipboard[i]
          let model = node.getModel()
          let newModel = {
            ...model,
            id: utils.generateUUID(),
            x: model.x + 10 * this.pasteCount,
            y: model.y + 10 * this.pasteCount
          }
          graph.addItem('node', newModel)
        }

        // ************** 记录【粘贴】后的数据状态 start **************
        // 如果当前点过【撤销】了，【粘贴】后将取消【重做】功能
        // 重置undoCount，【粘贴】后的数据状态给(当前所在historyIndex + 1)，且清空这个时间点之后的记录
        if (this.undoCount > 0) {
          this.historyIndex = this.historyIndex - this.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
          for (let i = 1; i <= this.undoCount; i++) {
            let key = `graph_history_${this.historyIndex + i}`
            this.removeHistoryData(key)
          }
          this.undoCount = 0
        }
        // 存储【粘贴】后的数据状态
        this.historyIndex += 1
        key = `graph_history_${this.historyIndex}`
        let currentData = JSON.stringify(graph.save())
        this.addHistoryData(key, currentData)
        // ************** 记录【粘贴】后的数据状态 end **************
      }
    },
    deleteHandler() {
      let graph = this.graph
      let selectedNodes = graph.findAllByState('node', 'selected')
      let selectedEdges = graph.findAllByState('edge', 'selected')
      if (this.selectedNodes.length > 0 || this.selectedEdges.length > 0) {

        // ************** 记录【删除】前的数据状态 start **************
        let historyData = JSON.stringify(graph.save())
        let key = `graph_history_${this.historyIndex}`
        this.addHistoryData(key, historyData)
        // ************** 记录【删除】前的数据状态 end **************

        // 开始删除
        for (let i = 0; i < selectedNodes.length; i++) {
          graph.removeItem(selectedNodes[i])
        }
        for (let i = 0; i < selectedEdges.length; i++) {
          graph.removeItem(selectedEdges[i])
        }

        // ************** 记录【删除】后的数据状态 start **************
        // 如果当前点过【撤销】了，拖拽节点后将取消【重做】功能
        // 重置undoCount，【删除】后的数据状态给(当前所在historyIndex + 1)，且清空这个时间点之后的记录
        if (this.undoCount > 0) {
          this.historyIndex = this.historyIndex - this.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
          for (let i = 1; i <= this.undoCount; i++) {
            let key = `graph_history_${this.historyIndex + i}`
            this.removeHistoryData(key)
          }
          this.undoCount = 0
        }
        // 记录【删除】后的数据状态
        this.historyIndex += 1
        key = `graph_history_${this.historyIndex}`
        let currentData = JSON.stringify(graph.save())
        this.addHistoryData(key, currentData)
        // ************** 记录【删除】后的数据状态 end **************
      }
    },
    zoomInHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.zoom(1.2)
      }
    },
    zoomOutHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.zoom(0.8)
      }
    },
    autoZoomHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed && graph.save().nodes.length > 0) {
        graph.fitView(10)
        // this.zoomValue = graph.getZoom();  // TODO...怎么处理changeZoomHandler的二次触发问题
      }
    },
    resetZoomHandler() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.zoomTo(1)
      }
    },
    multiSelectHandler(event) {
      event.target.style.backgroundColor = '#EEEEEE'
      this.graph.setMode('multiSelect')
    },
    enableGridHandler(enableGrid) {
      if (enableGrid) {
        this.grid = new G6.Grid()
        this.graph.addPlugin(this.grid)
      } else {
        this.graph.removePlugin(this.grid)
      }
    },
    enableMinimapHandler(enableMinimap) {
      if (enableMinimap) {
        this.minimap = new G6.Minimap({
          size: [200, 120],
          type: 'default',
          className: 'g6-minimap-preview'
        })
        this.graph.addPlugin(this.minimap)
      } else {
        this.graph.removePlugin(this.minimap)
      }
    },
    // 右键菜单
    alignHandler(coordinate) {
      let graph = this.graph
      if (this.selectedNodes.length > 1 && this.selectedNode) {

        // ************** 记录【节点对齐】前的数据状态 start **************
        let historyData = JSON.stringify(graph.save())
        let key = `graph_history_${this.historyIndex}`
        this.addHistoryData(key, historyData)
        // ************** 记录【节点对齐】前的数据状态 end **************

        // 开始节点对齐
        let cfg = coordinate === 'horizontal' ? {y: this.selectedNode.getModel().y} : {x: this.selectedNode.getModel().x}
        for (let i = 0, len = this.selectedNodes.length; i < len; i++) {
          this.selectedNodes[i].updatePosition(cfg)
        }
        graph.refresh()

        // ************** 记录【节点对齐】后的数据状态 start **************
        // 如果当前点过【撤销】了，节点对齐后将取消【重做】功能
        // 重置undoCount，【节点对齐】后的数据状态给(当前所在historyIndex + 1)，且清空这个时间点之后的记录
        if (this.undoCount > 0) {
          this.historyIndex = this.historyIndex - this.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
          for (let i = 1; i <= this.undoCount; i++) {
            let key = `graph_history_${this.historyIndex + i}`
            this.removeHistoryData(key)
          }
          this.undoCount = 0
        }
        // 记录【节点对齐】后的数据状态
        this.historyIndex += 1
        key = `graph_history_${this.historyIndex}`
        let currentData = JSON.stringify(graph.save())
        this.addHistoryData(key, currentData)
        // ************** 记录【节点对齐】后的数据状态 end **************
      }
      this.rightMenuShow = false
    },
    addGroup() {
      let graph = this.graph
      if (graph && !graph.destroyed && this.selectedNodes.length > 1 && this.selectedNode) {

        // ************** 记录【添加分组】前的数据状态 start **************
        let historyData = JSON.stringify(graph.save())
        let key = `graph_history_${this.historyIndex}`
        this.addHistoryData(key, historyData)
        // ************** 记录【添加分组】前的数据状态 end **************

        let nodeIds = this.selectedNodes.map(item => {
          return item._cfg.id
        })
        // 创建分组
        let group = graph.addItem('group', {
          groupId: utils.generateUUID(),
          nodes: nodeIds,
          type: 'circle ',
          zIndex: 2,
          title: {
            text: '分组',
            stroke: '#87e8de',
            fill: '#87e8de',
            offsetX: 2,
            offsetY: 2
          }
        })

        // ************** 记录【添加分组】后的数据状态 start **************
        if (group) {
          // 如果当前点过【撤销】了，【添加分组】后将取消【重做】功能
          // 重置undoCount，【添加分组】后的数据状态给(当前所在historyIndex + 1)，且清空这个时间点之后的记录
          if (this.undoCount > 0) {
            this.historyIndex = this.historyIndex - this.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
            for (let i = 1; i <= this.undoCount; i++) {
              let key = `graph_history_${this.historyIndex + i}`
              this.removeHistoryData(key)
            }
            this.undoCount = 0
          }
          // 记录【添加分组】后的数据状态
          this.historyIndex += 1
          let key = `graph_history_${this.historyIndex}`
          let currentData = JSON.stringify(graph.save())
          this.addHistoryData(key, currentData)
        }
        // ************** 记录【添加分组】后的数据状态 end **************
        this.rightMenuShow = false
      }
    },
    removeGroup() {
      let graph = this.graph
      if (this.selectedGroupId) {

        // ************** 记录【删除】前的数据状态 start **************
        let historyData = JSON.stringify(graph.save())
        let key = `graph_history_${this.historyIndex}`
        this.addHistoryData(key, historyData)
        // ************** 记录【删除】前的数据状态 end **************

        // 开始删除
        graph.removeItem(this.selectedGroupId)

        // ************** 记录【删除】后的数据状态 start **************
        // 如果当前点过【撤销】了，删除分组后将取消【重做】功能
        // 重置undoCount，【删除】后的数据状态给(当前所在historyIndex + 1)，且清空这个时间点之后的记录
        if (this.undoCount > 0) {
          this.historyIndex = this.historyIndex - this.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
          for (let i = 1; i <= this.undoCount; i++) {
            let key = `graph_history_${this.historyIndex + i}`
            this.removeHistoryData(key)
          }
          this.undoCount = 0
        }
        // 记录【删除】后的数据状态
        this.historyIndex += 1
        key = `graph_history_${this.historyIndex}`
        let currentData = JSON.stringify(graph.save())
        this.addHistoryData(key, currentData)
        // ************** 记录【删除】后的数据状态 end **************
      }
    },
    addNode(clientX, clientY, nodeType) {
      let graph = this.graph
      if (graph && !graph.destroyed) {

        // ************** 记录【添加节点】前的数据状态 start **************
        let historyData = JSON.stringify(graph.save())
        let key = `graph_history_${this.historyIndex}`
        this.addHistoryData(key, historyData)
        // ************** 记录【添加节点】前的数据状态 end **************
        let graphData = graph.save();
        let relational = []
        let starts = false
        if (graphData.nodes.length > 0) {
          for (let i = 0; i < graphData.nodes.length; i++) {
            relational.push(graphData.nodes[i].tableId)
          }
          // todo 检查关联关系
          for (let i = 0; i < relational.length; i++) {
            this.relationalMap.map(item => {
              if (item.mainId === relational[i] && item.childId === nodeType.id) {
                starts = true
              }
            })
            this.relationalMap.map(item => {
              if (item.childId === relational[i] && item.mainId === nodeType.id) {
                starts = true
              }
            })
          }
        } else {
          starts = true
        }
        // 开始添加
        if (starts) {
          let droppoint = graph.getPointByClient(clientX, clientY)
          let columns = []
          for (let i = 0; i < nodeType.topSearchColumnList.length; i++) {
            let item = nodeType.topSearchColumnList[i]
            item.isQuery = 0
            item.ucCon = ''
            item.ucType = ''
            item.type = 1
            item.ucMock = ''
            item.ucName = item.columnComment
            columns.push(item)
          }

          let node = graph.addItem('node', {
            id: utils.generateUUID(),
            x: (droppoint.x) - (125),
            y: (droppoint.y) - (158),
            label: nodeType.tableName + ':' + nodeType.tableComment,
            labelCfg: {
              position: "bottom"
            },
            //todo 设置拖拽的属性
            tableId: nodeType.id,
            table: nodeType.tableName,
            type: 'dice-er-box',
            columns: columns,
            size: [300, 500],
            width: 250,
            height: 316,
            anchorPoints: [
              [0.5, 0], // top
              [1, 0.5], // right
              [0.5, 1], // bottom
              [0, 0.5] // left
            ],
            // 自定义属性
            appState: {
              alert: false
            }
          })

          // ************** 记录【添加节点】后的数据状态 start **************
          if (node) {
            // 如果当前点过【撤销】了，【添加节点】后将取消【重做】功能
            // 重置undoCount，【添加节点】后的数据状态给(当前所在historyIndex + 1)，且清空这个时间点之后的记录
            if (this.undoCount > 0) {
              this.historyIndex = this.historyIndex - this.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
              for (let i = 1; i <= this.undoCount; i++) {
                let key = `graph_history_${this.historyIndex + i}`
                this.removeHistoryData(key)
              }
              this.undoCount = 0
            }
            // 记录【添加节点】后的数据状态
            this.historyIndex += 1
            let key = `graph_history_${this.historyIndex}`
            let currentData = JSON.stringify(graph.save())
            this.addHistoryData(key, currentData)
          }
          // ************** 记录【添加节点】后的数据状态 end **************
        } else {
          this.msgError("此表无关联关系!")
        }
      }
    },
    unselectAllNodes() {
    },
    addHistoryData(key, value) {
      try {
        sessionStorage.setItem(key, value)
      } catch (oException) {
        if (oException.name === 'QuotaExceededError') {
          console.warn('已经超出本地存储限定大小，清空历史记录！')
          // 可进行超出限定大小之后的操作，如下面可以先清除记录，再次保存
          // sessionStorage.clear()
          this.clearHistoryData()
          this.historyIndex = 0
          this.undoCount = 0
          sessionStorage.setItem(key, value)
        }
      }
    },
    getHistoryData(key) {
      return sessionStorage.getItem(key)
    },
    removeHistoryData(key) {
      sessionStorage.removeItem(key)
    },
    clearHistoryData() {
      let keys = Object.keys(sessionStorage)
      keys.forEach(key => {
        if (key.startsWith('graph_history')) {
          sessionStorage.removeItem(key)
        }
      })
    },
    onresizeHandler() {
      // 实时监听窗口大小变化
      let self = this
      clearTimeout(this.onresizeTimeout)
      this.onresizeTimeout = setTimeout(() => {
        let graph = self.graph
        if (graph && !graph.destroyed) {
          let graphContainer = self.$refs.graphContainer
          let graphWidth = graphContainer.clientWidth
          let graphHeight = graphContainer.clientHeight
          graph.changeSize(graphWidth, graphHeight)
        }
      }, 1000)
    },
    /* 暴露给外部的接口 */
    refreshGraph() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.refresh()
      }
    },
    getGraphData() {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        return graph.save()
      } else {
        return {nodes: [], edges: []}
      }
    },
    changeGraphData(data) {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        graph.changeData(data)
      }
    },
    changeGraphSize(graphWidth = 0, graphHeight = 0) {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        let graphContainer = this.$refs.graphContainer
        graphWidth = graphWidth || graphContainer.clientWidth
        graphHeight = graphHeight || graphContainer.clientHeight
        graph.changeSize(graphWidth, graphHeight)
      }
    },
    changeGraphMode(graphData, graphMode) {
      this.graphMode = graphMode
      this.$nextTick(() => {
        this.initTopo(graphData)
        this.autoZoomHandler()
      })
    },
    changeGraphTheme(themeValue) {
      let graph = this.graph
      if (graph && !graph.destroyed) {
        let nodes = graph.getNodes()
        let edges = graph.getEdges()
        switch (themeValue) {
          case 'darkStyle':
            this.graphBg = 'dark-style'
            graph.$T = theme.darkStyle
            for (let i = 0, len = edges.length; i < len; i++) {
              let edge = edges[i]
              let edgeModel = edge.getModel()
              edgeModel.style = graph.$T.edgeStyle.default
              edge.update(edgeModel)
            }
            for (let i = 0, len = nodes.length; i < len; i++) {
              let node = nodes[i]
              let nodeModel = node.getModel()
              nodeModel.labelCfg = graph.$T.nodeLabelCfg
              node.update(nodeModel)
            }
            graph.paint()
            break
          case 'officeStyle':
            this.graphBg = 'office-style'
            graph.$T = theme.officeStyle
            for (let i = 0, len = edges.length; i < len; i++) {
              let edge = edges[i]
              let edgeModel = edge.getModel()
              edgeModel.style = graph.$T.edgeStyle.default
              edge.update(edgeModel)
            }
            for (let i = 0, len = nodes.length; i < len; i++) {
              let node = nodes[i]
              let nodeModel = node.getModel()
              nodeModel.labelCfg = graph.$T.nodeLabelCfg
              node.update(nodeModel)
            }
            graph.paint()
            break
          default:
            this.graphBg = 'default-style'
            graph.$T = theme.defaultStyle
            for (let i = 0, len = edges.length; i < len; i++) {
              let edge = edges[i]
              let edgeModel = edge.getModel()
              edgeModel.style = graph.$T.edgeStyle.default
              edge.update(edgeModel)
            }
            for (let i = 0, len = nodes.length; i < len; i++) {
              let node = nodes[i]
              let nodeModel = node.getModel()
              nodeModel.labelCfg = graph.$T.nodeLabelCfg
              node.update(nodeModel)
            }
            graph.paint()
        }
      }
    },
    /* 子组件向父组件传值 */
    autoRefreshHandler(interval) {
      this.$emit('doAutoRefresh', interval)
    },
    manualRefreshHandler() {
      this.$emit('doManualRefresh')
    },
    changeModeHandler(graphMode) {
      this.changeGraphTheme('defaultStyle')
      this.$emit('doChangeMode', graphMode)
    },
    // todo 预览
    searchPreview() {
      let graphData = this.getGraphData()
      this.$emit('doPreview', graphData)
    },
    // 上线
    searchOnLine() {
      this.$emit('doOnLine')
    },
    // 保存函数
    saveDataHandler() {
      let graphData = this.getGraphData()
      this.$emit('doSaveData', graphData)
    }
  }
}
</script>
<style lang="scss" scoped>
@import '../../assets/iconfont/iconfont.css';

.top-img {
  overflow: hidden;
  /*height: 0px;*/
  transition: all 0.8s;
}

*[draggable = true] {
  -khtml-user-drag: element;
}

.topology {
  height: 100%;
  margin: 0;
  padding: 0;
}

.container {
  /*height: calc(100% - 55px);*/
  height: 100%;
  /*width: calc(100% - 5px);*/

  .left {
    float: left;
    display: inline-block;
    width: 12%;
    height: 100%;
    min-height: calc(600px - 55px);
    padding-top: 0;
    color: #333;
    font-size: 12px;
    text-align: center;
    background-color: #F7F9FB;
    border-right: 1px solid #E6E9ED;

    .node-type {
      display: inline-block;
      width: 55px;
      height: 55px;
      text-align: center;
      vertical-align: middle;
      border: 1px solid transparent;
      margin: 3px 3px 20px;

      img {
        height: 48px;
        width: 48px;
        padding-top: 5px;
      }

      span {
        font-size: 14px;
        display: block;
        line-height: 3px;
      }
    }

    .node-type:hover {
      cursor: move;
      border: 1px solid #ccc;
    }
  }

  .center {
    display: inline-block;
    height: calc(100% - 55px);
  }

  .input-item {
    margin-top: 8px;

    .input-select {
      margin-top: 8px;
    }
  }

  .right {
    float: right;
    display: inline-block;
    width: 12%;
    height: 100%;
    min-height: calc(600px - 55px);
    padding-top: 0;
    color: #333;
    font-size: 12px;
    text-align: left;
    background-color: #F7F9FB;
    border-left: 1px solid #E6E9ED;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;

    .detail-pannel {
      height: 60%;
      min-height: 360px;

      .block-container {
        padding: 16px 8px;

        .params-input {
          -webkit-appearance: none;
          background-color: #FFF;
          background-image: none;
          border-radius: 4px;
          border: 1px solid #DCDFE6;
          box-sizing: border-box;
          color: #606266;
          display: inline-block;
          outline: 0;
          transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
          padding: 0 5px;
          width: 100%;
          height: 25px;
          line-height: 25px;
          font-size: 12px;
        }
      }
    }

    .navigator-pannel {
      height: 40%;
      min-height: 135px;

      .navigator {
        padding: 1px;
        height: 55%;
        min-height: 135px;
      }
    }

    .pannel-title {
      padding-left: 12px;
      height: 32px;
      color: #000;
      line-height: 32px;
      background-color: #EBEEF2;
      border-top: 1px solid #DCE3E8;
      border-bottom: 1px solid #DCE3E8;
    }
  }

  .graph-container {
    height: 100%;
    min-height: calc(600px - 55px);

    #mount-topology {
      width: 100%;
      height: 100%;
    }
  }
}

.right-menu {
  position: absolute;
  padding: 10px 5px;
  list-style: none;
  background-color: #fff;
  opacity: 1;
  border: 1px solid #DCDFE6;
  border-radius: 10px;

  li {
    padding: 5px;
    list-style-type: none;
    cursor: pointer;
  }

  li:hover {
    color: #409EFF;
  }
}

// 背景主题
.default-style {
  background: #fff;
}

.dark-style {
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%), radial-gradient(at top center, rgba(255, 255, 255, 0.40) 0%, rgba(0, 0, 0, 0.40) 120%) #989898;
  background-blend-mode: multiply, multiply;
}

.office-style {
  background-image: linear-gradient(to right, #fef6de 0%, #f3ebd3 100%);
}
</style>

<style lang="scss">
.g6-tooltip {
  padding: 10px 6px;
  color: #444;
  background-color: rgba(255, 255, 255, 0.9);
  border: 1px solid #e2e2e2;
  border-radius: 4px;
}

// 预览模式自动生成的节点
.graph-container {
  #mount-topology .g6-minimap-preview {
    position: absolute;
    right: 10px;
    bottom: 60px;
    border: 1px solid #e2e2e2;
  }
}
</style>
