index.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. <template>
  2. <div class="task-progress-manage">
  3. <!-- 页面标题 -->
  4. <!-- <h2>成本监审任务进度跟踪</h2> -->
  5. <div v-if="activeView == 'list'">
  6. <div class="search-container">
  7. <el-form :inline="true" :model="searchForm" class="demo-form-inline">
  8. <el-form-item label="年度:">
  9. <el-date-picker
  10. v-model="searchForm.year"
  11. type="year"
  12. placeholder="选择年"
  13. format="yyyy"
  14. value-format="yyyy"
  15. ></el-date-picker>
  16. </el-form-item>
  17. <el-form-item label="监审项目名称:">
  18. <el-input
  19. v-model="searchForm.projectName"
  20. placeholder="请输入监审项目名称"
  21. clearable
  22. maxlength="30"
  23. ></el-input>
  24. </el-form-item>
  25. <el-form-item>
  26. <el-button
  27. type="primary"
  28. icon="iconfont-5039297 icon-chaxun"
  29. @click="handleSearch"
  30. >
  31. 搜索
  32. </el-button>
  33. <el-button
  34. plain
  35. type="primary"
  36. icon="iconfont-5039297 icon-zhongzhi"
  37. @click="handleReset"
  38. >
  39. 重置
  40. </el-button>
  41. </el-form-item>
  42. </el-form>
  43. </div>
  44. <!-- 数据表格 -->
  45. <cost-audit-table
  46. v-loading="loading"
  47. :table-data="tableData"
  48. :columns="tableColumns"
  49. :border="true"
  50. :row-class-name="getRowClassName"
  51. :show-pagination="true"
  52. :pagination="pagination"
  53. :table-props="{
  54. rowKey: 'id',
  55. treeProps: { children: 'children', hasChildren: 'hasChildren' },
  56. defaultExpandAll: true,
  57. showExpandColumn: false,
  58. }"
  59. @pagination-change="handlePaginationChange"
  60. >
  61. <template #projectName="{ row }">
  62. <span v-if="row.isSubTask" class="link-text" @click="handleView(row)">
  63. {{ row.projectName }}
  64. </span>
  65. <span v-else class="link-text" @click="handleViewTaskDetail(row)">
  66. {{ row.projectName }}
  67. </span>
  68. </template>
  69. <!-- 预警列自定义内容 -->
  70. <template #warning="{ row }">
  71. <span :class="['warning-point', computeWarning(row)]"></span>
  72. </template>
  73. <!-- 操作列自定义内容 -->
  74. <template #action="{ row }">
  75. <template v-if="row.isSubTask">
  76. <el-button
  77. size="mini"
  78. type="text"
  79. @click="handleMessage(row, 'chengben')"
  80. >
  81. 查看
  82. </el-button>
  83. <el-button
  84. v-if="row.status != 400 && row.status != 300"
  85. size="mini"
  86. type="text"
  87. @click="handleUrgeReporting(row)"
  88. >
  89. 催报
  90. </el-button>
  91. </template>
  92. <template v-else>
  93. <el-button
  94. size="mini"
  95. type="text"
  96. @click="handleViewTaskDetail(row)"
  97. >
  98. 详情
  99. </el-button>
  100. <el-button
  101. v-if="
  102. row.status != 400 &&
  103. row.status != 300 &&
  104. row.currentNode !== 'gd'
  105. "
  106. size="mini"
  107. type="text"
  108. @click="handlePause(row)"
  109. >
  110. 中止
  111. </el-button>
  112. <el-button
  113. v-if="row.status == '300'"
  114. type="text"
  115. @click="handleHf(row)"
  116. >
  117. 恢复
  118. </el-button>
  119. <el-button
  120. v-if="
  121. row.status != 400 &&
  122. row.status != 300 &&
  123. row.currentNode !== 'gd'
  124. "
  125. size="mini"
  126. type="text"
  127. @click="handleDelegate(row)"
  128. >
  129. 催办
  130. </el-button>
  131. <el-button
  132. v-if="
  133. row.status != 400 &&
  134. row.status != 300 &&
  135. row.currentNode !== 'gd' &&
  136. row.warningStatus !== 'green'
  137. "
  138. size="mini"
  139. type="text"
  140. @click="handleUrge(row)"
  141. >
  142. 督办
  143. </el-button>
  144. <!-- <el-button
  145. v-if="row.status != 400 && row.status != 300"
  146. size="mini"
  147. type="text"
  148. @click="handleToDo(row)"
  149. >
  150. 代办
  151. </el-button> -->
  152. </template>
  153. </template>
  154. <template slot="empty">
  155. <Empty></Empty>
  156. </template>
  157. </cost-audit-table>
  158. </div>
  159. <!-- 详情内容 -->
  160. <!-- <div v-if="activeView == 'detail'" class="detail-content">
  161. <detail-tabs :task-data="taskData" :project="project" @detailClose="handleDetailClose"></detail-tabs>
  162. </div> -->
  163. <!-- 任务中止表单弹窗 -->
  164. <el-dialog
  165. title="任务中止"
  166. :visible.sync="showPauseForm"
  167. width="40%"
  168. :close-on-click-modal="false"
  169. >
  170. <div class="dialog-content">
  171. <el-form ref="pauseForm" :model="pauseForm" label-width="100px">
  172. <el-form-item label="中止原因:">
  173. <el-input
  174. v-model="pauseForm.reason"
  175. type="textarea"
  176. rows="5"
  177. placeholder="请输入中止原因"
  178. maxlength="500"
  179. show-word-limit
  180. ></el-input>
  181. </el-form-item>
  182. </el-form>
  183. </div>
  184. <div slot="footer" class="dialog-footer">
  185. <el-button type="primary" @click="handlePauseFormSubmit">
  186. 确定
  187. </el-button>
  188. <el-button @click="handlePauseFormCancel">取消</el-button>
  189. </div>
  190. </el-dialog>
  191. <!-- 任务督办弹窗 -->
  192. <el-dialog
  193. title="任务督办"
  194. :visible.sync="showUrgeForm"
  195. width="50%"
  196. :modal="true"
  197. :close-on-click-modal="false"
  198. >
  199. <div class="dialog-content">
  200. <el-form ref="urgeForm" :model="urgeForm" label-width="140px">
  201. <el-form-item label="选择督办人员:">
  202. <el-select
  203. v-model="urgeForm.remindPerson"
  204. placeholder="请选择选择督办人员"
  205. style="width: 100%"
  206. >
  207. <el-option
  208. v-for="(item, index) in urgeUserList"
  209. :key="index"
  210. :label="item.fullname"
  211. :value="item.userId"
  212. ></el-option>
  213. </el-select>
  214. </el-form-item>
  215. <el-form-item label="选择要求办结时间:">
  216. <el-date-picker
  217. v-model="urgeForm.requireTime"
  218. type="date"
  219. placeholder="请选择要求办结时间"
  220. style="width: 100%"
  221. format="yyyy-MM-dd"
  222. value-format="yyyy-MM-dd"
  223. ></el-date-picker>
  224. </el-form-item>
  225. <el-form-item label="督办要求:">
  226. <el-input
  227. v-model="urgeForm.content"
  228. type="textarea"
  229. rows="5"
  230. placeholder="请输入督办要求"
  231. style="width: 100%"
  232. maxlength="500"
  233. show-word-limit
  234. ></el-input>
  235. </el-form-item>
  236. <!-- <el-form-item label="发送方式:">
  237. <el-checkbox-group v-model="urgeForm.sendType">
  238. <el-checkbox label="站内消息">站内消息</el-checkbox>
  239. <el-checkbox label="短信通知">短信通知</el-checkbox>
  240. </el-checkbox-group>
  241. </el-form-item> -->
  242. </el-form>
  243. </div>
  244. <div slot="footer" class="dialog-footer">
  245. <el-button @click="handleUrgeCancel">取消</el-button>
  246. <el-button type="primary" @click="handleUrgeSubmit">发送</el-button>
  247. </div>
  248. </el-dialog>
  249. <!-- 任务催报弹窗 -->
  250. <el-dialog
  251. title="任务催报"
  252. :visible.sync="showUrgeReportingForm"
  253. width="50%"
  254. :modal="true"
  255. :close-on-click-modal="false"
  256. >
  257. <div class="dialog-content">
  258. <el-form
  259. ref="urgeReportingFormRef"
  260. :model="urgeReportingForm"
  261. label-width="100px"
  262. >
  263. <el-form-item label="催办内容:">
  264. <el-input
  265. v-model="urgeReportingForm.content"
  266. type="textarea"
  267. rows="5"
  268. placeholder="请输入催办内容"
  269. style="width: 100%"
  270. maxlength="500"
  271. show-word-limit
  272. ></el-input>
  273. </el-form-item>
  274. </el-form>
  275. </div>
  276. <div slot="footer" class="dialog-footer">
  277. <el-button @click="handleUrgeReportingCancel">取消</el-button>
  278. <el-button type="primary" @click="handleUrgeReportingSubmit">
  279. 发送
  280. </el-button>
  281. </div>
  282. </el-dialog>
  283. <!-- 任务催办弹窗 -->
  284. <el-dialog
  285. title="任务催办"
  286. :visible.sync="showDelegateForm"
  287. width="50%"
  288. :modal="true"
  289. :close-on-click-modal="false"
  290. >
  291. <div class="dialog-content">
  292. <el-form ref="delegateForm" :model="delegateForm" label-width="100px">
  293. <el-form-item label="监审组人员:">
  294. <el-select
  295. v-model="delegateForm.userIds"
  296. multiple
  297. clearable
  298. placeholder="请选择监审组人员"
  299. style="width: 100%"
  300. >
  301. <el-option
  302. v-for="(item, index) in delegateUserList"
  303. :key="index"
  304. :label="item.fullname"
  305. :value="item.userId"
  306. ></el-option>
  307. </el-select>
  308. </el-form-item>
  309. <el-form-item label="催办内容:">
  310. <el-input
  311. v-model="delegateForm.content"
  312. type="textarea"
  313. rows="5"
  314. placeholder="请输入催办内容"
  315. style="width: 100%"
  316. maxlength="500"
  317. show-word-limit
  318. ></el-input>
  319. </el-form-item>
  320. <!-- <el-form-item label="发送方式:">
  321. <el-checkbox-group v-model="delegateForm.sendType">
  322. <el-checkbox label="站内消息">站内消息</el-checkbox>
  323. <el-checkbox label="短信通知">短信通知</el-checkbox>
  324. </el-checkbox-group>
  325. </el-form-item> -->
  326. </el-form>
  327. </div>
  328. <div slot="footer" class="dialog-footer">
  329. <el-button @click="handleDelegateCancel">取消</el-button>
  330. <el-button type="primary" @click="handleDelegateSubmit">发送</el-button>
  331. </div>
  332. </el-dialog>
  333. <!-- 任务详情弹窗(原组件暂保留,不再从列表入口打开) -->
  334. <task-detail
  335. ref="taskDetail"
  336. :visible.sync="taskDetailVisible"
  337. :is-view="isView"
  338. />
  339. <!-- 任务信息弹窗 -->
  340. <task-info ref="taskInfo" />
  341. <!-- 成本监审任务制定弹窗(封装自 tabs.vue,列表“详情/查看”入口使用) -->
  342. <task-customized-release-dialog
  343. :visible.sync="taskReleaseDialogVisible"
  344. :project="project"
  345. :is-view="true"
  346. @backToList="taskReleaseDialogVisible = false"
  347. @close="taskReleaseDialogVisible = false"
  348. />
  349. <!-- 成本监审信息弹窗 -->
  350. <cbjs-info
  351. :id="cbjsInfoData && cbjsInfoData.id"
  352. :selected-project="cbjsInfoData"
  353. :visible.sync="cbjsInfoVisible"
  354. :current-node="cbjsInfoData && cbjsInfoData.currentNode"
  355. :current-status="cbjsInfoData && cbjsInfoData.status"
  356. />
  357. </div>
  358. </template>
  359. <script>
  360. import { taskList } from '@/api/taskProgressManage'
  361. import { dictMixin } from '@/mixins/useDict'
  362. import { getAllUnitList } from '@/api/auditEntityManage'
  363. import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
  364. import { getUserList, getTaskUserList } from '@/api/uc'
  365. import { getCostProjectDetail } from '@/api/taskCustomizedRelease.js'
  366. import { doProcessBtn } from '@/api/dataPreliminaryReview'
  367. import TaskDetail from '@/components/task/taskDetail.vue'
  368. import TaskCustomizedReleaseDialog from '@/components/task/TaskCustomizedReleaseDialog.vue'
  369. import { createSuperviseTask } from '@/api/audit/supervise'
  370. import cbjsInfo from '@/components/task/cbjsInfo.vue'
  371. import taskInfo from '@/components/task/taskInfo.vue'
  372. export default {
  373. components: {
  374. CostAuditTable,
  375. TaskDetail,
  376. TaskCustomizedReleaseDialog,
  377. cbjsInfo,
  378. taskInfo,
  379. },
  380. mixins: [dictMixin],
  381. data() {
  382. return {
  383. // 弹窗相关
  384. cbjsInfoData: null, // 存储当前选中的成本监审信息
  385. cbjsInfoVisible: false, // 控制成本监审信息弹窗的显示/隐藏
  386. dictData: {
  387. auditType: [], //监审形式
  388. projectProposal: [], //立项来源
  389. },
  390. activeView: 'list',
  391. loading: false,
  392. isView: true,
  393. searchForm: {
  394. year: '',
  395. projectName: '',
  396. },
  397. taskData: {},
  398. project: {},
  399. tableData: [],
  400. // 分页
  401. pagination: {
  402. currentPage: 1,
  403. pageSize: 10,
  404. pageSizes: [10, 20, 50, 100],
  405. total: 0,
  406. },
  407. // 表格列配置
  408. tableColumns: [
  409. {
  410. prop: 'serialNumber',
  411. label: '序号',
  412. width: 80,
  413. align: 'center',
  414. formatter: (row) => {
  415. return row.pid == 0 ? row.parentIndex : ''
  416. },
  417. },
  418. {
  419. prop: 'year',
  420. label: '立项年度',
  421. width: 100,
  422. align: 'center',
  423. formatter: (row) => {
  424. return row.year || ''
  425. },
  426. renderHeader: ({ column, $index }) => {
  427. return <span>{column.label}</span>
  428. },
  429. slotName: 'expand',
  430. },
  431. {
  432. prop: 'projectName',
  433. label: '成本监审项目名称',
  434. align: 'left',
  435. headerAlign: 'center',
  436. showOverflowTooltip: true,
  437. slotName: 'projectName',
  438. },
  439. {
  440. prop: 'auditedUnitId',
  441. label: '被监审单位',
  442. align: 'left',
  443. headerAlign: 'center',
  444. showOverflowTooltip: true,
  445. formatter: (row) => {
  446. // 优先使用后端返回的名称,兼容多个名称逗号分隔
  447. if (row && row.auditedUnitName) return row.auditedUnitName
  448. return this.getUnitName(row.auditedUnitId)
  449. },
  450. },
  451. {
  452. prop: 'auditPeriod',
  453. label: '监审期间',
  454. width: 150,
  455. align: 'center',
  456. },
  457. {
  458. prop: 'sourceType',
  459. label: '立项来源',
  460. width: 100,
  461. align: 'center',
  462. formatter: (row) => {
  463. return this.getDictName(
  464. 'projectProposal',
  465. row && row.sourceType !== undefined && row.sourceType !== null
  466. ? String(row.sourceType)
  467. : ''
  468. )
  469. },
  470. },
  471. {
  472. prop: 'auditType',
  473. label: '监审形式',
  474. width: 100,
  475. align: 'center',
  476. formatter: (row) => {
  477. return this.getDictName(
  478. 'auditType',
  479. row && row.auditType !== undefined && row.auditType !== null
  480. ? String(row.auditType)
  481. : ''
  482. )
  483. },
  484. },
  485. {
  486. prop: 'status',
  487. label: '状态',
  488. width: 150,
  489. align: 'center',
  490. formatter: (row) => {
  491. const left = row.currentNodeName || ''
  492. const right = row.statusName || this.getStatusName(row.status)
  493. // 当 currentNode 是 'gd' 时,只显示 currentNodeName,不显示 -statusName
  494. if (row.currentNode === 'gd') {
  495. return left || right
  496. }
  497. return left ? `${left}-${right}` : right
  498. },
  499. },
  500. {
  501. prop: 'warning',
  502. label: '预警',
  503. width: 80,
  504. align: 'center',
  505. slotName: 'warning',
  506. },
  507. {
  508. prop: 'action',
  509. label: '操作',
  510. width: 240,
  511. align: 'center',
  512. slotName: 'action',
  513. },
  514. ],
  515. // 弹窗状态
  516. showPauseConfirm: false,
  517. showPauseForm: false,
  518. showUrgeForm: false,
  519. showDelegateForm: false,
  520. showUrgeReportingForm: false,
  521. // 当前选中的任务
  522. currentTask: null,
  523. // 表单数据
  524. pauseForm: {
  525. reason: '',
  526. },
  527. urgeForm: {
  528. remindPerson: '',
  529. requireTime: '',
  530. content: '',
  531. // sendType: '站内消息',
  532. },
  533. delegateForm: {
  534. userIds: [],
  535. content: '',
  536. // sendType: '站内消息',
  537. },
  538. urgeReportingForm: {
  539. content: '',
  540. },
  541. unitList: [],
  542. // 弹窗人员列表(按接口 getUserList(projectId,type) 动态加载)
  543. delegateUserList: [], // 催办弹窗:监审组人员(type=0)
  544. urgeUserList: [], // 督办弹窗:督办人员(type=1)
  545. // 任务详情弹窗
  546. taskDetailVisible: false,
  547. // 成本监审任务制定弹窗
  548. taskReleaseDialogVisible: false,
  549. }
  550. },
  551. mounted() {
  552. this.getAllUnitList()
  553. this.generateTableData()
  554. },
  555. methods: {
  556. async fetchUrgeUserList() {
  557. try {
  558. const res = await getTaskUserList({ code: 'dbfzr' })
  559. return (res && res.value) || []
  560. } catch (e) {
  561. return []
  562. }
  563. },
  564. async fetchUserListByProjectId({ projectId, type }) {
  565. if (!projectId) return []
  566. try {
  567. const res = await getUserList({ projectId, type })
  568. return (res && res.value) || []
  569. } catch (e) {
  570. return []
  571. }
  572. },
  573. getStatusName(status) {
  574. // 100待提交、200审核中、400办结、300中止
  575. switch (status) {
  576. case '100':
  577. return '待提交'
  578. case '200':
  579. return '审核中'
  580. case '400':
  581. return '办结'
  582. case '300':
  583. return '中止'
  584. default:
  585. return ''
  586. }
  587. },
  588. getAllUnitList() {
  589. getAllUnitList().then((res) => {
  590. this.unitList = res.value || []
  591. })
  592. },
  593. getUnitName(unitId) {
  594. // 直接处理unitId值,而不是row对象
  595. if (unitId && typeof unitId === 'string' && unitId.includes(',')) {
  596. // 如果包含逗号,分割成数组并查找对应的unitName
  597. const unitIds = unitId.split(',')
  598. return unitIds
  599. .map((id) => {
  600. const unit = this.unitList.find((item) => item.unitId == id)
  601. return unit ? unit.unitName : ''
  602. })
  603. .filter((name) => name) // 过滤空值
  604. .join(',')
  605. } else {
  606. // 单个unitId的情况
  607. const unit = this.unitList.find((item) => item.unitId == unitId)
  608. return unit ? unit.unitName : ''
  609. }
  610. },
  611. computeWarning(row) {
  612. // 优先使用后端返回的 warningStatus:green/yellow/res(red)
  613. if (!row) return ''
  614. const ws = (row.warningStatus || '').toString().toLowerCase()
  615. if (ws === 'green') return 'green'
  616. if (ws === 'yellow') return 'yellow'
  617. if (ws === 'res' || ws === 'red') return 'red'
  618. // 兼容无 warningStatus 时按截止期计算
  619. const parse = (v) => (v ? new Date(v) : null)
  620. const now = new Date()
  621. const nodeDdl = parse(row.nodeDeadline)
  622. const procDdl = parse(row.processDeadline)
  623. if (nodeDdl && now <= nodeDdl) return 'green'
  624. if (nodeDdl && procDdl) {
  625. if (now > nodeDdl && now <= procDdl) return 'yellow'
  626. if (now > procDdl) return 'red'
  627. }
  628. if (!nodeDdl && procDdl) return now <= procDdl ? 'green' : 'red'
  629. return ''
  630. },
  631. generateTableData() {
  632. this.loading = true
  633. taskList({
  634. projectName: this.searchForm.projectName,
  635. year: this.searchForm.year,
  636. pageNum: this.pagination.currentPage,
  637. pageSize: this.pagination.pageSize,
  638. })
  639. .then((res) => {
  640. // console.log(res,'表格')
  641. // 兼容分页与非分页返回
  642. const records = res?.value?.records || res?.value || []
  643. const total =
  644. Number(res?.value?.total) ||
  645. Number(res?.total) ||
  646. (Array.isArray(records) ? records.length : 0)
  647. // 统一将 childTasks 规范化为 children,并补齐 pid / isSubTask
  648. const normalized = this.normalizeChildren(
  649. Array.isArray(records) ? records : []
  650. )
  651. this.tableData = normalized
  652. this.pagination.total = Number(total) || 0
  653. // 同步后端分页信息(如有)
  654. if (Number.isFinite(Number(res?.value?.current))) {
  655. this.pagination.currentPage = Number(res.value.current)
  656. }
  657. if (Number.isFinite(Number(res?.value?.size))) {
  658. this.pagination.pageSize = Number(res.value.size)
  659. }
  660. // 移除不需要的属性
  661. this.tableData = this.removeItemFromTree(this.tableData)
  662. let parentIndex = 1
  663. this.tableData.forEach((item) => {
  664. if (item.pid == 0) {
  665. item.parentIndex = parentIndex++
  666. }
  667. })
  668. this.loading = false
  669. })
  670. .catch(() => {
  671. this.loading = false
  672. this.$message.error('获取数据失败')
  673. })
  674. },
  675. handlePaginationChange({ currentPage, pageSize }) {
  676. this.pagination.currentPage = currentPage
  677. this.pagination.pageSize = pageSize
  678. this.generateTableData()
  679. },
  680. removeItemFromTree(treeData) {
  681. // 边界条件检查
  682. if (!treeData || !Array.isArray(treeData)) {
  683. return []
  684. }
  685. // 创建新数组,避免修改原数据
  686. return treeData.map((item) => {
  687. // 创建当前节点的副本
  688. const newItem = { ...item }
  689. // 如果有hasChildren属性则删除
  690. if ('hasChildren' in newItem) {
  691. delete newItem.hasChildren
  692. }
  693. // 递归处理子节点 先检查children是否存在且为数组
  694. if (
  695. newItem.children &&
  696. Array.isArray(newItem.children) &&
  697. newItem.children.length > 0
  698. ) {
  699. newItem.children = this.removeItemFromTree(newItem.children)
  700. }
  701. return newItem
  702. })
  703. },
  704. normalizeChildren(list, parentId = 0) {
  705. if (!Array.isArray(list)) return []
  706. return list.map((node) => {
  707. const copied = { ...node }
  708. const rawChildren = Array.isArray(node.children)
  709. ? node.children
  710. : Array.isArray(node.childTasks)
  711. ? node.childTasks
  712. : []
  713. // 设置父子关系与子任务标识
  714. copied.pid =
  715. node.pid !== undefined && node.pid !== null ? node.pid : parentId
  716. // 注意:后端可能返回字符串 '0',需要统一判断
  717. const pidStr = copied.pid != null ? String(copied.pid) : '0'
  718. copied.isSubTask = pidStr !== '0'
  719. // 递归规范化子节点
  720. const children = this.normalizeChildren(rawChildren, node.id)
  721. delete copied.childTasks
  722. copied.children = children
  723. return copied
  724. })
  725. },
  726. handleReset() {
  727. this.searchForm = {
  728. projectName: '',
  729. year: '',
  730. }
  731. this.pagination.currentPage = 1
  732. this.generateTableData()
  733. },
  734. handleSearch() {
  735. this.pagination.currentPage = 1
  736. this.generateTableData()
  737. },
  738. getRowClassName({ row }) {
  739. if (row.isSubTask) {
  740. return 'sub-task-row'
  741. }
  742. return ''
  743. },
  744. // handleView(row) {
  745. // // 子任务查看:统一走任务制定弹窗(只读)
  746. // this.openTaskReleaseDialog(row)
  747. // },
  748. // 任务中止相关方法
  749. handlePause(row) {
  750. this.currentTask = row
  751. this.$confirm(
  752. '如您选择中止操作,该任务将停止办理,是否中止?',
  753. '中止确认',
  754. {
  755. confirmButtonText: '确定',
  756. cancelButtonText: '取消',
  757. type: 'warning',
  758. }
  759. )
  760. .then(() => {
  761. this.showPauseForm = true
  762. })
  763. .catch(() => {
  764. this.$message({
  765. type: 'info',
  766. message: '已取消',
  767. })
  768. })
  769. },
  770. handlePauseCancel() {
  771. this.showPauseConfirm = false
  772. this.currentTask = null
  773. },
  774. handlePauseConfirm() {
  775. this.showPauseConfirm = false
  776. this.showPauseForm = true
  777. },
  778. handlePauseFormCancel() {
  779. this.showPauseForm = false
  780. this.pauseForm.reason = ''
  781. },
  782. handlePauseFormSubmit() {
  783. if (!this.currentTask) return
  784. const params = {
  785. taskId: this.currentTask.id,
  786. key: 2,
  787. status: 300,
  788. processNodeKey: this.currentTask.currentNode,
  789. content: this.pauseForm.reason,
  790. }
  791. doProcessBtn(params)
  792. .then((res) => {
  793. if (res && Number(res.code) === 200) {
  794. this.$message.success('任务已中止')
  795. this.showPauseForm = false
  796. this.pauseForm.reason = ''
  797. this.currentTask = null
  798. this.generateTableData()
  799. } else {
  800. this.$message.error(res?.message || '操作失败')
  801. }
  802. })
  803. .catch(() => {
  804. this.$message.error('操作失败')
  805. })
  806. },
  807. // 恢复任务
  808. async handleHf(row) {
  809. if (!row || !row.id) {
  810. this.$message.error('缺少任务ID')
  811. return
  812. }
  813. // 弹出确认对话框
  814. this.$confirm('确定要恢复此任务吗?', '恢复任务', {
  815. confirmButtonText: '确定',
  816. cancelButtonText: '取消',
  817. type: 'warning',
  818. })
  819. .then(async () => {
  820. try {
  821. const params = {
  822. taskId: row.id,
  823. key: 2,
  824. status: 200,
  825. processNodeKey: row.currentNode,
  826. }
  827. const response = await doProcessBtn(params)
  828. if (response && response.code === 200) {
  829. this.$message.success('恢复任务成功')
  830. // 刷新列表
  831. this.generateTableData()
  832. } else {
  833. this.$message.error(response?.message || '恢复任务失败')
  834. }
  835. } catch (error) {
  836. // this.$message.error('恢复任务失败')
  837. console.error('恢复任务失败:', error)
  838. }
  839. })
  840. .catch(() => {
  841. // 用户取消操作
  842. this.$message.info('已取消恢复任务')
  843. })
  844. },
  845. // 任务督办相关方法
  846. async handleUrge(row) {
  847. console.log('任务督办:', row)
  848. this.currentTask = row
  849. this.urgeForm = {
  850. remindPerson: '',
  851. requireTime: '',
  852. content: '',
  853. // sendType: [],
  854. }
  855. // 督办人员下拉:走 getTaskUserList,参数 code='dbfzr'
  856. this.urgeUserList = await this.fetchUrgeUserList()
  857. this.showUrgeForm = true
  858. },
  859. handleUrgeCancel() {
  860. this.showUrgeForm = false
  861. this.urgeForm = {
  862. remindPerson: '',
  863. requireTime: '',
  864. content: '',
  865. // sendType: [],
  866. }
  867. this.urgeUserList = []
  868. },
  869. handleUrgeSubmit() {
  870. if (!this.currentTask) return
  871. const projectId =
  872. this.currentTask.projectId || this.currentTask.projectID || ''
  873. const supervisorId = this.urgeForm.remindPerson
  874. const requireTime = this.urgeForm.requireTime
  875. const requireContent = (this.urgeForm.content || '').trim()
  876. if (!projectId) {
  877. this.$message.warning('缺少项目ID,无法发送督办')
  878. return
  879. }
  880. if (!supervisorId) {
  881. this.$message.warning('请选择督办人员')
  882. return
  883. }
  884. if (!requireContent) {
  885. this.$message.warning('请输入督办要求')
  886. return
  887. }
  888. const loading = this.$loading({
  889. lock: true,
  890. text: '发送中...',
  891. spinner: 'el-icon-loading',
  892. background: 'rgba(0,0,0,0.3)',
  893. })
  894. createSuperviseTask({
  895. projectId,
  896. supervisorId,
  897. requireContent,
  898. requireTime,
  899. })
  900. .then((res) => {
  901. if (res && Number(res.code) === 200) {
  902. this.$message.success('督办信息已发送')
  903. this.showUrgeForm = false
  904. this.urgeForm = { remindPerson: '', content: '', requireTime: '' }
  905. this.currentTask = null
  906. this.generateTableData()
  907. } else {
  908. this.$message.error(res?.message || '发送失败')
  909. }
  910. })
  911. .catch(() => {
  912. this.$message.error('发送失败')
  913. })
  914. .finally(() => {
  915. loading && loading.close && loading.close()
  916. })
  917. },
  918. // 任务代办相关方法
  919. async handleDelegate(row) {
  920. console.log('任务代办:', row)
  921. this.currentTask = row
  922. this.showDelegateForm = true
  923. this.delegateForm = {
  924. userIds: [],
  925. content: '',
  926. }
  927. const projectId = row?.projectId || row?.projectID || row?.id || ''
  928. this.delegateUserList = await this.fetchUserListByProjectId({
  929. projectId,
  930. type: 0,
  931. })
  932. },
  933. handleDelegateCancel() {
  934. this.showDelegateForm = false
  935. this.delegateForm = {
  936. userIds: [],
  937. content: '',
  938. // sendType: '站内消息',
  939. }
  940. this.delegateUserList = []
  941. },
  942. handleDelegateSubmit() {
  943. if (!this.currentTask) return
  944. if (
  945. !Array.isArray(this.delegateForm.userIds) ||
  946. this.delegateForm.userIds.length === 0
  947. ) {
  948. this.$message.warning('请选择监审组人员')
  949. return
  950. }
  951. const params = {
  952. taskId: this.currentTask.id,
  953. key: 5,
  954. status: this.currentTask.status,
  955. processNodeKey: this.currentTask.currentNode,
  956. content: this.delegateForm.content,
  957. userIds: this.delegateForm.userIds.join(','),
  958. }
  959. doProcessBtn(params)
  960. .then((res) => {
  961. if (res && Number(res.code) === 200) {
  962. this.$message.success('催办信息已发送')
  963. this.showDelegateForm = false
  964. this.delegateForm = { userIds: [], content: '' }
  965. this.currentTask = null
  966. this.generateTableData()
  967. } else {
  968. this.$message.error(res?.message || '操作失败')
  969. }
  970. })
  971. .catch(() => {
  972. this.$message.error('操作失败')
  973. })
  974. },
  975. // 任务详情相关方法
  976. handleViewTaskDetail(row) {
  977. // 主任务详情:打开成本监审任务制定弹窗(只读)
  978. this.openTaskReleaseDialog(row)
  979. },
  980. handleView(row) {
  981. this.$refs.taskDetail.open(row, 'chengben')
  982. },
  983. // 打开成本监审任务制定弹窗(从进度列表“详情/查看”入口)
  984. openTaskReleaseDialog(row) {
  985. if (!row) return
  986. const projectId =
  987. row.projectId || row.projectID || row.id || row.taskId || ''
  988. if (!projectId) {
  989. this.$message &&
  990. this.$message.warning &&
  991. this.$message.warning('缺少项目ID,无法查看详情')
  992. return
  993. }
  994. this.isView = true
  995. // 加载项目详情后再打开弹窗
  996. getCostProjectDetail({ id: projectId })
  997. .then((res) => {
  998. this.project = (res && res.value) || {}
  999. this.taskReleaseDialogVisible = true
  1000. })
  1001. .catch(() => {
  1002. // 回退:若接口失败,至少把当前行数据带入
  1003. this.project = row || {}
  1004. this.taskReleaseDialogVisible = true
  1005. })
  1006. },
  1007. getProject() {
  1008. getCostProjectDetail({
  1009. id: this.taskData.projectId,
  1010. })
  1011. .then((res) => {
  1012. this.project = {
  1013. ...res.value,
  1014. }
  1015. })
  1016. .catch(() => {
  1017. this.project = this.taskData
  1018. })
  1019. },
  1020. handleDetailClose() {
  1021. this.activeView = 'list'
  1022. this.taskData = null
  1023. },
  1024. // 代办
  1025. handleToDo(row) {
  1026. console.log('代办', row)
  1027. },
  1028. // 催报
  1029. handleUrgeReporting(row) {
  1030. this.currentTask = row
  1031. this.urgeReportingForm = {
  1032. content: '',
  1033. }
  1034. this.showUrgeReportingForm = true
  1035. },
  1036. handleUrgeReportingCancel() {
  1037. this.showUrgeReportingForm = false
  1038. this.urgeReportingForm = { content: '' }
  1039. this.currentTask = null
  1040. },
  1041. handleUrgeReportingSubmit() {
  1042. if (!this.currentTask) return
  1043. // 不再由弹窗选择被监审单位:默认取当前行的 auditedUnitId
  1044. const auditedUnitIds = this.currentTask.auditedUnitId
  1045. ? String(this.currentTask.auditedUnitId)
  1046. : ''
  1047. const params = {
  1048. taskId: this.currentTask.id,
  1049. key: 10,
  1050. status: this.currentTask.status,
  1051. processNodeKey: this.currentTask.currentNode,
  1052. content: this.urgeReportingForm.content,
  1053. auditedUnitIds,
  1054. }
  1055. doProcessBtn(params)
  1056. .then((res) => {
  1057. if (res && Number(res.code) === 200) {
  1058. this.$message.success('催报信息已发送')
  1059. this.showUrgeReportingForm = false
  1060. this.urgeReportingForm = { content: '' }
  1061. this.currentTask = null
  1062. this.generateTableData()
  1063. } else {
  1064. this.$message.error(res?.message || '操作失败')
  1065. }
  1066. })
  1067. .catch(() => {
  1068. this.$message.error('操作失败')
  1069. })
  1070. },
  1071. // 查看 - 修改为打开cbjsInfo弹窗
  1072. handleMessage(row, type) {
  1073. if (type === 'chengben') {
  1074. this.cbjsInfoData = { ...row, taskId: row.id }
  1075. console.log(this.cbjsInfoData, '数据')
  1076. this.cbjsInfoVisible = true
  1077. } else if (this.$refs.taskInfo) {
  1078. this.$refs.taskInfo.open(row, type)
  1079. } else {
  1080. console.warn('taskInfo 组件未找到,请确保已正确导入和注册')
  1081. // 如果 taskInfo 组件不存在,尝试使用其他方式处理
  1082. this.$message.warning('功能暂不可用,请联系管理员')
  1083. }
  1084. },
  1085. },
  1086. }
  1087. </script>
  1088. <style scoped lang="scss">
  1089. @import '@/styles/costAudit.scss';
  1090. .task-progress-manage {
  1091. padding: 20px;
  1092. }
  1093. h2 {
  1094. margin-bottom: 20px;
  1095. font-size: 18px;
  1096. color: #303133;
  1097. }
  1098. .demo-form-inline {
  1099. display: flex;
  1100. align-items: center;
  1101. flex-wrap: wrap;
  1102. }
  1103. .description {
  1104. margin-top: 15px;
  1105. margin-bottom: 20px;
  1106. padding: 15px;
  1107. background-color: #fff7e6;
  1108. border: 1px solid #ffe7ba;
  1109. border-radius: 4px;
  1110. }
  1111. .description p {
  1112. margin: 5px 0;
  1113. line-height: 1.6;
  1114. }
  1115. /* 子任务样式 */
  1116. .el-table .sub-task-row {
  1117. background-color: #fafafa !important;
  1118. }
  1119. /* 预警点样式 */
  1120. .warning-point {
  1121. display: inline-block;
  1122. width: 12px;
  1123. height: 12px;
  1124. border-radius: 50%;
  1125. }
  1126. .warning-point.red {
  1127. background-color: #ff4949;
  1128. }
  1129. .warning-point.yellow {
  1130. background-color: #e6a23c;
  1131. }
  1132. .warning-point.green {
  1133. background-color: #67c23a;
  1134. }
  1135. .back-button-container {
  1136. margin-bottom: 20px;
  1137. }
  1138. .process-tab {
  1139. padding: 20px;
  1140. }
  1141. .process-time-form {
  1142. display: flex;
  1143. align-items: center;
  1144. }
  1145. </style>