src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue
@@ -51,8 +51,12 @@
          width="70"></el-table-column>
        <el-table-column label="设备编号" min-width="170" prop="deviceNumber"></el-table-column>
        <el-table-column label="设备名称" min-width="180" prop="deviceName" show-overflow-tooltip></el-table-column>
        <el-table-column label="核查时间" min-width="120" prop="checkTime"
          :formatter="formatCheckMonth"></el-table-column>
        <el-table-column label="设备型号" min-width="120" prop="deviceModel" show-overflow-tooltip></el-table-column>
        <el-table-column label="核查时间" min-width="120">
          <template #default="scope">
            {{ scope.row.completeDate || formatCheckMonth(null, null, scope.row.checkTime) }}
          </template>
        </el-table-column>
        <el-table-column label="核查指标" min-width="140" prop="checkIndex"> </el-table-column>
        <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>
@@ -89,7 +93,7 @@
              @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"
            <el-button v-if="scope.row.recordStatus !== 2"
              size="small" link type="danger" @click="deleteFun1(scope.row.planDetailsId)">删除</el-button>
          </template>
        </el-table-column>
@@ -107,16 +111,20 @@
          <div class="form-section-title">基本信息</div>
          <el-row :gutter="12">
            <el-col :span="12">
              <el-form-item label="仪器名称:" prop="deviceName">
              <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-input v-if="operationType === 'view'" :model-value="formatCheckMonth(null, null, form.checkTime)"
                  disabled size="small"></el-input>
                <template v-else>
                  <el-date-picker v-model="checkTimeEditValue" clearable format="YYYY-MM-DD" placeholder="选择检查日期"
                    size="small" style="width:100%" type="date" value-format="YYYY-MM-DD">
                  </el-date-picker>
                  <div v-if="isMonthOnlyCheckTime" class="check-time-tip">系统登记只到月份,选择具体日期后将按日保存</div>
                </template>
              </el-form-item>
            </el-col>
            <el-col :span="8">
@@ -212,7 +220,7 @@
        </div>
      </el-form>
      <template #footer><span class="dialog-footer">
        <el-button @click="resetForm">取 消</el-button>
        <el-button @click="handleCancel">取 消</el-button>
        <el-button :loading="submitFormLoading" type="primary" @click="submitForm"
          v-if="operationType !== 'view'">确 认</el-button>
      </span></template>
@@ -234,7 +242,7 @@
            <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) || '—' }}
            <span class="export-panel-label">核查时间</span>{{ downRow.completeDate || formatCheckMonth(null, null, downRow.checkTime) || '—' }}
          </div>
        </div>
        <div v-if="!canExport" class="export-panel-tip">
