workflowTab.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. <template>
  2. <div class="task-formulate">
  3. <div class="operation-bar">
  4. <span>预定的监审工作起止时间:</span>
  5. <el-date-picker
  6. v-model="formData.workflow.plannedAuditStartDate"
  7. type="date"
  8. placeholder="开始日期"
  9. style="width: 150px; margin: 0 10px"
  10. format="yyyy-MM-dd"
  11. value-format="yyyy-MM-dd"
  12. disabled
  13. ></el-date-picker>
  14. <span>至</span>
  15. <el-date-picker
  16. v-model="formData.workflow.plannedAuditEndDate"
  17. type="date"
  18. placeholder="结束日期"
  19. style="width: 150px; margin: 0 10px"
  20. format="yyyy-MM-dd"
  21. value-format="yyyy-MM-dd"
  22. disabled
  23. ></el-date-picker>
  24. </div>
  25. <div class="operation-bar">
  26. <el-button
  27. type="success"
  28. plain
  29. icon="el-icon-circle-plus"
  30. :disabled="isView"
  31. @click="handleSetWorkflow"
  32. >
  33. 工作环节设置
  34. </el-button>
  35. </div>
  36. <CostAuditTable
  37. :table-data="workflowData.list"
  38. :columns="workflowData.listColumns"
  39. :show-index="true"
  40. :show-action-column="true"
  41. >
  42. <template #action="{ row }">
  43. <!-- v-if="row.nodeType == 1" -->
  44. <el-button type="text" :disabled="isView" @click="handleSetStep(row)">
  45. 设置
  46. </el-button>
  47. </template>
  48. </CostAuditTable>
  49. <div class="table-description">
  50. 说明:为所有内部环节预置办理人员、流转操作按钮、环节截止时间;环节时限不能超过预定的工作流程起止时间。
  51. </div>
  52. <el-dialog
  53. title="工作环节设置"
  54. :visible.sync="dialogs.setStepDialogVisible"
  55. width="70%"
  56. >
  57. <div class="operation-bar">
  58. <span>请选择预定的监审工作起止时间:</span>
  59. <el-date-picker
  60. v-model="formData.workflow.plannedAuditStartDate"
  61. type="date"
  62. placeholder="开始日期"
  63. style="width: 150px; margin: 0 10px"
  64. format="yyyy-MM-dd"
  65. value-format="yyyy-MM-dd"
  66. ></el-date-picker>
  67. <span>至</span>
  68. <el-date-picker
  69. v-model="formData.workflow.plannedAuditEndDate"
  70. type="date"
  71. placeholder="结束日期"
  72. style="width: 150px; margin: 0 10px"
  73. format="yyyy-MM-dd"
  74. value-format="yyyy-MM-dd"
  75. ></el-date-picker>
  76. </div>
  77. <CostAuditTable
  78. :table-data="workflowData.stepList"
  79. :columns="workflowData.workflowColumns"
  80. :show-index="true"
  81. :show-pagination="true"
  82. :show-action-column="true"
  83. :table-props="{
  84. maxHeight: '500',
  85. }"
  86. :pagination="workflowData.pagination"
  87. @pagination-change="handlePaginationChange"
  88. >
  89. <template #mainUserId="{ row }">
  90. <el-select
  91. v-if="row.nodeType == 1"
  92. v-model="row.mainUserId"
  93. filterable
  94. allow-create
  95. default-first-option
  96. placeholder="请选择主办人员"
  97. style="width: 100%"
  98. >
  99. <el-option
  100. v-for="item in formatterMainUserList()"
  101. :key="item.userId"
  102. :label="item.fullname"
  103. :value="item.userId"
  104. />
  105. </el-select>
  106. </template>
  107. <template #userId="{ row }">
  108. <el-select
  109. v-if="row.nodeType == 1"
  110. v-model="row.userId"
  111. multiple
  112. filterable
  113. allow-create
  114. default-first-option
  115. placeholder="请选择从办人员"
  116. style="width: 100%"
  117. :disabled="!row.mainUserId"
  118. >
  119. <el-option
  120. v-for="item in formatterUserList(row.mainUserId)"
  121. :key="item.userId"
  122. :label="item.fullname"
  123. :value="item.userId"
  124. />
  125. </el-select>
  126. </template>
  127. <template #status="{ row }">
  128. <el-select
  129. v-if="row.nodeType == 1"
  130. v-model="row.status"
  131. multiple
  132. filterable
  133. allow-create
  134. default-first-option
  135. placeholder="请选择预置流转操作"
  136. style="width: 100%"
  137. >
  138. <el-option
  139. v-for="item in dictData['processStatus']"
  140. :key="item.key"
  141. :label="item.name"
  142. :value="item.key"
  143. />
  144. </el-select>
  145. </template>
  146. <template #endTime="{ row }">
  147. <el-date-picker
  148. v-model="row.endTime"
  149. type="date"
  150. placeholder="请选择截止时间"
  151. format="yyyy-MM-dd"
  152. value-format="yyyy-MM-dd"
  153. style="width: 100%"
  154. :picker-options="getPickerOptions(row)"
  155. ></el-date-picker>
  156. </template>
  157. </CostAuditTable>
  158. <div slot="footer" class="dialog-footer">
  159. <el-button
  160. type="primary"
  161. :loading="loading.saveStep"
  162. @click="handleSaveSetStep"
  163. >
  164. 确认
  165. </el-button>
  166. <el-button @click="dialogs.setStepDialogVisible = false">
  167. 取消
  168. </el-button>
  169. </div>
  170. </el-dialog>
  171. <!-- 设置流程环节弹窗 -->
  172. <el-dialog
  173. v-loading="loading.save"
  174. title="设置流程环节"
  175. :visible.sync="dialogs.stepDialogVisible"
  176. width="50%"
  177. :close-on-click-modal="false"
  178. >
  179. <el-form
  180. ref="currentStepForm"
  181. :model="formData.currentStep"
  182. label-width="120px"
  183. :rules="formData.currentStepRules"
  184. >
  185. <el-form-item label="流程环节:" prop="processNodeValue">
  186. <el-input
  187. v-model="formData.currentStep.processNodeValue"
  188. disabled
  189. ></el-input>
  190. </el-form-item>
  191. <el-form-item label="主办人员:" prop="mainUserId">
  192. <el-select
  193. v-model="formData.currentStep.mainUserId"
  194. filterable
  195. allow-create
  196. default-first-option
  197. placeholder="请选择主办人员"
  198. style="width: 100%"
  199. :disabled="formData.currentStep.nodeType == 0"
  200. >
  201. <el-option
  202. v-for="item in formatterMainUserList()"
  203. :key="item.userId"
  204. :label="item.fullname"
  205. :value="item.userId"
  206. ></el-option>
  207. </el-select>
  208. </el-form-item>
  209. <el-form-item label="从办人员:" prop="userId">
  210. <el-select
  211. v-model="formData.currentStep.userId"
  212. multiple
  213. filterable
  214. allow-create
  215. default-first-option
  216. placeholder="请选择从办人员"
  217. style="width: 100%"
  218. :disabled="
  219. !formData.currentStep.mainUserId ||
  220. formData.currentStep.nodeType == 0
  221. "
  222. >
  223. <el-option
  224. v-for="item in formatterUserList(formData.currentStep.mainUserId)"
  225. :key="item.userId"
  226. :label="item.fullname"
  227. :value="item.userId"
  228. ></el-option>
  229. </el-select>
  230. </el-form-item>
  231. <el-form-item label="预置流转操作:" prop="status">
  232. <el-select
  233. v-model="formData.currentStep.status"
  234. filterable
  235. multiple
  236. allow-create
  237. default-first-option
  238. placeholder="请选择预置流转操作"
  239. style="width: 100%"
  240. :disabled="formData.currentStep.nodeType == 0"
  241. >
  242. <el-option
  243. v-for="item in dictData['processStatus']"
  244. :key="item.key"
  245. :label="item.name"
  246. :value="item.key"
  247. />
  248. </el-select>
  249. </el-form-item>
  250. <el-form-item label="截止时间:" prop="endTime">
  251. <el-date-picker
  252. v-model="formData.currentStep.endTime"
  253. type="date"
  254. placeholder="请选择截止时间"
  255. format="yyyy-MM-dd"
  256. value-format="yyyy-MM-dd"
  257. style="width: 100%"
  258. :disabled="isTimePickerDisabled"
  259. :picker-options="getPickerOptions(formData.currentStep)"
  260. ></el-date-picker>
  261. </el-form-item>
  262. </el-form>
  263. <div slot="footer" class="dialog-footer">
  264. <el-button
  265. type="primary"
  266. :loading="loading.save"
  267. @click="handleSaveStep('currentStepForm', 'list')"
  268. >
  269. 确认
  270. </el-button>
  271. <el-button @click="dialogs.stepDialogVisible = false">取消</el-button>
  272. </div>
  273. </el-dialog>
  274. </div>
  275. </template>
  276. <script>
  277. import { taskMixin } from './index.js'
  278. import {
  279. getCostProjectNodeTmpletePageList,
  280. saveProcess,
  281. } from '@/api/taskCustomizedRelease.js'
  282. import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
  283. export default {
  284. components: { CostAuditTable },
  285. mixins: [taskMixin],
  286. props: {
  287. // 父组件传递的参数
  288. project: {
  289. type: Object,
  290. default: () => {},
  291. },
  292. isView: {
  293. type: Boolean,
  294. default: false,
  295. },
  296. },
  297. data() {
  298. return {
  299. formData: {
  300. currentStepRules: {
  301. endTime: [
  302. { required: true, message: '请选择截止时间', trigger: 'change' },
  303. {
  304. validator: (rule, value, callback) => {
  305. // 检查单个环节时间是否在总流程时间范围内
  306. if (
  307. !this.formData.workflow.plannedAuditStartDate ||
  308. !this.formData.workflow.plannedAuditEndDate
  309. ) {
  310. return callback()
  311. }
  312. if (value < this.formData.workflow.plannedAuditStartDate) {
  313. return callback(new Error('截止时间不能早于工作开始时间'))
  314. }
  315. if (value > this.formData.workflow.plannedAuditEndDate) {
  316. return callback(new Error('截止时间不能晚于工作结束时间'))
  317. }
  318. callback()
  319. },
  320. trigger: 'change',
  321. },
  322. ],
  323. },
  324. },
  325. }
  326. },
  327. computed: {
  328. // 计算是否应该禁用当前步骤的时间选择器
  329. isTimePickerDisabled() {
  330. // 如果当前没有选中步骤,不禁用
  331. if (
  332. !this.formData.currentStep ||
  333. !this.formData.currentStep.processNodeKey
  334. ) {
  335. return false
  336. }
  337. // 获取当前步骤列表(优先使用已保存的列表)
  338. const stepList =
  339. this.workflowData.list.length > 0
  340. ? this.workflowData.list
  341. : this.workflowData.stepList
  342. if (!stepList || stepList.length <= 1) {
  343. return false
  344. }
  345. // 找到当前步骤在列表中的索引
  346. const currentIndex = stepList.findIndex(
  347. (item) =>
  348. item.processNodeKey === this.formData.currentStep.processNodeKey
  349. )
  350. // 如果是第一个步骤或找不到当前步骤,不禁用
  351. if (currentIndex <= 0) {
  352. return false
  353. }
  354. // 检查上一个步骤是否有设置时间
  355. const prevStep = stepList[currentIndex - 1]
  356. return !prevStep.endTime
  357. },
  358. },
  359. watch: {
  360. // 监听workflowData.stepList变化,动态更新下一个环节的最小可选时间
  361. 'workflowData.stepList': {
  362. handler(newList) {
  363. // 对步骤列表按顺序排序,确保时间验证的准确性
  364. if (newList && newList.length > 0) {
  365. // 这里可以根据需要添加排序逻辑,假设步骤已经按顺序排列
  366. }
  367. },
  368. deep: true,
  369. },
  370. },
  371. mounted() {
  372. this.getWorkflow()
  373. },
  374. methods: {
  375. formatterUserList(userId) {
  376. // 筛选出除主办人员之外的人员
  377. return this.formatterMainUserList().filter(
  378. (item) => item.userId !== userId
  379. )
  380. },
  381. formatterMainUserList() {
  382. // 防御性编程,确保userList存在
  383. if (!this.userList || !Array.isArray(this.userList)) {
  384. return []
  385. }
  386. // 防御性编程,确保project.projectMembers存在且为字符串
  387. if (
  388. !this.project ||
  389. !this.project.projectMembers ||
  390. typeof this.project.projectMembers !== 'string'
  391. ) {
  392. return this.userList // 返回全部用户作为后备方案
  393. }
  394. try {
  395. // 分割项目组成员ID并过滤用户列表
  396. const projectMemberIds = this.project.projectMembers.split(',')
  397. const filteredUsers = this.userList.filter((item) =>
  398. projectMemberIds.includes(item.userId)
  399. )
  400. return filteredUsers
  401. } catch (error) {
  402. return this.userList // 出错时返回全部用户作为后备方案
  403. }
  404. },
  405. // 获取日期选择器的配置选项,动态设置最小可选日期
  406. getPickerOptions(row) {
  407. const stepList = this.workflowData.stepList
  408. const rowIndex = stepList.findIndex(
  409. (item) => item.processNodeKey === row.processNodeKey
  410. )
  411. let pickerOptions = {
  412. disabledDate: (time) => {
  413. // 基础限制:不能早于工作开始时间,不能晚于工作结束时间
  414. const startLimit = this.formData.workflow.plannedAuditStartDate
  415. ? new Date(this.formData.workflow.plannedAuditStartDate).getTime()
  416. : null
  417. const endLimit = this.formData.workflow.plannedAuditEndDate
  418. ? new Date(this.formData.workflow.plannedAuditEndDate).getTime()
  419. : null
  420. if (startLimit && time.getTime() < startLimit - 8.64e7) {
  421. return true // 禁用开始时间之前的日期
  422. }
  423. if (endLimit && time.getTime() > endLimit) {
  424. return true // 禁用结束时间之后的日期
  425. }
  426. // 如果不是第一个步骤,需要确保时间大于上一个步骤的时间
  427. if (rowIndex > 0) {
  428. const prevStep = stepList[rowIndex - 1]
  429. if (prevStep && prevStep.endTime) {
  430. const prevEndTime = new Date(prevStep.endTime).getTime()
  431. if (time.getTime() <= prevEndTime - 8.64e7) {
  432. return true // 禁用上一步骤结束时间之前的日期
  433. }
  434. }
  435. }
  436. return false
  437. },
  438. }
  439. return pickerOptions
  440. },
  441. handleSetWorkflow() {
  442. this.getUser()
  443. this.dialogs.setStepDialogVisible = true
  444. if (this.workflowData.list.length > 0) {
  445. this.workflowData.stepList = this.workflowData.list
  446. this.workflowData.stepList = this.workflowData.list.map((item) => ({
  447. ...item,
  448. userId: item.userId ? item.userId.split(',') : [],
  449. status: item.status ? item.status.split(',') : [],
  450. }))
  451. } else {
  452. getCostProjectNodeTmpletePageList({
  453. pageNum: this.workflowData.pagination.currentPage,
  454. pageSize: this.workflowData.pagination.pageSize,
  455. processId: '1',
  456. }).then((res) => {
  457. this.workflowData.stepList = res.value.records.map((item) => ({
  458. ...item,
  459. userId: item.userId ? item.userId.split(',') : [],
  460. status: item.status ? item.status.split(',') : [],
  461. }))
  462. this.workflowData.pagination.total = res.value.total
  463. })
  464. }
  465. },
  466. // 设置流程环节
  467. handleSetStep(row) {
  468. this.getUser()
  469. this.formData.currentStep = {
  470. ...row,
  471. userId: row.userId ? row.userId.split(',') : [],
  472. status: row.status ? row.status.split(',') : [],
  473. }
  474. this.dialogs.stepDialogVisible = true
  475. this.loading.save = false
  476. // 重置表单验证状态
  477. this.$nextTick(() => {
  478. if (this.$refs.currentStepForm) {
  479. this.$refs.currentStepForm.clearValidate()
  480. }
  481. })
  482. },
  483. // 保存流程环节设置
  484. handleSaveStep(formName, type) {
  485. this.$refs[formName].validate((valid) => {
  486. if (valid) {
  487. if (type == 'list') {
  488. this.workflowData.list = this.workflowData.list.map((item) => {
  489. if (
  490. item.processNodeKey ==
  491. this.formData.currentStep.processNodeKey
  492. ) {
  493. return {
  494. ...item,
  495. ...this.formData.currentStep,
  496. userId: this.formData.currentStep.userId.join(','),
  497. status: this.formData.currentStep.status.join(','),
  498. }
  499. }
  500. return item
  501. })
  502. // 触发自定义事件通知父组件数据已更新
  503. this.$emit('update:workflowData', { ...this.workflowData })
  504. this.handleSaveSetStep('list')
  505. }
  506. } else {
  507. return false
  508. }
  509. })
  510. },
  511. // 保存工作环节设置
  512. handleSaveSetStep(type) {
  513. if (!this.formData.workflow.plannedAuditStartDate) {
  514. this.$message.error('请选择预定的监审工作开始日期')
  515. return
  516. }
  517. if (!this.formData.workflow.plannedAuditEndDate) {
  518. this.$message.error('请选择预定的监审工作结束日期')
  519. return
  520. }
  521. // 验证环节时间顺序,确保下一个环节的时间大于上一个环节的时间
  522. let stepList =
  523. type === 'list' ? this.workflowData.list : this.workflowData.stepList
  524. if (stepList && stepList.length > 1) {
  525. // 按顺序排序步骤(假设存在排序字段或按数组顺序)
  526. const sortedSteps = [...stepList].sort((a, b) => {
  527. // 假设步骤有某种排序标识,这里简单按数组顺序处理
  528. return stepList.indexOf(a) - stepList.indexOf(b)
  529. })
  530. // 验证每个步骤的时间是否符合要求
  531. for (let i = 1; i < sortedSteps.length; i++) {
  532. const currentStep = sortedSteps[i]
  533. const prevStep = sortedSteps[i - 1]
  534. // 检查当前步骤和上一步骤是否都有时间设置
  535. if (currentStep.endTime && prevStep.endTime) {
  536. if (new Date(currentStep.endTime) <= new Date(prevStep.endTime)) {
  537. this.$message.error(
  538. `第${i + 1}个环节的截止时间必须大于第${i}个环节的截止时间`
  539. )
  540. return
  541. }
  542. } else if (currentStep.endTime && !prevStep.endTime) {
  543. this.$message.error(`请先设置第${i}个环节的截止时间`)
  544. return
  545. } else if (!currentStep.endTime && prevStep.endTime) {
  546. this.$message.error(`请设置第${i + 1}个环节的截止时间`)
  547. return
  548. }
  549. }
  550. }
  551. this.loading.save = true
  552. let data = {
  553. nodeReqList: [
  554. {
  555. endTime: '',
  556. id: '',
  557. mainUserId: '',
  558. processNodeKey: '',
  559. status: '',
  560. userId: '',
  561. },
  562. ],
  563. plannedAuditEndDate: this.formData.workflow.plannedAuditEndDate,
  564. plannedAuditStartDate: this.formData.workflow.plannedAuditStartDate,
  565. processId: this.workflowData.detailInfo.processId || '1',
  566. projectId: this.project.projectId,
  567. }
  568. if (type == 'list') {
  569. data.nodeReqList = this.workflowData.list
  570. } else {
  571. data.nodeReqList = this.workflowData.stepList.map((item) => {
  572. return {
  573. ...item,
  574. userId: item.userId.join(','),
  575. status: item.status.join(','),
  576. }
  577. })
  578. }
  579. saveProcess(data)
  580. .then((res) => {
  581. if (res.code === 200) {
  582. this.$message.success('保存成功')
  583. this.loading.save = false
  584. if (type == 'list') {
  585. this.dialogs.stepDialogVisible = false
  586. } else {
  587. this.dialogs.setStepDialogVisible = false
  588. }
  589. this.getWorkflow()
  590. }
  591. })
  592. .catch(() => {
  593. this.loading.save = false
  594. })
  595. },
  596. },
  597. }
  598. </script>
  599. <style scoped lang="scss">
  600. @import '@/styles/costAudit.scss';
  601. </style>