TaskFlow.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. >{{ val }}</el-radio
  152. >
  153. </el-radio-group>
  154. </el-form-item>
  155. <el-form-item
  156. v-if="auditModal.approvePass === 'REJECT'"
  157. prop="setup"
  158. label="驳回节点:"
  159. >
  160. <el-select
  161. v-model="auditModal.setup"
  162. placeholder="请选择"
  163. style="width: 100%"
  164. >
  165. <el-option
  166. v-for="item in rejectSetupList"
  167. :key="item.taskKey"
  168. :value="item.setup"
  169. :label="item.name"
  170. >
  171. </el-option>
  172. </el-select>
  173. </el-form-item>
  174. <el-form-item
  175. v-if="auditModal.approvePass !== 'EXCHANGE'"
  176. :key="auditModal.approvePass"
  177. prop="remark"
  178. label="审批意见:"
  179. >
  180. <el-input
  181. class="mb-2"
  182. v-model="auditModal.remark"
  183. type="textarea"
  184. resize="none"
  185. :rows="5"
  186. :maxlength="100"
  187. clearable
  188. show-word-limit
  189. placeholder="建议不超过100个字"
  190. ref="ReasonInput"
  191. ></el-input>
  192. </el-form-item>
  193. <el-form-item
  194. v-if="auditModal.approvePass === 'EXCHANGE'"
  195. prop="userId"
  196. label="审批人:"
  197. >
  198. <el-tag
  199. v-for="user in exchangeUsers"
  200. :key="user.id"
  201. :disable-transitions="false"
  202. size="medium"
  203. >
  204. {{ user.name }}
  205. </el-tag>
  206. <el-button
  207. class="ml-2"
  208. size="mini"
  209. type="primary"
  210. @click="toSelectExchangeUser"
  211. >选择用户</el-button
  212. >
  213. </el-form-item>
  214. </el-form>
  215. </div>
  216. <!-- select-user-dialog -->
  217. <select-user-dialog
  218. v-if="taskStatus.IS_AUDIT || IS_NEED_SELECT_APPROVE_USER"
  219. ref="SelectUserDialog"
  220. :user-limit-count="userLimitCount"
  221. :filter-roles="userFilterRoles"
  222. :users="curSelectedUsers"
  223. @modified="userSelected"
  224. ></select-user-dialog>
  225. </div>
  226. </template>
  227. <script>
  228. import { mapStates, mapMutations } from "vuex";
  229. import {
  230. taskFlowDetail,
  231. taskFlowNodeInfo,
  232. flowDetailByFlowId,
  233. } from "../../../base/api";
  234. import { attachmentPreview } from "../../../login/api";
  235. import { TASK_AUDIT_RESULT } from "@/constants/enumerate";
  236. import SelectUserDialog from "../../../base/components/SelectUserDialog";
  237. export default {
  238. name: "task-flow",
  239. components: { SelectUserDialog },
  240. data() {
  241. return {
  242. flowList: [],
  243. flowInfo: {},
  244. flowHistoryList: [],
  245. exchangeUsers: [],
  246. selectUserType: "exchange", // exchange:转审,approve:下一节点审核
  247. curSelectedUsers: [],
  248. auditLogCache: { paper: {}, card: {} },
  249. // 选择下一节点审批人
  250. IS_NEED_SELECT_APPROVE_USER: false,
  251. nextFlowTaskResult: {}, //下一节点信息
  252. approveUsers: [],
  253. userLimitCount: 1,
  254. userFilterRoles: [],
  255. // audit
  256. TASK_AUDIT_RESULT: { ...TASK_AUDIT_RESULT, EXCHANGE: "转他人审批" },
  257. rejectSetupList: [], // 可以驳回的节点
  258. auditModal: {
  259. approvePass: "PASS",
  260. setup: "",
  261. remark: "",
  262. userId: "",
  263. },
  264. auditRules: {
  265. approvePass: [
  266. {
  267. required: true,
  268. },
  269. ],
  270. setup: [
  271. {
  272. required: true,
  273. validator: (rule, value, callback) => {
  274. if (this.auditModal.approvePass === "REJECT" && !value) {
  275. callback(new Error(`请选择要驳回到的节点`));
  276. } else {
  277. callback();
  278. }
  279. },
  280. trigger: "change",
  281. },
  282. ],
  283. remark: [
  284. {
  285. required: false,
  286. validator: (rule, value, callback) => {
  287. if (this.auditModal.approvePass === "REJECT" && !value) {
  288. callback(new Error(`请输入审批意见`));
  289. } else {
  290. callback();
  291. }
  292. },
  293. trigger: "change",
  294. },
  295. ],
  296. userId: [
  297. {
  298. required: true,
  299. message: "请选择审批人",
  300. trigger: "change",
  301. },
  302. ],
  303. },
  304. };
  305. },
  306. computed: {
  307. ...mapStates("exam", [
  308. "editType",
  309. "examTask",
  310. "curTaskApply",
  311. "taskStatus",
  312. ]),
  313. },
  314. methods: {
  315. ...mapMutations("exam", ["setEditType", "setExamTask", "setCurTaskApply"]),
  316. async initData() {
  317. const paperAttachments = this.curTaskApply.paperAttachmentIds
  318. ? JSON.parse(this.curTaskApply.paperAttachmentIds)
  319. : [];
  320. const auditLogCache = { paper: {}, card: {} };
  321. paperAttachments.forEach((paper) => {
  322. if (
  323. paper.attachmentId &&
  324. this.curTaskApply.auditContent.includes("PAPER")
  325. )
  326. auditLogCache.paper[paper.attachmentId] = false;
  327. if (paper.cardId && this.curTaskApply.auditContent.includes("CARD"))
  328. auditLogCache.card[paper.cardId] = false;
  329. });
  330. this.auditLogCache = auditLogCache;
  331. if (this.curTaskApply.flowStatus === "START") {
  332. await this.getFlowList();
  333. } else {
  334. await this.getFlowHistory();
  335. if (!this.taskStatus.IS_PREVIEW && this.curTaskApply.review)
  336. this.getCurFlowNodeInfo();
  337. }
  338. },
  339. async getFlowHistory() {
  340. if (!this.curTaskApply.flowId) return;
  341. const data = await taskFlowDetail(this.curTaskApply.flowId).catch(
  342. () => {}
  343. );
  344. if (!data) return;
  345. const FLOW_STATUS = {
  346. SUBMIT: "success",
  347. APPROVE: "success",
  348. EXCHANGE: "success",
  349. REJECT: "danger",
  350. END: "success",
  351. };
  352. let nextFlowId = "";
  353. this.flowHistoryList = data.tfFlowViewLogResultList.map((item, index) => {
  354. const nextItem = data.tfFlowViewLogResultList[index + 1];
  355. nextFlowId = nextItem ? nextItem.flowId : item.flowId;
  356. item.nextIsNewFlow = nextFlowId !== item.flowId;
  357. item.type = FLOW_STATUS[item.approveOperation];
  358. item.attachments = item.paperAttachmentId
  359. ? JSON.parse(item.paperAttachmentId)
  360. : [];
  361. item.isApproveSetFlowNextNode = false;
  362. return item;
  363. });
  364. const flowIsEnd = data.currFlowTaskResult.taskKey === "end";
  365. this.flowHistoryList.push({
  366. type: flowIsEnd ? "success" : "current",
  367. stepKey: this.$randomCode(),
  368. approveSetup: data.currFlowTaskResult.setup, //审批人节点
  369. approveRemark: data.currFlowTaskResult.taskName, //审批信息
  370. approveOperation: flowIsEnd ? "END" : "",
  371. approveUserName: data.currFlowTaskResult.approveUserNames, //审批人
  372. createTime: null,
  373. nextIsNewFlow: false,
  374. isApproveSetFlowNextNode: false,
  375. attachments: [],
  376. });
  377. },
  378. async getFlowList() {
  379. if (!this.curTaskApply.flowId) return;
  380. const data = await flowDetailByFlowId(this.curTaskApply.flowId);
  381. if (!data) return;
  382. const modelType =
  383. data.flowTaskResultList[0] && data.flowTaskResultList[0].modelType;
  384. this.flowInfo = {
  385. customFlowId: data.id,
  386. version: data.version,
  387. };
  388. const nextFlowNodeIndex = 1;
  389. this.IS_NEED_SELECT_APPROVE_USER = modelType === "APPROVE_SET";
  390. const flowList = data.flowTaskResultList || [];
  391. this.flowList = flowList.map((flow, index) => {
  392. flow.isApproveSetFlowNextNode =
  393. this.IS_NEED_SELECT_APPROVE_USER && index === nextFlowNodeIndex;
  394. return flow;
  395. });
  396. if (this.flowList.length) {
  397. this.flowList[0].type = "success";
  398. }
  399. this.nextFlowTaskResult = this.flowList[nextFlowNodeIndex];
  400. },
  401. async getCurFlowNodeInfo() {
  402. const data = await taskFlowNodeInfo(this.curTaskApply.flowTaskId);
  403. this.rejectSetupList = (data && data.rejectSetupList) || [];
  404. this.rejectSetupList.forEach((item) => {
  405. item.name = `【${item.taskName}】${item.approveUserNames}`;
  406. });
  407. this.nextFlowTaskResult = data.nextFlowTaskResult;
  408. // 被打回给命题老师之后,命题老师只能通过。
  409. if (data.setup === 1 && !this.rejectSetupList.length) {
  410. this.TASK_AUDIT_RESULT = { PASS: "通过" };
  411. this.auditModal.approvePass = "PASS";
  412. }
  413. // 判断发起人自选,不管approveUserNames有没有值,都可以选择下个审核人员
  414. const { modelType, taskKey } = this.nextFlowTaskResult;
  415. this.IS_NEED_SELECT_APPROVE_USER =
  416. modelType === "APPROVE_SET" && taskKey.toLowerCase() !== "end";
  417. this.flowHistoryList[
  418. this.flowHistoryList.length - 1
  419. ].isApproveSetFlowNextNode = this.IS_NEED_SELECT_APPROVE_USER;
  420. },
  421. approvePassChange() {
  422. this.auditRules.remark[0].required =
  423. this.auditModal.approvePass === "REJECT";
  424. },
  425. async downloadPaper(attachment) {
  426. if (!attachment.attachmentId) return;
  427. this.addPreviewLog(attachment, "paper");
  428. const data = await attachmentPreview(attachment.attachmentId);
  429. window.open(data.url);
  430. },
  431. toSelectNextFlowUser() {
  432. if (!this.IS_NEED_SELECT_APPROVE_USER) return;
  433. this.userLimitCount =
  434. this.nextFlowTaskResult.approveUserCountType === "ONE" ? 1 : 0;
  435. this.userFilterRoles =
  436. this.nextFlowTaskResult.approveUserSelectRange === "ROLE"
  437. ? this.nextFlowTaskResult.approveUserSelectRoles.map(
  438. (item) => item.id
  439. )
  440. : [];
  441. this.selectUserType = "approve";
  442. this.curSelectedUsers = this.approveUsers;
  443. this.$refs.SelectUserDialog.open();
  444. },
  445. toSelectExchangeUser() {
  446. this.userLimitCount = 1;
  447. this.userFilterRoles = [];
  448. this.curSelectedUsers = this.exchangeUsers;
  449. this.selectUserType = "exchange";
  450. this.$refs.SelectUserDialog.open();
  451. },
  452. userSelected(users) {
  453. if (this.selectUserType === "exchange") {
  454. this.exchangeUsers = users;
  455. this.auditModal.userId = users[0].id;
  456. } else {
  457. this.approveUsers = users;
  458. }
  459. },
  460. // action
  461. async checkAuditData() {
  462. const res =
  463. !Object.values(this.auditLogCache.paper).some((item) => !item) &&
  464. !Object.values(this.auditLogCache.card).some((item) => !item);
  465. if (!res) {
  466. this.$message.error(
  467. "还有未被查看过的题卡或试卷,请点击试卷及题卡进行查看审核"
  468. );
  469. return;
  470. }
  471. const valid = await this.$refs.auditModalComp.validate().catch(() => {});
  472. if (!valid) return;
  473. if (
  474. this.auditModal.approvePass === "PASS" &&
  475. this.IS_NEED_SELECT_APPROVE_USER &&
  476. !this.approveUsers.length
  477. ) {
  478. this.$message.error("请设置审核人员!");
  479. return;
  480. }
  481. return true;
  482. },
  483. getAuditData() {
  484. let datas = {};
  485. if (this.auditModal.approvePass === "EXCHANGE") {
  486. datas = {
  487. approvePass: this.auditModal.approvePass,
  488. taskId: this.curTaskApply.flowTaskId,
  489. userId: this.auditModal.userId,
  490. };
  491. } else {
  492. datas = { ...this.auditModal };
  493. datas.taskId = this.curTaskApply.flowTaskId;
  494. if (
  495. this.auditModal.approvePass === "PASS" &&
  496. this.IS_NEED_SELECT_APPROVE_USER
  497. )
  498. datas.approveUserIds = this.approveUsers.map((item) => item.id);
  499. }
  500. return datas;
  501. },
  502. getData() {
  503. if (this.IS_NEED_SELECT_APPROVE_USER) {
  504. return {
  505. approveUserIds: this.approveUsers.map((item) => item.id),
  506. };
  507. }
  508. return {};
  509. },
  510. checkData() {
  511. if (this.IS_NEED_SELECT_APPROVE_USER && !this.approveUsers.length) {
  512. this.$message.error("请设置审核人员!");
  513. return;
  514. }
  515. return true;
  516. },
  517. },
  518. };
  519. </script>