Browse Source

Merge pull request #115 from GoldenZqqq/feature/bpm

工作流流程新增/修改页面bug修复;保存/发布按钮展示与否逻辑、样式优化
芋道源码 7 months ago
parent
commit
241e27137c

+ 16 - 0
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue

@@ -1,6 +1,7 @@
 <template>
   <div v-loading="loading" class="overflow-auto">
     <SimpleProcessModel
+      ref="simpleProcessModelRef"
       v-if="processNodeTree"
       :flow-node="processNodeTree"
       :readonly="false"
@@ -225,4 +226,19 @@ onMounted(async () => {
     loading.value = false
   }
 })
+
+const simpleProcessModelRef = ref()
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  if (simpleProcessModelRef.value) {
+    return await simpleProcessModelRef.value.getCurrentFlowData()
+  }
+  return undefined
+}
+
+defineExpose({
+  getCurrentFlowData,
+  updateModel
+})
 </script>

+ 27 - 19
src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue

@@ -8,15 +8,6 @@
           <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
           <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
         </el-button-group>
-        <el-button
-          v-if="!readonly"
-          size="default"
-          class="ml-4px"
-          type="primary"
-          :icon="Select"
-          @click="saveSimpleFlowModel"
-          >保存模型</el-button
-        >
       </el-row>
     </div>
     <div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
@@ -42,7 +33,8 @@
 import ProcessNodeTree from './ProcessNodeTree.vue'
 import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
 import { useWatchNode } from './node'
-import { Select, ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
+import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
+
 defineOptions({
   name: 'SimpleProcessModel'
 })
@@ -58,6 +50,7 @@ const props = defineProps({
     default: true
   }
 })
+
 const emits = defineEmits<{
   'save': [node: SimpleFlowNode | undefined]
 }>()
@@ -68,6 +61,7 @@ provide('readonly', props.readonly)
 let scaleValue = ref(100)
 const MAX_SCALE_VALUE = 200
 const MIN_SCALE_VALUE = 50
+
 // 放大
 const zoomIn = () => {
   if (scaleValue.value == MAX_SCALE_VALUE) {
@@ -75,6 +69,7 @@ const zoomIn = () => {
   }
   scaleValue.value += 10
 }
+
 // 缩小
 const zoomOut = () => {
   if (scaleValue.value == MIN_SCALE_VALUE) {
@@ -82,21 +77,14 @@ const zoomOut = () => {
   }
   scaleValue.value -= 10
 }
+
 const processReZoom = () => {
   scaleValue.value = 100
 }
 
 const errorDialogVisible = ref(false)
 let errorNodes: SimpleFlowNode[] = []
-const saveSimpleFlowModel = async () => {
-  errorNodes = []
-  validateNode(processNodeTree.value, errorNodes)
-  if (errorNodes.length > 0) {
-    errorDialogVisible.value = true
-    return
-  }
-  emits('save', processNodeTree.value)
-}
+
 // 校验节点设置。 暂时以 showText 为空 未节点错误配置
 const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
   if (node) {
@@ -135,6 +123,26 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
     }
   }
 }
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    errorNodes = []
+    validateNode(processNodeTree.value, errorNodes)
+    if (errorNodes.length > 0) {
+      errorDialogVisible.value = true
+      return undefined
+    }
+    return processNodeTree.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return undefined
+  }
+}
+
+defineExpose({
+  getCurrentFlowData
+})
 </script>
 
 <style lang="scss" scoped></style>

+ 0 - 7
src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue

@@ -160,13 +160,6 @@
             <XButton preIcon="ep:refresh" @click="processRestart()" />
           </el-tooltip>
         </ElButtonGroup>
-        <XButton
-          preIcon="ep:plus"
-          title="保存模型"
-          @click="processSave"
-          :type="props.headerButtonType"
-          :disabled="simulationStatus"
-        />
       </template>
       <!-- 用于打开本地文件-->
       <input

+ 43 - 1
src/views/bpm/model/editor/index.vue

