processSubmitDialog.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. <template>
  2. <ele-modal
  3. custom-class="ele-dialog-form long-dialog-form"
  4. :centered="true"
  5. :visible="processSubmitDialogFlag"
  6. v-if="processSubmitDialogFlag"
  7. title="提交审核"
  8. append-to-body
  9. :close-on-click-modal="false"
  10. width="50%"
  11. :before-close="cancel"
  12. :maxable="true"
  13. :resizable="true"
  14. >
  15. <!-- <el-steps :active="active" align-center style="transform: scale(0.8)">-->
  16. <!-- <el-step title="流程发起"></el-step>-->
  17. <!-- <el-step title="流程通知"></el-step>-->
  18. <!-- </el-steps>-->
  19. <el-form
  20. ref="form"
  21. :rules="rules"
  22. class="el-form-box"
  23. :model="form"
  24. label-width="80px"
  25. >
  26. <el-row v-show="active == 0">
  27. <el-col :span="12">
  28. <el-form-item label="流程分类">
  29. <ele-tree-select
  30. @change="changeLCFL"
  31. clearable
  32. ref="processTypeRef"
  33. filterable
  34. :data="LCFLList"
  35. v-model="form.LCFL"
  36. childrenKey="children"
  37. valueKey="id"
  38. labelKey="name"
  39. placeholder="请选择"
  40. default-expand-all
  41. />
  42. </el-form-item>
  43. </el-col>
  44. <el-col :span="12">
  45. <el-form-item label="发起流程">
  46. <el-select
  47. filterable
  48. v-model="form.FQLC"
  49. @change="changeFQLC"
  50. style="width: 100%"
  51. >
  52. <el-option
  53. v-for="item in processList"
  54. :key="item.id"
  55. :value="item.id"
  56. :label="item.name"
  57. ></el-option>
  58. </el-select>
  59. </el-form-item>
  60. </el-col>
  61. </el-row>
  62. <el-row v-show="active == 0">
  63. <el-col :span="12">
  64. <el-form-item label="流程名称">
  65. <el-input v-model="form.name" clearable></el-input>
  66. </el-form-item>
  67. </el-col>
  68. <el-col :span="12">
  69. <el-form-item label="流程标识">
  70. <el-input v-model="form.key" disabled></el-input>
  71. </el-form-item>
  72. </el-col>
  73. </el-row>
  74. <headerTitle
  75. v-show="active == 0"
  76. title="流程执行人/流程图"
  77. style="margin-top: 30px"
  78. ></headerTitle>
  79. <el-table
  80. v-show="active == 0"
  81. :data="datasource"
  82. border
  83. style="margin-bottom: 10px"
  84. >
  85. <el-table-column
  86. label="任务节点"
  87. align="center"
  88. prop="taskDefinitionName"
  89. width="140"
  90. fixed
  91. />
  92. <el-table-column
  93. label="需要审批的角色"
  94. align="center"
  95. prop="options"
  96. min-width="200px"
  97. >
  98. <template v-slot="scope">
  99. <div
  100. v-if="
  101. scope.row.type !== 60 &&
  102. scope.row.type !== 70 &&
  103. scope.row.type !== 71 &&
  104. scope.row.options.length > 0
  105. "
  106. style="display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;"
  107. >
  108. <el-tag
  109. size="small"
  110. :key="option"
  111. v-for="option in scope.row.options"
  112. style="margin: 1px;"
  113. hit
  114. >
  115. {{ getAssignRuleOptionName(scope.row, option) }}
  116. </el-tag>
  117. </div>
  118. <el-tag
  119. size="small"
  120. v-if="
  121. scope.row.type === 60 ||
  122. scope.row.type === 70 ||
  123. scope.row.type === 80 ||
  124. scope.row.type === 71
  125. "
  126. >
  127. {{ getAssignRuleOptionName(scope.row) }}
  128. </el-tag>
  129. </template>
  130. </el-table-column>
  131. <el-table-column
  132. label="审批人选择"
  133. align="center"
  134. min-width="240px"
  135. >
  136. <template v-slot="scope">
  137. <el-select
  138. v-if="!isInitiatorNode(scope.row)"
  139. v-model="scope.row.selectedUsers"
  140. multiple
  141. filterable
  142. clearable
  143. placeholder="请选择审批人"
  144. style="width: 100%;"
  145. >
  146. <el-option
  147. v-for="user in getFilteredUserOptions(scope.row)"
  148. :key="user.id"
  149. :label="user.nickname || user.name"
  150. :value="user.id"
  151. />
  152. </el-select>
  153. <span v-else style="color: #909399; font-size: 12px;">发起人</span>
  154. </template>
  155. </el-table-column>
  156. <el-table-column
  157. label="审批方式"
  158. align="center"
  159. prop="approvalType"
  160. width="100"
  161. >
  162. <template v-slot="scope">
  163. <el-tag
  164. :type="scope.row.approvalType === 1 ? 'success' : 'warning'"
  165. size="small"
  166. >
  167. {{ getApprovalTypeName(scope.row.approvalType) }}
  168. </el-tag>
  169. </template>
  170. </el-table-column>
  171. </el-table>
  172. <my-process-viewer
  173. v-show="active == 0"
  174. style="min-width: 100%; height: 200px"
  175. key="designer"
  176. v-model="bpmnXML"
  177. />
  178. <el-table v-show="active == 1" :data="form.noticeScope" border>
  179. <el-table-column
  180. label="类型"
  181. align="center"
  182. prop="taskDefinitionName"
  183. width="140"
  184. fixed
  185. />
  186. <el-table-column
  187. label="结果"
  188. align="center"
  189. prop="options"
  190. min-width="140px"
  191. >
  192. </el-table-column>
  193. </el-table>
  194. </el-form>
  195. <div slot="footer">
  196. <!-- <el-button-->
  197. <!-- type="primary"-->
  198. <!-- size="small"-->
  199. <!-- v-if="active == 0"-->
  200. <!-- @click="() => (active = 1)"-->
  201. <!-- >下一步-->
  202. <!-- </el-button>-->
  203. <!-- <el-button-->
  204. <!-- type="primary"-->
  205. <!-- size="small"-->
  206. <!-- v-if="active == 1"-->
  207. <!-- @click="() => (active = 0)"-->
  208. <!-- >上一步-->
  209. <!-- </el-button>-->
  210. <el-button type="primary" size="small" @click="submit" v-click-once
  211. >提交</el-button
  212. >
  213. <el-button
  214. v-if="isNotNeedProcess"
  215. type="primary"
  216. size="small"
  217. @click="submit1"
  218. v-click-once
  219. >提交并发布</el-button
  220. >
  221. <el-button size="small" @click="cancel">关闭</el-button>
  222. </div>
  223. </ele-modal>
  224. </template>
  225. <script>
  226. import {
  227. getModelPage,
  228. getProcessDefinitionBpmnXML,
  229. getProcessDefinitionInfo,
  230. getTaskAssignRuleList,
  231. listAllUserBind,
  232. listSimpleUserGroups,
  233. processInstanceCreateAPI,
  234. getProduceTreeByCode,
  235. produceroutingRelease
  236. } from './api';
  237. import { treeClassifyCodeEnum } from '@/enum/dict';
  238. import { listRoles } from '@/api/system/role';
  239. import { listOrganizations } from '@/api/system/organization';
  240. import dictMixins from '@/mixins/dictMixins';
  241. import { getByCode } from '@/api/system/dictionary-data';
  242. import { topLevel1, topLevel2, topLevel3 } from '@/enum/dict';
  243. export default {
  244. name: 'processSubmitDialog',
  245. mixins: [dictMixins],
  246. props: {
  247. processSubmitDialogFlag: {
  248. type: Boolean,
  249. default: false
  250. },
  251. isNotNeedProcess: {
  252. type: Boolean,
  253. default: false
  254. },
  255. approvalStatus: {
  256. type: Number,
  257. default: 2
  258. },
  259. apiFunName: {
  260. type: String,
  261. default: ''
  262. },
  263. customSubmit: {
  264. type: Function,
  265. default: null
  266. }
  267. },
  268. data() {
  269. return {
  270. form: {
  271. LCFL: '',
  272. FQLC: '',
  273. processDefinitionId: '',
  274. name: '',
  275. businessId: '',
  276. noticeScope: [],
  277. businessKey: '',
  278. formCreateUserId: ''
  279. },
  280. produceroutingRelease,
  281. active: 0,
  282. bpmnXML: null,
  283. LCFLList: [],
  284. processList: [],
  285. datasource: [],
  286. roleOptions: [],
  287. deptOptions: [],
  288. deptTreeOptions: [],
  289. postOptions: [],
  290. userOptions: [],
  291. userGroupOptions: [],
  292. dictList: {},
  293. rules: {},
  294. loading: null
  295. };
  296. },
  297. async created() {
  298. let typeObj = await getProduceTreeByCode('PROCESS001');
  299. this.LCFLList = typeObj[0].children;
  300. // 获得角色列表
  301. this.roleOptions = [];
  302. listRoles({
  303. current: 1,
  304. size: 9999
  305. }).then((data) => {
  306. this.roleOptions.push(...data.list);
  307. });
  308. // 获得部门列表
  309. this.deptOptions = [];
  310. this.deptTreeOptions = [];
  311. listOrganizations().then((data) => {
  312. this.deptOptions.push(...data);
  313. this.deptTreeOptions.push(...this.handleTree(data, 'id'));
  314. });
  315. // 获得岗位列表 暂无岗位概念
  316. this.postOptions = [];
  317. /*listSimplePosts().then(response => {
  318. this.postOptions.push(...response.data);
  319. });*/
  320. // 获得用户列表
  321. this.userOptions = [];
  322. listAllUserBind().then((data) => {
  323. this.userOptions.push(...data);
  324. });
  325. // 获得用户组列表
  326. this.userGroupOptions = [];
  327. listSimpleUserGroups().then((response) => {
  328. this.userGroupOptions.push(...response);
  329. });
  330. //this.dictEnum['工作流任务分配自定义脚本']
  331. await this.getDictList(this.dictEnum['工作流任务分配自定义脚本']);
  332. await this.getDictList(this.dictEnum['工种类型']);
  333. },
  334. methods: {
  335. init(row = {}) {
  336. this.form = { ...this.form, ...row };
  337. // this.form.businessId = row.id;
  338. // this.form.businessKey = row.businessKey;
  339. // this.form.formCreateUserId = row.createUserId;
  340. this.getDefaultInfo(row.businessKey);
  341. },
  342. async getDefaultInfo(businessKey) {
  343. let info = await getProcessDefinitionInfo({ code: businessKey });
  344. this.form.LCFL = info?.category;
  345. this.form.FQLC = info?.modelId;
  346. this.form.name = info?.name;
  347. this.form.key = info?.key;
  348. this.form.processDefinitionId = info?.id;
  349. if (this.form.LCFL) await this.getProcessList(this.form.LCFL);
  350. if (this.form.FQLC) {
  351. await this.getProcessDefinitionBpmnXMLInfo(info.id);
  352. await this.getTaskAssignRuleListInfo({
  353. modelId: info.modelId,
  354. processDefinitionId: info.id
  355. });
  356. }
  357. },
  358. async changeLCFL(val) {
  359. this.bpmnXML = null;
  360. this.form.processDefinitionId = null;
  361. this.form.FQLC = null;
  362. await this.getProcessList(val);
  363. },
  364. async getProcessList(val) {
  365. let params = {
  366. pageNo: 1,
  367. pageSize: 999,
  368. processTypeId: val
  369. };
  370. const { list } = await getModelPage(params);
  371. this.processList = list.filter((item) => item.processDefinition);
  372. },
  373. async changeFQLC(val) {
  374. if (!val) return;
  375. let find = this.processList.find((item) => item.id === val) || {};
  376. this.form.name = find.name;
  377. this.form.key = find.key;
  378. this.form.processDefinitionId = find.processDefinition.id;
  379. await this.getProcessDefinitionBpmnXMLInfo(find.processDefinition.id);
  380. await this.getTaskAssignRuleListInfo({
  381. modelId: find.id,
  382. processDefinitionId: find.processDefinition.id
  383. });
  384. },
  385. async getProcessDefinitionBpmnXMLInfo(val) {
  386. // 加载流程图
  387. this.bpmnXML = await getProcessDefinitionBpmnXML(val);
  388. },
  389. async getTaskAssignRuleListInfo(find) {
  390. this.datasource = await getTaskAssignRuleList({
  391. modelId: find.modelId,
  392. processDefinitionId: find.processDefinitionId
  393. });
  394. // 从 bpmnXML 解析每个节点是否是多实例,用于判断审批方式
  395. this.datasource.forEach(row => {
  396. row.approvalType = this.getApprovalTypeFromBpmn(row.taskDefinitionKey);
  397. });
  398. },
  399. getApprovalTypeFromBpmn(taskDefinitionKey) {
  400. // 如果 bpmnXML 不存在,默认单人审批
  401. if (!this.bpmnXML) return 1;
  402. // 在 BPMN XML 中找到该 userTask 节点的内容
  403. const regex = new RegExp(
  404. `<[^:]*:?userTask[^>]*\\s(id="${taskDefinitionKey}")[^>]*>[\\s\\S]*?</[^:]*:?userTask>`
  405. );
  406. const match = this.bpmnXML.match(regex);
  407. // 没有匹配到或没有多实例配置 → 单人审批;有多实例配置 → 多人审批
  408. return match && match[0].includes('multiInstanceLoopCharacteristics') ? 2 : 1;
  409. },
  410. getAssignRuleOptionName(row, option) {
  411. if (row.type == 10) {
  412. for (const roleOption of this.roleOptions) {
  413. if (roleOption.id === option) {
  414. return roleOption.name;
  415. }
  416. }
  417. } else if (row.type === 20 || row.type === 21) {
  418. for (const deptOption of this.deptOptions) {
  419. if (deptOption.id === option) {
  420. return deptOption.name;
  421. }
  422. }
  423. } else if (row.type === 22) {
  424. option = option + ''; // 转换成 string
  425. return this.getDictV(this.dictEnum['工种类型'], option);
  426. } else if (row.type === 30 || row.type === 31 || row.type === 32) {
  427. for (const userOption of this.userOptions) {
  428. if (userOption.id === option) {
  429. return userOption.nickname || userOption.name;
  430. }
  431. }
  432. } else if (row.type === 40) {
  433. for (const userGroupOption of this.userGroupOptions) {
  434. if (userGroupOption.id === option) {
  435. return userGroupOption.name;
  436. }
  437. }
  438. } else if (row.type === 50) {
  439. option = option + ''; // 转换成 string
  440. return this.getDictV(
  441. this.dictEnum['工作流任务分配自定义脚本'],
  442. option
  443. );
  444. } else if (row.type === 60) {
  445. return row.variableName;
  446. } else if (row.type === 70 || row.type == 71) {
  447. let data = JSON.parse(row.variableName);
  448. if (data.direction == 1) {
  449. return topLevel2.find((item) => item.value == data.topLevel)?.label;
  450. } else {
  451. return topLevel1.find((item) => item.value == data.topLevel)?.label;
  452. }
  453. } else if (row.type === 80) {
  454. let data = JSON.parse(row.variableName);
  455. return topLevel3.find((item) => item.value == data.topLevel)?.label;
  456. }
  457. return '未知(' + option + ')';
  458. },
  459. isInitiatorNode(row) {
  460. return row.type === 60;
  461. },
  462. getFilteredUserOptions(row) {
  463. return this.userOptions;
  464. },
  465. getApprovalTypeName(type) {
  466. return type === 1 ? '单人审批' : '多人审批';
  467. },
  468. /**
  469. * 构造树型结构数据
  470. * @param {*} data 数据源
  471. * @param {*} id id字段 默认 'id'
  472. * @param {*} parentId 父节点字段 默认 'parentId'
  473. * @param {*} children 孩子节点字段 默认 'children'
  474. * @param {*} rootId 根Id 默认 0
  475. */
  476. handleTree(data, id, parentId, children, rootId) {
  477. id = id || 'id';
  478. parentId = parentId || 'parentId';
  479. children = children || 'children';
  480. rootId =
  481. rootId ||
  482. Math.min.apply(
  483. Math,
  484. data.map((item) => {
  485. return item[parentId];
  486. })
  487. ) ||
  488. 0;
  489. //对源数据深度克隆
  490. const cloneData = JSON.parse(JSON.stringify(data));
  491. //循环所有项
  492. const treeData = cloneData.filter((father) => {
  493. let branchArr = cloneData.filter((child) => {
  494. //返回每一项的子级数组
  495. return father[id] == child[parentId];
  496. });
  497. branchArr.length > 0 ? (father.children = branchArr) : '';
  498. //返回第一层
  499. return father[parentId] == rootId;
  500. });
  501. return treeData !== '' ? treeData : data;
  502. },
  503. getDictV(code, val) {
  504. if (!this.dictList[code]) return '';
  505. return this.dictList[code].find((item) => item.value == val)?.label;
  506. },
  507. async getDictList(code) {
  508. let { data: res } = await getByCode(code);
  509. this.dictList[code] = res.map((item) => {
  510. let values = Object.keys(item);
  511. return {
  512. value: values[0],
  513. label: item[values[0]]
  514. };
  515. });
  516. },
  517. async submit() {
  518. this.loading = this.$loading({
  519. lock: true,
  520. text: '提交中',
  521. background: 'rgba(0, 0, 0, 0.7)'
  522. });
  523. // 从表格中构建审批人映射: { taskDefinitionKey: [userId1, userId2] }
  524. const assignee = {};
  525. this.datasource.forEach(row => {
  526. if (row.selectedUsers && row.selectedUsers.length > 0) {
  527. assignee[row.taskDefinitionKey] = row.selectedUsers;
  528. }
  529. });
  530. this.form.assignee = assignee;
  531. const submitPromise = this.customSubmit
  532. ? this.customSubmit(this.form.businessId, this.form)
  533. : processInstanceCreateAPI(this.form);
  534. await submitPromise
  535. .then((res) => {
  536. console.log(res, 'res');
  537. this.loading.close();
  538. this.$message('提交成功');
  539. this.cancel();
  540. })
  541. .catch((err) => {
  542. this.loading.close();
  543. console.error(err, 'err');
  544. });
  545. },
  546. async submit1() {
  547. this.loading = this.$loading({
  548. lock: true,
  549. text: '提交中',
  550. background: 'rgba(0, 0, 0, 0.7)'
  551. });
  552. let params = {
  553. id: this.form.businessId,
  554. approvalStatus: this.approvalStatus,
  555. ...this.form
  556. };
  557. // 从表格中构建审批人映射
  558. const assignee = {};
  559. this.datasource.forEach(row => {
  560. if (row.selectedUsers && row.selectedUsers.length > 0) {
  561. assignee[row.taskDefinitionKey] = row.selectedUsers;
  562. }
  563. });
  564. params.assignee = assignee;
  565. await this[this.apiFunName](params)
  566. .then((res) => {
  567. console.log(res, 'res');
  568. this.loading.close();
  569. this.$message('提交成功');
  570. this.cancel();
  571. })
  572. .catch((err) => {
  573. console.error(err, 'err');
  574. this.loading.close();
  575. // this.$message.error(err.message || '提交审核失败');
  576. });
  577. //await processInstanceUpdateStatusAPI(params);
  578. // this.$message('提交发布成功');
  579. // this.loading.close();
  580. // this.$emit('reload');
  581. // this.cancel();
  582. },
  583. cancel() {
  584. this.$emit('update:processSubmitDialogFlag', false);
  585. this.$emit('reload');
  586. }
  587. }
  588. };
  589. </script>
  590. <style scoped lang="scss"></style>