Bladeren bron

Merge branch 'master' of http://1.71.9.215:3000/feiyi/cbjsxt-front-master

luzhixia 1 maand geleden
bovenliggende
commit
f31d8d212f

+ 324 - 0
src/components/task/TaskCustomizedReleaseDialog.vue

@@ -0,0 +1,324 @@
+<template>
+  <el-dialog
+    :visible.sync="visible"
+    title="成本监审任务制定"
+    width="80%"
+    top="5vh"
+    :close-on-click-modal="false"
+    class="audit-task-manage-dialog"
+    @close="handleClose"
+  >
+    <div class="audit-task-manage-container">
+      <!-- 页面标题(弹窗内可选保留,视设计需求,可注释掉) -->
+      <!-- <div class="page-header">
+        <h3>成本监审任务制定</h3>
+      </div> -->
+
+      <!-- 操作按钮 -->
+      <!-- <div class="operation-bar">
+        <el-button
+          v-if="activeTab === 'scenario'"
+          plain
+          type="primary"
+          :loading="loading.save"
+          icon="el-icon-success"
+          :disabled="isView"
+          @click="handleSave"
+        >
+          保存
+        </el-button>
+        <el-button
+          plain
+          type="primary"
+          class="ml20"
+          icon="el-icon-arrow-left"
+          @click="handleBack"
+        >
+          返回
+        </el-button>
+      </div> -->
+
+      <!-- 标签页容器 -->
+      <div class="tabs-container">
+        <el-tabs
+          v-model="activeTab"
+          type="border-card"
+          @tab-click="handleTabClick"
+        >
+          <!-- 监审立项信息 -->
+          <el-tab-pane label="监审立项信息" name="basicInfo">
+            <basicInfoTab :project="formData.basicInfo" :is-view="isView" />
+          </el-tab-pane>
+
+          <!-- 监审工作方案 -->
+          <el-tab-pane label="监审工作方案" name="scenario">
+            <el-form
+              v-loading="scenarioData.saveScenario"
+              :model="formData.workPlan"
+              label-width="150px"
+              label-position="top"
+              style="width: 50%"
+              :disabled="isView"
+            >
+              <el-form-item label="监审项目基本情况" class="form-section">
+                <el-input
+                  v-model="formData.workPlan.basicInfo"
+                  type="textarea"
+                  :rows="5"
+                  placeholder="(非必填写)"
+                  class="form-textarea"
+                  :maxlength="500"
+                  show-word-limit
+                />
+              </el-form-item>
+
+              <el-form-item
+                label="监审的范围、内容、重点及其他重要事项"
+                class="form-section"
+              >
+                <el-input
+                  v-model="formData.workPlan.scopeContent"
+                  type="textarea"
+                  :rows="5"
+                  placeholder="(非必填写)"
+                  class="form-textarea"
+                  :maxlength="500"
+                  show-word-limit
+                />
+              </el-form-item>
+
+              <el-form-item label="监审的步骤和方法" class="form-section">
+                <el-input
+                  v-model="formData.workPlan.stepsMethods"
+                  type="textarea"
+                  :rows="5"
+                  placeholder="(非必填写)"
+                  class="form-textarea"
+                  :maxlength="500"
+                  show-word-limit
+                />
+              </el-form-item>
+
+              <el-form-item label="其他有关内容" class="form-section">
+                <el-input
+                  v-model="formData.workPlan.otherContent"
+                  type="textarea"
+                  :rows="5"
+                  placeholder="(非必填写)"
+                  class="form-textarea"
+                  :maxlength="500"
+                  show-word-limit
+                />
+              </el-form-item>
+
+              <el-form-item label="相关资料" class="form-section">
+                <div class="file-upload-wrapper">
+                  <UploadComponent
+                    :upload-mode="'multiple'"
+                    :max-size="50 * 1024 * 1024"
+                    :allowed-types="['xlsx', 'xls', 'doc', 'docx', 'pdf']"
+                    :files-list="formData.workPlan.attachmentIds"
+                    button-text="上传附件"
+                    :is-disabled="isView"
+                    @removeFile="removeFile"
+                    @saveFiles="saveFiles"
+                  />
+                </div>
+              </el-form-item>
+            </el-form>
+          </el-tab-pane>
+
+          <!-- 报送资料要求 -->
+          <el-tab-pane label="报送资料要求" name="material">
+            <materialTab :project="project" :is-view="isView" />
+          </el-tab-pane>
+
+          <!-- 成本调查表 -->
+          <el-tab-pane label="成本调查表" name="survey">
+            <surveyTab :project="project" :is-view="isView" />
+          </el-tab-pane>
+
+          <!-- 监审工作流程 -->
+          <el-tab-pane label="监审工作流程" name="workflow">
+            <workflowTab
+              :project="project"
+              :is-view="isView"
+              :workflow-data="workflowData"
+            />
+          </el-tab-pane>
+
+          <!-- 监审通知 -->
+          <el-tab-pane label="监审通知" name="auditNotice">
+            <auditNoticeTab
+              ref="auditNoticeTab"
+              :project="project"
+              :is-view="false"
+              :document-data="documentData"
+              @refresh="getDocumentData"
+              @paginationChange="handlePaginationChange"
+            />
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+  import UploadComponent from '@/components/costAudit/UploadComponent.vue'
+  import basicInfoTab from './mounTaskComponents/basicInfoTab.vue'
+  import materialTab from './mounTaskComponents/materialTab.vue'
+  import surveyTab from './mounTaskComponents/surveyTab.vue'
+  import workflowTab from './mounTaskComponents/workflowTab.vue'
+  import auditNoticeTab from './mounTaskComponents/auditNoticeTab.vue'
+  import { taskMixin } from './mounTaskComponents/index.js'
+  import {
+    addCostProjectScenario,
+    updateCostProjectScenario,
+  } from '@/api/taskCustomizedRelease.js'
+
+  export default {
+    name: 'TaskCustomizedReleaseDialog',
+    components: {
+      UploadComponent,
+      basicInfoTab,
+      materialTab,
+      surveyTab,
+      workflowTab,
+      auditNoticeTab,
+    },
+    mixins: [taskMixin],
+    props: {
+      // 弹窗显示控制
+      visible: {
+        type: Boolean,
+        default: false,
+      },
+      // 项目信息
+      project: {
+        type: Object,
+        default: () => ({}),
+      },
+      // 是否查看模式
+      isView: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {}
+    },
+    watch: {
+      // 弹窗打开时默认切换到“监审立项信息”标签
+      visible(newVal) {
+        if (newVal) {
+          this.activeTab = 'basicInfo'
+        }
+      },
+    },
+    mounted() {
+      this.handleTabClick()
+    },
+    methods: {
+      // 保存
+      handleSave() {
+        switch (this.activeTab) {
+          case 'scenario':
+            this.saveWorkPlan()
+            break
+          case 'material':
+            this.saveMaterial()
+            break
+          case 'auditNotice':
+            this.$refs.auditNoticeTab &&
+              this.$refs.auditNoticeTab.handleSaveDocument &&
+              this.$refs.auditNoticeTab.handleSaveDocument()
+            break
+          default:
+            break
+        }
+      },
+      //  saveFiles 方法(保持原 tabs.vue 逻辑)
+      saveFiles(data) {
+        try {
+          if (!Array.isArray(this.formData.workPlan.attachmentIds)) {
+            this.formData.workPlan.attachmentIds = []
+          }
+          if (!Array.isArray(data)) {
+            console.warn('saveFiles方法期望接收数组类型参数')
+            return
+          }
+          const newPaths = data
+            .map((element) => {
+              if (typeof element === 'string') {
+                return element
+              }
+              return element?.savePath || element?.filePath || null
+            })
+            .filter((path) => path && typeof path === 'string')
+          const uniquePaths = [
+            ...new Set([...this.formData.workPlan.attachmentIds, ...newPaths]),
+          ]
+          this.formData.workPlan.attachmentIds = uniquePaths
+        } catch (error) {
+          console.error('处理文件路径时发生错误:', error)
+        }
+      },
+      //  removeFile 方法(保持原 tabs.vue 逻辑)
+      removeFile(index, removedFile) {
+        try {
+          const currentPaths = this.formData.workPlan.attachmentIds || []
+          if (currentPaths.length > 0 && typeof currentPaths[0] === 'string') {
+            currentPaths.splice(index, 1)
+            this.formData.workPlan.attachmentIds = [...currentPaths]
+          } else {
+            const filteredPaths = currentPaths.filter((item) => {
+              if (typeof item === 'string') {
+                const fileName = item.substring(item.lastIndexOf('/') + 1)
+                return fileName !== removedFile.fileName
+              }
+              return (
+                item !== removedFile && item.fileName !== removedFile.fileName
+              )
+            })
+            this.formData.workPlan.attachmentIds = filteredPaths
+          }
+        } catch (error) {
+          console.error('删除文件时发生错误:', error)
+        }
+      },
+      // 保存工作方案(与原 tabs.vue 完全一致)
+      saveWorkPlan() {
+        this.scenarioData.saveScenario = true
+        const data = {
+          ...this.formData.workPlan,
+          attachmentIds: this.formData.workPlan.attachmentIds.join(','),
+          projectId: this.project.projectId,
+        }
+        if (this.scenarioData.addScenario) {
+          addCostProjectScenario(data).then(() => {
+            this.scenarioData.saveScenario = false
+            this.$message.success('保存成功')
+          })
+        } else {
+          updateCostProjectScenario(data).then(() => {
+            this.scenarioData.saveScenario = false
+            this.$message.success('保存成功')
+          })
+        }
+      },
+      // 返回
+      handleBack() {
+        this.$emit('backToList')
+      },
+      handleClose() {
+        this.$emit('update:visible', false)
+        this.$emit('close')
+      },
+    },
+  }
+</script>
+
+<style scoped lang="scss">
+  @import '@/styles/costAudit.scss';
+</style>

+ 1039 - 0
src/components/task/mounTaskComponents/auditNoticeTab.vue

