Odoo|当我在Odoo用画布创建流程图

发布时间 2023-10-26 09:41:31作者: CrossPython

在Odoo14中,如何在form表单中最上面插入一个Canvas的画布控件呢?

 

首先,我发现在Odoo中,form表单会在每次重置后只进入一次form视图的init和renderButtons等相关的初始化视图方法。但是在二次渲染视图时,会出现"不触发"和"找不到相关DOM元素"的问题。

 

为了解决这个问题,我们需要使用form视图的Renderer配置属性,并改写Renderer中的内容,因为Renderer是百分百会进入和触发的。

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部分的代码,记得引入相关模块 

/**记得引入*/
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文件中,然后在你的文件中提前引入。

 

 

代码如下 

 

/**
*流向图组件,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中调用

 

//调用的时候可以直接放到上面的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制作的贪吃蛇小游戏,大家可以自己去尝试一下。

 

 

<!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>