用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

转自https://blog.csdn.net/oaa608868/article/details/53471733?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-8.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-8.control

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值