@@ -0,0 +1,1039 @@
+<template>
+  <!-- 直接复用原 auditNoticeTab 代码 -->
+  <div class="catalog-manage">
+    <div class="documents-layout">
+      <!-- 左侧文书类型列表 -->
+      <div class="documents-type-list">
+        <h3>监审文书类型:</h3>
+        <div
+          v-for="type in documentData.documentTypes"
+          :key="type.id"
+          class="type-item"
+          :class="{ active: activeDocumentTypeId === type.id }"
+          @click="handleDocumentTypeClick(type)"
+        >
+          {{ type.documentName }}
+        </div>
+      </div>
+
+      <!-- 右侧文书列表表格 -->
+      <div class="documents-content">
+        <div class="operation-bar">
+          <!-- <el-button
+            v-if="!isView"
+            plain
+            type="success"
+            icon="el-icon-circle-plus"
+            @click="handleGenerateDocument"
+          >
+            生成文书
+          </el-button> -->
+        </div>
+        <CostAuditTable
+          :table-data="documentData.list"
+          :columns="documentData.documentColumns"
+          :show-index="true"
+          :show-pagination="true"
+          :show-action-column="true"
+          :pagination="documentData.pagination"
+          @pagination-change="handlePaginationChange"
+        >
+          <template #documentId="{ row }">
+            {{ getDocumenType(row) }}
+          </template>
+          <template #enterpriseId="{ row }">
+            {{ getEnterpriseName(row) }}
+          </template>
+          <template #generateTime="{ row }">
+            <div>
+              {{ row.generateTime ? row.generateTime.split(' ')[0] : '' }}
+            </div>
+            <div>
+              {{ row.generateTime ? row.generateTime.split(' ')[1] : '' }}
+            </div>
+          </template>
+          <template #scanDocumentUrl="scope">
+            <el-button
+              v-if="!isView"
+              type="text"
+              size="mini"
+              @click="handleUploadScan(scope.row, 'scanDocumentUrl')"
+            >
+              上传附件
+            </el-button>
+            <el-button
+              type="text"
+              size="mini"
+              @click="handleViewScan(scope.row.scanDocumentUrl)"
+            >
+              查看附件
+            </el-button>
+          </template>
+          <template #feedbackDocumentUrl="scope">
+            <div v-if="getDocumenType(scope.row).includes('送达回证')">
+              <span>
+                {{ scope.row.feedbackDocumentUrl ? '已回传' : '未回传' }}
+              </span>
+              <el-button
+                v-if="scope.row.feedbackDocumentUrl"
+                type="text"
+                size="mini"
+                @click="handleViewScan(scope.row.feedbackDocumentUrl)"
+              >
+                查看附件
+              </el-button>
+            </div>
+          </template>
+          <template #electronicDocumentUrl="scope">
+            <el-button
+              v-if="!isView"
+              type="text"
+              size="mini"
+              :disabled="true"
+              @click="handleEditDocument(scope.row)"
+            >
+              修改
+            </el-button>
+            <el-button
+              v-if="!isView"
+              type="text"
+              size="mini"
+              :disabled="true"
+              @click="handleDeleteDocument(scope.row)"
+            >
+              删除
+            </el-button>
+            <el-button
+              type="text"
+              size="mini"
+              @click="handleDownloadDocument(scope.row)"
+            >
+              下载
+            </el-button>
+          </template>
+        </CostAuditTable>
+      </div>
+    </div>
+    <div style="margin-top: 20px; font-size: 14px" class="table-description">
+      说明:此处只能生成各被监审单位的《成本监审通知书》和《送达回证》,同时接收或上传被监审单位的反馈的《送达回证》。
+    </div>
+
+    <!-- 编辑监审通知书 -->
+    <CostAuditDialog
+      :title="documentDialogTitle"
+      :visible="documentDialogVisible"
+      width="82%"
+      :close-on-click-modal="false"
+      @cancel="handleCancel"
+      @confirm="handleConfirm"
+    >
+      <div class="document-edit-container">
+        <!-- 左侧:文书参数设置 -->
+        <div class="document-params">
+          <h4>文书参数设置:</h4>
+          <el-form
+            ref="documentForm"
+            v-loading="loading.saveDocument"
+            :model="document"
+            label-width="170px"
+            size="small"
+            :rules="documentRules"
+          >
+            <el-form-item label="选择模板:" prop="documentId">
+              <el-select
+                v-model="document.documentId"
+                placeholder="请选择模板"
+                style="width: 100%"
+                @change="handleTemplateChange"
+              >
+                <el-option
+                  v-for="item in documentData.documentTypes"
+                  :key="item.id"
+                  :label="item.documentName"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="通知书文号:" prop="documentNumber">
+              <el-input
+                v-model="document.documentNumber"
+                placeholder="请选择通知书文号"
+                style="width: 74%"
+                disabled
+              ></el-input>
+              <el-button
+                type="primary"
+                size="small"
+                class="ml10"
+                @click="selectClick"
+              >
+                选择文号
+              </el-button>
+            </el-form-item>
+            <el-form-item label="被监审单位" prop="enterpriseId">
+              <div style="display: flex; align-items: center; gap: 15px">
+                <el-select
+                  v-model="document.enterpriseId"
+                  placeholder="请选择被监审单位"
+                  style="width: 100%"
+                  clearable
+                  :multiple="isMultipleMode"
+                  @change="handleEnterpriseChange"
+                >
+                  <el-option
+                    v-for="item in allUnits"
+                    :key="item.unitId"
+                    :label="item.unitName"
+                    :value="item.unitId"
+                  ></el-option>
+                </el-select>
+              </div>
+            </el-form-item>
+            <el-form-item label="是否推送被监审单位:" prop="isPushed">
+              <el-radio-group v-model="document.isPushed">
+                <el-radio label="1">是</el-radio>
+                <el-radio label="0">否</el-radio>
+              </el-radio-group>
+            </el-form-item>
+
+            <!-- 数据内容区域 -->
+            <div style="margin-top: 20px">
+              <h4 style="margin-bottom: 10px">数据内容:</h4>
+              <el-table
+                :data="costDocumentTemplateFiles"
+                style="
+                  width: 100%;
+                  border: 1px solid #dcdfe6;
+                  border-radius: 4px;
+                "
+              >
+                <el-table-column
+                  prop="originalText"
+                  label="数据项"
+                  width="120"
+                  align="center"
+                  show-overflow-tooltip
+                ></el-table-column>
+                <el-table-column
+                  prop="originalText"
+                  label="描述"
+                  min-width="120"
+                  align="center"
+                  show-overflow-tooltip
+                ></el-table-column>
+                <el-table-column
+                  prop="dataValue"
+                  label="数据值"
+                  min-width="150"
+                  align="center"
+                  show-overflow-tooltip
+                >
+                  <template slot-scope="scope">
+                    <el-input
+                      v-if="scope.row.originalText !== '需要提供材料'"
+                      v-model="scope.row.dataValue"
+                      size="small"
+                      placeholder="请输入数据值"
+                    ></el-input>
+                    <div v-else>
+                      <el-button
+                        type="text"
+                        size="small"
+                        @click="handleUploadClick(scope.row)"
+                      >
+                        上传附件
+                      </el-button>
+                      <el-button
+                        type="text"
+                        size="small"
+                        @click="handleViewScan(scope.row.dataValue)"
+                      >
+                        查看附件
+                      </el-button>
+                    </div>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <div style="margin-top: 10px; font-size: 12px; color: #909399">
+                说明:数据内容可以在此处直接修改,修改后的数据将用于生成监审文书。
+              </div>
+            </div>
+          </el-form>
+        </div>
+
+        <!-- 右侧:模板预览和编辑区 -->
+        <div class="document-preview">
+          <TemplatePreviewEdit :active-tab="activeTab" :file-url="fileUrl" />
+        </div>
+      </div>
+    </CostAuditDialog>
+    <CostAuditDialog
+      :title="dialogTitle"
+      :visible="dialogVisible"
+      :width="dialogWidth"
+      :close-on-click-modal="false"
+      @cancel="handleCancel"
+      @confirm="handleConfirm"
+    >
+      <cost-audit-table
+        :table-data="selectDocumentWhData"
+        :columns="selectDocumentWhColumns"
+        :show-selection="true"
+        :show-pagination="true"
+        :pagination="selectDocumentWhPagination"
+        @pagination-change="selectDocumentWhPaginationChange"
+        @selection-change="selectDocumentWhSelectionChange"
+      >
+        <template #createTime="{ row }">
+          <div>{{ row.createTime ? row.createTime.split(' ')[0] : '-' }}</div>
+          <div>{{ row.createTime ? row.createTime.split(' ')[1] : '-' }}</div>
+        </template>
+      </cost-audit-table>
+    </CostAuditDialog>
+  </div>
+</template>
+<script>
+  import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
+  import CostAuditDialog from '@/components/costAudit/CostAuditDialog.vue'
+  import TemplatePreviewEdit from '@/components/costAudit/TemplatePreviewEdit.vue'
+  import { getAllUnitList } from '@/api/auditEntityManage'
+  import {
+    getCostProjectDocumentFile,
+    queryByDocumentIdandWhereValue,
+  } from '@/api/auditReviewDocManage.js'
+  import { getData } from '@/api/auditDocNoManage.js'
+  import {
+    addCostProjectDocument,
+    updateCostProjectDocument,
+    deleteCostProjectDocument,
+    getCostProjectDocumentDetail,
+    updateScan,
+    downDocument,
+  } from '@/api/taskCustomizedRelease.js'
+  import { dictMixin, regionMixin } from '@/mixins/useDict'
+  import { uploadFile } from '@/api/file'
+  export default {
+    components: { CostAuditTable, CostAuditDialog, TemplatePreviewEdit },
+    mixins: [dictMixin, regionMixin],
+    props: {
+      project: {
+        type: Object,
+        default: () => {},
+      },
+      isView: {
+        type: Boolean,
+        default: false,
+      },
+      documentData: {
+        type: Object,
+        default: () => {},
+      },
+    },
+    data() {
+      return {
+        isMultipleMode: false,
+        dictData: {
+          whGenerateType: [],
+        },
+        activeDocumentTypeId: '',
+        document: {
+          createBy: '',
+          createTime: '',
+          documentAlias: '',
+          documentId: '',
+          documentName: '',
+          documentNumber: '',
+          documentType: '',
+          documentWhId: '',
+          electronicDocumentUrl: '',
+          enterpriseId: [],
+          feedbackDocumentUrl: '',
+          feedbackTime: '',
+          generateTime: '',
+          id: '',
+          isDeleted: '',
+          isPushed: '',
+          orderNum: 0,
+          pkVal: '',
+          projectId: '',
+          pushTime: '',
+          scanDocumentUrl: '',
+          updateBy: '',
+          updateTime: '',
+        },
+        loading: {
+          saveDocument: false,
+        },
+        activeView: 'list',
+        activeTab: 'preview',
+        allUnits: [],
+        dialogVisible: false,
+        dialogTitle: '选择文号',
+        documentDialogVisible: false,
+        documentDialogTitle: '添加监审通知书',
+        dialogWidth: '80%',
+        fileUrl: '',
+        selectDocumentWhData: [],
+        selectDocumentWhPagination: {
+          currentPage: 1,
+          pageSize: 10,
+          total: 0,
+        },
+        selectDocumentWhSelection: [],
+        costDocumentTemplateFiles: [],
+        documentRules: {
+          enterpriseId: [
+            {
+              required: true,
+              message: '请选择被监审单位',
+              trigger: 'change',
+            },
+          ],
+          documentId: [
+            {
+              required: true,
+              message: '请选择模板',
+              trigger: 'change',
+            },
+          ],
+          isPushed: [
+            {
+              required: true,
+              message: '请选择否推送被监审单位',
+              trigger: 'change',
+            },
+          ],
+        },
+        dataUploadUrl: [],
+      }
+    },
+    computed: {
+      selectDocumentWhColumns() {
+        return [
+          {
+            prop: 'whType',
+            label: '文号分类',
+            showOverflowTooltip: true,
+            align: 'center',
+            formatter: (row) => {
+              const documentName =
+                this.documentData.documentTypes.find(
+                  (item) => item.id == row.whType
+                )?.documentName || '-'
+              return documentName
+            },
+          },
+          {
+            prop: 'whName',
+            label: '文号名称',
+            showOverflowTooltip: true,
+            align: 'center',
+          },
+          {
+            prop: 'areaCode',
+            label: '适用区域',
+            showOverflowTooltip: true,
+            align: 'center',
+            formatter: (row) => this.regionNameMap[row.areaCode] || '-',
+          },
+          {
+            prop: 'generateType',
+            label: '生成类型',
+            showOverflowTooltip: true,
+            align: 'center',
+            width: 120,
+            formatter: (row) =>
+              this.getDictName('whGenerateType', row.generateType),
+          },
+        ]
+      },
+    },
+    watch: {
+      costDocumentTemplateFiles: {
+        handler(newVal) {
+          if (newVal.length > 0) {
+            this.costDocumentTemplateFiles.forEach((item) => {
+              if (
+                item.pinyin.includes('ShiJian') &&
+                (item.dataValue == null || item.dataValue == '')
+              ) {
+                const date = new Date()
+                const year = date.getFullYear()
+                const month = String(date.getMonth() + 1).padStart(2, '0')
+                const day = String(date.getDate()).padStart(2, '0')
+                const hours = String(date.getHours()).padStart(2, '0')
+                const minutes = String(date.getMinutes()).padStart(2, '0')
+                const seconds = String(date.getSeconds()).padStart(2, '0')
+                item.dataValue = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+              }
+              if (
+                item.originalText.includes('需要提供材料') &&
+                item.dataValue
+              ) {
+                this.dataUploadUrl = item.dataValue
+              }
+            })
+          }
+        },
+        deep: true,
+      },
+    },
+    methods: {
+      handleDocumentTypeClick(data) {
+        this.activeDocumentTypeId = data.id
+        this.$emit('refresh', data)
+      },
+      getEnterpriseName(row) {
+        let enterpriseIds = []
+        if (Array.isArray(row.enterpriseId)) {
+          enterpriseIds = row.enterpriseId
+        } else if (typeof row.enterpriseId === 'string') {
+          enterpriseIds = row.enterpriseId
+            .split(',')
+            .map((id) => id.trim())
+            .filter((id) => id)
+        } else if (row.enterpriseId) {
+          enterpriseIds = [row.enterpriseId]
+        }
+
+        if (enterpriseIds.length > 0) {
+          return enterpriseIds
+            .map(
+              (id) => this.allUnits.find((item) => item.unitId == id)?.unitName
+            )
+            .filter((name) => name)
+            .join(', ')
+        }
+        return '-'
+      },
+      getDocumenType(row) {
+        return this.documentData.documentTypes.find(
+          (item) => item.id == row.documentId
+        )?.documentName
+      },
+      handlePaginationChange({ currentPage, pageSize }) {
+        this.$emit('paginationChange', { currentPage, pageSize })
+      },
+
+      loadOpts() {
+        getAllUnitList().then((res) => {
+          this.allUnits = res.value || []
+          this.allUnits = this.allUnits.filter((item) => item.status == 1)
+          if (this.project.auditedUnitId) {
+            let auditedUnitIds = []
+            if (Array.isArray(this.project.auditedUnitId)) {
+              auditedUnitIds = this.project.auditedUnitId
+            } else if (
+              typeof this.project.auditedUnitId === 'string' &&
+              this.project.auditedUnitId.includes(',')
+            ) {
+              auditedUnitIds = this.project.auditedUnitId
+                .split(',')
+                .map((id) => id.trim())
+            } else {
+              auditedUnitIds = [this.project.auditedUnitId]
+            }
+            this.allUnits = this.allUnits.filter((item) =>
+              auditedUnitIds.includes(item.unitId)
+            )
+          }
+        })
+      },
+
+      handleGenerateDocument() {
+        this.documentDialogTitle = '添加监审通知书'
+        this.documentDialogVisible = true
+        this.activeView = 'form'
+        this.costDocumentTemplateFiles = []
+        this.document = {
+          createBy: '',
+          createTime: '',
+          documentAlias: '',
+          documentId: '',
+          documentName: '',
+          documentNumber: '',
+          documentType: '',
+          documentWhId: '',
+          electronicDocumentUrl: '',
+          enterpriseId: this.isMultipleMode ? [] : '',
+          feedbackDocumentUrl: '',
+          feedbackTime: '',
+          generateTime: '',
+          id: '',
+          isDeleted: '',
+          isPushed: '1',
+          orderNum: this.documentData.list.length + 1,
+          pkVal: '',
+          projectId: '',
+          pushTime: '',
+          scanDocumentUrl: '',
+          updateBy: '',
+          updateTime: '',
+        }
+        this.loadOpts()
+        if (this.activeDocumentTypeId) {
+          this.document.documentId = this.activeDocumentTypeId
+          this.handleTemplateChange()
+        }
+      },
+      getDetail() {
+        getCostProjectDocumentDetail({
+          projectId: this.project.projectId,
+        }).then((res) => {
+          if (res.value) {
+            this.document = {
+              ...this.document,
+              ...res.value,
+            }
+            this.loadOpts()
+          }
+        })
+      },
+      selectClick() {
+        this.dialogVisible = true
+        this.activeView = 'table'
+        this.getWhListData()
+      },
+      getWhListData() {
+        getData({
+          page: this.selectDocumentWhPagination.currentPage,
+          pageSize: this.selectDocumentWhPagination.pageSize,
+          whType: this.document.documentId,
+        }).then((res) => {
+          this.selectDocumentWhData = res.rows || []
+          this.selectDocumentWhPagination.total = res.total || 0
+          if (this.selectDocumentWhData.length > 0) {
+            this.fetchRegionNames(this.selectDocumentWhData, 'areaCode')
+          }
+        })
+      },
+      selectDocumentWhPaginationChange({ currentPage, pageSize }) {
+        this.selectDocumentWhPagination.currentPage = currentPage
+        this.selectDocumentWhPagination.pageSize = pageSize
+      },
+      selectDocumentWhSelectionChange(selection) {
+        if (selection.length > 1) {
+          this.$message.error('只能选择一个文号!')
+          return
+        } else {
+          this.selectDocumentWhSelection = selection
+        }
+      },
+      handleTemplateChange() {
+        const data = this.documentData.documentTypes.find(
+          (item) => item.id === this.document.documentId
+        )
+        this.fileUrl = data.fileUrl
+        this.document.documentName = data.documentName
+        this.document.documentNumber = ''
+        this.document.documentWhId = ''
+        this.getDocumentData()
+      },
+      getDocumentData() {
+        if (this.document.id === null || this.document.id === '') {
+          queryByDocumentIdandWhereValue({
+            documentId: this.document.documentId,
+            whereValue: this.project.projectId,
+          }).then((res) => {
+            this.costDocumentTemplateFiles = res.value || []
+          })
+        } else {
+          getCostProjectDocumentFile({
+            id: this.document.id,
+          }).then((res) => {
+            this.costDocumentTemplateFiles = res.value || []
+          })
+        }
+      },
+      handleConfirm() {
+        switch (this.activeView) {
+          case 'table':
+            this.handleConfirmSelect()
+            break
+          case 'form':
+            this.handleSaveDocument()
+            break
+          default:
+            break
+        }
+      },
+      handleConfirmSelect() {
+        if (this.selectDocumentWhSelection.length !== 1) {
+          this.$message.error('请选择一个文号!')
+          return
+        }
+        const selectedDocument = this.selectDocumentWhSelection[0]
+
+        this.document.documentNumber = selectedDocument.whNo
+        this.document.documentWhId = selectedDocument.id
+        this.costDocumentTemplateFiles.forEach((item) => {
+          if (
+            item.pinyin.includes('WenHao') ||
+            item.pinyin.includes('WenJianHao')
+          ) {
+            item.dataValue = selectedDocument.whNo
+          }
+          if (item.pinyin.includes('SongDaWenShuMingCheng')) {
+            item.dataValue = selectedDocument.whName
+          }
+        })
+        this.dialogVisible = false
+        this.activeView = 'form'
+      },
+      handleEnterpriseChange() {
+        if (this.document.enterpriseId) {
+          const unit = this.allUnits.find(
+            (item) => item.unitId === this.document.enterpriseId
+          )
+          this.costDocumentTemplateFiles.forEach((item) => {
+            if (item.pinyin.includes('BeiJianShenDanWei')) {
+              item.dataValue = unit.unitName
+            }
+            if (item.pinyin.includes('ShouSongDaRen')) {
+              item.dataValue = unit.contactName
+            }
+            if (item.pinyin.includes('BeiJianShenDanWeiBanGongDiDian')) {
+              item.dataValue = unit.address
+            }
+            if (item.pinyin.includes('BeiJianShenDanWeiLianXiRenDianHua')) {
+              item.dataValue = unit.contactMobile
+            }
+          })
+        }
+      },
+      handleSaveDocument() {
+        this.$refs.documentForm.validate((valid) => {
+          if (!valid) {
+            this.$message.error('请填写必填项!')
+            return false
+          }
+          this.loading.saveDocument = true
+          if (this.document.id) {
+            updateCostProjectDocument({
+              ...this.document,
+              costProjectDocumentFiles: this.costDocumentTemplateFiles,
+              projectId: this.project.projectId,
+              electronicDocumentUrl:
+                this.document.electronicDocumentUrl || this.fileUrl,
+              enterpriseId: this.isMultipleMode
+                ? this.document.enterpriseId.join(',')
+                : this.document.enterpriseId,
+            })
+              .then(() => {
+                this.loading.saveDocument = false
+                this.$message.success('保存成功!')
+                this.documentDialogVisible = false
+                this.activeView = ''
+                this.$emit('refresh', this.project.projectId)
+              })
+              .catch(() => {
+                this.loading.saveDocument = false
+              })
+          } else {
+            addCostProjectDocument({
+              ...this.document,
+              projectId: this.project.projectId,
+              costProjectDocumentFiles: this.costDocumentTemplateFiles || [],
+              enterpriseId: this.isMultipleMode
+                ? this.document.enterpriseId.join(',')
+                : this.document.enterpriseId,
+              electronicDocumentUrl: this.fileUrl,
+            })
+              .then(() => {
+                this.loading.saveDocument = false
+                this.$message.success('保存成功!')
+                this.documentDialogVisible = false
+                this.activeView = ''
+                this.$emit('refresh', this.project.projectId)
+              })
+              .catch(() => {
+                this.loading.saveDocument = false
+              })
+          }
+        })
+      },
+      handleCancel() {
+        if (this.activeView === 'form') {
+          this.documentDialogVisible = false
+        } else {
+          this.activeView = 'form'
+          this.dialogVisible = false
+        }
+      },
+
+      handleUploadScan(row) {
+        let loading = null
+        const input = document.createElement('input')
+        input.type = 'file'
+        input.accept = '.pdf,.doc,.docx,.xls,.xlsx,.csv'
+
+        input.onchange = async (event) => {
+          const file = event.target.files[0]
+          if (!file) return
+
+          try {
+            const maxSize = 50 * 1024 * 1024
+            if (file.size > maxSize) {
+              this.$message.error('文件大小不能超过50MB!')
+              return
+            }
+
+            const allowedFormats = [
+              '.pdf',
+              '.doc',
+              '.docx',
+              '.xls',
+              '.xlsx',
+              'csv',
+            ]
+            const fileName = file.name.toLowerCase()
+            const isValidFormat = allowedFormats.some((format) =>
+              fileName.endsWith(format)
+            )
+
+            if (!isValidFormat) {
+              this.$message.error(
+                '只允许上传.pdf,.doc,.docx,.xls,.xlsx,.csv格式的文件!'
+              )
+              return
+            }
+
+            loading = this.$baseLoading(1, '文件上传中...')
+
+            const formData = new FormData()
+            formData.append('file', file)
+
+            const uploadRes = await uploadFile('/api/file/v1/upload', formData)
+
+            if (!uploadRes || !uploadRes.value) {
+              return
+            }
+
+            const fileInfo = uploadRes.value
+            const updateData = {
+              id: row.id,
+              scanDocumentUrl: fileInfo?.savePath,
+            }
+
+            await updateScan(updateData)
+
+            this.$message.success('文件上传并更新成功!')
+            this.$emit('refresh', this.project.projectId)
+          } catch (error) {
+            this.$message.error('操作失败:' + (error.message || '未知错误'))
+          } finally {
+            loading && loading.close && loading.close()
+          }
+        }
+        input.click()
+      },
+      handleEditDocument(row) {
+        this.documentDialogTitle = '修改监审通知书'
+        this.documentDialogVisible = true
+        this.activeView = 'form'
+        this.loadOpts()
+        getCostProjectDocumentDetail({ id: row.id }).then((res) => {
+          this.document = {
+            ...this.document,
+            ...res.value,
+          }
+          this.document.enterpriseId = this.isMultipleMode
+            ? this.document.enterpriseId.split(',')
+            : this.document.enterpriseId
+          this.fileUrl = this.document.electronicDocumentUrl
+          this.getDocumentData()
+        })
+      },
+      handleDeleteDocument(row) {
+        this.$confirm('确认删除该监审通知书?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(() => {
+            deleteCostProjectDocument(row.id)
+              .then(() => {
+                this.$message.success('删除成功!')
+                this.$emit('refresh', this.project.projectId)
+              })
+              .catch(() => {
+                this.$message.error('删除失败!')
+              })
+          })
+          .catch(() => {})
+      },
+      handleDownloadDocument(row) {
+        this.$loading({
+          lock: true,
+          text: '文件下载中...',
+          spinner: 'el-icon-loading',
+          background: 'rgba(0, 0, 0, 0.7)',
+        })
+
+        downDocument({
+          id: row.id,
+        })
+          .then((res) => {
+            this.$loading().close()
+
+            if (!res || !res.state) {
+              this.$message.error(
+                `下载失败:${res?.message || '未获取到文件数据'}`
+              )
+              return
+            }
+
+            const fileUrl = res.value
+            if (!fileUrl) {
+              this.$message.error('下载失败:未获取到文件URL')
+              return
+            }
+
+            let fileName = ''
+
+            const urlParts = fileUrl.split('/')
+            let urlFileName = urlParts[urlParts.length - 1]
+
+            if (urlFileName.includes('?')) {
+              urlFileName = urlFileName.split('?')[0]
+            }
+
+            if (urlFileName && /\.[a-zA-Z0-9]+$/.test(urlFileName)) {
+              fileName = urlFileName
+            } else {
+              fileName =
+                row.documentName ||
+                `${row.documentName}_${new Date().getTime()}`
+
+              if (!/\.[a-zA-Z0-9]+$/.test(fileName)) {
+                fileName += '.pdf'
+              }
+            }
+            const link = document.createElement('a')
+            link.style.display = 'none'
+            link.href = fileUrl
+            link.download = fileName
+            document.body.appendChild(link)
+            link.click()
+            document.body.removeChild(link)
+          })
+          .catch(() => {
+            this.$loading().close()
+          })
+      },
+      handleViewScan(fileUrl) {
+        if (!fileUrl) {
+          this.$message.error('暂无文件!')
+          return
+        }
+        const encodedUrl = encodeURIComponent(
+          Base64.encode(window.context.form + fileUrl)
+        )
+        window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
+      },
+      handleUploadClick(row) {
+        let loading = null
+        const input = document.createElement('input')
+        input.type = 'file'
+        input.accept = '.pdf,.doc,.docx,.xls,.xlsx,.csv'
+
+        input.onchange = async (event) => {
+          const file = event.target.files[0]
+          if (!file) return
+
+          try {
+            const maxSize = 50 * 1024 * 1024
+            if (file.size > maxSize) {
+              this.$message.error('文件大小不能超过50MB!')
+              return
+            }
+
+            const allowedFormats = [
+              '.pdf',
+              '.doc',
+              '.docx',
+              '.xls',
+              '.xlsx',
+              'csv',
+            ]
+            const fileName = file.name.toLowerCase()
+            const isValidFormat = allowedFormats.some((format) =>
+              fileName.endsWith(format)
+            )
+
+            if (!isValidFormat) {
+              this.$message.error(
+                '只允许上传.pdf,.doc,.docx,.xls,.xlsx,.csv格式的文件!'
+              )
+              return
+            }
+
+            loading = this.$baseLoading(1, '文件上传中...')
+
+            const formData = new FormData()
+            formData.append('file', file)
+
+            const uploadRes = await uploadFile('/api/file/v1/upload', formData)
+
+            if (!uploadRes || !uploadRes.value) {
+              return
+            }
+
+            row.dataValue = uploadRes.value.savePath
+            this.$message.success('上传成功!')
+          } catch (error) {
+            this.$message.error('上传失败:' + (error.message || '未知错误'))
+          } finally {
+            loading && loading.close && loading.close()
+          }
+        }
+        input.click()
+      },
+    },
+  }
+</script>
+<style scoped lang="scss">
+  @import '@/styles/costAudit.scss';
+
+  .documents-layout {
+    display: flex;
+    gap: 20px;
+  }
+
+  .documents-type-list {
+    width: 200px;
+    border-right: 1px solid #ebeef5;
+    padding-right: 20px;
+  }
+
+  .type-item {
+    padding: 8px 12px;
+    cursor: pointer;
+    border-radius: 4px;
+    margin-bottom: 6px;
+  }
+
+  .type-item.active {
+    background-color: #ecf5ff;
+    color: #409eff;
+  }
+
+  .documents-content {
+    flex: 1;
+  }
+
+  .document-edit-container {
+    display: flex;
+    gap: 20px;
+  }
+
+  .document-params {
+    width: 50%;
+  }
+
+  .document-preview {
+    flex: 1;
+    border-left: 1px solid #ebeef5;
+    padding-left: 20px;
+  }
+</style>

