gaoluyang
2 天以前 29be8639dc16b5d82bddc6099b1fa5cdca75f77a
src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
@@ -1,38 +1,82 @@
<template>
  <!--  7.7质量监控计划-->
  <div class="app-container">
    <div class="table-card">
      <div style="display: flex;justify-content: space-between">
        <div style="display: flex;">
          <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
            <span style="width: 88px;font-size: 14px;font-weight: 700;color: #606266;">计划名称</span>
            <el-input size="small" placeholder="请输入" clearable v-model="yearForm.monitorName"
                      @keyup.enter="getYearPlanList"></el-input>
          </div>
          <div style="line-height: 30px;">
            <el-button type="primary" size="small" @click="getYearPlanList">查询</el-button>
            <el-button size="small" @click="clearYear">重置</el-button>
          </div>
    <section v-if="pageMode === 'list'" class="plan-list-card">
      <div class="plan-list-toolbar">
        <div class="plan-list-filters">
          <span class="annual-plan-label">计划年度</span>
          <el-date-picker v-model="selectedYear" type="year" value-format="YYYY" format="YYYY年" :clearable="false"
            size="small" style="width: 130px" @change="handleYearChange" />
          <span class="filter-label">计划名称</span>
          <el-input v-model="yearForm.monitorName" clearable placeholder="请输入" size="small" style="width: 220px"
            @keyup.enter="getYearPlanList" />
          <el-button type="primary" size="small" @click="getYearPlanList">查询</el-button>
          <el-button size="small" @click="clearYear">重置</el-button>
        </div>
        <div style="line-height: 30px;">
          <el-button size="small" type="primary" @click="leadInto">导入</el-button>
        <div class="plan-list-actions">
          <el-button size="small" @click="leadInto">导入</el-button>
          <el-button type="primary" size="small" @click="openCreatePlan">新建年度计划</el-button>
        </div>
      </div>
      <lims-table :tableData="yearTableData" :column="yearColumnData" :page="yearPage" :tableLoading="yearLoading"
                  :height="'calc(100vh - 34em)'" @pagination="pagination" :rowClick="rowClick" key="yearTableData"></lims-table>
      <lims-table :tableData="yearTableData" :column="yearColumnData" :page="yearPage"
        :tableLoading="yearLoading" :height="'calc(100vh - 11em)'" @pagination="pagination" key="yearTableData">
        <template #planActions="{ row }">
          <div class="detail-actions">
            <el-button link type="primary" size="small" @click="openPlan(row)">查看计划</el-button>
            <el-button v-if="canReviewRow(row)" link type="primary" size="small" @click="examinePlan(row)">审核</el-button>
            <el-button v-if="canApproveRow(row)" link type="primary" size="small" @click="approvalPlan(row)">批准</el-button>
            <el-dropdown trigger="click" @command="command => handlePlanCommand(command, row)">
              <el-button link type="primary" size="small">更多⌄</el-button>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item command="export" :disabled="row.ratifyStatus !== 1">导出年度计划</el-dropdown-item>
                  <el-dropdown-item command="delete" divided :disabled="!canDeleteRow(row)">删除</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </div>
        </template>
      </lims-table>
    </section>
    <template v-else>
    <div class="annual-plan-header">
      <div class="annual-plan-main">
        <el-button link type="primary" size="small" @click="backToPlanList">← 返回年度计划</el-button>
        <span class="annual-plan-label">{{ selectedYear }}年度</span>
        <template v-if="currentPlan">
          <strong>{{ currentPlan.monitorName }}</strong>
          <el-tag :type="planStatusType">{{ planStatusText }}</el-tag>
          <span>编制:{{ currentPlan.writeName || '-' }} {{ formatDate(currentPlan.writeTime) }}</span>
          <span>审核:{{ currentPlan.examineName || '-' }}</span>
          <span>批准:{{ currentPlan.ratifyName || '-' }}</span>
        </template>
        <el-tag v-else type="info">该年度尚未建立计划</el-tag>
      </div>
      <div class="annual-plan-actions">
        <el-button v-if="canEditPlan" type="primary" size="small" @click="submitPlan">提交审核</el-button>
        <el-button v-if="canReviewPlan" type="primary" size="small" @click="examinePlan(currentPlan)">审核</el-button>
        <el-button v-if="canApprovePlan" type="primary" size="small" @click="approvalPlan(currentPlan)">批准</el-button>
        <el-button size="small" @click="leadInto">导入</el-button>
        <el-button v-if="currentPlan" size="small" :disabled="currentPlan.ratifyStatus !== 1" @click="handleDown(currentPlan)">导出年度计划</el-button>
      </div>
    </div>
    <div style="margin-top: 20px">
      <div style="display: flex;justify-content: space-between">
    <div class="plan-detail-card">
      <el-alert v-if="currentPlan && currentPlan.ratifyStatus !== 1" type="info" :closable="false" show-icon
        style="margin-bottom: 12px"
        title="检测类质控项目需在年度计划审核、批准通过后,点击对应行的“下发任务”进行下发;非检测类项目无需下发。" />
      <div class="plan-detail-toolbar">
        <div style="display: flex;">
          <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
            <span style="width: 88px;font-size: 14px;font-weight: 700;color: #606266;">监控目的</span>
            <el-input v-model="yearDetailForm.monitorPurpose" placeholder="请输入" size="small"
            <span style="width: 88px;font-size: 14px;font-weight: 700;color: var(--lims-text-2);">质控项目</span>
            <el-input v-model="yearDetailForm.monitorProject" placeholder="请输入" size="small"
                      @keyup.enter="getYearDetailPlanList"></el-input>
          </div>
          <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
            <span style="width: 88px;font-size: 14px;font-weight: 700;color: #606266;">监控项目</span>
            <el-input v-model="yearDetailForm.monitorProject" placeholder="请输入" size="small"
                      @keyup.enter="getYearDetailPlanList"></el-input>
            <span style="width: 70px;font-size: 14px;font-weight: 700;color: var(--lims-text-2);">负责人</span>
            <el-select v-model="yearDetailForm.responsibleUserId" clearable filterable size="small" style="width: 160px">
              <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id" />
            </el-select>
          </div>
          <div style="line-height: 30px;">
            <el-button type="primary" size="small" @click="getYearDetailPlanList">查询</el-button>
