yangg 1 day ago
parent
commit
dd4a3f62ad
5 changed files with 625 additions and 0 deletions
  1. 1 0
      package.json
  2. 560 0
      src/components/SystemGraph/index.vue
  3. 6 0
      src/router/menu.js
  4. 34 0
      src/views/report.vue
  5. 24 0
      src/views/system-graph.vue

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@ant-design/icons-vue": "^7.0.1",
     "@ant-design/icons-vue": "^7.0.1",
+    "@antv/g6": "^5.0.49",
     "@vue-office/docx": "^1.6.3",
     "@vue-office/docx": "^1.6.3",
     "@vue-office/excel": "^1.7.14",
     "@vue-office/excel": "^1.7.14",
     "@vue-office/pdf": "^2.0.10",
     "@vue-office/pdf": "^2.0.10",

+ 560 - 0
src/components/SystemGraph/index.vue

@@ -0,0 +1,560 @@
+<template>
+  <div class="system-graph">
+    <div id="container" ref="container"></div>
+   <!--  <div class="control-panel">
+      <button @click="handleMatch">匹配模式</button>
+    </div> -->
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, onUnmounted } from 'vue';
+import { Graph } from '@antv/g6';
+import { GADDI } from '@antv/algorithm';
+import { color } from 'echarts';
+
+const container = ref(null);
+let graph = null;
+
+// 系统集成数据
+const systemData = {
+  nodes: [
+    {
+      id: 'ERP',
+      label: 'ERP',
+      style: {
+        x: 167.86420885505296,
+        y: 94.28884847734246,
+        size: 35,
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      labelCfg: {
+        style: {
+            fill: '#fff',
+            fontSize: 14,
+        }
+        },
+      cluster: 'nodeType-1',
+      description: '企业资源计划系统'
+    },
+    // ERP子模块
+    {
+      id: 'ERP-Finance',
+      label: 'Finance',
+      style: {
+        x: 127.86420885505296,
+        y: 134.28884847734246,
+        size: 27,
+        labelFontSize: 8,
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      parentId: 'ERP'
+    },
+    {
+      id: 'ERP-HR',
+      label: 'HR',
+      style: {
+        x: 147.86420885505296,
+        y: 154.28884847734246,
+        size: 27,
+        labelFontSize: 8,
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      parentId: 'ERP'
+    },
+    {
+      id: 'ERP-Purchase',
+      label: 'Purchase',
+      style: {
+        x: 187.86420885505296,
+        y: 154.28884847734246,
+        size: 27,
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      parentId: 'ERP'
+    },
+    {
+      id: 'ERP-Sales',
+      label: 'Sales',
+      style: {
+        x: 207.86420885505296,
+        y: 134.28884847734246,
+        size: 27,
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      parentId: 'ERP'
+    },
+    {
+      id: 'ERP-Inventory',
+      label: 'Inventory',
+      style: {
+        x: 167.86420885505296,
+        y: 174.28884847734246,
+        size: 27,
+        labelFontSize: 5,
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      parentId: 'ERP'
+    },
+    {
+      id: 'MES',
+      label: 'MES',
+      style: {
+        x: 113.58792632732174,
+        y: 25.785315472468127
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      description: '制造执行系统'
+    },
+    {
+      id: 'PLM',
+      label: 'PLM',
+      style: {
+        x: 179.59682070334452,
+        y: 38.87850516662148
+      },
+      data: {
+        cluster: 'nodeType-2'
+      },
+      cluster: 'nodeType-2',
+      description: '产品生命周期管理'
+    },
+    {
+      id: 'QMS',
+      label: 'QMS',
+      style: {
+        x: 204.20226244579672,
+        y: -5.33508012158744
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      description: '质量管理系统'
+    },
+    {
+      id: 'PMS',
+      label: 'PMS',
+      style: {
+        x: 308.74171746938134,
+        y: 10.554714934145961
+      },
+      data: {
+        cluster: 'nodeType-1'
+      },
+      cluster: 'nodeType-1',
+      description: '项目管理系统'
+    },
+    {
+      id: 'WMS',
+      label: 'WMS',
+      style: {
+        x: 341.99836557519745,
+        y: 48.30310308747067
+      },
+      data: {
+        cluster: 'nodeType-2'
+      },
+      cluster: 'nodeType-2',
+      description: '仓库管理系统'
+    }
+  ],
+  edges: [
+    // ERP子模块连接
+    {
+      source: 'ERP',
+      target: 'ERP-Finance',
+      data: { cluster: 'submodule' },
+      cluster: 'submodule',
+      id: 'edge-erp-finance'
+    },
+    {
+      source: 'ERP',
+      target: 'ERP-HR',
+      data: { cluster: 'submodule' },
+      cluster: 'submodule',
+      id: 'edge-erp-hr'
+    },
+    {
+      source: 'ERP',
+      target: 'ERP-Purchase',
+      data: { cluster: 'submodule' },
+      cluster: 'submodule',
+      id: 'edge-erp-purchase'
+    },
+    {
+      source: 'ERP',
+      target: 'ERP-Sales',
+      data: { cluster: 'submodule' },
+      cluster: 'submodule',
+      id: 'edge-erp-sales'
+    },
+    {
+      source: 'ERP',
+      target: 'ERP-Inventory',
+      data: { cluster: 'submodule' },
+      cluster: 'submodule',
+      id: 'edge-erp-inventory'
+    },
+    // 系统间连接
+    { 
+      source: 'ERP', 
+      target: 'MES', 
+      data: { cluster: 'edgeType-0' },
+      cluster: 'edgeType-0',
+      id: 'edge-erp-mes'
+    },
+    { 
+      source: 'ERP', 
+      target: 'PLM', 
+      data: { cluster: 'edgeType-1' },
+      cluster: 'edgeType-1',
+      id: 'edge-erp-plm'
+    },
+    
+    { 
+      source: 'MES', 
+      target: 'QMS', 
+      data: { cluster: 'edgeType-2' },
+      cluster: 'edgeType-2',
+      id: 'edge-mes-qms'
+    },
+    { 
+      source: 'PLM', 
+      target: 'PMS', 
+      data: { cluster: 'edgeType-0' },
+      cluster: 'edgeType-0',
+      id: 'edge-plm-pms'
+    },
+    { 
+      source: 'QMS', 
+      target: 'WMS', 
+      data: { cluster: 'edgeType-1' },
+      cluster: 'edgeType-1',
+      id: 'edge-qms-wms'
+    },
+    { 
+      source: 'PMS', 
+      target: 'WMS', 
+      data: { cluster: 'edgeType-2' },
+      cluster: 'edgeType-2',
+      id: 'edge-pms-wms'
+    },
+    { 
+      source: 'PLM', 
+      target: 'QMS', 
+      data: { cluster: 'edgeType-21' },
+      cluster: 'edgeType-21',
+      id: 'edge-pms-wms1'
+    }
+  ]
+};
+
+const graphData = ref(systemData);
+
+// 处理窗口大小变化
+const handleResize = () => {
+  if (!graph || graph.get('destroyed')) return;
+  const width = 900;
+  const height = 800 || 500;
+  graph.changeSize(width, height);
+};
+
+const getGraphOptions = () => {
+  const width = 900;
+  const height = 800 || 600;
+
+  return {
+    width,
+    height,
+    autoFit: 'view',
+    modes: {
+      default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'click-select'],
+    },
+    defaultNode: {
+      draggable: true,
+      labelCfg: {
+        style: {
+          fontSize: 16,
+          fill: '#000',
+        }
+      },
+    },
+    node: {
+      style: (node) => {
+        const isSubmodule = node.cluster === 'submodule';
+        return {
+          labelPlacement: isSubmodule ? 'bottom' : 'center',
+          labelText: (d) => d.label,
+          stroke: isSubmodule ? '#1890ff' : '#5F95FF',
+          lineWidth: isSubmodule ? 1 : 2,
+          fill: '#fff',
+          shadowColor: isSubmodule ? '#e8e8e8' : '#d9d9d9',
+          shadowBlur: isSubmodule ? 5 : 10,
+          radius: isSubmodule ? 15 : 30,
+          fontSize: isSubmodule ? 12 : 16,
+          fontWeight: isSubmodule ? 400 : 500,
+          opacity: isSubmodule ? 0.8 : 1,
+        };
+      },
+      palette: {
+        type: 'group',
+        field: 'cluster',
+        color: {
+          'nodeType-0': '#1890ff',
+          'nodeType-1': '#52c41a',
+          'nodeType-2': '#722ed1',
+          'submodule': '#40a9ff'
+        }
+      },
+      state: {
+        hover: {
+          stroke: '#40a9ff',
+          lineWidth: (node) => node.cluster === 'submodule' ? 2 : 3,
+          shadowColor: '#1890ff',
+          shadowBlur: (node) => node.cluster === 'submodule' ? 8 : 15,
+          opacity: 1,
+        },
+        selected: {
+          stroke: '#1890ff',
+          lineWidth: (node) => node.cluster === 'submodule' ? 2 : 3,
+          shadowColor: '#1890ff',
+          shadowBlur: (node) => node.cluster === 'submodule' ? 8 : 15,
+        }
+      }
+    },
+    edge: {
+      style: (edge) => {
+        const isSubmodule = edge.cluster === 'submodule';
+        return {
+          stroke: isSubmodule ? '#40a9ff' : '#91d5ff',
+          lineWidth: isSubmodule ? 1 : 2,
+          endArrow: isSubmodule ? false : {
+            path: 'M 0,0 L 12,6 L 12,-6 Z',
+            fill: '#91d5ff',
+          },
+          radius: isSubmodule ? 10 : 20,
+          opacity: isSubmodule ? 0.5 : 0.8,
+          lineDash: isSubmodule ? [2, 2] : null,
+        };
+      },
+      palette: {
+        type: 'group',
+        field: 'cluster',
+        color: {
+          'edgeType-0': '#1890ff',
+          'edgeType-1': '#52c41a',
+          'edgeType-2': '#722ed1',
+          'submodule': '#40a9ff'
+        }
+      },
+      state: {
+        hover: {
+          stroke: '#1890ff',
+          lineWidth: (edge) => edge.cluster === 'submodule' ? 1.5 : 3,
+          opacity: 1,
+        }
+      }
+    },
+    plugins: [
+      {
+        type: 'legend',
+        nodeField: 'cluster',
+        position: 'bottom',
+      },
+      {
+        key: 'hull-0',
+        type: 'hull',
+        members: [],
+      },
+      {
+        key: 'hull-1',
+        type: 'hull',
+        members: [],
+      },
+    ],
+  };
+};
+
+const handleMatch = () => {
+  if (!graphData.value || !graph) return;
+  
+  const matches = GADDI(graphData.value, pattern, true, undefined, undefined, 'cluster', 'cluster');
+  matches.forEach((match, i) => {
+    graph.updatePlugin({
+      key: `hull-${i}`,
+      members: match.nodes.map((node) => node.id),
+    });
+  });
+  graph.render();
+};
+
+onMounted(() => {
+  try {
+    // 初始化图形
+    graph = new Graph({
+      ...getGraphOptions(),
+      container: container.value,
+      data: graphData.value,
+      autoFit: 'view',
+      behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
+      layout: {
+        type: 'gForce',
+        preventOverlap: true,
+        nodeStrength: -100,
+        edgeStrength: 0.1,
+        nodeSize: 60,
+        nodeSpacing: 100,
+        linkDistance: 200,
+        gravity: 1,
+        maxIteration: 1000,
+        minMovement: 0.1,
+        damping: 0.9,
+        maxSpeed: 1000,
+        center: [400, 300]
+      },
+      node: {
+        style: {
+          labelPlacement: 'center',
+          labelText: (d) => d.label,
+          stroke: '#61DDAA',
+          lineWidth: 1,
+          labelFontSize: 6,
+          labelFill: '#fff',
+        },
+        labelCfg: {
+          style: {
+            fontSize: 16,
+            fill: '#000',
+          }
+        },
+        
+        palette: {
+          type: 'group',
+          field: 'cluster',
+          color: ['#61DDAA', '#65789B'],
+        },
+      },
+      edge: {
+        style: {
+          endArrow: true,
+        },
+        palette: {
+          type: 'group',
+          field: 'cluster',
+          color: ['#61DDAA', '#65789B'],
+        },
+      },
+      plugins: [
+        {
+          type: 'legend',
+          nodeField: 'cluster',
+          position: 'bottom',
+        },
+        {
+          key: 'hull-0',
+          type: 'hull',
+          members: [],
+        },
+        {
+          key: 'hull-1',
+          type: 'hull',
+          members: [],
+        },
+      ],
+    });
+    
+    graph.render();
+
+    // 添加事件监听
+    window.addEventListener('resize', handleResize);
+  } catch (error) {
+    console.error('Failed to fetch graph data:', error);
+  }
+  
+  // 添加节点点击事件
+  graph.on('node:click', (evt) => {
+    const { item } = evt;
+    const model = item.getModel();
+    console.log('Clicked node:', model);
+  });
+
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+// 组件卸载时清理
+onUnmounted(() => {
+  window.removeEventListener('resize', handleResize);
+  if (graph) {
+    graph.destroy();
+  }
+});
+</script>
+
+<style scoped>
+.system-graph {
+  width: 100%;
+  height: 100vh;
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+}
+
+#container {
+  width: 900px;
+  height: 100vh;
+  border: 1px solid #eee;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+  margin-bottom: 20px;
+}
+
+.control-panel {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  padding: 10px;
+}
+
+.control-panel button {
+  padding: 8px 16px;
+  background: #1890ff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+}
+
+.control-panel button:hover {
+  background: #40a9ff;
+}
+
+.control-panel button:active {
+  background: #096dd9;
+}
+</style>

+ 6 - 0
src/router/menu.js

@@ -17,6 +17,12 @@ export const ROUTE = [
         name: 'role',
         name: 'role',
         title: '角色管理',
         title: '角色管理',
         component: () => import('@views/role.vue')
         component: () => import('@views/role.vue')
+      },
+      {
+        path: '/system-graph',
+        name: 'system-graph',
+        title: '系统架构图',
+        component: () => import('@views/system-graph.vue')
       }
       }
     ]
     ]
   },
   },

+ 34 - 0
src/views/report.vue

@@ -97,11 +97,27 @@
 
 
       <!-- 添加模型配置和语音配置的容器 -->   <!-- <PermissionConfig/> -->
       <!-- 添加模型配置和语音配置的容器 -->   <!-- <PermissionConfig/> -->
       <div class="config-container">
       <div class="config-container">
+        <div class="config-header" style="border-bottom: 1px solid #e5e5e5;" @click="showSystemGraph">
+            <img src="../assets/lock.png" alt="permission" class="config-icon" />
+            <span>系统配置</span>
+          </div>
+          
+          <!-- 系统图抽屉 -->
+          <a-drawer
+            title="系统配置图"
+            placement="right"
+            :width="1000"
+            :visible="systemGraphVisible"
+            @close="closeSystemGraph"
+          >
+            <SystemGraph />
+          </a-drawer>
          <div class="config-header" @click="goToRoleManagement">
          <div class="config-header" @click="goToRoleManagement">
             <img src="../assets/lock.png" alt="permission" class="config-icon" />
             <img src="../assets/lock.png" alt="permission" class="config-icon" />
             <span>角色及权限管理</span>
             <span>角色及权限管理</span>
             
             
           </div>
           </div>
+          
         <!-- <div class="role-management-entry" @click="goToRoleManagement">
         <!-- <div class="role-management-entry" @click="goToRoleManagement">
           <img src="../assets/lock.png" alt="role" class="config-icon" />
           <img src="../assets/lock.png" alt="role" class="config-icon" />
           <span>角色管理</span>
           <span>角色管理</span>
@@ -803,6 +819,8 @@
 </template>
 </template>
 
 
 <script setup>
 <script setup>
+import SystemGraph from '../components/SystemGraph/index.vue';
+
 import { ref, reactive, nextTick, onUnmounted, onMounted, computed, watch } from "vue";
 import { ref, reactive, nextTick, onUnmounted, onMounted, computed, watch } from "vue";
 import {
 import {
   PlusOutlined,
   PlusOutlined,
@@ -882,6 +900,22 @@ const router = useRouter()
 const goToRoleManagement = () => {
 const goToRoleManagement = () => {
   router.push('/role');
   router.push('/role');
 };
 };
+
+// 系统图抽屉控制
+const systemGraphVisible = ref(false);
+
+const showSystemGraph = () => {
+  systemGraphVisible.value = true;
+};
+
+const closeSystemGraph = () => {
+  systemGraphVisible.value = false;
+};
+
+// 保留原有的跳转函数以备用
+const goToSystemGraph = () => {
+  router.push('/system-graph');
+};
 // 会话数据
 // 会话数据
 const conversations = ref([
 const conversations = ref([
   {
   {

+ 24 - 0
src/views/system-graph.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="system-graph-page">
+    <SystemGraph />
+  </div>
+</template>
+
+<script>
+import { defineComponent } from 'vue';
+import SystemGraph from '../components/SystemGraph/index.vue';
+
+export default defineComponent({
+  name: 'SystemGraphPage',
+  components: {
+    SystemGraph,
+  },
+});
+</script>
+
+<style scoped>
+.system-graph-page {
+  width: 100%;
+  height: 100%;
+}
+</style>