吴羡
昨天 e3686634452289d43e750a44f30f72b0e7754866
修复人员管理模块各标签页问题并完善设备、文件受控页面

- 人员管理:修复人员基本信息、培训计划、岗位职责、培训记录、任职授权记录、人员能力的查询、分页、联动、编辑权限、上传校验与错误提示
- 任职授权记录导出改用 JL04《检测人员上岗证》模板
- 设备保养计划、文件受控申请与记录页面功能完善
- PDF 盖章预览改用本地打包 pdf.js,去除公网 CDN 依赖
已添加1个文件
已修改24个文件
1983 ■■■■■ 文件已修改
index.html 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/import-templates/文件受控申请导入模板.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
src/api/cnas/personal/personJobResponsibilities.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/UpPdfStamp/index.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/components/Edit.vue 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/components/ViewRecord.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/components/mandateAdd.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/index.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/job-responsibilities.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/mandate.vue 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/personnel-information.vue 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/personnel-list.vue 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/personnel/personnelInfo/tabs/training-record.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/equipmentMaintenancePlan.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/record.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentControl/components/FileObsoletionRequest.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/cancellationRecord.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/distributionCollectionRecord.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/documentRecords/revisionRecord.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html
@@ -218,7 +218,6 @@
  <!-- Luckysheet å¿…须先于自动注入的 Vue åº”用脚本加载(classic script å…ˆäºŽ type=module æ‰§è¡Œï¼‰ -->
  <script src="/luckysheet/plugins/js/plugin.js"></script>
  <script src="/luckysheet/luckysheet.umd.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
  <script type="module" src="/src/main.js"></script>
</body>
public/import-templates/ÎļþÊÜ¿ØÉêÇëµ¼ÈëÄ£°å.xlsx
Binary files differ
src/api/cnas/personal/personJobResponsibilities.js
@@ -29,7 +29,7 @@
export function personJobResponsibilitiesDelete(query) {
  return request({
    url: "/personJobResponsibilities/personJobResponsibilitiesDelete",
    method: "delete ",
    method: "delete",
    params: query
  });
}
src/components/UpPdfStamp/index.vue
@@ -20,6 +20,7 @@
<script>
import jsPDF from 'jspdf'
import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?worker&url'
// åŽŸå®žçŽ°ç”¨ require("@/assets/stamps/" + name + ".png"),Vite é‡Œæ²¡æœ‰ require;
// ç”¨ import.meta.glob æŠŠå°ç« å›¾æ‰“成一张 åå­—→URL çš„表,效果等价。
@@ -97,10 +98,22 @@
        this.$message.error('请选择 PDF æ–‡ä»¶')
      }
    },
    loadPDF(typedArray) {
      pdfjsLib.getDocument(typedArray).promise.then(pdfDoc_ => {
        this.pdfDoc = pdfDoc_
        this.canvasNumPages = this.pdfDoc._pdfInfo.numPages
    async loadPDF(typedArray) {
      try {
        // ç”¨æœ¬åœ°æ‰“包的 pdf.js,不再依赖公网 CDN;内网加载不到会造成预览区只剩空白画布
        const pdfjsLib = await import('pdfjs-dist/build/pdf.mjs')
        pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl
        const loadingTask = pdfjsLib.getDocument({
          data: typedArray,
          useWasm: false,
          useSystemFonts: true,
          isEvalSupported: false
        })
        const pdfDoc_ = await loadingTask.promise
        // pdf.js 5 çš„类内部用了 # ç§æœ‰å­—段,被 Vue3 å“åº”式包成 Proxy åŽ getPage ä¼šæŠ›
        // "Cannot read private member ... from an object whose class did not declare it",必须 markRaw
        this.pdfDoc = markRaw(pdfDoc_)
        this.canvasNumPages = pdfDoc_.numPages
        this.stamps = []
        this.contextList = []
        for (let i = 1; i <= this.canvasNumPages; i++) {
@@ -109,11 +122,20 @@
          })
          this.stamps.push([])
        }
      })
      } catch (e) {
        console.error('[UpPdfStamp] PDF åŠ è½½å¤±è´¥', e)
        this.pdfDoc = null
        this.canvasNumPages = 0
        this.stamps = []
        this.contextList = []
        this.$message.error('文件预览失败,请检查文件是否损坏或稍后重试')
        this.$emit('uploadPDFErr')
      }
    },
    // æ¸²æŸ“指定页面
    renderPage(pageNum) {
      this.pdfDoc.getPage(pageNum).then(page => {
    async renderPage(pageNum) {
      try {
        const page = await this.pdfDoc.getPage(pageNum)
        const canvas = this.canvasEls[pageNum - 1]
        if (!canvas) return
        // åŽŸå®žçŽ°æ˜¯ push,位置取决于各页 getPage çš„ Promise å®Œæˆé¡ºåºï¼Œ
@@ -124,17 +146,18 @@
        canvas.width = viewport.width
        canvas.height = viewport.height
        page
          .render({
            canvasContext: this.contextList[pageNum - 1],
            viewport: viewport
          })
          .promise.then(() => {
            this.stamps[pageNum - 1].forEach(m => {
              this.drawStamps(m.x, m.y, pageNum - 1)
            })
          })
      })
        await page.render({
          canvasContext: this.contextList[pageNum - 1],
          viewport: viewport
        }).promise
        this.stamps[pageNum - 1].forEach(m => {
          this.drawStamps(m.x, m.y, pageNum - 1)
        })
      } catch (e) {
        console.error('[UpPdfStamp] PDF é¡µé¢æ¸²æŸ“失败', e)
        this.$message.error('文件预览渲染失败,请稍后重试')
        this.$emit('uploadPDFErr')
      }
    },
    // å•击--添加章
    handleCanvasClick(event, i) {
src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue
@@ -1,19 +1,20 @@
<template>
  <div>
    <el-dialog v-model="addTrainingPlanDia" title="新增培训计划" width="50%" @close="closeAdd">
    <el-dialog v-model="addTrainingPlanDia" :title="operationType === 'edit' ? '编辑培训计划' : '新增培训计划'" width="50%" @close="closeAdd">
      <div class="body">
        <el-form ref="trainingPlanForm" :model="trainingPlan" label-position="right" label-width="90px" :rules="trainingPlanRules">
          <el-row>
            <el-col :span="12">
              <el-form-item label="培训日期:" prop="trainingDate">
                <el-date-picker v-model="trainingPlan.trainingDate" format="YYYY-MM"
                                placeholder="选择日期" size="small" value-format="YYYY-MM"
                                type="month" style="width: 100%"></el-date-picker>
                <el-date-picker v-model="trainingPlan.trainingDate" format="YYYY-MM-DD"
                                placeholder="选择日期" size="small" value-format="YYYY-MM-DD"
                                type="date" style="width: 100%"></el-date-picker>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="课时:" prop="classHour">
                <el-input type="number" v-model="trainingPlan.classHour" label="描述文字" size="small"></el-input>
                <el-input-number v-model="trainingPlan.classHour" :min="0" :max="9999" :precision="0"
                                 controls-position="right" size="small" style="width: 100%"></el-input-number>
              </el-form-item>
            </el-col>
            <el-col :span="12">
@@ -101,11 +102,11 @@
        planId: '',
        id: '',
        trainingDate: '',
        classHour: '',
        classHour: null,
        trainingObjectives: '',
        participants: '',
        trainingContent: '',
        trainingLecturerId: '',
        trainingLecturerId: null,
        trainingLecturerName: '',
        trainingMode: '',
        holdingDepartment: '',
@@ -126,17 +127,22 @@
      this.operationType = type
      if (this.operationType === 'edit') {
        this.trainingPlan = this.HaveJson(row)
        // åŽ†å²æ•°æ®å¯èƒ½åªå­˜äº†å¹´æœˆï¼Œè¡¥é½åˆ°å½“æœˆ1日,避免日期控件无法回显
        if (this.trainingPlan.trainingDate && /^\d{4}-\d{2}$/.test(this.trainingPlan.trainingDate)) {
          this.trainingPlan.trainingDate += '-01'
        }
        this.lecturerType = row.trainingLecturerId ? 'internal' : (row.trainingLecturerName ? 'external' : 'internal')
      } else {
        this.lecturerType = 'internal'
        this.trainingPlan = {
          id: '',
          trainingDate: '',
          classHour: '',
          classHour: null,
          trainingObjectives: '',
          participants: '',
          trainingContent: '',
          trainingLecturerId: '',
          trainingLecturerId: null,
          trainingLecturerName: '',
          trainingMode: '',
          holdingDepartment: '',
          remarks: '',
@@ -144,6 +150,9 @@
      }
      this.trainingPlan.planId = id
      this.getUserList()
      this.$nextTick(() => {
        this.$refs.trainingPlanForm && this.$refs.trainingPlanForm.clearValidate()
      })
    },
    handleLecturerTypeChange(type) {
      if (type === 'internal') {
@@ -172,15 +181,20 @@
            this.trainingPlan.trainingLecturerName = null
          }
          this.submitAddLoading = true
          this.trainingPlan.planId = this.currentChangeRow.id
          if (!this.trainingPlan.planId) {
            this.trainingPlan.planId = this.currentChangeRow.id
          }
          const personTrainingDetailed = this.trainingPlan
          addOrUpdatePersonTrainingDetailed(personTrainingDetailed).then(res => {
            this.submitAddLoading = false
            if (res.code == 200) {
              this.$message.success('提交成功');
              this.closeAdd();
            } else {
              this.$message.error(res.msg || '提交失败')
            }
          }).catch(() => {
            this.$message.error('提交失败')
          }).finally(() => {
            this.submitAddLoading = false
          })
        }
@@ -188,16 +202,22 @@
    },
    // å…³é—­å¼¹æ¡†
    closeAdd() {
      this.$refs['trainingPlanForm'].resetFields();
      if (this.$refs['trainingPlanForm']) {
        this.$refs['trainingPlanForm'].resetFields();
      }
      this.$emit('search')
      this.addTrainingPlanDia = false;
    },
    // èŽ·å–è´Ÿè´£äººä¿¡æ¯æŽ¥å£
    // èŽ·å–è®²å¸ˆå€™é€‰äººï¼ˆå…¨ç³»ç»Ÿäººå‘˜ï¼Œä¸é™æœ¬éƒ¨é—¨ï¼‰
    getUserList() {
      selectUserCondition({type: 2}).then(res => {
      selectUserCondition({ isCustom: 0 }).then(res => {
        if (res.code == 200) {
          this.responsibleOptions = res.data
          this.responsibleOptions = Array.isArray(res.data) ? res.data : []
        } else {
          this.responsibleOptions = []
        }
      }).catch(() => {
        this.responsibleOptions = []
      })
    },
  },
src/views/CNAS/personnel/personnelInfo/components/Edit.vue
@@ -3,7 +3,7 @@
    <div class="page-header">
      <h4><span class="line"></span><span>培训与考核记录</span></h4>
      <div class="btns">
        <el-button size="small" type="primary" @click="submitForm(3)" v-if="currentRow.state !== 1">撤销</el-button>
        <el-button size="small" type="primary" @click="submitForm(3)" v-if="currentRow.state === 2">撤销</el-button>
        <el-button size="small" type="primary" @click="submitForm(0)" v-if="currentRow.state !== 1">提交</el-button>
        <el-button size="small" @click="$emit('goBack')">返回</el-button>
      </div>
@@ -226,8 +226,7 @@
        current:1
      },
      addUserTableInfo: {
        name: null,
        type: 2
        name: null
      },
      multipleSelection: [],
      userList: [],
@@ -240,39 +239,56 @@
    this.trainingForm = this.currentRow
    this.getInfo()
    this.getUserList()
    this.isDisabled = this.trainingForm.assessmentUserId !== this.userId
    this.isDisabled = this.trainingForm.assessmentUserId != this.userId
  },
  // æ–¹æ³•集合
  methods: {
    // èŽ·å–å½“å‰æ•°æ®
    async getInfo() {
    getInfo() {
      this.trainingLoading = true
      await trainingAndAssessmentRecordsPage({
      trainingAndAssessmentRecordsPage({
        trainingDetailedId: this.currentRow.id,
        userName: this.userName
      }).then(res => {
        if (res.code === 200) {
          this.trainingTableData = res.data
          this.trainingTableData = res.data || []
        } else {
          this.trainingTableData = []
          this.$message.error(res.msg || '考核人员加载失败')
        }
      }).catch(() => {
        this.trainingTableData = []
      }).finally(() => {
        this.trainingLoading = false
      })
    },
    updatePersonResult(row) {
      outOfFocusPreservation(row).then(res => {
        this.$message.success("操作成功!")
        if (res.code == 200) {
          this.$message.success("操作成功!")
        } else {
          this.$message.error(res.msg || '考核结果保存失败')
        }
      }).catch(() => {
        this.$message.error('考核结果保存失败')
      })
    },
    addPerson() {
      this.isSelectedList = this.trainingTableData.map(item => item.userId)
      if (this.currentRow.state === 1) {
        this.$message.warning('培训已完成,不能新增人员')
        return
      }
      this.selectUserDia = true;
      this.multipleSelection = []
      this.selectUserList()
    },
    selectUserList () {
    selectUserList() {
      this.tableLoading1 = true
      selectUserCondition({...this.addUserTableInfo}).then(res => {
        this.tableLoading1 = false
        this.tableData1 = res.data
      }).catch(err => {
      selectUserCondition({ name: this.addUserTableInfo.name || '', isCustom: 0 }).then(res => {
        // å·²åœ¨åå•中的人员不再重复展示,避免重复添加
        const existed = new Set(this.trainingTableData.map(item => String(item.userId)))
        this.tableData1 = (res.data || []).filter(item => !existed.has(String(item.id)))
      }).catch(() => {}).finally(() => {
        this.tableLoading1 = false
      })
    },
@@ -281,7 +297,7 @@
      this.multipleSelection = val
    },
    selectUser() {
      let selects = this.multipleSelection;
      let selects = this.multipleSelection || [];
      if (selects.length == 0) {
        this.$message.error('未选择数据');
        return;
@@ -296,9 +312,16 @@
        list.push(obj)
      });
      newPersonnelAddedToTrainingRecords(list).then(res => {
        this.isSelectedList = []
        this.selectUserDia = false;
        this.getInfo()
        if (res.code == 200) {
          this.$message.success('添加成功')
          this.multipleSelection = []
          this.selectUserDia = false;
          this.getInfo()
        } else {
          this.$message.error(res.msg || '添加失败')
        }
      }).catch(() => {
        this.$message.error('添加失败')
      })
    },
    // æ‰¹é‡åˆ é™¤
@@ -307,7 +330,11 @@
    },
    batchDelete() {
      if (this.multipleSelection.length > 0) {
        let ids = this.multipleSelection.map(item => item.trainingRecordId).join(',')
        let ids = this.multipleSelection.map(item => item.trainingRecordId).filter(id => id != null).join(',')
        if (!ids) {
          this.$message.warning('所选人员缺少培训记录id,无法删除')
          return
        }
        this.$confirm('是否确认删除所选择的数据?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
@@ -316,37 +343,54 @@
          deleteTrainingAndAssessmentRecords({ids: ids}).then(res => {
            if (res.code == 200) {
              this.$message.success('删除成功');
              this.multipleSelection = []
              this.getInfo()
            } else {
              this.$message.error(res.msg || '删除失败')
            }
          }).catch(() => {
            this.$message.error('删除失败')
          });
        }).catch(() => {
          this.$message.warning('取消删除');
        });
        }).catch(() => {});
      } else {
        this.$message.warning('请选择需要删除的数据')
      }
    },
    handleSizeChange(val) {
      this.search.size = val;
      this.getInfo();
    },
    handleCurrentChange(val) {
      this.search.current = val;
      this.getInfo();
    },
    /**
     * @desc æäº¤è¡¨å•
     * @desc æäº¤è¡¨å• status=3 ä¸ºæ’¤é”€ï¼Œå…¶ä»–为提交
     */
    async submitForm(status) {
      if (!this.trainingForm.id) {
        this.$message.warning('培训记录不存在,请返回后重新打开')
        return
      }
      let state = this.currentRow.state
      if (this.trainingForm.assessmentUserId) {
        state = 2
      }
      if (this.trainingForm.comprehensiveAssessment) {
        state = 1
      }
      if (status === 3) {
        if (this.trainingForm.assessmentUserId != this.userId) {
          this.$message.warning('您不是本次培训的评价人,无法撤销')
          return
        }
        state = 3
      } else if (state === 3) {
        if (!this.trainingForm.assessmentUserId) {
          this.$message.warning('请先选择评价人')
          return
        }
        state = 2
      } else if (state === 2) {
        if (this.trainingForm.assessmentUserId != this.userId) {
          this.$message.warning('您不是本次培训的评价人,无法提交评价')
          return
        }
        if (!this.trainingForm.comprehensiveAssessment) {
          this.$message.warning('请填写本次培训综合评价')
          return
        }
        if (!this.trainingForm.assessmentDate) {
          this.$message.warning('请选择评价时间')
          return
        }
        state = 1
      }
      let data = {
        assessmentMethod: this.trainingForm.assessmentMethod,
@@ -358,22 +402,28 @@
        assessmentDate: this.trainingForm.assessmentDate,
        state: state
      }
      let code = {}
      if (state === 2) {
        code = await trainingAndAssessmentRecordsAdded(data)
      } else {
        code = await trainingAndAssessmentRecordsEvaluate(data)
      }
      this.isDisabled = this.trainingForm.assessmentUserId !== this.userId
      if(code.code === 200) {
        this.currentRow.state = state
        this.$message.success("操作成功")
      try {
        const res = state === 2
          ? await trainingAndAssessmentRecordsAdded(data)
          : await trainingAndAssessmentRecordsEvaluate(data)
        if (res.code === 200) {
          this.trainingForm.state = state
          this.currentRow.state = state
          this.isDisabled = this.trainingForm.assessmentUserId != this.userId
          this.$message.success("操作成功")
        } else {
          this.$message.error(res.msg || '操作失败')
        }
      } catch (e) {
        // é”™è¯¯æç¤ºç”±è¯·æ±‚拦截器统一处理
      }
    },
    getUserList(){
      selectUserCondition({ type: 2 }).then((res) => {
        this.userList = res.data;
      })
      selectUserCondition({ isCustom: 0 }).then((res) => {
        if (res.code == 200) {
          this.userList = res.data || []
        }
      }).catch(() => {})
    },
  }
};
src/views/CNAS/personnel/personnelInfo/components/ViewRecord.vue
@@ -99,13 +99,17 @@
    },
    // æŸ¥è¯¢é™„件列表
    searchTableList () {
      if (!this.info || !this.info.id) {
        this.tableData = []
        return
      }
      this.tableLoading = true
      getTrainingDetailedFileList({trainingDetailedId: this.info.id}).then(res => {
        this.tableData = res.data || []
      }).catch(() => {
        this.tableData = []
      }).finally(() => {
        this.tableLoading = false
        this.tableData = res.data
      }).catch(err => {
        this.tableLoading = false
        console.log('err---', err);
      })
    },
    closeFilesLook () {
@@ -124,37 +128,43 @@
      }).then(() => {
        this.tableLoading = true
        delTrainingDetailedFileList({detailedFileId: row.detailedFileId}).then(res => {
          if (res.code == 200) {
            this.$message.success('删除成功')
            this.searchTableList()
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        }).finally(() => {
          this.tableLoading = false
          this.$message.success('删除成功')
          this.searchTableList()
        }).catch(err => {
          this.tableLoading = false
          console.log('err---', err);
        })
      }).catch(() => {})
    },
    // ä¸Šä¼ éªŒè¯
    fileBeforeUpload(file) {
      let flag = true
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
        flag = false
        return Promise.reject()
      }
      if (!flag) {
        return Promise.reject(flag); //正确的终止
      if (!/\.(jpg|jpeg|png|gif|doc|docx|xls|xlsx|ppt|pptx|pdf|zip|rar)$/i.test(file.name || '')) {
        this.$message.error('不支持的文件格式')
        this.$refs.upload.clearFiles()
        return Promise.reject()
      }
    },
    onError(err, file, fileList,type) {
      this.$message.error('上传失败')
      this.$refs.upload.clearFiles()
      this.$refs.upload && this.$refs.upload.clearFiles()
    },
    handleSuccessUp(response, ) {
      this.upLoading = false;
      if (response.code == 200) {
        this.$message.success('上传成功');
        this.searchTableList()
    handleSuccessUp(response) {
      if (!response || response.code != 200) {
        this.$message.error((response && (response.msg || response.message)) || '上传失败')
        return
      }
      this.$message.success('上传成功');
      this.searchTableList()
    },
    // æŸ¥çœ‹æ–‡ä»¶
    handleLook(row){
src/views/CNAS/personnel/personnelInfo/components/mandateAdd.vue
@@ -1,7 +1,7 @@
<template>
  <div class="add">
    <el-dialog
      :title="isEdit ?'编辑附件资料':'新增附件资料'"
      :title="isEdit ? '编辑任职授权记录' : '新增任职授权记录'"
      v-model="dialogVisible"
      width="50%"
    >
@@ -51,6 +51,8 @@
            <el-upload ref="upload" style="float: left; margin: 0 12px 0 20px;" :action="action"
                       :headers="uploadHeader"
                       :show-file-list="false"
                       accept=".doc,.docx,.pdf,.jpg,.jpeg,.png"
                       :before-upload="beforeUpload"
                       :on-success="onSuccess">
              <template #trigger><el-button class="uploadFile"  size="small" type="primary">上传</el-button></template>
            </el-upload>
@@ -93,8 +95,8 @@
      rules: {
        certificateNumber: [{ required: true, message: '请输入证书编号', trigger: 'blur' }],
        userId: [{ required: true, message: '请选择被任职人员', trigger: 'change' }],
        post: [{ required: true, message: '任职岗位', trigger: 'blur' }],
        operationType: [{ required: true, message: '操作类型', trigger: 'blur' }],
        post: [{ required: true, message: '请输入任职岗位', trigger: 'blur' }],
        operationType: [{ required: true, message: '请输入操作类型', trigger: 'blur' }],
      },
      loading: false,
      userList: []
@@ -107,19 +109,61 @@
  },
  methods: {
    onSuccess(response, file, fileList) {
      this.form["systemFileName"] = response.data
      if (response && response.code === 200 && response.data) {
        this.form.systemFileName = response.data
        this.form.fileName = file.name
        this.$message.success('上传成功')
      } else {
        this.$refs.upload && this.$refs.upload.clearFiles()
        this.$message.error((response && response.msg) || '上传失败')
      }
    },
    deleteFile(fileName) {
      deleteCNASFile({fileName: fileName}).then(res => {
        this.$message.success('删除成功!')
        this.form["systemFileName"] = null
      if (!fileName) {
        this.form.systemFileName = ''
        return
      }
      this.$confirm('是否删除已上传的上岗证书文件?', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteCNASFile({fileName: fileName}).then(res => {
          if (res.code === 200) {
            this.$message.success('删除成功!')
            this.form.systemFileName = ''
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        })
      }).catch(() => {})
    },
    resetData() {
      this.form = {
        id: undefined,
        certificateNumber: undefined,
        userId: undefined,
        post: undefined,
        operationType: undefined,
        fileList: [],
        fileName: undefined,
        systemFileName: undefined,
        remarks: undefined,
        num2: undefined,
        num1: undefined
      }
      this.$nextTick(() => {
        if (this.$refs.ruleForm) this.$refs.ruleForm.clearValidate()
        if (this.$refs.upload) this.$refs.upload.clearFiles()
      })
    },
    async openDialog(row, type) {
      await this.getUserList()
      this.dialogVisible = true
      if(type) {
        this.isEdit = true
      this.isEdit = !!type
      this.resetData()
      if (type) {
        this.form.id = row.id
        this.form.certificateNumber = row.certificateNumber
        this.form.userId = Number(row.userId)
@@ -129,12 +173,10 @@
        this.form.remarks = row.remarks
        this.form.num1 = row.num1
        this.form.num2 = row.num2
      } else {
        this.resetForm('ruleForm')
      }
      this.dialogVisible = true
    },
    closeDialog() {
      this.resetForm('ruleForm')
      this.dialogVisible = false
    },
    /**
@@ -142,7 +184,7 @@
     */
    async getUserList() {
      try {
        const res = await selectUserCondition()
        const res = await selectUserCondition({ isCustom: 0 })
        if (res.code !== 200) throw new Error('人员列表接口返回失败')
        this.userList = Array.isArray(res.data) ? res.data : []
      } catch (error) {
@@ -169,10 +211,16 @@
          }
          this.loading = true
          addOrUpdatePersonPostAuthorizationRecord(params).then(res => {
            this.loading = false
            this.closeDialog()
            this.$emit('refresh')
          }).catch(err => {
            if (res.code === 200) {
              this.$message.success(this.isEdit ? '修改成功' : '新增成功')
              this.closeDialog()
              this.$emit('refresh')
            } else {
              this.$message.error(res.msg || '保存失败')
            }
          }).catch(() => {
            this.$message.error('保存失败')
          }).finally(() => {
            this.loading = false
          })
        }
@@ -184,13 +232,16 @@
    beforeUpload(file) {
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
        return false;
      } else {
        this.form.fileList = [file]
        this.form.fileName = file.name
        return true;
      }
      const ext = (file.name.split('.').pop() || '').toLowerCase()
      if (!['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png'].includes(ext)) {
        this.$message.error('仅支持上传 doc、docx、pdf、jpg、png æ ¼å¼çš„æ–‡ä»¶');
        return false;
      }
      this.form.fileList = [file]
      this.form.fileName = file.name
      return true;
    },
  }
}
src/views/CNAS/personnel/personnelInfo/index.vue
@@ -22,7 +22,7 @@
      <el-tree
        ref="tree"
        :data="list"
        :default-expanded-keys="[22]"
        :default-expanded-keys="expandedKeys"
        :expand-on-click-node="false"
        :filter-node-method="filterNode"
        :props="{ children: 'children', label: 'name' }"
@@ -38,7 +38,7 @@
                class="node_i" :name="data.children != undefined&&data.children.length>0 ? 'el-icon-folder-opened' : 'el-icon-tickets'"></lims-icon>
                {{ data.name }}</span>
            </el-col>
            <el-col v-if="node.level>1 && data.id !== null" :span="2" style="text-align: right;">
            <el-col v-if="node.level>1 && data.id != null" :span="2" style="text-align: right;">
              <el-button size="small" link type="primary" @click.stop="remove(node, data)">
                <lims-icon name="el-icon-delete"></lims-icon>
              </el-button>
@@ -53,7 +53,8 @@
          <PersonnelList v-if="activeName === '人员基本信息' && isShowAll" ref="personnelList" :currentCompaniesList="currentCompaniesList"
                         :departId="departId" @refreshTree="refreshTree"
                         @updatePerson="updatePerson"></PersonnelList>
          <personnelInformation v-if="activeName === '人员基本信息' && !isShowAll" :clickNodeVal="clickNodeVal"></personnelInformation>
          <personnelInformation v-if="activeName === '人员基本信息' && !isShowAll" :clickNodeVal="clickNodeVal"
                               @refreshTree="refreshTree"></personnelInformation>
        </el-tab-pane>
        <el-tab-pane label="培训计划" name="培训计划">
          <PersonnelTraining v-if="activeName === '培训计划'" ref="personnelTraining"
@@ -75,6 +76,7 @@
        <el-tab-pane label="人员能力" name="人员能力">
          <personnel-capacity v-if="activeName === '人员能力'" ref="personnelCapacity"
                              :departId="departId"
                              :personName="isDepartment ? '' : (clickNodeVal.name || '')"
                              :isDepartment="isDepartment"></personnel-capacity>
        </el-tab-pane>
      </el-tabs>
@@ -118,6 +120,7 @@
      activeName: '人员基本信息',
      departId: 1,
      list: [],
      expandedKeys: [],
      addDia: false,
      addOb: {
        name: '',
@@ -147,28 +150,38 @@
  },
  methods: {
    remove(node, data) {
      this.$confirm("是否删除该层级", "提示", {
        type: "error"
      this.$confirm(`确定删除架构“${data.name}”吗?<br/>该架构下不能存在下级架构或人员,否则无法删除。`, "提示", {
        type: "warning",
        dangerouslyUseHTMLString: true
      }).then(() => {
        delDepartmentLims({
          id: data.id
        }).then(res => {
          this.$message.success('已删除')
          this.geList();
          if (res.code == 200) {
            this.$message.success('已删除')
            if (this.addOb.fatherId === data.id) {
              this.addOb.fatherId = ''
            }
            this.geList();
          }
        })
      }).catch(e => {})
    },
    addStandardTree() {
      if (this.addOb.name == null || this.addOb.factory == '') {
        this.$message.error('构架名称是必填项')
      if (!this.addOb.name || String(this.addOb.name).trim() === '') {
        this.$message.error('架构名称是必填项')
        return
      }
      addDepartmentLims(this.addOb).then(res => {
        this.$message.success('添加成功')
        this.addDia = false
        this.geList();
        this.addOb.name = ''
        this.addOb.fatherId = ''
      addDepartmentLims({
        name: String(this.addOb.name).trim(),
        fatherId: this.addOb.fatherId
      }).then(res => {
        if (res.code == 200) {
          this.$message.success('添加成功')
          this.addDia = false
          this.addOb.name = ''
          this.geList();
        }
      })
    },
    handleAdd() {
@@ -180,24 +193,24 @@
    },
    // äººå‘˜åˆ—表编辑
    updatePerson(row) {
      const node = this.findNodeById(this.list, row.name);
      const node = this.findNodeByUserId(this.list, row.userId);
      if (node) {
        this.handleNodeClick(node);
      } else {
        this.$message.warning('未找到该人员');
        this.$message.warning('未找到该人员,请刷新左侧人员树后重试');
      }
    },
    // æ–°å»ºäººå‘˜åŽåˆ·æ–°æ ‘
    // æ–°å»º/编辑人员后刷新树
    refreshTree () {
      this.geList()
    },
    findNodeById(treeData, name) {
    findNodeByUserId(treeData, userId) {
      for (let i = 0; i < treeData.length; i++) {
        if (treeData[i].name === name) {
        if (treeData[i].userId !== undefined && treeData[i].userId !== null && treeData[i].userId == userId) {
          return treeData[i]; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
        }
        if (treeData[i].children && treeData[i].children.length > 0) {
          const foundNode = this.findNodeById(treeData[i].children, name);
          const foundNode = this.findNodeByUserId(treeData[i].children, userId);
          if (foundNode) {
            return foundNode; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
          }
@@ -211,10 +224,14 @@
    // èŽ·å–æ ‘
    geList() {
      selectCNSAPersonTree().then(res => {
        this.list = res.data;
        if(this.list.length > 0) {
        this.list = res.data || [];
        // åˆ·æ–°æ ‘时不要把人视角强行切回部门视角,否则其他标签页会拿人员id当部门id查询
        if (this.list.length > 0 && !(this.clickNodeVal && this.clickNodeVal.userId != null)) {
          this.isDepartment = true;
        }
        this.expandedKeys = this.list.filter(item => item.id != null).map(item => item.id);
      }).catch(() => {
        this.list = []
      });
    },
    filterNode(value, data) {
@@ -230,24 +247,32 @@
        this.clickNodeVal.level = node.level;
        this.clickNodeVal.parent = node.parent.data;
      }
      this.entity.departLimsId = val.id;
      this.addOb.fatherId = val.id;
      // æ˜¯å¦æ˜¾ç¤ºè®¾å¤‡è¯¦æƒ…
      this.isShowAll = val.id !== null;
      if (val.id) {    // å¦‚果是实验室
        this.departId = val.id;
        this.isDepartment = true;
      }
      if (val.userId) { // æ˜¯äººå‘˜
      if (val.userId !== undefined && val.userId !== null) {
        // æ˜¯äººå‘˜ï¼Œå±•示人员详情
        this.isShowAll = false;
        this.departId = val.userId;
        this.isDepartment = false
        this.isDepartment = false;
        this.addOb.fatherId = '';
      } else {
        // æ˜¯å®žéªŒå®¤/部门
        this.isShowAll = true;
        this.addOb.fatherId = val.id;
        if (val.id) {
          this.departId = val.id;
          this.isDepartment = true;
        }
      }
    },
    getNodeParent(val) {
      if (val.parent != null) {
      this.currentCompaniesList = [];
      this.buildNodeParent(val);
    },
    buildNodeParent(val) {
      if (val.parent != null && val.data && val.data.id != null) {
        this.currentCompaniesList[val.level - 1] = val.data.id;
        this.selectTree += ' - ' + val.label;
        this.getNodeParent(val.parent);
      }
      if (val.parent != null) {
        this.buildNodeParent(val.parent);
      }
    }
  }
src/views/CNAS/personnel/personnelInfo/tabs/job-responsibilities.vue
@@ -26,6 +26,12 @@
        <el-table-column label="所属部门" min-width="180" prop="departLimsName"></el-table-column>
        <el-table-column label="工作目标" min-width="180" prop="jobObjective"></el-table-column>
        <el-table-column label="岗位职责" min-width="180" prop="jobResponsibilities"></el-table-column>
        <el-table-column label="当前状态" min-width="120" prop="currentState">
          <template v-slot="scope">
            <el-tag v-if="scope.row.currentState" :type="stateTagType(scope.row.currentState)">{{ scope.row.currentState }}</el-tag>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <el-table-column label="任职人" min-width="180" prop="incumbentName"></el-table-column>
        <el-table-column label="任职人审核日期" min-width="180" prop="incumbentDate"></el-table-column>
        <el-table-column label="主管" min-width="180" prop="supervisorName"></el-table-column>
@@ -35,7 +41,8 @@
            <el-button v-if="!isDepartment || scope.row.currentState === '关闭'" size="small" link type="primary"
              @click="handleViewClick(scope.row, 'view')">查看
            </el-button>
            <el-button v-if="isDepartment && scope.row.currentState !== '关闭'" size="small" link type="primary"
            <el-button v-if="scope.row.currentState !== '关闭' && (isDepartment || scope.row.incumbentId == departId)"
              size="small" link type="primary"
              @click="handleViewClick(scope.row, 'edit')">编辑
            </el-button>
            <el-button size="small" link type="primary" @click="downLoadPost(scope.row)">导出</el-button>
@@ -44,13 +51,15 @@
          </template>
        </el-table-column>
      </el-table>
      <el-pagination :current-page="1" :page-size="search.size" :page-sizes="[10, 20, 30, 50, 100]"
        :total="search.total" layout="->,total, sizes, prev, pager, next, jumper" background style="margin-top: 10px"
      <el-pagination v-model:current-page="search.current" v-model:page-size="search.size"
        :page-sizes="[10, 20, 30, 50, 100]" :total="search.total"
        layout="->,total, sizes, prev, pager, next, jumper" background style="margin-top: 10px"
        @size-change="handleSizeChange" @current-change="handleCurrentChange">
      </el-pagination>
    </div>
    <!-- æ–°å¢žå²—位职责 -->
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible" title="新增岗位职责"
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible"
      :title="operationType === 'view' ? '查看岗位职责' : (operationType === 'edit' ? '编辑岗位职责' : '新增岗位职责')"
      width="50%" @close="resetForm">
      <el-steps :active="currentStep" align-center finish-status="success">
        <el-step v-for="(v, i) in steps" :key="i" :title="v" style="cursor:pointer"
@@ -82,7 +91,7 @@
              <el-col v-if="currentStep === 0 || operationType === 'view'" :span="12">
                <el-form-item :rules="[{ required: currentStep === 0, message: '请选择任职人', trigger: 'change' }]"
                  label="任职人:" prop="incumbentId">
                  <el-select v-model="form.incumbentId" :disabled="operationType === 'view'" clearable filterable
                  <el-select v-model="form.incumbentId" :disabled="operationType === 'view' || !isDepartment" clearable filterable
                    placeholder="请选择任职人" size="small" style="width: 100%;">
                    <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
                    </el-option>
@@ -112,11 +121,13 @@
        </div>
      </el-form>
      <template #footer><span  class="dialog-footer">
        <el-button v-if="currentStep !== 0 && currentStep !== 3" @click="submitForm('3reject')">驳回</el-button>
        <el-button v-if="currentStep === 0" @click="submitForm('2save')">保存</el-button>
        <el-button v-if="currentStep !== 3" type="primary" @click="submitForm('1submit')">{{ currentStep === 0 ? '提交' :
          '通过'
          }}</el-button>
        <el-button v-if="operationType !== 'view' && currentStep !== 0 && currentStep !== 3"
          :loading="submitLoading" @click="submitForm('3reject')">驳回</el-button>
        <el-button v-if="operationType !== 'view' && currentStep === 0" :loading="submitLoading"
          @click="submitForm('2save')">保存</el-button>
        <el-button v-if="operationType !== 'view' && currentStep !== 3" type="primary" :loading="submitLoading"
          @click="submitForm('1submit')">{{ currentStep === 0 ? '提交' : '通过' }}</el-button>
        <el-button @click="dialogVisible = false">关 é—­</el-button>
      </span></template>
    </el-dialog>
  </div>
@@ -131,6 +142,7 @@
} from "@/api/cnas/personal/personJobResponsibilities";
import { selectUserCondition } from "@/api/cnas/resourceDemand/facilitiesEnvironment/facilitiesAndEnvironment";
import { mapGetters } from "@/utils/store-compat";
import { blobValidate } from "@/utils/ruoyi";
export default {
  data() {
@@ -145,6 +157,8 @@
        total: 0
      },
      dialogVisible: false,
      submitLoading: false,
      downLoading: false,
      currentStep: 0, // æ­¥éª¤æ¡æ˜¾ç¤ºç¬¬å‡ æ­¥
      currentStepClick: 0, // ç‚¹å‡»æ­¥éª¤æ¡å˜åŒ–
      operationType: '',
@@ -183,12 +197,16 @@
  },
  methods: {
    addPost() {
      this.dialogVisible = true;
      if (!this.isDepartment && !this.departId) {
        this.$message.warning('请先在左侧选择人员或部门')
        return
      }
      this.operationType = '';
      this.form = {
        postName: '', // å²—位名称
        jobObjective: '', // å·¥ä½œç›®æ ‡
        jobResponsibilities: '', // å²—位职责
        incumbentId: '', // ä»»èŒäºº
        incumbentId: this.isDepartment ? '' : this.departId, // äººå‘˜è§†è§’默认就是当前人员
        supervisorId: '', // æ“ä½œäºº
        submitPerson: '', // ä¸»ç®¡
        submitDate: '', // æ—¥æœŸ
@@ -196,6 +214,7 @@
      };
      this.currentStep = 0;
      this.getUserList();
      this.dialogVisible = true;
    },
    // æŸ¥è¯¢åˆ—表信息
    getPostList(userId) {
@@ -213,44 +232,48 @@
      }
      this.tableLoading = true
      personJobResponsibilitiesSelect(params).then(res => {
        this.tableLoading = false
        this.tableData = res.data.records;
        this.search.total = res.data.total;
      }).catch(err => {
        this.tableData = (res.data && res.data.records) || [];
        this.search.total = (res.data && res.data.total) || 0;
      }).catch(() => {
        this.tableData = []
        this.search.total = 0
      }).finally(() => {
        this.tableLoading = false
      })
    },
    stateTagType(state) {
      if (state === '关闭') return 'success'
      if (state === '提交') return 'info'
      return 'warning'
    },
    userNameById(id) {
      const item = this.responsibleOptions.find(opt => String(opt.id) === String(id))
      return item ? item.name : ''
    },
    //提交表单
    async submitForm(saveState) {
      if (this.submitLoading) {
        return
      }
      this.$refs.form.validate((valid) => {
        if (valid === true || saveState !== '1submit') {
          // ç»™å½“前环节设置创建人与时间
          let user = this.nickName;
          const dateTime = dateFormat(new Date());
          // ç»™å½“前环节设置操作人与时间(任职人/主管环节记录对应人员姓名)
          const operatorName = this.nickName;
          // æ³¨æ„ï¼šdateFormat å·¥å…·åªè¯†åˆ«å°å†™ hh,getHours() æœ¬èº«å³ 24 å°æ—¶åˆ¶
          const dateTime = dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss');
          // èŽ·å–å½“å‰çŽ¯èŠ‚æ“ä½œäººä¸Žæ—¥æœŸ
          switch (this.currentStep) {
            case 0:
              this.form.submittingOperator = user.name;
              this.form.submittingOperator = operatorName;
              this.form.submittingDate = dateTime;
              break;
            case 1:
              this.form.incumbentOperator = user.name;
              this.form.incumbentOperator = this.userNameById(this.form.incumbentId) || operatorName;
              this.form.incumbentDate = dateTime;
              break;
            case 2:
              this.form.supervisorOperator = user.name;
              this.form.supervisorOperator = this.userNameById(this.form.supervisorId) || operatorName;
              this.form.supervisorDate = dateTime;
              break;
            default:
              break;
          }
          // èŽ·å–å½“å‰çŽ¯èŠ‚è´Ÿè´£äºº
          switch (saveState === '3reject' ? this.currentStep - 1 : this.currentStep) {
            case 1:
              this.form.currentResponsible = this.form.submittingOperator;
              break;
            case 2:
              this.form.currentResponsible = this.form.incumbentOperator;
              break;
            default:
              break;
@@ -273,14 +296,42 @@
            default:
              break;
          }
          // å½“前环节负责人:0-提交人 1-任职人 2-主管 3-流程结束
          switch (currentStepAction) {
            case 0:
              this.form.currentResponsible = this.form.submittingOperator;
              break;
            case 1:
              this.form.currentResponsible = this.userNameById(this.form.incumbentId);
              break;
            case 2:
              this.form.currentResponsible = this.userNameById(this.form.supervisorId);
              break;
            default:
              this.form.currentResponsible = '';
              break;
          }
          // èŽ·å–å½“å‰çŠ¶æ€
          this.form.currentState = currentStepAction === 3 ? '关闭' : this.steps[currentStepAction];
          personJobResponsibilitiesSave(this.form).then(res => {
          // ç©ºå€¼è½¬ null,避免后端 Integer å­—段接收到空字符串导致提交失败
          const payload = { ...this.form };
          if (payload.supervisorId === '' || payload.supervisorId === undefined) payload.supervisorId = null;
          if (payload.incumbentId === '' || payload.incumbentId === undefined) payload.incumbentId = null;
          delete payload.submitPerson;
          delete payload.submitDate;
          this.submitLoading = true
          personJobResponsibilitiesSave(payload).then(res => {
            if (res.code == 200) {
              this.$message.success('提交成功');
              this.getPostList(this.departId);
              this.dialogVisible = false;
            } else {
              this.$message.error(res.msg || '提交失败')
            }
          }).catch(() => {
            this.$message.error('提交失败')
          }).finally(() => {
            this.submitLoading = false
          });
        } else {
          let step = this.steps[this.currentStep];
@@ -292,8 +343,9 @@
    handleViewClick(row, type) {
      this.operationType = type;
      this.getUserList();
      row.incumbentId = Number(row.incumbentId);
      this.form = { ...row };
      this.form.incumbentId = row.incumbentId != null ? Number(row.incumbentId) : null;
      this.form.supervisorId = row.supervisorId != null ? Number(row.supervisorId) : null;
      switch (row.currentState) {
        case '提交':
          this.currentStep = 0;
@@ -316,21 +368,30 @@
        default:
          break;
      }
      this.form.currentState = this.currentStep;
      this.currentStepClick = this.currentStep === 3 ? 0 : this.currentStep
      console.log('this.form---', this.form);
      this.dialogVisible = true;
    },
    // ä¸‹è½½å²—位职责
    downLoadPost(row) {
      exportPersonJobResponsibilities({ id: row.id }).then(res => {
      if (this.downLoading) {
        return
      }
      this.downLoading = true
      exportPersonJobResponsibilities({ id: row.id }).then(async res => {
        if (!blobValidate(res)) {
          const rspObj = JSON.parse(await res.text())
          this.$message.error(rspObj.msg || rspObj.message || '导出失败')
          return
        }
        const blob = new Blob([res], { type: 'application/octet-stream' });
        this.$download.saveAs(blob, row.incumbentName + '-岗位职责' + '.docx');
        this.$download.saveAs(blob, (row.incumbentName || row.postName || '岗位职责') + '-岗位职责' + '.docx');
      }).catch(() => {}).finally(() => {
        this.downLoading = false
      })
    },
    // åˆ é™¤å²—位职责
    deletePost(row) {
      this.$confirm('此操作将永久删除此数据, æ˜¯å¦ç»§ç»­?', '提示', {
      this.$confirm(`确定删除“${row.postName || '该岗位职责'}”吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
@@ -339,31 +400,46 @@
          if (res.code == 200) {
            this.$message.success('删除成功');
            this.getPostList(this.departId);
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        });
      }).catch(() => {
        this.$message.error('删除失败');
      });
      }).catch(() => {});
    },
    resetForm() {
      this.$refs.form.resetFields();
      if (this.$refs.form) {
        this.$refs.form.resetFields();
      }
      this.submitLoading = false
    },
    refreshTable() {
      this.search.current = 1;
      this.getPostList(this.departId);
    },
    // èŽ·å–è´Ÿè´£äººä¿¡æ¯æŽ¥å£
    // èŽ·å–è´Ÿè´£äººä¿¡æ¯æŽ¥å£ï¼ˆä»»èŒäººã€ä¸»ç®¡ä»Žå…¨å…¬å¸äººå‘˜ä¸­é€‰æ‹©ï¼‰
    getUserList() {
      selectUserCondition({type: 2}).then(res => {
      selectUserCondition({ isCustom: 0 }).then(res => {
        if (res.code == 200) {
          this.responsibleOptions = res.data
          this.responsibleOptions = Array.isArray(res.data) ? res.data : []
        } else {
          this.responsibleOptions = []
        }
      }).catch(() => {
        this.responsibleOptions = []
      })
    },
    choiceStep(index) {
      this.currentStepClick = index;
      // æŸ¥çœ‹æ—¶å¯åˆ‡æ¢æ­¥éª¤æŸ¥çœ‹å„环节信息,编辑时不允许跳步骤
      if (this.operationType === 'view') {
        this.currentStep = index
        this.currentStepClick = index;
      }
    },
    handleSizeChange(val) {
      this.search.size = val;
      this.search.current = 1;
      this.getPostList(this.departId);
    },
    handleCurrentChange(val) {
@@ -374,6 +450,7 @@
  watch: {
    departId: {
      handler(newId, oldId) {
        this.search.current = 1;
        this.getPostList(newId);
      }
    }
src/views/CNAS/personnel/personnelInfo/tabs/mandate.vue
@@ -1,9 +1,9 @@
<template>
  <div class="flex_column">
    <div>
      <div v-if="isDepartment" style="display: flex;justify-content: space-between;margin-bottom: 10px">
        <el-button size="small" type="primary" @click="getTableData">刷新</el-button>
        <el-button size="small" type="primary" :icon="$icon('el-icon-plus')" @click="openDialog">新增</el-button>
      <div style="display: flex;justify-content: space-between;margin-bottom: 10px">
        <el-button size="small" type="primary" @click="refreshTable">刷新</el-button>
        <el-button v-if="isDepartment" size="small" type="primary" :icon="$icon('el-icon-plus')" @click="openDialog">新增</el-button>
      </div>
      <lims-table :tableData="tableData" :column="columnData"
                  @pagination="page" :height="'calc(100vh - 18em)'"
@@ -19,7 +19,7 @@
  deletePersonPostAuthorizationRecord, exportPersonPostAuthorizationRecord,
  PersonPostAuthorizationRecordPage
} from "@/api/cnas/personal/personPostAuthorizationRecord";
import {delCustomById} from "@/api/system/customer";
import { blobValidate } from "@/utils/ruoyi";
export default {
  components: {
@@ -42,9 +42,6 @@
    return {
      columnData: [
        {
          label: '序号',
          prop: 'id'
        }, {
          label: '证书编号',
          prop: 'certificateNumber'
        }, {
@@ -103,7 +100,8 @@
        size: 20,
        total: 0
      },
      loading: false
      loading: false,
      downLoading: false
    }
  },
  mounted() {
@@ -113,61 +111,97 @@
    openDialog(row, type=false) {
      this.$refs.mandateModal.openDialog(row, type)
    },
    refreshTable() {
      this.pagination.current = 1
      this.getTableData()
    },
    /**
     * @desc æŸ¥è¯¢è¡¨æ ¼æ•°æ®
     */
    async getTableData() {
      const params = this.isDepartment ? {
        departLimsId: this.departId,
      const params = {
        current: this.pagination.current,
        size: this.pagination.pageSize
      } : {
        userId: this.departId,
        current: this.pagination.current,
        size: this.pagination.pageSize
        size: this.pagination.size
      }
      if (this.isDepartment) {
        params.departLimsId = this.departId
      } else {
        params.userId = this.departId
      }
      this.loading = true
      PersonPostAuthorizationRecordPage(params).then(res => {
      try {
        const res = await PersonPostAuthorizationRecordPage(params)
        this.tableData = (res.data && res.data.records) || []
        this.pagination.total = (res.data && res.data.total) || 0
      } catch (e) {
        this.tableData = []
        this.pagination.total = 0
      } finally {
        this.loading = false
        this.tableData = res.data.records
        this.pagination.total = res.data.total
      }).catch(err => {
        this.loading = false
      })
      }
    },
    page (page) {
      this.pagination.size = page.limit
      this.pagination.current = page.page
      this.getTableData()
    },
    /**
     * @desc åˆ é™¤ä»»èŒè®°å½•
     */
    deleteNotify(id) {
      this.$confirm('是否删除当前数据?', "警告", {
      this.$confirm('删除后该任职授权记录及其上传的上岗证书文件将一并删除,是否继续?', "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(() => {
        deletePersonPostAuthorizationRecord({id: id}).then(res => {
          this.$message.success('删除成功')
          this.getTableData()
          if (res.code === 200) {
            this.$message.success('删除成功')
            this.getTableData()
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(e => {
          this.$message.error('删除失败')
        })
      }).catch(() => {})
    },
    handleDown(row){
      exportPersonPostAuthorizationRecord({id:row.id}).then(res => {
        const blob = new Blob([res],{ type: 'application/octet-stream' });
        this.$download.saveAs(blob, '任职授权-'+row.certificateNumber+'-'+row.post + '.docx')
      })
    async handleDown(row) {
      if (this.downLoading) return
      if (!row.id) {
        this.$message.warning('缺少任职授权记录id,无法下载')
        return
      }
      this.downLoading = true
      try {
        const res = await exportPersonPostAuthorizationRecord({id: row.id})
        if (blobValidate(res)) {
          const blob = new Blob([res], {type: 'application/octet-stream'})
          this.$download.saveAs(blob, (row.userName || '任职授权') + '的检测人员上岗证.docx')
        } else {
          const text = await res.text()
          let message = '下载失败'
          try {
            message = JSON.parse(text).msg || message
          } catch (e) {}
          this.$message.error(message)
        }
      } catch (e) {
        this.$message.error('下载失败')
      } finally {
        this.downLoading = false
      }
    }
  },
  watch: {
    departId: {
      handler(newId, oldId) {
        if (newId) {
          this.pagination.current = 1
          this.getTableData();
        } else {
          this.tableData = []
          this.pagination.total = 0
        }
      }
    }
src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue
@@ -13,7 +13,7 @@
        </div>
      </div>
      <div style="line-height: 30px;">
        <el-button v-if="!isDepartment" size="small" type="primary" @click="addAppointPost('add')">新增</el-button>
        <el-button size="small" type="primary" @click="addAppointPost('add')">新增</el-button>
      </div>
    </div>
    <lims-table :tableData="tableData" :column="yearColumnData"
@@ -30,9 +30,9 @@
      <el-form v-if="dialogVisible" ref="infoForm" :model="form" :rules="rules">
        <el-col :span="12">
          <el-form-item label="人员:" label-width="110px" prop="userId">
            <el-select v-model="form.userId" disabled clearable
            <el-select v-model="form.userId" :disabled="operationType !== 'add' || !isDepartment" clearable
                       filterable size="small" style="width: 100%;">
              <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
              <el-option v-for="item in personOptions" :key="item.id" :label="item.name" :value="item.id">
              </el-option>
            </el-select>
          </el-form-item>
@@ -78,8 +78,8 @@
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item prop="relatedYearsRemarks    ">
            <el-input v-model="form.relatedYearsRemarks    " :disabled="operationType === 'view' || operationType === 'confirm'" size="small"></el-input>
          <el-form-item prop="relatedYearsRemarks">
            <el-input v-model="form.relatedYearsRemarks" :disabled="operationType === 'view' || operationType === 'confirm'" size="small"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="8">
@@ -164,17 +164,17 @@
        <el-col :span="12">
          <el-form-item prop="confirmDate" label="确认时间:">
            <el-date-picker v-model="form.confirmDate"
                            format="YYYY-MM-DD"
                            :disabled="operationType === 'view' || operationType === 'confirm'"
                            placeholder="选择日期"
                            format="YYYY-MM-DD HH:mm:ss"
                            disabled
                            placeholder="确认后由系统自动记录"
                            size="small"
                            value-format="YYYY-MM-DD HH:mm:ss"
                            type="date"></el-date-picker>
                            type="datetime"></el-date-picker>
          </el-form-item>
        </el-col>
      </el-form>
      <template #footer><span  class="dialog-footer">
        <el-button v-if="operationType !== 'view'" @click="resetForm">取消</el-button>
        <el-button @click="resetForm">{{ operationType === 'view' ? '关闭' : '取消' }}</el-button>
        <el-button v-if="operationType !== 'view' && operationType !== 'confirm'" type="primary" @click="submitForm">保存</el-button>
        <el-button v-if="operationType !== 'view' && operationType !== 'confirm'" type="primary" @click="submitForm1">提交并通知</el-button>
        <el-button v-if="operationType === 'confirm'" type="primary" @click="verifyGet">确认</el-button>
@@ -195,6 +195,8 @@
  submitConfirmPersonnelCapability
} from "@/api/cnas/personal/personPersonnelCapacity";
import {selectUserCondition} from "@/api/cnas/resourceDemand/facilitiesEnvironment/facilitiesAndEnvironment";
import {basicInformationOfPersonnelSelectPage} from "@/api/cnas/personal/personalList";
import {blobValidate} from "@/utils/ruoyi";
import {mapGetters} from "@/utils/store-compat";
export default {
@@ -208,6 +210,10 @@
    isDepartment: {
      type: Boolean,
      default: false
    },
    personName: {
      type: String,
      default: ''
    }
  },
  components: {
@@ -258,8 +264,10 @@
              return '可胜任该岗位';
            } else if (params == 'You can work while training') {
              return '可边培训边上岗';
            } else {
            } else if (params == 'Iconpetent for the position') {
              return '不胜任该岗位';
            } else {
              return '-';
            }
          },
          formatType: (params) => {
@@ -267,8 +275,10 @@
              return 'success';
            } else if (params == 'You can work while training') {
              return 'warning';
            } else {
            } else if (params == 'Iconpetent for the position') {
              return 'danger';
            } else {
              return 'info';
            }
          }
        }, {
@@ -278,7 +288,7 @@
        }, {
          label: '确认日期',
          prop: 'confirmDate',
          width: '160'
          width: '170'
        }, {
          dataType: 'action',
          label: '操作',
@@ -348,15 +358,17 @@
      verifyGetId: '',
      form: {},
      responsibleOptions: [],
      personOptions: [],
      saving: false,
      downLoading: false,
      rules: {
        confirmOperatingPersonnelId: [{ required: true, message: '请选择确认人', trigger: 'change' }],
        confirmDate: [{ required: true, message: '请选择确认时间', trigger: 'change' }],
        userId: [{ required: true, message: '请选择人员', trigger: 'change' }],
        academicDegree: [{ required: true, message: '请输入学历查核结果', trigger: 'blur' }],
        academicConformNot: [{ required: true, message: '请选择学历符合与否', trigger: 'change' }],
        relatedYears: [{ required: true, message: '请输入相关年限查核结果', trigger: 'blur' }],
        relatedYearsConformNot: [{ required: true, message: '请选择相关年限符合与否', trigger: 'change' }],
        relatedTraining: [{ required: true, message: '请输入相培训限查核结果', trigger: 'blur' }],
        relatedTraining: [{ required: true, message: '请输入相关培训查核结果', trigger: 'blur' }],
        relatedTrainingConformNot: [{ required: true, message: '请选择相关培训符合与否', trigger: 'change' }],
        relevantExperience: [{ required: true, message: '请输入相关经验查核结果', trigger: 'blur' }],
        relevantExperienceConformNot: [{ required: true, message: '请选择相关经验符合与否', trigger: 'change' }],
@@ -372,15 +384,35 @@
  },
  methods: {
    // ä¸‹è½½
    downLoadPost(row) {
      exportPersonnelCapacity({id: row.id}).then(res => {
        this.outLoading = false
        const blob = new Blob([res],{ type: 'application/msword' });
        this.$download.saveAs(blob, '人员能力导出.docx')
      })
    async downLoadPost(row) {
      if (this.downLoading) return
      if (!row.id) {
        this.$message.warning('缺少人员能力记录id,无法导出')
        return
      }
      this.downLoading = true
      try {
        const res = await exportPersonnelCapacity({id: row.id})
        if (blobValidate(res)) {
          const blob = new Blob([res], {type: 'application/msword'})
          this.$download.saveAs(blob, (row.userName || '人员能力') + '人员能力认定.docx')
        } else {
          const text = await res.text()
          let message = '导出失败'
          try {
            message = JSON.parse(text).msg || message
          } catch (e) {}
          this.$message.error(message)
        }
      } catch (e) {
        this.$message.error('导出失败')
      } finally {
        this.downLoading = false
      }
    },
    // æŸ¥è¯¢
    refreshTable() {
      this.search.current = 1
      this.getList(this.departId);
    },
    // èŽ·å–äººå‘˜èƒ½åŠ›åˆ—è¡¨ä¿¡æ¯
@@ -400,17 +432,20 @@
        }
      this.yearLoading = true
      personPersonnelCapacityPage(params).then(res => {
        this.yearLoading = false
        this.tableData = res.data.records;
        this.search.total = res.data.total;
        this.tableData = (res.data && res.data.records) || [];
        this.search.total = (res.data && res.data.total) || 0;
      }).catch(err => {
        console.log(err);
        this.tableData = []
        this.search.total = 0
      }).finally(() => {
        this.yearLoading = false
      })
    },
    pagination (page) {
      this.search.size = page.limit
      this.refreshTable()
      this.search.current = page.page
      this.getList(this.departId)
    },
    changeLine (val) {
      if(val) {
@@ -421,61 +456,99 @@
    },
    // æ–°å¢ž
    addAppointPost(type) {
      if (this.departId == null) {
        this.$message.warning('请先在左侧选择人员或部门')
        return
      }
      this.operationType = type;
      this.title = '新增能力认定'
      this.dialogVisible = true;
      this.form = {}
      this.form.userId = this.departId
      this.verifyGetId = ''
      if (!this.isDepartment) {
        this.form.userId = this.departId
        this.personOptions = [{id: this.departId, name: this.personName || '当前人员'}]
      } else {
        this.personOptions = []
        this.getPersonList()
      }
      this.dialogVisible = true;
      this.getUserList();
    },
    // ç¼–辑/查看
    // ç¼–辑/查看/确认
    handleViewClick(type, row) {
      this.operationType = type;
      this.verifyGetId = row.id
      this.title = this.operationType === 'edit' ? '修改能力认定' : '查看能力认定'
      this.title = type === 'edit' ? '修改能力认定' : (type === 'confirm' ? '确认能力认定' : '查看能力认定')
      this.dialogVisible = true;
      this.form = {...row}
      this.personOptions = [{id: row.userId, name: row.userName}]
      this.getUserList();
    },
    // ä¿å­˜
    submitForm() {
      if (this.saving) return
      this.$refs.infoForm.validate((valid) => {
        if (valid) {
          this.saving = true
          addOrUpdatePersonPersonnelCapacity(this.form).then(res => {
            if (res.code == 200) {
              this.$message.success('提交成功');
              this.$message.success('保存成功');
              this.getList(this.departId);
              this.dialogVisible = false;
            } else {
              this.$message.error(res.msg || '保存失败')
            }
          });
          }).catch(() => {
            this.$message.error('保存失败')
          }).finally(() => {
            this.saving = false
          })
        }
      });
    },
    // æäº¤å¹¶é€šçŸ¥
    submitForm1() {
      if (this.saving) return
      this.$refs.infoForm.validate((valid) => {
        if (valid) {
          submitConfirmPersonnelCapability(this.form).then(res => {
          this.saving = true
          const params = {...this.form}
          // ç¡®è®¤æ—¥æœŸç”±ç¡®è®¤äººåœ¨ç¡®è®¤æ—¶ç”±ç³»ç»Ÿè®°å½•,提交时不上传
          delete params.confirmDate
          submitConfirmPersonnelCapability(params).then(res => {
            if (res.code == 200) {
              this.$message.success('提交成功');
              this.$message.success('提交成功,已通知确认人');
              this.getList(this.departId);
              this.dialogVisible = false;
            } else {
              this.$message.error(res.msg || '提交失败')
            }
          });
          }).catch(() => {
            this.$message.error('提交失败')
          }).finally(() => {
            this.saving = false
          })
        }
      });
    },
    verifyGet () {
      if (this.saving) return
      this.saving = true
      confirmPersonnelCapability({id: this.verifyGetId}).then(res => {
        if (res.code == 200) {
          this.$message.success('确认成功');
          this.resetForm()
          this.getList(this.departId);
        } else {
          this.$message.error(res.msg || '确认失败')
        }
      });
      }).catch(() => {
        this.$message.error('确认失败')
      }).finally(() => {
        this.saving = false
      })
    },
    // åˆ é™¤å²—位职责
    // åˆ é™¤äººå‘˜èƒ½åŠ›è®°å½•
    deletePost(id) {
      this.$confirm('此操作将永久删除此数据, æ˜¯å¦ç»§ç»­?', '提示', {
        confirmButtonText: '确定',
@@ -486,21 +559,38 @@
          if (res.code == 200) {
            this.$message.success('删除成功');
            this.getList(this.departId);
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败');
        });
      }).catch(() => {
        this.$message.error('删除失败');
      });
      }).catch(() => {});
    },
    resetForm() {
      this.$refs.infoForm.resetFields();
      this.dialogVisible = false;
      this.$nextTick(() => {
        if (this.$refs.infoForm) {
          this.$refs.infoForm.resetFields();
        }
      })
    },
    getUserList(){
      selectUserCondition({type: 2}).then(res => {
        if (res.code == 200) {
          this.responsibleOptions = res.data
          this.responsibleOptions = res.data || []
        }
      }).catch(() => {
        this.responsibleOptions = []
      })
    },
    // éƒ¨é—¨è§†è§’新增时,可选本部门下的人员
    getPersonList() {
      basicInformationOfPersonnelSelectPage({departmentId: this.departId, size: 1000, current: 1}).then(res => {
        const records = (res.data && res.data.records) || []
        this.personOptions = records.map(item => ({id: item.userId, name: item.name}))
      }).catch(() => {
        this.personOptions = []
      })
    },
  },
@@ -508,7 +598,13 @@
    // ç›‘听点击el-tree的数据,进行数据刷新
    departId: {
      handler(newId, oldId) {
        this.getList(newId);
        this.search.current = 1
        if (newId) {
          this.getList(newId);
        } else {
          this.tableData = []
          this.search.total = 0
        }
      }
    },
  }
src/views/CNAS/personnel/personnelInfo/tabs/personnel-information.vue
@@ -2,7 +2,7 @@
  <div>
    <div style="text-align: right;margin-bottom: 10px">
      <!--       <el-button size="small" @click="$emit('goBackList')">返回</el-button>-->
      <el-button size="small" type="primary" @click="downPerson">下载档案</el-button>
      <el-button :loading="downLoading" size="small" type="primary" @click="downPerson">下载档案</el-button>
      <el-button size="small" type="primary" @click="dialogVisible = true">人员分类</el-button>
      <el-button :loading="saveLoading" size="small" type="primary" @click="save">保存</el-button>
    </div>
@@ -273,7 +273,8 @@
                </el-form-item>
              </el-col>
              <el-col :span="6">
                <el-upload ref="upload" :action="action" :headers="uploadHeader"
                <el-upload ref="pictureUpload" :action="action" :headers="uploadHeader"
                  accept=".jpg,.jpeg,.png" :before-upload="imageBeforeUpload"
                  :on-success="(response, file, fileList) => onSuccess(response, file, fileList, 'pictureUrl')"
                  :show-file-list="false" style="float: left; margin: 0 10px 0 10px;">
                  <template #trigger><el-button  class="uploadFile" size="small" type="primary">上传</el-button></template>
@@ -292,7 +293,8 @@
                </el-form-item>
              </el-col>
              <el-col :span="6">
                <el-upload ref="upload" :action="action" :headers="uploadHeader"
                <el-upload ref="signatureUpload" :action="action" :headers="uploadHeader"
                  accept=".jpg,.jpeg,.png" :before-upload="imageBeforeUpload"
                  :on-success="(response, file, fileList) => onSuccess(response, file, fileList, 'signatureUrl')"
                  :show-file-list="false" style="float: left; margin: 0 10px 0 10px;">
                  <template #trigger><el-button  class="uploadFile" size="small" type="primary">上传</el-button></template>
@@ -319,7 +321,7 @@
                </el-form-item>
              </el-col>
              <el-col :span="4">
                <el-upload ref='upload' :action="fileAction" :auto-upload="true" :data="{ userId: clickNodeVal.userId }"
                <el-upload ref='fileUpload' :action="fileAction" :auto-upload="true" :data="{ userId: clickNodeVal.userId }"
                  :before-upload="fileBeforeUpload" :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'
@@ -380,8 +382,7 @@
    </el-dialog>
    <!-- æ–°å¢žé™„件资料 -->
    <el-dialog :before-close="handleClose" :title="title" v-model="dialogVisible1" width="40%"
      @open="getComparisonList">
    <el-dialog :before-close="handleClose" :title="title" v-model="dialogVisible1" width="40%">
      <el-form ref="annex" :model="annex" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="16">
@@ -488,6 +489,7 @@
  updateBasicInfoWorkList
} from "@/api/cnas/personal/personalList";
import limsTable from "@/components/Table/lims-table.vue";
import { blobValidate } from "@/utils/ruoyi";
export default {
  props: {
@@ -501,6 +503,7 @@
    return {
      operationType: '',
      basicInfoWorkId: '',
      downLoading: false,
      dialogVisible2: false,
      annex2: {
        workExperience: ''
@@ -670,6 +673,7 @@
      dialogVisible: false,
      checkList: [],
      successFileList: [], // é˜²æ­¢åŽç«¯å‡ºçŽ°è„æ•°æ®
      pendingDeleteFiles: [], // ä¿å­˜æˆåŠŸåŽå†åˆ é™¤çš„æ—§æ–‡ä»¶
      isSave: false,
    }
  },
@@ -690,10 +694,22 @@
  methods: {
    // ä¸‹è½½æ¡£æ¡ˆ
    downPerson() {
      console.log('this.clickNodeVal.userId',this.clickNodeVal.userId)
      exportPersonBasicInfoById({ id: this.clickNodeVal.userId }).then(res => {
      const userId = this.clickNodeVal.userId
      if (!userId) {
        this.$message.warning('请先选择人员')
        return
      }
      this.downLoading = true
      exportPersonBasicInfoById({ id: userId }).then(async res => {
        if (!blobValidate(res)) {
          const rspObj = JSON.parse(await res.text())
          this.$message.error(rspObj.msg || '下载失败')
          return
        }
        const blob = new Blob([res], { type: 'application/msword' });
        this.$download.saveAs(blob, '人员档案.docx');
        this.$download.saveAs(blob, (this.form.name ? this.form.name + '人员档案' : '人员档案') + '.docx');
      }).catch(() => {}).finally(() => {
        this.downLoading = false
      })
    },
    // ä¸Šä¼ éªŒè¯
@@ -701,35 +717,52 @@
      let flag = true
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
        this.$refs.fileUpload && this.$refs.fileUpload.clearFiles()
        flag = false
      }
      if (!flag) {
        return Promise.reject(flag); //正确的终止
      }
    },
    // ä¸ªäººç…§ç‰‡/电子签名只允许图片
    imageBeforeUpload(file) {
      if (!/\.(jpg|jpeg|png)$/i.test(file.name || '')) {
        this.$message.error('仅支持上传 jpg、jpeg、png æ ¼å¼çš„图片')
        return false
      }
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M')
        return false
      }
      return true
    },
    onError(err, file, fileList, type) {
      this.$message.error('上传失败')
      this.$refs.upload.clearFiles()
      this.$refs.fileUpload && this.$refs.fileUpload.clearFiles()
    },
    handleSuccessUp(response,) {
      this.upLoading = false;
      if (response.code == 200) {
      if (response && response.code == 200) {
        this.$message.success('上传成功');
        this.searchTableList()
      } else {
        this.$message.error(response.message);
        this.$message.error((response && (response.msg || response.message)) || '上传失败');
      }
    },
    // æŸ¥è¯¢é™„件列表
    searchTableList() {
      const userId = this.clickNodeVal && this.clickNodeVal.userId
      if (!userId) {
        this.tableData = []
        return
      }
      this.tableLoading = true
      getBasicInfoFileList({ userId: this.clickNodeVal.userId }).then(res => {
        this.tableLoading = false
        this.tableData = res.data
      getBasicInfoFileList({ userId: userId }).then(res => {
        this.tableData = res.data || []
      }).catch(err => {
        this.tableLoading = false
        this.tableData = []
        console.log('err---', err);
      }).finally(() => {
        this.tableLoading = false
      })
    },
    // ä¸‹è½½
@@ -745,18 +778,18 @@
      }).then(() => {
        this.tableLoading = true
        delBasicInfoFileList({ basicInfoFileId: row.basicInfoFileId }).then(res => {
          this.tableLoading = false
          this.$message.success('删除成功')
          this.searchTableList();
        }).catch(err => {
          if (res.code == 200) {
            this.$message.success('删除成功')
            this.searchTableList();
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        }).finally(() => {
          this.tableLoading = false
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      })
      }).catch(() => {})
    },
    // æ‰“开工作经历探况
    annexAdd1(type, row) {
@@ -772,6 +805,10 @@
    },
    // æäº¤å·¥ä½œç»åކ
    submitForm2() {
      if (!this.annex2.workExperience || !String(this.annex2.workExperience).trim()) {
        this.$message.warning('请输入工作经历')
        return
      }
      const params = {
        workExperience: this.annex2.workExperience,
        userId: this.clickNodeVal.userId,
@@ -780,22 +817,30 @@
      this.tableLoading1 = true
      if (this.operationType === 'add') {
        addBasicInfoWork(params).then(res => {
          this.tableLoading1 = false
          if (res.code == 200) {
            this.dialogVisible2 = false
            this.$message.success('新增成功')
            this.searchTableList2();
          } else {
            this.$message.error(res.msg || '新增失败')
          }
        }).catch(err => {
        }).catch(() => {
          this.$message.error('新增失败')
        }).finally(() => {
          this.tableLoading1 = false
        })
      } else {
        updateBasicInfoWorkList(params).then(res => {
          this.tableLoading1 = false
          this.dialogVisible2 = false
          this.$message.success('修改成功')
          this.searchTableList2();
        }).catch(err => {
          if (res.code == 200) {
            this.dialogVisible2 = false
            this.$message.success('修改成功')
            this.searchTableList2();
          } else {
            this.$message.error(res.msg || '修改失败')
          }
        }).catch(() => {
          this.$message.error('修改失败')
        }).finally(() => {
          this.tableLoading1 = false
        })
      }
@@ -814,28 +859,34 @@
      }).then(() => {
        this.tableLoading1 = true
        delBasicInfoWorkList({ basicInfoWorkId: row.basicInfoWorkId }).then(res => {
          this.tableLoading1 = false
          this.$message.success('删除成功')
          this.searchTableList2();
        }).catch(err => {
          if (res.code == 200) {
            this.$message.success('删除成功')
            this.searchTableList2();
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        }).finally(() => {
          this.tableLoading1 = false
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
      }).catch(() => {})
    },
    // æŸ¥è¯¢å·¥ä½œç»åŽ†åˆ—è¡¨
    searchTableList2() {
      const userId = this.clickNodeVal && this.clickNodeVal.userId
      if (!userId) {
        this.tableData1 = []
        return
      }
      this.tableLoading1 = true
      getBasicInfoWorkList({ userId: this.clickNodeVal.userId }).then(res => {
        this.tableLoading1 = false
        this.tableData1 = res.data
      getBasicInfoWorkList({ userId: userId }).then(res => {
        this.tableData1 = res.data || []
      }).catch(err => {
        this.tableLoading1 = false
        this.tableData1 = []
        console.log('err---', err);
      }).finally(() => {
        this.tableLoading1 = false
      })
    },
    annexAdd(type, row) {
@@ -843,12 +894,37 @@
        this.title = '编辑附件资料'
        this.addOrupdate = 1
        getAnnex({ id: row.id }).then(res => {
          if (res.code != 200 || !res.data) {
            this.$message.error(res.msg || '附件资料加载失败')
            return
          }
          this.annex = res.data
          this.imageUrl = this.javaApi + '/img/' + res.data.fileName
          this.dialogVisible1 = true
        }).catch(() => {
          this.$message.error('附件资料加载失败')
        })
        return
      } else {
        this.title = '新增附件资料'
        this.addOrupdate = 2
        this.imageUrl = ''
        this.annex = {
          userId: '',
          idNumber: '',
          issueUnit: '',
          level: '',
          periodValidity: '',
          copy: '',
          original: '',
          fileName: ''
        }
        if (this.$refs.fileName) {
          this.$refs.fileName.clearFiles()
        }
        this.$nextTick(() => {
          this.$refs.annex && this.$refs.annex.clearValidate()
        })
      }
      this.dialogVisible1 = true
    },
@@ -870,33 +946,41 @@
      if (this.addOrupdate === 1) {
        updateAnnex(this.annex).then(res => {
          if (res.code == 200) {
            getAnnexByUserId({ userId: this.clickNodeVal.userId }).then(res => {
              this.imageUrl = ''
              this.$refs.fileName.clearFiles()
              this.annex.fileName = ''
              this.resetForm('annex')
              this.annexList = res.data
              this.dialogVisible1 = false
              this.$message.success('更新成功!')
            })
            this.refreshAnnexList('更新成功!')
          } else {
            this.$message.error(res.msg || '更新失败')
          }
        }).catch(() => {
          this.$message.error('更新失败')
        })
      } else {
        this.annex.id = null
        addAnnex(this.annex).then(res => {
          if (res.code == 200) {
            getAnnexByUserId({ userId: this.clickNodeVal.userId }).then(res => {
              this.imageUrl = ''
              this.$refs.fileName.clearFiles()
              this.annex.fileName = ''
              this.resetForm('annex')
              this.annexList = res.data
              this.dialogVisible1 = false
              this.$message.success('保存成功')
            })
            this.refreshAnnexList('保存成功')
          } else {
            this.$message.error(res.msg || '保存失败')
          }
        }).catch(() => {
          this.$message.error('保存失败')
        })
      }
    },
    refreshAnnexList(tip) {
      getAnnexByUserId({ userId: this.clickNodeVal.userId }).then(res => {
        this.imageUrl = ''
        if (this.$refs.fileName) {
          this.$refs.fileName.clearFiles()
        }
        this.annex.fileName = ''
        this.resetForm('annex')
        this.annexList = res.data || []
        this.dialogVisible1 = false
        this.$message.success(tip)
      }).catch(() => {
        this.annexList = []
        this.$message.error('附件资料刷新失败,请重新打开页面查看')
      })
    },
    deleteAnnex(row) {
      this.$confirm('此操作将永久删除该文件, æ˜¯å¦ç»§ç»­?', '提示', {
@@ -905,16 +989,22 @@
        type: 'warning'
      }).then(() => {
        deleteAnnex({ id: row.id }).then(res => {
          this.$message.success('删除成功!')
          this.annexList = this.annexList.filter(item => item.id != row.id)
          if (res.code == 200) {
            this.$message.success('删除成功!')
            this.annexList = this.annexList.filter(item => item.id != row.id)
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        })
      })
      }).catch(() => {})
    },
    beforeAvatarUpload(file) {
      let flag = true
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M');
        this.$refs.upload.clearFiles()
        this.$refs.fileName && this.$refs.fileName.clearFiles()
        flag = false
      }
      if (!flag) {
@@ -925,6 +1015,10 @@
      return this.$download.saveAs(fileName, fileName)
    },
    async onSuccess(response, file, fileList, entityVal) {
      if (!response || response.code != 200) {
        this.$message.error((response && (response.msg || response.message)) || '上传失败')
        return
      }
      if (entityVal == 'fileName') {
        this.annex.fileName = response.data
      }
@@ -934,58 +1028,90 @@
    },
    // åˆå§‹åŒ–调用
    init() {
      if (!this.clickNodeVal.userId) {
        let user = JSON.parse(localStorage.getItem('user'))
        this.getUserBasisInfo(user.userId)
        this.clickNodeVal.userId = user.userId
      } else {
        this.getUserBasisInfo(this.clickNodeVal.userId)
      let userId = this.clickNodeVal && this.clickNodeVal.userId
      if (!userId) {
        let user = JSON.parse(localStorage.getItem('user') || '{}')
        userId = user.userId
        this.clickNodeVal.userId = userId
      }
      if (userId) {
        this.getUserBasisInfo(userId)
      }
    },
    getUserBasisInfo(userId) {
      getCNASPersonnelInfo({ userId: userId }).then(res => {
        this.form = res.data.PersonBasicInfoDto
        this.department = res.data.department
        this.annexList = res.data.annexList
        this.form.departLimsId = res.data.PersonBasicInfoDto.departLimsId.split(',').filter(a => a != "").map(Number)
        const dto = (res.data && res.data.PersonBasicInfoDto) || {}
        this.form = { ...dto }
        this.department = (res.data && res.data.department) || []
        this.annexList = (res.data && res.data.annexList) || []
        this.form.departLimsId = dto.departLimsId
          ? String(dto.departLimsId).split(',').filter(a => a != "").map(Number)
          : []
      }).catch(() => {
        this.form = {}
        this.annexList = []
        this.tableData = []
        this.tableData1 = []
      })
    },
    async save() {
      const userId = this.clickNodeVal && this.clickNodeVal.userId
      if (!userId) {
        this.$message.warning('请先在左侧选择人员')
        return
      }
      this.saveLoading = true
      this.form.userId = this.clickNodeVal.userId
      if (Array.isArray(this.form.departLimsId)) {
        if (this.form.departLimsId.length > 0) {
          this.form.departLimsId = this.form.departLimsId.join(',').trim() + ','
      const payload = { ...this.form }
      payload.userId = userId
      if (Array.isArray(payload.departLimsId)) {
        if (payload.departLimsId.length > 0) {
          payload.departLimsId = payload.departLimsId.join(',').trim() + ','
        } else {
          this.form.departLimsId = ''
          payload.departLimsId = ''
        }
      }
      saveCNASPersonnelInfo(this.form).then(res => {
      saveCNASPersonnelInfo(payload).then(res => {
        if (res.code == 200) {
          this.isSave = true
          this.deletePendingFiles()
          this.getUserBasisInfo(userId)
          this.$emit('refreshTree')
          this.$message.success('保存成功!')
        } else {
          this.$message.error(res.msg || '保存失败')
        }
      }).catch(() => {
        this.$message.error('保存失败')
      }).finally(() => {
        this.saveLoading = false
        this.isSave = true
        this.getUserBasisInfo(this.clickNodeVal.userId)
        this.$message.success('保存成功!')
      })
    },
    // å–人员分类的字典
    // æ‰“开人员分类弹框时回显已保存的分类
    getComparisonList() {
      if (this.checkList.length > 0) {
        this.form.personnelClassification = this.checkList.split(',')
      }
      this.checkList = this.form.personnelClassification
        ? String(this.form.personnelClassification).split(/[,,]/).filter(m => m)
        : []
    },
    clickPersonnelClassificationSure() {
      this.dialogVisible = false
      this.form.personnelClassification = this.checkList.filter(m => m).join(',')
    },
    async deleteFile(fileName, entityVal) {
      await deleteCNASFile({ fileName: fileName }).then(res => {
        this.$message.success('删除成功!')
        this.form[entityVal] = null
      this.$confirm('确定删除该文件吗?保存后生效。', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // ä¿å­˜æˆåŠŸåŽå†åˆ é™¤ç‰©ç†æ–‡ä»¶ï¼Œé¿å…å–æ¶ˆç¼–è¾‘åŽæ•°æ®åº“ä»æŒ‡å‘å·²åˆ é™¤çš„æ–‡ä»¶
        if (fileName) {
          this.pendingDeleteFiles.push(fileName)
        }
        this.form[entityVal] = ''
        let index = this.successFileList.indexOf(fileName)
        if (index != -1) {
          this.successFileList.splice(index, 1)
        }
      })
      }).catch(() => {})
    },
    cancellation() {
      this.resetForm('annex')
@@ -1006,7 +1132,20 @@
        original: '',
        fileName: ''
      }
      if (this.$refs.fileName) {
        this.$refs.fileName.clearFiles()
      }
      done();
    },
    // ä¿å­˜æˆåŠŸåŽæ¸…ç†ç‰©ç†æ–‡ä»¶
    deletePendingFiles() {
      if (!this.pendingDeleteFiles.length) {
        return
      }
      this.pendingDeleteFiles.forEach(fileName => {
        deleteCNASFile({ fileName }).catch(() => {})
      })
      this.pendingDeleteFiles = []
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
@@ -1015,6 +1154,9 @@
      this.form.age = this.calculateAge(val)
    },
    calculateAge(birthDateString) {
      if (!birthDateString) {
        return undefined
      }
      // è§£æžå‡ºç”Ÿæ—¥æœŸå­—符串为日期对象
      const birthDate = new Date(birthDateString);
src/views/CNAS/personnel/personnelInfo/tabs/personnel-list.vue
@@ -5,10 +5,10 @@
        <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="entity.name"
                    @keyup.enter="refreshTable"></el-input>
                    @keyup.enter="handleSearch"></el-input>
        </div>
        <div style="line-height: 30px;">
          <el-button type="primary" size="small" @click="refreshTable">查询</el-button>
          <el-button type="primary" size="small" @click="handleSearch">查询</el-button>
          <el-button size="small" @click="refresh">重置</el-button>
        </div>
      </div>
@@ -40,7 +40,7 @@
          </template>
        </el-table-column>
      </el-table>
      <el-pagination :current-page="1" v-model:current-page="page.current" :page-size="page.size"
      <el-pagination v-model:current-page="page.current" v-model:page-size="page.size"
        :page-sizes="[10, 20, 30, 50, 100]" :total="page.total" background
        layout="->,total, sizes, prev, pager, next, jumper" style="margin-top: 10px" @size-change="handleSizeChange"
        @current-change="handleCurrentChange">
@@ -79,6 +79,7 @@
  upUserDepardLimsId
} from "@/api/cnas/personal/personalList";
import store from "@/store";
import { blobValidate } from "@/utils/ruoyi";
export default {
  name: 'PersonnelList',
@@ -174,6 +175,11 @@
      this.entity.name = ''
      this.refreshTable();
    },
    // æŸ¥è¯¢
    handleSearch() {
      this.page.current = 1
      this.refreshTable()
    },
    // æŸ¥è¯¢äººå‘˜åˆ—表数据
    refreshTable() {
      this.tableLoading = true;
@@ -185,32 +191,35 @@
        name: this.entity.name,
      }
      basicInformationOfPersonnelSelectPage(params).then(res => {
        this.tableLoading = false;
        this.page.total = res.data.total;
        this.tableData = res.data.records;
        this.page.total = (res.data && res.data.total) || 0;
        this.tableData = (res.data && res.data.records) || [];
      }).catch(err => {
        this.page.total = 0;
        this.tableData = [];
      }).finally(() => {
        this.tableLoading = false;
      })
    },
    // åˆ é™¤äººå‘˜
    // ä»Žäººå‘˜æž¶æž„中移除
    deletePerson(row) {
      this.$confirm('是否删除当前数据?', '警告', {
      this.$confirm(`确定将“${row.name}”从人员架构中移除吗?移除后该人员不会出现在左侧人员树中(不会删除用户账号)。`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        delUserDepardLimsId({ id: row.userId }).then(res => {
          this.$message.success('删除成功');
          this.refreshTable();
          this.$emit('refreshTree')
        }).catch(e => {
          this.$message.error('删除失败');
        });
          if (res.code == 200) {
            this.$message.success('已从人员架构中移除');
            this.refreshTable();
            this.$emit('refreshTree')
          }
        }).catch(() => {});
      }).catch(() => {
      });
    },
    handleSizeChange(val) {
      this.page.size = val;
      this.page.current = 1;
      this.refreshTable();
    },
    handleCurrentChange(val) {
@@ -225,10 +234,11 @@
    // æŸ¥è¯¢æ–°å¢žå¼¹æ¡†çš„人员列表
    selectUserList() {
      this.tableLoading1 = true
      selectUserCondition().then(res => {
        this.tableLoading1 = false
        this.tableData1 = res.data
      }).catch(err => {
      selectUserCondition({ name: this.addUserTableInfo.name || '', isCustom: 0 }).then(res => {
        this.tableData1 = res.data || []
      }).catch(() => {
        this.tableData1 = []
      }).finally(() => {
        this.tableLoading1 = false
      })
    },
@@ -238,7 +248,7 @@
    },
    // æäº¤éœ€è¦æ·»åŠ çš„äººå‘˜
    selectUser() {
      if (!this.currentCompaniesList.length > 0) {
      if (!this.currentCompaniesList || this.currentCompaniesList.length === 0) {
        this.$message.warning("请选择部门!")
        return;
      }
@@ -261,21 +271,35 @@
        ids: JSON.stringify(ids),
        id: str
      }).then(res => {
        this.selectUserDia = false;
        this.$message.success('添加成功');
        this.refreshTable();
        this.$emit('refreshTree')
        if (res.code == 200) {
          this.selectUserDia = false;
          this.multipleSelection = [];
          this.$message.success('添加成功');
          this.refreshTable();
          this.$emit('refreshTree')
        } else {
          this.$message.error(res.msg || '添加失败')
        }
      }).catch(() => {
        this.$message.error('添加失败')
      });
    },
    // å¯¼å‡ºäººå‘˜ä¿¡æ¯
    handleDown() {
      this.outLoading = true;
      let entity = this.HaveJson(this.entity)
      delete entity.orderBy;
      exportPersonBasicInfo({ ...entity }).then(res => {
        this.outLoading = false;
      exportPersonBasicInfo({
        name: this.entity.name,
        departmentId: this.departId
      }).then(async res => {
        if (!blobValidate(res)) {
          const rspObj = JSON.parse(await res.text())
          this.$message.error(rspObj.msg || '导出失败')
          return
        }
        const blob = new Blob([res], { type: 'application/octet-stream' });
        this.$download.saveAs(blob, '人员信息.xlsx')
      }).catch(() => {}).finally(() => {
        this.outLoading = false;
      })
    },
  },
@@ -285,6 +309,9 @@
        if (newId) {
          this.page.current = 1
          this.refreshTable();
        } else {
          this.tableData = []
          this.page.total = 0
        }
      }
    }
src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue
@@ -10,10 +10,10 @@
          <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="page.compilerName"
                      @keyup.enter="getYearPlanList(departId)"></el-input>
                      @keyup.enter="searchYearPlan"></el-input>
          </div>
          <div style="line-height: 30px;">
            <el-button type="primary" size="small" @click="getYearPlanList(departId)">查询</el-button>
            <el-button type="primary" size="small" @click="searchYearPlan">查询</el-button>
          </div>
        </div>
        <div style="line-height: 30px;">
@@ -21,7 +21,8 @@
        </div>
      </div>
      <lims-table :tableData="yearPlanTableData" :column="yearPlanColumn" :currentChange="currentChange"
        @pagination="pagination" height="40vh" :page="page" :tableLoading="yearLoading"></lims-table>
        :highlightCurrentRow="true" @pagination="pagination" height="40vh" :page="page"
        :tableLoading="yearLoading"></lims-table>
    </div>
    <div v-if="!editPlanShow" class="table">
      <div>
@@ -78,7 +79,7 @@
      </span>
      <template #footer><span  class="dialog-footer">
        <el-button :loading="approvalLoading" @click="handleApproval(2)">不批准</el-button>
        <el-button :loading="reviewLoading" type="primary" @click="handleApproval(1)">批 å‡†</el-button>
        <el-button :loading="approvalLoading" type="primary" @click="handleApproval(1)">批 å‡†</el-button>
      </span></template>
    </el-dialog>
    <el-dialog v-model="uploadDia" title="数据导入" width="500px">
@@ -136,6 +137,7 @@
  queryTheAnnualPlanDetailsTable, reviewAnnualPersonnelTraining
} from "@/api/cnas/personal/personalTraining";
import { selectUserCondition } from "@/api/system/user";
import { blobValidate } from "@/utils/ruoyi";
export default {
  name: 'PersonnelTraining',
@@ -404,6 +406,7 @@
            {
              name: '编辑',
              type: 'text',
              disabled: () => !this.isOperation,
              clickFun: (row) => {
                this.addTrainingPlan('edit', row);
              },
@@ -411,6 +414,7 @@
            {
              name: '结果明细',
              type: 'text',
              disabled: () => !this.isOperation,
              clickFun: (row) => {
                this.editInDetail(row);
              },
@@ -462,6 +466,7 @@
      multipleSelection: [], // å¹´åº¦æ˜Žç»†è¡¨é€‰ä¸­çš„æ•°æ®
      uploadDia: false,
      uploading: false,
      downLoading: false,
      isOperation: false,
      ViewRecord: false,
      fileList: [],
@@ -478,9 +483,22 @@
  },
  methods: {
    searchTable() {
      this.inDetailPagination.current = 1
      this.getInDetailPlan(this.currentRowId)
    },
    // æŸ¥è¯¢-年度计划表
    searchYearPlan() {
      this.page.current = 1
      this.getYearPlanList(this.departId)
    },
    clearPlanSelection() {
      this.currentRowId = null
      this.currentChangeRow = {}
      this.isOperation = false
      this.inDetailPlanTableData = []
      this.inDetailPagination.total = 0
      this.inDetailPagination.current = 1
    },
    getYearPlanList(userId) {
      this.yearLoading = true
      const params = this.isDepartment ?
@@ -496,36 +514,43 @@
          compilerName: this.page.compilerName,
        }
      personTraining(params).then(res => {
        this.yearLoading = false
        this.yearPlanTableData = res.data.records;
        this.pagination.total = res.data.total;
        this.yearPlanTableData = (res.data && res.data.records) || [];
        this.page.total = (res.data && res.data.total) || 0;
        if (this.yearPlanTableData.length > 0) {
          this.currentRowId = this.yearPlanTableData[0].id
          this.currentChange(this.yearPlanTableData[0])
        } else {
          this.clearPlanSelection()
        }
      }).catch(err => {
      }).catch(() => {
        this.yearPlanTableData = []
        this.page.total = 0
        this.clearPlanSelection()
      }).finally(() => {
        this.yearLoading = false
      })
    },
    pagination(page) {
      this.page.size = page.limit
      this.getYearPlanList()
      this.page.current = page.page
      this.getYearPlanList(this.departId)
    },
    currentChange(row) {
      const now = new Date();
      const currentYear = now.getFullYear();
      if (row) {
        this.currentChangeRow = row
        this.currentRowId = row.id
        if (row.createTime.slice(0, 4) == currentYear) {
          this.isOperation = true;
        } else {
          this.isOperation = false;
        }
        this.getInDetailPlan(row.id)
      if (!row) {
        return
      }
      const currentYear = new Date().getFullYear();
      this.currentChangeRow = row
      this.currentRowId = row.id
      const planYear = row.planYear ? String(row.planYear) : (row.createTime ? String(row.createTime).slice(0, 4) : '')
      this.isOperation = planYear === String(currentYear)
    },
    getInDetailPlan(id) {
      // éƒ¨é—¨è§†è§’下必须先选中一份年度计划,否则会把所有部门的明细都查出来
      if (this.isDepartment && !id) {
        this.inDetailPlanTableData = []
        this.inDetailPagination.total = 0
        return
      }
      if (this.inDetailForm.trainingDate === null) {
        this.inDetailForm.trainingDate = ''
      }
@@ -533,7 +558,7 @@
      const params =
      {
        userId: userId,
        size: this.inDetailPagination.pageSize,
        size: this.inDetailPagination.size,
        current: this.inDetailPagination.current,
        id: id,
        trainingLecturerName: this.inDetailForm.trainingLecturerName,
@@ -541,15 +566,18 @@
      }
      this.yearDetailLoading = true
      queryTheAnnualPlanDetailsTable(params).then(res => {
        this.yearDetailLoading = false
        this.inDetailPlanTableData = res.data.records;
        this.inDetailPagination.total = res.data.total;
      }).catch(err => {
        this.inDetailPlanTableData = (res.data && res.data.records) || [];
        this.inDetailPagination.total = (res.data && res.data.total) || 0;
      }).catch(() => {
        this.inDetailPlanTableData = []
        this.inDetailPagination.total = 0
      }).finally(() => {
        this.yearDetailLoading = false
      })
    },
    pagination1(page) {
      this.inDetailPagination.size = page.limit
      this.inDetailPagination.current = page.page
      this.getInDetailPlan(this.currentRowId)
    },
    // æ–°å¢žå¹´åº¦è®¡åˆ’明细表
@@ -558,39 +586,54 @@
        this.$message.warning('请选择一条计划进行新增')
        return
      }
      if (!this.isOperation) {
        this.$message.warning('仅可编辑当前年度的培训计划')
        return
      }
      this.$refs.addPlan.showDialog(this.currentRowId, type, row);
    },
    // å¹´åº¦è®¡åˆ’表-删除
    deleteFun(id) {
      this.$confirm('此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?', '提示', {
      this.$confirm('删除后该计划的明细和培训记录将一并删除,是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        personTrainingDelete({ id: id }).then(res => {
          this.$message.success('删除成功!');
          this.getYearPlanList(this.departId);
          if (res.code == 200) {
            this.$message.success('删除成功!');
            if (this.currentRowId === id) {
              this.clearPlanSelection()
            }
            this.getYearPlanList(this.departId);
          } else {
            this.$message.error(res.msg || '删除失败')
          }
        }).catch(() => {
          this.$message.error('删除失败')
        });
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },
    // å¹´åº¦è®¡åˆ’表-审核
    handleCheck(id) {
      this.currentRowId = id
      this.approverId = ''
      this.auditRemarks = ''
      this.reviewDialog = true
      this.getUserList()
    },
    // æäº¤å®¡æ ¸
    handleReview(status) {
      if (status === 1 && !this.approverId) {
        this.$message.warning('请选择批准人')
        return
      }
      const personTrainingUpdateDto = {
        id: this.currentRowId,
        auditRemarks: this.auditRemarks,
        reviewerStatus: status,
        approverId: this.approverId
        approverId: status === 1 ? this.approverId : null
      }
      this.reviewLoading = true
      reviewAnnualPersonnelTraining(personTrainingUpdateDto).then(res => {
@@ -598,9 +641,12 @@
          this.$message.success('提交成功!');
          this.reviewDialog = false
          this.getYearPlanList(this.departId);
        } else {
          this.$message.error(res.msg || '提交失败')
        }
        this.reviewLoading = false
      }).catch(() => {
        this.$message.error('提交失败')
      }).finally(() => {
        this.reviewLoading = false
      })
    },
@@ -622,18 +668,31 @@
          this.$message.success('提交成功!');
          this.approvalDialog = false
          this.getYearPlanList(this.departId);
        } else {
          this.$message.error(res.msg || '提交失败')
        }
        this.approvalLoading = false
      }).catch(() => {
        this.$message.error('提交失败')
      }).finally(() => {
        this.approvalLoading = false
      })
    },
    // å¹´åº¦è®¡åˆ’表-下载
    downLoadPost(row) {
      exportPersonTraining({ id: row.id }).then(res => {
        this.outLoading = false
      if (this.downLoading) {
        return
      }
      this.downLoading = true
      exportPersonTraining({ id: row.id }).then(async res => {
        if (!blobValidate(res)) {
          const rspObj = JSON.parse(await res.text())
          this.$message.error(rspObj.msg || rspObj.message || '导出失败')
          return
        }
        const blob = new Blob([res], { type: 'application/msword' });
        this.$download.saveAs(blob, row.fileName + '.docx')
        this.$download.saveAs(blob, (row.fileName || '人员培训计划') + '.docx')
      }).catch(() => {}).finally(() => {
        this.downLoading = false
      })
    },
    // æ‰“开导入弹框
@@ -665,26 +724,32 @@
        this.$message.error('未选择文件')
        return
      }
      this.uploading = true
      this.$refs.upload.submit();
      this.uploading = false
    },
    onSuccess(response, file, fileList) {
      this.$refs.upload.clearFiles()
      this.uploadDia = false
      this.uploading = false
      if (response.code != 200) {
        this.$message.error(response.msg)
      const file = this.fileList[0].raw || this.fileList[0]
      if (!/\.(xls|xlsx)$/i.test(file.name || '')) {
        this.$message.error('请选择 .xls æˆ– .xlsx æ ¼å¼çš„æ–‡ä»¶')
        return
      }
      if (file.size > 1024 * 1024 * 10) {
        this.$message.error('上传文件不超过10M')
        return
      }
      this.uploading = true
      this.$refs.upload.submit();
    },
    onSuccess(response, file, fileList) {
      this.uploading = false
      if (!response || response.code != 200) {
        this.$message.error((response && (response.msg || response.message)) || '上传失败')
        return
      }
      this.$refs.upload && this.$refs.upload.clearFiles()
      this.uploadDia = false
      this.$message.success('上传成功')
      this.standardList = []
      this.productList = []
      this.getYearPlanList(this.departId)
    },
    onError(err, file, fileList) {
      this.$message.error('上传失败')
      this.$refs.upload.clearFiles()
      this.$refs.upload && this.$refs.upload.clearFiles()
      this.uploading = false
    },
    beforeUpload(file, fileList) {
@@ -702,6 +767,10 @@
    },
    // å¹´åº¦è®¡åˆ’明细表-编辑
    editInDetail(row) {
      if (!this.isOperation) {
        this.$message.warning('仅可编辑当前年度的培训计划')
        return
      }
      this.editPlanShow = true;
      this.currentRow = row
    },
@@ -718,10 +787,20 @@
    },
    // å¹´åº¦è®¡åˆ’明细表-下载
    downLoadInDetail(row) {
      exportPersonTrainingRecord({ id: row.id }).then(res => {
        this.outLoading = false
      if (this.downLoading) {
        return
      }
      this.downLoading = true
      exportPersonTrainingRecord({ id: row.id }).then(async res => {
        if (!blobValidate(res)) {
          const rspObj = JSON.parse(await res.text())
          this.$message.error(rspObj.msg || rspObj.message || '导出失败')
          return
        }
        const blob = new Blob([res], { type: 'application/msword' });
        this.$download.saveAs(blob, '人员培训与考核记录.docx')
      }).catch(() => {}).finally(() => {
        this.downLoading = false
      })
    },
    // å¹´åº¦è®¡åˆ’明细表-多选
@@ -744,10 +823,13 @@
            if (res.code == 200) {
              this.$message.success('删除成功');
              this.getInDetailPlan(this.currentRowId);
            } else {
              this.$message.error(res.msg || '删除失败')
            }
          }).catch(() => {
            this.$message.error('删除失败')
          });
        }).catch(() => {
          this.$message.warning('取消删除');
        });
      } else {
        this.$message.warning('请选择需要删除的数据')
@@ -755,18 +837,22 @@
    },
    handleSizeChange(val) {
      this.inDetailPagination.size = val;
      this.getInDetailPlan(this.currentChangeRow.id)
      this.getInDetailPlan(this.currentRowId)
    },
    handleCurrentChange(val) {
      this.inDetailPagination.current = val;
      this.getInDetailPlan(this.currentChangeRow.id)
      this.getInDetailPlan(this.currentRowId)
    },
    // èŽ·å–è´Ÿè´£äººä¿¡æ¯æŽ¥å£
    getUserList() {
      selectUserCondition().then(res => {
      selectUserCondition({ isCustom: 0 }).then(res => {
        if (res.code == 200) {
          this.responsibleOptions = res.data
          this.responsibleOptions = Array.isArray(res.data) ? res.data : []
        } else {
          this.responsibleOptions = []
        }
      }).catch(() => {
        this.responsibleOptions = []
      });
    }
  },
@@ -774,19 +860,21 @@
    // ç›‘听点击el-tree的数据,进行数据刷新
    departId: {
      handler(newId, oldId) {
        this.page.current = 1
        this.inDetailPagination.current = 1
        if (this.isDepartment) {
          this.getYearPlanList(newId);
        } else {
          this.clearPlanSelection()
          this.getInDetailPlan('')
        }
      }
    },
    currentRowId: {
      handler(newId, oldId) {
        if (newId) {
          console.log('newId', newId);
        if (newId && newId !== oldId) {
          this.inDetailPagination.current = 1
          this.getInDetailPlan(this.currentChangeRow.id)
          this.getInDetailPlan(newId)
        }
      }
    }
src/views/CNAS/personnel/personnelInfo/tabs/training-record.vue
@@ -11,11 +11,11 @@
            <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
              <span style="width: 48px;font-size: 14px;font-weight: 700;color: var(--lims-text-2);">姓名</span>
              <el-input v-model="trainingPagination.userName" class="search"
                        @keyup.enter="getPersonnelTraining(departId)"
                        @keyup.enter="refreshTable"
                        clearable placeholder="请输入" size="small"></el-input>
            </div>
            <div style="line-height: 30px;">
              <el-button size="small" type="primary" @click="getPersonnelTraining(departId)">查询</el-button>
              <el-button size="small" type="primary" @click="refreshTable">查询</el-button>
            </div>
          </div>
        </div>
@@ -27,7 +27,7 @@
      </div>
      <div :style="`width: ${isDepartment ? '49%' : '100%'};`">
        <div class="title">
          <span style="font-weight: bold">年度计划明细</span>
          <span style="font-weight: bold">{{ isDepartment ? '年度计划明细' : '培训记录明细' }}</span>
        </div>
        <div style="display: flex;justify-content: space-between;">
          <el-form :model="searchForm" ref="searchForm" size="small" :inline="true">
@@ -37,14 +37,14 @@
                clearable
                format="YYYY"
                placeholder="选择年"
                @change="queryPersonnelDetailsPage(currentChangeRow.userId)"
                @change="searchDetails"
                size="small"
                type="year"
                value-format="YYYY">
              </el-date-picker>
            </el-form-item>
            <el-form-item>
              <el-button size="small" type="primary" @click="queryPersonnelDetailsPage(currentChangeRow.userId)">查询</el-button>
              <el-button size="small" type="primary" @click="searchDetails">查询</el-button>
            </el-form-item>
          </el-form>
          <div>
@@ -62,6 +62,7 @@
<script>
import limsTable from "@/components/Table/lims-table.vue";
import { blobValidate } from "@/utils/ruoyi";
import {
  exportTrainingRecord,
  queryPersonnelDetails,
@@ -107,7 +108,7 @@
          width: '100'
        }, {
          label: '入单位时间',
          prop: 'unitTime',
          prop: 'unitTimeSting',
          width: '150'
        },
        // {
@@ -166,24 +167,53 @@
    };
  },
  mounted() {
    this.getPersonnelTraining(this.departId);
    if (this.isDepartment) {
      this.getPersonnelTraining(this.departId);
    } else {
      this.queryPersonnelDetailsPage(this.departId);
    }
  },
  methods: {
    // æŸ¥è¯¢
    refreshTable() {
      this.trainingPagination.current = 1;
      this.getPersonnelTraining(this.departId);
    },
    // æ˜Žç»†æŸ¥è¯¢ï¼ˆå¹´ä»½/人员变化时回到第一页)
    searchDetails() {
      this.trainingPersonPagination.current = 1;
      const userId = this.isDepartment
        ? (this.currentChangeRow && this.currentChangeRow.userId)
        : this.departId;
      this.queryPersonnelDetailsPage(userId);
    },
    // åŸ¹è®­è®°å½•导出
    openDownloadDia (row) {
      let date = this.searchForm.trainingDate
      if (!date) {
        date = this.$moment().format('YYYY')
    openDownloadDia(row) {
      const userId = this.isDepartment ? (row && row.userId) : this.departId;
      if (!userId) {
        this.$message.warning('请先选择需要导出的人员');
        return;
      }
      exportTrainingRecord({userId: row.userId, trainingDate: date}).then(res => {
        this.outLoading = false
        const blob = new Blob([res],{ type: 'application/msword' });
        this.$download.saveAs(blob, '培训记录导出.docx');
      })
      if (this.outLoading) {
        return;
      }
      let date = this.searchForm.trainingDate;
      if (!date) {
        date = this.$moment().format('YYYY');
      }
      this.outLoading = true;
      exportTrainingRecord({userId: userId, trainingDate: date}).then(async res => {
        if (!blobValidate(res)) {
          const rspObj = JSON.parse(await res.text());
          this.$message.error(rspObj.msg || rspObj.message || '导出失败');
          return;
        }
        const blob = new Blob([res], {type: 'application/msword'});
        const personName = this.isDepartment && row && row.name ? row.name + '-' : '';
        this.$download.saveAs(blob, personName + date + '培训记录.docx');
      }).catch(() => {}).finally(() => {
        this.outLoading = false;
      });
    },
    // èŽ·å–å®žéªŒå®¤-培训计划列表信息
    getPersonnelTraining(departId) {
@@ -195,25 +225,38 @@
      }
      this.trainingLoading = true
      trainingSelectTrainingRecord(params).then(res => {
        this.trainingLoading = false
        this.trainingTableData = res.data.records;
        this.trainingPagination.total = res.data.total;
        this.trainingTableData = (res.data && res.data.records) || [];
        this.trainingPagination.total = (res.data && res.data.total) || 0;
        if (this.trainingTableData.length > 0) {
          this.$refs.trainingTableData.setCurrentRow(this.trainingTableData[0])
          this.currentChangeRow = this.trainingTableData[0];
          this.$refs.trainingTableData && this.$refs.trainingTableData.setCurrent(this.trainingTableData[0]);
        } else {
          this.currentChangeRow = '';
          this.trainingPersonTableData = [];
          this.trainingPersonPagination.current = 1;
          this.trainingPersonPagination.total = 0;
        }
      }).catch(err => {
      }).catch(() => {}).finally(() => {
        this.trainingLoading = false
      })
    },
    // èŽ·å–ä¸ªäºº-培训计划列表信息
    // åˆ‡æ¢äººå‘˜
    currentChange(row) {
      this.currentChangeRow = row
      if (row === null) {
        row = this.trainingTableData[0]
      this.currentChangeRow = row || '';
      this.trainingPersonPagination.current = 1;
      if (!row) {
        this.trainingPersonTableData = [];
        this.trainingPersonPagination.total = 0;
        return;
      }
      this.queryPersonnelDetailsPage(row.userId)
    },
    queryPersonnelDetailsPage(userId) {
      if (!userId) {
        this.trainingPersonTableData = [];
        this.trainingPersonPagination.total = 0;
        return;
      }
      if (this.searchForm.trainingDate === null) {
        this.searchForm.trainingDate = ''
      }
@@ -225,30 +268,37 @@
      }
      this.trainingPersonLoading = true
      queryPersonnelDetails(params).then(res => {
        this.trainingPersonLoading = false
        this.trainingPersonTableData = res.data.records;
        this.trainingPersonPagination.total = res.data.total;
      }).catch(err => {
        this.trainingPersonTableData = (res.data && res.data.records) || [];
        this.trainingPersonPagination.total = (res.data && res.data.total) || 0;
      }).catch(() => {}).finally(() => {
        this.trainingPersonLoading = false
      })
    },
    // åˆ†é¡µ
    pagination(page) {
      this.trainingPagination.size = page.limit;
      this.trainingPagination.current = page.page;
      this.getPersonnelTraining(this.departId);
    },
    // åˆ†é¡µ
    pagination1(page) {
      this.trainingPersonPagination.size = page.limit;
      this.queryPersonnelDetailsPage(this.currentChangeRow.userId);
      this.trainingPersonPagination.current = page.page;
      const userId = this.isDepartment
        ? (this.currentChangeRow && this.currentChangeRow.userId)
        : this.departId;
      this.queryPersonnelDetailsPage(userId);
    },
  },
  watch: {
    departId: {
      handler(newId, oldId) {
        if (this.isDepartment) {
          this.trainingPagination.current = 1;
          this.getPersonnelTraining(newId);
        } else {
          this.currentChangeRow = '';
          this.trainingPersonPagination.current = 1;
          this.queryPersonnelDetailsPage(newId)
        }
      }
src/views/CNAS/resourceDemand/device/component/equipmentMaintenancePlan.vue
@@ -74,6 +74,17 @@
        <el-table-column label="备注" min-width="140" show-overflow-tooltip>
          <template #default="scope">{{ scope.row.recordRemark || '—' }}</template>
        </el-table-column>
        <el-table-column label="附件" min-width="130">
          <template #default="scope">
            <el-link v-if="scope.row.attachmentCount > 0" type="primary" @click="handleAttachment(scope.row)">
              æŸ¥çœ‹é™„件({{ scope.row.attachmentCount }})
            </el-link>
            <el-link v-else-if="canUploadAttachment(scope.row)" type="primary" @click="handleAttachment(scope.row)">
              ä¸Šä¼ é™„ä»¶
            </el-link>
            <span v-else>—</span>
          </template>
        </el-table-column>
        <el-table-column align="center" fixed="right" label="操作" min-width="300">
          <template #default="scope">
            <el-button size="small" link type="primary"
@@ -95,6 +106,10 @@
        @current-change="handleCurrentChange1">
      </el-pagination>
    </div>
    <lims-attachment ref="rowAttachment" upload-url="/deviceMaintenancePlan/uploadAttachment"
      select-url="/deviceMaintenancePlan/selectAttachments" delete-url="/deviceMaintenancePlan/deleteAttachment"
      id-param="maintenancePlanDetailId" name-field="deviceName" title="保养记录附件"
      :readonly="attachmentReadonly" @closed="getTableList"></lims-attachment>
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible"
      :title="dialogTitle" width="880px" @close="resetForm">
      <el-form ref="modelForm" :model="form" :rules="rules" label-width="150px" size="small">
@@ -155,6 +170,13 @@
          <el-input v-model="form.reviewRemark" :rows="2" placeholder="可填写审核意见"
            type="textarea"></el-input>
        </el-form-item>
        <div class="form-section-title">附件资料</div>
        <el-form-item label-width="0">
          <lims-attachment inline upload-url="/deviceMaintenancePlan/uploadAttachment"
            select-url="/deviceMaintenancePlan/selectAttachments" delete-url="/deviceMaintenancePlan/deleteAttachment"
            id-param="maintenancePlanDetailId" name-field="deviceName" title="保养记录附件"
            :main-id="form.maintenancePlanDetailId" :readonly="!isEdit"></lims-attachment>
        </el-form-item>
        <div class="check-note">备注:正常、良好打“√”,不正常及否打“×”。</div>
        <div v-if="cycleNote" class="check-note">{{ cycleNote }}</div>
      </el-form>
@@ -182,6 +204,7 @@
  reviewDeviceMaintenance,
} from '@/api/cnas/resourceDemand/device.js'
import { selectUserCondition } from '@/api/system/user'
import LimsAttachment from '@/components/Attachment/lims-attachment.vue'
import dayjs from 'dayjs'
import { formatDateOnly } from './dateFormat'
import { saveBlobOrShowError } from '@/utils/saveBlob'
@@ -201,6 +224,7 @@
export default {
  name: "equipment-maintenance-plan",
  components: { LimsAttachment },
  props: {
    clickNodeVal: {
      type: Object,
@@ -238,6 +262,7 @@
      outLoading: false,
      reviewLoading: false,
      userOptions: [],
      attachmentReadonly: false,
    }
  },
  mounted() {
@@ -426,10 +451,11 @@
        });
      });
    },
    // å¯¼å‡º
    // å¯¼å‡ºæ•´ä»½ä¿å…»è®¡åˆ’
    handleDownOne() {
      this.outLoading = true
      exportDeviceMaintenancePlan({ maintenancePlanId: this.planInfo.maintenancePlanId }).then(res => {
      const params = { maintenancePlanId: this.planInfo.maintenancePlanId }
      exportDeviceMaintenancePlan(params).then(res => {
        this.outLoading = false
        saveBlobOrShowError(res, '设备保养计划表.docx', 'application/msword')
      }).catch(() => {
@@ -440,6 +466,15 @@
    handleReview(row) {
      this.handleForm('review', row)
    },
    // å·²å®¡æ ¸é€šè¿‡/不通过的行只读,其余可上传附件
    canUploadAttachment(row) {
      return !row || (row.recordStatus !== 2 && row.recordStatus !== 3)
    },
    // æ‰“开行附件弹窗:可编辑的行允许直接上传/删除,已审核的行只读
    handleAttachment(row) {
      this.attachmentReadonly = !this.canUploadAttachment(row)
      this.$refs.rowAttachment.open(row)
    },
    // æäº¤å®¡æ ¸ç»“果:1 é€šè¿‡ï¼Œ0 ä¸é€šè¿‡
    submitReview(status) {
      this.reviewLoading = true
src/views/CNAS/resourceDemand/device/component/record.vue
@@ -3,12 +3,20 @@
  <div>
    <div class="search">
      <el-form :model="search" ref="search" size="small" :inline="true">
        <el-form-item label="样品编号">
          <el-input v-model="search.sampleCode" clearable placeholder="请输入" size="small"
        <el-form-item label="设备名称">
          <el-input v-model="search.deviceName" clearable placeholder="请输入" size="small"
                    @keyup.enter="getTableList()"></el-input>
        </el-form-item>
        <el-form-item label="设备型号">
          <el-input v-model="search.specificationModel" clearable placeholder="请输入" size="small"
                    @keyup.enter="getTableList()"></el-input>
        </el-form-item>
        <el-form-item label="设备编号">
          <el-input v-model="search.managementNumber" clearable placeholder="请输入" size="small"
                    @keyup.enter="getTableList()"></el-input>
        </el-form-item>
        <el-form-item label="样品编号">
          <el-input v-model="search.sampleCode" clearable placeholder="请输入" size="small"
                    @keyup.enter="getTableList()"></el-input>
        </el-form-item>
        <el-form-item>
@@ -17,7 +25,7 @@
        </el-form-item>
      </el-form>
      <div>
        <el-button :loading="outLoading" size="small" type="primary" @click="openHandleOut">导 å‡º</el-button>
        <el-button :loading="outLoading" size="small" type="primary" @click="handleDown">导 å‡º</el-button>
      </div>
    </div>
    <div>
@@ -186,17 +194,6 @@
        <el-button v-if="operationType !== 'view'" type="primary" @click="saveRecord">ç¡® å®š</el-button>
      </span></template>
    </el-dialog>
    <el-dialog v-model="ratifyDialog" title="导出" width="30%" @close="ratifyDialog = false, exportDate = ''">
      <span>时间:
        <el-date-picker v-model="exportDate" format="YYYY-MM" placeholder="选择月份" size="small" style="width:100%"
          type="month" value-format="YYYY-MM">
        </el-date-picker>
      </span>
      <template #footer><span class="dialog-footer">
        <el-button @click="ratifyDialog = false, exportDate = ''">取 æ¶ˆ</el-button>
        <el-button :loading="outLoading" type="primary" @click="handleDown">导 å‡º</el-button>
      </span></template>
    </el-dialog>
  </div>
</template>
<script>
@@ -226,8 +223,8 @@
  orderCode: '',
  sampleCode: '',
  useDate: today(),
  useStartTime: '',
  useEndTime: '',
  useStartTime: '10:00:00',
  useEndTime: '19:30:00',
  temperature: '',
  humidity: '',
  useBefore: 1,
@@ -244,12 +241,12 @@
        size: 20,
        current: 1,
        total: 0,
        sampleCode: '',
        managementNumber: ''
        deviceName: '',
        specificationModel: '',
        managementNumber: '',
        sampleCode: ''
      },
      outLoading: false,
      ratifyDialog: false,
      exportDate: '',
      userList: [],
      form: defaultForm(),
      dialogVisible: false,
@@ -292,8 +289,10 @@
        size: 20,
        current: 1,
        total: 0,
        sampleCode: '',
        managementNumber: ''
        deviceName: '',
        specificationModel: '',
        managementNumber: '',
        sampleCode: ''
      }
      this.getTableList();
    },
@@ -311,17 +310,14 @@
        this.tableLoading = false
      })
    },
    // æ‰“开导出弹框选择编制人、批准人
    openHandleOut() {
      this.ratifyDialog = true
    },
    //导出
    //导出:按主表单筛选区当前条件导出
    handleDown() {
      this.outLoading = true
      exportUseRecord({
        sampleCode: this.search.sampleCode,
        deviceName: this.search.deviceName,
        specificationModel: this.search.specificationModel,
        managementNumber: this.search.managementNumber,
        exportDate: this.exportDate
        sampleCode: this.search.sampleCode
      }).then(res => {
        this.outLoading = false
        saveBlobOrShowError(res, '仪器设备运行记录.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
@@ -390,8 +386,8 @@
        managementNumber: row.managementNumber || '',
        orderCode: row.orderCode || '',
        useDate: start ? start.slice(0, 10) : '',
        useStartTime: start.length >= 19 ? start.slice(11, 19) : '',
        useEndTime: end.length >= 19 ? end.slice(11, 19) : '',
        useStartTime: start.length >= 19 ? start.slice(11, 19) : '10:00:00',
        useEndTime: end.length >= 19 ? end.slice(11, 19) : '19:30:00',
        temperature: row.temperature || '',
        humidity: row.humidity || '',
        usePersonId: row.usePersonId || '',
@@ -439,7 +435,7 @@
<style scoped>
.search {
  height: 46px;
  min-height: 46px;
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue
@@ -14,8 +14,9 @@
        </div>
      </div>
      <div style="line-height: 30px;">
        <el-button size="small" @click="$refs.importDialog.open()">导 å…¥</el-button>
        <el-button size="small" type="primary" :icon="$icon('el-icon-plus')"
                   @click="addDialogVisible = true, addInfo = {}, file = null">文件受控申请</el-button>
                   @click="addDialogVisible = true, addInfo = {}, file = null, fileList = []">文件受控申请</el-button>
      </div>
    </div>
    <div class="table">
@@ -71,7 +72,9 @@
          <div class="search_thing">
            <div class="search_label">上传附件:</div>
            <div class="search_input"><el-upload style="margin: 8px 0 0px 50px;" action="#" :auto-upload="false"
                :multiple="false" accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' :on-change="handleChangeUpload" v-if="addDialogVisible">
                :multiple="false" accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png'
                :file-list="fileList" :on-change="handleChangeUpload" :on-remove="handleRemoveUpload"
                :before-remove="beforeRemoveUpload" :on-preview="handlePreviewUpload" v-if="addDialogVisible">
                <el-button size="small" type="primary">上传附件</el-button>
              </el-upload></div>
          </div>
@@ -90,15 +93,25 @@
        </el-col>
        <el-col :span="12" style="margin-bottom: 16px;">
          <div class="search_thing">
            <div class="search_label">提交日期:</div>
            <div class="search_label">发布日期:</div>
            <div class="search_input">
              <el-date-picker v-model="addInfo.submitDate" type="date" size="small" placeholder="选择日期"
              <el-date-picker v-model="addInfo.publishDate" type="date" size="small" placeholder="选择日期"
                format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%;">
              </el-date-picker>
            </div>
          </div>
        </el-col>
        <el-col :span="12" style="margin-bottom: 16px;">
          <div class="search_thing">
            <div class="search_label">实施日期:</div>
            <div class="search_input">
              <el-date-picker v-model="addInfo.implementDate" type="date" size="small" placeholder="选择日期"
                format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%;">
              </el-date-picker>
            </div>
          </div>
        </el-col>
        <el-col :span="24" style="margin-bottom: 16px;">
          <div class="search_thing">
            <div class="search_label">说明:</div>
            <div class="search_input"><el-input size="small" placeholder="请输入" clearable v-model="addInfo.instructions"
@@ -116,7 +129,7 @@
        style="height: 90vh;overflow-y: auto;" />
    </el-dialog>
    <el-dialog title="审核" v-model="checkDialogVisible" width="1000px" top="5vh">
      <UpPdfStamp ref="UpPdfStamp" v-if="checkDialogVisible" @uploadPDF="uploadPDF" :isUpFile="false"></UpPdfStamp>
      <UpPdfStamp ref="UpPdfStamp" v-if="checkDialogVisible" @uploadPDF="uploadPDF" @uploadPDFErr="handlePdfError" :isUpFile="false"></UpPdfStamp>
      <template #footer><span  class="dialog-footer">
        <el-button @click="handleCheckSub('不通过')" :loading="noCheckLoading">不通过</el-button>
        <el-button type="primary" @click="handleCheckSub('通过')" :loading="checkLoading">通 è¿‡</el-button>
@@ -133,6 +146,16 @@
        <el-button type="primary" @click="handleCheck0(currentInfo)">ç¡® å®š</el-button>
      </span></template>
    </el-dialog>
    <import-dialog
      ref="importDialog"
      title="导入文件受控申请"
      :action="importAction"
      accept=".xls,.xlsx"
      template-url="/import-templates/文件受控申请导入模板.xlsx?v=1"
      template-name="文件受控申请导入模板.xlsx"
      :tips="['表头必须与模板完全一致,不能增删列或修改列名', '发布日期、实施日期格式:YYYY-MM-DD', '导入后状态为草稿,申请人为空,可在页面上编辑']"
      @success="refreshTable"
    />
  </div>
</template>
@@ -140,6 +163,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 {
  selectUserCondition,
} from "@/api/business/inspectionTask.js";
@@ -159,10 +183,14 @@
  components: {
    filePreview,
    UpPdfStamp,
    limsTable
    limsTable,
    ImportDialog
  },
  computed: {
    ...mapGetters(["nickName"]),
    importAction() {
      return this.javaApi + '/manageDocumentControlled/importManageDocumentControlled'
    }
  },
  data() {
    return {
@@ -177,6 +205,7 @@
      personList: [],
      fileType: [],
      file: null,
      fileList: [],
      currentInfo: {},
      checkLoading: false,
      noCheckLoading: false,
@@ -204,13 +233,16 @@
            }
          },
        },
        { label: "文件名称", prop: "name", minWidth: "160" },
        { label: "文件版本", prop: "version", width: "100px" },
        { label: "发布日期", prop: "publishDate", width: "110px" },
        { label: "实施日期", prop: "implementDate", width: "110px" },
        {
          label: "申请人",
          prop: "createUserName",
        },
        { label: "申请时间", prop: "createTime" },
        { label: "说明", prop: "instructions" },
        { label: "提交日期", prop: "submitDate" },
        { label: "责任人", prop: "dutyUserName" },
        {
          label: "申请状态", prop: "state", dataType: "tag",
@@ -247,8 +279,11 @@
                this.handleCheck(row);
              },
              disabled: (row) => {
                if (row.state == '通过') {
                  return true
                }
                if(row.dutyUserName != null && row.dutyUserName != "") {
                  return !row.dutyUserName.includes(this.nickName) || row.state == '通过'
                  return !row.dutyUserName.includes(this.nickName)
                }else {
                  return false
                }
@@ -350,76 +385,79 @@
      });
    },
    // æäº¤
    buildFormData() {
      const fd = new FormData()
      // æ–‡ä»¶ä¿¡æ¯ä¸­ raw æ‰æ˜¯çœŸçš„æ–‡ä»¶
      if (this.file) {
        fd.append("file", this.file.raw)
      }
      const fields = ['id', 'documentCode', 'dutyUser', 'type', 'name', 'version', 'writer',
        'publishDate', 'implementDate', 'instructions']
      fields.forEach((key) => {
        const value = this.addInfo[key]
        // ç©ºæ—¥æœŸ/空下拉不传,避免后端日期或数字字段解析失败
        if (value !== undefined && value !== null && value !== '') {
          fd.append(key, value)
        }
      })
      return fd
    },
    handleAdd() {
      if (!this.addInfo.documentCode) return this.$message({ type: 'error', message: "请输入编号" })
      if (!this.addInfo.id) {
        // æ–°å¢ž
        let fd = new FormData();
        //文件信息中raw才是真的文件
        if (this.file) {
          fd.append("file", this.file.raw);
      const isEdit = !!this.addInfo.id
      const request = isEdit ? doManageDocumentControlled : addManageDocumentControlled
      this.addLoading = true
      request(this.buildFormData()).then(res => {
        this.addLoading = false
        if (res.code == 200) {
          this.$message({
            type: 'success',
            message: isEdit ? '修改成功' : '添加成功'
          })
          this.refreshTable()
          this.addDialogVisible = false
        } else {
          this.$message({
            type: 'error',
            message: isEdit ? '修改失败' : '添加失败'
          })
        }
        for (let m in this.addInfo) {
          fd.append(m, this.addInfo[m])
        }
        this.addLoading = true
        addManageDocumentControlled(fd).then(res => {
          this.addLoading = false
          if (res.code == 200) {
            this.$message({
              type: 'success',
              message: '添加成功'
            })
            this.refreshTable()
            this.addDialogVisible = false
          } else {
            this.$message({
              type: 'error',
              message: '添加失败'
            })
          }
        })
      } else {
        // ä¿®æ”¹
        let { id, documentCode, dutyUser, type, name, version, writer, submitDate, instructions } = this.addInfo
        let fd = new FormData();
        //文件信息中raw才是真的文件
        if (this.file) {
          fd.append("file", this.file.raw);
        }
        fd.append("id", id);
        fd.append("documentCode", documentCode);
        fd.append("dutyUser", dutyUser);
        fd.append("type", type);
        fd.append("name", name);
        fd.append("version", version);
        fd.append("writer", writer);
        fd.append("submitDate", submitDate);
        fd.append("instructions", instructions);
        this.addLoading = true
        doManageDocumentControlled(fd).then(res => {
          this.addLoading = false
          if (res.code == 200) {
            this.refreshTable()
            this.addDialogVisible = false
          } else {
            this.$message({
              type: 'error',
              message: '添加失败'
            })
          }
        })
      }
      }).catch(() => {
        this.addLoading = false
      })
    },
    handleChangeUpload(file, fileLists) {
      this.file = file
      this.addInfo['name'] = file.name
    },
    // ç§»é™¤æ–°é€‰çš„附件后不再上传;原有附件不允许在这里删除,只能选择新附件替换
    handleRemoveUpload() {
      this.file = null
    },
    beforeRemoveUpload(file) {
      if (file && !file.raw) {
        this.$message.warning('选择新附件保存后会自动替换原附件')
        return false
      }
      return true
    },
    handlePreviewUpload(file) {
      if (file && file.url) {
        this.$download.saveAs(file.url, file.name)
      }
    },
    fileNameFromUrl(url) {
      return (url || '').replace(/^[0-9a-fA-F-]{36}_/, '')
    },
    // ç¼–辑
    handleUpdate(row) {
      this.title = '文件变更申请'
      this.addInfo = this.HaveJson(row)
      this.file = null
      // å›žæ˜¾å·²ä¸Šä¼ çš„附件,保存时未重新选择则保留原附件
      this.fileList = row.url
        ? [{ name: this.fileNameFromUrl(row.url), url: this.javaApi + '/word/' + row.url }]
        : []
      this.addDialogVisible = true
    },
    // æŸ¥çœ‹é™„ä»¶
@@ -492,9 +530,13 @@
      } else {
        this.noCheckLoading = true
      }
      this.addLoading = true
      this.$refs['UpPdfStamp'].generatePDF()
    },
    // é¢„览失败或没选文件时复位按钮,避免“通过”一直转圈
    handlePdfError() {
      this.checkLoading = false
      this.noCheckLoading = false
    },
    handleDelete(row) {
      this.$confirm("是否删除该条数据?", "提示", {
        confirmButtonText: "确定",
src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue
@@ -1,6 +1,9 @@
<template>
  <!-- å‘放回收记录 -->
  <div>
    <div style="font-size: 12px; line-height: 20px; color: var(--el-text-color-secondary); margin-bottom: 8px;">
      æç¤ºï¼šæœ¬é¡µæäº¤çš„发放、回收记录会自动同步到菜单【发放与回收记录】,无需再到该页面重复录入。
    </div>
    <div class="search">
      <div>
        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue
@@ -1,6 +1,9 @@
<template>
  <!-- æ–‡ä»¶å˜æ›´ç”³è¯· -->
  <div>
    <div style="font-size: 12px; line-height: 20px; color: var(--el-text-color-secondary); margin-bottom: 8px;">
      æç¤ºï¼šæœ¬é¡µæäº¤çš„变更申请会自动同步到菜单【文件修订申请审批记录】,审核通过后同步批准人。
    </div>
    <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;">
src/views/CNAS/systemManagement/documentControl/components/FileObsoletionRequest.vue
@@ -1,6 +1,9 @@
<template>
  <!-- æ–‡ä»¶ä½œåºŸç”³è¯· -->
  <div>
    <div style="font-size: 12px; line-height: 20px; color: var(--el-text-color-secondary); margin-bottom: 8px;">
      æç¤ºï¼šå®¡æ ¸é€šè¿‡åŽä¼šè‡ªåŠ¨ä»Žæ–‡ä»¶æ¸…å•ç§»é™¤ï¼Œå¹¶åœ¨èœå•ã€ä½œåºŸæ–‡ä»¶é”€æ¯è®°å½•ã€‘ç”Ÿæˆä¸€æ¡å¾…ç™»è®°è®°å½•ã€‚
    </div>
    <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;">
src/views/CNAS/systemManagement/documentRecords/cancellationRecord.vue
@@ -1,6 +1,9 @@
<template>
  <!-- ä½œåºŸæ–‡ä»¶é”€æ¯è®°å½• -->
  <div class="capacity-scope">
    <div style="font-size: 12px; line-height: 20px; color: var(--el-text-color-secondary); margin-bottom: 8px;">
      æç¤ºï¼šæ–‡ä»¶ä½œåºŸç”³è¯·å®¡æ ¸é€šè¿‡åŽä¼šè‡ªåŠ¨åœ¨æœ¬é¡µç”Ÿæˆå¾…ç™»è®°è®°å½•ï¼Œè¯·è¡¥å……é”€æ¯æ•°é‡ã€é”€æ¯åŽŸå› åŽæ‰¹å‡†ã€‚
    </div>
    <div class="search">
      <div>
        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
src/views/CNAS/systemManagement/documentRecords/distributionCollectionRecord.vue
@@ -1,6 +1,9 @@
<template>
  <!-- æ‰€æœ‰æ–‡ä»¶ï¼ˆå†…、外部文件)的发放与回收记录 -->
  <div class="capacity-scope">
    <div style="font-size: 12px; line-height: 20px; color: var(--el-text-color-secondary); margin-bottom: 8px;">
      æç¤ºï¼šæœ¬é¡µè®°å½•由【文件受控申请 â†’ å‘放回收记录】自动生成;补录历史数据可使用“导入”或“新增”。
    </div>
    <div class="search">
      <div>
        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
src/views/CNAS/systemManagement/documentRecords/revisionRecord.vue
@@ -1,6 +1,9 @@
<template>
  <!-- æ–‡ä»¶ä¿®è®¢ç”³è¯·å®¡æ‰¹è®°å½• -->
  <div class="capacity-scope">
    <div style="font-size: 12px; line-height: 20px; color: var(--el-text-color-secondary); margin-bottom: 8px;">
      æç¤ºï¼šæœ¬é¡µè®°å½•由【文件受控申请 â†’ æ–‡ä»¶å˜æ›´ç”³è¯· / æ–‡ä»¶ä½œåºŸç”³è¯·ã€‘自动生成,可在本页补充章节号、审批意见并打印归档。
    </div>
    <div class="search">
      <div>
        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">