精华内容
下载资源
问答
  • 基于Qt的流程图设计

    2015-02-01 21:38:41
    基于QT开发的流程图绘制程序,提供了基本的图形,箭头等工具。QT是一款优秀的跨平台的GUI库.对学习QT绘图有一定参考作用
  • 本工程是使用QT 5.5和VS2010开发的流程图设计简易工具,QT SDK和VS的QT插件下载请参考:http://blog.csdn.net/joy58061678/article/details/50982576,源码参考URL为:...
  • 一个流程图,主要由横线,空心圆,空心圆中的实心圆三部分组成,并且随着状态的变化能控制其颜色 可以支持多分支,多分支中还包括奇数个分支和偶数个分支,实现思路上两者稍微不同 核心思路说明...

    综述:使用canvas实现了一个多分支流向图,总结下主要的实现思路,有需要的朋友可直接使用,假设如果不使用canvas来绘制,使用svg来设计又该如何实现呢,思考下?

    1.效果展示

    2.实现思路解析

    • 需求分析

    一个流程图,主要由横线,空心圆,空心圆中的实心圆三部分组成,并且随着状态的变化能控制其颜色

    可以支持多分支,多分支中还包括奇数个分支和偶数个分支,实现思路上两者稍微不同

    • 核心思路说明

    通过配置的数组解析绘制,如果是非数组元素,则就是画直线和圆圈,实心圆,并分别计算各自的位置(x,y)坐标,依次类推。

    如果是一个数组的元素,则说明是一个多分支的元素,需要向上和向下开分支,绘制多条直线和圆圈,实心圆,依次类推即可,并分别计算各自的位置(x,y)坐标,依次类推。

    3.遇到的问题总结

    • canvas是基于矢量绘制的图像,存在模糊的问题(参考连接),细节见js代码种的最后一个模块
    • 对于绘制直线的颜色控制逻辑,没有理清楚,废了些时间

    4.代码展示

    • javascript代码(组件实现代码),render方法过于冗余,但是好处是比较好理解,后期应该优化下
    /**
     *流向图组件,mouyao
     */
    var opsDirectionMap = 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(){
        var myCanvas=document.getElementById(this.config.placeId);
        this.resolveVagueProblem(myCanvas);
        this.render(myCanvas);
    };
    /*
    *判定是否数组
    */
    opsDirectionMap.prototype.isArrayFn =function(o) {
        return Object.prototype.toString.call(o) === '[object Array]';
    };
    /*
    *根据当前节点的执行状态,渲染圆点前的线条的颜色
    */
    opsDirectionMap.prototype.drawDashLine =function(ctx, x1, y1, x2, y2,data,index){
        ctx.lineWidth=1;
        ctx.beginPath();
        var x=(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);
        }else 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);
        }else{
            if(index!==0){ //删除第一个圆点的连接线
                ctx.moveTo(x1,y1);
                ctx.lineTo(x2 ,y2);
            }
        }
        if(data.isExcuted===true){
            ctx.strokeStyle="#009aff";
        }else if(data.isExcuted===false&&index!==0&&this.data[index-1].isExcuted===true&&!this.isArrayFn(this.data[index-1])){
            ctx.strokeStyle="#009aff";
        }else if(data.isExcuted===false&&this.isArrayFn(this.data[index-1])){
            //如果上一个元素是数组
            var arr=[];
            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){
            var this_ = this;
            this_.canvas = canvas;
            var ctx = canvas.getContext("2d");//上下文
            this_.ctx = ctx;
            var x = 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)
            var y =50;
            this_.data.forEach(function(item, index){
                if(!(item instanceof Array)){
                    x = index == 0?x:(x + this_.space);
                    if((index-1)>=0 && this_.data[index-1] instanceof Array){
                        var arr = this_.data[index-1];
                      
                        if(arr.length % 2==0){
                            var itemY = this_.angle;
                            for(var i=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;
                            }
                            var itemY = this_.angle;
                            for(var i=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{
                            var itemY = 0;
                            for(var i=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;
                            }
                            var itemY = this_.angle;
                            for(var i=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] instanceof Array)){
                        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] instanceof Array)){//上一级不是数组
                        var itemY = 0;
                        if(item.length%2==0){//偶数
                            itemY = this_.angle;
                            var dataArr = item.slice(0,item.length/2).reverse();
                            for(var i=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;
                            var dataArr = item.slice(item.length/2,item.length);
                            for(var i=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{//奇数
                            var dataArr = item.slice(0,parseInt(item.length/2)+1).reverse();
                            for(var i=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;
                            var dataArr = item.slice(parseInt(item.length/2)+1,item.length);
                            for(var i=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){//偶数
                            var itemY = this_.angle;
                            var dataArr = item.slice(0,item.length/2).reverse();
                            for(var i=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;
                            }
                            var itemY = this_.angle;
                            var dataArr = item.slice(item.length/2,item.length);
                            for(var i=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{//奇数
                            var itemY = 0;
                            var dataArr = item.slice(0,parseInt(item.length/2)+1).reverse();
                            for(var i=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;
                            }
                            var itemY = this_.angle;
                            var dataArr = item.slice(parseInt(item.length/2)+1,item.length);
                            for(var i=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) {
        var getPixelRatio = function (context) {
            var backingStore = context.backingStorePixelRatio ||
                context.webkitBackingStorePixelRatio ||
                context.mozBackingStorePixelRatio ||
                context.msBackingStorePixelRatio ||
                context.oBackingStorePixelRatio ||
                context.backingStorePixelRatio || 1;
            return (window.devicePixelRatio || 1) / backingStore;
        };
        //画文字
        myCanvas.style.border = "1px solid silver";
        var context = myCanvas.getContext("2d");
        var ratio = 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); 
    };
    • html调用代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title></title>
    </head>
    <body>
    <canvas id="renderArea" width="600" height="100">浏览器不支持canvas</canvas>
    <script type="text/javascript" src="ops-direction-map.js"></script>
    <script>
         var demo=new opsDirectionMap({
            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
            }
            ]
        });
    </script>
    </body>
    </html>

     

     

    展开全文
  • 基于vue的简单流程图开发

    千次阅读 2017-10-18 09:06:28
    拖了这么久,现在终于要开始硬着头皮写一篇基于vue+svg的流程图"伪教程"文章了。初次献丑,还请轻喷。 模块简介 项目地址 图片预览 出于学习vue而非兼容的目的,本项目仅考虑现代浏览器( 谷歌 ),部分兼容...

    严重拖延症,一方面这项目模块纯属个人娱乐。另一方面,流程图这块涉及的东西还是蛮多的,这次也只是介绍一些简单的部分。拖了这么久,现在终于要开始硬着头皮写一篇基于vue+svg的流程图"伪教程"文章了。初次献丑,还请轻喷。

    模块简介

    项目地址

    图片预览

    出于学习vue而非兼容的目的,本项目仅考虑现代浏览器( 谷歌 ),部分兼容问题还请见谅。

    本模块的开发源于对流程图的简单需求( 纯UI实现,暂不存在业务逻辑 ),这里不赘诉vue-cli生成的目录结构(可以参考这篇或自行谷歌)。

    项目实际用到的技术栈:SVG + vue + vuex

    功能介绍:

    • 画布缩放
    • 节点( 开始,基础,判断等 )添加,删除
    • 节点间连线( 直线/折线 )
    • 文本添加
    • 外部导入SVG图形
    • 撤销与重做

    画布缩放

    考虑到画布缩放后布局需保持一致,这里通过修改transform: scale(); transform-origin: ; 来实现,节点则相对父层定位。

    TODO: SVG最优缩放解决方案?

    节点相关

    下面我简单说一下思路:

    由于不存在业务逻辑,我把流程图简化为 开始 基础 判断 3个基础组件( 基于SVG )。

    如:

    <template>
        <!-- 开始 -->
        <ellipse v-bind="style"></ellipse>
        <!-- 基础 -->
        <rect v-bind="style"></rect>
        <!-- 判断 -->
        <path v-bind="style"></path>
    </template>复制代码

    这里说一下判断这个组件( 后期可能出现复杂形状均以path实现 ),一般由AI软件直接导出相关形状。

    左边工具栏跟画布中的相同图形源于同个组件,故设有两个样式,即 defaultStyledrawStyle。之前有考虑过,如果流程图的图形复杂多变的话,那这种模式岂不是每一个组件都得人为定义。同样,采用导入SVG也有类似问题。因为如果图形大小都不确定的话,除了支持图形修改大小,否者将导致画布出现大小不一的图形。( 非常遗憾这方面没有做出突破,不过这将成为未来改进的方向。)

    最开始采用的解决方案是以scale的方式,也就是统一让工具栏中的图形跟拖入画布中的图形成等比缩放关系。不过该方式会造成stroke也同比缩放,并非我们想要的。

    所以目前暂时采用写死的方式。

    注意: 在svg中 ellipse 定位相对于中心点,而rect定位是相对于左上角。

    TODO是否有办法将各组件定位源点设置为组件中心点。

    节点渲染

    节点渲染方面,由于之前是将图形作为组件,于是采用 component + is 的方式来渲染图形。同时也是以数据驱动的方式来渲染,即数据决定视图。

     <component v-for="(item,index) in nodeData" :is="item.type" :id="item.id" v-node inDraw></component>复制代码

    拖动节点涉及镜像节点时:

    <component :is="selNodeId" :transform="selNodeInfo.transform" v-if="isDragging" inDraw></component>复制代码

    代码直通车

    新增节点

    drag drop 的形式。采用该方式的好处是不需要模拟拖拽事件。也就是镜像什么的不需要自己做。( 画布内节点拖拽则使用原生模拟 )

    代码直通车

    对节点的操作均以指令( directives )的形式( 直接操作DOM )。这引发了我对该类项目是否适合用vue类框架来做的疑问,从开发效率方面,还是首选vue,但是从性能方面,由于没有深入研究,并没有发言权。

    TODO 场景模拟,假设我们需要移动画布内节点,通过directives的el来获取节点,然后通过el.onmousemove来修改data中对应的translate来实现位置的更改。这里修改data来驱动视图是我们常用的方式,但是我想不通的就是el.onmousemove来修改data实现的双向数据绑定所带来的性能在这里是否有体现。

    我所设想的是,是否涉及多依赖的时候,diff带来的性能提升才有价值。举个例子,我有一个列表,存在于data中的listData,然后在view中有多处关联listData。那此时操作listData比直接操作DOM来得更好些。

    看过相关vitrualDOM的介绍,通过diff可以只操作变化的DOM。

    获取SVG大小

    获取节点大小使用 getBoundingClientRect ,同时由于前面做了缩放功能,这里获取节点大小时需要除以缩放比例来获取正确值。

    let obj = el.getElementsByTagName('g')[0]
    let w = obj.getBoundingClientRect().width / _this.drawStyle.zoomRate
    let h = obj.getBoundingClientRect().height / _this.drawStyle.zoomRate
    let wh = {
        width: w,
        height: h
    }复制代码

    代码直通车

    节点操作总结

    由于节点的显示是基于NodeData,所以增删其实就是对NodeData的增删。

    主要代码

    连线相关

    连线其实也只是用到了svg的linepolyline,这里跟节点类似,均以组件的形式存在,并以lineData驱动连线视图。所以最终连线的增删也是对数据的操作。

    连接点的显示

    首先是链接点的位置( 绿色远点位置 ),之前基于jquery做的流程图是用div布局,现在用svg增加了难度,由于svg不能使用position,所以无法基于当前元素定位。采用的是土办法,即用图形大小+padding动态获取4个点的位置。期间,由于4个连线节点与图形节点有空隙,当mouseover不处于图形或节点时,事件无法触发。在此是模拟一个区域来解决的。由于个人经验问题,这部分代码完全就是命令式的风格。勿喷

    代码直通车
    代码直通车2

    连接处理

    节点间连线做了两种情况:(这里不讲诉从mousedown至mouseup具体细节,可以看这里

    其实很多人说,算法可以解决很多垃圾代码。可惜我还没掌握它的真谛,比如之前的图形组件,以及接下来的不同线条。其实都可以通过一定的算法得出来。我这里只讲讲最笨的方法,待我成长到能用算法来说话的时候,在回来好好理下这篇文章。

    • line直线

    直线无外乎就是两个点坐标,通过svg中的line来显示。这时候就得看项目的需求,我们假设最简单的情况,就是上面讲到过的4个连接点最为连线的起始或结束点。
    下面是计算图形中4个点的坐标位置

    
    computeLine(direction, obj) { // low不止一点点
        let { top, left, width, height } = obj
        let w = width / 2
        let h = height / 2
        switch (direction) {
        case 't':
            top = top - h
            break
        case 'b':
            top = top + h
            break
        case 'l':
            left = left - w
            break
        case 'r':
            left = left + w
            break
        default:
            break
        }
        return { top, left }
    }复制代码
    • polyline折线

    折线考虑的情况相对比较多一点,这边由于使用的是polyline,它的点位设置长这样子points="125,96 183.5,96 183.5,399 242,399"

    这个时候一般会把字符转化为较为好操作的数组或对象。折线涉及的开始点跟结束点跟上面介绍直线的点位一样,不同的是中间线的位置,如果不考虑复杂的情况,

    一般可以分为两种,上下,左右。通过获取开始与结束点的中点位置来确定中线即可以得到想要的折线。代码如下:(都是用简单粗暴的方式。)

    computePolyLine(start, end, direction) {
        let startPoint = {
        x: +(start.split(',')[0]),
        y: +(start.split(',')[1])
        }
        let endPoint = {
        x: +(end.split(',')[0]),
        y: +(end.split(',')[1])
        }
        let m1, m2
        switch (direction) {
        case 't':
        case 'b':
            let mY = startPoint.y + (endPoint.y - startPoint.y) / 2
            m1 = {
            x: startPoint.x,
            y: mY
            }
            m2 = {
            x: endPoint.x,
            y: mY
            }
            break
        case 'l':
        case 'r':
            let mX = startPoint.x + (endPoint.x - startPoint.x) / 2
            m1 = {
            x: mX,
            y: startPoint.y
            }
            m2 = {
            x: mX,
            y: endPoint.y
            }
            break
        default:
            break
        }
        return `${startPoint.x},${startPoint.y} ${m1.x},${m1.y} ${m2.x},${m2.y} ${endPoint.x},${endPoint.y}`
    }复制代码

    连线总结

    节点跟连线在渲染以及操作的处理上大同小异,这里不确定是否为最佳实践的有两个地方,一是采用component+is的形式来渲染组件,二是采用 diretives的方式来操作DOM。连线的计算形式也略显简单,这确实是需要一定时间来成长的。扯偏了,在这简单总结一下,无论是哪种连线方式,我们需要做的就是正确获取对应点的位置,然后修改数据来驱动视图。不过能在各种复杂的情况下总结出算法,也是一种跨越,加油吧。

    节点及连线的文本添加

    节点及连线的文本添加原理都一样,这里采用的是设置 contenteditable 当contenteditable为true时,html结构自动添加文本节点并且可编辑。更多细节可以参考张鑫旭的这篇

    顺道讲一下pointer-events本模块有两个地方用到该css属性。一个是文本添加这块,以及头部工具栏部分。

    CSS属性pointer-events允许作者控制特定的图形元素在何时成为鼠标事件的target。当未指定该属性时,SVG内容表现如同visiblePainted。

    除了指定元素不成为鼠标事件的目标,none值还指示鼠标事件穿过该元素,并指向位于元素下面的元素。

    更多细节关于pointer-events

    张鑫旭
    MDN

    TODO 文本编辑虽已实现功能,但这块BUG较多,还未完善。

    外部导入SVG

    这边也是用到了HTML5的Drop功能,显示则是用到了svg的images。拖拽实现比较简单:

    dropHandle (e) {
        let reader = new FileReader()
        let file = e.dataTransfer.files[0]
        reader.onload = (e) => {
            this.userImages.push(e.target.result)
        }
        reader.readAsDataURL(file)
    },
    dragoverHandle () {
    },
    dragstart (imgSrc) {
        event.dataTransfer.setData('URL', imgSrc)
    }复制代码

    这边需要注意的是@drop.stop.prevent="dropHandle" @dragover.stop.prevent="dragoverHandle"要阻止冒泡以及阻止浏览器默认行为。

    还有一个要注意的是dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题

    根据W3C标准,drag data store有三种模式,Read/write mode, Read-only mode跟Protected mode。细节

    Read/write mode
    读/写模式,在dragstart事件中使用,可以添加新数据到drag data store中。

    Read-only mode
    只读模式,在drop事件中使用,可以读取被拖拽数据,不可添加新数据。

    Protected mode
    保护模式,在所有其他的事件中使用,数据的列表可以被枚举,但是数据本身不可用且不能添加新数据。

    深入

    撤销与重做

    这一功能本质上是没有完成的,因为采用了一种偷懒的方式,vuex 生成 State 快照,生产环境不建议使用。

    基本原理就是通过vuex提交更高(mutation)来触发回调。以此来记录state 快照

    代码直通车

    总结

    本项目属于入门级的vue+vuex,但是并没有讲如何使用vue或者vuex,因为这些在官方文档其实都已经讲的非常清楚了。该项目也只是简单使用了如vue的自定义指令,MiXin等常用方法。诸如vue Render函数组件,不在本文谈论范围,这里简单讲下使用体验,render组件比较适合高自定义的组件(变化逻辑比较复杂)。因为一些简单组件其实更适合用tempalte的形式,虽然使用Render可以提高一定的性能( 减少了从tempalte到render这一步 ),但是很多现有的如sync,是render组件所不具备的( 需自己实现 )。vuex的使用,则需要注意的是object引用地址的问题。也就是说,要避免数据间的潜在影响。(虽然vuex自身也有规避这个问题)可以了解一下immutable

    本教程主要讲述一个基于vue如何实现一个简单的流程图,更多引发的思考是,什么项目更适合使用这种MVVM模式的框架,以及如何发挥VitrualDOM的价值。其实上面几个章节的点随便拿个出来都可以深入探讨出很多技术问题,以后有机会再陆续深入。

    展开全文
  • markdown 中流程图详解

    万次阅读 多人点赞 2018-12-13 17:34:29
    目录Mermaid 流程图示例结构显示方向节点连线Flowchart流程图示例结构节点语法节点定义节点连接连接样式关键字    markdown有不同的插件实现,对应的语法也不太一样,对于插件就是把引用的语法对应成响应的标签,...


       markdown有不同的插件实现,对应的语法也不太一样,对于插件就是把引用的语法对应成相应的标签,本文以CSDN中写作为例进行分别对mermaid和flowchart进行实例说明和语法解释。

    1. Mermaid 流程图

    示例

    	```mermaid
    	graph TB
    	A[Apple]-->B{Boy}
    	A---C(Cat)
    	B.->D((Dog))
    	C==喵==>D
    	style A fill:#2ff,fill-opacity:0.1,stroke:#faa,stroke-width:4px
    	style D stroke:#000,stroke-width:8px;
    	```
    
    Apple
    Boy
    Cat
    Dog

    语法结构

    	```mermaid
    	graph 方向
    	节点以及节点连线(定义和连线步骤可以分开)
    	(样式调整)
    	```
    

    显示方向

    • TB/TD( top bottom/top down)表示从上到下
    • BT(bottom top)表示从下到上
    • RL(right left)表示从右到左
    • LR(left right)表示从左到右

    节点类型

       节点本身的展现形式,是通过不同括号来代表各自不同的形状,默认为矩形。

    • 默认节点: A
    • 矩形节点: B[矩形]
    • 圆角矩形节点: C(圆角矩形)
    • 圆形节点: D((圆形))
    • 非对称节点: E>非对称]
    • 菱形节点: F{菱形}
    A
    矩形
    圆角矩形
    圆形
    非对称
    菱形

    语法详解

    节点连线

       线条本身的形式有多种,通过常规的英文格式的格式来标识,具体如下:

    • 箭头连接 A1- ->B1
    • 开放连接 A2- - -B2
    • 虚线箭头连接 A3.->B3 或者 A3-.->B3
    • 虚线连接 A4.-B4 或者 A4-.-B4
    • 粗线箭头连接 A5==>B5
    • 粗线开放连接 A6===B6
    • 标签虚线箭头连接 A7-.text.->B7
    • 标签开放连接 A8- -text- - -B8
    text
    text
    A1
    B1
    A2
    B2
    A3
    B3
    A4
    B4
    A5
    B5
    A6
    B6
    A7
    B7
    A8
    B8
    节点样式

       样式写法跟矢量图(SVG)中CSS的写法一致,含有的属性有:

    style 节点  样式
    

    2. Flowchart流程图

    示例

    	```mermaid
    	flowchat
    	st=>start: 开始
    	e=>end: 结束
    	op1=>operation: 操作1 | past
    	op2=>operation: 操作2 | current
    	op3=>operation: 操作3 | future
    	pa=>parallel: 多输出操作4 | approved
    	cond=>condition: 确认? | rejected
    	
    	st->op1->cond
    	cond(true)->e	
    	cond(no)->op2(right)->op3->pa(path1,right)->op1
    	pa(path2,left) ->e
    	st@>op1({"stroke":"Blue"})@>cond({"stroke":"Green"})@>e({"stroke":"Red","stroke-width":6,"arrow-end":"classic-wide-long"})
    	```
    
    Created with Raphaël 2.2.0 开始 操作1 确认? 结束 操作2 操作3 多输出操作4 yes no

    语法结构

    	```mermaid
    	flowchat
    	定义节点
    	连接节点
    	(样式调整)
    	```
    

    节点类型

      目前官网提供7种节点,其实还有很多别的节点类型,但可能插件脚本还没支持。

    • 开始(椭圆形):start
    • 结束(椭圆形):end
    • 操作(矩形):operation
    • 多输出操作(矩形):parallel
    • 条件判断(菱形):condition
    • 输入输出(平行四边形):inputoutput
    • 预处理/子程序(圣旨形):subroutine

    语法详解

    节点定义
    变量名=>节点标识: 节点显示名
    
    节点连线
    变量名1->变量名2->...->变量名n
    
    连线样式

      设置变量m和变量n之间连线的样式,具体样式由变量n后面key-value控制,需要两个变量之间有直接连线。语法中的连接符为(@>)。

    变量名m@>变量名n({"key":"value"})
    
    关键字
    • yes/true:condition类型变量连接时,用于分别表示yes条件的流向
    • no/false:同上,表示否定条件的流向
    • left/right:表示连线出口在节点位置(默认下面是出口,如op3),可以跟condition变量一起用:cond(yes,right)
    • path1/path2/path3:parallel变量的三个出口路径(默认下面是出口)
    节点状态

      为节点设置不同的状态,可以通过不同的颜色显示,其中状态包括下面6个,含义如英文所示,不过CSDN中好像目前还不支持:

    • past
    • current
    • future
    • approved
    • rejected
    • invalid

    3. 时序图

    示例

    	```mermaid
    	    sequenceDiagram
    	    participant 张 as 张三
    	    participant 李 as 李四
    	    participant 王 as  王五   
    	    张 ->> +李: 你好!李四, 最近怎么样?
    	    李-->> 王: 你最近怎么样,王五?
    	    李--x -张: 我很好,谢谢!
    	    activate 王
    	    李-x 王: 我很好,谢谢!   
    	    Note over 李,王: 李四想了很长时间, 文字太长了<br/>不适合放在一行.
    	    deactivate 王
    	    loop 李四再想想
    	    李-->>王: 我还要想想
    	    王-->>李: 想想吧
    	    end
    	    李-->>张: 打量着王五...
    	    张->>王: 很好... 王五, 你怎么样?
    	```
    
    张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 我还要想想 想想吧 loop [ 李四再想想 ] 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

    语法结构

    	```mermaid
    	    sequenceDiagram
    	    participant 别名 as 对象显示名(全部直接用显示名时可以不写)
    	    顺序增加图表中消息
    	    (可以加入标签提示)
    	```
    

    语法详解

    消息格式
    【对象1】【箭头类型】【对象2】 : 消息内容
    
    连接样式

    一共6种箭头类型的样式:

    • 实线:->
    • 虚线:- ->
    • 带箭头的实线:->>
    • 带箭头的虚线:- ->>
    • x的实线:-x
    • x的虚线:- -x
    激活方块

      这部分有两种写法,第一种是显示通过语法实现,语法如下,会在指定对象的消息中增加,示例中李四;第二种直接在对象前面增加加减号(开始时用加号“+”,结束时用减号“-”),则在加号对应的对象上开始,减号对应的时间结束,示例中王五。

    开始:activate 【对象】
    结束:deactivate 【对象】
    
    注释
    Note 【位置】 【对象】
    

    注释显示的位置有三个,以被标记的对象中心为参考系,基于横跨多个时,可以都逗号分隔,如示例:

    • right of
    • left of
    • over
    循环(while)

    见示例

    loop 循环说明
    【消息流】
    end
    
    条件(if/else)
    alt 条件说明
    【消息流】
    else
    【消息流】
    end
    
    opt 条件说明
    【消息流】
    end
    

    示例内容太多了,为了防止太臃肿,把条件内容独立出来进行演示,当有else时,用alt,否则用opt。

    	```mermaid
    	    sequenceDiagram
    	    participant 张 as 张三
    	    participant 李 as 李四
    	    张 ->> 李: 你好!李四, 最近怎么样?
    	    alt 如果感冒了
    	    李->> 张: 不太好,生病了。
    	    else 挺好的
    	    李->> 张: 我很好,谢谢。
    	    end
    	        opt 另外补充
    	        李->> 张: 谢谢问候。
    	    end
    	```
    
    张三 李四 你好!李四, 最近怎么样? 不太好,生病了。 我很好,谢谢。 alt [ 如果感冒了 ] [ 挺好的 ] 谢谢问候。 opt [ 另外补充 ] 张三 李四

    参考自:
    https://mermaidjs.github.io/
    http://flowchart.js.org/

    展开全文
  • [产品设计]如何绘制业务流程图(下)

    万次阅读 2017-02-08 15:50:09
    原文链接 http://www.woshipm.com/pmd/27239.html有一些同学看过了我写的《如何绘制业务流程图》,发私信过来希望我谈谈页面流程图。这个话题其实我也酝酿过,但一直没有写出来。细究起来,除了懒,原因其实有好几条...

    原文链接 http://www.woshipm.com/pmd/27239.html


    有一些同学看过了我写的《如何绘制业务流程图》,发私信过来希望我谈谈页面流程图。

    这个话题其实我也酝酿过,但一直没有写出来。细究起来,除了懒,原因其实有好几条:

    1. 这一年半来的工作都是围绕数据平台建设,不是很通用,没法举例。
    2. 虽然自己一直画页面流程图,但是说实话属于偏方多一些,按直觉行事,要总结出一两条可通用的“规则”比较难。

    今日因为因为天气预报号称有雷震大雨,取消了原有的外出计划,刚好在家里,想起抛出这块砖头,期望这个话题能激发更多的分享和讨论。

    案例呢……想一个通俗易懂又具代表性的案例真不容易,它不能太简单,太简单的话几乎上没有什么页面,也不能太复杂,太复杂了我还hold不住。

    刚好前不久在收拾家里闲置的衣服,舍不得扔但又不穿,当时唠叨一句,说要是有个地方能够提交下捐赠,有人上门收就好了。在我头疼要怎么举例演示页面流程图的时候,我就把这个大概YY了一下,就用“公益捐物网站”为例来说明吧。

    页面流程图是个好东西

    业务流程图重要的是描述谁在什么条件下做了什么事。

    而页面流程图是具体到了网站、系统、产品功能设计的时候,表现页面之前的流转关系——用户通过什么操作进了什么页面及后续的操作及页面。

    从需求到到解决方案无疑要经过很多阶段。需求的分析——用户是谁?用户的问题或需求是什么?用什么功能去满足需求或解决问题?这些功能的优先级是什么?这些问题都需要逐步得以明确,与此同时,你需要用一些线框图、原型或者DEMO(这些在我认为都是一个东西)去帮助自己精细化这些功能,想透彻那些需求。

    直接画单张页面的线框图当然是可以的,但是有可能会出现一下子进入单页面,不先系统性规划,考虑每项功能的前置和后置,每项操作的上下文,就很容易顾此失彼,遗漏重要状态或忽视本应简化的任务。

    说到这里,单页面的线框图很像PPT,我个人在做PPT之前,其实是一定要有脑图或者已经在一张大纸上将目录结构、每页的重点都写出来、画出来的。所以真正做PPT则纯粹是在做而已,可以做得很快,只因为心中早就有谱了。所以,在画线框图之前,我也习惯先将页面流程图画出来。

    好处之于对于设计师或产品经理:

    1. 页面流程图一张页面助你讲完完整的用户与系统的交互故事,借助它,你更容易知道流程中的潜在地雷是什么,哪里的效率比较低,有助于系统化、全局化、周全性的思考
    2. 细化工作量的基础,通过页面流程图可准确评估需要多少张页面。
    3. 聚焦:页面流程图中的每个页面都不必追求精细——你的目标是规划行为路径,而不是单页面交互设计,所以完全无需考虑页面内容、布局。所以你会更加聚焦于用户目标和任务的完成。不必过早陷入细节。
    4. 关键是很快。线框图有可能有几十张,你画起来没那么快,而且一旦进入细节,则还需要慢慢深究。但是页面流程图也许就是几个小时的事情。你就可以对整个项目心中有数了。

    好处之于开发工程师:

    他们会很乐意你在没有原型的时侯,第一时间拿出页面流程图和他讨论需求。相信我,这比单纯的功能列表或者有业务流程图更让他们兴奋。

    1. 可作为评估工作量的重要依据——可帮助他们对工作量也心中有数。
    2. 可做为开展代码工作的重要参考——特别是前端开发,必须得知道每一种操作指向什么页面。
    3. 他们会映射功能逻辑,会给你更多好的建议。

    绘制之前

    回到开头我们说的案例——公益捐物网站,这个仅仅是idea,真不足以让它变成一个产品。现在借着本话题,我们也尝试一下如何把随机迸发的一个idea快速转化为产品吧。

    第一步:idea大拷问

    此步的目的是验证一下idea的靠谱程度。怎么说靠谱?

    1. 有目标用户——不是火星人,而且有一定的规模性。

    2. 对目标用户有价值——推荐使用Before&After(这个术语不用google了,Heidi杜撰的)方式描述清楚。

    • Before——即现状分析(需求、问题)。在没有你的产品前,你的目标用户遇到的问题是什么?他们明确的及潜在的需求是什么?市场上已经有哪些产品?这些产品为什么没有满足这些需求,解决这个问题?(当然,内部产品,没必要分析太多竞品,但是脑子里要过一遍这些问题,没坏处。)
    •  After——这个产品如何满足需求及解决问题的?除此之外,潜在的利益是什么?可以分用户、公司多个维度阐释。

    3. 目标用户能用——有相应的能力储备(不需要经过学习、培训就可用你的产品),可及性(你能够去触及这些用户群体,让他们了解有这个产品可以用)

    本案例为了配合页面流程图随手拈来,单纯客观描述一下,诸位也可以帮忙诊断下是否靠谱。

    咱们YY下吧。

    假设我们是要做这样一个网站,必须有几个参与角色吧。必须有人去收衣物,可以和公益组织、社工群体、慈善组织取得联系,让他们成为第一种参与方。这里不展开了。

    当然也必须有人要捐赠衣物,这里,假设定位于年轻人群体,舍得买也舍得捐,有足够的能力储备可以使用在线系统而不是等收废品的大爷吆喝。

    目标用户:各居民区住户,年轻人为主,年龄在22到35岁

    Before:

    1. 空间矛盾:小户型房子,储物空间有限;时尚群体,消费空间很大,比较喜欢尝鲜,衣服和各种生活物品只进不出,没有足够地方容纳,必须要推陈应新。
    2. 处理旧衣物的方式有限:独生子女群体居多,也没有家人亲戚可以赠送。即使知道哪里有灾难发生,灾民缺衣少物也没有通道进行捐赠;二手市场耗费精力,且效果不好。……

    After:

    1. 可随时提交捐赠需求,等待有人上门收取,轻松做到眼不见心不烦。
    2. 捐赠带来额外好处:1. 换取公益积分(积分可用于订阅杂志、享受参与活动的商家优惠、换取书籍等)2. 公益积分可冲抵水电费……好吧,YY吧。

    不过写到这里,俺发现还是基本靠谱的吧,所以继续往下吧。。

    第二步:功能列表及优先级

    此步是进一步明确要做什么,以及用户大概会怎么参与使用。

    参与这个产品的有负责收衣服的,也有捐赠衣服的,单表这捐赠衣服的用户角色吧,免得不小心兜不住了。

    业务故事:小A有一批衣服需要捐赠,他在手机上提交一份捐赠需求,写明自己要捐赠什么衣服,新旧程度,多少数量,什么方式预约上门时间……小A提交捐赠后,收到预约电话,约好了3天后的周末下午上门取衣服。到了预定的时间,上门取衣服的社工检查了捐赠的数量后,拿出手机查找到小A捐赠的那笔单子,确认收到几件衣服,并发送积分。小A捐赠了几次衣服后,发现自己拥有了不少公益积分,小A可以在积分频道可以兑换书籍,也可以兑换一些公益合作商家的优惠卡,如洗车、吃饭等。

    故事里大概会包含什么功能呢?

    对于捐赠人:

    1. 登录/注册:支持用微博、QQ账户登录
    2. 填写并提交捐赠请求:捐赠内容、图片、新旧程度、上门时间(可选择提前电话预约)
    3. 查看并追踪捐赠状态:看到过去捐赠的各种衣物及领取的积分
    4. 捐赠衣物并获取积分
    5. 公益积分查看:查看自己的积分情况,历史总积分,已兑换的及未兑换的
    6. 积分兑换:兑换各参与的公益商家优惠券。

    部分业务流程图示例:

    2013-05-26_204954

    可以说这两步的工作是绘制页面流程图必不可少的准备,我们明白了要做什么,为哪些人做,主要的功能是哪些?功能之间的流程是什么样的。但是因为是互联网产品,这些流程必须以页面为承载体,比如“提交捐赠”是一项活动,到了页面设计时,我们要用几张页面去完成这一个动作呢?这些页面彼此是什么关系呢?

    绘制页面流程图

    现在我们已经知道了系统应该有哪些功能,我们应该提供哪些内容,现在就需要将这些功能及内容分配到不同的页面去。

    页面流程图简要

    1. 页面=操作+内容,操作是需要用户触发的,包含链接、按钮、表单等等。用户通过这些操作,看到同一个页面上不同的内容,或者跳转到其他的页面。
    2. 页面流程图目标:表现用户的不同的操作指令下不同页面流转关系。
    3. 页面流程图元素:页面、操作或状态、链接线
    4. 页面也有分类:请注意有些操作可能不会带你去一个实际的页面,而是有可能发个短信、发个邮件等,这些也需要被表现出来。

    2013-05-26_211618

    如何开始绘制?

    即像讲一个故事,最简单的就是从用户的第一个初始页面开始进行。

    或者有人问了:

    我的用户角色也许根本用不到有些操作,条条大路通罗马,他只选择了其中一条路径,那我怎么能把所有的页面都画出来呢?

    我的经验是:不要细分用户类型,而是根据页面穷举各项操作,基于假设判断用户若点击什么就会到哪里。

    在这个案例中,我希望用户的第一个页面是首页。

    他有两个主要被引导的操作:

    1. 可以查看捐赠或新提交一个捐赠
    2. 可以查看公益积分或兑换积分

    以下就是从这个页面开始的一系列页面流程:

    2013-05-26_205156

    这个图受篇幅限制没有全部画完,比如到了积分商城后还有一系列页面呢,有兴趣的同学继续试试。

    一些提示:

    1. 页面一般用矩形表示,页面上要体现关键的内容块及主要操作。
    2. 使用圆角矩形放到连接线上表示各项操作。一个页面可引出多个操作指向不同的页面。
    3. 只体现系统判断,用户本身的判断不需要体现出来——比如用户到了详情页面是要购买呢?还是加入收藏呢?还是离开呢?这些直接用操作指向不同的页面即可。
    4. 也有不适合用页面流程图去表现的网站,主要是因为操作类的更多不是纵深型的一步步流程,而是平行中跳转,如门户类网站查看新闻的用例、如音乐类网站等等。这种情况下,最好用站点地图(site map)去表达页面从属关系就可以了。

    可能大家会留意到,上图中的操作有可能就是设计时页面上实实在在的按钮或者文字链接,没错的。

    但是有些页面流程图未必是能够完全按操作、页面来连接的,下图是我在3年前画过的一个页面流程图(请注意这个流程图就不符合我说的几条规则)。当用户到达详情页后,他的下一步操作可能是什么?详情页的操作太多了……支付、加入购物车、加入收藏、推荐给朋友、离开、再逛……下图中的“以后再说”并不是操作,“对比后再决定”也不是操作,这正像什么呢?是对操作做一个人工的分类还是表现用户的意图倾向?当时我凭直觉去画这张图的时候,用意是在于探寻购物路径中有无可优化的空间,所以是想要把用户操作前的意图列举出来。如果用户喜欢这个商品的话,可能会想做什么?目前我们提供了哪些功能可以继续往下走?当不喜欢的话,他们可能想要做什么?我们目前又做了什么挽留?

    2013-05-26_210720

    【点击图片可查看大图】

    所以,我们也可以在页面流程图中加入“意图”项,你可以用你喜欢的形状去表示“意图”,比如椭圆。

    最后

    一定会有人问绘制工具是什么……

    文中所举的捐赠案例,我是用PPT随手画的。

    如果篇幅不需要太大,完全可以用PPT绘制,另存为图片,导入到AXURE原型工具里,然后在每个页面上加一个透明的矩形,再添加链接到相应的线框图页面。

    以前也多用AXURE绘制页面流程图,如:(这个例子中,操作按钮没有放到链接线上,是因为此用例页面数量不多,页面可以比较大,连接线可以直接从页面上的操作中画出)

    2013-05-26_212938

     

    最后我想说的是,页面流程图重在把事情讲清楚,把页面交代清楚,不必追求太多的规则和条条框框,你可以用你最舒适的方式去表达。文中所举的方式,是我比较习惯的,也欢迎各位同学交流下你们是怎么绘制页面流程图的。


    展开全文
  • echarts画工作流(流程图

    万次阅读 2019-09-23 19:58:15
    一般流程图如果仅仅是作为静态页面,不需要考虑交互还是很容易画出来的,但是如果需要考虑到交互效果,那就需要自己在基于对开源框架集成使用中多加思考了,毕竟现在很多开源框架的基本思想都是组...
  • 而今天我们要介绍的项目,就是基于Python和Graphviz开发的,能将源代码转化为流程图的工具:pycallgraph 1.准备 开始之前,你要确保Python和pip已经成功安装在电脑上噢,如果没有,请访问这篇文章:超详细Python安装...
  • GNU Radio 流程图编程(基于 GNU Radio Companion 平台)

    万次阅读 多人点赞 2019-03-24 11:27:05
    GNU Radio 流程图编程(基于 GNU Radio Companion 平台)0x00. GNU Radio 介绍0x01. 打开 GNU Radio0x02. 使用搜索功能寻找相应的模块0x03. 添加一个新的模块0x04. 修改模块的参数0x05. 建立信号流程图0x06. 对 GUI ...
  • Java程序员必备的一些流程图(拿走不谢)

    万次阅读 多人点赞 2019-07-08 15:09:35
    整理了一些Java基础流程图/架构图,做一下笔记,大家一起学习。 (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!) 1.spring的生命周期 Spring作为当前Java最流行、最...
  • 页面流程图

    千次阅读 2019-03-11 13:01:06
    有一些同学看过了我写的《如何绘制业务流程图》,发私信过来希望我谈谈页面流程图。 这个话题其实我也酝酿过,但一直没有写出来。细究起来,除了懒,原因其实有好几条: 这一年半来的工作都是围绕数据平台建设,不是...
  • 【常识】流程图绘制教程

    千次阅读 2019-12-19 16:08:00
    基于这个疑问,我出于好奇认真地查阅了一些资料,发现流程图确实有一套明确且通用的规范。很多人都没有意识到这些使用规范,认为流程图随随便便画一下,别人看得懂就行了,其实,这种看法是不正确的。 下面,根据我...
  • 大数据流程图

    万次阅读 2018-12-06 10:10:24
     1、大数据流程图      2、大数据各个环节主要技术    2.1、数据处理主要技术  Sqoop:(发音:skup)作为一款开源的离线数据传输工具,主要用于Hadoop(Hive) 与传统数据库(MyS...
  • vue-flowy前端流程图绘制工具

    千次阅读 2020-01-09 10:22:14
    vue-flowy ...基于Vue的智能流程图创建。 适用于Vue 2。 安装 通过NPM安装 $ npm install vue-flowy -save 通过纱安装 $ yarn add vue-flowy 注册为组件 import {VueFlow...
  • 这个View的需求是一个订单的流程图。具体效果如下    需求分析。初次看到这张图。感觉实现的方式有很多种。用组合控件的方式可以很方便的实现这种需求。一个LineaLayout中横向布局四个Tab,上面是...
  • 作者:赵丽丽 ...基于内容的图像检索(CBIR, Content Based Image Retrieval)是相对成熟的技术领域,在工业界也有广泛的应用场景,如搜索引擎(Google、百度)的以搜图功能,各电商网站(淘宝、Amazo...
  • Python 一键转化代码为流程图

    千次阅读 2020-05-04 15:27:22
    Graphviz是一个可以对图进行自动布局的绘图工具,由贝尔...而今天我们要介绍的项目,就是基于Python和Graphviz开发的,能将源代码转化为流程图的工具:pycallgraph。 1.准备 开始之前,你要确保Python和pip已经...
  • 背景       在阿里云,可能每时每刻都在身边充斥着“数据表”“模型”“调度”等等抽象概念。作为阿里云的前端,如何将这种抽象的概念实体化可视化地给客户或者其他合作伙伴展示...“流程图”,
  • 本来写完上篇,我发现没有太多...时间管理也如此,很多时间管理技巧牛逼的人未必能够把时间管理做到位,因为内心克服不了强大的拖延症,而克服拖延很多时候是一个心理问题而不是技巧问题……咳咳,这不是在说我自己吗
  • 一款合格的流程图应用程序,应该可以同时满足业务流程图、组织结构图、数据流程图、程序流程图、思维导图的绘制。并且可以直接通过图表,对数据进行可视化、分析以及整理归类,将大量数据化繁为简。如果可以的话,还...
  • 基于gitlab的CICD流程规范

    万次阅读 2021-09-27 15:46:09
    基于gitlab的CICD流程规范一、简介二、CICD流程图三、CICD说明四、结合CICD流程拓展1、业务代码-数据库基于flyway实现版本控制2、数据库版本控制3、二进制或内置五、代码质量检查及自动化测试(未来)六、疑问解答与...
  • 产品逻辑图画的很好看,确实好看,逻辑清清楚楚,一眼就能看清楚,这...本文关键词:免费画流程图、思维导图、甘特图软件推荐:用图表理清工作难题 文字的思考也有局限,例如之前我在规划自己一整天的 Google 云...
  • 绘制页面流程图

    千次阅读 2017-06-16 10:54:26
    业务故事:小A有一批衣服需要捐赠,他在手机上提交一份捐赠需求,写明自己要捐赠什么衣服,新旧程度,多少数量,什么方式预约上门时间……小A提交捐赠后,收到预约电话,约好了3天后的周末下午上门取衣服。...
  • 如何绘制业务流程图

    千次阅读 2017-06-10 16:32:48
    本来写完上篇,我发现没有太多...时间管理也如此,很多时间管理技巧牛逼的人未必能够把时间管理做到位,因为内心克服不了强大的拖延症,而克服拖延很多时候是一个心理问题而不是技巧问题……咳咳,这不是在说我自己吗
  • 软件自动化测试基本流程(附流程图)

    万次阅读 2020-06-09 14:37:58
    自动化测试与软件开发过程从本质上来讲是... 下面介绍一下软件自动化测试基本流程(附流程图) 制定测试计划 在展开自动化测试之前,最好做个测试计划,明确测试对象、测试目的、测试的项目内容、测试的方法、测试的进度
  • 对于“程序设计”的工作,许多初学者的理解就是“写代码”。同样,新手们苦恼的问题是,他们只会“写代码”。当接到一个新的任务,...就像建筑、机械等行业的要画设计、施工,程序设计的思路也有必要用的形式画
  • Web流程图的绘画指南

    千次阅读 2012-08-31 08:40:29
    转载自雷锋网 ...在作者设计生涯开始的早期,就开始接触流程图。一个好的流程图,可以用来理解需求、解析复杂的业务流程、同时也有助于交互设计。为了使流程图的价值最大化,文
  • RabbitMQ收发消息流程图

    千次阅读 2016-10-21 17:20:23
    这里主要是画个流程图,便于理解RabbitMQ的消息流程 Message​ Queue 模型中,向MQ发送消息的程序称为生产者,从MQ读取消息的是消费者 先说一下RabbitMQ的各个组件​ 程序和RabbitMQ之间的连接是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 411,068
精华内容 164,427
关键字:

基于时间的流程图

友情链接: 0racle.zip