TaskFlow.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <template>
  2. <div>
  3. <!-- 入库申请-审核阶段 -->
  4. <!-- audit history -->
  5. <div
  6. v-if="flowHistoryList.length && curTaskApply.flowStatus !== 'START'"
  7. class="task-audit-history flow-timeline"
  8. >
  9. <h4 class="mb-4">审核记录:</h4>
  10. <el-timeline>
  11. <el-timeline-item
  12. v-for="flow in flowHistoryList"
  13. :key="flow.stepKey"
  14. :type="flow.type"
  15. hide-timestamp
  16. size="large"
  17. placement="top"
  18. :class="{ 'timeline-item-stop': flow.nextIsNewFlow }"
  19. >
  20. <div class="flow-item">
  21. <div class="flow-item-content">
  22. <p v-if="flow.createTime" class="flow-item-time">
  23. {{ flow.createTime | timestampFilter }}
  24. </p>
  25. <h4 class="flow-item-title">{{ flow.approveUserName }}</h4>
  26. <p class="flow-item-desc">
  27. <span
  28. v-if="flow.approveOperation"
  29. :class="{
  30. 'color-danger': flow.approveOperation === 'REJECT',
  31. }"
  32. >{{
  33. flow.approveOperation | flowApproveOperationTypeFilter
  34. }}</span
  35. >
  36. <span>{{ flow.approveRemark }}</span>
  37. </p>
  38. <div v-if="flow.attachments.length" class="flow-item-attachment">
  39. <span>附件:</span>
  40. <more-btn
  41. v-for="item in flow.attachments"
  42. :key="item.name"
  43. type="text"
  44. class="btn-primary"
  45. :data="`${item.name}卷:${item.filename}`"
  46. :show-count="20"
  47. @click="downloadPaper(item)"
  48. ></more-btn>
  49. </div>
  50. <div
  51. v-if="flow.isApproveSetFlowNextNode && approveUsers.length"
  52. class="flow-item-users"
  53. >
  54. <span>审批人:</span>
  55. <el-tag
  56. v-for="user in approveUsers"
  57. :key="user.id"
  58. size="small"
  59. :disable-transitions="false"
  60. >
  61. {{ user.name }}
  62. </el-tag>
  63. </div>
  64. </div>
  65. <div
  66. v-if="
  67. flow.isApproveSetFlowNextNode &&
  68. ((taskStatus.IS_AUDIT && auditModal.approvePass === 'PASS') ||
  69. curTaskApply.flowStatus === 'REJECT' ||
  70. curTaskApply.flowStatus === 'CANCEL')
  71. "
  72. class="flow-item-action"
  73. >
  74. <el-button
  75. class="user-select"
  76. icon="el-icon-plus"
  77. @click="toSelectNextFlowUser"
  78. ></el-button>
  79. <p v-if="!approveUsers.length" class="tips-info tips-error">
  80. 请选择审核人
  81. </p>
  82. </div>
  83. </div>
  84. </el-timeline-item>
  85. </el-timeline>
  86. </div>
  87. <!-- 入库申请-申请阶段 -->
  88. <div
  89. v-if="flowList.length && curTaskApply.flowStatus === 'START'"
  90. class="task-audit-history flow-timeline"
  91. >
  92. <h4 class="mb-4">流程:</h4>
  93. <el-timeline>
  94. <el-timeline-item
  95. v-for="flow in flowList"
  96. :key="flow.taskKey"
  97. :type="flow.type"
  98. >
  99. <div class="flow-item">
  100. <div class="flow-item-content">
  101. <h4 class="flow-item-title">{{ flow.taskName }}</h4>
  102. <p v-if="flow.approveUserNames" class="flow-item-desc">
  103. {{ flow.approveUserNames }}
  104. </p>
  105. <div
  106. v-if="flow.isApproveSetFlowNextNode && approveUsers.length"
  107. class="flow-item-users"
  108. >
  109. <span>审批人:</span>
  110. <el-tag
  111. v-for="user in approveUsers"
  112. :key="user.id"
  113. size="small"
  114. :disable-transitions="false"
  115. >
  116. {{ user.name }}
  117. </el-tag>
  118. </div>
  119. </div>
  120. <div v-if="flow.isApproveSetFlowNextNode" class="flow-item-action">
  121. <el-button
  122. class="user-select"
  123. icon="el-icon-plus"
  124. @click="toSelectNextFlowUser"
  125. ></el-button>
  126. <p v-if="!approveUsers.length" class="tips-info tips-error">
  127. 请选择审核人
  128. </p>
  129. </div>
  130. </div>
  131. </el-timeline-item>
  132. </el-timeline>
  133. </div>
  134. <!-- audit -->
  135. <div v-if="taskStatus.IS_AUDIT" class="task-audit">
  136. <el-form
  137. ref="auditModalComp"
  138. :model="auditModal"
  139. :rules="auditRules"
  140. label-width="90px"
  141. >
  142. <el-form-item prop="approvePass" label="审批操作:">
  143. <el-radio-group
  144. v-model="auditModal.approvePass"
  145. @change="approvePassChange"
  146. >
  147. <el-radio
  148. v-for="(val, key) in TASK_AUDIT_RESULT"
  149. :key="key"
  150. :label="key"
  151. >
  152. {{ val }}
  153. <span v-if="key === 'EXCHANGE'" class="color-danger"
  154. >(当前审核环节不归自己审核,需要转给其他人审核)</span
  155. >
  156. </el-radio>
  157. </el-radio-group>
  158. </el-form-item>
  159. <el-form-item
  160. v-if="auditModal.approvePass === 'REJECT'"
  161. prop="setup"
  162. label="驳回节点:"
  163. >
  164. <el-select
  165. v-model="auditModal.setup"
  166. placeholder="请选择"
  167. style="width: 100%"
  168. >
  169. <el-option
  170. v-for="item in rejectSetupList"
  171. :key="item.taskKey"
  172. :value="item.setup"
  173. :label="item.name"
  174. >
  175. </el-option>
  176. </el-select>
  177. </el-form-item>
  178. <el-form-item
  179. v-if="auditModal.approvePass !== 'EXCHANGE'"
  180. :key="auditModal.approvePass"
  181. prop="remark"
  182. label="审批意见:"
  183. >
  184. <el-input
  185. class="mb-2"
  186. v-model="auditModal.remark"
  187. type="textarea"
  188. resize="none"
  189. :rows="5"
  190. :maxlength="100"
  191. clearable
  192. show-word-limit
  193. placeholder="建议不超过100个字"
  194. ref="ReasonInput"
  195. ></el-input>
  196. </el-form-item>
  197. <el-form-item
  198. v-if="auditModal.approvePass === 'EXCHANGE'"
  199. prop="userId"
  200. label="审批人:"
  201. >
  202. <el-tag
  203. v-for="user in exchangeUsers"
  204. :key="user.id"
  205. :disable-transitions="false"
  206. size="medium"
  207. >
  208. {{ user.name }}
  209. </el-tag>
  210. <el-button
  211. class="ml-2"
  212. size="mini"
  213. type="primary"
  214. @click="toSelectExchangeUser"
  215. >选择用户</el-button
  216. >
  217. </el-form-item>
  218. </el-form>
  219. </div>
  220. <!-- select-user-dialog -->
  221. <select-user-dialog
  222. v-if="taskStatus.IS_AUDIT || IS_NEED_SELECT_APPROVE_USER"
  223. ref="SelectUserDialog"
  224. :user-limit-count="userLimitCount"
  225. :filter-roles="userFilterRoles"
  226. :users="curSelectedUsers"
  227. @modified="userSelected"
  228. ></select-user-dialog>
  229. </div>
  230. </template>
  231. <script>
  232. import { mapState, mapMutations, mapActions } from "vuex";
  233. import {
  234. taskFlowDetail,
  235. taskFlowNodeInfo,
  236. flowDetailByFlowId,
  237. } from "../../../base/api";
  238. import { TASK_AUDIT_RESULT } from "@/constants/enumerate";
  239. import SelectUserDialog from "../../../base/components/SelectUserDialog";
  240. export default {
  241. name: "task-flow",
  242. components: { SelectUserDialog },
  243. data() {
  244. return {
  245. flowList: [],
  246. flowInfo: {},
  247. flowHistoryList: [],
  248. exchangeUsers: [],
  249. selectUserType: "exchange", // exchange:转审,approve:下一节点审核
  250. curSelectedUsers: [],
  251. // 选择下一节点审批人
  252. IS_NEED_SELECT_APPROVE_USER: false,
  253. nextFlowTaskResult: {}, //下一节点信息
  254. approveUsers: [],
  255. userLimitCount: 1,
  256. userFilterRoles: [],
  257. // audit
  258. TASK_AUDIT_RESULT: { ...TASK_AUDIT_RESULT, EXCHANGE: "转他人审批" },
  259. rejectSetupList: [], // 可以驳回的节点
  260. auditModal: {
  261. approvePass: "PASS",
  262. setup: "",
  263. remark: "",
  264. userId: "",
  265. },
  266. auditRules: {
  267. approvePass: [
  268. {
  269. required: true,
  270. },
  271. ],
  272. setup: [
  273. {
  274. required: true,
  275. validator: (rule, value, callback) => {
  276. if (this.auditModal.approvePass === "REJECT" && !value) {
  277. callback(new Error(`请选择要驳回到的节点`));
  278. } else {
  279. callback();
  280. }
  281. },
  282. trigger: "change",
  283. },
  284. ],
  285. remark: [
  286. {
  287. required: false,
  288. validator: (rule, value, callback) => {
  289. if (this.auditModal.approvePass === "REJECT" && !value) {
  290. callback(new Error(`请输入审批意见`));
  291. } else {
  292. callback();
  293. }
  294. },
  295. trigger: "change",
  296. },
  297. ],
  298. userId: [
  299. {
  300. required: true,
  301. message: "请选择审批人",
  302. trigger: "change",
  303. },
  304. ],
  305. },
  306. };
  307. },
  308. computed: {
  309. ...mapState("exam", [
  310. "editType",
  311. "examTask",
  312. "curTaskApply",
  313. "taskStatus",
  314. "auditLogCache",
  315. ]),
  316. },
  317. mounted() {
  318. this.initData();
  319. },
  320. methods: {
  321. ...mapMutations("exam", [
  322. "setEditType",
  323. "setExamTask",
  324. "setCurTaskApply",
  325. "setAuditLogCache",
  326. ]),
  327. ...mapActions("exam", ["addPreviewLog"]),
  328. async initData() {
  329. const auditLogCache = { paper: {}, card: {} };
  330. (this.curTaskApply.examTaskDetailList || []).map((item) => {
  331. const paperAttachmentIds = item.paperAttachmentIds
  332. ? JSON.parse(item.paperAttachmentIds)
  333. : [];
  334. paperAttachmentIds.forEach((aitem) => {
  335. aitem.serialNumber = item.serialNumber;
  336. if (
  337. aitem.attachmentId &&
  338. this.curTaskApply.auditContent.includes("PAPER")
  339. )
  340. auditLogCache.paper[aitem.attachmentId] = false;
  341. if (aitem.cardId && this.curTaskApply.auditContent.includes("CARD"))
  342. auditLogCache.card[aitem.cardId] = false;
  343. });
  344. });
  345. this.setAuditLogCache(auditLogCache);
  346. if (this.curTaskApply.flowStatus === "START") {
  347. await this.getFlowList();
  348. } else {
  349. await this.getFlowHistory();
  350. if (!this.taskStatus.IS_PREVIEW && this.curTaskApply.review)
  351. this.getCurFlowNodeInfo();
  352. }
  353. },
  354. async getFlowHistory() {
  355. if (!this.curTaskApply.flowId) return;
  356. const data = await taskFlowDetail(this.curTaskApply.flowId).catch(
  357. () => {}
  358. );
  359. if (!data) return;
  360. const FLOW_STATUS = {
  361. SUBMIT: "success",
  362. APPROVE: "success",
  363. EXCHANGE: "success",
  364. REJECT: "danger",
  365. END: "success",
  366. };
  367. let nextFlowId = "";
  368. this.flowHistoryList = data.tfFlowViewLogResultList.map((item, index) => {
  369. const nextItem = data.tfFlowViewLogResultList[index + 1];
  370. nextFlowId = nextItem ? nextItem.flowId : item.flowId;
  371. item.nextIsNewFlow = nextFlowId !== item.flowId;
  372. item.type = FLOW_STATUS[item.approveOperation];
  373. item.attachments = item.paperAttachmentId
  374. ? JSON.parse(item.paperAttachmentId)
  375. : [];
  376. item.isApproveSetFlowNextNode = false;
  377. return item;
  378. });
  379. const flowIsEnd = data.currFlowTaskResult.taskKey === "end";
  380. this.flowHistoryList.push({
  381. type: flowIsEnd ? "success" : "current",
  382. stepKey: this.$randomCode(),
  383. approveSetup: data.currFlowTaskResult.setup, //审批人节点
  384. approveRemark: data.currFlowTaskResult.taskName, //审批信息
  385. approveOperation: flowIsEnd ? "END" : "",
  386. approveUserName: data.currFlowTaskResult.approveUserNames, //审批人
  387. createTime: null,
  388. nextIsNewFlow: false,
  389. isApproveSetFlowNextNode: false,
  390. attachments: [],
  391. });
  392. },
  393. async getFlowList() {
  394. if (!this.curTaskApply.flowId) return;
  395. const data = await flowDetailByFlowId(this.curTaskApply.flowId);
  396. if (!data) return;
  397. const modelType =
  398. data.flowTaskResultList[0] && data.flowTaskResultList[0].modelType;
  399. this.flowInfo = {
  400. customFlowId: data.id,
  401. version: data.version,
  402. };
  403. const nextFlowNodeIndex = 1;
  404. this.IS_NEED_SELECT_APPROVE_USER = modelType === "APPROVE_SET";
  405. const flowList = data.flowTaskResultList || [];
  406. this.flowList = flowList.map((flow, index) => {
  407. flow.isApproveSetFlowNextNode =
  408. this.IS_NEED_SELECT_APPROVE_USER && index === nextFlowNodeIndex;
  409. return flow;
  410. });
  411. if (this.flowList.length) {
  412. this.flowList[0].type = "success";
  413. }
  414. this.nextFlowTaskResult = this.flowList[nextFlowNodeIndex];
  415. },
  416. async getCurFlowNodeInfo() {
  417. const data = await taskFlowNodeInfo(this.curTaskApply.flowTaskId);
  418. this.rejectSetupList = (data && data.rejectSetupList) || [];
  419. this.rejectSetupList.forEach((item) => {
  420. item.name = `【${item.taskName}】${item.approveUserNames}`;
  421. });
  422. this.nextFlowTaskResult = data.nextFlowTaskResult;
  423. // 被打回给命题老师之后,命题老师只能通过。
  424. if (data.setup === 1 && !this.rejectSetupList.length) {
  425. this.TASK_AUDIT_RESULT = { PASS: "通过" };
  426. this.auditModal.approvePass = "PASS";
  427. }
  428. // 判断发起人自选,不管approveUserNames有没有值,都可以选择下个审核人员
  429. const { modelType, taskKey } = this.nextFlowTaskResult;
  430. this.IS_NEED_SELECT_APPROVE_USER =
  431. modelType === "APPROVE_SET" && taskKey.toLowerCase() !== "end";
  432. this.flowHistoryList[
  433. this.flowHistoryList.length - 1
  434. ].isApproveSetFlowNextNode = this.IS_NEED_SELECT_APPROVE_USER;
  435. },
  436. approvePassChange() {
  437. this.auditRules.remark[0].required =
  438. this.auditModal.approvePass === "REJECT";
  439. this.$nextTick(() => {
  440. this.$refs.auditModalComp.clearValidate();
  441. });
  442. },
  443. async downloadPaper(attachment) {
  444. if (!attachment.attachmentId) return;
  445. this.addPreviewLog({ attachment, type: "paper" });
  446. this.$emit("view-attachment", attachment);
  447. },
  448. toSelectNextFlowUser() {
  449. if (!this.IS_NEED_SELECT_APPROVE_USER) return;
  450. this.userLimitCount =
  451. this.nextFlowTaskResult.approveUserCountType === "ONE" ? 1 : 0;
  452. this.userFilterRoles =
  453. this.nextFlowTaskResult.approveUserSelectRange === "ROLE"
  454. ? this.nextFlowTaskResult.approveUserSelectRoles.map(
  455. (item) => item.id
  456. )
  457. : [];
  458. this.selectUserType = "approve";
  459. this.curSelectedUsers = this.approveUsers;
  460. this.$refs.SelectUserDialog.open();
  461. },
  462. toSelectExchangeUser() {
  463. this.userLimitCount = 1;
  464. this.userFilterRoles = [];
  465. this.curSelectedUsers = this.exchangeUsers;
  466. this.selectUserType = "exchange";
  467. this.$refs.SelectUserDialog.open();
  468. },
  469. userSelected(users) {
  470. if (this.selectUserType === "exchange") {
  471. this.exchangeUsers = users;
  472. this.auditModal.userId = users[0].id;
  473. } else {
  474. this.approveUsers = users;
  475. }
  476. },
  477. // action
  478. async checkAuditData() {
  479. const res =
  480. !Object.values(this.auditLogCache.paper).some((item) => !item) &&
  481. !Object.values(this.auditLogCache.card).some((item) => !item);
  482. if (!res) {
  483. this.$message.error(
  484. "还有未被查看过的题卡或试卷,请点击试卷及题卡进行查看审核"
  485. );
  486. return;
  487. }
  488. const valid = await this.$refs.auditModalComp.validate().catch(() => {});
  489. if (!valid) return;
  490. if (
  491. this.auditModal.approvePass === "PASS" &&
  492. this.IS_NEED_SELECT_APPROVE_USER &&
  493. !this.approveUsers.length
  494. ) {
  495. this.$message.error("请设置审核人员!");
  496. return;
  497. }
  498. return true;
  499. },
  500. getAuditData() {
  501. let datas = {};
  502. if (this.auditModal.approvePass === "EXCHANGE") {
  503. datas = {
  504. approvePass: this.auditModal.approvePass,
  505. taskId: this.curTaskApply.flowTaskId,
  506. userId: this.auditModal.userId,
  507. };
  508. } else {
  509. datas = { ...this.auditModal };
  510. datas.taskId = this.curTaskApply.flowTaskId;
  511. if (
  512. this.auditModal.approvePass === "PASS" &&
  513. this.IS_NEED_SELECT_APPROVE_USER
  514. )
  515. datas.approveUserIds = this.approveUsers.map((item) => item.id);
  516. }
  517. datas.actionName = this.TASK_AUDIT_RESULT[datas.approvePass];
  518. return datas;
  519. },
  520. getData() {
  521. if (this.IS_NEED_SELECT_APPROVE_USER) {
  522. return {
  523. approveUserIds: this.approveUsers.map((item) => item.id),
  524. };
  525. }
  526. return {};
  527. },
  528. checkData() {
  529. if (this.IS_NEED_SELECT_APPROVE_USER && !this.approveUsers.length) {
  530. this.$message.error("请设置审核人员!");
  531. return;
  532. }
  533. return true;
  534. },
  535. },
  536. };
  537. </script>