UserTask.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <template>
  2. <el-form label-width="120px">
  3. <el-form-item label="规则类型" prop="candidateStrategy">
  4. <el-select
  5. v-model="userTaskForm.candidateStrategy"
  6. clearable
  7. style="width: 100%"
  8. @change="changeCandidateStrategy"
  9. >
  10. <el-option
  11. v-for="(dict, index) in CANDIDATE_STRATEGY"
  12. :key="index"
  13. :label="dict.label"
  14. :value="dict.value"
  15. />
  16. </el-select>
  17. </el-form-item>
  18. <el-form-item
  19. v-if="userTaskForm.candidateStrategy == CandidateStrategy.ROLE"
  20. label="指定角色"
  21. prop="candidateParam"
  22. >
  23. <el-select
  24. v-model="userTaskForm.candidateParam"
  25. clearable
  26. multiple
  27. style="width: 100%"
  28. @change="updateElementTask"
  29. >
  30. <el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item
  34. v-if="
  35. userTaskForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
  36. userTaskForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
  37. userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
  38. "
  39. label="指定部门"
  40. prop="candidateParam"
  41. span="24"
  42. >
  43. <el-tree-select
  44. ref="treeRef"
  45. v-model="userTaskForm.candidateParam"
  46. :data="deptTreeOptions"
  47. :props="defaultProps"
  48. empty-text="加载中,请稍后"
  49. multiple
  50. node-key="id"
  51. show-checkbox
  52. @change="updateElementTask"
  53. />
  54. </el-form-item>
  55. <el-form-item
  56. v-if="userTaskForm.candidateStrategy == CandidateStrategy.POST"
  57. label="指定岗位"
  58. prop="candidateParam"
  59. span="24"
  60. >
  61. <el-select
  62. v-model="userTaskForm.candidateParam"
  63. clearable
  64. multiple
  65. style="width: 100%"
  66. @change="updateElementTask"
  67. >
  68. <el-option v-for="item in postOptions" :key="item.id" :label="item.name" :value="item.id" />
  69. </el-select>
  70. </el-form-item>
  71. <el-form-item
  72. v-if="userTaskForm.candidateStrategy == CandidateStrategy.USER"
  73. label="指定用户"
  74. prop="candidateParam"
  75. span="24"
  76. >
  77. <el-select
  78. v-model="userTaskForm.candidateParam"
  79. clearable
  80. multiple
  81. style="width: 100%"
  82. @change="updateElementTask"
  83. >
  84. <el-option
  85. v-for="item in userOptions"
  86. :key="item.id"
  87. :label="item.nickname"
  88. :value="item.id"
  89. />
  90. </el-select>
  91. </el-form-item>
  92. <el-form-item
  93. v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
  94. label="指定用户组"
  95. prop="candidateParam"
  96. >
  97. <el-select
  98. v-model="userTaskForm.candidateParam"
  99. clearable
  100. multiple
  101. style="width: 100%"
  102. @change="updateElementTask"
  103. >
  104. <el-option
  105. v-for="item in userGroupOptions"
  106. :key="item.id"
  107. :label="item.name"
  108. :value="item.id"
  109. />
  110. </el-select>
  111. </el-form-item>
  112. <el-form-item
  113. v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
  114. label="表单内用户字段"
  115. prop="formUser"
  116. >
  117. <el-select
  118. v-model="userTaskForm.candidateParam"
  119. clearable
  120. style="width: 100%"
  121. @change="handleFormUserChange"
  122. >
  123. <el-option
  124. v-for="(item, idx) in userFieldOnFormOptions"
  125. :key="idx"
  126. :label="item.title"
  127. :value="item.field"
  128. :disabled="!item.required"
  129. />
  130. </el-select>
  131. </el-form-item>
  132. <el-form-item
  133. v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
  134. label="表单内部门字段"
  135. prop="formDept"
  136. >
  137. <el-select
  138. v-model="userTaskForm.candidateParam"
  139. clearable
  140. style="width: 100%"
  141. @change="updateElementTask"
  142. >
  143. <el-option
  144. v-for="(item, idx) in deptFieldOnFormOptions"
  145. :key="idx"
  146. :label="item.title"
  147. :value="item.field"
  148. :disabled="!item.required"
  149. />
  150. </el-select>
  151. </el-form-item>
  152. <el-form-item
  153. v-if="
  154. userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
  155. userTaskForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
  156. userTaskForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
  157. userTaskForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
  158. "
  159. :label="deptLevelLabel!"
  160. prop="deptLevel"
  161. span="24"
  162. >
  163. <el-select v-model="deptLevel" clearable @change="updateElementTask">
  164. <el-option
  165. v-for="(item, index) in MULTI_LEVEL_DEPT"
  166. :key="index"
  167. :label="item.label"
  168. :value="item.value"
  169. />
  170. </el-select>
  171. </el-form-item>
  172. <el-form-item
  173. v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
  174. label="流程表达式"
  175. prop="candidateParam"
  176. >
  177. <el-input
  178. type="textarea"
  179. v-model="userTaskForm.candidateParam[0]"
  180. clearable
  181. style="width: 100%"
  182. @change="updateElementTask"
  183. />
  184. <XButton
  185. class="!w-1/1 mt-5px"
  186. type="success"
  187. preIcon="ep:select"
  188. title="选择表达式"
  189. size="small"
  190. @click="openProcessExpressionDialog"
  191. />
  192. <!-- 选择弹窗 -->
  193. <ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
  194. </el-form-item>
  195. <el-form-item label="跳过表达式" prop="skipExpression">
  196. <el-input
  197. type="textarea"
  198. v-model="userTaskForm.skipExpression"
  199. clearable
  200. style="width: 100%"
  201. @change="updateSkipExpression"
  202. />
  203. </el-form-item>
  204. </el-form>
  205. </template>
  206. <script lang="ts" setup>
  207. import {
  208. CANDIDATE_STRATEGY,
  209. CandidateStrategy,
  210. FieldPermissionType,
  211. MULTI_LEVEL_DEPT
  212. } from '@/components/SimpleProcessDesignerV2/src/consts'
  213. import { defaultProps, handleTree } from '@/utils/tree'
  214. import * as RoleApi from '@/api/system/role'
  215. import * as DeptApi from '@/api/system/dept'
  216. import * as PostApi from '@/api/system/post'
  217. import * as UserApi from '@/api/system/user'
  218. import * as UserGroupApi from '@/api/bpm/userGroup'
  219. import ProcessExpressionDialog from './ProcessExpressionDialog.vue'
  220. import { ProcessExpressionVO } from '@/api/bpm/processExpression'
  221. import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
  222. defineOptions({ name: 'UserTask' })
  223. const props = defineProps({
  224. id: String,
  225. type: String
  226. })
  227. const prefix = inject('prefix')
  228. const userTaskForm = ref({
  229. candidateStrategy: undefined, // 分配规则
  230. candidateParam: [], // 分配选项
  231. skipExpression: '' // 跳过表达式
  232. })
  233. const bpmnElement = ref()
  234. const bpmnInstances = () => (window as any)?.bpmnInstances
  235. const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
  236. const deptTreeOptions = ref() // 部门树
  237. const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
  238. const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
  239. const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
  240. const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ)
  241. // 表单内用户字段选项, 必须是必填和用户选择器
  242. const userFieldOnFormOptions = computed(() => {
  243. return formFieldOptions.filter((item) => item.type === 'UserSelect')
  244. })
  245. // 表单内部门字段选项, 必须是必填和部门选择器
  246. const deptFieldOnFormOptions = computed(() => {
  247. return formFieldOptions.filter((item) => item.type === 'DeptSelect')
  248. })
  249. const deptLevel = ref(1)
  250. const deptLevelLabel = computed(() => {
  251. let label = '部门负责人来源'
  252. if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
  253. label = label + '(指定部门向上)'
  254. } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
  255. label = label + '(表单内部门向上)'
  256. } else {
  257. label = label + '(发起人部门向上)'
  258. }
  259. return label
  260. })
  261. const otherExtensions = ref()
  262. const resetTaskForm = () => {
  263. const businessObject = bpmnElement.value.businessObject
  264. if (!businessObject) {
  265. return
  266. }
  267. const extensionElements =
  268. businessObject?.extensionElements ??
  269. bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
  270. userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
  271. (ex) => ex.$type === `${prefix}:CandidateStrategy`
  272. )?.[0]?.value
  273. const candidateParamStr = extensionElements.values?.filter(
  274. (ex) => ex.$type === `${prefix}:CandidateParam`
  275. )?.[0]?.value
  276. if (candidateParamStr && candidateParamStr.length > 0) {
  277. if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
  278. // 特殊:流程表达式,只有一个 input 输入框
  279. userTaskForm.value.candidateParam = [candidateParamStr]
  280. } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
  281. // 特殊:多级不部门负责人,需要通过'|'分割
  282. userTaskForm.value.candidateParam = candidateParamStr
  283. .split('|')[0]
  284. .split(',')
  285. .map((item) => {
  286. // 如果数字超出了最大安全整数范围,则将其作为字符串处理
  287. let num = Number(item)
  288. return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
  289. })
  290. deptLevel.value = +candidateParamStr.split('|')[1]
  291. } else if (
  292. userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
  293. userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
  294. ) {
  295. userTaskForm.value.candidateParam = +candidateParamStr
  296. deptLevel.value = +candidateParamStr
  297. } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
  298. userTaskForm.value.candidateParam = candidateParamStr.split('|')[0]
  299. deptLevel.value = +candidateParamStr.split('|')[1]
  300. } else {
  301. userTaskForm.value.candidateParam = candidateParamStr.split(',').map((item) => {
  302. // 如果数字超出了最大安全整数范围,则将其作为字符串处理
  303. let num = Number(item)
  304. return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
  305. })
  306. }
  307. } else {
  308. userTaskForm.value.candidateParam = []
  309. }
  310. otherExtensions.value =
  311. extensionElements.values?.filter(
  312. (ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
  313. ) ?? []
  314. // 跳过表达式
  315. if (businessObject.skipExpression != undefined) {
  316. userTaskForm.value.skipExpression = businessObject.skipExpression
  317. } else {
  318. userTaskForm.value.skipExpression = ''
  319. }
  320. // 改用通过extensionElements来存储数据
  321. return
  322. if (businessObject.candidateStrategy != undefined) {
  323. userTaskForm.value.candidateStrategy = parseInt(businessObject.candidateStrategy) as any
  324. } else {
  325. userTaskForm.value.candidateStrategy = undefined
  326. }
  327. if (businessObject.candidateParam && businessObject.candidateParam.length > 0) {
  328. if (userTaskForm.value.candidateStrategy === 60) {
  329. // 特殊:流程表达式,只有一个 input 输入框
  330. userTaskForm.value.candidateParam = [businessObject.candidateParam]
  331. } else {
  332. userTaskForm.value.candidateParam = businessObject.candidateParam
  333. .split(',')
  334. .map((item) => item)
  335. }
  336. } else {
  337. userTaskForm.value.candidateParam = []
  338. }
  339. }
  340. /** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */
  341. const changeCandidateStrategy = () => {
  342. userTaskForm.value.candidateParam = []
  343. deptLevel.value = 1
  344. if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
  345. // 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
  346. if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
  347. userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
  348. }
  349. }
  350. updateElementTask()
  351. }
  352. /** 选中某个 options 时候,更新 bpmn 图 */
  353. const updateElementTask = () => {
  354. let candidateParam =
  355. userTaskForm.value.candidateParam instanceof Array
  356. ? userTaskForm.value.candidateParam.join(',')
  357. : userTaskForm.value.candidateParam
  358. // 特殊处理多级部门情况
  359. if (
  360. userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
  361. userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
  362. ) {
  363. candidateParam += '|' + deptLevel.value
  364. }
  365. // 特殊处理发起人部门负责人、发起人连续部门负责人
  366. if (
  367. userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
  368. userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
  369. ) {
  370. candidateParam = deptLevel.value + ''
  371. }
  372. const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
  373. values: [
  374. ...otherExtensions.value,
  375. bpmnInstances().moddle.create(`${prefix}:CandidateStrategy`, {
  376. value: userTaskForm.value.candidateStrategy
  377. }),
  378. bpmnInstances().moddle.create(`${prefix}:CandidateParam`, {
  379. value: candidateParam
  380. })
  381. ]
  382. })
  383. bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
  384. extensionElements: extensions
  385. })
  386. // 改用通过extensionElements来存储数据
  387. return
  388. bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
  389. candidateStrategy: userTaskForm.value.candidateStrategy,
  390. candidateParam: userTaskForm.value.candidateParam.join(',')
  391. })
  392. }
  393. const updateSkipExpression = () => {
  394. if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
  395. bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
  396. skipExpression: userTaskForm.value.skipExpression
  397. })
  398. } else {
  399. bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
  400. skipExpression: null
  401. })
  402. }
  403. }
  404. // 打开监听器弹窗
  405. const processExpressionDialogRef = ref()
  406. const openProcessExpressionDialog = async () => {
  407. processExpressionDialogRef.value.open()
  408. }
  409. const selectProcessExpression = (expression: ProcessExpressionVO) => {
  410. userTaskForm.value.candidateParam = [expression.expression]
  411. updateElementTask()
  412. }
  413. const handleFormUserChange = (e) => {
  414. if (e === 'PROCESS_START_USER_ID') {
  415. userTaskForm.value.candidateParam = []
  416. userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
  417. }
  418. updateElementTask()
  419. }
  420. watch(
  421. () => props.id,
  422. () => {
  423. bpmnElement.value = bpmnInstances().bpmnElement
  424. nextTick(() => {
  425. resetTaskForm()
  426. })
  427. },
  428. { immediate: true }
  429. )
  430. onMounted(async () => {
  431. // 获得角色列表
  432. roleOptions.value = await RoleApi.getSimpleRoleList()
  433. // 获得部门列表
  434. const deptOptions = await DeptApi.getSimpleDeptList()
  435. deptTreeOptions.value = handleTree(deptOptions, 'id')
  436. // 获得岗位列表
  437. postOptions.value = await PostApi.getSimplePostList()
  438. // 获得用户列表
  439. userOptions.value = await UserApi.getSimpleUserList()
  440. // 获得用户组列表
  441. userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
  442. })
  443. onBeforeUnmount(() => {
  444. bpmnElement.value = null
  445. })
  446. </script>