Zimo пре 20 часа
родитељ
комит
a510bcda6d
3 измењених фајлова са 259 додато и 80 уклоњено
  1. 1 0
      package.json
  2. 30 0
      pnpm-lock.yaml
  3. 228 80
      src/views/test/index.vue

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "lint:lint-staged": "lint-staged -c "
   },
   "dependencies": {
+    "@antv/x6": "^3.1.6",
     "@element-plus/icons-vue": "^2.1.0",
     "@form-create/designer": "^3.2.6",
     "@form-create/element-ui": "^3.2.11",

+ 30 - 0
pnpm-lock.yaml

@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      '@antv/x6':
+        specifier: ^3.1.6
+        version: 3.1.6
       '@element-plus/icons-vue':
         specifier: ^2.1.0
         version: 2.3.1(vue@3.5.12(typescript@5.3.3))
@@ -433,6 +436,10 @@ packages:
   '@antfu/utils@0.7.10':
     resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
 
+  '@antv/x6@3.1.6':
+    resolution: {integrity: sha512-M/kWetMQ+0CbozONh6OQg5UgbN7SZ4GnNWiNxZQXBHfxjYCDk/rfXPD+CfZeePSvUDabTpRxGOu3obGu/vu06g==}
+    engines: {node: '>=20.0.0'}
+
   '@babel/code-frame@7.26.2':
     resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
     engines: {node: '>=6.9.0'}
@@ -3196,6 +3203,9 @@ packages:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}
 
+  dom-align@1.12.4:
+    resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==}
+
   dom-serializer@2.0.0:
     resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
 
@@ -4292,6 +4302,9 @@ packages:
       '@vueuse/core': '>=10.0.0'
       vue: '>=3.0.0'
 
+  mousetrap@1.6.5:
+    resolution: {integrity: sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==}
+
   mpd-parser@0.22.1:
     resolution: {integrity: sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==}
     hasBin: true
@@ -5274,6 +5287,10 @@ packages:
   util-deprecate@1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
 
+  utility-types@3.11.0:
+    resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
+    engines: {node: '>= 4'}
+
   uuid@10.0.0:
     resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
     hasBin: true
@@ -5628,6 +5645,13 @@ snapshots:
 
   '@antfu/utils@0.7.10': {}
 
+  '@antv/x6@3.1.6':
+    dependencies:
+      dom-align: 1.12.4
+      lodash-es: 4.17.21
+      mousetrap: 1.6.5
+      utility-types: 3.11.0
+
   '@babel/code-frame@7.26.2':
     dependencies:
       '@babel/helper-validator-identifier': 7.25.9
@@ -8886,6 +8910,8 @@ snapshots:
     dependencies:
       esutils: 2.0.3
 
+  dom-align@1.12.4: {}
+
   dom-serializer@2.0.0:
     dependencies:
       domelementtype: 2.3.0
@@ -10021,6 +10047,8 @@ snapshots:
       - react
       - react-dom
 
+  mousetrap@1.6.5: {}
+
   mpd-parser@0.22.1:
     dependencies:
       '@babel/runtime': 7.26.0
@@ -11025,6 +11053,8 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
+  utility-types@3.11.0: {}
+
   uuid@10.0.0: {}
 
   v3-jsoneditor@0.0.6:

+ 228 - 80
src/views/test/index.vue

@@ -1,96 +1,244 @@
 <script lang="ts" setup>
-import { IotDeviceApi } from '@/api/pms/device'
-import { SortField } from '@/components/ZmTable/token'
-import { useTableComponents } from '@/components/ZmTable/useTableComponents'
-import { DICT_TYPE, getDictOptions } from '@/utils/dict'
-
-interface List {
-  id: number
-  yfDeviceCode: string // 油服编码
-  deviceCode: string // 历史编码
-  deviceName: string // 设备名称
-  deptName: string // 所在部门
-  deviceStatus: string // 设备状态
-  assetProperty: string // 资产性质
-  assetClassName: string // 资产类别
-  manufacturer: string // 制造商
-  brandName: string // 品牌
-  model: string // 规格型号
-  chargeName: string // 负责人
-  useProject: string // 使用项目
-  assetOwnership: string // 资产归属
-}
+import { ref, onMounted, onUnmounted } from 'vue'
+import { Graph } from '@antv/x6'
 
-const loading = ref(false)
-const list = ref<List[]>([])
-const total = ref(0)
+const containerRef = ref<HTMLDivElement>()
+let graph: Graph | null = null
 
