submitVerify.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. <template>
  2. <el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false">
  3. <el-form v-loading="loading" :model="form" label-width="120px">
  4. <el-form-item :label="$t('components.process.messageReminder')">
  5. <el-checkbox-group v-model="form.messageType">
  6. <el-checkbox value="1" name="type" disabled>{{ $t('components.process.internalMessage') }}</el-checkbox>
  7. <el-checkbox value="2" name="type">{{ $t('components.process.email') }}</el-checkbox>
  8. <el-checkbox value="3" name="type">{{ $t('components.process.sms') }}</el-checkbox>
  9. </el-checkbox-group>
  10. </el-form-item>
  11. <el-form-item :label="$t('components.process.attachment')">
  12. <fileUpload v-model="form.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" />
  13. </el-form-item>
  14. <el-form-item :label="$t('components.process.copy')" v-if="buttonObj.copy">
  15. <el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
  16. <el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
  17. {{ user.userName }}
  18. </el-tag>
  19. </el-form-item>
  20. <el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" :label="$t('components.process.nextApprover')" prop="assigneeMap">
  21. <div v-for="(item, index) in nestNodeList" :key="index" style="margin-bottom: 5px; width: 500px">
  22. <span>【{{ item.nodeName }}】:</span>
  23. <el-input v-if="false" v-model="form.assigneeMap[item.nodeCode]" />
  24. <el-input :placeholder="$t('components.process.selectApproverPlaceholder')" readonly v-model="nickName[item.nodeCode]">
  25. <template v-slot:append>
  26. <el-button @click="choosePeople(item)" icon="search">{{ $t('components.process.choose') }}</el-button>
  27. </template>
  28. </el-input>
  29. </div>
  30. </el-form-item>
  31. <el-form-item v-if="task.flowStatus === 'waiting'" :label="$t('components.process.approvalOpinion')">
  32. <el-input v-model="form.message" type="textarea" resize="none" />
  33. </el-form-item>
  34. </el-form>
  35. <template #footer>
  36. <span class="dialog-footer">
  37. <el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask">{{ $t('components.process.submit') }}</el-button>
  38. <el-button v-if="task.flowStatus === 'waiting' && buttonObj.trust" :disabled="buttonDisabled" type="primary" @click="openDelegateTask">
  39. {{ $t('components.process.delegate') }}
  40. </el-button>
  41. <el-button v-if="task.flowStatus === 'waiting' && buttonObj.transfer" :disabled="buttonDisabled" type="primary" @click="openTransferTask">
  42. {{ $t('components.process.transfer') }}
  43. </el-button>
  44. <el-button
  45. v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0 && buttonObj.addSign"
  46. :disabled="buttonDisabled"
  47. type="primary"
  48. @click="openMultiInstanceUser"
  49. >
  50. {{ $t('components.process.addSign') }}
  51. </el-button>
  52. <el-button
  53. v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0 && buttonObj.subSign"
  54. :disabled="buttonDisabled"
  55. type="primary"
  56. @click="handleTaskUser"
  57. >
  58. {{ $t('components.process.reduceSign') }}
  59. </el-button>
  60. <el-button
  61. v-if="task.flowStatus === 'waiting' && buttonObj.termination"
  62. :disabled="buttonDisabled"
  63. type="danger"
  64. @click="handleTerminationTask"
  65. >
  66. {{ $t('components.process.terminate') }}
  67. </el-button>
  68. <el-button v-if="task.flowStatus === 'waiting' && buttonObj.back" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen">
  69. {{ $t('components.process.back') }}
  70. </el-button>
  71. <el-button :disabled="buttonDisabled" @click="cancel">{{ $t('components.process.cancel') }}</el-button>
  72. </span>
  73. </template>
  74. <!-- 抄送 -->
  75. <UserSelect ref="userSelectCopyRef" :multiple="true" :data="selectCopyUserIds" @confirm-call-back="userSelectCopyCallBack"></UserSelect>
  76. <!-- 转办 -->
  77. <UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect>
  78. <!-- 委托 -->
  79. <UserSelect ref="delegateTaskRef" :multiple="false" @confirm-call-back="handleDelegateTask"></UserSelect>
  80. <!-- 加签组件 -->
  81. <UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
  82. <!-- 弹窗选人 -->
  83. <UserSelect
  84. ref="porUserRef"
  85. :data="form.assigneeMap[nodeCode]"
  86. :multiple="true"
  87. :userIds="popUserIds"
  88. @confirm-call-back="handlePopUser"
  89. ></UserSelect>
  90. <!-- 驳回开始 -->
  91. <el-dialog v-model="backVisible" draggable :title="$t('components.process.backTitle')" width="40%" :close-on-click-modal="false">
  92. <el-form v-if="task.flowStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px">
  93. <el-form-item :label="$t('components.process.backNode')">
  94. <el-select v-model="backForm.nodeCode" clearable :placeholder="$t('components.process.selectNode')" style="width: 300px">
  95. <el-option v-for="item in taskNodeList" :key="item.nodeCode" :label="item.nodeName" :value="item.nodeCode" />
  96. </el-select>
  97. </el-form-item>
  98. <el-form-item :label="$t('components.process.messageReminder')">
  99. <el-checkbox-group v-model="backForm.messageType">
  100. <el-checkbox label="1" name="type" disabled>{{ $t('components.process.internalMessage') }}</el-checkbox>
  101. <el-checkbox label="2" name="type">{{ $t('components.process.email') }}</el-checkbox>
  102. <el-checkbox label="3" name="type">{{ $t('components.process.sms') }}</el-checkbox>
  103. </el-checkbox-group>
  104. </el-form-item>
  105. <el-form-item v-if="task.flowStatus === 'waiting'" :label="$t('components.process.attachment')">
  106. <fileUpload
  107. v-model="backForm.fileId"
  108. :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']"
  109. :file-size="20"
  110. />
  111. </el-form-item>
  112. <el-form-item :label="$t('components.process.approvalOpinion')">
  113. <el-input v-model="backForm.message" type="textarea" resize="none" />
  114. </el-form-item>
  115. </el-form>
  116. <template #footer>
  117. <div class="dialog-footer" style="float: right; padding-bottom: 20px">
  118. <el-button :disabled="backButtonDisabled" type="primary" @click="handleBackProcess">{{ $t('components.process.confirm') }}</el-button>
  119. <el-button :disabled="backButtonDisabled" @click="backVisible = false">{{ $t('components.process.cancel') }}</el-button>
  120. </div>
  121. </template>
  122. </el-dialog>
  123. <!-- 驳回结束 -->
  124. <el-dialog
  125. v-model="deleteSignatureVisible"
  126. draggable
  127. :title="$t('components.process.reduceSignTitle')"
  128. width="700px"
  129. height="400px"
  130. append-to-body
  131. :close-on-click-modal="false"
  132. >
  133. <div>
  134. <el-table :data="deleteUserList" border>
  135. <el-table-column prop="nodeName" :label="$t('components.process.taskName')" />
  136. <el-table-column prop="nickName" :label="$t('components.process.handler')" />
  137. <el-table-column :label="$t('components.process.operation')" align="center" width="160">
  138. <template #default="scope">
  139. <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)"
  140. >{{ $t('components.process.delete') }}
  141. </el-button>
  142. </template>
  143. </el-table-column>
  144. </el-table>
  145. </div>
  146. </el-dialog>
  147. </el-dialog>
  148. </template>
  149. <script setup lang="ts">
  150. import { ref } from 'vue';
  151. import { ComponentInternalInstance } from 'vue';
  152. import { ElForm } from 'element-plus';
  153. import {
  154. completeTask,
  155. backProcess,
  156. getTask,
  157. taskOperation,
  158. terminationTask,
  159. getBackTaskNode,
  160. currentTaskAllUser,
  161. getNextNodeList
  162. } from '@/api/workflow/task';
  163. import UserSelect from '@/components/UserSelect';
  164. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  165. import { FlowCopyVo, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
  166. const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
  167. const transferTaskRef = ref<InstanceType<typeof UserSelect>>();
  168. const delegateTaskRef = ref<InstanceType<typeof UserSelect>>();
  169. const multiInstanceUserRef = ref<InstanceType<typeof UserSelect>>();
  170. const porUserRef = ref<InstanceType<typeof UserSelect>>();
  171. const props = defineProps({
  172. taskVariables: {
  173. type: Object as () => Record<string, any>,
  174. default: () => {}
  175. }
  176. });
  177. //遮罩层
  178. const loading = ref(true);
  179. //按钮
  180. const buttonDisabled = ref(true);
  181. //任务id
  182. const taskId = ref<string>('');
  183. //抄送人
  184. const selectCopyUserList = ref<FlowCopyVo[]>([]);
  185. //抄送人id
  186. const selectCopyUserIds = ref<string>(undefined);
  187. //自定义节点变量
  188. const varNodeList = ref<Map<string, string>>(undefined);
  189. //可减签的人员
  190. const deleteUserList = ref<any>([]);
  191. //弹窗可选择的人员id
  192. const popUserIds = ref<any>([]);
  193. //驳回是否显示
  194. const backVisible = ref(false);
  195. const backLoading = ref(true);
  196. const backButtonDisabled = ref(true);
  197. // 可驳回得任务节点
  198. const taskNodeList = ref([]);
  199. const nickName = ref({});
  200. //节点编码
  201. const nodeCode = ref<string>('');
  202. const buttonObj = ref<any>({
  203. pop: false,
  204. trust: false,
  205. transfer: false,
  206. addSign: false,
  207. subSign: false,
  208. termination: false,
  209. back: false
  210. });
  211. //下一节点列表
  212. const nestNodeList = ref([]);
  213. //任务
  214. const task = ref<FlowTaskVO>({
  215. id: undefined,
  216. createTime: undefined,
  217. updateTime: undefined,
  218. tenantId: undefined,
  219. definitionId: undefined,
  220. instanceId: undefined,
  221. flowName: undefined,
  222. businessId: undefined,
  223. nodeCode: undefined,
  224. nodeName: undefined,
  225. flowCode: undefined,
  226. flowStatus: undefined,
  227. formCustom: undefined,
  228. formPath: undefined,
  229. nodeType: undefined,
  230. nodeRatio: undefined,
  231. applyNode: false,
  232. buttonList: [],
  233. copyList: [],
  234. varList: undefined,
  235. businessCode: undefined,
  236. businessTitle: undefined
  237. });
  238. const dialog = reactive<DialogOption>({
  239. visible: false,
  240. title: '提示'
  241. });
  242. //减签弹窗
  243. const deleteSignatureVisible = ref(false);
  244. const form = ref<Record<string, any>>({
  245. taskId: undefined,
  246. message: undefined,
  247. assigneeMap: {},
  248. variables: {},
  249. messageType: ['1'],
  250. flowCopyList: []
  251. });
  252. const backForm = ref<Record<string, any>>({
  253. taskId: undefined,
  254. nodeCode: undefined,
  255. message: undefined,
  256. variables: {},
  257. messageType: ['1']
  258. });
  259. //打开弹窗
  260. const openDialog = async (id?: string) => {
  261. selectCopyUserIds.value = undefined;
  262. selectCopyUserList.value = [];
  263. form.value.fileId = undefined;
  264. taskId.value = id;
  265. form.value.message = undefined;
  266. dialog.visible = true;
  267. loading.value = true;
  268. buttonDisabled.value = true;
  269. const response = await getTask(taskId.value);
  270. task.value = response.data;
  271. buttonObj.value = {};
  272. task.value.buttonList?.forEach((e) => {
  273. buttonObj.value[e.code] = e.show;
  274. });
  275. selectCopyUserList.value = task.value.copyList;
  276. selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
  277. varNodeList.value = task.value.varList;
  278. console.log('varNodeList', varNodeList.value);
  279. buttonDisabled.value = false;
  280. try {
  281. const data = {
  282. taskId: taskId.value,
  283. variables: props.taskVariables
  284. };
  285. const nextData = await getNextNodeList(data);
  286. nestNodeList.value = nextData.data;
  287. } finally {
  288. loading.value = false;
  289. }
  290. };
  291. onMounted(() => {});
  292. const emits = defineEmits(['submitCallback', 'cancelCallback']);
  293. /** 办理流程 */
  294. const handleCompleteTask = async () => {
  295. form.value.taskId = taskId.value;
  296. form.value.variables = props.taskVariables;
  297. let verify = false;
  298. if (buttonObj.value.pop && nestNodeList.value && nestNodeList.value.length > 0) {
  299. nestNodeList.value.forEach((e) => {
  300. if (
  301. Object.keys(form.value.assigneeMap).length === 0 ||
  302. form.value.assigneeMap[e.nodeCode] === '' ||
  303. form.value.assigneeMap[e.nodeCode] === null ||
  304. form.value.assigneeMap[e.nodeCode] === undefined
  305. ) {
  306. verify = true;
  307. }
  308. });
  309. if (verify) {
  310. proxy?.$modal.msgWarning('请选择审批人!');
  311. return false;
  312. }
  313. } else {
  314. form.value.assigneeMap = {};
  315. }
  316. if (selectCopyUserList.value && selectCopyUserList.value.length > 0) {
  317. const flowCopyList = [];
  318. selectCopyUserList.value.forEach((e) => {
  319. const copyUser = {
  320. userId: e.userId,
  321. userName: e.userName
  322. };
  323. flowCopyList.push(copyUser);
  324. });
  325. form.value.flowCopyList = flowCopyList;
  326. }
  327. await proxy?.$modal.confirm('是否确认提交?');
  328. loading.value = true;
  329. buttonDisabled.value = true;
  330. try {
  331. await completeTask(form.value);
  332. dialog.visible = false;
  333. emits('submitCallback');
  334. proxy?.$modal.msgSuccess('操作成功');
  335. } finally {
  336. loading.value = false;
  337. buttonDisabled.value = false;
  338. }
  339. };
  340. /** 驳回弹窗打开 */
  341. const handleBackProcessOpen = async () => {
  342. backForm.value = {};
  343. backForm.value.messageType = ['1'];
  344. backVisible.value = true;
  345. backLoading.value = true;
  346. backButtonDisabled.value = true;
  347. const data = await getBackTaskNode(task.value.id, task.value.nodeCode);
  348. taskNodeList.value = data.data;
  349. backLoading.value = false;
  350. backButtonDisabled.value = false;
  351. backForm.value.nodeCode = taskNodeList.value[0].nodeCode;
  352. };
  353. /** 驳回流程 */
  354. const handleBackProcess = async () => {
  355. backForm.value.taskId = taskId.value;
  356. await proxy?.$modal.confirm('是否确认驳回到申请人?');
  357. loading.value = true;
  358. backLoading.value = true;
  359. backButtonDisabled.value = true;
  360. await backProcess(backForm.value).finally(() => {
  361. loading.value = false;
  362. buttonDisabled.value = false;
  363. });
  364. dialog.visible = false;
  365. backLoading.value = false;
  366. backButtonDisabled.value = false;
  367. emits('submitCallback');
  368. proxy?.$modal.msgSuccess('操作成功');
  369. };
  370. //取消
  371. const cancel = async () => {
  372. dialog.visible = false;
  373. buttonDisabled.value = false;
  374. nickName.value = {};
  375. form.value.assigneeMap = {};
  376. emits('cancelCallback');
  377. };
  378. //打开抄送人员
  379. const openUserSelectCopy = () => {
  380. userSelectCopyRef.value.open();
  381. };
  382. //确认抄送人员
  383. const userSelectCopyCallBack = (data: FlowCopyVo[]) => {
  384. if (data && data.length > 0) {
  385. selectCopyUserList.value = data;
  386. selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
  387. }
  388. };
  389. //删除抄送人员
  390. const handleCopyCloseTag = (user: FlowCopyVo) => {
  391. const userId = user.userId;
  392. // 使用split删除用户
  393. const index = selectCopyUserList.value.findIndex((item) => item.userId === userId);
  394. selectCopyUserList.value.splice(index, 1);
  395. selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
  396. };
  397. //加签
  398. const openMultiInstanceUser = async () => {
  399. multiInstanceUserRef.value.open();
  400. };
  401. //加签
  402. const addMultiInstanceUser = async (data) => {
  403. if (data && data.length > 0) {
  404. const taskOperationBo = reactive<TaskOperationBo>({
  405. userIds: data.map((e) => e.userId),
  406. taskId: taskId.value,
  407. message: form.value.message
  408. });
  409. await proxy?.$modal.confirm('是否确认提交?');
  410. loading.value = true;
  411. buttonDisabled.value = true;
  412. await taskOperation(taskOperationBo, 'addSignature').finally(() => {
  413. loading.value = false;
  414. buttonDisabled.value = false;
  415. });
  416. dialog.visible = false;
  417. emits('submitCallback');
  418. proxy?.$modal.msgSuccess('操作成功');
  419. } else {
  420. proxy?.$modal.msgWarning('请选择用户!');
  421. }
  422. };
  423. //减签
  424. const deleteMultiInstanceUser = async (row) => {
  425. await proxy?.$modal.confirm('是否确认提交?');
  426. loading.value = true;
  427. buttonDisabled.value = true;
  428. const taskOperationBo = reactive<TaskOperationBo>({
  429. userIds: [row.userId],
  430. taskId: taskId.value,
  431. message: form.value.message
  432. });
  433. await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
  434. loading.value = false;
  435. buttonDisabled.value = false;
  436. });
  437. dialog.visible = false;
  438. emits('submitCallback');
  439. proxy?.$modal.msgSuccess('操作成功');
  440. };
  441. //打开转办
  442. const openTransferTask = () => {
  443. transferTaskRef.value.open();
  444. };
  445. //转办
  446. const handleTransferTask = async (data) => {
  447. if (data && data.length > 0) {
  448. const taskOperationBo = reactive<TaskOperationBo>({
  449. userId: data[0].userId,
  450. taskId: taskId.value,
  451. message: form.value.message
  452. });
  453. await proxy?.$modal.confirm('是否确认提交?');
  454. loading.value = true;
  455. buttonDisabled.value = true;
  456. await taskOperation(taskOperationBo, 'transferTask').finally(() => {
  457. loading.value = false;
  458. buttonDisabled.value = false;
  459. });
  460. dialog.visible = false;
  461. emits('submitCallback');
  462. proxy?.$modal.msgSuccess('操作成功');
  463. } else {
  464. proxy?.$modal.msgWarning('请选择用户!');
  465. }
  466. };
  467. //打开委托
  468. const openDelegateTask = () => {
  469. delegateTaskRef.value.open();
  470. };
  471. //委托
  472. const handleDelegateTask = async (data) => {
  473. if (data && data.length > 0) {
  474. const taskOperationBo = reactive<TaskOperationBo>({
  475. userId: data[0].userId,
  476. taskId: taskId.value,
  477. message: form.value.message
  478. });
  479. await proxy?.$modal.confirm('是否确认提交?');
  480. loading.value = true;
  481. buttonDisabled.value = true;
  482. await taskOperation(taskOperationBo, 'delegateTask').finally(() => {
  483. loading.value = false;
  484. buttonDisabled.value = false;
  485. });
  486. dialog.visible = false;
  487. emits('submitCallback');
  488. proxy?.$modal.msgSuccess('操作成功');
  489. } else {
  490. proxy?.$modal.msgWarning('请选择用户!');
  491. }
  492. };
  493. //终止任务
  494. const handleTerminationTask = async () => {
  495. const params = {
  496. taskId: taskId.value,
  497. comment: form.value.message
  498. };
  499. await proxy?.$modal.confirm('是否确认终止?');
  500. loading.value = true;
  501. buttonDisabled.value = true;
  502. await terminationTask(params).finally(() => {
  503. loading.value = false;
  504. buttonDisabled.value = false;
  505. });
  506. dialog.visible = false;
  507. emits('submitCallback');
  508. proxy?.$modal.msgSuccess('操作成功');
  509. };
  510. const handleTaskUser = async () => {
  511. const data = await currentTaskAllUser(taskId.value);
  512. deleteUserList.value = data.data;
  513. if (deleteUserList.value && deleteUserList.value.length > 0) {
  514. deleteUserList.value.forEach((e) => {
  515. e.nodeName = task.value.nodeName;
  516. });
  517. }
  518. deleteSignatureVisible.value = true;
  519. };
  520. // 选择人员
  521. const choosePeople = async (data) => {
  522. if (!data.permissionFlag) {
  523. proxy?.$modal.msgError('没有可选择的人员,请联系管理员!');
  524. }
  525. popUserIds.value = data.permissionFlag;
  526. nodeCode.value = data.nodeCode;
  527. porUserRef.value.open();
  528. };
  529. //确认选择
  530. const handlePopUser = async (userList) => {
  531. const userIds = userList.map((item) => {
  532. return item.userId;
  533. });
  534. const nickNames = userList.map((item) => {
  535. return item.nickName;
  536. });
  537. form.value.assigneeMap[nodeCode.value] = userIds.join(',');
  538. nickName.value[nodeCode.value] = nickNames.join(',');
  539. };
  540. /**
  541. * 对外暴露子组件方法
  542. */
  543. defineExpose({
  544. openDialog
  545. });
  546. </script>