From e3d1f957fb81d28210a779a9fcd92525b80145e9 Mon Sep 17 00:00:00 2001 From: zhf <1204297681@qq.com> Date: Fri, 23 Aug 2024 20:47:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ER=E8=AE=BE=E8=AE=A1=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E8=BF=9E=E7=BA=BF=E9=94=9A=E7=82=B9=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../er-design-left-side.scss | 1 + .../er-design-right-toolbar.controller.ts | 79 ++++++++++- .../er-design-right-toolbar.scss | 4 + .../er-design-right-toolbar.state.ts | 9 ++ .../er-design-right-toolbar.tsx | 9 +- packages/er-design/src/style/index.scss | 45 ++++-- packages/er-design/src/utils/er-util.ts | 114 +++++++++++++++ .../x6-cell-controller/x6-cell-controller.ts | 12 ++ .../controller/x6-controller/x6-controller.ts | 40 +++++- .../x6-link-controller/x6-link-controller.ts | 131 +++++++++++++++++- 10 files changed, 426 insertions(+), 18 deletions(-) diff --git a/packages/er-design/src/panel-items/er-design-left-side/er-design-left-side.scss b/packages/er-design/src/panel-items/er-design-left-side/er-design-left-side.scss index 9e95131c..e396b71d 100644 --- a/packages/er-design/src/panel-items/er-design-left-side/er-design-left-side.scss +++ b/packages/er-design/src/panel-items/er-design-left-side/er-design-left-side.scss @@ -173,6 +173,7 @@ cursor: pointer; @include e(icon) { + flex: 0 0 auto; margin-right: getCssVar('spacing', 'tight'); .#{bem('icon')} { diff --git a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.controller.ts b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.controller.ts index 0ce159fe..e5c22b52 100644 --- a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.controller.ts +++ b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.controller.ts @@ -1,8 +1,10 @@ import { PanelItemController } from '@ibiz-template/runtime'; +import { Edge, Node } from '@antv/x6'; import { ERDesignRightToolbarState } from './er-design-right-toolbar.state'; import { ERDesignLeftSideController } from '../er-design-left-side/er-design-left-side.controller'; -import { X6Controller } from '../../x6'; +import { X6Controller, X6NodeData } from '../../x6'; import { ERDesignContentController } from '../er_design_content/er-design-content.controller'; +import { addToggleButton, removeToggleButton } from '../../utils/er-util'; export class ERDesignRightToolbarController extends PanelItemController { declare state: ERDesignRightToolbarState; @@ -71,6 +73,7 @@ export class ERDesignRightToolbarController extends PanelItemController { this.x6?.g.on('scale', ({ sy }) => { this.state.zoomValue = Number(sy.toFixed(1)); }); + this.state.isDefaultOpenAnchorEdit = this.x6.getDefaultOpenAnchorEdit(); } /** @@ -111,7 +114,23 @@ export class ERDesignRightToolbarController extends PanelItemController { * @author zhanghengfeng * @date 2024-08-21 15:08:44 */ - editAnchor(): void {} + editAnchor(): void { + this.state.isDefaultOpenAnchorEdit = !this.state.isDefaultOpenAnchorEdit; + if (!this.x6) { + return; + } + this.x6.setDefaultOpenAnchorEdit(this.state.isDefaultOpenAnchorEdit); + const cell = this.x6.g.getSelectedCells()?.[0]; + if (cell && this.x6.g.isEdge(cell)) { + removeToggleButton(cell); + addToggleButton( + this.x6.g, + cell, + this.state.isDefaultOpenAnchorEdit, + false, + ); + } + } /** * 移除锚点 @@ -119,7 +138,61 @@ export class ERDesignRightToolbarController extends PanelItemController { * @author zhanghengfeng * @date 2024-08-21 15:08:16 */ - removeAnchor(): void {} + async removeAnchor(): Promise { + const result = await ibiz.confirm.warning({ + title: '', + desc: '是否移除图中所有自定义锚点', + }); + if (!result) { + return; + } + if (!this.x6) { + return; + } + this.x6.nodeList.forEach(item => { + const node = this.x6?.g.getCellById(item.id) as Node; + if (!node) { + return; + } + const shapeParams: Array<{ psderid: string; vertices: [] }> = JSON.parse( + node.data?.data?.shapeparams ? node.data.data.shapeparams : '[]', + ); + if (Array.isArray(shapeParams)) { + const filterShapeParams = shapeParams.filter(_item => { + if (!_item.psderid) { + return false; + } + const edge = this.x6?.g.getCellById(_item.psderid) as Edge; + if (!edge) { + return false; + } + const source = edge.source as Edge.TerminalCellData; + const target = edge.target as Edge.TerminalCellData; + if (source && target && source.cell === target.cell) { + return true; + } + return false; + }); + const data = node.data as X6NodeData; + if (data && data.data) { + data.data.shapeparams = JSON.stringify(filterShapeParams); + } + } + }); + this.x6.linkList.forEach(item => { + const edge = this.x6?.g.getCellById(item.id) as Edge; + if (!edge) { + return; + } + const source = edge.source as Edge.TerminalCellData; + const target = edge.target as Edge.TerminalCellData; + if (source && target && source.cell === target.cell) { + return; + } + edge.setVertices([]); + }); + ibiz.message.success('移除所有锚点成功'); + } /** * 处理实体菜单点击 diff --git a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.scss b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.scss index 9753d517..307f1b9a 100644 --- a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.scss +++ b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.scss @@ -120,6 +120,10 @@ height: 16px; fill: currentcolor; } + + @include when(active) { + color: getCssVar(color, primary, hover); + } } @include b(er-design-right-toolbar-anchor-remove-btn) { diff --git a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.state.ts b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.state.ts index 0f71eee5..fa456c36 100644 --- a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.state.ts +++ b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.state.ts @@ -56,4 +56,13 @@ export class ERDesignRightToolbarState extends PanelItemState { * @date 2024-08-21 15:08:47 */ activeEntityItems = ['ZH', 'EN', 'TYPE']; + + /** + * 是否默认开启连线锚点编辑 + * + * @author zhanghengfeng + * @date 2024-08-23 18:08:56 + * @type {boolean} + */ + isDefaultOpenAnchorEdit: boolean = false; } diff --git a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.tsx b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.tsx index 93be7de8..ff8c835a 100644 --- a/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.tsx +++ b/packages/er-design/src/panel-items/er-design-right-toolbar/er-design-right-toolbar.tsx @@ -56,8 +56,13 @@ export default defineComponent({
c.editAnchor()} > span .ibiz-toolbar-item-icon + .ibiz-toolbar-item-text { + .el-button + > span + .#{bem(toolbar-item-icon)} + + .#{bem(toolbar-item-text)} { margin-top: getCssVar('spacing', 'super-tight'); } } } + + .#{bem('er-toggle-button', '', 'rt')}, + .#{bem('er-toggle-button', '', 'lt')} { + transform: translateX(-8px) translateY(-24px); + } + + .#{bem('er-toggle-button', 'wrapper', 'rt')}, + .#{bem('er-toggle-button', 'wrapper', 'lt')} { + transform: translateY(-16px); + } + + .#{bem('er-toggle-button', '', 'rb')}, + .#{bem('er-toggle-button', '', 'lb')} { + transform: translateX(-8px) translateY(8px); + } + + .#{bem('er-toggle-button', 'wrapper', 'rb')}, + .#{bem('er-toggle-button', 'wrapper', 'lb')} { + transform: translateY(16px); + } } diff --git a/packages/er-design/src/utils/er-util.ts b/packages/er-design/src/utils/er-util.ts index 30841ba3..6ecc2371 100644 --- a/packages/er-design/src/utils/er-util.ts +++ b/packages/er-design/src/utils/er-util.ts @@ -1,3 +1,5 @@ +import { useNamespace } from '@ibiz-template/vue3-util'; +import { Cell, Edge, Graph, Node } from '@antv/x6'; import { X6LinkData } from '../x6'; export const getLinkLabels = (link: X6LinkData): IData => { @@ -72,3 +74,115 @@ export const getLinkLabels = (link: X6LinkData): IData => { } return result; }; + +// 是否是锚点编辑切换按钮 +export const isToggleButton = (item: Cell.ToolItem): boolean => { + if (item && typeof item === 'object') { + return ( + item.name === 'button' && item.args && item.args.name === 'toggle-button' + ); + } + return false; +}; + +// 删除锚点编辑切换按钮 +export const removeToggleButton = (edge: Edge): Boolean => { + const tools = edge.getTools()?.items; + if (tools && tools.length) { + const toggleButtonIndex = tools.findIndex(item => { + return isToggleButton(item); + }); + if (toggleButtonIndex !== -1) { + edge.removeTool(toggleButtonIndex); + return true; + } + } + return false; +}; + +// 添加锚点编辑切换按钮 +export const addToggleButton = ( + g: Graph, + edge: Edge, + active: boolean = false, + showVertices: boolean = true, +) => { + const source = edge.source as Edge.TerminalCellData; + const target = edge.target as Edge.TerminalCellData; + let position: string = 'lb'; + if (source && target) { + const sourceCell = g.getCellById(source.cell) as unknown as Node; + const targetCell = g.getCellById(target.cell) as unknown as Node; + const sourcePoint = sourceCell?.position(); + const targetPoint = targetCell?.position(); + if (sourcePoint && targetPoint) { + if (sourcePoint.x < targetPoint.x) { + if (sourcePoint.y > targetPoint.y) { + position = 'lb'; + } else { + position = 'lt'; + } + } + if (sourcePoint.y > targetPoint.y) { + position = 'rb'; + } else { + position = 'rt'; + } + } + } + const activePath = + 'M3.43085937,9.53105469 C3.47324219,9.5734375 3.54160156,9.5734375 3.58535156,9.53105469 L5.47617187,7.64980469 L7.22070312,9.40527344 C7.26308594,9.44765625 7.3328125,9.44765625 7.37519531,9.40527344 L11.1404297,5.64140625 C11.1828125,5.59902344 11.1828125,5.52929688 11.1404297,5.48691406 L10.5990234,4.94550781 C10.5566406,4.903125 10.4869141,4.903125 10.4445312,4.94550781 L7.3,8.08867188 L5.55820312,6.3359375 C5.51582031,6.29355469 5.44609375,6.29355469 5.40371094,6.3359375 L2.89082031,8.83378906 C2.8484375,8.87617188 2.8484375,8.94589844 2.89082031,8.98828125 L3.43085937,9.53105469 Z'; + const inactivePath = + 'M13.7234375,6.1265625 L9.8734375,2.2765625 C9.771875,2.175 9.6390625,2.125 9.50625,2.125 C9.3734375,2.125 9.240625,2.175 9.1390625,2.2765625 L6.621875,4.7953125 C6.43125,4.7734375 6.2390625,4.7640625 6.046875,4.7640625 C4.903125,4.7640625 3.759375,5.140625 2.8203125,5.89375 C2.5796875,6.0859375 2.559375,6.446875 2.778125,6.665625 L5.6171875,9.5046875 L2.2515625,12.8671875 C2.2109375,12.9078125 2.184375,12.9625 2.1796875,13.0203125 L2.1265625,13.6015625 C2.1125,13.7484375 2.2296875,13.8734375 2.375,13.8734375 C2.3828125,13.8734375 2.390625,13.8734375 2.3984375,13.871875 L2.9796875,13.81875 C3.0375,13.8140625 3.0921875,13.7875 3.1328125,13.746875 L6.4984375,10.38125 L9.3375,13.2203125 C9.4390625,13.321875 9.571875,13.371875 9.7046875,13.371875 C9.85625,13.371875 10.00625,13.30625 10.109375,13.178125 C10.9890625,12.0796875 11.3546875,10.7046875 11.20625,9.375 L13.7234375,6.8578125 C13.925,6.6578125 13.925,6.3296875 13.7234375,6.1265625 Z M10.409375,8.5828125 L10.0265625,8.965625 L10.0859375,9.503125 C10.14375,10.0296875 10.1015625,10.553125 9.9578125,11.0609375 C9.8734375,11.3578125 9.7578125,11.640625 9.6109375,11.9078125 L4.09375,6.3875 C4.2953125,6.2765625 4.5046875,6.1828125 4.7234375,6.1078125 C5.1484375,5.9609375 5.59375,5.8875 6.046875,5.8875 C6.196875,5.8875 6.3484375,5.8953125 6.4984375,5.9125 L7.0359375,5.971875 L7.41875,5.5890625 L9.5078125,3.5 L12.5,6.4921875 L10.409375,8.5828125 Z'; + const ns = useNamespace('er-toggle-button'); + const toggle = [ + { + tagName: 'circle', + selector: 'button', + attrs: { + r: 7, + stroke: '#FB8C02', + fill: '#FFFFFF', + class: ns.em('wrapper', position), + cursor: 'pointer', + }, + }, + { + tagName: 'path', + selector: 'icon', + attrs: { + d: active ? activePath : inactivePath, + fill: '#FB8C02', + class: ns.m(position), + stroke: 'none', + 'stroke-width': 2, + 'pointer-events': 'none', + }, + }, + ]; + + const toggleButton: Cell.ToolItem = { + name: 'button', + args: { + name: 'toggle-button', + active, + markup: toggle, + distance: 0, + zIndex: 999, + onClick: () => { + const result = removeToggleButton(edge); + if (result) { + addToggleButton(g, edge, !active); + } + }, + }, + }; + edge.addTools(toggleButton); + const hasVerticesTool = edge.hasTool('vertices'); + if (hasVerticesTool && !active) { + edge.removeTool('vertices'); + } + if (!hasVerticesTool && active && showVertices) { + edge.addTools('vertices'); + } +}; diff --git a/packages/er-design/src/x6/controller/x6-cell-controller/x6-cell-controller.ts b/packages/er-design/src/x6/controller/x6-cell-controller/x6-cell-controller.ts index ebeef6a5..a5017bba 100644 --- a/packages/er-design/src/x6/controller/x6-cell-controller/x6-cell-controller.ts +++ b/packages/er-design/src/x6/controller/x6-cell-controller/x6-cell-controller.ts @@ -3,6 +3,7 @@ import { Graph } from '@antv/x6'; import { IPanelController } from '@ibiz-template/runtime'; import { IDEEditForm } from '@ibiz/model-core'; import { X6Cell } from '../../vo/x6-cell'; +import { X6Controller } from '../x6-controller/x6-controller'; /** * X6图形控制器 @@ -56,6 +57,15 @@ export abstract class X6CellController { */ items: X6Cell[] = []; + /** + * x6控制器 + * + * @author zhanghengfeng + * @date 2024-08-23 20:08:42 + * @type {X6Controller} + */ + x6!: X6Controller; + /** * Creates an instance of X6CellController. * @param {IPanelController} panel @@ -65,10 +75,12 @@ export abstract class X6CellController { constructor( protected panel: IPanelController, g: Graph, + x6: X6Controller, ) { this.context = panel.context; this.params = panel.params; this.g = g; + this.x6 = x6; } /** diff --git a/packages/er-design/src/x6/controller/x6-controller/x6-controller.ts b/packages/er-design/src/x6/controller/x6-controller/x6-controller.ts index ed2f163e..09e541d2 100644 --- a/packages/er-design/src/x6/controller/x6-controller/x6-controller.ts +++ b/packages/er-design/src/x6/controller/x6-controller/x6-controller.ts @@ -103,6 +103,37 @@ export class X6Controller { */ linkList: X6LinkData[] = []; + /** + * 是否默认开启连线锚点编辑 + * + * @author zhanghengfeng + * @date 2024-08-23 18:08:06 + * @type {boolean} + */ + private isDefaultOpenAnchorEdit: boolean = false; + + /** + * 获取默认连线锚点编辑状态 + * + * @author zhanghengfeng + * @date 2024-08-23 20:08:36 + * @return {*} {boolean} + */ + getDefaultOpenAnchorEdit(): boolean { + return this.isDefaultOpenAnchorEdit; + } + + /** + * 设置默认连线锚点编辑状态 + * + * @author zhanghengfeng + * @date 2024-08-23 20:08:59 + * @param {boolean} value + */ + setDefaultOpenAnchorEdit(value: boolean): void { + this.isDefaultOpenAnchorEdit = value; + } + /** * x6图形配置 * @@ -123,6 +154,10 @@ export class X6Controller { panning: { enabled: true, }, + scaling: { + min: 0.2, + max: 2, + }, // 滚轮缩放 mousewheel: { enabled: true, @@ -192,8 +227,8 @@ export class X6Controller { }; this.graphOptions.container = dom; this.g = new Graph(this.graphOptions); - this.link = new X6LinkController(panel, this.g); - this.node = new X6NodeController(panel, this.g); + this.link = new X6LinkController(panel, this.g, this); + this.node = new X6NodeController(panel, this.g, this); this.init(); } @@ -310,6 +345,7 @@ export class X6Controller { return this.link.createCell(data); }), ); + this.g.centerContent(); } /** diff --git a/packages/er-design/src/x6/controller/x6-link-controller/x6-link-controller.ts b/packages/er-design/src/x6/controller/x6-link-controller/x6-link-controller.ts index f038ad6f..0c85478f 100644 --- a/packages/er-design/src/x6/controller/x6-link-controller/x6-link-controller.ts +++ b/packages/er-design/src/x6/controller/x6-link-controller/x6-link-controller.ts @@ -8,11 +8,17 @@ import { RuntimeModelError } from '@ibiz-template/core'; import { Cell, Dom, Edge } from '@antv/x6'; import ContextMenu from '@imengyu/vue3-context-menu'; import { clone } from 'ramda'; +import { debounce } from 'lodash-es'; import { X6CellController } from '../x6-cell-controller/x6-cell-controller'; import { X6LinkService } from '../../service'; -import { X6LinkData } from '../../vo'; +import { X6LinkData, X6NodeData } from '../../vo'; import { ShapeParam } from '../../../interface'; import { edgeSize } from '../../../custom-cell/config'; +import { + addToggleButton, + isToggleButton, + removeToggleButton, +} from '../../../utils/er-util'; /** * 连线控制器 @@ -81,6 +87,10 @@ export class X6LinkController extends X6CellController { } this.service = new X6LinkService(this.model); this.initEvents(); + this.handleEdgeVerticesChange = debounce( + this.handleEdgeVerticesChange, + 300, + ); } /** @@ -105,12 +115,131 @@ export class X6LinkController extends X6CellController { this.panel.view.call('onActive', data); } edge.trigger('selected', args); + this.handleEdgeSelect(edge); + }); + g.on('edge:unselected', ({ edge }) => { + this.handleEdgeUnselect(edge); + }); + g.on('edge:mouseenter', ({ edge }) => { + this.handleEdgeMouseEnter(edge); + }); + g.on('edge:mouseleave', ({ edge }) => { + this.handleEdgeMouseLeave(edge); + }); + g.on('edge:change:vertices', ({ edge }) => { + this.handleEdgeVerticesChange(edge); }); g.on('edge:contextmenu', ({ e, edge }) => { this.contextMenu(e, edge); }); } + /** + * 处理连线选中 + * + * @author zhanghengfeng + * @date 2024-08-23 19:08:55 + * @param {Edge} edge + */ + handleEdgeSelect(edge: Edge): void { + edge.setAttrs({ + line: { + stroke: '#2194FF', + }, + }); + addToggleButton(this.g, edge, this.x6.getDefaultOpenAnchorEdit()); + } + + /** + * 处理连线取消选中 + * + * @author zhanghengfeng + * @date 2024-08-23 19:08:08 + * @param {Edge} edge + */ + handleEdgeUnselect(edge: Edge): void { + edge.setAttrs({ + line: { + stroke: '#9d9d9f', + }, + }); + removeToggleButton(edge); + } + + /** + * 处理鼠标移入连线 + * + * @author zhanghengfeng + * @date 2024-08-23 19:08:44 + * @param {Edge} edge + */ + handleEdgeMouseEnter(edge: Edge): void { + if (!edge.hasTool('vertices')) { + const tools = edge.getTools()?.items; + if (tools && tools.length) { + const toggleButton = tools.find(item => { + return isToggleButton(item); + }); + if ( + typeof toggleButton === 'object' && + toggleButton.args && + toggleButton.args.active + ) { + edge.addTools('vertices'); + } + } + } + } + + /** + * 处理鼠标移出连线 + * + * @author zhanghengfeng + * @date 2024-08-23 19:08:08 + * @param {Edge} edge + */ + handleEdgeMouseLeave(edge: Edge): void { + if (edge.hasTool('vertices')) { + edge.removeTool('vertices'); + } + } + + /** + * 处理连线锚点变化 + * + * @author zhanghengfeng + * @date 2024-08-23 19:08:43 + * @param {Edge} edge + */ + handleEdgeVerticesChange(edge: Edge): void { + const source = edge.source as Edge.TerminalCellData; + if (source && source.cell) { + const sourceNode = this.g.getCellById(source.cell); + if (sourceNode) { + const edges = this.g.getConnectedEdges(sourceNode, { + incoming: false, + outgoing: true, + }); + const shapeParams: ShapeParam[] = []; + if (edges && edges.length) { + edges.forEach(item => { + const vertices = item.getVertices(); + if (vertices && vertices.length) { + shapeParams.push({ + psderid: item.id, + vertices, + }); + } + }); + } + const data = sourceNode.data as X6NodeData; + if (data && data.data) { + data.data.shapeparams = JSON.stringify(shapeParams); + } + } + } + } + /** * 触发右键菜单 * -- Gitee