+ 341 - 0
src/components/task/mounTaskComponents/basicInfoTab.vue

@@ -0,0 +1,341 @@
+<template>
+  <div class="basic-info-tab">
+    <el-form
+      ref="initiationForm"
+      :model="formData.basicInfo"
+      label-width="150px"
+      class="initiation-form two-column-form"
+      disabled
+    >
+      <!-- 成本监审项目名称 -->
+      <el-form-item label="成本监审项目名称:" prop="projectName">
+        <el-input
+          v-model="formData.basicInfo.projectName"
+          placeholder="请输入成本监审项目名称"
+          style="width: 100%"
+        ></el-input>
+      </el-form-item>
+
+      <!-- 关联成本监审目录 -->
+      <el-form-item label="关联成本监审目录:" prop="catalogId">
+        <CatalogCascader
+          ref="catalogCascader"
+          :form-item="{ placeholder: '请选择监审目录' }"
+          style="width: 100%"
+          :value="formData.basicInfo.catalogId"
+        />
+      </el-form-item>
+
+      <!-- 监审地区 -->
+      <el-form-item label="监审地区:">
+        <RegionSelector
+          :initial-area-code="formData.basicInfo.areaCode"
+          :disabled="false"
+          style="width: 100%"
+        ></RegionSelector>
+      </el-form-item>
+
+      <!-- 被监审单位 -->
+      <el-form-item label="被监审单位" prop="auditedUnitName">
+        <el-select
+          v-if="formData.basicInfo.auditedUnitId.length > 0"
+          v-model="formData.basicInfo.auditedUnitId"
+          placeholder="请选择单位"
+          clearable
+          multiple
+          style="width: 100%"
+          readonly
+        >
+          <el-option
+            v-for="unit in unitList"
+            :key="unit.unitId"
+            :label="unit.unitName"
+            :value="unit.unitId"
+          />
+        </el-select>
+        <el-input
+          v-else
+          v-model="formData.basicInfo.auditUnitName"
+          style="width: 100%"
+          placeholder="请输入被监审单位"
+          readonly
+        />
+      </el-form-item>
+
+      <!-- 监审主体 -->
+      <el-form-item label="监审主体:" prop="orgId">
+        <el-select
+          v-model="formData.basicInfo.orgId"
+          placeholder="请选择监审主体"
+          style="width: 100%"
+          clearable
+        >
+          <el-option
+            v-for="Org in OrgList"
+            :key="Org.id"
+            :label="Org.name"
+            :value="Org.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <!-- 归属年度 -->
+      <el-form-item label="归属年度:" prop="projectYear">
+        <el-date-picker
+          v-model="formData.basicInfo.projectYear"
+          style="width: 100%"
+          type="year"
+          placeholder="请选择归属年度"
+          format="yyyy"
+          value-format="yyyy"
+          clearable
+        ></el-date-picker>
+      </el-form-item>
+
+      <!-- 立项来源 -->
+      <el-form-item label="立项来源:" prop="sourceType">
+        <el-select
+          v-model="formData.basicInfo.sourceType"
+          placeholder="请选择立项类型"
+          style="width: 100%"
+        >
+          <el-option
+            v-for="item in dictData['projectProposal']"
+            :key="item.key"
+            :label="item.name"
+            :value="item.key"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+
+      <!-- 监审形式 -->
+      <el-form-item label="监审形式:" prop="auditType">
+        <el-select
+          v-model="formData.basicInfo.auditType"
+          placeholder="请选择监审形式"
+          style="width: 100%"
+        >
+          <el-option
+            v-for="item in dictData['auditType']"
+            :key="item.key"
+            :label="item.name"
+            :value="item.key"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+
+      <!-- 监审期间 -->
+      <el-form-item label="监审期间:" prop="auditPeriodArray">
+        <div class="cost-period-container">
+          <div class="cost-years-wrapper">
+            <div
+              v-for="(year, index) in formData.basicInfo.auditPeriodArray"
+              :key="index"
+              class="cost-year-item"
+            >
+              <el-date-picker
+                v-model="year.value"
+                style="width: 100%"
+                type="year"
+                placeholder="请选择年份"
+                format="yyyy"
+                value-format="yyyy"
+                clearable
+              ></el-date-picker>
+            </div>
+          </div>
+        </div>
+      </el-form-item>
+
+      <!-- 是否参加听证 -->
+      <el-form-item label="是否参加听证:" prop="needHearing">
+        <el-radio-group v-model="formData.basicInfo.needHearing">
+          <el-radio label="0">是</el-radio>
+          <el-radio label="1">否</el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <!-- 是否应急项目 -->
+      <el-form-item label="是否应急项目:" prop="isEmergency">
+        <el-radio-group v-model="formData.basicInfo.isEmergency">
+          <el-radio label="0">是</el-radio>
+          <el-radio label="1">否</el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <!-- 立项理由 -->
+      <el-form-item label="立项理由:" prop="establishmentReason">
+        <el-input
+          v-model="formData.basicInfo.establishmentReason"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入立项理由"
+          style="width: 100%"
+        ></el-input>
+      </el-form-item>
+
+      <!-- 立项依据 -->
+      <el-form-item label="立项依据:">
+        <UploadComponent
+          :upload-mode="'single'"
+          :max-size="50 * 1024 * 1024"
+          :allowed-types="['xlsx', 'xls', 'doc', 'docx', 'pdf']"
+          :files-list="accordingFileList"
+          :is-disabled="true"
+        />
+      </el-form-item>
+
+      <!-- 其他材料 -->
+      <el-form-item label="其他材料:">
+        <UploadComponent
+          :upload-mode="'single'"
+          :max-size="50 * 1024 * 1024"
+          :allowed-types="['xlsx', 'xls', 'doc', 'docx', 'pdf']"
+          :files-list="otherFileList"
+          :is-disabled="true"
+        />
+      </el-form-item>
+
+      <!-- 监审任务负责人 -->
+      <el-form-item label="监审任务负责人:" prop="leaderId">
+        <el-select
+          v-model="formData.basicInfo.leaderId"
+          placeholder="请选择负责人"
+          style="width: 100%"
+        >
+          <el-option
+            v-for="(item, index) in userList"
+            :key="index"
+            :label="item.fullname"
+            :value="item.userId"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+
+      <!-- 监审任务组成员 -->
+      <el-form-item label="监审任务组成员:" prop="auditTeamMembers">
+        <el-select
+          v-model="formData.basicInfo.auditTeamMembers"
+          placeholder="请选择成员"
+          style="width: 100%"
+          multiple
+        >
+          <el-option
+            v-for="(item, index) in userList"
+            :key="index"
+            :label="item.fullname"
+            :value="item.userId"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+
+      <!-- 其他要求 -->
+      <el-form-item label="其他专家:" prop="expertStr">
+        <el-input
+          v-model="formData.basicInfo.expertStr"
+          placeholder="请输入其他专家"
+          style="width: 100%"
+        ></el-input>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+  import { taskMixin } from './index.js'
+  import RegionSelector from '@/views/costAudit/projectInfo/auditProjectManage/annualReviewPlan/RegionSelector.vue'
+  import UploadComponent from '@/components/costAudit/UploadComponent.vue'
+  import CatalogCascader from '@/views/costAudit/projectInfo/auditProjectManage/annualReviewPlan/CatalogCascader.vue'
+  import { getDefaultDem, getOrgListByDemId } from '@/api/annualReviewPlan'
+  import { getAllUnitList } from '@/api/auditEntityManage'
+  export default {
+    name: 'BasicInfoTab',
+    components: {
+      RegionSelector,
+      UploadComponent,
+      CatalogCascader,
+    },
+    mixins: [taskMixin],
+    props: {
+      // 父组件传递的参数
+      project: {
+        type: Object,
+        default: () => {},
+      },
+    },
+    data() {
+      return {
+        // 添加其他数据源
+        userList: [],
+        OrgList: [],
+        accordingFileList: [],
+        otherFileList: [],
+        unitList: [],
+      }
+    },
+    // 添加watch监听project变化,确保项目数据更新时重新加载数据
+    watch: {
+      project: {
+        handler() {
+          if (this.project && this.project.projectId) {
+            this.formData.basicInfo = this.project
+            console.log(this.formData.basicInfo)
+            this.formData.basicInfo.auditedUnitId = this.project.auditedUnitId
+              ? this.project.auditedUnitId.split(',')
+              : []
+            this.formData.basicInfo.auditPeriodArray = this.project.auditPeriod
+              .split(',')
+              .map((year) => ({ value: year }))
+            this.formData.basicInfo.auditTeamMembers =
+              this.project.projectMembers.split(',')
+            this.otherFileList = this.project.otherFileUrl
+              ? this.project.otherFileUrl.split(',')
+              : []
+            this.accordingFileList = this.project.accordingFileUrl
+              ? this.project.accordingFileUrl.split(',')
+              : []
+          }
+        },
+        deep: true,
+        immediate: true,
+      },
+    },
+    mounted() {
+      this.getAllUnitList()
+      this.getUser()
+      this.getDefaultDem()
+    },
+    methods: {
+      getAllUnitList() {
+        getAllUnitList().then((res) => {
+          this.unitList = res.value || []
+        })
+      },
+      // 获取默认维度
+      getDefaultDem() {
+        getDefaultDem().then((res) => {
+          if (res && res.code === 200) {
+            const demId = res.value ? res.value.id : null
+            if (demId) {
+              this.getOrgListByDemId(demId)
+            }
+          }
+        })
+      },
+      // 根据维度ID获取单位列表
+      getOrgListByDemId(demId) {
+        getOrgListByDemId({ demId }).then((res) => {
+          if (res && res.code === 200) {
+            this.OrgList = res.value || []
+          }
+        })
+      },
+    },
+  }
+</script>
+<style lang="scss" scoped>
+  .two-column-form {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+  }
+</style>

