├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── README.md ├── babel.config.js ├── examples ├── App.vue ├── assets │ ├── iconfont │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── images │ │ ├── 404-images │ │ │ ├── 404-cloud.png │ │ │ └── 404.png │ │ ├── blue.svg │ │ ├── client.png │ │ ├── database.png │ │ ├── firewall.png │ │ ├── green.svg │ │ ├── purple.svg │ │ ├── server.png │ │ ├── topology.gif │ │ └── topology.png │ └── logo.png ├── components │ ├── FooterBar.vue │ ├── HeaderBar.vue │ └── HelloWorld.vue ├── main.js ├── router.js ├── store.js ├── styles │ ├── common.scss │ └── index.scss ├── utils │ └── index.js └── views │ ├── demos │ └── topology.vue │ ├── error-pages │ └── 404.vue │ └── pages │ ├── about.vue │ ├── home.vue │ ├── index.vue │ ├── mobile.vue │ └── test.vue ├── package-lock.json ├── package.json ├── packages ├── assets │ ├── iconfont │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── images │ │ ├── client.png │ │ ├── database.png │ │ ├── firewall.png │ │ └── server.png │ └── logo.png ├── cc-elements │ ├── button.vue │ ├── checkbox.vue │ ├── dropdown.vue │ ├── index.js │ └── loading.vue ├── cc-topology │ ├── index.js │ └── src │ │ ├── behavior │ │ ├── click-add-edge.js │ │ ├── click-event-edit.js │ │ ├── drag-add-edge.js │ │ ├── drag-event-edit.js │ │ ├── hover-event-edit.js │ │ ├── index.js │ │ └── keyup-event-edit.js │ │ ├── cc-topology.vue │ │ ├── config │ │ ├── edge.js │ │ └── index.js │ │ ├── edge │ │ ├── base.js │ │ ├── cc-brokenline.js │ │ ├── cc-cubic.js │ │ ├── cc-line.js │ │ ├── cc-polyline.js │ │ ├── index.js │ │ └── polyline-finding.js │ │ ├── graph │ │ └── index.js │ │ ├── node │ │ ├── base.js │ │ ├── cc-image.js │ │ ├── cc-rect.js │ │ └── index.js │ │ ├── plugins │ │ └── d3-installer.js │ │ ├── theme │ │ ├── dark-style.js │ │ ├── default-style.js │ │ ├── index.js │ │ └── office-style.js │ │ ├── toolbar-edit.vue │ │ ├── toolbar-preview.vue │ │ └── utils │ │ ├── anchor │ │ ├── draw.js │ │ ├── index.js │ │ ├── set-state.js │ │ └── update.js │ │ ├── edge │ │ ├── index.js │ │ └── set-state.js │ │ ├── index.js │ │ └── node │ │ ├── index.js │ │ └── set-state.js └── index.js ├── postcss.config.js ├── prettier.config.js ├── public ├── cceditor.png ├── favicon.ico └── index.html └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "@vue/prettier"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 10 | // 强制使用单引号 11 | 'quotes': ['error', 'single'], 12 | // 关闭行末分号提示/报错 13 | 'semi': 0 14 | }, 15 | parserOptions: { 16 | parser: "babel-eslint" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # 忽略目录 2 | .idea/ 3 | examples/ 4 | packages/ 5 | 6 | # 忽略指定文件 7 | vue.config.js 8 | babel.config.js 9 | *.map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CCEditor 2 | 3 |

4 | cceditor 5 |

6 | 7 |

8 | vue 9 | g6 10 | license 11 | release 12 | 13 | sponsor 14 | 15 |

