zhengnaiwen_citu hace 6 meses
padre
commit
afaabf3826

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 860 - 6
package-lock.json


+ 1 - 0
package.json

@@ -8,6 +8,7 @@
     "build": "vue-cli-service build --mode production"
   },
   "dependencies": {
+    "@antv/g6": "^5.0.44",
     "@babel/plugin-transform-runtime": "^7.24.6",
     "@babel/polyfill": "^7.12.1",
     "@babel/preset-env": "^7.23.9",

+ 0 - 4
public/index.html

@@ -6,10 +6,6 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title><%= htmlWebpackPlugin.options.title %></title>
-    <!-- 生产环境外配置api地址 -->
-    <% if (process.env.NODE_ENV=== 'production' ) { %>
-      <script src="<%= BASE_URL %>config.js"></script>
-    <% } %>
     <style>
       /* 滚动条样式 */
       ::-webkit-scrollbar {

+ 9 - 0
src/api/system.js

@@ -158,3 +158,12 @@ export function downloadRosterTemplate (params) {
 export function organizationDrill (params) {
   return http.post('/digitizationData/employee/organization/tree/subordinates', params)
 }
+
+// 获取机构图
+export function getOrganizationAtlas () {
+  return http.post('/organization/atlas')
+}
+// 机构图钻取员工图例
+export function getOrganizationAtlasEmployee (params) {
+  return http.post('/organization/employee/atlas', params)
+}

+ 3 - 1
src/components/AutoComponents/ECharts/eCharts.js

@@ -9,7 +9,8 @@ import {
   TooltipComponent,
   GridComponent,
   DatasetComponent,
-  TransformComponent
+  TransformComponent,
+  LegendComponent
 } from 'echarts/components'
 // 标签自动布局、全局过渡动画等特性
 import { LabelLayout, UniversalTransition } from 'echarts/features'
@@ -30,6 +31,7 @@ class EChartsComponent {
         LineChart,
         LabelLayout,
         UniversalTransition,
+        LegendComponent,
         CanvasRenderer
       ]
     )

+ 141 - 0
src/utils/antvG6.js

@@ -0,0 +1,141 @@
+// import { Rect } from '@antv/g'
+import {
+  Badge,
+  // idOf,
+  CommonEvent,
+  BaseBehavior,
+  // BaseNode
+  Circle
+  // treeToGraphData
+} from '@antv/g6'
+
+/**
+ * 自定义树结构展开/收起
+ */
+export class CollapseExpandTree extends BaseBehavior {
+  constructor (context, options) {
+    super(context, options)
+    this.bindEvents()
+  }
+
+  update (options) {
+    this.unbindEvents()
+    super.update(options)
+    this.bindEvents()
+  }
+
+  bindEvents () {
+    const { graph } = this.context
+    graph.on('collapse-expand', this.onCollapseExpand)
+  }
+
+  unbindEvents () {
+    const { graph } = this.context
+    graph.off('collapse-expand', this.onCollapseExpand)
+  }
+
+  status = 'idle';
+
+  onCollapseExpand = async (event) => {
+    this.status = 'busy'
+    const { id, collapsed } = event
+    const { graph } = this.context
+    await graph.frontElement(id)
+    if (collapsed) await graph.collapseElement(id)
+    else await graph.expandElement(id)
+    this.status = 'idle'
+  }
+}
+
+export class MindMapNode extends Circle {
+  static defaultStyleProps = {
+    showIcon: true
+  };
+
+  constructor (options) {
+    Object.assign(options.style, MindMapNode.defaultStyleProps)
+    super(options)
+  }
+
+  // 获取子数据
+  get childrenData () {
+    // 调用上下文中的model对象的getChildrenData方法,传入当前对象的id,返回子数据
+    return this.context.model.getChildrenData(this.id)
+  }
+
+  status = true
+
+  drawCollapseShape (attributes, container) {
+    // const iconStyle = this.getCollapseStyle(attributes)
+    const nodes = this.context.graph.getNodeData(this.id)
+    if (!nodes.children && !nodes.hasChildren) {
+      return
+    }
+    const btn = this.upsert('collapse-expand', Badge, {
+      backgroundFill: '#409EFF',
+      backgroundHeight: 26,
+      backgroundWidth: 26,
+      lineHeight: 50,
+      cursor: 'pointer',
+      fill: '#fff',
+      fontSize: 24,
+      text: attributes.collapsed || nodes.hasChildren ? '+' : '-',
+      textAlign: 'center',
+      visibility: 'visible',
+      x: 40,
+      y: 0
+    }, container)
+
+    this.forwardEvent(btn, CommonEvent.CLICK, async (event) => {
+      event.stopPropagation()
+      if (!this.status) {
+        return
+      }
+      const parent = this.context.graph.getNodeData(this.id)
+      if (parent.hasChildren) {
+        await this.addChildrenData(parent)
+        return
+      }
+      this.status = false
+      this.context.graph.emit('collapse-expand', {
+        id: this.id,
+        collapsed: !attributes.collapsed
+      })
+      setTimeout(() => {
+        this.status = true
+      }, 500)
+    })
+  }
+
+  async addChildrenData (parent) {
+    this.status = false
+    const { graph } = this.context
+    // 展开关闭
+    const data = await parent.getChildren(this.id)
+    if (!data) {
+      return
+    }
+    graph.addNodeData(data.nodes)
+    graph.addEdgeData(data.edges)
+    graph.updateNodeData([{
+      id: this.id,
+      hasChildren: false,
+      children: [...data.nodes.map(e => e.id)]
+    }])
+    await graph.render()
+    this.status = true
+  }
+
+  forwardEvent (target, type, listener) {
+    if (target && !Reflect.has(target, '__bind__')) {
+      Reflect.set(target, '__bind__', true)
+      target.addEventListener(type, listener)
+    }
+  }
+
+  render (attributes = this.parsedAttributes, container = this) {
+    super.render(attributes, container)
+
+    this.drawCollapseShape(attributes, container)
+  }
+}

+ 1 - 1
src/utils/request.js

@@ -7,7 +7,7 @@ import qs from 'qs'
 import Vue from 'vue'
 // create an axios instance
 const service = axios.create({
-  baseURL: window?.g?.VUE_APP_BASE_API ?? process.env.VUE_APP_BASE_API,
+  baseURL: process.env.VUE_APP_BASE_API,
   // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
   // withCredentials: true, // send cookies when cross-domain requests
   timeout: 120000 // request timeout

+ 205 - 33
src/views/humanResources/organizationStructure/index.vue

@@ -1,58 +1,214 @@
 <template>
-  <m-table
-    v-loading="loading"
-    row-key="uuid"
-    card-title="组织机构"
-    :items="organizationTree"
-    :headers="headers"
-    :page-size="total"
-    :page-current="1"
-    :total="total"
-    :expand-row-keys="expandRowKeys"
-    lazy
-    :load="load"
-    :tree-props="{children: 'child'}"
-    :default-sort="{ prop: 'sort', order: 'ascending' }"
-  >
-  </m-table>
+  <div class="fullBox white pa-3">
+    <div ref="graphRef" v-loading="loading" class="fullBox"></div>
+    <!-- <RelationGraph ref="graphRef" :options="graphOptions" :on-node-expand="onNodeExpand" :on-node-collapse="onNodeCollapse" v-loading="loading"></RelationGraph> -->
+  </div>
 </template>
 
 <script>
+// import RelationGraph from 'relation-graph'
+
+import {
+  CollapseExpandTree,
+  MindMapNode
+} from '@/utils/antvG6'
+import {
+  Graph,
+  register,
+  ExtensionCategory
+} from '@antv/g6'
 import {
-  organizationDrill
+  getOrganizationAtlas,
+  getOrganizationAtlasEmployee
 } from '@/api/system'
 import { mapGetters } from 'vuex'
 
+const NODE_TYPE = {
+  type: 'MindMapNode',
+  style: function (d) {
+    return {
+      label: true,
+      labelFontSize: 24,
+      labelLineHeight: 48,
+      labelPlacement: 'right',
+      labelPadding: [0, 20],
+      labelText: d.name,
+      labelOffsetX: d.depth === 3 ? 40 : 80,
+      labelBackground: true,
+      labelBackgroundFill: '#EFF0F0',
+      labelBackgroundRadius: 8,
+      port: true,
+      ports: [{ placement: 'right' }, { placement: 'left' }]
+      // badges: [
+      //   { text: d.tag || '', placement: 'right-bottom', fontSize: 16 }
+      // ]
+    }
+  }
+}
+
 export default {
   name: 'organization-structure',
   data () {
     return {
-      expandRowKeys: [],
-      loading: false,
-      total: 0,
-      headers: [
-        { label: '机构名称', prop: 'organizationName' }
-      ]
+      loading: false
     }
   },
   computed: {
     ...mapGetters(['organizationTree'])
   },
-  created () {
-    this.init()
+  mounted () {
+    register(ExtensionCategory.BEHAVIOR, 'collapse-expand-tree', CollapseExpandTree)
+    register(ExtensionCategory.NODE, 'MindMapNode', MindMapNode)
+    // this.renderGraph(treeToGraphData(this.assignTree(this.organizationTree)[0]))
+    this.onInit()
   },
   methods: {
-    async init () {
-      if (!this.organizationTree.length) {
-        return
+    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)
       }
-      this.expandRowKeys = [this.organizationTree[0].uuid]
-      this.total = this.organizationTree.length
     },
-    async load (tree, treeNode, resolve) {
+    assignTree (tree) {
+      return tree.map(e => {
+        const { child, organizationNo, organizationName, ...rest } = e
+        if (e.child && e.child.length) {
+          rest.children = this.assignTree(e.child)
+        }
+        return {
+          id: organizationNo,
+          name: organizationName,
+          getChildren: this.getChildren,
+          ...rest
+        }
+      })
+    },
+    renderGraph (data) {
+      const 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' }
+              // { id: 'export', value: 'export' },
+              // { id: 'request-fullscreen', value: 'request-fullscreen' },
+              // { id: 'exit-fullscreen', value: 'exit-fullscreen' }
+            ],
+            onClick: (value) => {
+              const zoom = graph.getZoom()
+              console.log(zoom)
+              // 处理按钮点击事件
+              if (value === 'zoom-in') {
+                if (zoom > 2) {
+                  return
+                }
+                graph.zoomTo(zoom + 0.1)
+              } else if (value === 'zoom-out') {
+                if (zoom < 0.5) {
+                  return
+                }
+                graph.zoomTo(zoom - 0.1)
+              } else if (value === 'auto-fit') {
+                graph.fitView()
+              }
+            }
+          }
+        ],
+        behaviors: [
+          'drag-canvas',
+          // 'zoom-canvas',
+          'scroll-canvas',
+          'drag-element',
+          'collapse-expand-tree'
+        ],
+        transforms: ['assign-color-by-branch'],
+        animation: false,
+        layout: {
+          type: 'compact-box',
+          getHeight: function getHeight () {
+            return 32
+          },
+          getWidth: function getWidth () {
+            return 32
+          },
+          getVGap: function getVGap () {
+            return 10
+          },
+          getHGap: function getHGap () {
+            return 200
+          },
+          preventOverlap: true, // 防止节点重叠
+          iterations: 200, // 迭代次数
+          animation: true, // 启用布局动画
+          direction: 'LR',
+          nodeSep: 100,
+          rankSep: 500, // 层间距(px)
+          ranker: 'tight-tree', // 布局的模式 'network-simplex' | 'tight-tree' | 'longest-path'
+          rankdir: 'LR', // 布局的方向
+          nodeSize: 100, // 节点大小(直径)
+          radial: false
+        },
+        node: NODE_TYPE,
+        edge: {
+          type: 'cubic-horizontal',
+          style: function (d) {
+            return {
+              endArrow: true,
+              lineWidth: 2,
+              stroke: '#999'
+            }
+          }
+        }
+      })
+
+      console.log(graph)
+      graph.render()
+    },
+    async onInit () {
       try {
-        const { data } = await organizationDrill({ deptName: tree.organizationName })
-        resolve(data)
+        const { data } = await getOrganizationAtlas()
+        const { nodes, edges } = data
+        this.renderGraph({
+          nodes: nodes.map(e => {
+            return {
+              ...e,
+              getChildren: this.getChildren
+            }
+          }),
+          edges
+        })
       } catch (error) {
         this.$message.error(error)
       }
@@ -62,5 +218,21 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.fullBox {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+::v-deep .toolbar {
+  width: 50px;
+  padding: 20px 0;
+  border: 1px solid #ccc;
+  .g6-toolbar-item {
+    width: 100%;
+    height: 20px;
+    padding: 10px 0;
+  }
+}
 
 </style>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio