Prechádzať zdrojové kódy

Merge remote-tracking branch 'new/master'

zzw 1 týždeň pred
rodič
commit
4c0631853c

+ 23 - 4
src/components/task/cbjsInfo.vue

@@ -139,19 +139,38 @@
     computed: {
       dialogTitle() {
         // 根据节点类型设置标题
+        // if (
+        //   this.currentNode === 'sdshenhe' &&
+        //   this.currentStatus === '审核中'
+        // ) {
+        //   return '审核详情'
+        // } else if (this.currentNode === 'clcs') {
+        //   return '成本调查详情'
+        // } else if (
+        //   this.currentNode === 'yjgaozhi' ||
+        //   this.currentNode === 'yjfk'
+        // ) {
+        //   return '意见告知'
+        // }
+        // return '成本审核详情'
         if (
           this.currentNode === 'sdshenhe' &&
           this.currentStatus === '审核中'
         ) {
-          return '审核详情'
-        } else if (this.currentNode === 'clcs') {
-          return '成本调查详情'
+          return '审核'
         } else if (
           this.currentNode === 'yjgaozhi' ||
-          this.currentNode === 'yjfk'
+          this.currentNode === 'yjgz'
         ) {
           return '意见告知'
+        } else if (this.currentNode === 'clcs') {
+          return '资料初审'
+        } else if (this.currentNode === 'sdsh') {
+          return '成本审核'
+        } else if (this.currentNode === 'yjfk') {
+          return '意见反馈'
         }
+        // 兜底:必须返回一个字符串,避免 eslint 报错
         return '成本审核详情'
       },
       // 从 selectedProject 中派生 auditedUnitId / catalogId,供子组件查询接口使用

+ 23 - 4
src/components/task/cbjsInfoo.vue

@@ -142,19 +142,38 @@
     computed: {
       dialogTitle() {
         // 根据节点类型设置标题
+        // if (
+        //   this.currentNode === 'sdshenhe' &&
+        //   this.currentStatus === '审核中'
+        // ) {
+        //   return '审核详情'
+        // } else if (this.currentNode === 'clcs') {
+        //   return '成本调查详情'
+        // } else if (
+        //   this.currentNode === 'yjgaozhi' ||
+        //   this.currentNode === 'yjfk'
+        // ) {
+        //   return '意见告知'
+        // }
+        // return '成本审核详情'
         if (
           this.currentNode === 'sdshenhe' &&
           this.currentStatus === '审核中'
         ) {
-          return '审核详情'
-        } else if (this.currentNode === 'clcs') {
-          return '成本调查详情'
+          return '审核'
         } else if (
           this.currentNode === 'yjgaozhi' ||
-          this.currentNode === 'yjfk'
+          this.currentNode === 'yjgz'
         ) {
           return '意见告知'
+        } else if (this.currentNode === 'clcs') {
+          return '资料初审'
+        } else if (this.currentNode === 'sdsh') {
+          return '成本审核'
+        } else if (this.currentNode === 'yjfk') {
+          return '意见反馈'
         }
+        // 兜底:必须返回一个字符串,避免 eslint 报错
         return '成本审核详情'
       },
       // 从 selectedProject 中派生 auditedUnitId / catalogId,供子组件查询接口使用

+ 20 - 6
src/components/task/components/auditOpinion.vue

@@ -410,7 +410,6 @@
         }
         return '未知文件'
       },
-      // 查看文件
       handleFileView(file) {
         let fileUrl = ''
         if (typeof file === 'string') {
@@ -422,12 +421,27 @@
         } else if (file && file.savePath) {
           fileUrl = file.savePath
         }
-
-        if (fileUrl) {
-          window.open(fileUrl, '_blank')
-        } else {
-          this.$message.warning('文件地址无效')
+        const normalized = this.normalizeUrl(fileUrl)
+        if (!normalized) {
+          this.$message.error('暂无文件!')
+          return
         }
+
+        // 对文件URL进行Base64编码
+        const encodedUrl = encodeURIComponent(Base64.encode(normalized))
+
+        // 构建 kkFileView 预览URL
+        // onlinePreview - 在线预览
+        // onlinePreview?type=pdf - 强制使用PDF模式预览
+        window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
+      },
+      normalizeUrl(u) {
+        if (!u) return ''
+        if (/^https?:\/\//i.test(u)) return u
+        const base = (window.context && window.context.form) || ''
+        if (!base) return u
+        if (u.startsWith('/')) return base + u
+        return base.replace(/\/$/, '') + '/' + u
       },
     },
   }

+ 170 - 23
src/components/task/taskInfo.vue