+ 613 - 0
src/components/task/mounTaskComponents/index.js

@@ -0,0 +1,613 @@
+import {
+  getCostProjectDetail,
+  getCostProjectScenarioDetail,
+  getCostProjectDocumentPageList,
+  getCostProjectMaterialPageList,
+  getCostProjectNodeTmpletePageList,
+  getCostProjectNodeTmpleteGetDetail,
+  getCostProjectSurveyPageList,
+} from '@/api/taskCustomizedRelease.js'
+import { getAllUserList } from '@/api/uc'
+import { getCostSurveyTemplates } from '@/api/catalogManage.js'
+import { getWhCateList, getDocList } from '@/api/auditReviewDocManage.js'
+// 引入地区选择混入
+import {
+  dictMixin,
+  regionMixin,
+  catalogMixin,
+  commonMixin,
+} from '@/mixins/useDict'
+
+export const taskMixin = {
+  mixins: [dictMixin, regionMixin, catalogMixin, commonMixin],
+  data() {
+    return {
+      dictData: {
+        auditType: [], //监审形式
+        materialType: [], //资料类别
+        formatAsk: [], //格式要求
+        processNodeType: [], //流程环节类型
+        projectProposal: [], //立项来源
+        processStatus: [], //预置流转操作
+        whGenerateType: [],
+      },
+      userList: [],
+      // 弹窗状态管理
+      dialogs: {
+        materialDialogVisible: false,
+        setStepDialogVisible: false,
+        stepDialogVisible: false,
+        documentDialogVisible: false,
+      },
+      loading: {
+        save: false,
+        saveDocument: false,
+      },
+      // 当前激活的标签页
+      activeTab: 'scenario',
+      formCities: [],
+      formDistricts: [],
+      // 所有表单数据聚合
+      formData: {
+        // 监审立项信息表单
+        basicInfo: {
+          projectName: '',
+          catalogId: '',
+          areaCode: '',
+          auditedUnitName: '',
+          auditedUnitId: '',
+          orgId: '',
+          orgName: '',
+          planYear: '',
+          projectYear: '',
+          sourceType: '',
+          auditPeriod: '',
+          auditPeriodArray: [{ value: '' }],
+          needHearing: 1,
+          isEmergency: 1,
+          establishmentReason: '',
+          accordingFileUrl: '',
+          otherFileUrl: '',
+          expertStr: '',
+          auditGroup: '',
+          projectMembers: '',
+          auditTeamMembers: [],
+        },
+
+        // 监审工作方案表单
+        workPlan: {
+          basicInfo: '',
+          scopeContent: '',
+          stepsMethods: '',
+          otherContent: '',
+          attachmentIds: [],
+        },
+
+        // 材料表单(弹窗用)
+        material: {
+          materialType: '综合性材料',
+          materialName: '',
+          materialAsk: '',
+          formatAsk: '文档文件',
+          orderNum: '',
+          isMust: '否',
+        },
+
+        // 当前流程环节(弹窗用)
+        currentStep: {
+          mainUserId: '',
+          userId: [],
+          status: [],
+        },
+        currentStepRules: {
+          mainUserId: [
+            { required: true, message: '请选择主办人员', trigger: 'change' },
+          ],
+          userId: [
+            {
+              required: true,
+              message: '请选择从办人员',
+              trigger: 'change',
+            },
+          ],
+          status: [
+            {
+              required: true,
+              message: '请输入预置流转操作',
+              trigger: 'blur',
+            },
+          ],
+          endTime: [
+            { required: true, message: '请选择截止时间', trigger: 'change' },
+          ],
+        },
+
+        // 监审工作流程表单
+        workflow: {
+          plannedAuditEndDate: '',
+          plannedAuditStartDate: '',
+        },
+      },
+      scenarioData: {
+        addScenario: false,
+        saveScenario: false,
+      },
+
+      workflowData: {
+        list: [],
+        detailInfo: {},
+        stepList: [],
+        pagination: {
+          currentPage: 1,
+          pageSize: 10,
+          total: 0,
+        },
+        listColumns: [
+          {
+            prop: 'processNodeValue',
+            label: '流程环节',
+            width: 100,
+            align: 'center',
+          },
+          {
+            prop: 'nodeType',
+            label: '环节类型',
+            width: 100,
+            align: 'center',
+            formatter: (row) => {
+              return this.getDictName('processNodeType', row.nodeType)
+            },
+          },
+          {
+            prop: 'mainUserName',
+            label: '主办人员',
+            minWidth: 100,
+            align: 'left',
+          },
+          {
+            prop: 'userName',
+            label: '从办人员',
+            minWidth: 150,
+            align: 'left',
+          },
+          {
+            prop: 'status',
+            label: '预置流转操作',
+            minWidth: 150,
+            align: 'left',
+            formatter: (row) => {
+              return this.getDictName('processStatus', row.status)
+            },
+          },
+          {
+            prop: 'endTime',
+            label: '预置环节截止时间',
+            width: 140,
+            align: 'center',
+          },
+          {
+            prop: 'action',
+            label: '操作',
+            width: 100,
+            align: 'center',
+            slotName: 'action',
+          },
+        ],
+        workflowColumns: [
+          {
+            prop: 'processNodeValue',
+            label: '流程环节',
+            width: 100,
+            align: 'center',
+          },
+          {
+            prop: 'nodeType',
+            label: '环节类型',
+            width: 100,
+            align: 'center',
+            formatter: (row) => {
+              return this.getDictName('processNodeType', row.nodeType)
+            },
+          },
+          {
+            prop: 'mainUserId',
+            label: '主办人员',
+            minWidth: 100,
+            align: 'left',
+            slotName: 'mainUserId',
+          },
+          {
+            prop: 'userId',
+            label: '从办人员',
+            minWidth: 200,
+            align: 'left',
+            slotName: 'userId',
+          },
+          {
+            prop: 'status',
+            label: '预置流转操作',
+            minWidth: 150,
+            align: 'left',
+            slotName: 'status',
+          },
+          {
+            prop: 'endTime',
+            label: '预置环节截止时间',
+            width: 150,
+            align: 'center',
+            slotName: 'endTime',
+          },
+        ],
+      },
+
+      documentData: {
+        documentTypes: [],
+        selectedDoc: 'notice',
+        list: [],
+        pagination: {
+          currentPage: 1,
+          pageSize: 10,
+          total: 0,
+        },
+        dataList: [], // 保持原数据结构,实际使用时会填充
+        documentColumns: [
+          {
+            prop: 'documentId',
+            label: '文书类型',
+            align: 'center',
+            slotName: 'documentId',
+          },
+          {
+            prop: 'documentNumber',
+            label: '文书文号',
+            width: 200,
+            align: 'center',
+          },
+          {
+            prop: 'enterpriseId',
+            label: '被监审单位',
+            width: 200,
+            align: 'left',
+            slotName: 'enterpriseId',
+          },
+          {
+            prop: 'generateTime',
+            label: '生成时间',
+            width: 100,
+            align: 'center',
+            slotName: 'generateTime',
+          },
+          {
+            prop: 'electronicDocumentUrl',
+            label: '电子文书',
+            width: 200,
+            align: 'center',
+            slotName: 'electronicDocumentUrl',
+          },
+          {
+            prop: 'scanDocumentUrl',
+            label: '上传扫描件',
+            width: 150,
+            align: 'center',
+            slotName: 'scanDocumentUrl',
+          },
+          {
+            prop: 'isPushed',
+            label: '是否推送被监审单位',
+            width: 100,
+            align: 'center',
+            formatter: (row) => (row.isPushed == 1 ? '是' : '否'),
+          },
+          {
+            prop: 'feedbackDocumentUrl',
+            label: '被监审单位反馈资料',
+            width: 150,
+            align: 'center',
+            slotName: 'feedbackDocumentUrl',
+          },
+        ],
+      },
+    }
+  },
+  computed: {
+    surveyData() {
+      return {
+        list: [],
+        pagination: {
+          currentPage: 1,
+          pageSize: 50,
+          total: 0,
+        },
+        surveyColumns: [
+          {
+            prop: 'surveyTemplateName',
+            label: '成本调查表',
+            width: 300,
+            align: 'left',
+            showOverflowTooltip: true,
+          },
+          {
+            prop: 'templateType',
+            label: '资料类型',
+            width: 120,
+            align: 'center',
+            formatter: (row) => {
+              return row.templateType == '1'
+                ? '单记录'
+                : row.templateType == '2'
+                ? '固定表'
+                : '动态表'
+            },
+          },
+          // {
+          //   prop: 'isMust',
+          //   label: '是否必填',
+          //   width: 100,
+          //   align: 'center',
+          // },
+          {
+            prop: 'action',
+            label: '操作',
+            width: 120,
+            align: 'center',
+          },
+        ],
+      }
+    },
+    // 数据列表聚合
+    materialData() {
+      return {
+        list: [],
+        pagination: {
+          currentPage: 1,
+          pageSize: 50,
+          total: 0,
+        },
+        materialDialogTitle: '添加',
+        materialDialogVisible: false,
+        saveMaterial: false,
+        addMaterial: false,
+        materialColumns: [
+          {
+            prop: 'informationType',
+            label: '材料分类',
+            width: 120,
+            align: 'center',
+            formatter: (row) => {
+              return this.getDictName('materialType', row.informationType)
+            },
+          },
+          {
+            prop: 'informationName',
+            label: '材料名称',
+            minWidth: 200,
+            align: 'left',
+          },
+          {
+            prop: 'informationRequire',
+            label: '材料要求说明',
+            minWidth: 300,
+            align: 'left',
+          },
+          {
+            prop: 'formatRequired',
+            label: '格式要求',
+            width: 120,
+            align: 'center',
+            formatter: (row) => {
+              return this.getDictName('formatAsk', row.formatRequired)
+            },
+          },
+          {
+            prop: 'orderNum',
+            label: '排序',
+            width: 120,
+            align: 'center',
+            slotName: 'orderNum',
+          },
+          {
+            prop: 'action',
+            label: '操作',
+            align: 'center',
+            width: 200,
+            slotName: 'action',
+            actions: [
+              {
+                name: 'edit',
+                label: '修改',
+                type: 'text',
+                size: 'mini',
+                onClick: this.handleEditMaterial,
+                disabled: this.isView,
+              },
+              {
+                name: 'delete',
+                label: '删除',
+                type: 'text',
+                size: 'mini',
+                className: 'delete-btn',
+                onClick: this.handleDeleteMaterial,
+                disabled: this.isView,
+              },
+            ],
+          },
+        ],
+      }
+    },
+  },
+  watch: {
+    activeTab: {
+      handler(newVal) {
+        this.handleTabClick()
+      },
+      immediate: false, // 可选:如果需要在组件初始化时就执行,可以设置为true
+    },
+  },
+  methods: {
+    async handleTabClick() {
+      switch (this.activeTab) {
+        case 'basicInfo':
+          this.getBasicInfo()
+          break
+        case 'scenario':
+          this.getScenarioData()
+          break
+        case 'material':
+          this.getMaterialData()
+          break
+        case 'survey':
+          // this.getSurveyData()
+          break
+        case 'auditNotice':
+          // const res = await getWhCateList()
+          this.getDocumentData()
+          break
+        case 'workflow':
+          this.getWorkflow()
+          break
+        default:
+          break
+      }
+    },
+    // 获取用户信息
+    getUser() {
+      getAllUserList()
+        .then((res) => {
+          this.userList = res.value || []
+        })
+        .catch(() => {})
+    },
+    getBasicInfo() {
+      const pid =
+        (this.project && (this.project.projectId || this.project.id)) ||
+        (this.taskData && (this.taskData.projectId || this.taskData.id)) ||
+        ''
+      if (!pid) return
+      getCostProjectDetail({ id: pid })
+        .then((res) => {
+          this.formData.basicInfo = {
+            ...this.project,
+            ...res.value,
+          }
+        })
+        .catch(() => {
+          this.formData.basicInfo = this.project
+        })
+    },
+    // 获取监审工作方案数据
+    getScenarioData() {
+      const pid =
+        (this.project && (this.project.projectId || this.project.id)) ||
+        (this.taskData && (this.taskData.projectId || this.taskData.id)) ||
+        ''
+      if (!pid) return
+      getCostProjectScenarioDetail({ projectId: pid }).then((res) => {
+        if (res.value) {
+          this.formData.workPlan = res.value
+          this.formData.workPlan.attachmentIds = res.value.attachmentIds
+            ? res.value.attachmentIds.split(',')
+            : []
+        } else {
+          this.scenarioData.addScenario = true
+        }
+      })
+    },
+    // 报送资料要求数据
+    getMaterialData() {
+      const pid =
+        (this.project && (this.project.projectId || this.project.id)) ||
+        (this.taskData && (this.taskData.projectId || this.taskData.id)) ||
+        ''
+      if (!pid) return
+      getCostProjectMaterialPageList({
+        pageNum: this.materialData.pagination.currentPage,
+        pageSize: this.materialData.pagination.pageSize,
+        projectId: pid,
+      }).then((res) => {
+        if (res.value.code == 200) {
+          this.materialData.list = res.value.value.records || []
+          this.materialData.pagination.total = res.value.value.total
+        } else {
+          this.materialData.list = []
+          this.materialData.pagination.total = 0
+        }
+      })
+    },
+    // 获取成本调查表数据
+    getSurveyData() {
+      getCostSurveyTemplates({
+        // pageNum: this.surveyData.pagination.currentPage,
+        // pageSize: this.surveyData.pagination.pageSize,
+        catalogId: this.project.catalogId,
+      }).then((res) => {
+        this.surveyData.list = res.value
+        // this.surveyData.pagination.total = res.value.total
+      })
+    },
+    // 获取监审通知数据
+    async getDocumentData(data) {
+      const res = await getDocList({
+        page: 1,
+        pageSize: 50,
+      })
+      this.documentData.documentTypes = res.value.records || []
+      const pid =
+        (this.project && (this.project.projectId || this.project.id)) ||
+        (this.taskData && (this.taskData.projectId || this.taskData.id)) ||
+        ''
+      if (!pid) return
+      getCostProjectDocumentPageList({
+        pageNum: this.documentData.pagination.currentPage,
+        pageSize: this.documentData.pagination.pageSize,
+        projectId: pid,
+        documentName: data ? data.documentName : '',
+      }).then((res) => {
+        this.documentData.list = res.value.value.records
+        this.documentData.pagination.total = res.value.value.total
+      })
+    },
+    // 获取流程数据
+    getWorkflow() {
+      const pid =
+        (this.project && (this.project.projectId || this.project.id)) ||
+        (this.taskData && (this.taskData.projectId || this.taskData.id)) ||
+        ''
+      if (!pid) return
+      getCostProjectNodeTmpleteGetDetail({ projectId: pid }).then((res) => {
+        this.workflowData.list = res.value.nodeList || []
+        this.workflowData.detailInfo = res.value
+        this.formData.workflow.plannedAuditStartDate =
+          res.value.plannedAuditStartDate
+        this.formData.workflow.plannedAuditEndDate =
+          res.value.plannedAuditEndDate
+      })
+    },
+    handlePaginationChange({ currentPage, pageSize }) {
+      switch (this.activeTab) {
+        case 'material':
+          this.materialData.pagination.currentPage = currentPage
+          this.materialData.pagination.pageSize = pageSize
+          this.getMaterialData()
+          break
+        case 'survey':
+          this.surveyData.pagination.currentPage = currentPage
+          this.surveyData.pagination.pageSize = pageSize
+          this.getSurveyData()
+          break
+        default:
+          break
+      }
+    },
+    formattertUserName(userId) {
+      if (userId) {
+        let arr = userId.split(',')
+        return arr
+          .map((item) => {
+            let user = this.userList.find((user) => user.userId == item)
+            return user.fullname
+          })
+          .join(',')
+      }
+    },
+  },
+}

