<template>
  <div class="fullBox white pa-3 relative" ref="boxRef">
    <div ref="graphRef" v-loading="loading" class="fullBox"></div>
    <div class="btnBox pa-1">
      <el-upload class="el-button pa-0" action="#" :show-file-list="false" :http-request="onImport">
        <m-button type="orange" icon="el-icon-upload2" size="small" :loading="importLoading">导入</m-button>
      </el-upload>
      <m-button type="orange" icon="el-icon-download" size="small" :loading="exportLoading" @click="onExport">导出</m-button>
      <m-button type="orange" icon="el-icon-download" size="small" :loading="downloadLoading" @click="onDownload">模板下载</m-button>
      <m-button type="orange" icon="el-icon-plus" size="small" @click="onAdd">新增机构</m-button>
    </div>
    <div ref="contextMenuRefs" class="contextMenu">
      <el-card shadow="always" :body-style="{padding: 0}">
        <div
          v-for="menu in menus"
          :key="menu.prop"
          class="contextMenuItem pa-3"
          @click="onMenuClick(menu.prop)"
        >
          {{ menu.label }}
        </div>
      </el-card>
    </div>
    <OrganizationEdit ref="organizationEditRefs" @refresh="onRefresh"></OrganizationEdit>
    <OrganizationAdd ref="organizationAddRefs" @refresh="onRefresh"></OrganizationAdd>
  </div>
</template>

<script>
import OrganizationEdit from './organizationEdit.vue'
import OrganizationAdd from './organizationAdd.vue'
import {
  CollapseExpandTree,
  MindMapNode
} from '@/utils/antvG6'
import {
  Graph,
  register,
  NodeEvent,
  CommonEvent,
  CanvasEvent,
  ExtensionCategory
} from '@antv/g6'
import {
  getOrganizationAtlas,
  getOrganizationAtlasEmployee,
  importOrganization,
  exportOrganization,
  downloadOrganization,
  deleteOrganization
} from '@/api/system'
import { mapGetters } from 'vuex'
import {
  upload,
  download
} from '@/utils/elementUploadAndDownload'

const NODE_TYPE = {
  type: 'MindMapNode',
  style: function (d) {
    return {
      fill: '#ff650e',
      size: 15,
      label: true,
      labelFontSize: 14,
      labelLineHeight: 20,
      labelPlacement: 'right',
      labelPadding: [10],
      labelText: d.name,
      labelOffsetX: d.depth === 3 ? 20 : 50,
      labelBackground: true,
      labelBackgroundFill: '#EFF0F0',
      labelBackgroundRadius: 8,
      port: true,
      ports: [{ placement: 'right' }, { placement: 'left' }]
    }
  }
}

