gaoluyang
4 小时以前 29be8639dc16b5d82bddc6099b1fa5cdca75f77a
feat(import): 统一导入弹框与模板,修复模板日期格式及上传报错

- 新增 ImportDialog 通用导入弹框及文件清单、文件记录、危险源、检验对象/项目参数、标准查新、质量监督/监控、人员培训等导入模板
- 导入模板日期列设为文本格式,避免输入 2026-03-15 被自动转成 2026/3/15
- 修复 el-upload 未暴露 uploadFiles 导致点击上传报 Cannot read properties of undefined (reading 'length')
- 质量监督记录增加监督方式、被监督人确认意见,监督内容对齐受控表 LMSY-CX29-JL02
- 质量监督明细取消定期/动态监督切换,监督原因改为可手工填写
- 质量监控明细及评价弹框优化
- 新增标准方法变更页、标准方法原始记录模板绑定
- 修复 plain 按钮文字不可见、登录页密码显示切换

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加16个文件
已修改23个文件
1242 ■■■■■ 文件已修改
public/import-templates/人员培训年度计划导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/危险因素辨识与风险评价导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/外来文件确认导入模板.docx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/文件作废记录导入模板.docx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/文件发放回收记录导入模板.docx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/文件定期审查记录导入模板.docx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/文件审批记录导入模板.docx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/文件清单导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/标准查新导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/检验对象导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/检验项目参数导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/质量监控计划导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/质量监督计划导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/重大风险因素分析及控制计划导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
src/api/cnas/process/ensureResults/qualitySupervise.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/standard/standardLibrary.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImportDialog/index.vue 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/ensureResults/ensureResultsValidity/components/inspectionEvaluationDialog.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/ensureResults/qualityControlPlan/components/detailFormDialog.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/ensureResults/qualityControlPlan/components/recordsDialog.vue 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/methodVerification/component/formDIa.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/standardMethodsChange/index.vue 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/process/method/standardNoveltyRetrieval/index.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/deviceOverview.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentControl/components/FileList.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/approvalRecord.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/cancellationRecord.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/distributionCollectionRecord.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/outDocumenRecordt.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/regularReviewsRecord.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/measuresDealRisks/components/hazardIdentificationRiskAssessment.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/measuresDealRisks/components/listRiskAnalysisControlPlans.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/structural/capabilityAndLaboratory/capability/index.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/ÈËÔ±ÅàѵÄê¶È¼Æ»®µ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/ΣÏÕÒòËØ±æÊ¶Óë·çÏÕÆÀ¼Ûµ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/ÍâÀ´ÎļþÈ·Èϵ¼ÈëÄ£°å.docx
Binary files differ
public/import-templates/Îļþ×÷·Ï¼Ç¼µ¼ÈëÄ£°å.docx
Binary files differ
public/import-templates/Îļþ·¢·Å»ØÊռǼµ¼ÈëÄ£°å.docx
Binary files differ
public/import-templates/Îļþ¶¨ÆÚÉó²é¼Ç¼µ¼ÈëÄ£°å.docx
Binary files differ
public/import-templates/ÎļþÉóÅú¼Ç¼µ¼ÈëÄ£°å.docx
Binary files differ
public/import-templates/ÎļþÇåµ¥µ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/±ê×¼²éе¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/¼ìÑé¶ÔÏóµ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/¼ìÑéÏîÄ¿²ÎÊýµ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/ÖÊÁ¿¼à¿Ø¼Æ»®µ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/ÖÊÁ¿¼à¶½¼Æ»®µ¼ÈëÄ£°å.xlsx
Binary files differ
public/import-templates/ÖØ´ó·çÏÕÒòËØ·ÖÎö¼°¿ØÖƼƻ®µ¼ÈëÄ£°å.xlsx
Binary files differ
src/api/cnas/process/ensureResults/qualitySupervise.js
@@ -125,6 +125,14 @@
    data: query,
  })
}
// è¢«ç›‘督人员确认监督记录(程序4.2.5)
export function confirmSuperviseRecord(query) {
  return request({
    url: '/qualitySupervise/confirmSuperviseRecord',
    method: 'post',
    data: query,
  })
}
// èŽ·å–å½“å‰ç›‘ç£è®°å½•çš„ç›‘ç£å‘˜åˆ—è¡¨
export function getRecordUser(query) {
  return request({
src/api/standard/standardLibrary.js
@@ -136,6 +136,11 @@
  return request({ url: "/standardTree/selectTemplateBindings", method: "get", params: query });
}
// æ ¹æ®æ£€æµ‹æ ‡å‡†ï¼ˆæ ‡å‡†æ–¹æ³•)查询标准库中已绑定的原始记录模板
export function selectTemplateBindingsByStandardMethod(query) {
  return request({ url: "/standardTree/selectTemplateBindingsByStandardMethod", method: "get", params: query });
}
export function saveTemplateBindings(data) {
  return request({ url: "/standardTree/saveTemplateBindings", method: "post", data });
}
src/assets/styles/element-ui.scss
@@ -149,7 +149,9 @@
  font-size: 14px !important;
}
.el-button--primary {
// ä»…作用于实心主按钮;plain/link/text æŒ‰é’®çš„æ–‡å­—与背景由 Element Plus çš„主题变量控制,
// å¦åˆ™è¿™é‡Œä¼šæŠŠ plain æŒ‰é’®çš„背景也刷成主色,与同色的文字重叠导致“看不见字”。
.el-button--primary:not(.is-plain):not(.is-link):not(.is-text) {
  background: var(--el-color-primary);
  border-color: var(--el-color-primary);
}
src/components/ImportDialog/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,204 @@
<template>
  <el-dialog :title="title" v-model="visible" width="520px" append-to-body @close="handleClose">
    <div v-if="templateUrl" class="template-tip">
      <el-icon><InfoFilled /></el-icon>
      <span>第一次导入请先下载模板,按模板格式填写后再上传。</span>
      <el-button link type="primary" @click="handleDownloadTemplate">下载导入模板</el-button>
    </div>
    <el-upload
      ref="uploadRef"
      drag
      name="file"
      :action="action"
      :headers="headers"
      :accept="accept"
      :limit="1"
      :multiple="false"
      :auto-upload="false"
      :on-change="handleFileChange"
      :on-remove="handleFileRemove"
      :on-exceed="handleExceed"
      :on-success="handleSuccess"
      :on-error="handleError"
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">将文件拖到此处,或<em>点击选择文件</em></div>
    </el-upload>
    <div class="upload-tips">
      <div>支持格式:{{ acceptText }}</div>
      <div>单个文件不超过 {{ maxSizeMb }}MB,一次只能上传一个文件。</div>
      <div v-for="(tip, index) in tips" :key="index">{{ tip }}</div>
    </div>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="visible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" :loading="uploading" @click="handleSubmit">开始导入</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed, getCurrentInstance, nextTick, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { InfoFilled, UploadFilled } from '@element-plus/icons-vue'
const { proxy } = getCurrentInstance()
const props = defineProps({
  title: {
    type: String,
    default: '数据导入'
  },
  // ä¸Šä¼ æŽ¥å£å®Œæ•´åœ°å€
  action: {
    type: String,
    required: true
  },
  // å…è®¸çš„æ–‡ä»¶åŽç¼€ï¼Œå¦‚ '.xls,.xlsx'
  accept: {
    type: String,
    default: '.xls,.xlsx'
  },
  // æ¨¡æ¿æ–‡ä»¶åœ°å€ï¼ˆæ”¾åœ¨ public ä¸‹ï¼Œå¦‚ /import-templates/xxx.xlsx)
  templateUrl: {
    type: String,
    default: ''
  },
  templateName: {
    type: String,
    default: '导入模板'
  },
  // æ–‡ä»¶å¤§å°ä¸Šé™ï¼ˆMB)
  maxSizeMb: {
    type: Number,
    default: 10
  },
  // é¢å¤–填写说明
  tips: {
    type: Array,
    default: () => []
  }
})
const emit = defineEmits(['success'])
const uploadRef = ref(null)
const visible = ref(false)
const selectedFile = ref(null)
const uploading = ref(false)
const headers = computed(() => proxy.uploadHeader || {})
const acceptText = computed(() => props.accept.split(',').map(item => item.trim()).join('、'))
function open() {
  visible.value = true
  uploading.value = false
  nextTick(() => {
    selectedFile.value = null
    uploadRef.value?.clearFiles()
  })
}
function handleClose() {
  uploading.value = false
  selectedFile.value = null
  uploadRef.value?.clearFiles()
}
function handleDownloadTemplate() {
  const link = document.createElement('a')
  link.href = props.templateUrl
  link.download = props.templateName
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}
function handleFileChange(file) {
  selectedFile.value = file
}
function handleFileRemove() {
  selectedFile.value = null
}
function handleExceed() {
  ElMessage.warning('一次只能上传一个文件,请先移除已选择的文件')
}
function validateFile(file) {
  const name = (file.name || '').toLowerCase()
  const allowed = props.accept.split(',').map(item => item.trim().toLowerCase()).filter(Boolean)
  if (allowed.length && !allowed.some(ext => name.endsWith(ext))) {
    ElMessage.error(`请选择 ${acceptText.value} æ ¼å¼çš„æ–‡ä»¶`)
    uploadRef.value?.clearFiles()
    selectedFile.value = null
    return false
  }
  if (file.size && file.size > props.maxSizeMb * 1024 * 1024) {
    ElMessage.error(`上传文件不超过${props.maxSizeMb}M`)
    uploadRef.value?.clearFiles()
    selectedFile.value = null
    return false
  }
  return true
}
function handleSubmit() {
  const file = selectedFile.value
  if (!file) {
    ElMessage.warning('请先选择要导入的文件')
    return
  }
  if (!validateFile(file)) {
    return
  }
  uploading.value = true
  uploadRef.value.submit()
}
function handleSuccess(response) {
  uploading.value = false
  if (response && response.code != null && response.code !== 200) {
    ElMessage.error(response.msg || '导入失败')
    return
  }
  ElMessage.success('导入成功')
  visible.value = false
  uploadRef.value?.clearFiles()
  selectedFile.value = null
  emit('success', response)
}
function handleError() {
  uploading.value = false
  ElMessage.error('导入失败,请检查文件格式和内容后重试')
}
defineExpose({ open })
</script>
<style scoped>
.template-tip {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px;
  padding: 8px 12px;
  border-radius: 4px;
  background: var(--el-color-primary-light-9);
  color: var(--el-text-color-regular);
  font-size: 13px;
}
.upload-tips {
  margin-top: 10px;
  color: var(--el-text-color-secondary);
  font-size: 12px;
  line-height: 20px;
}
</style>
src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue
@@ -17,7 +17,7 @@
          </div>
        </div>
        <div style="line-height: 30px;">
          <el-button size="small" type="primary" @click="uploadDia = true, getUserList()">导入</el-button>
          <el-button size="small" type="primary" @click="openUpload">导入</el-button>
        </div>
      </div>
      <lims-table :tableData="yearPlanTableData" :column="yearPlanColumn" :currentChange="currentChange"
@@ -95,14 +95,24 @@
          </el-option>
        </el-select>
      </div>
      <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="javaApi + '/personTraining/personTrainingImport' + '?planYear=' + planYear + '&reviewerId=' + reviewerId"
          :auto-upload="false" :before-upload="beforeUpload" :file-list="fileList" :headers="uploadHeader" :limit="1"
          :on-error="onError" :on-success="onSuccess" accept=".xlsx" drag name="file">
          :auto-upload="false" :before-upload="beforeUpload" v-model:file-list="fileList" :headers="uploadHeader" :limit="1"
          :on-error="onError" :on-success="onSuccess" accept=".xls,.xlsx" drag 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-DD,课时填写整数。</div>
        <div>导入只读取模板的第一个工作表。</div>
      </div>
      <template #footer><span  class="dialog-footer">
        <el-button @click="uploadDia = false">取 æ¶ˆ</el-button>
@@ -626,6 +636,21 @@
        this.$download.saveAs(blob, row.fileName + '.docx')
      })
    },
    // æ‰“开导入弹框
    openUpload() {
      this.fileList = []
      this.uploadDia = true
      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)
    },
    // å¹´åº¦è®¡åˆ’-导入
    submitUpload() {
      if (!this.planYear) {
@@ -636,7 +661,7 @@
        this.$message.error('请选择审核人')
        return
      }
      if (this.$refs.upload.uploadFiles.length == 0) {
      if (!this.fileList || this.fileList.length == 0) {
        this.$message.error('未选择文件')
        return
      }
@@ -663,11 +688,17 @@
      this.uploading = false
    },
    beforeUpload(file, fileList) {
      if (file.type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
        this.$message.error('上传文件格式不正确');
      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()
        return false;
      }
      return true;
    },
    // å¹´åº¦è®¡åˆ’明细表-编辑
    editInDetail(row) {
src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue
@@ -52,9 +52,16 @@
            </el-select>
          </el-form-item></el-col>
          <el-col :span="12"><el-form-item label="原始记录模板" prop="templateIds">
            <el-select v-model="form.templateIds" multiple collapse-tags filterable style="width: 100%" placeholder="可选择多个模板">
            <el-select v-model="form.templateIds" multiple collapse-tags filterable style="width: 100%"
              :loading="templateBindingLoading" :disabled="!form.standardMethodId"
              :placeholder="form.standardMethodId ? '请选择原始记录模板' : '请先选择检测标准'"
              :no-data-text="form.standardMethodId ? '该检测标准暂未绑定原始记录模板' : '请先选择检测标准'">
              <el-option v-for="item in templateOptions" :key="item.id" :value="item.id" :label="item.name" />
            </el-select>
            <div v-if="form.standardMethodId && !templateBindingLoading && templateOptions.length === 0"
              style="font-size: 12px; line-height: 18px; color: var(--el-color-warning); margin-top: 4px;">
              è¯¥æ£€æµ‹æ ‡å‡†åœ¨æ ‡å‡†åº“中尚未绑定原始记录模板,请先在标准库中完成绑定。
            </div>
          </el-form-item></el-col>
          <el-col :span="12"><el-form-item label="样品名称"><el-input v-model="form.sampleName" /></el-form-item></el-col>
          <el-col :span="12"><el-form-item label="样品规格"><el-input v-model="form.sampleSpec" /></el-form-item></el-col>
@@ -84,6 +91,7 @@
} from "@/api/cnas/process/ensureResults/qualityMonitor";
import { selectStandardMethodList } from '@/api/cnas/process/method/standardMethod';
import { selectStandardTemplatePageList } from '@/api/standard/model';
import { selectTemplateBindingsByStandardMethod } from '@/api/standard/standardLibrary';
import { selectUserCondition } from '@/api/performance/class';
export default {
@@ -119,7 +127,9 @@
        dueDate: null,
      },
      standardOptions: [],
      allTemplateOptions: [],
      templateOptions: [],
      templateBindingLoading: false,
      userOptions: [],
      rules: {
        monitorProject: [{ required: true, message: '请输入质控项目', trigger: 'blur' }],
@@ -137,12 +147,18 @@
  methods: {
    openDia(type, row) {
      this.formDia = true
      selectStandardMethodList({ current: 1, size: 1000 }).then(res => { this.standardOptions = res.data?.records || res.data || [] })
      selectStandardTemplatePageList({ current: 1, size: 1000 }).then(res => { this.templateOptions = res.data?.records || res.data || [] })
      selectUserCondition({ type: 1 }).then(res => { this.userOptions = res.data || [] })
      this.operationType = type
      this.templateOptions = []
      this.allTemplateOptions = []
      this.templateBindingLoading = false
      selectStandardMethodList({ current: 1, size: 1000 }).then(res => { this.standardOptions = res.data?.records || res.data || [] })
      selectStandardTemplatePageList({ current: 1, size: 1000 }).then(res => { this.allTemplateOptions = res.data?.records || res.data || [] })
      selectUserCondition({ type: 1 }).then(res => { this.userOptions = res.data || [] })
      if (type === 'edit') {
        this.searchInfo(row)
        if (row.itemKind === 'INSPECTION' && row.standardMethodId) {
          this.loadBoundTemplates(row.standardMethodId, this.form.templateIds)
        }
      }
    },
    searchInfo (row) {
@@ -156,6 +172,37 @@
      if (standard) this.form.technicalStandardText = `${standard.code || ''} ${standard.name || ''}`.trim()
      this.form.templateIds = []
      this.form.templateId = null
      // å’Œå§”托下单保持一致:选择检测标准后,只展示该标准在标准库中已绑定的原始记录模板
      this.loadBoundTemplates(id)
    },
    loadBoundTemplates(standardMethodId, selectedIds = null) {
      const currentIds = Array.isArray(selectedIds) ? selectedIds.map(Number) : null
      this.templateOptions = []
      if (currentIds === null) this.form.templateIds = []
      if (!standardMethodId) return
      this.templateBindingLoading = true
      selectTemplateBindingsByStandardMethod({ standardMethodId }).then(res => {
        const bound = (res.data || []).map(item => ({ id: Number(item.templateId), name: item.name }))
        this.templateOptions = bound
        if (currentIds === null) {
          // æ–°å¢žæˆ–切换检测标准时,默认选中该标准已绑定的全部模板
          this.form.templateIds = bound.map(item => item.id)
          return
        }
        // ç¼–辑时保留原选择;若模板后来被解绑,补充名称避免下拉框只显示 ID
        const boundIds = new Set(bound.map(item => item.id))
        currentIds.forEach(id => {
          if (!boundIds.has(id)) {
            const template = this.allTemplateOptions.find(item => Number(item.id) === id)
            if (template) this.templateOptions.push({ id: Number(template.id), name: template.name })
          }
        })
        this.form.templateIds = currentIds
      }).catch(() => {
        this.templateOptions = []
      }).finally(() => {
        this.templateBindingLoading = false
      })
    },
    buildEntity () {
      const entity = this.HaveJson(this.form)
src/views/CNAS/process/ensureResults/ensureResultsValidity/components/inspectionEvaluationDialog.vue
@@ -7,7 +7,8 @@
              :closable="false"/>
    <el-alert v-if="!canEdit" :title="editNotice" :type="loaded ? 'warning' : 'info'"
              :closable="false" style="margin-top:8px"/>
    <el-form :model="form" label-width="110px" style="margin-top: 16px">
    <div class="role-hint">监督员默认为计划中指定的评价人,被监督人员为计划中指定的检测员。</div>
    <el-form :model="form" label-width="150px" style="margin-top: 16px">
      <el-row :gutter="12">
        <el-col :span="12">
          <el-form-item label="检测项目">
@@ -43,14 +44,14 @@
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="监督员">
          <el-form-item label="监督员(评价人)">
            <el-select v-model="form.supervisorUserId" filterable style="width: 100%" :disabled="!canEdit">
              <el-option v-for="person in supervisorOptions" :key="person.id" :value="person.id" :label="person.name"/>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="被监督人员">
          <el-form-item label="被监督人员(检测员)">
            <el-input :model-value="supervisedPersonName" disabled/>
          </el-form-item>
        </el-col>
@@ -168,12 +169,23 @@
    },
    supervisedPersonName() {
      const id = this.form.inspectorUserId || this.row?.inspectorUserId
      return this.people.find(person => Number(person.id) === Number(id))?.name || ''
      return this.people.find(person => Number(person.id) === Number(id))?.name
        || this.form.inspectorName || this.row?.inspectorName || ''
    },
    supervisorOptions() {
      const inspectorId = Number(this.form.inspectorUserId || this.row?.inspectorUserId)
      const evaluatorId = Number(this.row?.evaluatorUserId)
      return this.people.filter(person => Number(person.id) !== inspectorId && Number(person.id) !== evaluatorId)
      // ç›‘督员默认就是计划中的评价人,因此这里不能把评价人排除掉;只排除被监督的检测员
      const options = this.people.filter(person => Number(person.id) !== inspectorId)
      // å·²ä¿å­˜çš„监督员如果不在人员下拉列表中(被角色/状态过滤或已停用),补一条选项保证能反显
      const savedId = this.form.supervisorUserId
      if (savedId && !options.some(person => Number(person.id) === Number(savedId))) {
        const saved = this.people.find(person => Number(person.id) === Number(savedId))
        options.unshift({
          id: savedId,
          name: saved?.name || this.form.supervisorName || `用户 ${savedId}`
        })
      }
      return options
    }
  },
  methods: {
@@ -192,7 +204,14 @@
        this.people = res.data || []
      })
      getInspectionEvaluation(row.qualityMonitorDetailsId).then(res => {
        if (res.data) this.form = {...res.data, resultRows: res.data.resultRows || [], measurementConfirmed: false}
        if (res.data) {
          this.form = {...res.data, resultRows: res.data.resultRows || [], measurementConfirmed: false}
          // å…œåº•:历史评价记录没有监督员时,默认带出计划评价人
          if (!this.form.supervisorUserId && this.row?.evaluatorUserId) {
            this.form.supervisorUserId = this.row.evaluatorUserId
            this.form.supervisorName = this.people.find(person => Number(person.id) === Number(this.row.evaluatorUserId))?.name || ''
          }
        }
      }).catch(() => {
        this.loadError = '评价记录读取失败,请刷新后重试'
      }).finally(() => {
@@ -231,3 +250,12 @@
  }
}
</script>
<style scoped>
.role-hint {
  margin-top: 8px;
  font-size: 12px;
  line-height: 18px;
  color: var(--el-text-color-secondary);
}
</style>
src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
@@ -62,9 +62,11 @@
      </div>
    </div>
    <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;">
          <span class="section-title">质控项目</span>
          <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: var(--lims-text-2);">质控项目</span>
            <el-input v-model="yearDetailForm.monitorProject" placeholder="请输入" size="small"
@@ -212,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>
@@ -247,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>
@@ -397,8 +410,7 @@
        }, {
          label: '监控任务编号', prop: 'inspectionTaskCode', minWidth: '150px'
        }, {
          label: '任务状态', prop: 'inspectionTaskState', width: '100px',
          formatData: value => this.inspectionTaskStatus(value)
          label: '任务状态', prop: 'taskStatusText', width: '110px'
        },{
          dataType: 'tag',
          label: '评价状态',
@@ -575,10 +587,26 @@
    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()
@@ -588,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.selectedYear
          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) {
@@ -615,6 +660,12 @@
        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
@@ -738,7 +789,10 @@
      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.$nextTick(() => this.$refs.yearDetailTableData?.$refs?.multipleTable?.doLayout())
      }).catch(err => {
@@ -791,6 +845,7 @@
        }
        this.lookDialogVisible = true
      } else {
        this.fileList1 = []
        this.uploadDia1 = true
      }
    },
@@ -820,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
      }
@@ -991,9 +1046,14 @@
      if (command === 'export') this.handleDown(row)
      if (command === 'delete') this.delPlan(row)
    },
    inspectionTaskStatus(value) {
    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 value === null || value === undefined ? '未下发' : (map[value] || '处理中')
      return map[value] || '处理中'
    },
    handleDetailCommand(command, row) {
      const handlers = {
src/views/CNAS/process/ensureResults/qualityControlPlan/components/detailFormDialog.vue
@@ -38,7 +38,7 @@
        </el-col>
        <el-col :span="12">
          <el-form-item label="监督原因" prop="superviseReason">
            <el-input v-model="form.superviseReason" clearable disabled size="small"></el-input>
            <el-input v-model="form.superviseReason" clearable placeholder="请输入监督原因" size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
@@ -94,15 +94,10 @@
  },
  // æ–¹æ³•集合
  methods: {
    openDia(type, row, causeType) {
    openDia(type, row) {
      this.formDia = true
      this.operationType = type
      this.getUserList()
      if (causeType == 1) {
        this.form.superviseReason = '定期监督'
      } else {
        this.form.superviseReason = '动态监督'
      }
      if (type === 'edit') {
        this.searchInfo(row)
      }
@@ -115,6 +110,12 @@
    },
    searchInfo (row) {
      this.form = {...row}
      // å¯¼å…¥çš„历史数据可能是完整日期(2026/3/15),月份选择器需要 YYYY.M æ‰èƒ½å›žæ˜¾
      const text = row.superviseTime ? String(row.superviseTime).trim() : ''
      const matched = text.match(/^(\d{4})\s*[./å¹´-]\s*(\d{1,2})(?:\s*[./月-]\s*\d{1,2}\s*日?)?$/)
      if (matched && Number(matched[2]) >= 1 && Number(matched[2]) <= 12) {
        this.form.superviseTime = `${matched[1]}.${Number(matched[2])}`
      }
    },
    // æäº¤è¡¨å•
    submitForm () {
src/views/CNAS/process/ensureResults/qualityControlPlan/components/recordsDialog.vue
@@ -70,6 +70,18 @@
          </tr>
          <tr>
            <td class="td-title">
              <p>监督方式:</p>
            </td>
            <td colspan="5">
              <el-select v-if="currentStep === 0" v-model="superviseModeList" multiple filterable collapse-tags
                         placeholder="请选择监督方式(程序4.2.4)" size="small" style="width: 100%">
                <el-option v-for="item in superviseModeItems" :key="item" :label="item" :value="item"></el-option>
              </el-select>
              <span v-else class="td-info"> {{ form.superviseMethod }}</span>
            </td>
          </tr>
          <tr>
            <td class="td-title">
              <p><span class="required-span">* </span>监督记录:</p>
            </td>
            <td colspan="5">
@@ -108,6 +120,23 @@
                        type="textarea">
              </el-input>
              <span v-else class="td-info"> {{ form.rectifyAdvice }}</span>
            </td>
          </tr>
          <tr>
            <td class="td-title">
              <p>被监督人员确认意见:</p>
            </td>
            <td colspan="5">
              <span v-if="form.confirmOpinion" class="td-info">{{ form.confirmOpinion }}</span>
              <template v-else-if="canConfirm">
                <el-input v-model="confirmOpinion" :rows="2" size="small" type="textarea"
                          placeholder="对监督记录真实性表示认可。"/>
                <el-button :loading="confirming" size="small" type="primary" style="margin-top: 6px"
                           @click="confirmRecord">ç¡® è®¤</el-button>
              </template>
              <span v-else class="td-info">
                {{ form.superviseDetailsRecordId ? '待被监督人员确认' : '监督员提交后由被监督人员确认' }}
              </span>
            </td>
          </tr>
          <tr>
@@ -170,11 +199,26 @@
<script>
import {
  addSuperviseDetailRecord,
  addSuperviseRecordOpinion, getRecordUser,
  addSuperviseRecordOpinion, confirmSuperviseRecord, getRecordUser,
  getSuperviseDetailRecord
} from "@/api/cnas/process/ensureResults/qualitySupervise";
import {selectUserCondition} from "@/api/business/inspectionTask";
import {mapGetters} from "@/utils/store-compat";
// ç›‘督内容9项,与受控记录表 LMSY-CX29-JL02 çš„æ–‡å­—及横排顺序完全一致
const SUPERVISE_CONTENT_ITEMS = [
  '人员持证及培训', '设施及环境条件', '检测方法',
  '仪器设备管理', '测量溯源性', '标准物质管理',
  '样品制备和处置', '检测数据处理', '结果报告结果'
]
// ç›‘督方式,对应《质量监督程序》4.2.4
const SUPERVISE_MODE_ITEMS = [
  '核查原始记录和报告',
  '现场监督实际操作过程',
  '与被监督对象交流',
  '内部质量控制/外部质量评估',
  '培训效果评价'
]
export default {
  name: 'recordsDialog',
@@ -200,6 +244,8 @@
        ratifyOpinion: '',
        isAccording: '',
        isFinish: '',
        superviseMethod: '',
        confirmOpinion: '',
      },
      currentStep: 0,
      editLoad: false,
@@ -209,11 +255,27 @@
      ratifyUserId: '',
      superviseeList: [],
      superviseContentList: [],
      superviseContentItems: ['人员持证及培训', '设施及环境条件', '检测方法', '仪器设备管理', '测量溯源性', '标准物质管理', '样品制备和处置', '检测数据处理', '结果报告结果'],
      superviseContentItems: SUPERVISE_CONTENT_ITEMS,
      superviseModeItems: SUPERVISE_MODE_ITEMS,
      superviseModeList: [],
      confirmOpinion: '',
      confirming: false,
    };
  },
  computed: {
    ...mapGetters(["userId"]),
    ...mapGetters(["userId", "nickName"]),
    // è®°å½•已提交且尚未确认时,只有被监督人员本人可以确认(程序4.2.5)
    canConfirm() {
      if (!this.form.superviseDetailsRecordId || this.form.confirmOpinion) {
        return false
      }
      const members = (this.form.testMember || '').split('、').map(item => item.trim()).filter(Boolean)
      if (this.nickName && members.includes(this.nickName)) {
        return true
      }
      // å…œåº•:计划明细中登记的被监督人员
      return !!this.supervisedUserId && Number(this.supervisedUserId) === Number(this.userId)
    },
    contentRows() {
      const rows = []
      for (let i = 0; i < this.superviseContentItems.length; i += 3) {
@@ -255,6 +317,8 @@
        this.ratifyUserId = res.data.ratifyUserId
        this.superviseeList = this.form.testMember ? this.form.testMember.split('、').filter(i => i) : []
        this.superviseContentList = this.form.superviseContent ? this.form.superviseContent.split('、').filter(i => i) : []
        this.superviseModeList = this.form.superviseMethod ? this.form.superviseMethod.split('、').filter(i => i) : []
        this.confirmOpinion = this.form.confirmOpinion || ''
        this.getRecordUser()
      }).catch(err => {
        console.log('err---', err);
@@ -273,6 +337,7 @@
        }
        this.form.testMember = this.superviseeList.join('、')
        this.form.superviseContent = this.superviseContentList.join('、')
        this.form.superviseMethod = this.superviseModeList.join('、')
        this.addInfo()
      } else {
        this.editInfo(isFinish)
@@ -311,6 +376,20 @@
        this.editLoad = false
      })
    },
    // è¢«ç›‘督人员确认监督记录(程序4.2.5)
    confirmRecord () {
      const opinion = (this.confirmOpinion || '').trim() || '对监督记录真实性表示认可。'
      this.confirming = true
      confirmSuperviseRecord({
        superviseDetailsId: this.form.superviseDetailsId,
        confirmOpinion: opinion
      }).then(() => {
        this.form.confirmOpinion = opinion
        this.$message.success('已确认监督记录')
      }).finally(() => {
        this.confirming = false
      })
    },
    // å…³é—­å¼¹æ¡†
    closeRecordsDia () {
      this.formDia = false
@@ -331,7 +410,12 @@
    },
    getRecordUser () {
      getRecordUser({superviseDetailsId: this.form.superviseDetailsId}).then(res => {
        this.recordUserList = res.data
        this.recordUserList = res.data || []
        // è®¡åˆ’已指定监督员但尚未产生记录时,默认反显第一位监督员;
        // åŽç«¯åªæœ‰åœ¨å­˜åœ¨ä¸Šæœˆè®°å½•时才会自动带出监督员,首次填写会为空。
        if (this.currentStep === 0 && !this.form.supervisor && this.recordUserList.length) {
          this.form.supervisor = this.recordUserList[0].userName
        }
      })
    },
  }
src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue
@@ -25,12 +25,6 @@
      <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;">
            <el-radio-group v-model="yearDetailForm.causeType" @change="getYearDetailPlan" size="small">
              <el-radio-button :value="1">定期监督</el-radio-button>
              <el-radio-button :value="2">动态监督</el-radio-button>
            </el-radio-group>
          </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: var(--lims-text-2);">监控目的</span>
            <el-input v-model="yearDetailForm.supervisePurpose" placeholder="请输入" size="small"
                      @keyup.enter="getYearDetailPlan"></el-input>
@@ -118,33 +112,46 @@
    <!--导入计划-->
    <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: 80px">年份:</span>
        <el-date-picker v-model="superviseYear" type="year" value-format="YYYY" clearable size="small" format="YYYY"
          placeholder="选择年">
        </el-date-picker>
        <span style="width: 110px">监督员:</span>
        <el-select v-model="recordUserIds" placeholder="请选择" size="small" @change="splitList" :multiple-limit="2"
          filterable multiple style="width: 100%">
          <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option>
        </el-select>
      </div>
      <div style="display: flex;align-items: center;margin: 10px 0">
        <div style="width: 60px">批准人:</div>
        <el-select v-model="ratifyUserId" clearable filterable size="small" style="width: 50%;">
          <el-option v-for="item in userList" :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="superviseYear" 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="recordUserIds" placeholder="请选择" @change="splitList" :multiple-limit="2"
            filterable multiple style="width: 100%">
            <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="批准人">
          <el-select v-model="ratifyUserId" clearable filterable style="width: 100%">
            <el-option v-for="item in userList" :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="upload1" :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'
          :limit="1" accept=".xls,.xlsx"
          :on-change="beforeUpload" :on-error="onError" :on-success="handleSuccessUp" drag
          :data="{ recordUserIds: recordUserIds1, superviseYear: superviseYear, ratifyUserId: ratifyUserId }"
          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>监督日期建议填“年.月”(如 2026.3);填成 2026-03-15 ä¹Ÿå¯ä»¥ï¼Œç³»ç»Ÿä¼šè‡ªåŠ¨æ¢ç®—ä¸º 2026.3。</div>
        <div>被监督人员必须填写系统中已存在的人员姓名,否则整批导入失败。</div>
        <div>导入只读取模板的第一个工作表。</div>
        <div style="color: var(--el-color-warning);">导入后计划为“待批准”:需使用所选批准人账号登录并点击“批准”,批准后才能下发和导出。</div>
      </div>
      <template #footer><span  class="dialog-footer">
        <el-button @click="closeUploadDia">取 æ¶ˆ</el-button>
