소스 검색

Merge branch 'dev-bxy' into 'dev'

feat: 完成应急架构体系图mock

See merge request product-group-fe/sfy-safety-group/sfy-safety!114
毕欣怡 10 달 전
부모
커밋
c2c88821d0
2개의 변경된 파일112개의 추가작업 그리고 71개의 파일을 삭제
  1. 91 62
      src/views/emergency/components/OrgChart.vue
  2. 21 9
      src/views/emergency/organization/PageOrganization.vue

+ 91 - 62
src/views/emergency/components/OrgChart.vue

@@ -4,12 +4,17 @@
 
 <script setup lang="ts">
   import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
-  import { Graph, treeToGraphData } from '@antv/g6';
+  import { Graph, treeToGraphData, register, ExtensionCategory, NodeEvent, Polyline, CanvasEvent } from '@antv/g6';
+
+  /**
+   * @description: 为了确保图的正确渲染和交互,建议按照 G6 标准数据结构组织数据。
+   * 每个元素(节点、边、组合)应包含一个 data 字段,用于存放业务数据和自定义属性。
+   */
 
   // 定义 props 接口
   interface TreeData {
     id: string;
-    label: string;
+    data: { name: string };
     children?: TreeData[];
   }
 
@@ -17,12 +22,27 @@
     treeData: TreeData;
   }>();
 
-  // 转换数据
-  const data = treeToGraphData(props.treeData);
-  // 图表容器引用
-  const container = ref<HTMLDivElement | null>(null);
+  const emits = defineEmits<{
+    (event: 'node-click', nodeData: any): void;
+    (event: 'canvas-click'): void;
+  }>();
+
+  const data = treeToGraphData(props.treeData); // 通过 treeToGraphData 方法,将树形结构数据转换为 G6 的标准数据结构
+  const container = ref<HTMLDivElement | null>(null); // 图表容器引用
   let graph: any = null;
 
