You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
513 lines
18 KiB
513 lines
18 KiB
<template> |
|
<basic-container class="wf-design"> |
|
<el-steps :active="step" |
|
finish-status="success" |
|
simple |
|
style="margin-bottom: 20px;"> |
|
<el-step title="设计表单" |
|
icon="el-icon-edit"></el-step> |
|
<el-step title="设计流程" |
|
icon="el-icon-upload"> |
|
<template #title> |
|
设计流程 |
|
<el-tooltip v-show="step == '1'" |
|
content="全屏"> |
|
<i class="el-icon-full-screen" |
|
@click="handleFullScreen"></i> |
|
</el-tooltip> |
|
</template> |
|
</el-step> |
|
<el-step title="完成" |
|
icon="el-icon-circle-check"></el-step> |
|
</el-steps> |
|
|
|
<div v-show="step == 0"> |
|
<avue-form style="margin-bottom: 66px;" |
|
ref="form1" |
|
:option="step1.option" |
|
v-model="step1.form"> |
|
<template #tip> |
|
<el-link type="primary" |
|
:underline="false" |
|
@click="$router.push('/plugin/workflow/design/form')">没有想要的表单?点击去设计</el-link> |
|
</template> |
|
<template #form> |
|
<avue-form v-if="option && ((option.column && option.column.length > 0) || (option.group && option.group.length > 0))" |
|
ref="form2" |
|
v-model="form" |
|
:option="option"></avue-form> |
|
</template> |
|
</avue-form> |
|
</div> |
|
<div v-if="step == 1"> |
|
<wf-design id="bpmn2" |
|
ref="bpmn2" |
|
style="height: calc(100vh - 290px); background: white;" |
|
:options="step2.option"></wf-design> |
|
</div> |
|
<div v-if="step == 2"> |
|
<wf-design ref="bpmn3" |
|
style="height: calc(100vh - 290px)" |
|
:options="step3.option"></wf-design> |
|
</div> |
|
|
|
<div class="foot-item" |
|
:style="{width: isCollapse? 'calc(100% - 80px)': 'calc(100% - 260px)' }"> |
|
<el-button type="primary" |
|
size="medium" |
|
v-if="step > 0" |
|
@click="step--">上一步</el-button> |
|
<el-button type="success" |
|
size="medium" |
|
v-if="step < 2" |
|
@click="handleNextStep">下一步</el-button> |
|
<el-button type="success" |
|
size="medium" |
|
v-if="step == 2" |
|
@click="handleSave">保存</el-button> |
|
</div> |
|
</basic-container> |
|
</template> |
|
|
|
<script> |
|
import { getDetail as getFormByKey } from '@/api/plugin/workflow/form' |
|
import { submit, getDetail } from '@/api/plugin/workflow/model' |
|
import { getList as buttonList } from '@/api/plugin/workflow/button' |
|
import { getList as formList } from '@/api/plugin/workflow/form' |
|
import { getList as conditionList } from '@/api/plugin/workflow/condition' |
|
|
|
import { fullscreenToggel } from "@/util/util"; |
|
|
|
import { mapGetters } from 'vuex' |
|
|
|
export default { |
|
name: 'design', |
|
computed: { |
|
...mapGetters(['tag', 'isCollapse', 'language']), |
|
}, |
|
watch: { |
|
'$route.params.id': { |
|
handler(val) { |
|
if (!val || val == 0) return |
|
getDetail(val).then(res => { |
|
this.process = res.data.data |
|
const { formKey, xml, exForm } = this.process |
|
this.$set(this.step2.option, 'xml', xml) |
|
this.$set(this.step2.option, 'process', this.process) |
|
if (formKey.startsWith('wf_ex_')) { // 外置表单 |
|
const column = [] |
|
exForm.forEach(ex => { |
|
column.push({ |
|
label: ex.name, |
|
prop: ex.id, |
|
readable: true, |
|
writable: true |
|
}) |
|
}) |
|
this.$set(this.step1.form, 'column', column) |
|
this.$set(this.step1.form, 'formType', 2) |
|
this.$set(this.step1.form, 'exFormKey', formKey.substring(6)) |
|
} else if (formKey.startsWith('wf_indep_')) { |
|
this.$set(this.step1.form, 'formType', 3) |
|
} else { |
|
this.$set(this.step1.form, 'formKey', formKey) |
|
} |
|
}) |
|
}, |
|
immediate: true |
|
}, |
|
language(val) { |
|
const option = { |
|
lang: val |
|
} |
|
if (this.$refs.bpmn2) { |
|
this.$refs.bpmn2.getData('xml', false, false).then(data => { |
|
option.xml = data |
|
this.$set(this.step2, 'option', { |
|
...this.step2.option, |
|
...option |
|
}) |
|
}) |
|
} else this.$set(this.step2, 'option', { |
|
...this.step2.option, |
|
...option |
|
}) |
|
} |
|
}, |
|
data() { |
|
const _this = this |
|
return { |
|
form: {}, |
|
option: {}, |
|
step: 0, |
|
step1: { |
|
form: {}, |
|
option: { |
|
menuBtn: false, |
|
group: [{ |
|
labelPosition: 'left', |
|
label: '选择表单', |
|
icon: 'el-icon-warning-outline', |
|
arrow: false, |
|
column: [{ |
|
label: '表单类型', |
|
prop: 'formType', |
|
type: 'radio', |
|
dicData: [{ |
|
label: '内置表单', |
|
value: 1 |
|
}, { |
|
label: '外置表单', |
|
value: 2 |
|
}, { |
|
label: '节点独立表单', |
|
value: 3 |
|
}], |
|
span: 24, |
|
value: 1, |
|
event: { |
|
change: (val) => { |
|
if (!val) return |
|
val = typeof val == 'object'? val.value: val |
|
if (val == 1) { |
|
this.findObject(this.step1.option.group[0].column, 'exFormKey').display = false |
|
this.findObject(this.step1.option.group[1].column, 'column').display = false |
|
this.findObject(this.step1.option.group[0].column, 'formKey').display = true |
|
this.findObject(this.step1.option.group[0].column, 'tip').display = true |
|
this.findObject(this.step1.option.group[1].column, 'form').display = true |
|
this.step1.option.group[1].display = true |
|
} else if (val == 2) { |
|
this.findObject(this.step1.option.group[0].column, 'exFormKey').display = true |
|
this.findObject(this.step1.option.group[1].column, 'column').display = true |
|
this.findObject(this.step1.option.group[0].column, 'formKey').display = false |
|
this.findObject(this.step1.option.group[0].column, 'tip').display = false |
|
this.option = {} |
|
this.step1.option.group[1].display = true |
|
} else if (val == 3) { |
|
this.findObject(this.step1.option.group[0].column, 'exFormKey').display = false |
|
this.findObject(this.step1.option.group[1].column, 'column').display = false |
|
this.findObject(this.step1.option.group[0].column, 'formKey').display = false |
|
this.findObject(this.step1.option.group[0].column, 'tip').display = false |
|
this.option = {} |
|
this.step1.option.group[1].display = false |
|
} |
|
} |
|
} |
|
}, { |
|
label: '表单key', |
|
prop: 'exFormKey', |
|
display: false, |
|
rules: [{ required: true, message: '请输入外置表单key' }] |
|
}, { |
|
label: '表单', |
|
prop: 'formKey', |
|
type: 'select', |
|
props: { |
|
label: 'name', |
|
value: 'formKey' |
|
}, |
|
dicData: [], |
|
event: { |
|
change: (val) => { |
|
val = typeof val == 'object'? val.value: val |
|
_this.option = { menuBtn: false, readonly: true } |
|
if (val) { |
|
getFormByKey({ formKey: val }).then(res => { |
|
_this.option = { ...eval('(' + res.data.data.content.replace(/this/g, '_this') + ')'), menuBtn: false, readonly: true } |
|
_this.findObject(this.step1.option.group[1].column, 'form').display = true |
|
}).catch(() => { |
|
_this.findObject(this.step1.option.group[1].column, 'form').display = false |
|
}) |
|
} else { |
|
_this.findObject(this.step1.option.group[1].column, 'form').display = false |
|
} |
|
} |
|
}, |
|
rules: [{ required: true, message: '请选择表单' }], |
|
display: true, |
|
filterable: true, |
|
}, { |
|
labelWidth: 0, |
|
prop: 'tip', |
|
formslot: true |
|
}] |
|
}, { |
|
label: '表单预览', |
|
icon: 'el-icon-view', |
|
display: true, |
|
arrow: false, |
|
column: [{ |
|
prop: 'form', |
|
labelWidth: 0, |
|
span: 24, |
|
formslot: true, |
|
display: false |
|
}, { |
|
prop: 'column', |
|
labelWidth: '0', |
|
tip: '可用于控制外置表单字段的显隐配置,如果希望自己控制请忽略此字段', |
|
tipPlacement: 'top', |
|
span: 24, |
|
type: 'dynamic', |
|
children: { |
|
align: 'center', |
|
column: [{ |
|
label: '字段', |
|
prop: 'label', |
|
rules: [{ required: true, message: '请输入字段名' }] |
|
}, { |
|
label: '属性', |
|
prop: 'prop', |
|
rules: [{ required: true, message: '请输入属性名' }] |
|
}, { |
|
label: '默认可读', |
|
prop: 'readable', |
|
type: 'switch', |
|
disabled: true, |
|
value: true |
|
}, { |
|
label: '默认可写', |
|
prop: 'writable', |
|
type: 'switch', |
|
disabled: true, |
|
value: true |
|
}] |
|
}, |
|
display: false |
|
}] |
|
}] |
|
}, |
|
}, |
|
step2: { |
|
option: { |
|
config: false, |
|
mode: 'edit', |
|
engine: 'flowable', |
|
toolbar: ['open', 'create', 'fit', 'zoom-in', 'zoom-out', 'undo', 'redo', 'import', 'preview'], |
|
script: { |
|
script: { |
|
enable: false, |
|
alert: '使用之前请先了解脚本会带来的危害,若确定使用请参考文档放开此配置。<br>1、脚本中可以完全访问JVM。<br>2、脚本执行时阻塞许多系统资源。<br>3、脚本执行死循环/占用大量内存等会导致程序崩溃。' |
|
}, |
|
shell: { |
|
enable: false, |
|
alert: '使用之前请先了解Shell会带来的危害,若确定使用请参考文档放开此配置。<br>因不确定是否可执行危险命令,如rm -rf *,请充分了解之后再使用。', |
|
pattern: '(rm|mv|kill|ifconfig|docker|reboot|dd|wget|shutdown|halt|poweroff|init|:(){:|:&};:|^foo^bar)' |
|
} |
|
} |
|
} |
|
}, |
|
step3: { |
|
option: { |
|
config: false, |
|
mode: 'view', |
|
simulation: true, |
|
minimap: true, |
|
engine: 'flowable' |
|
} |
|
}, |
|
process: {}, |
|
fullscreen: false |
|
} |
|
}, |
|
mounted() { |
|
this.getButtonList() |
|
this.getUserListV2() |
|
this.getFormList() |
|
this.getConditionList() |
|
}, |
|
methods: { |
|
handleNextStep() { |
|
switch (this.step) { |
|
case 0: |
|
this.$refs.form1.validate((valid, done) => { |
|
if (valid) { |
|
const { formType, formKey, exFormKey, column } = this.step1.form |
|
if (formType == 1) { // 内置表单 |
|
this.process.formKey = formKey |
|
this.$set(this.step2.option, 'form', this.option) |
|
} else if (formType == 2) { // 外置表单 |
|
this.process.formKey = "wf_ex_" + exFormKey |
|
this.$set(this.step2.option, 'exForm', { |
|
exFormKey, column |
|
}) |
|
} else if (formType == 3) { // 独立表单 |
|
this.$set(this.step2.option, 'indepForm', { |
|
mode: 'indep', |
|
list: this.formList |
|
}) |
|
} |
|
this.step++ |
|
done() |
|
} |
|
}) |
|
break; |
|
case 1: |
|
if (this.step1.form.formType == 3) { // 节点独立表单 |
|
const registry = this.$refs.bpmn2.getElementRegistry().getAll() |
|
let errorList = [] |
|
registry.forEach(ele => { |
|
this.validateIndepFormOption(ele, errorList) |
|
}) |
|
if (errorList.length > 0) { |
|
errorList = new Set(errorList) |
|
let message = '' |
|
errorList.forEach(err => { |
|
const { businessObject } = err |
|
const { id, name } = businessObject |
|
message += `<p>${name || id} 节点未正确配置表单</p>` |
|
}) |
|
this.$message({ |
|
type: 'error', |
|
dangerouslyUseHTMLString: true, |
|
message |
|
}) |
|
return |
|
} |
|
} |
|
this.$refs.bpmn2.getData('xml').then(data => { |
|
this.$set(this.step2.option, 'xml', data) |
|
this.$set(this.step3.option, 'xml', data) |
|
this.process.xml = data |
|
this.step++ |
|
}) |
|
break; |
|
} |
|
}, |
|
handleSave() { |
|
const registry = this.$refs.bpmn3.getElementRegistry().getAll() |
|
const { businessObject } = registry[0] |
|
const { id, name, documentation } = businessObject |
|
const description = (documentation && documentation.length > 0) ? documentation[0].text : null |
|
|
|
const { formType } = this.step1.form |
|
if (formType == 3) { // 节点独立表单 |
|
const startEvent = registry.find(r => r.type == 'bpmn:StartEvent') |
|
if (startEvent) { |
|
const indepFormKey = startEvent.businessObject.extensionElements.values.find(v => v.$type == 'flowable:indepFormKey') |
|
if (indepFormKey) this.process.formKey = 'wf_indep_' + indepFormKey.value |
|
} |
|
} |
|
|
|
const params = { |
|
...this.process, |
|
modelKey: id, |
|
name, |
|
description |
|
} |
|
|
|
if (this.process.id) { |
|
this.$confirm('是否将此模型保存为新版本?这意味着可以返回到以前的版本。', '提示', { |
|
distinguishCancelAndClose: true, |
|
confirmButtonText: '否', |
|
cancelButtonText: '是', |
|
type: 'warning' |
|
}).then(() => { |
|
params.newVersion = false |
|
|
|
submit(params).then(() => { |
|
this.$message.success("操作成功") |
|
this.$store.commit('DEL_TAG', this.tag) |
|
this.$router.push("/plugin/workflow/design/model") |
|
}) |
|
}).catch(action => { |
|
if (action == 'cancel') { |
|
params.newVersion = true |
|
|
|
submit(params).then(() => { |
|
this.$message.success("操作成功") |
|
this.$store.commit('DEL_TAG', this.tag) |
|
this.$router.push("/plugin/workflow/design/model") |
|
}) |
|
} |
|
}) |
|
} else { |
|
submit(params).then(() => { |
|
this.$message.success("操作成功") |
|
this.$store.commit('DEL_TAG', this.tag) |
|
this.$router.push("/plugin/workflow/design/model") |
|
}) |
|
} |
|
}, |
|
validateIndepFormOption(element, errorList) { |
|
const { type, businessObject, children } = element |
|
const indepFormKey = 'flowable:IndepFormKey' |
|
const indepFormSummary = 'flowable:IndepFormSummary' |
|
if ('bpmn:StartEvent' == type) { |
|
const extensionElements = businessObject.extensionElements |
|
if (extensionElements && extensionElements.values && extensionElements.values.length > 0) { |
|
const summary = extensionElements.values.find(v => v.$type == indepFormSummary) |
|
if (!extensionElements.values.find(v => v.$type == indepFormKey) && (!summary || summary == '0')) errorList.push(element) |
|
} else errorList.push(element) |
|
} else if ('bpmn:UserTask' == type) { |
|
const summary = businessObject['indepFormSummary'] |
|
if (!businessObject['indepFormKey'] && (!summary || summary == '0')) errorList.push(element) |
|
} else if ('bpmn:SubProcess' == type) { |
|
children.forEach(ele => this.validateIndepFormOption(ele, errorList)) |
|
} |
|
}, |
|
getButtonList() { |
|
buttonList(1, -1, { status: 1 }).then(res => { |
|
const list = res.data.data.records.map(l => { |
|
return { |
|
label: l.name, |
|
prop: l.buttonKey, |
|
display: l.display |
|
} |
|
}) |
|
this.$set(this.step2.option, 'button', list) |
|
}) |
|
}, |
|
getFormList() { |
|
formList(1, -1, { 'status_equal': '1' }).then(res => { |
|
this.formList = res.data.data.records |
|
this.findObject(this.step1.option.group, 'formKey').dicData = this.formList |
|
}) |
|
}, |
|
getUserListV2() { |
|
this.$set(this.step2.option, 'user', { |
|
version: 'v2', |
|
userUrl: '/api/blade-user/search/user', |
|
roleUrl: '/api/blade-system/search/role', |
|
deptUrl: '/api/blade-system/search/dept', |
|
postUrl: '/api/blade-system/search/post', |
|
customUrl: '/api/blade-workflow/design/condition/list' |
|
}) |
|
}, |
|
getConditionList() { |
|
conditionList(1, -1, { type: 'flow', status: 1 }).then(res => { |
|
this.$set(this.step2.option, 'condition', res.data.data.records) |
|
}) |
|
}, |
|
handleFullScreen() { |
|
fullscreenToggel() |
|
this.$store.commit('SET_COLLAPSE') |
|
}, |
|
} |
|
} |
|
</script> |
|
<style lang="scss"> |
|
.wf-design { |
|
.avue-group__title { |
|
margin-top: 8px; |
|
} |
|
} |
|
</style> |
|
<style scoped lang="scss"> |
|
.foot-item { |
|
position: fixed; |
|
bottom: 0; |
|
margin-left: -20px; |
|
// right: 0; |
|
z-index: 101; |
|
height: 66px; |
|
background-color: #fff; |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
-webkit-transition: 0.3s; |
|
transition: 0.3s; |
|
-webkit-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|
} |
|
</style> |