公司研发的管理系统有工作流图形化设计和查看功能,这个功能的开发历史比较久远。在那个暗无天日的年月里,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

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

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

    从零开始学数据分析,什么程度可以找到工作?( 内附20G.5000分钟数据分析工具教程大合集 )   我现在在Coursera上面学data science 中的R programming,过去很少接 ...

  2. 《工作型PPT设计之道》培训心得

    参加包翔老师的“工作型PPT设计之道>培训,颇多心得,后来为部门新员工和同组同事做了转化培训,将心得整理成一份PPT讲义,效果颇佳.现将主要心得整理于此.因时间仓促,24条心得有拼凑之嫌,有待今 ...

  3. 基于MATLAB2016b图形化设计自动生成Verilog语言的积分模块及其应用

    在电力电子变流器设备中,常常需要计算发电量,由于电力电子变流器设备一般是高频变流设备,所以发电量的计算几乎时实时功率的积分,此时就会用到一个积分模块.发电量计算的公式如下:Q=∫P. FPGA由于其并 ...

  4. vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图

    vue移动端金融UI组件库滴滴MandMobile面向金融场景设计附功能思维导图 Mand Mobile是面向金融场景设计的移动端组件库,基于Vue.js实现.目前已实际应用于滴滴四大金融业务板块的1 ...

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

    一.基本信息 标题:基于ASP.NET的高校辅导员工作管理系统的设计与实现 时间:2017 出版源:南通理工学院 关键词:ASP.NET; SQL Server; 高校; 管理系统; 辅导员; 二.研 ...

  6. Python 爬虫的工具列表 附Github代码下载链接

    Python爬虫视频教程零基础小白到scrapy爬虫高手-轻松入门 https://item.taobao.com/item.htm?spm=a1z38n.10677092.0.0.482434a6E ...

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

    <html 5+css 3网页设计经典范例(附cd光盘1张)>共分为18章,涵盖了html 5和css3中各方面的技术知识.主要内容包括html 5概述.html 5与html 4的区别. ...

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

    上一节讲述了在没有MMU的CPU(如80251.MIPS M控制器系列.ARM cortex m系列)上实现虚拟内存管理的集成硬件设计方法.新设计的内存管理管理单元要实现虚拟内存管理还须要操作系统.代 ...

  9. XGBoost参数调优完全指南(附Python代码)

    XGBoost参数调优完全指南(附Python代码):http://www.2cto.com/kf/201607/528771.html https://www.zhihu.com/question/ ...

随机推荐

  1. Scrapy官网程序执行示例

    Windows 10家庭中文版本,Python 3.6.4,Scrapy 1.5.0, Scrapy已经安装很久了,前面也看了不少Scrapy的资料,自己尝试使其抓取微博的数据时,居然连登录页面(首页 ...

  2. C++之可调用对象

    C++中的可调用对象分为以下几种: 函数 函数指针 lambda表达式 bind创建的对象 重载了函数调用运算符(即“()”)的类 函数.函数指针不再介绍.lambda表达式与bind创建的类参考—— ...

  3. TypeScript 2 : 获取当前日期及前后范围日期【Array】

    前言 今天有个接口字段需求,要写一个今天及前几天的日期传过去: 在网上找了下都木有什么比较好的方案:就自己写了一个. 因为技术栈就是NG2+TS2+WEBPACK,这里的代码需要一定的TS2及ES6的 ...

  4. hdu 1007 N个点中输出2点的最小距离的一半

    分治法 Sample Input20 01 121 11 13-1.5 00 00 1.50 Sample Output0.710.000.75 # include <iostream> ...

  5. C语言:10个整数排序(别忘了负数)

    题目内容: 10个整数排序(别忘了负数) 例如 input 1 0 2 0 3 4 1 9 8 7 output 0 0 1 1 2 3 4 7 8 9 编码: void sort(int *a); ...

  6. centos6.8部署denyhosts设置sshd黑名单

    DenyHosts是Python语言写的一个程序,它会分析sshd的日志文件(/var/log/secure),当发现重 复的攻击时就会记录IP到/etc/hosts.deny文件,从而达到自动屏IP ...

  7. mysql 拾遗提高(函数、事务、索引)

    目录 1.tips 2.事务(transaction) 3.索引(index) 4.数据库的导出和备份 5.函数 6.防SQL注入 7.使用Explain分析SQL语句 8.视图(view) 1.ti ...

  8. Ionic Js七:手势事件

    1.on-hold 长按的时间是500毫秒. HTML 代码 <button on-hold="onHold()" class="button">长 ...

  9. 20169211 《Linux内核原理与分析》第十一周作业

    SET-UID程序漏洞实验 一.实验简介 Set-UID 是Unix系统中的一个重要的安全机制.当一个Set-UID程序运行的时候,它被假设为具有拥有者的权限.例如,如果程序的拥有者是root,那么任 ...

  10. 美团DB数据同步到数据仓库的架构与实践

    背景 在数据仓库建模中,未经任何加工处理的原始业务层数据,我们称之为ODS(Operational Data Store)数据.在互联网企业中,常见的ODS数据有业务日志数据(Log)和业务DB数据( ...