index1.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <template>
  2. <!--新的流程定义界面-->
  3. <div v-loading="isView" class="flow-containers" :class="{ 'view-mode': isView }">
  4. <el-container style="height: 100%">
  5. <el-header style="border-bottom: 1px solid rgb(218 218 218);height: auto;padding-left:0">
  6. <div style="display: flex; padding: 10px 0; justify-content: space-between;">
  7. <el-button-group>
  8. <el-upload action="" :before-upload="openBpmn" style="margin-right: 10px; display:inline-block;">
  9. <el-tooltip effect="dark" content="加载xml" placement="bottom">
  10. <el-button size="mini" icon="el-icon-folder-opened" />
  11. </el-tooltip>
  12. </el-upload>
  13. <el-tooltip effect="dark" content="新建" placement="bottom">
  14. <el-button size="mini" icon="el-icon-circle-plus" @click="newDiagram" />
  15. </el-tooltip>
  16. <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
  17. <el-button size="mini" icon="el-icon-rank" @click="fitViewport" />
  18. </el-tooltip>
  19. <el-tooltip effect="dark" content="放大" placement="bottom">
  20. <el-button size="mini" icon="el-icon-zoom-in" @click="zoomViewport(true)" />
  21. </el-tooltip>
  22. <el-tooltip effect="dark" content="缩小" placement="bottom">
  23. <el-button size="mini" icon="el-icon-zoom-out" @click="zoomViewport(false)" />
  24. </el-tooltip>
  25. <el-tooltip effect="dark" content="后退" placement="bottom">
  26. <el-button size="mini" icon="el-icon-back" @click="modeler.get('commandStack').undo()" />
  27. </el-tooltip>
  28. <el-tooltip effect="dark" content="前进" placement="bottom">
  29. <el-button size="mini" icon="el-icon-right" @click="modeler.get('commandStack').redo()" />
  30. </el-tooltip>
  31. <!-- <el-button size="mini" icon="el-icon-share" @click="processSimulation">-->
  32. <!-- {{ this.simulationStatus ? '退出模拟' : '开启模拟' }}-->
  33. <!-- </el-button>-->
  34. <!-- <el-button size="mini" icon="el-icon-first-aid-kit" @click="handlerIntegrityCheck">-->
  35. <!-- {{ this.bpmnlintStatus ? '关闭检查' : '开启检查' }}-->
  36. <!-- </el-button>-->
  37. </el-button-group>
  38. <el-button-group>
  39. <el-button size="mini" icon="el-icon-view" @click="showXML">查看xml</el-button>
  40. <el-button size="mini" icon="el-icon-download" @click="saveXML(true)">下载xml</el-button>
  41. <el-button size="mini" icon="el-icon-picture" @click="saveImg('svg', true)">下载svg</el-button>
  42. <el-button size="mini" type="primary" @click="save">保存模型</el-button>
  43. <el-button size="mini" type="danger" @click="goBack">关闭</el-button>
  44. </el-button-group>
  45. </div>
  46. </el-header>
  47. <!-- 流程设计页面 -->
  48. <el-container style="align-items: stretch">
  49. <el-main>
  50. <div ref="canvas" class="canvas" />
  51. </el-main>
  52. <!--右侧属性栏-->
  53. <el-card shadow="never" class="normalPanel">
  54. <designer v-if="loadCanvas"></designer>
  55. </el-card>
  56. </el-container>
  57. </el-container>
  58. </div>
  59. </template>
  60. <script>
  61. // 汉化
  62. //import customTranslate from './customPanel/customTranslate'
  63. import customTranslate from './common/customTranslate'
  64. import Modeler from 'bpmn-js/lib/Modeler'
  65. import Designer from './designer'
  66. import getInitStr from './flowable/init'
  67. import {StrUtil} from '@/utils/StrUtil'
  68. // 引入flowable的节点文件
  69. import FlowableModule from './flowable/flowable.json'
  70. import customControlsModule from './customPanel'
  71. export default {
  72. name: "BpmnModel",
  73. components: {Designer},
  74. /** 组件传值 */
  75. props : {
  76. xml: {
  77. type: String,
  78. default: ''
  79. },
  80. isView: {
  81. type: Boolean,
  82. default: false
  83. },
  84. },
  85. data() {
  86. return {
  87. modeler: null,
  88. zoom: 1,
  89. loadCanvas: false, // 当前组件渲染然后再加载canvas
  90. simulationStatus: false,
  91. bpmnlintStatus: false,
  92. simulation: true,
  93. designer: true,
  94. }
  95. },
  96. /** 传值监听 */
  97. watch: {
  98. xml: {
  99. handler(newVal) {
  100. if (StrUtil.isNotBlank(newVal)) {
  101. this.createNewDiagram(newVal)
  102. } else {
  103. this.newDiagram()
  104. }
  105. },
  106. immediate: true, // 立即生效
  107. },
  108. },
  109. computed: {
  110. additionalModules() {
  111. const Modules = [];
  112. Modules.push(customControlsModule);
  113. Modules.push({ //汉化
  114. translate: ['value', customTranslate]
  115. });
  116. return Modules;
  117. },
  118. },
  119. mounted() {
  120. /** 创建bpmn 实例 */
  121. const modeler = new Modeler({
  122. container: this.$refs.canvas,
  123. additionalModules: this.additionalModules,
  124. moddleExtensions: {
  125. flowable: FlowableModule
  126. },
  127. keyboard: { bindTo: document },
  128. })
  129. this.modeler = modeler;
  130. // 注册 modeler 相关信息
  131. this.modelerStore.modeler = modeler;
  132. this.modelerStore.modeling = modeler.get("modeling");
  133. this.modelerStore.moddle = modeler.get("moddle");
  134. this.modelerStore.canvas = modeler.get("canvas");
  135. this.modelerStore.bpmnFactory = modeler.get("bpmnFactory");
  136. this.modelerStore.elRegistry = modeler.get("elementRegistry");
  137. // 直接点击新建按钮时,进行新增流程图
  138. if (StrUtil.isBlank(this.xml)) {
  139. this.newDiagram()
  140. } else {
  141. this.createNewDiagram(this.xml)
  142. }
  143. },
  144. methods: {
  145. // 根据默认文件初始化流程图
  146. newDiagram() {
  147. this.createNewDiagram(getInitStr())
  148. },
  149. // 根据提供的xml创建流程图
  150. async createNewDiagram(data) {
  151. // 将字符串转换成图显示出来
  152. // data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
  153. if (StrUtil.isNotBlank(this.modelerStore.modeler)) {
  154. data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function (match, str) {
  155. return str.replace(/</g, '&lt;')
  156. }
  157. )
  158. try {
  159. await this.modelerStore.modeler.importXML(data)
  160. this.fitViewport()
  161. } catch (err) {
  162. console.error(err.message, err.warnings)
  163. }
  164. }
  165. },
  166. // 让图能自适应屏幕
  167. fitViewport() {
  168. this.zoom = this.modelerStore.canvas.zoom('fit-viewport')
  169. const bbox = document.querySelector('.flow-containers .viewport').getBBox()
  170. const currentViewBox = this.modelerStore.canvas.viewbox()
  171. const elementMid = {
  172. x: bbox.x + bbox.width / 2 - 65,
  173. y: bbox.y + bbox.height / 2
  174. }
  175. this.modelerStore.canvas.viewbox({
  176. x: elementMid.x - currentViewBox.width / 2,
  177. y: elementMid.y - currentViewBox.height / 2,
  178. width: currentViewBox.width,
  179. height: currentViewBox.height
  180. })
  181. this.zoom = bbox.width / currentViewBox.width * 1.8
  182. this.loadCanvas = true;
  183. },
  184. // 放大缩小
  185. zoomViewport(zoomIn = true) {
  186. this.zoom = this.modelerStore.canvas.zoom()
  187. this.zoom += (zoomIn ? 0.1 : -0.1)
  188. this.modelerStore.canvas.zoom(this.zoom)
  189. },
  190. // 获取流程基础信息
  191. getProcess() {
  192. const element = this.getProcessElement()
  193. return {
  194. id: element.id,
  195. name: element.name,
  196. category: element.processCategory
  197. }
  198. },
  199. // 获取流程主面板节点
  200. getProcessElement() {
  201. const rootElements = this.modelerStore.modeler.getDefinitions().rootElements
  202. for (let i = 0; i < rootElements.length; i++) {
  203. if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]
  204. }
  205. },
  206. // 保存xml
  207. async saveXML(download = false) {
  208. try {
  209. const {xml} = await this.modelerStore.modeler.saveXML({format: true})
  210. if (download) {
  211. this.downloadFile(`${this.getProcessElement().name}.bpmn20.xml`, xml, 'application/xml')
  212. }
  213. return xml
  214. } catch (err) {
  215. console.log(err)
  216. }
  217. },
  218. // 在线查看xml
  219. async showXML() {
  220. try {
  221. const xmlStr = await this.saveXML()
  222. this.$emit('showXML', xmlStr)
  223. } catch (err) {
  224. console.log(err)
  225. }
  226. },
  227. // 保存流程图为svg
  228. async saveImg(type = 'svg', download = false) {
  229. try {
  230. const {svg} = await this.modelerStore.modeler.saveSVG({format: true})
  231. if (download) {
  232. this.downloadFile(this.getProcessElement().name, svg, 'image/svg+xml')
  233. }
  234. return svg
  235. } catch (err) {
  236. console.log(err)
  237. }
  238. },
  239. // 保存流程图
  240. async save() {
  241. const process = this.getProcess()
  242. const xml = await this.saveXML()
  243. const svg = await this.saveImg()
  244. const result = {process, xml, svg}
  245. this.$emit('save', result)
  246. window.parent.postMessage(result, '*')
  247. this.goBack();
  248. },
  249. // 打开流程文件
  250. openBpmn(file) {
  251. const reader = new FileReader()
  252. reader.readAsText(file, 'utf-8')
  253. reader.onload = () => {
  254. this.createNewDiagram(reader.result)
  255. }
  256. return false
  257. },
  258. // 下载流程文件
  259. downloadFile(filename, data, type) {
  260. const a = document.createElement('a');
  261. const url = window.URL.createObjectURL(new Blob([data], {type: type}));
  262. a.href = url
  263. a.download = filename
  264. a.click()
  265. window.URL.revokeObjectURL(url)
  266. },
  267. /** 关闭当前标签页并返回上个页面 */
  268. goBack() {
  269. const obj = {path: "/flowable/definition", query: {t: Date.now()}};
  270. this.$tab.closeOpenPage(obj);
  271. },
  272. }
  273. }
  274. </script>
  275. <style lang="scss">
  276. /*左边工具栏以及修改节点的样式*/
  277. @import "~bpmn-js/dist/assets/diagram-js.css";
  278. @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
  279. @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
  280. @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";
  281. //@import "~bpmn-js-bpmnlint/dist/assets/css/bpmn-js-bpmnlint.css";
  282. .view-mode {
  283. .el-header, .el-aside, .djs-palette, .bjs-powered-by {
  284. display: none;
  285. }
  286. .el-loading-mask {
  287. background-color: initial;
  288. }
  289. .el-loading-spinner {
  290. display: none;
  291. }
  292. }
  293. .flow-containers {
  294. width: 100%;
  295. height: 100%;
  296. .canvas {
  297. min-height: 850px;
  298. width: 100%;
  299. height: 100%;
  300. background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
  301. }
  302. .panel {
  303. position: absolute;
  304. right: 0;
  305. top: 50px;
  306. width: 300px;
  307. }
  308. .load {
  309. margin-right: 10px;
  310. }
  311. .normalPanel {
  312. width: 460px;
  313. height: 100%;
  314. padding: 20px 20px;
  315. }
  316. .el-main {
  317. position: relative;
  318. padding: 0;
  319. }
  320. .el-main .button-group {
  321. display: flex;
  322. flex-direction: column;
  323. position: absolute;
  324. width: auto;
  325. height: auto;
  326. top: 10px;
  327. right: 10px;
  328. }
  329. .button-group .el-button {
  330. width: 100%;
  331. margin: 0 0 5px;
  332. }
  333. }
  334. </style>