├── .browserslistrc ├── src ├── assets │ ├── wx.jpg │ ├── point.png │ ├── sample.jpg │ ├── wxqrcode.jpg │ └── svg │ │ ├── 2结束.svg │ │ ├── 1开始.svg │ │ ├── 8个人服务.svg │ │ ├── 17工作包.svg │ │ ├── 5文件数据.svg │ │ ├── 10大数据服务.svg │ │ ├── 11个人源服务.svg │ │ ├── 14描述统计.svg │ │ ├── 6数据校验.svg │ │ ├── 16连接.svg │ │ ├── 15清洗.svg │ │ ├── 18存储.svg │ │ ├── 21封装包.svg │ │ ├── 13测试报告.svg │ │ ├── 7单键查询.svg │ │ ├── 3横向分割.svg │ │ ├── 4纵向分割.svg │ │ ├── 9外部服务.svg │ │ ├── 侧边栏数据集.svg │ │ ├── 侧边栏notebook.svg │ │ ├── 侧边栏数据分析.svg │ │ ├── 12Python.svg │ │ ├── 侧边栏模型跑批.svg │ │ ├── 20执行.svg │ │ ├── 19导出.svg │ │ └── 侧边栏测试任务.svg ├── style │ └── index.less ├── common │ └── until.js ├── store │ └── index.js ├── router │ └── index.js ├── main.js ├── App.vue └── views │ ├── config │ ├── init.js │ ├── data.json │ ├── commonConfig.js │ └── methods.js │ ├── Home.vue │ └── components │ └── node-item.vue ├── babel.config.js ├── public ├── favicon.ico ├── vue.config.js └── index.html ├── vue.config.js ├── .gitignore ├── .eslintrc.js ├── package.json ├── README.md └── LICENSE /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /src/assets/wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-RoadFly/jsplumb-vue-workFlow/HEAD/src/assets/wx.jpg -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-RoadFly/jsplumb-vue-workFlow/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-RoadFly/jsplumb-vue-workFlow/HEAD/src/assets/point.png -------------------------------------------------------------------------------- /src/assets/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-RoadFly/jsplumb-vue-workFlow/HEAD/src/assets/sample.jpg -------------------------------------------------------------------------------- /src/assets/wxqrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-RoadFly/jsplumb-vue-workFlow/HEAD/src/assets/wxqrcode.jpg -------------------------------------------------------------------------------- /src/style/index.less: -------------------------------------------------------------------------------- 1 | html , body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | } -------------------------------------------------------------------------------- /src/common/until.js: -------------------------------------------------------------------------------- 1 | //生成指定长度的唯一ID 2 | export function GenNonDuplicateID(randomLength) { 3 | return Number( 4 | Math.random() 5 | .toString() 6 | .substr(3, randomLength) + Date.now() 7 | ).toString(36); 8 | } -------------------------------------------------------------------------------- /public/vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | chainWebpack: config => { 4 | config 5 | .plugin('html') 6 | .tap(args => { 7 | args[0].title= 'jsplumb绘制流程图' 8 | return args 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/store/index.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 | }, 9 | mutations: { 10 | }, 11 | actions: { 12 | }, 13 | modules: { 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const resolve = dir => { 3 | return path.join(__dirname, dir) 4 | } 5 | module.exports = { 6 | publicPath: './', 7 | chainWebpack: config => { 8 | config.resolve.alias.set('@', resolve('src')) 9 | }, 10 | productionSourceMap: false 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home 12 | } 13 | ] 14 | 15 | const router = new VueRouter({ 16 | routes 17 | }) 18 | 19 | export default router 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | "no-unused-vars": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import Contextmenu from 'vue-contextmenujs' 6 | import ViewUI from 'view-design'; 7 | import 'view-design/dist/styles/iview.css'; 8 | import '@/style/index.less' 9 | 10 | Vue.config.productionTip = false 11 | 12 | Vue.use(Contextmenu); 13 | Vue.use(ViewUI); 14 | new Vue({ 15 | router, 16 | store, 17 | render: h => h(App) 18 | }).$mount('#app') 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/svg/2结束.svg: -------------------------------------------------------------------------------- 1 | 2.结束 -------------------------------------------------------------------------------- /src/assets/svg/1开始.svg: -------------------------------------------------------------------------------- 1 | 1.开始 -------------------------------------------------------------------------------- /src/assets/svg/8个人服务.svg: -------------------------------------------------------------------------------- 1 | 8个人服务 -------------------------------------------------------------------------------- /src/assets/svg/17工作包.svg: -------------------------------------------------------------------------------- 1 | 17工作包 -------------------------------------------------------------------------------- /src/assets/svg/5文件数据.svg: -------------------------------------------------------------------------------- 1 | 5文件数据 -------------------------------------------------------------------------------- /src/assets/svg/10大数据服务.svg: -------------------------------------------------------------------------------- 1 | 10大数据服务 -------------------------------------------------------------------------------- /src/assets/svg/11个人源服务.svg: -------------------------------------------------------------------------------- 1 | 11个人源服务 -------------------------------------------------------------------------------- /src/assets/svg/14描述统计.svg: -------------------------------------------------------------------------------- 1 | 14描述统计 -------------------------------------------------------------------------------- /src/assets/svg/6数据校验.svg: -------------------------------------------------------------------------------- 1 | 6数据校验 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsplumb-vue-wordflow", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "jquery": "^3.5.1", 13 | "jsplumb": "^2.15.5", 14 | "panzoom": "^9.4.1", 15 | "view-design": "^4.5.0-beta.3", 16 | "vue": "^2.6.11", 17 | "vue-click-outside": "^1.1.0", 18 | "vue-contextmenujs": "^1.3.13", 19 | "vue-router": "^3.2.0", 20 | "vuex": "^3.4.0" 21 | }, 22 | "devDependencies": { 23 | "@vue/cli-plugin-babel": "~4.5.0", 24 | "@vue/cli-plugin-eslint": "~4.5.0", 25 | "@vue/cli-plugin-router": "~4.5.0", 26 | "@vue/cli-plugin-vuex": "~4.5.0", 27 | "@vue/cli-service": "~4.5.0", 28 | "babel-eslint": "^10.1.0", 29 | "eslint": "^6.7.2", 30 | "eslint-plugin-vue": "^6.2.2", 31 | "less": "^3.0.4", 32 | "less-loader": "^5.0.0", 33 | "vue-template-compiler": "^2.6.11" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/assets/svg/16连接.svg: -------------------------------------------------------------------------------- 1 | 16连接 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsplumb-vue-wordflow 2 | 3 | 4 | ### 效果图 5 | 6 | ![Image text](https://github.com/Code-RoadFly/jsplumb-vue-wordFlow/blob/main/src/assets/sample.jpg) 7 | 8 | ### [Demo地址](https://code-roadfly.github.io/jsplumb-vue-workFlow/#/) 9 | 10 | 11 | ### 安装依赖 12 | ``` 13 | npm install 14 | ``` 15 | 16 | ### 本地运行 17 | ``` 18 | npm run serve 19 | ``` 20 | ### 现支持的功能: 21 | 22 | - 节点的拖拽(1. 将左侧节点拖入画布,添加节点;2. 在画布拖拽移动节点) 23 | - 在节点之间添加连线 24 | - 画布支持缩放 (滚轮缩放,快捷键+,-缩放) 25 | - 画布的整体平移 26 | - 节点编辑 (双击节点,编辑节点名称) 27 | - 节点,连线的删除 (节点右键删除,连线双击删除) 28 | - 节点对齐辅助线 29 | ### 后续支持功能: 30 | 31 | - 框选功能,按下Ctrl时,可以进行点选 32 | - 添加泳道功能 (泳道可调节大小) 33 | 34 | - ## 赞助和微信交流 35 | 36 | **_如果该项目确实帮助到了您_**,为您节省了时间,请您不吝赞助,请作者喝一杯秋天的奶茶,暖一暖冰冷的心吧,哈哈哈,优化项目真的都是用爱发电^_^,不能打赏的朋友麻烦帮点个免费的赞 37 | 38 | 赞助二维码 39 | 40 | 打赏的朋友欢迎**添加微信**,交流前端开发中遇到的技术、问题和困惑。 41 | 42 | 【**仅添加**打赏过的用户(工作太忙了,请理解),不定期删除屏蔽朋友圈的好友】 43 | 44 | 个人微信 45 | -------------------------------------------------------------------------------- /src/assets/svg/15清洗.svg: -------------------------------------------------------------------------------- 1 | 15清洗 -------------------------------------------------------------------------------- /src/views/config/init.js: -------------------------------------------------------------------------------- 1 | const nodeTypeList = [{ 2 | type: 'start', 3 | typeName: '开始', 4 | nodeName: '开始', 5 | logImg: require('@/assets/svg/1开始.svg'), 6 | log_bg_color: 'rgba(0, 128, 0, 0.2)' 7 | },{ 8 | type: 'end', 9 | typeName: '结束', 10 | nodeName: '结束', 11 | logImg: require('@/assets/svg/2结束.svg'), 12 | log_bg_color: 'rgba(255, 0, 0, 0.2)' 13 | },{ 14 | type: 'dataSet', 15 | typeName: '文件', 16 | nodeName: '文件', 17 | logImg: require('@/assets/svg/5文件数据.svg'), 18 | log_bg_color: 'rgba(0, 128, 0, 0.2)' 19 | },{ 20 | type: 'encode', 21 | typeName: '加密', 22 | nodeName: '加密', 23 | logImg: require('@/assets/svg/6数据校验.svg'), 24 | log_bg_color: 'rgba(163, 117, 233, 0.2)' 25 | },{ 26 | type: 'personService', 27 | typeName: '个人服务', 28 | nodeName: '个人服务', 29 | logImg: require('@/assets/svg/8个人服务.svg'), 30 | log_bg_color: 'rgba(132, 166, 251, 0.2)' 31 | },{ 32 | type: 'arrange', 33 | typeName: '清洗', 34 | nodeName: '清洗', 35 | logImg: require('@/assets/svg/15清洗.svg'), 36 | log_bg_color: 'rgba(250, 205, 81, 0.2)' 37 | }] 38 | 39 | console.log(nodeTypeList) 40 | 41 | export {nodeTypeList}; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Code-RoadFly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/assets/svg/18存储.svg: -------------------------------------------------------------------------------- 1 | 18存储 -------------------------------------------------------------------------------- /src/assets/svg/21封装包.svg: -------------------------------------------------------------------------------- 1 | 21封装包 -------------------------------------------------------------------------------- /src/views/config/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodeList": [{"type":"start","typeName":"开始","nodeName":"开始","id":"34v56ha2l9c000","top":"160px","left":"100px"},{"type":"dataSet","typeName":"文件","nodeName":"文件","id":"5sdjugrcqhc000","top":"160px","left":"315px"},{"type":"encode","typeName":"加密","nodeName":"加密","id":"3atqi5p6oa4000","top":"80px","left":"600px"},{"type":"personService","typeName":"个人服务","nodeName":"个人服务","id":"49vcu89p5q0000","top":"245px","left":"600px"},{"type":"arrange","typeName":"清洗","nodeName":"清洗","id":"1jhiilb0t2tc00","top":"180px","left":"880px"},{"type":"end","typeName":"结束","nodeName":"结束","id":"1ogr3wzy6zhc00","top":"180px","left":"1160px"}], 3 | "lineList": [{"from":"34v56ha2l9c000","to":"5sdjugrcqhc000","label":"连线名称","id":"5n6pp5xqd6s000","Remark":""},{"from":"5sdjugrcqhc000","to":"3atqi5p6oa4000","label":"连线名称","id":"2a0ya9j1kev400","Remark":""},{"from":"5sdjugrcqhc000","to":"49vcu89p5q0000","label":"连线名称","id":"zoisvo5gpvk00","Remark":""},{"from":"3atqi5p6oa4000","to":"1jhiilb0t2tc00","label":"连线名称","id":"4xkb3dju1g0000","Remark":""},{"from":"49vcu89p5q0000","to":"1jhiilb0t2tc00","label":"连线名称","id":"ldc917l47w000","Remark":""},{"from":"1jhiilb0t2tc00","to":"1ogr3wzy6zhc00","label":"连线名称","id":"478galw3u34000","Remark":""}] 4 | } -------------------------------------------------------------------------------- /src/assets/svg/13测试报告.svg: -------------------------------------------------------------------------------- 1 | 13测试报告 -------------------------------------------------------------------------------- /src/assets/svg/7单键查询.svg: -------------------------------------------------------------------------------- 1 | 7单键查询 -------------------------------------------------------------------------------- /src/assets/svg/3横向分割.svg: -------------------------------------------------------------------------------- 1 | 3.横向分割 -------------------------------------------------------------------------------- /src/assets/svg/4纵向分割.svg: -------------------------------------------------------------------------------- 1 | 4.纵向分割 -------------------------------------------------------------------------------- /src/assets/svg/9外部服务.svg: -------------------------------------------------------------------------------- 1 | 9外部服务 -------------------------------------------------------------------------------- /src/assets/svg/侧边栏数据集.svg: -------------------------------------------------------------------------------- 1 | 侧边栏数据集 -------------------------------------------------------------------------------- /src/assets/svg/侧边栏notebook.svg: -------------------------------------------------------------------------------- 1 | 侧边栏notebook -------------------------------------------------------------------------------- /src/assets/svg/侧边栏数据分析.svg: -------------------------------------------------------------------------------- 1 | 侧边栏数据分析 -------------------------------------------------------------------------------- /src/assets/svg/12Python.svg: -------------------------------------------------------------------------------- 1 | 12Python -------------------------------------------------------------------------------- /src/assets/svg/侧边栏模型跑批.svg: -------------------------------------------------------------------------------- 1 | 侧边栏模型跑批 -------------------------------------------------------------------------------- /src/assets/svg/20执行.svg: -------------------------------------------------------------------------------- 1 | 20执行 -------------------------------------------------------------------------------- /src/views/config/commonConfig.js: -------------------------------------------------------------------------------- 1 | export const jsplumbSetting = { 2 | grid: [10, 10], 3 | // 动态锚点、位置自适应 4 | Anchors: [ 5 | "TopCenter", 6 | "RightMiddle", 7 | "BottomCenter", 8 | "LeftMiddle" 9 | ], 10 | Container: "flow", 11 | // 连线的样式 StateMachine、Flowchart,有四种默认类型:Bezier(贝塞尔曲线),Straight(直线),Flowchart(流程图),State machine(状态机) 12 | Connector: ["Flowchart", { cornerRadius: 5, alwaysRespectStubs: true, stub: 5 }], 13 | // 鼠标不能拖动删除线 14 | ConnectionsDetachable: false, 15 | // 删除线的时候节点不删除 16 | DeleteEndpointsOnDetach: false, 17 | // 连线的端点 18 | // Endpoint: ["Dot", {radius: 5}], 19 | Endpoint: [ 20 | "Rectangle", 21 | { 22 | height: 10, 23 | width: 10 24 | } 25 | ], 26 | // 线端点的样式 27 | EndpointStyle: { 28 | fill: "rgba(255,255,255,0)", 29 | outlineWidth: 1 30 | }, 31 | LogEnabled: false, //是否打开jsPlumb的内部日志记录 32 | // 绘制线 33 | PaintStyle: { 34 | stroke: "#409eff", 35 | strokeWidth: 2 36 | }, 37 | HoverPaintStyle: { stroke: "#ff00cc", strokeWidth: 2 }, 38 | // 绘制箭头 39 | Overlays: [ 40 | [ 41 | "Arrow", 42 | { 43 | width: 8, 44 | length: 8, 45 | location: 1 46 | } 47 | ] 48 | ], 49 | RenderMode: "svg" 50 | } 51 | 52 | // jsplumb连接参数 53 | export const jsplumbConnectOptions = { 54 | isSource: true, 55 | isTarget: true, 56 | // 动态锚点、提供了4个方向 Continuous、AutoDefault 57 | anchor: [ 58 | "TopCenter", 59 | "RightMiddle", 60 | "BottomCenter", 61 | "LeftMiddle" 62 | ] 63 | } 64 | 65 | export const jsplumbSourceOptions = { 66 | filter: ".node-anchor", //触发连线的区域 67 | /*"span"表示标签,".className"表示类,"#id"表示元素id*/ 68 | filterExclude: false, 69 | anchor: [ 70 | "TopCenter", 71 | "RightMiddle", 72 | "BottomCenter", 73 | "LeftMiddle" 74 | ], 75 | allowLoopback: false 76 | } 77 | 78 | export const jsplumbTargetOptions = { 79 | filter: ".node-anchor", 80 | /*"span"表示标签,".className"表示类,"#id"表示元素id*/ 81 | filterExclude: false, 82 | anchor: [ 83 | "TopCenter", 84 | "RightMiddle", 85 | "BottomCenter", 86 | "LeftMiddle" 87 | ], 88 | allowLoopback: false 89 | } -------------------------------------------------------------------------------- /src/assets/svg/19导出.svg: -------------------------------------------------------------------------------- 1 | 19导出 -------------------------------------------------------------------------------- /src/assets/svg/侧边栏测试任务.svg: -------------------------------------------------------------------------------- 1 | 侧边栏测试任务 -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 89 | 90 | 149 | 150 | -------------------------------------------------------------------------------- /src/views/components/node-item.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 123 | 124 | -------------------------------------------------------------------------------- /src/views/config/methods.js: -------------------------------------------------------------------------------- 1 | import panzoom from "panzoom"; 2 | import { GenNonDuplicateID } from "@/common/until"; 3 | 4 | const methods = { 5 | init() { 6 | this.jsPlumb.ready(() => { 7 | // 导入默认配置 8 | this.jsPlumb.importDefaults(this.jsplumbSetting); 9 | //完成连线前的校验 10 | this.jsPlumb.bind("beforeDrop", evt => { 11 | let res = () => { } //此处可以添加是否创建连接的校验, 返回 false 则不添加; 12 | return res 13 | }) 14 | // 连线创建成功后,维护本地数据 15 | this.jsPlumb.bind("connection", evt => { 16 | this.addLine(evt) 17 | }); 18 | //连线双击删除事件 19 | this.jsPlumb.bind("dblclick",(conn, originalEvent) => { 20 | this.confirmDelLine(conn) 21 | }) 22 | //断开连线后,维护本地数据 23 | this.jsPlumb.bind("connectionDetached", evt => { 24 | this.deleLine(evt) 25 | }) 26 | this.loadEasyFlow(); 27 | // 会使整个jsPlumb立即重绘。 28 | this.jsPlumb.setSuspendDrawing(false, true); 29 | }); 30 | this.initPanZoom(); 31 | }, 32 | // 加载流程图 33 | loadEasyFlow() { 34 | // 初始化节点 35 | for (let i = 0; i < this.data.nodeList.length; i++) { 36 | let node = this.data.nodeList[i]; 37 | // 设置源点,可以拖出线连接其他节点 38 | this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions); 39 | // // 设置目标点,其他源点拖出的线可以连接该节点 40 | this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions); 41 | // this.jsPlumb.draggable(node.id); 42 | this.draggableNode(node.id) 43 | } 44 | 45 | // 初始化连线 46 | this.jsPlumb.unbind("connection"); //取消连接事件 47 | for (let i = 0; i < this.data.lineList.length; i++) { 48 | let line = this.data.lineList[i]; 49 | this.jsPlumb.connect( 50 | { 51 | source: line.from, 52 | target: line.to 53 | }, 54 | this.jsplumbConnectOptions 55 | ); 56 | } 57 | this.jsPlumb.bind("connection", evt => { 58 | let from = evt.source.id; 59 | let to = evt.target.id; 60 | this.data.lineList.push({ 61 | from: from, 62 | to: to, 63 | label: "连线名称", 64 | id: GenNonDuplicateID(8), 65 | Remark: "" 66 | }); 67 | }); 68 | }, 69 | draggableNode(nodeId) { 70 | this.jsPlumb.draggable(nodeId, { 71 | grid: this.commonGrid, 72 | drag: (params) => { 73 | this.alignForLine(nodeId, params.pos) 74 | }, 75 | start: () => { 76 | 77 | }, 78 | stop: (params) => { 79 | this.auxiliaryLine.isShowXLine = false 80 | this.auxiliaryLine.isShowYLine = false 81 | this.changeNodePosition(nodeId, params.pos) 82 | } 83 | }) 84 | }, 85 | //移动节点时,动态显示对齐线 86 | alignForLine(nodeId, position) { 87 | let showXLine = false, showYLine = false 88 | this.data.nodeList.some(el => { 89 | if(el.id !== nodeId && el.left == position[0]+'px') { 90 | this.auxiliaryLinePos.x = position[0] + 60; 91 | showYLine = true 92 | } 93 | if(el.id !== nodeId && el.top == position[1]+'px') { 94 | this.auxiliaryLinePos.y = position[1] + 20; 95 | showXLine = true 96 | } 97 | }) 98 | this.auxiliaryLine.isShowYLine = showYLine 99 | this.auxiliaryLine.isShowXLine = showXLine 100 | }, 101 | changeNodePosition(nodeId, pos) { 102 | this.data.nodeList.some(v => { 103 | if(nodeId == v.id) { 104 | v.left = pos[0] +'px' 105 | v.top = pos[1] + 'px' 106 | return true 107 | }else { 108 | return false 109 | } 110 | }) 111 | }, 112 | drag(ele, item) { 113 | this.currentItem = item; 114 | }, 115 | drop(event) { 116 | const containerRect = this.jsPlumb.getContainer().getBoundingClientRect(); 117 | const scale = this.getScale(); 118 | let left = (event.pageX - containerRect.left -60) / scale; 119 | let top = (event.pageY - containerRect.top -20) / scale; 120 | 121 | var temp = { 122 | ...this.currentItem, 123 | id: GenNonDuplicateID(8), 124 | top: (Math.round(top/20))*20 + "px", 125 | left: (Math.round(left/20))*20 + "px" 126 | }; 127 | this.addNode(temp); 128 | }, 129 | addLine(line) { 130 | let from = line.source.id; 131 | let to = line.target.id; 132 | this.data.lineList.push({ 133 | from: from, 134 | to: to, 135 | label: "连线名称", 136 | id: GenNonDuplicateID(8), 137 | Remark: "" 138 | }); 139 | }, 140 | confirmDelLine(line) { 141 | this.$Modal.confirm({ 142 | title: '删除连线', 143 | content: "

