import { Component, OnInit, TemplateRef } from '@angular/core';
import { _HttpClient } from '@delon/theme';
import { of } from 'rxjs/observable/of';
import { delay } from 'rxjs/operators';
import { Observable } from 'rxjs/Observable';
import { NzMessageService } from 'ng-zorro-antd';
import {
NzTreeNode,
NzFormatEmitEvent,
NzFormatBeforeDropEvent,
NzDropdownContextComponent,
NzDropdownService,
NzMenuItemDirective,
} from 'ng-zorro-antd';
import { TopoServiceService } from './topo-service.service';
@Component({
selector: 'app-device-tree',
template: `
`,
})
// (nzContextMenu)="mouseRight($event,template)"
export class DeviceTreeComponent implements OnInit {
private dropdown: NzDropdownContextComponent;
private selNode: any;
nodes = [];
expandKeys = [];
constructor(
private http: _HttpClient,
private nzDropdownService: NzDropdownService,
private topoServiceService: TopoServiceService,
private msg: NzMessageService,
) {}
draggable: any = true;
ngOnInit() {
this.http.get('/soc/deviceinfo/topotree').subscribe((res: any) => {
for (let ii = 0; ii < res.length; ii++) {
this.nodes.push(new NzTreeNode(res[ii]));
this.expandKeys.push(res[ii].key);
}
});
}
mouseRight($event: NzFormatEmitEvent, template: TemplateRef): void {
this.selNode = $event.node;
this.dropdown = this.nzDropdownService.create($event.event, template);
}
mouseAction(name: string, e: NzFormatEmitEvent): void {
e.event.cancelBubble = true;
}
/**
* 监听鼠标离开树dom,当是在拖拽状态返回一个true,让go图形模块接收这个状态,
* 确认是否要放置一个图形,以及要放置的图形
* @param e
*/
onMouseleave(e) {}
beforeDrop(arg: NzFormatBeforeDropEvent): Observable {
// if insert node into another node, wait 1s
if (arg.pos <= 1) {
return of(false);
}
}
/**
* 添加图形
*/
addClick(e) {
if (this.selNode) {
this.topoServiceService.StatusMission(this.selNode);
delete this.selNode;
}
}
close(e: NzMenuItemDirective): void {
this.dropdown.close();
}
/**
* 处理拖拽时的逻辑 父节点不能拖拽,组件不支持此类定制,此处给出提示
* @param e
*/
onDragStart(e) {
if (e.dragNode.children.length) {
delete this.selNode;
this.msg.error('请选择子类类型!');
} else {
this.selNode = e.dragNode;
}
}
}
通过onMousemove事件将左树的拖拽操作和右边流程图建立联系,实现拖拽绘制。界面如下图
代码:
import {
Component,
OnInit,
ViewChild,
ElementRef,
Input,
Output,
EventEmitter,
} from '@angular/core';
import { HttpService } from '@shared/http/http.service';
import { HttpHeaders } from '@angular/common/http';
import { NzMessageService } from 'ng-zorro-antd';
import * as go from 'assets/go.js';
import { NzTreeNode } from 'ng-zorro-antd';
import { TopoServiceService } from './topo-service.service';
@Component({
selector: 'app-topology',
templateUrl: './topology.component.html',
})
export class TopologyComponent implements OnInit {
private diagram: go.Diagram = new go.Diagram();
private imageMap = {
'1@1': 'assets/img/topo/switch.png',
'1@2': 'assets/img/topo/router.png',
'1@3': 'assets/img/topo/switch.png',
'1@4': 'assets/img/topo/switch.png',
'1@5': 'assets/img/topo/switch.png',
'1@6': 'assets/img/topo/switch.png',
'1@7': 'assets/img/topo/switch.png',
'2@1': 'assets/img/topo/switch.png',
'2@2': 'assets/img/topo/switch.png',
'2@3': 'assets/img/topo/switch.png',
'2@4': 'assets/img/topo/switch.png',
'2@5': 'assets/img/topo/fw.png',
'2@6': 'assets/img/topo/switch.png',
'2@7': 'assets/img/topo/switch.png',
'2@8': 'assets/img/topo/switch.png',
'2@9': 'assets/img/topo/switch.png',
'2@10': 'assets/img/topo/switch.png',
'2@11': 'assets/img/topo/switch.png',
'2@12': 'assets/img/topo/switch.png',
'2@13': 'assets/img/topo/switch.png',
'2@14': 'assets/img/topo/switch.png',
'2@15': 'assets/img/topo/security-audit.png',
'2@16': 'assets/img/topo/switch.png',
'2@17': 'assets/img/topo/switch.png',
'2@18': 'assets/img/topo/switch.png',
'2@19': 'assets/img/topo/switch.png',
'2@20': 'assets/img/topo/switch.png',
'3@1': 'assets/img/topo/switch.png',
'3@2': 'assets/img/topo/switch.png',
'3@3': 'assets/img/topo/switch.png',
'3@4': 'assets/img/topo/switch.png',
'3@5': 'assets/img/topo/switch.png',
'3@6': 'assets/img/topo/switch.png',
'4@1': 'assets/img/topo/switch.png',
'4@2': 'assets/img/topo/switch.png',
'4@3': 'assets/img/topo/switch.png',
'4@4': 'assets/img/topo/switch.png',
'5@1': 'assets/img/topo/switch.png',
'5@2': 'assets/img/topo/switch.png',
'5@3': 'assets/img/topo/switch.png',
'5@4': 'assets/img/topo/switch.png',
'5@5': 'assets/img/topo/switch.png',
'5@6': 'assets/img/topo/switch.png',
'6@1': 'assets/img/topo/plc.png',
'6@2': 'assets/img/topo/dcs.png',
'6@3': 'assets/img/topo/switch.png',
'6@4': 'assets/img/topo/switch.png',
'6@5': 'assets/img/topo/switch.png',
'6@6': 'assets/img/topo/switch.png',
'7@1': 'assets/img/topo/switch.png',
'7@2': 'assets/img/topo/switch.png',
'7@3': 'assets/img/topo/switch.png',
'8@1': 'assets/img/topo/switch.png',
'8@2': 'assets/img/topo/switch.png',
'8@3': 'assets/img/topo/switch.png',
};
@ViewChild('diagramDiv') private diagramRef: ElementRef;
@ViewChild('appDeviceTree') appDeviceTree: any;
@Input()
get model(): go.Model {
return this.diagram.model;
}
set model(val: go.Model) {
this.diagram.model = val;
}
@Output() nodeSelected = new EventEmitter();
@Output() modelChanged = new EventEmitter();
// 选中的左侧树节点
private selNode: NzTreeNode;
// 判断操作ID
private isOperation_id: any;
// 查询url
private searchUrl = 'api/search/network_topology_index/network_topology_type';
// 新增url
private saveUrl = 'api/es/create/network_topology_index/network_topology_type';
// 更新url
private updat3eUrl = 'api/es/update/network_topology_index/network_topology_type';
loading = false;
constructor(
private http: HttpService,
private topoServiceService: TopoServiceService,
private msg: NzMessageService,
) {
const $ = go.GraphObject.make;
const me = this;
// 创建绘图区域
this.diagram.initialContentAlignment = go.Spot.Center; // 绘制中心位置
this.diagram.allowDrop = true; // 是否可以拖拽
this.diagram.undoManager.isEnabled = true; // 是否可撤销
// 绘制背景网格
// this.diagram.grid = $(
// go.Panel,
// 'Grid',
// $(go.Shape, 'LineH', { stroke: 'lightgray', strokeWidth: 0.5 }),
// $(go.Shape, 'LineH', { stroke: 'gray', strokeWidth: 0.5, interval: 10 }),
// $(go.Shape, 'LineV', { stroke: 'lightgray', strokeWidth: 0.5 }),
// $(go.Shape, 'LineV', { stroke: 'gray', strokeWidth: 0.5, interval: 10 }),
// );
// 设计节点选中事件
this.diagram.addDiagramListener('ChangedSelection', e => {
const node = e.diagram.selection.first();
this.nodeSelected.emit(node instanceof go.Node ? node : null);
});
this.diagram.addModelChangedListener(
e => e.isTransactionFinished && this.modelChanged.emit(e),
);
// 节点选中模板
const nodeSelectionAdornmentTemplate = $(
go.Adornment,
'Auto',
$(go.Shape, {
fill: null,
stroke: 'deepskyblue',
strokeWidth: 1.5,
strokeDashArray: [4, 2],
}),
$(go.Placeholder),
);
// 节点缩放模板
const nodeResizeAdornmentTemplate = $(
go.Adornment,
'Spot',
{ locationSpot: go.Spot.Right },
$(go.Placeholder),
$(go.Shape, {
alignment: go.Spot.TopLeft,
cursor: 'nw-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.Top,
cursor: 'n-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.TopRight,
cursor: 'ne-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.Left,
cursor: 'w-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.Right,
cursor: 'e-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.BottomLeft,
cursor: 'se-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.Bottom,
cursor: 's-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
$(go.Shape, {
alignment: go.Spot.BottomRight,
cursor: 'sw-resize',
desiredSize: new go.Size(6, 6),
fill: 'lightblue',
stroke: 'deepskyblue',
}),
);
// 定义节点模板
this.diagram.nodeTemplate = $(
go.Node,
'Vertical', // 样式控制
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
go.Point.stringify,
),
// 注册选中模板
{
selectable: true,
selectionAdornmentTemplate: nodeSelectionAdornmentTemplate,
},
{
// the Node.location is at the center of each node
// locationSpot: go.Spot.Center,
// isShadowed: true,
// shadowColor: '#888',
// handle mouse enter/leave events to show/hide the ports
mouseEnter: function(e, obj) {
me.showPorts(obj.part, true);
},
mouseLeave: function(e, obj) {
me.showPorts(obj.part, false);
},
},
// 图标设置
$(
go.Panel,
'Auto',
{ name: 'PANEL' },
new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(
go.Size.stringify,
),
$(
go.Picture,
{ width: 64, height: 64 },
{
portId: '',
fromLinkable: false, // 控制是否点击图标进行连线
toLinkable: false,
cursor: 'pointer',
},
new go.Binding('source', 'imageIcon'),
),
// 设置上下左右连线气泡
me.makePort('T', go.Spot.Top, false, true),
me.makePort('L', go.Spot.Left, true, true),
me.makePort('R', go.Spot.Right, true, true),
me.makePort('B', go.Spot.Bottom, true, false),
),
// 标题设置
$(
go.TextBlock,
{ stroke: 'white', editable: false },
new go.Binding('text', 'title'),
),
);
// 连线模板
this.diagram.linkTemplate = $(
go.Link,
{ relinkableFrom: true, relinkableTo: true },
{
routing: go.Link.AvoidsNodes,
curve: go.Link.JumpOver,
corner: 5,
toShortLength: 4,
},
new go.Binding('points').makeTwoWay(),
$(go.Shape, { isPanelMain: true, stroke: 'black', strokeWidth: 5 }),
$(go.Shape, { isPanelMain: true, stroke: 'gray', strokeWidth: 3 }),
$(go.Shape, {
isPanelMain: true,
stroke: 'white',
strokeWidth: 1,
name: 'PIPE',
strokeDashArray: [10, 10],
}),
// $(go.Shape, { toArrow: 'Triangle', fill: 'black', stroke: null }),
$(
go.Shape, // 指向箭头
{ toArrow: 'Standard', stroke: 'rgb(171,110,53)' },
),
);
// 响应左侧树操作
topoServiceService.Status$.subscribe(message => {
this.selNode = message;
if (this.selNode && this.selNode.level === 2) {
const keyarr = this.selNode.key.split('@');
const keytype = keyarr[0] + '@' + '1'; // keyarr[1];
const nodeData = {
key: this.selNode.key,
title: this.selNode.title,
color: 'white',
loc: this.getLoc(),
imageIcon: this.imageMap[keytype],
};
this.diagram.model.addNodeData(nodeData);
}
// this.diagram.div.querySelector('div').style['zIndex'] = 1;
});
this.loop();
}
ngOnInit() {
const me = this;
me.diagram.div = me.diagramRef.nativeElement;
// me.locationDom = me.diagram.div.querySelector('div').querySelector('div');
// me.locationDom.onmousemove = function (e) {
// me.locDom2 = e;
// };
// 获取拓扑图
this.http.get(this.searchUrl).subscribe((res: any) => {
if (res.data.length > 0) {
this.isOperation_id = res.data[0].id;
this.diagram.model = go.Model.fromJson(res.data[0].topoValue);
}
});
}
/**
* 绘制上下左右气泡
* @param name
* @param spot
* @param output
* @param input
* @returns {any}
*/
makePort(name, spot, output, input) {
// the port is basically just a small circle that has a white stroke when it is made visible
return go.GraphObject.make(go.Shape, 'Circle', {
fill: 'transparent',
stroke: null, // this is changed to 'white' in the showPorts function
desiredSize: new go.Size(8, 8),
alignment: spot,
alignmentFocus: spot, // align the port on the main Shape
portId: name, // declare this object to be a 'port'
fromSpot: spot,
toSpot: spot, // declare where links may connect at this port
fromLinkable: output,
toLinkable: input, // declare whether the user may draw links to/from here
cursor: 'pointer', // show a different cursor to indicate potential link point
});
}
// Make all ports on a node visible when the mouse is over the node
showPorts(node, show) {
const diagram = node.diagram;
if (!diagram || diagram.isReadOnly || !diagram.allowLink) return;
node.ports.each(function(port) {
port.stroke = show ? '#B66C26' : null;
port.strokeWidth = 1;
port.strokeCap = 'butt';
port.strokeDashOffset = 0;
port.strokeJoin = 'miter';
port.strokeMiterLimit = 10;
});
}
/**
* 获取放置位置
* @returns {string}
*/
getLoc() {
const dom = this.diagramRef.nativeElement;
const divDom = dom.querySelector('div');
const locDom = this.locDom;
let x = 0,
y = 0;
// 当出滚动条时,使用真实高度来确定位置
if (
divDom.offsetWidth < divDom.scrollWidth ||
divDom.offsetHeight < divDom.scrollHeight
) {
const scrollHeight = divDom.scrollHeight - divDom.offsetHeight;
const scrollWidth = divDom.scrollWidth - divDom.offsetWidth;
x = scrollWidth + locDom.layerX - divDom.offsetWidth - 160;
y = scrollHeight + locDom.layerY - divDom.offsetHeight + 100;
} else {
x = -(divDom.scrollWidth - locDom.layerX) - 160;
y = -(divDom.scrollHeight - locDom.layerY) + 100;
}
return x + ' ' + y;
}
/**
* 线条动画
*/
loop() {
const me = this;
const diagram = this.diagram;
setTimeout(function() {
const oldskips = diagram.skipsUndoManager;
diagram.skipsUndoManager = true;
diagram.links.each(function(link) {
const shape = link.findObject('PIPE');
if (shape) {
const off = shape.strokeDashOffset - 2;
shape.strokeDashOffset = off <= 0 ? 20 : off;
}
});
diagram.skipsUndoManager = oldskips;
me.loop();
}, 100);
}
// 保存绘制模板
save() {
const value = { topoArea: '001', topoValue: this.diagram.model.toJson() };
this.loading = true;
if (this.isOperation_id) {
this.http.post(
this.updat3eUrl + '/' + this.isOperation_id,
value,
success => {
this.loading = false;
if (success.code !== '0') {
this.msg.error(success.msg);
} else {
this.msg.success(`更新成功!`);
}
},
error => {
this.msg.error(error);
},
);
} else {
this.http.post(
this.saveUrl,
value,
success => {
this.loading = false;
if (success.code !== '0') {
this.msg.error(success.msg);
} else {
this.msg.success(`保存成功!`);
}
},
error => {
this.msg.error(error);
},
);
}
}
locDom: any; // 保存鼠标移动的坐标信息
onMousemove(e) {
// 利用鼠标弹起瞬间触发的Mousemove事件 通知画布增加东西
this.locDom = e;
this.appDeviceTree.addClick();
}
}