UserTaskCustomConfig.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. <!-- UserTask 自定义配置:
  2. 1. 审批人与提交人为同一人时
  3. 2. 审批人拒绝时
  4. 3. 审批人为空时
  5. 4. 操作按钮
  6. 5. 字段权限
  7. 6. 审批类型
  8. 7. 是否需要签名
  9. -->
  10. <template>
  11. <div>
  12. <el-divider content-position="left">审批类型</el-divider>
  13. <el-form-item prop="approveType">
  14. <el-radio-group v-model="approveType.value">
  15. <el-radio
  16. v-for="(item, index) in APPROVE_TYPE"
  17. :key="index"
  18. :value="item.value"
  19. :label="item.value"
  20. >
  21. {{ item.label }}
  22. </el-radio>
  23. </el-radio-group>
  24. </el-form-item>
  25. <el-divider content-position="left">审批人拒绝时</el-divider>
  26. <el-form-item prop="rejectHandlerType">
  27. <el-radio-group
  28. v-model="rejectHandlerType"
  29. :disabled="returnTaskList.length === 0"
  30. @change="updateRejectHandlerType"
  31. >
  32. <div class="flex-col">
  33. <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
  34. <el-radio :key="item.value" :value="item.value" :label="item.label" />
  35. </div>
  36. </div>
  37. </el-radio-group>
  38. </el-form-item>
  39. <el-form-item
  40. v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
  41. label="驳回节点"
  42. prop="returnNodeId"
  43. >
  44. <el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
  45. <el-option
  46. v-for="item in returnTaskList"
  47. :key="item.id"
  48. :label="item.name"
  49. :value="item.id"
  50. />
  51. </el-select>
  52. </el-form-item>
  53. <el-divider content-position="left">审批人为空时</el-divider>
  54. <el-form-item prop="assignEmptyHandlerType">
  55. <el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
  56. <div class="flex-col">
  57. <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
  58. <el-radio :key="item.value" :value="item.value" :label="item.label" />
  59. </div>
  60. </div>
  61. </el-radio-group>
  62. </el-form-item>
  63. <el-form-item
  64. v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
  65. label="指定用户"
  66. prop="assignEmptyHandlerUserIds"
  67. span="24"
  68. >
  69. <el-select
  70. v-model="assignEmptyUserIds"
  71. clearable
  72. multiple
  73. style="width: 100%"
  74. @change="updateAssignEmptyUserIds"
  75. >
  76. <el-option
  77. v-for="item in userOptions"
  78. :key="item.id"
  79. :label="item.nickname"
  80. :value="item.id"
  81. />
  82. </el-select>
  83. </el-form-item>
  84. <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
  85. <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
  86. <div class="flex-col">
  87. <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
  88. <el-radio :key="item.value" :value="item.value" :label="item.label" />
  89. </div>
  90. </div>
  91. </el-radio-group>
  92. <el-divider content-position="left">操作按钮</el-divider>
  93. <div class="button-setting-pane">
  94. <div class="button-setting-title">
  95. <div class="button-title-label">操作按钮</div>
  96. <div class="pl-4 button-title-label">显示名称</div>
  97. <div class="button-title-label">启用</div>
  98. </div>
  99. <div class="button-setting-item" v-for="(item, index) in buttonsSettingEl" :key="index">
  100. <div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
  101. <div class="button-setting-item-label">
  102. <input
  103. type="text"
  104. class="editable-title-input"
  105. @blur="btnDisplayNameBlurEvent(index)"
  106. v-mountedFocus
  107. v-model="item.displayName"
  108. :placeholder="item.displayName"
  109. v-if="btnDisplayNameEdit[index]"
  110. />
  111. <el-button v-else text @click="changeBtnDisplayName(index)"
  112. >{{ item.displayName }} &nbsp;<Icon icon="ep:edit"
  113. /></el-button>
  114. </div>
  115. <div class="button-setting-item-label">
  116. <el-switch v-model="item.enable" />
  117. </div>
  118. </div>
  119. </div>
  120. <el-divider content-position="left">字段权限</el-divider>
  121. <div class="field-setting-pane" v-if="formType === 10">
  122. <div class="field-permit-title">
  123. <div class="setting-title-label first-title"> 字段名称 </div>
  124. <div class="other-titles">
  125. <span class="setting-title-label">只读</span>
  126. <span class="setting-title-label">可编辑</span>
  127. <span class="setting-title-label">隐藏</span>
  128. </div>
  129. </div>
  130. <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
  131. <div class="field-setting-item-label"> {{ item.title }} </div>
  132. <el-radio-group class="field-setting-item-group" v-model="item.permission">
  133. <div class="item-radio-wrap">
  134. <el-radio
  135. :value="FieldPermissionType.READ"
  136. size="large"
  137. :label="FieldPermissionType.READ"
  138. ><span></span
  139. ></el-radio>
  140. </div>
  141. <div class="item-radio-wrap">
  142. <el-radio
  143. :value="FieldPermissionType.WRITE"
  144. size="large"
  145. :label="FieldPermissionType.WRITE"
  146. ><span></span
  147. ></el-radio>
  148. </div>
  149. <div class="item-radio-wrap">
  150. <el-radio
  151. :value="FieldPermissionType.NONE"
  152. size="large"
  153. :label="FieldPermissionType.NONE"
  154. ><span></span
  155. ></el-radio>
  156. </div>
  157. </el-radio-group>
  158. </div>
  159. </div>
  160. <el-divider content-position="left">是否需要签名</el-divider>
  161. <el-form-item prop="signEnable">
  162. <el-switch v-model="signEnable.value" active-text="是" inactive-text="否" />
  163. </el-form-item>
  164. </div>
  165. </template>
  166. <script lang="ts" setup>
  167. import {
  168. ASSIGN_START_USER_HANDLER_TYPES,
  169. RejectHandlerType,
  170. REJECT_HANDLER_TYPES,
  171. ASSIGN_EMPTY_HANDLER_TYPES,
  172. AssignEmptyHandlerType,
  173. OPERATION_BUTTON_NAME,
  174. DEFAULT_BUTTON_SETTING,
  175. FieldPermissionType,
  176. APPROVE_TYPE,
  177. ApproveType,
  178. ButtonSetting
  179. } from '@/components/SimpleProcessDesignerV2/src/consts'
  180. import * as UserApi from '@/api/system/user'
  181. import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
  182. defineOptions({ name: 'ElementCustomConfig4UserTask' })
  183. const props = defineProps({
  184. id: String,
  185. type: String
  186. })
  187. const prefix = inject('prefix')
  188. // 审批人与提交人为同一人时
  189. const assignStartUserHandlerTypeEl = ref()
  190. const assignStartUserHandlerType = ref()
  191. // 审批人拒绝时
  192. const rejectHandlerTypeEl = ref()
  193. const rejectHandlerType = ref()
  194. const returnNodeIdEl = ref()
  195. const returnNodeId = ref()
  196. const returnTaskList = ref([])
  197. // 审批人为空时
  198. const assignEmptyHandlerTypeEl = ref()
  199. const assignEmptyHandlerType = ref()
  200. const assignEmptyUserIdsEl = ref()
  201. const assignEmptyUserIds = ref()
  202. // 操作按钮
  203. const buttonsSettingEl = ref()
  204. const { btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = useButtonsSetting()
  205. // 字段权限
  206. const fieldsPermissionEl = ref([])
  207. const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
  208. FieldPermissionType.READ
  209. )
  210. // 审批类型
  211. const approveType = ref({ value: ApproveType.USER })
  212. // 是否需要签名
  213. const signEnable = ref({ value: false })
  214. const elExtensionElements = ref()
  215. const otherExtensions = ref()
  216. const bpmnElement = ref()
  217. const bpmnInstances = () => (window as any)?.bpmnInstances
  218. const resetCustomConfigList = () => {
  219. bpmnElement.value = bpmnInstances().bpmnElement
  220. // 获取可回退的列表
  221. returnTaskList.value = findAllPredecessorsExcludingStart(
  222. bpmnElement.value.id,
  223. bpmnInstances().modeler
  224. )
  225. // 获取元素扩展属性 或者 创建扩展属性
  226. elExtensionElements.value =
  227. bpmnElement.value.businessObject?.extensionElements ??
  228. bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
  229. // 审批类型
  230. approveType.value =
  231. elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ApproveType`)?.[0] ||
  232. bpmnInstances().moddle.create(`${prefix}:ApproveType`, { value: ApproveType.USER })
  233. // 审批人与提交人为同一人时
  234. assignStartUserHandlerTypeEl.value =
  235. elExtensionElements.value.values?.filter(
  236. (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
  237. )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
  238. assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
  239. // 审批人拒绝时
  240. rejectHandlerTypeEl.value =
  241. elExtensionElements.value.values?.filter(
  242. (ex) => ex.$type === `${prefix}:RejectHandlerType`
  243. )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
  244. rejectHandlerType.value = rejectHandlerTypeEl.value.value
  245. returnNodeIdEl.value =
  246. elExtensionElements.value.values?.filter(
  247. (ex) => ex.$type === `${prefix}:RejectReturnTaskId`
  248. )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
  249. returnNodeId.value = returnNodeIdEl.value.value
  250. // 审批人为空时
  251. assignEmptyHandlerTypeEl.value =
  252. elExtensionElements.value.values?.filter(
  253. (ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
  254. )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
  255. assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
  256. assignEmptyUserIdsEl.value =
  257. elExtensionElements.value.values?.filter(
  258. (ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
  259. )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
  260. assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value?.split(',').map((item) => {
  261. // 如果数字超出了最大安全整数范围,则将其作为字符串处理
  262. let num = Number(item)
  263. return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
  264. })
  265. // 操作按钮
  266. buttonsSettingEl.value = elExtensionElements.value.values?.filter(
  267. (ex) => ex.$type === `${prefix}:ButtonsSetting`
  268. )
  269. if (buttonsSettingEl.value.length === 0) {
  270. DEFAULT_BUTTON_SETTING.forEach((item) => {
  271. buttonsSettingEl.value.push(
  272. bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, {
  273. 'flowable:id': item.id,
  274. 'flowable:displayName': item.displayName,
  275. 'flowable:enable': item.enable
  276. })
  277. )
  278. })
  279. }
  280. // 字段权限
  281. if (formType.value === 10) {
  282. const fieldsPermissionList = elExtensionElements.value.values?.filter(
  283. (ex) => ex.$type === `${prefix}:FieldsPermission`
  284. )
  285. fieldsPermissionEl.value = []
  286. getNodeConfigFormFields()
  287. // 由于默认添加了发起人元素,这里需要删掉
  288. fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
  289. fieldsPermissionConfig.value.forEach((element) => {
  290. element.permission =
  291. fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
  292. fieldsPermissionEl.value.push(
  293. bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, element)
  294. )
  295. })
  296. }
  297. // 保留剩余扩展元素,便于后面更新该元素对应属性
  298. otherExtensions.value =
  299. elExtensionElements.value.values?.filter(
  300. (ex) =>
  301. ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
  302. ex.$type !== `${prefix}:RejectHandlerType` &&
  303. ex.$type !== `${prefix}:RejectReturnTaskId` &&
  304. ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
  305. ex.$type !== `${prefix}:AssignEmptyUserIds` &&
  306. ex.$type !== `${prefix}:ButtonsSetting` &&
  307. ex.$type !== `${prefix}:FieldsPermission` &&
  308. ex.$type !== `${prefix}:ApproveType`
  309. ) ?? []
  310. // 是否需要签名
  311. signEnable.value =
  312. elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
  313. bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
  314. // 更新元素扩展属性,避免后续报错
  315. updateElementExtensions()
  316. }
  317. const updateAssignStartUserHandlerType = () => {
  318. assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
  319. updateElementExtensions()
  320. }
  321. const updateRejectHandlerType = () => {
  322. rejectHandlerTypeEl.value.value = rejectHandlerType.value
  323. returnNodeId.value = returnTaskList.value[0].id
  324. returnNodeIdEl.value.value = returnNodeId.value
  325. updateElementExtensions()
  326. }
  327. const updateReturnNodeId = () => {
  328. returnNodeIdEl.value.value = returnNodeId.value
  329. updateElementExtensions()
  330. }
  331. const updateAssignEmptyHandlerType = () => {
  332. assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
  333. updateElementExtensions()
  334. }
  335. const updateAssignEmptyUserIds = () => {
  336. assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
  337. updateElementExtensions()
  338. }
  339. const updateElementExtensions = () => {
  340. const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
  341. values: [
  342. ...otherExtensions.value,
  343. assignStartUserHandlerTypeEl.value,
  344. rejectHandlerTypeEl.value,
  345. returnNodeIdEl.value,
  346. assignEmptyHandlerTypeEl.value,
  347. assignEmptyUserIdsEl.value,
  348. approveType.value,
  349. ...buttonsSettingEl.value,
  350. ...fieldsPermissionEl.value,
  351. signEnable.value
  352. ]
  353. })
  354. bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
  355. extensionElements: extensions
  356. })
  357. }
  358. watch(
  359. () => props.id,
  360. (val) => {
  361. val &&
  362. val.length &&
  363. nextTick(() => {
  364. resetCustomConfigList()
  365. })
  366. },
  367. { immediate: true }
  368. )
  369. function findAllPredecessorsExcludingStart(elementId, modeler) {
  370. const elementRegistry = modeler.get('elementRegistry')
  371. const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
  372. const predecessors = new Set() // 使用 Set 来避免重复节点
  373. const visited = new Set() // 用于记录已访问的节点
  374. // 检查是否是开始事件节点
  375. function isStartEvent(element) {
  376. return element.type === 'bpmn:StartEvent'
  377. }
  378. function findPredecessorsRecursively(element) {
  379. // 如果该节点已经访问过,直接返回,避免循环
  380. if (visited.has(element)) {
  381. return
  382. }
  383. // 标记当前节点为已访问
  384. visited.add(element)
  385. // 获取与当前节点相连的所有连接
  386. const incomingConnections = allConnections.filter((connection) => connection.target === element)
  387. incomingConnections.forEach((connection) => {
  388. const source = connection.source // 获取前置节点
  389. // 只添加不是开始事件的前置节点
  390. if (!isStartEvent(source)) {
  391. predecessors.add(source.businessObject)
  392. // 递归查找前置节点
  393. findPredecessorsRecursively(source)
  394. }
  395. })
  396. }
  397. const targetElement = elementRegistry.get(elementId)
  398. if (targetElement) {
  399. findPredecessorsRecursively(targetElement)
  400. }
  401. return Array.from(predecessors) // 返回前置节点数组
  402. }
  403. function useButtonsSetting() {
  404. const buttonsSetting = ref<ButtonSetting[]>()
  405. // 操作按钮显示名称可编辑
  406. const btnDisplayNameEdit = ref<boolean[]>([])
  407. const changeBtnDisplayName = (index: number) => {
  408. btnDisplayNameEdit.value[index] = true
  409. }
  410. const btnDisplayNameBlurEvent = (index: number) => {
  411. btnDisplayNameEdit.value[index] = false
  412. const buttonItem = buttonsSetting.value![index]
  413. buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!
  414. }
  415. return {
  416. buttonsSetting,
  417. btnDisplayNameEdit,
  418. changeBtnDisplayName,
  419. btnDisplayNameBlurEvent
  420. }
  421. }
  422. const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
  423. onMounted(async () => {
  424. // 获得用户列表
  425. userOptions.value = await UserApi.getSimpleUserList()
  426. })
  427. </script>
  428. <style lang="scss" scoped>
  429. .button-setting-pane {
  430. display: flex;
  431. flex-direction: column;
  432. font-size: 14px;
  433. margin-top: 8px;
  434. .button-setting-desc {
  435. padding-right: 8px;
  436. margin-bottom: 16px;
  437. font-size: 16px;
  438. font-weight: 700;
  439. }
  440. .button-setting-title {
  441. display: flex;
  442. justify-content: space-between;
  443. align-items: center;
  444. height: 45px;
  445. padding-left: 12px;
  446. background-color: #f8fafc0a;
  447. border: 1px solid #1f38581a;
  448. & > :first-child {
  449. width: 100px !important;
  450. text-align: left !important;
  451. }
  452. & > :last-child {
  453. text-align: center !important;
  454. }
  455. .button-title-label {
  456. width: 150px;
  457. font-size: 13px;
  458. font-weight: 700;
  459. color: #000;
  460. text-align: left;
  461. }
  462. }
  463. .button-setting-item {
  464. align-items: center;
  465. display: flex;
  466. justify-content: space-between;
  467. height: 38px;
  468. padding-left: 12px;
  469. border: 1px solid #1f38581a;
  470. border-top: 0;
  471. & > :first-child {
  472. width: 100px !important;
  473. }
  474. & > :last-child {
  475. text-align: center !important;
  476. }
  477. .button-setting-item-label {
  478. width: 150px;
  479. overflow: hidden;
  480. text-align: left;
  481. text-overflow: ellipsis;
  482. white-space: nowrap;
  483. }
  484. .editable-title-input {
  485. height: 24px;
  486. max-width: 130px;
  487. margin-left: 4px;
  488. line-height: 24px;
  489. border: 1px solid #d9d9d9;
  490. border-radius: 4px;
  491. transition: all 0.3s;
  492. &:focus {
  493. border-color: #40a9ff;
  494. outline: 0;
  495. box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
  496. }
  497. }
  498. }
  499. }
  500. .field-setting-pane {
  501. display: flex;
  502. flex-direction: column;
  503. font-size: 14px;
  504. .field-setting-desc {
  505. padding-right: 8px;
  506. margin-bottom: 16px;
  507. font-size: 16px;
  508. font-weight: 700;
  509. }
  510. .field-permit-title {
  511. display: flex;
  512. justify-content: space-between;
  513. align-items: center;
  514. height: 45px;
  515. padding-left: 12px;
  516. line-height: 45px;
  517. background-color: #f8fafc0a;
  518. border: 1px solid #1f38581a;
  519. .first-title {
  520. text-align: left !important;
  521. }
  522. .other-titles {
  523. display: flex;
  524. justify-content: space-between;
  525. }
  526. .setting-title-label {
  527. display: inline-block;
  528. width: 100px;
  529. padding: 5px 0;
  530. font-size: 13px;
  531. font-weight: 700;
  532. color: #000;
  533. text-align: center;
  534. }
  535. }
  536. .field-setting-item {
  537. align-items: center;
  538. display: flex;
  539. justify-content: space-between;
  540. height: 38px;
  541. padding-left: 12px;
  542. border: 1px solid #1f38581a;
  543. border-top: 0;
  544. .field-setting-item-label {
  545. display: inline-block;
  546. width: 100px;
  547. min-height: 16px;
  548. overflow: hidden;
  549. text-overflow: ellipsis;
  550. white-space: nowrap;
  551. cursor: text;
  552. }
  553. .field-setting-item-group {
  554. display: flex;
  555. justify-content: space-between;
  556. .item-radio-wrap {
  557. display: inline-block;
  558. width: 100px;
  559. text-align: center;
  560. }
  561. }
  562. }
  563. }
  564. </style>