瀏覽代碼

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

GoldenZqqq 9 月之前
父節點
當前提交
8f7a75bf3b

+ 1 - 0
package.json

@@ -144,6 +144,7 @@
     "url": "https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues"
   },
   "homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3",
+  "web-types": "./web-types.json",
   "engines": {
     "node": ">= 16.0.0",
     "pnpm": ">=8.6.0"

+ 1 - 1
src/api/login/oauth2/index.ts

@@ -27,7 +27,7 @@ export const authorize = (
   return request.post({
     url: '/system/oauth2/authorize',
     headers: {
-      'Content-type': 'application/x-www-form-urlencoded'
+      'Content-Type': 'application/x-www-form-urlencoded'
     },
     params: {
       response_type: responseType,

+ 0 - 1
src/components/FormCreate/src/components/useApiSelect.tsx

@@ -185,7 +185,6 @@ export const useApiSelect = (option: ApiSelectProps) => {
             </el-select>
           )
         }
-        debugger
         return (
           <el-select
             class="w-1/1"

+ 22 - 2
src/components/SimpleProcessDesignerV2/src/consts.ts

@@ -79,7 +79,7 @@ export interface SimpleFlowNode {
   // 审批按钮设置
   buttonsSetting?: any[]
   // 表单权限
-  fieldsPermission?: Array<Record<string, string>>
+  fieldsPermission?: Array<Record<string, any>>
   // 审批任务超时处理
   timeoutHandler?: TimeoutHandler
   // 审批任务拒绝处理
@@ -97,7 +97,7 @@ export interface SimpleFlowNode {
   // 是否默认的条件
   defaultFlow?: boolean
   // 活动的状态,用于前端节点状态展示
-  activityStatus? : TaskStatusEnum
+  activityStatus?: TaskStatusEnum
 }
 // 候选人策略枚举 ( 用于审批节点。抄送节点 )
 export enum CandidateStrategy {
@@ -145,6 +145,14 @@ export enum CandidateStrategy {
    * 指定用户组
    */
   USER_GROUP = 40,
+  /**
+   * 表单内用户字段
+   */
+  FORM_USER = 50,
+  /**
+   * 表单内部门负责人
+   */
+  FORM_DEPT_LEADER = 51,
   /**
    * 流程表达式
    */
@@ -424,6 +432,8 @@ export const CANDIDATE_STRATEGY: DictDataVO[] = [
   { label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
   { label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
   { label: '用户组', value: CandidateStrategy.USER_GROUP },
+  { label: '表单内用户字段', value: CandidateStrategy.FORM_USER },
+  { label: '表单内部门负责人', value: CandidateStrategy.FORM_DEPT_LEADER },
   { label: '流程表达式', value: CandidateStrategy.EXPRESSION }
 ]
 // 审批节点 的审批类型
@@ -548,3 +558,13 @@ export const MULTI_LEVEL_DEPT: DictDataVO = [
   { label: '第 14 级部门', value: 14 },
   { label: '第 15 级部门', value: 15 }
 ]
+
+/**
+ * 流程实例的变量枚举
+ */
+export enum ProcessVariableEnum {
+  /**
+   * 发起用户 ID
+   */
+  START_USER_ID = 'PROCESS_START_USER_ID'
+}

+ 116 - 86
src/components/SimpleProcessDesignerV2/src/node.ts

@@ -14,7 +14,8 @@ import {
   NODE_DEFAULT_NAME,
   AssignStartUserHandlerType,
   AssignEmptyHandlerType,
-  FieldPermissionType
+  FieldPermissionType,
+  ProcessVariableEnum
 } from './consts'
 export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
   const node = ref<SimpleFlowNode>(props.flowNode)
@@ -27,12 +28,68 @@ export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlo
   return node
 }
 
+// 解析 formCreate 所有表单字段, 并返回
+const parseFormCreateFields = (formFields?: string[]) => {
+  const result: Array<Record<string, any>> = []
+  if (formFields) {
+    formFields.forEach((fieldStr: string) => {
+      parseFields(JSON.parse(fieldStr), result)
+    })
+  }
+  // 固定添加发起人 ID 字段
+  result.unshift({
+    field: ProcessVariableEnum.START_USER_ID,
+    title: '发起人',
+    type: 'UserSelect',
+    required: true
+  })
+  return result
+}
+
+// TODO @jason:parse 方法,是不是搞到 formCreate.ts。统一维护管理
+const parseFields = (
+  rule: Record<string, any>,
+  fields: Array<Record<string, any>>,
+  parentTitle: string = ''
+) => {
+  const { type, field, $required, title: tempTitle, children } = rule
+  if (field && tempTitle) {
+    let title = tempTitle
+    if (parentTitle) {
+      title = `${parentTitle}.${tempTitle}`
+    }
+    // TODO @jason:按照微信讨论的,非 $required 显示,但是 disable 不可选择
+    let required = false
+    if ($required) {
+      required = true
+    }
+    fields.push({
+      field,
+      title,
+      type,
+      required
+    })
+    // TODO 子表单 需要处理子表单字段
+    // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
+    //   // 解析子表单的字段
+    //   rule.props.rule.forEach((item) => {
+    //     parseFields(item, fieldsPermission, title)
+    //   })
+    // }
+  }
+  if (children && Array.isArray(children)) {
+    children.forEach((rule) => {
+      parseFields(rule, fields)
+    })
+  }
+}
+
 /**
  * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
  */
 export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
   // 字段权限配置. 需要有 field, title,  permissioin 属性
-  const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
+  const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
 
   const formType = inject<Ref<number>>('formType') // 表单类型
 
@@ -45,49 +102,26 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
   }
   // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
   const getDefaultFieldsPermission = (formFields?: string[]) => {
-    const defaultFieldsPermission: Array<Record<string, string>> = []
+    let defaultFieldsPermission: Array<Record<string, any>> = []
     if (formFields) {
-      formFields.forEach((fieldStr: string) => {
-        parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission)
+      defaultFieldsPermission = parseFormCreateFields(formFields).map((item) => {
+        return {
+          field: item.field,
+          title: item.title,
+          permission: defaultPermission
+        }
       })
     }
     return defaultFieldsPermission
   }
-  // 解析字段。赋给默认权限
-  const parseFieldsSetDefaultPermission = (
-    rule: Record<string, any>,
-    fieldsPermission: Array<Record<string, string>>,
-    parentTitle: string = ''
-  ) => {
-    const { /**type,*/ field, title: tempTitle, children } = rule
-    if (field && tempTitle) {
-      let title = tempTitle
-      if (parentTitle) {
-        title = `${parentTitle}.${tempTitle}`
-      }
-      fieldsPermission.push({
-        field,
-        title,
-        permission: defaultPermission
-      })
-      // TODO 子表单 需要处理子表单字段
-      // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
-      //   // 解析子表单的字段
-      //   rule.props.rule.forEach((item) => {
-      //     parseFieldsSetDefaultPermission(item, fieldsPermission, title)
-      //   })
-      // }
-    }
-    if (children && Array.isArray(children)) {
-      children.forEach((rule) => {
-        parseFieldsSetDefaultPermission(rule, fieldsPermission)
-      })
-    }
-  }
+
+  // 获取表单的所有字段,作为下拉框选项
+  const formFieldOptions = parseFormCreateFields(unref(formFields))
 
   return {
     formType,
     fieldsPermissionConfig,
+    formFieldOptions,
     getNodeConfigFormFields
   }
 }
@@ -95,50 +129,8 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
  * @description 获取表单的字段
  */
 export function useFormFields() {
-  // 解析后的表单字段
   const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
-  const parseFormFields = () => {
-    const parsedFormFields: Array<Record<string, string>> = []
-    if (formFields) {
-      formFields.value.forEach((fieldStr: string) => {
-        parseField(JSON.parse(fieldStr), parsedFormFields)
-      })
-    }
-    return parsedFormFields
-  }
-  // 解析字段。
-  const parseField = (
-    rule: Record<string, any>,
-    parsedFormFields: Array<Record<string, string>>,
-    parentTitle: string = ''
-  ) => {
-    const { field, title: tempTitle, children, type } = rule
-    if (field && tempTitle) {
-      let title = tempTitle
-      if (parentTitle) {
-        title = `${parentTitle}.${tempTitle}`
-      }
-      parsedFormFields.push({
-        field,
-        title,
-        type
-      })
-      // TODO 子表单 需要处理子表单字段
-      // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
-      //   // 解析子表单的字段
-      //   rule.props.rule.forEach((item) => {
-      //     parseFieldsSetDefaultPermission(item, fieldsPermission, title)
-      //   })
-      // }
-    }
-    if (children && Array.isArray(children)) {
-      children.forEach((rule) => {
-        parseField(rule, parsedFormFields)
-      })
-    }
-  }
-
-  return parseFormFields()
+  return parseFormCreateFields(unref(formFields))
 }
 
 export type UserTaskFormType = {
@@ -152,6 +144,8 @@ export type UserTaskFormType = {
   userGroups?: number[] // 用户组
   postIds?: number[] // 岗位
   expression?: string // 流程表达式
+  userFieldOnForm?: string // 表单内用户字段
+  deptFieldOnForm?: string // 表单内部门字段
   approveRatio?: number
   rejectHandlerType?: RejectHandlerType
   returnNodeId?: string
@@ -174,6 +168,8 @@ export type CopyTaskFormType = {
   userIds?: number[] // 用户
   userGroups?: number[] // 用户组
   postIds?: number[] // 岗位
+  userFieldOnForm?: string // 表单内用户字段
+  deptFieldOnForm?: string // 表单内部门字段
   expression?: string // 流程表达式
 }
 
@@ -187,6 +183,7 @@ export function useNodeForm(nodeType: NodeType) {
   const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
   const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
   const deptTreeOptions = inject('deptTree') // 部门树
+  const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
   const configForm = ref<UserTaskFormType | CopyTaskFormType>()
   if (nodeType === NodeType.USER_TASK_NODE) {
     configForm.value = {
@@ -282,6 +279,18 @@ export function useNodeForm(nodeType: NodeType) {
       }
     }
 
+    // 表单内用户字段
+    if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) {
+      const formFieldOptions = parseFormCreateFields(unref(formFields))
+      const item = formFieldOptions.find((item) => item.field === configForm.value?.userFieldOnForm)
+      showText = `表单用户:${item?.title}`
+    }
+
+    // 表单内部门负责人
+    if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER) {
+      showText = `表单内部门负责人`
+    }
+
     // 发起人自选
     if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
       showText = `发起人自选`
@@ -328,6 +337,9 @@ export function useNodeForm(nodeType: NodeType) {
       case CandidateStrategy.USER_GROUP:
         candidateParam = configForm.value.userGroups!.join(',')
         break
+      case CandidateStrategy.FORM_USER:
+        candidateParam = configForm.value.userFieldOnForm!
+        break
       case CandidateStrategy.EXPRESSION:
         candidateParam = configForm.value.expression!
         break
@@ -347,6 +359,13 @@ export function useNodeForm(nodeType: NodeType) {
         candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
         break
       }
+      // 表单内部门的负责人
+      case CandidateStrategy.FORM_DEPT_LEADER: {
+        // 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级
+        const deptFieldOnForm = configForm.value.deptFieldOnForm!
+        candidateParam = deptFieldOnForm.concat('|' + configForm.value.deptLevel + '')
+        break
+      }
       default:
         break
     }
@@ -376,6 +395,9 @@ export function useNodeForm(nodeType: NodeType) {
       case CandidateStrategy.USER_GROUP:
         configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
         break
+      case CandidateStrategy.FORM_USER:
+        configForm.value.userFieldOnForm = candidateParam
+        break
       case CandidateStrategy.EXPRESSION:
         configForm.value.expression = candidateParam
         break
@@ -396,6 +418,14 @@ export function useNodeForm(nodeType: NodeType) {
         configForm.value.deptLevel = +paramArray[1]
         break
       }
+      // 表单内的部门负责人
+      case CandidateStrategy.FORM_DEPT_LEADER: {
+        // 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级
+        const paramArray = candidateParam.split('|')
+        configForm.value.deptFieldOnForm = paramArray[0]
+        configForm.value.deptLevel = +paramArray[1]
+        break
+      }
       default:
         break
     }
@@ -481,22 +511,22 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
 /**
  * @description 根据节点任务状态,获取节点任务状态样式
  */
-export function  useTaskStatusClass(taskStatus: TaskStatusEnum | undefined) : string {
+export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): string {
   if (!taskStatus) {
     return ''
   }
-  if (taskStatus === TaskStatusEnum.APPROVE ) {
+  if (taskStatus === TaskStatusEnum.APPROVE) {
     return 'status-pass'
   }
-  if (taskStatus === TaskStatusEnum.RUNNING ) {
+  if (taskStatus === TaskStatusEnum.RUNNING) {
     return 'status-running'
   }
-  if (taskStatus === TaskStatusEnum.REJECT ) {
+  if (taskStatus === TaskStatusEnum.REJECT) {
     return 'status-reject'
   }
-  if (taskStatus === TaskStatusEnum.CANCEL ) {
+  if (taskStatus === TaskStatusEnum.CANCEL) {
     return 'status-cancel'
   }
-   
-  return '';
+
+  return ''
 }

+ 76 - 9
src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue

@@ -60,7 +60,8 @@
             <el-form-item
               v-if="
                 configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
-                configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER
+                configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
               "
               label="指定部门"
               prop="deptIds"
@@ -122,7 +123,55 @@
                 />
               </el-select>
             </el-form-item>
-
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
+              label="表单内用户字段"
+              prop="userFieldOnForm"
+            >
+              <el-select v-model="configForm.userFieldOnForm" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in userFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
+              label="表单内部门字段"
+              prop="deptFieldOnForm"
+            >
+              <el-select v-model="configForm.deptFieldOnForm" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in deptFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+                configForm.candidateStrategy ==
+                  CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+              "
+              :label="deptLevelLabel!"
+              prop="deptLevel"
+              span="24"
+            >
+              <el-select v-model="configForm.deptLevel" clearable>
+                <el-option
+                  v-for="(item, index) in MULTI_LEVEL_DEPT"
+                  :key="index"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
             <el-form-item
               v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
               label="流程表达式"
@@ -201,7 +250,8 @@ import {
   CandidateStrategy,
   NodeType,
   CANDIDATE_STRATEGY,
-  FieldPermissionType
+  FieldPermissionType,
+  MULTI_LEVEL_DEPT
 } from '../consts'
 import {
   useWatchNode,
@@ -221,6 +271,15 @@ const props = defineProps({
     required: true
   }
 })
+const deptLevelLabel = computed(() => {
+  let label = '部门负责人来源'
+  if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+    label = label + '(指定部门向上)'
+  } else {
+    label = label + '(发起人部门向上)'
+  }
+  return label
+})
 // 抽屉配置
 const { settingVisible, closeDrawer, openDrawer } = useDrawer()
 // 当前节点
@@ -230,9 +289,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_
 // 激活的 Tab 标签页
 const activeTabName = ref('user')
 // 表单字段权限配置
-const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
-  FieldPermissionType.READ
-)
+const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
+  useFormFieldsPermission(FieldPermissionType.READ)
+// 表单内用户字段选项, 必须是必填和用户选择器
+const userFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.required && item.type === 'UserSelect')
+})
+// 表单内部门字段选项, 必须是必填和部门选择器
+const deptFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.required && item.type === 'DeptSelect')
+})
 // 抄送人表单配置
 const formRef = ref() // 表单 Ref
 // 表单校验规则
@@ -243,6 +309,8 @@ const formRules = reactive({
   deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
   userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
   postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
+  userFieldOnForm: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
+  deptFieldOnForm: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
   expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }]
 })
 
@@ -260,9 +328,7 @@ const {
 const configForm = tempConfigForm as Ref<CopyTaskFormType>
 // 抄送人策略, 去掉发起人自选 和 发起人自己
 const copyUserStrategies = computed(() => {
-  return CANDIDATE_STRATEGY.filter(
-    (item) => item.value !== CandidateStrategy.START_USER
-  )
+  return CANDIDATE_STRATEGY.filter((item) => item.value !== CandidateStrategy.START_USER)
 })
 // 改变抄送人设置策略
 const changeCandidateStrategy = () => {
@@ -272,6 +338,7 @@ const changeCandidateStrategy = () => {
   configForm.value.postIds = []
   configForm.value.userGroups = []
   configForm.value.deptLevel = 1
+  configForm.value.userFieldOnForm = ''
 }
 // 保存配置
 const saveConfig = async () => {

+ 66 - 23
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -56,7 +56,6 @@
                 </el-radio>
               </el-radio-group>
             </el-form-item>
-
             <el-form-item
               v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
               label="指定角色"
@@ -94,25 +93,6 @@
                 show-checkbox
               />
             </el-form-item>
-            <el-form-item
-              v-if="
-                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
-                configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
-                configForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
-              "
-              :label="deptLevelLabel!"
-              prop="deptLevel"
-              span="24"
-            >
-              <el-select v-model="configForm.deptLevel" clearable>
-                <el-option
-                  v-for="(item, index) in MULTI_LEVEL_DEPT"
-                  :key="index"
-                  :label="item.label"
-                  :value="item.value"
-                />
-              </el-select>
-            </el-form-item>
             <el-form-item
               v-if="configForm.candidateStrategy == CandidateStrategy.POST"
               label="指定岗位"
@@ -157,6 +137,55 @@
                 />
               </el-select>
             </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
+              label="表单内用户字段"
+              prop="userFieldOnForm"
+            >
+              <el-select v-model="configForm.userFieldOnForm" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in userFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
+              label="表单内部门字段"
+              prop="deptFieldOnForm"
+            >
+              <el-select v-model="configForm.deptFieldOnForm" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in deptFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+                configForm.candidateStrategy ==
+                  CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+              "
+              :label="deptLevelLabel!"
+              prop="deptLevel"
+              span="24"
+            >
+              <el-select v-model="configForm.deptLevel" clearable>
+                <el-option
+                  v-for="(item, index) in MULTI_LEVEL_DEPT"
+                  :key="index"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
             <!-- TODO @jason:后续要支持选择已经存好的表达式 -->
             <el-form-item
               v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
@@ -468,6 +497,8 @@ const deptLevelLabel = computed(() => {
   let label = '部门负责人来源'
   if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
     label = label + '(指定部门向上)'
+  } else if (configForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
+    label = label + '(表单内部门向上)'
   } else {
     label = label + '(发起人部门向上)'
   }
@@ -482,9 +513,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_
 // 激活的 Tab 标签页
 const activeTabName = ref('user')
 // 表单字段权限设置
-const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
-  FieldPermissionType.READ
-)
+const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
+  useFormFieldsPermission(FieldPermissionType.READ)
+// 表单内用户字段选项, 必须是必填和用户选择器
+const userFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.required && item.type === 'UserSelect')
+})
+// 表单内部门字段选项, 必须是必填和部门选择器
+const deptFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.required && item.type === 'DeptSelect')
+})
 // 操作按钮设置
 const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
   useButtonsSetting()
@@ -498,6 +536,8 @@ const formRules = reactive({
   roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
   deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
   userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
+  userFieldOnForm: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
+  deptFieldOnForm: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
   postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
   expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }],
   approveMethod: [{ required: true, message: '多人审批方式不能为空', trigger: 'change' }],
@@ -533,6 +573,9 @@ const changeCandidateStrategy = () => {
   configForm.value.postIds = []
   configForm.value.userGroups = []
   configForm.value.deptLevel = 1
+  // TODO @jason:是不是 userFieldOnForm => formUser;deptFieldOnForm => formDeptLeader;原因是:想通前缀,好管理点
+  configForm.value.userFieldOnForm = ''
+  configForm.value.deptFieldOnForm = ''
   configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE
 }
 

+ 14 - 0
src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json

@@ -1211,6 +1211,20 @@
           "isAttr": true
         }
       ]
