吴羡
23 小时以前 d2eed0eb7c2f9537d5eaab7ccc3b6ab258959227
src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue
@@ -4,13 +4,14 @@
    <div style="margin: 10px 0 14px;">
      <div class="plan-top">
        <div class="plan-top-left">
          <span class="title" style="margin-bottom: 0;">{{ planInfo.planName || (planYear + '年度设备核查计划') }}</span>
          <span class="title" style="margin-bottom: 0;">{{ planInfo.planName || (planYear ? planYear + '年度设备核查计划' : '设备核查计划') }}</span>
          <el-select v-model="planYear" size="small" style="width: 110px; margin-left: 16px;" @change="handleYearChange">
            <el-option v-for="y in yearOptions" :key="y" :label="y" :value="y"></el-option>
          </el-select>
        </div>
        <div>
          <el-button size="small" type="primary" @click="loadPlan">刷新</el-button>
          <el-button size="small" type="primary" @click="handleAddRecord">新增核查记录</el-button>
          <el-button size="small" type="primary" @click="loadYearOptions">刷新</el-button>
          <el-button size="small" type="primary" @click="downLoadPost">导出</el-button>
        </div>
      </div>
@@ -56,7 +57,7 @@
        <el-table-column label="核查方法" min-width="140" prop="checkMethod" show-overflow-tooltip></el-table-column>
        <el-table-column label="结果如何判定" min-width="140" prop="howResults" show-overflow-tooltip></el-table-column>
        <el-table-column label="核查负责人" min-width="120" prop="checkChargerUser"></el-table-column>
        <el-table-column label="记录状态" min-width="100" prop="recordStatus">
        <el-table-column label="审核状态" min-width="100" prop="recordStatus">
          <template #default="scope">
            <el-tag type="" v-if="scope.row.recordStatus === 0" disable-transitions>未开始</el-tag>
            <el-tag type="warning" v-if="scope.row.recordStatus === 1" disable-transitions>待批准</el-tag>
