ProcessViewer.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <template>
  2. <div class="my-process-designer">
  3. <div class="my-process-designer__container">
  4. <div class="my-process-designer__canvas" style="height: 760px" ref="bpmnCanvas"></div>
  5. </div>
  6. </div>
  7. </template>
  8. <script setup lang="ts" name="MyProcessViewer">
  9. import BpmnViewer from 'bpmn-js/lib/Viewer'
  10. import DefaultEmptyXML from './plugins/defaultEmpty'
  11. import { onMounted, onBeforeUnmount, provide, ref, watch } from 'vue'
  12. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  13. const props = defineProps({
  14. value: {
  15. // BPMN XML 字符串
  16. type: String
  17. },
  18. prefix: {
  19. // 使用哪个引擎
  20. type: String,
  21. default: 'camunda'
  22. },
  23. activityData: {
  24. // 活动的数据。传递时,可高亮流程
  25. type: Array,
  26. default: () => []
  27. },
  28. processInstanceData: {
  29. // 流程实例的数据。传递时,可展示流程发起人等信息
  30. type: Object
  31. },
  32. taskData: {
  33. // 任务实例的数据。传递时,可展示 UserTask 审核相关的信息
  34. type: Array,
  35. default: () => []
  36. }
  37. })
  38. provide('configGlobal', props)
  39. const emit = defineEmits(['destroy'])
  40. let bpmnModeler
  41. const xml = ref('')
  42. const activityLists = ref([])
  43. const processInstance = ref(undefined)
  44. const taskList = ref([])
  45. const bpmnCanvas = ref()
  46. // const element = ref()
  47. const elementOverlayIds = ref()
  48. const overlays = ref()
  49. const initBpmnModeler = () => {
  50. if (bpmnModeler) return
  51. bpmnModeler = new BpmnViewer({
  52. container: bpmnCanvas.value,
  53. bpmnRenderer: {}
  54. })
  55. }
  56. /* 创建新的流程图 */
  57. const createNewDiagram = async (xml) => {
  58. // 将字符串转换成图显示出来
  59. let newId = `Process_${new Date().getTime()}`
  60. let newName = `业务流程_${new Date().getTime()}`
  61. let xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix)
  62. try {
  63. // console.log(this.bpmnModeler.importXML);
  64. let { warnings } = await bpmnModeler.importXML(xmlString)
  65. if (warnings && warnings.length) {
  66. warnings.forEach((warn) => console.warn(warn))
  67. }
  68. // 高亮流程图
  69. await highlightDiagram()
  70. const canvas = bpmnModeler.get('canvas')
  71. canvas.zoom('fit-viewport', 'auto')
  72. } catch (e) {
  73. console.error(e)
  74. // console.error(`[Process Designer Warn]: ${e?.message || e}`);
  75. }
  76. }
  77. /* 高亮流程图 */
  78. // TODO 芋艿:如果多个 endActivity 的话,目前的逻辑可能有一定的问题。https://www.jdon.com/workflow/multi-events.html
  79. const highlightDiagram = async () => {
  80. const activityList = activityLists.value
  81. if (activityList.length === 0) {
  82. return
  83. }
  84. // 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现
  85. // 再次基础上,增加不同审批结果的颜色等等
  86. let canvas = bpmnModeler.get('canvas')
  87. let todoActivity = activityList.find((m) => !m.endTime) // 找到待办的任务
  88. let endActivity = activityList[activityList.length - 1] // 获得最后一个任务
  89. // debugger
  90. // console.log(this.bpmnModeler.getDefinitions().rootElements[0].flowElements);
  91. bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n) => {
  92. let activity = activityList.find((m) => m.key === n.id) // 找到对应的活动
  93. if (!activity) {
  94. return
  95. }
  96. if (n.$type === 'bpmn:UserTask') {
  97. // 用户任务
  98. // 处理用户任务的高亮
  99. const task = taskList.value.find((m) => m.id === activity.taskId) // 找到活动对应的 taskId
  100. if (!task) {
  101. return
  102. }
  103. // 高亮任务
  104. canvas.addMarker(n.id, getResultCss(task.result))
  105. // 如果非通过,就不走后面的线条了
  106. if (task.result !== 2) {
  107. return
  108. }
  109. // 处理 outgoing 出线
  110. const outgoing = getActivityOutgoing(activity)
  111. outgoing?.forEach((nn) => {
  112. // debugger
  113. let targetActivity = activityList.find((m) => m.key === nn.targetRef.id)
  114. // 如果目标活动存在,则根据该活动是否结束,进行【bpmn:SequenceFlow】连线的高亮设置
  115. if (targetActivity) {
  116. canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo')
  117. } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
  118. // TODO 芋艿:这个流程,暂时没走到过
  119. canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo')
  120. canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo')
  121. } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
  122. // TODO 芋艿:这个流程,暂时没走到过
  123. if (!todoActivity && endActivity.key === n.id) {
  124. canvas.addMarker(nn.id, 'highlight')
  125. canvas.addMarker(nn.targetRef.id, 'highlight')
  126. }
  127. if (!activity.endTime) {
  128. canvas.addMarker(nn.id, 'highlight-todo')
  129. canvas.addMarker(nn.targetRef.id, 'highlight-todo')
  130. }
  131. }
  132. })
  133. } else if (n.$type === 'bpmn:ExclusiveGateway') {
  134. // 排它网关
  135. // 设置【bpmn:ExclusiveGateway】排它网关的高亮
  136. canvas.addMarker(n.id, getActivityHighlightCss(activity))
  137. // 查找需要高亮的连线
  138. let matchNN = undefined
  139. let matchActivity = undefined
  140. n.outgoing?.forEach((nn) => {
  141. let targetActivity = activityList.find((m) => m.key === nn.targetRef.id)
  142. if (!targetActivity) {
  143. return
  144. }
  145. // 特殊判断 endEvent 类型的原因,ExclusiveGateway 可能后续连有 2 个路径:
  146. // 1. 一个是 UserTask => EndEvent
  147. // 2. 一个是 EndEvent
  148. // 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。
  149. // 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~
  150. if (!matchActivity || matchActivity.type === 'endEvent') {
  151. matchNN = nn
  152. matchActivity = targetActivity
  153. }
  154. })
  155. if (matchNN && matchActivity) {
  156. canvas.addMarker(matchNN.id, getActivityHighlightCss(matchActivity))
  157. }
  158. } else if (n.$type === 'bpmn:ParallelGateway') {
  159. // 并行网关
  160. // 设置【bpmn:ParallelGateway】并行网关的高亮
  161. canvas.addMarker(n.id, getActivityHighlightCss(activity))
  162. n.outgoing?.forEach((nn) => {
  163. // 获得连线是否有指向目标。如果有,则进行高亮
  164. const targetActivity = activityList.find((m) => m.key === nn.targetRef.id)
  165. if (targetActivity) {
  166. canvas.addMarker(nn.id, getActivityHighlightCss(targetActivity)) // 高亮【bpmn:SequenceFlow】连线
  167. // 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然,如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。
  168. canvas.addMarker(nn.targetRef.id, getActivityHighlightCss(targetActivity))
  169. }
  170. })
  171. } else if (n.$type === 'bpmn:StartEvent') {
  172. // 开始节点
  173. n.outgoing?.forEach((nn) => {
  174. // outgoing 例如说【bpmn:SequenceFlow】连线
  175. // 获得连线是否有指向目标。如果有,则进行高亮
  176. let targetActivity = activityList.find((m) => m.key === nn.targetRef.id)
  177. if (targetActivity) {
  178. canvas.addMarker(nn.id, 'highlight') // 高亮【bpmn:SequenceFlow】连线
  179. canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)
  180. }
  181. })
  182. } else if (n.$type === 'bpmn:EndEvent') {
  183. // 结束节点
  184. if (!processInstance.value || processInstance.value.result === 1) {
  185. return
  186. }
  187. canvas.addMarker(n.id, getResultCss(processInstance.value.result))
  188. } else if (n.$type === 'bpmn:ServiceTask') {
  189. //服务任务
  190. if (activity.startTime > 0 && activity.endTime === 0) {
  191. //进入执行,标识进行色
  192. canvas.addMarker(n.id, getResultCss(1))
  193. }
  194. if (activity.endTime > 0) {
  195. // 执行完成,节点标识完成色, 所有outgoing标识完成色。
  196. canvas.addMarker(n.id, getResultCss(2))
  197. const outgoing = getActivityOutgoing(activity)
  198. outgoing?.forEach((out) => {
  199. canvas.addMarker(out.id, getResultCss(2))
  200. })
  201. }
  202. }
  203. })
  204. }
  205. const getActivityHighlightCss = (activity) => {
  206. return activity.endTime ? 'highlight' : 'highlight-todo'
  207. }
  208. const getResultCss = (result) => {
  209. if (result === 1) {
  210. // 审批中
  211. return 'highlight-todo'
  212. } else if (result === 2) {
  213. // 已通过
  214. return 'highlight'
  215. } else if (result === 3) {
  216. // 不通过
  217. return 'highlight-reject'
  218. } else if (result === 4) {
  219. // 已取消
  220. return 'highlight-cancel'
  221. }
  222. return ''
  223. }
  224. const getActivityOutgoing = (activity) => {
  225. // 如果有 outgoing,则直接使用它
  226. if (activity.outgoing && activity.outgoing.length > 0) {
  227. return activity.outgoing
  228. }
  229. // 如果没有,则遍历获得起点为它的【bpmn:SequenceFlow】节点们。原因是:bpmn-js 的 UserTask 拿不到 outgoing
  230. const flowElements = bpmnModeler.getDefinitions().rootElements[0].flowElements
  231. const outgoing = []
  232. flowElements.forEach((item) => {
  233. if (item.$type !== 'bpmn:SequenceFlow') {
  234. return
  235. }
  236. if (item.sourceRef.id === activity.key) {
  237. outgoing.push(item)
  238. }
  239. })
  240. return outgoing
  241. }
  242. const initModelListeners = () => {
  243. const EventBus = bpmnModeler.get('eventBus')
  244. // 注册需要的监听事件
  245. EventBus.on('element.hover', function (eventObj) {
  246. let element = eventObj ? eventObj.element : null
  247. elementHover(element)
  248. })
  249. EventBus.on('element.out', function (eventObj) {
  250. let element = eventObj ? eventObj.element : null
  251. elementOut(element)
  252. })
  253. }
  254. // 流程图的元素被 hover
  255. const elementHover = (element) => {
  256. element.value = element
  257. !elementOverlayIds.value && (elementOverlayIds.value = {})
  258. !overlays.value && (overlays.value = bpmnModeler.get('overlays'))
  259. // 展示信息
  260. console.log(activityLists.value, 'activityLists.value')
  261. console.log(element.value, 'element.value')
  262. const activity = activityLists.value.find((m) => m.key === element.value.id)
  263. console.log(activity, 'activityactivityactivityactivity')
  264. if (!activity) {
  265. return
  266. }
  267. console.log(elementOverlayIds.value, 'elementOverlayIds.value')
  268. console.log(element.value.id, 'element.value.id')
  269. console.log(
  270. elementOverlayIds.value[element.value.id],
  271. 'elementOverlayIds.value[element.value.id]'
  272. )
  273. console.log(
  274. !elementOverlayIds.value[element.value.id],
  275. '!elementOverlayIds.value[element.value.id]'
  276. )
  277. console.log(element.value.type, 'element.value.type')
  278. if (!elementOverlayIds.value[element.value.id] && element.value.type !== 'bpmn:Process') {
  279. console.log('进入')
  280. let html = `<div class="element-overlays">
  281. <p>Elemet id: ${element.value.id}</p>
  282. <p>Elemet type: ${element.value.type}</p>
  283. </div>` // 默认值
  284. console.log(processInstance.value, 'processInstance')
  285. if (element.value.type === 'bpmn:StartEvent' && processInstance.value) {
  286. html = `<p>发起人:${processInstance.value.startUser.nickname}</p>
  287. <p>部门:${processInstance.value.startUser.deptName}</p>
  288. <p>创建时间:${parseTime(processInstance.value.createTime)}`
  289. } else if (element.value.type === 'bpmn:UserTask') {
  290. // debugger
  291. let task = taskList.value.find((m) => m.id === activity.taskId) // 找到活动对应的 taskId
  292. if (!task) {
  293. return
  294. }
  295. html = `<p>审批人:${task.assigneeUser.nickname}</p>
  296. <p>部门:${task.assigneeUser.deptName}</p>
  297. <p>结果:${getIntDictOptions(
  298. DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
  299. task.result
  300. )}</p>
  301. <p>创建时间:${parseTime(task.createTime)}</p>`
  302. if (task.endTime) {
  303. html += `<p>结束时间:${parseTime(task.endTime)}</p>`
  304. }
  305. if (task.reason) {
  306. html += `<p>审批建议:${task.reason}</p>`
  307. }
  308. } else if (element.value.type === 'bpmn:ServiceTask' && processInstance.value) {
  309. if (activity.startTime > 0) {
  310. html = `<p>创建时间:${parseTime(activity.startTime)}</p>`
  311. }
  312. if (activity.endTime > 0) {
  313. html += `<p>结束时间:${parseTime(activity.endTime)}</p>`
  314. }
  315. console.log(html)
  316. } else if (element.value.type === 'bpmn:EndEvent' && processInstance.value) {
  317. html = `<p>结果:${getIntDictOptions(
  318. DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
  319. processInstance.value.result
  320. )}</p>`
  321. if (processInstance.value.endTime) {
  322. html += `<p>结束时间:${parseTime(processInstance.value.endTime)}</p>`
  323. }
  324. }
  325. console.log(html, 'html111111111111111')
  326. console.log(
  327. elementOverlayIds.value[element.value.id],
  328. 'elementOverlayIds.value[element.value.id]'
  329. )
  330. elementOverlayIds.value[element.value.id] = overlays.value.add(element.value, {
  331. position: { left: 0, bottom: 0 },
  332. html: `<div class="element-overlays">${html}</div>`
  333. })
  334. console.log(
  335. elementOverlayIds.value[element.value.id],
  336. 'elementOverlayIds.value[element.value.id]'
  337. )
  338. }
  339. }
  340. // 流程图的元素被 out
  341. const elementOut = (element) => {
  342. overlays.value.remove({ element })
  343. elementOverlayIds.value[element.id] = null
  344. }
  345. const parseTime = (time) => {
  346. if (!time) {
  347. return null
  348. }
  349. const format = '{y}-{m}-{d} {h}:{i}:{s}'
  350. let date
  351. if (typeof time === 'object') {
  352. date = time
  353. } else {
  354. if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
  355. time = parseInt(time)
  356. } else if (typeof time === 'string') {
  357. time = time
  358. .replace(new RegExp(/-/gm), '/')
  359. .replace('T', ' ')
  360. .replace(new RegExp(/\.[\d]{3}/gm), '')
  361. }
  362. if (typeof time === 'number' && time.toString().length === 10) {
  363. time = time * 1000
  364. }
  365. date = new Date(time)
  366. }
  367. const formatObj = {
  368. y: date.getFullYear(),
  369. m: date.getMonth() + 1,
  370. d: date.getDate(),
  371. h: date.getHours(),
  372. i: date.getMinutes(),
  373. s: date.getSeconds(),
  374. a: date.getDay()
  375. }
  376. const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
  377. let value = formatObj[key]
  378. // Note: getDay() returns 0 on Sunday
  379. if (key === 'a') {
  380. return ['日', '一', '二', '三', '四', '五', '六'][value]
  381. }
  382. if (result.length > 0 && value < 10) {
  383. value = '0' + value
  384. }
  385. return value || 0
  386. })
  387. return time_str
  388. }
  389. onMounted(() => {
  390. xml.value = props.value
  391. activityLists.value = props.activityData
  392. // 初始化
  393. initBpmnModeler()
  394. createNewDiagram(xml.value)
  395. // 初始模型的监听器
  396. initModelListeners()
  397. })
  398. onBeforeUnmount(() => {
  399. // this.$once('hook:beforeDestroy', () => {
  400. // })
  401. if (bpmnModeler) bpmnModeler.destroy()
  402. emit('destroy', bpmnModeler)
  403. bpmnModeler = null
  404. })
  405. watch(
  406. () => props.value,
  407. (newValue) => {
  408. console.log(newValue, 'oldVal')
  409. xml.value = newValue
  410. createNewDiagram(xml.value)
  411. }
  412. )
  413. watch(
  414. () => props.activityData,
  415. (newActivityData) => {
  416. activityLists.value = newActivityData
  417. createNewDiagram(xml.value)
  418. }
  419. )
  420. watch(
  421. () => props.processInstanceData,
  422. (newProcessInstanceData) => {
  423. processInstance.value = newProcessInstanceData
  424. createNewDiagram(xml.value)
  425. }
  426. )
  427. watch(
  428. () => props.taskData,
  429. (newTaskListData) => {
  430. taskList.value = newTaskListData
  431. createNewDiagram(xml.value)
  432. }
  433. )
  434. </script>
  435. <style>
  436. /** 处理中 */
  437. .highlight-todo.djs-connection > .djs-visual > path {
  438. stroke: #1890ff !important;
  439. stroke-dasharray: 4px !important;
  440. fill-opacity: 0.2 !important;
  441. }
  442. .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
  443. fill: #1890ff !important;
  444. stroke: #1890ff !important;
  445. stroke-dasharray: 4px !important;
  446. fill-opacity: 0.2 !important;
  447. }
  448. :deep(.highlight-todo.djs-connection > .djs-visual > path) {
  449. stroke: #1890ff !important;
  450. stroke-dasharray: 4px !important;
  451. fill-opacity: 0.2 !important;
  452. marker-end: url(#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr);
  453. }
  454. :deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
  455. fill: #1890ff !important;
  456. stroke: #1890ff !important;
  457. stroke-dasharray: 4px !important;
  458. fill-opacity: 0.2 !important;
  459. }
  460. /** 通过 */
  461. .highlight.djs-shape .djs-visual > :nth-child(1) {
  462. fill: green !important;
  463. stroke: green !important;
  464. fill-opacity: 0.2 !important;
  465. }
  466. .highlight.djs-shape .djs-visual > :nth-child(2) {
  467. fill: green !important;
  468. }
  469. .highlight.djs-shape .djs-visual > path {
  470. fill: green !important;
  471. fill-opacity: 0.2 !important;
  472. stroke: green !important;
  473. }
  474. .highlight.djs-connection > .djs-visual > path {
  475. stroke: green !important;
  476. }
  477. .highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
  478. fill: green !important; /* color elements as green */
  479. }
  480. :deep(.highlight.djs-shape .djs-visual > :nth-child(1)) {
  481. fill: green !important;
  482. stroke: green !important;
  483. fill-opacity: 0.2 !important;
  484. }
  485. :deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
  486. fill: green !important;
  487. }
  488. :deep(.highlight.djs-shape .djs-visual > path) {
  489. fill: green !important;
  490. fill-opacity: 0.2 !important;
  491. stroke: green !important;
  492. }
  493. :deep(.highlight.djs-connection > .djs-visual > path) {
  494. stroke: green !important;
  495. }
  496. /** 不通过 */
  497. .highlight-reject.djs-shape .djs-visual > :nth-child(1) {
  498. fill: red !important;
  499. stroke: red !important;
  500. fill-opacity: 0.2 !important;
  501. }
  502. .highlight-reject.djs-shape .djs-visual > :nth-child(2) {
  503. fill: red !important;
  504. }
  505. .highlight-reject.djs-shape .djs-visual > path {
  506. fill: red !important;
  507. fill-opacity: 0.2 !important;
  508. stroke: red !important;
  509. }
  510. .highlight-reject.djs-connection > .djs-visual > path {
  511. stroke: red !important;
  512. }
  513. .highlight-reject:not(.djs-connection) .djs-visual > :nth-child(1) {
  514. fill: red !important; /* color elements as green */
  515. }
  516. :deep(.highlight-reject.djs-shape .djs-visual > :nth-child(1)) {
  517. fill: red !important;
  518. stroke: red !important;
  519. fill-opacity: 0.2 !important;
  520. }
  521. :deep(.highlight-reject.djs-shape .djs-visual > :nth-child(2)) {
  522. fill: red !important;
  523. }
  524. :deep(.highlight-reject.djs-shape .djs-visual > path) {
  525. fill: red !important;
  526. fill-opacity: 0.2 !important;
  527. stroke: red !important;
  528. }
  529. :deep(.highlight-reject.djs-connection > .djs-visual > path) {
  530. stroke: red !important;
  531. }
  532. /** 已取消 */
  533. .highlight-cancel.djs-shape .djs-visual > :nth-child(1) {
  534. fill: grey !important;
  535. stroke: grey !important;
  536. fill-opacity: 0.2 !important;
  537. }
  538. .highlight-cancel.djs-shape .djs-visual > :nth-child(2) {
  539. fill: grey !important;
  540. }
  541. .highlight-cancel.djs-shape .djs-visual > path {
  542. fill: grey !important;
  543. fill-opacity: 0.2 !important;
  544. stroke: grey !important;
  545. }
  546. .highlight-cancel.djs-connection > .djs-visual > path {
  547. stroke: grey !important;
  548. }
  549. .highlight-cancel:not(.djs-connection) .djs-visual > :nth-child(1) {
  550. fill: grey !important; /* color elements as green */
  551. }
  552. :deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(1)) {
  553. fill: grey !important;
  554. stroke: grey !important;
  555. fill-opacity: 0.2 !important;
  556. }
  557. :deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(2)) {
  558. fill: grey !important;
  559. }
  560. :deep(.highlight-cancel.djs-shape .djs-visual > path) {
  561. fill: grey !important;
  562. fill-opacity: 0.2 !important;
  563. stroke: grey !important;
  564. }
  565. :deep(.highlight-cancel.djs-connection > .djs-visual > path) {
  566. stroke: grey !important;
  567. }
  568. .element-overlays {
  569. box-sizing: border-box;
  570. padding: 8px;
  571. background: rgba(0, 0, 0, 0.6);
  572. border-radius: 4px;
  573. color: #fafafa;
  574. width: 200px;
  575. }
  576. </style>