+  // 自定义edge样式
+  class AntLine extends Polyline {
+    onCreate() {
+      const shape = this.shapeMap.key;
+      shape.animate([{ lineDashOffset: 20 }, { lineDashOffset: 0 }], {
+        duration: 500,
+        iterations: Infinity,
+      });
+    }
+  }
+  register(ExtensionCategory.EDGE, 'ant-line', AntLine);
+
   // 初始化图表
   const initGraph = () => {
     if (!container.value) return;
@@ -36,12 +56,48 @@
     // 创建新的 G6 实例
     graph = new Graph({
       container: container.value,
-      // width: window.innerWidth,
-      // height: window.innerHeight,
+      padding: [20, 20, 20, 20], // 图表内边距
       data,
+      node: {
+        type: 'rect', // 使用内置的矩形节点类型
+        style: {
+          labelText: (d: any) => d.data.name, // 节点文本
+          labelFill: '#333', // 文本颜色
+          labelFontSize: 14, // 文本大小
+          size: [250, 50],
+          lineWidth: 1, // 边框宽度
+          // lineDash: [5, 5], // 虚线边框
+          stroke: '#1777FF', // 边框色
+          fill: '#E7F1FF', // 填充色
+          radius: 8,
+          labelPlacement: 'center',
+          ports: [{ placement: 'top' }, { placement: 'bottom' }],
+        },
+        // 节点状态样式
+        state: {
+          selected: {
+            fill: '#1777FF',
+            stroke: '#1777FF',
+            lineWidth: 1,
+            labelFill: '#fff', // 选中状态下文本颜色
+            labelFontSize: 16, // 选中状态下文本大小
+          },
+        },
+      },
+      edge: {
+        type: 'ant-line', // 边类型
+        style: {
+          stroke: '#1777FF', // 边的颜色
+          lineWidth: 1, // 边的宽度
+          lineDash: [10, 10],
+          endArrow: true, // 是否有箭头
+          router: {
+            type: 'orth',
+          },
+        },
+      },
       layout: {
         type: 'dagre',
-        rankdir: 'LR', // 水平方向布局
         nodesep: 100,
         ranksep: 120,
         preventOverlap: true, // 防止节点重叠
@@ -50,14 +106,6 @@
         iterations: 200, // 迭代次数
         animation: true, // 启用布局动画
       },
-      behaviors: [
-        'drag-canvas',
-        {
-          type: 'zoom-canvas',
-          sensitivity: 1.5, // 配置灵敏度
-          key: 'zoom-behavior', // 为交互指定key,便于后续更新
-        },
-      ],
       autoFit: {
         type: 'view', // 自适应类型:'view' 或 'center'
         options: {
@@ -71,68 +119,48 @@
           easing: 'ease-in-out', // 动画缓动函数
         },
       },
-      node: {
-        type: 'circle', // 节点类型
-        style: {
-          fill: '#e6f7ff', // 填充色
-          stroke: '#91d5ff', // 边框色
-          lineWidth: 1, // 边框宽度
-          r: 20, // 半径
-          labelText: (d) => d.id, // 标签文本
-        },
-        // 节点状态样式
-        state: {
-          hover: {
-            lineWidth: 2,
-            stroke: '#69c0ff',
-          },
-          selected: {
-            fill: '#bae7ff',
-            stroke: '#1890ff',
-            lineWidth: 2,
-          },
-        },
-      },
-      edge: {
-        type: 'polyline', // 边类型
-        style: {
-          stroke: '#91d5ff', // 边的颜色
-          lineWidth: 2, // 边的宽度
-          endArrow: true, // 是否有箭头
+      autoResize: true, // 自动调整大小
+      behaviors: [
+        'drag-canvas',
+        {
+          type: 'zoom-canvas',
+          sensitivity: 0.5, // 配置灵敏度
+          key: 'zoom-behavior', // 为交互指定key,便于后续更新
         },
-        // 边的状态样式
-        state: {
-          selected: {
-            stroke: '#1890ff',
-            lineWidth: 3,
-          },
+        'focus-element',
+        {
+          type: 'click-select',
+          state: 'selected',
+          unselectedState: 'inactive',
+          multiple: true,
+          trigger: ['shift'],
         },
-      },
+      ],
     });
 
     // 渲染
     graph.render();
 
-    // 添加交互效果
-    graph.on('node:mouseenter', (evt) => {
-      const node = evt.item;
-      graph.setItemState(node, 'hover', true);
+    // 监听节点点击事件
+    graph.on(NodeEvent.CLICK, (evt) => {
+      const { target } = evt;
+      const nodeData = graph.getNodeData(target.id); // 获取节点数据
+      emits('node-click', nodeData);
     });
 
-    graph.on('node:mouseleave', (evt) => {
-      const node = evt.item;
-      graph.setItemState(node, 'hover', false);
+    graph.on(CanvasEvent.CLICK, () => {
+      graph.fitCenter();
+      emits('canvas-click');
     });
 
-    // 响应窗口大小变化
     window.addEventListener('resize', handleResize);
   };
 
   // 处理窗口大小变化
   const handleResize = () => {
-    if (graph.get('destroyed')) return;
     if (container.value) {
-      graph.changeSize(container.value.clientWidth, container.value.clientHeight);
+      graph.resize(container.value.offsetWidth, container.value.offsetHeight);
+      graph.fitCenter();
     }
   };
 
@@ -153,6 +181,7 @@
   onBeforeUnmount(() => {
     window.removeEventListener('resize', handleResize);
     if (graph) {
+      graph.off(); // 移除所有事件监听
       graph.destroy();
     }
   });

+ 21 - 9
src/views/emergency/organization/PageOrganization.vue

@@ -4,7 +4,7 @@
       <div class="breadcrumb-title"> 应急架构体系 </div>
     </div>
     <div class="safety-platform-container__main">
-      <OrgChart :treeData="treeData" />
+      <OrgChart :treeData="treeData" @node-click="handleNodeClick" @canvas-click="handleCanvasClick" />
     </div>
     <div class="safety-platform-container__footer">
       <el-button> 编辑 </el-button>
@@ -17,20 +17,32 @@
 
   const treeData = {
     id: 'root',
-    label: '应急管理体系',
+    data: { name: '应急领导小组' },
     children: [
       {
         id: 'group1',
-        label: '应急领导小组',
-        children: [
-          { id: 'team1', label: '综合协调组' },
-          { id: 'team2', label: '抢险处置组' },
-          { id: 'team3', label: '应急抢修组' },
-          { id: 'team4', label: '救治保障组' },
-        ],
+        data: { name: '应急指挥小组' },
+      },
+      {
+        id: 'group2',
+        data: { name: '应急响应小组' },
+      },
+      {
+        id: 'group3',
+        data: { name: '应急支援小组' },
       },
     ],
   };
+
+  const handleNodeClick = (nodeData: any) => {
+    console.log('节点被点击:', nodeData);
+    // 在这里处理节点点击事件
+  };
+
+  const handleCanvasClick = () => {
+    console.log('画布被点击');
+    // 在这里处理画布点击事件
+  };
 </script>
 
 <style lang="scss" scoped>