Odoo|当我在Odoo用画布创建流程图
在Odoo14中,如何在form表单中最上面插入一个Canvas的画布控件呢?
首先,我发现在Odoo中,form表单会在每次重置后只进入一次form视图的init和renderButtons等相关的初始化视图方法。但是在二次渲染视图时,会出现"不触发"和"找不到相关DOM元素"的问题。
为了解决这个问题,我们需要使用form视图的Renderer配置属性,并改写Renderer中的内容,因为Renderer是百分百会进入和触发的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ExtendFromController = FormController.extend({ events: Object .assign({}, FormController.prototype.events, { 'click .bump-form-search' : '_bump_form_search' , 'click .reset-form-bump' : '_reset_form_bump' , 'click .add-bump-form' : '_add_bump_inventory' , }), / * * * @override * / init: function (parent, model, renderer, params) { this._super. apply (this, arguments); this.initialState.context }, }) var bump_formView = FormView.extend({ config: _.extend({}, FormView.prototype.config, { Controller: ExtendFromController, Renderer: GroupList }), _extractParamsFromAction:function (){ var params = this._super. apply (this, arguments); return params } view_registry.add( 'borrow_bump_form_list' , bump_formView); return bump_formView; }) |
代码仅仅作为示范,大家可以自己尝试检验一下
改写Renderer部分的代码,记得引入相关模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | / * * 记得引入 * / var FormController = require( 'web.FormController' ); var FormRenderer = require( 'web.FormRenderer' ); var GroupList = FormRenderer.extend({ / * * * @private * @param { Object } node * @returns {jQueryElement} * / _renderTagNotebook: function (node) { var self = this; var $headers = $( '<ul class="nav nav-tabs">' ); var $pages = $( '<div class="tab-content">' ); / / renderedTabs is used to aggregate the generated $headers and $pages / / alongside their node, so that their modifiers can be registered once / / all tabs have been rendered, to ensure that the first visible tab / / is correctly activated var renderedTabs = _. map (node.children, function (child, index) { var pageID = _.uniqueId( 'notebook_page_' ); var $header = self ._renderTabHeader(child, pageID); var $page = self ._renderTabPage(child, pageID); self ._handleAttributes($header, child); $headers.append($header); $pages.append($page); return { $header: $header, $page: $page, node: child, }; }); / / register the modifiers for each tab _.each(renderedTabs, function (tab) { self ._registerModifiers(tab.node, self .state, tab.$header, { callback: function (element, modifiers) { / / if the active tab is invisible, activate the first visible tab instead var $link = element.$el.find( '.nav-link' ); if (modifiers.invisible && $link.hasClass( 'active' )) { $link.removeClass( 'active' ); tab.$page.removeClass( 'active' ); self .inactiveNotebooks.push(renderedTabs); } if (!modifiers.invisible) { / / make first page active if there is only one page to display var $visibleTabs = $headers.find( 'li:not(.o_invisible_modifier)' ); if ($visibleTabs.length = = = 1 ) { self .inactiveNotebooks.push(renderedTabs); } } }, }); }); this._activateFirstVisibleTab(renderedTabs); var $notebookHeaders = $( '<div class="o_notebook_headers">' ).append($headers); var $notebook = $( '<div class="o_notebook">' ).append($notebookHeaders, $pages); $notebook[ 0 ].dataset.name = node.attrs.name || '_default_' ; this._registerModifiers(node, this.state, $notebook); this._handleAttributes($notebook, node); bump_form_process_status = this.state.data.process_status; states = this.state.data apply_id = this.state.data.apply_id.res_id / * * 在这里找相关的DOM和触发相关是业务操作,记得代码方法写在 * / $(document).ready(() = > { _this.$buttons.find( '.my_bump_buttons' ).hide() this.beforeCanvas(); / / 在这里调用创建Canvas的视图 }); return $notebook; }, / * * 代码方法写在这里 * / var nodes = [ { x: 100 , y: 100 , text: '开始' }, { x: 300 , y: 100 , text: '步骤1' }, { x: 300 , y: 200 , text: '步骤2' }, { x: 100 , y: 200 , text: '步骤3' }, { x: 200 , y: 300 , text: '结束' } ]; beforeCanvas:function (){ let canvasWidth = parseInt($( '.o_form_sheet' ).innerWidth()) - 400 let canvasBox = `<div class = "step-line step-box" ><canvas id = "flowchartCanvas" width = "${canvasWidth}" height = "400" >< / canvas>< / div>` $( '.o_form_sheet' ).before(canvasBox) this.canvasInit(); }, canvasInit:function (){ var canvas = this.$( '#flowchartCanvas' )[ 0 ]; var context = canvas.getContext( '2d' ); this.drawFlowchart(); }, / / 绘制节点 drawNode:function(x, y, text) { var canvas = this.$( '#flowchartCanvas' )[ 0 ]; var context = canvas.getContext( '2d' ); context.beginPath(); context.arc(x, y, 30 , 0 , 2 * Math.PI); context.stroke(); context.font = '14px Arial' ; context.textAlign = 'center' ; context.fillText(text, x, y + 5 ); }, / / 绘制连线 drawConnections:function() { var canvas = this.$( '#flowchartCanvas' )[ 0 ]; var context = canvas.getContext( '2d' ); context.beginPath(); context.moveTo(nodesCanvas[ 0 ].x, nodesCanvas[ 0 ].y); for (var i = 1 ; i < nodesCanvas.length; i + + ) { context.lineTo(nodesCanvas[i].x, nodesCanvas[i].y); } context.lineWidth = 5 ; context.strokeStyle = 'blue' context.lineCap = 'round' ; context.stroke(); }, / / 绘制节点和连线 drawFlowchart:function() { let self = this; var canvas = this.$( '#flowchartCanvas' )[ 0 ]; var context = canvas.getContext( '2d' ); / / 定义流程图的节点 / / 清空画布 context.clearRect( 0 , 0 , canvas.width, canvas.height); / / 绘制节点 nodesCanvas.forEach(function (node) { self .drawNode(node.x, node.y, node.text); }); / / 绘制连线 self .drawConnections(); }, / / 在节点上添加点击事件,点击时移动节点位置这个功能是尝试给相关节点添加事件,就和流程图一样。记得和上面一样封装在function中 canvas.addEventListener( 'mousedown' , function (e) { var mouseX = e.clientX - canvas.offsetLeft; var mouseY = e.clientY - canvas.offsetTop; for (var i = 0 ; i < nodes.length; i + + ) { var node = nodes[i]; var distance = Math.sqrt(Math. pow (mouseX - node.x, 2 ) + Math. pow (mouseY - node.y, 2 )); if (distance < = 30 ) { var offsetX = mouseX - node.x; var offsetY = mouseY - node.y; canvas.addEventListener( 'mousemove' , moveNode); canvas.addEventListener( 'mouseup' , stopMovingNode); canvas.addEventListener( 'mouseout' , stopMovingNode); function moveNode(e) { node.x = e.clientX - canvas.offsetLeft - offsetX; node.y = e.clientY - canvas.offsetTop - offsetY; drawFlowchart(); } function stopMovingNode() { canvas.removeEventListener( 'mousemove' , moveNode); canvas.removeEventListener( 'mouseup' , stopMovingNode); canvas.removeEventListener( 'mouseout' , stopMovingNode); } break ; } } }); }) |
会得到一个类似这样的canvas视图。这里我只是举个例子,关于如何绘制canvas,大家需要自己多下功夫,查看API和案例。
添加了事件后,是可以拖拽任意节点变形的。
你也可以使用别人封装好的JS代码。记得将它命名封装到一个单独的.js文件中,然后在你的文件中提前引入。
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | / * * * 流向图组件,mouyao * / varopsDirectionMap = function(option){ this.const(option); this.init(); }; / * * 配置项引入 * / opsDirectionMap.prototype.const = function(option){ this.r = option.r|| 4 ; / / 节点半径 this.config = option; this.data = option.data||[]; this.mLeft = option.mLeft|| - 20 ; / / 起点距左边距离 this.space = option.space|| 18 * this.r; / / 节点之间距离 this.angle = 2 * this.r; / / 分支上下之间的高度 this.nodeArr = []; / / 存储所有的圆点的信息和坐标 }; / * * 配置项引入 * / opsDirectionMap.prototype.init = function(){ varmyCanvas = document.getElementById(this.config.placeId); this.resolveVagueProblem(myCanvas); this.render(myCanvas); }; / * * 判定是否数组 * / opsDirectionMap.prototype.isArrayFn = function(o) { returnObject.prototype.toString.call(o) = = = '[object Array]' ; }; / * * 根据当前节点的执行状态,渲染圆点前的线条的颜色 * / opsDirectionMap.prototype.drawDashLine = function(ctx, x1, y1, x2, y2,data,index){ ctx.lineWidth = 1 ; ctx.beginPath(); varx = (x2 - x1) / 2 ; if (index> 0 &&!this.isArrayFn(this.data[index - 1 ])){ ctx.moveTo(x1,y1); ctx.lineTo(x1 + x ,y1); ctx.moveTo(x1 + x,y1); ctx.lineTo(x1 + x ,y2); ctx.moveTo(x1 + x,y2); ctx.lineTo(x2 ,y2); }elseif(index> 0 &&this.isArrayFn(this.data[index - 1 ])){ ctx.moveTo(x1,y1); ctx.lineTo(x1 + x ,y1); ctx.moveTo(x1 + x,y1); ctx.lineTo(x1 + x ,y2); ctx.moveTo(x1 + x,y2); ctx.lineTo(x2 ,y2); } else { if (index! = = 0 ){ / / 删除第一个圆点的连接线 ctx.moveTo(x1,y1); ctx.lineTo(x2 ,y2); } } if (data.isExcuted = = = true){ ctx.strokeStyle = "#009aff" ; }elseif(data.isExcuted = = = false&&index! = = 0 &&this.data[index - 1 ].isExcuted = = = true&&!this.isArrayFn(this.data[index - 1 ])){ ctx.strokeStyle = "#009aff" ; }elseif(data.isExcuted = = = false&&this.isArrayFn(this.data[index - 1 ])){ / / 如果上一个元素是数组 vararr = []; this.data[index - 1 ].some(function(item){ if (item.isExcuted = = = true){ arr.push(true); } }); if (arr.length = = = (this.data[index - 1 ]).length){ ctx.strokeStyle = "#009aff" ; } else { ctx.strokeStyle = "#959595" ; } } else { ctx.strokeStyle = "#959595" ; } ctx.stroke(); }; / * * 绘制线条,圆点,圆心,和说明文字 * / opsDirectionMap.prototype.render = function(canvas){ varthis_ = this; this_.canvas = canvas; varctx = canvas.getContext( "2d" ); / / 上下文 this_.ctx = ctx; varx = this_.mLeft; / / 每个操作的对象的坐标 / / var y = canvas.height / 2 ; / / x偏移量:this_.r * Math.sin(( 90 - itemY) * Math.PI / 180 ) / / y偏移量:this_.r * Math.cos(( 90 - itemY) * Math.PI / 180 ) vary = 50 ; this_.data.forEach(function(item, index){ if (!(item instanceofArray)){ x = index = = 0 ?x:(x + this_.space); if ((index - 1 )> = 0 && this_.data[index - 1 ] instanceofArray){ vararr = this_.data[index - 1 ]; if (arr.length % 2 = = 0 ){ varitemY = this_.angle; for (vari = 0 ;i<arr.length / 2 ;i + + ){ this_.drawDashLine(ctx, x - this_.space - this_.r + this_.r * Math.sin(( 90 - itemY) * Math.PI / 180 ), y - Math.tan(itemY * Math.PI / 180 ) * this_.space + this_.r * Math.cos(( 90 - itemY) * Math.PI / 180 ), x, y,item,index); itemY = itemY + this_.angle; } varitemY = this_.angle; for (vari = 0 ;i<arr.length / 2 ;i + + ){ this_.drawDashLine(ctx, x - this_.space - this_.r + this_.r * Math.sin(( 90 - itemY) * Math.PI / 180 ), y + Math.tan(itemY * Math.PI / 180 ) * this_.space - this_.r * Math.cos(( 90 - itemY) * Math.PI / 180 ), x, y,item,index); itemY = itemY + this_.angle; } } else { varitemY = 0 ; for (vari = 0 ;i<parseInt(arr.length / 2 ) + 1 ;i + + ){ console.log(arr[i]); this_.drawDashLine(ctx, x - this_.space - this_.r + this_.r * Math.sin(( 90 - itemY) * Math.PI / 180 ), y - Math.tan(itemY * Math.PI / 180 ) * this_.space + this_.r * Math.cos(( 90 - itemY) * Math.PI / 180 ), x, y,item,index); itemY = itemY + this_.angle; } varitemY = this_.angle; for (vari = 0 ;i<parseInt(arr.length / 2 );i + + ){ this_.drawDashLine(ctx, x - this_.space - this_.r + this_.r * Math.sin(( 90 - itemY) * Math.PI / 180 ), y + Math.tan(itemY * Math.PI / 180 ) * this_.space - this_.r * Math.cos(( 90 - itemY) * Math.PI / 180 ), x, y,item,index); itemY = itemY + this_.angle; } } } if (index = = 0 ){ ctx.moveTo(x,y); x = x + this_.space; } / / 绘制非数组直线 if (!((index - 1 )> = 0 && this_.data[index - 1 ] instanceofArray)){ this_.drawDashLine(ctx,x - this_.space, y, x, y,item,index); } ctx.moveTo(x + 2 * this_.r,y); / / 绘制节点,画圆 ctx.arc(x + this_.r,y,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.r,y:y,data:item}); ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); / / 节点标题note ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.noteColor; / / 字体颜色 / / 节点的名称设置 ctx.fillText(item.noteName,x + this_.r,y - this_.r - 10 ); ctx.fillStyle = "black" ; / / 字体颜色 x = x + 2 * this_.r; ctx.stroke(); ctx.beginPath(); ctx.moveTo(x,y); } else { / / 数组 if (!(this_.data[index - 1 ] instanceofArray)){ / / 上一级不是数组 varitemY = 0 ; if (item.length % 2 = = 0 ){ / / 偶数 itemY = this_.angle; vardataArr = item. slice ( 0 ,item.length / 2 ).reverse(); for (vari = 0 ;i<dataArr.length;i + + ){ this_.drawDashLine(ctx, x, y, x + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * (this_.space),this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space,y:y - Math.tan(itemY * Math.PI / 180 ) * (this_.space),data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = dataArr[i].isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * (this_.space) - this_.r - 10 ); ctx.fill(); ctx.moveTo(x + this_.r,y); itemY = itemY + this_.angle; } itemY = this_.angle; vardataArr = item. slice (item.length / 2 ,item.length); for (vari = 0 ;i<dataArr.length;i + + ){ this_.drawDashLine(ctx, x, y, x + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * (this_.space),this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space,y:y + Math.tan(itemY * Math.PI / 180 ) * (this_.space),data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = dataArr[i].isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * (this_.space) + this_.r + 10 ); ctx.fill(); itemY = itemY + this_.angle; } } else { / / 奇数 vardataArr = item. slice ( 0 ,parseInt(item.length / 2 ) + 1 ).reverse(); for (vari = 0 ;i<dataArr.length;i + + ){ this_.drawDashLine(ctx, x, y, x + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space,y:y - Math.tan(itemY * Math.PI / 180 ) * this_.space,data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor;; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * this_.space - this_.r - 10 ); ctx.fill(); itemY = itemY + this_.angle; } itemY = this_.angle; vardataArr = item. slice (parseInt(item.length / 2 ) + 1 ,item.length); for (vari = 0 ;i<dataArr.length;i + + ){ this_.drawDashLine(ctx, x, y, x + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space,y:y + Math.tan(itemY * Math.PI / 180 ) * this_.space,data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * this_.space + this_.r + 10 ); ctx.fill(); itemY = itemY + this_.angle; } } ctx.stroke(); ctx.beginPath(); x = x + this_.space + this_.r; ctx.moveTo(x,y); } else { / / 上一级是数组 if (item.length % 2 = = 0 ){ / / 偶数 varitemY = this_.angle; vardataArr = item. slice ( 0 ,item.length / 2 ).reverse(); for (vari = 0 ;i<dataArr.length;i + + ){ ctx.moveTo(x,y - Math.tan(itemY * Math.PI / 180 ) * this_.space); this_.drawDashLine(ctx, x, y - Math.tan(itemY * Math.PI / 180 ) * this_.space, x + this_.r + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space + this_.r, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space + this_.r,y:y - Math.tan(itemY * Math.PI / 180 ) * this_.space,data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space + this_.r, y - Math.tan(itemY * Math.PI / 180 ) * this_.space - this_.r - 10 ); ctx.fill(); itemY = itemY + this_.angle; } varitemY = this_.angle; vardataArr = item. slice (item.length / 2 ,item.length); for (vari = 0 ;i<dataArr.length;i + + ){ ctx.moveTo(x,y + Math.tan(itemY * Math.PI / 180 ) * this_.space); this_.drawDashLine(ctx, x, y + Math.tan(itemY * Math.PI / 180 ) * this_.space, x + this_.r + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space + this_.r, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space + this_.r,y:y + Math.tan(itemY * Math.PI / 180 ) * this_.space,data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space + this_.r, y + Math.tan(itemY * Math.PI / 180 ) * this_.space + this_.r + 10 ); ctx.fill(); itemY = itemY + this_.angle; } } else { / / 奇数 varitemY = 0 ; vardataArr = item. slice ( 0 ,parseInt(item.length / 2 ) + 1 ).reverse(); for (vari = 0 ;i<dataArr.length;i + + ){ ctx.moveTo(x,y - Math.tan(itemY * Math.PI / 180 ) * this_.space); this_.drawDashLine(ctx, x, y - Math.tan(itemY * Math.PI / 180 ) * this_.space, x + this_.r + this_.space, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space + this_.r, y - Math.tan(itemY * Math.PI / 180 ) * this_.space,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space + this_.r,y:y - Math.tan(itemY * Math.PI / 180 ) * this_.space,data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space + this_.r, y - Math.tan(itemY * Math.PI / 180 ) * this_.space - this_.r - 10 ); ctx.fill(); itemY = itemY + this_.angle; } varitemY = this_.angle; vardataArr = item. slice (parseInt(item.length / 2 ) + 1 ,item.length); for (vari = 0 ;i<dataArr.length;i + + ){ ctx.moveTo(x,y + Math.tan(itemY * Math.PI / 180 ) * this_.space); this_.drawDashLine(ctx, x, y + Math.tan(itemY * Math.PI / 180 ) * this_.space, x + this_.r + this_.space, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,dataArr[i],index); ctx.beginPath(); ctx.arc(x + this_.space + this_.r, y + Math.tan(itemY * Math.PI / 180 ) * this_.space,this_.r, 0 , 2 * Math.PI); this_.nodeArr.push({x:x + this_.space + this_.r,y:y + Math.tan(itemY * Math.PI / 180 ) * this_.space,data:dataArr[i]}); / / 节点信息 ctx.textAlign = "center" ; ctx.textBaseline = "middle" ; ctx.font = "bold 10px 宋体" ; / / 字体大小 ctx.fillStyle = item.isExcuted = = = true?this_.config.excutedCirclePointColor:this_.config.circlePointColor; / / 填充颜色 ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.fillStyle = dataArr[i].noteColor; / / 字体颜色 ctx.fillText(dataArr[i].noteName,x + this_.space + this_.r, y + Math.tan(itemY * Math.PI / 180 ) * this_.space + this_.r + 10 ); ctx.fill(); itemY = itemY + this_.angle; } } ctx.stroke(); ctx.beginPath(); x = x + this_.space + 2 * this_.r; ctx.moveTo(x,y); } } }); }; / * * 因为canvas绘制的是矢量图,会出现模糊问题,使用下边的方法解决 * 参考链接:https: / / zhuanlan.zhihu.com / p / 31426945 * / opsDirectionMap.prototype.resolveVagueProblem = function(myCanvas) { vargetPixelRatio = function(context) { varbackingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1 ; return (window.devicePixelRatio || 1 ) / backingStore; }; / / 画文字 myCanvas.style.border = "1px solid silver" ; varcontext = myCanvas.getContext( "2d" ); varratio = getPixelRatio(context); myCanvas.style.width = myCanvas.width + 'px' ; myCanvas.style.height = myCanvas.height + 'px' ; myCanvas.width = myCanvas.width * ratio; myCanvas.height = myCanvas.height * ratio; context.scale(ratio,ratio); }; |
然后可以在你的form的js中调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 | / / 调用的时候可以直接放到上面的beforeCanvas这个方法中,如果找不到对应的dom记得修改上面封装的js中的dom获取方式,断点查看 vardemo = newopsDirectionMap({ placeId: "renderArea" , excutedCirclePointColor: "#009aff" , / / 执行的节点的圆心颜色 circlePointColor: "#ffffff" , / / 未执行的的节点的圆心颜色 data:[{ noteName: '节点1' , noteColor: '#000000' , / / 说明文字的颜色 isExcuted:true / / 如果这里为true,则其前边的线条为蓝色,圆点为实心,否为白色 },{ noteName: '节点2' , noteColor: '#000000' , isExcuted:true },[ { noteName: '节点3-1' , noteColor: '#000000' , isExcuted:true }, { noteName: '节点3-2' , noteColor: '#000000' , isExcuted:false } ],{ noteName: '节点4' , noteColor: '#000000' , isExcuted:false },{ noteName: '节点5' , noteColor: '#000000' , isExcuted:false },[ { noteName: '节点6-1' , noteColor: '#000000' , isExcuted:false },{ noteName: '节点6-2' , noteColor: '#000000' , isExcuted:false, } ],{ noteName: '节点7' , noteColor: '#000000' , isExcuted:false } ] }); |
完成后可以得到一个的步骤图
剩下如何修改样式,代码事件等相关操作需要自己行根据自身的需求进行修改。
三、附赠:贪吃蛇小游戏
这里给大家附赠一个用Canvas制作的贪吃蛇小游戏,大家可以自己去尝试一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | <!DOCTYPE html> <html lang = "en" > <head> <meta charset = "UTF-8" > <meta name = "viewport" content = "width=device-width, initial-scale=1.0" > <title>canvas< / title> < / head> <style> .container{ width: 800px ; height: 800px ; margin: 50px auto; / * border: 1px solid #ccc; */ } .statistics{ height: 100px ; display: flex; background:green; font - size: 32px ; text - align: center; line - height: 100px ; color: #fff; } .statistics div{ width: 50 % ; } canvas{ background: #000; overflow: hidden; vertical - align:middle } .controller{ display: flex; height: 100px ; font - size: 32px ; text - align: center; line - height: 100px ; color: #fff; } .controller div{ width: 100 % ; cursor: pointer; } .controller div:active{ background:green!important; } .controller div:nth - child( 1 ){ background: #8fd229; } .controller div:nth - child( 2 ){ background: #9bd83c; } .controller div:nth - child( 3 ){ background: #96ce42; } < / style> <body> <div class = "container" > <div class = "statistics" > <div id = "score" >得分: 0 分< / div> <div id = "timer" >用时: 0 秒< / div> < / div> <canvas id = "game" width = "800" height = "600" >< / canvas> <div class = "controller" > <div onclick = "start()" >开始< / div> <div id = "stop" onclick = "stop()" >暂停< / div> <div onclick = "reset()" >重置< / div> < / div> < / div> < / body> <script> var canvas = document.getElementById( 'game' ); var game = { length: 3 , positions:[[ 360 , 500 ], [ 380 , 500 ],[ 400 , 500 ]], direction: 'right' , foodPositionInSnack:null, foodPosition:null, score: 0 }; var timer; var timeSpend = 0 ; var startTime = 0 ; var operateTime = 0 ; var speedFactor = 300 ; var willGrowUp = false; var gameStatus = true; var operateTimer; var operateQueue = []; function start(){ console.log( 'game_ start__' ); if ( !game.content ){ refreshTime(); gameInit(); } } function stop(){ gameStatus = ! gameStatus; if ( gameStatus ){ document.getElementById( 'stop' ).innerText = '暂停' refreshTime(); console.log( 'game_ restart' ); } else { document.getElementById( 'stop' ).innerText = '继续' clearInterval(timer); console.log( 'game_ stop' ); } } function reset(){ console.log( 'game_ reset' ); if ( !!game.content ){ timeSpend = 0 ; game = { length: 3 , positions:[[ 360 , 500 ], [ 380 , 500 ],[ 400 , 500 ]], direction: 'right' , foodPositionInSnack:null, foodPosition:null, score: 0 }; gameStatus = true; document.getElementById( 'stop' ).innerText = '暂停' gameInit(); } } function gameInit(){ var ctx = canvas.getContext( '2d' ); game.content = ctx; generateFood(); renderGame(); operateEventloop(); } function renderGame(timestamp){ var currentTime = performance.now(); if ( (currentTime - startTime) < speedFactor || !gameStatus ){ requestAnimationFrame(renderGame); return false; } operateEventloop(); startTime = currentTime; game.content.clearRect( 0 , 0 ,canvas.width,canvas.height); var x = 0 ,y = 0 ; switch (game.direction){ case 'left' : x - = 20 ; break ; case 'right' : x + = 20 ; break ; case 'top' : y - = 20 ; break ; case 'bottom' : y + = 20 ; break ; } / / 判断吃掉食物 const newPosition = [game.positions[game.positions.length - 1 ][ 0 ] + x, game.positions[game.positions.length - 1 ][ 1 ] + y]; / / 如果头部位置和食物位置重合,则吃掉,分数 + 1 , 并重新生成食物 if ( newPosition[ 0 ] = = game.foodPosition[ 0 ] && newPosition[ 1 ] = = game.foodPosition[ 1 ]){ game.foodPositionInSnack = [...game.foodPosition]; generateFood(); game.score + + ; document.getElementById( 'score' ).innerText = `得分:${game.score}分`; var accCoeff = Math.floor(game.score / 10 ); speedFactor = 300 - accCoeff * 40 > 50 ? 300 - accCoeff * 40 : 50 ; } / / 判断游戏失败 var isFailture = false; / / 1. 出界 if (newPosition[ 0 ] < 0 || newPosition[ 0 ] > = 800 || newPosition[ 1 ] < 0 || newPosition[ 1 ] > = 600 ){ isFailture = true; } / / 2. 吃到自己 game.positions.forEach(position = > { if (position[ 0 ] = = newPosition[ 0 ] && position[ 1 ] = = newPosition[ 1 ]){ isFailture = true; } }) if (isFailture){ alert(`Game Over \n 最终得分${game.score}`); return false; } game.positions.push(newPosition); / / 如果食物到达尾部,则变长一格 if ( !willGrowUp ){ game.positions.shift(); } else { game.foodPositionInSnack = null; } willGrowUp = false; if ( game.foodPositionInSnack ){ if ( game.positions[ 0 ][ 0 ] = = game.foodPositionInSnack[ 0 ] && game.positions[ 0 ][ 1 ] = = game.foodPositionInSnack[ 1 ] ){ willGrowUp = true; } } drawSnack(); drawFood(); requestAnimationFrame(renderGame); } function generateFood(){ var randomPos = randomPosition(); var hasRepeat = false; game.positions.forEach(position = > { if ( randomPos[ 0 ] = = position[ 0 ] && randomPos[ 1 ] = = [ 1 ]){ hasRepeat = true; } }) if ( hasRepeat ){ generateFood(); return ; } game.foodPosition = randomPos; } function randomPosition(){ return [Math.floor(Math.random() * 40 ) * 20 , Math.floor(Math.random() * 30 ) * 20 ]; } function drawSnack(){ var ctx = game.content; ctx.beginPath(); ctx.fillStyle = '#ccc' ; game.positions.forEach(position = > { ctx.fillRect(position[ 0 ] + 2.5 , position[ 1 ] + 2.5 , 15 , 15 ); }) } function drawFood(){ var ctx = game.content; ctx.beginPath(); ctx.fillStyle = '#ccc' ; ctx.arc(game.foodPosition[ 0 ] + 10 , game.foodPosition[ 1 ] + 10 , 8 , 0 , Math.PI * 2 , true); ctx.fill(); } function refreshTime(){ timer = setInterval(() = > { timeSpend + + ; document.getElementById( 'timer' ).innerText = `用时:${timeSpend}秒`; }, 1000 ); } function operateEventloop(){ if ( operateQueue.length ! = 0 ){ triggerOperate(operateQueue[ 0 ]); operateQueue.shift(); } } function triggerOperate(e){ switch (e.keyCode){ case 87 : if ( game.direction ! = 'bottom' ){ game.direction = 'top' ; } break ; case 83 : if ( game.direction ! = 'top' ){ game.direction = 'bottom' ; } break ; case 65 : if ( game.direction ! = 'right' ){ game.direction = 'left' ; } break ; case 68 : if ( game.direction ! = 'left' ){ game.direction = 'right' ; } break ; } } document.addEventListener( 'keyup' , function(e){ operateQueue.push(e); }) < / script> < / html> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2018-10-26 scrapy 自定义图片路径保存,并存到数据库中
2018-10-26 关于scrapy下载文件重命名的办法以及对应url没有文件后缀的办法
2018-10-26 下载转码
2018-10-26 scrapy 下载图片 from cuiqingcai
2018-10-26 Scrapy框架学习 - 使用内置的ImagesPipeline下载图片
2018-10-26 字符串处理