-const query = ref<Partial<List> & { pageNo: number; pageSize: number; sortingFields: SortField[] }>(
+// 1. 注册自定义节点
+Graph.registerNode(
+  'custom-node',
   {
-    pageNo: 1,
-    pageSize: 10,
-    sortingFields: []
-  }
+    attrs: {
+      body: { refWidth: 1, refHeight: 1, rx: 4 }
+    },
+    markup: [
+      { tagName: 'rect', selector: 'body' },
+      { tagName: 'path', selector: 'icon' },
+      { tagName: 'text', selector: 'title' }
+    ]
+  },
+  true
 )
 
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await IotDeviceApi.getIotDevicePage(query.value)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
+// 2. 抽离通用样式配置 (DRY原则,消除重复)
+const commonNodeAttrs = {
+  body: {
+    fill: {
+      type: 'linearGradient',
+      stops: [
+        { offset: '0%', color: 'rgba(10, 30, 60, 0.9)' },
+        { offset: '100%', color: 'rgba(4, 10, 25, 0.9)' }
+      ]
+    },
+    stroke: '#00EAFF',
+    strokeWidth: 2,
+    rx: 6,
+    filter: {
+      name: 'dropShadow',
+      args: { dx: 0, dy: 0, blur: 15, color: '#00EAFF' }
+    }
+  },
+  icon: {
+    fill: '#00EAFF',
+    refX: 0.5,
+    refY: 0.4,
+    xAlign: 'middle',
+    yAlign: 'middle',
+    transform: 'scale(3)'
+  },
+  title: {
+    fill: '#FFFFFF',
+    fontSize: 18,
+    fontWeight: 'bold',
+    letterSpacing: 4,
+    refX: 0.5,
+    refY: 0.8,
+    textAnchor: 'middle',
+    textVerticalAnchor: 'middle'
   }
 }
 
-getList()
+// 3. 集中管理节点数据和 SVG 图标路径
+const ICONS = {
+  compressor:
+    'M10.6 22q-1.275 0-1.937-.763T8 19.5q0-.65.288-1.263t.887-1.012q.55-.35.888-.9t.462-1.175l-.3-.15q-.15-.075-.275-.175l-2.3.825q-.425.15-.825.25T6 16q-1.575 0-2.788-1.375T2 10.6q0-1.275.763-1.937T4.475 8q.65 0 1.275.288t1.025.887q.35.55.9.887t1.175.463l.15-.3q.075-.15.175-.275l-.825-2.3q-.15-.425-.25-.825t-.1-.8q0-1.6 1.375-2.813T13.4 2q1.275 0 1.938.763T16 4.475q0 .65-.288 1.275t-.887 1.025q-.55.35-.887.9t-.463 1.175l.3.15q.15.075.275.175l2.3-.85q.425-.15.813-.237T17.975 8Q20 8 21 9.675t1 3.725q0 1.275-.8 1.938T19.425 16q-.625 0-1.213-.288t-.987-.887q-.35-.55-.9-.887t-1.175-.463l-.15.3q-.075.15-.175.275l.825 2.3q.15.4.25.763t.1.762q.025 1.625-1.35 2.875T10.6 22m1.4-8.5q.625 0 1.062-.437T13.5 12t-.437-1.062T12 10.5t-1.062.438T10.5 12t.438 1.063T12 13.5',
+  airSkid:
+    'M14.5 17c0 1.65-1.35 3-3 3s-3-1.35-3-3h2c0 .55.45 1 1 1s1-.45 1-1s-.45-1-1-1H2v-2h9.5c1.65 0 3 1.35 3 3M19 6.5C19 4.57 17.43 3 15.5 3S12 4.57 12 6.5h2c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5S16.33 8 15.5 8H2v2h13.5c1.93 0 3.5-1.57 3.5-3.5m-.5 4.5H2v2h16.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5v2c1.93 0 3.5-1.57 3.5-3.5S20.43 11 18.5 11',
+  psa: 'M3 21q-.825 0-1.412-.587T1 19v-5q0-.825.588-1.412T3 12V7.725q-.45-.275-.725-.712T2 6V5q0-.825.588-1.412T4 3h5q.825 0 1.413.588T11 5v1q0 .575-.275 1.013T10 7.725V12h4V7.725q-.45-.275-.725-.712T13 6V5q0-.825.588-1.412T15 3h5q.825 0 1.413.588T22 5v1q0 .575-.275 1.013T21 7.725V12q.825 0 1.413.588T23 14v5q0 .825-.587 1.413T21 21zm13-9h3V8h-3zM5 12h3V8H5z',
+  supercharger:
+    'M11.25 16.95V13.8l-2.225 2.225q.5.35 1.063.575t1.162.35m1.5-.025q.6-.1 1.163-.325t1.062-.575L12.75 13.8zm3.275-1.95q.35-.5.563-1.062t.337-1.163H13.8zM13.8 11.25h3.125q-.125-.575-.338-1.137t-.562-1.063zm-1.05-1.05l2.225-2.225q-.5-.35-1.063-.575t-1.162-.35zm-.038 2.513Q13 12.425 13 12t-.288-.712T12 11t-.712.288T11 12t.288.713T12 13t.713-.288M11.25 10.2V7.075q-.6.1-1.162.325t-1.063.575zm-4.175 1.05H10.2l-2.225-2.2q-.35.5-.575 1.05t-.325 1.15m.9 3.725L10.2 12.75H7.05q.125.6.35 1.163t.575 1.062M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z',
+  waterPump:
+    'm12 9l-.85 1.25q-.675 1.05-.913 1.613T10 13q0 .825.588 1.413T12 15t1.413-.587T14 13q0-.575-.238-1.137t-.912-1.613zm-4.425 7.425Q5.75 14.6 5.75 12t1.825-4.425T12 5.75t4.425 1.825T18.25 12t-1.825 4.425T12 18.25t-4.425-1.825M21 12v-1h-1.325q-.275-2-1.437-3.588T15.325 5H21V4h2v8zM1 20v-8h2v1h1.325q.275 2 1.438 3.588T8.675 19H3v1z',
+  pipe: 'M16 9v2H8V9h2V8H4v2H2V2h2v2h8a2 2 0 0 1 2 2v3zm-6 6v3a2 2 0 0 0 2 2h8v2h2v-8h-2v2h-6v-1h2v-2H8v2z'
+}
 
