Merge branch 'feature/bpm' of https://github.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm
# Conflicts: # src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue # src/views/bpm/model/editor/index.vuepull/624/MERGE
						commit
						c083a0c986
					
				| 
						 | 
				
			
			@ -38,12 +38,21 @@ import * as UserGroupApi from '@/api/bpm/userGroup'
 | 
			
		|||
defineOptions({
 | 
			
		||||
  name: 'SimpleProcessDesigner'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['success']) // 保存成功事件
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelId: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true
 | 
			
		||||
    required: false
 | 
			
		||||
  },
 | 
			
		||||
  modelKey: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: false
 | 
			
		||||
  },
 | 
			
		||||
  modelName: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: false
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,6 +78,33 @@ const message = useMessage() // 国际化
 | 
			
		|||
const processNodeTree = ref<SimpleFlowNode | undefined>()
 | 
			
		||||
const errorDialogVisible = ref(false)
 | 
			
		||||
let errorNodes: SimpleFlowNode[] = []
 | 
			
		||||
 | 
			
		||||
// 添加更新模型的方法
 | 
			
		||||
const updateModel = (key?: string, name?: string) => {
 | 
			
		||||
  if (!processNodeTree.value) {
 | 
			
		||||
    processNodeTree.value = {
 | 
			
		||||
      name: name || '发起人',
 | 
			
		||||
      type: NodeType.START_USER_NODE,
 | 
			
		||||
      id: NodeId.START_USER_NODE_ID,
 | 
			
		||||
      childNode: {
 | 
			
		||||
        id: NodeId.END_EVENT_NODE_ID,
 | 
			
		||||
        name: '结束',
 | 
			
		||||
        type: NodeType.END_EVENT_NODE
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if (name) {
 | 
			
		||||
    // 更新现有模型的名称
 | 
			
		||||
    processNodeTree.value.name = name
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 监听属性变化
 | 
			
		||||
watch([() => props.modelKey, () => props.modelName], ([newKey, newName]) => {
 | 
			
		||||
  if (!props.modelId && newKey && newName) {
 | 
			
		||||
    updateModel(newKey, newName)
 | 
			
		||||
  }
 | 
			
		||||
}, { immediate: true, deep: true })
 | 
			
		||||
 | 
			
		||||
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
 | 
			
		||||
  if (!simpleModelNode) {
 | 
			
		||||
    message.error('模型数据为空')
 | 
			
		||||
| 
						 | 
				
			
			@ -76,21 +112,28 @@ const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
 | 
			
		|||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    loading.value = true
 | 
			
		||||
    const data = {
 | 
			
		||||
      id: props.modelId,
 | 
			
		||||
      simpleModel: simpleModelNode
 | 
			
		||||
    }
 | 
			
		||||
    const result = await updateBpmSimpleModel(data)
 | 
			
		||||
    if (result) {
 | 
			
		||||
      message.success('修改成功')
 | 
			
		||||
      emits('success')
 | 
			
		||||
    if (props.modelId) {
 | 
			
		||||
      // 编辑模式
 | 
			
		||||
      const data = {
 | 
			
		||||
        id: props.modelId,
 | 
			
		||||
        simpleModel: simpleModelNode
 | 
			
		||||
      }
 | 
			
		||||
      const result = await updateBpmSimpleModel(data)
 | 
			
		||||
      if (result) {
 | 
			
		||||
        message.success('修改成功')
 | 
			
		||||
        emits('success')
 | 
			
		||||
      } else {
 | 
			
		||||
        message.alert('修改失败')
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      message.alert('修改失败')
 | 
			
		||||
      // 新建模式,直接返回数据
 | 
			
		||||
      emits('success', simpleModelNode)
 | 
			
		||||
    }
 | 
			
		||||
  } finally {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
 | 
			
		||||
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
 | 
			
		||||
  if (node) {
 | 
			
		||||
| 
						 | 
				
			
			@ -134,12 +177,14 @@ onMounted(async () => {
 | 
			
		|||
  try {
 | 
			
		||||
    loading.value = true
 | 
			
		||||
    // 获取表单字段
 | 
			
		||||
    const bpmnModel = await getModel(props.modelId)
 | 
			
		||||
    if (bpmnModel) {
 | 
			
		||||
      formType.value = bpmnModel.formType
 | 
			
		||||
      if (formType.value === 10) {
 | 
			
		||||
        const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
 | 
			
		||||
        formFields.value = bpmnForm?.fields
 | 
			
		||||
    if (props.modelId) {
 | 
			
		||||
      const bpmnModel = await getModel(props.modelId)
 | 
			
		||||
      if (bpmnModel) {
 | 
			
		||||
        formType.value = bpmnModel.formType
 | 
			
		||||
        if (formType.value === 10) {
 | 
			
		||||
          const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
 | 
			
		||||
          formFields.value = bpmnForm?.fields
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // 获得角色列表
 | 
			
		||||
| 
						 | 
				
			
			@ -155,14 +200,18 @@ onMounted(async () => {
 | 
			
		|||
    // 获取用户组列表
 | 
			
		||||
    userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
 | 
			
		||||
 | 
			
		||||
    //获取 SIMPLE 设计器模型
 | 
			
		||||
    const result = await getBpmSimpleModel(props.modelId)
 | 
			
		||||
    if (result) {
 | 
			
		||||
      processNodeTree.value = result
 | 
			
		||||
    } else {
 | 
			
		||||
      // 初始值
 | 
			
		||||
    if (props.modelId) {
 | 
			
		||||
      //获取 SIMPLE 设计器模型
 | 
			
		||||
      const result = await getBpmSimpleModel(props.modelId)
 | 
			
		||||
      if (result) {
 | 
			
		||||
        processNodeTree.value = result
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // 如果没有现有模型,创建初始模型
 | 
			
		||||
    if (!processNodeTree.value) {
 | 
			
		||||
      processNodeTree.value = {
 | 
			
		||||
        name: '发起人',
 | 
			
		||||
        name: props.modelName || '发起人',
 | 
			
		||||
        type: NodeType.START_USER_NODE,
 | 
			
		||||
        id: NodeId.START_USER_NODE_ID,
 | 
			
		||||
        childNode: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@
 | 
			
		|||
  </Dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defaultProps, findTreeNode, handleTree } from '@/utils/tree'
 | 
			
		||||
import { defaultProps, handleTree } from '@/utils/tree'
 | 
			
		||||
import * as DeptApi from '@/api/system/dept'
 | 
			
		||||
import * as UserApi from '@/api/system/user'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +50,7 @@ const emit = defineEmits<{
 | 
			
		|||
const { t } = useI18n() // 国际
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const deptTree = ref<Tree[]>([]) // 部门树形结构化
 | 
			
		||||
const deptList = ref<any[]>([]) // 保存扁平化的部门列表数据
 | 
			
		||||
const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
 | 
			
		||||
const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
 | 
			
		||||
const selectedUserIdList: any = ref([]) // 选中的用户列表
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +80,9 @@ const open = async (id: number, selectedList?: any[]) => {
 | 
			
		|||
  resetForm()
 | 
			
		||||
 | 
			
		||||
  // 加载部门、用户列表
 | 
			
		||||
  deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
 | 
			
		||||
  const deptData = await DeptApi.getSimpleDeptList()
 | 
			
		||||
  deptList.value = deptData // 保存扁平结构的部门数据
 | 
			
		||||
  deptTree.value = handleTree(deptData) // 转换成树形结构
 | 
			
		||||
  userList.value = await UserApi.getSimpleUserList()
 | 
			
		||||
 | 
			
		||||
  // 初始状态下,过滤列表等于所有用户列表
 | 
			
		||||
| 
						 | 
				
			
			@ -88,16 +91,31 @@ const open = async (id: number, selectedList?: any[]) => {
 | 
			
		|||
  dialogVisible.value = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取指定部门及其所有子部门的ID列表 */
 | 
			
		||||
const getChildDeptIds = (deptId: number, deptList: any[]): number[] => {
 | 
			
		||||
  const ids = [deptId]
 | 
			
		||||
  const children = deptList.filter((dept) => dept.parentId === deptId)
 | 
			
		||||
  children.forEach((child) => {
 | 
			
		||||
    ids.push(...getChildDeptIds(child.id, deptList))
 | 
			
		||||
  })
 | 
			
		||||
  return ids
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取部门过滤后的用户列表 */
 | 
			
		||||
const getUserList = async (deptId?: number) => {
 | 
			
		||||
const filterUserList = async (deptId?: number) => {
 | 
			
		||||
  formLoading.value = true
 | 
			
		||||
  try {
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    // TODO @芋艿:替换到 simple List 暂不支持 deptId 过滤
 | 
			
		||||
    // TODO @Zqqq:这个,可以使用前端过滤么?通过 deptList 获取到 deptId 子节点,然后去 userList
 | 
			
		||||
    const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
 | 
			
		||||
    // 更新过滤后的用户列表
 | 
			
		||||
    filteredUserList.value = data.list
 | 
			
		||||
    if (!deptId) {
 | 
			
		||||
      // 如果没有选择部门,显示所有用户
 | 
			
		||||
      filteredUserList.value = [...userList.value]
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 直接使用已保存的部门列表数据进行过滤
 | 
			
		||||
    const deptIds = getChildDeptIds(deptId, deptList.value)
 | 
			
		||||
 | 
			
		||||
    // 过滤出这些部门下的用户
 | 
			
		||||
    filteredUserList.value = userList.value.filter((user) => deptIds.includes(user.deptId))
 | 
			
		||||
  } finally {
 | 
			
		||||
    formLoading.value = false
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -121,6 +139,7 @@ const submitForm = async () => {
 | 
			
		|||
/** 重置表单 */
 | 
			
		||||
const resetForm = () => {
 | 
			
		||||
  deptTree.value = []
 | 
			
		||||
  deptList.value = []
 | 
			
		||||
  userList.value = []
 | 
			
		||||
  filteredUserList.value = []
 | 
			
		||||
  selectedUserIdList.value = []
 | 
			
		||||
| 
						 | 
				
			
			@ -128,7 +147,7 @@ const resetForm = () => {
 | 
			
		|||
 | 
			
		||||
/** 处理部门被点击 */
 | 
			
		||||
const handleNodeClick = (row: { [key: string]: any }) => {
 | 
			
		||||
  getUserList(row.id)
 | 
			
		||||
  filterUserList(row.id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '700px' }">
 | 
			
		||||
    <el-collapse v-model="activeTab">
 | 
			
		||||
  <div class="process-panel__container" :style="{ width: `${width}px` }">
 | 
			
		||||
    <el-collapse v-model="activeTab" v-if="isReady">
 | 
			
		||||
      <el-collapse-item name="base">
 | 
			
		||||
        <!-- class="panel-tab__title" -->
 | 
			
		||||
        <template #title>
 | 
			
		||||
| 
						 | 
				
			
			@ -119,24 +119,16 @@ const elementBusinessObject = ref<any>({}) // 元素 businessObject 镜像,提
 | 
			
		|||
const conditionFormVisible = ref(false) // 流转条件设置
 | 
			
		||||
const formVisible = ref(false) // 表单配置
 | 
			
		||||
const bpmnElement = ref()
 | 
			
		||||
const isReady = ref(false)
 | 
			
		||||
 | 
			
		||||
provide('prefix', props.prefix)
 | 
			
		||||
provide('width', props.width)
 | 
			
		||||
const bpmnInstances = () => (window as any)?.bpmnInstances
 | 
			
		||||
 | 
			
		||||
// 监听 props.bpmnModeler 然后 initModels
 | 
			
		||||
const unwatchBpmn = watch(
 | 
			
		||||
  () => props.bpmnModeler,
 | 
			
		||||
  () => {
 | 
			
		||||
    // 避免加载时 流程图 并未加载完成
 | 
			
		||||
    if (!props.bpmnModeler) {
 | 
			
		||||
      console.log('缺少props.bpmnModeler')
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log('props.bpmnModeler 有值了!!!')
 | 
			
		||||
    const w = window as any
 | 
			
		||||
    w.bpmnInstances = {
 | 
			
		||||
// 初始化 bpmnInstances
 | 
			
		||||
const initBpmnInstances = () => {
 | 
			
		||||
  if (!props.bpmnModeler) return false
 | 
			
		||||
  try {
 | 
			
		||||
    const instances = {
 | 
			
		||||
      modeler: props.bpmnModeler,
 | 
			
		||||
      modeling: props.bpmnModeler.get('modeling'),
 | 
			
		||||
      moddle: props.bpmnModeler.get('moddle'),
 | 
			
		||||
| 
						 | 
				
			
			@ -148,9 +140,45 @@ const unwatchBpmn = watch(
 | 
			
		|||
      selection: props.bpmnModeler.get('selection')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log(bpmnInstances(), 'window.bpmnInstances')
 | 
			
		||||
    getActiveElement()
 | 
			
		||||
    unwatchBpmn()
 | 
			
		||||
    // 检查所有实例是否都存在
 | 
			
		||||
    const allInstancesExist = Object.values(instances).every(instance => instance)
 | 
			
		||||
    if (allInstancesExist) {
 | 
			
		||||
      const w = window as any
 | 
			
		||||
      w.bpmnInstances = instances
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('初始化 bpmnInstances 失败:', error)
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const bpmnInstances = () => (window as any)?.bpmnInstances
 | 
			
		||||
 | 
			
		||||
// 监听 props.bpmnModeler 然后 initModels
 | 
			
		||||
const unwatchBpmn = watch(
 | 
			
		||||
  () => props.bpmnModeler,
 | 
			
		||||
  async () => {
 | 
			
		||||
    // 避免加载时 流程图 并未加载完成
 | 
			
		||||
    if (!props.bpmnModeler) {
 | 
			
		||||
      console.log('缺少props.bpmnModeler')
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      // 等待 modeler 初始化完成
 | 
			
		||||
      await nextTick()
 | 
			
		||||
      if (initBpmnInstances()) {
 | 
			
		||||
        isReady.value = true
 | 
			
		||||
        await nextTick()
 | 
			
		||||
        getActiveElement()
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error('modeler 实例未完全初始化')
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('初始化失败:', error)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
| 
						 | 
				
			
			@ -158,6 +186,8 @@ const unwatchBpmn = watch(
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
const getActiveElement = () => {
 | 
			
		||||
  if (!isReady.value || !props.bpmnModeler) return
 | 
			
		||||
 | 
			
		||||
  // 初始第一个选中元素 bpmn:Process
 | 
			
		||||
  initFormOnChanged(null)
 | 
			
		||||
  props.bpmnModeler.on('import.done', (e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -175,8 +205,11 @@ const getActiveElement = () => {
 | 
			
		|||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 初始化数据
 | 
			
		||||
const initFormOnChanged = (element) => {
 | 
			
		||||
  if (!isReady.value || !bpmnInstances()) return
 | 
			
		||||
 | 
			
		||||
  let activatedElement = element
 | 
			
		||||
  if (!activatedElement) {
 | 
			
		||||
    activatedElement =
 | 
			
		||||
| 
						 | 
				
			
			@ -184,32 +217,36 @@ const initFormOnChanged = (element) => {
 | 
			
		|||
      bpmnInstances().elementRegistry.find((el) => el.type === 'bpmn:Collaboration')
 | 
			
		||||
  }
 | 
			
		||||
  if (!activatedElement) return
 | 
			
		||||
  console.log(`
 | 
			
		||||
              ----------
 | 
			
		||||
      select element changed:
 | 
			
		||||
                id:  ${activatedElement.id}
 | 
			
		||||
              type:  ${activatedElement.businessObject.$type}
 | 
			
		||||
              ----------
 | 
			
		||||
              `)
 | 
			
		||||
  console.log('businessObject: ', activatedElement.businessObject)
 | 
			
		||||
  bpmnInstances().bpmnElement = activatedElement
 | 
			
		||||
  bpmnElement.value = activatedElement
 | 
			
		||||
  elementId.value = activatedElement.id
 | 
			
		||||
  elementType.value = activatedElement.type.split(':')[1] || ''
 | 
			
		||||
  elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
 | 
			
		||||
  conditionFormVisible.value = !!(
 | 
			
		||||
    elementType.value === 'SequenceFlow' &&
 | 
			
		||||
    activatedElement.source &&
 | 
			
		||||
    activatedElement.source.type.indexOf('StartEvent') === -1
 | 
			
		||||
  )
 | 
			
		||||
  formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    console.log(`
 | 
			
		||||
                ----------
 | 
			
		||||
        select element changed:
 | 
			
		||||
                  id:  ${activatedElement.id}
 | 
			
		||||
                type:  ${activatedElement.businessObject.$type}
 | 
			
		||||
                ----------
 | 
			
		||||
                `)
 | 
			
		||||
    console.log('businessObject: ', activatedElement.businessObject)
 | 
			
		||||
    bpmnInstances().bpmnElement = activatedElement
 | 
			
		||||
    bpmnElement.value = activatedElement
 | 
			
		||||
    elementId.value = activatedElement.id
 | 
			
		||||
    elementType.value = activatedElement.type.split(':')[1] || ''
 | 
			
		||||
    elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
 | 
			
		||||
    conditionFormVisible.value = !!(
 | 
			
		||||
      elementType.value === 'SequenceFlow' &&
 | 
			
		||||
      activatedElement.source &&
 | 
			
		||||
      activatedElement.source.type.indexOf('StartEvent') === -1
 | 
			
		||||
    )
 | 
			
		||||
    formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('初始化表单数据失败:', error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  const w = window as any
 | 
			
		||||
  w.bpmnInstances = null
 | 
			
		||||
  console.log(props, 'props1')
 | 
			
		||||
  console.log(props.bpmnModeler, 'props.bpmnModeler1')
 | 
			
		||||
  isReady.value = false
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -330,6 +330,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
 | 
			
		|||
          title: '查看 OA 请假',
 | 
			
		||||
          activeMenu: '/bpm/oa/leave'
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'manager/model/create-update',
 | 
			
		||||
        component: () => import('@/views/bpm/model/CreateUpdate.vue'),
 | 
			
		||||
        name: 'BpmModelCreateUpdate',
 | 
			
		||||
        meta: {
 | 
			
		||||
          noCache: true,
 | 
			
		||||
          hidden: true,
 | 
			
		||||
          canTo: true,
 | 
			
		||||
          title: '创建/修改流程',
 | 
			
		||||
          activeMenu: '/bpm/manager/model'
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -249,7 +249,7 @@ import { formatDate } from '@/utils/formatTime'
 | 
			
		|||
import * as ModelApi from '@/api/bpm/model'
 | 
			
		||||
import * as FormApi from '@/api/bpm/form'
 | 
			
		||||
import { setConfAndFields2 } from '@/utils/formCreate'
 | 
			
		||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
 | 
			
		||||
import { BpmModelFormType } from '@/utils/constants'
 | 
			
		||||
import { checkPermi } from '@/utils/permission'
 | 
			
		||||
import { useUserStoreWithOut } from '@/store/modules/user'
 | 
			
		||||
import { useAppStore } from '@/store/modules/app'
 | 
			
		||||
| 
						 | 
				
			
			@ -339,21 +339,22 @@ const handleChangeState = async (row: any) => {
 | 
			
		|||
 | 
			
		||||
/** 设计流程 */
 | 
			
		||||
const handleDesign = (row: any) => {
 | 
			
		||||
  if (row.type == BpmModelType.BPMN) {
 | 
			
		||||
    push({
 | 
			
		||||
      name: 'BpmModelEditor',
 | 
			
		||||
      query: {
 | 
			
		||||
        modelId: row.id
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  } else {
 | 
			
		||||
    push({
 | 
			
		||||
      name: 'SimpleModelDesign',
 | 
			
		||||
      query: {
 | 
			
		||||
        modelId: row.id
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  // if (row.type == BpmModelType.BPMN) {
 | 
			
		||||
  //   push({
 | 
			
		||||
  //     name: 'BpmModelEditor',
 | 
			
		||||
  //     query: {
 | 
			
		||||
  //       modelId: row.id
 | 
			
		||||
  //     }
 | 
			
		||||
  //   })
 | 
			
		||||
  // } else {
 | 
			
		||||
  //   push({
 | 
			
		||||
  //     name: 'SimpleModelDesign',
 | 
			
		||||
  //     query: {
 | 
			
		||||
  //       modelId: row.id
 | 
			
		||||
  //     }
 | 
			
		||||
  //   })
 | 
			
		||||
  // }
 | 
			
		||||
  push(`/bpm/manager/model/create-update?id=${row.id}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 发布流程 */
 | 
			
		||||
| 
						 | 
				
			
			@ -496,7 +497,11 @@ const handleDeleteCategory = async () => {
 | 
			
		|||
/** 添加流程模型弹窗 */
 | 
			
		||||
const modelFormRef = ref()
 | 
			
		||||
const openModelForm = (type: string, id?: number) => {
 | 
			
		||||
  modelFormRef.value.open(type, id)
 | 
			
		||||
  if (type === 'create') {
 | 
			
		||||
    push('/bpm/manager/model/create-update')
 | 
			
		||||
  } else {
 | 
			
		||||
    push(`/bpm/manager/model/create-update?id=${id}`)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,644 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <!-- 头部导航栏 -->
 | 
			
		||||
  <div
 | 
			
		||||
    class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- 左侧标题,固定宽度 -->
 | 
			
		||||
    <div class="w-200px flex items-center overflow-hidden">
 | 
			
		||||
      <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="router.back()" />
 | 
			
		||||
      <span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
 | 
			
		||||
        {{ formData.name || '创建流程' }}
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 步骤条,固定在中间 -->
 | 
			
		||||
    <div class="flex-1 flex items-center justify-center h-full">
 | 
			
		||||
      <div class="w-400px flex items-center justify-between h-full">
 | 
			
		||||
        <div
 | 
			
		||||
          v-for="(step, index) in steps"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          class="flex items-center cursor-pointer mx-15px relative h-full"
 | 
			
		||||
          :class="[
 | 
			
		||||
            currentStep === index
 | 
			
		||||
              ? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
 | 
			
		||||
              : 'text-gray-500'
 | 
			
		||||
          ]"
 | 
			
		||||
          @click="handleStepClick(index)"
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
 | 
			
		||||
            :class="[
 | 
			
		||||
              currentStep === index
 | 
			
		||||
                ? 'bg-[#3473ff] text-white border-[#3473ff]'
 | 
			
		||||
                : 'border-gray-300 bg-white text-gray-500'
 | 
			
		||||
            ]"
 | 
			
		||||
          >
 | 
			
		||||
            {{ index + 1 }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 右侧按钮,固定宽度 -->
 | 
			
		||||
    <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>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <!-- 主体内容 -->
 | 
			
		||||
  <ContentWrap class="mt-50px">
 | 
			
		||||
    <!-- 第一步:基本信息 -->
 | 
			
		||||
    <div v-show="currentStep === 0">
 | 
			
		||||
      <el-form
 | 
			
		||||
        ref="formRef"
 | 
			
		||||
        :model="formData"
 | 
			
		||||
        :rules="formRules"
 | 
			
		||||
        label-width="120px"
 | 
			
		||||
        class="mt-20px"
 | 
			
		||||
        :class="{ 'w-600px': currentStep === 0 }"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="流程标识" prop="key" class="mb-20px">
 | 
			
		||||
          <el-input v-model="formData.key" :disabled="!!formData.id" placeholder="请输入流标标识" />
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
            v-if="!formData.id"
 | 
			
		||||
            class="item"
 | 
			
		||||
            content="新建后,流程标识不可修改!"
 | 
			
		||||
            effect="light"
 | 
			
		||||
            placement="top"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon icon="ep:question" class="ml-5px" />
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
            v-else
 | 
			
		||||
            class="item"
 | 
			
		||||
            content="流程标识不可修改!"
 | 
			
		||||
            effect="light"
 | 
			
		||||
            placement="top"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon icon="ep:question" class="ml-5px" />
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="流程名称" prop="name" class="mb-20px">
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formData.name"
 | 
			
		||||
            :disabled="!!formData.id"
 | 
			
		||||
            clearable
 | 
			
		||||
            placeholder="请输入流程名称"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="流程分类" prop="category" class="mb-20px">
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formData.category"
 | 
			
		||||
            clearable
 | 
			
		||||
            placeholder="请选择流程分类"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
          >
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="category in categoryList"
 | 
			
		||||
              :key="category.code"
 | 
			
		||||
              :label="category.name"
 | 
			
		||||
              :value="category.code"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="流程图标" prop="icon" class="mb-20px">
 | 
			
		||||
          <UploadImg v-model="formData.icon" :limit="1" height="64px" width="64px" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="流程描述" prop="description" class="mb-20px">
 | 
			
		||||
          <el-input v-model="formData.description" clearable type="textarea" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="流程类型" prop="type" class="mb-20px">
 | 
			
		||||
          <el-radio-group v-model="formData.type">
 | 
			
		||||
            <el-radio
 | 
			
		||||
              v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
 | 
			
		||||
              :key="dict.value"
 | 
			
		||||
              :value="dict.value"
 | 
			
		||||
            >
 | 
			
		||||
              {{ dict.label }}
 | 
			
		||||
            </el-radio>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="是否可见" prop="visible" class="mb-20px">
 | 
			
		||||
          <el-radio-group v-model="formData.visible">
 | 
			
		||||
            <el-radio
 | 
			
		||||
              v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
 | 
			
		||||
              :key="dict.value"
 | 
			
		||||
              :value="dict.value"
 | 
			
		||||
            >
 | 
			
		||||
              {{ dict.label }}
 | 
			
		||||
            </el-radio>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="谁可以发起" prop="startUserType" class="mb-20px">
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formData.startUserType"
 | 
			
		||||
            placeholder="请选择谁可以发起"
 | 
			
		||||
            @change="handleStartUserTypeChange"
 | 
			
		||||
          >
 | 
			
		||||
            <el-option label="全员" :value="0" />
 | 
			
		||||
            <el-option label="指定人员" :value="1" />
 | 
			
		||||
            <el-option label="均不可提交" :value="2" />
 | 
			
		||||
          </el-select>
 | 
			
		||||
          <div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="user in selectedStartUsers"
 | 
			
		||||
              :key="user.id"
 | 
			
		||||
              class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
 | 
			
		||||
            >
 | 
			
		||||
              <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
 | 
			
		||||
              <el-avatar class="!m-5px" :size="28" v-else>
 | 
			
		||||
                {{ user.nickname.substring(0, 1) }}
 | 
			
		||||
              </el-avatar>
 | 
			
		||||
              {{ user.nickname }}
 | 
			
		||||
              <Icon
 | 
			
		||||
                icon="ep:close"
 | 
			
		||||
                class="ml-2 cursor-pointer hover:text-red-500"
 | 
			
		||||
                @click="handleRemoveStartUser(user)"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-button type="primary" link @click="openStartUserSelect">
 | 
			
		||||
              <Icon icon="ep:plus" />选择人员
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="流程管理员" prop="managerUserType" class="mb-20px">
 | 
			
		||||
          <el-select
 | 
			
		||||
            v-model="formData.managerUserType"
 | 
			
		||||
            placeholder="请选择流程管理员"
 | 
			
		||||
            @change="handleManagerUserTypeChange"
 | 
			
		||||
          >
 | 
			
		||||
            <el-option label="全员" :value="0" />
 | 
			
		||||
            <el-option label="指定人员" :value="1" />
 | 
			
		||||
            <el-option label="均不可提交" :value="2" />
 | 
			
		||||
          </el-select>
 | 
			
		||||
          <div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="user in selectedManagerUsers"
 | 
			
		||||
              :key="user.id"
 | 
			
		||||
              class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
 | 
			
		||||
            >
 | 
			
		||||
              <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
 | 
			
		||||
              <el-avatar class="!m-5px" :size="28" v-else>
 | 
			
		||||
                {{ user.nickname.substring(0, 1) }}
 | 
			
		||||
              </el-avatar>
 | 
			
		||||
              {{ user.nickname }}
 | 
			
		||||
              <Icon
 | 
			
		||||
                icon="ep:close"
 | 
			
		||||
                class="ml-2 cursor-pointer hover:text-red-500"
 | 
			
		||||
                @click="handleRemoveManagerUser(user)"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-button type="primary" link @click="openManagerUserSelect">
 | 
			
		||||
              <Icon icon="ep:plus" />选择人员
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div v-if="currentStep === 1">
 | 
			
		||||
      <!-- 第二步:表单设计 -->
 | 
			
		||||
      <el-form
 | 
			
		||||
        ref="formRef"
 | 
			
		||||
        :model="formData"
 | 
			
		||||
        :rules="formRules"
 | 
			
		||||
        label-width="120px"
 | 
			
		||||
        class="mt-20px"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="表单类型" prop="formType" class="mb-20px">
 | 
			
		||||
          <el-radio-group v-model="formData.formType">
 | 
			
		||||
            <el-radio
 | 
			
		||||
              v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
 | 
			
		||||
              :key="dict.value"
 | 
			
		||||
              :value="dict.value"
 | 
			
		||||
            >
 | 
			
		||||
              {{ dict.label }}
 | 
			
		||||
            </el-radio>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
 | 
			
		||||
          <el-select v-model="formData.formId" clearable style="width: 100%">
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="form in formList"
 | 
			
		||||
              :key="form.id"
 | 
			
		||||
              :label="form.name"
 | 
			
		||||
              :value="form.id"
 | 
			
		||||
            />
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="formData.formType === 20"
 | 
			
		||||
          label="表单提交路由"
 | 
			
		||||
          prop="formCustomCreatePath"
 | 
			
		||||
        >
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formData.formCustomCreatePath"
 | 
			
		||||
            placeholder="请输入表单提交路由"
 | 
			
		||||
            style="width: 330px"
 | 
			
		||||
          />
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
            class="item"
 | 
			
		||||
            content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
 | 
			
		||||
            effect="light"
 | 
			
		||||
            placement="top"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon icon="ep:question" class="ml-5px" />
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item
 | 
			
		||||
          v-if="formData.formType === 20"
 | 
			
		||||
          label="表单查看地址"
 | 
			
		||||
          prop="formCustomViewPath"
 | 
			
		||||
        >
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="formData.formCustomViewPath"
 | 
			
		||||
            placeholder="请输入表单查看的组件地址"
 | 
			
		||||
            style="width: 330px"
 | 
			
		||||
          />
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
            class="item"
 | 
			
		||||
            content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
 | 
			
		||||
            effect="light"
 | 
			
		||||
            placement="top"
 | 
			
		||||
          >
 | 
			
		||||
            <Icon icon="ep:question" class="ml-5px" />
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 第三步:流程设计 -->
 | 
			
		||||
    <div v-show="currentStep === 2">
 | 
			
		||||
      <!-- BPMN设计器 -->
 | 
			
		||||
      <template v-if="formData.type === BpmModelType.BPMN">
 | 
			
		||||
        <BpmModelEditor
 | 
			
		||||
          v-if="showDesigner"
 | 
			
		||||
          :model-id="formData.id"
 | 
			
		||||
          :model-key="formData.key"
 | 
			
		||||
          :model-name="formData.name"
 | 
			
		||||
          @success="handleDesignSuccess"
 | 
			
		||||
        />
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <!-- Simple设计器 -->
 | 
			
		||||
      <template v-else>
 | 
			
		||||
        <SimpleModelDesign
 | 
			
		||||
          v-if="showDesigner"
 | 
			
		||||
          :model-id="formData.id"
 | 
			
		||||
          :model-key="formData.key"
 | 
			
		||||
          :model-name="formData.name"
 | 
			
		||||
          @success="handleDesignSuccess"
 | 
			
		||||
        />
 | 
			
		||||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
 | 
			
		||||
  <!-- 用户选择弹窗 -->
 | 
			
		||||
  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import * as ModelApi from '@/api/bpm/model'
 | 
			
		||||
import * as FormApi from '@/api/bpm/form'
 | 
			
		||||
import { CategoryApi } from '@/api/bpm/category'
 | 
			
		||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import { BpmModelFormType, BpmModelType } from '@/utils/constants'
 | 
			
		||||
import * as UserApi from '@/api/system/user'
 | 
			
		||||
import { useUserStoreWithOut } from '@/store/modules/user'
 | 
			
		||||
import { UserVO } from '@/api/system/user'
 | 
			
		||||
import BpmModelEditor from './editor/index.vue'
 | 
			
		||||
import SimpleModelDesign from '../simple/SimpleModelDesign.vue'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
const userStore = useUserStoreWithOut()
 | 
			
		||||
 | 
			
		||||
// 步骤控制
 | 
			
		||||
const currentStep = ref(0)
 | 
			
		||||
// 表单数据
 | 
			
		||||
const formRef = ref()
 | 
			
		||||
const formData: any = ref({
 | 
			
		||||
  id: undefined,
 | 
			
		||||
  name: '',
 | 
			
		||||
  key: '',
 | 
			
		||||
  category: undefined,
 | 
			
		||||
  icon: undefined,
 | 
			
		||||
  description: '',
 | 
			
		||||
  type: BpmModelType.BPMN,
 | 
			
		||||
  formType: BpmModelFormType.NORMAL,
 | 
			
		||||
  formId: '',
 | 
			
		||||
  formCustomCreatePath: '',
 | 
			
		||||
  formCustomViewPath: '',
 | 
			
		||||
  visible: true,
 | 
			
		||||
  startUserType: undefined,
 | 
			
		||||
  managerUserType: undefined,
 | 
			
		||||
  startUserIds: [],
 | 
			
		||||
  managerUserIds: []
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 表单校验规则
 | 
			
		||||
const formRules = {
 | 
			
		||||
  name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
 | 
			
		||||
  key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
 | 
			
		||||
  category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
 | 
			
		||||
  icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
 | 
			
		||||
  type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
 | 
			
		||||
  formType: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
 | 
			
		||||
  formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
 | 
			
		||||
  formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
 | 
			
		||||
  formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }],
 | 
			
		||||
  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
 | 
			
		||||
  managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 流程设计器相关
 | 
			
		||||
const xmlString = ref(undefined)
 | 
			
		||||
 | 
			
		||||
// 数据列表
 | 
			
		||||
const formList: any = ref([])
 | 
			
		||||
const categoryList: any = ref([])
 | 
			
		||||
const userList = ref<UserVO[]>([])
 | 
			
		||||
const selectedStartUsers = ref<UserVO[]>([])
 | 
			
		||||
const selectedManagerUsers = ref<UserVO[]>([])
 | 
			
		||||
 | 
			
		||||
// 用户选择相关
 | 
			
		||||
const userSelectFormRef = ref()
 | 
			
		||||
const currentSelectType = ref<'start' | 'manager'>('start')
 | 
			
		||||
 | 
			
		||||
// 打开发起人选择
 | 
			
		||||
const openStartUserSelect = () => {
 | 
			
		||||
  currentSelectType.value = 'start'
 | 
			
		||||
  userSelectFormRef.value.open(0, selectedStartUsers.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 打开管理员选择
 | 
			
		||||
const openManagerUserSelect = () => {
 | 
			
		||||
  currentSelectType.value = 'manager'
 | 
			
		||||
  userSelectFormRef.value.open(0, selectedManagerUsers.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理用户选择确认
 | 
			
		||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
 | 
			
		||||
  if (currentSelectType.value === 'start') {
 | 
			
		||||
    selectedStartUsers.value = users
 | 
			
		||||
    formData.value.startUserIds = users.map((u) => u.id)
 | 
			
		||||
  } else {
 | 
			
		||||
    selectedManagerUsers.value = users
 | 
			
		||||
    formData.value.managerUserIds = users.map((u) => u.id)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理发起人类型变化
 | 
			
		||||
const handleStartUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedStartUsers.value = []
 | 
			
		||||
    formData.value.startUserIds = []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理管理员类型变化
 | 
			
		||||
const handleManagerUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedManagerUsers.value = []
 | 
			
		||||
    formData.value.managerUserIds = []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除发起人
 | 
			
		||||
const handleRemoveStartUser = (user: UserVO) => {
 | 
			
		||||
  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
 | 
			
		||||
  formData.value.startUserIds = formData.value.startUserIds.filter((id) => id !== user.id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除管理员
 | 
			
		||||
const handleRemoveManagerUser = (user: UserVO) => {
 | 
			
		||||
  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
 | 
			
		||||
  formData.value.managerUserIds = formData.value.managerUserIds.filter((id) => id !== user.id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存操作
 | 
			
		||||
const handleSave = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    if (formData.value.id) {
 | 
			
		||||
      await ModelApi.updateModel(formData.value)
 | 
			
		||||
      message.success('修改成功')
 | 
			
		||||
    } else {
 | 
			
		||||
      const result = await ModelApi.createModel(formData.value)
 | 
			
		||||
      formData.value.id = result.id // 保存id用于设计器
 | 
			
		||||
      message.success('新增成功')
 | 
			
		||||
    }
 | 
			
		||||
    // 不再跳转,继续停留在当前页面
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('保存失败:', error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 发布操作
 | 
			
		||||
const handleDeploy = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    await message.confirm('是否确认发布该流程?')
 | 
			
		||||
    // 发布时才进行全部校验
 | 
			
		||||
    await validateAllSteps()
 | 
			
		||||
    await handleSave()
 | 
			
		||||
    await ModelApi.deployModel(formData.value.id)
 | 
			
		||||
    message.success('发布成功')
 | 
			
		||||
    router.push({ path: '/bpm/manager/model' })
 | 
			
		||||
  } catch (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('请完善必填信息')
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 初始化数据
 | 
			
		||||
const initData = async () => {
 | 
			
		||||
  const modelId = route.query.id as unknown as string
 | 
			
		||||
  if (modelId) {
 | 
			
		||||
    // 修改场景
 | 
			
		||||
    formData.value = await ModelApi.getModel(modelId)
 | 
			
		||||
    // 加载数据时,根据已有的用户ID列表初始化已选用户
 | 
			
		||||
    if (formData.value.startUserIds?.length) {
 | 
			
		||||
      formData.value.startUserType = 1
 | 
			
		||||
      selectedStartUsers.value = userList.value.filter((user) =>
 | 
			
		||||
        formData.value.startUserIds.includes(user.id)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    if (formData.value.managerUserIds?.length) {
 | 
			
		||||
      formData.value.managerUserType = 1
 | 
			
		||||
      selectedManagerUsers.value = userList.value.filter((user) =>
 | 
			
		||||
        formData.value.managerUserIds.includes(user.id)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // 新增场景
 | 
			
		||||
    formData.value.managerUserIds.push(userStore.getUser.id)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 获取表单列表
 | 
			
		||||
  formList.value = await FormApi.getFormSimpleList()
 | 
			
		||||
  // 获取分类列表
 | 
			
		||||
  categoryList.value = await CategoryApi.getCategorySimpleList()
 | 
			
		||||
  // 获取用户列表
 | 
			
		||||
  userList.value = await UserApi.getSimpleUserList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  await initData()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 第一步校验
 | 
			
		||||
const validateStep1 = async () => {
 | 
			
		||||
  await formRef.value?.validate(['name', 'key', 'category', 'icon', 'type', 'visible'])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 第二步校验
 | 
			
		||||
const validateStep2 = async () => {
 | 
			
		||||
  await formRef.value?.validate([
 | 
			
		||||
    'formType',
 | 
			
		||||
    'formId',
 | 
			
		||||
    'formCustomCreatePath',
 | 
			
		||||
    'formCustomViewPath'
 | 
			
		||||
  ])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 第三步校验
 | 
			
		||||
const validateStep3 = async () => {
 | 
			
		||||
  if (!xmlString.value) {
 | 
			
		||||
    throw new Error('请设计流程')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const validateAllSteps = async () => {
 | 
			
		||||
  for (const step of steps) {
 | 
			
		||||
    if (step.validator) {
 | 
			
		||||
      await step.validator()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const steps = [
 | 
			
		||||
  { title: '基本信息', validator: validateStep1 },
 | 
			
		||||
  { title: '表单设计', validator: validateStep2 },
 | 
			
		||||
  { title: '流程设计', validator: validateStep3 }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
// 处理设计器保存成功
 | 
			
		||||
const handleDesignSuccess = (bpmnXml?: string) => {
 | 
			
		||||
  if (bpmnXml) {
 | 
			
		||||
    // 新建时,保存设计器生成的XML
 | 
			
		||||
    formData.value.bpmnXml = bpmnXml
 | 
			
		||||
  }
 | 
			
		||||
  message.success('保存成功')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 步骤切换处理
 | 
			
		||||
const handleStepClick = async (index: number) => {
 | 
			
		||||
  // 如果是切换到第三步(流程设计),需要校验key和name
 | 
			
		||||
  if (index === 2) {
 | 
			
		||||
    if (!formData.value.key || !formData.value.name) {
 | 
			
		||||
      message.warning('请先填写流程标识和流程名称')
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  currentStep.value = index
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 添加一个计算属性来判断是否显示设计器
 | 
			
		||||
const showDesigner = computed(() => {
 | 
			
		||||
  return (
 | 
			
		||||
    currentStep.value === 2 &&
 | 
			
		||||
    Boolean(formData.value.id || (formData.value.key && formData.value.name))
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 监听步骤变化,确保数据准备完成
 | 
			
		||||
watch(
 | 
			
		||||
  () => currentStep.value,
 | 
			
		||||
  async (newStep) => {
 | 
			
		||||
    if (newStep === 2) {
 | 
			
		||||
      await nextTick()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: false }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 在组件卸载时清理
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  const w = window as any
 | 
			
		||||
  if (w.bpmnInstances) {
 | 
			
		||||
    w.bpmnInstances = null
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.process-panel {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bg-gray-100 {
 | 
			
		||||
  background-color: #f5f7fa;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: #e6e8eb;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .ep-close {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    color: #909399;
 | 
			
		||||
    transition: color 0.3s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: #f56c6c;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.border-bottom {
 | 
			
		||||
  border-bottom: 1px solid #dcdfe6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-primary {
 | 
			
		||||
  color: #3473ff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bg-primary {
 | 
			
		||||
  background-color: #3473ff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.border-primary {
 | 
			
		||||
  border-color: #3473ff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-16px {
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-2px {
 | 
			
		||||
  height: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx-15px {
 | 
			
		||||
  margin-left: 15px;
 | 
			
		||||
  margin-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -123,29 +123,69 @@
 | 
			
		|||
          </el-radio>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="谁可以发起" prop="startUserIds">
 | 
			
		||||
      <el-form-item label="谁可以发起" prop="startUserType">
 | 
			
		||||
        <el-select
 | 
			
		||||
          v-model="formData.startUserIds"
 | 
			
		||||
          multiple
 | 
			
		||||
          placeholder="请选择可发起人,默认(不选择)则所有人都可以发起"
 | 
			
		||||
          v-model="formData.startUserType"
 | 
			
		||||
          placeholder="请选择谁可以发起"
 | 
			
		||||
          @change="handleStartUserTypeChange"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="user in userList"
 | 
			
		||||
            :key="user.id"
 | 
			
		||||
            :label="user.nickname"
 | 
			
		||||
            :value="user.id"
 | 
			
		||||
          />
 | 
			
		||||
          <el-option label="全员" :value="0" />
 | 
			
		||||
          <el-option label="指定人员" :value="1" />
 | 
			
		||||
          <el-option label="均不可提交" :value="2" />
 | 
			
		||||
        </el-select>
 | 
			
		||||
        <div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
 | 
			
		||||
          <div
 | 
			
		||||
            v-for="user in selectedStartUsers"
 | 
			
		||||
            :key="user.id"
 | 
			
		||||
            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
 | 
			
		||||
          >
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-else>
 | 
			
		||||
              {{ user.nickname.substring(0, 1) }}
 | 
			
		||||
            </el-avatar>
 | 
			
		||||
            {{ user.nickname }}
 | 
			
		||||
            <Icon
 | 
			
		||||
              icon="ep:close"
 | 
			
		||||
              class="ml-2 cursor-pointer hover:text-red-500"
 | 
			
		||||
              @click="handleRemoveStartUser(user)"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-button type="primary" link @click="openStartUserSelect">
 | 
			
		||||
            <Icon icon="ep:plus" />选择人员
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="流程管理员" prop="managerUserIds">
 | 
			
		||||
        <el-select v-model="formData.managerUserIds" multiple placeholder="请选择流程管理员">
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="user in userList"
 | 
			
		||||
            :key="user.id"
 | 
			
		||||
            :label="user.nickname"
 | 
			
		||||
            :value="user.id"
 | 
			
		||||
          />
 | 
			
		||||
      <el-form-item label="流程管理员" prop="managerUserType">
 | 
			
		||||
        <el-select
 | 
			
		||||
          v-model="formData.managerUserType"
 | 
			
		||||
          placeholder="请选择流程管理员"
 | 
			
		||||
          @change="handleManagerUserTypeChange"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option label="全员" :value="0" />
 | 
			
		||||
          <el-option label="指定人员" :value="1" />
 | 
			
		||||
          <el-option label="均不可提交" :value="2" />
 | 
			
		||||
        </el-select>
 | 
			
		||||
        <div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
 | 
			
		||||
          <div
 | 
			
		||||
            v-for="user in selectedManagerUsers"
 | 
			
		||||
            :key="user.id"
 | 
			
		||||
            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
 | 
			
		||||
          >
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
 | 
			
		||||
            <el-avatar class="!m-5px" :size="28" v-else>
 | 
			
		||||
              {{ user.nickname.substring(0, 1) }}
 | 
			
		||||
            </el-avatar>
 | 
			
		||||
            {{ user.nickname }}
 | 
			
		||||
            <Icon
 | 
			
		||||
              icon="ep:close"
 | 
			
		||||
              class="ml-2 cursor-pointer hover:text-red-500"
 | 
			
		||||
              @click="handleRemoveManagerUser(user)"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-button type="primary" link @click="openManagerUserSelect">
 | 
			
		||||
            <Icon icon="ep:plus" />选择人员
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
| 
						 | 
				
			
			@ -153,6 +193,7 @@
 | 
			
		|||
      <el-button @click="dialogVisible = false">取 消</el-button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </Dialog>
 | 
			
		||||
  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +219,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
 | 
			
		|||
const dialogTitle = ref('') // 弹窗的标题
 | 
			
		||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 | 
			
		||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
 | 
			
		||||
const formData = ref({
 | 
			
		||||
const formData: any = ref({
 | 
			
		||||
  id: undefined,
 | 
			
		||||
  name: '',
 | 
			
		||||
  key: '',
 | 
			
		||||
| 
						 | 
				
			
			@ -191,6 +232,8 @@ const formData = ref({
 | 
			
		|||
  formCustomCreatePath: '',
 | 
			
		||||
  formCustomViewPath: '',
 | 
			
		||||
  visible: true,
 | 
			
		||||
  startUserType: undefined,
 | 
			
		||||
  managerUserType: undefined,
 | 
			
		||||
  startUserIds: [],
 | 
			
		||||
  managerUserIds: []
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -211,6 +254,10 @@ const formRef = ref() // 表单 Ref
 | 
			
		|||
const formList = ref([]) // 流程表单的下拉框的数据
 | 
			
		||||
const categoryList = ref([]) // 流程分类列表
 | 
			
		||||
const userList = ref<UserVO[]>([]) // 用户列表
 | 
			
		||||
const selectedStartUsers = ref<UserVO[]>([]) // 已选择的发起人列表
 | 
			
		||||
const selectedManagerUsers = ref<UserVO[]>([]) // 已选择的管理员列表
 | 
			
		||||
const userSelectFormRef = ref() // 用户选择弹窗ref
 | 
			
		||||
const currentSelectType = ref<'start' | 'manager'>('start') // 当前选择的是发起人还是管理员
 | 
			
		||||
 | 
			
		||||
/** 打开弹窗 */
 | 
			
		||||
const open = async (type: string, id?: string) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +273,19 @@ const open = async (type: string, id?: string) => {
 | 
			
		|||
    } finally {
 | 
			
		||||
      formLoading.value = false
 | 
			
		||||
    }
 | 
			
		||||
    // 加载数据时,根据已有的用户ID列表初始化已选用户
 | 
			
		||||
    if (formData.value.startUserIds?.length) {
 | 
			
		||||
      formData.value.startUserType = 1
 | 
			
		||||
      selectedStartUsers.value = userList.value.filter((user) =>
 | 
			
		||||
        formData.value.startUserIds.includes(user.id)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    if (formData.value.managerUserIds?.length) {
 | 
			
		||||
      formData.value.managerUserType = 1
 | 
			
		||||
      selectedManagerUsers.value = userList.value.filter((user) =>
 | 
			
		||||
        formData.value.managerUserIds.includes(user.id)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    formData.value.managerUserIds.push(userStore.getUser.id)
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -293,9 +353,85 @@ const resetForm = () => {
 | 
			
		|||
    formCustomCreatePath: '',
 | 
			
		||||
    formCustomViewPath: '',
 | 
			
		||||
    visible: true,
 | 
			
		||||
    startUserType: undefined,
 | 
			
		||||
    managerUserType: undefined,
 | 
			
		||||
    startUserIds: [],
 | 
			
		||||
    managerUserIds: []
 | 
			
		||||
  }
 | 
			
		||||
  formRef.value?.resetFields()
 | 
			
		||||
  selectedStartUsers.value = []
 | 
			
		||||
  selectedManagerUsers.value = []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理发起人类型变化
 | 
			
		||||
const handleStartUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedStartUsers.value = []
 | 
			
		||||
    formData.value.startUserIds = []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理管理员类型变化
 | 
			
		||||
const handleManagerUserTypeChange = (value: number) => {
 | 
			
		||||
  if (value !== 1) {
 | 
			
		||||
    selectedManagerUsers.value = []
 | 
			
		||||
    formData.value.managerUserIds = []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 打开发起人选择
 | 
			
		||||
const openStartUserSelect = () => {
 | 
			
		||||
  currentSelectType.value = 'start'
 | 
			
		||||
  userSelectFormRef.value.open(0, selectedStartUsers.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 打开管理员选择
 | 
			
		||||
const openManagerUserSelect = () => {
 | 
			
		||||
  currentSelectType.value = 'manager'
 | 
			
		||||
  userSelectFormRef.value.open(0, selectedManagerUsers.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理用户选择确认
 | 
			
		||||
const handleUserSelectConfirm = (_, users: UserVO[]) => {
 | 
			
		||||
  if (currentSelectType.value === 'start') {
 | 
			
		||||
    selectedStartUsers.value = users
 | 
			
		||||
    formData.value.startUserIds = users.map((u) => u.id)
 | 
			
		||||
  } else {
 | 
			
		||||
    selectedManagerUsers.value = users
 | 
			
		||||
    formData.value.managerUserIds = users.map((u) => u.id)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除发起人
 | 
			
		||||
const handleRemoveStartUser = (user: UserVO) => {
 | 
			
		||||
  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
 | 
			
		||||
  formData.value.startUserIds = formData.value.startUserIds.filter((id) => id !== user.id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除管理员
 | 
			
		||||
const handleRemoveManagerUser = (user: UserVO) => {
 | 
			
		||||
  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
 | 
			
		||||
  formData.value.managerUserIds = formData.value.managerUserIds.filter((id) => id !== user.id)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.bg-gray-100 {
 | 
			
		||||
  background-color: #f5f7fa;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background-color: #e6e8eb;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .ep-close {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    color: #909399;
 | 
			
		||||
    transition: color 0.3s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: #f56c6c;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@
 | 
			
		|||
    <!-- 流程设计器,负责绘制流程等 -->
 | 
			
		||||
    <MyProcessDesigner
 | 
			
		||||
      key="designer"
 | 
			
		||||
      v-if="xmlString !== undefined"
 | 
			
		||||
      v-model="xmlString"
 | 
			
		||||
      :value="xmlString"
 | 
			
		||||
      v-bind="controlForm"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,12 +10,14 @@
 | 
			
		|||
      ref="processDesigner"
 | 
			
		||||
      @init-finished="initModeler"
 | 
			
		||||
      :additionalModel="controlForm.additionalModel"
 | 
			
		||||
      :model="model"
 | 
			
		||||
      @save="save"
 | 
			
		||||
    />
 | 
			
		||||
    <!-- 流程属性器,负责编辑每个流程节点的属性 -->
 | 
			
		||||
    <MyProcessPenal
 | 
			
		||||
      v-if="isModelerReady && modeler"
 | 
			
		||||
      key="penal"
 | 
			
		||||
      :bpmnModeler="modeler as any"
 | 
			
		||||
      :bpmnModeler="modeler"
 | 
			
		||||
      :prefix="controlForm.prefix"
 | 
			
		||||
      class="process-panel"
 | 
			
		||||
      :model="model"
 | 
			
		||||
| 
						 | 
				
			
			@ -35,8 +36,13 @@ import { getForm, FormVO } from '@/api/bpm/form'
 | 
			
		|||
 | 
			
		||||
defineOptions({ name: 'BpmModelEditor' })
 | 
			
		||||
 | 
			
		||||
const router = useRouter() // 路由
 | 
			
		||||
const { query } = useRoute() // 路由的查询
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelId?: string
 | 
			
		||||
  modelKey?: string
 | 
			
		||||
  modelName?: string
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['success'])
 | 
			
		||||
const message = useMessage() // 国际化
 | 
			
		||||
 | 
			
		||||
// 表单信息
 | 
			
		||||
| 
						 | 
				
			
			@ -45,8 +51,10 @@ const formType = ref(20)
 | 
			
		|||
provide('formFields', formFields)
 | 
			
		||||
provide('formType', formType)
 | 
			
		||||
 | 
			
		||||
const xmlString = ref(undefined) // BPMN XML
 | 
			
		||||
const modeler = ref(null) // BPMN Modeler
 | 
			
		||||
const xmlString = ref<string>('') // BPMN XML
 | 
			
		||||
const modeler = shallowRef() // BPMN Modeler
 | 
			
		||||
const processDesigner = ref()
 | 
			
		||||
const isModelerReady = ref(false)
 | 
			
		||||
const controlForm = ref({
 | 
			
		||||
  simulation: true,
 | 
			
		||||
  labelEditing: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -57,67 +65,142 @@ const controlForm = ref({
 | 
			
		|||
})
 | 
			
		||||
const model = ref<ModelApi.ModelVO>() // 流程模型的信息
 | 
			
		||||
 | 
			
		||||
// 初始化 bpmnInstances
 | 
			
		||||
const initBpmnInstances = () => {
 | 
			
		||||
  if (!modeler.value) return false
 | 
			
		||||
  try {
 | 
			
		||||
    const instances = {
 | 
			
		||||
      modeler: modeler.value,
 | 
			
		||||
      modeling: modeler.value.get('modeling'),
 | 
			
		||||
      moddle: modeler.value.get('moddle'),
 | 
			
		||||
      eventBus: modeler.value.get('eventBus'),
 | 
			
		||||
      bpmnFactory: modeler.value.get('bpmnFactory'),
 | 
			
		||||
      elementFactory: modeler.value.get('elementFactory'),
 | 
			
		||||
      elementRegistry: modeler.value.get('elementRegistry'),
 | 
			
		||||
      replace: modeler.value.get('replace'),
 | 
			
		||||
      selection: modeler.value.get('selection')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 检查所有实例是否都存在
 | 
			
		||||
    return Object.values(instances).every(instance => instance)
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('初始化 bpmnInstances 失败:', error)
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 初始化 modeler */
 | 
			
		||||
const initModeler = (item) => {
 | 
			
		||||
  setTimeout(() => {
 | 
			
		||||
const initModeler = async (item) => {
 | 
			
		||||
  try {
 | 
			
		||||
    modeler.value = item
 | 
			
		||||
  }, 10)
 | 
			
		||||
    // 等待 modeler 初始化完成
 | 
			
		||||
    await nextTick()
 | 
			
		||||
 | 
			
		||||
    // 确保 modeler 的所有实例都已经准备好
 | 
			
		||||
    if (initBpmnInstances()) {
 | 
			
		||||
      isModelerReady.value = true
 | 
			
		||||
      if (!props.modelId && props.modelKey && props.modelName) {
 | 
			
		||||
        await updateModelData(props.modelKey, props.modelName)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      console.error('modeler 实例未完全初始化')
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('初始化 modeler 失败:', error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取默认的BPMN XML */
 | 
			
		||||
const getDefaultBpmnXml = (key: string, name: string) => {
 | 
			
		||||
  return `<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<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">
 | 
			
		||||
  <process id="${key}" name="${name}" isExecutable="true" />
 | 
			
		||||
  <bpmndi:BPMNDiagram id="BPMNDiagram">
 | 
			
		||||
    <bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
 | 
			
		||||
  </bpmndi:BPMNDiagram>
 | 
			
		||||
</definitions>`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 添加/修改模型 */
 | 
			
		||||
const save = async (bpmnXml: string) => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    ...model.value,
 | 
			
		||||
    bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
 | 
			
		||||
  } as unknown as ModelApi.ModelVO
 | 
			
		||||
  // 提交
 | 
			
		||||
  if (data.id) {
 | 
			
		||||
    await ModelApi.updateModelBpmn(data)
 | 
			
		||||
    message.success('修改成功')
 | 
			
		||||
  } else {
 | 
			
		||||
    await ModelApi.updateModelBpmn(data)
 | 
			
		||||
    message.success('新增成功')
 | 
			
		||||
  try {
 | 
			
		||||
    if (props.modelId) {
 | 
			
		||||
      // 编辑模式
 | 
			
		||||
      const data = {
 | 
			
		||||
        ...model.value,
 | 
			
		||||
        bpmnXml: bpmnXml
 | 
			
		||||
      } as unknown as ModelApi.ModelVO
 | 
			
		||||
      await ModelApi.updateModelBpmn(data)
 | 
			
		||||
      emit('success')
 | 
			
		||||
    } else {
 | 
			
		||||
      // 新建模式,直接返回XML
 | 
			
		||||
      emit('success', bpmnXml)
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('保存失败:', error)
 | 
			
		||||
    message.error('保存失败')
 | 
			
		||||
  }
 | 
			
		||||
  // 跳转回去
 | 
			
		||||
  close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 关闭按钮 */
 | 
			
		||||
const close = () => {
 | 
			
		||||
  router.push({ path: '/bpm/manager/model' })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 初始化 */
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  const modelId = query.modelId as unknown as number
 | 
			
		||||
  if (!modelId) {
 | 
			
		||||
    message.error('缺少模型 modelId 编号')
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  // 查询模型
 | 
			
		||||
  const data = await ModelApi.getModel(modelId)
 | 
			
		||||
  if (!data.bpmnXml) {
 | 
			
		||||
    // 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的
 | 
			
		||||
    data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<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">
 | 
			
		||||
  <process id="${data.key}" name="${data.name}" isExecutable="true" />
 | 
			
		||||
  <bpmndi:BPMNDiagram id="BPMNDiagram">
 | 
			
		||||
    <bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
 | 
			
		||||
  </bpmndi:BPMNDiagram>
 | 
			
		||||
</definitions>`
 | 
			
		||||
  try {
 | 
			
		||||
    if (props.modelId) {
 | 
			
		||||
      // 编辑模式
 | 
			
		||||
      // 查询模型
 | 
			
		||||
      const data = await ModelApi.getModel(props.modelId)
 | 
			
		||||
      model.value = {
 | 
			
		||||
        ...data,
 | 
			
		||||
        bpmnXml: undefined // 清空 bpmnXml 属性
 | 
			
		||||
      }
 | 
			
		||||
      xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
 | 
			
		||||
    } else if (props.modelKey && props.modelName) {
 | 
			
		||||
      // 新建模式
 | 
			
		||||
      xmlString.value = getDefaultBpmnXml(props.modelKey, props.modelName)
 | 
			
		||||
      model.value = {
 | 
			
		||||
        key: props.modelKey,
 | 
			
		||||
        name: props.modelName
 | 
			
		||||
      } as ModelApi.ModelVO
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('初始化失败:', error)
 | 
			
		||||
    message.error('初始化失败')
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
  formType.value = data.formType
 | 
			
		||||
  if (data.formType === 10) {
 | 
			
		||||
    const bpmnForm = (await getForm(data.formId)) as unknown as FormVO
 | 
			
		||||
    formFields.value = bpmnForm?.fields
 | 
			
		||||
// 更新模型数据
 | 
			
		||||
const updateModelData = async (key?: string, name?: string) => {
 | 
			
		||||
  if (key && name) {
 | 
			
		||||
    xmlString.value = getDefaultBpmnXml(key, name)
 | 
			
		||||
    model.value = {
 | 
			
		||||
      ...model.value,
 | 
			
		||||
      key: key,
 | 
			
		||||
      name: name
 | 
			
		||||
    } as ModelApi.ModelVO
 | 
			
		||||
    // 确保更新后重新渲染
 | 
			
		||||
    await nextTick()
 | 
			
		||||
    if (processDesigner.value?.refresh) {
 | 
			
		||||
      processDesigner.value.refresh()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  model.value = {
 | 
			
		||||
    ...data,
 | 
			
		||||
    bpmnXml: undefined // 清空 bpmnXml 属性
 | 
			
		||||
// 监听key和name的变化
 | 
			
		||||
watch([() => props.modelKey, () => props.modelName], async ([newKey, newName]) => {
 | 
			
		||||
  if (!props.modelId && newKey && newName && modeler.value) {
 | 
			
		||||
    await updateModelData(newKey, newName)
 | 
			
		||||
  }
 | 
			
		||||
}, { immediate: true, deep: true })
 | 
			
		||||
 | 
			
		||||
// 在组件卸载时清理
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  isModelerReady.value = false
 | 
			
		||||
  modeler.value = null
 | 
			
		||||
  // 清理全局实例
 | 
			
		||||
  const w = window as any
 | 
			
		||||
  if (w.bpmnInstances) {
 | 
			
		||||
    w.bpmnInstances = null
 | 
			
		||||
  }
 | 
			
		||||
  xmlString.value = data.bpmnXml
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,6 +106,7 @@ import CategoryDraggableModel from './CategoryDraggableModel.vue'
 | 
			
		|||
 | 
			
		||||
defineOptions({ name: 'BpmModel' })
 | 
			
		||||
 | 
			
		||||
const { push } = useRouter()
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const loading = ref(true) // 列表的加载中
 | 
			
		||||
const isCategorySorting = ref(false) // 是否 category 正处于排序状态
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +125,11 @@ const handleQuery = () => {
 | 
			
		|||
/** 添加/修改操作 */
 | 
			
		||||
const formRef = ref()
 | 
			
		||||
const openForm = (type: string, id?: number) => {
 | 
			
		||||
  formRef.value.open(type, id)
 | 
			
		||||
  if (type === 'create') {
 | 
			
		||||
    push('/bpm/manager/model/create-update')
 | 
			
		||||
  } else {
 | 
			
		||||
    push(`/bpm/manager/model/create-update?id=${id}`)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 流程表单的详情按钮操作 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,12 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <ContentWrap :bodyStyle="{ padding: '20px 16px' }">
 | 
			
		||||
    <SimpleProcessDesigner :model-id="modelId" @success="close" />
 | 
			
		||||
    <SimpleProcessDesigner 
 | 
			
		||||
      :model-id="modelId" 
 | 
			
		||||
      :model-key="modelKey"
 | 
			
		||||
      :model-name="modelName"
 | 
			
		||||
      @success="handleSuccess" 
 | 
			
		||||
      ref="designerRef"
 | 
			
		||||
    />
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
| 
						 | 
				
			
			@ -9,11 +15,26 @@ import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/
 | 
			
		|||
defineOptions({
 | 
			
		||||
  name: 'SimpleModelDesign'
 | 
			
		||||
})
 | 
			
		||||
const router = useRouter() // 路由
 | 
			
		||||
const { query } = useRoute() // 路由的查询
 | 
			
		||||
const modelId = query.modelId as string
 | 
			
		||||
const close = () => {
 | 
			
		||||
  router.push({ path: '/bpm/manager/model' })
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelId?: string
 | 
			
		||||
  modelKey?: string
 | 
			
		||||
  modelName?: string
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['success'])
 | 
			
		||||
const designerRef = ref()
 | 
			
		||||
 | 
			
		||||
// 监听属性变化
 | 
			
		||||
watch([() => props.modelKey, () => props.modelName], ([newKey, newName]) => {
 | 
			
		||||
  if (designerRef.value && newKey && newName) {
 | 
			
		||||
    designerRef.value.updateModel(newKey, newName)
 | 
			
		||||
  }
 | 
			
		||||
}, { immediate: true, deep: true })
 | 
			
		||||
 | 
			
		||||
// 修改成功回调
 | 
			
		||||
const handleSuccess = (data?: any) => {
 | 
			
		||||
  emit('success', data)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue