index.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. value?: string
  40. }>()
  41. const emit = defineEmits(['success', 'init-finished'])
  42. const message = useMessage() // 国际化
  43. // 表单信息
  44. const formFields = ref<string[]>([])
  45. const formType = ref(20)
  46. provide('formFields', formFields)
  47. provide('formType', formType)
  48. const xmlString = ref<string>('') // BPMN XML
  49. const modeler = shallowRef() // BPMN Modeler
  50. const processDesigner = ref()
  51. const isModelerReady = ref(false)
  52. const controlForm = ref({
  53. simulation: true,
  54. labelEditing: false,
  55. labelVisible: false,
  56. prefix: 'flowable',
  57. headerButtonSize: 'mini',
  58. additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
  59. })
  60. const model = ref<ModelApi.ModelVO>() // 流程模型的信息
  61. // 初始化 bpmnInstances
  62. const initBpmnInstances = () => {
  63. if (!modeler.value) return false
  64. try {
  65. const instances = {
  66. modeler: modeler.value,
  67. modeling: modeler.value.get('modeling'),
  68. moddle: modeler.value.get('moddle'),
  69. eventBus: modeler.value.get('eventBus'),
  70. bpmnFactory: modeler.value.get('bpmnFactory'),
  71. elementFactory: modeler.value.get('elementFactory'),
  72. elementRegistry: modeler.value.get('elementRegistry'),
  73. replace: modeler.value.get('replace'),
  74. selection: modeler.value.get('selection')
  75. }
  76. // 检查所有实例是否都存在
  77. return Object.values(instances).every((instance) => instance)
  78. } catch (error) {
  79. console.error('初始化 bpmnInstances 失败:', error)
  80. return false
  81. }
  82. }
  83. /** 初始化 modeler */
  84. const initModeler = async (item) => {
  85. try {
  86. modeler.value = item
  87. // 等待 modeler 初始化完成
  88. await nextTick()
  89. // 确保 modeler 的所有实例都已经准备好
  90. if (initBpmnInstances()) {
  91. isModelerReady.value = true
  92. emit('init-finished')
  93. // 初始化完成后,设置初始值
  94. if (props.modelId) {
  95. // 编辑模式
  96. const data = await ModelApi.getModel(props.modelId)
  97. model.value = {
  98. ...data,
  99. bpmnXml: undefined // 清空 bpmnXml 属性
  100. }
  101. xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
  102. } else if (props.modelKey && props.modelName) {
  103. // 新建模式
  104. xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
  105. model.value = {
  106. key: props.modelKey,
  107. name: props.modelName
  108. } as ModelApi.ModelVO
  109. }
  110. // 导入XML并刷新视图
  111. await nextTick()
  112. try {
  113. await modeler.value.importXML(xmlString.value)
  114. if (processDesigner.value?.refresh) {
  115. processDesigner.value.refresh()
  116. }
  117. } catch (error) {
  118. console.error('导入XML失败:', error)
  119. }
  120. } else {
  121. console.error('modeler 实例未完全初始化')
  122. }
  123. } catch (error) {
  124. console.error('初始化 modeler 失败:', error)
  125. }
  126. }
  127. /** 获取默认的BPMN XML */
  128. const getDefaultBpmnXml = (key: string, name: string) => {
  129. return `<?xml version="1.0" encoding="UTF-8"?>
  130. <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">
  131. <process id="${key}" name="${name}" isExecutable="true" />
  132. <bpmndi:BPMNDiagram id="BPMNDiagram">
  133. <bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
  134. </bpmndi:BPMNDiagram>
  135. </definitions>`
  136. }
  137. /** 添加/修改模型 */
  138. const save = async (bpmnXml: string) => {
  139. try {
  140. xmlString.value = bpmnXml
  141. if (props.modelId) {
  142. // 编辑模式
  143. const data = {
  144. ...model.value,
  145. bpmnXml: bpmnXml
  146. } as unknown as ModelApi.ModelVO
  147. await ModelApi.updateModelBpmn(data)
  148. emit('success')
  149. } else {
  150. // 新建模式,直接返回XML
  151. emit('success', bpmnXml)
  152. }
  153. } catch (error) {
  154. console.error('保存失败:', error)
  155. message.error('保存失败')
  156. }
  157. }
  158. // 监听 key、name 和 value 的变化
  159. watch(
  160. [() => props.modelKey, () => props.modelName, () => props.value],
  161. async ([newKey, newName, newValue]) => {
  162. if (!props.modelId && isModelerReady.value) {
  163. let shouldRefresh = false
  164. if (newKey && newName) {
  165. const newXml = newValue || getDefaultBpmnXml(newKey, newName)
  166. if (newXml !== xmlString.value) {
  167. xmlString.value = newXml
  168. shouldRefresh = true
  169. }
  170. model.value = {
  171. ...model.value,
  172. key: newKey,
  173. name: newName
  174. } as ModelApi.ModelVO
  175. } else if (newValue && newValue !== xmlString.value) {
  176. xmlString.value = newValue
  177. shouldRefresh = true
  178. }
  179. if (shouldRefresh) {
  180. // 确保更新后重新渲染
  181. await nextTick()
  182. if (processDesigner.value?.refresh) {
  183. try {
  184. await modeler.value?.importXML(xmlString.value)
  185. processDesigner.value.refresh()
  186. } catch (error) {
  187. console.error('导入XML失败:', error)
  188. }
  189. }
  190. }
  191. }
  192. },
  193. { deep: true }
  194. )
  195. // 在组件卸载时清理
  196. onBeforeUnmount(() => {
  197. isModelerReady.value = false
  198. modeler.value = null
  199. // 清理全局实例
  200. const w = window as any
  201. if (w.bpmnInstances) {
  202. w.bpmnInstances = null
  203. }
  204. })
  205. /** 获取 XML 字符串 */
  206. const saveXML = async () => {
  207. if (!modeler.value) {
  208. return { xml: xmlString.value }
  209. }
  210. try {
  211. const result = await modeler.value.saveXML({ format: true })
  212. xmlString.value = result.xml
  213. return result
  214. } catch (error) {
  215. console.error('获取XML失败:', error)
  216. return { xml: xmlString.value }
  217. }
  218. }
  219. /** 获取SVG字符串 */
  220. const saveSVG = async () => {
  221. if (!modeler.value) {
  222. return { svg: undefined }
  223. }
  224. try {
  225. return await modeler.value.saveSVG()
  226. } catch (error) {
  227. console.error('获取SVG失败:', error)
  228. return { svg: undefined }
  229. }
  230. }
  231. /** 刷新视图 */
  232. const refresh = async () => {
  233. if (processDesigner.value?.refresh && modeler.value) {
  234. try {
  235. await modeler.value.importXML(xmlString.value)
  236. processDesigner.value.refresh()
  237. } catch (error) {
  238. console.error('刷新视图失败:', error)
  239. }
  240. }
  241. }
  242. // 暴露必要的属性和方法给父组件
  243. defineExpose({
  244. modeler,
  245. isModelerReady,
  246. saveXML,
  247. saveSVG,
  248. refresh
  249. })
  250. </script>
  251. <style lang="scss">
  252. .process-panel__container {
  253. position: absolute;
  254. top: 172px;
  255. right: 70px;
  256. }
  257. </style>