From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue |  470 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 470 insertions(+), 0 deletions(-)

diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
new file mode 100644
index 0000000..e4091b9
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
@@ -0,0 +1,470 @@
+<template>
+  <el-form label-width="120px">
+    <el-form-item label="瑙勫垯绫诲瀷" prop="candidateStrategy">
+      <el-select
+        v-model="userTaskForm.candidateStrategy"
+        clearable
+        style="width: 100%"
+        @change="changeCandidateStrategy"
+      >
+        <el-option
+          v-for="(dict, index) in CANDIDATE_STRATEGY"
+          :key="index"
+          :label="dict.label"
+          :value="dict.value"
+        />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="userTaskForm.candidateStrategy == CandidateStrategy.ROLE"
+      label="鎸囧畾瑙掕壊"
+      prop="candidateParam"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        multiple
+        style="width: 100%"
+        @change="updateElementTask"
+      >
+        <el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="
+        userTaskForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
+      "
+      label="鎸囧畾閮ㄩ棬"
+      prop="candidateParam"
+      span="24"
+    >
+      <el-tree-select
+        ref="treeRef"
+        v-model="userTaskForm.candidateParam"
+        :data="deptTreeOptions"
+        :props="defaultProps"
+        empty-text="鍔犺浇涓紝璇风◢鍚�"
+        multiple
+        node-key="id"
+        show-checkbox
+        @change="updateElementTask"
+      />
+    </el-form-item>
+    <el-form-item
+      v-if="userTaskForm.candidateStrategy == CandidateStrategy.POST"
+      label="鎸囧畾宀椾綅"
+      prop="candidateParam"
+      span="24"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        multiple
+        style="width: 100%"
+        @change="updateElementTask"
+      >
+        <el-option v-for="item in postOptions" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="userTaskForm.candidateStrategy == CandidateStrategy.USER"
+      label="鎸囧畾鐢ㄦ埛"
+      prop="candidateParam"
+      span="24"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        multiple
+        style="width: 100%"
+        @change="updateElementTask"
+      >
+        <el-option
+          v-for="item in userOptions"
+          :key="item.id"
+          :label="item.nickname"
+          :value="item.id"
+        />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
+      label="鎸囧畾鐢ㄦ埛缁�"
+      prop="candidateParam"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        multiple
+        style="width: 100%"
+        @change="updateElementTask"
+      >
+        <el-option
+          v-for="item in userGroupOptions"
+          :key="item.id"
+          :label="item.name"
+          :value="item.id"
+        />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
+      label="琛ㄥ崟鍐呯敤鎴峰瓧娈�"
+      prop="formUser"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        style="width: 100%"
+        @change="handleFormUserChange"
+      >
+        <el-option
+          v-for="(item, idx) in userFieldOnFormOptions"
+          :key="idx"
+          :label="item.title"
+          :value="item.field"
+          :disabled="!item.required"
+        />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
+      label="琛ㄥ崟鍐呴儴闂ㄥ瓧娈�"
+      prop="formDept"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        style="width: 100%"
+        @change="updateElementTask"
+      >
+        <el-option
+          v-for="(item, idx) in deptFieldOnFormOptions"
+          :key="idx"
+          :label="item.title"
+          :value="item.field"
+          :disabled="!item.required"
+        />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="
+        userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+      "
+      :label="deptLevelLabel!"
+      prop="deptLevel"
+      span="24"
+    >
+      <el-select v-model="deptLevel" clearable @change="updateElementTask">
+        <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="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
+      label="娴佺▼琛ㄨ揪寮�"
+      prop="candidateParam"
+    >
+      <el-input
+        type="textarea"
+        v-model="userTaskForm.candidateParam[0]"
+        clearable
+        style="width: 100%"
+        @change="updateElementTask"
+      />
+      <XButton
+        class="!w-1/1 mt-5px"
+        type="success"
+        preIcon="ep:select"
+        title="閫夋嫨琛ㄨ揪寮�"
+        size="small"
+        @click="openProcessExpressionDialog"
+      />
+      <!-- 閫夋嫨寮圭獥 -->
+      <ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
+    </el-form-item>
+
+    <el-form-item label="璺宠繃琛ㄨ揪寮�" prop="skipExpression">
+      <el-input
+        type="textarea"
+        v-model="userTaskForm.skipExpression"
+        clearable
+        style="width: 100%"
+        @change="updateSkipExpression"
+      />
+    </el-form-item>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import {
+  CANDIDATE_STRATEGY,
+  CandidateStrategy,
+  FieldPermissionType,
+  MULTI_LEVEL_DEPT
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as RoleApi from '@/api/system/role'
+import * as DeptApi from '@/api/system/dept'
+import * as PostApi from '@/api/system/post'
+import * as UserApi from '@/api/system/user'
+import * as UserGroupApi from '@/api/bpm/userGroup'
+import ProcessExpressionDialog from './ProcessExpressionDialog.vue'
+import { ProcessExpressionVO } from '@/api/bpm/processExpression'
+import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
+
+defineOptions({ name: 'UserTask' })
+const props = defineProps({
+  id: String,
+  type: String
+})
+const prefix = inject('prefix')
+const userTaskForm = ref({
+  candidateStrategy: undefined, // 鍒嗛厤瑙勫垯
+  candidateParam: [], // 鍒嗛厤閫夐」
+  skipExpression: '' // 璺宠繃琛ㄨ揪寮�
+})
+const bpmnElement = ref()
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+const roleOptions = ref<RoleApi.RoleVO[]>([]) // 瑙掕壊鍒楄〃
+const deptTreeOptions = ref() // 閮ㄩ棬鏍�
+const postOptions = ref<PostApi.PostVO[]>([]) // 宀椾綅鍒楄〃
+const userOptions = ref<UserApi.UserVO[]>([]) // 鐢ㄦ埛鍒楄〃
+const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 鐢ㄦ埛缁勫垪琛�
+
+const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ)
+// 琛ㄥ崟鍐呯敤鎴峰瓧娈甸�夐」, 蹇呴』鏄繀濉拰鐢ㄦ埛閫夋嫨鍣�
+const userFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'UserSelect')
+})
+// 琛ㄥ崟鍐呴儴闂ㄥ瓧娈甸�夐」, 蹇呴』鏄繀濉拰閮ㄩ棬閫夋嫨鍣�
+const deptFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'DeptSelect')
+})
+
+const deptLevel = ref(1)
+const deptLevelLabel = computed(() => {
+  let label = '閮ㄩ棬璐熻矗浜烘潵婧�'
+  if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+    label = label + '(鎸囧畾閮ㄩ棬鍚戜笂)'
+  } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
+    label = label + '(琛ㄥ崟鍐呴儴闂ㄥ悜涓�)'
+  } else {
+    label = label + '(鍙戣捣浜洪儴闂ㄥ悜涓�)'
+  }
+  return label
+})
+
+const otherExtensions = ref()
+
+const resetTaskForm = () => {
+  const businessObject = bpmnElement.value.businessObject
+  if (!businessObject) {
+    return
+  }
+
+  const extensionElements =
+    businessObject?.extensionElements ??
+    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+  userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
+    (ex) => ex.$type === `${prefix}:CandidateStrategy`
+  )?.[0]?.value
+  const candidateParamStr = extensionElements.values?.filter(
+    (ex) => ex.$type === `${prefix}:CandidateParam`
+  )?.[0]?.value
+  if (candidateParamStr && candidateParamStr.length > 0) {
+    if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
+      // 鐗规畩锛氭祦绋嬭〃杈惧紡锛屽彧鏈変竴涓� input 杈撳叆妗�
+      userTaskForm.value.candidateParam = [candidateParamStr]
+    } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+      // 鐗规畩锛氬绾т笉閮ㄩ棬璐熻矗浜猴紝闇�瑕侀�氳繃'|'鍒嗗壊
+      userTaskForm.value.candidateParam = candidateParamStr
+        .split('|')[0]
+        .split(',')
+        .map((item) => {
+          // 濡傛灉鏁板瓧瓒呭嚭浜嗘渶澶у畨鍏ㄦ暣鏁拌寖鍥达紝鍒欏皢鍏朵綔涓哄瓧绗︿覆澶勭悊
+          let num = Number(item)
+          return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
+        })
+      deptLevel.value = +candidateParamStr.split('|')[1]
+    } else if (
+      userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+      userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
+    ) {
+      userTaskForm.value.candidateParam = +candidateParamStr
+      deptLevel.value = +candidateParamStr
+    } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
+      userTaskForm.value.candidateParam = candidateParamStr.split('|')[0]
+      deptLevel.value = +candidateParamStr.split('|')[1]
+    } else {
+      userTaskForm.value.candidateParam = candidateParamStr.split(',').map((item) => {
+        // 濡傛灉鏁板瓧瓒呭嚭浜嗘渶澶у畨鍏ㄦ暣鏁拌寖鍥达紝鍒欏皢鍏朵綔涓哄瓧绗︿覆澶勭悊
+        let num = Number(item)
+        return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
+      })
+    }
+  } else {
+    userTaskForm.value.candidateParam = []
+  }
+
+  otherExtensions.value =
+    extensionElements.values?.filter(
+      (ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
+    ) ?? []
+
+  // 璺宠繃琛ㄨ揪寮�
+  if (businessObject.skipExpression != undefined) {
+    userTaskForm.value.skipExpression = businessObject.skipExpression
+  } else {
+    userTaskForm.value.skipExpression = ''
+  }
+
+  // 鏀圭敤閫氳繃extensionElements鏉ュ瓨鍌ㄦ暟鎹�
+  return
+  if (businessObject.candidateStrategy != undefined) {
+    userTaskForm.value.candidateStrategy = parseInt(businessObject.candidateStrategy) as any
+  } else {
+    userTaskForm.value.candidateStrategy = undefined
+  }
+  if (businessObject.candidateParam && businessObject.candidateParam.length > 0) {
+    if (userTaskForm.value.candidateStrategy === 60) {
+      // 鐗规畩锛氭祦绋嬭〃杈惧紡锛屽彧鏈変竴涓� input 杈撳叆妗�
+      userTaskForm.value.candidateParam = [businessObject.candidateParam]
+    } else {
+      userTaskForm.value.candidateParam = businessObject.candidateParam
+        .split(',')
+        .map((item) => item)
+    }
+  } else {
+    userTaskForm.value.candidateParam = []
+  }
+}
+
+/** 鏇存柊 candidateStrategy 瀛楁鏃讹紝闇�瑕佹竻绌� candidateParam锛屽苟瑙﹀彂 bpmn 鍥炬洿鏂� */
+const changeCandidateStrategy = () => {
+  userTaskForm.value.candidateParam = []
+  deptLevel.value = 1
+  // 娉ㄩ噴 by 鑺嬭壙锛氳繖涓氦浜掑緢澶氱敤鎴峰弽棣堣垂瑙o紝https://t.zsxq.com/xNmas 鎵�浠ユ殏鏃跺睆钄�
+  // if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
+  //   // 鐗规畩澶勭悊琛ㄥ崟鍐呯敤鎴峰瓧娈碉紝褰撳彧鏈夊彂璧蜂汉閫夐」鏃跺簲閫変腑鍙戣捣浜�
+  //   if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
+  //     userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
+  //   }
+  // }
+  updateElementTask()
+}
+
+/** 閫変腑鏌愪釜 options 鏃跺�欙紝鏇存柊 bpmn 鍥�  */
+const updateElementTask = () => {
+  let candidateParam =
+    userTaskForm.value.candidateParam instanceof Array
+      ? userTaskForm.value.candidateParam.join(',')
+      : userTaskForm.value.candidateParam
+
+  // 鐗规畩澶勭悊澶氱骇閮ㄩ棬鎯呭喌
+  if (
+    userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+    userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+  ) {
+    candidateParam += '|' + deptLevel.value
+  }
+  // 鐗规畩澶勭悊鍙戣捣浜洪儴闂ㄨ礋璐d汉銆佸彂璧蜂汉杩炵画閮ㄩ棬璐熻矗浜�
+  if (
+    userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+    userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
+  ) {
+    candidateParam = deptLevel.value + ''
+  }
+
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: [
+      ...otherExtensions.value,
+      bpmnInstances().moddle.create(`${prefix}:CandidateStrategy`, {
+        value: userTaskForm.value.candidateStrategy
+      }),
+      bpmnInstances().moddle.create(`${prefix}:CandidateParam`, {
+        value: candidateParam
+      })
+    ]
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+
+  // 鏀圭敤閫氳繃extensionElements鏉ュ瓨鍌ㄦ暟鎹�
+  return
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    candidateStrategy: userTaskForm.value.candidateStrategy,
+    candidateParam: userTaskForm.value.candidateParam.join(',')
+  })
+}
+
+const updateSkipExpression = () => {
+  if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
+    bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+      skipExpression: userTaskForm.value.skipExpression
+    })
+  } else {
+    bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+      skipExpression: null
+    })
+  }
+}
+
+// 鎵撳紑鐩戝惉鍣ㄥ脊绐�
+const processExpressionDialogRef = ref()
+const openProcessExpressionDialog = async () => {
+  processExpressionDialogRef.value.open()
+}
+const selectProcessExpression = (expression: ProcessExpressionVO) => {
+  userTaskForm.value.candidateParam = [expression.expression]
+  updateElementTask()
+}
+
+const handleFormUserChange = (e) => {
+  if (e === 'PROCESS_START_USER_ID') {
+    userTaskForm.value.candidateParam = []
+    userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
+  }
+  updateElementTask()
+}
+
+watch(
+  () => props.id,
+  () => {
+    bpmnElement.value = bpmnInstances().bpmnElement
+    nextTick(() => {
+      resetTaskForm()
+    })
+  },
+  { immediate: true }
+)
+
+onMounted(async () => {
+  // 鑾峰緱瑙掕壊鍒楄〃
+  roleOptions.value = await RoleApi.getSimpleRoleList()
+  // 鑾峰緱閮ㄩ棬鍒楄〃
+  const deptOptions = await DeptApi.getSimpleDeptList()
+  deptTreeOptions.value = handleTree(deptOptions, 'id')
+  // 鑾峰緱宀椾綅鍒楄〃
+  postOptions.value = await PostApi.getSimplePostList()
+  // 鑾峰緱鐢ㄦ埛鍒楄〃
+  userOptions.value = await UserApi.getSimpleUserList()
+  // 鑾峰緱鐢ㄦ埛缁勫垪琛�
+  userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
+})
+
+onBeforeUnmount(() => {
+  bpmnElement.value = null
+})
+</script>

--
Gitblit v1.8.0