diff --git a/hchyun-ui/.env.staging b/hchyun-ui/.env.staging index ab67141..481f2c7 100644 --- a/hchyun-ui/.env.staging +++ b/hchyun-ui/.env.staging @@ -5,5 +5,5 @@ ENV = 'staging' # 宏驰云管理系统/测试环境 #VUE_APP_BASE_API = '/dev-api' -VUE_APP_BASE_API = 'http://apig.odliken.top/dev-api' +VUE_APP_BASE_API = 'https://apig.odliken.top/dev-api' #VUE_APP_BASE_API = 'http://hchyunapi.tomey.live/dev-api' diff --git a/hchyun-ui/src/views/system/top/demo-topology.vue b/hchyun-ui/src/views/system/top/demo-topology.vue index 3c0c0e9..fd86ae6 100644 --- a/hchyun-ui/src/views/system/top/demo-topology.vue +++ b/hchyun-ui/src/views/system/top/demo-topology.vue @@ -15,7 +15,7 @@ + + diff --git a/hchyun-ui/src/views/tool/top/packages/elements/checkbox.vue b/hchyun-ui/src/views/tool/top/packages/elements/checkbox.vue new file mode 100644 index 0000000..aa36f62 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/elements/checkbox.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/elements/dropdown.vue b/hchyun-ui/src/views/tool/top/packages/elements/dropdown.vue new file mode 100644 index 0000000..fc71705 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/elements/dropdown.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/elements/index.js b/hchyun-ui/src/views/tool/top/packages/elements/index.js new file mode 100644 index 0000000..81367e7 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/elements/index.js @@ -0,0 +1,17 @@ +/** + * @author: clay + * @data: 2019/11/14 + * @description: ClayTop内部的通用组件 + */ + +import Checkbox from './checkbox' +import Button from './button' +import Dropdown from './dropdown' +import Loading from './loading' + +export { + Checkbox, + Button, + Dropdown, + Loading +} diff --git a/hchyun-ui/src/views/tool/top/packages/elements/loading.vue b/hchyun-ui/src/views/tool/top/packages/elements/loading.vue new file mode 100644 index 0000000..f410a92 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/elements/loading.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/click-add-edge.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/click-add-edge.js new file mode 100644 index 0000000..76831ff --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/click-add-edge.js @@ -0,0 +1,93 @@ +/** + * @author: clay + * @data: 2019/07/05 + * @description: edit mode: 通过先后点击两个节点来添加连线(容易和节点点击动作交叉,已弃用) + */ + + +export default { + name: 'click-add-edge', + options: { + getEvents() { + return { + 'node:click': 'onNodeClick', + 'canvas:mousemove': 'onMousemove', + 'edge:click': 'onEdgeClick' // 点击空白处,取消边 + } + }, + onNodeClick(event) { + let graph = this.graph + let node = event.item + let point = { x: event.x, y: event.y } + let model = node.getModel() + let edgeShape = self.currentEdgeShape.guid || 'line' + if (this.addingEdge && this.edge) { + // 点击第二个节点 + graph.updateItem(this.edge, { + target: model.id + }) + this.edge = null + this.addingEdge = false + // 记录【连线】前后的数据状态 + if (this.historyData) { + let graph = this.graph + // 如果当前点过【撤销】了,连线后没有【重做】功能 + // 重置undoCount,连线后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录 + if (self.undoCount > 0) { + self.historyIndex = self.historyIndex - self.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 + for (let i = 1; i <= self.undoCount; i++) { + let key = `graph_history_${self.historyIndex + i}` + self.removeHistoryData(key) + } + self.undoCount = 0 + } else { + // 正常顺序执行的情况,记录【连线】前的数据状态 + let key = `graph_history_${self.historyIndex}` + self.addHistoryData(key, this.historyData) + } + // 记录【连线】后的数据状态 + self.historyIndex += 1 + let key = `graph_history_${self.historyIndex}` + let currentData = JSON.stringify(graph.save()) + self.addHistoryData(key, currentData) + } + } else { + // 点击第一个节点 + this.historyData = JSON.stringify(graph.save()) + if (edgeShape === 'stepline') { + this.edge = graph.addItem('edge', { + source: model.id, + target: point, + type: edgeShape, + controlPoints: [{ x: 100, y: 70 }] + }) + } else { + this.edge = graph.addItem('edge', { + source: model.id, + target: point, + type: edgeShape + }) + } + this.addingEdge = true + } + }, + onMousemove(event) { + const point = { x: event.x, y: event.y } + if (this.addingEdge && this.edge) { + this.graph.updateItem(this.edge, { + target: point + }) + } + }, + onEdgeClick(ev) { + let graph = this.graph + const currentEdge = ev.item + // 拖拽过程中,点击会点击到新增的边上 + if (this.addingEdge && this.edge === currentEdge) { + graph.removeItem(this.edge) + this.edge = null + this.addingEdge = false + } + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/click-event-edit.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/click-event-edit.js new file mode 100644 index 0000000..674f850 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/click-event-edit.js @@ -0,0 +1,172 @@ +/** + * @author: winyuan + * @data: 2019/07/16 + * @repository: https://github.com/winyuan + * @description: edit mode: 鼠标点击交互 + */ + +// 用来获取调用此js的vue组件实例(this) +let vm = null; + +const sendThis = (_this) => { + vm = _this; +}; + +export default { + sendThis, // 暴露函数 + name: "click-event-edit", + options: { + getEvents() { + return { + "node:click": "onNodeClick", + "node:contextmenu": "onNodeRightClick", + "edge:click": "onEdgeClick", + "edge:contextmenu": "onEdgeRightClick", + "canvas:click": "onCanvasClick", + }; + }, + + + onNodeClick(event) { + // todo..."selected"是g6自带的状态,在"drag-add-edge"中的"node:mouseup"事件也会触发,故此处不需要设置"selected"状态 + // let clickNode = event.item; + // clickNode.setState('selected', !clickNode.hasState('selected')); + + + vm.currentFocus = "node"; + vm.rightMenuShow = false; + this.updateVmData(event); + }, + onNodeRightClick(event) { + let graph = vm.graph; + let clickNode = event.item; + let clickNodeModel = clickNode.getModel(); + let selectedNodes = graph.findAllByState("node", "selected"); + let selectedNodeIds = selectedNodes.map(item => { + return item.getModel().id; + }); + vm.selectedNode = clickNode; + // 如果当前点击节点是之前选中的某个节点,就进行下面的处理 + if (selectedNodes.length > 1 && selectedNodeIds.indexOf(clickNodeModel.id) > -1) { + vm.rightMenuShow = true; + let rightMenu = vm.$refs.rightMenu; + rightMenu.style.left = event.clientX + "px"; + rightMenu.style.top = event.clientY + "px"; + } else { + // 隐藏右键菜单 + vm.rightMenuShow = false; + // 先取消所有节点的选中状态 + selectedNodes.forEach(node => { + node.setState("selected", false); + }); + // 再添加该节点的选中状态 + clickNode.setState("selected", true); + vm.currentFocus = "node"; + this.updateVmData(event); + } + graph.paint(); + }, + onEdgeClick(event) { + let clickEdge = event.item; + clickEdge.setState("selected", !clickEdge.hasState("selected")); + vm.currentFocus = "edge"; + this.updateVmData(event); + }, + onEdgeRightClick(event) { + let graph = vm.graph; + let clickEdge = event.item; + let clickEdgeModel = clickEdge.getModel(); + let selectedEdges = graph.findAllByState("edge", "selected"); + // 如果当前点击节点不是之前选中的单个节点,才进行下面的处理 + if (!(selectedEdges.length === 1 && clickEdgeModel.id === selectedEdges[0].getModel().id)) { + // 先取消所有节点的选中状态 + graph.findAllByState("edge", "selected").forEach(edge => { + edge.setState("selected", false); + }); + // 再添加该节点的选中状态 + clickEdge.setState("selected", true); + vm.currentFocus = "edge"; + this.updateVmData(event); + } + let point = { x: event.x, y: event.y }; + }, + onCanvasClick() { + vm.currentFocus = "canvas"; + vm.rightMenuShow = false; + }, + updateVmData(event) { + let self = this + if (event.item._cfg.type === "node") { + + // const item = event.item; + // let group = item.getContainer(); + // let children = group.get("children"); + // for (let i = 0, len = children.length; i < len; i++) { + // const shape = children[i]; + // if (shape.get("name") === "collapses") { + // console.log(shape.get("name"),"dkfjg"); + // self.graph.updateItem(item, { + // collapsed: true, + // size: [300, 50] + // }); + // setTimeout(() => graph.layout(), 100); + // } else if (shape.get("name") === "expands") { + // + // console.log(shape.get("name"),"expands"); + // self.graph.updateItem(item, { + // collapsed: false, + // size: [300, 500] + // }); + // setTimeout(() => graph.layout(), 100); + // } + // } + // 更新vm的data: selectedNode 和 selectedNodeParams + + + let clickNode = event.item; + // console.log(clickNode) + if (clickNode.hasState("selected")) { + let clickNodeModel = clickNode.getModel(); + vm.selectedNode = clickNode; + let nodeAppConfig = { ...vm.nodeAppConfig }; + Object.keys(nodeAppConfig).forEach(function(key) { + nodeAppConfig[key] = ""; + }); + vm.selectedNodeParams = { + label: clickNodeModel.label || "", + appConfig: { ...nodeAppConfig, ...clickNodeModel.appConfig } + }; + } + } else if (event.item._cfg.type === "edge") { + // 更新vm的data: selectedEdge 和 selectedEdgeParams + let clickEdge = event.item; + if (clickEdge.hasState("selected")) { + let clickEdgeModel = clickEdge.getModel(); + vm.selectedEdge = clickEdge; + let edgeAppConfig = { ...vm.edgeAppConfig }; + Object.keys(edgeAppConfig).forEach(function(key) { + edgeAppConfig[key] = ""; + }); + vm.selectedEdgeParams = { + label: clickEdgeModel.label || "", + appConfig: { ...edgeAppConfig, ...clickEdgeModel.appConfig } + }; + } + } + } + } +}; +const isInBBox = (point, bbox) => { + const { + x, + y + } = point; + const { + minX, + minY, + maxX, + maxY + } = bbox; + + return x < maxX && x > minX && y > minY && y < maxY; +}; diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/dice-er-edge.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/dice-er-edge.js new file mode 100644 index 0000000..5224317 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/dice-er-edge.js @@ -0,0 +1,6 @@ +/** + * @author: clay + * @data: 2021/5/18 14:21 + * @email: clay@hchyun.com + * @description: node + */ diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/dice-er-node.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/dice-er-node.js new file mode 100644 index 0000000..0f8881b --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/dice-er-node.js @@ -0,0 +1,124 @@ +/** + * @author: clay + * @data: 2021/5/14 23:20 + * @email: clay@hchyun.com + * @description: node + */ +const isInBBox = (point, bbox) => { + const { + x, + y + } = point; + const { + minX, + minY, + maxX, + maxY + } = bbox; + + return x < maxX && x > minX && y > minY && y < maxY; +}; + +export default { + name: 'dice-er-node', + options: { + getDefaultCfg() { + return { + multiple: true, + }; + }, + getEvents() { + return { + itemHeight: 50, + wheel: "scroll", + "node:click": "click", + "node:mousemove": "moves", + }; + }, + scroll(e) { + e.preventDefault() + const { + graph + } = this; + const nodes = graph.getNodes().filter((n) => { + const bbox = n.getBBox(); + + return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); + }); + if (nodes) { + nodes.forEach((node) => { + const model = node.getModel(); + if (model.attrs.length < 9) { + return; + } + const idx = model.startIndex || 0; + let startX = model.startX || 0.5; + let startIndex = idx + e.deltaY * 0.02; + startX -= e.deltaX; + if (startIndex < 0) { + startIndex = 0; + } + if (startX > 0) { + startX = 0; + } + if (startIndex > model.attrs.length - 1) { + startIndex = model.attrs.length - 1; + } + graph.update(node, { + startIndex, + startX, + }); + }); + } + + + }, + click(e) { + const { + graph + } = this; + const { + // eslint-disable-next-line no-unused-vars + y + } = e; + const item = e.item; + const shape = e.shape; + if (!item) { + return; + } + if (shape.get("name") === "collapse") { + graph.updateItem(item, { + collapsed: true, + size: [300, 50], + }); + setTimeout(() => graph.layout(), 100); + } else if (shape.get("name") === "expand") { + graph.updateItem(item, { + collapsed: false, + size: [300, 500], + }); + setTimeout(() => graph.layout(), 100); + }else { + // eslint-disable-next-line no-unused-vars + const model = item.getModel(); + console.log(JSON.stringify(model)); + console.log(model.style.default.fill = '#4eb922'); + console.log(model.style.selected.shadowColor = '#4eb922'); + + } + }, + moves(e) { + const name = e.shape.get("name"); + const item = e.item; + if (name && name.startsWith("item")) { + this.graph.updateItem(item, { + selectedIndex: Number(name.split("-")[1]), + }); + } else { + this.graph.updateItem(item, { + selectedIndex: NaN, + }); + } + }, + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/drag-add-edge.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/drag-add-edge.js new file mode 100644 index 0000000..cab35e9 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/drag-add-edge.js @@ -0,0 +1,177 @@ +/** + * @author: clay + * @data: 2019/07/16 + * @description: edit mode: 通过拖拽节点上的锚点添加连线 + */ +import utils from "../utils"; + +// 用来获取调用此js的vue组件实例(this) +let vm = null; + +const sendThis = (_this) => { + vm = _this; +}; + +import G6 from "@antv/g6"; +import theme from "../theme"; + +export default { + sendThis, // 暴露函数 + name: "drag-add-edge", + options: { + getEvents() { + return { + "node:mousedown": "onNodeMousedown", + "node:mouseup": "onNodeMouseup", + "edge:mouseup": "onEdgeMouseup", + "mousemove": "onMousemove" + }; + }, + onNodeMousedown(event) { + let self = this; + // 交互过程中的信息 + self.evtInfo = { + action: null, + node: event.item, + target: event.target + }; + if (self.evtInfo.target && self.evtInfo.target.attrs.name) { + // todo...未来可能针对锚点增加其它功能(例如拖拽调整大小) + switch (self.evtInfo.target.attrs.name) { + case "anchor": + self.evtInfo.action = "drawEdge"; + break; + } + } + if (self.evtInfo && self.evtInfo.action) { + self[self.evtInfo.action].start.call(self, event); + } + }, + onNodeMouseup(event) { + let self = this; + if (self.evtInfo && self.evtInfo.action) { + self[self.evtInfo.action].stop.call(self, event); + } + }, + onEdgeMouseup(event) { + let self = this; + if (self.evtInfo && self.evtInfo.action === "drawEdge") { + self[self.evtInfo.action].stop.call(self, event); + } + }, + onMousemove(event) { + let self = this; + if (self.evtInfo && self.evtInfo.action) { + self[self.evtInfo.action].move.call(self, event); + } + }, + drawEdge: { + isMoving: false, + currentLine: null, + start: function(event) { + let self = this; + let themeStyle = theme.defaultStyle; // todo...先使用默认主题,后期可能增加其它风格的主体 + + // ************** 暂存【连线】前的数据状态 start ************** + let graph = vm.graph; + self.historyData = JSON.stringify(graph.save()); + // ************** 暂存【连线】前的数据状态 end ************** + + let sourceAnchor = self.evtInfo.node.getAnchorPoints(); + let sourceNodeModel = self.evtInfo.node.getModel(); + // 锚点数据 + let anchorPoints = self.evtInfo.node.getAnchorPoints(); + // 处理线条目标点 + if (anchorPoints && anchorPoints.length) { + // 获取距离指定坐标最近的一个锚点 + // sourceAnchor = self.evtInfo.node.getLinkPoint({ + // x: event.x, + // y: event.y + // }) + } + console.log(anchorPoints, sourceAnchor); + self.drawEdge.currentLine = self.graph.addItem("edge", { + // id: G6.Util.uniqueId(), // 这种生成id的方式有bug,会重复 + id: utils.generateUUID(), + // 起始节点 + source: sourceNodeModel.id, + sourceAnchor: sourceAnchor ? sourceAnchor.anchorIndex : "", + // 终止节点/位置 + target: { + x: event.x, + y: event.y + }, + type: self.graph.$C.edge.type || "top-line", + style: G6.Util.mix({}, themeStyle.edgeStyle.default, self.graph.$C.edge.style) + }); + self.drawEdge.isMoving = true; + }, + move(event) { + let self = this; + if (self.drawEdge.isMoving && self.drawEdge.currentLine) { + self.graph.updateItem(self.drawEdge.currentLine, { + target: { + x: event.x, + y: event.y + } + }); + } + }, + stop(event) { + let self = this; + if (self.drawEdge.isMoving) { + if (self.drawEdge.currentLine === event.item) { + // 画线过程中点击则移除当前画线 + self.graph.removeItem(event.item); + } else { + let targetNode = event.item; + let targetNodeModel = targetNode.getModel(); + let targetAnchor = null; + // 锚点数据 + let anchorPoints = targetNode.getAnchorPoints(); + // 处理线条目标点 + if (anchorPoints && anchorPoints.length) { + // 获取距离指定坐标最近的一个锚点 + targetAnchor = targetNode.getLinkPoint({ + x: event.x, + y: event.y + }); + } + self.graph.updateItem(self.drawEdge.currentLine, { + target: targetNodeModel.id, + targetAnchor: targetAnchor ? targetAnchor.anchorIndex : "" + }); + + // ************** 记录historyData的逻辑 start ************** + if (this.historyData) { + let graph = this.graph; + // 如果当前点过【撤销】了,拖拽节点后没有【重做】功能 + // 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录 + if (vm.undoCount > 0) { + vm.historyIndex = vm.historyIndex - vm.undoCount; // 此时的historyIndex应当更新为【撤销】后所在的索引位置 + for (let i = 1; i <= vm.undoCount; i++) { + let key = `graph_history_${vm.historyIndex + i}`; + vm.removeHistoryData(key); + } + vm.undoCount = 0; + } else { + // 正常顺序执行的情况,记录拖拽前的数据状态 + let key = `graph_history_${vm.historyIndex}`; + vm.addHistoryData(key, this.historyData); + } + // 记录拖拽后的数据状态 + vm.historyIndex += 1; + let key = `graph_history_${vm.historyIndex}`; + let currentData = JSON.stringify(graph.save()); + vm.addHistoryData(key, currentData); + } + // ************** 记录historyData的逻辑 end ************** + } + } + self.drawEdge.currentLine = null; + self.drawEdge.isMoving = false; + self.evtInfo = null; + } + } + } +}; diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/drag-event-edit.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/drag-event-edit.js new file mode 100644 index 0000000..08ebc57 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/drag-event-edit.js @@ -0,0 +1,53 @@ +/** + * @author: clay + * @data: 2019/07/16 + * @description: edit mode: 鼠标拖动节点的交互(记录拖拽前后的数据,用于【撤销】和【重做】) + */ + +// 用来获取调用此js的vue组件实例(this) +let vm = null + +const sendThis = (_this) => { + vm = _this +} + +export default { + sendThis, // 暴露函数 + name: 'drag-event-edit', + options: { + getEvents() { + return { + 'node:dragstart': 'onNodeDragstart', + 'node:dragend': 'onNodeDragend' + } + }, + onNodeDragstart() { + let graph = vm.graph + this.historyData = JSON.stringify(graph.save()) + }, + onNodeDragend() { + if (this.historyData) { + let graph = this.graph + // 如果当前点过【撤销】了,拖拽节点后没有【重做】功能 + // 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录 + if (vm.undoCount > 0) { + vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 + for (let i = 1; i <= vm.undoCount; i++) { + let key = `graph_history_${vm.historyIndex + i}` + vm.removeHistoryData(key) + } + vm.undoCount = 0 + } else { + // 正常顺序执行的情况,记录拖拽前的数据状态 + let key = `graph_history_${vm.historyIndex}` + vm.addHistoryData(key, this.historyData) + } + // 记录拖拽后的数据状态 + vm.historyIndex += 1 + let key = `graph_history_${vm.historyIndex}` + let currentData = JSON.stringify(graph.save()) + vm.addHistoryData(key, currentData) + } + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/hover-event-edit.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/hover-event-edit.js new file mode 100644 index 0000000..8754a5e --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/hover-event-edit.js @@ -0,0 +1,45 @@ +/** + * @author: clay + * @data: 2019/07/16 + * @description: edit mode: 悬浮交互 + */ +// 用来获取调用此js的vue组件实例(this) +let vm = null; +const sendThis = (_this) => { + vm = _this; +}; +export default { + sendThis, // 暴露函数 + name: "hover-event-edit", + options: { + getEvents() { + return { + "node:mouseover": "onNodeHover", + "node:mouseout": "onNodeOut", + }; + }, + onNodeHover(event) { + let graph = vm.graph; + let hoverNode = event.item; + + const name = event.shape.get("name"); + const item = event.item; + + if (name && name.startsWith("item")) { + graph.updateItem(item, { + selectedIndex: Number(name.split("-")[1]) + }); + } else { + graph.updateItem(item, { + selectedIndex: NaN + }); + } + // console.log(item); + hoverNode.setState("hover", true); + }, + onNodeOut(event) { + let hoverNode = event.item; + hoverNode.setState("hover", false); + } + } +}; diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/index.js new file mode 100644 index 0000000..94a269b --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/index.js @@ -0,0 +1,30 @@ +/** + * @author: clay + * @data: 2019/07/16 + * @description: register behaviors + */ + +import dragAddEdge from './drag-add-edge' +import hoverEventEdit from './hover-event-edit' +import clickEventEdit from './click-event-edit' +import dragEventEdit from './drag-event-edit' +import keyupEventEdit from './keyup-event-edit' +import diceErScroll from './dice-er-node' + +const obj = { + // dragAddEdge, + // hoverEventEdit, + // clickEventEdit, + dragEventEdit, + keyupEventEdit, + diceErScroll +} + +export default { + obj, + register(G6) { + Object.values(obj).map(item => { + G6.registerBehavior(item.name, item.options) + }) + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/keyup-event-edit.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/keyup-event-edit.js new file mode 100644 index 0000000..cdb6af3 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/keyup-event-edit.js @@ -0,0 +1,63 @@ +/** + * @author: clay + * @data: 2019/07/16 + * @description: edit mode: 键盘事件的交互,主要是删除节点和连线(记录删除前后的数据,用于【撤销】和【重做】) + */ + +// 用来获取调用此js的vue组件实例(this) +let vm = null + +const sendThis = (_this) => { + vm = _this +} + +export default { + sendThis, // 暴露函数 + name: 'keyup-event-edit', + options: { + getEvents() { + return { + 'keyup': 'onKeyup' + } + }, + onKeyup(event) { + let graph = this.graph + let selectedNodes = graph.findAllByState('node', 'selected') + let selectedEdges = graph.findAllByState('edge', 'selected') + if (event.keyCode === 46 && (selectedNodes.length > 0 || selectedEdges.length > 0)) { + + // ************** 记录【删除】前的数据状态 start ************** + let historyData = JSON.stringify(graph.save()) + let key = `graph_history_${vm.historyIndex}` + vm.addHistoryData(key, historyData) + // ************** 记录【删除】前的数据状态 end ************** + + // 开始删除 + for (let i = 0; i < selectedNodes.length; i++) { + graph.removeItem(selectedNodes[i]) + } + for (let i = 0; i < selectedEdges.length; i++) { + graph.removeItem(selectedEdges[i]) + } + + // ************** 记录【删除】后的数据状态 start ************** + // 如果当前点过【撤销】了,拖拽节点后将取消【重做】功能 + // 重置undoCount,【删除】后的数据状态给(当前所在historyIndex + 1),且清空这个时间点之后的记录 + if (vm.undoCount > 0) { + vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 + for (let i = 1; i <= vm.undoCount; i++) { + let key = `graph_history_${vm.historyIndex + i}` + vm.removeHistoryData(key) + } + vm.undoCount = 0 + } + // 记录【删除】后的数据状态 + vm.historyIndex += 1 + key = `graph_history_${vm.historyIndex}` + let currentData = JSON.stringify(graph.save()) + vm.addHistoryData(key, currentData) + // ************** 记录【删除】后的数据状态 end ************** + } + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/wheel-node.js b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/wheel-node.js new file mode 100644 index 0000000..8b9a443 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/behavior/wheel-node.js @@ -0,0 +1,58 @@ +/** + * @author: clay + * @data: 2021/5/13 23:14 + * @email: clay@hchyun.com + * @description: node + */ + + +export default { + name: "wheel-node", + options: { + getEvents() { + return { + wheel: "scorll" + }; + }, + scorll(e) { + console.log(456) + e.preventDefault(); + const { + graph + } = this; + const nodes = graph.getNodes().filter((n) => { + const bbox = n.getBBox(); + + return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); + }); + if (nodes) { + nodes.forEach((node) => { + const model = node.getModel(); + if (model.attrs.length < 9) { + return; + } + const idx = model.startIndex || 0; + let startX = model.startX || 0.5; + let startIndex = idx + e.deltaY * 0.02; + startX -= e.deltaX; + if (startIndex < 0) { + startIndex = 0; + } + if (startX > 0) { + startX = 0; + } + if (startIndex > model.attrs.length - 1) { + startIndex = model.attrs.length - 1; + } + graph.update(node, { + startIndex, + startX, + }); + }); + } + + + }, + } +}; + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/config/edge.js b/hchyun-ui/src/views/tool/top/packages/topology/src/config/edge.js new file mode 100644 index 0000000..a12862d --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/config/edge.js @@ -0,0 +1,13 @@ +/** + * @author: clay + * @data: 2019/08/16 + * @description: 线条的后期设置 + */ + +export default { + type: 'cc-line', + style: { + startArrow: false, + endArrow: false + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/config/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/config/index.js new file mode 100644 index 0000000..abed973 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/config/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2019/08/16 + * @description: 配置 + */ + +import edge from './edge' + +export default { + edge +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/edge/dice-er-edge.js b/hchyun-ui/src/views/tool/top/packages/topology/src/edge/dice-er-edge.js new file mode 100644 index 0000000..6dcbdac --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/edge/dice-er-edge.js @@ -0,0 +1,130 @@ +/** + * @author: clay + * @data: 2021/5/14 23:46 + * @email: clay@hchyun.com + * @description: node + */ +import {Util} from '@antv/g6' +export default { + name: 'dice-er-edge', + options: { + draw(cfg, group) { + //todo 画线 + // console.log("cfg",cfg,"group",group) + const edge = group.cfg.item; + const sourceNode = edge.getSource().getModel(); + const targetNode = edge.getTarget().getModel(); + + const sourceIndex = sourceNode.attrs.findIndex( + (e) => e.key === cfg.sourceKey + ); + + const sourceStartIndex = sourceNode.startIndex || 0; + + let sourceY = 15; + + if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) { + sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * 30; + sourceY = Math.min(sourceY, 300); + } + + const targetIndex = targetNode.attrs.findIndex( + (e) => e.key === cfg.targetKey + ); + + const targetStartIndex = targetNode.startIndex || 0; + + let targetY = 15; + + if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) { + targetY = (targetIndex - targetStartIndex + 0.5) * 30 + 30; + targetY = Math.min(targetY, 300); + } + + const startPoint = { + ...cfg.startPoint + }; + const endPoint = { + ...cfg.endPoint + }; + + startPoint.y = startPoint.y + sourceY; + endPoint.y = endPoint.y + targetY; + + let shape; + if (sourceNode.id !== targetNode.id) { + shape = group.addShape("path", { + attrs: { + stroke: "#5B8FF9", + path: [ + ["M", startPoint.x, startPoint.y], + [ + "C", + endPoint.x / 3 + (2 / 3) * startPoint.x, + startPoint.y, + endPoint.x / 3 + (2 / 3) * startPoint.x, + endPoint.y, + endPoint.x, + endPoint.y, + ], + ], + endArrow: true, + }, + name: "path-shape", + }); + } else if (!sourceNode.collapsed) { + let gap = Math.abs((startPoint.y - endPoint.y) / 3); + if (startPoint["index"] === 1) { + gap = -gap; + } + shape = group.addShape("path", { + attrs: { + stroke: "#5B8FF9", + path: [ + ["M", startPoint.x, startPoint.y], + [ + "C", + startPoint.x - gap, + startPoint.y, + startPoint.x - gap, + endPoint.y, + startPoint.x, + endPoint.y, + ], + ], + endArrow: true, + }, + name: "path-shape", + }); + } + + return shape; + }, + afterDraw(cfg, group) { + // eslint-disable-next-line no-unused-vars + const labelCfg = cfg.labelCfg || {}; + const edge = group.cfg.item; + const sourceNode = edge.getSource().getModel(); + const targetNode = edge.getTarget().getModel(); + if (sourceNode.collapsed && targetNode.collapsed) { + return; + } + const path = group.find( + (element) => element.get("name") === "path-shape" + ); + + const labelStyle = Util.getLabelPosition(path, 0.5, 0, 0, true); + const label = group.addShape("text", { + attrs: { + ...labelStyle, + text: cfg.label || '', + fill: "#000", + textAlign: "center", + stroke: "#fff", + lineWidth: 1, + }, + }); + label.rotateAtStart(labelStyle.rotate); + }, + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/edge/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/edge/index.js new file mode 100644 index 0000000..fe26e88 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/edge/index.js @@ -0,0 +1,18 @@ +/** + * @author: clay + * @data: 2019/07/18 + * @description: register edges + */ + + +import diceErEdge from './dice-er-edge' + +const obj = { + diceErEdge +} + +export default function(G6) { + Object.values(obj).map(item => { + G6.registerEdge(item.name, item.options, item.extendName) + }) +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/graph/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/graph/index.js new file mode 100644 index 0000000..896a784 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/graph/index.js @@ -0,0 +1,159 @@ +/** + * @author: clay + * @data: 2019/07/05 + * @description: 图的布局方式/图的初始化 + */ + +import d3 from '../plugins/d3-installer' +import theme from '../theme' + +/** + * 图的布局方式/图的初始化 + * @type {{commonGraph: (function(*, *): G6.Graph)}} + */ +const initGraph = { + /** + * 一般布局 + * @param G6 + * @param options + * @returns {G6.Graph} + */ + commonGraph: function(G6, options) { + let graphData = options.graphData + let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + // 生成G6实例 + let graph = new G6.Graph({ + plugins: options.plugins, + container: options.container, + width: options.width, + height: options.height, + // layout: { + // type: 'random', + // width: options.width, + // height: options.height + // }, + defaultNode: { + type: 'dice-er-box', + labelCfg: { + position: 'bottom' + } + }, + defaultEdge: { + type: 'dice-er-edge', + labelCfg: { + position: 'center', + autoRotate: false + } + }, + nodeStateStyles: themeStyle.nodeStyle, + // nodeStyle: { + // selected: { + // shadowColor: '#626262', + // shadowBlur: 8, + // shadowOffsetX: -1, + // shadowOffsetY: 3 + // } + // }, + edgeStateStyles: themeStyle.edgeStyle, + // edgeStyle: { + // default: { + // stroke: '#e2e2e2', + // lineWidth: 3, + // lineAppendWidth: 10 + // }, + // selected: { + // shadowColor: '#626262', + // shadowBlur: 3 + // } + // }, + modes: options.modes + }) + // 将 read 方法分解成 data() 和 render 方法,便于整个生命周期的管理 + graph.read(graphData) + graph.render() + // 返回G6实例 + return graph + }, + /** + * 力导布局 + * @param G6 + * @param options + * @returns {*} + */ + forceLayoutGraph: function(resolve, G6, options) { + let graphData = options.graphData + let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + // 生成G6实例 + let graph = new G6.Graph({ + container: options.container, + width: options.width, + height: options.height, + nodeStateStyles: themeStyle.nodeStyle, + edgeStateStyles: themeStyle.edgeStyle + }) + // 初始化力导布局 + let simulation = d3 + .forceSimulation() + .force( + 'link', + d3 + .forceLink() + .id(function(d) { + return d.id + }) + .distance(linkDistance) + .strength(0.5) + ) + .force('charge', d3.forceManyBody().strength(-500).distanceMax(500).distanceMin(100)) + .force('center', d3.forceCenter(options.width / 2, options.height / 2)) + // 定义节点数据 + simulation.nodes(graphData.nodes).on('tick', ticked) + // 定义连线数据 + let edges = [] + for (let i = 0; i < graphData.edges.length; i++) { + edges.push({ + id: graphData.edges[i].id, + source: graphData.edges[i].source, + target: graphData.edges[i].target + }) + } + simulation.force('link').links(edges) + graph.data(graphData) + graph.render() + + function linkDistance(d) { + return 150 + } + + function ticked() { + // protect: planA: 移除事件监听器 planB: 手动停止力模拟 + if (graph.destroyed) { + // simulation.nodes(graphData.nodes).on('tick', null) + simulation.stop() + return + } + if (!graph.get('data')) { + // 若是第一次渲染,定义数据,渲染 + graph.data(graphData) + graph.render() + } else { + // 后续渲染,直接刷新所有点和边的位置 + graph.refreshPositions() + } + } + + // 控制时间: 只布局10秒 + let t = setTimeout(function() { + simulation.stop() + resolve(graph) + }, 10000) + + // 判断force-layout结束 + simulation.on('end', () => { + clearTimeout(t) + resolve(graph) + }) + } +} + +export default initGraph diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/node/dice-er-box.js b/hchyun-ui/src/views/tool/top/packages/topology/src/node/dice-er-box.js new file mode 100644 index 0000000..80f2fee --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/node/dice-er-box.js @@ -0,0 +1,280 @@ +/** + * @author: clay + * @data: 2021/5/15 0:16 + * @email: clay@hchyun.com + * @description: node + */ +import utils from '../utils/index' + +const itemHeight = 30; +export default { + name: 'dice-er-box', + options: { + setState(name, value, item,group) { + // 设置节点状态 + // utils.node.setState(name, value, item,group); + // 设置锚点状态 + utils.anchor.setState(name, value, item); + // //设置收缩状态 + // utils.collapse.setState(item) + }, + draw(cfg, group) { + const width = 250; + const height = 316; + const itemCount = 10; + const boxStyle = { + stroke: "#096DD9", + radius: 4, + }; + + const { + attrs = [], + startIndex = 0, + selectedIndex, + collapsed, + icon, + } = cfg; + const list = attrs; + const afterList = list.slice( + Math.floor(startIndex), + Math.floor(startIndex + itemCount - 1) + ); + const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30; + + group.addShape("rect", { + attrs: { + fill: boxStyle.stroke, + height: 30, + width, + radius: [boxStyle.radius, boxStyle.radius, 0, 0], + }, + draggable: true, + }); + + let fontLeft = 12; + + if (icon && icon.show !== false) { + group.addShape("image", { + attrs: { + x: 8, + y: 8, + height: 16, + width: 16, + ...icon, + }, + }); + fontLeft += 18; + } + + group.addShape("text", { + attrs: { + y: 22, + x: fontLeft, + fill: "#fff", + text: cfg.label, + fontSize: 12, + fontWeight: 500, + }, + }); + + group.addShape("rect", { + attrs: { + x: 0, + y: collapsed ? 30 : 300, + height: 15, + width, + fill: "#eee", + radius: [0, 0, boxStyle.radius, boxStyle.radius], + cursor: "pointer", + }, + name: collapsed ? "expand" : "collapse", + }); + + group.addShape("text", { + attrs: { + x: width / 2 - 6, + y: (collapsed ? 30 : 300) + 12, + text: collapsed ? "+" : "-", + width, + fill: "#000", + radius: [0, 0, boxStyle.radius, boxStyle.radius], + cursor: "pointer", + }, + name: collapsed ? "expand" : "collapse", + }); + + const keyshape = group.addShape("rect", { + attrs: { + x: 0, + y: 0, + width, + height: collapsed ? 45 : height, + ...boxStyle, + }, + draggable: true, + }); + + if (collapsed) { + return keyshape; + } + + const listContainer = group.addGroup({}); + listContainer.setClip({ + type: "rect", + attrs: { + x: -8, + y: 30, + width: width + 16, + height: 300 - 30, + }, + }); + listContainer.addShape({ + type: "rect", + attrs: { + x: 1, + y: 30, + width: width - 2, + height: 300 - 30, + fill: "#fff", + }, + draggable: true, + }); + + if (list.length > itemCount) { + const barStyle = { + width: 4, + padding: 0, + boxStyle: { + stroke: "#00000022", + }, + innerStyle: { + fill: "#00000022", + }, + }; + + listContainer.addShape("rect", { + attrs: { + y: 30, + x: width - barStyle.padding - barStyle.width, + width: barStyle.width, + height: height - 30, + ...barStyle.boxStyle, + }, + }); + + const indexHeight = + afterList.length > itemCount ? + (afterList.length / list.length) * height : + 10; + + listContainer.addShape("rect", { + attrs: { + y: 30 + + barStyle.padding + + (startIndex / list.length) * (height - 30), + x: width - barStyle.padding - barStyle.width, + width: barStyle.width, + height: Math.min(height, indexHeight), + ...barStyle.innerStyle, + }, + }); + } + if (afterList) { + afterList.forEach((e, i) => { + const isSelected = + Math.floor(startIndex) + i === Number(selectedIndex); + let { + key = "", type + } = e; + if (type) { + key += " - " + type; + } + const label = key.length > 26 ? key.slice(0, 24) + "..." : key; + + listContainer.addShape("rect", { + attrs: { + x: 1, + y: i * itemHeight - itemHeight / 2 + offsetY, + width: width - 4, + height: itemHeight, + radius: 2, + lineWidth: 1, + cursor: "pointer", + }, + name: `item-${Math.floor(startIndex) + i}-content`, + draggable: true, + }); + + if (!cfg.hideDot) { + // utils.anchor.erDrawLeft(group,label,0,i * itemHeight + offsetY) + // utils.anchor.erDrawLeft(group,label,width,i * itemHeight + offsetY) + listContainer.addShape("marker", { + attrs: { + x: 0, + y: i * itemHeight + offsetY, + r: 3, + stroke: boxStyle.stroke, + fill: "white", + radius: 2, + lineWidth: 1, + cursor: "pointer", + }, + name: 'marker-shape' + }); + listContainer.addShape("marker", { + attrs: { + x: width, + y: i * itemHeight + offsetY, + r: 3, + stroke: boxStyle.stroke, + fill: "white", + radius: 2, + lineWidth: 1, + cursor: "pointer", + }, + name: 'markerBg-shape' + }); + + + + + + + + + } + + listContainer.addShape("text", { + attrs: { + x: 12, + y: i * itemHeight + offsetY + 6, + text: label, + fontSize: 12, + fill: "#000", + fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol", + full: e, + fontWeight: isSelected ? 500 : 100, + cursor: "pointer", + }, + name: `item-${Math.floor(startIndex) + i}`, + }); + }); + } + + + // console.log(keyshape); + return keyshape; + }, + getAnchorPoints() { + return [ + [0, 0], + [1, 0], + ]; + }, + } +} + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/node/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/node/index.js new file mode 100644 index 0000000..7ed5492 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/node/index.js @@ -0,0 +1,20 @@ +/** + * @author: clay + * @data: 2019/07/05 + * @description: register nodes + */ + + +import diceErBox from './dice-er-box' +const obj = { + diceErBox +} + +export default { + obj, + register(G6) { + Object.values(obj).map(item => { + G6.registerNode(item.name, item.options, item.extendName) + }) + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/plugins/d3-installer.js b/hchyun-ui/src/views/tool/top/packages/topology/src/plugins/d3-installer.js new file mode 100644 index 0000000..5991259 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/plugins/d3-installer.js @@ -0,0 +1,9 @@ +/** + * @author: clay + * @data: 2019/07/05 + * @description: install 3rd plugins + */ + +import * as d3 from 'd3-force/dist/d3-force' + +export default d3 diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/theme/dark-style.js b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/dark-style.js new file mode 100644 index 0000000..b7c1832 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/dark-style.js @@ -0,0 +1,177 @@ +/** + * @author: clay + * @data: 2019/11/20 + * @description: dark style + */ + +export default { + // 节点样式 + nodeStyle: { + default: { + stroke: '#CED4D9', + fill: 'transparent', + shadowOffsetX: 0, + shadowOffsetY: 4, + shadowBlur: 10, + shadowColor: 'rgba(13, 26, 38, 0.08)', + lineWidth: 1, + radius: 4, + strokeOpacity: 0.7 + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + // shadowColor: '#626262', + // shadowBlur: 8, + // shadowOffsetX: -1, + // shadowOffsetY: 3 + }, + unselected: { + shadowColor: '' + } + }, + // 节点标签样式 + nodeLabelCfg: { + positions: 'center', + style: { + fill: '#FFF' + } + }, + // 连线样式 + edgeStyle: { + default: { + stroke: '#53da3a', + lineWidth: 2, + strokeOpacity: 0.92, + lineAppendWidth: 10 + // endArrow: true + }, + active: { + shadowColor: 'red', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + inactive: { + shadowColor: '' + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + unselected: { + shadowColor: '' + } + }, + // 锚点样式 + anchorStyle: { + default: { + radius: 3, + symbol: 'circle', + fill: '#FFFFFF', + fillOpacity: 0, + stroke: '#1890FF', + strokeOpacity: 0, + cursor: 'crosshair' + }, + hover: { + fillOpacity: 1, + strokeOpacity: 1 + }, + unhover: { + fillOpacity: 0, + strokeOpacity: 0 + }, + active: { + fillOpacity: 1, + strokeOpacity: 1 + }, + inactive: { + fillOpacity: 0, + strokeOpacity: 0 + } + }, + // 锚点背景样式 + anchorBgStyle: { + default: { + radius: 10, + symbol: 'circle', + fill: '#1890FF', + fillOpacity: 0, + stroke: '#1890FF', + strokeOpacity: 0, + cursor: 'crosshair' + }, + hover: { + fillOpacity: 1, + strokeOpacity: 1 + }, + unhover: { + fillOpacity: 0, + strokeOpacity: 0 + }, + active: { + fillOpacity: 0.3, + strokeOpacity: 0.5 + }, + inactive: { + fillOpacity: 0, + strokeOpacity: 0 + } + }, + + + nodeActivedOutterStyle: { lineWidth: 0 }, + groupSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 }, + nodeSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 }, + edgeActivedStyle: { stroke: '#1890FF', strokeOpacity: .92 }, + nodeActivedStyle: { fill: '#F3F9FF', stroke: '#1890FF' }, + groupActivedStyle: { stroke: '#1890FF' }, + edgeSelectedStyle: { lineWidth: 2, strokeOpacity: .92, stroke: '#A3B1BF' }, + nodeSelectedStyle: { fill: '#F3F9FF', stroke: '#1890FF', fillOpacity: .4 }, + groupSelectedStyle: { stroke: '#1890FF', fillOpacity: .92 }, + + groupBackgroundPadding: [40, 10, 10, 10], + groupLabelOffsetX: 10, + groupLabelOffsetY: 10, + edgeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' }, + edgeLabelRectPadding: 4, + edgeLabelRectStyle: { fill: 'white' }, + nodeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' }, + groupStyle: { stroke: '#CED4D9', radius: 4 }, + groupLabelStyle: { fill: '#666', textAlign: 'left', textBaseline: 'top' }, + multiSelectRectStyle: { fill: '#1890FF', fillOpacity: .08, stroke: '#1890FF', opacity: .1 }, + dragNodeHoverToGroupStyle: { stroke: '#1890FF', lineWidth: 2 }, + dragNodeLeaveFromGroupStyle: { stroke: '#BAE7FF', lineWidth: 2 }, + anchorPointStyle: { radius: 3.5, fill: '#fff', stroke: '#1890FF', lineAppendWidth: 12 }, + anchorHotsoptStyle: { radius: 12, fill: '#1890FF', fillOpacity: .25 }, + anchorHotsoptActivedStyle: { radius: 14 }, + anchorPointHoverStyle: { radius: 4, fill: '#1890FF', fillOpacity: 1, stroke: '#1890FF' }, + nodeControlPointStyle: { radius: 4, fill: '#fff', shadowBlur: 4, shadowColor: '#666' }, + edgeControlPointStyle: { radius: 6, symbol: 'square', lineAppendWidth: 6, fillOpacity: 0, strokeOpacity: 0 }, + nodeSelectedBoxStyle: { stroke: '#C2C2C2' }, + cursor: { + panningCanvas: '-webkit-grabbing', + beforePanCanvas: '-webkit-grab', + hoverNode: 'move', + hoverEffectiveAnchor: 'crosshair', + hoverEdge: 'default', + hoverGroup: 'move', + hoverUnEffectiveAnchor: 'default', + hoverEdgeControllPoint: 'crosshair', + multiSelect: 'crosshair' + }, + nodeDelegationStyle: { + stroke: '#1890FF', + fill: '#1890FF', + fillOpacity: .08, + lineDash: [4, 4], + radius: 4, + lineWidth: 1 + }, + edgeDelegationStyle: { stroke: '#1890FF', lineDash: [4, 4], lineWidth: 1 } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/theme/default-style.js b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/default-style.js new file mode 100644 index 0000000..7462011 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/default-style.js @@ -0,0 +1,178 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: default style + */ + +export default { + // 节点样式 + nodeStyle: { + default: { + stroke: '#CED4D9', + fill: 'transparent', + // shadowOffsetX: 0, + // shadowOffsetY: 4, + shadowBlur: 10, + shadowColor: 'rgba(13, 26, 38, 0.08)', + lineWidth: 1, + radius: 4, + strokeOpacity: 0.7 + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 2, + // shadowOffsetX: 0, + // shadowOffsetY: 0, + // fontSize:'50' + // shadowColor: '#626262', + // shadowBlur: 8, + // shadowOffsetX: -1, + // shadowOffsetY: 3 + }, + unselected: { + shadowColor: '' + } + }, + // 节点标签样式 + nodeLabelCfg: { + positions: 'center', + style: { + fill: '#000' + } + }, + // 连线样式 + edgeStyle: { + default: { + stroke: '#A3B1BF', + lineWidth: 2, + strokeOpacity: 0.92, + lineAppendWidth: 10 + // endArrow: true + }, + active: { + shadowColor: 'red', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + inactive: { + shadowColor: '' + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + unselected: { + shadowColor: '' + } + }, + // 锚点样式 + anchorStyle: { + default: { + r: 10, + // symbol: 'circle', + fill: '#FFFFFF', + fillOpacity: 0, + stroke: '#1890FF', + strokeOpacity: 0, + // cursor: 'crosshair' + }, + hover: { + fillOpacity: 1, + strokeOpacity: 1 + }, + unhover: { + fillOpacity: 0, + strokeOpacity: 0 + }, + active: { + fillOpacity: 1, + strokeOpacity: 1 + }, + inactive: { + fillOpacity: 0, + strokeOpacity: 0 + } + }, + // 锚点背景样式 + anchorBgStyle: { + default: { + r: 10, + // symbol: 'circle', + fill: '#1890FF', + fillOpacity: 0, + stroke: '#1890FF', + strokeOpacity: 0, + // cursor: 'crosshair' + }, + hover: { + fillOpacity: 1, + strokeOpacity: 1 + }, + unhover: { + fillOpacity: 0, + strokeOpacity: 0 + }, + active: { + fillOpacity: 0.3, + strokeOpacity: 0.5 + }, + inactive: { + fillOpacity: 0, + strokeOpacity: 0 + } + }, + + + nodeActivedOutterStyle: { lineWidth: 0 }, + groupSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 }, + nodeSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 }, + edgeActivedStyle: { stroke: '#1890FF', strokeOpacity: .92 }, + nodeActivedStyle: { fill: '#F3F9FF', stroke: '#1890FF' }, + groupActivedStyle: { stroke: '#1890FF' }, + edgeSelectedStyle: { lineWidth: 2, strokeOpacity: .92, stroke: '#A3B1BF' }, + nodeSelectedStyle: { fill: '#F3F9FF', stroke: '#1890FF', fillOpacity: .4 }, + groupSelectedStyle: { stroke: '#1890FF', fillOpacity: .92 }, + + groupBackgroundPadding: [40, 10, 10, 10], + groupLabelOffsetX: 10, + groupLabelOffsetY: 10, + edgeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' }, + edgeLabelRectPadding: 4, + edgeLabelRectStyle: { fill: 'white' }, + nodeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' }, + groupStyle: { stroke: '#CED4D9', radius: 4 }, + groupLabelStyle: { fill: '#666', textAlign: 'left', textBaseline: 'top' }, + multiSelectRectStyle: { fill: '#1890FF', fillOpacity: .08, stroke: '#1890FF', opacity: .1 }, + dragNodeHoverToGroupStyle: { stroke: '#1890FF', lineWidth: 2 }, + dragNodeLeaveFromGroupStyle: { stroke: '#BAE7FF', lineWidth: 2 }, + anchorPointStyle: { radius: 3.5, fill: '#fff', stroke: '#1890FF', lineAppendWidth: 12 }, + anchorHotsoptStyle: { radius: 12, fill: '#1890FF', fillOpacity: .25 }, + anchorHotsoptActivedStyle: { radius: 14 }, + anchorPointHoverStyle: { radius: 4, fill: '#1890FF', fillOpacity: 1, stroke: '#1890FF' }, + nodeControlPointStyle: { radius: 4, fill: '#fff', shadowBlur: 4, shadowColor: '#666' }, + edgeControlPointStyle: { radius: 6, symbol: 'square', lineAppendWidth: 6, fillOpacity: 0, strokeOpacity: 0 }, + nodeSelectedBoxStyle: { stroke: '#C2C2C2' }, + cursor: { + panningCanvas: '-webkit-grabbing', + beforePanCanvas: '-webkit-grab', + hoverNode: 'move', + hoverEffectiveAnchor: 'crosshair', + hoverEdge: 'default', + hoverGroup: 'move', + hoverUnEffectiveAnchor: 'default', + hoverEdgeControllPoint: 'crosshair', + multiSelect: 'crosshair' + }, + nodeDelegationStyle: { + stroke: '#1890FF', + fill: '#1890FF', + fillOpacity: .08, + lineDash: [4, 4], + radius: 4, + lineWidth: 1 + }, + edgeDelegationStyle: { stroke: '#1890FF', lineDash: [4, 4], lineWidth: 1 } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/theme/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/index.js new file mode 100644 index 0000000..02e514a --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/index.js @@ -0,0 +1,15 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: 编辑器主题样式 - 节点、连线的预设样式 + */ + +import defaultStyle from './default-style' +import darkStyle from './dark-style' +import officeStyle from './office-style' + +export default { + defaultStyle, + darkStyle, + officeStyle +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/theme/office-style.js b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/office-style.js new file mode 100644 index 0000000..b1e21a7 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/theme/office-style.js @@ -0,0 +1,177 @@ +/** + * @author: clay + * @data: 2019/11/21 + * @description: office style + */ + +export default { + // 节点样式 + nodeStyle: { + default: { + stroke: '#CED4D9', + fill: '#FFFFFF', + shadowOffsetX: 0, + shadowOffsetY: 4, + shadowBlur: 10, + shadowColor: 'rgba(13, 26, 38, 0.08)', + lineWidth: 1, + radius: 4, + strokeOpacity: 0.7 + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 4, + shadowOffsetX: 1, + shadowOffsetY: 1 + // shadowColor: '#626262', + // shadowBlur: 8, + // shadowOffsetX: -1, + // shadowOffsetY: 3 + }, + unselected: { + shadowColor: '' + } + }, + // 节点标签样式 + nodeLabelCfg: { + positions: 'center', + style: { + fill: '#000' + } + }, + // 连线样式 + edgeStyle: { + default: { + stroke: '#41c23a', + lineWidth: 2, + strokeOpacity: 0.92, + lineAppendWidth: 10 + // endArrow: true + }, + active: { + shadowColor: 'red', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + inactive: { + shadowColor: '' + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + unselected: { + shadowColor: '' + } + }, + // 锚点样式 + anchorStyle: { + default: { + radius: 3, + symbol: 'circle', + fill: '#FFFFFF', + fillOpacity: 0, + stroke: '#1890FF', + strokeOpacity: 0, + cursor: 'crosshair' + }, + hover: { + fillOpacity: 1, + strokeOpacity: 1 + }, + unhover: { + fillOpacity: 0, + strokeOpacity: 0 + }, + active: { + fillOpacity: 1, + strokeOpacity: 1 + }, + inactive: { + fillOpacity: 0, + strokeOpacity: 0 + } + }, + // 锚点背景样式 + anchorBgStyle: { + default: { + radius: 10, + symbol: 'circle', + fill: '#1890FF', + fillOpacity: 0, + stroke: '#1890FF', + strokeOpacity: 0, + cursor: 'crosshair' + }, + hover: { + fillOpacity: 1, + strokeOpacity: 1 + }, + unhover: { + fillOpacity: 0, + strokeOpacity: 0 + }, + active: { + fillOpacity: 0.3, + strokeOpacity: 0.5 + }, + inactive: { + fillOpacity: 0, + strokeOpacity: 0 + } + }, + + + nodeActivedOutterStyle: { lineWidth: 0 }, + groupSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 }, + nodeSelectedOutterStyle: { stroke: '#E0F0FF', lineWidth: 2 }, + edgeActivedStyle: { stroke: '#1890FF', strokeOpacity: .92 }, + nodeActivedStyle: { fill: '#F3F9FF', stroke: '#1890FF' }, + groupActivedStyle: { stroke: '#1890FF' }, + edgeSelectedStyle: { lineWidth: 2, strokeOpacity: .92, stroke: '#A3B1BF' }, + nodeSelectedStyle: { fill: '#F3F9FF', stroke: '#1890FF', fillOpacity: .4 }, + groupSelectedStyle: { stroke: '#1890FF', fillOpacity: .92 }, + + groupBackgroundPadding: [40, 10, 10, 10], + groupLabelOffsetX: 10, + groupLabelOffsetY: 10, + edgeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' }, + edgeLabelRectPadding: 4, + edgeLabelRectStyle: { fill: 'white' }, + nodeLabelStyle: { fill: '#666', textAlign: 'center', textBaseline: 'middle' }, + groupStyle: { stroke: '#CED4D9', radius: 4 }, + groupLabelStyle: { fill: '#666', textAlign: 'left', textBaseline: 'top' }, + multiSelectRectStyle: { fill: '#1890FF', fillOpacity: .08, stroke: '#1890FF', opacity: .1 }, + dragNodeHoverToGroupStyle: { stroke: '#1890FF', lineWidth: 2 }, + dragNodeLeaveFromGroupStyle: { stroke: '#BAE7FF', lineWidth: 2 }, + anchorPointStyle: { radius: 3.5, fill: '#fff', stroke: '#1890FF', lineAppendWidth: 12 }, + anchorHotsoptStyle: { radius: 12, fill: '#1890FF', fillOpacity: .25 }, + anchorHotsoptActivedStyle: { radius: 14 }, + anchorPointHoverStyle: { radius: 4, fill: '#1890FF', fillOpacity: 1, stroke: '#1890FF' }, + nodeControlPointStyle: { radius: 4, fill: '#fff', shadowBlur: 4, shadowColor: '#666' }, + edgeControlPointStyle: { radius: 6, symbol: 'square', lineAppendWidth: 6, fillOpacity: 0, strokeOpacity: 0 }, + nodeSelectedBoxStyle: { stroke: '#C2C2C2' }, + cursor: { + panningCanvas: '-webkit-grabbing', + beforePanCanvas: '-webkit-grab', + hoverNode: 'move', + hoverEffectiveAnchor: 'crosshair', + hoverEdge: 'default', + hoverGroup: 'move', + hoverUnEffectiveAnchor: 'default', + hoverEdgeControllPoint: 'crosshair', + multiSelect: 'crosshair' + }, + nodeDelegationStyle: { + stroke: '#1890FF', + fill: '#1890FF', + fillOpacity: .08, + lineDash: [4, 4], + radius: 4, + lineWidth: 1 + }, + edgeDelegationStyle: { stroke: '#1890FF', lineDash: [4, 4], lineWidth: 1 } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/toolbar-edit.vue b/hchyun-ui/src/views/tool/top/packages/topology/src/toolbar-edit.vue new file mode 100644 index 0000000..10eacef --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/toolbar-edit.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/toolbar-preview.vue b/hchyun-ui/src/views/tool/top/packages/topology/src/toolbar-preview.vue new file mode 100644 index 0000000..c0c7a0d --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/toolbar-preview.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/topology.vue b/hchyun-ui/src/views/tool/top/packages/topology/src/topology.vue new file mode 100644 index 0000000..5db142b --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/topology.vue @@ -0,0 +1,1372 @@ + + + + + + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/draw.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/draw.js new file mode 100644 index 0000000..3913093 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/draw.js @@ -0,0 +1,60 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: draw anchor + */ + +import theme from '../../theme' + +export default function(cfg, group) { + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + let { anchorPoints, width, height, id } = cfg + if (anchorPoints && anchorPoints.length) { + for (let i = 0, len = anchorPoints.length; i < len; i++) { + let [x, y] = anchorPoints[i] + // 计算Marker中心点坐标 + let originX = -width / 2 + let originY = -height / 2 + let anchorX = x * width + originX + let anchorY = y * height + originY + // 添加锚点背景 + let anchorBgShape = group.addShape('marker', { + id: id + '_anchor_bg_' + i, + attrs: { + name: 'anchorBg', + x: anchorX, + y: anchorY, + // 锚点默认样式 + ...themeStyle.anchorBgStyle.default + }, + draggable: false, + name: 'markerBg-shape' + }) + // 添加锚点Marker形状 + // eslint-disable-next-line no-unused-vars + let anchorShape = group.addShape('marker', { + id: id + '_anchor_' + i, + attrs: { + name: 'anchor', + x: anchorX, + y: anchorY, + // 锚点默认样式 + ...themeStyle.anchorStyle.default + }, + draggable: false, + name: 'marker-shape' + }) + + anchorShape.on('mouseenter', function() { + anchorBgShape.attr({ + ...themeStyle.anchorBgStyle.active + }) + }) + anchorShape.on('mouseleave', function() { + anchorBgShape.attr({ + ...themeStyle.anchorBgStyle.inactive + }) + }) + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/er-draw-left.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/er-draw-left.js new file mode 100644 index 0000000..b56b459 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/er-draw-left.js @@ -0,0 +1,44 @@ +import theme from '../../theme' + + + + +export default function(group,label,x,y,i) { + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + let anchorBgShape = group.addShape('marker', { + id: label+ '_anchor_bg_lift_' + i, + attrs: { + name: 'anchorBg', + x: x, + y: y, + ...themeStyle.anchorBgStyle.default + }, + draggable: false, + name: 'markerBg-shape' + }) + + let anchorShape = group.addShape('marker', { + id: label+ '_anchor_bg_lift_' + i, + attrs: { + name: 'anchor', + x: x, + y: y, + ...themeStyle.anchorStyle.default + }, + draggable: false, + name: 'marker-shape' + }) + + anchorShape.on('mouseenter', function() { + anchorBgShape.attr({ + ...themeStyle.anchorBgStyle.active + }) + }) + anchorShape.on('mouseleave', function() { + anchorBgShape.attr({ + ...themeStyle.anchorBgStyle.inactive + }) + }) + + +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/index.js new file mode 100644 index 0000000..ed1f7f1 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/index.js @@ -0,0 +1,17 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: anchor + */ + +// import draw from './draw' +import erDrawLeft from './er-draw-left' +import setState from './set-state' +// import update from './update' + +export default { + // draw, + setState, + // update, + erDrawLeft, +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/set-state.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/set-state.js new file mode 100644 index 0000000..0713055 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/set-state.js @@ -0,0 +1,27 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: set anchor state + */ + +import theme from '../../theme' + +export default function(name, value, item) { + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + // console.log(item) + if (name === 'hover') { + let group = item.getContainer() + let children = group.get('children') + for (let i = 0, len = children.length; i < len; i++) { + let child = children[i] + // 处理锚点状态 + if (child.attrs.name === 'anchor') { + if (value) { + child.attr(themeStyle.anchorStyle.hover) + } else { + child.attr(themeStyle.anchorStyle.unhover) + } + } + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/update.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/update.js new file mode 100644 index 0000000..1509f05 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/anchor/update.js @@ -0,0 +1,31 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: update anchor + */ + +export default function(cfg, group) { + let { anchorPoints, width, height, id } = cfg + if (anchorPoints && anchorPoints.length) { + for (let i = 0, len = anchorPoints.length; i < len; i++) { + let [x, y] = anchorPoints[i] + // 计算Marker中心点坐标 + let originX = -width / 2 + let originY = -height / 2 + let anchorX = x * width + originX + let anchorY = y * height + originY + // 锚点背景 + let anchorBgShape = group.findById(id + '_anchor_bg_' + i) + // 锚点 + let anchorShape = group.findById(id + '_anchor_' + i) + anchorBgShape.attr({ + x: anchorX, + y: anchorY + }) + anchorShape.attr({ + x: anchorX, + y: anchorY + }) + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/index.js new file mode 100644 index 0000000..d43dc28 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2021/5/11 17:28 + * @email: clay@hchyun.com + * @description: node + */ +import setState from './set-state' + +export default { + setState +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/move.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/move.js new file mode 100644 index 0000000..139e6d3 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/move.js @@ -0,0 +1,57 @@ +/** + * @author: clay + * @data: 2021/5/11 17:33 + * @email: clay@hchyun.com + * @description: node + */ +export default function(e){ + e.preventDefault(); + const { + graph + } = this; + const nodes = graph.getNodes().filter((n) => { + const bbox = n.getBBox(); + + return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox); + }); + if (nodes) { + nodes.forEach((node) => { + const model = node.getModel(); + if (model.attrs.length < 9) { + return; + } + const idx = model.startIndex || 0; + let startX = model.startX || 0.5; + let startIndex = idx + e.deltaY * 0.02; + startX -= e.deltaX; + if (startIndex < 0) { + startIndex = 0; + } + if (startX > 0) { + startX = 0; + } + if (startIndex > model.attrs.length - 1) { + startIndex = model.attrs.length - 1; + } + graph.update(node, { + startIndex, + startX, + }); + }); + } +} +const isInBBox = (point, bbox) => { + const { + x, + y + } = point; + const { + minX, + minY, + maxX, + maxY + } = bbox; + + return x < maxX && x > minX && y > minY && y < maxY; +}; + diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/set-state.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/set-state.js new file mode 100644 index 0000000..c946239 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/collapse/set-state.js @@ -0,0 +1,33 @@ +/** + * @author: clay + * @data: 2021/5/11 17:28 + * @email: clay@hchyun.com + * @description: node + */ +export default function(e){ + const { + graph + } = this; + const { + y + } = e; + const item = e.item; + const shape = e.shape; + if (!item) { + return; + } + + if (shape.get("name") === "collapse") { + graph.updateItem(item, { + collapsed: true, + size: [300, 50], + }); + setTimeout(() => graph.layout(), 100); + } else if (shape.get("name") === "expand") { + graph.updateItem(item, { + collapsed: false, + size: [300, 500], + }); + setTimeout(() => graph.layout(), 100); + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/edge/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/edge/index.js new file mode 100644 index 0000000..8002373 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/edge/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: edge + */ + +import setState from './set-state' + +export default { + setState +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/edge/set-state.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/edge/set-state.js new file mode 100644 index 0000000..9090426 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/edge/set-state.js @@ -0,0 +1,26 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: set edge state + */ + +import theme from '../../theme' + +export default function(name, value, item) { + const group = item.getContainer() + const shape = group.get('children')[0] // 顺序根据 draw 时确定 + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + if (name === 'active') { + if (value) { + shape.attr(themeStyle.edgeStyle.active) + } else { + shape.attr(themeStyle.edgeStyle.inactive) + } + } else if (name === 'selected') { + if (value) { + shape.attr(themeStyle.edgeStyle.selected) + } else { + shape.attr(themeStyle.edgeStyle.unselected) + } + } +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/index.js new file mode 100644 index 0000000..2b33f54 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/index.js @@ -0,0 +1,66 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: graph utils + */ + +import node from './node' +import anchor from './anchor' +import edge from './edge' +import collapse from './collapse' + +/** + * 比较两个对象的内容是否相同(两个对象的键值都相同) + * @param obj1 + * @param obj2 + * @returns {*} + */ +const isObjectValueEqual = function(obj1, obj2) { + let o1 = obj1 instanceof Object + let o2 = obj2 instanceof Object + // 不是对象的情况 + if (!o1 || !o2) { + return obj1 === obj2 + } + // 对象的属性(key值)个数不相等 + if (Object.keys(obj1).length !== Object.keys(obj2).length) { + return false + } + // 判断每个属性(如果属性值也是对象则需要递归) + for (let attr in obj1) { + let t1 = obj1[attr] instanceof Object + let t2 = obj2[attr] instanceof Object + if (t1 && t2) { + return isObjectValueEqual(obj1[attr], obj2[attr]) + } else if (obj1[attr] !== obj2[attr]) { + return false + } + } + return true +} + + +/** + * 生成uuid算法,碰撞率低于1/2^^122 + * @returns {string} + */ +const generateUUID = function() { + let d = new Date().getTime() + // x 是 0-9 或 a-f 范围内的一个32位十六进制数 + let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + let r = (d + Math.random() * 16) % 16 | 0 + d = Math.floor(d / 16) + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) + }) + return uuid +} + +export default { + node, + anchor, + edge, + collapse, + // 通用工具类函数 + isObjectValueEqual, + generateUUID +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/node/index.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/node/index.js new file mode 100644 index 0000000..61e5741 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/node/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: node + */ + +import setState from './set-state' + +export default { + setState +} diff --git a/hchyun-ui/src/views/tool/top/packages/topology/src/utils/node/set-state.js b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/node/set-state.js new file mode 100644 index 0000000..4b60368 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/packages/topology/src/utils/node/set-state.js @@ -0,0 +1,27 @@ +/** + * @author: clay + * @data: 2019/08/15 + * @description: set node state + */ + +import theme from '../../theme' + +export default function(name, value, item) { + const group = item.getContainer() + const shape = group.get('children')[0] // 顺序根据 draw 时确定 + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + if (name === 'active') { + if (value) { + shape.attr(themeStyle.nodeStyle.active) + } else { + shape.attr(themeStyle.nodeStyle.inactive) + } + } else if (name === 'selected') { + if (value) { + group.attr(themeStyle.nodeStyle.selected) + } else { + group.attr(themeStyle.nodeStyle.default) + } + } + +} diff --git a/hchyun-ui/src/views/tool/top/topology.vue b/hchyun-ui/src/views/tool/top/topology.vue new file mode 100644 index 0000000..fd86ae6 --- /dev/null +++ b/hchyun-ui/src/views/tool/top/topology.vue @@ -0,0 +1,224 @@ + + + + + + + diff --git a/hchyun-ui/src/views/tool/top/utils/index.js b/hchyun-ui/src/views/tool/top/utils/index.js new file mode 100644 index 0000000..21ebc8c --- /dev/null +++ b/hchyun-ui/src/views/tool/top/utils/index.js @@ -0,0 +1,47 @@ +/** + * Created by clay on 2019/10/14 + * Description: common utils + */ + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} targetObj + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments: deepClone') + } + let targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(key => { + if (source[key] && typeof source[key] === 'object') { + targetObj[key] = deepClone(source[key]) + } else { + targetObj[key] = source[key] + } + }) + return targetObj +} + +/** + * Randomly extract one or more elements from an array + * If you want to use a perfect solution, use lodash's _.sample or _.sampleSize + * @param {Array} arr + * @param {number} count + * @returns {Array} arr + */ +export function getRandomArrayElements(arr, count = 1) { + if (count > arr.length) { + throw new Error('error arguments: count is greater than length of array') + } + let shuffled = arr.slice(0), i = arr.length, min = i - count, temp, index + while (i-- > min) { + index = Math.floor((i + 1) * Math.random()) + temp = shuffled[index] + shuffled[index] = shuffled[i] + shuffled[i] = temp + } + return shuffled.slice(min) +}