1.安装@datafe/graph-react包
yarn add @datafe/graph-react
npm install @datafe/graph-react
2.代码demo
同级文件目录index.tsx,dragPanel.tsx,data.ts,style.scss
1.index.tsx 画布页面,左侧拖拽怒路和画布操作
import { Button, Icon } from '@ss/mtd-react'
import React, { useState, useEffect } from 'react'
import {} from '@/pages/lark/api'
import PageTitle from '@/pages/lark/views/components/page-title'
import './style.scss'
import {
GraphReact,
ToolBox,
Menu,
Graph,
INode,
IEdge,
// IPort,
IEdgeModel,
IAction,
} from '@datafe/graph-react'
import dataMock from './data'
import DragPanel from './dragPanel'
const RecallProgramGraph = (props: any) => {
console.log('props==', props)
// 定义画布的交互,可以根据需求删除不需要的
const action = [
'drag-blank',
'drag-node',
'click-select',
'connect-edge',
'wheel-move',
'wheel-zoom',
'brush-select',
] as IAction | any
const dragComponent = {
offsetX: 0,
offsetY: 0,
label: '',
}
const [graph, setGraph] = useState<Graph>((null as unknown) as Graph)
const [showMenu, setShowMenu] = useState(false)
const [activeNodeId, setActiveNodeId] = useState('')
const handleNodeContextMenu = ({ data }: { data: { id: string } }) => {
setShowMenu(true)
setActiveNodeId(data.id)
}
const handleConnent = (data: IEdgeModel) => {
graph.addEdge(data)
}
const handleDrop = (e: any) => {
e.preventDefault()
const x = e.x - dragComponent.offsetX * graph.getZoom()
const y = e.y - dragComponent.offsetY * graph.getZoom()
const point = graph.getPointByClient(x, y)
graph.addNode({
label: dragComponent.label,
x: point.x,
y: point.y,
width: 160,
height: 40,
})
}
useEffect(() => {
if (!graph) return
// 在这里绑定画布所需的事件
graph.on('node:contextmenu', handleNodeContextMenu)
graph.on('edge:connected', handleConnent)
graph.on('drop', handleDrop) //监听拖拽
// 模拟接口返回数据
setTimeout(() => {
graph.data(dataMock)
}, 100)
}, [graph])
const handleDragStart = (data: any) => {
console.log('data88', data)
dragComponent.offsetX = data.offsetX
dragComponent.offsetY = data.offsetY
dragComponent.label = data.itemLabel
}
const nodeComponent = (node: INode) => {
return (
<div className={`component-item ${node.hasState('selected') && 'selected'}`}>
{node.model.label}
</div>
)
}
const edgeComponent = (edge: IEdge) => {
const path = `M ${edge.source.x},${edge.source.y} L ${edge.target.x},${edge.target.y}`
return <path d={path} markerEnd="url(#arrow)" className="graph-custom-edge" />
}
const init = (graph: Graph) => {
setGraph(graph)
}
const deleteNode = () => {
graph.deleteNode(activeNodeId)
}
const save = () => {
console.log('graph', graph)
console.log('graph.getDataModel', graph.getDataModel()) //获取节点和边的数据
}
return (
<div style={{ background: 'white', padding: '0px 15px 15px' }}>
<PageTitle title="召排运维" />
<div className="graphBox">
<DragPanel dragStart={handleDragStart} />
<GraphReact
node={nodeComponent}
edge={edgeComponent}
// port={portComponent}
init={init}
action={action}
defaultNode={{ width: 160, height: 40 }}
// onDrop={handleDrop}
>
<ToolBox />
{showMenu ? (
<Menu>
<div className="menu-item" onClick={deleteNode}>
删除
</div>
</Menu>
) : (
''
)}
</GraphReact>
</div>
<Button type="primary" onClick={save}>
保存
</Button>
<div>
<Icon type="save-o" />
</div>
</div>
)
}
export default RecallProgramGraph
2.dragPanel.tsx 左侧拖拽目录
import React from 'react'
import './style.scss'
const sideMenu = [
{ label: 't1', value: 't1' },
{ label: 't2', value: 't2' },
{ label: 't3', value: 't3' },
{ label: 't4', value: 't4' },
{ label: 't5', value: 't5' },
{ label: 't6', value: 't6' },
{ label: 't7', value: 't7' },
{ label: 't8', value: 't8' },
{ label: 't9', value: 't9' },
{ label: 't10', value: 't10' },
{ label: 't11', value: 't11' },
{ label: 't12', value: 't12' },
{ label: 't13', value: 't13' },
{ label: 't14', value: 't14' },
{ label: 't15', value: 't15' },
{ label: 't16', value: 't16' },
]
export default (props: any) => {
const handleDragStart = (e: any, itemLabel: any) => {
const dom = e.target
dom.classList.add('active')
const rect = dom.getBoundingClientRect()
const offsetX = e.clientX - rect.x
const offsetY = e.clientY - rect.y
props.dragStart && props.dragStart({ offsetX, offsetY, itemLabel })
}
const handleDragEnd = (e: any) => {
e.target.classList.remove('active')
}
return (
<div className="component-panel">
<div className="title">拖动组件到画布</div>
<div className="item-box">
{sideMenu.map(item => (
<li
key={item.value}
style={{ marginTop: '10px' }}
className="component-item"
onDragStart={e => handleDragStart(e, item.label)}
onDragEnd={handleDragEnd}
draggable
>
<span> {item.label} </span>
</li>
))}
</div>
</div>
)
}
3.data.ts画布初始mock数据
export default {
nodes: [
{
id: '1',
// label: '工具栏悬浮有说明',
label: 'test1',
nodeId: 232,
},
{
id: '2',
// label: '拖动添加组件',
label: 'test2',
ports: [
{
type: 'in',
id: 'port3',
},
{
type: 'in',
id: 'port4',
},
{
type: 'out',
id: 'port5',
},
],
},
{
id: '3',
// label: '拖动插槽连线',
label: '测试3',
},
{
id: '4',
// label: '右键可删除',
label: '测试4',
},
{
id: '5',
// label: '交互可配置',
label: '测试5',
},
{
id: '6',
// label: '交互可配置',
label: '测试6',
},
{
id: '7',
label: '交互可配置',
ports: [
{
type: 'in',
id: 'port14',
},
{
type: 'in',
id: 'port15',
},
{
type: 'out',
id: 'port16',
},
],
},
],
edges: [
{
fromNodeId: '1',
toNodeId: '5',
id: 'edge1',
},
{
fromNodeId: '3',
toNodeId: '2',
toPortId: 'port3',
id: 'edge2',
},
{
fromNodeId: '1',
toNodeId: '3',
id: 'edge3',
},
{
fromNodeId: '7',
toNodeId: '4',
fromPortId: 'port16',
id: 'edge4',
},
{
fromNodeId: '4',
toNodeId: '6',
id: 'edge5',
},
{
fromNodeId: '2',
toNodeId: '6',
fromPortId: 'port5',
id: 'edge6',
},
{
fromNodeId: '5',
toNodeId: '7',
toPortId: 'port14',
id: 'edge7',
},
],
}
4.style.scss 画布和左侧目录样式
/* dragPanel 样式 */
.component-panel {
position: absolute;
top: 10px;
left: 10px;
width: 180px;
// height: 150px;
height: 680px;
user-select: none;
box-sizing: border-box;
background: #fcfcfc;
z-index: 1;
// padding: 10px;
box-shadow: 0 0 4px 1px #eee;
border: 1px solid red;
}
.title {
font-size: 12px;
color: #222;
text-align: center;
// margin: 10px 10px;
padding: 10px 0px;
margin: 0px 0px 10px;
box-shadow: 0 0px 4px 1px rgba(0, 0, 0, 0.01), 0 3px 6px 3px rgba(0, 0, 0, 0.01),
0 2px 6px 0 rgba(0, 0, 0, 0.03);
}
.item-box {
width: 180px;
// height: 150px;
height: 620px;
padding: 0px 10px;
overflow-y: auto;
}
.component-item {
display: flex;
height: 40px;
box-sizing: border-box;
color: #222;
font-size: 12px;
padding-left: 40px;
align-items: center;
cursor: pointer;
user-select: none;
border: 1px solid #222;
border-radius: 4px;
background: #fff;
}
.selected {
border-width: 1.5px !important;
background: #eea !important;
}
.active {
border: 1px dashed;
cursor: grabbing;
}
.graph-custom-edge {
stroke: #aaa;
fill: transparent;
stroke-width: 1.5;
}
.graph-react-wrapper {
width: 100%;
height: 700px !important;
// border: 1px solid green;
}
/* 如果想修改自带样式,可以使用直接样式覆盖 */
.graph-react-wrapper .graph-react-tool-box {
background: #fcfcfc;
border: none;
color: #222;
box-shadow: 0 0 4px 1px #eee;
border-radius: 4px;
}
.graph-react-wrapper .graph-react-tool-box-item:hover {
background: #e9e9e9;
}
.graph-react-wrapper .graph-react-tool-box-disable {
color: #eee;
}
.menu-item {
height: 30px;
line-height: 30px;
color: #555;
font-size: 12px;
cursor: pointer;
background: #fff;
text-align: center;
}
.menu-item:hover {
color: #222;
background: #eee;
}
.graphBox {
position: relative;
width: 100%;
min-height: 700px;
border: 1px solid red;
}