export default {
  name: 'organization-structure',
  components: {
    OrganizationEdit,
    OrganizationAdd
  },
  data () {
    return {
      menus: [
        { label: '编辑', prop: 'edit' },
        { label: '删除', prop: 'delete' }
      ],
      nodes: null,
      loading: false,
      importLoading: false,
      exportLoading: false,
      downloadLoading: false,
      graph: null
    }
  },
  computed: {
    ...mapGetters(['organizationTree'])
  },
  async mounted () {
    register(ExtensionCategory.BEHAVIOR, 'collapse-expand-tree', CollapseExpandTree)
    register(ExtensionCategory.NODE, 'MindMapNode', MindMapNode)
    const graphData = await this.onInit()
    this.$nextTick(() => {
      this.initGraph(graphData)
      document.addEventListener('click', this.onClick)
      this.$refs.boxRef.addEventListener('contextmenu', (e) => {
        e.preventDefault()
      })
    })
  },
  beforeDestroy () {
    if (this.graph) {
      this.graph.off()
    }
    document.removeEventListener('click', this.onClick)
    this.$refs.boxRef.removeEventListener('contextmenu', (e) => {
      e.preventDefault()
    })
  },
  methods: {
    onClick () {
      this.$refs.contextMenuRefs.style.display = 'none'
    },
    async initData () {
      await this.$store.dispatch('system/getOrganizationTree')
      const data = await this.onInit()
      return data
    },
    async onRefresh () {
      const data = await this.initData()
      this.renderGraph(data)
    },
    drawGraph (data) {
      this.graph.setData(data)
      this.graph.draw()
    },
    async renderGraph (data) {
      this.graph.setData(data)
      this.graph.render()
    },
    async onInit () {
      this.loading = true
      try {
        const { data } = await getOrganizationAtlas()
        const { nodes, edges } = data
        const graphData = {
          nodes: nodes.map(e => {
            return {
              ...e,
              getChildren: this.getChildren
            }
          }),
          edges
        }
        return graphData
      } catch (error) {
        this.$message.error(error)
        return { nodes: [], edges: [] }
      } finally {
        this.loading = false
      }
    },
    async initGraph (data) {
      this.graph = new Graph({
        container: this.$refs.graphRef,
        width: this.$refs.graphRef.clientWidth,
        height: this.$refs.graphRef.clientHeight,
        data,
        autoFit: 'center',
        autoResize: false,
        enable: false,
        plugins: [
          // 'minimap',
          // 'contextmenu',
          {
            className: 'toolbar',
            position: 'right-top',
            type: 'toolbar',
            getItems: () => [
              { id: 'zoom-in', value: 'zoom-in' },
              { id: 'zoom-out', value: 'zoom-out' },
              { id: 'auto-fit', value: 'auto-fit' }
            ],
            onClick: (value) => {
              const zoom = this.graph.getZoom()
              // 处理按钮点击事件
              if (value === 'zoom-in') {
                if (zoom > 2) {
                  return
                }
                this.graph.zoomTo(zoom + 0.1)
              } else if (value === 'zoom-out') {
                if (zoom < 0.5) {
                  return
                }
                this.graph.zoomTo(zoom - 0.1)
              } else if (value === 'auto-fit') {
                this.graph.fitView()
              }
            }
          }
        ],
        behaviors: [
          'drag-canvas',
          'scroll-canvas',
          'drag-element',
          'collapse-expand-tree'
        ],
        animation: false,
        layout: {
          type: 'compact-box',
          getHeight: function getHeight () {
            return 32
          },
          getWidth: function getWidth () {
            return 32
          },
          getVGap: function getVGap (e) {
            return 8
          },
          getHGap: function getHGap (e) {
            return 150
          },
          radial: false
        },
        node: NODE_TYPE,
        edge: {
          type: 'cubic-horizontal',
          style: function (d) {
            return {
              endArrow: true,
              lineWidth: 1,
              stroke: '#aaa'
            }
          }
        }
      })
      await this.graph.render()
      this.onGraphHandles(this.graph)
      data.nodes.forEach(e => {
        if (e.depth === 1) {
          this.graph.collapseElement(e.id)
        }
      })
      // 关闭之后重新排版计算
      this.graph.render()
    },
    onGraphHandles (graph) {
      const contextMenu = this.$refs.contextMenuRefs
      const { width, height } = this.$refs.boxRef.getBoundingClientRect()
      const { left, top } = this.$refs.boxRef.getBoundingClientRect()
      const content = {
        x: 0,
        y: 0,
        width,
        height
      }
      graph.on(NodeEvent.CONTEXT_MENU, (e) => {
        const { target } = e // 获取被点击节点的 ID
        this.nodes = graph.getNodeData(target.id)
        e.preventDefault()
        const { x, y } = e.client
        content.x = x
        content.y = y
        contextMenu.style.left = x - left + 'px'
        contextMenu.style.top = y - top + 'px'
        contextMenu.style.display = 'block'
      }).on(CommonEvent.WHEEL, () => {
        contextMenu.style.display = 'none'
      }).on(CanvasEvent.CONTEXT_MENU, (e) => {
        contextMenu.style.display = 'none'
      })
    },
    async onDelete (nodes) {
      this.$confirm('是否删除该项', '提示').then(async () => {
        try {
          await deleteOrganization({
            organizationNo: nodes.id
          })
          this.$message.success('删除成功')
          const data = await this.initData()
          this.drawGraph(data)
        } catch (error) {
          this.$message.error(error)
        }
      }).catch(_ => {})
    },
    async getChildren (organizationNo) {
      try {
        const { data } = await getOrganizationAtlasEmployee({ organizationNo })
        return {
          nodes: data.nodes.map(e => {
            const { labelOffsetX, ...obj } = NODE_TYPE
            return {
              id: e.id,
              name: e.text,
              hasChildren: false,
              depth: 3,
              ...obj
            }
          }),
          edges: data.lines.map(e => {
            return {
              source: e.from,
              target: e.to
            }
          })
        }
      } catch (error) {
        this.$message.error(error)
      }
    },
    onMenuClick (prop) {
      if (prop === 'edit') {
        this.editTag(this.nodes)
      }
      if (prop === 'delete') {
        this.onDelete(this.nodes)
      }
    },
    async onImport (response) {
      this.importLoading = true
      await upload(importOrganization, response.file, this.onInit)
      this.importLoading = false
    },
    async editTag (nodes) {
      this.loading = true
      await this.$refs.organizationEditRefs.open(nodes)
      this.loading = false
    },
    onAdd () {
      this.$refs.organizationAddRefs.open()
    },
    async onExport () {
      this.exportLoading = true
      await download(exportOrganization)
      this.exportLoading = false
    },
    async onDownload () {
      this.downloadLoading = true
      await download(downloadOrganization)
      this.downloadLoading = false
    }
  }
}
</script>

<style lang="scss" scoped>
.contextMenu {
  display: none;
  position: absolute;
  width: 200px;
}
.fullBox {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  position: relative;
}
.btnBox {
  position: absolute;
  left: 10px;
  top: 10px;
}

::v-deep .toolbar {
  width: 50px;
  padding: 20px 0;
  border: 1px solid #ccc;
  .g6-toolbar-item {
    width: 100%;
    height: 20px;
    padding: 10px 0;
  }
}

.relative {
  position: relative;
}

.contextMenuItem {
  font-size: 14px;
  color: #333;
  cursor: pointer;
  &:hover {
    color: #FFF;
    background: $theme-color;
  }
}

</style>