@@ -64,26 +65,32 @@
            <el-tag type="danger" v-if="scope.row.recordStatus === 3" disable-transitions>不通过</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="对比状态" min-width="100" prop="recordContrastStatus">
        <el-table-column label="备注" min-width="130" prop="remark"></el-table-column>
        <el-table-column label="附件" min-width="140">
          <template #default="scope">
            <el-tag type="" v-if="scope.row.recordContrastStatus === 0" disable-transitions>未开始</el-tag>
            <el-tag type="warning" v-if="scope.row.recordContrastStatus === 1" disable-transitions>待批准</el-tag>
            <el-tag type="success" v-if="scope.row.recordContrastStatus === 2" disable-transitions>通过</el-tag>
            <el-tag type="danger" v-if="scope.row.recordContrastStatus === 3" disable-transitions>不通过</el-tag>
            <el-link v-if="scope.row.attachmentCount > 0" type="primary" @click="handleAttachment(scope.row)">
              查看附件({{ scope.row.attachmentCount }})
            </el-link>
            <el-link v-else-if="scope.row.fileUrl" type="primary" @click="openAttachment(scope.row)">
              {{ scope.row.fileName || '查看附件' }}
            </el-link>
            <span v-else>—</span>
          </template>
        </el-table-column>
        <el-table-column label="备注" min-width="130" prop="remark"></el-table-column>
        <!-- 操作按钮 -->
        <el-table-column align="center" fixed="right" label="操作" min-width="260">
        <el-table-column align="center" fixed="right" label="操作" min-width="320">
          <template #default="scope">
            <el-button size="small" link type="primary"
              :disabled="scope.row.recordStatus === 2 || scope.row.recordStatus === 3"
              @click="handleForm('edit', scope.row)">编辑</el-button>
            <el-button size="small" link type="primary" @click="handleForm('view', scope.row)">查看</el-button>
            <el-button size="small" link type="primary" @click="handleRecord(scope.row)">记录</el-button>
            <el-button size="small" link type="primary" @click="handleCheck(scope.row)">对比</el-button>
            <el-button size="small" link type="primary" @click="handleDown(scope.row)">导出</el-button>
            <el-button size="small" link type="danger"
              @click="deleteFun1(scope.row.planDetailsId)">删除</el-button>
            <el-button v-if="scope.row.recordStatus === 0 || scope.row.recordStatus === 1"
              size="small" link type="primary" :disabled="!scope.row.checkChargerUser"
              @click="handleRecord(scope.row)">审核</el-button>
            <el-button v-if="scope.row.recordStatus === 2 || scope.row.recordStatus === 3"
              size="small" link type="primary" @click="handleDown(scope.row)">导出</el-button>
            <el-button v-if="scope.row.recordStatus === 0 && scope.row.recordContrastStatus === 0"
              size="small" link type="danger" @click="deleteFun1(scope.row.planDetailsId)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -93,57 +100,116 @@
        @size-change="handleSizeChange1" @current-change="handleCurrentChange1">
      </el-pagination>
    </div>
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible" title="设备核查计划"
      width="80%" @close="resetForm">
      <el-form ref="modelForm" :model="form" :rules="rules" label-width="110px">
        <el-col :span="12">
          <el-form-item label="设备编号:" prop="deviceNumber">
            <el-input v-model="form.deviceNumber" :disabled="operationType === 'view'" clearable
              size="small"></el-input>
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible"
      :title="operationType === 'view' ? '设备核查记录' : '编辑核查记录'" top="5vh" width="960px" @close="resetForm">
      <el-form ref="modelForm" :model="form" :rules="rules" label-width="104px">
        <div class="form-section">
          <div class="form-section-title">基本信息</div>
          <el-row :gutter="12">
            <el-col :span="12">
              <el-form-item label="仪器名称:" prop="deviceName">
                <el-input v-model="form.deviceName" disabled size="small"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="检查日期:" prop="checkTime">
                <el-date-picker v-model="form.checkTime" :disabled="operationType === 'view'" clearable
                  format="YYYY-MM" placeholder="选择检查日期" size="small" style="width:100%" type="month"
                  value-format="YYYY-MM">
                </el-date-picker>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="管理编号:" prop="deviceNumber">
                <el-input v-model="form.deviceNumber" disabled size="small"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="设备型号:" prop="deviceModel">
                <el-input v-model="form.deviceModel" disabled size="small"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="核查人:" prop="checkChargerUserId">
                <el-select v-model="form.checkChargerUserId" @change="setCheckChargerUser" clearable filterable
                  :disabled="operationType === 'view'" placeholder="请选择" size="small" style="width: 100%">
                  <el-option v-for="item in chargerOptions" :key="item.id" :label="item.name"
                    :value="item.id"></el-option>
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
        </div>
        <div class="form-section">
          <div class="form-section-title">核查内容</div>
          <el-row :gutter="12">
            <el-col :span="8">
              <el-form-item label="环境温湿度:" prop="environment">
                <el-input v-model="form.environment" :disabled="operationType === 'view'" clearable size="small"
                  placeholder="如:25℃ / 50%RH"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="核查依据:" prop="checkBasis">
                <el-input v-model="form.checkBasis" :disabled="operationType === 'view'" clearable
                  size="small"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="核查指标:" prop="checkIndex">
                <el-input v-model="form.checkIndex" :disabled="operationType === 'view'" clearable
                  size="small"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="核查方式:" prop="checkMethod">
                <el-input v-model="form.checkMethod" :disabled="operationType === 'view'" clearable :rows="2"
                  size="small" type="textarea"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="结果如何判定:" prop="howResults">
                <el-input v-model="form.howResults" :disabled="operationType === 'view'" clearable :rows="2"
                  size="small" type="textarea"></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="外观情况:">
                <el-radio-group v-model="form.appearanceStatus" :disabled="operationType === 'view'">
                  <el-radio value="良好">良好</el-radio>
                  <el-radio value="一般">一般</el-radio>
                  <el-radio value="损坏">损坏</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="运行状态:">
                <el-radio-group v-model="form.runningStatus" :disabled="operationType === 'view'">
                  <el-radio value="正常">正常</el-radio>
                  <el-radio value="异常">异常</el-radio>
                  <el-radio value="停机">停机</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
            <el-col :span="24">
              <el-form-item label="备注:" prop="remark">
                <el-input v-model="form.remark" :disabled="operationType === 'view'" clearable size="small"></el-input>
              </el-form-item>
            </el-col>
          </el-row>
        </div>
        <div class="form-section">
          <div class="form-section-title">附件资料</div>
          <el-form-item label-width="0">
            <lims-attachment inline upload-url="/deviceExaminePlan/uploadAttachment"
              select-url="/deviceExaminePlan/selectAttachments" delete-url="/deviceExaminePlan/deleteAttachment"
              id-param="planDetailsId" name-field="deviceName" title="核查记录附件"
              :main-id="form.planDetailsId" :readonly="operationType === 'view'"></lims-attachment>
            <el-link v-if="form.fileUrl" type="primary" @click="openAttachment(form)">
              {{ form.fileName || '查看原附件' }}
            </el-link>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="设备名称:" prop="deviceName">
            <el-input v-model="form.deviceName" :disabled="operationType === 'view'" clearable size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="核查时间:" prop="checkTime">
            <el-date-picker v-model="form.checkTime" :disabled="operationType === 'view'" clearable format="YYYY-MM"
              placeholder="选择日期" size="small" style="width:100%" type="month" value-format="YYYY-MM">
            </el-date-picker>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="核查指标:" prop="checkIndex">
            <el-input v-model="form.checkIndex" :disabled="operationType === 'view'" clearable size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="核查方法:" prop="checkMethod">
            <el-input v-model="form.checkMethod" :disabled="operationType === 'view'" clearable size="small"
              type="textarea"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="结果如何判定:" prop="howResults">
            <el-input v-model="form.howResults" :disabled="operationType === 'view'" clearable size="small"
              type="textarea"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="核查责任人:" prop="checkChargerUserId">
            <el-select v-model="form.checkChargerUserId" @change="setCheckChargerUser"
              :disabled="operationType === 'view'" placeholder="请选择" size="small" style="width: 100%">
              <el-option v-for="item in chargerOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="备注:" prop="remark">
            <el-input v-model="form.remark" :disabled="operationType === 'view'" clearable size="small"></el-input>
          </el-form-item>
        </el-col>
        </div>
      </el-form>
      <template #footer><span class="dialog-footer">
        <el-button @click="resetForm">取 消</el-button>