@@ -735,7 +735,7 @@
                 <div class="opinion-item">
                   <label>被监审单位基本情况及主要财务状况</label>
                   <el-input
-                    v-model="formData.auditOpinion.basicFinancialInfo"
+                    v-model="formData.auditOpinion.basicSituation"
                     type="textarea"
                     rows="5"
                     disabled
@@ -744,7 +744,7 @@
                 <div class="opinion-item">
                   <label>监审项目现行执行的价格标准</label>
                   <el-input
-                    v-model="formData.auditOpinion.priceStandard"
+                    v-model="formData.auditOpinion.currentPriceStandard"
                     type="textarea"
                     rows="5"
                     disabled
@@ -777,7 +777,7 @@
                 <div class="feedback-item">
                   <label>被监审单位反馈意见</label>
                   <el-input
-                    v-model="formData.auditOpinion.enterpriseFeedback"
+                    v-model="formData.auditOpinion.feedbackOpinion"
                     type="textarea"
                     rows="5"
                     disabled
@@ -787,25 +787,67 @@
                   <label>被监审单位反馈资料</label>
                   <div class="file-list-display">
                     <div
-                      v-for="(file, index) in formData.auditOpinion.fileList"
-                      :key="index"
+                      v-if="formData.auditOpinion.feedbackMaterial"
                       class="file-item"
                     >
                       <i class="iconfont-5039297 icon-wenjian"></i>
-                      <span>{{ file.name }}</span>
+                      <span
+                        class="file-link"
+                        @click="
+                          handleFilePreview(
+                            formData.auditOpinion.feedbackMaterial
+                          )
+                        "
+                      >
+                        {{
+                          getFeedbackDisplayName(
+                            formData.auditOpinion.feedbackMaterial
+                          )
+                        }}
+                      </span>
                     </div>
-                    <div
-                      v-if="
-                        !formData.auditOpinion.fileList ||
-                        formData.auditOpinion.fileList.length === 0
-                      "
-                      style="color: #909399"
-                    >
-                      暂无文件
+                    <div v-else class="no-file">
+                      <span style="color: #909399">暂无附件</span>
                     </div>
                   </div>
                 </div>
               </div>
+
+              <div
+                v-if="formData.auditOpinion.conclusionOpinion"
+                class="cost-opinion-section"
+              >
+                <h3>成本审核结论意见</h3>
+                <div class="opinion-item">
+                  <label>成本审核结论意见</label>
+                  <el-input
+                    v-model="formData.auditOpinion.conclusionOpinion"
+                    type="textarea"
+                    rows="5"
+                    disabled
+                  />
+                </div>
+                <div class="opinion-item">
+                  <label>其他需要说明的事项</label>
+                  <el-input
+                    v-model="formData.auditOpinion.otherExplanations"
+                    type="textarea"
+                    rows="5"
+                    disabled
+                  />
+                </div>
+                <div class="opinion-item">
+                  <label>备注</label>
+                  <el-input
+                    v-model="formData.auditOpinion.remark"
+                    type="textarea"
+                    :rows="3"
+                    maxlength="500"
+                    show-word-limit
+                    disabled
+                  ></el-input>
+                </div>
+              </div>
             </div>
           </div>
         </el-tab-pane>
