Browse Source

feat(topo): 实现动画

main
betaqi 4 weeks ago
parent
commit
93117269de
  1. 2
      global.types/auto-imports.d.ts
  2. 2
      global.types/components.d.ts
  3. 4550
      package-lock.json
  4. 10
      package.json
  5. 10
      src/router/index.ts
  6. 15
      src/views/layout/index.vue
  7. 1
      src/views/stationData/transferData.vue
  8. 40
      src/views/testG6/index.vue
  9. 51
      src/views/testG6/utils/MyLineEdge.ts
  10. 226
      src/views/testG6/utils/data.ts
  11. 102
      src/views/testG6/utils/index.ts
  12. 10
      tsconfig.json

2
global.types/auto-imports.d.ts vendored

@ -72,6 +72,6 @@ declare global {
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue') import('vue')
} }

2
global.types/components.d.ts vendored

@ -43,7 +43,7 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }
export interface ComponentCustomProperties { export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective'] vLoading: typeof import('element-plus/es')['ElLoadingDirective']
} }
} }

4550
package-lock.json generated

File diff suppressed because it is too large Load Diff

10
package.json

@ -13,6 +13,8 @@
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
}, },
"dependencies": { "dependencies": {
"@antv/g-svg": "^2.0.42",
"@antv/g6": "^5.0.49",
"@types/qs": "^6.9.18", "@types/qs": "^6.9.18",
"@unocss/reset": "^66.0.0", "@unocss/reset": "^66.0.0",
"axios": "^1.8.4", "axios": "^1.8.4",
@ -32,11 +34,11 @@
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.310", "@iconify/json": "^2.2.310",
"@tsconfig/node22": "^22.0.0", "@tsconfig/node22": "^22.0.0",
"@types/node": "^22.13.5", "@types/node": "^24.2.1",
"@unocss/preset-icons": "^66.0.0", "@unocss/preset-icons": "^66.0.0",
"@unocss/preset-rem-to-px": "^66.0.0", "@unocss/preset-rem-to-px": "^66.0.0",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^4.1.1", "@vitejs/plugin-vue-jsx": "^4.2.0",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^7.0.2", "npm-run-all2": "^7.0.2",
"sass": "^1.85.0", "sass": "^1.85.0",
@ -46,7 +48,7 @@
"unplugin-auto-import": "^19.1.0", "unplugin-auto-import": "^19.1.0",
"unplugin-icons": "^22.1.0", "unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.4.0", "unplugin-vue-components": "^28.4.0",
"vite": "^7.1.2", "vite": "^6.3.5",
"vite-plugin-vue-devtools": "^7.7.2", "vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.2" "vue-tsc": "^2.2.2"
} }

10
src/router/index.ts

@ -52,6 +52,16 @@ export const defaultRouter = [
isShow: true, isShow: true,
icon: 'i-mingcute:task-line', icon: 'i-mingcute:task-line',
} }
},
{
path: '/testG6',
name: 'testG6',
component: () => import('@/views/testG6/index.vue'),
meta: {
title: '测试G6',
isShow: true,
icon: 'i-mingcute:task-line',
}
} }
], ],
}, },

15
src/views/layout/index.vue