@@ -151,25 +217,47 @@
          v-if="operationType !== 'view'">确 认</el-button>
      </span></template>
    </el-dialog>
    <el-dialog v-model="downloadDialog" title="导出" width="600px">
      <span>
        <el-button :loading="outLoading" type="primary" :disabled="!downRow.recordStatus"
          @click="controlDown">核查记录</el-button>
        <el-button :loading="outLoading" type="primary" :disabled="!downRow.recordContrastStatus"
          @click="processingDown">核查比对记录</el-button>
      </span>
    <el-dialog v-model="downloadDialog" title="导出" width="480px">
      <div class="export-panel">
        <div class="export-panel-head">
          <span class="export-panel-name">仪器设备期间核查记录表</span>
          <el-tag v-if="downRow.recordStatus === 2" type="success" disable-transitions>通过</el-tag>
          <el-tag v-else-if="downRow.recordStatus === 1" type="warning" disable-transitions>待批准</el-tag>
          <el-tag v-else-if="downRow.recordStatus === 3" type="danger" disable-transitions>不通过</el-tag>
          <el-tag v-else type="info" disable-transitions>未开始</el-tag>
        </div>
        <div class="export-panel-meta">
          <div class="export-panel-item">
            <span class="export-panel-label">设备编号</span>{{ downRow.deviceNumber || '—' }}
          </div>
          <div class="export-panel-item">
            <span class="export-panel-label">设备名称</span>{{ downRow.deviceName || '—' }}
          </div>
          <div class="export-panel-item">
            <span class="export-panel-label">核查时间</span>{{ formatCheckMonth(null, null, downRow.checkTime) || '—' }}
          </div>
        </div>
        <div v-if="!canExport" class="export-panel-tip">
          {{ downRow.recordStatus === 1 ? '该计划行尚未审核完成,审核通过或不通过后才能导出。' : '该计划行还没有核查记录,录入并提交审核后才能导出。' }}
        </div>
      </div>
      <template #footer><span class="dialog-footer">
        <el-button @click="downloadDialog = false">取 消</el-button>
        <el-button :loading="outLoading" type="primary" :disabled="!canExport"
          @click="controlDown">导出记录表</el-button>
      </span></template>
    </el-dialog>
    <equipment-check v-if="equipmentCheck" ref="equipmentCheck" @closeDialog="closeDialog"></equipment-check>
    <check-record v-if="checkRecord" ref="checkRecord" @closeDialog1="closeDialog1"></check-record>
    <lims-attachment ref="rowAttachment" upload-url="/deviceExaminePlan/uploadAttachment"
      select-url="/deviceExaminePlan/selectAttachments" delete-url="/deviceExaminePlan/deleteAttachment"
      id-param="planDetailsId" name-field="deviceName" title="核查记录附件" :readonly="attachmentReadonly"
      @closed="getYearTableDetailData"></lims-attachment>
  </div>