@@ -1050,6 +1092,7 @@
     downloadTemplate,
   } from '@/api/audit/survey'
   import { getListBySurveyTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
+  import { getPreliminaryOpinion } from '@/api/audit/preliminaryOpinion'
   export default {
     name: 'TaskInfo',
     components: {
@@ -1150,11 +1193,11 @@
           dataRequirements: [],
           costSurveyData: [],
           auditOpinion: {
-            basicFinancialInfo: '',
-            priceStandard: '',
+            basicSituation: '',
+            currentPriceStandard: '',
             costComposition: '',
             preliminaryOpinion: '',
-            enterpriseFeedback: '',
+            feedbackOpinion: '',
             fileList: [],
           },
           messageNotice: [],
@@ -1288,6 +1331,31 @@
       this.initData()
     },
     methods: {
+      handleFilePreview(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
+        }
+        const normalized = this.normalizeUrl(fileUrl)
+        if (!normalized) {
+          this.$message.error('暂无文件!')
+          return
+        }
+
+        // 对文件URL进行Base64编码
+        const encodedUrl = encodeURIComponent(Base64.encode(normalized))
+
+        // 构建 kkFileView 预览URL
+        // onlinePreview - 在线预览
+        // onlinePreview?type=pdf - 强制使用PDF模式预览
+        window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
+      },
       // 从URL/路径解析文件名(支持逗号分隔的多附件)
       getFileNameFromUrl(url) {
         if (!url) return ''
@@ -2526,13 +2594,57 @@
         try {
           this.loading = true
           // TODO: 调用监审意见接口
-          // const res = await getAuditOpinion(this.currentTaskInfo.taskId)
-          // if (res && res.code === 200) {
-          //   this.formData.auditOpinion = res.value || {}
-          // }
+          const res = await getPreliminaryOpinion({
+            taskId: this.currentTaskInfo.userTask.id,
+          })
+          let feedbackMaterial = ''
+          if (res && res.value) {
+            const data = res.value
+            // 回显反馈意见数据(如果接口返回了反馈意见)
+            if (
+              data.feedbackOpinion !== undefined ||
+              data.feedbackMaterials !== undefined
+            ) {
+              // 处理单个附件(可能是字符串或数组的第一个元素)
+              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
+                  }
+                }
+              }
+              feedbackMaterial = feedbackMaterial
+            }
 
-          // 暂时使用默认数据
-          this.tabLoadedStatus.auditOpinion = true
+            // 回显初步审核意见数据
+            this.formData.auditOpinion = {
+              basicSituation: data.basicSituation || '',
+              currentPriceStandard: data.currentPriceStandard || '',
+              costComposition: data.costComposition || '',
+              preliminaryOpinion: data.preliminaryOpinion || '',
+              feedbackOpinion: data.feedbackOpinion || '',
+              feedbackMaterial: feedbackMaterial,
+              conclusionOpinion: data.conclusionOpinion || '',
+              otherExplanations: data.otherExplanations || '',
+              remark: data.remark || '',
+            }
+          }
         } catch (error) {
           console.error('加载监审意见失败:', error)
           // this.$message.error('加载监审意见失败')
@@ -2800,4 +2912,39 @@
       width: 45%;
     }
   }
+
+  .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>

+ 139 - 2
src/views/EntDeclaration/auditTaskManagement/components/AuditOpinionTab.vue

@@ -73,6 +73,15 @@
           >
             <i class="iconfont-5039297 icon-wenjian"></i>
             <span>{{ file.name || file }}</span>
+            <el-button
+              v-if="file.url"
+              type="text"
+              size="small"
+              style="margin-left: 10px; color: #409eff"
+              @click="handleFilePreview(file)"
+            >
+              预览
+            </el-button>
           </div>
           <div
             v-if="
@@ -112,6 +121,28 @@
                 上传附件
               </el-button>
             </el-tooltip>
+            <template #file="{ file }">
+              <div class="el-upload-list__item">
+                <el-icon class="el-icon-document"></el-icon>
+                <span class="el-upload-list__item-name">{{ file.name }}</span>
+                <div class="el-upload-list__item-actions">
+                  <span
+                    class="el-upload-list__item-action"
+                    @click="handleFilePreview(file)"
+                  >
+                    <el-icon class="el-icon-view"></el-icon>
+                    <span>预览</span>
+                  </span>
+                  <span
+                    class="el-upload-list__item-action"
+                    @click="handleFileRemove(file)"
+                  >
+                    <el-icon class="el-icon-delete"></el-icon>
+                    <span>删除</span>
+                  </span>
+                </div>
+              </div>
+            </template>
           </el-upload>
         </div>
       </div>
@@ -196,6 +227,11 @@
           remarks: '',
         },
         isInternalUpdate: false, // 标记是否是内部更新(避免watch循环触发)
+        // 文件预览相关
+        previewDialogVisible: false,
+        previewFileName: '',
+        previewFileUrl: '',
+        previewFileType: '',
       }
     },
     watch: {
@@ -472,10 +508,57 @@
         )
       },
       // 移除文件
