ProcessViewer.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <template>
  2. <div class="my-process-designer">
  3. <div class="my-process-designer__container">
  4. <div class="my-process-designer__canvas" ref="bpmn-canvas"></div>
  5. </div>
  6. </div>
  7. </template>
  8. <script>
  9. import BpmnViewer from "bpmn-js/lib/Viewer";
  10. import DefaultEmptyXML from "./plugins/defaultEmpty";
  11. export default {
  12. name: "MyProcessViewer",
  13. componentName: "MyProcessViewer",
  14. props: {
  15. value: String, // xml 字符串
  16. prefix: {
  17. type: String,
  18. default: "camunda"
  19. },
  20. taskData: {
  21. type: Array,
  22. default: () => []
  23. }
  24. },
  25. data() {
  26. return {
  27. xml: '',
  28. tasks: [],
  29. };
  30. },
  31. mounted() {
  32. this.xml = this.value;
  33. this.tasks = this.taskData;
  34. // 初始化
  35. this.initBpmnModeler();
  36. this.createNewDiagram(this.xml);
  37. this.$once("hook:beforeDestroy", () => {
  38. if (this.bpmnModeler) this.bpmnModeler.destroy();
  39. this.$emit("destroy", this.bpmnModeler);
  40. this.bpmnModeler = null;
  41. });
  42. },
  43. watch: {
  44. value: function (newValue) { // 在 xmlString 发生变化时,重新创建,从而绘制流程图
  45. this.xml = newValue;
  46. this.createNewDiagram(this.xml);
  47. },
  48. taskData: function (newTaskData) {
  49. this.tasks = newTaskData;
  50. this.createNewDiagram(this.xml);
  51. }
  52. },
  53. methods: {
  54. initBpmnModeler() {
  55. if (this.bpmnModeler) return;
  56. this.bpmnModeler = new BpmnViewer({
  57. container: this.$refs["bpmn-canvas"],
  58. bpmnRenderer: {
  59. }
  60. })
  61. },
  62. /* 创建新的流程图 */
  63. async createNewDiagram(xml) {
  64. // 将字符串转换成图显示出来
  65. let newId = `Process_${new Date().getTime()}`;
  66. let newName = `业务流程_${new Date().getTime()}`;
  67. let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
  68. try {
  69. // console.log(this.bpmnModeler.importXML);
  70. let { warnings } = await this.bpmnModeler.importXML(xmlString);
  71. if (warnings && warnings.length) {
  72. warnings.forEach(warn => console.warn(warn));
  73. }
  74. // 高亮流程图
  75. await this.highlightDiagram();
  76. } catch (e) {
  77. console.error(e);
  78. // console.error(`[Process Designer Warn]: ${e?.message || e}`);
  79. }
  80. },
  81. /* 高亮流程图 */
  82. async highlightDiagram() {
  83. if (this.tasks.length === 0) {
  84. return;
  85. }
  86. if (!this.bpmnModeler.getDefinitions().rootElements[0].flowElements) {
  87. return;
  88. }
  89. // 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现
  90. let canvas = this.bpmnModeler.get('canvas');
  91. this.bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach(n => {
  92. let completeTask = this.tasks.find(m => m.definitionKey === n.id)
  93. let todoTask = this.tasks.find(m => !m.endTime)
  94. let endTask = this.tasks[this.tasks.length - 1]
  95. if (n.$type === 'bpmn:UserTask') {
  96. if (completeTask) {
  97. canvas.addMarker(n.id, completeTask.endTime ? 'highlight' : 'highlight-todo');
  98. // console.log(n.id + ' : ' + (completeTask.endTime ? 'highlight' : 'highlight-todo'));
  99. n.outgoing?.forEach(nn => {
  100. let targetTask = this.tasks.find(m => m.definitionKey === nn.targetRef.id)
  101. if (targetTask) {
  102. canvas.addMarker(nn.id, targetTask.endTime ? 'highlight' : 'highlight-todo');
  103. } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
  104. // canvas.addMarker(nn.id, 'highlight');
  105. canvas.addMarker(nn.id, completeTask.endTime ? 'highlight' : 'highlight-todo');
  106. canvas.addMarker(nn.targetRef.id, completeTask.endTime ? 'highlight' : 'highlight-todo');
  107. } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
  108. if (!todoTask && endTask.definitionKey === n.id) {
  109. canvas.addMarker(nn.id, 'highlight');
  110. canvas.addMarker(nn.targetRef.id, 'highlight');
  111. }
  112. if (!completeTask.endTime) {
  113. canvas.addMarker(nn.id, 'highlight-todo');
  114. canvas.addMarker(nn.targetRef.id, 'highlight-todo');
  115. }
  116. }
  117. });
  118. }
  119. } else if (n.$type === 'bpmn:ExclusiveGateway') {
  120. n.outgoing?.forEach(nn => {
  121. let targetTask = this.tasks.find(m => m.definitionKey === nn.targetRef.id)
  122. if (targetTask) {
  123. canvas.addMarker(nn.id, targetTask.endTime ? 'highlight' : 'highlight-todo');
  124. }
  125. })
  126. } else if (n.$type === 'bpmn:ParallelGateway') {
  127. if (completeTask) {
  128. canvas.addMarker(n.id, completeTask.endTime ? 'highlight' : 'highlight-todo')
  129. n.outgoing?.forEach(nn => {
  130. const targetTask = this.taskList.find(m => m.definitionKey === nn.targetRef.id)
  131. if (targetTask) {
  132. canvas.addMarker(nn.id, targetTask.endTime ? 'highlight' : 'highlight-todo')
  133. canvas.addMarker(nn.targetRef.id, targetTask.endTime ? 'highlight' : 'highlight-todo')
  134. }
  135. })
  136. }
  137. } else if (n.$type === 'bpmn:StartEvent') {
  138. n.outgoing?.forEach(nn => {
  139. let completeTask = this.tasks.find(m => m.definitionKey === nn.targetRef.id)
  140. if (completeTask) {
  141. canvas.addMarker(nn.id, 'highlight');
  142. canvas.addMarker(n.id, 'highlight');
  143. return
  144. }
  145. });
  146. } else if (n.$type === 'bpmn:EndEvent') {
  147. if (endTask.definitionKey === n.id && endTask.endTime) {
  148. canvas.addMarker(n.id, 'highlight')
  149. return
  150. }
  151. }
  152. })
  153. }
  154. }
  155. };
  156. </script>
  157. <style>
  158. .highlight.djs-shape .djs-visual > :nth-child(1) {
  159. fill: green !important;
  160. stroke: green !important;
  161. fill-opacity: 0.2 !important;
  162. }
  163. .highlight.djs-shape .djs-visual > :nth-child(2) {
  164. fill: green !important;
  165. }
  166. .highlight.djs-shape .djs-visual > path {
  167. fill: green !important;
  168. fill-opacity: 0.2 !important;
  169. stroke: green !important;
  170. }
  171. .highlight.djs-connection > .djs-visual > path {
  172. stroke: green !important;
  173. }
  174. .highlight-todo.djs-connection > .djs-visual > path {
  175. stroke: orange !important;
  176. stroke-dasharray: 4px !important;
  177. fill-opacity: 0.2 !important;
  178. }
  179. .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
  180. fill: orange !important;
  181. stroke: orange !important;
  182. stroke-dasharray: 4px !important;
  183. fill-opacity: 0.2 !important;
  184. }
  185. .highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
  186. fill: green !important; /* color elements as green */
  187. }
  188. /deep/.highlight.djs-shape .djs-visual > :nth-child(1) {
  189. fill: green !important;
  190. stroke: green !important;
  191. fill-opacity: 0.2 !important;
  192. }
  193. /deep/.highlight.djs-shape .djs-visual > :nth-child(2) {
  194. fill: green !important;
  195. }
  196. /deep/.highlight.djs-shape .djs-visual > path {
  197. fill: green !important;
  198. fill-opacity: 0.2 !important;
  199. stroke: green !important;
  200. }
  201. /deep/.highlight.djs-connection > .djs-visual > path {
  202. stroke: green !important;
  203. }
  204. /deep/.highlight-todo.djs-connection > .djs-visual > path {
  205. stroke: orange !important;
  206. stroke-dasharray: 4px !important;
  207. fill-opacity: 0.2 !important;
  208. marker-end: url(#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr);
  209. }
  210. /deep/.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
  211. fill: orange !important;
  212. stroke: orange !important;
  213. stroke-dasharray: 4px !important;
  214. fill-opacity: 0.2 !important;
  215. }
  216. </style>