|
|
@@ -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>
|