</template>
<script>
import EquipmentCheck from "./equipmentCheck.vue";
import CheckRecord from "./checkRecord.vue";
import LimsAttachment from '@/components/Attachment/lims-attachment.vue'
import {
  pageDeviceExaminePlan,
  getExaminePlanByYear,
@@ -186,7 +274,7 @@
export default {
  name: "equipment-verification-plan",
  // import 引入的组件需要注入到对象中才能使用
  components: { CheckRecord, EquipmentCheck },
  components: { CheckRecord, LimsAttachment },
  mixins: [draftSave],
  props: {
    clickNodeVal: {
@@ -216,25 +304,31 @@
      form: {
        deviceNumber: '',
        deviceName: '',
        deviceModel: '',
        checkTime: '',
        checkIndex: '',
        checkMethod: '',
        howResults: '',
        checkChargerUserId: '',
        checkChargerUser: '',
        environment: '',
        checkBasis: '',
        appearanceStatus: '',
        runningStatus: '',
        remark: '',
        fileName: '',
        fileUrl: '',
        planId: '',
        planDetailsId: '',
      },
      operationType: '',
      rules: {
        deviceNumber: [{ required: true, message: '请填写设备编号', trigger: 'blur' }],
        checkMethod: [{ required: true, message: '请填写核查方法', trigger: 'blur' }],
        checkMethod: [{ required: true, message: '请填写核查方式', trigger: 'blur' }],
      },
      yearTableDetailDataLoading: false,
      submitFormLoading: false,
      userList: [],
      equipmentCheck: false,
      attachmentReadonly: false,
      checkRecord: false,
      downloadDialog: false,
      outLoading: false,
@@ -246,7 +340,7 @@
  },
  // 方法集合
  methods: {
    // 年份下拉选项:已有计划年份 + 当前年份
    // 年份下拉只保留有计划明细的年份,空年份自动消失
    loadYearOptions() {
      pageDeviceExaminePlan({
        current: 1,
@@ -256,12 +350,19 @@
        if (res.code == 200) {
          years = (res.data.records || []).map(r => r.planYear).filter(Boolean)
        }
        const currentYear = String(new Date().getFullYear())
        if (years.indexOf(currentYear) === -1) {
          years.push(currentYear)
        const options = [...new Set(years)].sort((a, b) => b.localeCompare(a))
        this.yearOptions = options
        if (options.length === 0) {
          this.planYear = ''
          this.planInfo = {}
          this.yearTableDetailData = []
          this.pagination1.total = 0
          return
        }
        this.yearOptions = [...new Set(years)].sort((a, b) => b.localeCompare(a))
        this.planYear = currentYear
        if (options.indexOf(this.planYear) === -1) {
          const currentYear = String(new Date().getFullYear())
          this.planYear = options.indexOf(currentYear) > -1 ? currentYear : options[0]
        }
        this.loadPlan()
      })
    },
@@ -272,6 +373,12 @@
    },
    // 查询年份计划(不存在自动创建并补全到点明细),随后刷新明细
    loadPlan() {
      if (!this.planYear) {
        this.planInfo = {}
        this.yearTableDetailData = []
        this.pagination1.total = 0
        return
      }
      getExaminePlanByYear({ planYear: this.planYear }).then(res => {
        if (res.code == 200) {
          this.planInfo = res.data || {}
@@ -312,19 +419,27 @@
      })
    },
    // 到点需核查的行标红
    // 核查时间按月份显示(兼容历史数据中的完整日期)
    // 手动录入的核查日期显示到日,系统登记的月份数据仍按年月显示
    formatCheckMonth(row, column, cellValue) {
      if (cellValue === null || cellValue === undefined || cellValue === '') {
        return cellValue
      }
      const str = String(cellValue).trim().replace(/[./]/g, '-')
      const dateMatched = str.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/)
      if (dateMatched) {
        return `${dateMatched[1]}-${dateMatched[2].padStart(2, '0')}-${dateMatched[3].padStart(2, '0')}`
      }
      const matched = str.match(/^(\d{4})-(\d{1,2})/)
      if (matched) {
        return `${matched[1]}-${matched[2].padStart(2, '0')}`
      }
      const formatted = formatListDate(str)
      const fallback = String(formatted).match(/^(\d{4})-(\d{1,2})/)
      return fallback ? `${fallback[1]}-${fallback[2].padStart(2, '0')}` : cellValue
      const fallback = String(formatted).match(/^(\d{4})-(\d{1,2})-(\d{1,2})/)
      if (fallback) {
        return `${fallback[1]}-${fallback[2].padStart(2, '0')}-${fallback[3].padStart(2, '0')}`
      }
      const monthFallback = String(formatted).match(/^(\d{4})-(\d{1,2})/)
      return monthFallback ? `${monthFallback[1]}-${monthFallback[2].padStart(2, '0')}` : cellValue
    },
    tableRowClassName({ row }) {
      return row.redFlag === 1 ? 'red-row' : ''
@@ -381,14 +496,14 @@
    },
    // 明细-删除
    deleteFun1(id) {
      this.$confirm('此操作将永久删除该条明细, 是否继续?', '提示', {
      this.$confirm('删除后,若该设备还有审核通过的核查记录,档案的最近核查日期将回退到最近一次完成记录;若没有,则清空档案的核查日期和核查周期。是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        delDeviceExaminePlanDetail({ planDetailsId: id }).then(res => {
          this.$message.success('删除成功!');
          this.getYearTableDetailData()
          this.loadYearOptions()
        });
      }).catch(() => {
        this.$message({
@@ -408,29 +523,34 @@
      this.$refs.modelForm.resetFields();
      this.dialogVisible = false
    },
    // 打开对比流程弹框
    handleCheck(row) {
      this.equipmentCheck = true
      this.$nextTick(() => {
        this.$refs.equipmentCheck.openDialog(row)
      })
    },
    // 关闭对比弹框
    closeDialog() {
      this.equipmentCheck = false
      this.getYearTableDetailData()
    },
    // 打开记录弹框
    // 打开核查记录(待批准时用于审核)
    handleRecord(row) {
      this.checkRecord = true
      this.$nextTick(() => {
        this.$refs.checkRecord.openDialog(row)
      })
    },
    // 新增核查记录(可自选设备)
    handleAddRecord() {
      this.checkRecord = true
      this.$nextTick(() => {
        this.$refs.checkRecord.openAddDialog()
      })
    },
    // 打开附件
    openAttachment(row) {
      const isImage = /\.(jpg|jpeg|png|gif)$/i.test(row.fileUrl)
      window.open(this.javaApi + (isImage ? '/img/' : '/word/') + row.fileUrl, '_blank')
    },
    // 列表附件列只读查看(上传/删除入口只在编辑、新增弹窗内)
    handleAttachment(row) {
      this.attachmentReadonly = true
      this.$refs.rowAttachment.open(row)
    },
    // 关闭记录弹框
    closeDialog1() {
      this.checkRecord = false
      this.getYearTableDetailData()
      this.loadYearOptions()
    },
    // 打开导出弹框
    handleDown(row) {
@@ -476,7 +596,7 @@
      })
    },
    getUserList() {
      selectUserCondition().then(res => {
      selectUserCondition({ type: 4 }).then(res => {
        this.userList = res.data
      })
    },
@@ -491,6 +611,10 @@
    // 是否已完成审批(批准/不批准)
    isRatified() {
      return this.planInfo.ratifyStatus === 0 || this.planInfo.ratifyStatus === 1;
    },
    // 记录状态为通过或不通过(审核已结束)才允许导出
    canExport() {
      return this.downRow.recordStatus === 2 || this.downRow.recordStatus === 3;
    },
    // 责任人下拉:已保存的责任人不在候选列表时补一条,避免回显成用户ID
    chargerOptions() {
@@ -560,4 +684,81 @@
:deep(.red-row td) {
  background: #ffcccc !important;
}
.form-section {
  padding: 12px 14px 0;
  border: 1px solid #ebeef5;
  border-radius: 6px;
  margin-bottom: 10px;
}
.form-section:last-child {
  margin-bottom: 0;
}
.form-section-title {
  display: flex;
  align-items: center;
  padding-left: 8px;
  border-left: 3px solid var(--el-color-primary);
  margin-bottom: 12px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  color: #303133;
}
.form-section :deep(.el-form-item) {
  margin-bottom: 12px;
}
.export-panel {
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  padding: 18px 20px;
  background: #fafbfc;
}
.export-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.export-panel-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--lims-text);
}
.export-panel-meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13px;
  color: #606266;
}
.export-panel-item {
  display: flex;
  align-items: center;
}
.export-panel-label {
  width: 72px;
  flex-shrink: 0;
  color: #909399;
}
.export-panel-tip {
  margin-top: 14px;
  padding: 8px 12px;
  border-radius: 4px;
  background: #fdf6ec;
  color: #e6a23c;
  font-size: 13px;
  line-height: 1.5;
}
</style>