基于ng-alain和gojs绘制流程图

1、左树使用ng-alain组件
 
 
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: `
    <nz-tree [(ngModel)]="nodes"
             [nzShowLine]="true"
             [nzDraggable]="true"
             [nzBeforeDrop]="beforeDrop" 
             (mouseleave)="onMouseleave($event)"
             (nzOnDragStart)="onDragStart($event)"
             [nzDefaultExpandedKeys]="expandKeys"
             (nzOnDragEnter)="mouseAction('Enter', $event)"
             >
    </nz-tree>
    <!--<ng-template #template>-->
      <!--<ul nz-menu nzInDropDown (nzClick)="close($event)">-->
        <!--<li nz-menu-item (click)="addClick()">加入画布</li>-->
      <!--</ul>-->
    <!--</ng-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>): 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<boolean> {
    // 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;
    }
  }
}
2、右边是gojs的流程图

通过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<go.Node | null>();
  @Output() modelChanged = new EventEmitter<go.ChangedEvent>();

  // 选中的左侧树节点
  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();
  }
}

猜你喜欢

转载自blog.csdn.net/ligaoming_123/article/details/80655498