+    },
+    {
+      "name": "AssignStartUserHandlerType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
+      },
+      "properties": [
+      {
+        "name": "value",
+        "type": "Integer",
+        "isBody": true
+      }
+      ]
     }
   ],
   "emumerations": []

+ 4 - 0
src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue

@@ -54,6 +54,10 @@
         <template #title><Icon icon="ep:promotion" />其他</template>
         <element-other-config :id="elementId" />
       </el-collapse-item>
+      <el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig">
+        <template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template>
+        <element-custom-config :id="elementId" :type="elementType" />
+      </el-collapse-item>
     </el-collapse>
   </div>
 </template>

+ 84 - 0
src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue

@@ -0,0 +1,84 @@
+<!-- UserTask 自定义配置:
+     1. 审批人与提交人为同一人时
+-->
+<template>
+  <div class="panel-tab__content">
+    <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
+    <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
+      <div class="flex-col">
+        <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
+          <el-radio :key="item.value" :value="item.value" :label="item.label" />
+        </div>
+      </div>
+    </el-radio-group>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ASSIGN_START_USER_HANDLER_TYPES } from '@/components/SimpleProcessDesignerV2/src/consts'
+
+defineOptions({ name: 'ElementCustomConfig' })
+const props = defineProps({
+  id: String,
+  type: String
+})
+const prefix = inject('prefix')
+
+const elExtensionElements = ref()
+const assignStartUserHandlerTypeEl = ref()
+const assignStartUserHandlerType = ref()
+const otherExtensions = ref()
+const bpmnElement = ref()
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+const resetAttributesList = () => {
+  bpmnElement.value = bpmnInstances().bpmnElement
+
+  // 获取元素扩展属性 或者 创建扩展属性
+  elExtensionElements.value =
+    bpmnElement.value.businessObject?.extensionElements ??
+    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+
+  assignStartUserHandlerTypeEl.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
+    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
+  assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
+
+  // 保留剩余扩展元素,便于后面更新该元素对应属性
+  otherExtensions.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type !== `${prefix}:AssignStartUserHandlerType`
+    ) ?? []
+
+  // 更新元素扩展属性,避免后续报错
+  updateElementExtensions()
+}
+
+const updateAssignStartUserHandlerType = () => {
+  assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
+
+  updateElementExtensions()
+}
+
+const updateElementExtensions = () => {
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: otherExtensions.value.concat([assignStartUserHandlerTypeEl.value])
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+}
+
+watch(
+  () => props.id,
+  (val) => {
+    val &&
+      val.length &&
+      nextTick(() => {
+        resetAttributesList()
+      })
+  },
+  { immediate: true }
+)
+</script>