-      handleFileRemove(file, fileList) {
-        this.localFormData.feedbackMaterials = fileList
+      handleFileRemove(file) {
+        // 过滤掉要删除的文件
+        this.localFormData.feedbackMaterials =
+          this.localFormData.feedbackMaterials.filter(
+            (item) => item.uid !== file.uid
+          )
         this.$message.info(`${file.name} 已移除`)
       },
+      normalizeFileUrl(fileUrl) {
+        if (!fileUrl) return ''
+        const url = String(fileUrl).trim()
+        if (!url) return ''
+
+        // 已经是绝对地址
+        if (/^https?:\/\//i.test(url)) return url
+
+        // 相对地址:确保以 / 开头,避免拼接成 ...formapi/xxx 这种
+        const normalizedPath = url.startsWith('/') ? url : `/${url}`
+        return (window?.context?.form || '') + normalizedPath
+      },
+      // 文件预览
+      handleFilePreview(file) {
+        const normalized = this.normalizeFileUrl(file.url)
+        if (!normalized) {
+          this.$message.error('暂无文件!')
+          return
+        }
+
+        // 对文件URL进行Base64编码
+        const encodedUrl = encodeURIComponent(Base64.encode(normalized))
+
+        // 构建 kkFileView 预览URL
+        // onlinePreview - 在线预览
+        // onlinePreview?type=pdf - 强制使用PDF模式预览
+        window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
+      },
+      // 文件下载
+      handleFileDownload() {
+        if (!this.previewFileUrl) {
+          this.$message.warning('文件链接不存在,无法下载')
+          return
+        }
+
+        // 创建下载链接
+        const link = document.createElement('a')
+        link.href = this.previewFileUrl
+        link.download = this.previewFileName
+        document.body.appendChild(link)
+        link.click()
+        document.body.removeChild(link)
+      },
     },
   }
 </script>
@@ -555,4 +638,58 @@
   .upload-demo .el-button {
     float: right;
   }