-const { ZmTable, ZmTableColumn } = useTableComponents<List>()
+const nodesData = [
+  { id: 'ac1', title: '空压机', x: 40, y: 40, icon: ICONS.compressor },
+  { id: 'ac2', title: '空压机', x: 40, y: 280, icon: ICONS.compressor },
+  { id: 'ac3', title: '空压机', x: 40, y: 520, icon: ICONS.compressor },
+  { id: 'Air', title: '空气处理橇', x: 280, y: 280, icon: ICONS.airSkid },
+  { id: 'PSA', title: 'PSA', x: 520, y: 280, icon: ICONS.psa }, // 注意:原代码这里标题写的是空气处理橇,但图标是PSA,请确认是否需改名
+  { id: 'supercharger', title: '增压机', x: 760, y: 280, icon: ICONS.supercharger },
+  { id: 'waterpump1', title: '注水泵', x: 520, y: 680, icon: ICONS.waterPump },
+  { id: 'waterpump2', title: '注水泵', x: 520, y: 920, icon: ICONS.waterPump },
+  { id: 'casing', title: '套管', x: 1220, y: 680, icon: ICONS.pipe },
+  { id: 'olitube', title: '油管', x: 1220, y: 920, icon: ICONS.pipe }
+]
 
-const realValue = (value: string) => {
-  const option = getDictOptions(DICT_TYPE.PMS_ASSET_PROPERTY).find((item) => item.value === value)
-  return option?.label || value
-}
+const edgesData = [
+  // 三个空压机流向空气处理橇
+  { source: 'ac1', target: 'Air' },
+  { source: 'ac2', target: 'Air' },
+  { source: 'ac3', target: 'Air' },
+
+  // 空气处理橇 -> PSA -> 增压机
+  { source: 'Air', target: 'PSA' },
+  { source: 'PSA', target: 'supercharger' },
+
+  { source: 'supercharger', target: 'casing' },
+  { source: 'supercharger', target: 'olitube' },
+
+  { source: 'waterpump1', target: 'casing' },
+  { source: 'waterpump2', target: 'olitube' }
+]
+
+onMounted(() => {
+  if (!containerRef.value) return
+
+  graph = new Graph({
+    container: containerRef.value,
+    autoResize: true,
+    background: { color: '#0B1120' }, // 建议配合高科技风格改为暗色背景
+    grid: {
+      visible: true,
+      type: 'dot',
+      size: 20,
+      args: { color: '#1F3A5F', thickness: 2 } // 网格也调整为暗色系
+    },
+    mousewheel: false
+  })
+
+  // 4. 遍历数据批量生成节点
+  nodesData.forEach((node) => {
+    graph!.addNode({
+      id: node.id,
+      shape: 'custom-node',
+      x: node.x,
+      y: node.y,
+      width: 150,
+      height: 160,
+      attrs: {
+        body: commonNodeAttrs.body,
+        icon: { ...commonNodeAttrs.icon, d: node.icon },
+        title: { ...commonNodeAttrs.title, text: node.title }
+      }
+    })
+  })
+  edgesData.forEach((edge) => {
+    graph!.addEdge({
+      // 1. 核心优化:固定线条的起点和终点位置
+      source: {
+        cell: edge.source,
+        anchor: 'right' // 强制从右侧出
+      },
+      target: {
+        cell: edge.target,
+        anchor: 'left' // 强制从左侧入
+      },
+
+      // 2. 核心优化:使用曼哈顿路由规划线条走向,避免斜线穿插
+      router: {
+        name: 'manhattan',
+        args: {
+          padding: 30, // 线条拐弯处距离节点的缓冲间距
+          startDirections: ['right'], // 限制首段线条向右
+          endDirections: ['left'] // 限制末段线条向左
+        }
+      },
+
+      // 3. 连接器:在直角拐弯处增加大圆角,完美代替原先的 smooth,保留流体感
+      connector: {
+        name: 'rounded',
+        args: { radius: 24 } // 圆角半径可以根据喜好调大调小
+      },
+      markup: [
+        {
+          tagName: 'path',
+          selector: 'line'
+        }
+      ],
+      attrs: {
+        line: {
+          connection: true,
+          strokeWidth: 5,
+          strokeLinecap: 'round',
+
+          targetMarker: {
+            name: 'classic',
+            size: 16,
+            fill: '#00EAFF',
+            stroke: 'none',
+            offset: -4
+          },
+
+          strokeDasharray: '10, 20',
+          fill: 'none',
+
+          stroke: {
+            type: 'linearGradient',
+            stops: [
+              { offset: '0%', color: '#0A1E3C', opacity: 0.8 }, // 起点:与节点背景融合的暗色
+              { offset: '50%', color: '#0077FF', opacity: 0.8 }, // 中段:纯正科技蓝
+              { offset: '100%', color: '#00EAFF', opacity: 1 } // 终点:高亮青色,能量注入箭头
+            ]
+          }
+        }
+      },
+
+      animation: [
+        [
+          {
+            'attrs/line/opacity': [0.7, 1]
+          },
+          {
+            duration: 1000,
+            fill: 'forwards',
+            direction: 'alternate',
+            iterations: Infinity
+          }
+        ],
+        [
+          {
+            'attrs/line/strokeDashoffset': [30, 0]
+          },
+          {
+            duration: 500, // 800ms 匀速流动,不会太晃眼
+            iterations: Infinity
+          }
+        ]
+      ]
+    })
+  })
+})
+
+// 5. 页面销毁时清理画布,释放内存
+onUnmounted(() => {
+  if (graph) {
+    graph.dispose()
+  }
+})
 </script>
 
 <template>
