Merge branch 'master' of http://47.97.1.152:5880/r/zy-asrs-master
| | |
| | | "@ant-design/icons": "^5.3.0", |
| | | "@ant-design/pro-components": "^2.6.48", |
| | | "@antv/g6": "^4.8.24", |
| | | "@antv/x6": "^2.18.1", |
| | | "@antv/x6-plugin-stencil": "^2.1.5", |
| | | "@antv/xflow": "^2.0.4", |
| | | "@umijs/route-utils": "^2.2.2", |
| | | "antd": "^5.13.2", |
New file |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | import { Graph, Shape } from "@antv/x6"; |
| | | import { Snapline } from '@antv/x6-plugin-snapline'; |
| | | import { Selection } from '@antv/x6-plugin-selection'; |
| | | import { Keyboard } from '@antv/x6-plugin-keyboard'; |
| | | 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"; |
| | | |
| | | |
| | | export const GraphComponent = React.forwardRef((props, ref) => { |
| | | const container = useRef(null); |
| | | const stencilContainer = useRef(null); |
| | | |
| | | useEffect(() => { |
| | | const graph = initGrap(); |
| | | initBind(graph); |
| | | initStencil(graph); |
| | | |
| | | return () => graph.dispose(); |
| | | }) |
| | | |
| | | 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, |
| | | } |
| | | }); |
| | | |
| | | const graph = ref.current; |
| | | |
| | | const rect = graph.addNode({ |
| | | x: 60, |
| | | y: 60, |
| | | width: 120, |
| | | height: 40, |
| | | label: '订单管理', |
| | | ports: commonGraphPorts, |
| | | attrs: commonGraphAttrs, |
| | | }); |
| | | |
| | | const rect2 = graph.addNode({ |
| | | x: 240, |
| | | y: 240, |
| | | width: 120, |
| | | height: 40, |
| | | label: '库存管理', |
| | | ports: commonGraphPorts, |
| | | attrs: commonGraphAttrs, |
| | | }); |
| | | |
| | | graph.use( |
| | | new Snapline({ |
| | | enabled: true, |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new Selection({ |
| | | enabled: true, |
| | | multiple: true, |
| | | rubberband: true, |
| | | movable: true, |
| | | showNodeSelectionBox: true, |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new History({ |
| | | enabled: true, |
| | | }), |
| | | ) |
| | | |
| | | props.initHandle();//通知父组件初始化完成 |
| | | return graph; |
| | | } |
| | | |
| | | function initStencil(graph) { |
| | | const stencil = new Stencil({ |
| | | title: '全部', |
| | | target: graph, |
| | | search(cell, keyword) { |
| | | return cell.label.indexOf(keyword) !== -1 |
| | | }, |
| | | placeholder: '搜索组件', |
| | | notFoundText: '组件不存在', |
| | | collapsable: true, |
| | | stencilGraphHeight: 0, |
| | | groups: [ |
| | | { |
| | | name: 'group1', |
| | | title: '常用组件', |
| | | } |
| | | ], |
| | | }) |
| | | |
| | | stencilContainer.current.appendChild(stencil.container) |
| | | |
| | | |
| | | |
| | | const n1 = graph.createNode({ |
| | | shape: "rect", |
| | | width: 80, |
| | | height: 40, |
| | | label: "默认组件", |
| | | attrs: commonGraphAttrs, |
| | | }) |
| | | |
| | | const n2 = graph.createNode({ |
| | | shape: "rect", |
| | | width: 80, |
| | | height: 40, |
| | | label: "测试组件", |
| | | attrs: commonGraphAttrs, |
| | | }) |
| | | |
| | | stencil.load([n1, n2], 'group1') |
| | | } |
| | | |
| | | function initBind(graph) { |
| | | graph.use( |
| | | new Clipboard({ |
| | | enabled: true, |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new Keyboard({ |
| | | enabled: true, |
| | | global: true, |
| | | }) |
| | | ) |
| | | |
| | | graph.bindKey('ctrl+c', () => { |
| | | const cells = graph.getSelectedCells() |
| | | if (cells.length) { |
| | | graph.copy(cells) |
| | | } |
| | | return false |
| | | }) |
| | | |
| | | graph.bindKey('ctrl+v', () => { |
| | | if (!graph.isClipboardEmpty()) { |
| | | const cells = graph.paste({ offset: 32 }) |
| | | graph.cleanSelection() |
| | | graph.select(cells) |
| | | } |
| | | return false |
| | | }) |
| | | |
| | | graph.bindKey('ctrl+z', () => { |
| | | graph.undo() |
| | | return false |
| | | }) |
| | | |
| | | graph.bindKey('ctrl+y', () => { |
| | | graph.redo() |
| | | return false |
| | | }) |
| | | |
| | | graph.bindKey('del', (e) => { |
| | | let nodeSelected = graph.getSelectedCells(); // 获取选中的元素 |
| | | nodeSelected.forEach(cell => { |
| | | graph.removeCell(cell) |
| | | }) |
| | | return false |
| | | }) |
| | | } |
| | | |
| | | return ( |
| | | <> |
| | | <div className="app-stencil" ref={stencilContainer} /> |
| | | <div className="app-content" ref={container} /> |
| | | </> |
| | | ); |
| | | }) |
New file |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | |
| | | const commonGraphPorts = { |
| | | groups: { |
| | | top: { |
| | | position: 'top', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | bottom: { |
| | | position: 'bottom', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | left: { |
| | | position: 'left', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | right: { |
| | | position: 'right', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | | items: [ |
| | | { |
| | | id: 'port1', |
| | | group: 'top', |
| | | }, |
| | | { |
| | | id: 'port2', |
| | | group: 'bottom', |
| | | }, |
| | | { |
| | | id: 'port3', |
| | | group: 'left', |
| | | }, |
| | | { |
| | | id: 'port4', |
| | | group: 'right', |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | const commonGraphAttrs = { |
| | | body: { |
| | | fill: '#efefef', |
| | | stroke: '#4d4d4d', |
| | | strokeWidth: 2, |
| | | }, |
| | | } |
| | | |
| | | export { commonGraphPorts, commonGraphAttrs } |
New file |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | |
| | | export const GraphTools = ({ graphRef,isReady }) => { |
| | | |
| | | const exportData = () => { |
| | | const graph = graphRef.current; |
| | | if (graph) { |
| | | const data = graph.toJSON(); |
| | | console.log(data); |
| | | // 这里你可以将数据发送到服务器或保存到本地 |
| | | } |
| | | } |
| | | |
| | | return <button onClick={exportData}>导出数据</button>; |
| | | } |
New file |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | import { Stencil } from '@antv/x6-plugin-stencil'; |
| | | |
| | | export const StencilComponent = (({ graphRef, isReady, stencilContainer }) => { |
| | | |
| | | useEffect(() => { |
| | | if (isReady) { |
| | | const graph = graphRef.current; |
| | | |
| | | const stencil = new Stencil({ |
| | | title: 'Stencil', |
| | | target: graphRef.current, |
| | | search(cell, keyword) { |
| | | return cell.shape.indexOf(keyword) !== -1 |
| | | }, |
| | | placeholder: 'Search by shape name', |
| | | notFoundText: 'Not Found', |
| | | collapsable: true, |
| | | stencilGraphHeight: 0, |
| | | groups: [ |
| | | { |
| | | name: 'group1', |
| | | title: 'Group(Collapsable)', |
| | | }, |
| | | { |
| | | name: 'group2', |
| | | title: 'Group', |
| | | collapsable: false, |
| | | }, |
| | | ], |
| | | }) |
| | | |
| | | stencilContainer.current.appendChild(stencil.container) |
| | | |
| | | const commonAttrs = { |
| | | body: { |
| | | fill: '#fff', |
| | | stroke: '#8f8f8f', |
| | | strokeWidth: 1, |
| | | }, |
| | | } |
| | | |
| | | const n1 = graph.createNode({ |
| | | shape: 'rect', |
| | | x: 40, |
| | | y: 40, |
| | | width: 80, |
| | | height: 40, |
| | | label: 'rect', |
| | | attrs: commonAttrs, |
| | | }) |
| | | |
| | | const n2 = graph.createNode({ |
| | | shape: 'circle', |
| | | x: 180, |
| | | y: 40, |
| | | width: 40, |
| | | height: 40, |
| | | label: 'circle', |
| | | attrs: commonAttrs, |
| | | }) |
| | | |
| | | const n3 = graph.createNode({ |
| | | shape: 'ellipse', |
| | | x: 280, |
| | | y: 40, |
| | | width: 80, |
| | | height: 40, |
| | | label: 'ellipse', |
| | | attrs: commonAttrs, |
| | | }) |
| | | |
| | | const n4 = graph.createNode({ |
| | | shape: 'path', |
| | | x: 420, |
| | | y: 40, |
| | | width: 40, |
| | | height: 40, |
| | | // https://www.svgrepo.com/svg/13653/like |
| | | path: 'M24.85,10.126c2.018-4.783,6.628-8.125,11.99-8.125c7.223,0,12.425,6.179,13.079,13.543c0,0,0.353,1.828-0.424,5.119c-1.058,4.482-3.545,8.464-6.898,11.503L24.85,48L7.402,32.165c-3.353-3.038-5.84-7.021-6.898-11.503c-0.777-3.291-0.424-5.119-0.424-5.119C0.734,8.179,5.936,2,13.159,2C18.522,2,22.832,5.343,24.85,10.126z', |
| | | attrs: commonAttrs, |
| | | label: 'path123', |
| | | }) |
| | | |
| | | stencil.load([n1, n2], 'group1') |
| | | stencil.load([n3, n4], 'group2') |
| | | } |
| | | |
| | | }) |
| | | |
| | | return <div className="app-stencil" ref={stencilContainer} /> |
| | | }) |
| | |
| | | // 接口地址 |
| | | export const API_BASE_URL: string = 'http://172.16.0.219:9090/wcs'; |
| | | export const API_BASE_URL: string = 'http://127.0.0.1:9090/wcs'; |
| | | |
| | | // 项目名称 |
| | | export const PROJECT_NAME: string = 'admin'; |
| | |
| | | background-color: rgba(255, 255, 255, 0.9); |
| | | padding: 10px 8px; |
| | | box-shadow: rgb(174, 174, 174) 0px 0px 10px; |
| | | } |
| | | |
| | | .modalInput { |
| | | margin-top: 30px; |
| | | margin-bottom: 30px; |
| | | } |
| | |
| | | import React, { useEffect, useState } from 'react'; |
| | | import ReactDOM from 'react-dom'; |
| | | import { data } from './data'; |
| | | import G6 from '@antv/g6'; |
| | | import "./index.css" |
| | | import React, { useEffect, useRef, useState } from "react"; |
| | | import { Graph, Shape } from "@antv/x6"; |
| | | import { GraphComponent } from "../../components/Flow/GraphComponent"; |
| | | import { GraphTools } from "../../components/Flow/GraphTools"; |
| | | import './index.less'; |
| | | |
| | | export default function() { |
| | | const ref = React.useRef(null); |
| | | let graph = null; |
| | | export default function () { |
| | | const graphRef = useRef(null); |
| | | const [ready, setReady] = useState(false); |
| | | |
| | | useEffect(() => { |
| | | if (!graph) { |
| | | graph = new G6.Graph({ |
| | | container: ReactDOM.findDOMNode(ref.current), |
| | | width: document.documentElement.clientWidth, |
| | | height: document.documentElement.clientHeight, |
| | | modes: { |
| | | default: [ |
| | | 'drag-canvas', |
| | | 'zoom-canvas', |
| | | 'drag-node', |
| | | ], |
| | | edit: ['click-select'] |
| | | }, |
| | | layout: { |
| | | type: 'dagre', |
| | | direction: 'LR', |
| | | }, |
| | | defaultNode: { |
| | | shape: 'node', |
| | | type: 'rect', |
| | | labelCfg: { |
| | | style: { |
| | | fill: '#000000A6', |
| | | fontSize: 14, |
| | | }, |
| | | }, |
| | | style: { |
| | | // 仅在 keyShape 上生效 |
| | | fill: 'lightblue', |
| | | stroke: '#888', |
| | | lineWidth: 1, |
| | | radius: 7, |
| | | }, |
| | | linkPoints: { |
| | | top: true, |
| | | bottom: true, |
| | | left: true, |
| | | right: true, |
| | | // ... 四个圆的样式可以在这里指定 |
| | | }, |
| | | }, |
| | | defaultEdge: { |
| | | shape: 'polyline', |
| | | }, |
| | | nodeStateStyles: { |
| | | // 各状态下的样式,平铺的配置项仅在 keyShape 上生效。需要在其他 shape 样式上响应状态变化则写法不同,参见上文提到的 配置状态样式 链接 |
| | | hover: { |
| | | fillOpacity: 0.1, |
| | | lineWidth: 1, |
| | | }, |
| | | }, |
| | | }); |
| | | const initHandle = () => { |
| | | setReady(true); |
| | | } |
| | | graph.data(data); |
| | | graph.render(); |
| | | |
| | | // 监听鼠标进入节点事件 |
| | | graph.on('node:mouseenter', (evt) => { |
| | | const node = evt.item; |
| | | // 激活该节点的 hover 状态 |
| | | graph.setItemState(node, 'hover', true); |
| | | }); |
| | | |
| | | // 监听鼠标离开节点事件 |
| | | graph.on('node:mouseleave', (evt) => { |
| | | const node = evt.item; |
| | | // 关闭该节点的 hover 状态 |
| | | graph.setItemState(node, 'hover', false); |
| | | }); |
| | | useEffect(() => { |
| | | if (ready) { |
| | | // 你需要在loading状态改变后执行的代码 |
| | | console.log('graphRef is ready:', graphRef.current); |
| | | } |
| | | }, [ready]); |
| | | |
| | | graph.on('node:click', (evt) => { |
| | | const node = evt.item; |
| | | const model = node.getModel(); |
| | | // 在这里打开模态框或者编辑表格,传入model以便用于初始化编辑内容 |
| | | showModal(model); |
| | | }); |
| | | }, []); |
| | | |
| | | return <div ref={ref}></div>; |
| | | return ( |
| | | |
| | | <div className="stencil-app"> |
| | | <GraphTools isReady={ready} graphRef={graphRef} /> |
| | | <GraphComponent ref={graphRef} initHandle={initHandle} /> |
| | | </div> |
| | | ); |
| | | } |
New file |
| | |
| | | import React, { useRef, useEffect } from "react"; |
| | | import { Graph, Shape } from "@antv/x6"; |
| | | import { Snapline } from '@antv/x6-plugin-snapline'; |
| | | import { Selection } from '@antv/x6-plugin-selection'; |
| | | import { Keyboard } from '@antv/x6-plugin-keyboard'; |
| | | import { Clipboard } from '@antv/x6-plugin-clipboard'; |
| | | import { Stencil } from '@antv/x6-plugin-stencil'; |
| | | import './index.less'; |
| | | |
| | | |
| | | export default function () { |
| | | const container = useRef(null); |
| | | const stencilContainer = useRef(null); |
| | | const graphRef = useRef(null); |
| | | |
| | | useEffect(() => { |
| | | graphRef.current = new Graph({ |
| | | container: container.current, |
| | | // width: document.documentElement.clientWidth, |
| | | // height: document.documentElement.clientHeight, |
| | | // width: 200, |
| | | // height: 300, |
| | | // 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, |
| | | }, |
| | | }); |
| | | |
| | | const graph = graphRef.current; |
| | | |
| | | // // 开启节点可以被拖动交互 |
| | | // graph.on('cell:mouseenter', ({ cell }) => { |
| | | // if (cell.isNode()) { |
| | | // cell.addTools([ |
| | | // { |
| | | // name: 'boundary', |
| | | // }, |
| | | // ]) |
| | | // } |
| | | // }) |
| | | |
| | | // graph.on('node:mouseleave', ({ cell }) => { |
| | | // cell.removeTools() |
| | | // }) |
| | | |
| | | const rect = graph.addNode({ |
| | | x: 60, |
| | | y: 60, |
| | | width: 120, |
| | | height: 40, |
| | | label: '订单管理', |
| | | ports: { |
| | | groups: { |
| | | top: { |
| | | position: 'top', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | bottom: { |
| | | position: 'bottom', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | left: { |
| | | position: 'left', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | right: { |
| | | position: 'right', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | | items: [ |
| | | { |
| | | id: 'port1', |
| | | group: 'top', |
| | | }, |
| | | { |
| | | id: 'port2', |
| | | group: 'bottom', |
| | | }, |
| | | { |
| | | id: 'port3', |
| | | group: 'left', |
| | | }, |
| | | { |
| | | id: 'port4', |
| | | group: 'right', |
| | | }, |
| | | ], |
| | | }, |
| | | attrs: { |
| | | body: { |
| | | fill: '#efefef', |
| | | stroke: '#4d4d4d', |
| | | strokeWidth: 2, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | const rect2 = graph.addNode({ |
| | | x: 240, |
| | | y: 240, |
| | | width: 120, |
| | | height: 40, |
| | | label: '库存管理', |
| | | ports: { |
| | | groups: { |
| | | top: { |
| | | position: 'top', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | bottom: { |
| | | position: 'bottom', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | left: { |
| | | position: 'left', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | right: { |
| | | position: 'right', |
| | | attrs: { |
| | | circle: { |
| | | magnet: true, |
| | | stroke: '#8f8f8f', |
| | | r: 5, |
| | | }, |
| | | }, |
| | | }, |
| | | }, |
| | | items: [ |
| | | { |
| | | id: 'port1', |
| | | group: 'top', |
| | | }, |
| | | { |
| | | id: 'port2', |
| | | group: 'bottom', |
| | | }, |
| | | { |
| | | id: 'port3', |
| | | group: 'left', |
| | | }, |
| | | { |
| | | id: 'port4', |
| | | group: 'right', |
| | | }, |
| | | ], |
| | | }, |
| | | attrs: { |
| | | body: { |
| | | fill: '#efefef', |
| | | stroke: '#4d4d4d', |
| | | strokeWidth: 2, |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | // graph.addEdge({ |
| | | // source: { cell: rect.id }, |
| | | // target: { cell: rect2.id }, |
| | | // }); |
| | | |
| | | graph.use( |
| | | new Snapline({ |
| | | enabled: true, |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new Selection({ |
| | | enabled: true, |
| | | multiple: true, |
| | | rubberband: true, |
| | | movable: true, |
| | | showNodeSelectionBox: true, |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new Clipboard({ |
| | | enabled: true, |
| | | }), |
| | | ) |
| | | |
| | | graph.use( |
| | | new Keyboard({ |
| | | enabled: true, |
| | | global: true, |
| | | }) |
| | | ) |
| | | |
| | | graph.bindKey('ctrl+c', () => { |
| | | const cells = graph.getSelectedCells() |
| | | if (cells.length) { |
| | | graph.copy(cells) |
| | | } |
| | | return false |
| | | }) |
| | | |
| | | graph.bindKey('ctrl+v', () => { |
| | | if (!graph.isClipboardEmpty()) { |
| | | const cells = graph.paste({ offset: 32 }) |
| | | graph.cleanSelection() |
| | | graph.select(cells) |
| | | } |
| | | return false |
| | | }) |
| | | |
| | | const stencil = new Stencil({ |
| | | title: 'Stencil', |
| | | target: graph, |
| | | search(cell, keyword) { |
| | | return cell.shape.indexOf(keyword) !== -1 |
| | | }, |
| | | placeholder: 'Search by shape name', |
| | | notFoundText: 'Not Found', |
| | | collapsable: true, |
| | | stencilGraphHeight: 0, |
| | | groups: [ |
| | | { |
| | | name: 'group1', |
| | | title: 'Group(Collapsable)', |
| | | }, |
| | | { |
| | | name: 'group2', |
| | | title: 'Group', |
| | | collapsable: false, |
| | | }, |
| | | ], |
| | | }) |
| | | |
| | | stencilContainer.current.appendChild(stencil.container) |
| | | |
| | | const commonAttrs = { |
| | | body: { |
| | | fill: '#fff', |
| | | stroke: '#8f8f8f', |
| | | strokeWidth: 1, |
| | | }, |
| | | } |
| | | |
| | | const n1 = graph.createNode({ |
| | | shape: 'rect', |
| | | x: 40, |
| | | y: 40, |
| | | width: 80, |
| | | height: 40, |
| | | label: 'rect', |
| | | attrs: commonAttrs, |
| | | }) |
| | | |
| | | const n2 = graph.createNode({ |
| | | shape: 'circle', |
| | | x: 180, |
| | | y: 40, |
| | | width: 40, |
| | | height: 40, |
| | | label: 'circle', |
| | | attrs: commonAttrs, |
| | | }) |
| | | |
| | | const n3 = graph.createNode({ |
| | | shape: 'ellipse', |
| | | x: 280, |
| | | y: 40, |
| | | width: 80, |
| | | height: 40, |
| | | label: 'ellipse', |
| | | attrs: commonAttrs, |
| | | }) |
| | | |
| | | const n4 = graph.createNode({ |
| | | shape: 'path', |
| | | x: 420, |
| | | y: 40, |
| | | width: 40, |
| | | height: 40, |
| | | // https://www.svgrepo.com/svg/13653/like |
| | | path: 'M24.85,10.126c2.018-4.783,6.628-8.125,11.99-8.125c7.223,0,12.425,6.179,13.079,13.543c0,0,0.353,1.828-0.424,5.119c-1.058,4.482-3.545,8.464-6.898,11.503L24.85,48L7.402,32.165c-3.353-3.038-5.84-7.021-6.898-11.503c-0.777-3.291-0.424-5.119-0.424-5.119C0.734,8.179,5.936,2,13.159,2C18.522,2,22.832,5.343,24.85,10.126z', |
| | | attrs: commonAttrs, |
| | | label: 'path', |
| | | }) |
| | | |
| | | stencil.load([n1, n2], 'group1') |
| | | stencil.load([n3, n4], 'group2') |
| | | |
| | | return () => graph.dispose(); |
| | | }, []); |
| | | |
| | | const exportData = () => { |
| | | if (graphRef.current) { |
| | | const data = graphRef.current.toJSON(); |
| | | console.log(data); |
| | | } |
| | | } |
| | | |
| | | return ( |
| | | <div className="stencil-app"> |
| | | <button onClick={exportData}>导出数据</button> |
| | | <div className="app-stencil" ref={stencilContainer} /> |
| | | <div className="app-content" ref={container} /> |
| | | </div> |
| | | ); |
| | | } |
New file |
| | |
| | | import React, { useEffect, useState,useRef } from 'react'; |
| | | import ReactDOM from 'react-dom'; |
| | | import { data } from './data'; |
| | | import G6 from '@antv/g6'; |
| | | import "./index.css" |
| | | import { Modal, Input } from 'antd'; |
| | | |
| | | export default function() { |
| | | const ref = useRef(null); |
| | | const graphRef = useRef(null); |
| | | |
| | | useEffect(() => { |
| | | if (!graphRef.current) { |
| | | // // 注册自定义行为 |
| | | // G6.registerBehavior('drag-line', { |
| | | // getEvents() { |
| | | // return { |
| | | // 'edge:mousedown': 'onDragStart', |
| | | // 'canvas:mousemove': 'onDrag', |
| | | // 'canvas:mouseup': 'onDragEnd', |
| | | // } |
| | | // }, |
| | | // onDragStart(ev) { |
| | | // const edge = ev.item; |
| | | // this.edge = edge; |
| | | // this.dragging = true; |
| | | // console.log(ev) |
| | | // }, |
| | | // onDrag(ev) { |
| | | // if (!this.dragging) { |
| | | // return; |
| | | // } |
| | | // const { x, y } = ev; |
| | | |
| | | // // 更新边的 target(终点) |
| | | // this.edge.update({ |
| | | // target: { x, y }, |
| | | // }); |
| | | // }, |
| | | // onDragEnd() { |
| | | // this.edge = null; |
| | | // this.dragging = false; |
| | | // } |
| | | // }); |
| | | |
| | | G6.registerBehavior('click-add-edge', { |
| | | getEvents() { |
| | | return { |
| | | 'node:click': 'onClick', |
| | | mousemove: 'onMousemove', |
| | | 'edge:click': 'onEdgeClick' // 点击空白处,取消边 |
| | | }; |
| | | }, |
| | | onClick(ev) { |
| | | const node = ev.item; |
| | | const graph = this.graph; |
| | | const point = { |
| | | x: ev.x, |
| | | y: ev.y |
| | | }; |
| | | const model = node.getModel(); |
| | | if (this.addingEdge && this.edge) { |
| | | graph.updateItem(this.edge, { |
| | | target: model.id |
| | | }); |
| | | // graph.setItemState(this.edge, 'selected', true); |
| | | this.edge = null; |
| | | this.addingEdge = false; |
| | | } else { |
| | | this.edge = graph.addItem('edge', { |
| | | source: model.id, |
| | | target: point |
| | | }); |
| | | this.addingEdge = true; |
| | | } |
| | | }, |
| | | onMousemove(ev) { |
| | | const point = { |
| | | x: ev.x, |
| | | y: ev.y |
| | | }; |
| | | if (this.addingEdge && this.edge) { |
| | | this.graph.updateItem(this.edge, { |
| | | target: point |
| | | }); |
| | | } |
| | | }, |
| | | onEdgeClick(ev) { |
| | | const currentEdge = ev.item; |
| | | // 拖拽过程中,点击会点击到新增的边上 |
| | | if (this.addingEdge && this.edge == currentEdge) { |
| | | graph.removeItem(this.edge); |
| | | this.edge = null; |
| | | this.addingEdge = false; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | graphRef.current = new G6.Graph({ |
| | | container: ReactDOM.findDOMNode(ref.current), |
| | | width: document.documentElement.clientWidth, |
| | | height: document.documentElement.clientHeight, |
| | | modes: { |
| | | default: [ |
| | | 'drag-canvas', |
| | | 'zoom-canvas', |
| | | 'drag-node', |
| | | 'drag-line', |
| | | 'click-add-edge' |
| | | ], |
| | | addEdge: ['click-add-edge', 'click-select'] |
| | | }, |
| | | layout: { |
| | | type: 'dagre', |
| | | direction: 'LR', |
| | | }, |
| | | defaultNode: { |
| | | shape: 'node', |
| | | type: 'rect', |
| | | labelCfg: { |
| | | style: { |
| | | fill: '#000000A6', |
| | | fontSize: 14, |
| | | }, |
| | | }, |
| | | style: { |
| | | // 仅在 keyShape 上生效 |
| | | fill: 'lightblue', |
| | | stroke: '#888', |
| | | lineWidth: 1, |
| | | radius: 7, |
| | | }, |
| | | // linkPoints: { |
| | | // top: true, |
| | | // bottom: true, |
| | | // left: true, |
| | | // right: true, |
| | | // // ... 四个圆的样式可以在这里指定 |
| | | // }, |
| | | }, |
| | | defaultEdge: { |
| | | shape: 'polyline', |
| | | }, |
| | | nodeStateStyles: { |
| | | // 各状态下的样式,平铺的配置项仅在 keyShape 上生效。需要在其他 shape 样式上响应状态变化则写法不同,参见上文提到的 配置状态样式 链接 |
| | | hover: { |
| | | fillOpacity: 0.1, |
| | | lineWidth: 1, |
| | | }, |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | const graph = graphRef.current; |
| | | |
| | | graph.data(data); |
| | | graph.render(); |
| | | |
| | | graph.setMode("edit") |
| | | |
| | | // 监听鼠标进入节点事件 |
| | | graph.on('node:mouseenter', (evt) => { |
| | | const node = evt.item; |
| | | // 激活该节点的 hover 状态 |
| | | graph.setItemState(node, 'hover', true); |
| | | }); |
| | | |
| | | // 监听鼠标离开节点事件 |
| | | graph.on('node:mouseleave', (evt) => { |
| | | const node = evt.item; |
| | | // 关闭该节点的 hover 状态 |
| | | graph.setItemState(node, 'hover', false); |
| | | }); |
| | | |
| | | graph.on('node:dblclick', (evt) => { |
| | | const node = evt.item; |
| | | const model = node.getModel(); |
| | | // 在此处执行显示一个模态框或输入框的操作,将model.id传入用来确定哪个节点,model.label传入用来显示当前节点文字 |
| | | showModal(model.id, model.label); |
| | | }); |
| | | |
| | | // 添加行为 |
| | | graph.on('edge:click', (evt) => { |
| | | const { item } = evt; |
| | | |
| | | // 获取边的模型数据 |
| | | const model = item.getModel(); |
| | | |
| | | // 切换选中状态 |
| | | if (model.style?.stroke === '#f00') { |
| | | // 如果边处于选中状态,则取消选中 |
| | | graph.updateItem(item, { |
| | | style: { |
| | | ...model.style, |
| | | stroke: '#000', |
| | | }, |
| | | }); |
| | | } else { |
| | | // 如果边未被选中,则选中当前边 |
| | | graph.updateItem(item, { |
| | | style: { |
| | | ...model.style, |
| | | stroke: '#f00', |
| | | }, |
| | | }); |
| | | } |
| | | }); |
| | | }, []); |
| | | |
| | | const [modalVisible, setModalVisible] = useState(false); |
| | | const [currentNode, setCurrentNode] = useState(null); |
| | | const [currentLabel, setCurrentLabel] = useState(''); |
| | | |
| | | const showModal = (id, label) => { |
| | | setCurrentNode(id); // 设置当前节点 |
| | | setCurrentLabel(label); // 设置当前节点文字 |
| | | setModalVisible(true); // 打开模态框 |
| | | }; |
| | | |
| | | const updateLabel = (nodeId, newLabel) => { |
| | | if(graphRef.current){ |
| | | const node = graphRef.current.findById(nodeId); |
| | | graphRef.current.update(node, { |
| | | label: newLabel, |
| | | }); |
| | | graphRef.current.refresh(); |
| | | } |
| | | }; |
| | | |
| | | const handleOk = () => { |
| | | // 点击确定后更新节点 |
| | | updateLabel(currentNode, currentLabel); |
| | | setModalVisible(false); |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | // 关闭模态框 |
| | | setModalVisible(false); |
| | | }; |
| | | |
| | | return ( |
| | | <> |
| | | <div ref={ref} /> {/* g6图表的容器 */} |
| | | <Modal |
| | | open={modalVisible} |
| | | onOk={handleOk} |
| | | onCancel={handleCancel} |
| | | > |
| | | <div className='modalInput'> |
| | | <Input value={currentLabel} onChange={e => setCurrentLabel(e.target.value)} /> |
| | | </div> |
| | | </Modal> |
| | | </> |
| | | ); |
| | | } |
New file |
| | |
| | | .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; |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | import React from 'react' |
| | | import { NsGraph } from '@antv/xflow' |
| | | import { useAppContext } from '@antv/xflow' |
| | | import './edge2.less' |
| | | |
| | | const Edge2 = props => { |
| | | return <div className="edge2-container">React2</div> |
| | | } |
| | | export default Edge2 |
New file |
| | |
| | | import React from 'react' |
| | | import { NsGraph } from '@antv/xflow' |
| | | import { useAppContext } from '@antv/xflow' |
| | | import { Tooltip } from 'antd' |
| | | import './edge1.less' |
| | | |
| | | const Edge1 = props => { |
| | | const ctx = useAppContext() |
| | | // console.log('edge useAppContext', ctx); |
| | | // console.log('edge props:', props); |
| | | return ( |
| | | <div className="edge1-container"> |
| | | <Tooltip |
| | | title="这是连线上渲染的React内容" |
| | | // defaultVisible={true} |
| | | > |
| | | <div>hover我</div> |
| | | </Tooltip> |
| | | </div> |
| | | ) |
| | | } |
| | | export default Edge1 |
New file |
| | |
| | | .edge1-container { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 14px; |
| | | background-color: #fff; |
| | | border: 1px solid #690; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | } |
New file |
| | |
| | | .edge2-container { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 14px; |
| | | background-color: #fff; |
| | | border: 1px solid #690; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | } |
New file |
| | |
| | | import { NsGraph } from '@antv/xflow' |
| | | import React from 'react' |
| | | import './node1.less' |
| | | |
| | | const Node1 = props => { |
| | | /** |
| | | * 1. 节点的数据、位置信息通过props取 |
| | | * 2. 当节点被触发更新时, props返回的数据也会动态更新, 触发节点重新渲染 |
| | | */ |
| | | return ( |
| | | <div className="node1-container"> |
| | | <div>{'React节点1'}</div> |
| | | </div> |
| | | ) |
| | | } |
| | | export default Node1 |
New file |
| | |
| | | .node1-container { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | height: 100%; |
| | | font-weight: 600; |
| | | background-color: #fff; |
| | | border: 1px solid #873bf4; |
| | | border-radius: 4px; |
| | | } |
New file |
| | |
| | | import { NsGraph } from '@antv/xflow' |
| | | import React from 'react' |
| | | import { useAppContext } from '@antv/xflow' |
| | | import './node2.less' |
| | | |
| | | const Node2 = props => { |
| | | const ctx = useAppContext() |
| | | |
| | | return ( |
| | | <div className="node2-container"> |
| | | <div>{'React节点2'}</div> |
| | | </div> |
| | | ) |
| | | } |
| | | export default Node2 |
New file |
| | |
| | | .node2-container { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 100%; |
| | | height: 100%; |
| | | font-weight: 600; |
| | | background-color: #fff; |
| | | border: 1px solid #dd4a68; |
| | | border-radius: 4px; |
| | | } |