@ -16,17 +16,20 @@
EMU-主机运维平台 EMU-主机运维平台
</h2> </h2>
</RouterLink> </RouterLink>
<el-menu class="layout-menu" :default-active="activeMenu" @select="menuSelect" router :collapse="isCollapse"> <el-menu class="layout-menu" :default-active="activeMenu" @select="menuSelect" router
:collapse="isCollapse">
<template v-for="router in menuList"> <template v-for="router in menuList">
<template v-if="router.meta?.isShow"> <template v-if="router.meta?.isShow">
<el-sub-menu v-if="router?.children?.filter((item: any) => item.meta?.isShow).length" :index="router.path" <el-sub-menu v-if="router?.children?.filter((item: any) => item.meta?.isShow).length"
:index="router.path"
:key="router.path"> :key="router.path">
<template #title> <template #title>
<div :class="router.meta.icon" class="menu-icon"></div> <div :class="router.meta.icon" class="menu-icon"></div>
<span>{{ router.meta.title }}</span> <span>{{ router.meta.title }}</span>
</template> </template>
<template v-for="child in router?.children"> <template v-for="child in router?.children">
<el-menu-item v-if="child?.meta?.isShow" :key="child.path" :index="`${router.path}/${child.path}`"> <el-menu-item v-if="child?.meta?.isShow" :key="child.path"
:index="`${router.path}/${child.path}`">
<div :class="child.meta.icon" class="menu-icon"></div> <div :class="child.meta.icon" class="menu-icon"></div>
<span>{{ child.meta.title }}</span> <span>{{ child.meta.title }}</span>
</el-menu-item> </el-menu-item>
@ -52,7 +55,7 @@
</div> </div>
</el-header> </el-header>
<main class="main-wrap"> <main class="main-wrap">
<RouterView /> <RouterView/>
</main> </main>
</el-container> </el-container>
</el-container> </el-container>
@ -63,6 +66,7 @@
import { useTheme } from '@/composables/useTheme' import { useTheme } from '@/composables/useTheme'
import { defaultRouter } from '@/router' import { defaultRouter } from '@/router'
import dayjs from 'dayjs' import dayjs from 'dayjs'
const env = import.meta.env const env = import.meta.env
const unfold = 'i-icon-park-outline:menu-unfold' const unfold = 'i-icon-park-outline:menu-unfold'
const fold = 'i-icon-park-outline:menu-fold' const fold = 'i-icon-park-outline:menu-fold'
@ -74,9 +78,8 @@ const menuList = computed<any[]>(() => {
if (env.VITE_APP_ENV !== 'local') { if (env.VITE_APP_ENV !== 'local') {
data = data.filter(item => !['firmware-upload', 'task'].includes(item.name)) data = data.filter(item => !['firmware-upload', 'task'].includes(item.name))
} }
console.log(data)
return data return data
} }
) )
const circleUrl = ref( const circleUrl = ref(

1
src/views/stationData/transferData.vue

@ -148,6 +148,7 @@ import DeviceDrawer from './components/deviceDrawer.vue'
import { getFirmwarePath } from '@/api/module/firmware' import { getFirmwarePath } from '@/api/module/firmware'
import { createTask, type TaskCreateParams } from '@/api/module/taks' import { createTask, type TaskCreateParams } from '@/api/module/taks'
import EdfsWrap from "@/components/Edfs-wrap.vue";
const env = import.meta.env const env = import.meta.env
const onLineTransferDlgRef = ref<typeof OnLineTransferDlg>() const onLineTransferDlgRef = ref<typeof OnLineTransferDlg>()
const router = useRouter() const router = useRouter()

40
src/views/testG6/index.vue

@ -0,0 +1,40 @@
<template>
<div>Use G6 in Vue</div>
<div id="container" class="w-full h-full"></div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { ExtensionCategory, Graph, register } from '@antv/g6';
import { MyLineEdge } from './utils/MyLineEdge';
import DeviceData from './utils/data.ts';
import { flattenTree, GenerateGraphData } from "@/views/testG6/utils";
onMounted(() => {
register(ExtensionCategory.EDGE, 'my-line-edge', MyLineEdge);
const device = flattenTree(DeviceData)
console.log(device)
const graphData = new GenerateGraphData(device)
const graph = new Graph({
container: document.getElementById('container')!,
layout: {
type: 'dagre',
},
edge: {
type: 'my-line-edge',
},
data: {
nodes: graphData.getNodes(),
edges: graphData.getEdges()
},
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
});
console.log(graphData.getEdges())
graph.render();
});
</script>

51
src/views/testG6/utils/MyLineEdge.ts

@ -0,0 +1,51 @@
import { BaseEdge } from '@antv/g6';
import type { PathArray } from "@antv/util";
import { Circle } from '@antv/g';
import { subStyleProps } from '@antv/g6';
export class MyLineEdge extends BaseEdge {
getMarkerStyle(attributes: object) {
return {
r: 5,
fill: '#c3d5f9',
offsetPath: this.shapeMap.key, ...subStyleProps(attributes, 'marker')
};
}
onCreate() {
const marker = this.upsert('marker', Circle, this.getMarkerStyle(this.attributes), this)!;
marker.animate([{ offsetDistance: 0 }, { offsetDistance: 1 }], {
duration: 3000,
iterations: Infinity,
});
}
// 这里保持你的路径逻辑
protected getKeyPath(attrs: any): PathArray {
const { sourceNode, targetNode } = this;
const [sx, sy] = sourceNode.getPosition();
const [tx, ty] = targetNode.getPosition();
const [x1, y1] = sourceNode.getPosition();
const [x2, y2] = targetNode.getPosition();
// 固定的主干 X
const busX = attrs.busX ?? tx;
// return [
// ['M', x1, y1],
// ['L', x2, y2],
// ];
return [
['M', sx, sy],
['L', busX, sy],
['L', busX, ty],
['L', tx, ty],
];
}
}

226
src/views/testG6/utils/data.ts

@ -0,0 +1,226 @@
export default [{
"id": 398,
"sn": "ems1",
"name": "安庆滨江夜市站",
"sort": 0,
"parentId": 0,
"deviceId": "t00510398",
"dictName": "device_one_entity_type",
"level": 0,
"type": 0,
"description": null,
"children": [
{
"id": 400,
"sn": "yiming-EMS1",
"name": "储能单元1",
"sort": 1,
"parentId": 398,
"deviceId": "t00510400",
"dictName": "device_two_entity_type",
"level": 1,
"type": 1,
"description": "",
"children": [
{
"id": 404,
"sn": "B48100B210TF36003E-bms",
"name": "BMS-1",
"sort": 1,
"parentId": 400,
"deviceId": "t00510404",
"dictName": "",
"level": 2,
"type": 2,
"description": "",
"children": null
},
{
"id": 408,
"sn": "B48100B210TF36003E-pcs",
"name": "PCS-1",
"sort": 1,
"parentId": 400,
"deviceId": "t00510408",
"dictName": "",
"level": 2,
"type": 3,
"description": "",
"children": null
}
]
},
{
"id": 401,
"sn": "yiming-EMS2",
"name": "储能单元2",
"sort": 1,
"parentId": 398,
"deviceId": "t00510401",
"dictName": "device_two_entity_type",
"level": 1,
"type": 1,
"description": "",
"children": [
{
"id": 405,
"sn": "B48100B210TF36003K-bms",
"name": "BMS-2",
"sort": 1,
"parentId": 401,
"deviceId": "t00510405",
"dictName": "",
"level": 2,
"type": 2,
"description": "",
"children": null
},
{
"id": 409,
"sn": "B48100B210TF36003K-pcs",
"name": "PCS-2",
"sort": 1,
"parentId": 401,
"deviceId": "t00510409",
"dictName": "",
"level": 2,
"type": 3,
"description": "",
"children": null
}
]
},
// {
// "id": 402,
// "sn": "yiming-EMS3",
// "name": "储能单元3",
// "sort": 1,
// "parentId": 398,
// "deviceId": "t00510402",
// "dictName": "device_two_entity_type",
// "level": 1,
// "type": 1,
// "description": "",
// "children": [
// {
// "id": 406,
// "sn": "B48100B210TF36002V-bms",
// "name": "BMS-3",
// "sort": 1,
// "parentId": 402,
// "deviceId": "t00510406",
// "dictName": "",
// "level": 2,
// "type": 2,
// "description": "",
// "children": null
// },
// {
// "id": 410,
// "sn": "B48100B210TF36002V-pcs",
// "name": "PCS-3",
// "sort": 1,
// "parentId": 402,
// "deviceId": "t00510410",
// "dictName": "",
// "level": 2,
// "type": 3,
// "description": "",
// "children": null
// }
// ]
// },
// {
// "id": 403,
// "sn": "yiming-EMS4",
// "name": "储能单元4",
// "sort": 1,
// "parentId": 398,
// "deviceId": "t00510403",
// "dictName": "device_two_entity_type",
// "level": 1,
// "type": 1,
// "description": "",
// "children": [
// {
// "id": 407,
// "sn": "B48100B210TF360029-bms",
// "name": "BMS-4",
// "sort": 1,
// "parentId": 403,
// "deviceId": "t00510407",
// "dictName": "",
// "level": 2,
// "type": 2,
// "description": "",
// "children": null
// },
// {
// "id": 411,
// "sn": "B48100B210TF360029-pcs",
// "name": "PCS-4",
// "sort": 1,
// "parentId": 403,
// "deviceId": "t00510411",
// "dictName": "",
// "level": 2,
// "type": 3,
// "description": "",
// "children": null
// }
// ]
// },
// {
// "id": 412,
// "sn": "B48100B210TF36003E-em",
// "name": "电量信息-1",
// "sort": 1,
// "parentId": 398,
// "deviceId": "t00510412",
// "dictName": "device_two_entity_type",
// "level": 1,
// "type": 4,
// "description": "",
// "children": null
// },
// {
// "id": 413,
// "sn": "B48100B210TF36003K-em",
// "name": "电量信息-2",
// "sort": 1,
// "parentId": 398,
// "deviceId": "t00510413",
// "dictName": "device_two_entity_type",
// "level": 1,
// "type": 4,
// "description": "",
// "children": null
// },
// {
// "id": 414,
// "sn": "B48100B210TF36002V-em",
// "name": "电量信息-3",
// "sort": 1,
// "parentId": 398,
// "deviceId": "t00510414",
// "dictName": "device_two_entity_type",
// "level": 1,
// "type": 4,
// "description": "",
// "children": null
// },
// {
// "id": 415,
// "sn": "B48100B210TF360029-em",
// "name": "电量信息-4",
// "sort": 1,
// "parentId": 398,
// "deviceId": "t00510415",
// "dictName": "device_two_entity_type",
// "level": 1,
// "type": 4,
// "description": "",
// "children": null
// }
]
}]

102
src/views/testG6/utils/index.ts

@ -0,0 +1,102 @@
import { flatMap } from 'lodash-es'
import type { NodeData, EdgeData } from "@antv/g6";
type Device = any
export enum DeviceType {
Ems,
Ecu,
Bms,
Pcs,
Em,
'Em-Measure',
Tms,
Ffs,
Wpp,
Mppt,
Dgs,
Cac,
}
export function flattenTree(tree: Device[], dep = 0): Device[] {
return flatMap(tree, (node: Device,) => {
const { children, ...rest } = node
const current = { ...rest, dep }
return [current, ...(children ? flattenTree(children, dep + 1) : [])]
})
}
export class GenerateGraphData {
private readonly devices: Device[] = [];
private nodes: NodeData[] = [];
private edges: EdgeData[] = [];
constructor(devices: Device[]) {
this.devices = devices;
this.generateNodeData()
this.generateEdgeData();
}
private generateNodeData() {
for (const device of this.devices) {
const deviceChild = this.devices.filter((item: Device) => device.id === item.parentId)
this.nodes.push({
id: String(device.id),
label: device.name,
data: device
})
if (!!deviceChild.length) {
// 在每个有子节点的设备下插入一个虚拟节点
this.nodes.push({
id: `${device.id}-virtual`,
label: '',
type: '',
style: { opacity: 0 },
size: 1,
data: {
type: 'virtual-node',
parentId: String(device.id),
}
})
}
}
}
private generateEdgeData() {
for (const node of this.nodes) {
const customData: Device = node.data
const virtualId = `${customData.parentId}-virtual`;
const findParentNode: Device = this.nodes.find((n: NodeData) => n.id === customData.parentId);
if (!findParentNode) {
// 如果没有找到父节点,则可能是根节点或孤立节点
if (customData.type == DeviceType.Ems) continue;
this.edges.push({
source: virtualId,
target: node.id,
type: 'polyline',
data: customData
})
} else {
this.edges.push({
source: customData.type === 'virtual-node' ? customData.parentId : virtualId,
target: String(node.id),
type: 'polyline',
data: customData
})
}
}
}
getNodes(): NodeData[] {
return this.nodes;
}
getEdges(): EdgeData[] {
return this.edges;
}
}

10
tsconfig.json

@ -12,6 +12,12 @@
"types": [ "types": [
"node" "node"
], ],
"moduleResolution": "node" "moduleResolution": "node",
} "typeRoots": [
"node_modules/@types"
]
},
"include": [
"src/**/*"
]
} }
Loading…
Cancel
Save