dounai1306 / dounai1306.github.io

一些笔记 以及 一些demo https://dounai1306.github.io/
4 stars 0 forks source link

react gojs org Chart #74

Open dounai1306 opened 4 years ago

dounai1306 commented 4 years ago

https://gojs.net/latest/samples/orgChartEditor.html 的react版本,代码在react中可正常运行,部分标签缺失,替换后不影响代码运行。

import React, {FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import { makeStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import './Diagram.css';
import compose from "recompose/compose";
const styles = (theme: Theme) =>
  createStyles({
    root: {},
    container: {
      padding: '0px',
      textAlign: 'center'
    },
  });

const useStyles = makeStyles(styles);
type Props = {
  linkDataArray: any;
  nodeDataArray: any;
} & Partial<WithStyles<Partial<typeof styles>>>;
const Index: FunctionComponent<Props> = props => {
  const classes = useStyles(props);
  const { linkDataArray, nodeDataArray } = props;
  const skipsDiagramUpdate = false;
  const $ = go.GraphObject.make;
  useEffect(() => {
  }, []);

  function initDiagram() {
    const diagram =
      $(go.Diagram,
        {
          'undoManager.isEnabled': false,  // enable Ctrl-Z to undo and Ctrl-Y to redo
          // 'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' }, // 双击创建
          "grid.visible": true, //画布上面是否出现网格
          maxSelectionCount: 1, // users can select only one part at a time
          validCycle: go.Diagram.CycleDestinationTree, // make sure users can only create trees
          "clickCreatingTool.archetypeNodeData": { // allow double-click in background to create a new node
            name: "(new person)",
            title: "",
            comments: ""
          },
          "clickCreatingTool.insertPart": function(loc:any) {  // scroll to the new node
            var node = go.ClickCreatingTool.prototype.insertPart.call(this, loc);
            if (node !== null  || node !== undefined) {
              diagram.select(node);
              diagram.commandHandler.scrollToPart(node);
              diagram.commandHandler.editTextBlock(node.findObject("NAMETB") as any);
            }
            return node;
          },
          layout:
            $(go.TreeLayout,
              {
                treeStyle: go.TreeLayout.StyleLastParents,
                arrangement: go.TreeLayout.ArrangementHorizontal,
                // properties for most of the tree:
                angle: 90,
                layerSpacing: 35,
                // properties for the "last parents":
                alternateAngle: 90,
                alternateLayerSpacing: 35,
                alternateAlignment: go.TreeLayout.AlignmentBus,
                alternateNodeSpacing: 20
              }),

          allowZoom: true,
          model: $(go.TreeModel,  // GraphLinksModel :连线图 | Model:最基本的 | TreeModel:树形图
            {
              // linkKeyProperty: 'key'  // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
            })
        });
    // when the document is modified, add a "*" to the title and enable the "Save" button
    diagram.addDiagramListener("Modified", function(e) {
      var button = document.getElementById("SaveButton") as any;
      if (button) button.disabled = !diagram.isModified;
      var idx = document.title.indexOf("*");
      if (diagram.isModified) {
        if (idx < 0) document.title += "*";
      } else {
        if (idx >= 0) document.title = document.title.substr(0, idx);
      }
    });

    // manage boss info manually when a node or link is deleted from the diagram
    diagram.addDiagramListener("SelectionDeleting", function(e) {
      var part = e.subject.first(); // e.subject is the myDiagram.selection collection,
      // so we'll get the first since we know we only have one selection
      diagram.startTransaction("clear boss");
      if (part instanceof go.Node) {
        var it = part.findTreeChildrenNodes(); // find all child nodes
        while (it.next()) { // now iterate through them and clear out the boss information
          var child = it.value;
          var bossText = child.findObject("boss")  as any; // since the boss TextBlock is named, we can access it by name
          if (bossText === null || bossText === undefined) return;
          bossText.text = "";
        }
      } else if (part instanceof go.Link) {
        var child = part.toNode;
        var bossText = child.findObject("boss") as any; // since the boss TextBlock is named, we can access it by name
        if (bossText === null || bossText === undefined ) return;
        bossText.text = "";
      }
      diagram.commitTransaction("clear boss");
    });

    var levelColors = ["#AC193D", "#2672EC", "#8C0095", "#5133AB", "#008299", "#D24726", "#008A00", "#094AB2"];

    // override TreeLayout.commitNodes to also modify the background brush based on the tree depth level
    // diagram.layout.commitNodes = function() {
    //   go.TreeLayout.prototype.commitNodes.call(diagram.layout);  // do the standard behavior
    //   // then go through all of the vertexes and set their corresponding node's Shape.fill
    //   // to a brush dependent on the TreeVertex.level value
    //   diagram.layout.network.vertexes.each(function(v) {
    //     if (v.node) {
    //       var level = v.level % (levelColors.length);
    //       var color = levelColors[level];
    //       var shape = v.node.findObject("SHAPE");
    //       if (shape) shape.stroke = $(go.Brush, "Linear", { 0: color, 1: go.Brush.lightenBy(color, 0.05), start: go.Spot.Left, end: go.Spot.Right });
    //     }
    //   });
    // };
    // when a node is double-clicked, add a child to it
    function nodeDoubleClick(e:any, obj:any) {
      var clicked = obj.part;
      if (clicked !== null) {
        var thisemp = clicked.data;
        diagram.startTransaction("add employee");
        var newemp = {
          name: "(new person)",
          title: "",
          comments: "",
          parent: thisemp.key
        };
        diagram.model.addNodeData(newemp);
        diagram.commitTransaction("add employee");
      }
    }
    // this is used to determine feedback during drags
    function mayWorkFor(node1:any, node2:any) {
      if (!(node1 instanceof go.Node)) return false;  // must be a Node
      if (node1 === node2) return false;  // cannot work for yourself
      if (node2.isInTreeOf(node1)) return false;  // cannot work for someone who works for you
      return true;
    }

    // This function provides a common style for most of the TextBlocks.
    // Some of these values may be overridden in a particular TextBlock.
    function textStyle() {
      return { font: "9pt  Segoe UI,sans-serif", stroke: "white" };
    }

    // This converter is used by the Picture.
    function findHeadShot(key:any) {
      if (key < 0 || key > 16) return "images/HSnopic.jpg"; // There are only 16 images on the server
      return "images/HS" + key + ".jpg";
    }

    // define a simple Node template
    diagram.nodeTemplate =
      $(go.Node, 'Auto',  // the Shape will go around the TextBlock
        { doubleClick: nodeDoubleClick },
        { // handle dragging a Node onto a Node to (maybe) change the reporting relationship
          mouseDragEnter: function(e, node:any, prev) {
            var diagrama = node.diagram;
            var selnode = diagrama.selection.first();
            if (!mayWorkFor(selnode, node)) return;
            var shape = (node as go.Node).findObject("SHAPE");
            if (shape) {
              (shape as any)._prevFill = (shape as any).fill;  // remember the original brush
              (shape as any).fill = "darkred";
            }
          },
          mouseDragLeave: function(e, node, next) {
            var shape = (node as go.Node).findObject("SHAPE");
            if (shape && (shape as any)._prevFill) {
              (shape as any).fill = (shape as any)._prevFill;  // restore the original brush
            }
          },
          mouseDrop: function(e, node) {
            var diagramb = node.diagram;
            var selnode = (diagramb as any).selection.first();  // assume just one Node in selection
            if (mayWorkFor(selnode, node)) {
              // find any existing link into the selected node
              var link = selnode.findTreeParentLink();
              if (link !== null) {  // reconnect any existing link
                link.fromNode = node;
              } else {  // else create a new link
                diagram.toolManager.linkingTool.insertLink(node as any, (node as any).port, selnode, selnode.port);
              }
            }
          }
        },
        // for sorting, have the Node.text be the data.name
        new go.Binding("text", "name"),
        // bind the Part.layerName to control the Node's layer depending on whether it isSelected
        new go.Binding("layerName", "isSelected", function(sel) { return sel ? "Foreground" : ""; }).ofObject(),
        // define the node's outer shape
        $(go.Shape, "Rectangle",
          {
            name: "SHAPE", fill: "#333333", stroke: 'white', strokeWidth: 3.5,
            // set the port properties:
            portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer"
          }),
        $(go.Panel, "Horizontal",
          $(go.Picture,
            {
              name: "Picture",
              desiredSize: new go.Size(70, 70),
              margin: 1.5,
            },
            new go.Binding("source", "key", findHeadShot)),
          // define the panel where the text will appear
          $(go.Panel, "Table",
            {
              minSize: new go.Size(130, NaN),
              maxSize: new go.Size(150, NaN),
              margin: new go.Margin(6, 10, 0, 6),
              defaultAlignment: go.Spot.Left
            },
            $(go.RowColumnDefinition, { column: 2, width: 4 }),
            $(go.TextBlock, textStyle(),  // the name
              {
                row: 0, column: 0, columnSpan: 5,
                font: "12pt Segoe UI,sans-serif",
                editable: true, isMultiline: false,
                minSize: new go.Size(10, 16)
              },
              new go.Binding("text", "name").makeTwoWay()),
            $(go.TextBlock, "Title: ", textStyle(),
              { row: 1, column: 0 }),
            $(go.TextBlock, textStyle(),
              {
                row: 1, column: 1, columnSpan: 4,
                editable: true, isMultiline: false,
                minSize: new go.Size(10, 14),
                margin: new go.Margin(0, 0, 0, 3)
              },
              new go.Binding("text", "title").makeTwoWay()),
            $(go.TextBlock, textStyle(),
              { row: 2, column: 0 },
              new go.Binding("text", "key", function(v) { return "ID: " + v; })),
            $(go.TextBlock, textStyle(),
              { name: "boss", row: 2, column: 3, }, // we include a name so we can access this TextBlock when deleting Nodes/Links
              new go.Binding("text", "parent", function(v) { return "Boss: " + v; })),
            $(go.TextBlock, textStyle(),  // the comments
              {
                row: 3, column: 0, columnSpan: 5,
                font: "italic 9pt sans-serif",
                wrap: go.TextBlock.WrapFit,
                editable: true,  // by default newlines are allowed
                minSize: new go.Size(10, 14)
              },
              new go.Binding("text", "comments").makeTwoWay())
          )  // end Table Panel
        ) // end Horizontal Panel
      );

    // the context menu allows users to make a position vacant,
    // remove a role and reassign the subtree, or remove a department
    diagram.nodeTemplate.contextMenu =
      $("ContextMenu",
        $("ContextMenuButton",
          $(go.TextBlock, "Vacate Position"),
          {
            click: function(e:any, obj:any) {
              var node = obj.part.adornedPart;
              if (node !== null) {
                var thisemp = node.data;
                diagram.startTransaction("vacate");
                // update the key, name, and comments
                diagram.model.setDataProperty(thisemp, "name", "(Vacant)");
                diagram.model.setDataProperty(thisemp, "comments", "");
                diagram.commitTransaction("vacate");
              }
            }
          }
        ),
        $("ContextMenuButton",
          $(go.TextBlock, "Remove Role"),
          {
            click: function(e: any, obj:any) {
              // reparent the subtree to this node's boss, then remove the node
              var node = obj.part.adornedPart;
              if (node !== null) {
                diagram.startTransaction("reparent remove");
                var chl = node.findTreeChildrenNodes();
                // iterate through the children and set their parent key to our selected node's parent key
                while (chl.next()) {
                  var emp = chl.value;
                  // diagram.model.setParentKeyForNodeData(emp.data, node.findTreeParentNode().data.key);
                  diagram.model.setCategoryForNodeData(emp.data, node.findTreeParentNode().data.key);
                }
                // and now remove the selected node itself
                diagram.model.removeNodeData(node.data);
                diagram.commitTransaction("reparent remove");
              }
            }
          }
        ),
        $("ContextMenuButton",
          $(go.TextBlock, "Remove Department"),
          {
            click: function(e:any, obj:any) {
              // remove the whole subtree, including the node itself
              var node = obj.part.adornedPart;
              if (node !== null) {
                diagram.startTransaction("remove dept");
                diagram.removeParts(node.findTreeParts(), true);
                diagram.commitTransaction("remove dept");
              }
            }
          }
        )
      );

    // define the Link template
    diagram.linkTemplate =
      $(go.Link, go.Link.Orthogonal,
        { corner: 5, relinkableFrom: true, relinkableTo: true },
        $(go.Shape, { strokeWidth: 1.5, stroke: "#F5F5F5" }));  // the link shape

    return diagram;
  }
  function handleModelChange(changes: any) {
    console.log(nodeDataArray);
  }
  function save() {
    console.log(nodeDataArray);
  }
  return (
    <Container className={classes.container}>
      <div onClick={save}>aaaaaa</div>
      <ReactDiagram
        initDiagram={initDiagram}
        divClassName='diagram-component'
        skipsDiagramUpdate={skipsDiagramUpdate}
        nodeDataArray={nodeDataArray}
        onModelChange={handleModelChange}
      />
    </Container>
  );
};
const mapStateToProps = function(state: Store) {
  return {
    linkDataArray: [],
    nodeDataArray: [
      { key: "Alpha" },
      { key: "Beta", parent: "Alpha" },
      { key: "Gamma", parent: "Alpha" }
    ]
  };
};
const GoJsCntainer = compose<any, {}>(
  withLocale,
  withRouter,
  connect(
    mapStateToProps,
    null
  )
)(Index);

export default GoJsCntainer;

常见问题

官方栗子中,const shape = node.findObject(“SHAPE”)在ts里会提示 FindObject not available (node as go.Node).findObject("SHAPE")参考这里

dounai1306 commented 4 years ago
import React, {FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import {makeStyles, WithStyles, createStyles, Theme} from '@material-ui/core/styles';
import Container from '_components/Container/Container';
import * as go from 'gojs';
import {ReactDiagram} from 'gojs-react';
import './Diagram.css';
import {Store} from "_components/App/reducers";
import compose from "recompose/compose";
import withLocale from "_components/LocalProviderManage/withLocale";
import {withRouter} from "react-router";
import {connect} from "react-redux";
import Button from "@material-ui/core/Button/Button";

const styles = (theme: Theme) =>
  createStyles({
    root: {},
    container: {
      padding: '0px',
      textAlign: 'center'
    },
  });

const useStyles = makeStyles(styles);
type Props = {
  nodeDataArray: any;
} & Partial<WithStyles<Partial<typeof styles>>>;
const Index: FunctionComponent<Props> = props => {
  const classes = useStyles(props);
  const {nodeDataArray} = props;
  const skipsDiagramUpdate = false;
  const $ = go.GraphObject.make;
  useEffect(() => {
  }, []);
  let diagram: any;
  function textStyle() {
    return {font: "12px  sans-serif", stroke: "#2196f3"};
  }
  function initDiagram() {
    diagram =
      $(go.Diagram,
        {
          'undoManager.isEnabled': false,  // enable Ctrl-Z to undo and Ctrl-Y to redo
          "grid.visible": false, //画布上面是否出现网格
          maxSelectionCount: 1, // users can select only one part at a time
          validCycle: go.Diagram.CycleDestinationTree, // make sure users can only create trees
          "clickCreatingTool.archetypeNodeData": { // allow double-click in background to create a new node
            name: "dounai",
            title: "",
            comments: ""
          },
          // 可以通过双击创建节点,insertPart以编程方式创建part
          "clickCreatingTool.insertPart": function (loc: any) {  // scroll to the new node
            var node = go.ClickCreatingTool.prototype.insertPart.call(this, loc);
            if (node !== null || node !== undefined) {
              diagram.select(node);
              diagram.commandHandler.scrollToPart(node);
              // 名称使我们可以使用轻松在面板(所有节点也是面板)中找到GraphObjects Panel.findObject
              diagram.commandHandler.editTextBlock(node.findObject("NAMETB") as any);
            }
            return node;
          },
          layout:
            $(go.TreeLayout,

              {
                treeStyle: go.TreeLayout.StyleLastParents,
                arrangement: go.TreeLayout.ArrangementHorizontal,
                // properties for most of the tree:
                angle: 90,
                // angle定义树的方向,0为默认表示向右生产,180表示向左,90向下,270向上
                layerSpacing: 35,
                // 父节点和子节点之间的距离
                alternateAngle: 90,
                // 0 90 180 270  0在正X轴上,90在正Y轴上,生长方向
                alternateLayerSpacing: 35,
                // 父节点与其子节点之间的间隔距离
                alternateAlignment: go.TreeLayout.AlignmentBus,
                // 获取或设置父节点相对于其子节点的替代对齐方式
                alternateNodeSpacing: 20
              }),

          allowZoom: true,
          // 用户是否可以放大或缩小关系图
          model: $(go.TreeModel,  // GraphLinksModel :连线图 | Model:最基本的 | TreeModel:树形图
            {
              // linkKeyProperty: 'key'  // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
            })
        });

    // 当试图在编辑时,标题增加*进行提醒,同时启用保存按钮。
    diagram.addDiagramListener("Modified", function (e: any) {
      // todo: 这块要和保存按钮进行绑定
      var button = document.getElementById("SaveButton") as HTMLButtonElement;
      if (button) button.disabled = !diagram.isModified;

      var idx = document.title.indexOf("*");
      if (diagram.isModified) {
        if (idx < 0) document.title += "*";
      } else {
        if (idx >= 0) document.title = document.title.substr(0, idx);
      }
    });

    // 当一个节点或链接从图中删除时,手动管理boss信息
    diagram.addDiagramListener("SelectionDeleting", function (e: any) {
      // 用户将要删除CommandHandler.deleteSelection所选的部分.部件
      var part = e.subject.first();
      diagram.startTransaction("clear boss");
      // 由用户引起的每个更改(如单击按钮、更改焦点或拖动鼠标)都应该执行一个事务,所有更改都在其中进行。所有预定义的命令和工具都执行事务。
      if (part instanceof go.Node) {
        var it = part.findTreeChildrenNodes();
        // 查找所有子节点
        while (it.next()) {
          var child = it.value;
          var bossText = child.findObject("boss")  as any;
          // since the boss TextBlock is named, we can access it by name
          if (bossText === null || bossText === undefined) return;
          bossText.text = "";
        }
        // 遍历左右子节点,清除他们的父节点信息
      } else if (part instanceof go.Link) {
        var child = part.toNode;
        var bossText = child.findObject("boss") as any;
        if (bossText === null || bossText === undefined) return;
        bossText.text = "";
      }
      diagram.commitTransaction("clear boss");
      // 提交当前事物更改
    });

    // var levelColors = ["#AC193D", "#2672EC", "#8C0095", "#5133AB", "#008299", "#D24726", "#008A00", "#094AB2"];
    // 重写TreeLayout.commitNodes也可以根据树的深度级别修改背景笔刷 todo:可有可无
    // override TreeLayout.commitNodes to also modify the background brush based on the tree depth level
    // diagram.layout.commitNodes = function() {
    //   go.TreeLayout.prototype.commitNodes.call(diagram.layout);  // do the standard behavior
    //   // then go through all of the vertexes and set their corresponding node's Shape.fill
    //   // to a brush dependent on the TreeVertex.level value
    //   diagram.layout.network.vertexes.each(function(v) {
    //     if (v.node) {
    //       var level = v.level % (levelColors.length);
    //       var color = levelColors[level];
    //       var shape = v.node.findObject("SHAPE");
    //       if (shape) shape.stroke = $(go.Brush, "Linear", { 0: color, 1: go.Brush.lightenBy(color, 0.05), start: go.Spot.Left, end: go.Spot.Right });
    //     }
    //   });
    // };
    // 双击添加一个新的模块
    function nodeDoubleClick(e: any, obj: any) {
      var clicked = obj.part;
      if (clicked !== null) {
        var thisemp = clicked.data;
        diagram.startTransaction("add employee");
        var newemp = {
          name: "(new person)",
          title: "",
          comments: "",
          parent: thisemp.key
        };
        diagram.model.addNodeData(newemp);
        diagram.commitTransaction("add employee");
      }
    }

    // 拖拽反馈
    function mayWorkFor(node1: any, node2: any) {
      if (!(node1 instanceof go.Node)) return false;  // must be a Node
      if (node1 === node2) return false;  // cannot work for yourself
      if (node2.isInTreeOf(node1)) return false;  // cannot work for someone who works for you
      return true;
    }

    // 这里用不到
    // function findHeadShot(key: any) {
    //   if (key < 0 || key > 16) return "images/HSnopic.jpg"; // There are only 16 images on the server
    //   return "images/HS" + key + ".jpg";
    // }

    // 定义node tmp
    diagram.nodeTemplate =
      $(go.Node, 'Auto',
        // 该形状将围绕文本块
        {doubleClick: nodeDoubleClick},
        { // 处理将节点拖放到节点上(可能)更改 reporting relationship
          // 获取或设置当用户在拖动工具时将鼠标移动到此固定对象时要执行的函数
          mouseDragEnter: function (e, node: any, prev) {
            var diagrama = node.diagram;
            var selnode = diagrama.selection.first();
            if (!mayWorkFor(selnode, node)) return;
            var shape = (node as go.Node).findObject("SHAPE");
            if (shape) {
              // 固定对象的填充
              (shape as any)._prevFill = (shape as any).fill;
              (shape as any).fill = "green";
            }
          },
          // 获取或设置当用户在拖动工具时将鼠标移出此固定对象时要执行的函数
          mouseDragLeave: function (e, node, next) {
            var shape = (node as go.Node).findObject("SHAPE");
            if (shape && (shape as any)._prevFill) {
              (shape as any).fill = (shape as any)._prevFill;  // restore the original brush
            }
          },
          // 获取或设置当用户在拖动工具拖动结束时在此对象上放下选择项时要执行的函数
          mouseDrop: function (e, node) {
            var diagramb = node.diagram;
            var selnode = (diagramb as any).selection.first();  // assume just one Node in selection
            if (mayWorkFor(selnode, node)) {
              // find any existing link into the selected node
              var link = selnode.findTreeParentLink();
              if (link !== null) {  // reconnect any existing link
                link.fromNode = node;
              } else {  // else create a new link
                diagram.toolManager.linkingTool.insertLink(node as any, (node as any).port, selnode, selnode.port);
              }
            }
          }
        },
        // for sorting, have the Node.text be the data.name
        new go.Binding("text", "name"),
        // bind the Part.layerName to control the Node's layer depending on whether it isSelected
        new go.Binding("layerName", "isSelected", function (sel) {
          return sel ? "Foreground" : "";
        }).ofObject(),
        // define the node's outer shape
        // 我们想操纵一个属性,该属性属于Node的元素之一,也许是模板中任意深的元素。在示例图表中,每个节点都有一个Shape,如果我们想直接更改Shape的颜色,则需要对其进行引用。为了找到它,我们可以给Shape命名:
        // 名称使我们可以使用轻松在面板(所有节点也是面板)中找到GraphObjects Panel.findObject
        $(go.Shape, "Rectangle",
          {
            name: "SHAPE", fill: "white", stroke: '#2196f3', strokeWidth: 2,
            portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer"
          }),
        $(go.Panel, "Horizontal",
          // $(go.Picture,
          //   {
          //     name: "Picture",
          //     desiredSize: new go.Size(70, 70),
          //     margin: 1.5,
          //   },
          //   new go.Binding("source", "key", findHeadShot)),
          // define the panel where the text will appear
          $(go.Panel, "Table",
            // 设置节点大小
            {
              minSize: new go.Size(200, NaN),
              maxSize: new go.Size(300, NaN),
              margin: new go.Margin(6, 10, 0, 6),
              defaultAlignment: go.Spot.Left
            },
            $(go.RowColumnDefinition, {column: 2, width: 4}),
            $(go.TextBlock, textStyle(),
              {
                row: 0, column: 0, columnSpan: 5,
                font: "12px Segoe UI,sans-serif",
                editable: true,
                isMultiline: false,
                minSize: new go.Size(10, 16)
              },
              new go.Binding("text", "name").makeTwoWay()),
            // $(go.TextBlock, "Title: ", textStyle(),{row: 1, column: 0}),
            $(go.TextBlock, textStyle(),
              {
                row: 1, column: 1, columnSpan: 4,
                editable: true, isMultiline: false,
                minSize: new go.Size(10, 14),
                margin: new go.Margin(0, 0, 0, 3)
              },
              new go.Binding("text", "title").makeTwoWay()),
            $(go.TextBlock, textStyle(),
              {row: 2, column: 0},
              new go.Binding("text", "key", function (v) {
                return "当前模块ID: " + v;
              })),
            $(go.TextBlock, textStyle(),
              {name: "boss", row: 2, column: 3,}, // we include a name so we can access this TextBlock when deleting Nodes/Links
              new go.Binding("text", "parent", function (v) {
                return "父ID: " + v;
              })),
            $(go.TextBlock, textStyle(),  // the comments
              {
                row: 3, column: 0, columnSpan: 5,
                font: "italic 12px sans-serif",
                wrap: go.TextBlock.WrapFit,
                editable: true,  // by default newlines are allowed
                minSize: new go.Size(10, 14)
              },
              new go.Binding("text", "comments").makeTwoWay())
          )
        )
      );

    //关联菜单允许 删除角色并重新分配子树,或删除部门
    diagram.nodeTemplate.contextMenu =
      $("ContextMenu",
        // 这段不知道干嘛的
        // $("ContextMenuButton",
        //   $(go.TextBlock, "Vacate Position"),
        //   {
        //     click: function(e:any, obj:any) {
        //       var node = obj.part.adornedPart;
        //       if (node !== null) {
        //         var thisemp = node.data;
        //         diagram.startTransaction("vacate");
        //         // update the key, name, and comments
        //         diagram.model.setDataProperty(thisemp, "name", "(Vacant)");
        //         diagram.model.setDataProperty(thisemp, "comments", "");
        //         diagram.commitTransaction("vacate");
        //       }
        //     }
        //   }
        // ),
        $("ContextMenuButton",
          $(go.TextBlock, "删除单个"),
          {
            click: function (e: any, obj: any) {
              // reparent the subtree to this node's boss, then remove the node
              var node = obj.part.adornedPart;
              if (node !== null) {
                diagram.startTransaction("reparent remove");
                var chl = node.findTreeChildrenNodes();
                // iterate through the children and set their parent key to our selected node's parent key
                while (chl.next()) {
                  var emp = chl.value;
                  // diagram.model.setParentKeyForNodeData(emp.data, node.findTreeParentNode().data.key);
                  diagram.model.setCategoryForNodeData(emp.data, node.findTreeParentNode().data.key);
                }
                // and now remove the selected node itself
                diagram.model.removeNodeData(node.data);
                diagram.commitTransaction("reparent remove");
              }
            }
          }
        ),
        $("ContextMenuButton",
          $(go.TextBlock, "删除该层及以下组织"),
          {
            click: function (e: any, obj: any) {
              // remove the whole subtree, including the node itself
              var node = obj.part.adornedPart;
              if (node !== null) {
                diagram.startTransaction("remove dept");
                diagram.removeParts(node.findTreeParts(), true);
                diagram.commitTransaction("remove dept");
              }
            }
          }
        )
      );

    // define the Link template
    diagram.linkTemplate =
      $(go.Link, go.Link.Orthogonal,
        {corner: 10, relinkableFrom: true, relinkableTo: true},
        $(go.Shape, {strokeWidth: 2, stroke: "#2196f3"}));

    return diagram;
  }

  function handleModelChange(changes: any) {
    alert('GoJS model changed!');
  }

  const save = useCallback(() => {
    const nodeDataArray = diagram.model.toJson();
    console.log(JSON.parse(nodeDataArray).nodeDataArray);

  }, []);

  return (
    <Container className={classes.container}>
      <Button variant="contained" color="primary" onClick={save} id="SaveButton">保存</Button>
      <ReactDiagram
        initDiagram={initDiagram}
        divClassName='diagram-component'
        skipsDiagramUpdate={skipsDiagramUpdate}
        nodeDataArray={nodeDataArray}
        onModelChange={handleModelChange}
      />
    </Container>
  );
};
const mapStateToProps = function (state: Store) {
  return {
    nodeDataArray: [
      {key: "1", name: 'Nodel(A Size describes a width and a height)'},
      {key: "2", parent: "1", name: 'Transformer'},
      {key: "3", parent: "1", name: 'Transformer'},
      {key: "4", parent: "2", name: 'Route'},
      {key: "5", parent: "3", name: 'Route'},
      {key: "6", parent: "4", name: 'Container'},
      {key: "7", parent: "4", name: 'Container'},
      {key: "8", parent: "4", name: 'Container'},
    ]
  };
};
const GoJsCntainer = compose<any, {}>(
  withLocale,
  withRouter,
  connect(
    mapStateToProps,
    null
  )
)(Index);

export default GoJsCntainer;