+ 365 - 0
src/components/task/mounTaskComponents/materialTab.vue

@@ -0,0 +1,365 @@
+<template>
+  <div class="material-tab">
+    <span class="link-text">被监审单位需报送材料:</span>
+    <el-button
+      v-if="!isView"
+      plain
+      type="success"
+      icon="el-icon-circle-plus"
+      style="margin-bottom: 20px"
+      @click="handleAddMaterial"
+    >
+      添加材料
+    </el-button>
+    <CostAuditTable
+      class="mt20"
+      :table-data="materialList"
+      :columns="materialColumns"
+      :show-index="true"
+      :show-pagination="true"
+      :show-action-column="true"
+      :pagination="pagination"
+      @pagination-change="handlePaginationChange"
+    >
+      <template #orderNum="scope">
+        <el-input
+          v-model="scope.row.orderNum"
+          size="mini"
+          style="width: 60px"
+          @blur="handleSortChange(scope.row)"
+        ></el-input>
+      </template>
+      <template #action="scope">
+        <el-button
+          type="primary"
+          size="mini"
+          plain
+          icon="el-icon-edit"
+          :disabled="isView"
+          @click="handleEditMaterial(scope.row)"
+        >
+          修改
+        </el-button>
+        <el-button
+          type="danger"
+          size="mini"
+          plain
+          icon="el-icon-delete"
+          :disabled="isView"
+          @click="handleDeleteMaterial(scope.row)"
+        >
+          删除
+        </el-button>
+        />
+      </template>
+    </CostAuditTable>
+    <legal-dialog
+      ref="legalDialog"
+      :dialog-visible="materialDialogVisible"
+      :dialog-title="materialDialogTitle"
+      :template-data="templateData"
+      :template-columns="templateColumns"
+      :form-data="formData.material"
+      @submit="handleMaterialSubmit"
+      @cancel="handleMaterialCancel"
+    />
+  </div>
+</template>
+<script>
+  import {
+    addCostProjectMaterial,
+    updateCostProjectMaterial,
+    deleteCostProjectMaterial,
+    getCostProjectMaterialPageList,
+  } from '@/api/taskCustomizedRelease.js'
+  import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
+  import LegalDialog from '@/views/costAudit/baseInfo/catalogManage/legalDialog.vue'
+  import { dictMixin } from '@/mixins/useDict'
+  export default {
+    components: {
+      CostAuditTable,
+      LegalDialog,
+    },
+    mixins: [dictMixin],
+    props: {
+      // 父组件传递的参数
+      project: {
+        type: Object,
+        default: () => {},
+      },
+      isView: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        dictData: {
+          materialType: [], //资料类别
+          formatAsk: [], //格式要求
+        },
+        // 模板相关数据
+        templateData: [],
+        templateColumns: [
+          {
+            prop: 'surveyTemplateName',
+            label: '模板名称',
+            align: 'center',
+          },
+          {
+            prop: 'surveyTemplateCode',
+            label: '模板编码',
+            align: 'center',
+          },
+          {
+            prop: 'createTime',
+            label: '创建时间',
+            align: 'center',
+          },
+        ],
+        formData: {
+          material: {},
+        },
+        addMaterial: false,
+        materialDialogVisible: false,
+        materialDialogTitle: '添加',
+        // 简化数据结构,避免嵌套对象可能导致的数据绑定问题
+        materialList: [],
+        pagination: {
+          currentPage: 1,
+          pageSize: 50,
+          total: 0,
+        },
+        materialColumns: [],
+      }
+    },
+    computed: {},
+    watch: {
+      // 监听project变化,确保有项目ID时刷新数据
+      project: {
+        handler(newVal) {
+          if (newVal && newVal.projectId) {
+            this.loadMaterialData()
+          }
+        },
+        deep: true,
+      },
+    },
+    mounted() {
+      // 组件挂载时先初始化表格列配置
+      this.materialColumns = this.getMaterialColumns()
+      // 然后加载数据
+      if (this.project && this.project.projectId) {
+        this.loadMaterialData()
+      }
+    },
+    methods: {
+      // 获取表格列配置
+      getMaterialColumns() {
+        return [
+          {
+            prop: 'informationType',
+            label: '材料分类',
+            width: 120,
+            align: 'center',
+            formatter: (row) => {
+              return this.getDictName('materialType', row.informationType)
+            },
+          },
+          {
+            prop: 'informationName',
+            label: '材料名称',
+            minWidth: 200,
+            align: 'left',
+          },
+          {
+            prop: 'informationRequire',
+            label: '材料要求说明',
+            minWidth: 300,
+            align: 'left',
+          },
+          {
+            prop: 'formatRequired',
+            label: '格式要求',
+            width: 120,
+            align: 'center',
+            formatter: (row) => {
+              return this.getDictName('formatAsk', row.formatRequired)
+            },
+          },
+          {
+            prop: 'orderNum',
+            label: '排序',
+            width: 120,
+            align: 'center',
+            slotName: 'orderNum',
+          },
+          {
+            prop: 'action',
+            label: '操作',
+            align: 'center',
+            width: 200,
+            slotName: 'action',
+            actions: [
+              {
+                name: 'edit',
+                label: '修改',
+                type: 'text',
+                size: 'mini',
+                onClick: this.handleEditMaterial,
+                disabled: this.isView,
+              },
+              {
+                name: 'delete',
+                label: '删除',
+                type: 'text',
+                size: 'mini',
+                className: 'delete-btn',
+                onClick: this.handleDeleteMaterial,
+                disabled: this.isView,
+              },
+            ],
+          },
+        ]
+      },
+
+      // 加载材料数据
+      loadMaterialData() {
+        // 确保project和projectId存在
+        if (!this.project || !this.project.projectId) {
+          console.warn('项目ID不存在,无法加载材料数据')
+          return
+        }
+        const { currentPage, pageSize } = this.pagination
+        const params = {
+          projectId: this.project.projectId,
+          page: currentPage,
+          size: pageSize,
+        }
+        getCostProjectMaterialPageList(params)
+          .then((res) => {
+            if (res && res.value && res.value.value) {
+              this.materialList = Array.isArray(res.value.value.records)
+                ? res.value.value.records
+                : []
+              this.pagination.total = Number(res.value.value.total) || 0
+            } else {
+              this.materialList = []
+              this.pagination.total = 0
+            }
+          })
+          .catch((error) => {
+            console.error('加载材料数据失败:', error)
+            // 错误时清空数据避免显示异常
+            this.materialList = []
+            this.pagination.total = 0
+          })
+      },
+
+      // 排序变更
+      handleSortChange(row) {
+        // 排序逻辑
+        let data = {
+          ...row,
+          projectId: this.project.projectId,
+        }
+        updateCostProjectMaterial(data)
+          .then(() => {
+            this.$message.success('修改成功')
+            this.loadMaterialData()
+          })
+          .catch(() => {})
+      },
+      // 新增材料
+      handleAddMaterial() {
+        this.formData.material = {
+          informationType: '',
+          informationName: '',
+          informationRequire: '',
+          formatRequired: '',
+          templateId: '',
+          surveyTemplateName: '',
+          orderNum: 1,
+          isRequired: '0',
+        }
+        this.materialDialogVisible = true
+        this.materialDialogTitle = '添加'
+      },
+
+      // 编辑材料
+      handleEditMaterial(row) {
+        this.formData.material = { ...row }
+        this.materialDialogVisible = true
+        this.materialDialogTitle = '修改'
+      },
+
+      // 删除材料
+      handleDeleteMaterial(row) {
+        this.$confirm(
+          `确定要删除资料名称为${row.informationName}的数据吗?`,
+          '提示',
+          {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning',
+          }
+        )
+          .then(() => {
+            try {
+              deleteCostProjectMaterial(row.id).then(() => {
+                this.$message.success('删除成功')
+                this.loadMaterialData()
+              })
+            } catch (error) {
+              console.error('删除失败:', error)
+            }
+          })
+          .catch(() => {
+            this.$message.info('已取消删除')
+          })
+      },
+
+      // 保存材料
+      handleMaterialSubmit(formData) {
+        let data = {
+          ...formData,
+          projectId: this.project.projectId,
+        }
+        updateCostProjectMaterial(data)
+          .then(() => {
+            this.$message.success('保存成功')
+            this.$refs.legalDialog.setSubmitting(false)
+            this.materialDialogVisible = false
+            this.loadMaterialData()
+          })
+          .catch(() => {
+            this.$refs.legalDialog.setSubmitting(false)
+          })
+      },
+      handleLegalSubmit() {},
+      handleMaterialCancel() {
+        this.formData.material = {
+          informationType: '',
+          informationName: '',
+          informationRequire: '',
+          formatRequired: '',
+          templateId: '',
+          surveyTemplateName: '',
+          orderNum: 1,
+          isRequired: '0',
+        }
+        this.materialDialogVisible = false
+      },
+      getTemplateOptions() {},
+      templatePaginationChange() {},
+      handlePaginationChange({ currentPage, pageSize }) {
+        this.pagination.currentPage = currentPage
+        this.pagination.pageSize = pageSize
+        this.loadMaterialData()
+      },
+    },
+  }
+</script>
+<style lang="scss" scoped>
+  @import '@/styles/costAudit.scss';
+</style>