+
+  /* 文件列表样式 */
+  .el-upload-list__item {
+    display: flex;
+    align-items: center;
+    padding: 10px;
+    border: 1px solid #e9e9e9;
+    border-radius: 4px;
+    margin-bottom: 10px;
+    background-color: #f9f9f9;
+  }
+
+  .el-upload-list__item-name {
+    flex: 1;
+    margin-left: 10px;
+    color: #606266;
+  }
+
+  .el-upload-list__item-actions {
+    display: flex;
+    gap: 15px;
+  }
+
+  .el-upload-list__item-action {
+    color: #409eff;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 5px;
+  }
+
+  .el-upload-list__item-action:hover {
+    color: #66b1ff;
+  }
+
+  /* 预览容器样式 */
+  .preview-container {
+    padding: 10px;
+  }
+
+  .pdf-preview,
+  .image-preview {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .other-preview {
+    min-height: 300px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
 </style>

+ 200 - 104
src/views/costAudit/auditInfo/auditManage/auditOpinion.vue

@@ -16,11 +16,16 @@
       </div>
 
       <el-form
+        ref="preliminaryForm"
         :model="preliminaryOpinionForm"
+        :rules="preliminaryRules"
         label-width="180px"
         :class="{ 'disabled-section': isPreliminaryDisabled }"
       >
-        <el-form-item label="被监审单位基本情况及主要财务状况:">
+        <el-form-item
+          label="被监审单位基本情况及主要财务状况:"
+          prop="basicSituation"
+        >
           <el-input
             v-model="preliminaryOpinionForm.basicSituation"
             type="textarea"
@@ -30,7 +35,10 @@
             :disabled="isPreliminaryDisabled"
           ></el-input>
         </el-form-item>
-        <el-form-item label="监审项目现行执行的价格标准:">
+        <el-form-item
+          label="监审项目现行执行的价格标准:"
+          prop="currentPriceStandard"
+        >
           <el-input
             v-model="preliminaryOpinionForm.currentPriceStandard"
             type="textarea"
@@ -42,6 +50,7 @@
         </el-form-item>
         <el-form-item
           label="监审项目的成本构成、数据核增核减情况、依据及理由:"
+          prop="costComposition"
         >
           <el-input
             v-model="preliminaryOpinionForm.costComposition"
@@ -52,7 +61,7 @@
             :disabled="isPreliminaryDisabled"
           ></el-input>
         </el-form-item>
-        <el-form-item label="成本审核初步意见:">
+        <el-form-item label="成本审核初步意见:" prop="preliminaryOpinion">
           <el-input
             v-model="preliminaryOpinionForm.preliminaryOpinion"
             type="textarea"
@@ -87,7 +96,7 @@
             <i class="iconfont-5039297 icon-wenjian"></i>
             <span
               class="file-link"
-              @click="handleFileView(feedbackForm.feedbackMaterial)"
+              @click="handleFilePreview(feedbackForm.feedbackMaterial)"
             >
               {{ getFileName(feedbackForm.feedbackMaterial) }}
             </span>
@@ -114,8 +123,13 @@
         </el-button>
       </div>
 
-      <el-form :model="conclusionOpinionForm" label-width="180px">
-        <el-form-item label="成本审核结论意见:">
+      <el-form
+        ref="conclusionForm"
+        :model="conclusionOpinionForm"
+        :rules="conclusionRules"
+        label-width="180px"
+      >
+        <el-form-item label="成本审核结论意见:" prop="conclusionOpinion">
           <el-input
             v-model="conclusionOpinionForm.conclusionOpinion"
             type="textarea"
@@ -125,7 +139,7 @@
             :disabled="!isConclusionEditable"
           ></el-input>
         </el-form-item>
-        <el-form-item label="其他需要说明的事项:">
+        <el-form-item label="其他需要说明的事项:" prop="otherExplanations">
           <el-input
             v-model="conclusionOpinionForm.otherExplanations"
             type="textarea"
@@ -135,7 +149,7 @@
             :disabled="!isConclusionEditable"
           ></el-input>
         </el-form-item>
-        <el-form-item label="备注:">
+        <el-form-item label="备注:" prop="remark">
           <el-input
             v-model="conclusionOpinionForm.remark"
             type="textarea"
@@ -181,6 +195,41 @@
           preliminaryOpinion: '',
         },
 
+        preliminaryRules: {
+          basicSituation: [
+            {
+              required: true,
+              message: '请输入被监审单位基本情况及主要财务状况',
+              trigger: 'blur',
+            },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+          currentPriceStandard: [
+            {
+              required: true,
+              message: '请输入监审项目现行执行的价格标准',
+              trigger: 'blur',
+            },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+          costComposition: [
+            {
+              required: true,
+              message: '请输入监审项目的成本构成、数据核增核减情况、依据及理由',
+              trigger: 'blur',
+            },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+          preliminaryOpinion: [
+            {
+              required: true,
+              message: '请输入成本审核初步意见',
+              trigger: 'blur',
+            },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+        },
+
         feedbackForm: {
           feedbackOpinion: '',
           feedbackMaterial: '', // 反馈附件(单个URL字符串)
@@ -192,6 +241,29 @@
           otherExplanations: '',
           remark: '',
         },
+
+        conclusionRules: {
+          conclusionOpinion: [
+            {
+              required: true,
+              message: '请输入成本审核结论意见',
+              trigger: 'blur',
+            },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+          otherExplanations: [
+            {
+              required: true,
+              message: '请输入其他需要说明的事项',
+              trigger: 'blur',
+            },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+          remark: [
+            { required: true, message: '请输入备注', trigger: 'blur' },
+            { max: 500, message: '长度不能超过 500 个字符', trigger: 'blur' },
+          ],
+        },
       }
     },
     computed: {
@@ -301,41 +373,54 @@
           return
         }
 
-        // 判断是新增还是编辑(根据是否有 id)
-        const formData = {
-          basicSituation: this.preliminaryOpinionForm.basicSituation || '',
-          currentPriceStandard:
-            this.preliminaryOpinionForm.currentPriceStandard || '',
-          costComposition: this.preliminaryOpinionForm.costComposition || '',
-          preliminaryOpinion:
-            this.preliminaryOpinionForm.preliminaryOpinion || '',
-          taskId: this.id,
+        const form = this.$refs.preliminaryForm
+        if (!form || typeof form.validate !== 'function') {
+          this.$message.error('表单未初始化')
+          return
         }
 
-        // 如果有 id,则是编辑,需要添加 id 字段
-        if (this.preliminaryOpinionForm.id) {
-          formData.id = this.preliminaryOpinionForm.id
-        }
+        form.validate((valid) => {
+          if (!valid) return
+
+          // 判断是新增还是编辑(根据是否有 id)
+          const formData = {
+            basicSituation: this.preliminaryOpinionForm.basicSituation || '',
+            currentPriceStandard:
+              this.preliminaryOpinionForm.currentPriceStandard || '',
+            costComposition: this.preliminaryOpinionForm.costComposition || '',
+            preliminaryOpinion:
+              this.preliminaryOpinionForm.preliminaryOpinion || '',
+            taskId: this.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
+          // 如果有 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', { resetToFirst: true })
+                // this.$emit('close')
+              } else {
+                this.$message({ type: 'error', message: res.message })
               }
-              // 通知父组件刷新列表并关闭弹窗,要求分页回到第一页
-              // this.$emit('refresh', { resetToFirst: true })
-              // this.$emit('close')
-            } else {
-              this.$message({ type: 'error', message: res.message })
-            }
-          })
-          .catch((error) => {
-            // this.$message({ type: 'error', message: '保存失败,请稍后重试' })
-            console.error('保存初步审核意见失败:', error)
-          })
+            })
+            .catch((error) => {
+              // this.$message({ type: 'error', message: '保存失败,请稍后重试' })
+              console.error('保存初步审核意见失败:', error)
+            })
+        })
       },
 
       handleSaveConclusionOpinion() {
@@ -344,60 +429,75 @@
           return
         }
 
