浏览代码

Merge branch 'feature/bpm' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

jason 6 月之前
父节点
当前提交
e3db7d3014

+ 5 - 4
src/components/SimpleProcessDesignerV2/src/node.ts

@@ -16,7 +16,8 @@ import {
   FieldPermissionType,
   ListenerParam
 } from './consts'
-import { parseFormFields } from '@/components/FormCreate/src/utils/index'
+import { parseFormFields } from '@/components/FormCreate/src/utils'
+
 export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
   const node = ref<SimpleFlowNode>(props.flowNode)
   watch(
@@ -46,7 +47,7 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
   // 字段权限配置. 需要有 field, title,  permissioin 属性
   const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
 
-  const formType = inject<Ref<number|undefined>>('formType', ref()) // 表单类型
+  const formType = inject<Ref<number | undefined>>('formType', ref()) // 表单类型
 
   const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
 
@@ -108,7 +109,7 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
  * @description 获取表单的字段
  */
 export function useFormFields() {
-  const formFields = inject<Ref<string[]>>('formFields',ref([])) // 流程表单字段
+  const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
   return parseFormCreateFields(unref(formFields))
 }
 
@@ -178,7 +179,7 @@ export function useNodeForm(nodeType: NodeType) {
   const postOptions = inject<Ref<PostApi.PostVO[]>>('postList', ref([])) // 岗位列表
   const userOptions = inject<Ref<UserApi.UserVO[]>>('userList', ref([])) // 用户列表
   const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList', ref([])) // 部门列表
-  const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList',ref([])) // 用户组列表
+  const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList', ref([])) // 用户组列表
   const deptTreeOptions = inject('deptTree', ref()) // 部门树
   const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
   const configForm = ref<UserTaskFormType | CopyTaskFormType>()

+ 0 - 1
src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue

@@ -9,7 +9,6 @@
         ]"
       >
         <div class="node-title-container">
-          <!-- TODO @芋艿 需要更换图标 -->
           <div class="node-title-icon delay-node"><span class="iconfont icon-delay"></span></div>
           <input
             v-if="!readonly && showInput"

+ 21 - 12
src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue

@@ -1,6 +1,10 @@
 <template>
   <div class="panel-tab__content">
-    <el-radio-group v-model="approveMethod" @change="onApproveMethodChange">
+    <el-radio-group
+      v-if="type === 'UserTask'"
+      v-model="approveMethod"
+      @change="onApproveMethodChange"
+    >
       <div class="flex-col">
         <div v-for="(item, index) in APPROVE_METHODS" :key="index">
           <el-radio :value="item.value" :label="item.value">
@@ -23,6 +27,9 @@
         </div>
       </div>
     </el-radio-group>
+    <div v-else>
+      除了UserTask以外节点的多实例待实现
+    </div>
     <!-- 与Simple设计器配置合并,保留以前的代码 -->
     <el-form label-width="90px" style="display: none">
       <el-form-item label="快捷配置">
@@ -301,19 +308,21 @@ const approveMethod = ref()
 const approveRatio = ref(100)
 const otherExtensions = ref()
 const getElementLoopNew = () => {
-  const extensionElements =
-    bpmnElement.value.businessObject?.extensionElements ??
-    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
-  approveMethod.value = extensionElements.values.filter(
-    (ex) => ex.$type === `${prefix}:ApproveMethod`
-  )?.[0]?.value
+  if (props.type === 'UserTask') {
+    const extensionElements =
+      bpmnElement.value.businessObject?.extensionElements ??
+      bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+    approveMethod.value = extensionElements.values.filter(
+      (ex) => ex.$type === `${prefix}:ApproveMethod`
+    )?.[0]?.value
 
-  otherExtensions.value =
-    extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
+    otherExtensions.value =
+      extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
 
-  if (!approveMethod.value) {
-    approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
-    updateLoopCharacteristics()
+    if (!approveMethod.value) {
+      approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
+      updateLoopCharacteristics()
+    }
   }
 }
 const onApproveMethodChange = () => {

+ 95 - 42
src/views/bpm/model/CategoryDraggableModel.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="flex items-center h-50px">
+  <div class="flex items-center h-50px" v-memo="[categoryInfo.name, isCategorySorting]">
     <!-- 头部:分类名 -->
     <div class="flex items-center">
       <el-tooltip content="拖动排序" v-if="isCategorySorting">
@@ -13,7 +13,7 @@
       <div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
     </div>
     <!-- 头部:操作 -->
-    <div class="flex-1 flex" v-if="!isCategorySorting">
+    <div class="flex-1 flex" v-show="!isCategorySorting">
       <div
         v-if="categoryInfo.modelList.length > 0"
         class="ml-20px flex items-center"
@@ -69,16 +69,17 @@
   <el-collapse-transition>
     <div v-show="isExpand">
       <el-table
+        v-if="modelList && modelList.length > 0"
         :class="categoryInfo.name"
         ref="tableRef"
-        :header-cell-style="{ backgroundColor: isDark ? '' : '#edeff0', paddingLeft: '10px' }"
-        :cell-style="{ paddingLeft: '10px' }"
-        :row-style="{ height: '68px' }"
         :data="modelList"
         row-key="id"
+        :header-cell-style="tableHeaderStyle"
+        :cell-style="tableCellStyle"
+        :row-style="{ height: '68px' }"
       >
         <el-table-column label="流程名" prop="name" min-width="150">
-          <template #default="scope">
+          <template #default="{ row }">
             <div class="flex items-center">
               <el-tooltip content="拖动排序" v-if="isModelSorting">
                 <Icon
@@ -86,27 +87,25 @@
                   class="drag-icon cursor-move text-#8a909c mr-10px"
                 />
               </el-tooltip>
-              <el-image :src="scope.row.icon" class="h-38px w-38px mr-10px rounded" />
-              {{ scope.row.name }}
+              <el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
+              {{ row.name }}
             </div>
           </template>
         </el-table-column>
         <el-table-column label="可见范围" prop="startUserIds" min-width="150">
-          <template #default="scope">
-            <el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
-              全部可见
-            </el-text>
-            <el-text v-else-if="scope.row.startUsers.length == 1">
-              {{ scope.row.startUsers[0].nickname }}
+          <template #default="{ row }">
+            <el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
+            <el-text v-else-if="row.startUsers.length === 1">
+              {{ row.startUsers[0].nickname }}
             </el-text>
             <el-text v-else>
               <el-tooltip
                 class="box-item"
                 effect="dark"
                 placement="top"
-                :content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
+                :content="row.startUsers.map((user: any) => user.nickname).join('、')"
               >
-                {{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
+                {{ row.startUsers[0].nickname }}等 {{ row.startUsers.length }} 人可见
               </el-tooltip>
             </el-text>
           </template>
@@ -245,7 +244,6 @@
 <script lang="ts" setup>
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import Sortable from 'sortablejs'
-import { propTypes } from '@/utils/propTypes'
 import { formatDate } from '@/utils/formatTime'
 import * as ModelApi from '@/api/bpm/model'
 import * as FormApi from '@/api/bpm/form'
@@ -254,15 +252,49 @@ import { BpmModelFormType } from '@/utils/constants'
 import { checkPermi } from '@/utils/permission'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import { useAppStore } from '@/store/modules/app'
-import { cloneDeep } from 'lodash-es'
+import { cloneDeep, isEqual } from 'lodash-es'
 import { useTagsView } from '@/hooks/web/useTagsView'
+import { useDebounceFn } from '@vueuse/core'
 
 defineOptions({ name: 'BpmModel' })
 
-const props = defineProps({
-  categoryInfo: propTypes.object.def([]), // 分类后的数据
-  isCategorySorting: propTypes.bool.def(false) // 是否分类在排序
-})
+// 优化 Props 类型定义
+interface UserInfo {
+  nickname: string
+  [key: string]: any
+}
+
+interface ProcessDefinition {
+  deploymentTime: string
+  version: number
+  suspensionState: number
+}
+
+interface ModelInfo {
+  id: number
+  name: string
+  icon?: string
+  startUsers?: UserInfo[]
+  processDefinition?: ProcessDefinition
+  formType?: number
+  formId?: number
+  formName?: string
+  formCustomCreatePath?: string
+  managerUserIds?: number[]
+  [key: string]: any
+}
+
+interface CategoryInfoProps {
+  id: number
+  name: string
+  modelList: ModelInfo[]
+}
+
+const props = defineProps<{
+  categoryInfo: CategoryInfoProps
+  isCategorySorting: boolean
+}>()
+
 const emit = defineEmits(['success'])
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
@@ -271,10 +303,20 @@ const userStore = useUserStoreWithOut() // 用户信息缓存
 const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
 
 const isModelSorting = ref(false) // 是否正处于排序状态
-const originalData: any = ref([]) // 原始数据
-const modelList: any = ref([]) // 模型列表
+const originalData = ref<ModelInfo[]>([]) // 原始数据
+const modelList = ref<ModelInfo[]>([]) // 模型列表
 const isExpand = ref(false) // 是否处于展开状态
 
+// 使用 computed 优化表格样式计算
+const tableHeaderStyle = computed(() => ({
+  backgroundColor: isDark.value ? '' : '#edeff0',
+  paddingLeft: '10px'
+}))
+
+const tableCellStyle = computed(() => ({
+  paddingLeft: '10px'
+}))
+
 /** 权限校验:通过 computed 解决列表的卡顿问题 */
 const hasPermiUpdate = computed(() => {
   return checkPermi(['bpm:model:update'])
@@ -449,14 +491,15 @@ const handleModelSortCancel = () => {
 
 /** 创建拖拽实例 */
 const tableRef = ref()
-const initSort = () => {
+const initSort = useDebounceFn(() => {
   const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
+  if (!table) return
+
   Sortable.create(table, {
     group: 'shared',
     animation: 150,
     draggable: '.el-table__row',
     handle: '.drag-icon',
-    // 结束拖动事件
     onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
       if (oldDraggableIndex !== newDraggableIndex) {
         modelList.value.splice(
@@ -467,15 +510,18 @@ const initSort = () => {
       }
     }
   })
-}
+}, 200)
 
 /** 更新 modelList 模型列表 */
-const updateModeList = () => {
-  modelList.value = cloneDeep(props.categoryInfo.modelList)
-  if (props.categoryInfo.modelList.length > 0) {
-    isExpand.value = true
+const updateModeList = useDebounceFn(() => {
+  const newModelList = props.categoryInfo.modelList
+  if (!isEqual(modelList.value, newModelList)) {
+    modelList.value = cloneDeep(newModelList)
+    if (newModelList?.length > 0) {
+      isExpand.value = true
+    }
   }
-}
+}, 100)
 
 /** 重命名弹窗确定 */
 const renameCategoryVisible = ref(false)
@@ -526,14 +572,15 @@ const openModelForm = async (type: string, id?: number) => {
   }
 }
 
-watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
-watch(
-  () => props.isCategorySorting,
-  (val) => {
-    if (val) isExpand.value = false
-  },
-  { immediate: true }
-)
+watchEffect(() => {
+  if (props.categoryInfo?.modelList) {
+    updateModeList()
+  }
+
+  if (props.isCategorySorting) {
+    isExpand.value = false
+  }
+})
 </script>
 
 <style lang="scss">
@@ -550,10 +597,16 @@ watch(
 }
 </style>
 <style lang="scss" scoped>
-:deep() {
-  .el-table__cell {
+.category-draggable-model {
+  :deep(.el-table__cell) {
     overflow: hidden;
     border-bottom: none !important;
   }
+
+  // 优化表格渲染性能
+  :deep(.el-table__body) {
+    will-change: transform;
+    transform: translateZ(0);
+  }
 }
 </style>

+ 68 - 21
src/views/bpm/model/form/ExtraSettings.vue

@@ -75,12 +75,12 @@
         </el-radio-group>
       </div>
     </el-form-item>
-    <el-form-item v-if="modelData.customTitleSetting" class="mb-20px">
+    <el-form-item v-if="modelData.titleSetting" class="mb-20px">
       <template #label>
         <el-text size="large" tag="b">标题设置</el-text>
       </template>
       <div class="flex flex-col">
-        <el-radio-group v-model="modelData.customTitleSetting.enable">
+        <el-radio-group v-model="modelData.titleSetting.enable">
           <div class="flex flex-col">
             <el-radio :value="false"
               >系统默认 <el-text type="info"> 展示流程名称 </el-text></el-radio
@@ -96,18 +96,50 @@
           </div>
         </el-radio-group>
         <el-mention
-          v-if="modelData.customTitleSetting.enable"
-          v-model="modelData.customTitleSetting.title"
+          v-if="modelData.titleSetting.enable"
+          v-model="modelData.titleSetting.title"
           type="textarea"
           prefix="{"
           split="}"
           whole
-          :options="formFieldOptions"
-          placeholder="请插入表单字段或输入文本"
+          :options="formFieldOptions4Title"
+          placeholder="请插入表单字段(输入 '{' 可以选择表单字段)或输入文本"
           class="w-600px!"
         />
       </div>
     </el-form-item>
+    <el-form-item
+      v-if="modelData.summarySetting && modelData.formType === BpmModelFormType.NORMAL"
+      class="mb-20px"
+    >
+      <template #label>
+        <el-text size="large" tag="b">摘要设置</el-text>
+      </template>
+      <div class="flex flex-col">
+        <el-radio-group v-model="modelData.summarySetting.enable">
+          <div class="flex flex-col">
+            <el-radio :value="false">
+              系统默认 <el-text type="info"> 展示表单前 3 个字段 </el-text>
+            </el-radio>
+            <el-radio :value="true"> 自定义摘要 </el-radio>
+          </div>
+        </el-radio-group>
+        <el-select
+          class="w-500px!"
+          v-if="modelData.summarySetting.enable"
+          v-model="modelData.summarySetting.summary"
+          multiple
+          placeholder="请选择要展示的表单字段"
+        >
+          <el-option
+            v-for="item in formFieldOptions4Summary"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+    </el-form-item>
   </el-form>
 </template>
 
@@ -174,21 +206,30 @@ const numberExample = computed(() => {
 })
 
 /** 表单选项 */
-const formField = ref<Array<{ field: ProcessVariableEnum; title: string }>>([])
-const formFieldOptions = computed(() => {
+const formField = ref<Array<{ field: string; title: string }>>([])
+const formFieldOptions4Title = computed(() => {
+  let cloneFormField = formField.value.map((item) => {
+    return {
+      label: item.title,
+      value: item.field
+    }
+  })
   // 固定添加发起人 ID 字段
-  formField.value.unshift({
-    field: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
-    title: '流程名称'
+  cloneFormField.unshift({
+    label: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
+    value: '流程名称'
   })
-  formField.value.unshift({
-    field: ProcessVariableEnum.START_TIME,
-    title: '发起时间'
+  cloneFormField.unshift({
+    label: ProcessVariableEnum.START_TIME,
+    value: '发起时间'
   })
-  formField.value.unshift({
-    field: ProcessVariableEnum.START_USER_ID,
-    title: '发起人'
+  cloneFormField.unshift({
+    label: ProcessVariableEnum.START_USER_ID,
+    value: '发起人'
   })
+  return cloneFormField
+})
+const formFieldOptions4Summary = computed(() => {
   return formField.value.map((item) => {
     return {
       label: item.title,
@@ -211,12 +252,18 @@ const initData = () => {
   if (!modelData.value.autoApprovalType) {
     modelData.value.autoApprovalType = BpmAutoApproveType.NONE
   }
-  if (!modelData.value.customTitleSetting) {
-    modelData.value.customTitleSetting = {
+  if (!modelData.value.titleSetting) {
+    modelData.value.titleSetting = {
       enable: false,
       title: ''
     }
   }
+  if (!modelData.value.summarySetting) {
+    modelData.value.summarySetting = {
+      enable: false,
+      summary: []
+    }
+  }
 }
 defineExpose({ initData })
 
@@ -224,9 +271,9 @@ defineExpose({ initData })
 watch(
   () => modelData.value.formId,
   async (newFormId) => {
-    if (newFormId && modelData.value.formType === BpmModelFormType.CUSTOM) {
+    if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
       const data = await FormApi.getForm(newFormId)
-      const result: Array<{ field: ProcessVariableEnum; title: string }> = []
+      const result: Array<{ field: string; title: string }> = []
       if (data.fields) {
         data.fields.forEach((fieldStr: string) => {
           parseFormFields(JSON.parse(fieldStr), result)

+ 5 - 1
src/views/bpm/model/form/index.vue

@@ -155,9 +155,13 @@ const formData: any = ref({
     length: 5
   },
   autoApprovalType: BpmAutoApproveType.NONE,
-  customTitleSetting: {
+  titleSetting: {
     enable: false,
     title: ''
+  },
+  summarySetting: {
+    enable: false,
+    summary: []
   }
 })
 

+ 9 - 0
src/views/bpm/processInstance/index.vue

@@ -130,6 +130,15 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
+      <el-table-column label="摘要" prop="summary" min-width="180" fixed="left">
+        <template #default="scope">
+          <div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
+            <div v-for="(item, index) in scope.row.summary" :key="index">
+              <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
+            </div>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column
         label="流程分类"
         align="center"

+ 1 - 0
src/views/bpm/task/copy/index.vue

@@ -44,6 +44,7 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
+      <!-- TODO 芋艿:增加摘要 -->
       <el-table-column align="center" label="流程名" prop="processInstanceName" min-width="180" />
       <el-table-column
         align="center"

+ 14 - 5
src/views/bpm/task/done/index.vue

@@ -64,7 +64,7 @@
             :value="dict.value"
           />
         </el-select>
-      </el-form-item> 
+      </el-form-item>
 
       <!-- 高级筛选 -->
       <el-form-item :style="{ position: 'absolute', right: '0px' }">
@@ -77,9 +77,9 @@
         >
           <template #reference>
             <el-button @click="showPopover = !showPopover" >
-              <Icon icon="ep:plus" class="mr-5px" />高级筛选 
+              <Icon icon="ep:plus" class="mr-5px" />高级筛选
             </el-button>
-            
+
           </template>
           <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
             <el-select
@@ -95,7 +95,7 @@
                 :value="category.code"
               />
             </el-select>
-          </el-form-item>          
+          </el-form-item>
           <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"
@@ -122,6 +122,15 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
+      <el-table-column label="摘要" prop="summary" min-width="180">
+        <template #default="scope">
+          <div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
+            <div v-for="(item, index) in scope.row.summary" :key="index">
+              <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
+            </div>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column
         align="center"
         label="发起人"
@@ -195,7 +204,7 @@ const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   name: '',
-  category: undefined,  
+  category: undefined,
   status: undefined,
   createTime: []
 })

+ 12 - 3
src/views/bpm/task/todo/index.vue

@@ -60,9 +60,9 @@
         >
           <template #reference>
             <el-button @click="showPopover = !showPopover" >
-              <Icon icon="ep:plus" class="mr-5px" />高级筛选 
+              <Icon icon="ep:plus" class="mr-5px" />高级筛选
             </el-button>
-            
+
           </template>
           <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
             <el-select
@@ -78,7 +78,7 @@
                 :value="category.code"
               />
             </el-select>
-          </el-form-item>          
+          </el-form-item>
           <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"
@@ -105,6 +105,15 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
+      <el-table-column label="摘要" prop="summary" min-width="180">
+        <template #default="scope">
+          <div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
+            <div v-for="(item, index) in scope.row.summary" :key="index">
+              <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
+            </div>
+          </div>
+        </template>
+      </el-table-column>
       <el-table-column
         align="center"
         label="发起人"