@@ -205,11 +205,53 @@ onBeforeUnmount(() => {
     w.bpmnInstances = null
   }
 })
+
+/** 获取XML字符串 */
+const saveXML = async () => {
+  if (!modeler.value) {
+    return { xml: undefined }
+  }
+  try {
+    return await modeler.value.saveXML({ format: true })
+  } catch (error) {
+    console.error('获取XML失败:', error)
+    return { xml: undefined }
+  }
+}
+
+/** 获取SVG字符串 */
+const saveSVG = async () => {
+  if (!modeler.value) {
+    return { svg: undefined }
+  }
+  try {
+    return await modeler.value.saveSVG()
+  } catch (error) {
+    console.error('获取SVG失败:', error)
+    return { svg: undefined }
+  }
+}
+
+/** 刷新视图 */
+const refresh = () => {
+  if (processDesigner.value?.refresh) {
+    processDesigner.value.refresh()
+  }
+}
+
+// 暴露必要的属性和方法给父组件
+defineExpose({
+  modeler,
+  isModelerReady,
+  saveXML,
+  saveSVG,
+  refresh
+})
 </script>
 <style lang="scss">
 .process-panel__container {
   position: absolute;
-  top: 90px;
+  top: 180px;
   right: 60px;
 }
 </style>

+ 2 - 8
src/views/bpm/model/form/BasicInfo.vue

@@ -1,15 +1,9 @@
 <template>
-  <el-form
-    ref="formRef"
-    :model="modelData"
-    :rules="rules"
-    label-width="120px"
-    class="mt-20px w-600px"
-  >
+  <el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
     <el-form-item label="流程标识" prop="key" class="mb-20px">
       <div class="flex items-center">
         <el-input
-          class="!w-480px"
+          class="!w-440px"
           v-model="modelData.key"
           :disabled="!!modelData.id"
           placeholder="请输入流标标识"

+ 61 - 35
src/views/bpm/model/form/ProcessDesign.vue

@@ -7,6 +7,7 @@
       :model-key="modelData.key"
       :model-name="modelData.name"
       :value="modelData.bpmnXml"
+      ref="bpmnEditorRef"
       @success="handleDesignSuccess"
     />
   </template>
@@ -19,6 +20,7 @@
       :model-key="modelData.key"
       :model-name="modelData.name"
       :value="modelData.bpmnXml"
+      ref="simpleEditorRef"
       @success="handleDesignSuccess"
     />
   </template>