-  <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col mt-4">
-    <zm-table
-      :data="list"
-      :loading="loading"
-      :handle-query="getList"
-      v-model:sorting-fields="query.sortingFields"
-    >
-      <zm-table-column
-        zm-filterable
-        v-model:filter-model-value="query.yfDeviceCode"
-        zm-sortable
-        prop="yfDeviceCode"
-        label="油服编码"
-      />
-      <zm-table-column zm-filterable zm-sortable prop="deviceCode" label="历史编码" />
-      <zm-table-column zm-filterable zm-sortable prop="deviceName" label="设备名称" />
-      <zm-table-column zm-filterable zm-sortable prop="deptName" label="所在部门" />
-      <zm-table-column zm-filterable zm-sortable prop="deviceStatus" label="设备状态" />
-      <zm-table-column
-        zm-filterable
-        zm-sortable
-        prop="assetProperty"
-        label="资产性质"
-        :real-value="realValue"
-      >
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.PMS_ASSET_PROPERTY" :value="scope.row.assetProperty" />
-        </template>
-      </zm-table-column>
-      <zm-table-column zm-filterable zm-sortable prop="assetClassName" label="资产类别" />
-      <zm-table-column zm-filterable zm-sortable prop="manufacturer" label="制造商" />
-      <zm-table-column zm-filterable zm-sortable prop="brandName" label="品牌" />
-      <zm-table-column zm-filterable zm-sortable prop="model" label="规格型号" />
-      <zm-table-column zm-filterable zm-sortable prop="chargeName" label="负责人" />
-      <zm-table-column zm-filterable zm-sortable prop="useProject" label="使用项目" />
-      <zm-table-column zm-filterable zm-sortable prop="assetOwnership" label="资产归属" />
-    </zm-table>
+  <div
+    class="h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div
+      class="w-full h-full border-1 border-solid border-gray-300 rounded-lg shadow"
+      ref="containerRef"
+    ></div>
   </div>
 </template>
+
+<style scoped></style>