@@ -40,14 +84,65 @@
          </div>
        </div>
        <div style="line-height: 30px;">
          <el-button size="small" type="primary" @click="exportSignatureTemplate">导出签名模板</el-button>
          <el-button size="small" type="primary" @click="showDialog('add')">新增</el-button>
<!--          <el-button size="small" type="primary" @click="exportSignatureTemplate">导出签名模板</el-button>-->
          <el-button size="small" type="primary" :disabled="!canEditPlan" @click="showDialog('add')">新增质控项目</el-button>
        </div>
      </div>
      <lims-table :tableData="yearDetailTableData" :column="yearDetailColumnData" :page="yearDetailPage"
        :tableLoading="yearDetailLoading" :height="'calc(100vh - 34em)'" @pagination="pagination1" ref="yearDetailTableData"
        key="yearDetailTableData"></lims-table>
        :tableLoading="yearDetailLoading" :height="'calc(100vh - 15em)'" @pagination="pagination1" ref="yearDetailTableData"
        key="yearDetailTableData">
        <template #detailActions="{ row }">
          <div class="detail-actions">
            <el-button link type="primary" size="small" :disabled="!canEditPlan || !!row.inspectionTaskId"
              @click="showDialog('edit', row)">编辑</el-button>
            <el-button v-if="row.itemKind !== 'INSPECTION'" link type="primary" size="small"
              @click="carryOut(row)">实施</el-button>
            <el-button v-else-if="!row.inspectionTaskId" link type="primary" size="small"
              :disabled="row.planRatifyStatus !== 1" @click="dispatchTask(row)">下发任务</el-button>
            <el-button v-else link type="primary" size="small" @click="evaluate(row)">
              {{ row.inspectionTaskState === 5 ? '评价记录' : '评价要求' }}
            </el-button>
            <el-dropdown trigger="click" @command="command => handleDetailCommand(command, row)">
              <el-button link type="primary" size="small">更多⌄</el-button>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item v-if="row.itemKind !== 'INSPECTION'" command="complete">完成报告</el-dropdown-item>
                  <el-dropdown-item v-if="row.itemKind !== 'INSPECTION'" command="evaluate">评价</el-dropdown-item>
                  <el-dropdown-item v-if="row.itemKind === 'INSPECTION' && !row.inspectionTaskId" command="requirement">评价要求</el-dropdown-item>
                  <el-dropdown-item v-if="row.itemKind === 'INSPECTION' && row.inspectionTaskId" command="cancel"
                    :disabled="row.inspectionTaskOrderState !== 1 || row.inspectionTaskState !== 0">取消任务</el-dropdown-item>
                  <el-dropdown-item command="attachment">附件上传</el-dropdown-item>
                  <el-dropdown-item command="export">导出</el-dropdown-item>
                  <el-dropdown-item command="delete" divided
                    :disabled="!canEditPlan || !!row.inspectionTaskId">删除</el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </div>
        </template>
      </lims-table>
    </div>
    </template>
    <el-dialog v-model="createPlanDialog" title="新建年度质量控制计划" width="520px">
      <el-form :model="planForm" label-width="90px">
        <el-form-item label="年度">
          <el-date-picker v-model="planForm.monitorYear" type="year" value-format="YYYY" format="YYYY年"
            :clearable="false" style="width: 100%" placeholder="请选择年度" />
        </el-form-item>
        <el-form-item label="计划名称"><el-input v-model="planForm.monitorName" /></el-form-item>
        <el-form-item label="审核人">
          <el-select v-model="planForm.examineUserId" filterable style="width:100%">
            <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="createPlanDialog=false">取消</el-button>
        <el-button type="primary" :loading="createPlanLoading" @click="createPlan">确定</el-button>
      </template>
    </el-dialog>
    <!--新增修改弹框-->
    <detail-form-dialog v-if="formDia" ref="formDia" :qualityMonitorId="qualityMonitorId"
      @closeDia="closeDia"></detail-form-dialog>
