Open dounai1306 opened 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;
https://gojs.net/latest/samples/orgChartEditor.html 的react版本,代码在react中可正常运行,部分标签缺失,替换后不影响代码运行。
常见问题
官方栗子中,
const shape = node.findObject(“SHAPE”)
在ts里会提示 FindObject not available(node as go.Node).findObject("SHAPE")
参考这里