@@ -259,8 +267,8 @@
import CheckRecord from "./checkRecord.vue";
import LimsAttachment from '@/components/Attachment/lims-attachment.vue'
import {
  pageDeviceExaminePlan,
  getExaminePlanByYear,
  getExaminePlanYears,
  exportDeviceExaminePlanDetail,
  delDeviceExaminePlanDetail,
  pageDeviceExaminePlanDetail,
@@ -340,30 +348,24 @@
  },
  // 方法集合
  methods: {
    // 年份下拉只保留有计划明细的年份,空年份自动消失
    // 年份下拉只保留有计划明细的年份,空年份计划自动删除,默认显示本年度(本年度无计划时退回最新年份)
    loadYearOptions() {
      pageDeviceExaminePlan({
        current: 1,
        size: 99,
      }).then(res => {
        let years = []
        if (res.code == 200) {
          years = (res.data.records || []).map(r => r.planYear).filter(Boolean)
        }
        const options = [...new Set(years)].sort((a, b) => b.localeCompare(a))
        this.yearOptions = options
      const currentYear = String(new Date().getFullYear())
      const finish = (years) => {
        let options = [...new Set((years || []).map(y => String(y)).filter(Boolean))]
        options.sort((a, b) => b.localeCompare(a))
        if (options.length === 0) {
          this.planYear = ''
          this.planInfo = {}
          this.yearTableDetailData = []
          this.pagination1.total = 0
          return
          options = [currentYear]
        }
        if (options.indexOf(this.planYear) === -1) {
          const currentYear = String(new Date().getFullYear())
          this.planYear = options.indexOf(currentYear) > -1 ? currentYear : options[0]
        }
        this.yearOptions = options
        const keep = options.indexOf(String(this.planYear)) > -1
        this.planYear = keep ? String(this.planYear) : (options.includes(currentYear) ? currentYear : options[0])
        this.loadPlan()
      }
      getExaminePlanYears().then(res => {
        finish(res.code == 200 ? (res.data || []) : [])
      }).catch(() => {
        finish([])
      })
    },
    // 切换年份
@@ -471,7 +473,10 @@
      this.$nextTick(() => {
        this.form = this.HaveJson(row)
        if (this.operationType !== 'view') {
          this.draftRestore('equipmentVerificationPlan', 'form', this.form.planDetailsId, 'dialogVisible')
          const rowDeviceName = this.form.deviceName
          this.draftRestore('equipmentVerificationPlan', 'form', this.form.planDetailsId, 'dialogVisible',
            // 设备名称是禁用字段,草稿里为空说明是被表单重置清空的废草稿,直接丢弃
            draft => !!draft.deviceName || !rowDeviceName)
        }
      })
    },
@@ -496,7 +501,7 @@
    },
    // 明细-删除
    deleteFun1(id) {
      this.$confirm('删除后,若该设备还有审核通过的核查记录,档案的最近核查日期将回退到最近一次完成记录;若没有,则清空档案的核查日期和核查周期。是否继续?', '提示', {
      this.$confirm('删除后,该行的核查记录(未审核或审核不通过)及附件会一并删除;若该设备还有审核通过的核查记录,档案的最近核查日期将回退到最近一次完成记录;若没有,则清空档案的核查日期和核查周期。是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
@@ -512,13 +517,39 @@
        });
      });
    },
    // 导出年度计划表
    // 导出年度计划表(Excel):有筛选条件导出筛选结果,没有筛选导出该年份全部
    downLoadPost() {
      exportDeviceExaminePlanDetail({ planId: this.planInfo.planId }).then(res => {
        this.saveDocx(res, '仪器设备期间核查计划表.docx')
      exportDeviceExaminePlanDetail({
        planId: this.planInfo.planId,
        deviceName: this.searchForm.deviceName,
        deviceNumber: this.searchForm.deviceNumber,
      }).then(res => {
        const planName = this.planInfo.planName || (this.planYear ? this.planYear + '年度设备核查计划' : '设备核查计划')
        this.saveDocx(res, planName + '.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
      }).catch(() => { })
    },
    // 本次是否有改动(含打开时恢复的草稿)
    hasUnsavedEdit() {
      const state = this.draftStates && this.draftStates['equipmentVerificationPlan']
      return !!state && this.draftSnapshot(state) !== state.baseline
    },
    // 取消:无编辑直接退出;有编辑先确认,确认后丢弃草稿退出
    handleCancel() {
      if (this.operationType === 'view' || !this.hasUnsavedEdit()) {
        this.dialogVisible = false
        return
      }
      this.$confirm('是否确认取消编辑?本次编辑记录不保存!', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '继续编辑',
        type: 'warning'
      }).then(() => {
        this.draftClear('equipmentVerificationPlan')
        this.dialogVisible = false
      }).catch(() => { })
    },
    resetForm() {
      // 右上角关闭时 close 事件先于显隐监听触发,先把草稿存下再重置表单,避免重置后的空内容被存成草稿
      this.draftStop('equipmentVerificationPlan')
      this.$refs.modelForm.resetFields();
      this.dialogVisible = false
@@ -539,8 +570,7 @@
    },
    // 打开附件
    openAttachment(row) {
      const isImage = /\.(jpg|jpeg|png|gif)$/i.test(row.fileUrl)
      window.open(this.javaApi + (isImage ? '/img/' : '/word/') + row.fileUrl, '_blank')
      return this.$download.saveAs(row.fileUrl, row.fileName)
    },
    // 列表附件列只读查看(上传/删除入口只在编辑、新增弹窗内)
    handleAttachment(row) {
@@ -558,7 +588,7 @@
      this.downloadDialog = true
    },
    // 导出结果拦截: 后端返回错误 JSON 时提示错误, 不存成坏文件
    saveDocx(res, filename) {
    saveDocx(res, filename, mime = 'application/msword') {
      const reader = new FileReader();
      reader.readAsText(res, 'utf-8');
      reader.onload = () => {
@@ -568,11 +598,11 @@
          try { msg = JSON.parse(text).msg || msg; } catch (e) { }
          this.$message.error(msg);
        } else {
          this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename);
          this.$download.saveAs(new Blob([res], { type: mime }), filename);
        }
      };
      reader.onerror = () => {
        this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename);
        this.$download.saveAs(new Blob([res], { type: mime }), filename);
      };
    },
    // 核查记录导出
@@ -624,6 +654,19 @@
        return [...list, { id, name: this.form.checkChargerUser || String(id) }]
      }
      return list
    },
    // 日期控件只接受 YYYY-MM-DD;系统登记的待核查行只有年月,未改日期时不能把“日”写进表单
    checkTimeEditValue: {
      get() {
        const val = this.form.checkTime || ''
        return /^\d{4}-\d{2}$/.test(val) ? `${val}-01` : val
      },
      set(val) {
        this.form.checkTime = val || ''
      }
    },
    isMonthOnlyCheckTime() {
      return /^\d{4}-\d{2}$/.test(this.form.checkTime || '')
    },
  },
}
@@ -712,6 +755,14 @@
  margin-bottom: 12px;
}
.check-time-tip {
  width: 100%;
  font-size: 12px;
  line-height: 1.4;
  color: #909399;
  margin-top: 2px;
}
.export-panel {
  border: 1px solid #e4e7ed;
  border-radius: 6px;