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

Merge branch 'master' of http://116.204.116.5:3000/zzw/cbjsxt-front-master

suhp 1 месяц назад
Родитель
Сommit
052e3010dd

+ 407 - 0
src/components/task/cbjsInfo.vue

@@ -0,0 +1,407 @@
+<template>
+  <div class="cbjs-info-container">
+    <el-dialog
+      :visible.sync="visible"
+      :title="dialogTitle"
+      width="90%"
+      :close-on-click-modal="false"
+      @close="handleClose"
+    >
+      <!-- 标签页面 - 移除了操作按钮区域 -->
+      <el-tabs
+        v-model="activeTab"
+        type="card"
+        class="audit-tabs disabled-container"
+      >
+        <el-tab-pane label="报送资料" name="submitData">
+          <submit-data :id="id" :disabled="true" />
+        </el-tab-pane>
+        <el-tab-pane label="成本调查表" name="costSurvey">
+          <cost-survey :id="id" :disabled="true" />
+        </el-tab-pane>
+        <el-tab-pane
+          v-if="currentNode !== 'clcs'"
+          label="成本审核"
+          name="costAudit"
+        >
+          <cost-audit :id="id" :disabled="true" />
+        </el-tab-pane>
+        <el-tab-pane
+          v-if="currentNode !== 'clcs'"
+          label="工作底稿"
+          name="workDraft"
+        >
+          <work-draft :id="id" :disabled="true" />
+        </el-tab-pane>
+        <el-tab-pane
+          v-if="currentNode !== 'clcs'"
+          label="提取材料登记"
+          name="extractMaterial"
+        >
+          <extract-material :id="id" :disabled="true" />
+        </el-tab-pane>
+        <el-tab-pane
+          v-if="currentNode !== 'clcs'"
+          label="成本审核意见"
+          name="auditOpinion"
+        >
+          <audit-opinion
+            :id="id"
+            :current-node="currentNode"
+            :status="currentStatus"
+            :disabled="true"
+          />
+        </el-tab-pane>
+        <el-tab-pane label="消息通知" name="messageNotify">
+          <message-notify :id="id" :disabled="true" />
+        </el-tab-pane>
+      </el-tabs>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import costAudit from './components/costAudit.vue'
+  import costSurvey from './components/costSurvey.vue'
+  import submitData from './components/submitData.vue'
+  import workDraft from './components/workDraft.vue'
+  import extractMaterial from './components/extractMaterial.vue'
+  import auditOpinion from './components/auditOpinion.vue'
+  import messageNotify from './components/messageNotify.vue'
+
+  export default {
+    name: 'CbjsInfo',
+    components: {
+      costAudit,
+      costSurvey,
+      submitData,
+      workDraft,
+      extractMaterial,
+      auditOpinion,
+      messageNotify,
+    },
+    props: {
+      visible: {
+        type: Boolean,
+        default: false,
+      },
+      id: {
+        type: [String, Number],
+        default: null,
+      },
+      currentNode: {
+        type: String,
+        default: '',
+      },
+      currentStatus: {
+        type: String,
+        default: '',
+      },
+    },
+    data() {
+      return {
+        activeTab: 'submitData', // 默认选中报送资料标签页
+      }
+    },
+    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 '成本审核详情'
+      },
+    },
+    watch: {
+      visible(newVal) {
+        // 监听visible变化,弹窗打开时设置标签页
+        if (newVal && this.id) {
+          // 使用 $nextTick 确保 props 已更新
+          this.$nextTick(() => {
+            // 设置标签页
+            this.setActiveTab()
+          })
+        }
+      },
+      // 监听currentNode变化,如果弹窗已打开,更新标签页
+      currentNode(newVal, oldVal) {
+        if (this.visible && this.id && newVal && newVal !== oldVal) {
+          this.$nextTick(() => {
+            // 设置标签页
+            this.setActiveTab()
+          })
+        }
+      },
+      // 监听currentStatus变化,如果弹窗已打开,更新标签页
+      currentStatus(newVal, oldVal) {
+        if (this.visible && this.id && newVal && newVal !== oldVal) {
+          this.$nextTick(() => {
+            // 设置标签页
+            this.setActiveTab()
+          })
+        }
+      },
+      // 监听id变化,如果弹窗已打开,更新标签页
+      id(newVal) {
+        if (this.visible && newVal) {
+          this.$nextTick(() => {
+            // 设置标签页
+            this.setActiveTab()
+          })
+        }
+      },
+    },
+    mounted() {
+      // 设置标签页
+      this.setActiveTab()
+    },
+    methods: {
+      // 根据 currentNode 和 currentStatus 设置活动标签页
+      setActiveTab() {
+        if (this.currentNode === 'sdsh' && this.currentStatus === '审核中') {
+          this.activeTab = 'costAudit'
+        } else if (this.currentNode === 'clcs') {
+          // 如果 currentNode 是 'clcs',显示成本调查表标签页
+          this.activeTab = 'submitData'
+        } else if (
+          this.currentNode === 'yjgaozhi' ||
+          this.currentNode === 'yjfk'
+        ) {
+          // 如果 currentNode 是 'yjgaozhi',显示意见告知标签页(成本审核意见)
+          this.activeTab = 'auditOpinion'
+        } else {
+          // 其他情况默认显示报送资料标签页
+          this.activeTab = 'submitData'
+        }
+      },
+      handleClose() {
+        // 关闭弹窗时触发事件
+        this.$emit('update:visible', false)
+        this.$emit('close')
+      },
+      open() {
+        // 打开弹窗方法,供父组件通过ref调用
+        this.$emit('update:visible', true)
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  /* 直接设置弹窗主体和内容区域,确保滚动正常工作 */
+  .cbjs-info-container {
+    width: 100%;
+    height: 100%;
+  }
+
+  /* 关键修改:弹窗主体设置固定高度并隐藏溢出 */
+  :deep(.el-dialog__body) {
+    padding: 0 !important;
+    height: calc(100vh - 200px);
+    overflow: hidden;
+  }
+
+  /* 标签容器占满弹窗高度 */
+  .audit-tabs {
+    height: 100%;
+  }
+
+  /* 标签头保持固定位置 */
+  .audit-tabs .el-tabs__header {
+    margin-bottom: 0;
+    padding: 15px 15px 0;
+    background: #f5f7fa;
+    border-bottom: 1px solid #ebeef5;
+  }
+
+  /* 核心修改:内容区域设置固定高度并允许垂直滚动 */
+  .audit-tabs .el-tabs__content {
+    height: calc(100% - 60px); /* 减去标签头的高度 */
+    overflow-y: auto; /* 这是最重要的设置,确保内容超出时显示滚动条 */
+    padding: 15px;
+    box-sizing: border-box;
+  }
+
+  /* 优化滚动条样式 */
+  .audit-tabs .el-tabs__content::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  .audit-tabs .el-tabs__content::-webkit-scrollbar-track {
+    background: #f5f7fa;
+  }
+
+  .audit-tabs .el-tabs__content::-webkit-scrollbar-thumb {
+    background: #ccc;
+    border-radius: 4px;
+  }
+
+  .audit-tabs .el-tabs__content::-webkit-scrollbar-thumb:hover {
+    background: #999;
+  }
+
+  /* 确保标签页内容不受限制 */
+  .audit-tabs .el-tab-pane {
+    height: auto;
+    overflow: visible;
+  }
+
+  /* 确保子组件可以正常显示和滚动 */
+  .audit-tabs .el-tab-pane > div {
+    width: 100%;
+  }
+
+  /* 禁用容器样式 - 所有交互元素不可操作但不改变视觉样式 */
+
+  /* 确保 tab 头部完全可交互 */
+  .disabled-container :deep(.el-tabs__header),
+  .disabled-container :deep(.el-tabs__nav-wrap),
+  .disabled-container :deep(.el-tabs__nav),
+  .disabled-container :deep(.el-tabs__item) {
+    pointer-events: auto !important;
+    cursor: pointer !important;
+  }
+
+  /* 所有输入框禁用交互并置灰 */
+  .disabled-container :deep(.el-input__inner),
+  .disabled-container :deep(.el-input__wrapper),
+  .disabled-container :deep(.el-textarea__inner),
+  .disabled-container :deep(.el-select .el-input__inner),
+  .disabled-container :deep(.el-input-number__input) {
+    background-color: #f5f7fa !important;
+    color: #909399 !important;
+    border-color: #e4e7ed !important;
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  /* 所有输入框禁用状态 */
+  .disabled-container :deep(.el-input.is-disabled .el-input__inner),
+  .disabled-container :deep(.el-input.is-disabled .el-input__wrapper),
+  .disabled-container :deep(.el-textarea.is-disabled .el-textarea__inner) {
+    background-color: #f5f7fa !important;
+    color: #909399 !important;
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  /* 所有按钮禁用交互并置灰 */
+  .disabled-container :deep(.el-button) {
+    background-color: #f5f7fa !important;
+    border-color: #e4e7ed !important;
+    color: #909399 !important;
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-button--primary) {
+    background-color: #c0c4cc !important;
+    border-color: #c0c4cc !important;
+    color: #fff !important;
+  }
+
+  .disabled-container :deep(.el-button--text) {
+    color: #909399 !important;
+  }
+
+  /* 所有选择器禁用交互并置灰 */
+  .disabled-container :deep(.el-select) {
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-select .el-input) {
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-select .el-input__inner) {
+    background-color: #f5f7fa !important;
+    color: #909399 !important;
+    border-color: #e4e7ed !important;
+  }
+
+  /* 所有单选框和复选框禁用交互 */
+  .disabled-container :deep(.el-radio),
+  .disabled-container :deep(.el-checkbox) {
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-radio__input),
+  .disabled-container :deep(.el-checkbox__input) {
+    pointer-events: none !important;
+  }
+
+  /* 表格内的输入框和按钮置灰 */
+  .disabled-container :deep(.el-table .el-input__inner) {
+    background-color: #f5f7fa !important;
+    color: #909399 !important;
+    border-color: #e4e7ed !important;
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-table .el-button) {
+    background-color: #f5f7fa !important;
+    border-color: #e4e7ed !important;
+    color: #909399 !important;
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  /* 日期选择器置灰 */
+  .disabled-container :deep(.el-date-editor) {
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-date-editor .el-input__inner) {
+    background-color: #f5f7fa !important;
+    color: #909399 !important;
+    border-color: #e4e7ed !important;
+  }
+
+  /* 数字输入框置灰 */
+  .disabled-container :deep(.el-input-number) {
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  .disabled-container :deep(.el-input-number .el-input__inner) {
+    background-color: #f5f7fa !important;
+    color: #909399 !important;
+    border-color: #e4e7ed !important;
+  }
+
+  /* 上传组件 */
+  .disabled-container :deep(.el-upload) {
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  /* 链接 */
+  .disabled-container :deep(a) {
+    cursor: not-allowed !important;
+    pointer-events: none !important;
+  }
+
+  /* 标签页内容区域恢复交互以便滚动 */
+  .disabled-container .el-tabs__content {
+    pointer-events: auto;
+  }
+
+  /* 但标签页内容内的所有交互元素仍需禁用 */
+  /* .disabled-container .el-tabs__content > * {
+    pointer-events: none;
+  } */
+</style>

+ 509 - 0
src/components/task/components/auditOpinion.vue

@@ -0,0 +1,509 @@
+<template>
+  <div class="opinions-content">
+    <!-- 初步审核意见 -->
+    <div class="opinion-section">
+      <div class="opinion-header">
+        <h3>成本审核初步意见</h3>
+        <el-button
+          class="ml10"
+          type="primary"
+          size="small"
+          :disabled="isPreliminaryDisabled || disabled"
+          @click="handleSavePreliminaryOpinion"
+        >
+          保存
+        </el-button>
+      </div>
+
+      <el-form
+        :model="preliminaryOpinionForm"
+        label-width="180px"
+        :class="{ 'disabled-section': isPreliminaryDisabled }"
+      >
+        <el-form-item label="被审核单位基本情况及主要财务数据:">
+          <el-input
+            v-model="preliminaryOpinionForm.basicSituation"
+            type="textarea"
+            :rows="3"
+            :disabled="isPreliminaryDisabled || disabled"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="成本费用核算情况及问题:">
+          <el-input
+            v-model="preliminaryOpinionForm.currentPriceStandard"
+            type="textarea"
+            :rows="3"
+            :disabled="isPreliminaryDisabled || disabled"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="成本费用控制措施及建议:">
+          <el-input
+            v-model="preliminaryOpinionForm.costComposition"
+            type="textarea"
+            :rows="3"
+            :disabled="isPreliminaryDisabled || disabled"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="成本审核初步意见:">
+          <el-input
+            v-model="preliminaryOpinionForm.preliminaryOpinion"
+            type="textarea"
+            :rows="3"
+            :disabled="isPreliminaryDisabled || disabled"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <!-- 被审核单位反馈意见 -->
+    <div class="opinion-section">
+      <div class="opinion-header">
+        <h3>监审单位反馈意见:</h3>
+      </div>
+
+      <el-form :model="feedbackForm" label-width="180px">
+        <el-form-item label="被审核单位反馈意见:">
+          <el-input
+            v-model="feedbackForm.feedbackOpinion"
+            disabled
+            type="textarea"
+            :rows="3"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="被审核单位反馈附件:">
+          <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>
+
+    <!-- 成本审核结论意见 -->
+    <div class="opinion-section">
+      <div class="opinion-header">
+        <h3>成本审核结论意见</h3>
+        <el-button
+          class="ml10"
+          type="primary"
+          size="small"
+          :disabled="!isConclusionEditable || disabled"
+          @click="handleSaveConclusionOpinion"
+        >
+          保存
+        </el-button>
+      </div>
+
+      <el-form :model="conclusionOpinionForm" label-width="180px">
+        <el-form-item label="成本审核结论意见:">
+          <el-input
+            v-model="conclusionOpinionForm.conclusionOpinion"
+            type="textarea"
+            :rows="3"
+            :disabled="!isConclusionEditable || disabled"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="整改要求及时间:">
+          <el-input
+            v-model="conclusionOpinionForm.rectificationRequirements"
+            type="textarea"
+            :rows="3"
+            :disabled="!isConclusionEditable || disabled"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input
+            v-model="conclusionOpinionForm.remark"
+            type="textarea"
+            :rows="3"
+            :disabled="!isConclusionEditable || disabled"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</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: '',
+      },
+      disabled: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        // 审核意见表单数据
+        preliminaryOpinionForm: {
+          id: '',
+          basicSituation: '',
+          currentPriceStandard: '',
+          costComposition: '',
+          preliminaryOpinion: '',
+        },
+
+        feedbackForm: {
+          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() {
+        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() {
+        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('文件地址无效')
+        }
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .opinions-content {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+  }
+
+  .opinion-section {
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    padding: 20px;
+    margin-top: 20px;
+  }
+
+  .disabled-section {
+    opacity: 0.7;
+    background-color: #f5f5f5;
+  }
+
+  .opinion-header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 20px;
+  }
+
+  .opinion-header h3 {
+    margin: 0;
+    font-size: 14px;
+    font-weight: 500;
+  }
+
+  .opinion-section .el-form-item {
+    margin-bottom: 15px;
+  }
+
+  .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>

+ 347 - 0
src/components/task/components/costAudit.vue

@@ -0,0 +1,347 @@
+<template>
+  <div class="app-container">
+    <div class="audit-controls">
+      <el-form :model="auditForm">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="" prop="dataTable">
+              <template slot="label">
+                <el-radio
+                  v-model="auditForm.dataTable"
+                  :label="3"
+                  :disabled="disabled"
+                >
+                  选择调查表修改核定表
+                </el-radio>
+              </template>
+              <el-select
+                v-model="auditForm.dataTable"
+                :disabled="disabled"
+                placeholder="请选择"
+                style="width: 300px"
+              >
+                <el-option
+                  label="幼儿教育机构基本情况表"
+                  value="幼儿教育机构基本情况表"
+                ></el-option>
+                <el-option
+                  label="幼儿教育机构收入情况表"
+                  value="幼儿教育机构收入情况表"
+                ></el-option>
+                <el-option
+                  label="幼儿教育机构成本支出情况表"
+                  value="幼儿教育机构成本支出情况表"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="" prop="dataTable">
+              <template slot="label">
+                <el-radio
+                  v-model="auditForm.dataTable"
+                  :label="6"
+                  :disabled="disabled"
+                >
+                  选择核定表历史版本
+                </el-radio>
+              </template>
+              <el-select
+                v-model="auditForm.historyTemplate"
+                :disabled="disabled"
+                placeholder="请选择"
+                style="width: 300px"
+              >
+                <el-option
+                  label="2024年幼儿园定价成本核定模板"
+                  value="2024年幼儿园定价成本核定模板"
+                ></el-option>
+                <el-option
+                  label="2024年幼儿园定价成本核定模板"
+                  value="2024年幼儿园定价成本核定模板"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <el-button
+        type="primary"
+        size="small"
+        :disabled="disabled"
+        @click="handleExportData"
+      >
+        生成核定表
+      </el-button>
+      <el-button
+        type="primary"
+        size="small"
+        :disabled="disabled"
+        @click="handleExportData"
+      >
+        保存核定模版
+      </el-button>
+      <el-button
+        type="primary"
+        size="small"
+        :disabled="disabled"
+        @click="handleExportData"
+      >
+        导出模版
+      </el-button>
+      <el-button
+        type="primary"
+        size="small"
+        :disabled="disabled"
+        @click="handleImportData"
+      >
+        导入数据
+      </el-button>
+      <el-button
+        type="primary"
+        size="small"
+        :disabled="disabled"
+        @click="handleSaveAudit"
+      >
+        保存核定数据
+      </el-button>
+    </div>
+
+    <el-table :data="costAuditData" style="width: 100%" border>
+      <el-table-column
+        v-for="item in costAuditcolumn"
+        :key="item.prop"
+        :prop="item.prop"
+        :label="item.label"
+        :width="item.width"
+        :align="item.align"
+        :fixed="item.fixed"
+      >
+        <!-- 操作列的渲染逻辑修改 -->
+        <template slot-scope="scope">
+          <div v-if="item.prop === 'action'">
+            <el-button
+              type="text"
+              title="添加行"
+              @click="handleAddItem(scope.row)"
+            >
+              <i class="el-icon el-icon-circle-plus"></i>
+            </el-button>
+            <el-button
+              type="text"
+              title="删除行"
+              @click="handleDeleteItem(scope.row)"
+            >
+              <i class="el-icon el-icon-remove"></i>
+            </el-button>
+          </div>
+          <div v-else>
+            <el-input
+              v-model="scope.row[item.prop]"
+              :disabled="disabled"
+              placeholder="请输入"
+            />
+          </div>
+        </template>
+        <!-- 其他列保持不变 -->
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+  import taskMixins from './taskMixins.js'
+  export default {
+    name: 'CostAudit',
+    mixins: [taskMixins],
+    props: {
+      disabled: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        auditForm: {
+          surveyTableName: '',
+          templateType: '',
+          dataTable: '',
+          historyTemplate: '',
+        },
+        // 成本审核表格列配置
+        costAuditcolumn: [],
+        // 成本审核数据
+        costAuditData: [
+          {
+            id: '一',
+            itemName: '人员费用小计',
+            unit: '元',
+          },
+          {
+            id: 1,
+            itemName: '基本工资',
+            unit: '元',
+          },
+        ],
+      }
+    },
+    mounted() {
+      // 初始化列配置
+      this.initColumns()
+    },
+    methods: {
+      // 初始化列配置
+      initColumns() {
+        const baseColumns = [
+          {
+            prop: 'id',
+            label: '序号',
+            width: 80,
+            align: 'center',
+          },
+          {
+            prop: 'itemName',
+            label: '项目',
+            width: 180,
+          },
+          {
+            prop: 'unit',
+            label: '单位',
+            width: 100,
+            align: 'center',
+          },
+          {
+            prop: 'year2022BookValue',
+            label: '2022年账面值',
+            width: 120,
+            align: 'right',
+          },
+          {
+            prop: 'year2022Audit',
+            label: '2022年审核',
+            width: 120,
+            align: 'center',
+          },
+          {
+            prop: 'year2022ApprovedValue',
+            label: '2022年核定值',
+            width: 120,
+            align: 'right',
+          },
+          {
+            prop: 'year2023BookValue',
+            label: '2023年账面值',
+            width: 120,
+            align: 'right',
+          },
+          {
+            prop: 'year2023Audit',
+            label: '2023年审核',
+            width: 120,
+            align: 'center',
+          },
+          {
+            prop: 'year2023ApprovedValue',
+            label: '2023年核定值',
+            width: 120,
+            align: 'right',
+          },
+          {
+            prop: 'year2024BookValue',
+            label: '2024年账面值',
+            width: 120,
+            align: 'right',
+          },
+          {
+            prop: 'year2024Audit',
+            label: '2024年审核',
+            width: 120,
+            align: 'center',
+          },
+          {
+            prop: 'year2024ApprovedValue',
+            label: '2024年核定值',
+            width: 120,
+            align: 'right',
+          },
+        ]
+
+        // 如果不禁用,添加操作列
+        if (!this.disabled) {
+          baseColumns.push({
+            prop: 'action',
+            label: '操作',
+            width: 150,
+            align: 'center',
+            fixed: 'right',
+          })
+        }
+
+        this.costAuditcolumn = baseColumns
+      },
+      // 成本审核操作
+      handleImportData() {
+        this.$message({ type: 'info', message: '导入数据' })
+      },
+
+      handleExportData() {
+        this.$message({ type: 'info', message: '导出数据' })
+      },
+
+      handleSaveAudit() {
+        this.$message({ type: 'success', message: '成本审核数据已保存' })
+      },
+
+      handleRemark(row, field) {
+        this.$prompt('请输入说明', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          inputValue: row[field],
+        })
+          .then(({ value }) => {
+            row[field] = value
+            this.$message({ type: 'success', message: '说明已保存' })
+          })
+          .catch(() => {
+            this.$message({ type: 'info', message: '已取消' })
+          })
+      },
+
+      handleAddItem(row) {
+        // 添加行的逻辑
+        const newItem = {
+          // 新行的数据结构
+        }
+        this.costAuditData.push(newItem)
+      },
+      handleDeleteItem(row) {
+        // 删除行的逻辑
+        const index = this.costAuditData.indexOf(row)
+        if (index !== -1) {
+          this.costAuditData.splice(index, 1)
+        }
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .app-container {
+    padding: 20px;
+  }
+
+  .audit-controls {
+    margin-bottom: 20px;
+  }
+
+  .audit-controls .el-select {
+    margin-right: 10px;
+    width: 150px;
+  }
+
+  .el-icon {
+    font-size: 24px;
+  }
+</style>

+ 54 - 0
src/components/task/components/costSurvey.vue

@@ -0,0 +1,54 @@
+<template>
+  <div>
+    <!-- 成本调查表内容 -->
+    <el-table border :data="surveyData.list" style="width: 100%">
+      <el-table-column
+        prop="index"
+        label="序号"
+        width="80"
+        align="center"
+        header-align="center"
+      ></el-table-column>
+      <el-table-column
+        prop="name"
+        label="成本调查表"
+        min-width="200"
+        align="left"
+        header-align="center"
+        show-overflow-tooltip
+      ></el-table-column>
+      <el-table-column
+        prop="type"
+        label="资料类型"
+        width="120"
+        align="center"
+        header-align="center"
+      ></el-table-column>
+      <el-table-column
+        prop="required"
+        label="是否必填"
+        width="100"
+        align="center"
+        header-align="center"
+      ></el-table-column>
+      <el-table-column
+        label="操作"
+        width="120"
+        align="center"
+        header-align="center"
+      >
+        <template slot-scope="scope">
+          <el-button type="text" @click="handleViewTemplate(scope.row)">
+            查看模板
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+<script>
+  import taskMixins from './taskMixins.js'
+  export default {
+    mixins: [taskMixins],
+  }
+</script>

+ 683 - 0
src/components/task/components/extractMaterial.vue

@@ -0,0 +1,683 @@
+<template>
+  <div class="extract-material-container">
+    <div class="extract-controls">
+      <!-- <el-button type="primary" @click="handleAddExtract">添加资料</el-button> -->
+      <!-- <el-button
+        type="danger"
+        :disabled="selectedRows.length === 0"
+        @click="handleBatchDelete"
+      >
+        批量删除
+      </el-button> -->
+    </div>
+
+    <el-table
+      v-loading="loading"
+      :data="extractMaterials"
+      style="width: 100%"
+      stripe
+      border
+      size="small"
+    >
+      <!-- <el-table-column
+        type="selection"
+        width="55"
+        align="center"
+      ></el-table-column> -->
+      <el-table-column prop="id" label="编号" width="80">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="materialName"
+        label="材料名称"
+        width="200"
+        show-overflow-tooltip
+      ></el-table-column>
+      <el-table-column
+        prop="pages"
+        label="页数"
+        width="100"
+        align="center"
+      ></el-table-column>
+      <el-table-column
+        prop="remark"
+        label="备注"
+        min-width="200"
+        show-overflow-tooltip
+      ></el-table-column>
+      <el-table-column
+        prop="createTime"
+        label="提取时间"
+        width="200"
+        :formatter="formatDate"
+        align="center"
+        show-overflow-tooltip
+      ></el-table-column>
+      <el-table-column label="操作" width="180" fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            type="primary"
+            size="small"
+            :disabled="disabled"
+            @click="handleEditExtract(scope.row)"
+          >
+            修改
+          </el-button>
+          <el-button
+            type="danger"
+            size="small"
+            :disabled="disabled"
+            @click="handleDeleteExtract(scope.row)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-dialog
+      :visible.sync="dialogVisible"
+      :title="dialogTitle"
+      width="500px"
+      :close-on-click-modal="false"
+      :modal="false"
+      append-to-body
+    >
+      <el-form
+        ref="extractForm"
+        :model="extractForm"
+        :rules="rules"
+        label-width="100px"
+      >
+        <el-form-item label="材料名称" prop="materialName">
+          <el-input
+            v-model="extractForm.materialName"
+            placeholder="请输入材料名称"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="页数" prop="pageCount">
+          <el-input-number
+            v-model.number="extractForm.pageCount"
+            placeholder="请输入页数"
+            :min="1"
+            :step="1"
+          ></el-input-number>
+        </el-form-item>
+        <el-form-item label="序号" prop="orderNum">
+          <el-input-number
+            v-model.number="extractForm.orderNum"
+            placeholder="请输入序号"
+            :min="1"
+            :step="1"
+          ></el-input-number>
+        </el-form-item>
+        <el-form-item label="上传附件" prop="fileList">
+          <el-upload
+            class="upload-demo"
+            :action="''"
+            :http-request="handleFileUpload"
+            :on-remove="handleFileRemove"
+            :before-upload="beforeFileUpload"
+            :on-success="handleFileUploadSuccess"
+            :on-error="handleFileUploadError"
+            :on-progress="handleFileUploadProgress"
+            :file-list="extractForm.fileList"
+            :limit="1"
+            :on-exceed="handleFileExceed"
+          >
+            <el-button
+              v-show="
+                !extractForm.fileList || extractForm.fileList.length === 0
+              "
+              size="small"
+              type="primary"
+            >
+              选择文件
+            </el-button>
+            <div slot="tip" class="el-upload__tip">
+              支持 pdf, doc, docx, xls, xlsx, csv 格式,单个文件不超过50MB
+            </div>
+          </el-upload>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="extractForm.remark"
+            type="textarea"
+            placeholder="请输入备注"
+            :rows="4"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+      <div class="dialog-footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  // 暂时使用mock数据,后续根据实际API调整
+  import {
+    getExtractMaterialList,
+    addTaskEvidence,
+    updateTaskEvidence,
+    deleteTaskEvidence,
+  } from '@/api/audit/taskEvidence'
+  import { uploadFile } from '@/api/file'
+
+  export default {
+    name: 'ExtractMaterial',
+    props: {
+      id: {
+        type: [String, Number],
+        default: null,
+      },
+      disabled: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        loading: false,
+        extractMaterials: [],
+        selectedRows: [],
+        dialogVisible: false,
+        dialogTitle: '添加提取材料',
+        isEdit: false,
+        extractForm: {
+          id: '',
+          materialName: '',
+          pageCount: null,
+          orderNum: null,
+          remark: '',
+          fileList: [],
+          attachmentUrl: '',
+        },
+        rules: {
+          materialName: [
+            { required: true, message: '请输入材料名称', trigger: 'blur' },
+            {
+              min: 1,
+              max: 100,
+              message: '材料名称长度应在1-100个字符之间',
+              trigger: 'blur',
+            },
+          ],
+          pageCount: [
+            {
+              required: true,
+              message: '请输入页数',
+              trigger: ['blur', 'change'],
+            },
+            {
+              type: 'number',
+              message: '页数必须为数字',
+              trigger: ['blur', 'change'],
+            },
+            {
+              validator: (rule, value, callback) => {
+                if (value === null || value === undefined || value === '') {
+                  callback()
+                  return
+                }
+                if (typeof value !== 'number' || isNaN(value)) {
+                  callback(new Error('页数必须为数字'))
+                  return
+                }
+                if (value < 1) {
+                  callback(new Error('页数必须大于0'))
+                  return
+                }
+                callback()
+              },
+              trigger: ['blur', 'change'],
+            },
+          ],
+          orderNum: [
+            {
+              required: true,
+              message: '请输入序号',
+              trigger: ['blur', 'change'],
+            },
+            {
+              type: 'number',
+              message: '序号必须为数字',
+              trigger: ['blur', 'change'],
+            },
+            {
+              validator: (rule, value, callback) => {
+                if (value === null || value === undefined || value === '') {
+                  callback()
+                  return
+                }
+                if (typeof value !== 'number' || isNaN(value)) {
+                  callback(new Error('序号必须为数字'))
+                  return
+                }
+                if (value < 1) {
+                  callback(new Error('序号必须大于0'))
+                  return
+                }
+                callback()
+              },
+              trigger: ['blur', 'change'],
+            },
+          ],
+        },
+      }
+    },
+    mounted() {
+      this.getExtractMaterials()
+    },
+    beforeDestroy() {
+      // 清理定时器等资源
+      this.loading = false
+    },
+    methods: {
+      // 处理选中行变化
+      handleSelectionChange(selection) {
+        this.selectedRows = selection
+      },
+
+      // 获取提取材料列表
+      async getExtractMaterials() {
+        if (!this.id) {
+          return
+        }
+
+        try {
+          this.loading = true
+          const res = await getExtractMaterialList({
+            taskId: this.id,
+          })
+
+          if (res && res.value) {
+            // 统一字段名:如果后端返回的是 pageCount,映射为 pages
+            this.extractMaterials = (res.value || []).map((item) => ({
+              ...item,
+              pages: item.pages !== undefined ? item.pages : item.pageCount,
+            }))
+          } else {
+            this.extractMaterials = []
+          }
+        } catch (error) {
+          this.$message.error('获取提取材料列表失败')
+          console.error('获取提取材料列表失败:', error)
+          this.extractMaterials = []
+        } finally {
+          this.loading = false
+        }
+      },
+
+      // 添加资料
+      handleAddExtract() {
+        this.isEdit = false
+        this.dialogTitle = '添加提取材料'
+        this.extractForm = {
+          id: '',
+          materialName: '',
+          pageCount: null,
+          orderNum: null,
+          remark: '',
+          fileList: [],
+          attachmentUrl: '',
+        }
+        // 重置表单验证状态
+        if (this.$refs.extractForm) {
+          this.$refs.extractForm.resetFields()
+        }
+        this.dialogVisible = true
+      },
+
+      // 编辑资料
+      handleEditExtract(row) {
+        this.isEdit = true
+        this.dialogTitle = '编辑提取材料'
+        // 确保字段名统一,如果后端返回的是 pages,映射为 pageCount
+        // 处理文件列表
+        let fileList = []
+        if (row.attachmentUrl) {
+          fileList = [
+            {
+              name: row.fileName || '附件',
+              url: row.attachmentUrl,
+              response: { savePath: row.attachmentUrl },
+            },
+          ]
+        }
+        this.extractForm = {
+          ...row,
+          pageCount: row.pageCount !== undefined ? row.pageCount : row.pages,
+          orderNum: row.orderNum !== undefined ? row.orderNum : row.orderNumber,
+          fileList: fileList,
+          attachmentUrl: row.attachmentUrl || '',
+        }
+        this.dialogVisible = true
+      },
+
+      // 删除资料
+      async handleDeleteExtract(row) {
+        try {
+          await this.$confirm('确定要删除该提取材料吗?', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning',
+          })
+
+          const res = await deleteTaskEvidence({ id: row.id })
+
+          if (res.code === 200) {
+            this.$message.success('删除成功')
+            // 重新获取列表
+            this.getExtractMaterials()
+          } else {
+            this.$message.error(res.message || '删除失败')
+          }
+        } catch (error) {
+          if (error !== 'cancel') {
+            this.$message.error('删除失败')
+            console.error('删除提取材料失败:', error)
+          }
+        }
+      },
+
+      // 批量删除
+      async handleBatchDelete() {
+        if (this.selectedRows.length === 0) {
+          this.$message.warning('请选择要删除的提取材料')
+          return
+        }
+
+        try {
+          await this.$confirm(
+            `确定要删除选中的 ${this.selectedRows.length} 条提取材料吗?`,
+            '提示',
+            {
+              confirmButtonText: '确定',
+              cancelButtonText: '取消',
+              type: 'warning',
+            }
+          )
+
+          // 批量删除,逐条调用删除接口
+          const deletePromises = this.selectedRows.map((row) =>
+            deleteTaskEvidence({ id: row.id })
+          )
+          const results = await Promise.all(deletePromises)
+
+          // 检查是否有失败的
+          const failedCount = results.filter((res) => res.code !== 200).length
+          if (failedCount === 0) {
+            this.$message.success('批量删除成功')
+            this.selectedRows = [] // 清空选择
+            // 重新获取列表
+            this.getExtractMaterials()
+          } else {
+            this.$message.warning(
+              `批量删除完成,${
+                results.length - failedCount
+              } 条成功,${failedCount} 条失败`
+            )
+            // 即使有失败的,也刷新列表以获取最新数据
+            this.selectedRows = []
+            this.getExtractMaterials()
+          }
+        } catch (error) {
+          if (error !== 'cancel') {
+            this.$message.error('批量删除失败')
+            console.error('批量删除提取材料失败:', error)
+          }
+        }
+      },
+
+      // 提交表单
+      async handleSubmit() {
+        try {
+          await this.$refs.extractForm.validate()
+
+          const formData = {
+            id: this.extractForm.id,
+            materialName: this.extractForm.materialName,
+            pageCount: this.extractForm.pageCount,
+            orderNum: this.extractForm.orderNum,
+            remark: this.extractForm.remark,
+            attachmentUrl: this.extractForm.attachmentUrl,
+            taskId: this.id,
+          }
+
+          let res
+          if (this.isEdit) {
+            // 编辑模式,调用更新接口
+            res = await updateTaskEvidence(formData)
+          } else {
+            // 添加模式,调用新增接口
+            res = await addTaskEvidence(formData)
+          }
+
+          if (res.code === 200) {
+            this.$message.success(this.isEdit ? '更新成功' : '添加成功')
+            this.dialogVisible = false
+            this.getExtractMaterials()
+          } else {
+            this.$message.error(
+              res.message || (this.isEdit ? '更新失败' : '添加失败')
+            )
+          }
+        } catch (error) {
+          if (error !== 'cancel') {
+            this.$message.error(this.isEdit ? '更新失败' : '添加失败')
+            console.error(
+              this.isEdit ? '更新提取材料失败:' : '添加提取材料失败:',
+              error
+            )
+          }
+        }
+      },
+
+      // 文件上传前验证
+      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.extractForm.fileList &&
+          this.extractForm.fileList.length >= 1
+        ) {
+          this.$message.warning('只能上传一个文件,请先删除已上传的文件')
+          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,
+            }
+
+            if (onSuccess) {
+              onSuccess(fileInfo, fileObj)
+              this.extractForm.attachmentUrl = fileInfo.savePath || fileInfo.url
+            }
+
+            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.extractForm.fileList = fileList.map((item) => {
+          if (item.uid === file.uid && response) {
+            return {
+              ...item,
+              url: response.savePath || response.url || item.url,
+              response: response,
+            }
+          }
+          return item
+        })
+      },
+
+      // 上传进度回调
+      handleFileUploadProgress(event, file, fileList) {
+        // element-ui 会自动处理上传进度显示
+      },
+
+      // 上传失败回调
+      handleFileUploadError(err, file, fileList) {
+        console.error('文件上传错误:', err)
+        this.$message.error(`${file.name} 上传失败`)
+        // 从文件列表中移除失败的文件
+        this.extractForm.fileList = fileList.filter(
+          (item) => item.uid !== file.uid
+        )
+      },
+
+      // 超出文件数量限制
+      handleFileExceed(files, fileList) {
+        this.$message.warning(
+          `当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${fileList.length} 个文件`
+        )
+      },
+
+      // 移除文件
+      handleFileRemove(file, fileList) {
+        this.extractForm.fileList = fileList
+        this.extractForm.attachmentUrl = ''
+        this.$message.info(`${file.name} 已移除`)
+      },
+
+      // 格式化日期
+      formatDate(row, column, cellValue) {
+        if (!cellValue) return ''
+        try {
+          const date = new Date(cellValue)
+          if (isNaN(date.getTime())) return ''
+          return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
+            2,
+            '0'
+          )}-${String(date.getDate()).padStart(2, '0')} ${String(
+            date.getHours()
+          ).padStart(2, '0')}:${String(date.getMinutes()).padStart(
+            2,
+            '0'
+          )}:${String(date.getSeconds()).padStart(2, '0')}`
+        } catch (error) {
+          return cellValue
+        }
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .extract-material-container {
+    padding: 20px;
+  }
+
+  .extract-controls {
+    margin-bottom: 20px;
+    text-align: right;
+  }
+
+  .dialog-footer {
+    text-align: center;
+  }
+
+  /* 操作按钮样式优化 */
+  .el-button--small {
+    margin-right: 8px;
+  }
+
+  /* 表格行悬停效果 */
+  .el-table--enable-row-hover .el-table__body tr:hover > td {
+    background-color: #f5f7fa;
+  }
+</style>

+ 109 - 0
src/components/task/components/messageNotify.vue

@@ -0,0 +1,109 @@
+<template>
+  <div>
+    <el-table style="width: 100%; margin-top: 20px" border :data="formData">
+      <el-table-column prop="id" label="序号" width="120" align="center">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="noticeTitle"
+        label="消息主题"
+        width="200"
+        align="center"
+      ></el-table-column>
+      <el-table-column
+        prop="noticeSource"
+        label="消息来源"
+        width="150"
+        align="center"
+      ></el-table-column>
+      <el-table-column
+        prop="noticeContent"
+        label="消息内容"
+        min-width="350"
+        align="center"
+      ></el-table-column>
+      <el-table-column
+        prop="createTime"
+        label="发送时间"
+        width="230"
+        align="center"
+      ></el-table-column>
+    </el-table>
+    <el-pagination
+      background
+      layout="total, sizes, prev, pager, next"
+      :current-page="pagination.currentPage"
+      :page-sizes="[10, 20, 30, 50]"
+      :page-size="pagination.pageSize"
+      :total="pagination.total"
+      style="margin-top: 20px; text-align: right"
+      @current-change="$emit('handle-page-change', $event)"
+      @size-change="$emit('handle-size-change', $event)"
+    />
+  </div>
+</template>
+<script>
+  import { sendMessage } from '@/api/auditTaskProcessing'
+  export default {
+    name: 'MessageNoticeTab',
+    props: {
+      id: {
+        type: String,
+        default: '',
+      },
+      formData: {
+        type: Array,
+        default: () => [],
+      },
+      pagination: {
+        type: Object,
+        default: () => ({ currentPage: 1, pageSize: 10, total: 0 }),
+      },
+    },
+    mounted() {
+      this.getNoticeList()
+    },
+    methods: {
+      // 获取消息通知列表
+      async getNoticeList() {
+        if (!this.id) {
+          return
+        }
+        this.$emit('update:loading', true)
+        const params = {
+          taskId: this.id,
+          pageNum: this.pagination.currentPage,
+          pageSize: this.pagination.pageSize,
+        }
+        try {
+          const res = await sendMessage(params)
+          console.log('消息通知', res)
+          if (res && res.code === 200 && res.value) {
+            this.$emit('update:formData', res.value.records || [])
+            this.$emit('update:pagination', {
+              ...this.pagination,
+              total: res.value.total || 0,
+            })
+          } else {
+            this.$emit('update:formData', [])
+            this.$emit('update:pagination', {
+              ...this.pagination,
+              total: 0,
+            })
+          }
+        } catch (err) {
+          console.error('获取消息通知失败', err)
+          this.$emit('update:formData', [])
+          this.$emit('update:pagination', {
+            ...this.pagination,
+            total: 0,
+          })
+        } finally {
+          this.$emit('update:loading', false)
+        }
+      },
+    },
+  }
+</script>

+ 435 - 0
src/components/task/components/submitData.vue

@@ -0,0 +1,435 @@
+<template>
+  <div class="audit-review">
+    <div
+      v-for="category in materialCategories"
+      :key="category.type"
+      class="material-category"
+    >
+      <h3 class="category-title">{{ category.typeName }}</h3>
+      <el-table
+        v-loading="loading"
+        :data="category.items"
+        stripe
+        style="width: 100%"
+      >
+        <el-table-column
+          prop="orderNum"
+          label="序号"
+          width="80"
+          align="center"
+        />
+
+        <el-table-column
+          prop="informationName"
+          label="报送资料"
+          min-width="200"
+        />
+
+        <el-table-column label="资料类型" width="120" align="center">
+          <template slot-scope="scope">
+            <span
+              :class="{
+                'template-tag': scope.row.templateId,
+              }"
+            >
+              {{ getFormatType(scope.row.formatRequired) }}
+            </span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="是否必项" width="100" align="center">
+          <template slot-scope="scope">
+            {{ scope.row.isRequired === '1' ? '是' : '否' }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="是否上传" width="100" align="center">
+          <template slot-scope="scope">
+            <span
+              :style="{
+                color: scope.row.isUpload === '1' ? '#67C23A' : '#F56C6C',
+              }"
+            >
+              {{ scope.row.isUpload === '1' ? '已上传' : '未上传' }}
+            </span>
+          </template>
+        </el-table-column>
+
+        <el-table-column
+          prop="createTime"
+          label="上传时间"
+          width="150"
+          align="center"
+        />
+        <el-table-column label="初审结果" width="100" align="center">
+          <template slot-scope="scope">
+            <span
+              :class="{
+                'result-pending':
+                  !scope.row.auditedStatus || scope.row.auditedStatus === '0',
+                'result-pass': scope.row.auditedStatus === '1',
+                'result-fail': scope.row.auditedStatus === '2',
+              }"
+            >
+              {{
+                !scope.row.auditedStatus || scope.row.auditedStatus === '0'
+                  ? '未审核'
+                  : scope.row.auditedStatus === '1'
+                  ? '通过'
+                  : '不通过'
+              }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" align="center">
+          <template slot-scope="scope">
+            <template v-if="scope.row.isUpload === '1'">
+              <el-button
+                v-if="
+                  scope.row.auditedStatus === '0' &&
+                  scope.row.currentNode === 'clcs'
+                "
+                type="text"
+                size="small"
+                @click="handleAuditMaterial(scope.row)"
+              >
+                审核
+              </el-button>
+              <el-button
+                type="text"
+                size="small"
+                @click="handleViewDownload(scope.row)"
+              >
+                查看
+              </el-button>
+            </template>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 资料审核弹窗 -->
+    <el-dialog
+      title="资料审核"
+      :visible.sync="showAuditDialog"
+      width="400px"
+      center
+      :modal="false"
+      append-to-body
+    >
+      <div class="audit-material-info">
+        <p>
+          <strong>资料名称:</strong>
+          {{
+            (currentAuditMaterial && currentAuditMaterial.informationName) || ''
+          }}
+        </p>
+      </div>
+      <el-form ref="auditForm" :model="auditForm" label-width="80px">
+        <el-form-item label="审核结果" prop="auditedStatus">
+          <el-radio-group v-model="auditForm.auditedStatus">
+            <el-radio label="1">审核通过</el-radio>
+            <el-radio label="2">不通过</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="showAuditDialog = false">取消</el-button>
+        <el-button type="primary" @click="handleAuditSubmit">提交</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style scoped>
+  .result-pending {
+    color: #909399;
+  }
+
+  .result-pass {
+    color: #67c23a;
+  }
+
+  .result-fail {
+    color: #f56c6c;
+  }
+</style>
+
+<script>
+  import {
+    getTaskRequirementList,
+    addOrUpdateTaskRequirement,
+  } from '@/api/audit/auditIndex'
+  import {
+    getDataPreliminaryReviewButton,
+    doProcessBtn,
+  } from '@/api/dataPreliminaryReview'
+
+  export default {
+    name: 'AuditReview',
+    props: {
+      id: {
+        type: [String, Number],
+        default: null,
+      },
+      currentNode: {
+        type: String,
+        default: '',
+      },
+    },
+    data() {
+      return {
+        buttonData: [], //资料初审按钮数据
+        activeTab: 'materials', // 当前激活的标签页
+        showRejectDialog: false, // 初审退回弹窗显示状态
+        showAbortDialog: false, // 中止监审弹窗显示状态
+        loading: false, // 加载状态
+        // 初审退回表单数据
+        rejectForm: {
+          opinion: '在初审阶段,需补充完善相关材料。',
+          sendMethods: ['站内消息', '短信通知'],
+        },
+        // 中止监审表单数据
+        abortForm: {
+          opinion:
+            '在初审阶段,该企业资料不符合审查要求,中止该单位成本监审资格。',
+          sendMethods: ['站内消息', '短信通知'],
+        },
+
+        // 资料审核弹窗状态
+        showAuditDialog: false,
+        // 当前审核的资料
+        currentAuditMaterial: null,
+        // 资料审核表单数据
+        auditForm: {
+          auditedStatus: '通过', // 默认审核通过
+        },
+        // 报送资料表格数据
+        materialData: [],
+        // 按类型分组的材料数据
+        materialCategories: [],
+      }
+    },
+    watch: {
+      // 监听标签页切换事件
+      activeTab(newTab) {
+        // 当切换到成本调查表标签页时,模拟加载过程
+        if (newTab === 'costSurvey') {
+          this.loadCostSurveyData()
+        }
+      },
+    },
+    created() {
+      this.loadMaterialData()
+    },
+    mounted() {},
+    methods: {
+      // 加载报送资料数据
+      async loadMaterialData() {
+        try {
+          this.loading = true
+          const response = await getTaskRequirementList(this.id)
+          console.log('接口返回数据:', response)
+          // 尝试多种可能的数据结构
+          this.materialData = response.value || response.data || response || []
+          // 确保 materialData 是数组
+          if (!Array.isArray(this.materialData)) {
+            this.materialData = []
+          }
+          this.processMaterialData()
+        } catch (error) {
+          console.error('获取报送资料数据失败:', error)
+          this.$message.error('获取报送资料数据失败')
+        } finally {
+          this.loading = false
+        }
+      },
+      // 处理材料数据按类型分组
+      processMaterialData() {
+        // 确保 materialData 存在且为数组
+        if (!this.materialData || !Array.isArray(this.materialData)) {
+          this.materialCategories = []
+          return
+        }
+        const typeMap = {
+          1: '综合性资料',
+          2: '财务会计资料',
+          3: '其他资料',
+        }
+        const groupedData = {}
+        this.materialData.forEach((item) => {
+          const type = item.informationType
+          if (!groupedData[type]) {
+            groupedData[type] = {
+              type: type,
+              typeName: typeMap[type] || '其他资料',
+              items: [],
+            }
+          }
+          groupedData[type].items.push(item)
+        })
+        // 按指定顺序排列并为每个分类下的材料重新分配序号
+        this.materialCategories = []
+        ;['1', '2', '3'].forEach((type) => {
+          if (groupedData[type]) {
+            // 为当前分类下的材料重新分配序号,从1开始递增
+            groupedData[type].items.forEach((item, index) => {
+              item.orderNum = index + 1
+            })
+            this.materialCategories.push(groupedData[type])
+          }
+        })
+      },
+      // 获取格式类型
+      getFormatType(formatRequired) {
+        const formatMap = {
+          1: '文档文件',
+          2: '预置模板',
+          3: 'Excel文件',
+        }
+        return formatMap[formatRequired] || '文档文件'
+      },
+      // 资料初审按钮点击事件
+      handleAuditPass(row) {
+        this.$confirm(`确定要${row.value}吗?`, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(() => {
+            // 这里可以添加初审通过的API调用逻辑
+            doProcessBtn({
+              taskId: this.id,
+              key: row.key,
+            })
+              .then((res) => {
+                console.log('接口返回数据:', res)
+                if (res.code === 200 && res.value) {
+                  this.$message({ type: 'success', message: res.value })
+                } else {
+                  this.$message({ type: 'error', message: res.message })
+                }
+              })
+              .catch((err) => {
+                this.$message({ type: 'error', message: err.message })
+              })
+          })
+          .catch(() => {
+            this.$message({ type: 'info', message: '已取消操作' })
+          })
+      },
+      // 查看下载文件
+      handleViewDownload(row) {
+        this.$message.info(`查看下载文件:${row.name}`)
+        // 这里可以添加查看下载文件的逻辑
+      },
+      // 查看报表
+      handleViewReport(row) {
+        this.$message.info(`查看报表:${row.name}`)
+        // 这里可以添加查看报表的逻辑
+      },
+      // 处理资料审核点击事件
+      handleAuditMaterial(row) {
+        this.currentAuditMaterial = { ...row } // 复制当前行数据
+        this.auditForm = {
+          auditedStatus:
+            this.currentAuditMaterial.auditStatus === '1' ? '通过' : '不通过',
+          auditOpinion: '',
+        }
+        this.showAuditDialog = true
+      },
+
+      // 提交资料审核结果
+      async handleAuditSubmit() {
+        try {
+          this.loading = true
+          // 更新当前审核材料的审核状态
+          this.currentAuditMaterial.auditedStatus = this.auditForm.auditedStatus
+          this.currentAuditMaterial.auditResult = this.auditForm.auditedStatus
+
+          // 调用接口提交整个对象
+          await addOrUpdateTaskRequirement(this.currentAuditMaterial)
+
+          this.$message({ type: 'success', message: '资料审核操作已提交' })
+
+          // 更新本地数据,使表格显示最新审核结果
+          this.loadMaterialData()
+
+          this.showAuditDialog = false
+        } catch (error) {
+          console.error('资料审核提交失败:', error)
+          this.$message.error('资料审核提交失败,请重试')
+        } finally {
+          this.loading = false
+        }
+      },
+    },
+  }
+</script>
+<style scoped>
+  .audit-review {
+    padding: 5px;
+    background-color: #ffffff;
+  }
+
+  .btn-group {
+    margin-bottom: 20px;
+  }
+
+  .btn-group .el-button {
+    margin-right: 10px;
+  }
+
+  .material-category {
+    margin-bottom: 30px;
+  }
+
+  .category-title {
+    margin: 0 0 15px 0;
+    padding: 8px 12px;
+    background-color: #f5f7fa;
+    border-left: 4px solid #409eff;
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+  }
+
+  .template-tag {
+    background-color: #e6f7ff;
+    color: #1677ff;
+    padding: 2px 8px;
+    border-radius: 4px;
+    font-size: 12px;
+  }
+
+  .result-pass {
+    color: #67c23a;
+    font-weight: bold;
+  }
+
+  .result-fail {
+    color: #f56c6c;
+    font-weight: bold;
+  }
+
+  /* 表格样式调整 */
+  .el-table th {
+    background-color: #fafafa;
+  }
+
+  /* 弹窗样式调整 */
+  .el-dialog__body {
+    padding: 20px;
+  }
+
+  .audit-material-info {
+    margin-bottom: 15px;
+    padding: 10px;
+    background-color: #f5f7fa;
+    border-radius: 4px;
+  }
+
+  .audit-material-info p {
+    margin: 0;
+  }
+</style>

+ 168 - 0
src/components/task/components/taskMixins.js

@@ -0,0 +1,168 @@
+export default {
+  data() {
+    return {
+      workflowList: [],
+      // 所有表单数据聚合
+      formData: {
+        // 监审立项信息表单
+        basicInfo: {
+          projectName: '',
+          relatedCatalog: '',
+          province: '',
+          city: '',
+          district: '',
+          auditedUnit: '',
+          auditBody: '',
+          planYear: '',
+          initiationType: '',
+          auditForm: '',
+          costYears: [{ value: '2022' }, { value: '2023' }, { value: '2024' }],
+          needAudit: false,
+          needEvaluation: false,
+          initiationReason: '',
+          // 修改为与图片一致的立项依据文件列表
+          initiationBasisFiles: [
+            { name: '太原市电力公司关于电网输配电价格调整申请表.pdf' },
+            { name: '******依据文件.pdf' },
+          ],
+          // 修改为与图片一致的其他材料文件列表
+          otherFiles: [{ name: '立项审批表.pdf' }],
+          auditLeader: '',
+          auditTeamMembers: '',
+          otherRequirements: '',
+        },
+
+        // 监审工作方案表单
+        workPlan: {
+          basicInfo: '',
+          stepsMethods: '',
+          otherContent: '',
+        },
+
+        // 材料表单(弹窗用)
+        material: {
+          category: '综合性材料',
+          name: '',
+          requirements: '',
+          format: '文档文件',
+          sort: '',
+          required: '否',
+        },
+
+        // 当前流程环节(弹窗用)
+        currentStep: {
+          manager: '',
+          deadline: '',
+        },
+
+        // 监审工作流程表单
+        workflow: {
+          startDate: '2025-01-20',
+          endDate: '2025-03-30',
+        },
+
+        // 文档表单(弹窗用)
+        document: {
+          template: 'costAuditNotice',
+          documentNumber: '晋成审(2025)11号',
+          auditedUnit: 'A公司',
+          auditProject: '',
+          auditMatters: '',
+          auditPeriod: '',
+          auditTeam: '',
+          teamLeader: '',
+          noticeDate: '',
+        },
+      },
+      // 数据列表聚合
+      materialData: {
+        list: [
+          {
+            id: 1,
+            index: 1,
+            category: '综合性材料',
+            name: '企业基本情况介绍',
+            requirements: '包括企业名称、企业地址、企业员工数量、……',
+            format: '文档文件',
+            sort: 1,
+          },
+          {
+            id: 2,
+            index: 2,
+            category: '综合性材料',
+            name: '组织结构图',
+            requirements: '……',
+            format: '文档文件',
+            sort: 2,
+          },
+          {
+            id: 3,
+            index: 3,
+            category: '综合性材料',
+            name: '营业执照',
+            requirements: '……',
+            format: '文档文件',
+            sort: 3,
+          },
+          {
+            id: 4,
+            index: 4,
+            category: '财务会计资料',
+            name: '财务会计报表',
+            requirements: '……',
+            format: 'excel文件',
+            sort: 4,
+          },
+          {
+            id: 5,
+            index: 5,
+            category: '财务会计资料',
+            name: '资产卡片',
+            requirements: '……',
+            format: '预置模板',
+            sort: 5,
+          },
+        ],
+      },
+      surveyData: {
+        list: [
+          {
+            id: 1,
+            index: 1,
+            name: '封面',
+            type: '模板定制',
+            required: '是',
+          },
+          {
+            id: 2,
+            index: 2,
+            name: '企业基本情况调查表',
+            type: '模板定制',
+            required: '是',
+          },
+          {
+            id: 3,
+            index: 3,
+            name: '企业成本费用调查表',
+            type: '模板定制',
+            required: '是',
+          },
+          {
+            id: 4,
+            index: 4,
+            name: '企业期间费用调查表',
+            type: '模板定制',
+            required: '是',
+          },
+          {
+            id: 5,
+            index: 5,
+            name: '企业职工薪酬调查表',
+            type: '模板定制',
+            required: '是',
+          },
+        ],
+      },
+    }
+  },
+}

+ 695 - 0
src/components/task/components/workDraft.vue

@@ -0,0 +1,695 @@
+<template>
+  <div class="work-draft-container">
+    <ht-editor
+      v-model="workingPaperContent"
+      class="working-paper-editor"
+      height="500px"
+      width="100%"
+      :config="editorConfig"
+      :disabled="disabled"
+      @ready="onEditorReady"
+    />
+    <!-- 工作底稿列表 -->
+    <div>
+      <el-button
+        type="primary"
+        size="small"
+        :disabled="disabled"
+        @click="handleAddWorkingPaper"
+      >
+        核增核减记录
+      </el-button>
+    </div>
+    <el-table
+      v-loading="loading"
+      :data="workingPaperRecords"
+      style="width: 100%; margin-top: 20px"
+      border
+    >
+      <!-- <el-table-column type="selection" width="55"></el-table-column> -->
+      <el-table-column prop="id" label="序号" width="80">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="auditSubject"
+        label="核增核减科目"
+        width="180"
+      ></el-table-column>
+      <el-table-column
+        prop="basicInfo"
+        label="基本情况"
+        width="200"
+      ></el-table-column>
+      <el-table-column prop="auditDesc" label="核增核减说明"></el-table-column>
+      <el-table-column
+        prop="auditTime"
+        label="时间"
+        width="180"
+      ></el-table-column>
+      <el-table-column label="附件" width="100">
+        <template slot-scope="scope">
+          <el-button
+            v-if="scope.row.attachments && scope.row.attachments.length > 0"
+            type="text"
+            size="small"
+            @click="handlePreviewWorkingPaperAttachment(scope.row)"
+          >
+            查看
+          </el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="150">
+        <template slot-scope="scope">
+          <el-button
+            type="text"
+            size="small"
+            :disabled="disabled"
+            @click="handleEditWorkingPaper(scope.row)"
+          >
+            修改
+          </el-button>
+          <el-button
+            type="text"
+            size="small"
+            :disabled="disabled"
+            @click="handleDeleteWorkingPaper(scope.row)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 工作底稿编辑弹窗 -->
+    <el-dialog
+      :title="workingPaperDialogTitle"
+      :visible.sync="workingPaperDialogVisible"
+      width="70%"
+      :modal="false"
+      append-to-body
+    >
+      <el-form
+        ref="workingPaperForm"
+        :model="workingPaperForm"
+        :rules="workingPaperRules"
+        label-width="120px"
+      >
+        <el-form-item label="被审核科目" prop="subject">
+          <el-input
+            v-model="workingPaperForm.subject"
+            placeholder="请输入被审核科目"
+          />
+        </el-form-item>
+        <el-form-item label="基本情况" prop="basicSituation">
+          <el-input
+            v-model="workingPaperForm.basicSituation"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入基本情况"
+          />
+        </el-form-item>
+        <el-form-item label="核增核减说明" prop="description">
+          <el-input
+            v-model="workingPaperForm.description"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入核增核减说明"
+          />
+        </el-form-item>
+        <el-form-item label="日期">
+          <el-date-picker
+            v-model="workingPaperForm.auditDate"
+            type="date"
+            placeholder="选择日期"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="时间范围">
+          <div style="display: flex; gap: 10px">
+            <el-time-picker
+              v-model="workingPaperForm.startTime"
+              format="HH:mm"
+              placeholder="开始时间"
+              style="flex: 1"
+            />
+            <span>-</span>
+            <el-time-picker
+              v-model="workingPaperForm.endTime"
+              format="HH:mm"
+              placeholder="结束时间"
+              style="flex: 1"
+            />
+          </div>
+        </el-form-item>
+        <el-form-item label="上传附件" prop="fileList">
+          <el-upload
+            class="upload-demo"
+            :action="''"
+            :http-request="handleFileUpload"
+            :on-remove="handleFileRemove"
+            :before-upload="beforeFileUpload"
+            :on-success="handleFileUploadSuccess"
+            :on-error="handleFileUploadError"
+            :on-progress="handleFileUploadProgress"
+            :file-list="workingPaperForm.fileList"
+            :limit="1"
+            :on-exceed="handleFileExceed"
+          >
+            <el-button
+              v-show="
+                !workingPaperForm.fileList ||
+                workingPaperForm.fileList.length === 0
+              "
+              size="small"
+              type="primary"
+            >
+              选择文件
+            </el-button>
+            <div slot="tip" class="el-upload__tip">
+              支持 pdf, doc, docx, xls, xlsx, csv 格式,单个文件不超过50MB
+            </div>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="workingPaperDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleWorkingPaperSubmit">
+          确定
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import {
+    getTaskDraftList,
+    addTaskDraft,
+    deleteTaskDraft,
+  } from '@/api/audit/taskDraft'
+  import { uploadFile } from '@/api/file'
+  export default {
+    name: 'WorkDraft',
+    props: {
+      id: {
+        type: [String, Number],
+        default: null,
+      },
+      disabled: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        loading: false,
+        editorConfig: {
+          // 可以在这里覆盖全局配置的选项
+          height: '300px',
+          placeholder: '请输入内容...',
+          excludeMenus: ['image', 'video'],
+        },
+        // 工作底稿数据
+        workingPaperContent: '工作底稿内容将在这里展示。',
+        // 工作底稿记录列表
+        workingPaperRecords: [],
+        // 工作底稿弹窗
+        workingPaperDialogVisible: false,
+        workingPaperDialogTitle: '添加工作底稿',
+        isEditWorkingPaper: false,
+        workingPaperForm: {
+          id: '',
+          subject: '',
+          basicSituation: '',
+          description: '',
+          auditDate: '',
+          startTime: '',
+          endTime: '',
+          attachments: [],
+          fileList: [],
+          attachmentUrl: '',
+        },
+        workingPaperRules: {
+          subject: [
+            { required: true, message: '请输入被审核科目', trigger: 'blur' },
+          ],
+          basicSituation: [
+            { required: true, message: '请输入基本情况', trigger: 'blur' },
+          ],
+          description: [
+            { required: true, message: '请输入核增核减说明', trigger: 'blur' },
+          ],
+        },
+      }
+    },
+    mounted() {
+      this.getWorkingPaperRecords()
+    },
+    methods: {
+      // 获取工作底稿列表
+      async getWorkingPaperRecords() {
+        if (!this.id) {
+          return
+        }
+        try {
+          this.loading = true
+          const res = await getTaskDraftList({ taskId: this.id })
+          if (res && res.value) {
+            // 处理数据,格式化时间显示
+            this.workingPaperRecords = (res.value || []).map((item) => {
+              // 格式化时间显示,如果后端返回的是 time 字段,直接使用
+              let auditTime = ''
+              if (item.time) {
+                auditTime = item.time
+              } else if (item.auditTime) {
+                auditTime = item.auditTime
+              } else if (item.auditDate) {
+                const date = new Date(item.auditDate)
+                const formattedDate = `${date.getFullYear()}-${String(
+                  date.getMonth() + 1
+                ).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
+                const startTime = item.startTime || '00:00'
+                const endTime = item.endTime || '23:59'
+                auditTime = `${formattedDate} ${startTime}-${endTime}`
+              }
+              // 字段名映射
+              return {
+                ...item,
+                auditSubject: item.subject || item.auditSubject,
+                basicInfo: item.basicSituation || item.basicInfo,
+                auditDesc: item.description || item.auditDesc,
+                auditTime: auditTime,
+              }
+            })
+          } else {
+            this.workingPaperRecords = []
+          }
+        } catch (error) {
+          this.$message.error('获取工作底稿列表失败')
+          console.error('获取工作底稿列表失败:', error)
+          this.workingPaperRecords = []
+        } finally {
+          this.loading = false
+        }
+      },
+      onEditorReady(editor) {
+        // 编辑器初始化完成后的回调
+        console.log('编辑器已就绪', editor)
+        // 可以在这里获取编辑器实例,进行更多操作
+      },
+      // 工作底稿操作
+      handleAddWorkingPaper() {
+        this.isEditWorkingPaper = false
+        this.workingPaperDialogTitle = '添加工作底稿'
+        this.workingPaperForm = {
+          id: '',
+          subject: '',
+          basicSituation: '',
+          description: '',
+          auditDate: new Date(),
+          startTime: '',
+          endTime: '',
+          attachments: [],
+          fileList: [],
+          attachmentUrl: '',
+        }
+        // 重置表单验证状态
+        if (this.$refs.workingPaperForm) {
+          this.$refs.workingPaperForm.resetFields()
+        }
+        this.workingPaperDialogVisible = true
+      },
+
+      handleEditWorkingPaper(row) {
+        this.isEditWorkingPaper = true
+        this.workingPaperDialogTitle = '修改工作底稿'
+        // 解析时间字符串
+        let auditDate = new Date()
+        let startTime = null
+        let endTime = null
+
+        // 优先使用 time 字段,如果没有则使用 auditTime,再没有则使用分开的日期时间
+        const timeStr = row.time || row.auditTime || ''
+        if (timeStr) {
+          const timeParts = timeStr.split(' ')
+          if (timeParts.length >= 2) {
+            const date = timeParts[0]
+            const timeRange = timeParts[1].split('-')
+            auditDate = new Date(date)
+            // 将时间字符串转换为 Date 对象
+            if (timeRange[0]) {
+              const [hours, minutes] = timeRange[0].split(':')
+              startTime = new Date()
+              startTime.setHours(
+                parseInt(hours) || 0,
+                parseInt(minutes) || 0,
+                0,
+                0
+              )
+            }
+            if (timeRange[1]) {
+              const [hours, minutes] = timeRange[1].split(':')
+              endTime = new Date()
+              endTime.setHours(
+                parseInt(hours) || 0,
+                parseInt(minutes) || 0,
+                0,
+                0
+              )
+            }
+          }
+        } else if (row.auditDate) {
+          auditDate = new Date(row.auditDate)
+          // 将时间字符串转换为 Date 对象
+          if (row.startTime) {
+            const [hours, minutes] = row.startTime.split(':')
+            startTime = new Date()
+            startTime.setHours(
+              parseInt(hours) || 0,
+              parseInt(minutes) || 0,
+              0,
+              0
+            )
+          }
+          if (row.endTime) {
+            const [hours, minutes] = row.endTime.split(':')
+            endTime = new Date()
+            endTime.setHours(parseInt(hours) || 0, parseInt(minutes) || 0, 0, 0)
+          }
+        }
+
+        // 处理文件列表
+        let fileList = []
+        if (row.attachmentUrl) {
+          fileList = [
+            {
+              name: row.fileName || '附件',
+              url: row.attachmentUrl,
+              response: { savePath: row.attachmentUrl },
+            },
+          ]
+        }
+
+        // 字段名映射
+        this.workingPaperForm = {
+          ...row,
+          subject: row.subject || row.auditSubject || '',
+          basicSituation: row.basicSituation || row.basicInfo || '',
+          description: row.description || row.auditDesc || '',
+          auditDate: auditDate,
+          startTime: startTime,
+          endTime: endTime,
+          fileList: fileList,
+          attachmentUrl: row.attachmentUrl || '',
+        }
+        this.workingPaperDialogVisible = true
+      },
+
+      async handleDeleteWorkingPaper(row) {
+        try {
+          await this.$confirm('确定要删除这条工作底稿吗?', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning',
+          })
+
+          const res = await deleteTaskDraft({ id: row.id })
+          if (res.code === 200) {
+            this.$message.success('工作底稿已删除')
+            // 重新获取列表
+            this.getWorkingPaperRecords()
+          } else {
+            this.$message.error(res.message || '删除失败')
+          }
+        } catch (error) {
+          if (error !== 'cancel') {
+            this.$message.error('删除失败')
+            console.error('删除工作底稿失败:', error)
+          }
+        }
+      },
+
+      async handleWorkingPaperSubmit() {
+        try {
+          await this.$refs.workingPaperForm.validate()
+
+          // 格式化日期
+          const auditDate = new Date(this.workingPaperForm.auditDate)
+          const formattedDate = `${auditDate.getFullYear()}-${String(
+            auditDate.getMonth() + 1
+          ).padStart(2, '0')}-${String(auditDate.getDate()).padStart(2, '0')}`
+
+          // 格式化时间(如果没有选择时间,使用默认值)
+          const startTime = this.workingPaperForm.startTime
+            ? this.formatTime(this.workingPaperForm.startTime)
+            : '00:00'
+          const endTime = this.workingPaperForm.endTime
+            ? this.formatTime(this.workingPaperForm.endTime)
+            : '23:59'
+
+          // 合并日期和时间范围为 time 字段
+          const time = `${formattedDate} ${startTime}-${endTime}`
+
+          // 构造提交数据
+          const formData = {
+            taskId: this.id,
+            subject: this.workingPaperForm.subject,
+            basicSituation: this.workingPaperForm.basicSituation,
+            description: this.workingPaperForm.description,
+            time: time,
+            attachmentUrl: this.workingPaperForm.attachmentUrl,
+          }
+
+          // 如果是编辑模式,添加 id
+          if (this.isEditWorkingPaper && this.workingPaperForm.id) {
+            formData.id = this.workingPaperForm.id
+          }
+
+          const res = await addTaskDraft(formData)
+          if (res.code === 200) {
+            this.$message.success(
+              this.isEditWorkingPaper ? '工作底稿修改成功' : '工作底稿添加成功'
+            )
+            this.workingPaperDialogVisible = false
+            // 重新获取列表
+            this.getWorkingPaperRecords()
+          } else {
+            this.$message.error(
+              res.message || (this.isEditWorkingPaper ? '修改失败' : '添加失败')
+            )
+          }
+        } catch (error) {
+          if (error !== 'cancel') {
+            this.$message.error(
+              this.isEditWorkingPaper ? '修改失败' : '添加失败'
+            )
+            console.error('提交工作底稿失败:', error)
+          }
+        }
+      },
+
+      // 格式化时间为 HH:mm 格式
+      formatTime(time) {
+        if (!time) return ''
+        if (typeof time === 'string') {
+          // 如果已经是字符串格式 HH:mm,直接返回
+          if (/^\d{2}:\d{2}$/.test(time)) {
+            return time
+          }
+        }
+        // 如果是 Date 对象
+        if (time instanceof Date) {
+          const hours = String(time.getHours()).padStart(2, '0')
+          const minutes = String(time.getMinutes()).padStart(2, '0')
+          return `${hours}:${minutes}`
+        }
+        return ''
+      },
+
+      handlePreviewWorkingPaperAttachment(row) {
+        const attachments =
+          row.attachments && Array.isArray(row.attachments)
+            ? row.attachments
+            : []
+        if (attachments.length === 0) {
+          this.$message.info('暂无附件')
+          return
+        }
+        this.$message({
+          type: 'info',
+          message: `预览附件:${attachments.join(', ')}`,
+        })
+      },
+
+      // 文件上传前验证
+      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.workingPaperForm.fileList &&
+          this.workingPaperForm.fileList.length >= 1
+        ) {
+          this.$message.warning('只能上传一个文件,请先删除已上传的文件')
+          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,
+            }
+
+            if (onSuccess) {
+              onSuccess(fileInfo, fileObj)
+              this.workingPaperForm.attachmentUrl =
+                fileInfo.savePath || fileInfo.url
+            }
+
+            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.workingPaperForm.fileList = fileList.map((item) => {
+          if (item.uid === file.uid && response) {
+            return {
+              ...item,
+              url: response.savePath || response.url || item.url,
+              response: response,
+            }
+          }
+          return item
+        })
+      },
+
+      // 上传进度回调
+      handleFileUploadProgress(event, file, fileList) {
+        // element-ui 会自动处理上传进度显示
+      },
+
+      // 上传失败回调
+      handleFileUploadError(err, file, fileList) {
+        console.error('文件上传错误:', err)
+        this.$message.error(`${file.name} 上传失败`)
+        // 从文件列表中移除失败的文件
+        this.workingPaperForm.fileList = fileList.filter(
+          (item) => item.uid !== file.uid
+        )
+      },
+
+      // 超出文件数量限制
+      handleFileExceed(files, fileList) {
+        this.$message.warning(
+          `当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${fileList.length} 个文件`
+        )
+      },
+
+      // 移除文件
+      handleFileRemove(file, fileList) {
+        this.workingPaperForm.fileList = fileList
+        this.workingPaperForm.attachmentUrl = ''
+        this.$message.info(`${file.name} 已移除`)
+      },
+    },
+  }
+</script>
+
+<style scoped>
+  .work-draft-container {
+    padding: 20px;
+  }
+
+  .working-paper-editor {
+    margin-bottom: 20px;
+  }
+</style>