+ 198 - 0
src/components/task/mounTaskComponents/surveyTab.vue

@@ -0,0 +1,198 @@
+<template>
+  <div>
+    <!-- 表格组件 -->
+    <CostAuditTable
+      style="width: 50%"
+      :table-data="surveyData.list"
+      :columns="getSurveyColumns()"
+      :show-action-column="true"
+    ></CostAuditTable>
+    <!-- 成本调查表查看弹窗 -->
+    <SurveyDialog
+      :dialog-visible="contentEditDialogVisible"
+      :dialog-title="contentEditDialogTitle"
+      :form-data="contentEditForm"
+      :audit-period-from-parent="auditPeriodFromProject"
+      :disabled="contentEditDisabled"
+      @cancel="handleContentEditCancel"
+    />
+  </div>
+</template>
+<script>
+  import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
+  import SurveyDialog from '@/views/costAudit/baseInfo/catalogManage/surveyDialog.vue'
+  import { getCostSurveyTemplates } from '@/api/catalogManage.js'
+  export default {
+    components: {
+      CostAuditTable,
+      SurveyDialog,
+    },
+    props: {
+      // 父组件传递的参数
+      project: {
+        type: Object,
+        default: () => {},
+      },
+      isView: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        // 内容编辑弹窗相关
+        contentEditDialogVisible: false,
+        contentEditDialogTitle: '内容维护',
+        contentEditDisabled: true,
+        contentEditForm: {
+          surveyTemplateName: '',
+          templateType: '1',
+          // 单记录列表
+          tableHeaders: [],
+          // 固定表列表
+          fixedTable: [],
+          // 动态表列表
+          dynamicTable: [],
+        },
+        // 内部管理调查数据
+        surveyData: {
+          list: [],
+        },
+      }
+    },
+    computed: {
+      // 从项目中提取监审期间(字符串或数组),优先 basicInfo.auditPeriod
+      auditPeriodFromProject() {
+        const p = this.project || {}
+        if (p.basicInfo && p.basicInfo.auditPeriod)
+          return p.basicInfo.auditPeriod
+        if (p.auditPeriod) return p.auditPeriod
+        if (p.data && p.data.basicInfo && p.data.basicInfo.auditPeriod) {
+          return p.data.basicInfo.auditPeriod
+        }
+        return ''
+      },
+    },
+    watch: {
+      // 监听project变化,确保有项目ID时刷新数据
+      project: {
+        handler(newVal) {
+          if (newVal && newVal.projectId) {
+            this.loadSurveyData()
+          }
+        },
+        deep: true,
+        immediate: true,
+      },
+    },
+    mounted() {
+      // 组件挂载时加载数据
+      if (this.project && this.project.projectId) {
+        this.loadSurveyData()
+      }
+    },
+
+    methods: {
+      // 加载调查模板数据
+      loadSurveyData() {
+        if (!this.project || !this.project.projectId) {
+          console.warn('项目ID不存在,无法加载调查模板数据')
+          return
+        }
+
+        const params = {
+          catalogId: this.project.catalogId,
+        }
+        getCostSurveyTemplates(params)
+          .then((res) => {
+            if (res && res.value) {
+              this.surveyData.list = Array.isArray(res.value) ? res.value : []
+            }
+          })
+          .catch((error) => {
+            console.error('加载调查模板数据失败:', error)
+            this.surveyData.list = []
+          })
+      },
+
+      // 获取带操作按钮的表格列配置
+      getSurveyColumns() {
+        return [
+          {
+            prop: 'surveyTemplateName',
+            label: '模板名称',
+            minWidth: 180,
+            align: 'left',
+          },
+          {
+            prop: 'templateType',
+            label: '模板类型',
+            width: 120,
+            align: 'center',
+            formatter: (row) => {
+              return row.templateType == '1'
+                ? '单记录'
+                : row.templateType == '2'
+                ? '固定表'
+                : '动态表'
+            },
+          },
+          {
+            prop: 'action',
+            label: '操作',
+            align: 'center',
+            width: 120,
+            actions: [
+              {
+                name: 'view',
+                label: '查看模板',
+                type: 'text',
+                onClick: this.handleViewTemplate,
+              },
+            ],
+          },
+        ]
+      },
+      // 查看成本调查表内容弹窗
+      handleViewTemplate(data) {
+        this.contentEditForm = {
+          surveyTemplateName: data.surveyTemplateName || '',
+          templateType: data.templateType || '1',
+          data: {
+            ...data,
+            surveyId: data.surveyTemplateId,
+          },
+          tableHeaders: [],
+          fixedTable: {
+            tableHeaders: [],
+            fixedTables: [],
+            fixedTablesTitle: [],
+            fixedTableHeaders: [],
+          },
+          dynamicTable: {
+            tableHeaders: [],
+            dynamicTables: [],
+            dynamicTablesTitle: [],
+            dynamicTableHeaders: [],
+          },
+          isDynamicTables: false,
+          isFixedTables: false,
+        }
+        this.contentEditDialogTitle = '查看'
+        this.contentEditDialogVisible = true
+      },
+      // 关闭内容编辑弹窗
+      handleContentEditCancel() {
+        this.contentEditDialogVisible = false
+        this.contentEditForm = {
+          surveyTemplateName: '',
+          templateType: '1',
+          tableHeaders: [],
+          fixedTable: [],
+          dynamicTable: [],
+        }
+      },
+    },
+  }
+</script>
+<style lang="scss" scoped></style>

