diff --git a/global.types/env.d.ts b/global.types/env.d.ts index 265b871..2e33c62 100644 --- a/global.types/env.d.ts +++ b/global.types/env.d.ts @@ -1,7 +1,8 @@ /// declare module '*.vue' { - import { ComponentOptions } from 'vue' - const componentOptions: ComponentOptions - export default componentOptions + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component } -declare module "element-plus/dist/locale/zh-cn.mjs"; \ No newline at end of file +declare module "element-plus/dist/locale/zh-cn.mjs"; + diff --git a/src/api/module/transfer/index.ts b/src/api/module/transfer/index.ts index d615538..f0889a2 100644 --- a/src/api/module/transfer/index.ts +++ b/src/api/module/transfer/index.ts @@ -63,11 +63,11 @@ export interface IPointGroupParams { export interface IPointGroupOV { - id: number, + id: number | string, ip: string, name: string, port: number, - slave_addr: number, + slave_addr: number | string, type: string, children?: IPointGroupOV[] } diff --git a/src/hooks/useG6/index.ts b/src/hooks/useG6/index.ts new file mode 100644 index 0000000..28d1d8b --- /dev/null +++ b/src/hooks/useG6/index.ts @@ -0,0 +1,103 @@ +import { merge } from "lodash-es"; +import type { IUseG6Options } from "./useG6"; +import { VueNode } from "g6-extension-vue"; +import { GenerateGraphData, MyLineEdge } from "./utils"; +import { + ExtensionCategory, + Graph, + type GraphOptions, + register, + type PluginOptions +} from "@antv/g6"; + +export function useG6({ + tree, + nodeComponent, + nodeSize, + layoutType = 'TB' + }: IUseG6Options, + plugins?: PluginOptions +) { + + register(ExtensionCategory.NODE, 'vue-node', VueNode); + register(ExtensionCategory.EDGE, 'my-line-edge', MyLineEdge); + + const container = ref() + const graphData = new GenerateGraphData(tree) + + const LR = () => ({ + layout: { + type: 'compact-box', + direction: 'LR', + getHeight: function getHeight() { + return 32; + }, + getWidth: function getWidth() { + return 32; + }, + getVGap: function getVGap() { + return 10; + }, + getHGap: function getHGap() { + return 100; + }, + }, + node: { + style: { + ports: [{ placement: 'right' }, { placement: 'left' }], + } + } + }) + + const TB = () => ({ + layout: { + type: 'antv-dagre', + }, + node: { + style: { + size: nodeSize, + dx: -nodeSize[0] / 2, + dy: -nodeSize[1] / 2, + ports: [{ placement: 'top' }, { placement: 'bottom' }], + } + } + }) + + const options: GraphOptions = { + container: container.value!, + padding: 50, + autoFit: 'view', + node: { + type: 'vue-node', + style: { + component: (data: any) => { + return h(nodeComponent, { data: data }) + }, + }, + }, + edge: { + type: 'my-line-edge', + style: { + radius: 10, + router: { + type: 'orth', + }, + }, + }, + data: { + nodes: graphData.getNodes(), + edges: graphData.getEdges() + }, + plugins, + behaviors: ['drag-canvas', 'zoom-canvas'], + } + + const mergeOption = merge({}, options, layoutType === 'LR' ? LR() : TB()) + + const graph = new Graph(Object.assign(options, mergeOption)); + + return { + container, + graph + } +} \ No newline at end of file diff --git a/src/hooks/useG6/useG6.d.ts b/src/hooks/useG6/useG6.d.ts new file mode 100644 index 0000000..04328c0 --- /dev/null +++ b/src/hooks/useG6/useG6.d.ts @@ -0,0 +1,9 @@ +import type { Component } from "vue"; + + +interface IUseG6Options { + tree: T[], + nodeComponent: Component, + nodeSize: [number, number] + layoutType?: 'LR' | 'TB' +} \ No newline at end of file diff --git a/src/hooks/useG6/utils/GenerateGraphData.ts b/src/hooks/useG6/utils/GenerateGraphData.ts new file mode 100644 index 0000000..c5f89e5 --- /dev/null +++ b/src/hooks/useG6/utils/GenerateGraphData.ts @@ -0,0 +1,49 @@ +import type { EdgeData, NodeData } from "@antv/g6"; +import type { Device, MyNodeData } from "@/types/device"; + +export class GenerateGraphData { + private readonly devices: Device[] = []; + private nodes: MyNodeData[] = []; + private edges: EdgeData[] = []; + + constructor(devices: Device[]) { + this.devices = devices; + this.generateNodeData() + this.generateEdgeData(); + } + + private generateNodeData() { + for (const device of this.devices) { + this.nodes.push({ + id: String(device.name), + label: device.name, + data: Object.assign(device, { + // typeString: DeviceType[device.type], + }), + }) + } + } + + private generateEdgeData() { + for (const node of this.nodes) { + const customData: Device = node.data + const findParentNode: Device = this.nodes.some((n: MyNodeData) => n.id === String(customData.parentName)); + + if (findParentNode) { + this.edges.push({ + source: String(customData.parentName), + target: String(node.id), + data: customData, + }) + } + } + } + + getNodes(): NodeData[] { + return this.nodes as NodeData[]; + } + + getEdges(): EdgeData[] { + return this.edges; + } +} diff --git a/src/hooks/useG6/utils/MyLineEdge.ts b/src/hooks/useG6/utils/MyLineEdge.ts new file mode 100644 index 0000000..b30138c --- /dev/null +++ b/src/hooks/useG6/utils/MyLineEdge.ts @@ -0,0 +1,64 @@ +import { Polyline } from '@antv/g6'; +import { Circle } from '@antv/g'; +import { subStyleProps } from '@antv/g6'; +import type { Device } from "@/types/device"; + +export class MyLineEdge extends Polyline { + getMarkerStyle(attributes: object) { + return { + r: 4, + fill: '#58C448', + offsetPath: this.shapeMap.key, ...subStyleProps(attributes, 'marker') + }; + } + + onCreate() { + const marker = this.upsert('marker', Circle, this.getMarkerStyle(this.attributes), this)!; + + const prev = this.context.model.getRelatedEdgesData(this.sourceNode.id) // 获取源节点的相关边数据 + .find((edge) => edge.target === this.targetNode.id) as Device; + + + const depth = prev!.data!.depth; + const delay = depth * 3000; // 每级延迟 3 秒 + marker.animate( + [{ offsetDistance: 0 }, { offsetDistance: 1 }], + { + duration: 3000, + iterations: Infinity, + delay, + } + ); + + // 涟漪效果:在 marker 外套一个圆 + const ripple = this.upsert( + 'ripple', + Circle, + { + r: 4, // 初始半径和 marker 一样 + stroke: '#58C448', + lineWidth: 2, + fill: 'none', + offsetPath: this.shapeMap.key, + }, + this + )!; + + // 涟漪动画:半径变大 + 透明度变小 + ripple.animate( + [ + { offsetDistance: 0, r: 4, opacity: 0.7 }, + { offsetDistance: 1, r: 6, opacity: 0 }, + ], + { + duration: 3000, + iterations: Infinity, + delay, + } + ); + // marker.animate([{ offsetDistance: 0 }, { offsetDistance: 1 }], { + // duration: 3000, + // iterations: Infinity, + // }); + } +} diff --git a/src/hooks/useG6/utils/index.ts b/src/hooks/useG6/utils/index.ts new file mode 100644 index 0000000..23d5a6e --- /dev/null +++ b/src/hooks/useG6/utils/index.ts @@ -0,0 +1,4 @@ +export * from './GenerateGraphData' +export * from './MyLineEdge' + + diff --git a/src/router/index.ts b/src/router/index.ts index 20b9f46..c1c6fbc 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -32,16 +32,16 @@ export const defaultRouter = [ icon: '', }, }, - // { - // path: '/station/topology', - // name: 'data-topology', - // component: () => import('@/views/stationData/transfer/index.vue'), - // meta: { - // title: '数据拓扑', - // isShow: false, - // icon: '', - // }, - // }, + { + path: '/station/topology', + name: 'data-topology', + component: () => import('@/views/stationData/topology/index.vue'), + meta: { + title: '数据拓扑', + isShow: false, + icon: '', + }, + }, // 固件上传 { path: '/firmware-upload', diff --git a/src/types/device.ts b/src/types/device.ts new file mode 100644 index 0000000..e7f5dd3 --- /dev/null +++ b/src/types/device.ts @@ -0,0 +1,5 @@ +import type { NodeData } from "@antv/g6"; +export type Device = any +export interface MyNodeData extends Omit { + data?: Device; +} \ No newline at end of file diff --git a/src/views/layout/index.vue b/src/views/layout/index.vue index 4955935..ab5c063 100644 --- a/src/views/layout/index.vue +++ b/src/views/layout/index.vue @@ -94,7 +94,7 @@ const getIconClass = (icon: string) => { const { push, currentRoute } = useRouter() const activeMenu = computed(() => { const { meta, path } = unref(currentRoute) - if (path.includes('/station/data-transfer')) { + if (['/station/topology', '/station/data-transfer'].includes(path)) { return '/station' } return path diff --git a/src/views/stationData/transfer/components/newDataChart.vue b/src/views/stationData/component/newDataChart.vue similarity index 95% rename from src/views/stationData/transfer/components/newDataChart.vue rename to src/views/stationData/component/newDataChart.vue index 80b6a90..0f55ae2 100644 --- a/src/views/stationData/transfer/components/newDataChart.vue +++ b/src/views/stationData/component/newDataChart.vue @@ -1,8 +1,13 @@ diff --git a/src/views/stationData/component/pointCheckbox.vue b/src/views/stationData/component/pointCheckbox.vue new file mode 100644 index 0000000..e3d5b60 --- /dev/null +++ b/src/views/stationData/component/pointCheckbox.vue @@ -0,0 +1,87 @@ + + + + + + + + \ No newline at end of file diff --git a/src/views/stationData/topology/components/Node.vue b/src/views/stationData/topology/components/Node.vue new file mode 100644 index 0000000..636a0b4 --- /dev/null +++ b/src/views/stationData/topology/components/Node.vue @@ -0,0 +1,114 @@ + + + + + \ No newline at end of file diff --git a/src/views/stationData/topology/components/detailDrawer.vue b/src/views/stationData/topology/components/detailDrawer.vue new file mode 100644 index 0000000..94d0841 --- /dev/null +++ b/src/views/stationData/topology/components/detailDrawer.vue @@ -0,0 +1,336 @@ + + + + + diff --git a/src/views/stationData/topology/index.vue b/src/views/stationData/topology/index.vue new file mode 100644 index 0000000..0bdae7b --- /dev/null +++ b/src/views/stationData/topology/index.vue @@ -0,0 +1,145 @@ + + + + + \ No newline at end of file diff --git a/src/views/stationData/topology/utils/index.ts b/src/views/stationData/topology/utils/index.ts new file mode 100644 index 0000000..c9579f6 --- /dev/null +++ b/src/views/stationData/topology/utils/index.ts @@ -0,0 +1,22 @@ +import { flatMap } from 'lodash-es' +import type { Device } from "@/types/device"; + +export const NODE_SIZE: [number, number] = [130, 36] + +export enum DeviceType { + emu = "emu",// 前端默认跟类型 + mce = "mce", + bms = "bms", + bms_stack = "bms_stack", + pcs = "pcs", + bms_cluster = "bms_cluster", + bms_cell = "bms_cell" +} + +export function flattenTree(tree: Device[], depth = 0): Device[] { + return flatMap(tree, (node: Device,) => { + const { children, ...rest } = node + const current = { ...rest, depth } + return [current, ...(children ? flattenTree(children, depth + 1) : [])] + }) +} \ No newline at end of file diff --git a/src/views/stationData/transfer/components/PointGroupTree.vue b/src/views/stationData/transfer/components/PointGroupTree.vue index c9122a4..bbb32a5 100644 --- a/src/views/stationData/transfer/components/PointGroupTree.vue +++ b/src/views/stationData/transfer/components/PointGroupTree.vue @@ -2,8 +2,10 @@
- + - diff --git a/src/views/stationData/transfer/components/deviceDrawer.vue b/src/views/stationData/transfer/components/deviceDrawer.vue index b90b6b2..1b43d2d 100644 --- a/src/views/stationData/transfer/components/deviceDrawer.vue +++ b/src/views/stationData/transfer/components/deviceDrawer.vue @@ -5,12 +5,17 @@ :before-close="handleBeforeClose" @opened="onDrawerOpened">
- +
@@ -29,7 +34,7 @@
查询数据 -
@@ -38,10 +43,8 @@