@@ -56,6 +151,8 @@
      @closeCarryOutDia="closeCarryOutDia"></carry-out-dialog>
    <!--评价流程弹框-->
    <evaluate-dialog v-if="evaluateDialog" ref="evaluateDialog" @closeEvaDia="closeEvaDia"></evaluate-dialog>
    <inspection-evaluation-dialog v-if="inspectionEvaluateDialog" ref="inspectionEvaluateDialog"
      @close="closeInspectionEvaluateDialog" />
    <el-dialog v-model="examineDialog" title="审核" width="30%" @close="closeExamineDia">
      <span>
        审核备注:
@@ -117,27 +214,38 @@
    <!--导入计划-->
    <el-dialog v-model="uploadDia" title="数据导入" width="500px" :close-on-click-modal="false"
      :close-on-press-escape="false">
      <div style="display: flex; align-items: center;">
        <span style="width: 60px">年份:</span>
        <el-date-picker v-model="planYear" type="year" value-format="YYYY" clearable size="small" format="YYYY"
          placeholder="选择年">
        </el-date-picker>
      </div>
      <div style="display: flex;align-items: center;margin: 10px 0">
        <div style="width: 60px">审核人:</div>
        <el-select v-model="examineUserId" clearable filterable size="small" style="width: 50%;">
          <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
          </el-option>
        </el-select>
      <el-form label-width="70px" size="small">
        <el-form-item label="年份">
          <el-date-picker v-model="planYear" type="year" value-format="YYYY" clearable format="YYYY"
            placeholder="选择年" style="width: 100%">
          </el-date-picker>
        </el-form-item>
        <el-form-item label="审核人">
          <el-select v-model="examineUserId" clearable filterable style="width: 100%">
            <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
            </el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
        <span>第一次导入请先下载模板,按模板格式填写后再上传。</span>
        <el-button link type="primary" @click="downloadTemplate">下载导入模板</el-button>
      </div>
      <div style="margin: 0 auto;">
        <el-upload ref="upload" :action="action" :auto-upload="false" :file-list="fileList" :headers="uploadHeader"
          :limit="1" accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
        <el-upload ref="upload" :action="action" :auto-upload="false" v-model:file-list="fileList" :headers="uploadHeader"
          :limit="1" accept=".xls,.xlsx"
          :on-change="beforeUpload" :on-error="onError" :on-success="handleSuccessUp" drag
          :data="{ planYear: planYear, examineUserId: examineUserId }" name="file">
          :data="{ monitorYear: planYear, examineUserId: examineUserId }" name="file">
          <lims-icon name="el-icon-upload"></lims-icon>
          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        </el-upload>
      </div>
      <div style="margin-top: 10px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px;">
        <div>支持格式:.xls、.xlsx;单个文件不超过10MB。</div>
        <div>质控项目、技术标准、质控方式、完成时限、负责人为必填项。</div>
        <div>完成时限格式必须为 YYYY-MM(模板中该列为文本格式,直接输入 2026-12 即可);负责人必须填写系统中已存在的人员姓名。</div>
        <div>导入后计划保存为草稿。请在计划详情中编辑质控项目并选择事项类别;检测类还需选择检测标准、原始记录模板、检测员、评价人并填写任务截止日,确认后再提交审核。</div>
        <div>导入只读取模板的第一个工作表。</div>
      </div>
      <template #footer><span  class="dialog-footer">
        <el-button @click="uploadDia = false">取 消</el-button>
@@ -152,7 +260,7 @@
      </div>
      <div style="margin: 0 auto;">
        <el-upload ref="upload1" :action="action1" :auto-upload="false"
          :data="{ qualityMonitorDetailsId: qualityMonitorDetailsId }" :file-list="fileList1" :headers="uploadHeader"
          :data="{ qualityMonitorDetailsId: qualityMonitorDetailsId }" v-model:file-list="fileList1" :headers="uploadHeader"
          :limit="1" :on-change="beforeUpload1" :on-error="onError1" :on-success="onSuccess1" accept='.doc,.docx,application/pdf,' drag
          name="file">
          <lims-icon name="el-icon-upload"></lims-icon>