@@ -38,7 +40,8 @@ const props = defineProps({
 
 const emit = defineEmits(['update:modelValue', 'success'])
 
-const xmlString = ref<string>()
+const bpmnEditorRef = ref()
+const simpleEditorRef = ref()
 
 // 创建本地数据副本
 const modelData = computed({
@@ -46,45 +49,73 @@ const modelData = computed({
   set: (val) => emit('update:modelValue', val)
 })
 
-// 监听modelValue变化,确保XML数据同步
-watch(
-  () => props.modelValue,
-  (newVal) => {
-    if (newVal?.bpmnXml) {
-      xmlString.value = newVal.bpmnXml
+/** 获取当前流程数据 */
+const getProcessData = async () => {
+  try {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      // BPMN设计器
+      if (bpmnEditorRef.value) {
+        const { xml } = await bpmnEditorRef.value.saveXML()
+        if (xml) {
+          return xml
+        }
+      }
+    } else {
+      // Simple设计器
+      if (simpleEditorRef.value) {
+        const flowData = await simpleEditorRef.value.getCurrentFlowData()
+        if (flowData) {
+          return flowData // 直接返回流程数据对象,不需要转换为字符串
+        }
+      }
     }
-  },
-  { immediate: true, deep: true }
-)
-
-/** 处理设计器保存成功 */
-const handleDesignSuccess = (bpmnXml?: string) => {
-  if (bpmnXml) {
-    xmlString.value = bpmnXml
-    modelData.value = {
-      ...modelData.value,
-      bpmnXml
-    }
-    emit('success', bpmnXml)
+    return undefined
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return undefined
   }
 }
 
 /** 表单校验 */
 const validate = async () => {
-  // 获取最新的XML数据
-  const currentXml = xmlString.value || modelData.value?.bpmnXml
+  try {
+    // 根据流程类型获取对应的编辑器引用
+    const editorRef =
+      modelData.value.type === BpmModelType.BPMN ? bpmnEditorRef.value : simpleEditorRef.value
+    if (!editorRef) {
+      throw new Error('流程设计器未初始化')
+    }
+
+    // 获取最新的流程数据
+    const processData = await getProcessData()
+    if (!processData) {
+      throw new Error('请设计流程')
+    }
 
-  // 如果是修改场景且有XML数据(无论是新的还是原有的)
-  if (modelData.value.id && currentXml) {
     return true
+  } catch (error) {
+    throw error
   }
+}
 
-  // 新增场景必须有XML数据
-  if (!currentXml) {
-    throw new Error('请设计流程')
+/** 处理设计器保存成功 */
+const handleDesignSuccess = (data?: any) => {
+  if (data) {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      modelData.value = {
+        ...modelData.value,
+        bpmnXml: data,
+        simpleModel: null
+      }
+    } else {
+      modelData.value = {
+        ...modelData.value,
+        bpmnXml: null,
+        simpleModel: data
+      }
+    }
+    emit('success', data)
   }
-
-  return true
 }
 
 /** 是否显示设计器 */
@@ -92,13 +123,8 @@ const showDesigner = computed(() => {
   return Boolean(modelData.value?.key && modelData.value?.name)
 })
 
-/** 获取当前XML字符串 */
-const getXmlString = () => {
-  return xmlString.value || modelData.value?.bpmnXml || ''
-}
-
 defineExpose({
   validate,
-  getXmlString
+  getProcessData
 })
 </script>

+ 154 - 53
src/views/bpm/model/form/index.vue

@@ -7,7 +7,7 @@
       >
         <!-- 左侧标题 -->
         <div class="w-200px flex items-center overflow-hidden">
-          <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="router.back()" />
+          <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
           <span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
             {{ formData.name || '创建流程' }}
           </span>
@@ -44,15 +44,15 @@
 
         <!-- 右侧按钮 -->
         <div class="w-200px flex items-center justify-end gap-2">
-          <el-button @click="handleSave">保 存</el-button>
-          <el-button type="primary" @click="handleDeploy">发 布</el-button>
+          <el-button v-if="route.params.id" type="success" @click="handleDeploy">发 布</el-button>
+          <el-button type="primary" @click="handleSave">保 存</el-button>
         </div>
       </div>
 
       <!-- 主体内容 -->
       <div class="mt-50px">
         <!-- 第一步:基本信息 -->
-        <div v-if="currentStep === 0" class="mx-auto max-w-1024px">
+        <div v-if="currentStep === 0" class="mx-auto w-560px">
           <BasicInfo
             v-model="formData"
             :categoryList="categoryList"
@@ -62,7 +62,7 @@
         </div>
 
         <!-- 第二步:表单设计 -->
-        <div v-if="currentStep === 1" class="mx-auto max-w-1024px">
+        <div v-if="currentStep === 1" class="mx-auto w-560px">
           <FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
         </div>
 
@@ -90,8 +90,10 @@ import { BpmModelType, BpmModelFormType } from '@/utils/constants'
 import BasicInfo from './BasicInfo.vue'
 import FormDesign from './FormDesign.vue'
 import ProcessDesign from './ProcessDesign.vue'
+import { useTagsViewStore } from '@/store/modules/tagsView'
 
 const router = useRouter()
+const { delView } = useTagsViewStore() // 视图操作
 const route = useRoute()
 const message = useMessage()
 const userStore = useUserStoreWithOut()
@@ -102,24 +104,24 @@ const formDesignRef = ref()
 const processDesignRef = ref()
 
 /** 步骤校验函数 */
-const validateStep1 = async () => {
+const validateBasic = async () => {
   await basicInfoRef.value?.validate()
 }
 
-const validateStep2 = async () => {
+const validateForm = async () => {
   await formDesignRef.value?.validate()
 }
 
-const validateStep3 = async () => {
+const validateProcess = async () => {
   await processDesignRef.value?.validate()
 }
 
 // 步骤控制
 const currentStep = ref(0)
 const steps = [
-  { title: '基本信息', validator: validateStep1 },
-  { title: '表单设计', validator: validateStep2 },
-  { title: '流程设计', validator: validateStep3 }
+  { title: '基本信息', validator: validateBasic },
+  { title: '表单设计', validator: validateForm },
+  { title: '流程设计', validator: validateProcess }
 ]
 
 // 表单数据
@@ -166,71 +168,154 @@ const initData = async () => {
   userList.value = await UserApi.getSimpleUserList()
 }
 
+/** 校验所有步骤数据是否完整 */
+const validateAllSteps = async () => {
+  try {
+    // 基本信息校验
+    await basicInfoRef.value?.validate()
+    if (!formData.value.key || !formData.value.name || !formData.value.category) {
+      currentStep.value = 0
+      throw new Error('请完善基本信息')
+    }
+    
+    // 表单设计校验
+    await formDesignRef.value?.validate()
+    if (formData.value.formType === 10 && !formData.value.formId) {
+      currentStep.value = 1
+      throw new Error('请选择流程表单')
+    }
+    if (
+      formData.value.formType === 20 &&
+      (!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
+    ) {
+      currentStep.value = 1
+      throw new Error('请完善自定义表单信息')
+    }
+    
+    // 流程设计校验
+    await processDesignRef.value?.validate()
+    const processData = await processDesignRef.value?.getProcessData()
+    if (!processData) {
+      currentStep.value = 2
+      throw new Error('请设计流程')
+    }
+    
+    return true
+  } catch (error) {
+    throw error
+  }
+}
+
 /** 保存操作 */
 const handleSave = async () => {
   try {
-    // 保存前确保所有步骤的数据都已经验证通过
-    for (const step of steps) {
-      if (step.validator) {
-        await step.validator()
-      }
+    // 保存前校验所有步骤的数据
+    await validateAllSteps()
+    
+    // 获取最新的流程设计数据
+    const processData = await processDesignRef.value?.getProcessData()
+    if (!processData) {
+      throw new Error('获取流程数据失败')
     }
 
-    // 如果是在第三步,需要先获取最新的流程设计数据
-    if (currentStep.value === 2) {
-      await nextTick()
-      const bpmnXml = processDesignRef.value?.getXmlString()
-      // 确保有XML数据
-      if (!bpmnXml) {
-        throw new Error('请设计流程')
-      }
-      formData.value.bpmnXml = bpmnXml
+    // 更新表单数据
+    const modelData = {
+      ...formData.value
     }
-
+    if (formData.value.type === BpmModelType.BPMN) {
+      modelData.bpmnXml = processData
+      modelData.simpleModel = null
+    } else {
+      modelData.bpmnXml = null
+      modelData.simpleModel = processData // 直接使用流程数据对象
+    }
+    
     if (formData.value.id) {
-      await ModelApi.updateModel(formData.value)
+      // 修改场景
+      await ModelApi.updateModel(modelData)
       message.success('修改成功')
+      // 询问是否发布流程
+      try {
+        await message.confirm('修改流程成功,是否发布流程?')
+        // 用户点击确认,执行发布
+        await handleDeploy()
+      } catch {
+        // 用户点击取消,停留在当前页面
+      }
     } else {
-      const result = await ModelApi.createModel(formData.value)
-      formData.value.id = result.id
+      // 新增场景
+      const result = await ModelApi.createModel(modelData)
+      formData.value.id = result
       message.success('新增成功')
+      try {
+        await message.confirm('创建流程成功,是否继续编辑?')
+        // 用户点击继续编辑,跳转到编辑页面
+        await nextTick()
+        // 先删除当前页签
+        delView(unref(router.currentRoute))
+        // 跳转到编辑页面
+        await router.push({
+          name: 'BpmModelUpdate',
+          params: { id: formData.value.id }
+        })
+      } catch {
+        // 先删除当前页签
+        delView(unref(router.currentRoute))
+        // 用户点击返回列表
+        await router.push({ name: 'BpmModel' })
+      }
     }
-  } catch (error) {
+  } catch (error: any) {
     console.error('保存失败:', error)
-    message.error(error.message || '保存失败')
-    throw error
+    message.warning(error.message || '请完善所有步骤的必填信息')
   }
 }
 
 /** 发布操作 */
 const handleDeploy = async () => {
   try {
-    await message.confirm('是否确认发布该流程?')
+    // 修改场景下直接发布,新增场景下需要先确认
+    if (!formData.value.id) {
+      await message.confirm('是否确认发布该流程?')
+    }
+
+    // 校验所有步骤
+    await validateAllSteps()
+    
+    // 获取最新的流程设计数据
+    const processData = await processDesignRef.value?.getProcessData()
+    if (!processData) {
+      throw new Error('获取流程数据失败')
+    }
+
+    // 更新表单数据
+    const modelData = {
+      ...formData.value
+    }
+    if (formData.value.type === BpmModelType.BPMN) {
+      modelData.bpmnXml = processData
+      modelData.simpleModel = null
+    } else {
+      modelData.bpmnXml = null
+      modelData.simpleModel = processData // 直接使用流程数据对象
+    }
+
     // 先保存所有数据
-    await handleSave()
+    if (formData.value.id) {
+      await ModelApi.updateModel(modelData)
+    } else {
+      const result = await ModelApi.createModel(modelData)
+      formData.value.id = result.id
+    }
+
     // 发布
     await ModelApi.deployModel(formData.value.id)
     message.success('发布成功')
+    // 返回列表页
     router.push({ name: 'BpmModel' })
-  } catch (error) {
+  } catch (error: any) {
     console.error('发布失败:', error)
-    if (error instanceof Error) {
-      // 校验失败时,跳转到对应步骤
-      const failedStep = steps.findIndex((step) => {
-        try {
-          step.validator && step.validator()
-          return false
-        } catch {
-          return true
-        }
-      })
-      if (failedStep !== -1) {
-        currentStep.value = failedStep
-        message.warning('请完善必填信息')
-      } else {
-        message.error(error.message || '发布失败')
-      }
-    }
+    message.warning(error.message || '发布失败')
   }
 }
 
@@ -243,7 +328,7 @@ const handleStepClick = async (index: number) => {
       return
     }
   }
-  
+
   // 只有在向后切换时才进行校验
   if (index > currentStep.value) {
     try {
@@ -267,10 +352,26 @@ const handleDesignSuccess = (bpmnXml?: string) => {
   }
 }
 
+/** 返回列表页 */
+const handleBack = () => {
+  // 先删除当前页签
+  delView(unref(router.currentRoute))
+  // 跳转到列表页
+  router.push({ name: 'BpmModel' })
+}
+
 /** 初始化 */
 onMounted(async () => {
   await initData()
 })
+
+// 添加组件卸载前的清理代码
+onBeforeUnmount(() => {
+  // 清理所有的引用
+  basicInfoRef.value = null
+  formDesignRef.value = null
+  processDesignRef.value = null
+})
 </script>
 
 <style lang="scss" scoped>

+ 17 - 0
src/views/bpm/simple/SimpleModelDesign.vue

@@ -36,5 +36,22 @@ watch([() => props.modelKey, () => props.modelName], ([newKey, newName]) => {
 const handleSuccess = (data?: any) => {
   emit('success', data)
 }
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    if (designerRef.value) {
+      return await designerRef.value.getCurrentFlowData()
+    }
+    return undefined
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return undefined
+  }
+}
+
+defineExpose({
+  getCurrentFlowData
+})
 </script>
 <style lang="scss" scoped></style>