From a51df527100b743e32df2b2b655321c9189ef54c Mon Sep 17 00:00:00 2001 From: "20932067@zju.edu.cn" Date: Mon, 10 May 2021 23:54:51 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90top?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hchyun-ui/package.json | 1 + hchyun-ui/src/assets/styles/hcy.scss | 2 +- .../src/directive/permission/hasPermi.js | 2 +- hchyun-ui/src/directive/permission/hasRole.js | 2 +- hchyun-ui/src/router/index.js | 13 + hchyun-ui/src/utils/hcy.js | 2 +- .../src/views/system/top/demo-topology.vue | 563 +++++++ hchyun-ui/src/views/system/top/index.js | 47 + .../top/packages/assets/iconfont/iconfont.css | 109 ++ .../top/packages/assets/iconfont/iconfont.eot | Bin 0 -> 6896 bytes .../top/packages/assets/iconfont/iconfont.js | 1 + .../top/packages/assets/iconfont/iconfont.svg | 95 ++ .../top/packages/assets/iconfont/iconfont.ttf | Bin 0 -> 6728 bytes .../packages/assets/iconfont/iconfont.woff | Bin 0 -> 4180 bytes .../packages/assets/iconfont/iconfont.woff2 | Bin 0 -> 3404 bytes .../top/packages/assets/images/client.png | Bin 0 -> 2141 bytes .../top/packages/assets/images/database.png | Bin 0 -> 3577 bytes .../top/packages/assets/images/firewall.png | Bin 0 -> 7316 bytes .../top/packages/assets/images/server.png | Bin 0 -> 1555 bytes .../views/system/top/packages/assets/logo.png | Bin 0 -> 5691 bytes .../system/top/packages/elements/button.vue | 93 ++ .../system/top/packages/elements/checkbox.vue | 79 + .../system/top/packages/elements/dropdown.vue | 184 +++ .../system/top/packages/elements/index.js | 17 + .../system/top/packages/elements/loading.vue | 92 ++ .../src/views/system/top/packages/top.js | 39 + .../system/top/packages/topology/index.js | 16 + .../topology/src/behavior/click-add-edge.js | 95 ++ .../topology/src/behavior/click-event-edit.js | 124 ++ .../topology/src/behavior/drag-add-edge.js | 173 ++ .../topology/src/behavior/drag-event-edit.js | 53 + .../topology/src/behavior/hover-event-edit.js | 25 + .../packages/topology/src/behavior/index.js | 28 + .../topology/src/behavior/keyup-event-edit.js | 63 + .../top/packages/topology/src/config/edge.js | 13 + .../top/packages/topology/src/config/index.js | 11 + .../top/packages/topology/src/edge/base.js | 29 + .../topology/src/edge/cc-brokenline.js | 52 + .../packages/topology/src/edge/cc-cubic.js | 15 + .../top/packages/topology/src/edge/cc-line.js | 15 + .../packages/topology/src/edge/cc-polyline.js | 77 + .../top/packages/topology/src/edge/index.js | 23 + .../topology/src/edge/polyline-finding.js | 275 ++++ .../top/packages/topology/src/graph/index.js | 159 ++ .../top/packages/topology/src/node/base.js | 21 + .../top/packages/topology/src/node/index.js | 22 + .../packages/topology/src/node/top-image.js | 126 ++ .../packages/topology/src/node/top-rect.js | 30 + .../topology/src/plugins/d3-installer.js | 9 + .../packages/topology/src/theme/dark-style.js | 177 ++ .../topology/src/theme/default-style.js | 177 ++ .../top/packages/topology/src/theme/index.js | 15 + .../topology/src/theme/office-style.js | 177 ++ .../packages/topology/src/toolbar-edit.vue | 151 ++ .../packages/topology/src/toolbar-preview.vue | 171 ++ .../top/packages/topology/src/topology.vue | 1299 +++++++++++++++ .../topology/src/utils/anchor/draw.js | 59 + .../topology/src/utils/anchor/index.js | 15 + .../topology/src/utils/anchor/set-state.js | 26 + .../topology/src/utils/anchor/update.js | 31 + .../packages/topology/src/utils/edge/index.js | 11 + .../topology/src/utils/edge/set-state.js | 26 + .../top/packages/topology/src/utils/index.js | 64 + .../packages/topology/src/utils/node/index.js | 11 + .../topology/src/utils/node/set-state.js | 26 + hchyun-ui/vue.config.js | 3 + packages/.idea/.gitignore | 5 + packages/.idea/modules.xml | 8 + packages/.idea/packages.iml | 12 + packages/assets/iconfont/iconfont.css | 127 ++ packages/assets/iconfont/iconfont.eot | Bin 0 -> 6896 bytes packages/assets/iconfont/iconfont.js | 1 + packages/assets/iconfont/iconfont.svg | 95 ++ packages/assets/iconfont/iconfont.ttf | Bin 0 -> 6728 bytes packages/assets/iconfont/iconfont.woff | Bin 0 -> 4180 bytes packages/assets/iconfont/iconfont.woff2 | Bin 0 -> 3404 bytes packages/assets/images/client.png | Bin 0 -> 2141 bytes packages/assets/images/database.png | Bin 0 -> 3577 bytes packages/assets/images/firewall.png | Bin 0 -> 7316 bytes packages/assets/images/icon/pc_icon_bc.png | Bin 0 -> 305 bytes packages/assets/images/icon/pc_icon_cxx.png | Bin 0 -> 138 bytes packages/assets/images/icon/pc_icon_fh.png | Bin 0 -> 202 bytes packages/assets/images/icon/pc_icon_sx.png | Bin 0 -> 159 bytes packages/assets/images/icon/pc_icon_xx.png | Bin 0 -> 178 bytes packages/assets/images/icon/pc_icon_zdbj.png | Bin 0 -> 268 bytes packages/assets/images/server.png | Bin 0 -> 1555 bytes packages/assets/logo.png | Bin 0 -> 5691 bytes packages/cc-elements/button.vue | 93 ++ packages/cc-elements/checkbox.vue | 79 + packages/cc-elements/dropdown.vue | 228 +++ packages/cc-elements/index.js | 18 + packages/cc-elements/loading.vue | 92 ++ packages/cc-topology/index.js | 17 + .../src/behavior/click-add-edge.js | 96 ++ .../src/behavior/click-event-edit.js | 161 ++ .../cc-topology/src/behavior/drag-add-edge.js | 220 +++ .../src/behavior/drag-event-edit.js | 54 + .../src/behavior/hover-event-edit.js | 26 + packages/cc-topology/src/behavior/index.js | 29 + .../src/behavior/keyup-event-edit.js | 64 + packages/cc-topology/src/cc-topology.vue | 1455 +++++++++++++++++ packages/cc-topology/src/config/edge.js | 18 + packages/cc-topology/src/config/index.js | 12 + packages/cc-topology/src/edge/base.js | 30 + packages/cc-topology/src/edge/cc-cubic.js | 16 + .../cc-topology/src/edge/crudedottedline.js | 53 + packages/cc-topology/src/edge/dottedline.js | 53 + packages/cc-topology/src/edge/index.js | 24 + .../cc-topology/src/edge/polyline-finding.js | 275 ++++ packages/cc-topology/src/edge/solidline.js | 53 + packages/cc-topology/src/graph/index.js | 160 ++ packages/cc-topology/src/node/base.js | 22 + packages/cc-topology/src/node/cc-image.js | 127 ++ packages/cc-topology/src/node/cc-rect.js | 31 + packages/cc-topology/src/node/index.js | 23 + .../cc-topology/src/plugins/d3-installer.js | 10 + packages/cc-topology/src/theme/dark-style.js | 178 ++ .../cc-topology/src/theme/default-style.js | 220 +++ packages/cc-topology/src/theme/index.js | 16 + .../cc-topology/src/theme/office-style.js | 178 ++ packages/cc-topology/src/toolbar-edit.vue | 207 +++ packages/cc-topology/src/utils/anchor/draw.js | 60 + .../cc-topology/src/utils/anchor/index.js | 16 + .../cc-topology/src/utils/anchor/set-state.js | 27 + .../cc-topology/src/utils/anchor/update.js | 32 + packages/cc-topology/src/utils/edge/index.js | 12 + .../cc-topology/src/utils/edge/set-state.js | 27 + packages/cc-topology/src/utils/index.js | 65 + packages/cc-topology/src/utils/node/index.js | 12 + .../cc-topology/src/utils/node/set-state.js | 27 + packages/index.js | 40 + packages/top/Review.vue | 24 + packages/top/ViewTop.vue | 23 + packages/top/index.vue | 24 + packages/top/topology.vue | 176 ++ 135 files changed, 10381 insertions(+), 4 deletions(-) create mode 100644 hchyun-ui/src/views/system/top/demo-topology.vue create mode 100644 hchyun-ui/src/views/system/top/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.css create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.eot create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.js create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.svg create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.ttf create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.woff create mode 100644 hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.woff2 create mode 100644 hchyun-ui/src/views/system/top/packages/assets/images/client.png create mode 100644 hchyun-ui/src/views/system/top/packages/assets/images/database.png create mode 100644 hchyun-ui/src/views/system/top/packages/assets/images/firewall.png create mode 100644 hchyun-ui/src/views/system/top/packages/assets/images/server.png create mode 100644 hchyun-ui/src/views/system/top/packages/assets/logo.png create mode 100644 hchyun-ui/src/views/system/top/packages/elements/button.vue create mode 100644 hchyun-ui/src/views/system/top/packages/elements/checkbox.vue create mode 100644 hchyun-ui/src/views/system/top/packages/elements/dropdown.vue create mode 100644 hchyun-ui/src/views/system/top/packages/elements/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/elements/loading.vue create mode 100644 hchyun-ui/src/views/system/top/packages/top.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-add-edge.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-event-edit.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-add-edge.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-event-edit.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/hover-event-edit.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/behavior/keyup-event-edit.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/config/edge.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/config/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/base.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-brokenline.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-cubic.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-line.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-polyline.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/edge/polyline-finding.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/graph/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/node/base.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/node/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/node/top-image.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/node/top-rect.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/plugins/d3-installer.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/theme/dark-style.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/theme/default-style.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/theme/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/theme/office-style.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/toolbar-edit.vue create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/toolbar-preview.vue create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/topology.vue create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/draw.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/set-state.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/update.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/set-state.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/node/index.js create mode 100644 hchyun-ui/src/views/system/top/packages/topology/src/utils/node/set-state.js create mode 100644 packages/.idea/.gitignore create mode 100644 packages/.idea/modules.xml create mode 100644 packages/.idea/packages.iml create mode 100644 packages/assets/iconfont/iconfont.css create mode 100644 packages/assets/iconfont/iconfont.eot create mode 100644 packages/assets/iconfont/iconfont.js create mode 100644 packages/assets/iconfont/iconfont.svg create mode 100644 packages/assets/iconfont/iconfont.ttf create mode 100644 packages/assets/iconfont/iconfont.woff create mode 100644 packages/assets/iconfont/iconfont.woff2 create mode 100644 packages/assets/images/client.png create mode 100644 packages/assets/images/database.png create mode 100644 packages/assets/images/firewall.png create mode 100644 packages/assets/images/icon/pc_icon_bc.png create mode 100644 packages/assets/images/icon/pc_icon_cxx.png create mode 100644 packages/assets/images/icon/pc_icon_fh.png create mode 100644 packages/assets/images/icon/pc_icon_sx.png create mode 100644 packages/assets/images/icon/pc_icon_xx.png create mode 100644 packages/assets/images/icon/pc_icon_zdbj.png create mode 100644 packages/assets/images/server.png create mode 100644 packages/assets/logo.png create mode 100644 packages/cc-elements/button.vue create mode 100644 packages/cc-elements/checkbox.vue create mode 100644 packages/cc-elements/dropdown.vue create mode 100644 packages/cc-elements/index.js create mode 100644 packages/cc-elements/loading.vue create mode 100644 packages/cc-topology/index.js create mode 100644 packages/cc-topology/src/behavior/click-add-edge.js create mode 100644 packages/cc-topology/src/behavior/click-event-edit.js create mode 100644 packages/cc-topology/src/behavior/drag-add-edge.js create mode 100644 packages/cc-topology/src/behavior/drag-event-edit.js create mode 100644 packages/cc-topology/src/behavior/hover-event-edit.js create mode 100644 packages/cc-topology/src/behavior/index.js create mode 100644 packages/cc-topology/src/behavior/keyup-event-edit.js create mode 100644 packages/cc-topology/src/cc-topology.vue create mode 100644 packages/cc-topology/src/config/edge.js create mode 100644 packages/cc-topology/src/config/index.js create mode 100644 packages/cc-topology/src/edge/base.js create mode 100644 packages/cc-topology/src/edge/cc-cubic.js create mode 100644 packages/cc-topology/src/edge/crudedottedline.js create mode 100644 packages/cc-topology/src/edge/dottedline.js create mode 100644 packages/cc-topology/src/edge/index.js create mode 100644 packages/cc-topology/src/edge/polyline-finding.js create mode 100644 packages/cc-topology/src/edge/solidline.js create mode 100644 packages/cc-topology/src/graph/index.js create mode 100644 packages/cc-topology/src/node/base.js create mode 100644 packages/cc-topology/src/node/cc-image.js create mode 100644 packages/cc-topology/src/node/cc-rect.js create mode 100644 packages/cc-topology/src/node/index.js create mode 100644 packages/cc-topology/src/plugins/d3-installer.js create mode 100644 packages/cc-topology/src/theme/dark-style.js create mode 100644 packages/cc-topology/src/theme/default-style.js create mode 100644 packages/cc-topology/src/theme/index.js create mode 100644 packages/cc-topology/src/theme/office-style.js create mode 100644 packages/cc-topology/src/toolbar-edit.vue create mode 100644 packages/cc-topology/src/utils/anchor/draw.js create mode 100644 packages/cc-topology/src/utils/anchor/index.js create mode 100644 packages/cc-topology/src/utils/anchor/set-state.js create mode 100644 packages/cc-topology/src/utils/anchor/update.js create mode 100644 packages/cc-topology/src/utils/edge/index.js create mode 100644 packages/cc-topology/src/utils/edge/set-state.js create mode 100644 packages/cc-topology/src/utils/index.js create mode 100644 packages/cc-topology/src/utils/node/index.js create mode 100644 packages/cc-topology/src/utils/node/set-state.js create mode 100644 packages/index.js create mode 100644 packages/top/Review.vue create mode 100644 packages/top/ViewTop.vue create mode 100644 packages/top/index.vue create mode 100644 packages/top/topology.vue diff --git a/hchyun-ui/package.json b/hchyun-ui/package.json index 356f7da..09861f1 100644 --- a/hchyun-ui/package.json +++ b/hchyun-ui/package.json @@ -36,6 +36,7 @@ "management-system" ], "dependencies": { + "@antv/g6": "^4.2.7", "@riophae/vue-treeselect": "0.4.0", "axios": "0.18.1", "clipboard": "2.0.4", diff --git a/hchyun-ui/src/assets/styles/hcy.scss b/hchyun-ui/src/assets/styles/hcy.scss index ef22569..9b75fd1 100644 --- a/hchyun-ui/src/assets/styles/hcy.scss +++ b/hchyun-ui/src/assets/styles/hcy.scss @@ -1,6 +1,6 @@ /** * 通用css样式布局处理 - * Copyright (c) 2019 hchyun + * Copyright (c) 2021 hchyun */ /** 基础通用 **/ diff --git a/hchyun-ui/src/directive/permission/hasPermi.js b/hchyun-ui/src/directive/permission/hasPermi.js index 0e75612..e7a7a56 100644 --- a/hchyun-ui/src/directive/permission/hasPermi.js +++ b/hchyun-ui/src/directive/permission/hasPermi.js @@ -1,6 +1,6 @@ /** * 操作权限处理 - * Copyright (c) 2019 hchyun + * Copyright (c) 2021 hchyun */ import store from '@/store' diff --git a/hchyun-ui/src/directive/permission/hasRole.js b/hchyun-ui/src/directive/permission/hasRole.js index 00f730c..62f5eb8 100644 --- a/hchyun-ui/src/directive/permission/hasRole.js +++ b/hchyun-ui/src/directive/permission/hasRole.js @@ -1,6 +1,6 @@ /** * 角色权限处理 - * Copyright (c) 2019 hchyun + * Copyright (c) 2021 hchyun */ import store from '@/store' diff --git a/hchyun-ui/src/router/index.js b/hchyun-ui/src/router/index.js index 86fac08..4211155 100644 --- a/hchyun-ui/src/router/index.js +++ b/hchyun-ui/src/router/index.js @@ -92,6 +92,19 @@ export const constantRoutes = [ } ] }, + { + path: '/top', + component: Layout, + hidden: true, + children: [ + { + path: '/', + component: (resolve) => require(['@/views/system/top/demo-topology'], resolve), + name:'Top', + meta: {title: 'top视图'} + } + ] + }, { path: '/apiclass', component: Layout, diff --git a/hchyun-ui/src/utils/hcy.js b/hchyun-ui/src/utils/hcy.js index e587de4..b830040 100644 --- a/hchyun-ui/src/utils/hcy.js +++ b/hchyun-ui/src/utils/hcy.js @@ -1,6 +1,6 @@ /** * 通用js方法封装处理 - * Copyright (c) 2019 hchyun + * Copyright (c) 2021 hchyun */ const baseURL = process.env.VUE_APP_BASE_API diff --git a/hchyun-ui/src/views/system/top/demo-topology.vue b/hchyun-ui/src/views/system/top/demo-topology.vue new file mode 100644 index 0000000..6aacb23 --- /dev/null +++ b/hchyun-ui/src/views/system/top/demo-topology.vue @@ -0,0 +1,563 @@ + + + + + + + diff --git a/hchyun-ui/src/views/system/top/index.js b/hchyun-ui/src/views/system/top/index.js new file mode 100644 index 0000000..be5e346 --- /dev/null +++ b/hchyun-ui/src/views/system/top/index.js @@ -0,0 +1,47 @@ +/** + * Created by clay on 2021/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) +} diff --git a/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.css b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.css new file mode 100644 index 0000000..142743d --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.css @@ -0,0 +1,109 @@ +@font-face {font-family: "iconfont"; + src: url('iconfont.eot?t=1568972645985'); /* IE9 */ + src: url('iconfont.eot?t=1568972645985#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAA1MAAsAAAAAGkgAAAz/AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCGNAqiNJtOATYCJANgCzIABCAFhG0Hgggb6BVRlHJWSbKvBuyGeKPMp/UYzGar17rEgTXbq2lrenLOOiJE8OITJy7o6fe+23NLTyvVJoAwKGwrQsbFosAJPEZ3+Ytm/BseN+3fS0glCVCTCasY3VadGPyVrtxu0utIModSm3RSUfZHsjncObIyr8P0tHWYOJ0iZ73RM+2Zsj9df+4zw/JNJ9NJdwqhRklfCGc8j+3e58ZmgnVRSEKWoklKoPcrmZk0P0/vAQIBvH8/VamVDeUAJF3vd7MA0gtNCSshGl4/et+X5y2na8nOGlLHnKxoLUA8VuZibvEGsDHrBry0BQQUMFyDQ83xaxAyVtWrb5F0j+khUKzKBLeXr99Kh8NzCehMY1nptPycxS7JGwzDFukbmNR8MRW+5qXXb00Cw7fwgs+PZ125e8B32V4da2Ma5KA/HXgZDiw4BnAwk3GbBmxJj3OtmP7ceP4B7BPhSU/MEskVU6nwQu9EH7xRxVXS/eHM0Zd9YXi589MDTvXcO37+eLIvQ1Rn/RceIJoTYFwonswTIykqqxkxasyMKBHChAPqQO8G0MJa5oAekKsR0YOqCaYACYBEIOMAOZAJQDEQB1AByYAKIB7wAhABTmA1DPpgNRL6UUgBxAGpgBIgFnAPSAM4gYwAxoCMAu5jNcYwjGwkh2ggs4AyINOAC0AmAZexF1MAMJ8Ocw7woOJvQKg4+840uwPrToBSd7dEvK4t7vqaNfFFG7BPmh1xcj7mKetK4OMad2POjc62lYVVQ4stLy8tV6JEg9uc643L1cHTEs84tW63xsev5q0SacLNnHdans+TeT+TrS6PO/hylgoKhZfDejUj72RllMIEPrGHm0p736c9zytc+yj3BEMZ9dHRZi5qp4djvHrKzSpcOprmAxJqQnihShgQFtgigBsqGECffqJ/xD7UPdYg7DAu0I3GYcftqU4L4glRlYKRUFU0s8SmS1tESc5xGkAAAm7ElR8XPFOMjRcQBNI2kszvscejltEUsnOY4iPkLjIwa8BJnQliXA9BFAKCriRUMfJCIKnrBDzWSPBb6Gte6PHQLpfC7aa83tWd1s9xOzW718uYXK4NbC+OAyXobBDiAneOIKCgtwJQ79PgPByT4zv5KF/Y92Q6ZrrO4NDiScE7XTEARi9rJUIn+WZtahwSYBfad216u2fZdZfo5JVk5C3HlLPuwlvexFNXN+sHLrDn+5O6Rzn7JqTSj7DDGr0xEjH0TsV6Lprpps/7Sb+SM2alSWeIQIxWgq9fzM7u+jwf/31KUweVzqfWmaJwzqTW9QJQdH1JTujN0YTZDCFna8A9pMQNBgAerfpzEBLd7FVtijSZcDfnUJ0zbeiwo6d6u6fOdj/NmM1ROVV74VQPMRoJSgFaGsyo6jHvpNCToRTqUjXzLgmK97XbNS8D8TofjPO1BlfB3s4aI5/kEfqaTZWlCjXlMX0LoaAJyHmWut1K4mB9uFwXBglAnG2NW33VZWW7AQ5wKKiiTrIZ8CHN148AsMnayEdByNnX6oaGtt3FN8dkMQiBcgQfEDx2Hd8CIQ42V07YBTvlUUD9dA0sMPRGIKw1Wag/F4+abNVguU3AEAUJY7Q5GkKCyjOZInQRc8IwGiP1XUoSydvSOnFdiskC5xq7oueAvDDkpSAltu7iQO54T09kzwClyzCd4k5Hn8vXpxvb2BMRhk5a5yL0XgbgNJF9mmEdQ6EkpVgLgY8qaLWA8Glmw0AkwlCat1xTInCMPOWZs+JtZ9Lc8OZGEzz+SXf+JtB1GWzsvshd2ky9DcULNNDzOY07IyTWyxh4ywXONieV2OFZjrfqbKdOn+t72wqOnW/QXdxUB2bPbz/9ZC4fWmwpuO2+zpm25ewZw2uzJhPKLuvul4w7V0Egx3qTyG7bIYI7F22zMv236iMpowbruQS3/s66/hiBZRCf313YC8m8zvNzubN+KF7YNkDM687cxc16I2vYlnzmZqU0vJuLtgYBJtwEWrtNKRu8vBJLZADg0cjwAblWZ3U1I8xjKEjIpzqH2RG9nRtV+D+aAzI9Vqpz8jLdOfPOcc68UStaWSVTUUUdEkeGPQwLbTW3IAU/FxQ59ziXgoUqwcSHG2vx2KrttdXihK+uFY9rW0U4v5b5eNwaZ5Udjbc8+ZPe8APhNzA/eJiygtlM/GF/VljBPMQBWKD1qymweYiDZe2SAEm/Vmp/GkSVwAzmPro+Ep8bPyTN+BCee2ZkiX2EJ6LG70gld/45dGjePGz2ZTpzMLDt4KHBxFyQ+SSu8MiRwjHZ96p2rNDmP2Kr95dZSMphcVBkHlk6xcKswO6nSX+7LRciJ4WCiV+GHG8PPiPRsA2tz/zlkDocMwRC8qClYBJY9DL/x5/2ZbT+D+1419uu7KjXy7d73x1HAWgRB/z0470ZbTPSr+9FensQYt/b2xtFenprc09PRkpYUxhNoynpcLY8FXY46UgYxR9Jak5LhzWD7Fp5tHylnJXjXiG3qHGrY5bDiquR9p19ny+eWZA46ZykSWYu/rzYKXZGVUY9b1YDZ5xICHKGjgg2oGhhYVYWuwfK2/Zfc7CSFeGIKHkGFD1sWtQ09SemsL9wsGRfMj4WXMkP5Vfi4xGVe/cuWfHm2q2Ag2P/X7At+ULQB31oM7hbKVtbWVEJ0qZWRoyYDmiVDiGYBoRnzmSL4MC4gkB8vfJT4eBBebGYIxzJkjSoYLhfYLG+XB7Z1mbowXyY1nZlF8vlGCZZcT4s8GfKrXgFWVshU+bJB4LLGtm8L1s3H5uQST6TabGgO1htbPNa8nYPKbGJghwySZvy3L3cgAnZweY1xJ/vSVYU/iR5tnqFMwebWHOwpYT89vPn31RhtPLwihSFeA8oIlXgPYkyxC3BX+GIV8/HXskLtlzwe1h1bEv7LtxZvP3pF2vXYT/KMknZqrwDRY2FmeI/35u/oq4krNTskLd7ger5N8RXUPHl4j3lK1M36YHELuVrwUrP6teP+NwmkbBSNkGtBB67sgKpXwKmJkyVSmd+9nbDOMH89rFWbeM3062+VqJ0fmmI8M1vJwxpkoPCkO3zK4Ua38Hvf+nuOTHLr8UZ0eTcozrVzWuTqcLIEEY0GtKAREqYFdTBWP+879ekSFVDFkRentU7c4H47/ZYAS8KzbmaS1BfHJn7oiCK1oqmKTueELpFq38KZHOroqOq1B3qn5v7Po8mD+xq/SlHsfNcmuDigdR953ZVpOwcfSn1wOg3/4admg5ak6fBVve00aQiLTt9dNbS3dXA/nSor0wtFjHKN+2otm+orCxTpGTsb2pRItdHRPHA3JAzD4ty/gIDvti/5Lm+3auWNRYtojdt35iTCwbGo/+W509eea6YJOUoV8DTSHbyeDIbZIXc9pVfAMyfPGbl3eUL7vGsf73++zVDazv6rhdtt13KK5odWLiqqZ5e5O7O3lgAsz4WhV11FB8L4IvDAidvfhQ7me0XE50EEqHw+8mQl1Fb1lnyVfmr5NksG3qm1e2h5IcsWxqi+79/aKM+I6dqCnmofkefLCcdDjJifP/+cTAfrNhS1BEjaPLFLKjfUr8gxtckiOko0jHz3ratio2zykRVKPiHp8jbPS/z0Gxvs3Pp247AI4GXpTN/1SWHi6XFc8sMdwVP1z3HH1zxNS0RoWmbJ9ng2YNXecA5XeWz2XoDwS+ZgX/7mxlGNINJqR/suoLv1N1gRyW5+IYr6BzxliF0qfg5CxonajlQqWjjQWmR9QXKft6jadDZAIADSAsxLItK/XxbfGTQpNJtHRvw0cwaG7fzGT5U5f+b6QjKsmZNQaBsLIUyR0wneoFtRRY/bMkE/v9em6kULvo1iLgrs/OnzSGACqr6KRvGSAzeqB4pD1KwDCy2P9zfLh6YL58nUNJxBs/U9Fi6v0OPYFKjLZnXKc4woIhL4CHKpkkf9YnNODseKbGZmhjRCrGSbc5VHhOvuiZBckeKI0UjV1Nq1alk4NBBQmTcJWKM+kGscWMSJFSOE2/WdyQYjyXFi8zHWe3mc++RQDJoEe1EqtCZJMS9ou83xMFKarpy4R/IpzvFyeHxYNsrOCAfA/h1fMpshCEsxAuZGViLoiTMQPFhwlzeHh0Z04CHCotKzzMESJyuqAlqu7pbKcgxd2Y9bfG/gVhgSWQ54lfdf4B46csL2WbJcuBeNed0xL5Y66zFTtVT3RByf4IKwjKqAyviSNhtTpcBCju0ZQyjdGs2PVOlq3YYnxXbhMbad1Re/PkYWTl5BUUlZRVVNY1oVGMa14QmNaVpzWhWc5rXAia948ZZlLqzRywOMHAzLWQMndU2NdAp96TiIK1P99BQWO7aLglS11QWJHUNCmwObOqgQaCxEZyy1E3KHcaDpas7bhdJI6nyHmF64MH+TrCReXQ9E6z1igBcKyLMYWLmQGtoltJzuldMGMp2cPmWkSSKsFUTlj31VSo=') format('woff2'), + url('iconfont.woff?t=1568972645985') format('woff'), + url('iconfont.ttf?t=1568972645985') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('iconfont.svg?t=1568972645985#iconfont') format('svg'); /* iOS 4.1- */ +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-download:before { + content: "\e68d"; +} + +.icon-zoom-out:before { + content: "\e69c"; +} + +.icon-image:before { + content: "\e752"; +} + +.icon-iconedit:before { + content: "\e650"; +} + +.icon-actualsize:before { + content: "\e665"; +} + +.icon-copy:before { + content: "\e61c"; +} + +.icon-zoom-in:before { + content: "\e600"; +} + +.icon-clear:before { + content: "\e700"; +} + +.icon-flow-line:before { + content: "\e660"; +} + +.icon-redo:before { + content: "\e716"; +} + +.icon-undo:before { + content: "\e71a"; +} + +.icon-fit:before { + content: "\e7cb"; +} + +.icon-to-front:before { + content: "\e7cc"; +} + +.icon-to-back:before { + content: "\e7cd"; +} + +.icon-roi-select:before { + content: "\e7ce"; +} + +.icon-json:before { + content: "\e623"; +} + +.icon-fullscreen:before { + content: "\e648"; +} + +.icon-broken:before { + content: "\e9ad"; +} + +.icon-curve:before { + content: "\e9b0"; +} + +.icon-paste:before { + content: "\e963"; +} + +.icon-group:before { + content: "\e915"; +} + +.icon-ungroup:before { + content: "\e917"; +} + +.icon-arrow-dropdown:before { + content: "\e601"; +} + diff --git a/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.eot b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..3eda34f5e80e272c158b0ad093896a3703808aac GIT binary patch literal 6896 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 OgI>mF^3y_F#{L)V2<(sm literal 0 HcmV?d00001 diff --git a/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.js b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.js new file mode 100644 index 0000000..9467797 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.js @@ -0,0 +1 @@ +!function(i){var c,o='',h=(c=document.getElementsByTagName("script"))[c.length-1].getAttribute("data-injectcss");if(h&&!i.__iconfont__svg__cssinject__){i.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var h=function(){document.removeEventListener("DOMContentLoaded",h,!1),c()};document.addEventListener("DOMContentLoaded",h,!1)}else document.attachEvent&&(t=c,l=i.document,e=!1,(v=function(){try{l.documentElement.doScroll("left")}catch(c){return void setTimeout(v,50)}o()})(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,o())});function o(){e||(e=!0,t())}var t,l,e,v}(function(){var c,h;(c=document.createElement("div")).innerHTML=o,o=null,(h=c.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",function(c,h){h.firstChild?function(c,h){h.parentNode.insertBefore(c,h)}(c,h.firstChild):h.appendChild(c)}(h,document.body))})}(window); \ No newline at end of file diff --git a/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.svg b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.svg new file mode 100644 index 0000000..f4aa4a7 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.svg @@ -0,0 +1,95 @@ + + + + + +Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.ttf b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d5ab0e1e683081abdffb407c1af09caa4fed3d75 GIT binary patch literal 6728 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 LkHC+HRHOd~otURy literal 0 HcmV?d00001 diff --git a/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.woff2 b/hchyun-ui/src/views/system/top/packages/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..482e53ec52020c23825ea4c31e5ecc3f5315c595 GIT binary patch literal 3404 zcmV-S4YTrhPew8T0RR9101Zq43jhEB02)XD01W>C0RR9100000000000000000000 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 D)Kace literal 0 HcmV?d00001 diff --git a/hchyun-ui/src/views/system/top/packages/assets/images/database.png b/hchyun-ui/src/views/system/top/packages/assets/images/database.png new file mode 100644 index 0000000000000000000000000000000000000000..809869eb84f82bd570a12ba3c3ca3a8848e9e188 GIT binary patch literal 3577 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 CNZ-r= literal 0 HcmV?d00001 diff --git a/hchyun-ui/src/views/system/top/packages/assets/images/server.png b/hchyun-ui/src/views/system/top/packages/assets/images/server.png new file mode 100644 index 0000000000000000000000000000000000000000..0e49219ad8c0958451ec50be2ee49a6c9be55455 GIT binary patch literal 1555 zcmaKsc~BEq9LKkWLqSAQ3@RWVD2HSd5&|qBZcGLuO$^~iMMAP9E997DX$a~_iKrkb z1r*d^qk|j*-W230S|I}rD5&Eo6c0oN6;w*=h*r8`I#cRQJ3GJqz4!fne&6@rAMa)J zxWU$z%Pau^ux5ua`FIX7#vF6}A31g<7*D2Rm;(dAr6X3K1!nl%Ss23S06_X$0La(} z08_Y>F#-UoWB_=T002-401y?r#;^dqV4fHn%mk*5pT4D{5LYZPHiu>L?3=kJE-Raq zmjnPn$YC?#h_wDO-R@ZyZtJ5(|5;2VDl@dT`EJ&z(H%8)H5Ys&b^W{I_fC$SkJdG8Tv7hLM(-3Asi0@Do@_s5 zIF*_Hc$}uNX`qkvttcqIZgTO>0PjS;X713iH4rhujzF zuuG+l|43gHD;}PF-E*AS_*Nr-Ohzb%Y&nyl-kt>!)-I7sMdum z+&~JnYP4uayVLfZgJ6K|8O?aLY<}0ypD)yywe)yYSLNmMN8O^5Y`0^6MSU-yc4>3X z>|R(sXab|XwKdO!Rn&{Rab?KiEna?m<~p+A#1reEy*@;wsp%fw+M1`I?#C%$gFRR0 zB;5Jke%~syE^U9a=67AKygF+_zw+9lqp=RIO9qn7-JElBB2b%y)wC}6{sGSAvhXyX zz5{YBie!fIx38_m+G76Pt$-7<@08daDV{o{Y+n_(smO3|9mpG{>@dtT;bCH4W%#3h zS-0zE%XT;MRZZA}Zk~RRw88YDf?#E52+j8#EvYx~gLWNLU7@xfQ)FI|5x3uC3+@4eQD)X*TW^4)2@J*z* zXVG=L#-MuA`pUB4@XDy_LDNSj15c(UIon#?o_%zT3~0I;eHgzeRrG zeoOuB}$mCW?=JBrvsa%96Y5u$6 zmiT@#-muBIf=Zr*sRc>|08>>;FhwpDq=2YYAV%;&g~^~wf)_!NTnMs_DrZLZ^Z`Ls zCPLCk5=@%XQl~LKVD?742uZ3m5rUXdWO?*WZ^8Ia5Nn!$X*o2zO8O=@nYhvVkDmpB%vt?1cIPME=54KTCJ4J zF;Ir6RVX$yCm{lr1{KO>BpHH%LXpf9m5Tong)-3SpaLlriZQ4(I+=v;cIx#04tzC$ M&Ehi8GZHfY0;Tk7SO5S3 literal 0 HcmV?d00001 diff --git a/hchyun-ui/src/views/system/top/packages/assets/logo.png b/hchyun-ui/src/views/system/top/packages/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fb8384357c5f9fb233e5d7e67263bd08380b54cc GIT binary patch literal 5691 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 + + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/elements/checkbox.vue b/hchyun-ui/src/views/system/top/packages/elements/checkbox.vue new file mode 100644 index 0000000..2609e14 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/elements/checkbox.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/elements/dropdown.vue b/hchyun-ui/src/views/system/top/packages/elements/dropdown.vue new file mode 100644 index 0000000..5659449 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/elements/dropdown.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/elements/index.js b/hchyun-ui/src/views/system/top/packages/elements/index.js new file mode 100644 index 0000000..03d3e71 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/elements/index.js @@ -0,0 +1,17 @@ +/** + * @author: clay + * @data: 2021/11/14 + * @description: cceditor内部的通用组件 + */ + +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/system/top/packages/elements/loading.vue b/hchyun-ui/src/views/system/top/packages/elements/loading.vue new file mode 100644 index 0000000..a746470 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/elements/loading.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/top.js b/hchyun-ui/src/views/system/top/packages/top.js new file mode 100644 index 0000000..a4baaf4 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/top.js @@ -0,0 +1,39 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 整合所有的组件,对外导出,即一个完整的组件库 + */ + +import Topology from './topology' + +// 存储组件列表 +const components = [ + Topology +] + +// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册 +const install = function(Vue) { + // 判断是否安装 + if (install.installed) return + // 遍历注册全局组件 + console.info('install----CCEditor: All----') + components.map(component => Vue.component(component.name, component)) +} + +// 判断是否是直接引入文件 +if (typeof window !== 'undefined' && window.Vue) { + install(window.Vue) +} + +export default { + // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 + install, + // 以下是具体的组件列表 + Topology +} + +export { + install, + // 以下是具体的组件列表 + Topology +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/index.js b/hchyun-ui/src/views/system/top/packages/topology/index.js new file mode 100644 index 0000000..caeacf1 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/index.js @@ -0,0 +1,16 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 导入组件,组件必须声明 name + */ + +import Topology from './src/topology' + +// 为组件提供 install 安装方法,供按需引入 +Topology.install = function(Vue) { + console.info('install----CCEditor: Topology----') + Vue.component(Topology.name, Topology) +} + +// 默认导出组件 +export default Topology diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-add-edge.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-add-edge.js new file mode 100644 index 0000000..d4c2c36 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-add-edge.js @@ -0,0 +1,95 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: edit mode: 通过先后点击两个节点来添加连线(容易和节点点击动作交叉,已弃用) + */ + +import G6 from '@antv/g6' +import theme from '../theme' + +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/system/top/packages/topology/src/behavior/click-event-edit.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-event-edit.js new file mode 100644 index 0000000..8ed3f44 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/click-event-edit.js @@ -0,0 +1,124 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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) { + if (event.item._cfg.type === 'node') { + // 更新vm的data: selectedNode 和 selectedNodeParams + let clickNode = event.item + 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 } + } + } + } + } + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-add-edge.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-add-edge.js new file mode 100644 index 0000000..b6cb140 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-add-edge.js @@ -0,0 +1,173 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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 + 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 + }) + } + 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 || 'cc-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/system/top/packages/topology/src/behavior/drag-event-edit.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-event-edit.js new file mode 100644 index 0000000..cd18baa --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/drag-event-edit.js @@ -0,0 +1,53 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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/system/top/packages/topology/src/behavior/hover-event-edit.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/hover-event-edit.js new file mode 100644 index 0000000..bf2b7e1 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/hover-event-edit.js @@ -0,0 +1,25 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: edit mode: 悬浮交互 + */ + +export default { + name: 'hover-event-edit', + options: { + getEvents() { + return { + 'node:mouseover': 'onNodeHover', + 'node:mouseout': 'onNodeOut' + } + }, + onNodeHover(event) { + let hoverNode = event.item + hoverNode.setState('hover', true) + }, + onNodeOut(event) { + let hoverNode = event.item + hoverNode.setState('hover', false) + } + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/behavior/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/index.js new file mode 100644 index 0000000..6d2f96a --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/index.js @@ -0,0 +1,28 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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' + +const obj = { + dragAddEdge, + hoverEventEdit, + clickEventEdit, + dragEventEdit, + keyupEventEdit +} + +export default { + obj, + register(G6) { + Object.values(obj).map(item => { + G6.registerBehavior(item.name, item.options) + }) + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/behavior/keyup-event-edit.js b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/keyup-event-edit.js new file mode 100644 index 0000000..cbd8830 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/behavior/keyup-event-edit.js @@ -0,0 +1,63 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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/system/top/packages/topology/src/config/edge.js b/hchyun-ui/src/views/system/top/packages/topology/src/config/edge.js new file mode 100644 index 0000000..2604961 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/config/edge.js @@ -0,0 +1,13 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 线条的后期设置 + */ + +export default { + type: 'cc-line', + style: { + startArrow: false, + endArrow: false + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/config/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/config/index.js new file mode 100644 index 0000000..a6c12bb --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/config/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 配置 + */ + +import edge from './edge' + +export default { + edge +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/base.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/base.js new file mode 100644 index 0000000..691a2fa --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/base.js @@ -0,0 +1,29 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 线公共方法 + */ + +import utils from '../utils' + +export default { + draw(cfg, group) { + const { startPoint, endPoint } = cfg + const keyShape = group.addShape('path', { + className: 'edge-shape', + attrs: { + ...cfg.style, + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x, endPoint.y] + ] + }, + name: 'edge-shape' + }) + return keyShape + }, + setState(name, value, item) { + // 设置边状态 + utils.edge.setState(name, value, item) + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-brokenline.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-brokenline.js new file mode 100644 index 0000000..45a294c --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-brokenline.js @@ -0,0 +1,52 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 折线 + */ + +import base from './base' +import theme from '../theme' + +/** + * fix: 继承 polyline 在 G6 3.x 里面有bug + * 现实现方法参考 https://g6.antv.vision/zh/examples/shape/customEdge#customPolyline + */ +export default { + name: 'cc-brokenline', + extendName: 'line', + options: { + ...base, + getPath(points) { + const startPoint = points[0] + const endPoint = points[1] + return [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y] + ] + }, + getShapeStyle(cfg) { + const { startPoint, endPoint } = cfg + const controlPoints = this.getControlPoints(cfg) + let points = [startPoint] // 添加起始点 + // 添加控制点 + if (controlPoints) { + points = points.concat(controlPoints) + } + // 添加结束点 + points.push(endPoint) + const path = this.getPath(points) + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + const style = { + stroke: '#BBB', + lineWidth: 1, + path, + startArrow: false, + endArrow: false, + ...themeStyle.edgeStyle.default + } + return style + } + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-cubic.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-cubic.js new file mode 100644 index 0000000..dc01adb --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-cubic.js @@ -0,0 +1,15 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 曲线 + */ + +import base from './base' + +export default { + name: 'cc-cubic', + extendName: 'cubic', + options: { + ...base + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-line.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-line.js new file mode 100644 index 0000000..898b532 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-line.js @@ -0,0 +1,15 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 直线 + */ + +import base from './base' + +export default { + name: 'cc-line', + extendName: 'line', + options: { + ...base + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-polyline.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-polyline.js new file mode 100644 index 0000000..7435908 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/cc-polyline.js @@ -0,0 +1,77 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 多段线 + */ + +import base from './base' +import { polylineFinding } from './polyline-finding' + +export default { + name: 'cc-polyline', + extendName: 'single-edge', + options: { + ...base, + draw(cfg, group) { + const { startPoint, endPoint } = cfg + const controlPoints = this.getControlPoints(cfg) + let points = [startPoint] + if (controlPoints) { + points.push(controlPoints) + } + points.push(endPoint) + let path = this.getPath(points) + const keyShape = group.addShape('path', { + className: 'edge-shape', + attrs: { + ...cfg, + path: path + }, + draggable: true, + name: 'edge-shape' + }) + return keyShape + }, + getPath(points) { + const path = [] + for (let i = 0; i < points.length; i++) { + const point = points[i] + if (i === 0) { + path.push(['M', point.x, point.y]) + } else if (i === points.length - 1) { + path.push(['L', point.x, point.y]) + } else { + const prevPoint = points[i - 1] + let nextPoint = points[i + 1] + let cornerLen = 5 + if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) { + if (prevPoint.x === point.x) { + path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen]) + } else if (prevPoint.y === point.y) { + path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]) + } + } + const yLen = Math.abs(point.y - nextPoint.y) + const xLen = Math.abs(point.x - nextPoint.x) + if (yLen > 0 && yLen < cornerLen) { + cornerLen = yLen + } else if (xLen > 0 && xLen < cornerLen) { + cornerLen = xLen + } + if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) { + path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen]) + } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) { + path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]) + } + } + } + return path + }, + getControlPoints(cfg) { + if (!cfg.sourceNode) { + return cfg.controlPoints + } + return polylineFinding(cfg.sourceNode, cfg.targetNode, cfg.startPoint, cfg.endPoint, 40) + } + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/index.js new file mode 100644 index 0000000..76924a7 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/index.js @@ -0,0 +1,23 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: register edges + */ + +import ccLine from './cc-line' +import ccBrokenline from './cc-brokenline' +import ccPolyline from './cc-polyline' +import ccCubic from './cc-cubic' + +const obj = { + ccLine, + ccBrokenline, + ccPolyline, + ccCubic +} + +export default function(G6) { + Object.values(obj).map(item => { + G6.registerEdge(item.name, item.options, item.extendName) + }) +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/edge/polyline-finding.js b/hchyun-ui/src/views/system/top/packages/topology/src/edge/polyline-finding.js new file mode 100644 index 0000000..39531fc --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/edge/polyline-finding.js @@ -0,0 +1,275 @@ +/** + * 带圆角折线连线的策略 + * 文档:https://www.yuque.com/antv/blog/eyi70n + * 参考:https://github.com/guozhaolong/wfd/blob/master/src/item/edge.js + * 引用:https://github.com/OXOYO/X-Flowchart-Vue/blob/master/src/global/lib/g6/edge/polylineFinding.js + */ + +// 折线寻径 +export const polylineFinding = function(sNode, tNode, sPort, tPort, offset = 10) { + const sourceBBox = sNode && sNode.getBBox ? sNode.getBBox() : getPointBBox(sPort) + const targetBBox = tNode && tNode.getBBox ? tNode.getBBox() : getPointBBox(tPort) + // 获取节点带 offset 的区域(扩展区域) + const sBBox = getExpandedBBox(sourceBBox, offset) + const tBBox = getExpandedBBox(targetBBox, offset) + // 获取扩展区域上的起始和终止连接点 + const sPoint = getExpandedPort(sBBox, sPort) + const tPoint = getExpandedPort(tBBox, tPort) + // 获取合法折点集 + let points = getConnectablePoints(sBBox, tBBox, sPoint, tPoint) + // 过滤合法点集,预处理、剪枝等 + filterConnectablePoints(points, sBBox) + // 过滤合法点集,预处理、剪枝等 + filterConnectablePoints(points, tBBox) + // 用 A-Star 算法寻径 + let polylinePoints = AStar(points, sPoint, tPoint, sBBox, tBBox) + return polylinePoints +} + +const getPointBBox = function(t) { + return { + centerX: t.x, + centerY: t.y, + minX: t.x, + minY: t.y, + maxX: t.x, + maxY: t.y, + height: 0, + width: 0 + } +} + +// 获取扩展区域 +const getExpandedBBox = function(bbox, offset) { + if (bbox.width === 0 && bbox.height === 0) { + return bbox + } + return { + centerX: bbox.centerX, + centerY: bbox.centerY, + minX: bbox.minX - offset, + minY: bbox.minY - offset, + maxX: bbox.maxX + offset, + maxY: bbox.maxY + offset, + height: bbox.height + 2 * offset, + width: bbox.width + 2 * offset + } +} + +// 获取扩展区域上的连接点 +const getExpandedPort = function(bbox, point) { + // 判断连接点在上下左右哪个区域,相应地给x或y加上或者减去offset + if (Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height) { + return { + x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, + y: point.y + } + } + return { + x: point.x, + y: point.y > bbox.centerY ? bbox.maxY : bbox.minY + } +} + +// 获取合法折点集合 +const getConnectablePoints = function(sBBox, tBBox, sPoint, tPoint) { + let lineBBox = getBBoxFromVertexes(sPoint, tPoint) + let outerBBox = combineBBoxes(sBBox, tBBox) + let sLineBBox = combineBBoxes(sBBox, lineBBox) + let tLineBBox = combineBBoxes(tBBox, lineBBox) + let points = [ + ...vertexOfBBox(sLineBBox), + ...vertexOfBBox(tLineBBox), + ...vertexOfBBox(outerBBox) + ] + const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY } + let bboxes = [outerBBox, sLineBBox, tLineBBox, lineBBox] + bboxes.forEach(bbox => { + // 包含 bbox 延长线和线段的相交线 + points = [ + ...points, + ...crossPointsByLineAndBBox(bbox, centerPoint) + ] + }) + points.push({ x: sPoint.x, y: tPoint.y }) + points.push({ x: tPoint.x, y: sPoint.y }) + return points +} + +const getBBoxFromVertexes = function(sPoint, tPoint) { + const minX = Math.min(sPoint.x, tPoint.x) + const maxX = Math.max(sPoint.x, tPoint.x) + const minY = Math.min(sPoint.y, tPoint.y) + const maxY = Math.max(sPoint.y, tPoint.y) + + return { + centerX: (minX + maxX) / 2, + centerY: (minY + maxY) / 2, + maxX: maxX, + maxY: maxY, + minX: minX, + minY: minY, + height: maxY - minY, + width: maxX - minX + } +} + +const combineBBoxes = function(sBBox, tBBox) { + const minX = Math.min(sBBox.minX, tBBox.minX) + const minY = Math.min(sBBox.minY, tBBox.minY) + const maxX = Math.max(sBBox.maxX, tBBox.maxX) + const maxY = Math.max(sBBox.maxY, tBBox.maxY) + + return { + centerX: (minX + maxX) / 2, + centerY: (minY + maxY) / 2, + minX: minX, + minY: minY, + maxX: maxX, + maxY: maxY, + height: maxY - minY, + width: maxX - minX + } +} + +const vertexOfBBox = function(bbox) { + return [ + { x: bbox.minX, y: bbox.minY }, + { x: bbox.maxX, y: bbox.minY }, + { x: bbox.maxX, y: bbox.maxY }, + { x: bbox.minX, y: bbox.maxY } + ] +} + +const crossPointsByLineAndBBox = function(bbox, centerPoint) { + let crossPoints = [] + if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) { + crossPoints = [ + ...crossPoints, + { x: centerPoint.x, y: bbox.minY }, + { x: centerPoint.x, y: bbox.maxY } + ] + } + if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) { + crossPoints = [ + ...crossPoints, + { x: bbox.minX, y: centerPoint.y }, + { x: bbox.maxX, y: centerPoint.y } + ] + } + + return crossPoints +} + +// 过滤连接点 +const filterConnectablePoints = function(points, bbox) { + return points.filter(point => { + return point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY + }) +} + +const crossBBox = function(bboxes, p1, p2) { + for (let i = 0; i < bboxes.length; i++) { + const bbox = bboxes[i] + if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) { + if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) { + return true + } + } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) { + if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) { + return true + } + } + } + return false +} + +const getCost = function(p1, p2) { + return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) +} + +// aStar 寻径 +const AStar = function(points, sPoint, tPoint, sBBox, tBBox) { + const openList = [sPoint] + const closeList = [] + points.forEach(item => { + item.id = item.x + '-' + item.y + }) + let tmpArr = [] + points.forEach(item => { + if (!tmpArr.includes(target => target.id === item.id)) { + tmpArr.push(item) + } + }) + points = [ + ...tmpArr, + tPoint + ] + let endPoint + while (openList.length > 0) { + let minCostPoint + openList.forEach((p, i) => { + if (!p.parent) { + p.f = 0 + } + if (!minCostPoint) { + minCostPoint = p + } + if (p.f < minCostPoint.f) { + minCostPoint = p + } + }) + if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) { + endPoint = minCostPoint + break + } + openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1) + closeList.push(minCostPoint) + const neighbor = points.filter(p => { + return (p.x === minCostPoint.x || p.y === minCostPoint.y) && + !(p.x === minCostPoint.x && p.y === minCostPoint.y) && + !crossBBox([sBBox, tBBox], minCostPoint, p) + } + ) + neighbor.forEach(p => { + const inOpen = openList.find(o => o.x === p.x && o.y === p.y) + const currentG = getCost(p, minCostPoint) + if (!closeList.find(o => o.x === p.x && o.y === p.y)) { + if (inOpen) { + if (p.g > currentG) { + p.parent = minCostPoint + p.g = currentG + p.f = p.g + p.h + } + } else { + p.parent = minCostPoint + p.g = currentG + let h = getCost(p, tPoint) + if (crossBBox([tBBox], p, tPoint)) { + // 如果穿过bbox则增加该点的预估代价为bbox周长的一半 + h += (tBBox.width / 2 + tBBox.height / 2) + } + p.h = h + p.f = p.g + p.h + openList.push(p) + } + } + }) + } + if (endPoint) { + const result = [] + result.push({ + x: endPoint.x, + y: endPoint.y + }) + while (endPoint.parent) { + endPoint = endPoint.parent + result.push({ + x: endPoint.x, + y: endPoint.y + }) + } + return result.reverse() + } + return [] +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/graph/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/graph/index.js new file mode 100644 index 0000000..0e9c346 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/graph/index.js @@ -0,0 +1,159 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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: 'top-rect', + labelCfg: { + position: 'bottom' + } + }, + defaultEdge: { + type: 'cc-line', + 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/system/top/packages/topology/src/node/base.js b/hchyun-ui/src/views/system/top/packages/topology/src/node/base.js new file mode 100644 index 0000000..eb7feb1 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/node/base.js @@ -0,0 +1,21 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 节点基础方法 + */ + +import utils from '../utils' + +export default { + setState(name, value, item) { + // 设置节点状态 + utils.node.setState(name, value, item) + // 设置锚点状态 + utils.anchor.setState(name, value, item) + }, + // 绘制后附加锚点 + afterDraw(cfg, group) { + // 绘制锚点 + utils.anchor.draw(cfg, group) + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/node/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/node/index.js new file mode 100644 index 0000000..85fcbaf --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/node/index.js @@ -0,0 +1,22 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: register nodes + */ + +import ccRect from './top-rect' +import ccImage from './top-image' + +const obj = { + ccRect, + ccImage +} + +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/system/top/packages/topology/src/node/top-image.js b/hchyun-ui/src/views/system/top/packages/topology/src/node/top-image.js new file mode 100644 index 0000000..1ffb211 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/node/top-image.js @@ -0,0 +1,126 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 图片节点 + */ + +import utils from '../utils' + +// 用来获取调用此js的vue组件实例(this) +let vm = null + +const sendThis = (_this) => { + vm = _this +} + +export default { + sendThis, + name: 'top-image', + extendName: 'image', + options: { + setState(name, value, item) { + // 设置节点状态 + utils.node.setState(name, value, item) + // 设置锚点状态 + if (vm.graphMode === 'edit') { + utils.anchor.setState(name, value, item) + } + }, + // 绘制后附加锚点 + afterDraw(cfg, group) { + // 绘制锚点 + if (vm.graphMode === 'edit') { + utils.anchor.draw(cfg, group) + } + }, + // 设置告警状态 + afterUpdate(cfg, node) { + const group = node.getContainer() + // 获取children + const halos = group.findAll(function(item) { + return item.attrs.name === 'halo' + }) + // 告警 + if (cfg.appState && cfg.appState.alert) { + if (halos.length > 0) { + return + } + let size = this.getSize(cfg) || [48, 48] + let r = size[0] / 2 + let { id } = cfg + let halo1 = group.addShape('circle', { + id: id + '_halo_' + 1, + attrs: { + name: 'halo', + x: 0, + y: 0, + r: r, + fill: cfg.color || '#F56C6C', + opacity: 0.6 + }, + name: 'halo', + zIndex: -3 + }) + let halo2 = group.addShape('circle', { + id: id + '_halo_' + 2, + attrs: { + name: 'halo', + x: 0, + y: 0, + r: r, + fill: cfg.color || '#F56C6C', // 为了显示清晰,随意设置了颜色 + opacity: 0.6 + }, + name: 'halo', + zIndex: -2 + }) + let halo3 = group.addShape('circle', { + id: id + '_halo_' + 3, + attrs: { + name: 'halo', + x: 0, + y: 0, + r: r, + fill: cfg.color || '#F56C6C', + opacity: 0.6 + }, + name: 'halo', + zIndex: -1 + }) + group.sort() // 排序,根据zIndex 排序 + halo1.animate({ // 逐渐放大,并消失 + r: r + 10, + opacity: 0.1, + }, { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 0 // 无延迟 + }) + halo2.animate({ // 逐渐放大,并消失 + r: r + 10, + opacity: 0.1 + }, { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 1000 // 1 秒延迟 + }) + halo3.animate({ // 逐渐放大,并消失 + r: r + 10, + opacity: 0.1 + }, { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 2000 // 2 秒延迟 + }) + } else { + halos.forEach(halo => { + // FIXME: G6 3.x在底层库遗留了bug,导致removeChild()方法报错,等待解决 + group.removeChild(halo) + }) + } + } + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/node/top-rect.js b/hchyun-ui/src/views/system/top/packages/topology/src/node/top-rect.js new file mode 100644 index 0000000..4945056 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/node/top-rect.js @@ -0,0 +1,30 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: 矩形节点 + */ + +import base from './base' +import theme from '../theme' + +export default { + name: 'top-rect', + extendName: 'rect', + options: { + ...base, + getShapeStyle(cfg) { + const size = this.getSize(cfg) || [48, 48] + const width = size[0] + const height = size[1] + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + const style = { + x: 0 - width / 2, + y: 0 - height / 2, + width: width, + height: height, + ...themeStyle.nodeStyle.default + } + return style + } + } +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/plugins/d3-installer.js b/hchyun-ui/src/views/system/top/packages/topology/src/plugins/d3-installer.js new file mode 100644 index 0000000..1018837 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/plugins/d3-installer.js @@ -0,0 +1,9 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: install 3rd plugins + */ + +import * as d3 from 'd3-force/dist/d3-force' + +export default d3 diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/theme/dark-style.js b/hchyun-ui/src/views/system/top/packages/topology/src/theme/dark-style.js new file mode 100644 index 0000000..ab8409b --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/theme/dark-style.js @@ -0,0 +1,177 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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/system/top/packages/topology/src/theme/default-style.js b/hchyun-ui/src/views/system/top/packages/topology/src/theme/default-style.js new file mode 100644 index 0000000..7459cb6 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/theme/default-style.js @@ -0,0 +1,177 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + // 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: 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: { + 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/system/top/packages/topology/src/theme/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/theme/index.js new file mode 100644 index 0000000..dd2290f --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/theme/index.js @@ -0,0 +1,15 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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/system/top/packages/topology/src/theme/office-style.js b/hchyun-ui/src/views/system/top/packages/topology/src/theme/office-style.js new file mode 100644 index 0000000..48f425c --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/theme/office-style.js @@ -0,0 +1,177 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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: 0, + shadowOffsetY: 0 + // 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/system/top/packages/topology/src/toolbar-edit.vue b/hchyun-ui/src/views/system/top/packages/topology/src/toolbar-edit.vue new file mode 100644 index 0000000..70eeb55 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/toolbar-edit.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/toolbar-preview.vue b/hchyun-ui/src/views/system/top/packages/topology/src/toolbar-preview.vue new file mode 100644 index 0000000..be9a3eb --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/toolbar-preview.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/topology.vue b/hchyun-ui/src/views/system/top/packages/topology/src/topology.vue new file mode 100644 index 0000000..ac989be --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/topology.vue @@ -0,0 +1,1299 @@ + + + + + + diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/draw.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/draw.js new file mode 100644 index 0000000..a92d1d0 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/draw.js @@ -0,0 +1,59 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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形状 + 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/system/top/packages/topology/src/utils/anchor/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/index.js new file mode 100644 index 0000000..58cdc6d --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/index.js @@ -0,0 +1,15 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: anchor + */ + +import draw from './draw' +import setState from './set-state' +import update from './update' + +export default { + draw, + setState, + update +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/set-state.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/set-state.js new file mode 100644 index 0000000..f51ca10 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/set-state.js @@ -0,0 +1,26 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: set anchor state + */ + +import theme from '../../theme' + +export default function(name, value, item) { + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + 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/system/top/packages/topology/src/utils/anchor/update.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/update.js new file mode 100644 index 0000000..e5ac825 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/anchor/update.js @@ -0,0 +1,31 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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/system/top/packages/topology/src/utils/edge/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/index.js new file mode 100644 index 0000000..da9ab39 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: edge + */ + +import setState from './set-state' + +export default { + setState +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/set-state.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/set-state.js new file mode 100644 index 0000000..5703455 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/edge/set-state.js @@ -0,0 +1,26 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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/system/top/packages/topology/src/utils/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/index.js new file mode 100644 index 0000000..5c1f3d9 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/index.js @@ -0,0 +1,64 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: graph utils + */ + +import node from './node' +import anchor from './anchor' +import edge from './edge' + +/** + * 比较两个对象的内容是否相同(两个对象的键值都相同) + * @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, + // 通用工具类函数 + isObjectValueEqual, + generateUUID +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/utils/node/index.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/node/index.js new file mode 100644 index 0000000..f33b64d --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/node/index.js @@ -0,0 +1,11 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @description: node + */ + +import setState from './set-state' + +export default { + setState +} diff --git a/hchyun-ui/src/views/system/top/packages/topology/src/utils/node/set-state.js b/hchyun-ui/src/views/system/top/packages/topology/src/utils/node/set-state.js new file mode 100644 index 0000000..fffada0 --- /dev/null +++ b/hchyun-ui/src/views/system/top/packages/topology/src/utils/node/set-state.js @@ -0,0 +1,26 @@ +/** + * @author: clay + * @data: 2021/5/10 + * @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) { + shape.attr(themeStyle.nodeStyle.selected) + } else { + shape.attr(themeStyle.nodeStyle.default) + } + } +} diff --git a/hchyun-ui/vue.config.js b/hchyun-ui/vue.config.js index ceac998..7d6abea 100644 --- a/hchyun-ui/vue.config.js +++ b/hchyun-ui/vue.config.js @@ -45,6 +45,9 @@ module.exports = { // }, // disableHostCheck: true // }, + // 强制内联CSS + // 默认true: 使用CSS分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用 ")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var h=function(){document.removeEventListener("DOMContentLoaded",h,!1),c()};document.addEventListener("DOMContentLoaded",h,!1)}else document.attachEvent&&(t=c,l=i.document,e=!1,(v=function(){try{l.documentElement.doScroll("left")}catch(c){return void setTimeout(v,50)}o()})(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,o())});function o(){e||(e=!0,t())}var t,l,e,v}(function(){var c,h;(c=document.createElement("div")).innerHTML=o,o=null,(h=c.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",function(c,h){h.firstChild?function(c,h){h.parentNode.insertBefore(c,h)}(c,h.firstChild):h.appendChild(c)}(h,document.body))})}(window); \ No newline at end of file diff --git a/packages/assets/iconfont/iconfont.svg b/packages/assets/iconfont/iconfont.svg new file mode 100644 index 0000000..f4aa4a7 --- /dev/null +++ b/packages/assets/iconfont/iconfont.svg @@ -0,0 +1,95 @@ + + + + + +Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/assets/iconfont/iconfont.ttf b/packages/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d5ab0e1e683081abdffb407c1af09caa4fed3d75 GIT binary patch literal 6728 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 LkHC+HRHOd~otURy literal 0 HcmV?d00001 diff --git a/packages/assets/iconfont/iconfont.woff2 b/packages/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..482e53ec52020c23825ea4c31e5ecc3f5315c595 GIT binary patch literal 3404 zcmV-S4YTrhPew8T0RR9101Zq43jhEB02)XD01W>C0RR9100000000000000000000 z0000SLIzd3Z3lt~8|W2LlyX){vabg0hIpgQr}Y@j zW~#iGCoJVtlA z9yjgZOGqUNP?HI(7~NK#82pv4+-}nANHT{~n{-l9_D8Zg+&Qu`ufz1Tb(lDvB4@qP zGwqqOKV5(BGsE&tC(}uH3ZX_xUkGPBkL}*)#%6+bQAk3TqDe}i_bZv1G(S)80RjQM ze?L{Jl?~+pBwg=!vjEZyO$jR@8eTto-Dc z9w3?%U|cAk%o8IiDytbWYRrr%5yFH6>Ok)X(85~gfF6*m5z&LH38nxEfJ7kU06CBe zKrs*pPyxw+3LqZv0uTXCU^NW-U^No@QAh#A0jYozAQo^BNCP;5i~z=fi~{asHO4TE zY$S(hAhUonAk%;gKqdi~u@_SSFrN-{4)CDjZ$PLx`_44G1M5zJQg^pRysjRYQv{FE1@uqC|t+oa>Ft)xpye&p5Six5me> z=B*NG!p)p_TAt67c|Vh_E{{9-a+V4Tg_py6H6!n2Wt74MKCy?JN_*d@J=JRFnbJNYPI*!N^QX1dPc(BetK zJ6$mVMlWk6LMQoVZE746U>DkVZF;+B*>#ubjy5d=lheo zM>+c@q|%SD!y3IY5;62n#d88*7NxzaaW~E7Ih(L^1g0El9&hGj=AOC$yQwNpK zr`DOGaL%OG^#V|IeMwH}&C!I}3?XN0z&)hI4F&)nt@?8i65Z@oZHhFRa5v{roilCd zu%oAXyQj`}Kh2oUQ8`t6;ZzSX8VO1PS{lr#>M`%6(37DQ>QXiHE(wbFwc9l>1MxZ^ zjPtbySHa%S8Y7?N5&D`&i$l2HGhK9D?#W%-fF$jTj1RoGQb{*dWAr3ZIPS^!I zwDV@j-7AN zUEEX$X3uXw{bVj5T5Kt}?Y_>Lw&m;@!)vo96Ur{@?w5=^S3w}hdXva*+aZEG7j3gL z{kQ5PDWeAKxdgZV&bs~>fn^Y%-(A=X$-K_$fr7o*%7^+8&&u}ngcRD zR;qLIGTk}z&T-DXQ7c+mB~wvF9TG=|Jq!zNHMbxIKNl1^_c)h=g(|_shZ}40*sATd z)#8M&t`(1KTSYj(mic(x8fTRqjki4cliu(H;Wscpc$l(+*-ZRle^yw*Jj4N5p!KUM zuz84sW$lsxNxxQVe;P!UzzpU-x;_%08$Tq?_z<3ZW@L$dgeRiL??@$g{yB7L-aKsf zWjb>(u{ID0v1HJ#1RkH1#K@u)lDyWM48q z7F@{>Sz94f=E(HdRp32|GQ=&x zU*U*X&&OWL3$|SFdsuaBOZzU|S-kz}7i-sHKgu#m*{Zw)MU91-;y>S;zoM=rtkmp~ zw|jx=`8UK@Q1RvBJ>@G?H|c@IF6C>%O3&)okNDgsk+4!WL9GNHyRrhQUjn8kOqEJ! ze)e|5IKlk(u~u#4H`A@YR-$x%X^8O6Zzl|Cl7qsK?ei;z8sEY1f9dWyG0U&T8PVk2 zqdL{iYm=$M$Ph*}8q$D7N|+VY!C3#i@7JVARYMj;UY^xEbAkBp_E-Tg3eCBiOHf}N znfsz3iq?vzDLbAJx<#vh3S@JuqNA$R9qON(`#z5*5A16FDMzvMT$2R`;~gq#`XleRS5+-POSUbf~XPEfz7#H|?mlZ>X#+Q>0|< zZ)#B@*GEM0z}%2C4~ugC0t3F-zvQ{T-K&;07A>MTZQq!a3kJrc|CZ-ZUU{yVB;_bq zz|%-}@^~^EWQE-Jm0tkNpFC#e-Q^4J@vMKn{`)mUYdiX`7j3s)$}5^3Sh%XGo?hhc z&fZu6vpyDuU3C;63*d{x0w-^N6g!#i7ZaTX5~1+>$&i;(Th>|fRr#yr*(@7+rq%7C zAFS9?)Da_S^2IEu-%`II#@8f> z=_t}M^WL_tijA|%L{%vGho{KfJumanY;Uu3>D!LLBY~HtGk?`3hl{1fbIS~O1y9#K z#~)nrH7yaLX`3h6;Ms#$dB8bc<+Is(1Hmtufq(m(8AddNNvR*~x`OZ2-C##0x%h@F z=p6BuA#|zuISU#mYB^A;XyZX?k@W@2e%_;L(AfZh14s)oEQ?D0d|P~E(4^9B9UJh` z%o-cF^BF!=<^P-M2+Fc%O+lb+EQK;hOecDQZABJ8Y{>-v|6ZG^6fXKTh`1}W^QX-r zfC{SmDI3N}4Bn_7DGySx3@o-k+}|!9m@l6vP||S*&(!o-y1zq@U{a$knb#@KFrbJ_ z;33MUNgwrz%{V(ADY2QF7|{x`l5Nga9uu#+CP8vXiX%lMS5s=$sbt{LK|*BQC1Q;F zL9B6O5+qcP6K~dcBp8n+#f!|xS?%U??~y<Mi=rgVK=EP1T;3iX8PkA=3X4k5GXzMSuBZuW zySiH`$T4?jJ#F#7fmmQkWI5tj-G6|1>B|e*X328kUNz@*#J*VTti?{%Q{519e}W3a zGO7bB;z-zSPL}}+JG5mCqjYOFJyWHtb{L-(+l0p2cT`^d`50L_c?Cr!WffI5jc8P3 z8rOs-HKl3IXjXHY*8(QJw`7fBZb0Aw5mfH{cD z7zF@65&+&u0RZMa02pzKYJ*&%f>t!%-v>fy&3f0RL$I8K4d(&?eC<*!D`W(jK%_Pg zNAT4i($q5Cp|;F2=MVsBHsO3cNe5qz7ZKPS9++g53XbEGRMa-y#;d2IYOrrN(+_VR z-676a^IfUU%vk3>Vq7;WJ3;xZo?`U`|4V|dvq^H46SmhIW-#@eo5|R@4H~|b7pMWt z{DcC7b1p9IH3lYK$qP*z?cR&2_||=RLO>KjQSp=B0N*pz*0%DaCcr)FKqyRI_) z(1$axoy_-VDAna^341vH9k9WoY_javzBg~CBy;SyM^YNTJz(9Gwi??+)XxN#J-X#4 zw(KF-Ps`PtG|zO-y?=x)BT|JzlZ;TQ=A@sfTll$0wqIExm*>i8t<2urV9ByKW^Hfu zX{+#fJjv2910JH1)zs7sExh~|IO{g0a-EcgM?UdvCgTl!7fL@g3z9!5r}P@zY_`BB zk!D9GWM*dVb!XeHNjYQv?%Z6iuF5^Em@;A+bhbd=*!nSUZ%0~0iR9D2L@#bnoyg_% zP25vitea?1yhN7{q%P@q+1;$i zuFfi+6biQ!4{1HP5q-rdr;o3M{LFWcd0q@2g2XC z)iNf&i0MAfp;vl^{D|^eL8 z#)|}j@kUH%=JGo$Ep!m)H`hmHPG(++CvWeGmcmkYb$RVSy*5FVk|SP$?x~!Armt<` z@XsJn;_PIA?6Nn^&MJQ$ziGn>Dn0VqF;fFCQ8=0O@7_WAm>4U-@-{pa zM+C*|ai6&#E1cA_DdsF=m6sj>zozN+KkR>I7;iBWGtWKa58wTw3lBHFJm4-GTRhR? zPE4fF)!FwdOmhP|=7^jAacJ7Kq4F1W;i|1V|+FD23EL+rNKlytkc&#GzlkBpPCNbexF^II98^w8+gt)LbAw-{JQ;0 z;&w4-yS#FN_$uIYr5<#zf4SuUB*U9#M%B|5h6=Y8w?&&Q*4Z>Ci|(dhvVNcKH5NHj zVzyg}AGYFO#pO#UhVPsnXn%ck=CjfxYW$ee%#8bia`k3;NBXyqVK>%CZjSxbR{ufG zK@#7o?(!+TwO{^ohsPpM_4bgTt+j#exseLT(1Ue-t*Mbe?c0Y;?vO_2CSBf@TXpAB zr$w{Hlb^`sQ`^{&k#W~7zez#YTFKly7PvF@&*6elDc`NV`rM$C1-8GKbrWCREj-o6 zht0OL8vSnQ%7b2x@ouF~PwWU;C_Y)J{3ysWCcBIT(>mnWt?DRZ66cSq%F5>1&kAku z!3_>9JZoqMe!#S;sC;bPp3o$dJk@wm5=T_@jwjbG1SgCaH2b%n?@X?9BhsJTd0gW5 zdU3DY>iX65t90_=K0a;LIWn1bzWGC+VQ<*%S;@~q(&x8018T^h>5E#ED>MJ3T(caJ zs+xb7G?q)6_H-8xe0jAh>@s}zYa&}FRi=qn} zWY9@>rPqi#A-L8%!T1z5UZ%R6;HsMwDh*kG+ON90I;k^taj5utrux^RSFNpn)wQ*l zmqjwX>ykJp)>TX-Dq*1p>BmStiYk}D=H43V= zUL-*maVd?5gn?1!Sfy3h8Ptg1#quWSq+x7G$X)JZ(~~Eg1j>k;WZ7VYqGfEnYWeh7 zw`kdMk*T`#Xs*=>xJ=D%aPi&lO=k+z&C8(IeH+hrA1{W=12MED5CXs!iE_3^I#?qe zNwz4AJqm-g-HCLGu@7#-Wy1n_2wDuwn!$|wA4y{&mK?YUl&#%T*x5PR+CbBmBeT4r(*T@rfKP>2ROY_{ D)Kace literal 0 HcmV?d00001 diff --git a/packages/assets/images/database.png b/packages/assets/images/database.png new file mode 100644 index 0000000000000000000000000000000000000000..809869eb84f82bd570a12ba3c3ca3a8848e9e188 GIT binary patch literal 3577 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 CNZ-r= literal 0 HcmV?d00001 diff --git a/packages/assets/images/icon/pc_icon_bc.png b/packages/assets/images/icon/pc_icon_bc.png new file mode 100644 index 0000000000000000000000000000000000000000..06fb1cd31fbb2ae36886919b95eb42584f569842 GIT binary patch literal 305 zcmV-10nYx3P)?tbg;CCSO6clRF2 zK=R7nF9GaRRl8dXGaISubByskP1Db|ZJ#=vB*{tBG9RPTk!-1LE9EwN} zz~ijyS&VVA02RRKS6J8eYo6!V9jvPACd;zJjSxWHonM+!4j`L7b+uv$p%38bkHNl_ zWjR*WRk-0RrrSFVrvdn9kKX}UidIj}%8W00000NkvXXu0mjf DVr78N literal 0 HcmV?d00001 diff --git a/packages/assets/images/icon/pc_icon_cxx.png b/packages/assets/images/icon/pc_icon_cxx.png new file mode 100644 index 0000000000000000000000000000000000000000..92ab002f4427a7d82535c7d4dceaecc84768be8e GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^NSeY`?}B!fql!ke+*VAr m$dogE5+)hqul&S{g(1}?rm$tzoxMQA89ZJ6T-G@yGywpA*(^B# literal 0 HcmV?d00001 diff --git a/packages/assets/images/icon/pc_icon_fh.png b/packages/assets/images/icon/pc_icon_fh.png new file mode 100644 index 0000000000000000000000000000000000000000..50f8b3c32fd26355e2effd91ee142d7df97ca3c9 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp@K+MO%1|+}KPrC%9CVIL!hG+yQrzkKkS-w2_-~a#r z)s+paf`Wo%BqSwYOG-*Em6VWB`HzG-Ci8zQP4bb(bhlciO>ZdzmQwal1$zk7~B_^bAPSOTn%&_gQu&X%Q~loCIA-7 BPoV$+ literal 0 HcmV?d00001 diff --git a/packages/assets/images/icon/pc_icon_sx.png b/packages/assets/images/icon/pc_icon_sx.png new file mode 100644 index 0000000000000000000000000000000000000000..3800bb8c02c3fb674d0d643159e4b691efd191a5 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^DnQJ~!3HGV&G(%EQfZzpjv*f2Z>JvQJz&7W{QBgq z)P&wu*$0eXri9c!iInWLd6?aNhLdx}tJ0hm9W&nj3vo|$U(g}Jux#etrw&&cG|T=Jwt5)5==s*(M0>KN$Lz zp}?;7g2I}DEqsfaH+{UMuy?C)%iqSh>D!(eD{OXmi~RUruJC(On)dG1PSfW}g~l>W eIdh3;g7lXC$%US?RSp3i!QkoY=d#Wzp$P!%&_=cZ literal 0 HcmV?d00001 diff --git a/packages/assets/images/icon/pc_icon_zdbj.png b/packages/assets/images/icon/pc_icon_zdbj.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4288284d751b0aad95a2bcf8a6aa1403fde8d0 GIT binary patch literal 268 zcmV+n0rUQeP)^v;F1 z0G}VyG;NsK#5vde3%mkY6x2A5rP={dEr7>DSOA{av4}GwssXqW(I^NrTRz5ih{2qh zTV~ex-k$lvJ0000F#-UoWB_=T002-401y?r#;^dqV4fHn%mk*5pT4D{5LYZPHiu>L?3=kJE-Raq zmjnPn$YC?#h_wDO-R@ZyZtJ5(|5;2VDl@dT`EJ&z(H%8)H5Ys&b^W{I_fC$SkJdG8Tv7hLM(-3Asi0@Do@_s5 zIF*_Hc$}uNX`qkvttcqIZgTO>0PjS;X713iH4rhujzF zuuG+l|43gHD;}PF-E*AS_*Nr-Ohzb%Y&nyl-kt>!)-I7sMdum z+&~JnYP4uayVLfZgJ6K|8O?aLY<}0ypD)yywe)yYSLNmMN8O^5Y`0^6MSU-yc4>3X z>|R(sXab|XwKdO!Rn&{Rab?KiEna?m<~p+A#1reEy*@;wsp%fw+M1`I?#C%$gFRR0 zB;5Jke%~syE^U9a=67AKygF+_zw+9lqp=RIO9qn7-JElBB2b%y)wC}6{sGSAvhXyX zz5{YBie!fIx38_m+G76Pt$-7<@08daDV{o{Y+n_(smO3|9mpG{>@dtT;bCH4W%#3h zS-0zE%XT;MRZZA}Zk~RRw88YDf?#E52+j8#EvYx~gLWNLU7@xfQ)FI|5x3uC3+@4eQD)X*TW^4)2@J*z* zXVG=L#-MuA`pUB4@XDy_LDNSj15c(UIon#?o_%zT3~0I;eHgzeRrG zeoOuB}$mCW?=JBrvsa%96Y5u$6 zmiT@#-muBIf=Zr*sRc>|08>>;FhwpDq=2YYAV%;&g~^~wf)_!NTnMs_DrZLZ^Z`Ls zCPLCk5=@%XQl~LKVD?742uZ3m5rUXdWO?*WZ^8Ia5Nn!$X*o2zO8O=@nYhvVkDmpB%vt?1cIPME=54KTCJ4J zF;Ir6RVX$yCm{lr1{KO>BpHH%LXpf9m5Tong)-3SpaLlriZQ4(I+=v;cIx#04tzC$ M&Ehi8GZHfY0;Tk7SO5S3 literal 0 HcmV?d00001 diff --git a/packages/assets/logo.png b/packages/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fb8384357c5f9fb233e5d7e67263bd08380b54cc GIT binary patch literal 5691 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 + + + + + + diff --git a/packages/cc-elements/checkbox.vue b/packages/cc-elements/checkbox.vue new file mode 100644 index 0000000..aa36f62 --- /dev/null +++ b/packages/cc-elements/checkbox.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/packages/cc-elements/dropdown.vue b/packages/cc-elements/dropdown.vue new file mode 100644 index 0000000..497489e --- /dev/null +++ b/packages/cc-elements/dropdown.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/packages/cc-elements/index.js b/packages/cc-elements/index.js new file mode 100644 index 0000000..ae59602 --- /dev/null +++ b/packages/cc-elements/index.js @@ -0,0 +1,18 @@ +/** + * @author: winyuan + * @data: 2019/11/14 + * @repository: https://github.com/winyuan + * @description: cceditor内部的通用组件 + */ + +import Checkbox from './checkbox' +import Button from './button' +import Dropdown from './dropdown' +import Loading from './loading' + +export { + Checkbox, + Button, + Dropdown, + Loading +} diff --git a/packages/cc-elements/loading.vue b/packages/cc-elements/loading.vue new file mode 100644 index 0000000..f410a92 --- /dev/null +++ b/packages/cc-elements/loading.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/packages/cc-topology/index.js b/packages/cc-topology/index.js new file mode 100644 index 0000000..70e0b68 --- /dev/null +++ b/packages/cc-topology/index.js @@ -0,0 +1,17 @@ +/** + * @author: winyuan + * @data: 2019/08/20 + * @repository: https://github.com/winyuan + * @description: 导入组件,组件必须声明 name + */ + +import CCTopology from './src/cc-topology' + +// 为组件提供 install 安装方法,供按需引入 +CCTopology.install = function(Vue) { + console.info('install----CCEditor: CCTopology----') + Vue.component(CCTopology.name, CCTopology) +} + +// 默认导出组件 +export default CCTopology diff --git a/packages/cc-topology/src/behavior/click-add-edge.js b/packages/cc-topology/src/behavior/click-add-edge.js new file mode 100644 index 0000000..267f299 --- /dev/null +++ b/packages/cc-topology/src/behavior/click-add-edge.js @@ -0,0 +1,96 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @description: edit mode: 通过先后点击两个节点来添加连线(容易和节点点击动作交叉,已弃用) + */ + +// import G6 from '@antv/g6' +// import theme from '../theme' + +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/packages/cc-topology/src/behavior/click-event-edit.js b/packages/cc-topology/src/behavior/click-event-edit.js new file mode 100644 index 0000000..3af735e --- /dev/null +++ b/packages/cc-topology/src/behavior/click-event-edit.js @@ -0,0 +1,161 @@ +/** + * @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) { + console.log(event,'88888888888888888888888888888') + 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) { + // todo + let clickEdge = event.item + // // todo 入口 + clickEdge.setState('selected', !clickEdge.hasState('selected')) + + + this.onEdgeRightClick(event) + + + vm.currentFocus = 'edge' + vm.rightMenuShow = false + 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)) { + // 先取消所有节点的选中状态 + selectedEdges.forEach(edge => { + edge.setState('selected', false) + }) + // 再添加该节点的选中状态 + clickEdge.setState('selected', true) + vm.currentFocus = 'edge' + this.updateVmData(event) + } + // eslint-disable-next-line no-unused-vars + let point = { x: event.x, y: event.y } + }, + onCanvasClick() { + vm.currentFocus = 'canvas' + vm.rightMenuShow = false + }, + updateVmData(event) { + if (event.item._cfg.type === 'node') { + // 更新vm的data: selectedNode 和 selectedNodeParams + //TODO 修改右侧输入信息 + let clickNode = event.item + if (clickNode.hasState('selected')) { + console.log(clickNode.getModel()) + + let clickNodeModel = clickNode.getModel() + vm.selectedNode = clickNode + //todo 修改右侧输入信息 + let nodeAppConfig = { ...vm.nodeAppConfig } + + + // console.log(nodeAppConfig,'nodeAppConfig') + + + + Object.keys(nodeAppConfig).forEach(function(key) { + nodeAppConfig[key] = '' + }) + let uuids =[] + for (let i = 0;i { + 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 + 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 + }) + } + if (self.graph.$C.edge.type=='dottedline'){ + console.log(1) + 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 || 'solidline', + //todo 修改类型 + // style:null + style: G6.Util.mix({}, themeStyle.edgeStyle.dottedline, self.graph.$C.edge.style) + }) + }else if (self.graph.$C.edge.type=='solidline'){ + console.log(2) + 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 || 'solidline', + //todo 修改类型 + // style:null + style: G6.Util.mix({}, themeStyle.edgeStyle.solidline, self.graph.$C.edge.style) + }) + }else if (self.graph.$C.edge.type=='crudedottedline'){ + console.log(3) + + 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 || 'solidline', + //todo 修改类型 + // style:null + style: G6.Util.mix({}, themeStyle.edgeStyle.crudedottedline, self.graph.$C.edge.style) + }) + } + + // console.log(self.drawEdge) + console.log(self.graph.$C.edge.type) + // self.drawEdge.currentLine.style = G6.Util.mix({}, themeStyle.edgeStyle.dottedline, 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/packages/cc-topology/src/behavior/drag-event-edit.js b/packages/cc-topology/src/behavior/drag-event-edit.js new file mode 100644 index 0000000..eb77d22 --- /dev/null +++ b/packages/cc-topology/src/behavior/drag-event-edit.js @@ -0,0 +1,54 @@ +/** + * @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: '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/packages/cc-topology/src/behavior/hover-event-edit.js b/packages/cc-topology/src/behavior/hover-event-edit.js new file mode 100644 index 0000000..9167b72 --- /dev/null +++ b/packages/cc-topology/src/behavior/hover-event-edit.js @@ -0,0 +1,26 @@ +/** + * @author: winyuan + * @data: 2019/07/16 + * @repository: https://github.com/winyuan + * @description: edit mode: 悬浮交互 + */ + +export default { + name: 'hover-event-edit', + options: { + getEvents() { + return { + 'node:mouseover': 'onNodeHover', + 'node:mouseout': 'onNodeOut' + } + }, + onNodeHover(event) { + let hoverNode = event.item + hoverNode.setState('hover', true) + }, + onNodeOut(event) { + let hoverNode = event.item + hoverNode.setState('hover', false) + } + } +} diff --git a/packages/cc-topology/src/behavior/index.js b/packages/cc-topology/src/behavior/index.js new file mode 100644 index 0000000..2ed8c8c --- /dev/null +++ b/packages/cc-topology/src/behavior/index.js @@ -0,0 +1,29 @@ +/** + * @author: winyuan + * @data: 2019/07/16 + * @repository: https://github.com/winyuan + * @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' + +const obj = { + dragAddEdge, + hoverEventEdit, + clickEventEdit, + dragEventEdit, + keyupEventEdit, +} + +export default { + obj, + register(G6) { + Object.values(obj).map(item => { + G6.registerBehavior(item.name, item.options) + }) + } +} diff --git a/packages/cc-topology/src/behavior/keyup-event-edit.js b/packages/cc-topology/src/behavior/keyup-event-edit.js new file mode 100644 index 0000000..7867acc --- /dev/null +++ b/packages/cc-topology/src/behavior/keyup-event-edit.js @@ -0,0 +1,64 @@ +/** + * @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: '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/packages/cc-topology/src/cc-topology.vue b/packages/cc-topology/src/cc-topology.vue new file mode 100644 index 0000000..4324c65 --- /dev/null +++ b/packages/cc-topology/src/cc-topology.vue @@ -0,0 +1,1455 @@ + + + + + + diff --git a/packages/cc-topology/src/config/edge.js b/packages/cc-topology/src/config/edge.js new file mode 100644 index 0000000..658b4ce --- /dev/null +++ b/packages/cc-topology/src/config/edge.js @@ -0,0 +1,18 @@ +/** + * @author: winyuan + * @data: 2019/08/16 + * @repository: https://github.com/winyuan + * @description: 线条的后期设置 + */ +//配置是否有箭头 +import G6 from '@antv/g6' +export default { + type: 'solidline', + style: { + startArrow: false, + endArrow: { + path: G6.Arrow.vee(10, 20, 15), + d: 25 + } + } +} diff --git a/packages/cc-topology/src/config/index.js b/packages/cc-topology/src/config/index.js new file mode 100644 index 0000000..6af1d9a --- /dev/null +++ b/packages/cc-topology/src/config/index.js @@ -0,0 +1,12 @@ +/** + * @author: winyuan + * @data: 2019/08/16 + * @repository: https://github.com/winyuan + * @description: 配置 + */ + +import edge from './edge' + +export default { + edge +} diff --git a/packages/cc-topology/src/edge/base.js b/packages/cc-topology/src/edge/base.js new file mode 100644 index 0000000..4a10aea --- /dev/null +++ b/packages/cc-topology/src/edge/base.js @@ -0,0 +1,30 @@ +/** + * @author: winyuan + * @data: 2019/07/18 + * @repository: https://github.com/winyuan + * @description: 线公共方法 + */ + +import utils from '../utils' + +export default { + draw(cfg, group) { + const { startPoint, endPoint } = cfg + const keyShape = group.addShape('path', { + className: 'edge-shape', + attrs: { + ...cfg.style, + path: [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x, endPoint.y] + ] + }, + name: 'edge-shape' + }) + return keyShape + }, + setState(name, value, item) { + // 设置边状态 + utils.edge.setState(name, value, item) + } +} diff --git a/packages/cc-topology/src/edge/cc-cubic.js b/packages/cc-topology/src/edge/cc-cubic.js new file mode 100644 index 0000000..306b6d1 --- /dev/null +++ b/packages/cc-topology/src/edge/cc-cubic.js @@ -0,0 +1,16 @@ +/** + * @author: winyuan + * @data: 2019/07/18 + * @repository: https://github.com/winyuan + * @description: 曲线 + */ + +import base from './base' + +export default { + name: 'cc-cubic', + extendName: 'cubic', + options: { + ...base + } +} diff --git a/packages/cc-topology/src/edge/crudedottedline.js b/packages/cc-topology/src/edge/crudedottedline.js new file mode 100644 index 0000000..4250df0 --- /dev/null +++ b/packages/cc-topology/src/edge/crudedottedline.js @@ -0,0 +1,53 @@ +/** + * @author: winyuan + * @data: 2019/10/22 + * @repository: https://github.com/winyuan + * @description: 折线 + */ + +import base from './base' +import theme from '../theme' + +/** + * fix: 继承 polyline 在 G6 3.x 里面有bug + * 现实现方法参考 https://g6.antv.vision/zh/examples/shape/customEdge#customPolyline + */ +export default { + name: 'crudedottedline', + extendName: 'line', + options: { + ...base, + getPath(points) { + const startPoint = points[0] + const endPoint = points[1] + return [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y] + ] + }, + getShapeStyle(cfg) { + const { startPoint, endPoint } = cfg + const controlPoints = this.getControlPoints(cfg) + let points = [startPoint] // 添加起始点 + // 添加控制点 + if (controlPoints) { + points = points.concat(controlPoints) + } + // 添加结束点 + points.push(endPoint) + const path = this.getPath(points) + const themeStyle = theme.darkStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + const style = { + stroke: '#BBB', + lineWidth: 1, + path, + startArrow: false, + endArrow: true, + ...themeStyle.edgeStyle.default + } + return style + } + } +} diff --git a/packages/cc-topology/src/edge/dottedline.js b/packages/cc-topology/src/edge/dottedline.js new file mode 100644 index 0000000..2576283 --- /dev/null +++ b/packages/cc-topology/src/edge/dottedline.js @@ -0,0 +1,53 @@ +/** + * @author: winyuan + * @data: 2019/10/22 + * @repository: https://github.com/winyuan + * @description: 折线 + */ + +import base from './base' +import theme from '../theme' + +/** + * fix: 继承 polyline 在 G6 3.x 里面有bug + * 现实现方法参考 https://g6.antv.vision/zh/examples/shape/customEdge#customPolyline + */ +export default { + name: 'dottedline', + extendName: 'line', + options: { + ...base, + getPath(points) { + const startPoint = points[0] + const endPoint = points[1] + return [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y] + ] + }, + getShapeStyle(cfg) { + const { startPoint, endPoint } = cfg + const controlPoints = this.getControlPoints(cfg) + let points = [startPoint] // 添加起始点 + // 添加控制点 + if (controlPoints) { + points = points.concat(controlPoints) + } + // 添加结束点 + points.push(endPoint) + const path = this.getPath(points) + const themeStyle = theme.darkStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + const style = { + stroke: '#BBB', + lineWidth: 1, + path, + startArrow: false, + endArrow: true, + ...themeStyle.edgeStyle.default + } + return style + } + } +} diff --git a/packages/cc-topology/src/edge/index.js b/packages/cc-topology/src/edge/index.js new file mode 100644 index 0000000..0ea5ba3 --- /dev/null +++ b/packages/cc-topology/src/edge/index.js @@ -0,0 +1,24 @@ +/** + * @author: winyuan + * @data: 2019/07/18 + * @repository: https://github.com/winyuan + * @description: register edges + */ + +import ccCubic from './cc-cubic' +import dottedline from './dottedline' +import solidline from './solidline' +import crudedottedline from './crudedottedline' + +const obj = { + ccCubic, + dottedline, + solidline, + crudedottedline +} + +export default function(G6) { + Object.values(obj).map(item => { + G6.registerEdge(item.name, item.options, item.extendName) + }) +} diff --git a/packages/cc-topology/src/edge/polyline-finding.js b/packages/cc-topology/src/edge/polyline-finding.js new file mode 100644 index 0000000..39531fc --- /dev/null +++ b/packages/cc-topology/src/edge/polyline-finding.js @@ -0,0 +1,275 @@ +/** + * 带圆角折线连线的策略 + * 文档:https://www.yuque.com/antv/blog/eyi70n + * 参考:https://github.com/guozhaolong/wfd/blob/master/src/item/edge.js + * 引用:https://github.com/OXOYO/X-Flowchart-Vue/blob/master/src/global/lib/g6/edge/polylineFinding.js + */ + +// 折线寻径 +export const polylineFinding = function(sNode, tNode, sPort, tPort, offset = 10) { + const sourceBBox = sNode && sNode.getBBox ? sNode.getBBox() : getPointBBox(sPort) + const targetBBox = tNode && tNode.getBBox ? tNode.getBBox() : getPointBBox(tPort) + // 获取节点带 offset 的区域(扩展区域) + const sBBox = getExpandedBBox(sourceBBox, offset) + const tBBox = getExpandedBBox(targetBBox, offset) + // 获取扩展区域上的起始和终止连接点 + const sPoint = getExpandedPort(sBBox, sPort) + const tPoint = getExpandedPort(tBBox, tPort) + // 获取合法折点集 + let points = getConnectablePoints(sBBox, tBBox, sPoint, tPoint) + // 过滤合法点集,预处理、剪枝等 + filterConnectablePoints(points, sBBox) + // 过滤合法点集,预处理、剪枝等 + filterConnectablePoints(points, tBBox) + // 用 A-Star 算法寻径 + let polylinePoints = AStar(points, sPoint, tPoint, sBBox, tBBox) + return polylinePoints +} + +const getPointBBox = function(t) { + return { + centerX: t.x, + centerY: t.y, + minX: t.x, + minY: t.y, + maxX: t.x, + maxY: t.y, + height: 0, + width: 0 + } +} + +// 获取扩展区域 +const getExpandedBBox = function(bbox, offset) { + if (bbox.width === 0 && bbox.height === 0) { + return bbox + } + return { + centerX: bbox.centerX, + centerY: bbox.centerY, + minX: bbox.minX - offset, + minY: bbox.minY - offset, + maxX: bbox.maxX + offset, + maxY: bbox.maxY + offset, + height: bbox.height + 2 * offset, + width: bbox.width + 2 * offset + } +} + +// 获取扩展区域上的连接点 +const getExpandedPort = function(bbox, point) { + // 判断连接点在上下左右哪个区域,相应地给x或y加上或者减去offset + if (Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height) { + return { + x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, + y: point.y + } + } + return { + x: point.x, + y: point.y > bbox.centerY ? bbox.maxY : bbox.minY + } +} + +// 获取合法折点集合 +const getConnectablePoints = function(sBBox, tBBox, sPoint, tPoint) { + let lineBBox = getBBoxFromVertexes(sPoint, tPoint) + let outerBBox = combineBBoxes(sBBox, tBBox) + let sLineBBox = combineBBoxes(sBBox, lineBBox) + let tLineBBox = combineBBoxes(tBBox, lineBBox) + let points = [ + ...vertexOfBBox(sLineBBox), + ...vertexOfBBox(tLineBBox), + ...vertexOfBBox(outerBBox) + ] + const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY } + let bboxes = [outerBBox, sLineBBox, tLineBBox, lineBBox] + bboxes.forEach(bbox => { + // 包含 bbox 延长线和线段的相交线 + points = [ + ...points, + ...crossPointsByLineAndBBox(bbox, centerPoint) + ] + }) + points.push({ x: sPoint.x, y: tPoint.y }) + points.push({ x: tPoint.x, y: sPoint.y }) + return points +} + +const getBBoxFromVertexes = function(sPoint, tPoint) { + const minX = Math.min(sPoint.x, tPoint.x) + const maxX = Math.max(sPoint.x, tPoint.x) + const minY = Math.min(sPoint.y, tPoint.y) + const maxY = Math.max(sPoint.y, tPoint.y) + + return { + centerX: (minX + maxX) / 2, + centerY: (minY + maxY) / 2, + maxX: maxX, + maxY: maxY, + minX: minX, + minY: minY, + height: maxY - minY, + width: maxX - minX + } +} + +const combineBBoxes = function(sBBox, tBBox) { + const minX = Math.min(sBBox.minX, tBBox.minX) + const minY = Math.min(sBBox.minY, tBBox.minY) + const maxX = Math.max(sBBox.maxX, tBBox.maxX) + const maxY = Math.max(sBBox.maxY, tBBox.maxY) + + return { + centerX: (minX + maxX) / 2, + centerY: (minY + maxY) / 2, + minX: minX, + minY: minY, + maxX: maxX, + maxY: maxY, + height: maxY - minY, + width: maxX - minX + } +} + +const vertexOfBBox = function(bbox) { + return [ + { x: bbox.minX, y: bbox.minY }, + { x: bbox.maxX, y: bbox.minY }, + { x: bbox.maxX, y: bbox.maxY }, + { x: bbox.minX, y: bbox.maxY } + ] +} + +const crossPointsByLineAndBBox = function(bbox, centerPoint) { + let crossPoints = [] + if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) { + crossPoints = [ + ...crossPoints, + { x: centerPoint.x, y: bbox.minY }, + { x: centerPoint.x, y: bbox.maxY } + ] + } + if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) { + crossPoints = [ + ...crossPoints, + { x: bbox.minX, y: centerPoint.y }, + { x: bbox.maxX, y: centerPoint.y } + ] + } + + return crossPoints +} + +// 过滤连接点 +const filterConnectablePoints = function(points, bbox) { + return points.filter(point => { + return point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY + }) +} + +const crossBBox = function(bboxes, p1, p2) { + for (let i = 0; i < bboxes.length; i++) { + const bbox = bboxes[i] + if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) { + if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) { + return true + } + } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) { + if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) { + return true + } + } + } + return false +} + +const getCost = function(p1, p2) { + return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) +} + +// aStar 寻径 +const AStar = function(points, sPoint, tPoint, sBBox, tBBox) { + const openList = [sPoint] + const closeList = [] + points.forEach(item => { + item.id = item.x + '-' + item.y + }) + let tmpArr = [] + points.forEach(item => { + if (!tmpArr.includes(target => target.id === item.id)) { + tmpArr.push(item) + } + }) + points = [ + ...tmpArr, + tPoint + ] + let endPoint + while (openList.length > 0) { + let minCostPoint + openList.forEach((p, i) => { + if (!p.parent) { + p.f = 0 + } + if (!minCostPoint) { + minCostPoint = p + } + if (p.f < minCostPoint.f) { + minCostPoint = p + } + }) + if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) { + endPoint = minCostPoint + break + } + openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1) + closeList.push(minCostPoint) + const neighbor = points.filter(p => { + return (p.x === minCostPoint.x || p.y === minCostPoint.y) && + !(p.x === minCostPoint.x && p.y === minCostPoint.y) && + !crossBBox([sBBox, tBBox], minCostPoint, p) + } + ) + neighbor.forEach(p => { + const inOpen = openList.find(o => o.x === p.x && o.y === p.y) + const currentG = getCost(p, minCostPoint) + if (!closeList.find(o => o.x === p.x && o.y === p.y)) { + if (inOpen) { + if (p.g > currentG) { + p.parent = minCostPoint + p.g = currentG + p.f = p.g + p.h + } + } else { + p.parent = minCostPoint + p.g = currentG + let h = getCost(p, tPoint) + if (crossBBox([tBBox], p, tPoint)) { + // 如果穿过bbox则增加该点的预估代价为bbox周长的一半 + h += (tBBox.width / 2 + tBBox.height / 2) + } + p.h = h + p.f = p.g + p.h + openList.push(p) + } + } + }) + } + if (endPoint) { + const result = [] + result.push({ + x: endPoint.x, + y: endPoint.y + }) + while (endPoint.parent) { + endPoint = endPoint.parent + result.push({ + x: endPoint.x, + y: endPoint.y + }) + } + return result.reverse() + } + return [] +} diff --git a/packages/cc-topology/src/edge/solidline.js b/packages/cc-topology/src/edge/solidline.js new file mode 100644 index 0000000..ed4bd1a --- /dev/null +++ b/packages/cc-topology/src/edge/solidline.js @@ -0,0 +1,53 @@ +/** + * @author: winyuan + * @data: 2019/10/22 + * @repository: https://github.com/winyuan + * @description: 折线 + */ + +import base from './base' +import theme from '../theme' + +/** + * fix: 继承 polyline 在 G6 3.x 里面有bug + * 现实现方法参考 https://g6.antv.vision/zh/examples/shape/customEdge#customPolyline + */ +export default { + name: 'solidline', + extendName: 'line', + options: { + ...base, + getPath(points) { + const startPoint = points[0] + const endPoint = points[1] + return [ + ['M', startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, startPoint.y], + ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, endPoint.y], + ['L', endPoint.x, endPoint.y] + ] + }, + getShapeStyle(cfg) { + const { startPoint, endPoint } = cfg + const controlPoints = this.getControlPoints(cfg) + let points = [startPoint] // 添加起始点 + // 添加控制点 + if (controlPoints) { + points = points.concat(controlPoints) + } + // 添加结束点 + points.push(endPoint) + const path = this.getPath(points) + const themeStyle = theme.darkStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + const style = { + stroke: '#BBB', + lineWidth: 1, + path, + startArrow: false, + endArrow: true, + ...themeStyle.edgeStyle.default + } + return style + } + } +} diff --git a/packages/cc-topology/src/graph/index.js b/packages/cc-topology/src/graph/index.js new file mode 100644 index 0000000..78c29e8 --- /dev/null +++ b/packages/cc-topology/src/graph/index.js @@ -0,0 +1,160 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @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: 'cc-rect', + labelCfg: { + position: 'bottom' + } + }, + defaultEdge: { + type: 'cc-line', + 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/packages/cc-topology/src/node/base.js b/packages/cc-topology/src/node/base.js new file mode 100644 index 0000000..92212bb --- /dev/null +++ b/packages/cc-topology/src/node/base.js @@ -0,0 +1,22 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @description: 节点基础方法 + */ + +import utils from '../utils' + +export default { + setState(name, value, item) { + // 设置节点状态 + utils.node.setState(name, value, item) + // 设置锚点状态 + utils.anchor.setState(name, value, item) + }, + // 绘制后附加锚点 + afterDraw(cfg, group) { + // 绘制锚点 + utils.anchor.draw(cfg, group) + } +} diff --git a/packages/cc-topology/src/node/cc-image.js b/packages/cc-topology/src/node/cc-image.js new file mode 100644 index 0000000..371b9e8 --- /dev/null +++ b/packages/cc-topology/src/node/cc-image.js @@ -0,0 +1,127 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @description: 图片节点 + */ + +import utils from '../utils' + +// 用来获取调用此js的vue组件实例(this) +let vm = null + +const sendThis = (_this) => { + vm = _this +} + +export default { + sendThis, + name: 'cc-image', + extendName: 'image', + options: { + setState(name, value, item) { + // 设置节点状态 + utils.node.setState(name, value, item) + // 设置锚点状态 + if (vm.graphMode === 'edit') { + utils.anchor.setState(name, value, item) + } + }, + // 绘制后附加锚点 + afterDraw(cfg, group) { + // 绘制锚点 + if (vm.graphMode === 'edit') { + utils.anchor.draw(cfg, group) + } + }, + // 设置告警状态 + afterUpdate(cfg, node) { + const group = node.getContainer() + // 获取children + const halos = group.findAll(function(item) { + return item.attrs.name === 'halo' + }) + // 告警 + if (cfg.appState && cfg.appState.alert) { + if (halos.length > 0) { + return + } + let size = this.getSize(cfg) || [48, 48] + let r = size[0] / 2 + let { id } = cfg + let halo1 = group.addShape('circle', { + id: id + '_halo_' + 1, + attrs: { + name: 'halo', + x: 0, + y: 0, + r: r, + fill: cfg.color || '#F56C6C', + opacity: 0.6 + }, + name: 'halo', + zIndex: -3 + }) + let halo2 = group.addShape('circle', { + id: id + '_halo_' + 2, + attrs: { + name: 'halo', + x: 0, + y: 0, + r: r, + fill: cfg.color || '#F56C6C', // 为了显示清晰,随意设置了颜色 + opacity: 0.6 + }, + name: 'halo', + zIndex: -2 + }) + let halo3 = group.addShape('circle', { + id: id + '_halo_' + 3, + attrs: { + name: 'halo', + x: 0, + y: 0, + r: r, + fill: cfg.color || '#F56C6C', + opacity: 0.6 + }, + name: 'halo', + zIndex: -1 + }) + group.sort() // 排序,根据zIndex 排序 + halo1.animate({ // 逐渐放大,并消失 + r: r + 10, + opacity: 0.1, + }, { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 0 // 无延迟 + }) + halo2.animate({ // 逐渐放大,并消失 + r: r + 10, + opacity: 0.1 + }, { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 1000 // 1 秒延迟 + }) + halo3.animate({ // 逐渐放大,并消失 + r: r + 10, + opacity: 0.1 + }, { + repeat: true, // 循环 + duration: 3000, + easing: 'easeCubic', + delay: 2000 // 2 秒延迟 + }) + } else { + halos.forEach(halo => { + // FIXME: G6 3.x在底层库遗留了bug,导致removeChild()方法报错,等待解决 + group.removeChild(halo) + }) + } + } + } +} diff --git a/packages/cc-topology/src/node/cc-rect.js b/packages/cc-topology/src/node/cc-rect.js new file mode 100644 index 0000000..270ce18 --- /dev/null +++ b/packages/cc-topology/src/node/cc-rect.js @@ -0,0 +1,31 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @description: 矩形节点 + */ + +import base from './base' +import theme from '../theme' + +export default { + name: 'cc-rect', + extendName: 'rect', + options: { + ...base, + getShapeStyle(cfg) { + const size = this.getSize(cfg) || [48, 48] + const width = size[0] + const height = size[1] + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + const style = { + x: 0 - width / 2, + y: 0 - height / 2, + width: width, + height: height, + ...themeStyle.nodeStyle.default + } + return style + } + } +} diff --git a/packages/cc-topology/src/node/index.js b/packages/cc-topology/src/node/index.js new file mode 100644 index 0000000..da8366f --- /dev/null +++ b/packages/cc-topology/src/node/index.js @@ -0,0 +1,23 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @description: register nodes + */ + +import ccRect from './cc-rect' +import ccImage from './cc-image' + +const obj = { + ccRect, + ccImage +} + +export default { + obj, + register(G6) { + Object.values(obj).map(item => { + G6.registerNode(item.name, item.options, item.extendName) + }) + } +} diff --git a/packages/cc-topology/src/plugins/d3-installer.js b/packages/cc-topology/src/plugins/d3-installer.js new file mode 100644 index 0000000..a805344 --- /dev/null +++ b/packages/cc-topology/src/plugins/d3-installer.js @@ -0,0 +1,10 @@ +/** + * @author: winyuan + * @data: 2019/07/05 + * @repository: https://github.com/winyuan + * @description: install 3rd plugins + */ + +import * as d3 from 'd3-force/dist/d3-force' + +export default d3 diff --git a/packages/cc-topology/src/theme/dark-style.js b/packages/cc-topology/src/theme/dark-style.js new file mode 100644 index 0000000..aad3328 --- /dev/null +++ b/packages/cc-topology/src/theme/dark-style.js @@ -0,0 +1,178 @@ +/** + * @author: winyuan + * @data: 2019/11/20 + * @repository: https://github.com/winyuan + * @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/packages/cc-topology/src/theme/default-style.js b/packages/cc-topology/src/theme/default-style.js new file mode 100644 index 0000000..efbb257 --- /dev/null +++ b/packages/cc-topology/src/theme/default-style.js @@ -0,0 +1,220 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @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: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + // shadowColor: '#626262', + // shadowBlur: 8, + // shadowOffsetX: -1, + // shadowOffsetY: 3 + }, + unselected: { + shadowColor: '' + } + }, + // 节点标签样式 + nodeLabelCfg: { + positions: 'center', + style: { + fill: '#000' + } + }, + // 连线样式 + edgeStyle: { + default: { + //修改连线颜色 + stroke: 'red', + //todo 边的宽度 + lineWidth: 2, + strokeOpacity: 0.92, + lineAppendWidth: 10, + endArrow: true, + startArrow: false, + //todo 添加虚线 + // lineDash: [2, 2] + }, + solidline: { + //实线 + stroke: 'blue', + //todo 边的宽度 + lineWidth: 1.5, + strokeOpacity: 0.92, + lineAppendWidth: 10, + endArrow: true, + startArrow: false, + }, + dottedline: { + //虚线 + stroke: 'blue', + //todo 边的宽度 + lineWidth: 1.5, + strokeOpacity: 0.92, + lineAppendWidth: 10, + endArrow: true, + startArrow: false, + //todo 添加虚线 + lineDash: [2, 2] + }, + crudedottedline: { + //粗虚线 + stroke: 'blue', + //todo 边的宽度 + lineWidth: 3, + strokeOpacity: 0.92, + lineAppendWidth: 10, + endArrow: true, + startArrow: false, + //todo 添加虚线 + lineDash: [2, 2] + }, + active: { + shadowColor: 'red', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + inactive: { + shadowColor: '' + }, + selected: { + shadowColor: '#ff240b', + shadowBlur: 4, + shadowOffsetX: 0, + shadowOffsetY: 0 + }, + unselected: { + shadowColor: '', + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0 + } + }, + // 锚点样式 + anchorStyle: { + default: { + r: 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: { + 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/packages/cc-topology/src/theme/index.js b/packages/cc-topology/src/theme/index.js new file mode 100644 index 0000000..352020a --- /dev/null +++ b/packages/cc-topology/src/theme/index.js @@ -0,0 +1,16 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @description: 编辑器主题样式 - 节点、连线的预设样式 + */ + +import defaultStyle from './default-style' +import darkStyle from './dark-style' +import officeStyle from './office-style' + +export default { + defaultStyle, + darkStyle, + officeStyle +} diff --git a/packages/cc-topology/src/theme/office-style.js b/packages/cc-topology/src/theme/office-style.js new file mode 100644 index 0000000..9c08efd --- /dev/null +++ b/packages/cc-topology/src/theme/office-style.js @@ -0,0 +1,178 @@ +/** + * @author: winyuan + * @data: 2019/11/21 + * @repository: https://github.com/winyuan + * @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: 0, + shadowOffsetY: 0 + // 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/packages/cc-topology/src/toolbar-edit.vue b/packages/cc-topology/src/toolbar-edit.vue new file mode 100644 index 0000000..1abcbe2 --- /dev/null +++ b/packages/cc-topology/src/toolbar-edit.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/packages/cc-topology/src/utils/anchor/draw.js b/packages/cc-topology/src/utils/anchor/draw.js new file mode 100644 index 0000000..45d167d --- /dev/null +++ b/packages/cc-topology/src/utils/anchor/draw.js @@ -0,0 +1,60 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @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形状 + 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/packages/cc-topology/src/utils/anchor/index.js b/packages/cc-topology/src/utils/anchor/index.js new file mode 100644 index 0000000..6f8f2a2 --- /dev/null +++ b/packages/cc-topology/src/utils/anchor/index.js @@ -0,0 +1,16 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @description: anchor + */ + +import draw from './draw' +import setState from './set-state' +import update from './update' + +export default { + draw, + setState, + update +} diff --git a/packages/cc-topology/src/utils/anchor/set-state.js b/packages/cc-topology/src/utils/anchor/set-state.js new file mode 100644 index 0000000..6e481d7 --- /dev/null +++ b/packages/cc-topology/src/utils/anchor/set-state.js @@ -0,0 +1,27 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @description: set anchor state + */ + +import theme from '../../theme' + +export default function(name, value, item) { + const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 + 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/packages/cc-topology/src/utils/anchor/update.js b/packages/cc-topology/src/utils/anchor/update.js new file mode 100644 index 0000000..0f43473 --- /dev/null +++ b/packages/cc-topology/src/utils/anchor/update.js @@ -0,0 +1,32 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @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/packages/cc-topology/src/utils/edge/index.js b/packages/cc-topology/src/utils/edge/index.js new file mode 100644 index 0000000..690f906 --- /dev/null +++ b/packages/cc-topology/src/utils/edge/index.js @@ -0,0 +1,12 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @description: edge + */ + +import setState from './set-state' + +export default { + setState +} diff --git a/packages/cc-topology/src/utils/edge/set-state.js b/packages/cc-topology/src/utils/edge/set-state.js new file mode 100644 index 0000000..3bb60b7 --- /dev/null +++ b/packages/cc-topology/src/utils/edge/set-state.js @@ -0,0 +1,27 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @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/packages/cc-topology/src/utils/index.js b/packages/cc-topology/src/utils/index.js new file mode 100644 index 0000000..36fb8f1 --- /dev/null +++ b/packages/cc-topology/src/utils/index.js @@ -0,0 +1,65 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @description: graph utils + */ + +import node from './node' +import anchor from './anchor' +import edge from './edge' + +/** + * 比较两个对象的内容是否相同(两个对象的键值都相同) + * @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, + // 通用工具类函数 + isObjectValueEqual, + generateUUID +} diff --git a/packages/cc-topology/src/utils/node/index.js b/packages/cc-topology/src/utils/node/index.js new file mode 100644 index 0000000..95fa208 --- /dev/null +++ b/packages/cc-topology/src/utils/node/index.js @@ -0,0 +1,12 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @description: node + */ + +import setState from './set-state' + +export default { + setState +} diff --git a/packages/cc-topology/src/utils/node/set-state.js b/packages/cc-topology/src/utils/node/set-state.js new file mode 100644 index 0000000..4503f47 --- /dev/null +++ b/packages/cc-topology/src/utils/node/set-state.js @@ -0,0 +1,27 @@ +/** + * @author: winyuan + * @data: 2019/08/15 + * @repository: https://github.com/winyuan + * @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) { + shape.attr(themeStyle.nodeStyle.selected) + } else { + shape.attr(themeStyle.nodeStyle.default) + } + } +} diff --git a/packages/index.js b/packages/index.js new file mode 100644 index 0000000..c0c4ce4 --- /dev/null +++ b/packages/index.js @@ -0,0 +1,40 @@ +/** + * @author: winyuan + * @data: 2019/08/20 + * @repository: https://github.com/winyuan + * @description: 整合所有的组件,对外导出,即一个完整的组件库 + */ + +import CCTopology from './cc-topology' + +// 存储组件列表 +const components = [ + CCTopology +] + +// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册 +const install = function(Vue) { + // 判断是否安装 + if (install.installed) return + // 遍历注册全局组件 + console.info('install----CCEditor: All----') + components.map(component => Vue.component(component.name, component)) +} + +// 判断是否是直接引入文件 +if (typeof window !== 'undefined' && window.Vue) { + install(window.Vue) +} + +export default { + // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 + install, + // 以下是具体的组件列表 + CCTopology +} + +export { + install, + // 以下是具体的组件列表 + CCTopology +} diff --git a/packages/top/Review.vue b/packages/top/Review.vue new file mode 100644 index 0000000..e4412dd --- /dev/null +++ b/packages/top/Review.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/top/ViewTop.vue b/packages/top/ViewTop.vue new file mode 100644 index 0000000..5378759 --- /dev/null +++ b/packages/top/ViewTop.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/top/index.vue b/packages/top/index.vue new file mode 100644 index 0000000..4b4e2cc --- /dev/null +++ b/packages/top/index.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/packages/top/topology.vue b/packages/top/topology.vue new file mode 100644 index 0000000..0f66c1b --- /dev/null +++ b/packages/top/topology.vue @@ -0,0 +1,176 @@ + + + + + + +