@@ -170,17 +278,20 @@
<script>
import DetailFormDialog from './components/detailFormDialog.vue';
import InspectionEvaluationDialog from './components/inspectionEvaluationDialog.vue';
import CarryOutDialog from './components/carryOutDialog.vue';
import EvaluateDialog from './components/evaluateDialog.vue';
import filePreview from "@/components/Preview/filePreview.vue";
import limsTable from "@/components/Table/lims-table.vue";
import {
  createAnnualPlan, getAnnualPlan, submitAnnualPlan,
  delQualityMonitorDetail,
  delQualitySupervise,
  examineQualityMonitor, exportQualityMonitorDetail, exportQualityMonitorEvaluate, exportQualityMonitorRatify,
  pageQualityMonitor, pageQualityMonitorDetail, ratifyFinishReport,
  ratifyQualityMonitor
} from "@/api/cnas/process/ensureResults/qualityMonitor";
import { dispatchInspectionTask, cancelInspectionTask } from "@/api/cnas/process/ensureResults/qualityMonitor";
import { selectUserCondition } from "@/api/performance/class";
import { mapGetters } from "@/utils/store-compat";
import ViewRecord from "./components/ViewRecord.vue";
@@ -188,11 +299,19 @@
export default {
  name: 'EnsureResultsValidity',
  // import 引入的组件需要注入到对象中才能使用
  components: {ViewRecord, limsTable, filePreview, EvaluateDialog, CarryOutDialog, DetailFormDialog },
  components: {ViewRecord, limsTable, filePreview, EvaluateDialog, CarryOutDialog, DetailFormDialog, InspectionEvaluationDialog },
  data() {
    // 这里存放数据
    return {
      ViewRecord: false,
      inspectionEvaluateDialog: false,
      pageMode: 'list',
      selectedYear: String(new Date().getFullYear()),
      currentPlan: null,
      selectedPlanId: null,
      createPlanDialog: false,
      createPlanLoading: false,
      planForm: { monitorYear: String(new Date().getFullYear()), monitorName: '', examineUserId: null },
      examineUserId: '',
      responsibleOptions: [],
      yearForm: {
@@ -207,247 +326,91 @@
        {
          label: '计划名称',
          prop: 'monitorName',
          width: '150px'
          minWidth: '220px'
        }, {
          label: '计划年度',
          prop: 'monitorYear',
          width: '100px'
        }, {
          dataType: 'tag',
          label: '计划状态',
          prop: 'workflowStatus',
          width: '110px',
          formatData: (value, row) => this.planWorkflowText(value),
          formatType: value => this.planWorkflowType(value)
        }, {
          label: '编制人',
          prop: 'writeName',
          width: '100'
          minWidth: '100px'
        }, {
          label: '编制日期',
          prop: 'writeTime',
          width: '160'
        }, {
          dataType: 'tag',
          label: '审核状态',
          prop: 'examineStatus',
          width: '100',
          formatData: (params) => {
            if (params === 0) {
              return '不通过';
            } else if (params === 1) {
              return '通过';
            } else {
              return null;
            }
          },
          formatType: (params) => {
            if (params === 0) {
              return 'danger';
            } else if (params === 1) {
              return 'success';
            } else {
              return null;
            }
          }
        }, {
          label: '审核内容',
          prop: 'examineRemark',
          width: '100'
          minWidth: '150px',
          formatData: value => this.formatDate(value)
        }, {
          label: '审核人',
          prop: 'examineName',
          width: '100'
          minWidth: '100px'
        }, {
          label: '审核日期',
          prop: 'examineTime',
          width: '160'
        }, {
          dataType: 'tag',
          label: '批准状态',
          prop: 'ratifyStatus',
          width: '100',
          formatData: (params) => {
            if (params === 0) {
              return '不批准';
            } else if (params === 1) {
              return '批准';
            } else {
              return null;
            }
          },
          formatType: (params) => {
            if (params === 0) {
              return 'danger';
            } else if (params === 1) {
              return 'success';
            } else {
              return null;
            }
          }
        }, {
          label: '批准内容',
          prop: 'ratifyRemark',
          width: '100'
          minWidth: '150px',
          formatData: value => this.formatDate(value)
        }, {
          label: '批准人',
          prop: 'ratifyName',
          width: '100'
          minWidth: '100px'
        }, {
          label: '批准日期',
          prop: 'ratifyTime',
          width: '160'
          minWidth: '150px',
          formatData: value => this.formatDate(value)
        }, {
          dataType: 'action',
          dataType: 'slot',
          slot: 'planActions',
          label: '操作',
          fixed: 'right',
          operation: [
            {
              name: '审核',
              type: 'text',
              disabled: (row) => {
                if (row.examineStatus === 1 || this.userId != row.examineUserId) {
                  return true
                } else {
                  return false
                }
              },
              clickFun: (row) => {
                this.examinePlan(row)
              }
            },
            {
              name: '批准',
              type: 'text',
              disabled: (row) => {
                if (row.ratifyStatus === 1 || row.examineStatus !== 1 || this.userId != row.ratifyUserId) {
                  return true
                } else {
                  return false
                }
              },
              clickFun: (row) => {
                this.approvalPlan(row)
              }
            },
            {
              name: '导出',
              type: 'text',
              clickFun: (row) => {
                this.handleDown(row)
              },
              disabled: (row) => {
                if (row.ratifyStatus !== 1) {
                  return true
                } else {
                  return false
                }
              },
            },
            {
              name: '删除',
              type: 'text',
              color: 'var(--el-color-danger)',
              clickFun: (row) => {
                this.delPlan(row)
              },
              disabled: (row) => {
                if (row.examineStatus === 1) {
                  return true;
                } else {
                  return false;
                }
              },
            }
          ]
          width: '220px'
        }],
      yearTableData: [],  // 年表
      yearLoading: false,
      yearDetailForm: {
        monitorPurpose: '',
        monitorProject: '',
        responsibleUserId: null,
      },
      yearDetailColumnData: [
        {
          label: '监控目的',
          prop: 'monitorPurpose',
          width: '150px',
          showOverflowTooltip: true,
        }, {
          label: '计划开展时间',
          prop: 'plannedTime',
          width: '150px'
        }, {
          label: '监控项目',
          label: '质控项目',
          prop: 'monitorProject',
          width: '150px'
          minWidth: '140px'
        }, {
          label: '参加单位(人员)',
          prop: 'participant',
          width: '190px'
          label: '技术标准',
          prop: 'technicalStandardText',
          minWidth: '220px'
        }, {
          label: '预算(元)',
          prop: 'budget',
          width: '150px'
        }, {
          label: '组织人员',
          prop: 'organization',
          width: '150px'
        }, {
          label: '监控方式',
          label: '质控方式',
          prop: 'monitorWay',
          width: '150px'
        },{
          dataType: 'tag',
          label: '实施状态',
          prop: 'detailsRatifyStatus',
          width: '100',
          formatData: (params) => {
            if (params === 0) {
              return '未开始';
            } else if (params === 1) {
              return '待评价';
            }  else if (params === 2) {
              return '不批准';
            } else if (params === 3) {
              return '已批准';
            } else {
              return null;
            }
          },
          formatType: (params) => {
            if (params === 0) {
              return '';
            } else if (params === 1) {
              return 'warning';
            } else if (params === 2) {
              return 'danger';
            }  else if (params === 3) {
              return 'success';
            } else {
              return null;
            }
          }
        },{
          dataType: 'tag',
          label: '报告状态',
          prop: 'reportStatus',
          width: '100',
          formatData: (params) => {
            if (params === 0) {
              return '未开始';
            } else if (params === 1) {
              return '待评价';
            }  else if (params === 2) {
              return '不批准';
            } else if (params === 3) {
              return '已批准';
            } else {
              return null;
            }
          },
          formatType: (params) => {
            if (params === 0) {
              return '';
            } else if (params === 1) {
              return 'warning';
            } else if (params === 2) {
              return 'danger';
            }  else if (params === 3) {
              return 'success';
            } else {
              return null;
            }
          }
          minWidth: '120px'
        }, {
          label: '完成时限',
          prop: 'completionMonth',
          width: '110px',
          formatData: value => value ? value.replace('-', '.') : ''
        }, {
          label: '负责人',
          prop: 'responsibleName',
          minWidth: '100px'
        }, {
          label: '评价人',
          prop: 'evaluatorName',
          minWidth: '100px'
        }, {
          label: '事项类别', prop: 'itemKind', width: '100px',
          formatData: value => value === 'INSPECTION' ? '检测类' : '非检测类'
        }, {
          label: '监控任务编号', prop: 'inspectionTaskCode', minWidth: '150px'
        }, {
          label: '任务状态', prop: 'taskStatusText', width: '110px'
        },{
          dataType: 'tag',
          label: '评价状态',
@@ -480,61 +443,11 @@
            }
          }
        }, {
          dataType: 'action',
          dataType: 'slot',
          slot: 'detailActions',
          label: '操作',
          fixed: 'right',
          operation: [
            {
              name: '编辑',
              type: 'text',
              clickFun: (row) => {
                this.showDialog('edit', row)
              }
            },
            {
              name: '实施',
              type: 'text',
              clickFun: (row) => {
                this.carryOut(row)
              }
            },
            {
              name: '完成报告',
              type: 'text',
              clickFun: (row) => {
                this.record(row)
              },
            },
            {
              name: '评价',
              type: 'text',
              clickFun: (row) => {
                this.evaluate(row)
              }
            },
            {
              name: '附件上传',
              type: 'text',
              clickFun: (row) => {
                this.viewTestRecord(row)
              }
            },
            {
              name: '导出',
              type: 'text',
              clickFun: (row) => {
                this.downLoadPost(row);
              },
            },
            {
              name: '删除',
              type: 'text',
              color: 'var(--el-color-danger)',
              clickFun: (row) => {
                this.delYearPlanDetail(row)
              }
            }
          ]
          width: '220px'
        }
      ],
      yearDetailTableData: [], // 年明细表
@@ -574,31 +487,91 @@
    };
  },
  mounted() {
    this.getUserList()
    this.getYearPlanList()
  },
  // 方法集合
  methods: {
    // 查询年度计划表
    getYearPlanList() {
      const entity = {
        monitorName: this.yearForm.monitorName,
      }
      const page = this.yearPage
      this.yearLoading = true
      pageQualityMonitor({ ...entity, ...page }).then(res => {
        this.yearLoading = false
        this.yearTableData = res.data.records
        this.yearPage.total = res.data.total
        if (this.yearTableData.length > 0) {
          this.rowClick(this.yearTableData[0])
        }
      }).catch(err => {
        console.log('err---', err);
      const entity = {
        monitorYear: this.selectedYear,
        monitorName: this.yearForm.monitorName
      }
      return pageQualityMonitor({ ...entity, ...this.yearPage }).then(res => {
        this.yearTableData = res.data?.records || []
        this.yearPage.total = res.data?.total || 0
      }).finally(() => {
        this.yearLoading = false
      })
    },
    loadAnnualPlan() {
      if (!this.selectedPlanId) return Promise.resolve()
      return this.loadPlanVersion()
    },
    loadPlanVersion() {
      if (!this.selectedPlanId) return Promise.resolve()
      return getAnnualPlan({ monitorYear: this.selectedYear, qualityMonitorId: this.selectedPlanId }).then(res => {
        this.currentPlan = res.data || null
        this.qualityMonitorId = this.currentPlan?.qualityMonitorId || ''
        this.yearDetailPage.current = 1
        return this.getYearDetailPlanList()
      })
    },
    openPlan(row, addFirstDetail = false) {
      this.selectedYear = String(row.monitorYear || this.selectedYear)
      this.selectedPlanId = row.qualityMonitorId
      this.pageMode = 'detail'
      return this.loadPlanVersion().then(() => {
        if (addFirstDetail) this.showDialog('add')
      })
    },
    backToPlanList() {
      this.pageMode = 'list'
      this.currentPlan = null
      this.qualityMonitorId = ''
      this.yearDetailForm = { monitorProject: '', responsibleUserId: null }
      this.getYearPlanList()
    },
    handleYearChange() {
      this.yearPage.current = 1
      this.getYearPlanList()
    },
    openCreatePlan() {
      this.planForm = {
        monitorYear: String(new Date().getFullYear()),
        monitorName: '',
        examineUserId: null
      }
      this.createPlanDialog = true
    },
    createPlan() {
      if (!this.planForm.monitorYear || !this.planForm.monitorName || !this.planForm.examineUserId) {
        this.$message.warning('请选择年度、填写计划名称并选择审核人')
        return
      }
      this.createPlanLoading = true
      createAnnualPlan({ ...this.planForm }).then(res => {
        this.createPlanDialog = false
        this.selectedYear = String(res.data.monitorYear)
        this.yearPage.current = 1
        this.$message.success('年度计划已创建,请填写第一个质控项目')
        return this.getYearPlanList().then(() => this.openPlan(res.data, true))
      }).finally(() => { this.createPlanLoading = false })
    },
    submitPlan() {
      this.$confirm('提交后质控项目将锁定,确认提交审核?', '提交审核').then(() => {
        submitAnnualPlan(this.qualityMonitorId).then(() => {
          this.$message.success('已提交审核')
          this.loadPlanVersion()
          this.getYearPlanList()
        })
      }).catch(() => {})
    },
    clearYear() {
      this.yearForm.monitorName = ''
      this.yearPage.current = 1
      this.getYearPlanList()
    },
    pagination({ page, limit }) {
@@ -609,14 +582,31 @@
    pagination1({ page, limit }) {
      this.yearDetailPage.current = page;
      this.yearDetailPage.size = limit;
      this.getYearPlanList();
      this.getYearDetailPlanList();
    },
    leadInto() {
      this.uploadDia = true
      this.planYear = this.selectedYear
      this.fileList = []
      this.getUserList()
    },
    // 下载导入模板
    downloadTemplate() {
      const link = document.createElement('a')
      // 带版本参数避免浏览器继续使用缓存里的旧模板
      link.href = '/import-templates/质量监控计划导入模板.xlsx?v=2'
      link.download = '质量监控计划导入模板.xlsx'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
    },
    // 导入流程
    beforeUpload(file) {
      if (!/\.(xls|xlsx)$/i.test(file.name || '')) {
        this.$message.error('请选择 .xls 或 .xlsx 格式的文件');
        this.$refs.upload.clearFiles()
        return false;
      }
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
@@ -626,23 +616,40 @@
      }
    },
    onError(err, file, fileList) {
      this.$message.error('上传失败')
      this.$refs.upload.clearFiles()
      this.uploading = false
      this.fileList = []
      this.$message.error('上传失败,请重新选择文件')
    },
    handleSuccessUp(response) {
      this.uploading = false;
      if (response.code == 200) {
        this.$message.success('上传成功');
        this.$message.success('导入成功,计划已保存为草稿,请补全质控项目后提交审核');
        const importedPlan = response.data || null
        this.timer = setTimeout(() => {
          this.uploadDia = false
          this.fileList = []
          this.planYear = ''
          this.examineUserId = ''
          this.getYearPlanList()
          // 导入完成后直接进入计划详情,方便继续编辑质控项目、选择检测标准等
          if (importedPlan && importedPlan.qualityMonitorId) {
            this.openPlan(importedPlan)
          } else {
            this.getYearPlanList()
          }
        }, 1000)
      } else {
        this.$message.error(response.msg)
        this.$message.error(response.msg || '上传失败')
        // 后端返回业务错误时 HTTP 状态仍是 200,el-upload 会把文件标记为 success,
        // 直接再次调用 submit() 不会重新上传,这里把文件恢复为待上传状态。
        this.resetUploadFiles()
      }
    },
    resetUploadFiles() {
      const files = this.$refs.upload?.uploadFiles || this.fileList || []
      files.forEach(file => {
        file.status = 'ready'
        file.response = undefined
        file.percentage = 0
      })
    },
    submitUpload() {
      if (!this.planYear) {
@@ -650,9 +657,15 @@
        return;
      }
      if (!this.examineUserId) {
        this.$message.warning('请选择批准人')
        this.$message.warning('请选择审核人')
        return;
      }
      const files = this.$refs.upload?.uploadFiles || this.fileList || []
      if (!files.length) {
        this.$message.warning('请选择要导入的文件')
        return;
      }
      this.uploading = true
      this.$refs.upload.submit();
    },
    // end
@@ -678,7 +691,7 @@
    closeExamineDia() {
      this.examineDialog = false
      this.examineInfo.examineRemark = ''
      this.getYearPlanList()
      this.refreshPlanData()
    },
    // 批准
    approvalPlan(row) {
@@ -701,7 +714,14 @@
    closeRatifyDia() {
      this.ratifyDialog = false
      this.ratifyInfo.ratifyRemark = ''
      this.getYearPlanList()
      this.refreshPlanData()
    },
    refreshPlanData() {
      return this.getYearPlanList().then(() => {
        if (this.pageMode === 'detail' && this.selectedPlanId) {
          return this.loadPlanVersion()
        }
      })
    },
    // 批准报告
    handleApproval(status) {
@@ -762,16 +782,19 @@
    getYearDetailPlanList() {
      const entity = {
        qualityMonitorId: this.qualityMonitorId,
        monitorPurpose: this.yearDetailForm.monitorPurpose,
        monitorProject: this.yearDetailForm.monitorProject,
        responsibleUserId: this.yearDetailForm.responsibleUserId,
      }
      const page = this.yearDetailPage
      this.yearDetailLoading = true
      pageQualityMonitorDetail({ ...entity, ...page }).then(res => {
        this.yearDetailLoading = false
        this.yearDetailTableData = res.data.records
        this.yearDetailTableData = res.data.records.map(item => ({
          ...item,
          taskStatusText: this.inspectionTaskStatus(item)
        }))
        this.yearDetailPage.total = res.data.total
        this.$refs.yearDetailTableData.$refs.multipleTable.doLayout()
        this.$nextTick(() => this.$refs.yearDetailTableData?.$refs?.multipleTable?.doLayout())
      }).catch(err => {
        console.log('err---', err);
        this.yearDetailLoading = false
@@ -780,8 +803,8 @@
    // 重置明细表
    clearDetail() {
      this.yearDetailForm = {
        monitorPurpose: '',
        monitorProject: ''
        monitorProject: '',
        responsibleUserId: null
      }
      this.getYearDetailPlanList()
    },
@@ -822,6 +845,7 @@
        }
        this.lookDialogVisible = true
      } else {
        this.fileList1 = []
        this.uploadDia1 = true
      }
    },
@@ -851,7 +875,7 @@
      this.uploading1 = false
    },
    submitUpload1() {
      if (this.$refs.upload1.uploadFiles.length === 0) {
      if (!this.fileList1 || this.fileList1.length === 0) {
        this.$message.error('未选择文件')
        return
      }
@@ -862,6 +886,11 @@
    // end
    // 打开评价弹框
    evaluate(row) {
      if (row.itemKind === 'INSPECTION') {
        this.inspectionEvaluateDialog = true
        this.$nextTick(() => this.$refs.inspectionEvaluateDialog.open(row))
        return
      }
      this.evaluateDialog = true
      this.$nextTick(() => {
        this.$refs.evaluateDialog.openDia(row)
@@ -870,6 +899,26 @@
    closeEvaDia() {
      this.evaluateDialog = false
      this.getYearDetailPlanList()
    },
    closeInspectionEvaluateDialog() {
      this.inspectionEvaluateDialog = false
      this.getYearDetailPlanList()
    },
    dispatchTask(row) {
      this.$confirm(`确认下发“${row.monitorProject}”的质量监控检验任务?`, '下发任务').then(() => {
        dispatchInspectionTask(row.qualityMonitorDetailsId).then(res => {
          this.$message.success(`已下发监控任务:${res.data.entrustCode}`)
          this.getYearDetailPlanList()
        })
      }).catch(() => {})
    },
    cancelTask(row) {
      this.$confirm(`确认取消监控任务“${row.inspectionTaskCode}”?已取消任务会保留历史记录。`, '取消任务').then(() => {
        cancelInspectionTask(row.qualityMonitorDetailsId).then(() => {
          this.$message.success('任务已取消')
          this.getYearDetailPlanList()
        })
      }).catch(() => {})
    },
    // 附件上传
    viewTestRecord (row) {
@@ -885,6 +934,14 @@
    },
    // 打开年度明细新增、修改弹框
    showDialog(type, row) {
      if (!this.qualityMonitorId) {
        this.$message.warning('请先新建年度质量控制计划')
        return
      }
      if (!this.canEditPlan) {
        this.$message.warning('当前计划状态不能维护质控项目')
        return
      }
      this.formDia = true
      this.$nextTick(() => {
        this.$refs.formDia.openDia(type, row)
@@ -949,11 +1006,98 @@
      link.target = '_blank';
      document.body.appendChild(link);
      link.click();
    },
    formatDate(value) {
      return value ? String(value).slice(0, 10) : '-'
    },
    planWorkflowText(value) {
      return ({
        DRAFT: '草稿',
        PENDING_REVIEW: '待审核',
        REVIEW_REJECTED: '审核退回',
        PENDING_APPROVAL: '待批准',
        APPROVAL_REJECTED: '批准退回',
        APPROVED: '已批准'
      })[value] || '历史计划'
    },
    planWorkflowType(value) {
      return ({
        DRAFT: 'info',
        PENDING_REVIEW: 'warning',
        REVIEW_REJECTED: 'danger',
        PENDING_APPROVAL: 'warning',
        APPROVAL_REJECTED: 'danger',
        APPROVED: 'success'
      })[value] || 'info'
    },
    canReviewRow(row) {
      return row.workflowStatus === 'PENDING_REVIEW'
        && Number(this.userId) === Number(row.examineUserId)
    },
    canApproveRow(row) {
      return row.workflowStatus === 'PENDING_APPROVAL'
        && Number(this.userId) === Number(row.ratifyUserId)
    },
    canDeleteRow(row) {
      return Number(this.userId) === Number(row.writeUserId)
        && ['DRAFT', 'REVIEW_REJECTED', 'APPROVAL_REJECTED'].includes(row.workflowStatus || 'DRAFT')
    },
    handlePlanCommand(command, row) {
      if (command === 'export') this.handleDown(row)
      if (command === 'delete') this.delPlan(row)
    },
    inspectionTaskStatus(row) {
      if (row.itemKind !== 'INSPECTION') return '无需下发'
      const value = row.inspectionTaskState
      if (value === null || value === undefined) {
        return Number(row.planRatifyStatus) === 1 ? '未下发' : '待计划批准'
      }
      const map = { 0: '待检测', 1: '检测中', 2: '检测中', 3: '待复核', 4: '复核退回', 5: '待评价' }
      return map[value] || '处理中'
    },
    handleDetailCommand(command, row) {
      const handlers = {
        complete: () => this.record(row),
        evaluate: () => this.evaluate(row),
        requirement: () => this.evaluate(row),
        cancel: () => this.cancelTask(row),
        attachment: () => this.viewTestRecord(row),
        export: () => this.downLoadPost(row),
        delete: () => this.delYearPlanDetail(row)
      }
      handlers[command]?.()
    }
  },
  // 用于上传文件的信息
  computed: {
    ...mapGetters(["userId", "nickName"]),
    effectivePlanStatus() {
      if (!this.currentPlan) return ''
      if (this.currentPlan.workflowStatus) return this.currentPlan.workflowStatus
      if (this.currentPlan.ratifyStatus === 1) return 'APPROVED'
      if (this.currentPlan.ratifyStatus === 0) return 'APPROVAL_REJECTED'
      if (this.currentPlan.examineStatus === 1) return 'PENDING_APPROVAL'
      if (this.currentPlan.examineStatus === 0) return 'REVIEW_REJECTED'
      return 'DRAFT'
    },
    planStatusText() {
      return ({DRAFT:'草稿',PENDING_REVIEW:'待审核',REVIEW_REJECTED:'审核退回',PENDING_APPROVAL:'待批准',APPROVAL_REJECTED:'批准退回',APPROVED:'已批准'})[this.effectivePlanStatus] || '历史计划'
    },
    planStatusType() {
      return ({DRAFT:'info',PENDING_REVIEW:'warning',REVIEW_REJECTED:'danger',PENDING_APPROVAL:'warning',APPROVAL_REJECTED:'danger',APPROVED:'success'})[this.effectivePlanStatus] || 'info'
    },
    canEditPlan() {
      return !!this.currentPlan && Number(this.userId) === Number(this.currentPlan.writeUserId)
        && ['DRAFT','REVIEW_REJECTED','APPROVAL_REJECTED'].includes(this.effectivePlanStatus)
    },
    canReviewPlan() {
      return !!this.currentPlan && this.effectivePlanStatus === 'PENDING_REVIEW'
        && Number(this.userId) === Number(this.currentPlan.examineUserId)
    },
    canApprovePlan() {
      return !!this.currentPlan && this.effectivePlanStatus === 'PENDING_APPROVAL'
        && Number(this.userId) === Number(this.currentPlan.ratifyUserId)
    },
    action() {
      return this.javaApi + '/qualityMonitor/importQualityMonitor'
    },
@@ -967,4 +1111,84 @@
};
</script>
<style scoped></style>
<style scoped>
.plan-list-card {
  min-width: 0;
  padding: 14px 16px;
  background: var(--lims-card-bg, var(--el-bg-color));
  border: 1px solid var(--el-border-color-light);
  border-radius: 4px;
}
.plan-list-toolbar,
.plan-list-filters,
.plan-list-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.plan-list-toolbar {
  justify-content: space-between;
  margin-bottom: 14px;
}
.filter-label {
  margin-left: 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--lims-text-2);
  white-space: nowrap;
}
.annual-plan-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 16px;
  margin-bottom: 16px;
  background: var(--lims-card-bg, var(--el-bg-color));
  border: 1px solid var(--el-border-color-light);
  border-radius: 4px;
}
.annual-plan-main, .annual-plan-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.annual-plan-label { font-weight: 700; color: var(--lims-text-2); }
.plan-detail-card {
  min-width: 0;
  background: var(--lims-card-bg, var(--el-bg-color));
}
.plan-detail-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}
.section-title {
  display: inline-flex;
  align-items: center;
  margin-right: 18px;
  font-size: 15px;
  font-weight: 700;
  color: var(--lims-text-1, var(--el-text-color-primary));
  white-space: nowrap;
}
.detail-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
}
@media (max-width: 1200px) {
  .annual-plan-header {
    align-items: flex-start;
    flex-direction: column;
  }
  .annual-plan-actions {
    width: 100%;
  }
}
</style>