index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <template>
  2. <div class="app-container">
  3. <el-card class="box-card" >
  4. <div slot="header" class="clearfix">
  5. <span class="el-icon-document">基础信息</span>
  6. <el-button style="float: right;" type="primary" @click="goBack">关闭</el-button>
  7. </div>
  8. <!--流程处理表单模块-->
  9. <el-col :span="16" :offset="6" v-if="variableOpen">
  10. <div>
  11. <parser :key="new Date().getTime()" :form-conf="variablesData" />
  12. </div>
  13. <!-- v-if="finished === 'true'" -->
  14. <div style="margin-left:10%;margin-bottom: 20px;font-size: 14px;" >
  15. <el-button icon="el-icon-edit-outline" type="success" class="editButton" size="mini" @click="handleComplete">审批</el-button>
  16. <!-- <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">委派</el-button>-->
  17. <!-- <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleAssign">转办</el-button>-->
  18. <!-- <el-button icon="el-icon-edit-outline" type="primary" size="mini" @click="handleDelegate">签收</el-button>-->
  19. <el-button icon="el-icon-refresh-left" type="warning" size="mini" @click="handleReturn">退回</el-button>
  20. <el-button icon="el-icon-circle-close" type="danger" size="mini" @click="handleReject">驳回</el-button>
  21. </div>
  22. </el-col>
  23. <!--初始化流程加载表单信息-->
  24. <el-col :span="16" :offset="4" v-if="formConfOpen">
  25. <div class="test-form">
  26. <parser :key="new Date().getTime()" :form-conf="formConf" @submit="submitForm" ref="parser" @getData="getData" />
  27. </div>
  28. </el-col>
  29. </el-card>
  30. <!--流程流转记录-->
  31. <el-card class="box-card" v-if="flowRecordList">
  32. <div slot="header" class="clearfix">
  33. <span class="el-icon-notebook-1">审批记录</span>
  34. </div>
  35. <el-col :span="16" :offset="4" >
  36. <div class="block">
  37. <el-timeline>
  38. <el-timeline-item
  39. v-for="(item,index ) in flowRecordList"
  40. :key="index"
  41. :icon="setIcon(item.finishTime)"
  42. :color="setColor(item.finishTime)"
  43. >
  44. <p style="font-weight: 700">{{item.taskName}}</p>
  45. <el-card :body-style="{ padding: '10px' }">
  46. <el-descriptions class="margin-top" :column="1" size="small" border>
  47. <el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
  48. <template slot="label"><i class="el-icon-user"></i>办理人</template>
  49. {{item.assigneeName}}
  50. <el-tag type="info" size="mini">{{item.deptName}}</el-tag>
  51. </el-descriptions-item>
  52. <el-descriptions-item v-if="item.candidate" label-class-name="my-label">
  53. <template slot="label"><i class="el-icon-user"></i>候选办理</template>
  54. {{item.candidate}}
  55. </el-descriptions-item>
  56. <el-descriptions-item label-class-name="my-label">
  57. <template slot="label"><i class="el-icon-date"></i>接收时间</template>
  58. {{item.createTime}}
  59. </el-descriptions-item>
  60. <el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
  61. <template slot="label"><i class="el-icon-date"></i>处理时间</template>
  62. {{item.finishTime}}
  63. </el-descriptions-item>
  64. <el-descriptions-item v-if="item.duration" label-class-name="my-label">
  65. <template slot="label"><i class="el-icon-time"></i>耗时</template>
  66. {{item.duration}}
  67. </el-descriptions-item>
  68. <el-descriptions-item v-if="item.comment" label-class-name="my-label">
  69. <template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
  70. {{item.comment.comment}}
  71. </el-descriptions-item>
  72. </el-descriptions>
  73. <!-- <p v-if="item.comment">-->
  74. <!-- <el-tag type="success" class="editButton" v-if="item.comment.type === '1'"> {{item.comment.comment}}</el-tag>-->
  75. <!-- <el-tag type="warning" v-if="item.comment.type === '2'"> {{item.comment.comment}}</el-tag>-->
  76. <!-- <el-tag type="danger" v-if="item.comment.type === '3'"> {{item.comment.comment}}</el-tag>-->
  77. <!-- </p>-->
  78. </el-card>
  79. </el-timeline-item>
  80. </el-timeline>
  81. </div>
  82. </el-col>
  83. </el-card>
  84. <el-card class="box-card">
  85. <div slot="header" class="clearfix">
  86. <span class="el-icon-picture-outline">流程图</span>
  87. </div>
  88. <flow :xmlData="xmlData" :taskData="taskList"></flow>
  89. </el-card>
  90. <!--审批正常流程-->
  91. <el-dialog :title="completeTitle" :visible.sync="completeOpen" :width="checkSendUser? '60%':'40%'" append-to-body>
  92. <el-form ref="taskForm" :model="taskForm" label-width="80px" >
  93. <el-form-item v-if="checkSendUser" prop="targetKey">
  94. <el-row :gutter="20">
  95. <!--部门数据-->
  96. <el-col :span="6" :xs="24">
  97. <h6>部门列表</h6>
  98. <div class="head-container">
  99. <el-input
  100. v-model="deptName"
  101. placeholder="请输入部门名称"
  102. clearable
  103. size="small"
  104. prefix-icon="el-icon-search"
  105. style="margin-bottom: 20px"
  106. />
  107. </div>
  108. <div class="head-container">
  109. <el-tree
  110. :data="deptOptions"
  111. :props="defaultProps"
  112. :expand-on-click-node="false"
  113. :filter-node-method="filterNode"
  114. ref="tree"
  115. default-expand-all
  116. @node-click="handleNodeClick"
  117. />
  118. </div>
  119. </el-col>
  120. <el-col :span="10" :xs="24">
  121. <h6>待选人员</h6>
  122. <el-table
  123. ref="singleTable"
  124. :data="userList"
  125. border
  126. style="width: 100%"
  127. @selection-change="handleSelectionChange">
  128. <el-table-column type="selection" width="50" align="center" />
  129. <el-table-column label="用户名" align="center" prop="nickName" />
  130. <el-table-column label="部门" align="center" prop="dept.deptName" />
  131. </el-table>
  132. </el-col>
  133. <el-col :span="8" :xs="24">
  134. <h6>已选人员</h6>
  135. <el-tag
  136. v-for="(user,index) in userData"
  137. :key="index"
  138. closable
  139. @close="handleClose(user)">
  140. {{user.nickName}} {{user.dept.deptName}}
  141. </el-tag>
  142. </el-col>
  143. </el-row>
  144. </el-form-item>
  145. <el-form-item label="处理意见" prop="comment" :rules="[{ required: true, message: '请输入处理意见', trigger: 'blur' }]">
  146. <el-input type="textarea" v-model="taskForm.comment" placeholder="请输入处理意见"/>
  147. </el-form-item>
  148. </el-form>
  149. <span slot="footer" class="dialog-footer">
  150. <el-button @click="completeOpen = false">取 消</el-button>
  151. <el-button type="primary" @click="taskComplete">确 定</el-button>
  152. </span>
  153. </el-dialog>
  154. <!--退回流程-->
  155. <el-dialog :title="returnTitle" :visible.sync="returnOpen" width="40%" append-to-body>
  156. <el-form ref="taskForm" :model="taskForm" label-width="80px" >
  157. <el-form-item label="退回节点" prop="targetKey">
  158. <el-radio-group v-model="taskForm.targetKey">
  159. <el-radio-button
  160. v-for="item in returnTaskList"
  161. :key="item.id"
  162. :label="item.id"
  163. >{{item.name}}</el-radio-button>
  164. </el-radio-group>
  165. </el-form-item>
  166. <el-form-item label="退回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
  167. <el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
  168. </el-form-item>
  169. </el-form>
  170. <span slot="footer" class="dialog-footer">
  171. <el-button @click="returnOpen = false">取 消</el-button>
  172. <el-button type="primary" @click="taskReturn">确 定</el-button>
  173. </span>
  174. </el-dialog>
  175. <!--驳回流程-->
  176. <el-dialog :title="rejectTitle" :visible.sync="rejectOpen" width="40%" append-to-body>
  177. <el-form ref="taskForm" :model="taskForm" label-width="80px" >
  178. <el-form-item label="驳回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
  179. <el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见"/>
  180. </el-form-item>
  181. </el-form>
  182. <span slot="footer" class="dialog-footer">
  183. <el-button @click="rejectOpen = false">取 消</el-button>
  184. <el-button type="primary" @click="taskReject">确 定</el-button>
  185. </span>
  186. </el-dialog>
  187. </div>
  188. </template>
  189. <script>
  190. import {flowRecord} from "@/api/flowable/finished";
  191. import Parser from '@/components/parser/Parser'
  192. import {definitionStart, getProcessVariables, readXml, getFlowViewer} from "@/api/flowable/definition";
  193. import {complete, rejectTask, returnList, returnTask, getNextFlowNode, delegate} from "@/api/flowable/todo";
  194. import flow from '@/views/flowable/task/record/flow'
  195. import {treeselect} from "@/api/system/dept";
  196. import "@riophae/vue-treeselect/dist/vue-treeselect.css";
  197. import Treeselect from "@riophae/vue-treeselect";
  198. import {listUser} from "@/api/system/user";
  199. export default {
  200. name: "Record",
  201. components: {
  202. Parser,
  203. flow,
  204. Treeselect
  205. },
  206. props: {},
  207. data() {
  208. return {
  209. // 模型xml数据
  210. xmlData: "",
  211. taskList: [],
  212. // 部门名称
  213. deptName: undefined,
  214. // 部门树选项
  215. deptOptions: undefined,
  216. // 用户表格数据
  217. userList: null,
  218. defaultProps: {
  219. children: "children",
  220. label: "label"
  221. },
  222. // 查询参数
  223. queryParams: {
  224. deptId: undefined
  225. },
  226. // 遮罩层
  227. loading: true,
  228. flowRecordList: [], // 流程流转数据
  229. formConfCopy: {},
  230. src: null,
  231. rules: {}, // 表单校验
  232. variablesForm: {}, // 流程变量数据
  233. taskForm:{
  234. returnTaskShow: false, // 是否展示回退表单
  235. delegateTaskShow: false, // 是否展示回退表单
  236. defaultTaskShow: true, // 默认处理
  237. sendUserShow: false, // 审批用户
  238. multiple: false,
  239. comment:"", // 意见内容
  240. procInsId: "", // 流程实例编号
  241. instanceId: "", // 流程实例编号
  242. deployId: "", // 流程定义编号
  243. taskId: "" ,// 流程任务编号
  244. procDefId: "", // 流程编号
  245. vars: "",
  246. targetKey:""
  247. },
  248. userDataList:[], // 流程候选人
  249. assignee: null,
  250. formConf: {}, // 默认表单数据
  251. formConfOpen: false, // 是否加载默认表单数据
  252. variables: [], // 流程变量数据
  253. variablesData: {}, // 流程变量数据
  254. variableOpen: false, // 是否加载流程变量数据
  255. returnTaskList: [], // 回退列表数据
  256. finished: 'false',
  257. completeTitle: null,
  258. completeOpen: false,
  259. returnTitle: null,
  260. returnOpen: false,
  261. rejectOpen: false,
  262. rejectTitle: null,
  263. userData:[],
  264. checkSendUser: false // 是否展示选择人员模块
  265. };
  266. },
  267. created() {
  268. this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
  269. this.taskForm.taskId = this.$route.query && this.$route.query.taskId;
  270. this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
  271. this.taskForm.executionId = this.$route.query && this.$route.query.executionId;
  272. this.taskForm.instanceId = this.$route.query && this.$route.query.procInsId;
  273. // 初始化表单
  274. this.taskForm.procDefId = this.$route.query && this.$route.query.procDefId;
  275. // 回显流程记录
  276. this.getFlowViewer(this.taskForm.procInsId,this.taskForm.executionId);
  277. this.getModelDetail(this.taskForm.deployId);
  278. // 流程任务重获取变量表单
  279. if (this.taskForm.taskId){
  280. this.processVariables( this.taskForm.taskId)
  281. this.getNextFlowNode(this.taskForm.taskId)
  282. this.taskForm.deployId = null
  283. }
  284. this.getFlowRecordList( this.taskForm.procInsId, this.taskForm.deployId);
  285. this.finished = this.$route.query && this.$route.query.finished
  286. },
  287. methods: {
  288. /** 查询部门下拉树结构 */
  289. getTreeselect() {
  290. treeselect().then(response => {
  291. this.deptOptions = response.data;
  292. });
  293. },
  294. /** 查询用户列表 */
  295. getList() {
  296. listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
  297. this.userList = response.rows;
  298. this.total = response.total;
  299. }
  300. );
  301. },
  302. // 筛选节点
  303. filterNode(value, data) {
  304. if (!value) return true;
  305. return data.label.indexOf(value) !== -1;
  306. },
  307. // 节点单击事件
  308. handleNodeClick(data) {
  309. this.queryParams.deptId = data.id;
  310. this.getList();
  311. },
  312. /** xml 文件 */
  313. getModelDetail(deployId) {
  314. // 发送请求,获取xml
  315. readXml(deployId).then(res => {
  316. this.xmlData = res.data
  317. })
  318. },
  319. getFlowViewer(procInsId,executionId) {
  320. getFlowViewer(procInsId,executionId).then(res => {
  321. this.taskList = res.data
  322. })
  323. },
  324. setIcon(val) {
  325. if (val) {
  326. return "el-icon-check";
  327. } else {
  328. return "el-icon-time";
  329. }
  330. },
  331. setColor(val) {
  332. if (val) {
  333. return "#2bc418";
  334. } else {
  335. return "#b3bdbb";
  336. }
  337. },
  338. // 多选框选中数据
  339. handleSelectionChange(selection) {
  340. if (selection) {
  341. this.userData = selection
  342. const selectVal = selection.map(item => item.userId);
  343. if (selectVal instanceof Array) {
  344. this.taskForm.values = {
  345. "approval": selectVal.join(',')
  346. }
  347. } else {
  348. this.taskForm.values = {
  349. "approval": selectVal
  350. }
  351. }
  352. }
  353. },
  354. // 关闭标签
  355. handleClose(tag) {
  356. this.userData.splice(this.userData.indexOf(tag), 1);
  357. this.$refs.singleTable.toggleRowSelection(tag, false)
  358. },
  359. /** 流程变量赋值 */
  360. handleCheckChange(val) {
  361. if (val instanceof Array) {
  362. this.taskForm.values = {
  363. "approval": val.join(',')
  364. }
  365. } else {
  366. this.taskForm.values = {
  367. "approval": val
  368. }
  369. }
  370. },
  371. /** 流程流转记录 */
  372. getFlowRecordList(procInsId, deployId) {
  373. const that = this
  374. const params = {procInsId: procInsId, deployId: deployId}
  375. flowRecord(params).then(res => {
  376. that.flowRecordList = res.data.flowList;
  377. // 流程过程中不存在初始化表单 直接读取的流程变量中存储的表单值
  378. if (res.data.formData) {
  379. that.formConf = res.data.formData;
  380. that.formConfOpen = true
  381. }
  382. }).catch(res => {
  383. this.goBack();
  384. })
  385. },
  386. fillFormData(form, data) {
  387. form.fields.forEach(item => {
  388. const val = data[item.__vModel__]
  389. if (val) {
  390. item.__config__.defaultValue = val
  391. }
  392. })
  393. },
  394. /** 获取流程变量内容 */
  395. processVariables(taskId) {
  396. if (taskId) {
  397. // 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
  398. getProcessVariables(taskId).then(res => {
  399. this.variablesData = res.data.variables;
  400. this.variableOpen = true
  401. });
  402. }
  403. },
  404. /** 根据当前任务或者流程设计配置的下一步节点 */
  405. getNextFlowNode(taskId) {
  406. // 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
  407. const params = {taskId: taskId}
  408. getNextFlowNode(params).then(res => {
  409. const data = res.data;
  410. if (data) {
  411. this.checkSendUser = true
  412. if (data.type === 'assignee') { // 指定人员
  413. this.userDataList = res.data.userList;
  414. } else if (data.type === 'candidateUsers') { // 指定人员(多个)
  415. this.userDataList = res.data.userList;
  416. this.taskForm.multiple = true;
  417. } else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
  418. res.data.roleList.forEach(role => {
  419. role.userId = role.roleId;
  420. role.nickName = role.roleName;
  421. })
  422. this.userDataList = res.data.roleList;
  423. this.taskForm.multiple = false;
  424. } else if (data.type === 'multiInstance') { // 会签?
  425. this.userDataList = res.data.userList;
  426. this.taskForm.multiple = true;
  427. }else if (data.type === 'fixed') { // 已经固定人员接收下一任务
  428. this.checkSendUser = false;
  429. }
  430. }
  431. })
  432. },
  433. /** 审批任务选择 */
  434. handleComplete() {
  435. this.completeOpen = true;
  436. this.completeTitle = "审批流程";
  437. this.getTreeselect();
  438. },
  439. /** 审批任务 */
  440. taskComplete() {
  441. if (!this.taskForm.values && this.checkSendUser){
  442. this.msgError("请选择流程接收人员");
  443. return;
  444. }
  445. if (!this.taskForm.comment){
  446. this.msgError("请输入审批意见");
  447. return;
  448. }
  449. complete(this.taskForm).then(response => {
  450. this.$modal.msgSuccess(response.msg);
  451. this.goBack();
  452. });
  453. },
  454. /** 委派任务 */
  455. handleDelegate() {
  456. this.taskForm.delegateTaskShow = true;
  457. this.taskForm.defaultTaskShow = false;
  458. },
  459. handleAssign(){
  460. },
  461. /** 返回页面 */
  462. goBack() {
  463. // 关闭当前标签页并返回上个页面
  464. this.$store.dispatch("tagsView/delView", this.$route);
  465. this.$router.go(-1)
  466. },
  467. /** 接收子组件传的值 */
  468. getData(data) {
  469. if (data) {
  470. const variables = [];
  471. data.fields.forEach(item => {
  472. let variableData = {};
  473. variableData.label = item.__config__.label
  474. // 表单值为多个选项时
  475. if (item.__config__.defaultValue instanceof Array) {
  476. const array = [];
  477. item.__config__.defaultValue.forEach(val => {
  478. array.push(val)
  479. })
  480. variableData.val = array;
  481. } else {
  482. variableData.val = item.__config__.defaultValue
  483. }
  484. variables.push(variableData)
  485. })
  486. this.variables = variables;
  487. }
  488. },
  489. /** 申请流程表单数据提交 */
  490. submitForm(data) {
  491. if (data) {
  492. const variables = data.valData;
  493. const formData = data.formData;
  494. formData.disabled = true;
  495. formData.formBtns = false;
  496. if (this.taskForm.procDefId) {
  497. variables.variables = formData;
  498. // 启动流程并将表单数据加入流程变量
  499. definitionStart(this.taskForm.procDefId, JSON.stringify(variables)).then(res => {
  500. this.$modal.msgSuccess(res.msg);
  501. this.goBack();
  502. })
  503. }
  504. }
  505. },
  506. /** 驳回任务 */
  507. handleReject() {
  508. this.rejectOpen = true;
  509. this.rejectTitle = "驳回流程";
  510. },
  511. /** 驳回任务 */
  512. taskReject() {
  513. this.$refs["taskForm"].validate(valid => {
  514. if (valid) {
  515. rejectTask(this.taskForm).then(res => {
  516. this.$modal.msgSuccess(res.msg);
  517. this.goBack();
  518. });
  519. }
  520. });
  521. },
  522. /** 可退回任务列表 */
  523. handleReturn() {
  524. this.returnOpen = true;
  525. this.returnTitle = "退回流程";
  526. returnList(this.taskForm).then(res => {
  527. this.returnTaskList = res.data;
  528. this.taskForm.values = null;
  529. })
  530. },
  531. /** 提交退回任务 */
  532. taskReturn() {
  533. this.$refs["taskForm"].validate(valid => {
  534. if (valid) {
  535. returnTask(this.taskForm).then(res => {
  536. this.$modal.msgSuccess(res.msg);
  537. this.goBack()
  538. });
  539. }
  540. });
  541. },
  542. /** 取消回退任务按钮 */
  543. cancelTask() {
  544. this.taskForm.returnTaskShow = false;
  545. this.taskForm.defaultTaskShow = true;
  546. this.taskForm.sendUserShow = true;
  547. this.returnTaskList = [];
  548. },
  549. /** 委派任务 */
  550. submitDeleteTask() {
  551. this.$refs["taskForm"].validate(valid => {
  552. if (valid) {
  553. delegate(this.taskForm).then(response => {
  554. this.$modal.msgSuccess(response.msg);
  555. this.goBack();
  556. });
  557. }
  558. });
  559. },
  560. /** 取消回退任务按钮 */
  561. cancelDelegateTask() {
  562. this.taskForm.delegateTaskShow = false;
  563. this.taskForm.defaultTaskShow = true;
  564. this.taskForm.sendUserShow = true;
  565. this.returnTaskList = [];
  566. },
  567. }
  568. };
  569. </script>
  570. <style lang="scss" scoped>
  571. .test-form {
  572. margin: 15px auto;
  573. width: 800px;
  574. padding: 15px;
  575. }
  576. .clearfix:before,
  577. .clearfix:after {
  578. display: table;
  579. content: "";
  580. }
  581. .clearfix:after {
  582. clear: both
  583. }
  584. .box-card {
  585. width: 100%;
  586. margin-bottom: 20px;
  587. }
  588. .el-tag + .el-tag {
  589. margin-left: 10px;
  590. }
  591. .my-label {
  592. background: #E1F3D8;
  593. }
  594. </style>