Просмотр исходного кода

意见反馈流程对接完成

shiyanyu 1 месяц назад
Родитель
Сommit
049aed1f19

+ 29 - 0
src/api/audit/preliminaryOpinion.js

@@ -0,0 +1,29 @@
+import request from '@/utils/request'
+
+const url = window.context.form
+
+// 根据任务id获取初步意见
+export function getPreliminaryOpinion(data) {
+  return request({
+    url: url + '/api/enterprise/opinion/getByTaskId?taskId=' + data.taskId,
+    method: 'get',
+  })
+}
+
+// 新增结论意见
+export function addPreliminaryOpinion(data) {
+  return request({
+    url: url + '/api/enterprise/opinion/save',
+    method: 'post',
+    data: data,
+  })
+}
+
+// 企业监审意见提交
+export function submitPreliminaryOpinion(data) {
+  return request({
+    url: url + '/api/enterprise/castTask/yjsave',
+    method: 'post',
+    data: data,
+  })
+}

+ 307 - 30
src/views/EntDeclaration/auditTaskManagement/components/AuditOpinionTab.vue

@@ -6,19 +6,19 @@
       <div class="opinion-item">
         <label>被监审单位基本情况及主要财务状况</label>
         <el-input
-          v-model="localFormData.basicFinancialInfo"
+          v-model="localFormData.basicSituation"
           type="textarea"
           rows="5"
-          :disabled="isViewMode"
+          :disabled="true"
         />
       </div>
       <div class="opinion-item">
         <label>监审项目现行执行的价格标准</label>
         <el-input
-          v-model="localFormData.priceStandard"
+          v-model="localFormData.currentPriceStandard"
           type="textarea"
           rows="5"
-          :disabled="isViewMode"
+          :disabled="true"
         />
       </div>
       <div class="opinion-item">
@@ -27,7 +27,7 @@
           v-model="localFormData.costComposition"
           type="textarea"
           rows="5"
-          :disabled="isViewMode"
+          :disabled="true"
         />
       </div>
       <div class="opinion-item">
@@ -36,7 +36,7 @@
           v-model="localFormData.preliminaryOpinion"
           type="textarea"
           rows="5"
-          :disabled="isViewMode"
+          :disabled="true"
         />
       </div>
     </div>
@@ -47,7 +47,7 @@
       <div class="feedback-item">
         <label>被监审单位反馈意见</label>
         <el-input
-          v-model="localFormData.enterpriseFeedback"
+          v-model="localFormData.feedbackOpinion"
           type="textarea"
           rows="5"
           :disabled="isViewMode"
@@ -57,7 +57,7 @@
         <label>被监审单位反馈资料</label>
         <div v-if="isViewMode" class="file-list-display">
           <div
-            v-for="(file, index) in localFormData.fileList"
+            v-for="(file, index) in localFormData.feedbackMaterials"
             :key="index"
             class="file-item"
           >
@@ -66,7 +66,8 @@
           </div>
           <div
             v-if="
-              !localFormData.fileList || localFormData.fileList.length === 0
+              !localFormData.feedbackMaterials ||
+              localFormData.feedbackMaterials.length === 0
             "
             style="color: #909399"
           >
@@ -76,16 +77,26 @@
         <div v-else class="upload-box">
           <el-upload
             class="upload-demo"
-            action="https://jsonplaceholder.typicode.com/posts/"
-            :on-preview="$emit('handle-preview', $event)"
-            :on-remove="$emit('handle-remove', $event)"
-            :before-remove="$emit('before-remove', $event)"
-            multiple
+            :action="''"
+            :http-request="handleFileUpload"
+            :on-remove="handleFileRemove"
+            :before-upload="beforeFileUpload"
+            :on-success="handleFileUploadSuccess"
+            :on-error="handleFileUploadError"
+            :on-progress="handleFileUploadProgress"
+            :file-list="localFormData.feedbackMaterials"
             :limit="1"
-            :on-exceed="$emit('handle-exceed', $event)"
-            :file-list="localFormData.fileList"
+            :on-exceed="handleFileExceed"
           >
-            <el-button size="small" type="text" :disabled="isViewMode">
+            <el-button
+              v-show="
+                !localFormData.feedbackMaterials ||
+                localFormData.feedbackMaterials.length === 0
+              "
+              size="small"
+              type="text"
+              :disabled="isViewMode"
+            >
               上传附件
             </el-button>
           </el-upload>
@@ -94,7 +105,7 @@
     </div>
 
     <!-- 成本审核结论意见 -->
-    <div class="feedback-section">
+    <!-- <div class="feedback-section">
       <h3>成本审核结论意见</h3>
       <div class="feedback-item">
         <label>成本审核结论意见</label>
@@ -123,23 +134,26 @@
           :disabled="isViewMode"
         />
       </div>
-    </div>
+    </div> -->
   </div>
 </template>
 
 <script>
