用zrender实现工作流图形化设计(附范例代码)

公司研发的管理系统有工作流图形化设计和查看功能,这个功能的开发历史比较久远。在那个暗无天日的年月里,IE几乎一统江湖,所以顺理成章地采用了当时红极一时的VML技术。

后来的事情大家都知道了,IE开始走下坡路,VML这个技术现在早已灭绝,导致原来的工作流图形化功能完全不能使用,所以需要采用新技术来重写工作流图形化功能。

多方对比之后,决定采用zrender库来实现(关于zrender库的介绍,请看http://ecomfe.github.io/zrender/),花了一天的时间,终于做出了一个大致的效果模型,如下图所示:

流程图由两类部件组成:活动部件和连接弧部件,每一类部件包含多个性状不同的部件。

以活动部件为例,圆形的是开始活动,平行四边形是自动活动,长方形是人工活动,等等。

在代码实现上,定义了Unit(部件基类),所有的部件都继承自这个基类。通过Graph类来管理整个流程图,包括所有部件、上下文菜单等等都由Graph来统一管理和调度,代码如下:

var Libra = {};
Libra.Workflow = {};

Libra.Workflow.Graph = function(type, options){
    var graph = this,
        activities = {},
        transitions = {};
    var zrenderInstance,
        contextMenuContainer;

    this.type = type;

    this.addActivity = function(activity){
        activity.graph = graph;
        activities[activity.id] = {object:activity};
    };

    this.getActivity = function(id){ return activities[id].object; };

    this.addTransition = function(transition){
        transition.graph = graph;
        transitions[transition.id] = {object:transition};
    };

    function modElements(shapes){
        shapes.each(function(shape){ zrenderInstance.modElement(shape); });
        return shapes;
    }

    // 当前正在拖放的节点
    var dragingActivity = null;
    // 活动节点拖放开始
    this.onActivityDragStart = function(activity){ dragingActivity = activity; };
    // 活动节点拖放结束
    this.onActivityDragEnd = function(){
        if(dragingActivity) refreshActivityTransitions(dragingActivity);
        dragingActivity = null;
    };
    // 拖动过程处理
    function zrenderInstanceOnMouseMove(){
        if(dragingActivity != null) refreshActivityTransitions(dragingActivity);
    }
    // 刷新活动相关的所有连接弧
    function refreshActivityTransitions(activity){
        var activityId = activity.id;
        for(var key in transitions){
            var transition = transitions[key].object;
            if(transition.from === activityId || transition.to == activityId){
                zrenderInstance.refreshShapes(modElements(transition.refresh(graph)));
            }
        }
    }

    // 当前选中的部件
    var selectedUnit = null;
    this.onUnitSelect = function(unit){
        if(selectedUnit) zrenderInstance.refreshShapes(modElements(selectedUnit.unselect(graph)));
        zrenderInstance.refreshShapes(modElements(unit.select(graph)));
        selectedUnit = unit;
    };

    // 记录当前鼠标在哪个部件上,可以用来生成上下文相关菜单
    var currentUnit = null;
    this.onUnitMouseOver = function(unit){
        currentUnit = unit;
    };
    this.onUnitMouseOut = function(unit){
        if(currentUnit === unit) currentUnit = null;
    };
    // 上下文菜单事件响应
    function onContextMenu(event){
        Event.stop(event);
        if(currentUnit) currentUnit.showContextMenu(event, contextMenuContainer, graph);
    }

    this.addShape = function(shape){
        zrenderInstance.addShape(shape);
    };

    // 初始化
    this.init = function(){
        var canvasElement = options.canvas.element;
        canvasElement.empty();
        canvasElement.setStyle({height: document.viewport.getHeight() + ‘px‘});
        zrenderInstance = graph.type.zrender.init(document.getElementById(canvasElement.identify()));
        for(var key in activities){ activities[key].object.addTo(graph); }
        for(var key in transitions){ transitions[key].object.addTo(graph); }

        // 创建上下文菜单容器
        contextMenuContainer = new Element(‘div‘, {‘class‘: ‘context-menu‘});
        contextMenuContainer.hide();
        document.body.appendChild(contextMenuContainer);
        Event.observe(contextMenuContainer, ‘mouseout‘, function(event){
                // 关闭时,应判断鼠标是否已经移出菜单容器
                if(!Position.within(contextMenuContainer, event.clientX, event.clientY)){
                    contextMenuContainer.hide();
                }
            });

        // 侦听拖动过程
        zrenderInstance.on(‘mousemove‘, zrenderInstanceOnMouseMove);
        // 上下文菜单
        Event.observe(document, ‘contextmenu‘, onContextMenu);
    };

    // 呈现或刷新呈现
    this.render = function(){
        var canvasElement = options.canvas.element;
        canvasElement.setStyle({height: document.viewport.getHeight() + ‘px‘});
        zrenderInstance.render();
    };
};

/*
 * 部件(包括活动和连接弧)
 */
Libra.Workflow.Unit = Class.create({
    id: null,
    title: null,
    graph: null,
    // 当前是否被选中
    selected: false,
    // 上下文菜单项集合
    contextMenuItems: [],

    initialize: function(options){
        var _this = this;
        _this.id = options.id;
        _this.title = options.title;
    },

    createShapeOptions: function(){
        var _this = this;
        return {
            hoverable : true,
            clickable : true,

            onclick: function(params){
                // 选中并高亮
                _this.graph.onUnitSelect(_this);
            },

            onmouseover: function(params){ _this.graph.onUnitMouseOver(_this); },
            onmouseout: function(params){ _this.graph.onUnitMouseOut(_this); }
        };
    },

    addTo: function(graph){},

    // 刷新显示
    refresh: function(graph){ return []; },

    // 选中
    select: function(graph){
        this.selected = true;
        return this.refresh(graph);
    },
    // 取消选中
    unselect: function(graph){
        this.selected = false;
        return this.refresh(graph);
    },

    // 显示上下文菜单
    showContextMenu: function(event, container, graph){
        container.hide();
        container.innerHTML = ‘‘;

        var ul = new Element(‘ul‘);
        container.appendChild(ul);
        this.buildContextMenuItems(ul, graph);

        // 加偏移,让鼠标位于菜单内
        var offset = -5;
        var rightEdge = document.body.clientWidth - event.clientX;
		var bottomEdge = document.body.clientHeight - event.clientY;
		if (rightEdge < container.offsetWidth)
			container.style.left = document.body.scrollLeft + event.clientX - container.offsetWidth + offset;
		else
			container.style.left = document.body.scrollLeft + event.clientX + offset;

		if (bottomEdge < container.offsetHeight)
			container.style.top = document.body.scrollTop + event.clientY - container.offsetHeight + offset;
		else
			container.style.top = document.body.scrollTop + event.clientY + offset;

        container.show();
    },

    // 创建上下文菜单项
    buildContextMenuItems: function(container, graph){
        var unit = this;
        unit.contextMenuItems.each(function(item){
                item.addTo(container);
            });
    }
});

zrender默认已经支持了对图形的拖动,所以活动部件的拖动只需要设置dragable属性为真即可。不过虽然活动部件可以拖动,但活动部件上的连接线不会跟着一起动,这需要侦听拖动开始事件、拖动结束事件以及拖动过程中的鼠标移动事件,来实现连接线的实时重绘。在Graph中侦听鼠标移动事件,就是为了实现连接线等相关图形的实时重绘。

每个部件都规划了八个连接点,默认情况下,连接弧不固定与某个连接点,而是根据活动部件的位置关系,自动找出最近的连接点,所以在拖动活动部件的时候,可以看到连接弧在活动部件上的连接点在不断变化。

上面只是以最简化的方式实现了工作流图形化设计的基本功能,完善的图形化设计应包含曲线、连接点的拖放等等,如下图所示:

上面是公司产品中的工作流图形化设计功能,功能相对于上面的范例要完善许多,但基本原理不变,无非就是细节处理更多一些。

特别是在画的地方花了很多时间,中学的平面几何知识几乎都忘记了,所以做起来花了不少功夫,这部分准备以后专门写篇文章来详谈。

本文的结尾会给出前期建模测试阶段的完整代码下载,是前期代码,不是最终代码,原因你懂的,见谅。

http://files.cnblogs.com/files/rrooyy/WorkflowGraphic.zip

时间: 12-14

用zrender实现工作流图形化设计(附范例代码)的相关文章

从零开始学数据分析,什么程度可以找到工作?( 内附20G、5000分钟数据分析工具教程大合集 )

从零开始学数据分析,什么程度可以找到工作?( 内附20G.5000分钟数据分析工具教程大合集 ) 我现在在Coursera上面学data science 中的R programming,过去很少接触过统计.计算机这两个学科,现在很想转行做数据.问题如下: 1.Data需要学到什么程度可以找工作?2.初级的数据分析会做哪些工作?3.数据分析有什么小方向吗?4.想要深度做数据分析有怎样的建议? 5.统计的学习应该从哪里下手? 本文将给你以上问题所有答案,文末还有UniCareer为大家独家整理的20

39.JAVA编程思想之外篇——JAVA图形化设计精简大全一文覆盖

39.JAVA编程思想之外篇--JAVA图形化设计精简大全一文覆盖 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51204948 目录 Java图形化界面设计--容器(JFrame)...1 Java基本类(JFC)...1 l     AWTAbstract Window Toolkit(AWT)抽象窗口工具包... 2 l     Swing包... 2 l     AWT和Swing的区别... 6 Swing基本框

工作型PPT设计的10大建议

今天收到一位十分热心关注我的朋友的email,他是一位“人民工程师”(即“教员”),肩负着传道授业解惑之重担,同时,鉴于他的问题也极具普遍性,所以,我打算在以后一段时间里,就这个议题做一些讨论和研讨.今天权当开个头吧. 这位教师提的问题其实很简单:“目前PPT设计的多种流派,似乎都不合适我们工作时运用,我日常工作中终究该如何设计较美观的PPT呢?” 您是不是也会有类型的问题呢?呵呵. 坦率的说,在我开博客之前的几个月里,这个问题一直纠缠着我,慢慢的,我明白了:纠结我心头的问题基本不是“我能够设计

基于ASP.NET的高校辅导员工作管理系统的设计与实现--论文随笔(四)

一.基本信息 标题:基于ASP.NET的高校辅导员工作管理系统的设计与实现 时间:2017 出版源:南通理工学院 关键词:ASP.NET; SQL Server; 高校; 管理系统; 辅导员; 二.研究背景 问题定义:高校学生数量越来越多,学生信息也越来越庞大,在辅导员的日常工作中,所使用的传统的电子表格和纸质文档容易丢失造成学校多部门间缺乏资源共享机制,同时管理不变,数据易丢失.为了提高辅导员队伍整体工作效率已经成为高校学生工作中有待解决的难题. 难点:引用ajax技术减轻服务器的负担, 相关

大学辅导员工作管理系统的设计与实现

一.基本信息 标题: 时间:2013 出版源:电子科技大学 关键词:B/S结构; 数据挖掘技术; 高校辅导员; 管理系统; 二.研究背景 问题定义:现在一些高校中没有专门针对大学辅导员管理工作的完善系统,辅导员面对各个部门都是自成模块,各个模块之间都没有很好的结合起来,各自只处理着自己部门相关工作,没有有效的沟通和合作.有些工作在多个部门之间有很多重复,冗余的工作 难点:不同的信息管理系统,开发技术手段不同,功能呢也不同,由于有些高校中,还未实现辅导员工作系统的使用,所以好的.合适的开发技术选择

高校辅导员工作管理系统的设计与实现--文献随笔(十一)

一.基本信息 标题:高校辅导员工作管理系统的设计与实现 时间:2014 出版源:华南理工大学 关键词:辅导员管理; 响应式; Asp.net MVC; 关联规则; 二.研究背景 问题定义:在我国高校的管理工作中,学生管理分为很多个模块,当前大部分高校都有自身的学术.后勤等方面的信息化管理系统,但是功能比较单一直接,缺乏资源共享机制,高校辅导员日常的基本工作是作为大学生政治思想的引导者,通过平时和学生之间的多方面接触,保证学生在校期间的各类具体活动状况的正常. 难点:系统采用分层的设计思想,按照主

SoC嵌入式软件架构设计之三:代码分块(Bank)设计原则

上一节讲述了在没有MMU的CPU(如80251.MIPS M控制器系列.ARM cortex m系列)上实现虚拟内存管理的集成硬件设计方法,新设计的内存管理管理单元要实现虚拟内存管理还需要操作系统.代码分块(Bank)的支持,详见SoC嵌入式软件架构设计之二:没有MMU的CPU实现虚拟内存管理的设计方法.这里要阐述Bank设计的一些原则. Bank设计是为了实现不同时刻运行的Bank(代码块)运行在同一块内存上,所以在运行之前操作系统需要将已存在内存的代码/数据进行缓存处理,并加载将要运行的Ba

如何实现在Windows上运行Linux程序,附示例代码

如何实现在Windows上运行Linux程序,附示例代码 微软在去年发布了Bash On Windows, 这项技术允许在Windows上运行Linux程序, 我相信已经有很多文章解释过Bash On Windows的原理,而今天的这篇文章将会讲解如何自己实现一个简单的原生Linux程序运行器, 这个运行器在用户层实现, 原理和Bash On Windows不完全一样,比较接近Linux上的Wine. 示例程序完整的代码在github上, 地址是 https://github.com/30324

HTML 5+CSS 3网页设计经典范例 (李俊民,黄盛奎) 随书光盘?

<html 5+css 3网页设计经典范例(附cd光盘1张)>共分为18章,涵盖了html 5和css3中各方面的技术知识.主要内容包括html 5概述.html 5与html 4的区别.html 5的结构.canvas绘图功能.网络上的视频和音频应用.表单应用.全新的文件应用.地理位置信息处理.web本地存储应用.离线web应用.web workers api的应用.应用websockets api通信.css样式入门.使用css控制文字样式.使用css设置图片和背景.使用css控制列表.c