+ 3 - 3
src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue

@@ -268,9 +268,9 @@ const bpmnInstances = () => (window as any)?.bpmnInstances
 const resetFormList = () => {
   bpmnELement.value = bpmnInstances().bpmnElement
   formKey.value = bpmnELement.value.businessObject.formKey
-  if (formKey.value?.length > 0) {
-    formKey.value = parseInt(formKey.value)
-  }
+  // if (formKey.value?.length > 0) {
+  //   formKey.value = parseInt(formKey.value)
+  // }
   // 获取元素扩展属性 或者 创建扩展属性
   elExtensionElements.value =
     bpmnELement.value.businessObject.get('extensionElements') ||

+ 1 - 1
src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue

@@ -80,7 +80,7 @@ const resetAttributesList = () => {
   otherExtensionList.value = [] // 其他扩展配置
   bpmnElementProperties.value =
     // bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
-    bpmnElement.value.businessObject?.extensionElements?.values.filter((ex) => {
+    bpmnElement.value.businessObject?.extensionElements?.values?.filter((ex) => {
       if (ex.$type !== `${prefix}:Properties`) {
         otherExtensionList.value.push(ex)
       }

+ 4 - 8
src/config/axios/index.ts

@@ -5,16 +5,12 @@ import { config } from './config'
 const { default_headers } = config
 
 const request = (option: any) => {
-  const { url, method, params, data, headersType, responseType, ...config } = option
+  const { headersType, headers, ...otherOption } = option
   return service({
-    url: url,
-    method,
-    params,
-    data,
-    ...config,
-    responseType: responseType,
+    ...otherOption,
     headers: {
-      'Content-Type': headersType || default_headers
+      'Content-Type': headersType || default_headers,
+      ...headers
     }
   })
 }

+ 20 - 24
src/config/axios/service.ts

@@ -1,10 +1,4 @@
-import axios, {
-  AxiosError,
-  AxiosInstance,
-  AxiosRequestHeaders,
-  AxiosResponse,
-  InternalAxiosRequestConfig
-} from 'axios'
+import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
 
 import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
 import qs from 'qs'
@@ -37,7 +31,11 @@ const whiteList: string[] = ['/login', '/refresh-token']
 const service: AxiosInstance = axios.create({
   baseURL: base_url, // api 的 base_url
   timeout: request_timeout, // 请求超时时间
-  withCredentials: false // 禁用 Cookie 等信息
+  withCredentials: false, // 禁用 Cookie 等信息
+  // 自定义参数序列化函数
+  paramsSerializer: (params) => {
+    return qs.stringify(params, { allowDots: true })
+  }
 })
 
 // request拦截器
@@ -52,28 +50,26 @@ service.interceptors.request.use(
       }
     })
     if (getAccessToken() && !isToken) {
-      ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
+      config.headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
     }
     // 设置租户
     if (tenantEnable && tenantEnable === 'true') {
       const tenantId = getTenantId()
-      if (tenantId) (config as Recordable).headers['tenant-id'] = tenantId
+      if (tenantId) config.headers['tenant-id'] = tenantId
     }
-    const params = config.params || {}
-    const data = config.data || false
-    if (
-      config.method?.toUpperCase() === 'POST' &&
-      (config.headers as AxiosRequestHeaders)['Content-Type'] ===
-        'application/x-www-form-urlencoded'
-    ) {
-      config.data = qs.stringify(data)
+    const method = config.method?.toUpperCase()
+    // 防止 GET 请求缓存
+    if (method === 'GET') {
+      config.headers['Cache-Control'] = 'no-cache'
+      config.headers['Pragma'] = 'no-cache'
     }
-    // get参数编码
-    if (config.method?.toUpperCase() === 'GET' && params) {
-      config.params = {}
-      const paramsStr = qs.stringify(params, { allowDots: true })
-      if (paramsStr) {
-        config.url = config.url + '?' + paramsStr
+    // 自定义参数序列化函数
+    else if (method === 'POST') {
+      const contentType = config.headers['Content-Type'] || config.headers['content-type']
+      if (contentType === 'application/x-www-form-urlencoded') {
+        if (config.data && typeof config.data !== 'string') {
+          config.data = qs.stringify(config.data)
+        }
       }
     }
     return config

+ 1 - 0
src/layout/components/Setting/src/Setting.vue

@@ -297,5 +297,6 @@ $prefix-cls: #{$namespace}-setting;
 
 .#{$prefix-cls} {
   border-radius: 6px 0 0 6px;
+  z-index: 1200;/*修正没有z-index会被表格层覆盖,值不要超过4000*/
 }
 </style>

+ 2 - 1
src/views/bpm/form/index.vue

@@ -143,8 +143,9 @@ const openForm = (id?: number) => {
   const toRouter: { name: string; query?: { id: number } } = {
     name: 'BpmFormEditor'
   }
+  console.log(typeof id)
   // 表单新建的时候id传的是event需要排除
-  if (typeof id === 'number') {
+  if (typeof id === 'number' || typeof id === 'string') {
     toRouter.query = {
       id
     }

+ 16 - 14
src/views/bpm/processInstance/create/index.vue

@@ -85,7 +85,7 @@
 <script lang="ts" setup>
 import * as DefinitionApi from '@/api/bpm/definition'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { CategoryApi } from '@/api/bpm/category'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
 import { groupBy } from 'lodash-es'
 
@@ -221,14 +221,14 @@ const handleSelect = async (row, formVariables?) => {
   processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
 }
 
-/** 处理滚动事件 */
-const handleScroll = (e) => {
+/** 处理滚动事件,和左侧分类联动 */
+const handleScroll = (e: any) => {
   // 直接使用事件对象获取滚动位置
   const scrollTop = e.scrollTop
 
   // 获取所有分类区域的位置信息
   const categoryPositions = categoryList.value
-    .map((category) => {
+    .map((category: CategoryVO) => {
       const categoryRef = proxy.$refs[`category-${category.code}`]
       if (categoryRef?.[0]) {
         return {
@@ -254,29 +254,31 @@ const handleScroll = (e) => {
 
   // 更新当前 active 的分类
   if (currentCategory && categoryActive.value.code !== currentCategory.code) {
-    categoryActive.value = categoryList.value.find((c) => c.code === currentCategory.code)
+    categoryActive.value = categoryList.value.find(
+      (c: CategoryVO) => c.code === currentCategory.code
+    )
   }
 }
 
-/** 初始化 */
-onMounted(() => {
-  getList()
-})
-
-/** 过滤出有流程的分类列表 */
+/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
 const availableCategories = computed(() => {
   if (!categoryList.value?.length || !processDefinitionGroup.value) {
     return []
   }
-  
+
   // 获取所有有流程的分类代码
   const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
-  
+
   // 过滤出有流程的分类
-  return categoryList.value.filter(category => 
+  return categoryList.value.filter((category: CategoryVO) =>
     availableCategoryCodes.includes(category.code)
   )
 })
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
 </script>
 
 <style lang="scss" scoped>

+ 19 - 0
web-types.json

@@ -0,0 +1,19 @@
+{
+  "$schema": "https://json.schemastore.org/web-types",
+  "framework": "vue",
+  "name": "name written in package.json",
+  "version": "version written in package.json",
+  "contributions": {
+    "html": {
+      "types-syntax": "typescript",
+      "attributes": [
+        {
+          "name": "v-hasPermi"
+        },
+        {
+          "name": "v-hasRole"
+        }
+      ]
+    }
+  }
+}