-        // 判断是新增还是编辑(根据是否有 id)
-        // 传递所有字段(包括初步意见、结论意见和反馈意见)
-        const formData = {
-          // 成本监审意见数据(初步意见)
-          basicSituation: this.preliminaryOpinionForm.basicSituation || '',
-          currentPriceStandard:
-            this.preliminaryOpinionForm.currentPriceStandard || '',
-          costComposition: this.preliminaryOpinionForm.costComposition || '',
-          preliminaryOpinion:
-            this.preliminaryOpinionForm.preliminaryOpinion || '',
-          // 成本审核结论意见数据
-          conclusionOpinion: this.conclusionOpinionForm.conclusionOpinion || '',
-          otherExplanations: this.conclusionOpinionForm.otherExplanations || '',
-          remark: this.conclusionOpinionForm.remark || '',
-          // 监审单位反馈意见数据
-          feedbackOpinion: this.feedbackForm.feedbackOpinion || '',
-          feedbackMaterials: this.feedbackForm.feedbackMaterial || '', // 反馈附件(单个URL字符串)
-          taskId: this.id,
+        const form = this.$refs.conclusionForm
+        if (!form || typeof form.validate !== 'function') {
+          this.$message.error('表单未初始化')
+          return
         }
 
-        // 如果有初步意见的 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
-        }
+        form.validate((valid) => {
+          if (!valid) return
 
-        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
+          // 判断是新增还是编辑(根据是否有 id)
+          // 传递所有字段(包括初步意见、结论意见和反馈意见)
+          const formData = {
+            // 成本监审意见数据(初步意见)
+            basicSituation: this.preliminaryOpinionForm.basicSituation || '',
+            currentPriceStandard:
+              this.preliminaryOpinionForm.currentPriceStandard || '',
+            costComposition: this.preliminaryOpinionForm.costComposition || '',
+            preliminaryOpinion:
+              this.preliminaryOpinionForm.preliminaryOpinion || '',
+            // 成本审核结论意见数据
+            conclusionOpinion:
+              this.conclusionOpinionForm.conclusionOpinion || '',
+            otherExplanations:
+              this.conclusionOpinionForm.otherExplanations || '',
+            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', { resetToFirst: true })
+                // this.$emit('close')
+              } else {
+                this.$message({ type: 'error', message: res.message })
               }
-              // 通知父组件刷新列表并关闭弹窗,要求分页回到第一页
-              // this.$emit('refresh', { resetToFirst: true })
-              // this.$emit('close')
-            } else {
-              this.$message({ type: 'error', message: res.message })
-            }
-          })
-          .catch((error) => {
-            // this.$message({ type: 'error', message: '保存失败,请稍后重试' })
-            console.error('保存审核结论意见失败:', error)
-          })
+            })
+            .catch((error) => {
+              // this.$message({ type: 'error', message: '保存失败,请稍后重试' })
+              console.error('保存审核结论意见失败:', error)
+            })
+        })
       },
       // 从URL获取文件名
       getFileNameFromUrl(url) {
@@ -424,8 +524,7 @@
         }
         return '未知文件'
       },
