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>

  

 

posted @   CrossPython  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需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 字符串处理
点击右上角即可分享
微信分享提示