16 | 17 | > A visual graph editor component library based on G6 and Vue.js.
18 | > 基于 G6 和 Vue 的可视化图形编辑组件库。 19 | 20 | ## 一、 版本 21 | 22 | | 开发版本 | 23 | | :-------- | 24 | | [v0.1.0](https://github.com/wenyuan/cceditor/tree/master) | 25 | 26 | ## 二、预览 27 | 28 | ![](./docs/preview.png) 29 | 30 | ## 三、项目依赖 31 | 32 | * [Vue.js(2.x)](https://cn.vuejs.org/) 33 | * [Element UI(2.x)](https://element.eleme.cn/2.0/#/zh-CN) 34 | * [AntV G6(4.x)](http://antv.alipay.com/zh-cn/index.html) 35 | 36 | ## 四、项目结构 37 | 38 | ``` 39 | . 40 | ├── packages // 源码 41 | ├── examples // 用例(开发调试用) 42 | ├── 其它 43 | | 44 | ``` 45 | 46 | ## 五、开发方式 47 | 48 | ### 1. 调试 49 | 50 | ```bash 51 | npm install 52 | npm run serve 53 | ``` 54 | 55 | ### 2. 发布 56 | 57 | ```bash 58 | npm run lib 59 | npm pack 60 | ``` 61 | 62 | ## 六、使用方式 63 | 64 | ### 1. 安装CCEditor 65 | 66 | ```bash 67 | # 本地安装 68 | npm install chaincloud-cceditor-0.1.0.tgz 69 | 70 | # npm安装(暂不支持) 71 | npm install @chaincloud/cceditor --save 72 | ``` 73 | 本地安装:[安装包下载](https://github.com/wenyuan/cceditor/releases) 74 | 75 | ### 2. 引入CCEditor 76 | 77 | #### 全局注册 78 | 79 | 在 `main.js` 中写入以下内容: 80 | * 完整引入:引入整个CCEditor: 81 | 82 | ```javascript 83 | import Vue from 'vue' 84 | import router from './router' 85 | import store from './store' 86 | import ElementUI from 'element-ui' 87 | 88 | import App from './App.vue' 89 | 90 | Vue.use(ElementUI) 91 | 92 | /* 全局注册:引入整个CCEditor */ 93 | import CCEditor from '@chaincloud/cceditor' 94 | Vue.use(CCEditor) 95 | 96 | Vue.config.productionTip = false 97 | 98 | new Vue({ 99 | router, 100 | store, 101 | render: h => h(App) 102 | }).$mount('#app') 103 | ``` 104 | * 按需引入:引入需要的模块,例如CCTopology: 105 | 106 | ```javascript 107 | import Vue from 'vue' 108 | import router from './router' 109 | import store from './store' 110 | import ElementUI from 'element-ui' 111 | 112 | import App from './App.vue' 113 | 114 | Vue.use(ElementUI) 115 | 116 | /* 全局注册:只加载CCTopology */ 117 | import { CCTopology } from '@chaincloud/cceditor' 118 | Vue.use(CCTopology) 119 | 120 | Vue.config.productionTip = false 121 | 122 | new Vue({ 123 | router, 124 | store, 125 | render: h => h(App) 126 | }).$mount('#app') 127 | ``` 128 | 129 | #### 局部注册 130 | 131 | 在 `ComponentA.vue` 中进行局部注册: 132 | ```vue 133 | 143 | ``` 144 | 145 | ### 3. 开始使用 146 | 147 | #### 属性 148 | 149 | TODO... 150 | 151 | #### 方法 152 | 153 | TODO... 154 | 155 | ## 七、备注 156 | 157 | * **\[已解决]** 升级 G6 到 3.5.0 后,发现 G6 底层有 [bug](https://github.com/antvis/G6/issues/1613):调用addItem方法报错:`p.getType is not a function`。该 bug 导致编辑(连线)功能不可用,需等待下个小版本的修复。—— 2020.06.02 158 | * **\[已解决]** 由于 G6 在 3.3.x 版本作了 breaking change,且遗留了一些 bug。本项目先暂停更新。预计 6 月份或 G6 底层这些重要 bug 修复后,继续跟进。—— 2020.03.01 159 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /examples/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1570676544597'); /* IE9 */ 3 | src: url('iconfont.eot?t=1570676544597#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAU8AAsAAAAACfQAAATtAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDMgqHAIV5ATYCJAMUCwwABCAFhG0HXBuZCBHVo4GR/UyIN14+uxup00M/BAkNRvqeiYev/V7P3d0HIVSgopKvMvGANiwskVBlMr5CVqiOZwkkdH76TX3HkYU1NZfQUtFZZCYSNiebE6ZmB3Quf2YJgP3fFum0lIWCRl6Hfta/7IsBY2dHl6YpTQNWx87ispfSqvVGnQIYCnAsVkr5WjWg/8Dy7v8Qb3WZxwl0m9IRqxV1BmAU+rhAnPbp1QWYhFGpYIZWUxcsLRCvQKNNs6gbAF56vx9/IT0YkFQZ9KmbPcu7g/5tzVuCLfZb0N4hgGg4O9hVZHRQiOtC5yngINyRdctB2iKAEmwlaahOF99ef0vsdgDphtTdxj88kpAVogZd2YvURUU5OJGFkfhxESP4cR0j84OIH4yywtHdqIkpEE8JdcKRBF5YL1dFs+w48togvzGq78zK29EbJB/E7/JWVTOvxC9W03liuCZUKQrskb22EJBlo7jDd4NSyp/KljbELT6aJMpmsInRimJqjfcRIATzqs6wygVgaTQIkoSM50Z8saoivuV+0yayxfOSwEnblEhivAMW2bglkJQqVeLGo15Btq1HL+aAd1AseCsPtR6Me7vRdrMt9kbkns+qbQ7bNntt3Ak7rTGrY+ibrS+R1oVIF+sPW7NuX4aVazxPhhFisK30M1hsPrPHyxMIGbdf8XZe3HOTIpBISTGJJE0iRlk2CzuCxCvZx04h6VamsDQvaeteuRIs4gZR52SVNi12+qpdDrJqVpVQPKmRDg2lGw2EuC2Nro333wy3Xx6FFz0y0zO6YSHOnx1TreU8MKutSDpjOQPI/xbsoeWqx/izcViY0c2k2QI5p0bIzH05/ZGLrslx0VxND2eDgz1cyjj2m9qkuefrfV9jIAcFKIEyXWBU7+YnZ8482fyOiVwU/t29N59zWJLBjNDSCPFF24Z6DJncwTypxL9iL+v95crqefM2n6v0PxtQ6aUfZO4wiB/m+XiJwdO9uJ1fz+DcuXKtlVpS1l2Dw1tqmy/OPMXALQDn3xAvXhgV++KGtrS5rq/nncuoc8/Q+8/xUS6uRxCH+zVQ/Z1HVxrfo4rJf3v+nZxNve+R0zuY6t+/nqO3vNTBEXPixejYRRcQjzc8e7gZb8FPH0Lm3PEW3m3DWdwijfrcV6ND3WcKbCwOz7kYVp830GWoT5/59XpEHcbU5NrUEspzbj3OYPVJto5BJwaiNofjmDbauM1t0PGIw5Qeza/36eMyNG8g9AbAjYWGfMsbvYBGeDQA+R6u+xeH2Zb+3OnkUviN0X2Dlk8XzugR70ZCwh9KpbHozyuhgRs7HZxEN1VFqrSEkruyOYAyyfpuqgZ077Dvw6jHGmcTWmMRJI1JyFrTxELtQKXHPNRaC9Bt1sbqHiOuAYvSFJhxn0AY9B6Sfh8gG/SRWKjfoDLuP9QGA0O3feSxZQ87OvyljFAwapB/8GopxrTwxc+9oX0nQXGJx3oh7d4HfdOlYzMWpCFm7B87MBswtGSYyGmY0gIrLQEVN455HdvWFL1Ro5ZcXZQRCkYN5B94tRTjNOdL5udvaN9JUEVVmf+FtPv2Qa/RNSDOytKo6lJ67x87YDbUKENLBhPYMRnhBazFgwIqbrgOwXXUYiPTVNlMr843UMDWod9HExTCiEYa1AScDjy7t6yVJ5XwkFBfc+R0zJIoRQuKVQUAAAA=') format('woff2'), 5 | url('iconfont.woff?t=1570676544597') format('woff'), 6 | url('iconfont.ttf?t=1570676544597') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1570676544597#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-github-circle:before { 19 | content: "\e6c5"; 20 | } 21 | 22 | .icon-github:before { 23 | content: "\e64a"; 24 | } 25 | 26 | .icon-github-rect:before { 27 | content: "\e6cb"; 28 | } 29 | 30 | .icon-github-dark:before { 31 | content: "\e6d4"; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.eot -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.js: -------------------------------------------------------------------------------- 1 | !function(d){var t,n='',e=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(e&&!d.__iconfont__svg__cssinject__){d.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}!function(t){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(t,0);else{var e=function(){document.removeEventListener("DOMContentLoaded",e,!1),t()};document.addEventListener("DOMContentLoaded",e,!1)}else document.attachEvent&&(o=t,i=d.document,c=!1,(a=function(){try{i.documentElement.doScroll("left")}catch(t){return void setTimeout(a,50)}n()})(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,n())});function n(){c||(c=!0,o())}var o,i,c,a}(function(){var t,e;(t=document.createElement("div")).innerHTML=n,n=null,(e=t.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",function(t,e){e.firstChild?function(t,e){e.parentNode.insertBefore(t,e)}(t,e.firstChild):e.appendChild(t)}(e,document.body))})}(window); -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /examples/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /examples/assets/images/404-images/404-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/404-images/404-cloud.png -------------------------------------------------------------------------------- /examples/assets/images/404-images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/404-images/404.png -------------------------------------------------------------------------------- /examples/assets/images/blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 16 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/assets/images/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/client.png -------------------------------------------------------------------------------- /examples/assets/images/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/database.png -------------------------------------------------------------------------------- /examples/assets/images/firewall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/firewall.png -------------------------------------------------------------------------------- /examples/assets/images/green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 17 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/assets/images/purple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 18 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/assets/images/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/server.png -------------------------------------------------------------------------------- /examples/assets/images/topology.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/topology.gif -------------------------------------------------------------------------------- /examples/assets/images/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/topology.png -------------------------------------------------------------------------------- /examples/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/logo.png -------------------------------------------------------------------------------- /examples/components/FooterBar.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 52 | 53 | 98 | -------------------------------------------------------------------------------- /examples/components/HeaderBar.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 56 | 57 | 96 | -------------------------------------------------------------------------------- /examples/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 97 | 98 | 99 | 118 | -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import router from './router' 3 | import store from './store' 4 | import ElementUI from 'element-ui' 5 | 6 | import '@/styles/index.scss' // global css 7 | import '@/styles/common.scss' // common css 8 | 9 | import App from './App.vue' 10 | 11 | Vue.use(ElementUI) 12 | 13 | /* 全局注册 */ 14 | 15 | /* 统一加载: prod */ 16 | // import CCEditor from '@chaincloud/cceditor' 17 | // Vue.use(CCEditor) 18 | 19 | /* 只加载CCTopology: prod */ 20 | // import { CCTopology } from '@chaincloud/cceditor' 21 | // Vue.use(CCTopology) 22 | 23 | /* 统一加载: dev */ 24 | // import CCEditor from '../packages/index' 25 | // Vue.use(CCEditor) 26 | 27 | /* 只加载CCTopology: dev */ 28 | // import { CCTopology } from '../packages/index'; 29 | // Vue.use(CCTopology); 30 | 31 | Vue.config.productionTip = false 32 | 33 | new Vue({ 34 | router, 35 | store, 36 | render: h => h(App) 37 | }).$mount('#app') 38 | -------------------------------------------------------------------------------- /examples/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | const router = new Router({ 7 | mode: 'history', 8 | base: process.env.BASE_URL, 9 | routes: [ 10 | { 11 | path: '/', 12 | component: () => 13 | import('@/views/pages/index.vue'), 14 | redirect: '/home', 15 | children: [ 16 | { 17 | path: 'home', 18 | name: 'Home', 19 | component: () => 20 | import(/* webpackChunkName: "home" */ '@/views/pages/home.vue') 21 | }, 22 | { 23 | path: 'about', 24 | name: 'About', 25 | // route level code-splitting 26 | // this generates a separate chunk (about.[hash].js) for this route 27 | // which is lazy-loaded when the route is visited. 28 | component: () => 29 | import(/* webpackChunkName: "about" */ '@/views/pages/about.vue') 30 | } 31 | ] 32 | }, 33 | { 34 | path: '/topology', 35 | name: 'Topology', 36 | component: () => 37 | import(/* webpackChunkName: "topology" */ '@/views/demos/topology.vue') 38 | }, 39 | { 40 | path: '/mobile', 41 | name: 'Mobile', 42 | component: () => 43 | import(/* webpackChunkName: "mobile" */ '@/views/pages/mobile.vue') 44 | }, 45 | { 46 | path: '*', 47 | component: () => 48 | import(/* webpackChunkName: "home" */ '@/views/error-pages/404.vue') 49 | } 50 | ] 51 | }) 52 | 53 | /** 54 | * 验证 55 | */ 56 | router.beforeEach((to, from, next) => { 57 | if (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) { 58 | if (to.path === '/mobile') { 59 | next() 60 | } else { 61 | next('/mobile') 62 | } 63 | } else { 64 | if (to.path === '/mobile') { 65 | next('/home') 66 | } else { 67 | next() 68 | } 69 | } 70 | }) 71 | 72 | export default router 73 | -------------------------------------------------------------------------------- /examples/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: {}, 8 | mutations: {}, 9 | actions: {} 10 | }) 11 | -------------------------------------------------------------------------------- /examples/styles/common.scss: -------------------------------------------------------------------------------- 1 | .pull-left { 2 | float: left; 3 | } 4 | 5 | .pull-right { 6 | float: right; 7 | } -------------------------------------------------------------------------------- /examples/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../node_modules/element-ui/lib/theme-chalk/index.css'; 2 | @import '../assets/iconfont/iconfont.css'; 3 | 4 | /* global css */ 5 | body { 6 | height: 100%; 7 | -moz-osx-font-smoothing: grayscale; 8 | -webkit-font-smoothing: antialiased; 9 | text-rendering: optimizeLegibility; 10 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 11 | } 12 | 13 | html { 14 | height: 100%; 15 | box-sizing: border-box; 16 | } 17 | 18 | #app { 19 | height: 100%; 20 | /*overflow-x: hidden;*/ 21 | /*overflow-y: hidden;*/ 22 | } 23 | 24 | * { 25 | padding: 0; 26 | margin: 0; 27 | } 28 | 29 | *, 30 | *:before, 31 | *:after { 32 | box-sizing: inherit; 33 | } 34 | 35 | a:focus, 36 | a:active { 37 | outline: none; 38 | } 39 | 40 | a, 41 | a:focus, 42 | a:hover { 43 | cursor: pointer; 44 | color: inherit; 45 | text-decoration: none; 46 | } 47 | 48 | div:focus { 49 | outline: none; 50 | } 51 | 52 | .clearfix { 53 | &:after { 54 | visibility: hidden; 55 | display: block; 56 | font-size: 0; 57 | content: " "; 58 | clear: both; 59 | height: 0; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wenyuan on 2019/10/14 3 | * Description: common utils 4 | */ 5 | 6 | /** 7 | * This is just a simple version of deep copy 8 | * Has a lot of edge cases bug 9 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep 10 | * @param {Object} source 11 | * @returns {Object} targetObj 12 | */ 13 | export function deepClone(source) { 14 | if (!source && typeof source !== 'object') { 15 | throw new Error('error arguments: deepClone') 16 | } 17 | let targetObj = source.constructor === Array ? [] : {} 18 | Object.keys(source).forEach(key => { 19 | if (source[key] && typeof source[key] === 'object') { 20 | targetObj[key] = deepClone(source[key]) 21 | } else { 22 | targetObj[key] = source[key] 23 | } 24 | }) 25 | return targetObj 26 | } 27 | 28 | /** 29 | * Randomly extract one or more elements from an array 30 | * If you want to use a perfect solution, use lodash's _.sample or _.sampleSize 31 | * @param {Array} arr 32 | * @param {number} count 33 | * @returns {Array} arr 34 | */ 35 | export function getRandomArrayElements(arr, count = 1) { 36 | if (count > arr.length) { 37 | throw new Error('error arguments: count is greater than length of array') 38 | } 39 | let shuffled = arr.slice(0), i = arr.length, min = i - count, temp, index 40 | while (i-- > min) { 41 | index = Math.floor((i + 1) * Math.random()) 42 | temp = shuffled[index] 43 | shuffled[index] = shuffled[i] 44 | shuffled[i] = temp 45 | } 46 | return shuffled.slice(min) 47 | } 48 | -------------------------------------------------------------------------------- /examples/views/error-pages/404.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 49 | 50 | 271 | -------------------------------------------------------------------------------- /examples/views/pages/about.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 32 | 33 | 65 | 66 | 67 | 86 | -------------------------------------------------------------------------------- /examples/views/pages/home.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 50 | 51 | 82 | -------------------------------------------------------------------------------- /examples/views/pages/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 60 | -------------------------------------------------------------------------------- /examples/views/pages/mobile.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 83 | 84 | 172 | -------------------------------------------------------------------------------- /examples/views/pages/test.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 144 | 145 | 180 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chaincloud/cceditor", 3 | "version": "0.1.0", 4 | "description": "A visual graph editor based on G6 and Vue.js.", 5 | "author": "wenyuan ", 6 | "main": "lib/cceditor.common.js", 7 | "keywords": [ 8 | "vue", 9 | "graph", 10 | "graph editor", 11 | "topo", 12 | "topology" 13 | ], 14 | "license": "MIT", 15 | "private": true, 16 | "scripts": { 17 | "serve": "vue-cli-service serve", 18 | "build": "vue-cli-service build", 19 | "lint": "vue-cli-service lint", 20 | "lib": "vue-cli-service build --target lib --name cceditor --dest lib packages/index.js" 21 | }, 22 | "dependencies": { 23 | "@antv/g6": "^4.5.0", 24 | "core-js": "^2.6.5", 25 | "element-ui": "^2.14.1", 26 | "lodash": "^4.17.15", 27 | "moment": "^2.27.0", 28 | "vue": "^2.6.10", 29 | "vue-router": "^3.0.3", 30 | "vuex": "^3.0.1" 31 | }, 32 | "devDependencies": { 33 | "@vue/cli-plugin-babel": "^3.9.0", 34 | "@vue/cli-plugin-eslint": "^3.9.0", 35 | "@vue/cli-service": "^3.9.0", 36 | "@vue/eslint-config-prettier": "^4.0.1", 37 | "babel-eslint": "^10.0.1", 38 | "eslint": "^5.16.0", 39 | "eslint-plugin-vue": "^5.0.0", 40 | "sass": "^1.18.0", 41 | "sass-loader": "^7.1.0", 42 | "vue-template-compiler": "^2.6.10" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1568972645985'); /* IE9 */ 3 | src: url('iconfont.eot?t=1568972645985#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1568972645985') format('woff'), 6 | url('iconfont.ttf?t=1568972645985') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1568972645985#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-download:before { 19 | content: "\e68d"; 20 | } 21 | 22 | .icon-zoom-out:before { 23 | content: "\e69c"; 24 | } 25 | 26 | .icon-image:before { 27 | content: "\e752"; 28 | } 29 | 30 | .icon-iconedit:before { 31 | content: "\e650"; 32 | } 33 | 34 | .icon-actualsize:before { 35 | content: "\e665"; 36 | } 37 | 38 | .icon-copy:before { 39 | content: "\e61c"; 40 | } 41 | 42 | .icon-zoom-in:before { 43 | content: "\e600"; 44 | } 45 | 46 | .icon-clear:before { 47 | content: "\e700"; 48 | } 49 | 50 | .icon-flow-line:before { 51 | content: "\e660"; 52 | } 53 | 54 | .icon-redo:before { 55 | content: "\e716"; 56 | } 57 | 58 | .icon-undo:before { 59 | content: "\e71a"; 60 | } 61 | 62 | .icon-fit:before { 63 | content: "\e7cb"; 64 | } 65 | 66 | .icon-to-front:before { 67 | content: "\e7cc"; 68 | } 69 | 70 | .icon-to-back:before { 71 | content: "\e7cd"; 72 | } 73 | 74 | .icon-roi-select:before { 75 | content: "\e7ce"; 76 | } 77 | 78 | .icon-json:before { 79 | content: "\e623"; 80 | } 81 | 82 | .icon-fullscreen:before { 83 | content: "\e648"; 84 | } 85 | 86 | .icon-broken:before { 87 | content: "\e9ad"; 88 | } 89 | 90 | .icon-curve:before { 91 | content: "\e9b0"; 92 | } 93 | 94 | .icon-paste:before { 95 | content: "\e963"; 96 | } 97 | 98 | .icon-group:before { 99 | content: "\e915"; 100 | } 101 | 102 | .icon-ungroup:before { 103 | content: "\e917"; 104 | } 105 | 106 | .icon-arrow-dropdown:before { 107 | content: "\e601"; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.eot -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.js: -------------------------------------------------------------------------------- 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); -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /packages/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /packages/assets/images/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/client.png -------------------------------------------------------------------------------- /packages/assets/images/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/database.png -------------------------------------------------------------------------------- /packages/assets/images/firewall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/firewall.png -------------------------------------------------------------------------------- /packages/assets/images/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/server.png -------------------------------------------------------------------------------- /packages/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/logo.png -------------------------------------------------------------------------------- /packages/cc-elements/button.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 55 | 56 | 94 | -------------------------------------------------------------------------------- /packages/cc-elements/checkbox.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 80 | -------------------------------------------------------------------------------- /packages/cc-elements/dropdown.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 72 | 73 | 185 | -------------------------------------------------------------------------------- /packages/cc-elements/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/11/14 4 | * @repository: https://github.com/wenyuan 5 | * @description: cceditor内部的通用组件 6 | */ 7 | 8 | import Checkbox from './checkbox' 9 | import Button from './button' 10 | import Dropdown from './dropdown' 11 | import Loading from './loading' 12 | 13 | export { 14 | Checkbox, 15 | Button, 16 | Dropdown, 17 | Loading 18 | } 19 | -------------------------------------------------------------------------------- /packages/cc-elements/loading.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 93 | -------------------------------------------------------------------------------- /packages/cc-topology/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/20 4 | * @repository: https://github.com/wenyuan 5 | * @description: 导入组件,组件必须声明 name 6 | */ 7 | 8 | import CCTopology from './src/cc-topology' 9 | 10 | // 为组件提供 install 安装方法,供按需引入 11 | CCTopology.install = function(Vue) { 12 | console.info('install----CCEditor: CCTopology----') 13 | Vue.component(CCTopology.name, CCTopology) 14 | } 15 | 16 | // 默认导出组件 17 | export default CCTopology 18 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/click-add-edge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: edit mode: 通过先后点击两个节点来添加连线(容易和节点点击动作交叉,已弃用) 6 | */ 7 | 8 | import G6 from '@antv/g6' 9 | import theme from '../theme' 10 | 11 | export default { 12 | name: 'click-add-edge', 13 | options: { 14 | getEvents() { 15 | return { 16 | 'node:click': 'onNodeClick', 17 | 'canvas:mousemove': 'onMousemove', 18 | 'edge:click': 'onEdgeClick' // 点击空白处,取消边 19 | } 20 | }, 21 | onNodeClick(event) { 22 | let graph = this.graph 23 | let node = event.item 24 | let point = { x: event.x, y: event.y } 25 | let model = node.getModel() 26 | let edgeShape = self.currentEdgeShape.guid || 'line' 27 | if (this.addingEdge && this.edge) { 28 | // 点击第二个节点 29 | graph.updateItem(this.edge, { 30 | target: model.id 31 | }) 32 | this.edge = null 33 | this.addingEdge = false 34 | // 记录【连线】前后的数据状态 35 | if (this.historyData) { 36 | let graph = this.graph 37 | // 如果当前点过【撤销】了,连线后没有【重做】功能 38 | // 重置undoCount,连线后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录 39 | if (self.undoCount > 0) { 40 | self.historyIndex = self.historyIndex - self.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 41 | for (let i = 1; i <= self.undoCount; i++) { 42 | let key = `graph_history_${self.historyIndex + i}` 43 | self.removeHistoryData(key) 44 | } 45 | self.undoCount = 0 46 | } else { 47 | // 正常顺序执行的情况,记录【连线】前的数据状态 48 | let key = `graph_history_${self.historyIndex}` 49 | self.addHistoryData(key, this.historyData) 50 | } 51 | // 记录【连线】后的数据状态 52 | self.historyIndex += 1 53 | let key = `graph_history_${self.historyIndex}` 54 | let currentData = JSON.stringify(graph.save()) 55 | self.addHistoryData(key, currentData) 56 | } 57 | } else { 58 | // 点击第一个节点 59 | this.historyData = JSON.stringify(graph.save()) 60 | if (edgeShape === 'stepline') { 61 | this.edge = graph.addItem('edge', { 62 | source: model.id, 63 | target: point, 64 | type: edgeShape, 65 | controlPoints: [{ x: 100, y: 70 }] 66 | }) 67 | } else { 68 | this.edge = graph.addItem('edge', { 69 | source: model.id, 70 | target: point, 71 | type: edgeShape 72 | }) 73 | } 74 | this.addingEdge = true 75 | } 76 | }, 77 | onMousemove(event) { 78 | const point = { x: event.x, y: event.y } 79 | if (this.addingEdge && this.edge) { 80 | this.graph.updateItem(this.edge, { 81 | target: point 82 | }) 83 | } 84 | }, 85 | onEdgeClick(ev) { 86 | let graph = this.graph 87 | const currentEdge = ev.item 88 | // 拖拽过程中,点击会点击到新增的边上 89 | if (this.addingEdge && this.edge === currentEdge) { 90 | graph.removeItem(this.edge) 91 | this.edge = null 92 | this.addingEdge = false 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/click-event-edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: edit mode: 鼠标点击交互 6 | */ 7 | 8 | // 用来获取调用此js的vue组件实例(this) 9 | let vm = null 10 | 11 | const sendThis = (_this) => { 12 | vm = _this 13 | } 14 | 15 | export default { 16 | sendThis, // 暴露函数 17 | name: 'click-event-edit', 18 | options: { 19 | getEvents () { 20 | return { 21 | 'node:click': 'onNodeClick', 22 | 'node:contextmenu': 'onNodeRightClick', 23 | 'edge:click': 'onEdgeClick', 24 | 'edge:contextmenu': 'onEdgeRightClick', 25 | 'combo:click': 'onComboClick', 26 | 'combo:contextmenu': 'onComboRightClick', 27 | 'canvas:click': 'onCanvasClick' 28 | } 29 | }, 30 | onNodeClick (event) { 31 | // todo..."selected"是g6自带的状态,在"drag-add-edge"中的"node:mouseup"事件也会触发,故此处不需要设置"selected"状态 32 | // let clickNode = event.item; 33 | // clickNode.setState('selected', !clickNode.hasState('selected')); 34 | vm.currentFocus = 'node' 35 | vm.rightMenuShow = false 36 | this.updateVmData(event) 37 | }, 38 | onNodeRightClick (event) { 39 | let graph = vm.graph 40 | let clickNode = event.item 41 | let clickNodeModel = clickNode.getModel() 42 | let selectedNodes = graph.findAllByState('node', 'selected') 43 | let selectedNodeIds = selectedNodes.map(item => { 44 | return item.getModel().id 45 | }) 46 | vm.selectedNode = clickNode 47 | // 如果当前点击节点是之前选中的某个节点,就进行下面的处理 48 | if (selectedNodes.length > 1 && selectedNodeIds.indexOf(clickNodeModel.id) > -1) { 49 | vm.rightMenuShow = true 50 | let rightMenu = vm.$refs.rightMenu 51 | rightMenu.style.left = event.clientX + 'px' 52 | rightMenu.style.top = event.clientY + 'px' 53 | } else { 54 | // 隐藏右键菜单 55 | vm.rightMenuShow = false 56 | // 先取消所有节点的选中状态 57 | selectedNodes.forEach(node => { 58 | node.setState('selected', false) 59 | }) 60 | // 再添加该节点的选中状态 61 | clickNode.setState('selected', true) 62 | vm.currentFocus = 'node' 63 | this.updateVmData(event) 64 | } 65 | graph.paint() 66 | }, 67 | onEdgeClick (event) { 68 | let clickEdge = event.item 69 | clickEdge.setState('selected', !clickEdge.hasState('selected')) 70 | vm.currentFocus = 'edge' 71 | this.updateVmData(event) 72 | }, 73 | onEdgeRightClick (event) { 74 | let graph = vm.graph 75 | let clickEdge = event.item 76 | let clickEdgeModel = clickEdge.getModel() 77 | let selectedEdges = graph.findAllByState('edge', 'selected') 78 | // 如果当前点击节点不是之前选中的单个节点,才进行下面的处理 79 | if (!(selectedEdges.length === 1 && clickEdgeModel.id === selectedEdges[0].getModel().id)) { 80 | // 先取消所有节点的选中状态 81 | graph.findAllByState('edge', 'selected').forEach(edge => { 82 | edge.setState('selected', false) 83 | }) 84 | // 再添加该节点的选中状态 85 | clickEdge.setState('selected', true) 86 | vm.currentFocus = 'edge' 87 | this.updateVmData(event) 88 | } 89 | // let point = { x: event.x, y: event.y } 90 | }, 91 | onComboClick (event) { 92 | vm.currentFocus = 'combo' 93 | this.updateVmData(event) 94 | }, 95 | onComboRightClick (event) { 96 | vm.rightMenuShow = true 97 | let rightMenu = vm.$refs.rightMenu 98 | rightMenu.style.left = event.clientX + 'px' 99 | rightMenu.style.top = event.clientY + 'px' 100 | vm.currentFocus = 'combo' 101 | this.updateVmData(event) 102 | }, 103 | onCanvasClick () { 104 | vm.currentFocus = 'canvas' 105 | vm.rightMenuShow = false 106 | }, 107 | updateVmData (event) { 108 | if (event.item._cfg.type === 'node') { 109 | // 更新vm的data: selectedNode 和 selectedNodeParams 110 | let clickNode = event.item 111 | if (clickNode.hasState('selected')) { 112 | let clickNodeModel = clickNode.getModel() 113 | vm.selectedNode = clickNode 114 | let nodeAppConfig = { ...vm.nodeAppConfig } 115 | Object.keys(nodeAppConfig).forEach(function (key) { 116 | nodeAppConfig[key] = '' 117 | }) 118 | vm.selectedNodeParams = { 119 | label: clickNodeModel.label || '', 120 | appConfig: { ...nodeAppConfig, ...clickNodeModel.appConfig } 121 | } 122 | } 123 | } else if (event.item._cfg.type === 'edge') { 124 | // 更新vm的data: selectedEdge 和 selectedEdgeParams 125 | let clickEdge = event.item 126 | if (clickEdge.hasState('selected')) { 127 | let clickEdgeModel = clickEdge.getModel() 128 | vm.selectedEdge = clickEdge 129 | let edgeAppConfig = { ...vm.edgeAppConfig } 130 | Object.keys(edgeAppConfig).forEach(function (key) { 131 | edgeAppConfig[key] = '' 132 | }) 133 | vm.selectedEdgeParams = { 134 | label: clickEdgeModel.label || '', 135 | appConfig: { ...edgeAppConfig, ...clickEdgeModel.appConfig } 136 | } 137 | } 138 | } else if (event.item._cfg.type === 'combo') { 139 | // 更新vm的data: selectedCombo 和 selectedComboParams 140 | let clickCombo = event.item 141 | let clickComboModel = clickCombo.getModel() 142 | vm.selectedCombo = clickCombo 143 | vm.selectedComboParams = { 144 | label: clickComboModel.label || '', 145 | labelPosition: clickComboModel.labelCfg.position || '', 146 | labelRefX: clickComboModel.labelCfg.refX || 0, 147 | labelRefY: clickComboModel.labelCfg.refY || 0, 148 | type: clickComboModel.type || '' 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/drag-add-edge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: edit mode: 通过拖拽节点上的锚点添加连线 6 | */ 7 | import { mix } from '@antv/util' 8 | import utils from '../utils' 9 | import theme from '../theme' 10 | 11 | // 用来获取调用此js的vue组件实例(this) 12 | let vm = null 13 | 14 | const sendThis = (_this) => { 15 | vm = _this 16 | } 17 | 18 | export default { 19 | sendThis, // 暴露函数 20 | name: 'drag-add-edge', 21 | options: { 22 | getEvents () { 23 | return { 24 | 'node:mousedown': 'onNodeMousedown', 25 | 'node:mouseup': 'onNodeMouseup', 26 | 'edge:mouseup': 'onEdgeMouseup', 27 | 'mousemove': 'onMousemove' 28 | } 29 | }, 30 | onNodeMousedown (event) { 31 | let self = this 32 | // 交互过程中的信息 33 | self.evtInfo = { 34 | action: null, 35 | node: event.item, 36 | target: event.target 37 | } 38 | if (self.evtInfo.target && self.evtInfo.target.attrs.name) { 39 | // todo...未来可能针对锚点增加其它功能(例如拖拽调整大小) 40 | switch (self.evtInfo.target.attrs.name) { 41 | case 'anchor': 42 | self.evtInfo.action = 'drawEdge' 43 | break 44 | } 45 | } 46 | if (self.evtInfo && self.evtInfo.action) { 47 | self[self.evtInfo.action].start.call(self, event) 48 | } 49 | }, 50 | onNodeMouseup (event) { 51 | let self = this 52 | if (self.evtInfo && self.evtInfo.action) { 53 | self[self.evtInfo.action].stop.call(self, event) 54 | } 55 | }, 56 | onEdgeMouseup (event) { 57 | let self = this 58 | if (self.evtInfo && self.evtInfo.action === 'drawEdge') { 59 | self[self.evtInfo.action].stop.call(self, event) 60 | } 61 | }, 62 | onMousemove (event) { 63 | let self = this 64 | if (self.evtInfo && self.evtInfo.action) { 65 | self[self.evtInfo.action].move.call(self, event) 66 | } 67 | }, 68 | drawEdge: { 69 | isMoving: false, 70 | currentLine: null, 71 | start: function (event) { 72 | let self = this 73 | let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 74 | 75 | // ************** 暂存【连线】前的数据状态 start ************** 76 | let graph = vm.graph 77 | self.historyData = JSON.stringify(graph.save()) 78 | // ************** 暂存【连线】前的数据状态 end ************** 79 | 80 | let sourceAnchor 81 | let sourceNodeModel = self.evtInfo.node.getModel() 82 | // 锚点数据 83 | let anchorPoints = self.evtInfo.node.getAnchorPoints() 84 | // 处理线条目标点 85 | if (anchorPoints && anchorPoints.length) { 86 | // 获取距离指定坐标最近的一个锚点 87 | sourceAnchor = self.evtInfo.node.getLinkPoint({ 88 | x: event.x, 89 | y: event.y 90 | }) 91 | } 92 | self.drawEdge.currentLine = self.graph.addItem('edge', { 93 | // id: G6.Util.uniqueId(), // 这种生成id的方式有bug,会重复 94 | id: utils.generateUUID(), 95 | // 起始节点 96 | source: sourceNodeModel.id, 97 | sourceAnchor: sourceAnchor ? sourceAnchor.anchorIndex : '', 98 | // 终止节点/位置 99 | target: { 100 | x: event.x, 101 | y: event.y 102 | }, 103 | type: self.graph.$C.edge.type || 'cc-line', 104 | style: mix({}, themeStyle.edgeStyle.default, self.graph.$C.edge.style) 105 | }) 106 | self.drawEdge.isMoving = true 107 | }, 108 | move (event) { 109 | let self = this 110 | if (self.drawEdge.isMoving && self.drawEdge.currentLine) { 111 | self.graph.updateItem(self.drawEdge.currentLine, { 112 | target: { 113 | x: event.x, 114 | y: event.y 115 | } 116 | }) 117 | } 118 | }, 119 | stop (event) { 120 | let self = this 121 | if (self.drawEdge.isMoving) { 122 | if (self.drawEdge.currentLine === event.item) { 123 | // 画线过程中点击则移除当前画线 124 | self.graph.removeItem(event.item) 125 | } else { 126 | let targetNode = event.item 127 | let targetNodeModel = targetNode.getModel() 128 | let targetAnchor = null 129 | // 锚点数据 130 | let anchorPoints = targetNode.getAnchorPoints() 131 | // 处理线条目标点 132 | if (anchorPoints && anchorPoints.length) { 133 | // 获取距离指定坐标最近的一个锚点 134 | targetAnchor = targetNode.getLinkPoint({ x: event.x, y: event.y }) 135 | } 136 | self.graph.updateItem(self.drawEdge.currentLine, { 137 | target: targetNodeModel.id, 138 | targetAnchor: targetAnchor ? targetAnchor.anchorIndex : '' 139 | }) 140 | 141 | // ************** 记录historyData的逻辑 start ************** 142 | if (this.historyData) { 143 | let graph = this.graph 144 | // 如果当前点过【撤销】了,拖拽节点后没有【重做】功能 145 | // 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录 146 | if (vm.undoCount > 0) { 147 | vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 148 | for (let i = 1; i <= vm.undoCount; i++) { 149 | let key = `graph_history_${vm.historyIndex + i}` 150 | vm.removeHistoryData(key) 151 | } 152 | vm.undoCount = 0 153 | } else { 154 | // 正常顺序执行的情况,记录拖拽前的数据状态 155 | let key = `graph_history_${vm.historyIndex}` 156 | vm.addHistoryData(key, this.historyData) 157 | } 158 | // 记录拖拽后的数据状态 159 | vm.historyIndex += 1 160 | let key = `graph_history_${vm.historyIndex}` 161 | let currentData = JSON.stringify(graph.save()) 162 | vm.addHistoryData(key, currentData) 163 | } 164 | // ************** 记录historyData的逻辑 end ************** 165 | } 166 | } 167 | self.drawEdge.currentLine = null 168 | self.drawEdge.isMoving = false 169 | self.evtInfo = null 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/drag-event-edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: edit mode: 鼠标拖动节点的交互(记录拖拽前后的数据,用于【撤销】和【重做】) 6 | */ 7 | 8 | // 用来获取调用此js的vue组件实例(this) 9 | let vm = null 10 | 11 | const sendThis = (_this) => { 12 | vm = _this 13 | } 14 | 15 | export default { 16 | sendThis, // 暴露函数 17 | name: 'drag-event-edit', 18 | options: { 19 | getEvents () { 20 | return { 21 | 'node:dragstart': 'onNodeDragstart', 22 | 'node:dragend': 'onNodeDragend' 23 | } 24 | }, 25 | onNodeDragstart () { 26 | let graph = vm.graph 27 | this.historyData = JSON.stringify(graph.save()) 28 | }, 29 | onNodeDragend () { 30 | if (this.historyData) { 31 | let graph = this.graph 32 | // 如果当前点过【撤销】了,拖拽节点后没有【重做】功能 33 | // 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录 34 | if (vm.undoCount > 0) { 35 | vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 36 | for (let i = 1; i <= vm.undoCount; i++) { 37 | let key = `graph_history_${vm.historyIndex + i}` 38 | vm.removeHistoryData(key) 39 | } 40 | vm.undoCount = 0 41 | } else { 42 | // 正常顺序执行的情况,记录拖拽前的数据状态 43 | let key = `graph_history_${vm.historyIndex}` 44 | vm.addHistoryData(key, this.historyData) 45 | } 46 | // 记录拖拽后的数据状态 47 | vm.historyIndex += 1 48 | let key = `graph_history_${vm.historyIndex}` 49 | let currentData = JSON.stringify(graph.save()) 50 | vm.addHistoryData(key, currentData) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/hover-event-edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: edit mode: 悬浮交互 6 | */ 7 | 8 | export default { 9 | name: 'hover-event-edit', 10 | options: { 11 | getEvents () { 12 | return { 13 | 'node:mouseover': 'onNodeHover', 14 | 'node:mouseout': 'onNodeOut', 15 | 'combo:mouseover': 'onComboHover', 16 | 'combo:mouseout': 'onComboOut' 17 | } 18 | }, 19 | onNodeHover (event) { 20 | let hoverNode = event.item 21 | hoverNode.setState('hover', true) 22 | }, 23 | onNodeOut (event) { 24 | let hoverNode = event.item 25 | hoverNode.setState('hover', false) 26 | }, 27 | onComboHover (event) { 28 | let hoverCombo = event.item 29 | hoverCombo.setState('hover', true) 30 | }, 31 | onComboOut (event) { 32 | let hoverCombo = event.item 33 | hoverCombo.setState('hover', false) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: register behaviors 6 | */ 7 | 8 | import dragAddEdge from './drag-add-edge' 9 | import hoverEventEdit from './hover-event-edit' 10 | import clickEventEdit from './click-event-edit' 11 | import dragEventEdit from './drag-event-edit' 12 | import keyupEventEdit from './keyup-event-edit' 13 | 14 | const obj = { 15 | dragAddEdge, 16 | hoverEventEdit, 17 | clickEventEdit, 18 | dragEventEdit, 19 | keyupEventEdit 20 | } 21 | 22 | export default { 23 | obj, 24 | register(G6) { 25 | Object.values(obj).map(item => { 26 | G6.registerBehavior(item.name, item.options) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/cc-topology/src/behavior/keyup-event-edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: edit mode: 键盘事件的交互,主要是删除节点和连线(记录删除前后的数据,用于【撤销】和【重做】) 6 | */ 7 | 8 | // 用来获取调用此js的vue组件实例(this) 9 | let vm = null 10 | 11 | const sendThis = (_this) => { 12 | vm = _this 13 | } 14 | 15 | export default { 16 | sendThis, // 暴露函数 17 | name: 'keyup-event-edit', 18 | options: { 19 | getEvents () { 20 | return { 21 | 'keyup': 'onKeyup' 22 | } 23 | }, 24 | onKeyup (event) { 25 | let graph = this.graph 26 | let selectedNodes = graph.findAllByState('node', 'selected') 27 | let selectedEdges = graph.findAllByState('edge', 'selected') 28 | if (event.keyCode === 46 && (selectedNodes.length > 0 || selectedEdges.length > 0)) { 29 | // ************** 记录【删除】前的数据状态 start ************** 30 | let historyData = JSON.stringify(graph.save()) 31 | let key = `graph_history_${vm.historyIndex}` 32 | vm.addHistoryData(key, historyData) 33 | // ************** 记录【删除】前的数据状态 end ************** 34 | // 开始删除 35 | for (let i = 0; i < selectedNodes.length; i++) { 36 | graph.removeItem(selectedNodes[i]) 37 | } 38 | for (let i = 0; i < selectedEdges.length; i++) { 39 | graph.removeItem(selectedEdges[i]) 40 | } 41 | // ************** 记录【删除】后的数据状态 start ************** 42 | // 如果当前点过【撤销】了,拖拽节点后将取消【重做】功能 43 | // 重置undoCount,【删除】后的数据状态给(当前所在historyIndex + 1),且清空这个时间点之后的记录 44 | if (vm.undoCount > 0) { 45 | vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置 46 | for (let i = 1; i <= vm.undoCount; i++) { 47 | let key = `graph_history_${vm.historyIndex + i}` 48 | vm.removeHistoryData(key) 49 | } 50 | vm.undoCount = 0 51 | } 52 | // 记录【删除】后的数据状态 53 | vm.historyIndex += 1 54 | key = `graph_history_${vm.historyIndex}` 55 | let currentData = JSON.stringify(graph.save()) 56 | vm.addHistoryData(key, currentData) 57 | // ************** 记录【删除】后的数据状态 end ************** 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/cc-topology/src/config/edge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: 线条的后期设置 6 | */ 7 | 8 | export default { 9 | type: 'cc-line', 10 | style: { 11 | startArrow: false, 12 | endArrow: false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/cc-topology/src/config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/16 4 | * @repository: https://github.com/wenyuan 5 | * @description: 配置 6 | */ 7 | 8 | import edge from './edge' 9 | 10 | export default { 11 | edge 12 | } 13 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/18 4 | * @repository: https://github.com/wenyuan 5 | * @description: 线公共方法 6 | */ 7 | 8 | import utils from '../utils' 9 | 10 | export default { 11 | drawShape(cfg, group) { 12 | const { startPoint, endPoint } = cfg 13 | let style = cfg.style 14 | // combo 收缩后的连线样式 15 | style.stroke = '#A3B1BF' 16 | style.lineWidth = 2 17 | const keyShape = group.addShape('path', { 18 | className: 'edge-shape', 19 | attrs: { 20 | ...style, 21 | path: [ 22 | ['M', startPoint.x, startPoint.y], 23 | ['L', endPoint.x, endPoint.y] 24 | ] 25 | }, 26 | name: 'edge-shape' 27 | }) 28 | return keyShape 29 | }, 30 | setState(name, value, item) { 31 | // 设置边状态 32 | utils.edge.setState(name, value, item) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/cc-brokenline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/10/22 4 | * @repository: https://github.com/wenyuan 5 | * @description: 折线 6 | */ 7 | 8 | import base from './base' 9 | import theme from '../theme' 10 | 11 | /** 12 | * fix: 继承 polyline 在 G6 3.x 里面有bug 13 | * 现实现方法参考 https://g6.antv.vision/zh/examples/shape/customEdge#customPolyline 14 | */ 15 | export default { 16 | name: 'cc-brokenline', 17 | extendName: 'line', 18 | options: { 19 | ...base, 20 | getPath(points) { 21 | const startPoint = points[0] 22 | const endPoint = points[1] 23 | return [ 24 | ['M', startPoint.x, startPoint.y], 25 | ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, startPoint.y], 26 | ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, endPoint.y], 27 | ['L', endPoint.x, endPoint.y] 28 | ] 29 | }, 30 | getShapeStyle(cfg) { 31 | const { startPoint, endPoint } = cfg 32 | const controlPoints = this.getControlPoints(cfg) 33 | let points = [startPoint] // 添加起始点 34 | // 添加控制点 35 | if (controlPoints) { 36 | points = points.concat(controlPoints) 37 | } 38 | // 添加结束点 39 | points.push(endPoint) 40 | const path = this.getPath(points) 41 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 42 | const style = { 43 | stroke: '#BBB', 44 | lineWidth: 1, 45 | path, 46 | startArrow: false, 47 | endArrow: false, 48 | ...themeStyle.edgeStyle.default 49 | } 50 | return style 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/cc-cubic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/18 4 | * @repository: https://github.com/wenyuan 5 | * @description: 曲线 6 | */ 7 | 8 | import base from './base' 9 | 10 | export default { 11 | name: 'cc-cubic', 12 | extendName: 'cubic', 13 | options: { 14 | ...base 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/cc-line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/18 4 | * @repository: https://github.com/wenyuan 5 | * @description: 直线 6 | */ 7 | 8 | import base from './base' 9 | 10 | export default { 11 | name: 'cc-line', 12 | extendName: 'line', 13 | options: { 14 | ...base 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/cc-polyline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/18 4 | * @repository: https://github.com/wenyuan 5 | * @description: 多段线 6 | */ 7 | 8 | import base from './base' 9 | import { polylineFinding } from './polyline-finding' 10 | 11 | export default { 12 | name: 'cc-polyline', 13 | extendName: 'single-edge', 14 | options: { 15 | ...base, 16 | drawShape(cfg, group) { 17 | const { startPoint, endPoint } = cfg 18 | const controlPoints = this.getControlPoints(cfg) 19 | let points = [startPoint] 20 | if (controlPoints) { 21 | points.push(controlPoints) 22 | } 23 | points.push(endPoint) 24 | let path = this.getPath(points) 25 | const keyShape = group.addShape('path', { 26 | className: 'edge-shape', 27 | attrs: { 28 | ...cfg, 29 | path: path 30 | }, 31 | draggable: true, 32 | name: 'edge-shape' 33 | }) 34 | return keyShape 35 | }, 36 | getPath(points) { 37 | const path = [] 38 | for (let i = 0; i < points.length; i++) { 39 | const point = points[i] 40 | if (i === 0) { 41 | path.push(['M', point.x, point.y]) 42 | } else if (i === points.length - 1) { 43 | path.push(['L', point.x, point.y]) 44 | } else { 45 | const prevPoint = points[i - 1] 46 | let nextPoint = points[i + 1] 47 | let cornerLen = 5 48 | if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) { 49 | if (prevPoint.x === point.x) { 50 | path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen]) 51 | } else if (prevPoint.y === point.y) { 52 | path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]) 53 | } 54 | } 55 | const yLen = Math.abs(point.y - nextPoint.y) 56 | const xLen = Math.abs(point.x - nextPoint.x) 57 | if (yLen > 0 && yLen < cornerLen) { 58 | cornerLen = yLen 59 | } else if (xLen > 0 && xLen < cornerLen) { 60 | cornerLen = xLen 61 | } 62 | if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) { 63 | path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen]) 64 | } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) { 65 | path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y]) 66 | } 67 | } 68 | } 69 | return path 70 | }, 71 | getControlPoints(cfg) { 72 | if (!cfg.sourceNode) { 73 | return cfg.controlPoints 74 | } 75 | return polylineFinding(cfg.sourceNode, cfg.targetNode, cfg.startPoint, cfg.endPoint, 40) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/18 4 | * @repository: https://github.com/wenyuan 5 | * @description: register edges 6 | */ 7 | 8 | import ccLine from './cc-line' 9 | import ccBrokenline from './cc-brokenline' 10 | import ccPolyline from './cc-polyline' 11 | import ccCubic from './cc-cubic' 12 | 13 | const obj = { 14 | ccLine, 15 | ccBrokenline, 16 | ccPolyline, 17 | ccCubic 18 | } 19 | 20 | export default function(G6) { 21 | Object.values(obj).map(item => { 22 | G6.registerEdge(item.name, item.options, item.extendName) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /packages/cc-topology/src/edge/polyline-finding.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 带圆角折线连线的策略 3 | * 文档:https://www.yuque.com/antv/blog/eyi70n 4 | * 参考:https://github.com/guozhaolong/wfd/blob/master/src/item/edge.js 5 | * 引用:https://github.com/OXOYO/X-Flowchart-Vue/blob/master/src/global/lib/g6/edge/polylineFinding.js 6 | */ 7 | 8 | // 折线寻径 9 | export const polylineFinding = function(sNode, tNode, sPort, tPort, offset = 10) { 10 | const sourceBBox = sNode && sNode.getBBox ? sNode.getBBox() : getPointBBox(sPort) 11 | const targetBBox = tNode && tNode.getBBox ? tNode.getBBox() : getPointBBox(tPort) 12 | // 获取节点带 offset 的区域(扩展区域) 13 | const sBBox = getExpandedBBox(sourceBBox, offset) 14 | const tBBox = getExpandedBBox(targetBBox, offset) 15 | // 获取扩展区域上的起始和终止连接点 16 | const sPoint = getExpandedPort(sBBox, sPort) 17 | const tPoint = getExpandedPort(tBBox, tPort) 18 | // 获取合法折点集 19 | let points = getConnectablePoints(sBBox, tBBox, sPoint, tPoint) 20 | // 过滤合法点集,预处理、剪枝等 21 | filterConnectablePoints(points, sBBox) 22 | // 过滤合法点集,预处理、剪枝等 23 | filterConnectablePoints(points, tBBox) 24 | // 用 A-Star 算法寻径 25 | let polylinePoints = AStar(points, sPoint, tPoint, sBBox, tBBox) 26 | return polylinePoints 27 | } 28 | 29 | const getPointBBox = function(t) { 30 | return { 31 | centerX: t.x, 32 | centerY: t.y, 33 | minX: t.x, 34 | minY: t.y, 35 | maxX: t.x, 36 | maxY: t.y, 37 | height: 0, 38 | width: 0 39 | } 40 | } 41 | 42 | // 获取扩展区域 43 | const getExpandedBBox = function(bbox, offset) { 44 | if (bbox.width === 0 && bbox.height === 0) { 45 | return bbox 46 | } 47 | return { 48 | centerX: bbox.centerX, 49 | centerY: bbox.centerY, 50 | minX: bbox.minX - offset, 51 | minY: bbox.minY - offset, 52 | maxX: bbox.maxX + offset, 53 | maxY: bbox.maxY + offset, 54 | height: bbox.height + 2 * offset, 55 | width: bbox.width + 2 * offset 56 | } 57 | } 58 | 59 | // 获取扩展区域上的连接点 60 | const getExpandedPort = function(bbox, point) { 61 | // 判断连接点在上下左右哪个区域,相应地给x或y加上或者减去offset 62 | if (Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height) { 63 | return { 64 | x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, 65 | y: point.y 66 | } 67 | } 68 | return { 69 | x: point.x, 70 | y: point.y > bbox.centerY ? bbox.maxY : bbox.minY 71 | } 72 | } 73 | 74 | // 获取合法折点集合 75 | const getConnectablePoints = function(sBBox, tBBox, sPoint, tPoint) { 76 | let lineBBox = getBBoxFromVertexes(sPoint, tPoint) 77 | let outerBBox = combineBBoxes(sBBox, tBBox) 78 | let sLineBBox = combineBBoxes(sBBox, lineBBox) 79 | let tLineBBox = combineBBoxes(tBBox, lineBBox) 80 | let points = [ 81 | ...vertexOfBBox(sLineBBox), 82 | ...vertexOfBBox(tLineBBox), 83 | ...vertexOfBBox(outerBBox) 84 | ] 85 | const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY } 86 | let bboxes = [outerBBox, sLineBBox, tLineBBox, lineBBox] 87 | bboxes.forEach(bbox => { 88 | // 包含 bbox 延长线和线段的相交线 89 | points = [ 90 | ...points, 91 | ...crossPointsByLineAndBBox(bbox, centerPoint) 92 | ] 93 | }) 94 | points.push({ x: sPoint.x, y: tPoint.y }) 95 | points.push({ x: tPoint.x, y: sPoint.y }) 96 | return points 97 | } 98 | 99 | const getBBoxFromVertexes = function(sPoint, tPoint) { 100 | const minX = Math.min(sPoint.x, tPoint.x) 101 | const maxX = Math.max(sPoint.x, tPoint.x) 102 | const minY = Math.min(sPoint.y, tPoint.y) 103 | const maxY = Math.max(sPoint.y, tPoint.y) 104 | 105 | return { 106 | centerX: (minX + maxX) / 2, 107 | centerY: (minY + maxY) / 2, 108 | maxX: maxX, 109 | maxY: maxY, 110 | minX: minX, 111 | minY: minY, 112 | height: maxY - minY, 113 | width: maxX - minX 114 | } 115 | } 116 | 117 | const combineBBoxes = function(sBBox, tBBox) { 118 | const minX = Math.min(sBBox.minX, tBBox.minX) 119 | const minY = Math.min(sBBox.minY, tBBox.minY) 120 | const maxX = Math.max(sBBox.maxX, tBBox.maxX) 121 | const maxY = Math.max(sBBox.maxY, tBBox.maxY) 122 | 123 | return { 124 | centerX: (minX + maxX) / 2, 125 | centerY: (minY + maxY) / 2, 126 | minX: minX, 127 | minY: minY, 128 | maxX: maxX, 129 | maxY: maxY, 130 | height: maxY - minY, 131 | width: maxX - minX 132 | } 133 | } 134 | 135 | const vertexOfBBox = function(bbox) { 136 | return [ 137 | { x: bbox.minX, y: bbox.minY }, 138 | { x: bbox.maxX, y: bbox.minY }, 139 | { x: bbox.maxX, y: bbox.maxY }, 140 | { x: bbox.minX, y: bbox.maxY } 141 | ] 142 | } 143 | 144 | const crossPointsByLineAndBBox = function(bbox, centerPoint) { 145 | let crossPoints = [] 146 | if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) { 147 | crossPoints = [ 148 | ...crossPoints, 149 | { x: centerPoint.x, y: bbox.minY }, 150 | { x: centerPoint.x, y: bbox.maxY } 151 | ] 152 | } 153 | if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) { 154 | crossPoints = [ 155 | ...crossPoints, 156 | { x: bbox.minX, y: centerPoint.y }, 157 | { x: bbox.maxX, y: centerPoint.y } 158 | ] 159 | } 160 | 161 | return crossPoints 162 | } 163 | 164 | // 过滤连接点 165 | const filterConnectablePoints = function(points, bbox) { 166 | return points.filter(point => { 167 | return point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY 168 | }) 169 | } 170 | 171 | const crossBBox = function(bboxes, p1, p2) { 172 | for (let i = 0; i < bboxes.length; i++) { 173 | const bbox = bboxes[i] 174 | if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) { 175 | if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) { 176 | return true 177 | } 178 | } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) { 179 | if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) { 180 | return true 181 | } 182 | } 183 | } 184 | return false 185 | } 186 | 187 | const getCost = function(p1, p2) { 188 | return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) 189 | } 190 | 191 | // aStar 寻径 192 | const AStar = function(points, sPoint, tPoint, sBBox, tBBox) { 193 | const openList = [sPoint] 194 | const closeList = [] 195 | points.forEach(item => { 196 | item.id = item.x + '-' + item.y 197 | }) 198 | let tmpArr = [] 199 | points.forEach(item => { 200 | if (!tmpArr.includes(target => target.id === item.id)) { 201 | tmpArr.push(item) 202 | } 203 | }) 204 | points = [ 205 | ...tmpArr, 206 | tPoint 207 | ] 208 | let endPoint 209 | while (openList.length > 0) { 210 | let minCostPoint 211 | openList.forEach((p, i) => { 212 | if (!p.parent) { 213 | p.f = 0 214 | } 215 | if (!minCostPoint) { 216 | minCostPoint = p 217 | } 218 | if (p.f < minCostPoint.f) { 219 | minCostPoint = p 220 | } 221 | }) 222 | if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) { 223 | endPoint = minCostPoint 224 | break 225 | } 226 | openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1) 227 | closeList.push(minCostPoint) 228 | const neighbor = points.filter(p => { 229 | return (p.x === minCostPoint.x || p.y === minCostPoint.y) && 230 | !(p.x === minCostPoint.x && p.y === minCostPoint.y) && 231 | !crossBBox([sBBox, tBBox], minCostPoint, p) 232 | } 233 | ) 234 | neighbor.forEach(p => { 235 | const inOpen = openList.find(o => o.x === p.x && o.y === p.y) 236 | const currentG = getCost(p, minCostPoint) 237 | if (!closeList.find(o => o.x === p.x && o.y === p.y)) { 238 | if (inOpen) { 239 | if (p.g > currentG) { 240 | p.parent = minCostPoint 241 | p.g = currentG 242 | p.f = p.g + p.h 243 | } 244 | } else { 245 | p.parent = minCostPoint 246 | p.g = currentG 247 | let h = getCost(p, tPoint) 248 | if (crossBBox([tBBox], p, tPoint)) { 249 | // 如果穿过bbox则增加该点的预估代价为bbox周长的一半 250 | h += (tBBox.width / 2 + tBBox.height / 2) 251 | } 252 | p.h = h 253 | p.f = p.g + p.h 254 | openList.push(p) 255 | } 256 | } 257 | }) 258 | } 259 | if (endPoint) { 260 | const result = [] 261 | result.push({ 262 | x: endPoint.x, 263 | y: endPoint.y 264 | }) 265 | while (endPoint.parent) { 266 | endPoint = endPoint.parent 267 | result.push({ 268 | x: endPoint.x, 269 | y: endPoint.y 270 | }) 271 | } 272 | return result.reverse() 273 | } 274 | return [] 275 | } 276 | -------------------------------------------------------------------------------- /packages/cc-topology/src/graph/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: 图的布局方式/图的初始化 6 | */ 7 | 8 | import theme from '../theme' 9 | 10 | /** 11 | * 图的布局方式/图的初始化 12 | * @type {{commonGraph: (function(*, *): G6.Graph)}} 13 | */ 14 | const initGraph = { 15 | /** 16 | * 一般布局 17 | * @param G6 18 | * @param options 19 | * @returns {G6.Graph} 20 | */ 21 | commonGraph: function (G6, options) { 22 | let graphData = options.graphData 23 | let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 24 | // 生成G6实例 25 | let graph = new G6.Graph({ 26 | plugins: options.plugins, 27 | container: options.container, 28 | width: options.width, 29 | height: options.height, 30 | groupByTypes: options.graphMode === 'edit', // TODO...G6 节点与边的层级(临时方案) 31 | defaultNode: { 32 | type: 'cc-rect', 33 | labelCfg: themeStyle.nodeLabelCfg 34 | }, 35 | defaultEdge: { 36 | type: 'cc-line', 37 | labelCfg: themeStyle.edgeLabelCfg 38 | }, 39 | defaultCombo: { 40 | type: 'circle', 41 | style: themeStyle.comboStyle.default, 42 | labelCfg: themeStyle.comboLabelCfg 43 | }, 44 | nodeStateStyles: themeStyle.nodeStyle, 45 | edgeStateStyles: themeStyle.edgeStyle, 46 | comboStateStyles: themeStyle.comboStyle, 47 | modes: options.modes 48 | }) 49 | // 将 read 方法分解成 data() 和 render 方法,便于整个生命周期的管理 50 | graph.read(graphData) 51 | graph.render() 52 | // 返回G6实例 53 | return graph 54 | } 55 | } 56 | 57 | export default initGraph 58 | -------------------------------------------------------------------------------- /packages/cc-topology/src/node/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: 节点基础方法 6 | */ 7 | 8 | import utils from '../utils' 9 | 10 | export default { 11 | setState(name, value, item) { 12 | // 设置节点状态 13 | utils.node.setState(name, value, item) 14 | // 设置锚点状态 15 | utils.anchor.setState(name, value, item) 16 | }, 17 | // 绘制后附加锚点 18 | afterDraw(cfg, group) { 19 | // 绘制锚点 20 | utils.anchor.draw(cfg, group) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/cc-topology/src/node/cc-image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: 图片节点 6 | */ 7 | 8 | import utils from '../utils' 9 | 10 | // 用来获取调用此js的vue组件实例(this) 11 | let vm = null 12 | 13 | const sendThis = (_this) => { 14 | vm = _this 15 | } 16 | 17 | export default { 18 | sendThis, 19 | name: 'cc-image', 20 | extendName: 'image', 21 | options: { 22 | setState (name, value, item) { 23 | // 设置节点状态 24 | utils.node.setState(name, value, item) 25 | // 设置锚点状态 26 | if (vm.graphMode === 'edit') { 27 | utils.anchor.setState(name, value, item) 28 | } 29 | }, 30 | // 绘制后附加锚点 31 | afterDraw (cfg, group) { 32 | // 绘制锚点 33 | if (vm.graphMode === 'edit') { 34 | utils.anchor.draw(cfg, group) 35 | } 36 | }, 37 | // 设置告警状态 38 | afterUpdate (cfg, node) { 39 | const group = node.getContainer() 40 | // 获取children 41 | const halos = group.findAll(function (item) { 42 | return item.attrs.name === 'halo' 43 | }) 44 | // 告警 45 | if (cfg.appState && cfg.appState.alert) { 46 | if (halos.length > 0) { 47 | return 48 | } 49 | let size = this.getSize(cfg) || [40, 40] 50 | let r = size[0] / 2 51 | let { id } = cfg 52 | let halo1 = group.addShape('circle', { 53 | id: id + '_halo_' + 1, 54 | attrs: { 55 | name: 'halo', 56 | x: 0, 57 | y: 0, 58 | r: r, 59 | fill: cfg.color || '#F56C6C', 60 | opacity: 0.6 61 | }, 62 | name: 'halo', 63 | zIndex: -3 64 | }) 65 | let halo2 = group.addShape('circle', { 66 | id: id + '_halo_' + 2, 67 | attrs: { 68 | name: 'halo', 69 | x: 0, 70 | y: 0, 71 | r: r, 72 | fill: cfg.color || '#F56C6C', // 为了显示清晰,随意设置了颜色 73 | opacity: 0.6 74 | }, 75 | name: 'halo', 76 | zIndex: -2 77 | }) 78 | let halo3 = group.addShape('circle', { 79 | id: id + '_halo_' + 3, 80 | attrs: { 81 | name: 'halo', 82 | x: 0, 83 | y: 0, 84 | r: r, 85 | fill: cfg.color || '#F56C6C', 86 | opacity: 0.6 87 | }, 88 | name: 'halo', 89 | zIndex: -1 90 | }) 91 | group.sort() // 排序,根据zIndex 排序 92 | halo1.animate({ // 逐渐放大,并消失 93 | r: r + 10, 94 | opacity: 0.1 95 | }, { 96 | repeat: true, // 循环 97 | duration: 3000, 98 | easing: 'easeCubic', 99 | delay: 0 // 无延迟 100 | }) 101 | halo2.animate({ // 逐渐放大,并消失 102 | r: r + 10, 103 | opacity: 0.1 104 | }, { 105 | repeat: true, // 循环 106 | duration: 3000, 107 | easing: 'easeCubic', 108 | delay: 1000 // 1 秒延迟 109 | }) 110 | halo3.animate({ // 逐渐放大,并消失 111 | r: r + 10, 112 | opacity: 0.1 113 | }, { 114 | repeat: true, // 循环 115 | duration: 3000, 116 | easing: 'easeCubic', 117 | delay: 2000 // 2 秒延迟 118 | }) 119 | } else { 120 | halos.forEach(halo => { 121 | group.removeChild(halo) 122 | }) 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /packages/cc-topology/src/node/cc-rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: 矩形节点 6 | */ 7 | 8 | import base from './base' 9 | import theme from '../theme' 10 | 11 | export default { 12 | name: 'cc-rect', 13 | extendName: 'rect', 14 | options: { 15 | ...base, 16 | getShapeStyle(cfg) { 17 | const size = this.getSize(cfg) || [48, 48] 18 | const width = size[0] 19 | const height = size[1] 20 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 21 | const style = { 22 | x: 0 - width / 2, 23 | y: 0 - height / 2, 24 | width: width, 25 | height: height, 26 | ...themeStyle.nodeStyle.default 27 | } 28 | return style 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/cc-topology/src/node/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: register nodes 6 | */ 7 | 8 | import ccRect from './cc-rect' 9 | import ccImage from './cc-image' 10 | 11 | const obj = { 12 | ccRect, 13 | ccImage 14 | } 15 | 16 | export default { 17 | obj, 18 | register(G6) { 19 | Object.values(obj).map(item => { 20 | G6.registerNode(item.name, item.options, item.extendName) 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/cc-topology/src/plugins/d3-installer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/07/05 4 | * @repository: https://github.com/wenyuan 5 | * @description: install 3rd plugins 6 | */ 7 | 8 | import * as d3 from 'd3-force/dist/d3-force' 9 | 10 | export default d3 11 | -------------------------------------------------------------------------------- /packages/cc-topology/src/theme/dark-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/11/20 4 | * @repository: https://github.com/wenyuan 5 | * @description: dark style 6 | */ 7 | 8 | export default { 9 | // 节点样式 10 | nodeStyle: { 11 | default: { 12 | stroke: '#CED4D9', 13 | fill: 'transparent', 14 | shadowOffsetX: 0, 15 | shadowOffsetY: 4, 16 | shadowBlur: 10, 17 | shadowColor: 'rgba(13, 26, 38, 0.08)', 18 | lineWidth: 1, 19 | radius: 4, 20 | strokeOpacity: 0.7 21 | }, 22 | selected: { 23 | shadowColor: '#ff240b', 24 | shadowBlur: 4, 25 | shadowOffsetX: 0, 26 | shadowOffsetY: 0 27 | // shadowColor: '#626262', 28 | // shadowBlur: 8, 29 | // shadowOffsetX: -1, 30 | // shadowOffsetY: 3 31 | }, 32 | unselected: { 33 | shadowColor: '' 34 | } 35 | }, 36 | // 节点标签样式 37 | nodeLabelCfg: { 38 | positions: 'center', 39 | style: { 40 | fill: '#FFF' 41 | } 42 | }, 43 | // 连线样式 44 | edgeStyle: { 45 | default: { 46 | stroke: '#53da3a', 47 | lineWidth: 2, 48 | strokeOpacity: 0.92, 49 | lineAppendWidth: 10 50 | // endArrow: true 51 | }, 52 | active: { 53 | shadowColor: 'red', 54 | shadowBlur: 4, 55 | shadowOffsetX: 0, 56 | shadowOffsetY: 0 57 | }, 58 | inactive: { 59 | shadowColor: '' 60 | }, 61 | selected: { 62 | shadowColor: '#ff240b', 63 | shadowBlur: 4, 64 | shadowOffsetX: 0, 65 | shadowOffsetY: 0 66 | }, 67 | unselected: { 68 | shadowColor: '' 69 | } 70 | }, 71 | // 连线标签样式 72 | edgeLabelCfg: { 73 | position: 'center', 74 | autoRotate: false, 75 | style: { 76 | fill: '#000' 77 | } 78 | }, 79 | // 锚点样式 80 | anchorStyle: { 81 | default: { 82 | radius: 3, 83 | symbol: 'circle', 84 | fill: '#FFFFFF', 85 | fillOpacity: 0, 86 | stroke: '#1890FF', 87 | strokeOpacity: 0, 88 | cursor: 'crosshair' 89 | }, 90 | hover: { 91 | fillOpacity: 1, 92 | strokeOpacity: 1 93 | }, 94 | unhover: { 95 | fillOpacity: 0, 96 | strokeOpacity: 0 97 | }, 98 | active: { 99 | fillOpacity: 1, 100 | strokeOpacity: 1 101 | }, 102 | inactive: { 103 | fillOpacity: 0, 104 | strokeOpacity: 0 105 | } 106 | }, 107 | // 锚点背景样式 108 | anchorBgStyle: { 109 | default: { 110 | radius: 10, 111 | symbol: 'circle', 112 | fill: '#1890FF', 113 | fillOpacity: 0, 114 | stroke: '#1890FF', 115 | strokeOpacity: 0, 116 | cursor: 'crosshair' 117 | }, 118 | hover: { 119 | fillOpacity: 1, 120 | strokeOpacity: 1 121 | }, 122 | unhover: { 123 | fillOpacity: 0, 124 | strokeOpacity: 0 125 | }, 126 | active: { 127 | fillOpacity: 0.3, 128 | strokeOpacity: 0.5 129 | }, 130 | inactive: { 131 | fillOpacity: 0, 132 | strokeOpacity: 0 133 | } 134 | }, 135 | // 分组样式 136 | comboStyle: { 137 | default: { 138 | fill: 'transparent', 139 | lineWidth: 1 140 | }, 141 | hover: { 142 | lineWidth: 3 143 | } 144 | }, 145 | // 分组标签样式 146 | comboLabelCfg: { 147 | position: 'top', 148 | style: { 149 | fontSize: 20, 150 | fill: '#4682B4' 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/cc-topology/src/theme/default-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: default style 6 | */ 7 | 8 | export default { 9 | // 节点样式 10 | nodeStyle: { 11 | default: { 12 | stroke: '#CED4D9', 13 | fill: 'transparent', 14 | shadowOffsetX: 0, 15 | shadowOffsetY: 4, 16 | shadowBlur: 10, 17 | shadowColor: 'rgba(13, 26, 38, 0.08)', 18 | lineWidth: 1, 19 | radius: 4, 20 | strokeOpacity: 0.7 21 | }, 22 | selected: { 23 | shadowColor: '#ff240b', 24 | shadowBlur: 4, 25 | shadowOffsetX: 0, 26 | shadowOffsetY: 0 27 | // shadowColor: '#626262', 28 | // shadowBlur: 8, 29 | // shadowOffsetX: -1, 30 | // shadowOffsetY: 3 31 | }, 32 | unselected: { 33 | shadowColor: '' 34 | } 35 | }, 36 | // 节点标签样式 37 | nodeLabelCfg: { 38 | positions: 'bottom', 39 | style: { 40 | fill: '#000' 41 | } 42 | }, 43 | // 连线样式 44 | edgeStyle: { 45 | default: { 46 | stroke: '#A3B1BF', 47 | lineWidth: 2, 48 | strokeOpacity: 0.92, 49 | lineAppendWidth: 10 50 | // endArrow: true 51 | }, 52 | active: { 53 | shadowColor: 'red', 54 | shadowBlur: 4, 55 | shadowOffsetX: 0, 56 | shadowOffsetY: 0 57 | }, 58 | inactive: { 59 | shadowColor: '' 60 | }, 61 | selected: { 62 | shadowColor: '#ff240b', 63 | shadowBlur: 4, 64 | shadowOffsetX: 0, 65 | shadowOffsetY: 0 66 | }, 67 | unselected: { 68 | shadowColor: '' 69 | } 70 | }, 71 | // 连线标签样式 72 | edgeLabelCfg: { 73 | position: 'center', 74 | autoRotate: false, 75 | style: { 76 | fill: '#000' 77 | } 78 | }, 79 | // 锚点样式 80 | anchorStyle: { 81 | default: { 82 | r: 3, 83 | symbol: 'circle', 84 | fill: '#FFFFFF', 85 | fillOpacity: 0, 86 | stroke: '#1890FF', 87 | strokeOpacity: 0, 88 | cursor: 'crosshair' 89 | }, 90 | hover: { 91 | fillOpacity: 1, 92 | strokeOpacity: 1 93 | }, 94 | unhover: { 95 | fillOpacity: 0, 96 | strokeOpacity: 0 97 | }, 98 | active: { 99 | fillOpacity: 1, 100 | strokeOpacity: 1 101 | }, 102 | inactive: { 103 | fillOpacity: 0, 104 | strokeOpacity: 0 105 | } 106 | }, 107 | // 锚点背景样式 108 | anchorBgStyle: { 109 | default: { 110 | r: 6, 111 | symbol: 'circle', 112 | fill: '#1890FF', 113 | fillOpacity: 0, 114 | stroke: '#1890FF', 115 | strokeOpacity: 0, 116 | cursor: 'crosshair' 117 | }, 118 | hover: { 119 | fillOpacity: 1, 120 | strokeOpacity: 1 121 | }, 122 | unhover: { 123 | fillOpacity: 0, 124 | strokeOpacity: 0 125 | }, 126 | active: { 127 | fillOpacity: 0.3, 128 | strokeOpacity: 0.5 129 | }, 130 | inactive: { 131 | fillOpacity: 0, 132 | strokeOpacity: 0 133 | } 134 | }, 135 | // 分组样式 136 | comboStyle: { 137 | default: { 138 | fill: 'transparent', 139 | lineWidth: 1 140 | }, 141 | hover: { 142 | lineWidth: 3 143 | } 144 | }, 145 | // 分组标签样式 146 | comboLabelCfg: { 147 | position: 'top', 148 | style: { 149 | fontSize: 20, 150 | fill: '#4682B4' 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/cc-topology/src/theme/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: 编辑器主题样式 - 节点、连线的预设样式 6 | */ 7 | 8 | import defaultStyle from './default-style' 9 | import darkStyle from './dark-style' 10 | import officeStyle from './office-style' 11 | 12 | export default { 13 | defaultStyle, 14 | darkStyle, 15 | officeStyle 16 | } 17 | -------------------------------------------------------------------------------- /packages/cc-topology/src/theme/office-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/11/21 4 | * @repository: https://github.com/wenyuan 5 | * @description: office style 6 | */ 7 | 8 | export default { 9 | // 节点样式 10 | nodeStyle: { 11 | default: { 12 | stroke: '#CED4D9', 13 | fill: '#FFFFFF', 14 | shadowOffsetX: 0, 15 | shadowOffsetY: 4, 16 | shadowBlur: 10, 17 | shadowColor: 'rgba(13, 26, 38, 0.08)', 18 | lineWidth: 1, 19 | radius: 4, 20 | strokeOpacity: 0.7 21 | }, 22 | selected: { 23 | shadowColor: '#ff240b', 24 | shadowBlur: 4, 25 | shadowOffsetX: 0, 26 | shadowOffsetY: 0 27 | // shadowColor: '#626262', 28 | // shadowBlur: 8, 29 | // shadowOffsetX: -1, 30 | // shadowOffsetY: 3 31 | }, 32 | unselected: { 33 | shadowColor: '' 34 | } 35 | }, 36 | // 节点标签样式 37 | nodeLabelCfg: { 38 | positions: 'center', 39 | style: { 40 | fill: '#000' 41 | } 42 | }, 43 | // 连线样式 44 | edgeStyle: { 45 | default: { 46 | stroke: '#41c23a', 47 | lineWidth: 2, 48 | strokeOpacity: 0.92, 49 | lineAppendWidth: 10 50 | // endArrow: true 51 | }, 52 | active: { 53 | shadowColor: 'red', 54 | shadowBlur: 4, 55 | shadowOffsetX: 0, 56 | shadowOffsetY: 0 57 | }, 58 | inactive: { 59 | shadowColor: '' 60 | }, 61 | selected: { 62 | shadowColor: '#ff240b', 63 | shadowBlur: 4, 64 | shadowOffsetX: 0, 65 | shadowOffsetY: 0 66 | }, 67 | unselected: { 68 | shadowColor: '' 69 | } 70 | }, 71 | // 连线标签样式 72 | edgeLabelCfg: { 73 | position: 'center', 74 | autoRotate: false, 75 | style: { 76 | fill: '#000' 77 | } 78 | }, 79 | // 锚点样式 80 | anchorStyle: { 81 | default: { 82 | radius: 3, 83 | symbol: 'circle', 84 | fill: '#FFFFFF', 85 | fillOpacity: 0, 86 | stroke: '#1890FF', 87 | strokeOpacity: 0, 88 | cursor: 'crosshair' 89 | }, 90 | hover: { 91 | fillOpacity: 1, 92 | strokeOpacity: 1 93 | }, 94 | unhover: { 95 | fillOpacity: 0, 96 | strokeOpacity: 0 97 | }, 98 | active: { 99 | fillOpacity: 1, 100 | strokeOpacity: 1 101 | }, 102 | inactive: { 103 | fillOpacity: 0, 104 | strokeOpacity: 0 105 | } 106 | }, 107 | // 锚点背景样式 108 | anchorBgStyle: { 109 | default: { 110 | radius: 10, 111 | symbol: 'circle', 112 | fill: '#1890FF', 113 | fillOpacity: 0, 114 | stroke: '#1890FF', 115 | strokeOpacity: 0, 116 | cursor: 'crosshair' 117 | }, 118 | hover: { 119 | fillOpacity: 1, 120 | strokeOpacity: 1 121 | }, 122 | unhover: { 123 | fillOpacity: 0, 124 | strokeOpacity: 0 125 | }, 126 | active: { 127 | fillOpacity: 0.3, 128 | strokeOpacity: 0.5 129 | }, 130 | inactive: { 131 | fillOpacity: 0, 132 | strokeOpacity: 0 133 | } 134 | }, 135 | // 分组样式 136 | comboStyle: { 137 | default: { 138 | fill: 'transparent', 139 | lineWidth: 1 140 | }, 141 | hover: { 142 | lineWidth: 3 143 | } 144 | }, 145 | // 分组标签样式 146 | comboLabelCfg: { 147 | position: 'top', 148 | style: { 149 | fontSize: 20, 150 | fill: '#4682B4' 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/cc-topology/src/toolbar-edit.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 47 | 48 | 109 | 125 | -------------------------------------------------------------------------------- /packages/cc-topology/src/toolbar-preview.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 27 | 84 | 100 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/anchor/draw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: draw anchor 6 | */ 7 | 8 | import theme from '../../theme' 9 | 10 | export default function(cfg, group) { 11 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 12 | let { anchorPoints, width, height, id } = cfg 13 | if (anchorPoints && anchorPoints.length) { 14 | for (let i = 0, len = anchorPoints.length; i < len; i++) { 15 | let [x, y] = anchorPoints[i] 16 | // 计算Marker中心点坐标 17 | let originX = -width / 2 18 | let originY = -height / 2 19 | let anchorX = x * width + originX 20 | let anchorY = y * height + originY 21 | // 添加锚点背景 22 | let anchorBgShape = group.addShape('marker', { 23 | id: id + '_anchor_bg_' + i, 24 | attrs: { 25 | name: 'anchorBg', 26 | x: anchorX, 27 | y: anchorY, 28 | // 锚点默认样式 29 | ...themeStyle.anchorBgStyle.default 30 | }, 31 | draggable: false, 32 | name: 'markerBg-shape' 33 | }) 34 | // 添加锚点Marker形状 35 | let anchorShape = group.addShape('marker', { 36 | id: id + '_anchor_' + i, 37 | attrs: { 38 | name: 'anchor', 39 | x: anchorX, 40 | y: anchorY, 41 | // 锚点默认样式 42 | ...themeStyle.anchorStyle.default 43 | }, 44 | draggable: false, 45 | name: 'marker-shape' 46 | }) 47 | 48 | anchorShape.on('mouseenter', function() { 49 | anchorBgShape.attr({ 50 | ...themeStyle.anchorBgStyle.active 51 | }) 52 | }) 53 | anchorShape.on('mouseleave', function() { 54 | anchorBgShape.attr({ 55 | ...themeStyle.anchorBgStyle.inactive 56 | }) 57 | }) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/anchor/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: anchor 6 | */ 7 | 8 | import draw from './draw' 9 | import setState from './set-state' 10 | import update from './update' 11 | 12 | export default { 13 | draw, 14 | setState, 15 | update 16 | } 17 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/anchor/set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: set anchor state 6 | */ 7 | 8 | import theme from '../../theme' 9 | 10 | export default function(name, value, item) { 11 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 12 | if (name === 'hover') { 13 | let group = item.getContainer() 14 | let children = group.get('children') 15 | for (let i = 0, len = children.length; i < len; i++) { 16 | let child = children[i] 17 | // 处理锚点状态 18 | if (child.attrs.name === 'anchor') { 19 | if (value) { 20 | child.attr(themeStyle.anchorStyle.hover) 21 | } else { 22 | child.attr(themeStyle.anchorStyle.unhover) 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/anchor/update.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: update anchor 6 | */ 7 | 8 | export default function(cfg, group) { 9 | let { anchorPoints, width, height, id } = cfg 10 | if (anchorPoints && anchorPoints.length) { 11 | for (let i = 0, len = anchorPoints.length; i < len; i++) { 12 | let [x, y] = anchorPoints[i] 13 | // 计算Marker中心点坐标 14 | let originX = -width / 2 15 | let originY = -height / 2 16 | let anchorX = x * width + originX 17 | let anchorY = y * height + originY 18 | // 锚点背景 19 | let anchorBgShape = group.findById(id + '_anchor_bg_' + i) 20 | // 锚点 21 | let anchorShape = group.findById(id + '_anchor_' + i) 22 | anchorBgShape.attr({ 23 | x: anchorX, 24 | y: anchorY 25 | }) 26 | anchorShape.attr({ 27 | x: anchorX, 28 | y: anchorY 29 | }) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/edge/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: edge 6 | */ 7 | 8 | import setState from './set-state' 9 | 10 | export default { 11 | setState 12 | } 13 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/edge/set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: set edge state 6 | */ 7 | 8 | import theme from '../../theme' 9 | 10 | export default function(name, value, item) { 11 | const group = item.getContainer() 12 | const shape = group.get('children')[0] // 顺序根据 draw 时确定 13 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 14 | if (name === 'active') { 15 | if (value) { 16 | shape.attr(themeStyle.edgeStyle.active) 17 | } else { 18 | shape.attr(themeStyle.edgeStyle.inactive) 19 | } 20 | } else if (name === 'selected') { 21 | if (value) { 22 | shape.attr(themeStyle.edgeStyle.selected) 23 | } else { 24 | shape.attr(themeStyle.edgeStyle.unselected) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: graph utils 6 | */ 7 | 8 | import node from './node' 9 | import anchor from './anchor' 10 | import edge from './edge' 11 | 12 | /** 13 | * 比较两个对象的内容是否相同(两个对象的键值都相同) 14 | * @param obj1 15 | * @param obj2 16 | * @returns {*} 17 | */ 18 | const isObjectValueEqual = function(obj1, obj2) { 19 | let o1 = obj1 instanceof Object 20 | let o2 = obj2 instanceof Object 21 | // 不是对象的情况 22 | if (!o1 || !o2) { 23 | return obj1 === obj2 24 | } 25 | // 对象的属性(key值)个数不相等 26 | if (Object.keys(obj1).length !== Object.keys(obj2).length) { 27 | return false 28 | } 29 | // 判断每个属性(如果属性值也是对象则需要递归) 30 | for (let attr in obj1) { 31 | let t1 = obj1[attr] instanceof Object 32 | let t2 = obj2[attr] instanceof Object 33 | if (t1 && t2) { 34 | return isObjectValueEqual(obj1[attr], obj2[attr]) 35 | } else if (obj1[attr] !== obj2[attr]) { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | 42 | 43 | /** 44 | * 生成uuid算法,碰撞率低于1/2^^122 45 | * @returns {string} 46 | */ 47 | const generateUUID = function() { 48 | let d = new Date().getTime() 49 | // x 是 0-9 或 a-f 范围内的一个32位十六进制数 50 | let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 51 | let r = (d + Math.random() * 16) % 16 | 0 52 | d = Math.floor(d / 16) 53 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) 54 | }) 55 | return uuid 56 | } 57 | 58 | export default { 59 | node, 60 | anchor, 61 | edge, 62 | // 通用工具类函数 63 | isObjectValueEqual, 64 | generateUUID 65 | } 66 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/node/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: node 6 | */ 7 | 8 | import setState from './set-state' 9 | 10 | export default { 11 | setState 12 | } 13 | -------------------------------------------------------------------------------- /packages/cc-topology/src/utils/node/set-state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/15 4 | * @repository: https://github.com/wenyuan 5 | * @description: set node state 6 | */ 7 | 8 | import theme from '../../theme' 9 | 10 | export default function(name, value, item) { 11 | const group = item.getContainer() 12 | const shape = group.get('children')[0] // 顺序根据 draw 时确定 13 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体 14 | if (name === 'active') { 15 | if (value) { 16 | shape.attr(themeStyle.nodeStyle.active) 17 | } else { 18 | shape.attr(themeStyle.nodeStyle.inactive) 19 | } 20 | } else if (name === 'selected') { 21 | if (value) { 22 | shape.attr(themeStyle.nodeStyle.selected) 23 | } else { 24 | shape.attr(themeStyle.nodeStyle.default) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: wenyuan 3 | * @data: 2019/08/20 4 | * @repository: https://github.com/wenyuan 5 | * @description: 整合所有的组件,对外导出,即一个完整的组件库 6 | */ 7 | 8 | import CCTopology from './cc-topology' 9 | 10 | // 存储组件列表 11 | const components = [ 12 | CCTopology 13 | ] 14 | 15 | // 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册 16 | const install = function(Vue) { 17 | // 判断是否安装 18 | if (install.installed) return 19 | // 遍历注册全局组件 20 | console.info('install----CCEditor: All----') 21 | components.map(component => Vue.component(component.name, component)) 22 | } 23 | 24 | // 判断是否是直接引入文件 25 | if (typeof window !== 'undefined' && window.Vue) { 26 | install(window.Vue) 27 | } 28 | 29 | export default { 30 | // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 31 | install, 32 | // 以下是具体的组件列表 33 | CCTopology 34 | } 35 | 36 | export { 37 | install, 38 | // 以下是具体的组件列表 39 | CCTopology 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // prettier.config.js or .prettierrc.js 返回对象 2 | module.exports = { 3 | eslintIntegration: true, // 开启 eslint 支持 4 | singleQuote: true, // 使用单引号 5 | tabWidth: 2, // 一个tab代表几个空格数,默认为80 6 | useTabs: false, // 是否使用tab进行缩进,默认为false,表示用空格进行缩减 7 | semi: false, // 行位是否使用分号,默认为true 8 | bracketSpacing: true //对象大括号直接是否有空格,默认为true,效果:{ foo: bar } 9 | } 10 | -------------------------------------------------------------------------------- /public/cceditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/public/cceditor.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | CCEditor 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | let path = require('path') 2 | 3 | function resolve(dir) { 4 | return path.join(__dirname, dir) 5 | } 6 | 7 | module.exports = { 8 | // 修改src为examples 9 | pages: { 10 | index: { 11 | entry: 'examples/main.js', 12 | template: 'public/index.html', 13 | filename: 'index.html' 14 | } 15 | }, 16 | // 强制内联CSS 17 | // 默认true: 使用CSS分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用