taskDetail.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <template>
  2. <el-dialog
  3. :visible.sync="dialogVisible"
  4. :title="title"
  5. width="90%"
  6. :close-on-click-modal="false"
  7. :close-on-press-escape="false"
  8. @close="handleClose"
  9. >
  10. <!-- 操作按钮 -->
  11. <div slot="title" class="operation-bar">
  12. <span>{{ title }}</span>
  13. </div>
  14. <!-- 标签页容器 -->
  15. <div class="tabs-container">
  16. <el-tabs
  17. v-model="activeTab"
  18. type="border-card"
  19. @tab-click="handleTabClick"
  20. >
  21. <!-- 监审立项信息 -->
  22. <el-tab-pane label="监审立项信息" name="basicInfo">
  23. <basicInfoTab
  24. ref="basicInfoTab"
  25. :project="currentProjectData"
  26. :is-view="isView"
  27. @catalog-id-updated="handleCatalogIdUpdated"
  28. />
  29. </el-tab-pane>
  30. <!-- 监审工作方案 -->
  31. <el-tab-pane label="监审工作方案" name="scenario">
  32. <workPlanTab
  33. :form-data.sync="formData.workPlan"
  34. :loading="scenarioData.saveScenario"
  35. :is-view="isView"
  36. :project="actualProject"
  37. @saveFiles="handleSaveFiles"
  38. @removeFile="handleRemoveFile"
  39. />
  40. </el-tab-pane>
  41. <!-- 报送资料要求 -->
  42. <el-tab-pane label="报送资料要求" name="material">
  43. <materialTab
  44. :project="actualProject"
  45. :is-view="isView"
  46. :material-data="materialData"
  47. @refresh="getMaterialData"
  48. @paginationChange="handlePaginationChange"
  49. @update:materialData="(val) => (materialData = val)"
  50. />
  51. </el-tab-pane>
  52. <!-- 成本调查表 -->
  53. <el-tab-pane label="成本调查表" name="survey">
  54. <surveyTab
  55. :project="actualProject"
  56. :is-view="isView"
  57. :survey-data="surveyData"
  58. />
  59. </el-tab-pane>
  60. <!-- 监审工作流程 -->
  61. <el-tab-pane label="监审工作流程" name="workflow">
  62. <workflowTab
  63. :project="actualProject"
  64. :is-view="isView"
  65. :workflow-data.sync="workflowData"
  66. />
  67. </el-tab-pane>
  68. <!-- 监审通知 -->
  69. <el-tab-pane label="监审通知" name="auditNotice">
  70. <auditNoticeTab
  71. ref="auditNoticeTab"
  72. :project="actualProject"
  73. :is-view="isView"
  74. :document-data="documentData"
  75. @refresh="getDocumentData"
  76. @paginationChange="handlePaginationChange"
  77. />
  78. </el-tab-pane>
  79. </el-tabs>
  80. </div>
  81. </el-dialog>
  82. </template>
  83. <script>
  84. import {
  85. addCostProjectScenario,
  86. updateCostProjectScenario,
  87. } from '@/api/taskCustomizedRelease.js'
  88. import basicInfoTab from './taskComponents/basicInfoTab.vue'
  89. import workPlanTab from './taskComponents/workPlanTab.vue'
  90. import materialTab from './taskComponents/materialTab.vue'
  91. import surveyTab from './taskComponents/surveyTab.vue'
  92. import workflowTab from './taskComponents/workflowTab.vue'
  93. import auditNoticeTab from './taskComponents/auditNoticeTab.vue'
  94. import { taskMixin } from '@/views/costAudit/projectInfo/auditTaskManage/taskCustomizedRelease/index.js'
  95. export default {
  96. name: 'TaskDetail',
  97. components: {
  98. basicInfoTab,
  99. workPlanTab,
  100. materialTab,
  101. surveyTab,
  102. workflowTab,
  103. auditNoticeTab,
  104. },
  105. mixins: [taskMixin],
  106. props: {
  107. visible: {
  108. type: Boolean,
  109. default: false,
  110. },
  111. title: {
  112. type: String,
  113. default: '任务详情',
  114. },
  115. project: {
  116. type: Object,
  117. default: () => {},
  118. },
  119. isView: {
  120. type: Boolean,
  121. default: false,
  122. },
  123. },
  124. data() {
  125. return {
  126. dialogVisible: false,
  127. currentProjectData: null,
  128. }
  129. },
  130. computed: {
  131. // 优先使用 currentProjectData,其次使用 project prop
  132. actualProject() {
  133. return this.currentProjectData || this.project
  134. },
  135. },
  136. watch: {
  137. visible(newVal) {
  138. this.dialogVisible = newVal
  139. if (newVal && this.actualProject && this.actualProject.projectId) {
  140. this.initData()
  141. }
  142. },
  143. dialogVisible(newVal) {
  144. this.$emit('update:visible', newVal)
  145. },
  146. // 监听 actualProject 变化,确保数据更新时重新加载
  147. 'actualProject.projectId': {
  148. handler(newProjectId) {
  149. if (newProjectId && this.dialogVisible) {
  150. this.initData()
  151. }
  152. },
  153. immediate: false,
  154. },
  155. },
  156. mounted() {
  157. this.dialogVisible = this.visible
  158. if (this.visible && this.actualProject && this.actualProject.projectId) {
  159. this.initData()
  160. }
  161. },
  162. methods: {
  163. initData() {
  164. this.handleTabClick()
  165. },
  166. // 处理 catalogId 更新事件
  167. handleCatalogIdUpdated(catalogId) {
  168. if (catalogId && this.currentProjectData) {
  169. // 更新 currentProjectData 中的 catalogId
  170. this.$set(this.currentProjectData, 'catalogId', catalogId)
  171. // 同时更新 basicInfo.catalogId,确保 surveyTab 可以获取到
  172. if (!this.currentProjectData.basicInfo) {
  173. this.$set(this.currentProjectData, 'basicInfo', {})
  174. }
  175. this.$set(this.currentProjectData.basicInfo, 'catalogId', catalogId)
  176. }
  177. },
  178. // 保存
  179. handleSave() {
  180. switch (this.activeTab) {
  181. case 'scenario':
  182. this.saveWorkPlan()
  183. break
  184. case 'material':
  185. this.saveMaterial()
  186. break
  187. case 'auditNotice':
  188. this.$refs.auditNoticeTab.handleSaveDocument()
  189. break
  190. default:
  191. break
  192. }
  193. },
  194. // saveFiles 方法
  195. handleSaveFiles(data) {
  196. this.saveFiles(data)
  197. },
  198. // removeFile 方法
  199. handleRemoveFile(index, removedFile, currentFiles) {
  200. this.removeFile(index, removedFile, currentFiles)
  201. },
  202. // saveFiles 方法(来自 mixin 的方法,但需要在组件中实现)
  203. saveFiles(data) {
  204. try {
  205. if (!Array.isArray(this.formData.workPlan.attachmentIds)) {
  206. this.formData.workPlan.attachmentIds = []
  207. }
  208. if (!Array.isArray(data)) {
  209. console.warn('saveFiles方法期望接收数组类型参数')
  210. return
  211. }
  212. const newPaths = data
  213. .map((element) => {
  214. if (typeof element === 'string') {
  215. return element
  216. }
  217. return element?.savePath || element?.filePath || null
  218. })
  219. .filter((path) => path && typeof path === 'string')
  220. const uniquePaths = [
  221. ...new Set([...this.formData.workPlan.attachmentIds, ...newPaths]),
  222. ]
  223. this.formData.workPlan.attachmentIds = uniquePaths
  224. } catch (error) {
  225. console.error('处理文件路径时发生错误:', error)
  226. }
  227. },
  228. // removeFile 方法
  229. removeFile(index, removedFile, currentFiles) {
  230. try {
  231. const currentPaths = this.formData.workPlan.attachmentIds || []
  232. if (currentPaths.length > 0 && typeof currentPaths[0] === 'string') {
  233. currentPaths.splice(index, 1)
  234. this.formData.workPlan.attachmentIds = [...currentPaths]
  235. } else {
  236. const filteredPaths = currentPaths.filter((item) => {
  237. if (typeof item === 'string') {
  238. const fileName = item.substring(item.lastIndexOf('/') + 1)
  239. return fileName !== removedFile.fileName
  240. }
  241. return (
  242. item !== removedFile && item.fileName !== removedFile.fileName
  243. )
  244. })
  245. this.formData.workPlan.attachmentIds = filteredPaths
  246. }
  247. } catch (error) {
  248. console.error('删除文件时发生错误:', error)
  249. }
  250. },
  251. // 打开弹窗方法
  252. open(data, type) {
  253. console.log('taskDetail open - data:', data)
  254. console.log('taskDetail open - type:', type)
  255. // 设置项目数据
  256. if (data) {
  257. // 根据类型提取 projectId
  258. const projectId =
  259. type === 'chengben'
  260. ? data.projectId || data.id
  261. : data.id || data.projectId
  262. // 构造项目对象
  263. this.currentProjectData = {
  264. projectId: projectId,
  265. taskId:
  266. type === 'chengben'
  267. ? data.taskId || data.id
  268. : data.userTask?.id || data.taskId || data.id,
  269. ...data,
  270. }
  271. }
  272. // 打开弹窗
  273. this.dialogVisible = true
  274. this.activeTab = 'basicInfo'
  275. // 初始化数据
  276. if (this.currentProjectData && this.currentProjectData.projectId) {
  277. this.$nextTick(() => {
  278. this.initData()
  279. })
  280. }
  281. },
  282. // 保存工作方案
  283. saveWorkPlan() {
  284. this.scenarioData.saveScenario = true
  285. let data = {
  286. ...this.formData.workPlan,
  287. attachmentIds: this.formData.workPlan.attachmentIds.join(','),
  288. projectId: this.actualProject.projectId,
  289. }
  290. if (this.scenarioData.addScenario) {
  291. addCostProjectScenario(data)
  292. .then((res) => {
  293. this.scenarioData.saveScenario = false
  294. this.$message.success('保存成功')
  295. })
  296. .catch(() => {
  297. this.scenarioData.saveScenario = false
  298. })
  299. } else {
  300. updateCostProjectScenario(data)
  301. .then((res) => {
  302. this.scenarioData.saveScenario = false
  303. this.$message.success('保存成功')
  304. })
  305. .catch(() => {
  306. this.scenarioData.saveScenario = false
  307. })
  308. }
  309. },
  310. // 关闭
  311. handleClose() {
  312. this.dialogVisible = false
  313. this.$emit('close')
  314. },
  315. },
  316. }
  317. </script>
  318. <style scoped lang="scss">
  319. .operation-bar {
  320. display: flex;
  321. justify-content: space-between;
  322. align-items: center;
  323. width: 100%;
  324. .header-buttons {
  325. display: flex;
  326. gap: 10px;
  327. }
  328. }
  329. .tabs-container {
  330. margin-top: 10px;
  331. }
  332. ::v-deep .el-dialog__header {
  333. padding: 20px 20px 10px;
  334. }
  335. ::v-deep .el-dialog__body {
  336. padding: 10px 20px 20px;
  337. }
  338. </style>