+  import { getPreliminaryOpinion } from '@/api/audit/preliminaryOpinion'
+  import { uploadFile } from '@/api/file'
+
   export default {
     name: 'AuditOpinionTab',
     props: {
       formData: {
         type: Object,
         default: () => ({
-          basicFinancialInfo: '',
-          priceStandard: '',
+          basicSituation: '',
+          currentPriceStandard: '',
           costComposition: '',
           preliminaryOpinion: '',
-          enterpriseFeedback: '',
-          fileList: [],
+          feedbackOpinion: '',
+          feedbackMaterials: [],
           conclusionOpinion: '',
           otherInstructions: '',
           remarks: '',
@@ -149,16 +163,21 @@
         type: Boolean,
         default: false,
       },
+      id: {
+        type: [String, Number],
+        default: null,
+      },
     },
     data() {
       return {
         localFormData: {
-          basicFinancialInfo: '',
-          priceStandard: '',
+          id: '',
+          basicSituation: '',
+          currentPriceStandard: '',
           costComposition: '',
           preliminaryOpinion: '',
-          enterpriseFeedback: '',
-          fileList: [],
+          feedbackOpinion: '',
+          feedbackMaterials: [],
           conclusionOpinion: '',
           otherInstructions: '',
           remarks: '',
@@ -172,9 +191,9 @@
           // 标记为内部更新,避免触发 localFormData 的 watch
           this.isInternalUpdate = true
           this.localFormData = { ...newVal }
-          // 确保 fileList 也是深拷贝
-          if (newVal.fileList) {
-            this.localFormData.fileList = [...newVal.fileList]
+          // 确保 feedbackMaterials 也是深拷贝
+          if (newVal.feedbackMaterials) {
+            this.localFormData.feedbackMaterials = [...newVal.feedbackMaterials]
           }
           this.$nextTick(() => {
             this.isInternalUpdate = false
@@ -192,6 +211,258 @@
         },
         deep: true,
       },
+      id: {
+        handler(newVal) {
+          // 当 id 变化时,重新获取数据
+          if (newVal) {
+            this.$nextTick(() => {
+              this.getPreliminaryOpinionData()
+            })
+          }
+        },
+        immediate: true,
+      },
+    },
+    mounted() {
+      // 组件挂载时,如果有 id,获取数据
+      if (this.id) {
+        this.$nextTick(() => {
+          this.getPreliminaryOpinionData()
+        })
+      }
+    },
+    activated() {
+      // 如果组件使用了 keep-alive,在激活时也获取数据
+      if (this.id) {
+        this.$nextTick(() => {
+          this.getPreliminaryOpinionData()
+        })
+      }
+    },
+    methods: {
+      // 获取初步审核意见数据
+      async getPreliminaryOpinionData() {
+        if (!this.id) {
+          // 触发数据加载完成事件
+          this.$emit('data-loaded')
+          return
+        }
+        try {
+          const res = await getPreliminaryOpinion({ taskId: this.id })
+          if (res && res.value) {
+            const data = res.value
+            // 标记为内部更新,避免触发 watch
+            this.isInternalUpdate = true
+            // 回显初步审核意见数据
+            this.localFormData = {
+              ...this.localFormData,
+              id: data.id || '',
+              basicSituation: data.basicSituation || '',
+              currentPriceStandard: data.currentPriceStandard || '',
+              costComposition: data.costComposition || '',
+              preliminaryOpinion: data.preliminaryOpinion || '',
+            }
+            // 如果有企业反馈数据,也回显
+            if (data.feedbackOpinion !== undefined) {
+              this.localFormData.feedbackOpinion = data.feedbackOpinion || ''
+            }
+            if (data.feedbackMaterials) {
+              // 如果返回的是URL数组,转换为文件对象数组
+              if (Array.isArray(data.feedbackMaterials)) {
+                this.localFormData.feedbackMaterials =
+                  data.feedbackMaterials.map((url, index) => {
+                    // 如果已经是对象,直接返回
+                    if (typeof url === 'object') {
+                      return url
+                    }
+                    // 如果是URL字符串,转换为文件对象
+                    return {
+                      uid: `file-${Date.now()}-${index}`,
+                      name: url.split('/').pop() || `文件${index + 1}`,
+                      url: url,
+                      status: 'success',
+                    }
+                  })
+              } else {
+                this.localFormData.feedbackMaterials = []
+              }
+            }
+            this.$nextTick(() => {
+              this.isInternalUpdate = false
+            })
+          } else {
+            // 如果没有数据,重置表单
+            this.isInternalUpdate = true
+            this.localFormData = {
+              ...this.localFormData,
+              id: '',
+              basicSituation: '',
+              currentPriceStandard: '',
+              costComposition: '',
+              preliminaryOpinion: '',
+            }
+            this.$nextTick(() => {
+              this.isInternalUpdate = false
+            })
+          }
+          // 触发数据加载完成事件
+          this.$emit('data-loaded')
+        } catch (error) {
+          console.error('获取初步审核意见失败:', error)
+          // 获取失败不影响页面显示,静默处理
+          // 触发数据加载完成事件(即使失败也关闭加载状态)
+          this.$emit('data-loaded')
+        }
+      },
+      // 文件上传前验证
+      beforeFileUpload(file) {
+        const allowedTypes = [
+          'application/pdf',
+          'application/msword',
+          'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+          'application/vnd.ms-excel',
+          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+          'text/csv',
+        ]
+
+        const allowedExtensions = [
+          '.pdf',
+          '.doc',
+          '.docx',
+          '.xls',
+          '.xlsx',
+          '.csv',
+        ]
+        const fileExtension = '.' + file.name.split('.').pop().toLowerCase()
+
+        // 检查文件格式
+        const isCorrectType = allowedTypes.includes(file.type)
+        const isCorrectExtension = allowedExtensions.includes(fileExtension)
+
+        if (!isCorrectType && !isCorrectExtension) {
+          this.$message.error(
+            '只允许上传 pdf, doc, docx, xls, xlsx, csv 格式的文件!'
+          )
+          return false
+        }
+
+        // 检查文件大小 (50MB)
+        const isLt50M = file.size / 1024 / 1024 < 50
+        if (!isLt50M) {
+          this.$message.error('文件大小不能超过 50MB!')
+          return false
+        }
+
+        return true
+      },
+      // 自定义上传方法
+      async handleFileUpload(options) {
+        const { file, onProgress, onSuccess, onError } = options
+
+        // 检查是否已经上传了文件
+        if (
+          this.localFormData.feedbackMaterials &&
+          this.localFormData.feedbackMaterials.length >= 1
+        ) {
+          this.$message.warning('只能上传一个文件,请先删除已上传的文件')
+          // 手动触发onError回调
+          if (onError) {
+            onError(new Error('只能上传一个文件'))
+          }
+          return
+        }
+
+        const formData = new FormData()
+        formData.append('file', file)
+
+        try {
+          // 显示上传进度
+          if (onProgress) {
+            onProgress({ percent: 0 })
+          }
+
+          // 调用上传API
+          const uploadRes = await uploadFile('/api/file/v1/upload', formData, {
+            onUploadProgress: (progressEvent) => {
+              // 计算上传进度
+              if (progressEvent.total) {
+                const percent = Math.round(
+                  (progressEvent.loaded * 100) / progressEvent.total
+                )
+                if (onProgress) {
+                  onProgress({ percent })
+                }
+              }
+            },
+          })
+
+          // 检查上传结果
+          if (uploadRes && uploadRes.code === 200 && uploadRes.value) {
+            const fileInfo = uploadRes.value
+            // 构造文件信息对象,符合element-ui upload组件的格式
+            const fileObj = {
+              uid: file.uid,
+              name: file.name,
+              status: 'success',
+              size: file.size,
+              response: fileInfo,
+              url: fileInfo.savePath || fileInfo.url, // 保存文件URL
+            }
+
+            if (onSuccess) {
+              onSuccess(fileInfo, fileObj)
+            }
+
+            this.$message.success(`${file.name} 上传成功`)
+          } else {
+            throw new Error(uploadRes?.message || '上传失败,请稍后重试')
+          }
+        } catch (error) {
+          console.error('文件上传失败:', error)
+          this.$message.error(`文件上传失败:${error.message || '未知错误'}`)
+          if (onError) {
+            onError(error)
+          }
+        }
+      },
+      // 上传成功回调
+      handleFileUploadSuccess(response, file, fileList) {
+        // 更新文件列表,添加文件URL信息
+        this.localFormData.feedbackMaterials = fileList.map((item) => {
+          if (item.uid === file.uid && response) {
+            return {
+              ...item,
+              url: response.savePath || response.url || item.url, // 保存文件URL
+              response: response,
+            }
+          }
+          return item
+        })
+      },
+      // 上传进度回调
+      handleFileUploadProgress(event, file, fileList) {
+        // 可以在这里显示上传进度,element-ui 会自动处理
+      },
+      // 上传失败回调
+      handleFileUploadError(err, file, fileList) {
+        console.error('文件上传错误:', err)
+        this.$message.error(`${file.name} 上传失败`)
+        // 从文件列表中移除失败的文件
+        this.localFormData.feedbackMaterials = fileList.filter(
+          (item) => item.uid !== file.uid
+        )
+      },
+      // 超出文件数量限制
+      handleFileExceed(files, fileList) {
+        this.$message.warning(
+          `当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${fileList.length} 个文件`
+        )
+      },
+      // 移除文件
+      handleFileRemove(file, fileList) {
+        this.localFormData.feedbackMaterials = fileList
+        this.$message.info(`${file.name} 已移除`)
+      },
     },
   }
 </script>
@@ -218,6 +489,12 @@
     background-color: #f9f9f9;
   }
 
+  .cost-opinion-section {
+    // 成本监审意见部分置灰样式
+    opacity: 0.7;
+    background-color: #f5f5f5;
+  }
+
   .opinion-item,
   .feedback-item {
     margin-bottom: 20px;

+ 113 - 14
src/views/EntDeclaration/auditTaskManagement/taskFillIn.vue

@@ -62,7 +62,11 @@
 
           <!-- 监审文书 -->
           <el-tab-pane
-            v-if="currentNode !== 'clcs' && currentNode !== 'jtsy'"
+            v-if="
+              currentNode !== 'clcs' &&
+              currentNode !== 'jtsy' &&
+              currentNode !== 'yjfk'
+            "
             label="监审文书"
             name="auditDocument"
           >
@@ -113,20 +117,27 @@
 
           <!-- 监审意见 -->
           <el-tab-pane
-            v-if="currentNode === 'jtsy'"
+            v-if="currentNode === 'yjfk'"
             label="监审意见"
             name="auditOpinion"
           >
             <audit-opinion-tab
+              :id="taskId"
+              ref="auditOpinionTabRef"
               :form-data="formData.auditOpinion"
               :is-view-mode="isViewMode"
               @change="handleAuditOpinionChange"
+              @data-loaded="handleAuditOpinionDataLoaded"
             />
           </el-tab-pane>
 
           <!-- 消息通知 -->
           <el-tab-pane
-            v-if="currentNode !== 'clcs' && currentNode !== 'jtsy'"
+            v-if="
+              currentNode !== 'clcs' &&
+              currentNode !== 'jtsy' &&
+              currentNode !== 'yjfk'
+            "
             label="消息通知"
             name="messageNotice"
           >
@@ -275,6 +286,7 @@
   import { getAllUserList } from '@/api/uc'
   import { dictMixin } from '@/mixins/useDict'
   import { uploadFile } from '@/api/file'
+  import { submitPreliminaryOpinion } from '@/api/audit/preliminaryOpinion'
   import ProjectInfoTab from './components/ProjectInfoTab.vue'
   import AuditDocumentTab from './components/AuditDocumentTab.vue'
   import DataRequirementsTab from './components/DataRequirementsTab.vue'
@@ -481,8 +493,8 @@
             costComposition:
               '此处为监审项目的成本构成、数据核增核减情况、依据及理由的内容...',
             preliminaryOpinion: '此处为成本审核初步意见的内容...',
-            enterpriseFeedback: '', // 被监审单位可输入反馈意见
-            fileList: [], // 被监审单位上传的反馈文件
+            feedbackOpinion: '', // 被监审单位可输入反馈意见
+            feedbackMaterials: [], // 被监审单位上传的反馈文件
           },
           messageNotice: [], // 消息通知列表
         },
@@ -881,6 +893,11 @@
       handleAuditOpinionChange(data) {
         this.formData.auditOpinion = { ...data }
       },
+      // 处理监审意见数据加载完成
+      handleAuditOpinionDataLoaded() {
+        // 数据加载完成后,关闭加载状态
+        this.tabLoading.auditOpinion = false
+      },
       // 表格行样式
       getRowClassName({ row }) {
         if (row.isCategoryHeader) {
@@ -901,8 +918,36 @@
         } else if (tab.name === 'messageNotice') {
           // 消息通知
           this.getSendMessage()
+        } else if (tab.name === 'auditOpinion') {
+          // 监审意见标签页,设置加载状态并调用接口
+          this.tabLoading.auditOpinion = true
+          // 通过 ref 调用子组件方法,确保接口被调用
+          this.$nextTick(() => {
+            this.$nextTick(() => {
+              const auditOpinionTab = this.$refs.auditOpinionTabRef
+              if (
+                auditOpinionTab &&
+                typeof auditOpinionTab.getPreliminaryOpinionData === 'function'
+              ) {
+                auditOpinionTab.getPreliminaryOpinionData()
+              } else {
+                // 如果找不到组件,可能是组件还未创建,延迟调用
+                setTimeout(() => {
+                  const tab = this.$refs.auditOpinionTabRef
+                  if (
+                    tab &&
+                    typeof tab.getPreliminaryOpinionData === 'function'
+                  ) {
+                    tab.getPreliminaryOpinionData()
+                  } else {
+                    this.tabLoading.auditOpinion = false
+                  }
+                }, 200)
+              }
+            })
+          })
         }
-        // 其他标签页(如 auditDocument、costSurvey、auditOpinion)如果不需要异步加载数据,则不需要设置加载状态
+        // 其他标签页(如 auditDocument、costSurvey)如果不需要异步加载数据,则不需要设置加载状态
 
         // 可添加标签页切换时的逻辑
         this.pageTitle = tab.label
@@ -1005,24 +1050,78 @@
       // 提交
       handleSubmit() {
         this.loading.submit = true
-        // 保存逻辑
-        setTimeout(() => {
-          submitTaskRequirement({ taskId: this.taskId })
+
+        // 判断是否为监审意见提交(currentNode === 'yjfk')
+        if (this.currentNode === 'yjfk') {
+          // 企业监审意见提交
+          // 处理文件列表,提取文件URL
+          // let fileUrlList = []
+          // if (this.formData.auditOpinion?.feedbackMaterials && Array.isArray(this.formData.auditOpinion.feedbackMaterials)) {
+          //   fileUrlList = this.formData.auditOpinion.feedbackMaterials.map((file) => {
+          //     // 如果file是字符串,直接返回
+          //     if (typeof file === 'string') {
+          //       return file
+          //     }
+          //     // 如果file是对象,提取url字段
+          //     return file.url || file.response?.savePath || file.savePath || ''
+          //   }).filter((url) => url) // 过滤空值
+          // }
+
+          const submitData = {
+            taskId: this.taskId,
+            // 成本监审意见数据
+            basicSituation: this.formData.auditOpinion?.basicSituation || '',
+            currentPriceStandard:
+              this.formData.auditOpinion?.currentPriceStandard || '',
+            costComposition: this.formData.auditOpinion?.costComposition || '',
+            preliminaryOpinion:
+              this.formData.auditOpinion?.preliminaryOpinion || '',
+            // 被监审单位反馈意见数据
+            feedbackOpinion: this.formData.auditOpinion?.feedbackOpinion || '',
+            feedbackMaterials:
+              this.formData.auditOpinion?.feedbackMaterials[0]?.url || '', // 附件URL
+          }
+
+          submitPreliminaryOpinion(submitData)
             .then((res) => {
               this.loading.submit = false
-              console.log('提交成功', res)
+              console.log('企业监审意见提交成功', res)
               if (res && res.code === 200) {
                 Message.success('提交成功')
+                // 关闭弹窗
                 this.handleBack()
+                // 通知父组件刷新列表
+                this.$emit('refresh')
               } else {
-                Message.error('提交失败')
+                Message.error(res?.message || '提交失败')
               }
             })
             .catch((err) => {
-              console.error('提交失败', err)
-              Message.error('提交失败')
+              this.loading.submit = false
+              console.error('企业监审意见提交失败', err)
+              Message.error('提交失败:' + (err.message || '未知错误'))
             })
-        }, 500)
+        } else {
+          // 其他情况走原有逻辑
+          setTimeout(() => {
+            submitTaskRequirement({ taskId: this.taskId })
+              .then((res) => {
+                this.loading.submit = false
+                console.log('提交成功', res)
+                if (res && res.code === 200) {
+                  Message.success('提交成功')
+                  this.handleBack()
+                } else {
+                  Message.error('提交失败')
+                }
+              })
+              .catch((err) => {
+                this.loading.submit = false
+                console.error('提交失败', err)
+                Message.error('提交失败')
+              })
+          }, 500)
+        }
       },
 
       // 保存

+ 16 - 0
src/views/EntDeclaration/auditTaskProcessing/index.vue

@@ -188,6 +188,16 @@
           >
             填报任务
           </el-button>
+          <el-button
+            v-if="
+              scope.row.currentNode === 'yjfk' && scope.row.status === '250'
+            "
+            size="mini"
+            type="text"
+            @click="handleEdit(scope.row)"
+          >
+            意见反馈
+          </el-button>
           <el-button size="mini" type="text" @click="handleMessage(scope.row)">
             查看
           </el-button>
@@ -223,6 +233,7 @@
       :task-info="currentTaskInfo"
       :view-mode="taskViewMode"
       @close="handleTaskFillInClose"
+      @refresh="handleTaskFillInRefresh"
     />
   </div>
 </template>
@@ -326,6 +337,11 @@
         this.taskFillInVisible = false
         this.currentTaskInfo = {}
       },
+      // 填报任务提交成功后刷新列表
+      handleTaskFillInRefresh() {
+        // 刷新列表数据
+        this.getTaskHandlingPage()
+      },
       // 查看
       handleMessage(row) {
         this.$refs.taskInfo.open(row)

+ 335 - 16
src/views/costAudit/auditInfo/auditManage/auditOpinion.vue

@@ -8,32 +8,40 @@
           class="ml10"
           type="primary"
           size="small"
+          :disabled="isPreliminaryDisabled"
           @click="handleSavePreliminaryOpinion"
         >
           保存
         </el-button>
       </div>
 
-      <el-form :model="preliminaryOpinionForm" label-width="180px">
+      <el-form
+        :model="preliminaryOpinionForm"
+        label-width="180px"
+        :class="{ 'disabled-section': isPreliminaryDisabled }"
+      >
         <el-form-item label="被审核单位基本情况及主要财务数据:">
           <el-input
-            v-model="preliminaryOpinionForm.basicInfo"
+            v-model="preliminaryOpinionForm.basicSituation"
             type="textarea"
             :rows="3"
+            :disabled="isPreliminaryDisabled"
           ></el-input>
         </el-form-item>
         <el-form-item label="成本费用核算情况及问题:">
           <el-input
-            v-model="preliminaryOpinionForm.costAccounting"
+            v-model="preliminaryOpinionForm.currentPriceStandard"
             type="textarea"
             :rows="3"
+            :disabled="isPreliminaryDisabled"
           ></el-input>
         </el-form-item>
         <el-form-item label="成本费用控制措施及建议:">
           <el-input
-            v-model="preliminaryOpinionForm.controlMeasures"
+            v-model="preliminaryOpinionForm.costComposition"
             type="textarea"
             :rows="3"
+            :disabled="isPreliminaryDisabled"
           ></el-input>
         </el-form-item>
         <el-form-item label="成本审核初步意见:">
@@ -41,6 +49,7 @@
             v-model="preliminaryOpinionForm.preliminaryOpinion"
             type="textarea"
             :rows="3"
+            :disabled="isPreliminaryDisabled"
           ></el-input>
         </el-form-item>
       </el-form>
@@ -62,11 +71,18 @@
           ></el-input>
         </el-form-item>
         <el-form-item label="被审核单位反馈附件:">
-          <el-input
-            v-model="feedbackForm.feedbackAttachment"
-            type="text"
-            disabled
-          ></el-input>
+          <div v-if="feedbackForm.feedbackMaterial" class="file-item">
+            <i class="el-icon-document"></i>
+            <span
+              class="file-link"
+              @click="handleFileView(feedbackForm.feedbackMaterial)"
+            >
+              {{ getFileName(feedbackForm.feedbackMaterial) }}
+            </span>
+          </div>
+          <div v-else class="no-file">
+            <span style="color: #909399">暂无附件</span>
+          </div>
         </el-form-item>
       </el-form>
     </div>
@@ -79,6 +95,7 @@
           class="ml10"
           type="primary"
           size="small"
+          :disabled="!isConclusionEditable"
           @click="handleSaveConclusionOpinion"
         >
           保存
@@ -91,6 +108,7 @@
             v-model="conclusionOpinionForm.conclusionOpinion"
             type="textarea"
             :rows="3"
+            :disabled="!isConclusionEditable"
           ></el-input>
         </el-form-item>
         <el-form-item label="整改要求及时间:">
@@ -98,6 +116,7 @@
             v-model="conclusionOpinionForm.rectificationRequirements"
             type="textarea"
             :rows="3"
+            :disabled="!isConclusionEditable"
           ></el-input>
         </el-form-item>
         <el-form-item label="备注:">
@@ -105,6 +124,7 @@
             v-model="conclusionOpinionForm.remark"
             type="textarea"
             :rows="3"
+            :disabled="!isConclusionEditable"
           ></el-input>
         </el-form-item>
       </el-form>
@@ -113,38 +133,297 @@
 </template>
 
 <script>
+  import {
+    getPreliminaryOpinion,
+    addPreliminaryOpinion,
+  } from '@/api/audit/preliminaryOpinion'
   export default {
     name: 'AuditOpinion',
+    props: {
+      id: {
+        type: [String, Number],
+        default: null,
+      },
+      currentNode: {
+        type: String,
+        default: '',
+      },
+      status: {
+        type: String,
+        default: '',
+      },
+    },
     data() {
       return {
         // 审核意见表单数据
         preliminaryOpinionForm: {
-          basicInfo: '',
-          costAccounting: '',
-          controlMeasures: '',
+          id: '',
+          basicSituation: '',
+          currentPriceStandard: '',
+          costComposition: '',
           preliminaryOpinion: '',
         },
 
         feedbackForm: {
-          feedbackOpinion: '我单位对审计初步意见无异议,将按照要求进行整改。',
-          feedbackAttachment: '反馈意见回复.docx',
+          feedbackOpinion: '',
+          feedbackMaterial: '', // 反馈附件(单个URL字符串)
         },
 
         conclusionOpinionForm: {
+          id: '',
           conclusionOpinion: '',
           rectificationRequirements: '',
           remark: '',
         },
       }
     },
+    computed: {
+      // 判断初步意见是否需要置灰(currentNode === 'yjfk' && status === '已反馈')
+      isPreliminaryDisabled() {
+        return this.currentNode === 'yjfk' && this.status === '已反馈'
+      },
+      // 判断结论意见是否可编辑(currentNode === 'yjfk' && status === '已反馈')
+      isConclusionEditable() {
+        return this.currentNode === 'yjfk' && this.status === '已反馈'
+      },
+    },
+    watch: {
+      id(newVal) {
+        // 当 id 变化时,重新获取数据
+        if (newVal) {
+          this.getPreliminaryOpinionData()
+        }
+      },
+    },
+    mounted() {
+      // 组件挂载时,如果有 id,获取数据
+      if (this.id) {
+        this.getPreliminaryOpinionData()
+      }
+    },
     methods: {
+      // 获取初步审核意见数据
+      async getPreliminaryOpinionData() {
+        if (!this.id) {
+          return
+        }
+        try {
+          const res = await getPreliminaryOpinion({ taskId: this.id })
+          if (res && res.value) {
+            const data = res.value
+            // 回显初步审核意见数据
+            if (data.id) {
+              this.preliminaryOpinionForm = {
+                id: data.id,
+                basicSituation: data.basicSituation || '',
+                currentPriceStandard: data.currentPriceStandard || '',
+                costComposition: data.costComposition || '',
+                preliminaryOpinion: data.preliminaryOpinion || '',
+              }
+            }
+            // 回显结论意见数据(如果接口返回了结论意见)
+            if (
+              data.conclusionOpinion !== undefined ||
+              data.rectificationRequirements !== undefined ||
+              data.remark !== undefined
+            ) {
+              this.conclusionOpinionForm = {
+                id: data.conclusionId || data.id || '',
+                conclusionOpinion: data.conclusionOpinion || '',
+                rectificationRequirements: data.rectificationRequirements || '',
+                remark: data.remark || '',
+              }
+            }
+            // 回显反馈意见数据(如果接口返回了反馈意见)
+            if (
+              data.feedbackOpinion !== undefined ||
+              data.feedbackMaterials !== undefined
+            ) {
+              // 处理单个附件(可能是字符串或数组的第一个元素)
+              let feedbackMaterial = ''
+              if (data.feedbackMaterials) {
+                if (typeof data.feedbackMaterials === 'string') {
+                  // 如果是字符串,直接使用
+                  feedbackMaterial = data.feedbackMaterials
+                } else if (
+                  Array.isArray(data.feedbackMaterials) &&
+                  data.feedbackMaterials.length > 0
+                ) {
+                  // 如果是数组,取第一个元素
+                  const firstItem = data.feedbackMaterials[0]
+                  if (typeof firstItem === 'string') {
+                    feedbackMaterial = firstItem
+                  } else if (firstItem && firstItem.url) {
+                    feedbackMaterial = firstItem.url
+                  } else if (
+                    firstItem &&
+                    firstItem.response &&
+                    firstItem.response.savePath
+                  ) {
+                    feedbackMaterial = firstItem.response.savePath
+                  }
+                }
+              }
+
+              this.feedbackForm = {
+                feedbackOpinion: data.feedbackOpinion || '',
+                feedbackMaterial: feedbackMaterial,
+              }
+            }
+          }
+        } catch (error) {
+          console.error('获取初步审核意见失败:', error)
+          // 获取失败不影响页面显示,静默处理
+        }
+      },
       // 成本审核意见操作
       handleSavePreliminaryOpinion() {
-        this.$message({ type: 'success', message: '初步审核意见已保存' })
+        if (!this.id) {
+          this.$message.error('缺少任务ID')
+          return
+        }
+
+        // 判断是新增还是编辑(根据是否有 id)
+        const formData = {
+          basicSituation: this.preliminaryOpinionForm.basicSituation || '',
+          currentPriceStandard:
+            this.preliminaryOpinionForm.currentPriceStandard || '',
+          costComposition: this.preliminaryOpinionForm.costComposition || '',
+          preliminaryOpinion:
+            this.preliminaryOpinionForm.preliminaryOpinion || '',
+          taskId: this.id,
+        }
+
+        // 如果有 id,则是编辑,需要添加 id 字段
+        if (this.preliminaryOpinionForm.id) {
+          formData.id = this.preliminaryOpinionForm.id
+        }
+
+        addPreliminaryOpinion(formData)
+          .then((res) => {
+            if (res.code === 200) {
+              this.$message({ type: 'success', message: '初步审核意见已保存' })
+              // 保存成功后,如果有返回 id,更新表单中的 id
+              if (res.value && res.value.id) {
+                this.preliminaryOpinionForm.id = res.value.id
+              }
+              // 通知父组件刷新列表
+              this.$emit('refresh')
+            } else {
+              this.$message({ type: 'error', message: res.message })
+            }
+          })
+          .catch((error) => {
+            this.$message({ type: 'error', message: '保存失败,请稍后重试' })
+            console.error('保存初步审核意见失败:', error)
+          })
       },
 
       handleSaveConclusionOpinion() {
-        this.$message({ type: 'success', message: '审核结论意见已保存' })
+        if (!this.id) {
+          this.$message.error('缺少任务ID')
+          return
+        }
+
+        // 判断是新增还是编辑(根据是否有 id)
+        // 传递所有字段(包括初步意见、结论意见和反馈意见)
+        const formData = {
+          // 成本监审意见数据(初步意见)
+          basicSituation: this.preliminaryOpinionForm.basicSituation || '',
+          currentPriceStandard:
+            this.preliminaryOpinionForm.currentPriceStandard || '',
+          costComposition: this.preliminaryOpinionForm.costComposition || '',
+          preliminaryOpinion:
+            this.preliminaryOpinionForm.preliminaryOpinion || '',
+          // 成本审核结论意见数据
+          conclusionOpinion: this.conclusionOpinionForm.conclusionOpinion || '',
+          rectificationRequirements:
+            this.conclusionOpinionForm.rectificationRequirements || '',
+          remark: this.conclusionOpinionForm.remark || '',
+          // 监审单位反馈意见数据
+          feedbackOpinion: this.feedbackForm.feedbackOpinion || '',
+          feedbackMaterials: this.feedbackForm.feedbackMaterial || '', // 反馈附件(单个URL字符串)
+          taskId: this.id,
+        }
+
+        // 如果有初步意见的 id,则添加 id 字段(用于编辑)
+        if (this.preliminaryOpinionForm.id) {
+          formData.id = this.preliminaryOpinionForm.id
+        }
+        // 如果有结论意见的 id,也添加(可能是 conclusionId)
+        if (
+          this.conclusionOpinionForm.id &&
+          this.conclusionOpinionForm.id !== this.preliminaryOpinionForm.id
+        ) {
+          formData.conclusionId = this.conclusionOpinionForm.id
+        }
+
+        addPreliminaryOpinion(formData)
+          .then((res) => {
+            if (res.code === 200) {
+              this.$message({ type: 'success', message: '审核结论意见已保存' })
+              // 保存成功后,如果有返回 id,更新表单中的 id
+              if (res.value && res.value.id) {
+                this.preliminaryOpinionForm.id = res.value.id
+              }
+              if (res.value && res.value.conclusionId) {
+                this.conclusionOpinionForm.id = res.value.conclusionId
+              }
+              // 通知父组件刷新列表并关闭弹窗
+              this.$emit('refresh')
+              this.$emit('close')
+            } else {
+              this.$message({ type: 'error', message: res.message })
+            }
+          })
+          .catch((error) => {
+            this.$message({ type: 'error', message: '保存失败,请稍后重试' })
+            console.error('保存审核结论意见失败:', error)
+          })
+      },
+      // 从URL获取文件名
+      getFileNameFromUrl(url) {
+        if (!url) return '未知文件'
+        try {
+          const urlParts = url.split('/')
+          const fileName = urlParts[urlParts.length - 1]
+          // 如果有查询参数,去除
+          return fileName.split('?')[0] || '未知文件'
+        } catch (e) {
+          return '未知文件'
+        }
+      },
+      // 获取文件名(支持对象和字符串)
+      getFileName(file) {
+        if (typeof file === 'string') {
+          return this.getFileNameFromUrl(file)
+        }
+        if (file && file.name) {
+          return file.name
+        }
+        if (file && file.url) {
+          return this.getFileNameFromUrl(file.url)
+        }
+        return '未知文件'
+      },
+      // 查看文件
+      handleFileView(file) {
+        let fileUrl = ''
+        if (typeof file === 'string') {
+          fileUrl = file
+        } else if (file && file.url) {
+          fileUrl = file.url
+        } else if (file && file.response) {
+          fileUrl = file.response.savePath || file.response.url || ''
+        } else if (file && file.savePath) {
+          fileUrl = file.savePath
+        }
+
+        if (fileUrl) {
+          window.open(fileUrl, '_blank')
+        } else {
+          this.$message.warning('文件地址无效')
+        }
       },
     },
   }
@@ -164,6 +443,11 @@
     margin-top: 20px;
   }
 
+  .disabled-section {
+    opacity: 0.7;
+    background-color: #f5f5f5;
+  }
+
   .opinion-header {
     display: flex;
     align-items: center;
@@ -183,4 +467,39 @@
   .ml10 {
     margin-left: 10px;
   }
+
+  .file-item {
+    display: flex;
+    align-items: center;
+    padding: 8px 12px;
+    background-color: #f5f7fa;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: background-color 0.3s;
+  }
+
+  .file-item:hover {
+    background-color: #e4e7ed;
+  }
+
+  .file-item i {
+    margin-right: 8px;
+    color: #409eff;
+    font-size: 16px;
+  }
+
+  .file-link {
+    color: #409eff;
+    text-decoration: none;
+    cursor: pointer;
+  }
+
+  .file-link:hover {
+    text-decoration: underline;
+  }
+
+  .no-file {
+    color: #909399;
+    padding: 8px 0;
+  }
 </style>

+ 26 - 3
src/views/costAudit/auditInfo/auditManage/details.vue

@@ -35,7 +35,13 @@
           <extract-material :id="id" />
         </el-tab-pane>
         <el-tab-pane label="成本审核意见" name="auditOpinion">
-          <audit-opinion :id="id" />
+          <audit-opinion
+            :id="id"
+            :current-node="currentNode"
+            :status="currentStatus"
+            @refresh="handleAuditOpinionRefresh"
+            @close="handleClose"
+          />
         </el-tab-pane>
       </el-tabs>
     </el-drawer>
@@ -97,6 +103,11 @@
           return '审核详情'
         } else if (this.currentNode === 'clcs') {
           return '成本调查详情'
+        } else if (
+          this.currentNode === 'yjgaozhi' ||
+          this.currentNode === 'yjfk'
+        ) {
+          return '意见告知'
         }
         return '成本审核详情'
       },
@@ -109,7 +120,7 @@
           this.$nextTick(() => {
             // 设置标签页
             this.setActiveTab()
-            // 获取资料初审按钮数据
+            // 弹窗打开时,无论什么情况都要获取资料初审按钮数据
             this.getPreliminaryReviewButton()
           })
         }
@@ -151,9 +162,10 @@
     mounted() {
       // 设置标签页
       this.setActiveTab()
-      // 如果组件挂载时弹窗已打开且有id,也调用一次
+      // 如果组件挂载时弹窗已打开且有id,也要获取按钮数据
       if (this.visible && this.id) {
         this.$nextTick(() => {
+          // 弹窗打开时,无论什么情况都要获取资料初审按钮数据
           this.getPreliminaryReviewButton()
         })
       }
@@ -170,6 +182,12 @@
         } else if (this.currentNode === 'clcs') {
           // 如果 currentNode 是 'clcs',显示成本调查表标签页
           this.activeTab = 'costSurvey'
+        } else if (
+          this.currentNode === 'yjgaozhi' ||
+          this.currentNode === 'yjfk'
+        ) {
+          // 如果 currentNode 是 'yjgaozhi',显示意见告知标签页(成本审核意见)
+          this.activeTab = 'auditOpinion'
         } else {
           // 其他情况默认显示报送资料标签页
           this.activeTab = 'submitData'
@@ -202,6 +220,11 @@
         this.$emit('update:visible', false)
         this.$emit('close')
       },
+      // 处理审核意见保存成功后的刷新
+      handleAuditOpinionRefresh() {
+        // 触发父组件刷新列表
+        this.$emit('refresh')
+      },
       open() {
         // 打开弹窗方法,供父组件通过ref调用
         this.$emit('update:visible', true)

+ 20 - 0
src/views/costAudit/auditInfo/auditManage/index.vue

@@ -123,6 +123,26 @@
               >
                 成本审核
               </el-button>
+              <el-button
+                v-if="
+                  scope.row.currentNode === 'yjgaozhi' &&
+                  scope.row.status === '审核中'
+                "
+                type="text"
+                @click="handleOpenDetails(scope.row)"
+              >
+                意见告知
+              </el-button>
+              <el-button
+                v-if="
+                  scope.row.currentNode === 'yjfk' &&
+                  scope.row.status === '已反馈'
+                "
+                type="text"
+                @click="handleOpenDetails(scope.row)"
+              >
+                审核
+              </el-button>
             </span>
           </template>
         </el-table-column>