| | |
| | | import { Clipboard } from '@antv/x6-plugin-clipboard'; |
| | | import { Stencil } from '@antv/x6-plugin-stencil'; |
| | | import { History } from '@antv/x6-plugin-history' |
| | | import { commonGraphPorts, commonGraphAttrs } from "./GraphConfig"; |
| | | |
| | | import { Transform } from '@antv/x6-plugin-transform' |
| | | import { commonGraphPorts, commonGraphAttrs, initGraphConnecting } from "./GraphConfig"; |
| | | |
| | | export const GraphComponent = React.forwardRef((props, ref) => { |
| | | const container = useRef(null); |
| | |
| | | function initGrap() { |
| | | ref.current = new Graph({ |
| | | container: container.current, |
| | | // width: document.documentElement.clientWidth, |
| | | // height: document.documentElement.clientHeight, |
| | | width: 200, |
| | | height: 500, |
| | | // grid: 1, |
| | | connecting: { |
| | | snap: true, |
| | | createEdge() { |
| | | return new Shape.Edge({ |
| | | attrs: { |
| | | line: { |
| | | stroke: '#a0a0a0', |
| | | strokeWidth: 1, |
| | | targetMarker: { |
| | | name: 'classic', |
| | | size: 8, |
| | | }, |
| | | }, |
| | | }, |
| | | }) |
| | | }, |
| | | connector: 'smooth', |
| | | allowMulti: true, |
| | | allowPort: true, |
| | | } |
| | | width: document.documentElement.clientWidth, |
| | | height: document.documentElement.clientHeight - 100, |
| | | // width: 200, |
| | | // height: 500, |
| | | connecting: initGraphConnecting, |
| | | }); |
| | | |
| | | const graph = ref.current; |
| | |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new Transform({ |
| | | resizing: { |
| | | enabled: true |
| | | }, |
| | | }), |
| | | ) |
| | | |
| | | props.initHandle();//通知父组件初始化完成 |
| | | return graph; |
| | | } |
| | |
| | | |
| | | stencilContainer.current.appendChild(stencil.container) |
| | | |
| | | |
| | | |
| | | const n1 = graph.createNode({ |
| | | shape: "rect", |
| | | width: 80, |
| | | height: 40, |
| | | label: "默认组件", |
| | | attrs: commonGraphAttrs, |
| | | ports: commonGraphPorts |
| | | }) |
| | | |
| | | const n2 = graph.createNode({ |
| | |
| | | height: 40, |
| | | label: "测试组件", |
| | | attrs: commonGraphAttrs, |
| | | ports: commonGraphPorts |
| | | }) |
| | | |
| | | stencil.load([n1, n2], 'group1') |
| | |
| | | }) |
| | | |
| | | graph.bindKey('ctrl+z', () => { |
| | | graph.undo() |
| | | if (graph.canUndo()) { |
| | | graph.undo() |
| | | } |
| | | return false |
| | | }) |
| | | |
| | | graph.bindKey('ctrl+y', () => { |
| | | graph.redo() |
| | | if (graph.canRedo()) { |
| | | graph.redo() |
| | | } |
| | | return false |
| | | }) |
| | | |
| | |
| | | }) |
| | | return false |
| | | }) |
| | | |
| | | graph.on('node:mouseenter', ({ node }) => { |
| | | const ports = document.querySelectorAll(".x6-port-body") |
| | | for (let i = 0, len = ports.length; i < len; i += 1) { |
| | | ports[i].style.visibility = 'visible' |
| | | } |
| | | }) |
| | | |
| | | graph.on('node:mouseleave', ({ node }) => { |
| | | const ports = document.querySelectorAll(".x6-port-body") |
| | | for (let i = 0, len = ports.length; i < len; i += 1) { |
| | | ports[i].style.visibility = 'hidden' |
| | | } |
| | | }) |
| | | |
| | | } |
| | | |
| | | return ( |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | import { Graph, Shape } from "@antv/x6"; |
| | | |
| | | const commonGraphPorts = { |
| | | groups: { |
| | |
| | | position: 'top', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | magnet: true, |
| | | stroke: '#5F95FF', |
| | | strokeWidth: 1, |
| | | fill: '#fff', |
| | | style: { |
| | | visibility: 'hidden', |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | |
| | | position: 'bottom', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | magnet: true, |
| | | stroke: '#5F95FF', |
| | | strokeWidth: 1, |
| | | fill: '#fff', |
| | | style: { |
| | | visibility: 'hidden', |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | |
| | | position: 'left', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | magnet: true, |
| | | stroke: '#5F95FF', |
| | | strokeWidth: 1, |
| | | fill: '#fff', |
| | | style: { |
| | | visibility: 'hidden', |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | |
| | | position: 'right', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | magnet: true, |
| | | stroke: '#5F95FF', |
| | | strokeWidth: 1, |
| | | fill: '#fff', |
| | | style: { |
| | | visibility: 'hidden', |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | |
| | | }, |
| | | } |
| | | |
| | | export { commonGraphPorts, commonGraphAttrs } |
| | | const initGraphConnecting = { |
| | | router: 'manhattan', |
| | | connector: { |
| | | name: 'rounded', |
| | | args: { |
| | | radius: 8, |
| | | }, |
| | | }, |
| | | anchor: 'center', |
| | | connectionPoint: 'anchor', |
| | | allowBlank: false, |
| | | snap: { |
| | | radius: 20, |
| | | }, |
| | | createEdge() { |
| | | return new Shape.Edge({ |
| | | attrs: { |
| | | line: { |
| | | stroke: '#A2B1C3', |
| | | strokeWidth: 2, |
| | | targetMarker: { |
| | | name: 'block', |
| | | width: 12, |
| | | height: 8, |
| | | }, |
| | | }, |
| | | }, |
| | | zIndex: 0, |
| | | }) |
| | | }, |
| | | validateConnection({ targetMagnet }) { |
| | | return !!targetMagnet |
| | | }, |
| | | } |
| | | |
| | | export { commonGraphPorts, commonGraphAttrs, initGraphConnecting } |
New file |
| | |
| | | import React, { useRef, useEffect, useState } from "react"; |
| | | import { Button, Drawer, Input } from 'antd'; |
| | | |
| | | const { TextArea } = Input; |
| | | |
| | | export const GraphDrawer = ({ graphRef, isReady }) => { |
| | | |
| | | const [open, setOpen] = useState(false); |
| | | |
| | | const [init, setInit] = useState(false); |
| | | |
| | | const [nodeData, setNodeData] = useState(null); |
| | | |
| | | const [codeContent,setCodeContent] = useState(null); |
| | | |
| | | const showDrawer = (graph, node) => { |
| | | setOpen(true); |
| | | setNodeData(node); |
| | | }; |
| | | |
| | | const onClose = (e) => { |
| | | setOpen(false); |
| | | setNodeData(null); |
| | | console.log(codeContent); |
| | | }; |
| | | |
| | | const textAreaChange = (e) => { |
| | | setCodeContent(e.target.value); |
| | | } |
| | | |
| | | useEffect(() => { |
| | | if (isReady) { |
| | | const graph = graphRef.current; |
| | | |
| | | if (!init) { |
| | | graph.on("node:dblclick", ({ node }) => { |
| | | console.log(node); |
| | | showDrawer(graph, node); |
| | | }) |
| | | setInit(true); |
| | | } |
| | | } |
| | | }) |
| | | |
| | | if(nodeData){ |
| | | return ( |
| | | <> |
| | | <Drawer title={nodeData.label} onClose={onClose} open={open} size="large"> |
| | | <p>ID:{nodeData.id}</p> |
| | | <p>可执行代码:</p> |
| | | <TextArea onChange={textAreaChange} rows={10} /> |
| | | </Drawer> |
| | | </> |
| | | ); |
| | | } |
| | | } |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | import React, { useRef, useEffect, useState } from "react"; |
| | | import { Button } from 'antd'; |
| | | |
| | | export const GraphTools = ({ graphRef,isReady }) => { |
| | | export const GraphTools = ({ graphRef, isReady }) => { |
| | | |
| | | const exportData = () => { |
| | | const graph = graphRef.current; |
| | | if (graph) { |
| | | if (isReady) { |
| | | const data = graph.toJSON(); |
| | | console.log(data); |
| | | // 这里你可以将数据发送到服务器或保存到本地 |
| | | } |
| | | } |
| | | |
| | | return <button onClick={exportData}>导出数据</button>; |
| | | return ( |
| | | <> |
| | | <Button type="primary" onClick={exportData}> |
| | | 导出数据 |
| | | </Button> |
| | | </> |
| | | ); |
| | | } |
New file |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | import { Button } from 'antd'; |
| | | |
| | | //右键菜单组件 |
| | | export const RightMenu = ((props) => { |
| | | |
| | | useEffect(() => { |
| | | if (props.isReady) { |
| | | const graph = props.graphRef.current; |
| | | |
| | | graph.on('blank:contextmenu', ({ e, x, y }) => { |
| | | // 阻止浏览器的默认行为 |
| | | e.preventDefault(); |
| | | closeContextMenu() |
| | | openContextMenu(e.clientX, e.clientY, graph); |
| | | }); |
| | | |
| | | graph.on('cell:mouseup blank:mouseup', closeContextMenu); |
| | | document.body.addEventListener('click', closeContextMenu); |
| | | } |
| | | }) |
| | | |
| | | }) |
| | | |
| | | function openContextMenu(x, y, graph) { |
| | | // 创建你的自定义菜单元素 |
| | | let menuElement = document.createElement('div'); |
| | | menuElement.classList.add('my-context-menu'); |
| | | menuElement.style.left = `${x}px`; |
| | | menuElement.style.top = `${y}px`; |
| | | |
| | | let dataList = ['选择', '移动'] |
| | | dataList.forEach((val) => { |
| | | // 在这里填充你的菜单内容,例如 |
| | | let menuItem = document.createElement('div'); |
| | | menuItem.classList.add("right-menu-button") |
| | | menuItem.textContent = val; |
| | | menuItem.onclick = (e) => { |
| | | // 在这里处理菜单项点击事件,例如你可以根据点击的菜单项进行不同的操作 |
| | | if (e.target.textContent == "选择") { |
| | | graph.disablePanning();//禁用移动 |
| | | graph.enableRubberband();//开启框选 |
| | | } else if (e.target.textContent == "移动") { |
| | | graph.enablePanning();//开启移动 |
| | | graph.disableRubberband();//禁用框选 |
| | | } |
| | | }; |
| | | menuElement.append(menuItem); |
| | | }) |
| | | |
| | | |
| | | // 最后,将菜单添加到文档中 |
| | | document.body.append(menuElement); |
| | | } |
| | | |
| | | function closeContextMenu() { |
| | | let menuElement = document.querySelector('.my-context-menu'); |
| | | if (menuElement) { |
| | | document.body.removeChild(menuElement); |
| | | } |
| | | } |
| | |
| | | import { Graph, Shape } from "@antv/x6"; |
| | | import { GraphComponent } from "../../components/Flow/GraphComponent"; |
| | | import { GraphTools } from "../../components/Flow/GraphTools"; |
| | | import { RightMenu } from "../../components/Flow/RightMenu"; |
| | | import { GraphDrawer } from "../../components/Flow/GraphDrawer"; |
| | | import './index.less'; |
| | | |
| | | export default function () { |
| | |
| | | }, [ready]); |
| | | |
| | | return ( |
| | | |
| | | <div className="stencil-app"> |
| | | <GraphTools isReady={ready} graphRef={graphRef} /> |
| | | <GraphComponent ref={graphRef} initHandle={initHandle} /> |
| | | <RightMenu isReady={ready} graphRef={graphRef} /> |
| | | <GraphDrawer isReady={ready} graphRef={graphRef} /> |
| | | </div> |
| | | ); |
| | | } |
| | |
| | | .stencil-app { |
| | | display: flex; |
| | | padding: 0; |
| | | font-family: sans-serif; |
| | | |
| | | .app-stencil { |
| | | position: relative; |
| | | width: 300px; |
| | | border: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .app-content { |
| | | flex: 1; |
| | | height: 380px; |
| | | margin-right: 8px; |
| | | margin-left: 8px; |
| | | box-shadow: 0 0 10px 1px #e9e9e9; |
| | | } |
| | | display: flex; |
| | | padding: 0; |
| | | font-family: sans-serif; |
| | | |
| | | .app-stencil { |
| | | position: relative; |
| | | width: 300px; |
| | | border: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | |
| | | .app-content { |
| | | flex: 1; |
| | | height: 380px; |
| | | margin-right: 8px; |
| | | margin-left: 8px; |
| | | box-shadow: 0 0 10px 1px #e9e9e9; |
| | | } |
| | | } |
| | | |
| | | .x6-node.node-active .x6-port .x6-port-body { |
| | | visibility: visible; |
| | | } |
| | | |
| | | .my-context-menu { |
| | | width: 150px; |
| | | // height: 100px; |
| | | background: #fff; |
| | | position: absolute; |
| | | border-radius: 10px; |
| | | overflow: hidden; |
| | | border: 1px #000 solid; |
| | | } |
| | | |
| | | .right-menu-button { |
| | | background: #fff; |
| | | padding: 5px 10px; |
| | | color: #000; |
| | | text-align: center; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .right-menu-button:hover { |
| | | background: rgb(22,119,255); |
| | | color: #fff; |
| | | } |