@@ -208,7 +215,7 @@
            } else if (params === 1) {
              return '批准';
            } else {
              return null;
              return '待批准';
            }
          },
          formatType: (params) => {
@@ -217,7 +224,7 @@
            } else if (params === 1) {
              return 'success';
            } else {
              return null;
              return 'warning';
            }
          }
        }, {
@@ -316,8 +323,7 @@
      yearLoading: false,
      yearDetailForm: {
        supervisePurpose: '',
        superviseProject: '',
        causeType: 1
        superviseProject: ''
      },
      yearDetailColumnData: [
        {
@@ -570,13 +576,29 @@
      this.yearPage.size = limit;
      this.getYearPlanList();
    },
    // ä¸‹è½½å¯¼å…¥æ¨¡æ¿
    downloadTemplate() {
      const link = document.createElement('a')
      // å¸¦ç‰ˆæœ¬å‚数避免浏览器继续使用缓存里的旧模板
      link.href = '/import-templates/质量监督计划导入模板.xlsx?v=3'
      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.upload1.clearFiles()
        return false;
      }
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload1.clearFiles()
        return false;
      }
      return true;
    },
    onError(err, file, fileList) {
      this.$message.error('上传失败')
@@ -585,7 +607,13 @@
    handleSuccessUp(response) {
      this.uploading = false;
      if (response.code == 200) {
        this.$message.success('上传成功');
        const approver = this.userList.find(item => Number(item.id) === Number(this.ratifyUserId))
        const approverName = approver ? approver.name : '所选批准人'
        if (Number(this.ratifyUserId) === Number(this.userId)) {
          this.$message.success('导入成功,请在列表中点击“批准”完成批准,批准后才能下发和导出')
        } else {
          this.$message.success(`导入成功,计划已提交给${approverName}批准;请用该账号登录批准,批准后才能下发和导出`)
        }
        this.recordUserIds = []
        this.superviseYear = ''
        this.fileList = []
@@ -636,7 +664,12 @@
    getUserList() {
      // è´¨é‡ç›‘督面向全实验室, äººå‘˜åˆ—表不按部门筛选
      selectUserCondition({ type: 1 }).then((res) => {
        this.userList = res.data || [];
        const list = res.data || []
        // å½“前登录用户可能因角色(如 admin)被接口过滤,仍允许选自己作为批准人,避免导入后无人可批准
        if (this.userId && !list.some(item => Number(item.id) === Number(this.userId))) {
          list.unshift({ id: this.userId, name: this.nickName })
        }
        this.userList = list
      })
    },
    // æ‰¹å‡†
@@ -747,18 +780,30 @@
      this.getYearDetailPlanList()
    },
    // èŽ·å–å¹´åº¦æ˜Žç»†è¡¨
    // ç›‘督日期统一成 "YYYY.M":兼容 2026-03-15 / 2026/3/15 / 2026å¹´3月15日 ç­‰å¯¼å…¥å†™æ³•
    normalizeSuperviseMonth(value) {
      if (!value) return value
      const text = String(value).trim()
      const matched = text.match(/^(\d{4})\s*[./å¹´-]\s*(\d{1,2})(?:\s*[./月-]\s*\d{1,2}\s*日?)?$/)
      if (!matched) return text
      const month = Number(matched[2])
      return month >= 1 && month <= 12 ? `${matched[1]}.${month}` : text
    },
    getYearDetailPlanList() {
      const entity = {
        superviseId: this.superviseId,
        supervisePurpose: this.yearDetailForm.supervisePurpose,
        superviseProject: this.yearDetailForm.superviseProject,
        causeType: this.yearDetailForm.causeType,
      }
      const page = this.yearDetailPage
      this.yearDetailLoading = true
      pageQualitySuperviseDetail({ ...entity, ...page }).then(res => {
        this.yearDetailLoading = false
        this.yearDetailTableData = res.data.records
        // åŽ†å²å¯¼å…¥çš„ç›‘ç£æ—¥æœŸå¯èƒ½æ˜¯ 2026/3/15、2026-03-15 ç­‰å®Œæ•´æ—¥æœŸï¼Œç»Ÿä¸€æ˜¾ç¤ºæˆ 2026.3
        this.yearDetailTableData = (res.data.records || []).map(item => ({
          ...item,
          superviseTime: this.normalizeSuperviseMonth(item.superviseTime)
        }))
        this.yearDetailPage.total = res.data.total
      }).catch(err => {
        console.log('err---', err);
@@ -779,7 +824,7 @@
    showDialog(type, row) {
      this.formDia = true
      this.$nextTick(() => {
        this.$refs.formDia.openDia(type, row, this.yearDetailForm.causeType)
        this.$refs.formDia.openDia(type, row)
      })
    },
    closeDia() {
@@ -872,7 +917,7 @@
    action() {
      return this.javaApi + '/qualitySupervise/importQualitySupervise'
    },
    ...mapGetters(["userId"]),
    ...mapGetters(["userId", "nickName"]),
  },
};
</script>
src/views/CNAS/process/method/methodVerification/component/formDIa.vue
@@ -1,11 +1,11 @@
<template>
  <div>
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false"
               :title="operationType === 'edit' ? '编辑' : operationType === 'view' ? '详情' : '新增'"
               :title="dialogTitle"
               v-model="formDia"
               width="90%" @close="closeDia">
      <div v-if="operationType === 'edit'" style="text-align: right">
        <el-button :disabled="form.confirmDate" size="small" type="primary" @click="validation">验证确认</el-button>
        <el-button :disabled="form.confirmDate" size="small" type="primary" @click="validation">{{ verifyType === 0 ? '变更确认' : '验证确认' }}</el-button>
      </div>
      <fieldset :disabled="operationType === 'view'" class="view-fieldset">
      <table border="1" cellspacing="10" class="tables">
@@ -14,7 +14,7 @@
            <p>标准方法</p>
          </td>
          <td>
            <p>验证原因</p>
            <p>{{ verifyType === 0 ? '变更原因' : '验证原因' }}</p>
          </td>
          <td>
            <p>主要技术变化</p>
@@ -221,7 +221,7 @@
          </td>
        </tr>
        <tr>
          <td>验证结论</td>
          <td>{{ verifyType === 0 ? '变更结论' : '验证结论' }}</td>
          <td colspan="7">
            <el-input v-model="form.verificationConclusion" :rows="2" placeholder="请输入内容" size="small" type="textarea"></el-input>
          </td>
@@ -323,6 +323,20 @@
    operationType: {
      type: String,
      default: () => ''
    },
    // 0 æ ‡å‡†æ–¹æ³•变更,1 æ–¹æ³•验证
    verifyType: {
      type: Number,
      default: 1
    }
  },
  computed: {
    typeLabel() {
      return this.verifyType === 0 ? '标准方法变更' : '方法验证'
    },
    dialogTitle() {
      if (this.operationType === 'view') return `查看${this.typeLabel}`
      return this.operationType === 'edit' ? `编辑${this.typeLabel}` : `新增${this.typeLabel}`
    }
  },
  data() {
@@ -414,7 +428,7 @@
      this.editLoad = true
      const processMethodSearchNews = this.HaveJson(this.form)
      processMethodSearchNews.confirmUser = processMethodSearchNews.confirmUser && processMethodSearchNews.confirmUser.join(',')
      processMethodSearchNews.operationType = 1
      processMethodSearchNews.operationType = this.verifyType
      if (this.operationType === 'edit') {
        this.editInfo(processMethodSearchNews)
      } else {
src/views/CNAS/process/method/standardMethodsChange/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,200 @@
<template>
  <div class="capacity-scope">
    <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: var(--lims-text-2);">标准方法</span>
          <el-input size="small" placeholder="请输入" clearable v-model="searchForm.standardName"
                    @keyup.enter="searchList"></el-input>
        </div>
        <div style="line-height: 30px;">
          <el-button type="primary" size="small" @click="searchList">查询</el-button>
          <el-button size="small" @click="resetSearchForm">重置</el-button>
        </div>
      </div>
      <div style="line-height: 30px;">
        <el-button size="small" type="primary" @click="openFormDia('add')">新 å¢ž</el-button>
      </div>
    </div>
    <div class="table">
      <limsTable :column="tableColumn" :height="'calc(100vh - 23em)'" :table-data="tableData"
                 :table-loading="tableLoading" :page="page"
                 @pagination="pagination">
      </limsTable>
    </div>
    <component :is="'formDIa'" v-if="formDIa" ref="formDIa" :operationType="operationType" :verify-type="0"
               @closeDia="closeDia" />
  </div>
</template>
<script>
import limsTable from '@/components/Table/lims-table.vue'
import formDIa from '../methodVerification/component/formDIa.vue'
import { delMethodVerify, exportMethodVerify, pagesMethodVerify } from '@/api/cnas/process/method/methodVerification'
export default {
  name: 'StandardMethodsChange',
  components: { limsTable, formDIa },
  data() {
    return {
      searchForm: {
        standardName: '',
        operationType: 0,
      },
      tableColumn: [
        {
          label: '标准方法',
          prop: 'methodName',
          minWidth: '120'
        },
        {
          label: '变更原因',
          prop: 'verifyReason',
          minWidth: '120'
        },
        {
          label: '主要技术变化',
          prop: 'technologyChange',
          minWidth: '120'
        },
        {
          label: '确认时间',
          prop: 'confirmDate',
          minWidth: '100'
        },
        {
          label: '创建时间',
          prop: 'createTime',
          minWidth: '100'
        },
        {
          dataType: 'action',
          minWidth: '60',
          label: '操作',
          operation: [
            {
              name: '详情',
              type: 'text',
              clickFun: (row) => {
                this.openFormDia('view', row);
              },
            },
            {
              name: '编辑',
              type: 'text',
              clickFun: (row) => {
                this.openFormDia('edit', row);
              },
            },
            {
              name: '导出',
              type: 'text',
              clickFun: (row) => {
                this.downLoadPost(row);
              },
            },
            {
              name: '删除',
              type: 'text',
              color: 'var(--el-color-danger)',
              clickFun: (row) => {
                this.deleteRow(row);
              },
            }
          ]
        }
      ],
      tableData: [],
      tableLoading: false,
      page: {
        size: 20,
        current: 1,
        total: 0
      },
      formDIa: false,
      operationType: '',
    };
  },
  mounted() {
    this.searchList()
  },
  methods: {
    // æŸ¥è¯¢åˆ—表
    searchList() {
      const entity = {
        standardName: this.searchForm.standardName,
        operationType: this.searchForm.operationType,
      }
      const page = this.page
      this.tableLoading = true
      pagesMethodVerify({ ...page, ...entity }).then(res => {
        this.tableLoading = false
        if (res.code === 200) {
          this.tableData = res.data.records
          this.page.total = res.data.total
        }
      }).catch(err => {
        console.log('err---', err);
        this.tableLoading = false
      })
    },
    // åˆ é™¤
    deleteRow(row) {
      this.$confirm('此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.tableLoading = true
        delMethodVerify({ methodVerifyId: row.methodVerifyId }).then(res => {
          this.tableLoading = false
          if (res.code === 200) {
            this.$message.success('删除成功')
            this.searchList()
          }
        }).catch(err => {
          this.tableLoading = false
          console.log('err---', err);
        })
      })
    },
    // é‡ç½®æŸ¥è¯¢æ¡ä»¶
    resetSearchForm() {
      this.searchForm.standardName = '';
      this.searchList()
    },
    openFormDia(type, row) {
      this.formDIa = true
      this.operationType = type
      this.$nextTick(() => {
        this.$refs.formDIa.openDia(row)
      })
    },
    // å¯¼å‡º
    downLoadPost(row) {
      exportMethodVerify({ methodVerifyId: row.methodVerifyId }).then(res => {
        const blob = new Blob([res], { type: 'application/msword' });
        this.$download.saveAs(blob, '标准方法变更记录.docx')
      })
    },
    // å…³é—­å¼¹æ¡†
    closeDia() {
      this.formDIa = false
      this.searchList()
    },
    // åˆ†é¡µåˆ‡æ¢
    pagination(page) {
      this.page.size = page.limit
      this.searchList();
    },
  }
};
</script>
<style scoped>
.search {
  height: 46px;
  display: flex;
  justify-content: space-between;
}
</style>
src/views/CNAS/process/method/standardNoveltyRetrieval/index.vue
@@ -24,12 +24,8 @@
      </div>
      <div style="line-height: 30px;" v-if="tabIndex === '0'">
        <el-button size="small" type="primary" @click="openApprovalDialog1">建 æ¡£</el-button>
        <el-upload ref='upload' :action="action" :before-upload="beforeUpload" :headers="uploadHeader" :on-error="onError"
                   :on-success="handleSuccessUp" :show-file-list="false"
                   accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
                   style="display: inline-block; margin-left: 10px;">
          <el-button :loading="upLoading" size="small" type="primary">导入</el-button>
        </el-upload>
        <el-button size="small" type="primary" style="margin-left: 10px;"
                   @click="$refs.importDialog.open()">导入</el-button>
        <el-button size="small" style="margin-left: 10px;" type="primary" @click="openFormDia">新 å¢ž</el-button>
      </div>
    </div>
@@ -88,12 +84,28 @@
                   @pagination="pagination1">
        </limsTable>
      </el-dialog>
      <import-dialog
        ref="importDialog"
        title="导入标准查新"
        :action="action"
        accept=".xls,.xlsx"
        template-url="/import-templates/标准查新导入模板.xlsx"
        template-name="标准查新导入模板.xlsx"
        :tips="[
          '表头为两行,请从第 3 è¡Œå¼€å§‹å¡«å†™æ•°æ®',
          '标准名称、标准号请填写现行标准的名称和编号',
          '查新来源按“标准网 â†’ å§”托情报所 â†’ æ ‡å‡†ä¹¦åº— â†’ å…¶ä»–”的顺序取第一个非空项',
          '备注列只能填写“替换”或“作废”,留空表示无备注'
        ]"
        @success="searchList"
      />
    </div>
  </div>
</template>
<script>
import limsTable from '@/components/Table/lims-table.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
import FormDialog from './component/FormDialog.vue';
import EditFormDia from './component/EditFormDia.vue';
import {
@@ -107,6 +119,7 @@
  components: {
    FormDialog,
    limsTable,
    ImportDialog,
    EditFormDia
  },
  data() {
@@ -483,7 +496,6 @@
      ],
      formDialog: false,
      editFormDialog: false,
      upLoading: false,
      outLoading: false,
      ratifyInfo: {
        writeUserId: '',
@@ -657,28 +669,6 @@
        const blob = new Blob([res], { type: 'application/octet-stream' });
        this.$download.saveAs(blob, '标准查新导出.xlsx')
      })
    },
    // å¯¼å…¥
    beforeUpload(file) {
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
        return false;
      } else {
        this.upLoading = true;
        return true;
      }
    },
    onError(err, file, fileList) {
      this.$message.error('上传失败')
      this.$refs.upload.clearFiles()
    },
    handleSuccessUp(response) {
      this.upLoading = false;
      if (response.code == 200) {
        this.$message.success('上传成功');
        this.searchList()
      }
    },
    // åˆ†é¡µ
    pagination(page) {
src/views/CNAS/resourceDemand/device/component/deviceOverview.vue
@@ -89,7 +89,7 @@
        <el-button link type="primary" @click="downloadTemplate">下载导入模板</el-button>
      </div>
      <el-upload ref="deviceUpload" drag name="file" accept=".xlsx" :action="importAction"
                 :headers="uploadHeader" :auto-upload="false" :limit="1" :file-list="importFiles"
                 :headers="uploadHeader" :auto-upload="false" :limit="1" v-model:file-list="importFiles"
                 :before-upload="beforeImport" :on-success="importSuccess" :on-error="importError">
        <lims-icon name="el-icon-upload"></lims-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
@@ -160,7 +160,7 @@
      }).catch(() => { })
    },
    submitImport() {
      if (!this.$refs.deviceUpload.uploadFiles.length) {
      if (!this.importFiles || this.importFiles.length === 0) {
        this.$message.warning('请选择需要导入的Excel文件')
        return
      }
src/views/CNAS/systemManagement/documentControl/components/FileList.vue
@@ -21,10 +21,7 @@
        </el-form>
      </div>
      <div>
        <el-upload :action="action" :multiple="false" accept='.xls,.xlsx' :headers="uploadHeader"
                   :on-change="beforeUpload" :on-error="onError" ref='upload' :on-success="handleSuccessUp"
                   :show-file-list="false">
          <el-button size="small" type="primary" :loading="upLoading">导入</el-button></el-upload>
        <el-button size="small" type="primary" @click="$refs.importDialog.open()">导入</el-button>
      </div>
    </div>
    <div class="table">
@@ -84,6 +81,16 @@
        <el-button :loading="uploading" type="primary" @click="submitProduct('currentInfoForm')">ç¡® è®¤</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入文件清单"
      :action="action"
      accept=".xls,.xlsx"
      template-url="/import-templates/文件清单导入模板.xlsx?v=2"
      template-name="文件清单导入模板.xlsx"
      :tips="['表头必须与模板完全一致,不能增删列或修改列名', '生效日期格式:YYYY-MM-DD']"
      @success="refreshTable"
    />
  </div>
</template>
@@ -91,6 +98,7 @@
import UpPdfStamp from '@/components/UpPdfStamp/index.vue'
import filePreview from '@/components/Preview/filePreview.vue'
import limsTable from "@/components/Table/lims-table.vue";
import ImportDialog from '@/components/ImportDialog/index.vue'
import {
  uploadFileManageDocumentList,
  pageManageDocumentList,
@@ -102,6 +110,7 @@
    UpPdfStamp,
    filePreview,
    limsTable,
    ImportDialog,
  },
  data() {
    return {
src/views/CNAS/systemManagement/documentRecords/approvalRecord.vue
@@ -20,10 +20,7 @@
      </div>
      <div>
        <el-button size="small" type="primary" @click="openAdd('新增')">新增</el-button>
        <el-upload :action="action" :multiple="false" :show-file-list="false" accept='.doc,.docx'
                   :headers="uploadHeader" :on-change="beforeUpload" :on-error="onError" ref='upload'
                   :on-success="handleSuccessUp" style="display: inline-block;margin: 0 8px">
          <el-button type="primary" size="small">导入</el-button></el-upload>
        <el-button type="primary" size="small" style="margin: 0 8px" @click="$refs.importDialog.open()">导入</el-button>
        <el-button size="small" type="primary" @click="handleDown" :loading="outLoading">导出</el-button>
      </div>
    </div>
@@ -77,11 +74,22 @@
        <el-button :loading="uploading" type="primary" @click="submitProduct('addForm')">ç¡® è®¤</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入文件审批记录"
      :action="action"
      accept=".doc,.docx"
      template-url="/import-templates/文件审批记录导入模板.docx"
      template-name="文件审批记录导入模板.docx"
      :tips="['请勿删除表头或增删列,数据从第 2 è¡Œå¼€å§‹å¡«å†™', '编制/审核/批准填写系统用户姓名', '批准日期格式:YYYY-MM-DD']"
      @success="refreshTable"
    />
  </div>
</template>
<script>
import limsTable from "@/components/Table/lims-table.vue";
import ImportDialog from '@/components/ImportDialog/index.vue'
import {
  selectUserCondition,
} from "@/api/business/inspectionTask.js";
@@ -97,7 +105,8 @@
export default {
  name: 'ApprovalRecord',
  components: {
    limsTable
    limsTable,
    ImportDialog
  },
  data() {
    return {
src/views/CNAS/systemManagement/documentRecords/cancellationRecord.vue
@@ -20,10 +20,7 @@
      </div>
      <div>
        <el-button size="small" type="primary" @click="openAdd">新增</el-button>
        <el-upload :action="action" :multiple="false" :show-file-list="false" accept='.doc,.docx'
                   :headers="uploadHeader" :on-change="beforeUpload" :on-error="onError" ref='upload'
                   :on-success="handleSuccessUp" style="display:inline-block;margin: 0 8px;">
          <el-button type="primary" size="small">导入</el-button></el-upload>
        <el-button type="primary" size="small" style="margin: 0 8px" @click="$refs.importDialog.open()">导入</el-button>
        <el-button size="small" type="primary" @click="handleDown" :loading="outLoading">导出</el-button>
      </div>
    </div>
@@ -80,11 +77,22 @@
        <el-button type="primary" @click="handleAdd" :loading="addLoading">ç¡® å®š</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入文件作废记录"
      :action="action"
      accept=".doc,.docx"
      template-url="/import-templates/文件作废记录导入模板.docx"
      template-name="文件作废记录导入模板.docx"
      :tips="['请勿删除表头或增删列,数据从第 2 è¡Œå¼€å§‹å¡«å†™', '数量必须填写整数', '申请日期、批准日期格式:YYYY-MM-DD']"
      @success="refreshTable"
    />
  </div>
</template>
<script>
import limsTable from "@/components/Table/lims-table.vue";
import ImportDialog from '@/components/ImportDialog/index.vue'
import {
  selectUserCondition,
} from "@/api/business/inspectionTask.js";
@@ -102,7 +110,8 @@
export default {
  name: 'CancellationRecord',
  components: {
    limsTable
    limsTable,
    ImportDialog
  },
  data() {
    return {
src/views/CNAS/systemManagement/documentRecords/distributionCollectionRecord.vue
@@ -20,10 +20,7 @@
      </div>
      <div>
        <el-button size="small" type="primary" @click="openAdd">新增</el-button>
        <el-upload :action="action" :multiple="false" :show-file-list="false" accept='.doc,.docx'
                   :headers="uploadHeader" :on-change="beforeUpload" :on-error="onError" ref='upload'
                   :on-success="handleSuccessUp" style="display:inline-block;margin: 0 8px;">
          <el-button type="primary" size="small">导入</el-button></el-upload>
        <el-button type="primary" size="small" style="margin: 0 8px" @click="$refs.importDialog.open()">导入</el-button>
        <el-button size="small" type="primary" @click="handleDown" :loading="outLoading">导出</el-button>
      </div>
    </div>
@@ -124,11 +121,22 @@
        <el-button type="primary" @click="handleAdd" :loading="addLoading">ç¡® å®š</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入文件发放回收记录"
      :action="action"
      accept=".doc,.docx"
      template-url="/import-templates/文件发放回收记录导入模板.docx"
      template-name="文件发放回收记录导入模板.docx"
      :tips="['请勿删除表头或增删列,数据从第 3 è¡Œå¼€å§‹å¡«å†™', '接收人、签收人填写系统用户姓名', '接收日期、签收日期格式:YYYY-MM-DD']"
      @success="refreshTable"
    />
  </div>
</template>
<script>
import limsTable from "@/components/Table/lims-table.vue";
import ImportDialog from '@/components/ImportDialog/index.vue'
import {
  getYearAndMonthAndDays
} from '@/utils/date'
@@ -150,7 +158,8 @@
export default {
  name: 'DistributionCollectionRecord',
  components: {
    limsTable
    limsTable,
    ImportDialog
  },
  data() {
    return {
src/views/CNAS/systemManagement/documentRecords/outDocumenRecordt.vue
@@ -5,10 +5,7 @@
      <el-tab-pane label="填写" name="填写" style="height: 100%;">
        <div style="display: flex;align-items: center;justify-content: flex-end;margin-bottom: 10px">
          <el-button size="small" type="primary" @click="openAdd('新增')" style="margin-left: 20px;">新增</el-button>
          <el-upload :action="action" :multiple="false" :show-file-list="false" accept='.doc,.docx'
            :headers="uploadHeader" :on-change="beforeUpload" :on-error="onError" ref='upload'
            :on-success="handleSuccessUp" style="display:inline-block;margin-left: 20px;">
            <el-button size="small" type="primary" :loading="upLoading">导入</el-button></el-upload>
          <el-button size="small" type="primary" style="margin-left: 20px" @click="$refs.importDialog.open()">导入</el-button>
        </div>
        <lims-table :tableData="tableData0" :column="column0" :page="page0" :tableLoading="tableLoading0"
                    :height="'calc(100vh - 290px)'" @pagination="pagination0"></lims-table>
@@ -79,12 +76,23 @@
        <el-button :loading="uploading" type="primary" @click="submitProduct('addForm')">ç¡® è®¤</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入外来文件确认"
      :action="action"
      accept=".doc,.docx"
      template-url="/import-templates/外来文件确认导入模板.docx"
      template-name="外来文件确认导入模板.docx"
      :tips="['请勿删除表头或增删列,数据从第 2 è¡Œå¼€å§‹å¡«å†™', '生效日期、作废日期格式:YYYY-MM-DD']"
      @success="getList0"
    />
  </div>
</template>
<script>
import limsTable from "@/components/Table/lims-table.vue";
import filePreview from '@/components/Preview/filePreview.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
import {
  addManageRecordVerify,
  pageManageRecordVerify,
@@ -99,6 +107,7 @@
  components: {
    filePreview,
    limsTable,
    ImportDialog,
  },
  data() {
    return {
src/views/CNAS/systemManagement/documentRecords/regularReviewsRecord.vue
@@ -22,10 +22,7 @@
          </div>
          <div>
            <el-button size="small" type="primary" @click="openAdd" v-if="addPower">新增</el-button>
            <el-upload :action="action" :multiple="false" :show-file-list="false" accept='.doc,.docx'
                       :headers="uploadHeader" :on-change="beforeUpload" :on-error="onError" ref='upload' v-if="upPower"
                       :on-success="handleSuccessUp" style="display:inline-block;margin-left: 20px;">
              <el-button type="primary" size="small">导入</el-button></el-upload>
            <el-button v-if="upPower" type="primary" size="small" style="margin-left: 20px" @click="$refs.importDialog.open()">导入</el-button>
          </div>
        </div>
        <div class="table">
@@ -122,12 +119,23 @@
        <el-button type="primary" @click="submitCheck('通过')" :loading="checkLoading">通 è¿‡</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入文件定期审查记录"
      :action="action"
      accept=".doc,.docx"
      template-url="/import-templates/文件定期审查记录导入模板.docx"
      template-name="文件定期审查记录导入模板.docx"
      :tips="['请勿删除表头或增删列,数据从第 2 è¡Œå¼€å§‹å¡«å†™', '适宜性填写“适宜 / ä¸é€‚宜”或“√ / Ã—”']"
      @success="refreshTable"
    />
  </div>
</template>
<script>
import limsTable from "@/components/Table/lims-table.vue";
import filePreview from '@/components/Preview/filePreview.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
import {
  exportOutManageRecordIntervals,
  addManageRecordIntervals,
@@ -145,7 +153,8 @@
  name: 'RegularReviewsRecord',
  components: {
    limsTable,
    filePreview
    filePreview,
    ImportDialog
  },
  data() {
    return {
src/views/CNAS/systemManagement/measuresDealRisks/components/hazardIdentificationRiskAssessment.vue
@@ -6,10 +6,7 @@
        <el-button type="primary" size="small" @click="addFun">新 å¢ž</el-button>
        <el-button type="primary" size="small" @click="approvalFun">审 æ‰¹</el-button>
        <el-button type="primary" size="small" @click="approveFun">批 å‡†</el-button>
        <el-upload style="display: inline-block; padding: 0 6px" :headers="uploadHeader" :action="action"
          :on-error="onError" :show-file-list="false" :on-success="onSuccess">
          <el-button size="small" type="primary">导 å…¥</el-button>
        </el-upload>
        <el-button size="small" type="primary" @click="$refs.importDialog.open()">导 å…¥</el-button>
        <el-button size="small" @click="openDownloadDia">导出</el-button>
      </div>
    </div>
@@ -121,6 +118,16 @@
        <el-button type="primary" @click="addApi" :loading="loading">ç¡® å®š</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入危险因素辨识与风险评价"
      :action="action"
      accept=".xls,.xlsx"
      template-url="/import-templates/危险因素辨识与风险评价导入模板.xlsx"
      template-name="危险因素辨识与风险评价导入模板.xlsx"
      :tips="['前两行为表头,数据从第 3 è¡Œå¼€å§‹å¡«å†™', '风险评价 L/E/C/D éœ€åˆ†åˆ«å¡«å†™']"
      @success="initData"
    />
  </div>
</template>
@@ -135,7 +142,9 @@
  exportHazardFactorIdentification,
} from '@/api/cnas/systemManagement/measuresDealRisks.js'
import { mapGetters } from "@/utils/store-compat";
import ImportDialog from '@/components/ImportDialog/index.vue'
export default {
  components: { ImportDialog },
  data() {
    return {
      dialogVisible: false,
src/views/CNAS/systemManagement/measuresDealRisks/components/listRiskAnalysisControlPlans.vue
@@ -6,10 +6,7 @@
        <el-button type="primary" size="small" @click="addFun">新 å¢ž</el-button>
        <el-button type="primary" size="small" @click="approvalFun">审 æ‰¹</el-button>
        <el-button type="primary" size="small" @click="approveFun">批 å‡†</el-button>
        <el-upload style="display: inline-block; padding: 0 6px" :action="action" :headers="uploadHeader"
          :on-error="onError" :show-file-list="false" :on-success="onSuccess">
          <el-button size="small" type="primary">导 å…¥</el-button>
        </el-upload>
        <el-button size="small" type="primary" @click="$refs.importDialog.open()">导 å…¥</el-button>
        <el-button size="small" @click="openDownloadDia">导出</el-button>
      </div>
    </div>
@@ -93,6 +90,16 @@
        <el-button type="primary" @click="addApi" :loading="loading">ç¡® å®š</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入重大风险因素分析及控制计划"
      :action="action"
      accept=".xls,.xlsx"
      template-url="/import-templates/重大风险因素分析及控制计划导入模板.xlsx"
      template-name="重大风险因素分析及控制计划导入模板.xlsx"
      :tips="['表头必须与模板一致,不能增删列或修改列名']"
      @success="initData"
    />
  </div>
</template>
@@ -106,7 +113,9 @@
  exportSignificantRiskFactors,
} from '@/api/cnas/systemManagement/measuresDealRisks.js'
import { mapGetters } from "@/utils/store-compat";
import ImportDialog from '@/components/ImportDialog/index.vue'
export default {
  components: { ImportDialog },
  data() {
    return {
      dialogVisible: false,
src/views/login.vue
@@ -92,6 +92,7 @@
          <el-input
            v-model="loginForm.password"
            type="password"
            show-password
            size="large"
            autocomplete="current-password"
            placeholder="请输入密码"
src/views/structural/capabilityAndLaboratory/capability/index.vue
@@ -9,14 +9,9 @@
          </el-radio-group>
        </el-col>
        <el-col :span="12" style="text-align: right;">
          <el-upload v-if="radio === 0" ref='upload1'
                     style="display: inline;margin-right: 8px"
                     :action="uploadAction1"
                     :before-upload="beforeUpload1" :headers="token" :on-error="onError1"
                     :on-success="handleSuccessUp1" :show-file-list="false" accept='.doc,.docx,.xls,.xlsx'>
            <el-button size="small" type="primary">导入</el-button>
          </el-upload>
          <el-button size="small" type="primary" v-if="radio === 1" @click="uploadDia = true">导入</el-button>
          <el-button v-if="radio === 0" size="small" type="primary" style="margin-right: 8px"
                     @click="$refs.importDialog0.open()">导入</el-button>
          <el-button size="small" type="primary" v-if="radio === 1" @click="$refs.importDialog1.open()">导入</el-button>
          <el-button size="small" type="primary" @click="openAdd()">新增</el-button>
        </el-col>
      </el-row>
@@ -92,22 +87,37 @@
        <el-button :loading="uploading" type="primary" @click="submitProduct">ç¡® è®¤</el-button>
      </span></template>
    </el-dialog>
    <el-dialog v-model="uploadDia" title="数据导入" width="500px">
      <div style="margin: 0 auto;">
        <el-upload ref="upload" :action="uploadAction"
                   :auto-upload="false" :file-list="fileList"
                   :headers="token" :limit="1"
                   :on-change="beforeUpload" :on-error="onError" :on-success="onSuccess" accept=".xlsx" drag
                   name="file">
          <lims-icon name="el-icon-upload"></lims-icon>
          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        </el-upload>
      </div>
      <template #footer><span  class="dialog-footer">
        <el-button @click="uploadDia = false">取 æ¶ˆ</el-button>
        <el-button :loading="uploading" type="primary" @click="submitUpload()">上 ä¼ </el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog0"
      title="导入检验项目参数"
      :action="uploadAction1"
      accept=".xls,.xlsx"
      template-url="/import-templates/检验项目参数导入模板.xlsx"
      template-name="检验项目参数导入模板.xlsx"
      :tips="[
        '每个数据行都必须填写:检验对象、检验项、检验项类型、检验值类型、特殊标识',
        '检验项类型只能填写“非采集类型”或“采集类型”',
        '检验值类型只能填写“数值”或“文本”',
        '特殊标识只能填写“是”或“否”',
        '多个产品/方法/条件/设备编号请用中文分号“;”分隔',
        '请删除多余的空白行,空白行会导致导入失败'
      ]"
      @success="refreshTable"
    />
    <import-dialog
      ref="importDialog1"
      title="导入检验对象"
      :action="uploadAction"
      accept=".xls,.xlsx"
      template-url="/import-templates/检验对象导入模板.xlsx"
      template-name="检验对象导入模板.xlsx"
      :tips="[
        '“场所”必须与系统中已有的场所名称完全一致,否则导入失败',
        '检测对象、产品名称必填',
        '同一检测对象有多个产品时,请填写多行'
      ]"
      @success="refreshTable"
    />
    <!--检验项目参数-编辑弹框-->
    <EditForm ref="editForm" @refreshList="refreshList1"></EditForm>
    <testObjectEditForm ref="testObjectEditForm" @refreshList="refreshList"></testObjectEditForm>
@@ -131,14 +141,14 @@
  selectTestObjectList, upProduct,
} from "@/api/structural/capability";
import limsTable from "@/components/Table/lims-table.vue";
import ImportDialog from "@/components/ImportDialog/index.vue";
import EditForm from "@/views/structural/capabilityAndLaboratory/capabilityComponents/EditForm.vue";
import testObjectEditForm from "@/views/structural/capabilityAndLaboratory/capabilityComponents/testObjectEditForm.vue";
import {getToken} from "@/utils/auth";
import { obtainItemParameterList } from '@/api/structural/laboratoryScope'
export default {
  name: 'Capability',
  components: {limsTable, EditForm, testObjectEditForm, BindPartDialog, BindSupplierDensityDialog},
  components: {limsTable, ImportDialog, EditForm, testObjectEditForm, BindPartDialog, BindSupplierDensityDialog},
  data() {
    return {
      uploadAction: import.meta.env.VITE_APP_BASE_API + '/capacityScope/importExcel',
@@ -374,9 +384,6 @@
      productRules: {
        name: [{ required: true, message: '请填写产品名称', trigger: 'blur' }]
      },
      uploadDia: false,
      fileList: [],
      token: {Authorization: "Bearer " + getToken()},
      uploading: false,
      currentRow: {}, // é€‰æ‹©é›¶ä»¶ç»‘定本条数据的信息
      currentSupplierDensityRow: {}, // é€‰æ‹©é›¶ä»¶ç»‘定本条数据的信息
@@ -396,63 +403,6 @@
    }
  },
  methods: {
    submitUpload() {
      if (this.$refs.upload.uploadFiles.length === 0) {
        this.$message.error('未选择文件')
        return
      }
      this.uploading = true
      this.$refs.upload.submit();
    },
    onSuccess(response, file, fileList) {
      this.$refs.upload.clearFiles()
      this.uploadDia = false
      this.uploading = false
      if (response.code !== 200) {
        this.$message.error(response.msg)
        return
      }
      this.$message.success('上传成功')
      this.productList = []
      this.refreshTable()
    },
    onError(err, file, fileList) {
      this.$message.error('上传失败')
      this.$refs.upload.clearFiles()
      this.uploading = false
    },
    beforeUpload(file, fileList) {
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
        return false;
      } else {
        return true;
      }
    },
    onError1(err, file, fileList) {
      this.$message.error('上传失败')
      this.$refs.upload1.clearFiles()
      this.uploading = false
    },
    beforeUpload1(file, fileList) {
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload1.clearFiles()
        return false;
      } else {
        return true;
      }
    },
    handleSuccessUp1 (response, file, fileList) {
      this.$refs.upload1.clearFiles()
      if (response.code !== 200) {
        this.$message.error(response.msg)
        return
      }
      this.$message.success('上传成功')
      this.refreshTable()
    },
    refreshList () {
      this.refreshTable()
    },