TaskForm.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <template>
  2. <view>
  3. <cu-custom bgColor="bg-blue" :isBack="true">
  4. <block slot="backText">返回</block>
  5. <block slot="content">{{title}}</block>
  6. </cu-custom>
  7. <!-- 菜单 -->
  8. <view class="bg-white nav fixed flex text-center" :style="[{top:CustomBar + 'px'}]">
  9. <view class="cu-item flex-sub" :class="0==tabIndex?'text-blue cur':''" @tap="tabSelect" data-id="0">
  10. 表单信息
  11. </view>
  12. <view class="cu-item flex-sub" v-if="procInsId" :class="2==tabIndex?'text-blue cur':''" @tap="tabSelect" data-id="2">
  13. 流转记录
  14. </view>
  15. <!-- <view class="cu-item flex-sub" :class="1==tabIndex?'text-blue cur':''" @tap="tabSelect" data-id="1">
  16. 流程图
  17. </view> -->
  18. </view>
  19. <view v-show="0 === tabIndex">
  20. <scroll-view scroll-y class="page">
  21. <view class=" bg-white margin-top">
  22. <TestActivitiLeaveForm v-if="formUrl.endsWith('TestActivitiAuditForm')" :formReadOnly="formReadOnly" :class="formReadOnly?'readonly':''" ref="form" :businessId="businessId"></TestActivitiLeaveForm>
  23. <!-- <component :formReadOnly="formReadOnly" :class="formReadOnly?'readonly':''" ref="form" :businessId="businessId" :is="form"></component> -->
  24. <PreviewForm :formData="formData" v-if="formType !== '2'" :processDefinitionId="procDefId" :edit="true" ref="form"></PreviewForm>
  25. </view>
  26. <view class=" bg-white margin-top" v-if="!procInsId || taskId">
  27. <form @submit="formSubmit">
  28. <view class="cu-list menu">
  29. <view class="cu-form-group" v-if="!procInsId" >
  30. <view class="title">
  31. <text class="red-color">* </text> 标题
  32. </view>
  33. <input placeholder="请输入流程标题" maxlength="200" v-model="title" name="title"></input>
  34. </view>
  35. <view class="cu-form-group" v-if="taskId">
  36. <view class="title">
  37. <text class="red-color">* </text> 内容
  38. </view>
  39. <textarea maxlength="2000" v-model="auditForm.message" name="message" placeholder="请输入审批意见"></textarea>
  40. </view>
  41. <view class="cu-form-group">
  42. <view class="title">是否抄送</view>
  43. <jp-switch v-model="isCC" ></jp-switch>
  44. </view>
  45. <view class="cu-form-group" v-if="isCC">
  46. <view class="title">抄送给</view>
  47. <user-select v-model="auditForm.userIds" ></user-select>
  48. </view>
  49. <view class="cu-form-group">
  50. <view class="title">指定下一步处理者</view>
  51. <jp-switch v-model="isAssign"></jp-switch>
  52. </view>
  53. <view class="cu-form-group" v-if="isAssign">
  54. <view class="title">指定</view>
  55. <user-select v-model="auditForm.assignee" ></user-select>
  56. </view>
  57. <user-select-dialog title="选择转办用户" ref="transferUserSelectDialog" :showRadio="true" :showCheckBox="false" @doSubmit="selectUsersToTransferTask"></user-select-dialog>
  58. <user-select-dialog title="选择委派用户" ref="delegateUserSelectDialog" :showRadio="true" :showCheckBox="false" @doSubmit="selectUsersToDelateTask"></user-select-dialog>
  59. <user-select-dialog title="选择加签用户" ref="addSignTaskUserSelectDialog" @doSubmit="selectUsersToAddSignTask"></user-select-dialog>
  60. <task-back-nodes ref="taskBackNodes" @getBackTaskDefKey="back"></task-back-nodes>
  61. </view>
  62. <view class="bottom-wrap flex">
  63. <view class="flex-sub" v-show="button.isHide === '0'"
  64. v-for="(button, index) in buttons" :key="index" >
  65. <button class="cu-btn lg block buttonBox" @click="submit(button, buttons)"> {{button.name}}</button>
  66. </view>
  67. </view>
  68. </form>
  69. </view>
  70. <view class="cu-tabbar-height"></view>
  71. </scroll-view>
  72. </view>
  73. <view v-show="1 === tabIndex">
  74. <view class="padding bg-white margin-top">
  75. <!-- <img :src="`/getAppFlowChart/`+procDefId+`/`+procInsId" class="procPic"/> -->
  76. </view>
  77. </view>
  78. <view v-show="2 === tabIndex">
  79. <view class="padding bg-white margin-top">
  80. <view class="cu-timeline" :key="index" v-for="(act, index) in historicTaskList">
  81. <view class="cu-time">{{act.histIns.startTime |formatDate('MM-DD')}}</view>
  82. <view class="cu-item text-blue">
  83. <view class="content">
  84. <view class="cu-capsule radius">
  85. <view class="cu-tag bg-cyan">{{act.histIns.activityName}}</view>
  86. <!-- <view class="cu-tag line-cyan">{{act.histIns.activityName}}</view> -->
  87. </view>
  88. <view class="margin-top">
  89. 审批人 : {{act.assigneeName}}
  90. </view>
  91. <view class="margin-top">
  92. 办理状态 :<view class="cu-tag bg-blue">{{act.comment.status}}</view>
  93. </view>
  94. <view class="margin-top">
  95. 审批意见 : {{act.comment.message}}
  96. </view>
  97. <view class="margin-top">
  98. 开始时间 : {{act.histIns.startTime |formatDate}}
  99. </view>
  100. <view class="margin-top">
  101. 结束时间 : {{act.histIns.endTime |formatDate}}
  102. </view>
  103. <view class="margin-top">
  104. 用时 : {{act.durationTime || '0秒'}}
  105. </view>
  106. </view>
  107. </view>
  108. </view>
  109. </view>
  110. </view>
  111. </view>
  112. </template>
  113. <script>
  114. import userSelect from '@/components/user-select/user-select.vue'
  115. import userSelectDialog from '@/components/user-select/user-select-dialog.vue'
  116. import PreviewForm from '../form/GenerateFlowableForm'
  117. import TaskBackNodes from './components/TaskBackNodes.vue'
  118. import TestActivitiLeaveForm from '@/pages/test/activiti/TestActivitiLeaveForm.vue'
  119. import moment from 'moment'
  120. import taskService from "@/api/flowable/taskService"
  121. import formService from "@/api/flowable/formService"
  122. import processService from "@/api/flowable/processService"
  123. import flowCopyService from "@/api/flowable/flowCopyService"
  124. import taskDefExtensionService from "@/api/flowable/taskDefExtensionService"
  125. var graceChecker = require("@/common/graceChecker.js");
  126. export default {
  127. onLoad: function (option) {
  128. this.flow = JSON.parse(decodeURIComponent(option.flow));
  129. this.procDefId = this.flow.procDefId
  130. this.procDefKey = this.flow.procDefKey
  131. this.formType = this.flow.formType
  132. this.formUrl = this.flow.formUrl
  133. this.taskId = this.flow.taskId
  134. this.taskDefKey = this.flow.taskDefKey
  135. this.status = this.flow.status
  136. this.title = this.flow.formTitle
  137. this.businessId = this.flow.businessId
  138. this.procInsId = this.flow.procInsId
  139. this.formReadOnly = this.flow.formReadOnly !== undefined && this.flow.formReadOnly !== 'false' && this.flow.formReadOnly !== false
  140. this.isCC = false
  141. this.isAssign = false
  142. this.auditForm.assignee = null
  143. this.auditForm.userIds = null
  144. this.auditForm.message = ''
  145. },
  146. async mounted () {
  147. if (this.formType === '2') { //外置表单
  148. if (this.formUrl === '/404') {
  149. this.form = null
  150. uni.showToast({ title: '没有关联流程表单!', icon: "none" });
  151. } else {
  152. // uniapp 不支持动态组件,所以通过名称匹配决定调用的表单组件
  153. if(this.formUrl.endsWith('TestActivitiAuditForm')){
  154. this.form = TestActivitiLeaveForm
  155. }else{
  156. uni.showToast({ title: '没有关联流程表单!', icon: "none" });
  157. }
  158. }
  159. } else { // 动态表单
  160. // 读取流程表单
  161. if (this.formUrl === '/404') {
  162. uni.showToast({ title: '没有关联流程表单!', icon: "none" });
  163. } else {
  164. let {data} = await formService.getMobileForm(this.formUrl);
  165. // 初始化动态表单
  166. data.forEach((item)=>{ //挂载 writable,readable,value 属性,是为了触发对这三个属性的监听
  167. item.writable = true
  168. item.readable = true
  169. if(this.isObjectValue(item)){
  170. item.value = null
  171. }else{
  172. item.value = ''
  173. }
  174. let input = JSON.parse(JSON.stringify(item))
  175. this.formData.push(input)
  176. })
  177. if (this.status === 'start') {
  178. // 读取启动表单配置
  179. let {data} = await formService.getStartFormData({processDefinitionId: this.procDefId})
  180. this.setData(data, 'start')
  181. } else {
  182. // 读取任务表单配置
  183. let {data} = await formService.getTaskFormData({taskId: this.taskId})
  184. this.setData(data, 'audit')
  185. }
  186. }
  187. }
  188. // 读取按钮配置
  189. if (this.status === 'start') {
  190. this.buttons = [{code: '_flow_start', name: '启动', isHide: '0'}]
  191. } else if (this.procDefKey && this.taskDefKey) {
  192. // 读取按钮
  193. taskDefExtensionService.queryByDefIdAndTaskId({
  194. processDefId: this.procDefKey,
  195. taskDefId: this.taskDefKey
  196. }).then(({data}) => {
  197. this.buttons = data.flowButtonList
  198. })
  199. }
  200. // 读取历史任务列表
  201. taskService.historicTaskList(this.procInsId).then(({data}) => {
  202. this.historicTaskList = data.reverse()
  203. })
  204. },
  205. components:{
  206. userSelect,
  207. userSelectDialog,
  208. TestActivitiLeaveForm,
  209. TaskBackNodes,
  210. PreviewForm
  211. },
  212. data() {
  213. return {
  214. flow: null,
  215. tabIndex: 0,
  216. form: null,
  217. formType: '',
  218. formUrl: '',
  219. taskSelectedTab: 'frist',
  220. historicTaskList: [],
  221. procDefId: '',
  222. procInsId: '',
  223. formReadOnly: false,
  224. procDefKey: '',
  225. taskId: '',
  226. formData: [],
  227. taskDefKey: '',
  228. status: '',
  229. title: '',
  230. businessId: '',
  231. buttons: [],
  232. isCC: false,
  233. isAssign: false,
  234. auditForm: {
  235. message: '',
  236. type: '',
  237. status: '',
  238. userIds: null,
  239. assignee: null
  240. }
  241. }
  242. },
  243. methods:{
  244. tabSelect (e) {
  245. this.tabIndex = parseInt(e.currentTarget.dataset.id);
  246. },
  247. // 为任务表单赋值
  248. setData (taskFormData, status) {
  249. this.formData.forEach((input)=>{
  250. let item = taskFormData.filter((item)=>{
  251. if(input.model === item.id){
  252. return true
  253. }else{
  254. return false
  255. }
  256. })[0]
  257. if(item){
  258. if(status === 'start'){
  259. this.isFixParam(input)
  260. if(this.isObjectValue(input)){
  261. if(input.options.defaultValue && typeof input.options.defaultValue=== 'string'){
  262. input.value = JSON.parse(input.options.defaultValue)
  263. }else{
  264. input.value = input.options.defaultValue
  265. }
  266. }else{
  267. input.value = input.options.defaultValue || ''
  268. }
  269. }else{
  270. if(this.isObjectValue(input)){
  271. if(item.value && typeof item.value=== 'string'){
  272. input.value = JSON.parse(item.value)
  273. }else {
  274. input.value = item.value
  275. }
  276. }else{
  277. input.value = item.value
  278. }
  279. }
  280. input.readable = item.readable
  281. input.writable = item.writable
  282. }else{
  283. input.readable = false
  284. }
  285. })
  286. },
  287. // 默认参数赋值替换 ${user.name}...
  288. isFixParam (input) {
  289. if(/^[$][{].*[}]$/.test(input.options.defaultValue)){
  290. let params = input.options.defaultValue.substring(2, input.options.defaultValue.length-1)
  291. if(params === "new Date()"){
  292. input.options.defaultValue = moment(new Date()).format('YYYY-MM-DD HH:mm:ss')
  293. }else{
  294. let value = {user: this.$store.state.user.userInfo}
  295. params.split('.').forEach((param)=>{
  296. value = value?.[param]
  297. })
  298. input.options.defaultValue = value
  299. }
  300. }
  301. },
  302. // 判断数据类型是否是非String类型
  303. isObjectValue (input) {
  304. if(input.type === 'checkbox' ||
  305. input.type === 'slider' ||
  306. input.type === 'switch' ||
  307. input.type === 'rate' ||
  308. input.type === 'imgupload' ||
  309. input.type === 'select' && input.options.multiple ||
  310. input.type === 'fileupload'){
  311. return true
  312. }
  313. return false
  314. },
  315. // 抄送
  316. cc (procInsId) {
  317. if (this.isCC && this.auditForm.userIds) {
  318. flowCopyService.save({
  319. userIds: this.auditForm.userIds,
  320. procDefId: this.procDefId,
  321. procInsId: procInsId,
  322. procDefName: '',
  323. procInsName: this.title,
  324. taskName: ''
  325. })
  326. }
  327. },
  328. // 暂存草稿
  329. save () {
  330. },
  331. // 启动流程
  332. start (vars) {
  333. if (this.formType === '2') { // 外置表单启动
  334. this.$refs.form.saveForm((businessTable, businessId) => {
  335. taskService.start({
  336. procDefKey: this.procDefKey,
  337. businessTable: businessTable,
  338. businessId: businessId,
  339. ...vars,
  340. title: this.title,
  341. assignee: this.auditForm.assignee
  342. }).then(({data}) => {
  343. uni.showToast({ title: "启动成功", icon: "success" });
  344. uni.navigateTo({
  345. url: '/pages/workbench/task/TodoList'
  346. })
  347. this.cc(data)
  348. })
  349. })
  350. } else { //动态表单启动
  351. this.$refs.form.submitStartFormData({
  352. processDefinitionId: this.procDefId,
  353. ...vars,
  354. title: this.title,
  355. assignee: this.auditForm.assignee
  356. }, (data) => {
  357. uni.showToast({ title: "启动成功", icon: "success" });
  358. uni.navigateTo({
  359. url: '/pages/workbench/task/TodoList'
  360. })
  361. this.cc(data)
  362. })
  363. }
  364. },
  365. // 同意
  366. agree (vars) {
  367. this.commit(vars) // 同意
  368. },
  369. // 驳回
  370. reject () {
  371. uni.showLoading()
  372. uni.showModal({
  373. title: '提示',
  374. content: '确定驳回流程吗?',
  375. success: (res) => {
  376. if (res.confirm) {
  377. taskService.backNodes(this.taskId).then(({data}) => {
  378. let backNodes = data
  379. if (backNodes.length > 0) {
  380. let backTaskDefKey = backNodes[backNodes.length - 1].taskDefKey
  381. this.back(backTaskDefKey)
  382. }
  383. })
  384. } else if (res.cancel) {
  385. uni.hideLoading()
  386. }
  387. }
  388. });
  389. },
  390. // 驳回到任意节点
  391. turnBack () {
  392. this.$refs.taskBackNodes.init(this.taskId)
  393. },
  394. // 回退到任意节点
  395. back (backTaskDefKey) {
  396. taskService.back({
  397. taskId: this.taskId,
  398. backTaskDefKey: backTaskDefKey,
  399. ...this.auditForm
  400. }).then(({data}) => {
  401. uni.showToast({ title: "驳回成功", icon: "success" });
  402. uni.navigateTo({
  403. url: '/pages/workbench/task/TodoList'
  404. })
  405. this.cc(data)
  406. })
  407. },
  408. // 加签
  409. addMultiInstance () {
  410. // this.$refs.addSignTaskUserSelectDialog.showModal()
  411. },
  412. selectUsersToAddSignTask (users) {
  413. let userIds = users.map((user) => {
  414. return user.id
  415. }).join(',')
  416. taskService.addSignTask({taskId: this.taskId, userIds: JSON.stringify(userIds), message: '', flag: false}).then(({data}) => {
  417. uni.showToast({ title: data, icon: "success" });
  418. })
  419. },
  420. // 减签
  421. delMultiInstance () {
  422. },
  423. // 转办
  424. transfer () {
  425. this.$refs.transferUserSelectDialog.showModal()
  426. },
  427. selectUsersToTransferTask (userId) {
  428. if(!userId){
  429. uni.showToast({ title: '没有选择任何用户!', icon: "none" });
  430. return
  431. }
  432. taskService.transfer(this.taskId, userId).then(({data}) => {
  433. uni.showToast({ title: data, icon: "success" });
  434. uni.navigateTo({
  435. url: '/pages/workbench/task/TodoList'
  436. })
  437. })
  438. },
  439. // 委托
  440. delegate () {
  441. this.$refs.delegateUserSelectDialog.showModal()
  442. },
  443. selectUsersToDelateTask (userId) {
  444. if(!userId){
  445. uni.showToast({ title: '没有选择任何用户!', icon: "none" });
  446. return
  447. }
  448. taskService.delegate(this.taskId, userId).then(({data}) => {
  449. uni.showToast({ title: data, icon: "success" });
  450. uni.navigateTo({
  451. url: '/pages/workbench/task/TodoList'
  452. })
  453. })
  454. },
  455. // 终止
  456. stop () {
  457. uni.showLoading()
  458. uni.showModal({
  459. title: '提示',
  460. content: '确定终止流程吗?',
  461. success: (res) => {
  462. if (res.confirm) {
  463. processService.stop(this.procInsId, this.auditForm.message).then(({data}) => {
  464. uni.showToast({ title: data, icon: "success" });
  465. uni.navigateTo({
  466. url: '/pages/workbench/task/TodoList'
  467. })
  468. })
  469. } else if (res.cancel) {
  470. uni.hideLoading()
  471. }
  472. }
  473. });
  474. },
  475. // 打印
  476. print () {
  477. },
  478. // 自定义按钮提交
  479. commit (vars) {
  480. //定义表单规则
  481. var rule = [
  482. {name:"message", checkType : "notnull", checkRule:"", errorMsg:"审批意见不能为空!"}
  483. ];
  484. //进行表单检查
  485. var formData = this.auditForm;
  486. var checkRes = graceChecker.check(formData, rule);
  487. if(!checkRes){
  488. uni.showToast({ title: graceChecker.error, icon: "none" });
  489. return;
  490. }
  491. if (this.formType === '2') { //外置表单审批
  492. this.$refs.form.saveForm((businessTable, businessId) => {
  493. taskService.audit({
  494. taskId: this.taskId,
  495. taskDefKey: this.taskDefKey,
  496. procInsId: this.procInsId,
  497. procDefId: this.procDefId,
  498. vars: vars,
  499. message: this.auditForm.message,
  500. assignee: this.auditForm.assignee
  501. }).then(({data}) => {
  502. uni.showToast({ title: "审批成功", icon: "success" });
  503. uni.navigateTo({
  504. url: '/pages/workbench/task/TodoList'
  505. })
  506. this.cc(data)
  507. })
  508. })
  509. } else { // 动态表单启动
  510. this.$refs.form.submitTaskFormData(vars, this.procInsId, this.taskId, this.auditForm.assignee, this.auditForm, (data) => {
  511. uni.showToast({ title: "启动成功", icon: "success" });
  512. uni.navigateTo({
  513. url: '/pages/workbench/task/TodoList'
  514. })
  515. //抄送
  516. this.cc(data)
  517. })
  518. }
  519. },
  520. submit (currentBtn, buttons) {
  521. let vars = {} // 存储流程变量
  522. // 把当前操作对应的自定义按钮(以_flow_开头的是系统按钮,排除在外)的编码,存储为对应的流程变量,值设置为true,其余自定义按钮编码对应的流程变量值为false。
  523. buttons.forEach((btn) => {
  524. if (btn.code && !btn.code.startsWith('_flow_')) {
  525. vars[btn.code] = false
  526. }
  527. })
  528. if (currentBtn.code && !currentBtn.code.startsWith('_flow_')) {
  529. vars[currentBtn.code] = true
  530. }
  531. vars.title = this.title // 标题
  532. vars.assignee = this.auditForm.assignee // 指定的下一步骤处理人
  533. this.auditForm.type = currentBtn.code // 提交类型
  534. this.auditForm.status = currentBtn.name // 按钮文字
  535. switch (currentBtn.code) {
  536. case '_flow_start': // 自动流程
  537. this.start(vars)
  538. break
  539. case '_flow_save': // 保存草稿
  540. this.save()
  541. break
  542. case '_flow_agree': // 同意
  543. this.agree()
  544. break
  545. case '_flow_reject': // 驳回
  546. this.reject()
  547. break
  548. case '_flow_back': // 驳回到任意步骤
  549. this.turnBack()
  550. break
  551. case '_flow_add_multi_instance': // 加签
  552. this.addMultiInstance()
  553. break
  554. case '_flow_del_multi_instance': // 减签
  555. this.delMultiInstance()
  556. break
  557. case '_flow_transfer': // 转办
  558. this.transfer()
  559. break
  560. case '_flow_delegate':// 外派
  561. this.delegate()
  562. break
  563. case '_flow_stop':// 终止
  564. this.stop()
  565. break
  566. case '_flow_print':// 打印
  567. this.print()
  568. break
  569. default:
  570. this.commit(vars) // 自定义按钮提交
  571. }
  572. }
  573. }
  574. }
  575. </script>
  576. <style>
  577. page {
  578. padding-top: 45px;
  579. }
  580. .cu-form-group .title {
  581. min-width: calc(4em + 40px);
  582. }
  583. uni-view:nth-child(1n+1)>uni-button.buttonBox{
  584. background:#0081ff;
  585. color: #FFFFFF;
  586. }
  587. uni-view:nth-child(1n+2)>uni-button.buttonBox{
  588. background:#f37b1d;
  589. color: #FFFFFF;
  590. }
  591. uni-button:nth-child(1n+3).buttonBox{
  592. background:#8dc63f;
  593. color: #FFFFFF;
  594. }
  595. uni-button:nth-child(1n+4).buttonBox{
  596. background:#1cbbb4;
  597. color: #FFFFFF;
  598. }
  599. uni-button:nth-child(1n+5).buttonBox{
  600. background:#a5673f;
  601. color: #FFFFFF;
  602. }
  603. </style>