index.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <ContentWrap>
  3. <!-- 流程设计器,负责绘制流程等 -->
  4. <MyProcessDesigner
  5. key="designer"
  6. v-model="xmlString"
  7. :value="xmlString"
  8. v-bind="controlForm"
  9. keyboard
  10. ref="processDesigner"
  11. @init-finished="initModeler"
  12. :additionalModel="controlForm.additionalModel"
  13. :model="model"
  14. @save="save"
  15. />
  16. <!-- 流程属性器,负责编辑每个流程节点的属性 -->
  17. <MyProcessPenal
  18. v-if="isModelerReady && modeler"
  19. key="penal"
  20. :bpmnModeler="modeler"
  21. :prefix="controlForm.prefix"
  22. class="process-panel"
  23. :model="model"
  24. />
  25. </ContentWrap>
  26. </template>
  27. <script lang="ts" setup>
  28. import { MyProcessDesigner, MyProcessPenal } from '@/components/bpmnProcessDesigner/package'
  29. // 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
  30. import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
  31. // 自定义左侧菜单(修改 默认任务 为 用户任务)
  32. import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
  33. import * as ModelApi from '@/api/bpm/model'
  34. defineOptions({ name: 'BpmModelEditor' })
  35. const props = defineProps<{
  36. modelId?: string
  37. modelKey?: string
  38. modelName?: string
  39. }>()
  40. const emit = defineEmits(['success'])
  41. const message = useMessage() // 国际化
  42. // 表单信息
  43. const formFields = ref<string[]>([])
  44. const formType = ref(20)
  45. provide('formFields', formFields)
  46. provide('formType', formType)
  47. const xmlString = ref<string>('') // BPMN XML
  48. const modeler = shallowRef() // BPMN Modeler
  49. const processDesigner = ref()
  50. const isModelerReady = ref(false)
  51. const controlForm = ref({
  52. simulation: true,
  53. labelEditing: false,
  54. labelVisible: false,
  55. prefix: 'flowable',
  56. headerButtonSize: 'mini',
  57. additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
  58. })
  59. const model = ref<ModelApi.ModelVO>() // 流程模型的信息
  60. // 初始化 bpmnInstances
  61. const initBpmnInstances = () => {
  62. if (!modeler.value) return false
  63. try {
  64. const instances = {
  65. modeler: modeler.value,
  66. modeling: modeler.value.get('modeling'),
  67. moddle: modeler.value.get('moddle'),
  68. eventBus: modeler.value.get('eventBus'),
  69. bpmnFactory: modeler.value.get('bpmnFactory'),
  70. elementFactory: modeler.value.get('elementFactory'),
  71. elementRegistry: modeler.value.get('elementRegistry'),
  72. replace: modeler.value.get('replace'),
  73. selection: modeler.value.get('selection')
  74. }
  75. // 检查所有实例是否都存在
  76. return Object.values(instances).every((instance) => instance)
  77. } catch (error) {
  78. console.error('初始化 bpmnInstances 失败:', error)
  79. return false
  80. }
  81. }
  82. /** 初始化 modeler */
  83. const initModeler = async (item) => {
  84. try {
  85. modeler.value = item
  86. // 等待 modeler 初始化完成
  87. await nextTick()
  88. // 确保 modeler 的所有实例都已经准备好
  89. if (initBpmnInstances()) {
  90. isModelerReady.value = true
  91. if (!props.modelId && props.modelKey && props.modelName) {
  92. await updateModelData(props.modelKey, props.modelName)
  93. }
  94. } else {
  95. console.error('modeler 实例未完全初始化')
  96. }
  97. } catch (error) {
  98. console.error('初始化 modeler 失败:', error)
  99. }
  100. }
  101. /** 获取默认的BPMN XML */
  102. const getDefaultBpmnXml = (key: string, name: string) => {
  103. return `<?xml version="1.0" encoding="UTF-8"?>
  104. <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
  105. <process id="${key}" name="${name}" isExecutable="true" />
  106. <bpmndi:BPMNDiagram id="BPMNDiagram">
  107. <bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
  108. </bpmndi:BPMNDiagram>
  109. </definitions>`
  110. }
  111. /** 添加/修改模型 */
  112. const save = async (bpmnXml: string) => {
  113. try {
  114. if (props.modelId) {
  115. // 编辑模式
  116. const data = {
  117. ...model.value,
  118. bpmnXml: bpmnXml
  119. } as unknown as ModelApi.ModelVO
  120. await ModelApi.updateModelBpmn(data)
  121. emit('success')
  122. } else {
  123. // 新建模式,直接返回XML
  124. emit('success', bpmnXml)
  125. }
  126. } catch (error) {
  127. console.error('保存失败:', error)
  128. message.error('保存失败')
  129. }
  130. }
  131. /** 初始化 */
  132. onMounted(async () => {
  133. try {
  134. if (props.modelId) {
  135. // 编辑模式
  136. // 查询模型
  137. const data = await ModelApi.getModel(props.modelId)
  138. model.value = {
  139. ...data,
  140. bpmnXml: undefined // 清空 bpmnXml 属性
  141. }
  142. xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
  143. } else if (props.modelKey && props.modelName) {
  144. // 新建模式
  145. xmlString.value = getDefaultBpmnXml(props.modelKey, props.modelName)
  146. model.value = {
  147. key: props.modelKey,
  148. name: props.modelName
  149. } as ModelApi.ModelVO
  150. }
  151. } catch (error) {
  152. console.error('初始化失败:', error)
  153. message.error('初始化失败')
  154. }
  155. })
  156. /** 更新模型数据 */
  157. const updateModelData = async (key?: string, name?: string) => {
  158. if (key && name) {
  159. xmlString.value = getDefaultBpmnXml(key, name)
  160. model.value = {
  161. ...model.value,
  162. key: key,
  163. name: name
  164. } as ModelApi.ModelVO
  165. // 确保更新后重新渲染
  166. await nextTick()
  167. if (processDesigner.value?.refresh) {
  168. processDesigner.value.refresh()
  169. }
  170. }
  171. }
  172. // 监听 key 和 name 的变化
  173. watch(
  174. [() => props.modelKey, () => props.modelName],
  175. async ([newKey, newName]) => {
  176. if (!props.modelId && newKey && newName && modeler.value) {
  177. await updateModelData(newKey, newName)
  178. }
  179. },
  180. { immediate: true, deep: true }
  181. )
  182. // 在组件卸载时清理
  183. onBeforeUnmount(() => {
  184. isModelerReady.value = false
  185. modeler.value = null
  186. // 清理全局实例
  187. const w = window as any
  188. if (w.bpmnInstances) {
  189. w.bpmnInstances = null
  190. }
  191. })
  192. /** 获取XML字符串 */
  193. const saveXML = async () => {
  194. if (!modeler.value) {
  195. return { xml: undefined }
  196. }
  197. try {
  198. return await modeler.value.saveXML({ format: true })
  199. } catch (error) {
  200. console.error('获取XML失败:', error)
  201. return { xml: undefined }
  202. }
  203. }
  204. /** 获取SVG字符串 */
  205. const saveSVG = async () => {
  206. if (!modeler.value) {
  207. return { svg: undefined }
  208. }
  209. try {
  210. return await modeler.value.saveSVG()
  211. } catch (error) {
  212. console.error('获取SVG失败:', error)
  213. return { svg: undefined }
  214. }
  215. }
  216. /** 刷新视图 */
  217. const refresh = () => {
  218. if (processDesigner.value?.refresh) {
  219. processDesigner.value.refresh()
  220. }
  221. }
  222. // 暴露必要的属性和方法给父组件
  223. defineExpose({
  224. modeler,
  225. isModelerReady,
  226. saveXML,
  227. saveSVG,
  228. refresh
  229. })
  230. </script>
  231. <style lang="scss">
  232. .process-panel__container {
  233. position: absolute;
  234. top: 180px;
  235. right: 60px;
  236. }
  237. </style>