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

218 篇文章 18 订阅
208 篇文章 8 订阅

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

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

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

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

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

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

+ View Code ?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值