-      // 查看文件
-      handleFileView(file) {
+      handleFilePreview(file) {
         let fileUrl = ''
         if (typeof file === 'string') {
           fileUrl = file
@@ -436,22 +535,19 @@
         } else if (file && file.savePath) {
           fileUrl = file.savePath
         }
-
-        if (!fileUrl) {
-          this.$message &&
-            this.$message.warning &&
-            this.$message.warning('文件地址无效')
+        const normalized = this.normalizeUrl(fileUrl)
+        if (!normalized) {
+          this.$message.error('暂无文件!')
           return
         }
 
-        const normalized = this.normalizeUrl(fileUrl)
-        const suggestName =
-          (file && (file.name || file.fileName)) ||
-          this.getFileName(file) ||
-          '下载文件'
-        this.downloadByFetch(normalized, suggestName).catch((e) => {
-          console.error('文件下载失败: ', e)
-        })
+        // 对文件URL进行Base64编码
+        const encodedUrl = encodeURIComponent(Base64.encode(normalized))
+
+        // 构建 kkFileView 预览URL
+        // onlinePreview - 在线预览
+        // onlinePreview?type=pdf - 强制使用PDF模式预览
+        window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
       },
       normalizeUrl(u) {
         if (!u) return ''

+ 33 - 28
src/views/costAudit/auditInfo/auditManage/collectiveMain.vue

@@ -5,8 +5,12 @@
         <span>集体审议</span>
       </div>
       <div v-if="isEditable" class="collective-header-right">
-        <el-button type="primary" @click="handlePrint">补充资料</el-button>
-        <el-button type="primary" @click="handleAddRecord">新增记录</el-button>
+        <el-button plain type="primary" @click="handlePrint">
+          补充资料
+        </el-button>
+        <el-button plain type="success" @click="handleAddRecord">
+          新增记录
+        </el-button>
       </div>
     </div>
 
@@ -104,14 +108,13 @@
       </el-table>
     </div>
 
-    <!-- 新增/修改集体审议记录弹窗 -->
     <el-dialog
       :title="dialogTitle"
       :visible.sync="showRecordDialog"
       width="800px"
       :close-on-click-modal="false"
       :close-on-press-escape="false"
-      :modal="false"
+      :modal="true"
       append-to-body
     >
       <div class="record-form">
@@ -175,7 +178,7 @@
             <label class="form-label required">主持人</label>
             <el-input
               v-model="formData.hostPerson"
-              placeholder="请输入参加人员"
+              placeholder="请输入主持人"
               maxlength="50"
               show-word-limit
               class="form-input"
@@ -184,7 +187,7 @@
         </div>
         <div class="form-row">
           <div class="form-item">
-            <label class="form-label">记录人</label>
+            <label class="form-label required">记录人</label>
             <el-input
               v-model="formData.recordPerson"
               placeholder="请输入记录人"
@@ -290,7 +293,9 @@
                   :open-delay="200"
                   content="仅支持 doc/docx/pdf,最多1个,单个≤50MB"
                 >
-                  <el-button size="small" type="primary">选择文件</el-button>
+                  <el-button plain size="small" type="primary">
+                    选择文件
+                  </el-button>
                 </el-tooltip>
               </el-upload>
               <div v-if="fileList.length > 0" class="file-name">
@@ -317,30 +322,30 @@
       </div>
 
       <!-- 弹窗底部按钮 -->
-      <div slot="footer" class="dialog-footer">
-        <el-button class="cancel-btn" @click="handleCancel">取消</el-button>
+      <div slot="footer" style="text-align: right">
         <el-button type="primary" class="save-btn" @click="handleSave">
-          保存
+          确定
         </el-button>
+        <el-button class="cancel-btn" @click="handleCancel">取消</el-button>
       </div>
     </el-dialog>
 
-    <!-- 补充资料弹窗 -->
     <el-dialog
       :visible.sync="showSupplementDialog"
-      title="补充料"
+      title="补充料"
       width="600px"
-      :modal="false"
+      :modal="true"
       custom-class="supplement-dialog"
+      append-to-body
     >
       <div class="supplement-dialog-content">
         <div class="supplement-form-item">
-          <label class="supplement-form-label required">补充料:</label>
+          <label class="supplement-form-label required">补充料:</label>
           <el-input
             v-model="additionalParams.content"
             type="textarea"
             :rows="6"
-            placeholder="请输入补充料"
+            placeholder="请输入补充料"
             maxlength="500"
             show-word-limit
             class="supplement-textarea"
@@ -373,8 +378,8 @@
         </div> -->
       </div>
       <div slot="footer" class="supplement-dialog-footer">
-        <el-button @click="showSupplementDialog = false">取消</el-button>
         <el-button type="primary" @click="submitSupplement">发送</el-button>
+        <el-button @click="showSupplementDialog = false">取消</el-button>
       </div>
     </el-dialog>
   </div>
@@ -463,7 +468,7 @@
         uploadData: {},
         // 当前操作类型:add/edit
         operationType: 'add',
-        // 补充料弹窗数据
+        // 补充料弹窗数据
         additionalParams: {
           content: '',
           // sendType: [],
@@ -692,7 +697,7 @@
         // this.formData.auditedUnit = 'XX有限公司'
       },
 
-      // 补充
+      // 补充
       handlePrint() {
         this.additionalParams = {
           content: '',
@@ -727,14 +732,14 @@
         }
       },
 
-      // 提交补充
+      // 提交补充
       async submitSupplement() {
-        // 验证补充
+        // 验证补充
         if (
           !this.additionalParams.content ||
           !this.additionalParams.content.trim()
         ) {
-          this.$message.error('请输入补充料')
+          this.$message.error('请输入补充料')
           return
         }
 
@@ -788,7 +793,7 @@
           const response = await getReviewTask(params)
 
           if (response && response.code === 200) {
-            this.$message.success(response.message || '补充料发送成功')
+            this.$message.success(response.message || '补充料发送成功')
             // 关闭弹窗
             this.showSupplementDialog = false
             // 重置表单数据
@@ -798,11 +803,11 @@
               selectedSubUnits: [],
             }
           } else {
-            this.$message.error(response?.message || '补充料发送失败')
+            this.$message.error(response?.message || '补充料发送失败')
           }
         } catch (error) {
-          console.error('补充料发送失败:', error)
-          // this.$message.error('补充料发送失败')
+          console.error('补充料发送失败:', error)
+          // this.$message.error('补充料发送失败')
         }
       },
 
@@ -1411,12 +1416,12 @@
     align-items: center;
     flex-wrap: wrap;
     flex: 1;
-    max-width: 400px;
+    /* max-width: 400px; */
   }
 
   /* 日期选择器样式 */
   .date-picker {
-    width: 180px;
+    width: 200px;
   }
 
   /* 日期范围分隔符 */