+ 685 - 0
src/components/task/mounTaskComponents/workflowTab.vue

@@ -0,0 +1,685 @@
+<template>
+  <!-- 直接复用原 workflowTab 代码 -->
+  <div class="task-formulate">
+    <div class="operation-bar">
+      <span>预定的监审工作起止时间:</span>
+      <el-date-picker
+        v-model="formData.workflow.plannedAuditStartDate"
+        type="date"
+        placeholder="开始日期"
+        style="width: 150px; margin: 0 10px"
+        format="yyyy-MM-dd"
+        value-format="yyyy-MM-dd"
+        disabled
+      ></el-date-picker>
+      <span>至</span>
+      <el-date-picker
+        v-model="formData.workflow.plannedAuditEndDate"
+        type="date"
+        placeholder="结束日期"
+        style="width: 150px; margin: 0 10px"
+        format="yyyy-MM-dd"
+        value-format="yyyy-MM-dd"
+        disabled
+      ></el-date-picker>
+    </div>
+    <div class="operation-bar">
+      <el-button
+        type="success"
+        plain
+        icon="el-icon-circle-plus"
+        :disabled="isView"
+        @click="handleSetWorkflow"
+      >
+        工作环节设置
+      </el-button>
+    </div>
+    <CostAuditTable
+      :table-data="workflowData.list"
+      :columns="workflowData.listColumns"
+      :show-index="true"
+      :show-action-column="true"
+    >
+      <template #action="{ row }">
+        <el-button type="text" :disabled="isView" @click="handleSetStep(row)">
+          设置
+        </el-button>
+      </template>
+    </CostAuditTable>
+    <div class="table-description">
+      说明:为所有内部环节预置办理人员、流转操作按钮、环节截止时间;环节时限不能超过预定的工作流程起止时间。
+    </div>
+    <el-dialog
+      title="工作环节设置"
+      :visible.sync="dialogs.setStepDialogVisible"
+      width="70%"
+    >
+      <div class="operation-bar">
+        <span>请选择预定的监审工作起止时间:</span>
+        <el-date-picker
+          v-model="formData.workflow.plannedAuditStartDate"
+          type="date"
+          placeholder="开始日期"
+          style="width: 150px; margin: 0 10px"
+          format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd"
+        ></el-date-picker>
+        <span>至</span>
+        <el-date-picker
+          v-model="formData.workflow.plannedAuditEndDate"
+          type="date"
+          placeholder="结束日期"
+          style="width: 150px; margin: 0 10px"
+          format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd"
+        ></el-date-picker>
+      </div>
+      <CostAuditTable
+        :table-data="workflowData.stepList"
+        :columns="workflowData.workflowColumns"
+        :show-index="true"
+        :show-pagination="true"
+        :show-action-column="true"
+        :table-props="{
+          maxHeight: '500',
+        }"
+        :pagination="workflowData.pagination"
+        @pagination-change="handlePaginationChange"
+      >
+        <template #mainUserId="{ row }">
+          <el-select
+            v-if="row.nodeType == 1"
+            v-model="row.mainUserId"
+            filterable
+            allow-create
+            default-first-option
+            placeholder="请选择主办人员"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in formatterMainUserList()"
+              :key="item.userId"
+              :label="item.fullname"
+              :value="item.userId"
+            />
+          </el-select>
+        </template>
+        <template #userId="{ row }">
+          <el-select
+            v-if="row.nodeType == 1"
+            v-model="row.userId"
+            multiple
+            filterable
+            allow-create
+            default-first-option
+            placeholder="请选择从办人员"
+            style="width: 100%"
+            :disabled="!row.mainUserId"
+          >
+            <el-option
+              v-for="item in formatterUserList(row.mainUserId)"
+              :key="item.userId"
+              :label="item.fullname"
+              :value="item.userId"
+            />
+          </el-select>
+        </template>
+        <template #status="{ row }">
+          <el-select
+            v-if="row.nodeType == 1"
+            v-model="row.status"
+            multiple
+            filterable
+            allow-create
+            default-first-option
+            placeholder="请选择预置流转操作"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in dictData['processStatus']"
+              :key="item.key"
+              :label="item.name"
+              :value="item.key"
+            />
+          </el-select>
+        </template>
+        <template #endTime="{ row }">
+          <el-date-picker
+            v-model="row.endTime"
+            type="date"
+            placeholder="请选择截止时间"
+            format="yyyy-MM-dd"
+            value-format="yyyy-MM-dd"
+            style="width: 100%"
+            :picker-options="getPickerOptions(row)"
+          ></el-date-picker>
+        </template>
+      </CostAuditTable>
+      <div slot="footer" class="dialog-footer">
+        <el-button
+          type="primary"
+          :loading="loading.saveStep"
+          @click="handleSaveSetStep"
+        >
+          确认
+        </el-button>
+        <el-button @click="dialogs.setStepDialogVisible = false">
+          取消
+        </el-button>
+      </div>
+    </el-dialog>
+    <!-- 设置流程环节弹窗 -->
+    <el-dialog
+      v-loading="loading.save"
+      title="设置流程环节"
+      :visible.sync="dialogs.stepDialogVisible"
+      width="50%"
+      :close-on-click-modal="false"
+    >
+      <el-form
+        ref="currentStepForm"
+        :model="formData.currentStep"
+        label-width="120px"
+        :rules="formData.currentStepRules"
+      >
+        <el-form-item label="流程环节:" prop="processNodeValue">
+          <el-input
+            v-model="formData.currentStep.processNodeValue"
+            disabled
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="主办人员:" prop="mainUserId">
+          <el-select
+            v-model="formData.currentStep.mainUserId"
+            filterable
+            allow-create
+            default-first-option
+            placeholder="请选择主办人员"
+            style="width: 100%"
+            :disabled="formData.currentStep.nodeType == 0"
+          >
+            <el-option
+              v-for="item in formatterMainUserList()"
+              :key="item.userId"
+              :label="item.fullname"
+              :value="item.userId"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="从办人员:" prop="userId">
+          <el-select
+            v-model="formData.currentStep.userId"
+            multiple
+            filterable
+            allow-create
+            default-first-option
+            placeholder="请选择从办人员"
+            style="width: 100%"
+            :disabled="
+              !formData.currentStep.mainUserId ||
+              formData.currentStep.nodeType == 0
+            "
+          >
+            <el-option
+              v-for="item in formatterUserList(formData.currentStep.mainUserId)"
+              :key="item.userId"
+              :label="item.fullname"
+              :value="item.userId"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预置流转操作:" prop="status">
+          <el-select
+            v-model="formData.currentStep.status"
+            filterable
+            multiple
+            allow-create
+            default-first-option
+            placeholder="请选择预置流转操作"
+            style="width: 100%"
+            :disabled="formData.currentStep.nodeType == 0"
+          >
+            <el-option
+              v-for="item in dictData['processStatus']"
+              :key="item.key"
+              :label="item.name"
+              :value="item.key"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="截止时间:" prop="endTime">
+          <el-date-picker
+            v-model="formData.currentStep.endTime"
+            type="date"
+            placeholder="请选择截止时间"
+            format="yyyy-MM-dd"
+            value-format="yyyy-MM-dd"
+            style="width: 100%"
+            :disabled="isTimePickerDisabled"
+            :picker-options="getPickerOptions(formData.currentStep)"
+          ></el-date-picker>
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button
+          type="primary"
+          :loading="loading.save"
+          @click="handleSaveStep('currentStepForm', 'list')"
+        >
+          确认
+        </el-button>
+        <el-button @click="dialogs.stepDialogVisible = false">取消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+  import { taskMixin } from './index.js'
+  import {
+    getCostProjectNodeTmpletePageList,
+    saveProcess,
+  } from '@/api/taskCustomizedRelease.js'
+  import CostAuditTable from '@/components/costAudit/CostAuditTable.vue'
+  export default {
+    components: { CostAuditTable },
+    mixins: [taskMixin],
+    props: {
+      // 父组件传递的参数
+      project: {
+        type: Object,
+        default: () => {},
+      },
+      isView: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    data() {
+      return {
+        formData: {
+          currentStepRules: {
+            endTime: [
+              { required: true, message: '请选择截止时间', trigger: 'change' },
+              {
+                validator: (rule, value, callback) => {
+                  if (
+                    !this.formData.workflow.plannedAuditStartDate ||
+                    !this.formData.workflow.plannedAuditEndDate
+                  ) {
+                    return callback()
+                  }
+                  if (value < this.formData.workflow.plannedAuditStartDate) {
+                    return callback(new Error('截止时间不能早于工作开始时间'))
+                  }
+                  if (value > this.formData.workflow.plannedAuditEndDate) {
+                    return callback(new Error('截止时间不能晚于工作结束时间'))
+                  }
+                  callback()
+                },
+                trigger: 'change',
+              },
+            ],
+          },
+        },
+      }
+    },
+    computed: {
+      isTimePickerDisabled() {
+        if (
+          !this.formData.currentStep ||
+          !this.formData.currentStep.processNodeKey
+        ) {
+          return false
+        }
+
+        const stepList =
+          this.workflowData.list.length > 0
+            ? this.workflowData.list
+            : this.workflowData.stepList
+
+        if (!stepList || stepList.length <= 1) {
+          return false
+        }
+
+        const currentIndex = stepList.findIndex(
+          (item) =>
+            item.processNodeKey === this.formData.currentStep.processNodeKey
+        )
+
+        if (currentIndex <= 0) {
+          return false
+        }
+
+        const prevStep = stepList[currentIndex - 1]
+        return !prevStep.endTime
+      },
+    },
+    watch: {
+      'workflowData.stepList': {
+        handler(newList) {
+          if (newList && newList.length > 0) {
+            // 需要时可在此添加排序或其他处理
+          }
+        },
+        deep: true,
+      },
+    },
+    mounted() {
+      this.getWorkflow()
+    },
+
+    methods: {
+      formatterUserList(userId) {
+        return this.formatterMainUserList().filter(
+          (item) => item.userId !== userId
+        )
+      },
+      formatterMainUserList() {
+        if (!this.userList || !Array.isArray(this.userList)) {
+          return []
+        }
+
+        if (
+          !this.project ||
+          !this.project.projectMembers ||
+          typeof this.project.projectMembers !== 'string'
+        ) {
+          return this.userList
+        }
+
+        try {
+          const projectMemberIds = this.project.projectMembers.split(',')
+          const filteredUsers = this.userList.filter((item) =>
+            projectMemberIds.includes(item.userId)
+          )
+          return filteredUsers
+        } catch (error) {
+          return this.userList
+        }
+      },
+      getPickerOptions(row) {
+        const stepList = this.workflowData.stepList
+        const rowIndex = stepList.findIndex(
+          (item) => item.processNodeKey === row.processNodeKey
+        )
+
+        const pickerOptions = {
+          disabledDate: (time) => {
+            const startLimit = this.formData.workflow.plannedAuditStartDate
+              ? new Date(this.formData.workflow.plannedAuditStartDate).getTime()
+              : null
+            const endLimit = this.formData.workflow.plannedAuditEndDate
+              ? new Date(this.formData.workflow.plannedAuditEndDate).getTime()
+              : null
+
+            if (startLimit && time.getTime() < startLimit - 8.64e7) {
+              return true
+            }
+            if (endLimit && time.getTime() > endLimit) {
+              return true
+            }
+
+            if (rowIndex > 0) {
+              const prevStep = stepList[rowIndex - 1]
+              if (prevStep && prevStep.endTime) {
+                const prevEndTime = new Date(prevStep.endTime).getTime()
+                if (time.getTime() <= prevEndTime - 8.64e7) {
+                  return true
+                }
+              }
+            }
+
+            return false
+          },
+        }
+
+        return pickerOptions
+      },
+      handleSetWorkflow() {
+        this.getUser()
+        this.dialogs.setStepDialogVisible = true
+        if (this.workflowData.list.length > 0) {
+          this.workflowData.stepList = this.workflowData.list
+          this.workflowData.stepList = this.workflowData.list.map((item) => {
+            const mapped = {
+              ...item,
+              userId: item.userId ? item.userId.split(',') : [],
+              status: item.status ? item.status.split(',') : [],
+            }
+            if (!mapped.status || mapped.status.length === 0) {
+              const defaultKey = this.isArchiveNode(mapped)
+                ? this.getProcessStatusKeyByName('归档')
+                : this.getProcessStatusKeyByName('通过')
+              mapped.status = defaultKey ? [defaultKey] : []
+            }
+            return mapped
+          })
+        } else {
+          getCostProjectNodeTmpletePageList({
+            pageNum: this.workflowData.pagination.currentPage,
+            pageSize: this.workflowData.pagination.pageSize,
+            processId: '1',
+          }).then((res) => {
+            this.workflowData.stepList = res.value.records.map((item) => {
+              const mapped = {
+                ...item,
+                userId: item.userId ? item.userId.split(',') : [],
+                status: item.status ? item.status.split(',') : [],
+              }
+              if (!mapped.status || mapped.status.length === 0) {
+                const defaultKey = this.isArchiveNode(mapped)
+                  ? this.getProcessStatusKeyByName('归档')
+                  : this.getProcessStatusKeyByName('通过')
+                mapped.status = defaultKey ? [defaultKey] : []
+              }
+              return mapped
+            })
+            this.workflowData.pagination.total = res.value.total
+          })
+        }
+      },
+      handleSetStep(row) {
+        this.getUser()
+        const statusArr = row.status ? row.status.split(',') : []
+        let finalStatus = statusArr
+        if (!finalStatus || finalStatus.length === 0) {
+          const defaultKey = this.isArchiveNode(row)
+            ? this.getProcessStatusKeyByName('归档')
+            : this.getProcessStatusKeyByName('通过')
+          finalStatus = defaultKey ? [defaultKey] : []
+        }
+        this.formData.currentStep = {
+          ...row,
+          userId: row.userId ? row.userId.split(',') : [],
+          status: finalStatus,
+        }
+        this.dialogs.stepDialogVisible = true
+        this.loading.save = false
+        this.$nextTick(() => {
+          if (this.$refs.currentStepForm) {
+            this.$refs.currentStepForm.clearValidate()
+          }
+        })
+      },
+
+      handleSaveStep(formName, type) {
+        this.$refs[formName].validate((valid) => {
+          if (valid) {
+            const needKey = this.isArchiveNode(this.formData.currentStep)
+              ? this.getProcessStatusKeyByName('归档')
+              : this.getProcessStatusKeyByName('通过')
+            const needName = this.isArchiveNode(this.formData.currentStep)
+              ? '归档'
+              : '通过'
+            const statusArr = Array.isArray(this.formData.currentStep.status)
+              ? this.formData.currentStep.status
+              : []
+            if (needKey && !statusArr.includes(needKey)) {
+              const stepName =
+                this.formData.currentStep.processNodeValue ||
+                this.formData.currentStep.processNodeKey ||
+                ''
+              this.$message.error(
+                `流程环节【${stepName}】预置流转操作必须包含“${needName}”`
+              )
+              return
+            }
+            if (type == 'list') {
+              this.workflowData.list = this.workflowData.list.map((item) => {
+                if (
+                  item.processNodeKey ==
+                  this.formData.currentStep.processNodeKey
+                ) {
+                  return {
+                    ...item,
+                    ...this.formData.currentStep,
+                    userId: this.formData.currentStep.userId.join(','),
+                    status: this.formData.currentStep.status.join(','),
+                  }
+                }
+                return item
+              })
+              this.$emit('update:workflowData', { ...this.workflowData })
+              this.handleSaveSetStep('list')
+            }
+          } else {
+            return false
+          }
+        })
+      },
+      handleSaveSetStep(type) {
+        if (!this.formData.workflow.plannedAuditStartDate) {
+          this.$message.error('请选择预定的监审工作开始日期')
+          return
+        }
+        if (!this.formData.workflow.plannedAuditEndDate) {
+          this.$message.error('请选择预定的监审工作结束日期')
+          return
+        }
+
+        let stepList =
+          type === 'list' ? this.workflowData.list : this.workflowData.stepList
+        if (stepList && stepList.length > 1) {
+          const sortedSteps = [...stepList].sort((a, b) => {
+            return stepList.indexOf(a) - stepList.indexOf(b)
+          })
+
+          for (let i = 1; i < sortedSteps.length; i++) {
+            const currentStep = sortedSteps[i]
+            const prevStep = sortedSteps[i - 1]
+
+            if (currentStep.endTime && prevStep.endTime) {
+              if (new Date(currentStep.endTime) <= new Date(prevStep.endTime)) {
+                this.$message.error(
+                  `第${i + 1}个环节的截止时间必须大于第${i}个环节的截止时间`
+                )
+                return
+              }
+            } else if (currentStep.endTime && !prevStep.endTime) {
+              this.$message.error(`请先设置第${i}个环节的截止时间`)
+              return
+            } else if (!currentStep.endTime && prevStep.endTime) {
+              this.$message.error(`请设置第${i + 1}个环节的截止时间`)
+              return
+            }
+          }
+        }
+
+        const listToCheck =
+          type === 'list'
+            ? this.workflowData.list.map((it) => ({
+                ...it,
+                statusArr:
+                  typeof it.status === 'string' && it.status
+                    ? it.status.split(',')
+                    : Array.isArray(it.status)
+                    ? it.status
+                    : [],
+              }))
+            : this.workflowData.stepList.map((it) => ({
+                ...it,
+                statusArr: Array.isArray(it.status) ? it.status : [],
+              }))
+        for (const it of listToCheck) {
+          const needKey = this.isArchiveNode(it)
+            ? this.getProcessStatusKeyByName('归档')
+            : this.getProcessStatusKeyByName('通过')
+          const needName = this.isArchiveNode(it) ? '归档' : '通过'
+          if (needKey && !it.statusArr.includes(needKey)) {
+            const stepName = it.processNodeValue || it.processNodeKey || ''
+            this.$message.error(
+              `流程环节【${stepName}】预置流转操作必须包含“${needName}”`
+            )
+            return
+          }
+        }
+
+        this.loading.save = true
+        let data = {
+          nodeReqList: [
+            {
+              endTime: '',
+              id: '',
+              mainUserId: '',
+              processNodeKey: '',
+              status: '',
+              userId: '',
+            },
+          ],
+          plannedAuditEndDate: this.formData.workflow.plannedAuditEndDate,
+          plannedAuditStartDate: this.formData.workflow.plannedAuditStartDate,
+          processId: this.workflowData.detailInfo.processId || '1',
+          projectId: this.project.projectId,
+        }
+        if (type == 'list') {
+          data.nodeReqList = this.workflowData.list
+        } else {
+          data.nodeReqList = this.workflowData.stepList.map((item) => {
+            return {
+              ...item,
+              userId: item.userId.join(','),
+              status: item.status.join(','),
+            }
+          })
+        }
+        saveProcess(data)
+          .then((res) => {
+            if (res.code === 200) {
+              this.$message.success('保存成功')
+              this.loading.save = false
+              if (type == 'list') {
+                this.dialogs.stepDialogVisible = false
+              } else {
+                this.dialogs.setStepDialogVisible = false
+              }
+              this.getWorkflow()
+            }
+          })
+          .catch(() => {
+            this.loading.save = false
+          })
+      },
+      isArchiveNode(row) {
+        const key = (row && row.processNodeKey) || ''
+        const val = (row && row.processNodeValue) || ''
+        const keyLower = String(key).toLowerCase()
+        return (
+          String(val).includes('归档') ||
+          keyLower === 'gd' ||
+          keyLower === 'guidang' ||
+          keyLower.includes('archive')
+        )
+      },
+      getProcessStatusKeyByName(name) {
+        const list = (this.dictData && this.dictData['processStatus']) || []
+        if (!Array.isArray(list)) return ''
+        const item = list.find((it) => it.name === name)
+        return item ? item.key : ''
+      },
+    },
+  }
+</script>
+<style scoped lang="scss">
+  @import '@/styles/costAudit.scss';
+</style>

+ 3 - 1
src/views/costAudit/baseInfo/catalogManage/surveyDialog.vue

@@ -7,6 +7,7 @@
       :close-on-click-modal="false"
       :show-confirm-btn="false"
       cancel-text="关闭"
+      append-to-body
       @cancel="handleCancel"
     >
       <div class="content-edit-container">
@@ -638,7 +639,8 @@
       // 监听表单数据变化
       formData: {
         handler(newVal) {
-          if (Object.keys(newVal).length > 0 && this.dialogVisible) {
+          // 只有在弹窗未打开时预初始化一次数据,避免弹窗打开后多次重复请求
+          if (Object.keys(newVal).length > 0 && !this.dialogVisible) {
             this.initForm()
           }
         },

+ 42 - 13
src/views/costAudit/projectInfo/auditTaskManage/taskProgressManage/index.vue

@@ -298,12 +298,21 @@
         <el-button type="primary" @click="handleDelegateSubmit">发送</el-button>
       </div>
     </el-dialog>
-    <!-- 任务详情弹窗(统一详情/查看入口) -->
+    <!-- 任务详情弹窗(原组件暂保留,不再从列表入口打开) -->
     <task-detail
       ref="taskDetail"
       :visible.sync="taskDetailVisible"
       :is-view="isView"
     />
+
+    <!-- 成本监审任务制定弹窗(封装自 tabs.vue,列表“详情/查看”入口使用) -->
+    <task-customized-release-dialog
+      :visible.sync="taskReleaseDialogVisible"
+      :project="project"
+      :is-view="true"
+      @backToList="taskReleaseDialogVisible = false"
+      @close="taskReleaseDialogVisible = false"
+    />
   </div>
 </template>
 
@@ -316,11 +325,13 @@
   import { getCostProjectDetail } from '@/api/taskCustomizedRelease.js'
   import { doProcessBtn } from '@/api/dataPreliminaryReview'
   import TaskDetail from '@/components/task/taskDetail.vue'
+  import TaskCustomizedReleaseDialog from '@/components/task/TaskCustomizedReleaseDialog.vue'
   import { createSuperviseTask } from '@/api/audit/supervise'
   export default {
     components: {
       CostAuditTable,
       TaskDetail,
+      TaskCustomizedReleaseDialog,
     },
     mixins: [dictMixin],
     data() {
@@ -436,7 +447,7 @@
           },
           {
             prop: 'warning',
-            label: '预警1',
+            label: '预警',
             width: 80,
             align: 'center',
             slotName: 'warning',
@@ -479,6 +490,8 @@
         userList: [],
         // 任务详情弹窗
         taskDetailVisible: false,
+        // 成本监审任务制定弹窗
+        taskReleaseDialogVisible: false,
       }
     },
     mounted() {
@@ -672,12 +685,8 @@
         return ''
       },
       handleView(row) {
-        // 子任务查看:打开统一任务详情弹窗
-        this.isView = true
-        this.taskDetailVisible = true
-        this.$nextTick(() => {
-          this.$refs.taskDetail && this.$refs.taskDetail.open(row, 'chengben')
-        })
+        // 子任务查看:统一走任务制定弹窗(只读)
+        this.openTaskReleaseDialog(row)
       },
       // 任务中止相关方法
       handlePause(row) {
@@ -854,12 +863,32 @@
 
       // 任务详情相关方法
       handleViewTaskDetail(row) {
-        // 主任务详情:打开统一任务详情弹窗
+        // 主任务详情:打开成本监审任务制定弹窗(只读)
+        this.openTaskReleaseDialog(row)
+      },
+      // 打开成本监审任务制定弹窗(从进度列表“详情/查看”入口)
+      openTaskReleaseDialog(row) {
+        if (!row) return
+        const projectId =
+          row.projectId || row.projectID || row.id || row.taskId || ''
+        if (!projectId) {
+          this.$message &&
+            this.$message.warning &&
+            this.$message.warning('缺少项目ID,无法查看详情')
+          return
+        }
         this.isView = true
-        this.taskDetailVisible = true
-        this.$nextTick(() => {
-          this.$refs.taskDetail && this.$refs.taskDetail.open(row, 'chengben')
-        })
+        // 加载项目详情后再打开弹窗
+        getCostProjectDetail({ id: projectId })
+          .then((res) => {
+            this.project = (res && res.value) || {}
+            this.taskReleaseDialogVisible = true
+          })
+          .catch(() => {
+            // 回退:若接口失败,至少把当前行数据带入
+            this.project = row || {}
+            this.taskReleaseDialogVisible = true
+          })
       },
       getProject() {
         getCostProjectDetail({