+ 1 - 1
src/views/costAudit/auditInfo/auditManage/extractMaterial.vue

@@ -55,7 +55,7 @@
         align="center"
         show-overflow-tooltip
       ></el-table-column>
-      <el-table-column label="操作" width="120" fixed="right">
+      <el-table-column label="操作" width="180" fixed="right">
         <template slot-scope="scope">
           <el-button
             type="primary"

+ 19 - 2
src/views/costAudit/auditInfo/auditManage/index.vue

@@ -194,6 +194,13 @@
       @refresh="handleMainRefresh"
     />
     <taskInfo ref="taskInfo" />
+    <!-- 成本监审信息弹窗 -->
+    <cbjs-info
+      :id="cbjsInfoData && cbjsInfoData.id"
+      :visible.sync="cbjsInfoVisible"
+      :current-node="cbjsInfoData && cbjsInfoData.currentNode"
+      :current-status="cbjsInfoData && cbjsInfoData.status"
+    />
   </div>
 </template>
 <script>
@@ -203,11 +210,13 @@
   // 成本监审任务列表API
   import { getReviewTaskList } from '@/api/audit/auditIndex'
   import taskInfo from '@/components/task/taskInfo.vue'
+  import cbjsInfo from '@/components/task/cbjsInfo.vue'
   export default {
     name: 'CostAuditManagement',
     components: {
       detailsDialog,
       taskInfo,
+      cbjsInfo,
       mainDetailsDialog,
     },
     data() {
@@ -225,6 +234,9 @@
         // 详情弹窗相关
         detailsVisible: false,
         selectedProject: null,
+        // cbjsInfo弹窗相关
+        cbjsInfoVisible: false,
+        cbjsInfoData: null,
       }
     },
 
@@ -428,9 +440,14 @@
         this.currentPage = current
         this.loadAuditProjectList()
       },
-      // 查看
+      // 查看 - 修改为打开cbjsInfo弹窗
       handleMessage(row, type) {
-        this.$refs.taskInfo.open(row, type)
+        if (type === 'chengben') {
+          this.cbjsInfoData = row
+          this.cbjsInfoVisible = true
+        } else {
+          this.$refs.taskInfo.open(row, type)
+        }
       },
     },
   }