确认删除该连线?

", 144 | onOk: () => { 145 | this.jsPlumb.deleteConnection(line) 146 | } 147 | }) 148 | }, 149 | deleLine(line) { 150 | this.data.lineList.forEach((item, index) => { 151 | if(item.from === line.sourceId && item.to === line.targetId) { 152 | this.data.lineList.splice(index, 1) 153 | } 154 | }) 155 | }, 156 | // dragover默认事件就是不触发drag事件,取消默认事件后,才会触发drag事件 157 | allowDrop(event) { 158 | event.preventDefault(); 159 | }, 160 | getScale() { 161 | let scale1; 162 | if (this.jsPlumb.pan) { 163 | const { scale } = this.jsPlumb.pan.getTransform(); 164 | scale1 = scale; 165 | } else { 166 | const matrix = window.getComputedStyle(this.jsPlumb.getContainer()).transform; 167 | scale1 = matrix.split(", ")[3] * 1; 168 | } 169 | this.jsPlumb.setZoom(scale1); 170 | return scale1; 171 | }, 172 | // 添加新的节点 173 | addNode(temp) { 174 | this.data.nodeList.push(temp); 175 | this.$nextTick(() => { 176 | this.jsPlumb.makeSource(temp.id, this.jsplumbSourceOptions); 177 | this.jsPlumb.makeTarget(temp.id, this.jsplumbTargetOptions); 178 | this.draggableNode(temp.id) 179 | }); 180 | }, 181 | 182 | initPanZoom() { 183 | const mainContainer = this.jsPlumb.getContainer(); 184 | const mainContainerWrap = mainContainer.parentNode; 185 | const pan = panzoom(mainContainer, { 186 | smoothScroll: false, 187 | bounds: true, 188 | // autocenter: true, 189 | zoomDoubleClickSpeed: 1, 190 | minZoom: 0.5, 191 | maxZoom: 2, 192 | //设置滚动缩放的组合键,默认不需要组合键 193 | beforeWheel: (e) => { 194 | console.log(e) 195 | // let shouldIgnore = !e.ctrlKey 196 | // return shouldIgnore 197 | }, 198 | beforeMouseDown: function(e) { 199 | // allow mouse-down panning only if altKey is down. Otherwise - ignore 200 | var shouldIgnore = e.ctrlKey; 201 | return shouldIgnore; 202 | } 203 | }); 204 | this.jsPlumb.mainContainerWrap = mainContainerWrap; 205 | this.jsPlumb.pan = pan; 206 | // 缩放时设置jsPlumb的缩放比率 207 | pan.on("zoom", e => { 208 | const { x, y, scale } = e.getTransform(); 209 | this.jsPlumb.setZoom(scale); 210 | //根据缩放比例,缩放对齐辅助线长度和位置 211 | this.auxiliaryLinePos.width = (1/scale) * 100 + '%' 212 | this.auxiliaryLinePos.height = (1/scale) * 100 + '%' 213 | this.auxiliaryLinePos.offsetX = -(x/scale) 214 | this.auxiliaryLinePos.offsetY = -(y/scale) 215 | }); 216 | pan.on("panend", (e) => { 217 | const {x, y, scale} = e.getTransform(); 218 | this.auxiliaryLinePos.width = (1/scale) * 100 + '%' 219 | this.auxiliaryLinePos.height = (1/scale) * 100 + '%' 220 | this.auxiliaryLinePos.offsetX = -(x/scale) 221 | this.auxiliaryLinePos.offsetY = -(y/scale) 222 | }) 223 | 224 | // 平移时设置鼠标样式 225 | mainContainerWrap.style.cursor = "grab"; 226 | mainContainerWrap.addEventListener("mousedown", function wrapMousedown() { 227 | this.style.cursor = "grabbing"; 228 | mainContainerWrap.addEventListener("mouseout", function wrapMouseout() { 229 | this.style.cursor = "grab"; 230 | }); 231 | }); 232 | mainContainerWrap.addEventListener("mouseup", function wrapMouseup() { 233 | this.style.cursor = "grab"; 234 | }); 235 | }, 236 | 237 | setNodeName(nodeId, name) { 238 | this.data.nodeList.some((v) => { 239 | if(v.id === nodeId) { 240 | v.nodeName = name 241 | return true 242 | }else { 243 | return false 244 | } 245 | }) 246 | }, 247 | 248 | //删除节点 249 | deleteNode(node) { 250 | this.data.nodeList.some((v,index) => { 251 | if(v.id === node.id) { 252 | this.data.nodeList.splice(index, 1) 253 | this.jsPlumb.remove(v.id) 254 | return true 255 | }else { 256 | return false 257 | } 258 | }) 259 | }, 260 | 261 | //更改连线状态 262 | changeLineState(nodeId, val) { 263 | console.log(val) 264 | let lines = this.jsPlumb.getAllConnections() 265 | lines.forEach(line => { 266 | if(line.targetId === nodeId || line.sourceId === nodeId) { 267 | if(val) { 268 | line.canvas.classList.add('active') 269 | }else { 270 | line.canvas.classList.remove('active') 271 | } 272 | } 273 | }) 274 | }, 275 | 276 | //初始化节点位置 (以便对齐,居中) 277 | fixNodesPosition() { 278 | if(this.data.nodeList && this.$refs.flowWrap) { 279 | const nodeWidth = 120 280 | const nodeHeight = 40 281 | let wrapInfo = this.$refs.flowWrap.getBoundingClientRect() 282 | let maxLeft = 0, minLeft = wrapInfo.width, maxTop = 0, minTop = wrapInfo.height; 283 | let nodePoint = { 284 | left: 0, 285 | right: 0, 286 | top: 0, 287 | bottom: 0 288 | } 289 | let fixTop = 0, fixLeft = 0; 290 | this.data.nodeList.forEach(el => { 291 | let top = Number(el.top.substring(0, el.top.length -2)) 292 | let left = Number(el.left.substring(0, el.left.length -2)) 293 | maxLeft = left > maxLeft ? left : maxLeft 294 | minLeft = left < minLeft ? left : minLeft 295 | maxTop = top > maxTop ? top : maxTop 296 | minTop = top < minTop ? top : minTop 297 | }) 298 | nodePoint.left = minLeft 299 | nodePoint.right = wrapInfo.width - maxLeft - nodeWidth 300 | nodePoint.top = minTop 301 | nodePoint.bottom = wrapInfo.height - maxTop - nodeHeight; 302 | 303 | fixTop = nodePoint.top !== nodePoint.bottom ? (nodePoint.bottom - nodePoint.top) / 2 : 0; 304 | fixLeft = nodePoint.left !== nodePoint.right ? (nodePoint.right - nodePoint.left) / 2 : 0; 305 | 306 | this.data.nodeList.map(el => { 307 | let top = Number(el.top.substring(0, el.top.length - 2)) + fixTop; 308 | let left = Number(el.left.substring(0, el.left.length - 2)) + fixLeft; 309 | el.top = (Math.round(top/20))* 20 + 'px' 310 | el.left = (Math.round(left/20))*20 + 'px' 311 | }) 312 | } 313 | }, 314 | } 315 | 316 | export default methods; --------------------------------------------------------------------------------