Quellcode durchsuchen

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

# Conflicts:
#	src/views/system/menu/index.vue
YunaiV vor 6 Monaten
Ursprung
Commit
c6f70cce00

+ 30 - 14
src/components/SimpleProcessDesignerV2/src/consts.ts

@@ -246,15 +246,15 @@ export type AssignEmptyHandler = {
 export type ListenerHandler = {
   enable: boolean
   path?: string
-  header?: ListenerParam[]
-  body?: ListenerParam[]
+  header?: HttpRequestParam[]
+  body?: HttpRequestParam[]
 }
-export type ListenerParam = {
+export type HttpRequestParam = {
   key: string
   type: number
   value: string
 }
-export enum ListenerParamTypeEnum {
+export enum BpmHttpRequestParamTypeEnum {
   /**
    * 固定值
    */
@@ -264,7 +264,7 @@ export enum ListenerParamTypeEnum {
    */
   FROM_FORM = 2
 }
-export const LISTENER_MAP_TYPES = [
+export const BPM_HTTP_REQUEST_PARAM_TYPES = [
   {
     value: 1,
     label: '固定值'
@@ -371,13 +371,13 @@ export enum TimeUnitType {
 /**
  * 条件节点设置结构定义,用于条件节点
  */
-export type ConditionSetting =  {
+export type ConditionSetting = {
   // 条件类型
-  conditionType?: ConditionType,
+  conditionType?: ConditionType
   // 条件表达式
-  conditionExpression?: string,
+  conditionExpression?: string
   // 条件组
-  conditionGroups?: ConditionGroup,
+  conditionGroups?: ConditionGroup
   // 是否默认的条件
   defaultFlow?: boolean
 }
@@ -710,13 +710,14 @@ export type RouterSetting = {
   conditionGroups: ConditionGroup
 }
 
-// ==================== 触发器相关定义 ==================== 
+// ==================== 触发器相关定义 ====================
 /**
  * 触发器节点结构定义
  */
 export type TriggerSetting = {
   type: TriggerTypeEnum
-  httpRequestSetting: HttpRequestTriggerSetting
+  httpRequestSetting?: HttpRequestTriggerSetting
+  normalFormSetting?: NormalFormTriggerSetting
 }
 
 /**
@@ -727,6 +728,10 @@ export enum TriggerTypeEnum {
    * 发送 HTTP 请求触发器
    */
   HTTP_REQUEST = 1,
+  /**
+   * 更新流程表单触发器
+   */
+  UPDATE_NORMAL_FORM = 2 // TODO @jason:FORM_UPDATE?
 }
 
 /**
@@ -736,11 +741,22 @@ export type HttpRequestTriggerSetting = {
   // 请求 URL
   url: string
   // 请求头参数设置
-  header?: ListenerParam[] // TODO 需要重命名一下
+  header?: HttpRequestParam[]
   // 请求体参数设置
-  body?: ListenerParam[]
+  body?: HttpRequestParam[]
+  // 请求响应设置
+  response?: Record<string, string>[]
+}
+
+/**
+ * 流程表单触发器配置结构定义
+ */
+export type NormalFormTriggerSetting = {
+  // 更新表单字段
+  updateFormFields?: Record<string, any>
 }
 
 export const TRIGGER_TYPES: DictDataVO[] = [
-  { label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST }
+  { label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
+  { label: '修改表单数据', value: TriggerTypeEnum.UPDATE_NORMAL_FORM }
 ]

+ 27 - 9
src/components/SimpleProcessDesignerV2/src/node.ts

@@ -14,7 +14,8 @@ import {
   AssignStartUserHandlerType,
   AssignEmptyHandlerType,
   FieldPermissionType,
-  ListenerParam
+  HttpRequestParam,
+  ProcessVariableEnum
 } from './consts'
 import { parseFormFields } from '@/components/FormCreate/src/utils'
 
@@ -105,14 +106,31 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
     getNodeConfigFormFields
   }
 }
+
 /**
- * @description 获取表单的字段
+ * @description 获取流程表单的字段
  */
 export function useFormFields() {
   const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
   return parseFormCreateFields(unref(formFields))
 }
 
+// TODO @芋艿:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。
+/**
+ * @description 获取流程表单的字段和发起人字段
+ */
+export function useFormFieldsAndStartUser() {
+  const injectFormFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
+  const formFields = parseFormCreateFields(unref(injectFormFields))
+  // 添加发起人
+  formFields.unshift({
+    field: ProcessVariableEnum.START_USER_ID,
+    title: '发起人',
+    required: true
+  })
+  return formFields
+}
+
 export type UserTaskFormType = {
   candidateStrategy: CandidateStrategy
   approveMethod: ApproveMethodType
@@ -139,20 +157,20 @@ export type UserTaskFormType = {
   taskCreateListenerEnable?: boolean
   taskCreateListenerPath?: string
   taskCreateListener?: {
-    header: ListenerParam[],
-    body: ListenerParam[]
+    header: HttpRequestParam[]
+    body: HttpRequestParam[]
   }
   taskAssignListenerEnable?: boolean
   taskAssignListenerPath?: string
   taskAssignListener?: {
-    header: ListenerParam[],
-    body: ListenerParam[]
+    header: HttpRequestParam[]
+    body: HttpRequestParam[]
   }
   taskCompleteListenerEnable?: boolean
   taskCompleteListenerPath?: string
-  taskCompleteListener?:{
-    header: ListenerParam[],
-    body: ListenerParam[]
+  taskCompleteListener?: {
+    header: HttpRequestParam[]
+    body: HttpRequestParam[]
   }
   signEnable: boolean
   reasonRequire: boolean

+ 4 - 16
src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue

@@ -47,10 +47,9 @@ import {
   SimpleFlowNode,
   ConditionType,
   COMPARISON_OPERATORS,
-  ProcessVariableEnum
 } from '../consts'
 import { getDefaultConditionNodeName } from '../utils'
-import { useFormFields } from '../node'
+import { useFormFieldsAndStartUser } from '../node'
 import Condition from './components/Condition.vue'
 const message = useMessage() // 消息弹窗
 defineOptions({
@@ -176,23 +175,12 @@ const getShowText = (): string => {
   }
   return showText
 }
-
-const fieldsInfo = useFormFields()
-/** 条件规则可选择的表单字段 */
-const fieldOptions = computed(() => {
-  const fieldsCopy = fieldsInfo.slice()
-  // 固定添加发起人 ID 字段
-  fieldsCopy.unshift({
-    field: ProcessVariableEnum.START_USER_ID,
-    title: '发起人',
-    required: true
-  })
-  return fieldsCopy
-})
+// 流程表单字段和发起人字段
+const fieldOptions = useFormFieldsAndStartUser()
 
 /** 获取字段名称 */
 const getFieldTitle = (field: string) => {
-  const item = fieldOptions.value.find((item) => item.field === field)
+  const item = fieldOptions.find((item) => item.field === field)
   return item?.title
 }
 

+ 1 - 0
src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue

@@ -124,6 +124,7 @@ const saveConfig = async () => {
   if (!valid) return false
   const showText = getShowText()
   if (!showText) return false
+  currentNode.value.name = nodeName.value!
   currentNode.value.showText = showText
   if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
     currentNode.value.delaySetting = {

+ 202 - 9
src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue

@@ -35,6 +35,7 @@
             />
           </el-select>
         </el-form-item>
+        <!-- HTTP 请求触发器 -->
         <div
           v-if="configForm.type === TriggerTypeEnum.HTTP_REQUEST && configForm.httpRequestSetting"
         >
@@ -46,14 +47,137 @@
               :closable="false"
             />
           </el-form-item>
+          <!-- 请求地址-->
           <el-form-item label="请求地址" prop="httpRequestSetting.url">
             <el-input v-model="configForm.httpRequestSetting.url" />
           </el-form-item>
+          <!-- 请求头,请求体设置-->
           <HttpRequestParamSetting
             :header="configForm.httpRequestSetting.header"
             :body="configForm.httpRequestSetting.body"
             :bind="'httpRequestSetting'"
           />
+          <!-- 返回值设置-->
+          <el-form-item label="返回值">
+            <el-alert
+              title="通过请求返回值, 可以修改流程表单的值"
+              type="warning"
+              show-icon
+              :closable="false"
+            />
+          </el-form-item>
+          <el-form-item>
+            <div
+              class="flex pt-2"
+              v-for="(item, index) in configForm.httpRequestSetting.response"
+              :key="index"
+            >
+              <div class="mr-2">
+                <el-form-item
+                  :prop="`httpRequestSetting.response.${index}.key`"
+                  :rules="{
+                    required: true,
+                    message: '表单字段不能为空',
+                    trigger: 'blur'
+                  }"
+                >
+                  <el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
+                    <el-option
+                      v-for="(field, fIdx) in formFields"
+                      :key="fIdx"
+                      :label="field.title"
+                      :value="field.field"
+                      :disabled="!field.required"
+                    />
+                  </el-select>
+                </el-form-item>
+              </div>
+              <div class="mr-2">
+                <el-form-item
+                  :prop="`httpRequestSetting.response.${index}.value`"
+                  :rules="{
+                    required: true,
+                    message: '请求返回字段不能为空',
+                    trigger: 'blur'
+                  }"
+                >
+                  <el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
+                </el-form-item>
+              </div>
+              <div class="mr-1 pt-1 cursor-pointer">
+                <Icon
+                  icon="ep:delete"
+                  :size="18"
+                  @click="deleteHttpResponseSetting(configForm.httpRequestSetting.response!, index)"
+                />
+              </div>
+            </div>
+            <el-button
+              type="primary"
+              text
+              @click="addHttpResponseSetting(configForm.httpRequestSetting.response!)"
+            >
+              <Icon icon="ep:plus" class="mr-5px" />添加一行
+            </el-button>
+          </el-form-item>
+        </div>
+        <div
+          v-if="
+            configForm.type === TriggerTypeEnum.UPDATE_NORMAL_FORM && configForm.normalFormSetting
+          "
+        >
+          <el-divider content-position="left">修改表单设置</el-divider>
+          <div
+            class="flex items-center"
+            v-for="key in Object.keys(configForm.normalFormSetting.updateFormFields!)"
+            :key="key"
+          >
+            <div class="mr-2 flex items-center">
+              <el-form-item>
+                <el-select
+                  class="w-160px!"
+                  :model-value="key"
+                  @update:model-value="(newKey) => updateFormFieldKey(key, newKey)"
+                  placeholder="请选择表单字段"
+                  :disabled="key !== ''"
+                >
+                  <el-option
+                    v-for="(field, fIdx) in optionalUpdateFormFields"
+                    :key="fIdx"
+                    :label="field.title"
+                    :value="field.field"
+                    :disabled="field.disabled"
+                  />
+                </el-select>
+              </el-form-item>
+            </div>
+            <div class="mx-2"><el-form-item>的值设置为</el-form-item></div>
+            <div class="mr-2">
+              <el-form-item
+                :prop="`normalFormSetting.updateFormFields.${key}`"
+                :rules="{
+                  required: true,
+                  message: '值不能为空',
+                  trigger: 'blur'
+                }"
+              >
+                <el-input
+                  class="w-160px"
+                  v-model="configForm.normalFormSetting.updateFormFields![key]"
+                  placeholder="请输入"
+                  :disabled="!key"
+                />
+              </el-form-item>
+            </div>
+            <div class="mr-1 pt-1 cursor-pointer">
+              <el-form-item>
+                <Icon icon="ep:delete" :size="18" @click="deleteFormFieldSetting(key)" />
+              </el-form-item>
+            </div>
+          </div>
+          <el-button type="primary" text @click="addFormFieldSetting()">
+            <Icon icon="ep:plus" class="mr-5px" />添加修改字段
+          </el-button>
         </div>
       </el-form>
     </div>
@@ -68,7 +192,7 @@
 </template>
 <script setup lang="ts">
 import { SimpleFlowNode, NodeType, TriggerSetting, TRIGGER_TYPES, TriggerTypeEnum } from '../consts'
-import { useWatchNode, useDrawer, useNodeName } from '../node'
+import { useWatchNode, useDrawer, useNodeName, useFormFields } from '../node'
 import HttpRequestParamSetting from './components/HttpRequestParamSetting.vue'
 
 defineOptions({
@@ -80,6 +204,7 @@ const props = defineProps({
     required: true
   }
 })
+const message = useMessage() // 消息弹窗
 // 抽屉配置
 const { settingVisible, closeDrawer, openDrawer } = useDrawer()
 // 当前节点
@@ -91,9 +216,7 @@ const formRef = ref() // 表单 Ref
 // 表单校验规则
 const formRules = reactive({
   type: [{ required: true, message: '触发器类型不能为空', trigger: 'change' }],
-  httpRequestSetting: {
-    url: [{ required: true, message: '请求地址不能为空', trigger: 'blur' }]
-  }
+  'httpRequestSetting.url': [{ required: true, message: '请求地址不能为空', trigger: 'blur' }]
 })
 // 触发器配置表单数据
 const configForm = ref<TriggerSetting>({
@@ -101,9 +224,56 @@ const configForm = ref<TriggerSetting>({
   httpRequestSetting: {
     url: '',
     header: [],
-    body: []
-  }
+    body: [],
+    response: []
+  },
+  normalFormSetting: { updateFormFields: {} }
 })
+// 流程表单字段
+const formFields = useFormFields()
+
+// 可选的修改的表单字段
+const optionalUpdateFormFields = computed(() => {
+  const usedFields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
+  return formFields.map((field) => ({
+    title: field.title,
+    field: field.field,
+    disabled: usedFields.includes(field.field)
+  }))
+})
+
+const updateFormFieldKey = (oldKey: string, newKey: string) => {
+  if (!configForm.value.normalFormSetting?.updateFormFields) return
+  const value = configForm.value.normalFormSetting.updateFormFields[oldKey]
+  delete configForm.value.normalFormSetting.updateFormFields[oldKey]
+  configForm.value.normalFormSetting.updateFormFields[newKey] = value
+}
+
+/** 添加 HTTP 请求返回值设置项*/
+const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
+  responseSetting.push({
+    key: '',
+    value: ''
+  })
+}
+
+/** 删除 HTTP 请求返回值设置项 */
+const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
+  responseSetting.splice(index, 1)
+}
+
+/** 添加修改表单设置项 */
+const addFormFieldSetting = () => {
+  if (configForm.value.normalFormSetting!.updateFormFields === undefined) {
+    configForm.value.normalFormSetting!.updateFormFields = {}
+  }
+  configForm.value.normalFormSetting!.updateFormFields[''] = undefined
+}
+/** 删除修改表单设置项 */
+const deleteFormFieldSetting = (key: string) => {
+  if (!configForm.value.normalFormSetting?.updateFormFields) return
+  delete configForm.value.normalFormSetting.updateFormFields[key]
+}
 
 /** 保存配置 */
 const saveConfig = async () => {
@@ -112,16 +282,31 @@ const saveConfig = async () => {
   if (!valid) return false
   const showText = getShowText()
   if (!showText) return false
+  currentNode.value.name = nodeName.value!
   currentNode.value.showText = showText
+  if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
+    configForm.value.normalFormSetting = undefined
+  }
+  if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
+    configForm.value.httpRequestSetting = undefined
+  }
   currentNode.value.triggerSetting = configForm.value
   settingVisible.value = false
   return true
 }
+
 /** 获取节点展示内容 */
 const getShowText = (): string => {
   let showText = ''
   if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
-    showText = `${configForm.value.httpRequestSetting.url}`
+    showText = `${configForm.value.httpRequestSetting?.url}`
+  } else if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
+    const updatefields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
+    if (updatefields.length === 0) {
+      message.warning('请设置修改表单字段')
+    } else {
+      showText = '修改表单数据'
+    }
   }
   return showText
 }
@@ -130,8 +315,16 @@ const getShowText = (): string => {
 const showTriggerNodeConfig = (node: SimpleFlowNode) => {
   nodeName.value = node.name
   if (node.triggerSetting) {
-    configForm.value.type = node.triggerSetting.type
-    configForm.value.httpRequestSetting = node.triggerSetting.httpRequestSetting
+    configForm.value = {
+      type: node.triggerSetting.type,
+      httpRequestSetting: node.triggerSetting.httpRequestSetting || {
+        url: '',
+        header: [],
+        body: [],
+        response: []
+      },
+      normalFormSetting: node.triggerSetting.normalFormSetting || { updateFormFields: {} }
+    }
   }
 }
 

+ 16 - 14
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -61,7 +61,7 @@
               label="指定角色"
               prop="roleIds"
             >
-              <el-select v-model="configForm.roleIds" clearable multiple style="width: 100%">
+              <el-select filterable v-model="configForm.roleIds" clearable multiple style="width: 100%">
                 <el-option
                   v-for="item in roleOptions"
                   :key="item.id"
@@ -99,7 +99,7 @@
               prop="postIds"
               span="24"
             >
-              <el-select v-model="configForm.postIds" clearable multiple style="width: 100%">
+              <el-select filterable v-model="configForm.postIds" clearable multiple style="width: 100%">
                 <el-option
                   v-for="item in postOptions"
                   :key="item.id"
@@ -114,7 +114,7 @@
               prop="userIds"
               span="24"
             >
-              <el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
+              <el-select filterable v-model="configForm.userIds" clearable multiple style="width: 100%">
                 <el-option
                   v-for="item in userOptions"
                   :key="item.id"
@@ -128,7 +128,7 @@
               label="指定用户组"
               prop="userGroups"
             >
-              <el-select v-model="configForm.userGroups" clearable multiple style="width: 100%">
+              <el-select filterable v-model="configForm.userGroups" clearable multiple style="width: 100%">
                 <el-option
                   v-for="item in userGroupOptions"
                   :key="item.id"
@@ -142,7 +142,7 @@
               label="表单内用户字段"
               prop="formUser"
             >
-              <el-select v-model="configForm.formUser" clearable style="width: 100%">
+              <el-select filterable v-model="configForm.formUser" clearable style="width: 100%">
                 <el-option
                   v-for="(item, idx) in userFieldOnFormOptions"
                   :key="idx"
@@ -157,7 +157,7 @@
               label="表单内部门字段"
               prop="formDept"
             >
-              <el-select v-model="configForm.formDept" clearable style="width: 100%">
+              <el-select filterable v-model="configForm.formDept" clearable style="width: 100%">
                 <el-option
                   v-for="(item, idx) in deptFieldOnFormOptions"
                   :key="idx"
@@ -179,7 +179,7 @@
               prop="deptLevel"
               span="24"
             >
-              <el-select v-model="configForm.deptLevel" clearable>
+              <el-select filterable v-model="configForm.deptLevel" clearable>
                 <el-option
                   v-for="(item, index) in MULTI_LEVEL_DEPT"
                   :key="index"
@@ -245,7 +245,7 @@
               label="驳回节点"
               prop="returnNodeId"
             >
-              <el-select v-model="configForm.returnNodeId" clearable style="width: 100%">
+              <el-select filterable v-model="configForm.returnNodeId" clearable style="width: 100%">
                 <el-option
                   v-for="item in returnTaskList"
                   :key="item.id"
@@ -293,6 +293,7 @@
                 />
               </el-form-item>
               <el-select
+                filterable
                 v-model="timeUnit"
                 class="mr-2"
                 :style="{ width: '100px' }"
@@ -332,6 +333,7 @@
               span="24"
             >
               <el-select
+                filterable
                 v-model="configForm.assignEmptyHandlerUserIds"
                 clearable
                 multiple
@@ -758,22 +760,22 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
   getNodeConfigFormFields(node.fieldsPermission)
   // 5. 监听器
   // 5.1 创建任务
-  configForm.value.taskCreateListenerEnable = node.taskCreateListener!.enable
-  configForm.value.taskCreateListenerPath = node.taskCreateListener!.path
+  configForm.value.taskCreateListenerEnable = node.taskCreateListener?.enable
+  configForm.value.taskCreateListenerPath = node.taskCreateListener?.path
   configForm.value.taskCreateListener = {
     header: node.taskCreateListener?.header ?? [],
     body: node.taskCreateListener?.body ?? []
   }
   // 5.2 指派任务
-  configForm.value.taskAssignListenerEnable = node.taskAssignListener!.enable
-  configForm.value.taskAssignListenerPath = node.taskAssignListener!.path
+  configForm.value.taskAssignListenerEnable = node.taskAssignListener?.enable
+  configForm.value.taskAssignListenerPath = node.taskAssignListener?.path
   configForm.value.taskAssignListener = {
     header: node.taskAssignListener?.header ?? [],
     body: node.taskAssignListener?.body ?? []
   }
  // 5.3 完成任务
-  configForm.value.taskCompleteListenerEnable = node.taskCompleteListener!.enable
-  configForm.value.taskCompleteListenerPath = node.taskCompleteListener!.path
+  configForm.value.taskCompleteListenerEnable = node.taskCompleteListener?.enable
+  configForm.value.taskCompleteListenerPath = node.taskCompleteListener?.path
   configForm.value.taskCompleteListener = {
     header: node.taskCompleteListener?.header ?? [],
     body: node.taskCompleteListener?.body ?? []

+ 5 - 13
src/components/SimpleProcessDesignerV2/src/nodes-config/components/Condition.vue

@@ -138,11 +138,10 @@ import {
   COMPARISON_OPERATORS,
   CONDITION_CONFIG_TYPES,
   ConditionType,
-  DEFAULT_CONDITION_GROUP_VALUE,
-  ProcessVariableEnum
+  DEFAULT_CONDITION_GROUP_VALUE
 } from '../../consts'
 import { BpmModelFormType } from '@/utils/constants'
-import { useFormFields } from '../../node'
+import { useFormFieldsAndStartUser } from '../../node'
 
 const props = defineProps({
   modelValue: {
@@ -170,17 +169,10 @@ const conditionConfigTypes = computed(() => {
     }
   })
 })
+
 /** 条件规则可选择的表单字段 */
-const fieldOptions = computed(() => {
-  const fieldsCopy = useFormFields().slice()
-  // 固定添加发起人 ID 字段
-  fieldsCopy.unshift({
-    field: ProcessVariableEnum.START_USER_ID,
-    title: '发起人',
-    required: true
-  })
-  return fieldsCopy
-})
+const fieldOptions = useFormFieldsAndStartUser()
+
 // 表单校验规则
 const formRules = reactive({
   conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],

+ 18 - 15
src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue

@@ -16,7 +16,7 @@
       <div class="mr-2">
         <el-select class="w-100px!" v-model="item.type">
           <el-option
-            v-for="types in LISTENER_MAP_TYPES"
+            v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES"
             :key="types.value"
             :label="types.label"
             :value="types.value"
@@ -33,7 +33,7 @@
           }"
         >
           <el-input
-            v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
+            v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
             class="w-160px"
             v-model="item.value"
           />
@@ -47,7 +47,7 @@
           }"
         >
           <el-select
-            v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
+            v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
             class="w-160px!"
             v-model="item.value"
           >
@@ -86,7 +86,7 @@
       <div class="mr-2">
         <el-select class="w-100px!" v-model="item.type">
           <el-option
-            v-for="types in LISTENER_MAP_TYPES"
+            v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES"
             :key="types.value"
             :label="types.label"
             :value="types.value"
@@ -103,7 +103,7 @@
           }"
         >
           <el-input
-            v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
+            v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
             class="w-160px"
             v-model="item.value"
           />
@@ -117,7 +117,7 @@
           }"
         >
           <el-select
-            v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
+            v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
             class="w-160px!"
             v-model="item.value"
           >
@@ -141,20 +141,20 @@
   </el-form-item>
 </template>
 <script setup lang="ts">
-import { ListenerParam, LISTENER_MAP_TYPES, ListenerParamTypeEnum } from '../../consts'
-import { useFormFields } from '../../node'
+import { HttpRequestParam, BPM_HTTP_REQUEST_PARAM_TYPES, BpmHttpRequestParamTypeEnum } from '../../consts'
+import { useFormFieldsAndStartUser } from '../../node'
 defineOptions({
   name: 'HttpRequestParamSetting'
 })
 
 const props = defineProps({
   header: {
-    type: Array as () => ListenerParam[],
+    type: Array as () => HttpRequestParam[],
     required: false,
     default: () => []
   },
   body: {
-    type: Array as () => ListenerParam[],
+    type: Array as () => HttpRequestParam[],
     required: false,
     default: () => []
   },
@@ -164,16 +164,19 @@ const props = defineProps({
   }
 })
 
-const formFieldOptions = useFormFields()
-
-const addHttpRequestParam = (arr: ListenerParam[]) => {
+// 流程表单字段,发起人字段
+const formFieldOptions = useFormFieldsAndStartUser()
+/** 添加请求配置项 */
+const addHttpRequestParam = (arr: HttpRequestParam[]) => {
   arr.push({
     key: '',
-    type: ListenerParamTypeEnum.FIXED_VALUE,
+    type: BpmHttpRequestParamTypeEnum.FIXED_VALUE,
     value: ''
   })
 }
-const deleteHttpRequestParam = (arr: ListenerParam[], index: number) => {
+
+/** 删除请求配置项 */
+const deleteHttpRequestParam = (arr: HttpRequestParam[], index: number) => {
   arr.splice(index, 1)
 }
 </script>

+ 12 - 0
src/router/modules/remaining.ts

@@ -307,6 +307,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
           activityId: route.query.activityId
         })
       },
+      {
+        path: 'process-instance/report',
+        component: () => import('@/views/bpm/processInstance/report/index.vue'),
+        name: 'BpmProcessInstanceReport',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '数据报表',
+          activeMenu: '/bpm/manager/model'
+        }
+      },
       {
         path: 'oa/leave/create',
         component: () => import('@/views/bpm/oa/leave/create.vue'),

+ 5 - 0
src/views/bpm/category/CategoryForm.vue

@@ -13,6 +13,9 @@
       <el-form-item label="分类标志" prop="code">
         <el-input v-model="formData.code" placeholder="请输入分类标志" />
       </el-form-item>
+      <el-form-item label="分类描述" prop="description">
+        <el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" />
+      </el-form-item>
       <el-form-item label="分类状态" prop="status">
         <el-radio-group v-model="formData.status">
           <el-radio
@@ -58,6 +61,7 @@ const formData = ref({
   id: undefined,
   name: undefined,
   code: undefined,
+  description: undefined,
   status: CommonStatusEnum.ENABLE,
   sort: undefined
 })
@@ -117,6 +121,7 @@ const resetForm = () => {
     id: undefined,
     name: undefined,
     code: undefined,
+    description: undefined,
     status: CommonStatusEnum.ENABLE,
     sort: undefined
   }

+ 20 - 0
src/views/bpm/model/CategoryDraggableModel.vue

@@ -192,6 +192,16 @@
                   <el-dropdown-item command="handleDefinitionList" v-if="hasPermiPdQuery">
                     历史
                   </el-dropdown-item>
+                  <el-dropdown-item
+                    command="handleReport"
+                    v-if="
+                      checkPermi(['bpm:process-instance:manager-query']) &&
+                      scope.row.processDefinition
+                    "
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    报表
+                  </el-dropdown-item>
                   <el-dropdown-item
                     command="handleChangeState"
                     v-if="hasPermiUpdate && scope.row.processDefinition"
@@ -301,6 +311,7 @@ const { t } = useI18n() // 国际化
 const { push } = useRouter() // 路由
 const userStore = useUserStoreWithOut() // 用户信息缓存
 const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
+const router = useRouter() // 路由
 
 const isModelSorting = ref(false) // 是否正处于排序状态
 const originalData = ref<ModelInfo[]>([]) // 原始数据
@@ -349,6 +360,15 @@ const handleModelCommand = (command: string, row: any) => {
     case 'handleClean':
       handleClean(row)
       break
+    case 'handleReport':
+      router.push({
+        name: 'BpmProcessInstanceReport',
+        query: {
+          processDefinitionId: row.processDefinition.id,
+          processDefinitionKey: row.key
+        }
+      })
+      break
     default:
       break
   }

+ 6 - 6
src/views/bpm/model/form/ExtraSettings.vue

@@ -216,16 +216,16 @@ const formFieldOptions4Title = computed(() => {
   })
   // 固定添加发起人 ID 字段
   cloneFormField.unshift({
-    label: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
-    value: '流程名称'
+    label: '流程名称',
+    value: ProcessVariableEnum.PROCESS_DEFINITION_NAME
   })
   cloneFormField.unshift({
-    label: ProcessVariableEnum.START_TIME,
-    value: '发起时间'
+    label: '发起时间',
+    value: ProcessVariableEnum.START_TIME
   })
   cloneFormField.unshift({
-    label: ProcessVariableEnum.START_USER_ID,
-    value: '发起人'
+    label: '发起人',
+    value: ProcessVariableEnum.START_USER_ID
   })
   return cloneFormField
 })

+ 10 - 0
src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue

@@ -114,6 +114,16 @@ const setSimpleModelNodeTaskStatus = (
       simpleModel.activityStatus = TaskStatusEnum.NOT_START
     }
   }
+  // 触发器节点
+  if (simpleModel.type === NodeType.TRIGGER_NODE) {
+    // 触发器节点,只有通过和未执行状态
+    if (finishedActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.APPROVE
+    } else {
+      simpleModel.activityStatus = TaskStatusEnum.NOT_START
+    }
+  }
+
   // 条件节点对应 SequenceFlow
   if (simpleModel.type === NodeType.CONDITION_NODE) {
     // 条件节点,只有通过和未执行状态

+ 274 - 0
src/views/bpm/processInstance/report/index.vue

@@ -0,0 +1,274 @@
+<template>
+  <doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
+
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="发起人" prop="startUserId">
+        <el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
+          <el-option
+            v-for="user in userList"
+            :key="user.id"
+            :label="user.nickname"
+            :value="user.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="流程名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入流程名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="流程状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择流程状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="发起时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="结束时间" prop="endTime">
+        <el-date-picker
+          v-model="queryParams.endTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item
+        v-for="(item, index) in formFields"
+        :key="index"
+        :label="item.title"
+        :prop="item.field"
+      >
+        <!-- TODO @lesan:目前只支持input类型的字符串搜索 -->
+        <el-input
+          :disabled="item.type !== 'input'"
+          v-model="queryParams.formFieldsParams[item.field]"
+          :placeholder="`请输入${item.title}`"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" border :data="list">
+      <el-table-column label="流程名称" align="center" prop="name" fixed="left" width="200" />
+      <el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
+      <el-table-column label="流程状态" prop="status" width="120">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="发起时间"
+        align="center"
+        prop="startTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column
+        label="结束时间"
+        align="center"
+        prop="endTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column
+        v-for="(item, index) in formFields"
+        :key="index"
+        :label="item.title"
+        :prop="item.field"
+        width="120"
+      >
+        <!-- TODO @lesan:可以根据formField的type进行展示方式的控制,现在全部以字符串 -->
+        <template #default="scope">
+          {{ scope.row.formVariables[item.field] ?? '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" fixed="right" width="180">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            v-hasPermi="['bpm:process-instance:cancel']"
+            @click="handleDetail(scope.row)"
+          >
+            详情
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            v-if="scope.row.status === 1"
+            v-hasPermi="['bpm:process-instance:query']"
+            @click="handleCancel(scope.row)"
+          >
+            取消
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import * as UserApi from '@/api/system/user'
+import * as DefinitionApi from '@/api/bpm/definition'
+import { parseFormFields } from '@/components/FormCreate/src/utils'
+import { ElMessageBox } from 'element-plus'
+
+defineOptions({ name: 'BpmProcessInstanceReport' })
+
+const router = useRouter() // 路由
+const { query } = useRoute()
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const formFields = ref()
+const processDefinitionId = query.processDefinitionId as string
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  startUserId: undefined,
+  name: '',
+  processDefinitionKey: query.processDefinitionKey,
+  status: undefined,
+  createTime: [],
+  endTime: [],
+  formFieldsParams: {}
+})
+const queryFormRef = ref() // 搜索的表单
+const userList = ref<any[]>([]) // 用户列表
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ProcessInstanceApi.getProcessInstanceManagerPage({
+      ...queryParams,
+      formFieldsParams: JSON.stringify(queryParams.formFieldsParams)
+    })
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 获取流程定义 */
+const getProcessDefinition = async () => {
+  const processDefinition = await DefinitionApi.getProcessDefinition(processDefinitionId)
+  formFields.value = parseFormCreateFields(processDefinition.formFields)
+}
+
+/** 解析表单字段 */
+const parseFormCreateFields = (formFields?: string[]) => {
+  const result: Array<Record<string, any>> = []
+  if (formFields) {
+    formFields.forEach((fieldStr: string) => {
+      parseFormFields(JSON.parse(fieldStr), result)
+    })
+  }
+  return result
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.formFieldsParams = {}
+  handleQuery()
+}
+
+/** 查看详情 */
+const handleDetail = (row) => {
+  router.push({
+    name: 'BpmProcessInstanceDetail',
+    query: {
+      id: row.id
+    }
+  })
+}
+
+/** 取消按钮操作 */
+const handleCancel = async (row) => {
+  // 二次确认
+  const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
+    confirmButtonText: t('common.ok'),
+    cancelButtonText: t('common.cancel'),
+    inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
+    inputErrorMessage: '取消原因不能为空'
+  })
+  // 发起取消
+  await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
+  message.success('取消成功')
+  // 刷新列表
+  await getList()
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  // 获取流程定义,用于 table column 的展示
+  await getProcessDefinition()
+  // 获取流程列表
+  await getList()
+  // 获取用户列表
+  userList.value = await UserApi.getSimpleUserList()
+})
+</script>

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

@@ -46,6 +46,15 @@
     <el-table v-loading="loading" :data="list">
       <!-- TODO 芋艿:增加摘要 -->
       <el-table-column align="center" label="流程名" prop="processInstanceName" min-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="流程发起人"

+ 3 - 3
src/views/bpm/task/done/index.vue

@@ -122,10 +122,10 @@
   <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">
+      <el-table-column label="摘要" prop="processInstance.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">
+          <div class="flex flex-col" v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0">
+            <div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
               <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
             </div>
           </div>

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

@@ -105,10 +105,10 @@
   <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">
+      <el-table-column label="摘要" prop="processInstance.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">
+          <div class="flex flex-col" v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0">
+            <div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
               <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
             </div>
           </div>