@@ -1633,7 +1638,7 @@
     }
   }
 
-  /* 补充料弹窗样式优化 */
+  /* 补充料弹窗样式优化 */
   ::v-deep .supplement-dialog {
     .el-dialog__header {
       padding: 20px 25px 15px;

+ 8 - 7
src/views/costAudit/auditInfo/auditManage/details.vue

@@ -123,20 +123,20 @@
         />
       </div>
     </el-dialog>
-    <!-- 补充料弹窗 -->
+    <!-- 补充料弹窗 -->
     <el-dialog
       :visible.sync="showSupplementDialog"
-      title="补充料"
+      title="补充料"
       width="500px"
     >
       <div class="dialog-content">
         <div class="form-item">
-          <label class="form-label">补充料:</label>
+          <label class="form-label">补充料:</label>
           <el-input
             v-model="additionalParams.content"
             type="textarea"
             :rows="6"
-            placeholder="请输入补充料"
+            placeholder="请输入补充料"
             maxlength="500"
             show-word-limit
           />
@@ -309,7 +309,6 @@
           return '审核'
         } else if (
           this.currentNode === 'yjgaozhi' ||
-          this.currentNode === 'yjfk' ||
           this.currentNode === 'yjgz'
         ) {
           return '意见告知'
@@ -317,6 +316,8 @@
           return '资料初审'
         } else if (this.currentNode === 'sdsh') {
           return '成本审核'
+        } else if (this.currentNode === 'yjfk') {
+          return '意见反馈'
         }
         // 兜底:必须返回一个字符串,避免 eslint 报错
         return '成本审核详情'
@@ -1019,13 +1020,13 @@
         }
       },
 
-      // 提交补充
+      // 提交补充
       submitSupplement() {
         if (
           !this.additionalParams.content ||
           !this.additionalParams.content.trim()
         ) {
-          this.$message.error('请输入补充料')
+          this.$message.error('请输入补充料')
           return
         }
         if (this.currentButton) {

+ 16 - 3
src/views/costAudit/auditInfo/auditManage/mainDetails.vue

@@ -165,7 +165,7 @@
               multiple
             >
               <el-option
-                v-for="(item, index) in nextUserList"
+                v-for="(item, index) in nextUserList1"
                 :key="index"
                 :label="item.fullname"
                 :value="item.userId"
@@ -347,6 +347,7 @@
   import { getTaskUserList, getOrgUserQuery } from '@/api/uc'
   import { getDetailByAccountOrId } from '@/api/user'
   import { saveProjectReviewIssue } from '@/api/auditManage'
+  import { getCostProjectDetail } from '@/api/taskCustomizedRelease.js'
   export default {
     name: 'Details',
     components: {
@@ -418,6 +419,8 @@
         currentButton: null,
         // 流转下一步可选办理人列表
         nextUserList: [],
+        // 从办人员可选办理人列表
+        nextUserList1: [],
         // 流转/退回操作参数
         processParams: {
           content: '', // 意见
@@ -594,11 +597,21 @@
         try {
           const res = await getTaskUserList({
             code: 'jsztry',
-            projectId: this.project.projectId,
+            // projectId: this.project.projectId,
           })
           this.nextUserList = res.value || []
+          const resp = await getCostProjectDetail({
+            id: this.project.projectId,
+          })
+          // 负责人id
+          let userId = resp.value ? resp.value.leaderIds.split(',') : []
+          // 从办人员可选办理人列表
+          this.nextUserList1 = res.value.filter(
+            (item) => !userId.includes(item.userId)
+          )
         } catch (e) {
           this.nextUserList = []
+          this.nextUserList1 = []
         }
       },
       // 获取主办人员和从办人员
@@ -679,7 +692,7 @@
           // sendType: [],
         }
         await this.loadNextUserList()
-        // await this.getZcUserList()
+        await this.getZcUserList()
         this.showProcessDialog = true
       },