123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- <template>
- <div v-loading="isView" class="flow-containers" :class="{ 'view-mode': isView }">
- <el-container style="height: 100%">
- <el-header style="border-bottom: 1px solid rgb(218 218 218);height: auto;">
- <div style="display: flex; padding: 10px 0px; justify-content: space-between;">
- <div>
- <el-upload action="" :before-upload="openBpmn" style="margin-right: 10px; display:inline-block;">
- <el-tooltip effect="dark" content="加载xml" placement="bottom">
- <el-button size="mini" icon="el-icon-folder-opened" />
- </el-tooltip>
- </el-upload>
- <el-tooltip effect="dark" content="新建" placement="bottom">
- <el-button size="mini" icon="el-icon-circle-plus" @click="newDiagram" />
- </el-tooltip>
- <el-tooltip effect="dark" content="自适应屏幕" placement="bottom">
- <el-button size="mini" icon="el-icon-rank" @click="fitViewport" />
- </el-tooltip>
- <el-tooltip effect="dark" content="放大" placement="bottom">
- <el-button size="mini" icon="el-icon-zoom-in" @click="zoomViewport(true)" />
- </el-tooltip>
- <el-tooltip effect="dark" content="缩小" placement="bottom">
- <el-button size="mini" icon="el-icon-zoom-out" @click="zoomViewport(false)" />
- </el-tooltip>
- <el-button size="mini" icon="el-icon-back" @click="modeler.get('commandStack').undo()" />
- </el-tooltip>
- <el-tooltip effect="dark" content="前进" placement="bottom">
- <el-button size="mini" icon="el-icon-right" @click="modeler.get('commandStack').redo()" />
- </el-tooltip>
- </div>
- <div>
- <el-button size="mini" icon="el-icon-download" @click="saveXML(true)">下载xml</el-button>
- <!-- <el-button size="mini" icon="el-icon-picture" @click="saveImg('svg', true)">下载svg</el-button> -->
- <el-button icon="el-icon-finished" type="text" @click="save">保 存</el-button>
- <el-button style="color: #F56C6C;" icon="el-icon-circle-close" type="text" @click="closeModel">取 消</el-button>
- </div>
- </div>
- </el-header>
- <el-container style="align-items: stretch">
- <el-main style="padding: 0;">
- <div ref="canvas" class="canvas" />
- </el-main>
- <el-aside style="width: 320px; min-height: 650px; background-color: #f0f2f5">
- <panel
- v-if="modeler"
- :modeler="modeler"
- :users="users"
- :groups="groups"
- :categorys="categorys"
- :tableName="tableName"
- :nodeIdFormJsonArray="nodeIdFormJsonArray"
- :menuConfig="menuConfig"
- :flowAbleIndex = "flowAbleIndex"
- />
- </el-aside>
- </el-container>
- </el-container>
- </div>
- </template>
- <script>
- // 汉化
- import customTranslate from './common/customTranslate'
- import Modeler from 'bpmn-js/lib/Modeler'
- import panel from './PropertyPanel'
- import BpmData from './BpmData'
- import getInitStr from './flowable/init'
- // 引入flowable的节点文件
- import flowableModdle from './flowable/flowable.json'
- export default {
- name: 'WorkflowBpmnModeler',
- components: {
- panel
- },
- props: {
- xml: {
- type: String,
- default: ''
- },
- users: {
- type: Array,
- default: () => []
- },
- groups: {
- type: Array,
- default: () => []
- },
- categorys: {
- type: Array,
- default: () => []
- },
- isView: {
- type: Boolean,
- default: false
- },
- menuConfigRefs : {
- type : Object,
- default : () => {}
- },
- tableName : {
- type: String,
- default: ''
- },
- menuId : {
- type: String,
- default: ''
- },
- papBusinessName : {
- type: String,
- default: ''
- },
- companyId : {
- type: String,
- default: ''
- }
- },
- data() {
- return {
- modeler: null,
- taskList: [],
- zoom: 1,
- nodeIdFormJsonArray : [],
- menuConfig : {},
- flowAbleIndex : this
- }
- },
- watch: {
- xml: function(val) {
- if (val) {
- this.createNewDiagram(val)
- }
- }
- },
- mounted() {
- // 生成实例
- this.modeler = new Modeler({
- container: this.$refs.canvas,
- additionalModules: [
- {
- translate: ['value', customTranslate]
- }
- ],
- moddleExtensions: {
- flowable: flowableModdle
- }
- })
- // 新增流程定义
- if (!this.xml) {
- //this.newDiagram()
- } else {
- this.createNewDiagram(this.xml)
- }
- this.getNodeFormJsonData();
- },
- methods: {
- newDiagram() {
- this.createNewDiagram(getInitStr(this.papBusinessName))
- },
- // 让图能自适应屏幕
- fitViewport() {
- this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
- const bbox = document.querySelector('.flow-containers .viewport').getBBox()
- const currentViewbox = this.modeler.get('canvas').viewbox()
- const elementMid = {
- x: bbox.x + bbox.width / 2 - 65,
- y: bbox.y + bbox.height / 2
- }
- this.modeler.get('canvas').viewbox({
- x: elementMid.x - currentViewbox.width / 2,
- y: elementMid.y - currentViewbox.height / 2,
- width: currentViewbox.width,
- height: currentViewbox.height
- })
- this.zoom = bbox.width / currentViewbox.width * 1.8
- },
- // 放大缩小
- zoomViewport(zoomIn = true) {
- this.zoom = this.modeler.get('canvas').zoom()
- this.zoom += (zoomIn ? 0.1 : -0.1)
- this.modeler.get('canvas').zoom(this.zoom)
- },
- async createNewDiagram(data) {
- // 将字符串转换成图显示出来
- // data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '<![CDATA[$1]]>')
- data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
- return str.replace(/</g, '<')
- })
- try {
- await this.modeler.importXML(data)
- this.adjustPalette()
- this.fitViewport()
- // this.fillColor()
- } catch (err) {
- console.error(err.message, err.warnings)
- }
- },
- // 调整左侧工具栏排版
- adjustPalette() {
- try {
- // 获取 bpmn 设计器实例
- const canvas = this.$refs.canvas
- const djsPalette = canvas.children[0].children[1].children[4]
- const djsPalStyle = {
- width: '130px',
- padding: '5px',
- background: 'white',
- left: '20px',
- borderRadius: 0
- }
- for (var key in djsPalStyle) {
- djsPalette.style[key] = djsPalStyle[key]
- }
- const palette = djsPalette.children[0]
- const allGroups = palette.children
- allGroups[0].style['display'] = 'none'
- // 修改控件样式
- for (var gKey in allGroups) {
- const group = allGroups[gKey]
- for (var cKey in group.children) {
- const control = group.children[cKey]
- const controlStyle = {
- display: 'flex',
- justifyContent: 'flex-start',
- alignItems: 'center',
- width: '100%',
- padding: '5px'
- }
- if (
- control.className &&
- control.dataset &&
- control.className.indexOf('entry') !== -1
- ) {
- const controlProps = new BpmData().getControl(
- control.dataset.action
- )
- control.innerHTML = `<div style='font-size: 14px;font-weight:500;margin-left:15px;'>${
- controlProps['title']
- }</div>`
- for (var csKey in controlStyle) {
- control.style[csKey] = controlStyle[csKey]
- }
- }
- }
- }
- } catch (e) {
- console.log(e)
- }
- },
- fillColor() {
- const canvas = this.modeler.get('canvas')
- this.modeler._definitions.rootElements[0].flowElements.forEach(n => {
- if (n.$type === 'bpmn:UserTask') {
- const completeTask = this.taskList.find(m => m.key === n.id) || { completed: true }
- const todoTask = this.taskList.find(m => !m.completed)
- const endTask = this.taskList[this.taskList.length - 1]
- if (completeTask) {
- canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
- n.outgoing?.forEach(nn => {
- const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
- if (targetTask) {
- canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
- } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
- // canvas.addMarker(nn.id, 'highlight');
- canvas.addMarker(nn.id, completeTask.completed ? 'highlight' : 'highlight-todo')
- canvas.addMarker(nn.targetRef.id, completeTask.completed ? 'highlight' : 'highlight-todo')
- } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
- if (!todoTask && endTask.key === n.id) {
- canvas.addMarker(nn.id, 'highlight')
- canvas.addMarker(nn.targetRef.id, 'highlight')
- }
- if (!completeTask.completed) {
- canvas.addMarker(nn.id, 'highlight-todo')
- canvas.addMarker(nn.targetRef.id, 'highlight-todo')
- }
- }
- })
- }
- } else if (n.$type === 'bpmn:ExclusiveGateway') {
- n.outgoing.forEach(nn => {
- const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
- if (targetTask) {
- canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
- }
- })
- }
- if (n.$type === 'bpmn:StartEvent') {
- n.outgoing.forEach(nn => {
- const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
- if (completeTask) {
- canvas.addMarker(nn.id, 'highlight')
- canvas.addMarker(n.id, 'highlight')
- return
- }
- })
- }
- })
- },
- // 对外 api
- getProcess() {
- var element = this.getProcessElement()
- return {
- id: element.id,
- name: element.name,
- category: element.$attrs['flowable:processCategory']
- }
- },
- getProcessElement() {
- var rootElements = this.modeler.getDefinitions().rootElements
- for (let i = 0; i < rootElements.length; i++) {
- if (rootElements[i].$type === 'bpmn:Process') {
- return rootElements[i]
- }
- }
- },
- async saveXML(download = false) {
- try {
- const { xml } = await this.modeler.saveXML({ format: true })
- if (download) {
- this.downloadFile(`${this.getProcessElement().name}.bpmn20.xml`, xml, 'application/xml')
- }
- return xml
- } catch (err) {
- console.log(err)
- }
- },
- async saveImg(type = 'svg', download = false) {
- try {
- const { svg } = await this.modeler.saveSVG({ format: true })
- if (download) {
- this.downloadFile(this.getProcessElement().name, svg, 'image/svg+xml')
- }
- return svg
- } catch (err) {
- console.log(err)
- }
- },
- async save() {
- var process = this.getProcess()
- var {xml} = await this.modeler.saveXML({ format: true })
-
- var precessElement = this.getProcessElement();
- var param = {};
- param.fileName = precessElement.name + '.bpmn20.xml';
- param.flowKey = precessElement.id;
- param.nodeFormList = this.nodeIdFormJsonArray;
- param.menuId = this.menuId;
- param.companyId = this.companyId;
- param.fileContent = xml;
-
- this.Axios.post('/api/flow/uploadXml', param).then(response => {
- if (response.data == null || response.data == "") {
- this.$message.error('流程标识key已存在,请重新命名!');
- } else {
- this.$message({
- message: '保存成功!',
- type: 'success'
- });
- this.closeModel()
- }
- }).catch(err => {
- this.$message.error('请求失败!');
- })
- },
- closeModel() {
- this.menuConfigRefs.drawerFlowable.hide();
- },
- openBpmn(file) {
- const reader = new FileReader()
- reader.readAsText(file, 'utf-8')
- reader.onload = () => {
- this.createNewDiagram(reader.result)
- }
- return false
- },
- downloadFile(filename, data, type) {
- var a = document.createElement('a')
- var url = window.URL.createObjectURL(new Blob([data], { type: type }))
- a.href = url
- a.download = filename
- a.click()
- window.URL.revokeObjectURL(url)
- },
- // 获取节点视图信息
- getNodeFormJsonData() {
- this.Axios.post('/api/server/getNodeFormJsonData', 'menuId=' + this.menuId + '&companyId=' + this.companyId).then(response => {
- if (!!response.data) {
- this.nodeIdFormJsonArray = response.data.data.nodeConfig;
- this.menuConfig = response.data.data.menuConfig;
- }
- }).catch(err => {
- console.log(err)
- this.$message.error('请求失败!');
- })
- },
- nodeConfigListener(data) {
- this.nodeIdFormJsonArray = data;
- }
- }
- }
- </script>
- <style lang="scss">
- /*左边工具栏以及编辑节点的样式*/
- @import "~bpmn-js/dist/assets/diagram-js.css";
- @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
- @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";
- @import "~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";
- .view-mode {
- .el-header, .el-aside, .djs-palette, .bjs-powered-by {
- display: none;
- }
- .el-loading-mask {
- background-color: initial;
- }
- .el-loading-spinner {
- display: none;
- }
- }
- .flow-containers {
- // background-color: #ffffff;
- width: 100%;
- height: 100%;
- .canvas {
- width: 100%;
- height: 100%;
- }
- .panel {
- position: absolute;
- right: 0;
- top: 50px;
- width: 300px;
- }
- .load {
- margin-right: 10px;
- }
- .el-form-item__label{
- font-size: 13px;
- }
- .djs-palette{
- left: 0px!important;
- top: 0px;
- border-top: none;
- }
- .djs-container svg {
- min-height: 650px;
- }
- // .highlight.djs-shape .djs-visual > :nth-child(1) {
- // fill: green !important;
- // stroke: green !important;
- // fill-opacity: 0.2 !important;
- // }
- // .highlight.djs-shape .djs-visual > :nth-child(2) {
- // fill: green !important;
- // }
- // .highlight.djs-shape .djs-visual > path {
- // fill: green !important;
- // fill-opacity: 0.2 !important;
- // stroke: green !important;
- // }
- // .highlight.djs-connection > .djs-visual > path {
- // stroke: green !important;
- // }
- // // .djs-connection > .djs-visual > path {
- // // stroke: orange !important;
- // // stroke-dasharray: 4px !important;
- // // fill-opacity: 0.2 !important;
- // // }
- // // .djs-shape .djs-visual > :nth-child(1) {
- // // fill: orange !important;
- // // stroke: orange !important;
- // // stroke-dasharray: 4px !important;
- // // fill-opacity: 0.2 !important;
- // // }
- // .highlight-todo.djs-connection > .djs-visual > path {
- // stroke: orange !important;
- // stroke-dasharray: 4px !important;
- // fill-opacity: 0.2 !important;
- // }
- // .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
- // fill: orange !important;
- // stroke: orange !important;
- // stroke-dasharray: 4px !important;
- // fill-opacity: 0.2 !important;
- // }
- // .overlays-div {
- // font-size: 10px;
- // color: red;
- // width: 100px;
- // top: -20px !important;
- // }
- }
- </style>
|