├── .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 |
2 |
39 |
40 | 打赏的朋友欢迎**添加微信**,交流前端开发中遇到的技术、问题和困惑。
41 |
42 | 【**仅添加**打赏过的用户(工作太忙了,请理解),不定期删除屏蔽朋友圈的好友】
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/assets/svg/15清洗.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/assets/svg/21封装包.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/assets/svg/7单键查询.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/3横向分割.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/4纵向分割.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/9外部服务.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/侧边栏数据集.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/侧边栏notebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/侧边栏数据分析.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/12Python.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/侧边栏模型跑批.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/20执行.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/assets/svg/侧边栏测试任务.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 | 确认删除该连线?
", 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; --------------------------------------------------------------------------------