details.vue 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. <template>
  2. <div class="details-container">
  3. <el-dialog
  4. :visible.sync="visible"
  5. :title="dialogTitle"
  6. width="80%"
  7. top="5vh"
  8. :close-on-click-modal="false"
  9. @close="handleClose"
  10. >
  11. <!-- 操作按钮区域 -->
  12. <div class="btn-group">
  13. <el-button
  14. v-for="item in buttonData"
  15. :key="item.id"
  16. type="primary"
  17. plain
  18. @click="handleAuditPass(item)"
  19. >
  20. {{ item.value }}
  21. </el-button>
  22. </div>
  23. <!-- 标签页面 -->
  24. <template v-if="!isShenhe">
  25. <el-tabs
  26. v-model="activeTab"
  27. type="card"
  28. class="audit-tabs"
  29. @tab-click="handleTabClick"
  30. >
  31. <el-tab-pane label="报送资料" name="submitData">
  32. <submit-data
  33. :id="id"
  34. :current-node="currentNode"
  35. :current-status="currentStatus"
  36. :materials="submitMaterials"
  37. />
  38. </el-tab-pane>
  39. <el-tab-pane label="成本调查表" name="costSurvey">
  40. <cost-survey
  41. :current-node="currentNode"
  42. :current-status="currentStatus"
  43. :paginated-data="costSurveyPaginated"
  44. :pagination="costSurveyPagination"
  45. :audited-unit-id="auditedUnitId"
  46. :upload-id="''"
  47. :survey-template-id="''"
  48. :catalog-id="catalogId"
  49. :project-id="selectedProject && selectedProject.projectId"
  50. @handle-page-change="handleCostSurveyPageChange"
  51. @handle-size-change="handleCostSurveySizeChange"
  52. @refresh="handleCostSurveyRefresh"
  53. />
  54. </el-tab-pane>
  55. <el-tab-pane
  56. v-if="currentNode !== 'clcs'"
  57. label="成本审核"
  58. name="costAudit"
  59. >
  60. <cost-audit
  61. :id="id"
  62. :key="costAuditKey"
  63. ref="costAudit"
  64. :selected-project="selectedProject"
  65. :current-node="currentNode"
  66. :current-status="currentStatus"
  67. :catalog-id="catalogId"
  68. :audited-unit-id="auditedUnitId"
  69. />
  70. </el-tab-pane>
  71. <el-tab-pane
  72. v-if="currentNode !== 'clcs'"
  73. label="工作底稿"
  74. name="workDraft"
  75. >
  76. <work-draft
  77. :id="id"
  78. ref="workDraft"
  79. :current-node="currentNode"
  80. :current-status="currentStatus"
  81. />
  82. </el-tab-pane>
  83. <el-tab-pane
  84. v-if="currentNode !== 'clcs'"
  85. label="提取资料登记"
  86. name="extractMaterial"
  87. >
  88. <extract-material
  89. :id="id"
  90. ref="extractMaterial"
  91. :current-node="currentNode"
  92. :current-status="currentStatus"
  93. />
  94. </el-tab-pane>
  95. <el-tab-pane
  96. v-if="currentNode !== 'clcs' && currentNode !== 'sdsh'"
  97. label="成本审核意见"
  98. name="auditOpinion"
  99. >
  100. <audit-opinion
  101. :id="id"
  102. ref="auditOpinion"
  103. :key="opinionKey"
  104. :current-node="currentNode"
  105. :current-status="currentStatus"
  106. @refresh="handleAuditOpinionRefresh"
  107. @close="handleClose"
  108. />
  109. </el-tab-pane>
  110. <el-tab-pane label="消息通知" name="messageNotify">
  111. <message-notify :id="id" ref="messageNotify" />
  112. </el-tab-pane>
  113. </el-tabs>
  114. </template>
  115. <!-- 只显示报送资料 -->
  116. <div v-else class="submit-data-container">
  117. <submit-data
  118. :id="id"
  119. :current-node="currentNode"
  120. :current-status="currentStatus"
  121. :materials="submitMaterials"
  122. />
  123. </div>
  124. </el-dialog>
  125. <!-- 补充材料弹窗 -->
  126. <el-dialog
  127. :visible.sync="showSupplementDialog"
  128. title="补充材料"
  129. width="500px"
  130. >
  131. <div class="dialog-content">
  132. <div class="form-item">
  133. <label class="form-label">补充材料:</label>
  134. <el-input
  135. v-model="additionalParams.content"
  136. type="textarea"
  137. :rows="6"
  138. placeholder="请输入补充材料"
  139. maxlength="500"
  140. show-word-limit
  141. />
  142. </div>
  143. <!-- <div class="form-item">
  144. <label class="form-label">发送方式:</label>
  145. <el-checkbox-group v-model="additionalParams.sendType">
  146. <el-checkbox label="site">站内消息</el-checkbox>
  147. <el-checkbox label="sms">短信通知</el-checkbox>
  148. </el-checkbox-group>
  149. </div> -->
  150. </div>
  151. <div slot="footer" class="dialog-footer">
  152. <el-button type="primary" @click="submitSupplement">发送</el-button>
  153. <el-button @click="showSupplementDialog = false">取消</el-button>
  154. </div>
  155. </el-dialog>
  156. <!-- 中止监审弹窗 -->
  157. <el-dialog
  158. :visible.sync="showAbortDialog"
  159. title="中止监审"
  160. width="500px"
  161. :modal="false"
  162. >
  163. <div class="dialog-content">
  164. <div class="form-item">
  165. <label class="form-label">中止意见:</label>
  166. <el-input
  167. v-model="additionalParams.content"
  168. type="textarea"
  169. :rows="6"
  170. placeholder="请输入中止意见"
  171. maxlength="500"
  172. show-word-limit
  173. />
  174. </div>
  175. <!-- <div class="form-item">
  176. <label class="form-label">发送方式:</label>
  177. <el-checkbox-group v-model="additionalParams.sendType">
  178. <el-checkbox label="site">站内消息</el-checkbox>
  179. <el-checkbox label="sms">短信通知</el-checkbox>
  180. </el-checkbox-group>
  181. </div> -->
  182. </div>
  183. <div slot="footer" class="dialog-footer">
  184. <el-button type="primary" @click="submitAbort">发送</el-button>
  185. <el-button @click="showAbortDialog = false">取消</el-button>
  186. </div>
  187. </el-dialog>
  188. <!-- 初审退回弹窗 -->
  189. <el-dialog
  190. :visible.sync="showRejectDialog"
  191. :title="dialogTitle + '退回'"
  192. width="500px"
  193. >
  194. <div class="dialog-content">
  195. <div class="form-item">
  196. <label class="form-label">退回意见:</label>
  197. <el-input
  198. v-model="additionalParams.content"
  199. type="textarea"
  200. :rows="6"
  201. placeholder="请输入退回意见"
  202. maxlength="500"
  203. show-word-limit
  204. />
  205. </div>
  206. <!-- <div class="form-item">
  207. <label class="form-label">发送方式:</label>
  208. <el-checkbox-group v-model="additionalParams.sendType">
  209. <el-checkbox label="site">站内消息</el-checkbox>
  210. <el-checkbox label="sms">短信通知</el-checkbox>
  211. </el-checkbox-group>
  212. </div> -->
  213. </div>
  214. <div slot="footer" class="dialog-footer">
  215. <el-button type="primary" @click="submitReject">发送</el-button>
  216. <el-button @click="showRejectDialog = false">取消</el-button>
  217. </div>
  218. </el-dialog>
  219. </div>
  220. </template>
  221. <script>
  222. import costAudit from './costAudit.vue'
  223. import costSurvey from './costSurvey.vue'
  224. import submitData from './submitData.vue'
  225. import workDraft from './workDraft.vue'
  226. import extractMaterial from './extractMaterial.vue'
  227. import auditOpinion from './auditOpinion.vue'
  228. import messageNotify from './messageNotify.vue'
  229. import {
  230. getDataPreliminaryReviewButton,
  231. doProcessBtn,
  232. } from '@/api/dataPreliminaryReview'
  233. import { getTaskRequirementList } from '@/api/auditTaskProcessing'
  234. import { getSurveyList } from '@/api/audit/survey'
  235. export default {
  236. name: 'Details',
  237. components: {
  238. costAudit,
  239. costSurvey,
  240. submitData,
  241. workDraft,
  242. extractMaterial,
  243. auditOpinion,
  244. messageNotify,
  245. },
  246. props: {
  247. visible: {
  248. type: Boolean,
  249. default: true,
  250. },
  251. id: {
  252. type: [String, Number],
  253. default: null,
  254. },
  255. selectedProject: {
  256. type: Object,
  257. default: () => ({}),
  258. },
  259. currentNode: {
  260. type: String,
  261. default: '',
  262. },
  263. currentStatus: {
  264. type: String,
  265. default: '',
  266. },
  267. // 任务信息(从props传入,替代路由query)
  268. taskInfo: {
  269. type: Object,
  270. default: () => ({}),
  271. },
  272. isShenhe: {
  273. type: Boolean,
  274. default: false,
  275. },
  276. },
  277. data() {
  278. return {
  279. buttonData: [], //资料初审按钮数据
  280. activeTab: 'submitData', // 默认选中报送资料标签页
  281. opinionKey: 0, // 用于强制重渲染成本审核意见子组件
  282. costAuditKey: 0, // 用于强制重渲染成本审核子组件,避免保留上次状态
  283. // 报送资料数据(从接口获取后下发给子组件)
  284. submitMaterials: [],
  285. // 成本调查表数据
  286. costSurveyAll: [],
  287. costSurveyPaginated: [],
  288. costSurveyPagination: { currentPage: 1, pageSize: 10, total: 0 },
  289. // 弹窗显示状态
  290. showSupplementDialog: false,
  291. showAbortDialog: false,
  292. showRejectDialog: false,
  293. // 弹窗数据
  294. additionalParams: {},
  295. // 当前操作按钮信息
  296. currentButton: null,
  297. }
  298. },
  299. computed: {
  300. dialogTitle() {
  301. // 根据节点类型设置标题
  302. if (
  303. this.currentNode === 'sdshenhe' &&
  304. this.currentStatus === '审核中'
  305. ) {
  306. return '审核'
  307. } else if (
  308. this.currentNode === 'yjgaozhi' ||
  309. this.currentNode === 'yjgz'
  310. ) {
  311. return '意见告知'
  312. } else if (this.currentNode === 'clcs') {
  313. return '资料初审'
  314. } else if (this.currentNode === 'sdsh') {
  315. return '成本审核'
  316. } else if (this.currentNode === 'yjfk') {
  317. return '意见反馈'
  318. }
  319. // 兜底:必须返回一个字符串,避免 eslint 报错
  320. return '成本审核详情'
  321. },
  322. // 从 selectedProject / taskInfo 中派生 auditedUnitId,供模板和子组件使用
  323. auditedUnitId() {
  324. return (
  325. (this.selectedProject &&
  326. (this.selectedProject.auditedUnitId ||
  327. this.selectedProject.auditedunitid)) ||
  328. (this.taskInfo &&
  329. (this.taskInfo.auditedUnitId || this.taskInfo.auditedunitid)) ||
  330. ''
  331. )
  332. },
  333. // 从 selectedProject / taskInfo 中派生 catalogId,供模板和查询使用
  334. catalogId() {
  335. return (
  336. (this.selectedProject &&
  337. (this.selectedProject.catalogId ||
  338. this.selectedProject.catalogid)) ||
  339. (this.taskInfo &&
  340. (this.taskInfo.catalogId || this.taskInfo.catalogid)) ||
  341. ''
  342. )
  343. },
  344. },
  345. watch: {
  346. visible(newVal) {
  347. // 监听visible变化,弹窗打开时设置标签页并获取按钮数据
  348. if (newVal && this.id) {
  349. // 每次打开强制刷新“成本审核意见”子组件,避免保留上次状态
  350. this.opinionKey++
  351. // 每次打开强制重建“成本审核”子组件,避免保留上次状态
  352. this.costAuditKey++
  353. this.$nextTick(() => {
  354. // 设置标签页
  355. this.setActiveTab()
  356. // 弹窗打开时,无论什么情况都要获取资料初审按钮数据
  357. this.getPreliminaryReviewButton()
  358. // 若默认在报送资料页,加载报送资料
  359. if (this.activeTab === 'submitData') {
  360. this.loadSubmitMaterials()
  361. }
  362. // 若程序设置的活动标签页为“成本审核”,需要主动触发子组件加载版本数据
  363. if (this.activeTab === 'costAudit') {
  364. this.$nextTick(() => {
  365. if (
  366. this.$refs.costAudit &&
  367. this.$refs.costAudit.loadFromTaskId
  368. ) {
  369. this.$refs.costAudit.loadFromTaskId()
  370. } else if (
  371. this.$refs.costAudit &&
  372. this.$refs.costAudit.getDetail
  373. ) {
  374. this.$refs.costAudit.getDetail()
  375. }
  376. })
  377. }
  378. // 若程序设置的活动标签页为“成本审核意见”,切换过来就调接口刷新
  379. if (this.activeTab === 'auditOpinion') {
  380. this.$nextTick(() => {
  381. if (
  382. this.$refs.auditOpinion &&
  383. this.$refs.auditOpinion.getPreliminaryOpinionData
  384. ) {
  385. this.$refs.auditOpinion.getPreliminaryOpinionData()
  386. }
  387. })
  388. }
  389. })
  390. }
  391. },
  392. // 监听currentNode变化,如果弹窗已打开,更新标签页并重新获取按钮数据
  393. currentNode(newVal, oldVal) {
  394. if (this.visible && this.id && newVal && newVal !== oldVal) {
  395. this.$nextTick(() => {
  396. // 设置标签页
  397. this.setActiveTab()
  398. // 重新获取按钮数据
  399. this.getPreliminaryReviewButton()
  400. // 如果当前应显示“成本审核”,主动触发加载
  401. if (this.activeTab === 'costAudit') {
  402. this.$nextTick(() => {
  403. if (
  404. this.$refs.costAudit &&
  405. this.$refs.costAudit.loadFromTaskId
  406. ) {
  407. this.$refs.costAudit.loadFromTaskId()
  408. } else if (
  409. this.$refs.costAudit &&
  410. this.$refs.costAudit.getDetail
  411. ) {
  412. this.$refs.costAudit.getDetail()
  413. }
  414. })
  415. }
  416. // 如果当前应显示“成本审核意见”,主动触发加载
  417. if (this.activeTab === 'auditOpinion') {
  418. this.$nextTick(() => {
  419. if (
  420. this.$refs.auditOpinion &&
  421. this.$refs.auditOpinion.getPreliminaryOpinionData
  422. ) {
  423. this.$refs.auditOpinion.getPreliminaryOpinionData()
  424. }
  425. })
  426. }
  427. })
  428. }
  429. },
  430. // 监听currentStatus变化,如果弹窗已打开,更新标签页并重新获取按钮数据
  431. currentStatus(newVal, oldVal) {
  432. if (this.visible && this.id && newVal && newVal !== oldVal) {
  433. this.$nextTick(() => {
  434. // 设置标签页
  435. this.setActiveTab()
  436. // 重新获取按钮数据
  437. this.getPreliminaryReviewButton()
  438. })
  439. }
  440. },
  441. // 监听id变化,如果弹窗已打开,重新获取按钮数据
  442. id(newVal) {
  443. if (this.visible && newVal) {
  444. this.$nextTick(() => {
  445. // 设置标签页
  446. this.setActiveTab()
  447. // 获取按钮数据
  448. this.getPreliminaryReviewButton()
  449. // 任务变化时,若在报送资料页,刷新报送资料
  450. if (this.activeTab === 'submitData') {
  451. this.loadSubmitMaterials()
  452. }
  453. // 若当前为成本审核标签,主动加载
  454. if (this.activeTab === 'costAudit') {
  455. this.$nextTick(() => {
  456. if (
  457. this.$refs.costAudit &&
  458. this.$refs.costAudit.loadFromTaskId
  459. ) {
  460. this.$refs.costAudit.loadFromTaskId()
  461. } else if (
  462. this.$refs.costAudit &&
  463. this.$refs.costAudit.getDetail
  464. ) {
  465. this.$refs.costAudit.getDetail()
  466. }
  467. })
  468. }
  469. // 若当前为成本审核意见标签,主动加载
  470. if (this.activeTab === 'auditOpinion') {
  471. this.$nextTick(() => {
  472. if (
  473. this.$refs.auditOpinion &&
  474. this.$refs.auditOpinion.getPreliminaryOpinionData
  475. ) {
  476. this.$refs.auditOpinion.getPreliminaryOpinionData()
  477. }
  478. })
  479. }
  480. })
  481. }
  482. },
  483. },
  484. mounted() {
  485. // 设置标签页
  486. this.setActiveTab()
  487. // 如果组件挂载时弹窗已打开且有id,也要获取按钮数据
  488. if (this.visible && this.id) {
  489. this.$nextTick(() => {
  490. // 弹窗打开时,无论什么情况都要获取资料初审按钮数据
  491. this.getPreliminaryReviewButton()
  492. if (this.activeTab === 'submitData') {
  493. this.loadSubmitMaterials()
  494. }
  495. // 如果默认即处于成本审核页签,也要主动触发加载
  496. if (this.activeTab === 'costAudit') {
  497. this.$nextTick(() => {
  498. if (this.$refs.costAudit && this.$refs.costAudit.loadFromTaskId) {
  499. this.$refs.costAudit.loadFromTaskId()
  500. } else if (
  501. this.$refs.costAudit &&
  502. this.$refs.costAudit.getDetail
  503. ) {
  504. this.$refs.costAudit.getDetail()
  505. }
  506. })
  507. }
  508. // 如果默认即处于成本审核意见页签,也要主动触发加载
  509. if (this.activeTab === 'auditOpinion') {
  510. this.$nextTick(() => {
  511. if (
  512. this.$refs.auditOpinion &&
  513. this.$refs.auditOpinion.getPreliminaryOpinionData
  514. ) {
  515. this.$refs.auditOpinion.getPreliminaryOpinionData()
  516. }
  517. })
  518. }
  519. })
  520. }
  521. },
  522. methods: {
  523. // 根据 currentNode 和 currentStatus 设置活动标签页
  524. setActiveTab() {
  525. if (this.currentNode === 'sdsh' && this.currentStatus === '审核中') {
  526. this.activeTab = 'costAudit'
  527. } else if (this.currentNode === 'clcs') {
  528. // 如果 currentNode 是 'clcs',显示成本调查表标签页
  529. this.activeTab = 'submitData'
  530. } else if (
  531. this.currentNode === 'yjgaozhi' ||
  532. this.currentNode === 'yjfk'
  533. ) {
  534. // 如果 currentNode 是 'yjgaozhi',显示意见告知标签页(成本审核意见)
  535. this.activeTab = 'auditOpinion'
  536. } else {
  537. // 其他情况默认显示报送资料标签页
  538. this.activeTab = 'submitData'
  539. }
  540. },
  541. // 获取资料初审按钮
  542. async getPreliminaryReviewButton() {
  543. // 直接从 props 获取 currentNode,确保是最新的值
  544. const currentNode = this.currentNode
  545. // 构建参数对象
  546. const params = {
  547. taskId: this.id,
  548. }
  549. // 只有当 currentNode 有值时才添加 processNodeKey
  550. if (currentNode && currentNode.trim() !== '') {
  551. params.processNodeKey = currentNode === 'ccls' ? 'clcs' : currentNode
  552. }
  553. try {
  554. const response = await getDataPreliminaryReviewButton(params)
  555. this.buttonData = response.value || []
  556. } catch (error) {
  557. this.buttonData = []
  558. }
  559. },
  560. handleClose() {
  561. // 关闭弹窗时触发事件
  562. this.$emit('update:visible', false)
  563. this.$emit('close')
  564. },
  565. // 处理审核意见保存成功后的刷新(透传payload)
  566. handleAuditOpinionRefresh(payload) {
  567. // 触发父组件刷新列表,透传重置页码标记
  568. this.$emit('refresh', payload)
  569. },
  570. open() {
  571. // 打开弹窗方法,供父组件通过ref调用
  572. this.$emit('update:visible', true)
  573. },
  574. // 成本调查表审核成功后刷新列表
  575. handleCostSurveyRefresh() {
  576. this.loadCostSurveyList()
  577. },
  578. handleTabClick(tab) {
  579. if (tab && tab.name === 'submitData' && this.id) {
  580. this.loadSubmitMaterials()
  581. }
  582. if (tab && tab.name === 'costSurvey' && this.id) {
  583. // 加载成本调查表列表
  584. console.log(122)
  585. this.loadCostSurveyList()
  586. }
  587. if (tab && tab.name === 'costAudit' && this.id) {
  588. // 加载成本审核列表
  589. this.$nextTick(() => {
  590. if (this.$refs.costAudit && this.$refs.costAudit.loadFromTaskId) {
  591. this.$refs.costAudit.loadFromTaskId()
  592. } else if (this.$refs.costAudit && this.$refs.costAudit.getDetail) {
  593. this.$refs.costAudit.getDetail()
  594. }
  595. })
  596. }
  597. if (tab && tab.name === 'auditOpinion' && this.id) {
  598. // 切换到成本审核意见时,立即调接口刷新
  599. this.$nextTick(() => {
  600. if (
  601. this.$refs.auditOpinion &&
  602. this.$refs.auditOpinion.getPreliminaryOpinionData
  603. ) {
  604. this.$refs.auditOpinion.getPreliminaryOpinionData()
  605. }
  606. })
  607. }
  608. if (tab && tab.name === 'workDraft' && this.id) {
  609. // 切换到工作底稿时,主动刷新(走接口)
  610. this.$nextTick(() => {
  611. if (this.$refs.workDraft) {
  612. if (this.$refs.workDraft.getWorkingPaperRecords) {
  613. this.$refs.workDraft.getWorkingPaperRecords()
  614. }
  615. if (this.$refs.workDraft.getWorkingPaperContent) {
  616. this.$refs.workDraft.getWorkingPaperContent()
  617. }
  618. }
  619. })
  620. }
  621. if (tab && tab.name === 'extractMaterial' && this.id) {
  622. // 切换到提取材料登记时,主动刷新(走接口)
  623. this.$nextTick(() => {
  624. if (
  625. this.$refs.extractMaterial &&
  626. this.$refs.extractMaterial.getExtractMaterials
  627. ) {
  628. this.$refs.extractMaterial.getExtractMaterials()
  629. }
  630. })
  631. }
  632. if (tab && tab.name === 'messageNotify' && this.id) {
  633. // 切换到消息通知时,主动刷新(走接口)
  634. this.$nextTick(() => {
  635. if (
  636. this.$refs.messageNotify &&
  637. this.$refs.messageNotify.getNoticeList
  638. ) {
  639. this.$refs.messageNotify.internalPagination.currentPage = 1
  640. this.$refs.messageNotify.getNoticeList()
  641. }
  642. })
  643. }
  644. },
  645. async loadSubmitMaterials() {
  646. try {
  647. const resp = await getTaskRequirementList(this.id)
  648. const list = resp?.value || resp?.data || resp || []
  649. this.submitMaterials = Array.isArray(list) ? list : []
  650. } catch (e) {
  651. this.submitMaterials = []
  652. }
  653. },
  654. // 加载“成本调查表”列表:使用 getSurveyList 接口(从父组件传入的 catalogId/auditedUnitId)
  655. async loadCostSurveyList() {
  656. console.log(this.taskInfo, '这行数据')
  657. try {
  658. const params = {
  659. taskId: this.taskInfo.taskId,
  660. pageNum: this.costSurveyPagination.currentPage,
  661. pageSize: this.costSurveyPagination.pageSize,
  662. type: 1,
  663. }
  664. console.log('[CostSurvey] call getSurveyList with:', params)
  665. const resp = await getSurveyList(params)
  666. // 兼容不同返回结构
  667. const raw =
  668. (resp &&
  669. ((resp.value && (resp.value.records || resp.value)) ||
  670. resp.data ||
  671. resp)) ||
  672. []
  673. const list = Array.isArray(raw)
  674. ? raw
  675. : Array.isArray(raw.records)
  676. ? raw.records
  677. : []
  678. const rows = list.map((it) => {
  679. const t = String(it.templateType || it.templatetype || '')
  680. const tableType =
  681. t === '1'
  682. ? '单记录'
  683. : t === '2'
  684. ? '固定表'
  685. : t === '3'
  686. ? '动态表'
  687. : ''
  688. return {
  689. id: it.uploadId || it.id || '',
  690. name: it.surveyTemplateName,
  691. dataType: it.dataType || '预置模板',
  692. tableType,
  693. isRequired: String(it.isRequired) === '1' ? '是' : '否',
  694. isUploaded: String(it.isUpload) === '1' || it.isUpload === true,
  695. uploadId: it.uploadId || it.id || '',
  696. surveyTemplateId: it.templateId || it.surveyTemplateId || '',
  697. catalogId: it.catalogId || this.catalogId || '',
  698. auditedUnitId: it.auditedUnitId || this.auditedUnitId || '',
  699. tableItems: it.tableItems || [],
  700. dynamicTableData: it.dynamicTableData || [],
  701. auditedStatus: it.auditedStatus,
  702. }
  703. })
  704. console.log(
  705. '[CostSurvey] mapped rows from getSurveyList:',
  706. rows.length,
  707. rows
  708. )
  709. this.costSurveyAll = rows
  710. this.costSurveyPagination.total = rows.length
  711. this.costSurveyPagination.currentPage = 1
  712. this.paginateCostSurvey()
  713. } catch (e) {
  714. console.error('[CostSurvey] loadCostSurveyList error:', e)
  715. this.costSurveyAll = []
  716. this.costSurveyPagination = { currentPage: 1, pageSize: 10, total: 0 }
  717. this.costSurveyPaginated = []
  718. }
  719. },
  720. paginateCostSurvey() {
  721. const { currentPage, pageSize } = this.costSurveyPagination
  722. const start = (currentPage - 1) * pageSize
  723. const end = start + pageSize
  724. this.costSurveyPaginated = this.costSurveyAll.slice(start, end)
  725. },
  726. handleCostSurveyPageChange(page) {
  727. this.costSurveyPagination.currentPage = page
  728. this.paginateCostSurvey()
  729. },
  730. handleCostSurveySizeChange(size) {
  731. this.costSurveyPagination.pageSize = size
  732. this.costSurveyPagination.currentPage = 1
  733. this.paginateCostSurvey()
  734. },
  735. // 处理审核操作按钮点击
  736. async handleAuditPass(item) {
  737. this.additionalParams = {
  738. content: '',
  739. // sendType: [], // 默认选中站内消息和短信通知
  740. }
  741. this.currentButton = item // 保存当前按钮信息
  742. console.log('点击的按钮数据:', item)
  743. // “通过”按钮:校验报送资料/成本调查表是否存在【已上传】但【未审核】的数据
  744. if (this.isPassAction(item)) {
  745. const ok = await this.validateAllUploadedAudited()
  746. if (!ok) return
  747. }
  748. const key = Number(item.key)
  749. if (key === 1) {
  750. this.showSupplementDialog = true
  751. } else if (key === 2) {
  752. // 中止监审
  753. this.showAbortDialog = true
  754. } else if (key === 4) {
  755. // 初审退回
  756. this.showRejectDialog = true
  757. } else {
  758. // 添加确认对话框
  759. this.$confirm(
  760. `确定要执行"${this.currentButton.value}"操作吗?`,
  761. '操作确认',
  762. {
  763. confirmButtonText: '确定',
  764. cancelButtonText: '取消',
  765. type: 'warning',
  766. }
  767. )
  768. .then(() => {
  769. this.executeAuditOperation()
  770. })
  771. .catch(() => {
  772. // 用户取消操作
  773. this.$message.info('已取消操作')
  774. })
  775. }
  776. },
  777. // 是否“通过”类操作(按钮文案包含“通过”时触发校验)
  778. isPassAction(item) {
  779. const text = (item && (item.value || item.label || '')) || ''
  780. return String(text).includes('通过')
  781. },
  782. // 校验:报送资料/成本调查表中是否存在【已上传】但【未审核】的数据
  783. async validateAllUploadedAudited() {
  784. try {
  785. // 确保两边数据都是最新的
  786. await Promise.all([
  787. this.loadSubmitMaterials(),
  788. this.loadCostSurveyList(),
  789. ])
  790. // 同步刷新相关 tab 数据,避免因为未切换到页签导致 refs 数据为空
  791. await this.validateRequiredTabDataBeforePass({ refreshOnly: true })
  792. // 1) 报送资料:接口字段通常是 isUpload: '1'/'0'
  793. const submitPending = (this.submitMaterials || []).filter((it) => {
  794. const uploaded = String(it && it.isUpload) === '1'
  795. const pending =
  796. !it.auditedStatus || String(it.auditedStatus) === '0'
  797. return uploaded && pending
  798. })
  799. if (submitPending.length > 0) {
  800. const names = submitPending
  801. .slice(0, 5)
  802. .map(
  803. (it) =>
  804. it.informationName ||
  805. it.name ||
  806. it.informationTitle ||
  807. '未命名资料'
  808. )
  809. .join('、')
  810. this.$message.warning(
  811. `报送资料中存在已上传但未审核的数据:${names}${
  812. submitPending.length > 5 ? ' 等' : ''
  813. },请先完成审核后再执行“通过”。`
  814. )
  815. return false
  816. }
  817. // 2) 成本调查表:本页映射字段为 isUploaded(boolean) / auditedStatus
  818. const surveyPending = (this.costSurveyAll || []).filter((it) => {
  819. const uploaded = it && it.isUploaded === true
  820. const pending =
  821. !it.auditedStatus || String(it.auditedStatus) === '0'
  822. return uploaded && pending
  823. })
  824. if (surveyPending.length > 0) {
  825. const names = surveyPending
  826. .slice(0, 5)
  827. .map((it) => it.name || it.surveyTemplateName || '未命名调查表')
  828. .join('、')
  829. this.$message.warning(
  830. `成本调查表中存在已上传但未审核的数据:${names}${
  831. surveyPending.length > 5 ? ' 等' : ''
  832. },请先完成审核后再执行“通过”。`
  833. )
  834. return false
  835. }
  836. // 3) 通过前置校验:成本审核模板/工作底稿在线编辑/提取资料登记
  837. const okTabs = await this.validateRequiredTabDataBeforePass({
  838. refreshOnly: false,
  839. })
  840. if (!okTabs) return false
  841. return true
  842. } catch (e) {
  843. // 若校验过程中接口失败,保守起见不允许通过
  844. this.$message.error('校验未审核数据失败,请稍后重试')
  845. return false
  846. }
  847. },
  848. // “通过”前置校验:
  849. // 1) 成本审核:必须已生成模板(至少有模板ID/表头)
  850. // 2) 工作底稿:在线编辑内容或核增核减记录至少存在其一
  851. // 3) 提取资料登记:列表至少一条
  852. // refreshOnly=true 时仅刷新子组件数据,不弹提示、不做拦截
  853. async validateRequiredTabDataBeforePass({ refreshOnly } = {}) {
  854. try {
  855. // 仅当对应 tab 存在时才检查(clcs 阶段不显示这些 tab)
  856. const hasAuditTabs =
  857. this.currentNode !== 'clcs' && this.currentNode !== ''
  858. if (!hasAuditTabs) return true
  859. // 先尽可能刷新数据
  860. const tasks = []
  861. if (this.$refs.costAudit) {
  862. if (typeof this.$refs.costAudit.getDetail === 'function') {
  863. tasks.push(this.$refs.costAudit.getDetail())
  864. } else if (
  865. typeof this.$refs.costAudit.loadFromTaskId === 'function'
  866. ) {
  867. tasks.push(this.$refs.costAudit.loadFromTaskId())
  868. }
  869. }
  870. if (this.$refs.workDraft) {
  871. if (
  872. typeof this.$refs.workDraft.getWorkingPaperContent === 'function'
  873. ) {
  874. tasks.push(this.$refs.workDraft.getWorkingPaperContent())
  875. }
  876. if (
  877. typeof this.$refs.workDraft.getWorkingPaperRecords === 'function'
  878. ) {
  879. tasks.push(this.$refs.workDraft.getWorkingPaperRecords())
  880. }
  881. }
  882. if (this.$refs.extractMaterial) {
  883. if (
  884. typeof this.$refs.extractMaterial.getExtractMaterials ===
  885. 'function'
  886. ) {
  887. tasks.push(this.$refs.extractMaterial.getExtractMaterials())
  888. }
  889. }
  890. if (tasks.length) {
  891. try {
  892. await Promise.all(tasks)
  893. } catch (e) {
  894. // ignore: 由后续判断兜底
  895. }
  896. }
  897. if (refreshOnly) return true
  898. // -------- 1) 成本审核:必须已生成模板 --------
  899. if (this.$refs.costAudit) {
  900. const ca = this.$refs.costAudit
  901. const tplId =
  902. (ca.auditForm &&
  903. (ca.auditForm.surveyTemplateId ||
  904. ca.auditForm.dataTable ||
  905. ca.auditForm.historyTemplate)) ||
  906. ''
  907. const hasHeaders =
  908. (Array.isArray(ca.tableHeadersRes) &&
  909. ca.tableHeadersRes.length > 0) ||
  910. (Array.isArray(ca.costAuditcolumn) &&
  911. ca.costAuditcolumn.length > 0)
  912. if (!tplId || !hasHeaders) {
  913. this.$message.warning(
  914. '【成本审核】未生成核定表模板,请先生成模板后再执行“通过”。'
  915. )
  916. return false
  917. }
  918. }
  919. // -------- 2) 工作底稿:在线编辑内容/记录至少一项有数据 --------
  920. if (this.$refs.workDraft) {
  921. const wd = this.$refs.workDraft
  922. const content = (wd.workingPaperContent || '').trim()
  923. const records = Array.isArray(wd.workingPaperRecords)
  924. ? wd.workingPaperRecords
  925. : []
  926. if (!content && records.length === 0) {
  927. this.$message.warning(
  928. '【工作底稿】在线编辑内容为空且核增核减记录为空,请先录入后再执行“通过”。'
  929. )
  930. return false
  931. }
  932. }
  933. // -------- 3) 提取资料登记:列表至少一条 --------
  934. if (this.$refs.extractMaterial) {
  935. const em = this.$refs.extractMaterial
  936. const list = Array.isArray(em.extractMaterials)
  937. ? em.extractMaterials
  938. : []
  939. if (list.length === 0) {
  940. this.$message.warning(
  941. '【提取资料登记】暂无数据,请先添加提取资料后再执行“通过”。'
  942. )
  943. return false
  944. }
  945. }
  946. return true
  947. } catch (e) {
  948. if (!refreshOnly) {
  949. this.$message.error(
  950. '校验工作底稿/提取资料/成本审核模板失败,请稍后重试'
  951. )
  952. }
  953. return false
  954. }
  955. },
  956. // 执行审核操作
  957. async executeAuditOperation() {
  958. if (!this.id) {
  959. this.$message.error('缺少任务ID')
  960. return
  961. }
  962. if (!this.currentButton) {
  963. this.$message.error('操作失败:缺少按钮信息')
  964. return
  965. }
  966. try {
  967. const params = {
  968. taskId: this.id,
  969. processNodeKey: this.currentNode,
  970. key: this.currentButton.key,
  971. // sendType: this.additionalParams.sendType?.join(','),
  972. content: this.additionalParams.content,
  973. }
  974. const response = await doProcessBtn(params)
  975. console.log('操作结果:', response)
  976. if (response && response.code === 200) {
  977. this.$message.success(response.value)
  978. // 关闭所有弹窗
  979. this.showSupplementDialog = false
  980. this.showAbortDialog = false
  981. this.showRejectDialog = false
  982. // 关闭主弹窗
  983. this.handleClose()
  984. // 触发父组件刷新列表
  985. this.$emit('refresh')
  986. }
  987. } catch (error) {
  988. this.$message.error(this.project.currentNodeName + '驳回')
  989. }
  990. },
  991. // 提交补充材料
  992. submitSupplement() {
  993. if (
  994. !this.additionalParams.content ||
  995. !this.additionalParams.content.trim()
  996. ) {
  997. this.$message.error('请输入补充材料')
  998. return
  999. }
  1000. if (this.currentButton) {
  1001. this.executeAuditOperation(this.currentButton)
  1002. } else {
  1003. this.$message.error('操作失败:缺少按钮信息')
  1004. }
  1005. },
  1006. // 提交中止监审
  1007. submitAbort() {
  1008. if (
  1009. !this.additionalParams.content ||
  1010. !this.additionalParams.content.trim()
  1011. ) {
  1012. this.$message.error('请输入中止意见')
  1013. return
  1014. }
  1015. if (this.currentButton) {
  1016. this.executeAuditOperation(this.currentButton)
  1017. } else {
  1018. this.$message.error('操作失败:缺少按钮信息')
  1019. }
  1020. },
  1021. // 提交初审退回
  1022. submitReject() {
  1023. if (
  1024. !this.additionalParams.content ||
  1025. !this.additionalParams.content.trim()
  1026. ) {
  1027. this.$message.error('请输入退回意见')
  1028. return
  1029. }
  1030. if (this.currentButton) {
  1031. this.executeAuditOperation(this.currentButton)
  1032. } else {
  1033. this.$message.error('操作失败:缺少按钮信息')
  1034. }
  1035. },
  1036. },
  1037. }
  1038. </script>
  1039. <style scoped>
  1040. .btn-group {
  1041. /* margin-bottom: 20px;
  1042. margin-left: 20px; */
  1043. }
  1044. .btn-group .el-button {
  1045. margin-right: 10px;
  1046. }
  1047. .details-container {
  1048. width: 100%;
  1049. height: 100%;
  1050. }
  1051. .audit-tabs {
  1052. height: calc(100vh - 150px);
  1053. }
  1054. .audit-tabs .el-tabs__header {
  1055. margin-bottom: 0;
  1056. padding: 15px 15px 0;
  1057. background: #f5f7fa;
  1058. border-bottom: 1px solid #ebeef5;
  1059. }
  1060. .audit-tabs .el-tabs__nav-wrap {
  1061. padding-bottom: 10px;
  1062. }
  1063. .audit-tabs .el-tabs__content {
  1064. height: calc(100% - 60px);
  1065. padding: 15px;
  1066. overflow-y: auto;
  1067. }
  1068. .audit-tabs .el-tab-pane {
  1069. height: 100%;
  1070. }
  1071. /* 弹窗样式 */
  1072. .dialog-content {
  1073. padding: 10px 0;
  1074. }
  1075. .form-item {
  1076. display: flex;
  1077. align-items: flex-start;
  1078. margin-bottom: 20px;
  1079. }
  1080. .form-label {
  1081. display: inline-block;
  1082. width: 100px;
  1083. text-align: center;
  1084. color: #606266;
  1085. vertical-align: top;
  1086. flex-shrink: 0;
  1087. }
  1088. .form-item .el-checkbox-group {
  1089. display: inline-block;
  1090. margin-left: 10px;
  1091. }
  1092. .form-item .el-checkbox {
  1093. margin-right: 20px;
  1094. vertical-align: middle;
  1095. }
  1096. .form-item .el-input,
  1097. .form-item .el-textarea {
  1098. flex: 1;
  1099. }
  1100. .form-item .el-input__inner {
  1101. width: 100%;
  1102. margin-left: 0;
  1103. }
  1104. ::v-deep .el-dialog__body {
  1105. max-height: 720px;
  1106. overflow: auto;
  1107. }
  1108. ::v-deep .el-tabs > .el-tabs__content {
  1109. padding: 20px 0;
  1110. }
  1111. ::v-deep .el-dialog__footer {
  1112. text-align: right;
  1113. }
  1114. </style>