feat(device,supply,satisfaction): 设备计划改造、耗材采购调整与满意度照片上传

- 设备保养计划:审核/退回、计划与记录联动、只读台账与Word导出、年份下拉规则
- 设备核查计划:检查日期精确到日、审核完成时间口径、附件随记录提交
- 服务和供应品采购:存放位置手填、入库单价联动参考价格、移除目录维护
- 客户满意度:新增/编辑支持照片上传与预览
- 附件与文件预览:待上传照片预览、更多文件格式支持
已修改12个文件
1322 ■■■■■ 文件已修改
src/api/cnas/resourceDemand/device.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Attachment/lims-attachment.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Preview/filePreview.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/externalService/serviceAndSupplyPro/component/Edit.vue 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/externalService/serviceAndSupplyPro/component/Store.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/externalService/serviceAndSupplyPro/index.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/checkRecord.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/equipmentMaintenance.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/equipmentMaintenancePlan.vue 372 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/resourceDemand/device/component/usageAuthorization.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/cnas/resourceDemand/device.js
@@ -332,6 +332,14 @@
    params: query,
  });
}
//设备核查计划 年份列表(没有明细的空年份自动删除)
export function getExaminePlanYears(query) {
  return request({
    url: "/deviceExaminePlan/getPlanYears",
    method: "get",
    params: query,
  });
}
// 设备核查计划提交批准通知
export function submitRatifyDeviceExaminePlan(data) {
@@ -557,6 +565,14 @@
export function getMaintenancePlanByYear(query) {
  return request({
    url: "/deviceMaintenancePlan/getPlanByYear",
    method: "get",
    params: query,
  });
}
//设备保养计划 年份列表(没有明细的空年份自动删除)
export function getMaintenancePlanYears(query) {
  return request({
    url: "/deviceMaintenancePlan/getPlanYears",
    method: "get",
    params: query,
  });
@@ -1334,7 +1350,14 @@
    data: query,
  });
}
//保养记录 审核
export function reviewDeviceMaintenance(data) {
  return request({
    url: "/deviceMaintain/reviewDeviceMaintenance",
    method: "post",
    data: data,
  });
}
//使用记录 导出
export function exportDeviceMaintenance(query) {
  return request({
src/components/Attachment/lims-attachment.vue
@@ -5,7 +5,7 @@
        <div class="el-upload__tip">
          {{ readonly ? '仅可预览、下载附件' : ((currentId || deferUpload) ? tip : emptyTip) }}
        </div>
        <el-upload v-if="!readonly" :action="uploadAction" :headers="uploadHeader" multiple
        <el-upload v-if="!readonly" :action="uploadAction" :headers="uploadHeader" multiple :accept="accept"
          :show-file-list="false" :disabled="!canUpload" :auto-upload="!deferUpload"
          :on-change="handleFileChange" :on-success="handleUploadSuccess"
          :on-error="handleUploadError">
@@ -18,6 +18,7 @@
        <el-table-column label="操作" width="170">
          <template #default="scope">
            <template v-if="scope.row.pending">
              <el-button link type="primary" @click="previewPending(scope.row)">预览</el-button>
              <el-button link type="danger" @click="removePending(scope.row)">删除</el-button>
            </template>
            <template v-else>
@@ -32,7 +33,7 @@
    <el-dialog v-else :title="dialogTitle" v-model="visible" width="760px" append-to-body @close="$emit('closed')">
      <div class="attachment-header">
        <div class="el-upload__tip">{{ readonly ? '仅可预览、下载附件' : tip }}</div>
        <el-upload v-if="!readonly" :action="uploadAction" :headers="uploadHeader" multiple
        <el-upload v-if="!readonly" :action="uploadAction" :headers="uploadHeader" multiple :accept="accept"
          :show-file-list="false" :on-success="handleUploadSuccess" :on-error="handleUploadError">
          <el-button size="small" type="primary">上传附件</el-button>
        </el-upload>
@@ -52,7 +53,7 @@
        <el-button @click="visible = false">关 闭</el-button>
      </template>
    </el-dialog>
    <el-dialog title="预览附件" v-model="previewVisible" fullscreen append-to-body>
    <el-dialog title="预览附件" v-model="previewVisible" fullscreen append-to-body @close="releasePreviewBlob">
      <file-preview v-if="previewVisible" :file-url="previewUrl" :current-file="previewRow" />
    </el-dialog>
  </div>
@@ -147,6 +148,11 @@
    deferUpload: {
      type: Boolean,
      default: false
    },
    // 允许选择的文件类型,如 .jpg,.png;为空不限制
    accept: {
      type: String,
      default: ''
    }
  },
  watch: {
@@ -173,7 +179,8 @@
      currentRow: {},
      previewVisible: false,
      previewRow: {},
      previewUrl: ''
      previewUrl: '',
      previewBlobUrl: ''
    }
  },
  computed: {
@@ -201,6 +208,9 @@
    uploadAction() {
      return this.javaApi + this.uploadUrl + '?' + this.idParam + '=' + this.currentId
    }
  },
  beforeUnmount() {
    this.releasePreviewBlob()
  },
  methods: {
    // 打开附件管理弹窗
@@ -293,7 +303,26 @@
        this.loadList()
      })
    },
    // 待上传文件用本地临时地址预览,提交后再走正式地址
    previewPending(row) {
      const item = this.pendingFiles.find(pending => pending.uid === row.uid)
      if (!item) {
        return
      }
      this.releasePreviewBlob()
      this.previewBlobUrl = URL.createObjectURL(item.raw)
      this.previewRow = { fileName: item.fileName, fileUrl: this.previewBlobUrl }
      this.previewUrl = this.previewBlobUrl
      this.previewVisible = true
    },
    releasePreviewBlob() {
      if (this.previewBlobUrl) {
        URL.revokeObjectURL(this.previewBlobUrl)
        this.previewBlobUrl = ''
      }
    },
    previewFile(row) {
      this.releasePreviewBlob()
      this.previewRow = row
      this.previewUrl = this.fullUrl(row.fileUrl)
      this.previewVisible = true
src/components/Preview/filePreview.vue
@@ -110,8 +110,12 @@
    }
  },
  computed: {
    // blob 临时地址本身不带扩展名,待上传文件靠原始文件名判断类型
    currentFileName() {
      return (this.currentFile && this.currentFile.fileName) || ''
    },
    isImage() {
      return /\.(jpg|jpeg|png|gif)$/i.test(this.fileUrl)
      return /\.(jpg|jpeg|png|gif)$/i.test(this.fileUrl) || /\.(jpg|jpeg|png|gif)$/i.test(this.currentFileName)
    },
    imgUrl() {
      // 图片存在 /img 静态路径下,调用方传的可能是 /word 地址,这里统一换成 /img
@@ -155,7 +159,7 @@
      }
    },
    fileExt() {
      const match = this.fileUrl.match(/\.([a-zA-Z0-9]+)$/)
      const match = (this.currentFileName || this.fileUrl).match(/\.([a-zA-Z0-9]+)$/)
      return match ? match[1].toUpperCase() : '未知'
    },
    // 调用方传了 OnlyOffice 的配置才走文档服务,否则用浏览器内置阅读器
src/views/CNAS/externalService/serviceAndSupplyPro/component/Edit.vue
@@ -1,127 +1,125 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    title="添加耗材项目信息"
    width="70%"
    :title="model.id ? '编辑耗材' : '新增耗材'"
    width="1100px"
  >
    <el-form ref="modelForm" :model="model" :rules="rules" label-width="100px">
      <el-col :span="12">
        <el-form-item label="耗材类型" prop="consumablesType">
          <el-select v-model="model.consumablesType" placeholder="请选择耗材类型" size="small" style="width: 100%">
            <el-option v-for="(v, i) in dict.type.consumables_type" :key="i" :label="v.label" :value="v.value"></el-option>
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="货号">
          <el-input v-model="model.itemNumber" placeholder="请输入货号" size="small"></el-input>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="耗材名称">
          <el-input v-model="model.consumablesName" placeholder="请输入耗材名称" size="small"></el-input>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="规格">
          <el-input v-model="model.specifications" placeholder="请输入规格" size="small"></el-input>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="计量单位">
          <el-input v-model="model.unit" placeholder="请输入计量单位" size="small"></el-input>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="参考价格">
          <el-input-number v-model="model.referencePrice" :min="0" :precision="2" :step="0.01" placeholder="请输入参考价格"
                           size="small"></el-input-number>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="存放位置">
          <el-select v-model="model.contentId" placeholder="请选择存放位置" size="small" style="width: 100%">
            <el-option v-for="(v, i) in procurementSuppliesContentOptions" :key="i" :label="v.nodeName"
                       :value="v.id"></el-option>
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="负责人">
          <el-select v-model="model.personInCharge" placeholder="请选择负责人" size="small" style="width: 100%">
            <el-option v-for="(v, i) in userOptions" :key="i" :label="v.name" :value="v.id"></el-option>
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="库存上限">
          <el-input-number v-model="model.upperLimit" :min="0" :step="1" placeholder="请输入库存上限" size="small"></el-input-number>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="库存下限">
          <el-input-number v-model="model.lowerLimit" :min="0" :step="1" placeholder="请输入库存下限" size="small"></el-input-number>
        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="供应商">
          <el-select v-model="model.supplier" placeholder="请选择供应商" size="small" style="width: 100%">
            <el-option v-for="(v, i) in supplierOptions" :key="i" :label="v.supplierName"
                       :value="v.supplierManagementId"></el-option>
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="耗材图标">
          <div class="rows">
            <el-input v-model="model.consumablesIcon" placeholder="请输入耗材图标" size="small" style="width: 90%;"/>
            <el-upload
              ref="upload"
              :action="action"
              :headers="uploadHeader"
              :on-success="onSuccessIcon"
              :show-file-list="false"
              style="float: left; margin: 0 12px 0 20px;"
            >
              <template #trigger><el-button  class="uploadFile" size="small" type="primary">浏览</el-button></template>
            </el-upload>
          </div>
        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="耗材附件">
          <div class="rows">
            <el-input v-model="model.attachment" placeholder="请输入耗材附件" size="small" style="width: 90%;"/>
            <el-upload
              ref="upload"
              :action="action"
              :on-success="onSuccessFile"
              :show-file-list="false"
              :headers="uploadHeader"
              style="float: left; margin: 0 12px 0 20px;"
            >
              <template #trigger><el-button  class="uploadFile" size="small" type="primary">浏览</el-button></template>
            </el-upload>
          </div>
        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="备注">
          <el-input
            v-model="model.remark"
            :rows="2"
            placeholder="请输入备注"
            size="small"
            type="textarea"
          >
          </el-input>
        </el-form-item>
      </el-col>
      <div class="section-title">基本信息</div>
      <el-row :gutter="24">
        <el-col :span="12">
          <el-form-item label="耗材类型" prop="consumablesType">
            <el-select v-model="model.consumablesType" placeholder="请选择耗材类型" style="width: 100%">
              <el-option v-for="(v, i) in dict.type.consumables_type" :key="i" :label="v.label" :value="v.value"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="耗材名称">
            <el-input v-model="model.consumablesName" clearable placeholder="请输入耗材名称"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="货号">
            <el-input v-model="model.itemNumber" clearable placeholder="请输入货号"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格">
            <el-input v-model="model.specifications" clearable placeholder="请输入规格"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="计量单位">
            <el-input v-model="model.unit" clearable placeholder="请输入计量单位"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="参考价格">
            <el-input-number v-model="model.referencePrice" :min="0" :precision="2" :step="0.01" placeholder="请输入参考价格"
                             controls-position="right" style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="存放位置">
            <el-input v-model="model.storageLocation" clearable placeholder="请输入存放位置"></el-input>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="负责人">
            <el-select v-model="model.personInCharge" placeholder="请选择负责人" style="width: 100%">
              <el-option v-for="(v, i) in userOptions" :key="i" :label="v.name" :value="v.id"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <div class="section-title">库存与供应商</div>
      <el-row :gutter="24">
        <el-col :span="12">
          <el-form-item label="库存上限">
            <el-input-number v-model="model.upperLimit" :min="0" :step="1" placeholder="请输入库存上限"
                             controls-position="right" style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="库存下限">
            <el-input-number v-model="model.lowerLimit" :min="0" :step="1" placeholder="请输入库存下限"
                             controls-position="right" style="width: 100%"></el-input-number>
          </el-form-item>
        </el-col>
        <el-col :span="24">
          <el-form-item label="供应商">
            <el-select v-model="model.supplier" placeholder="请选择供应商" style="width: 100%">
              <el-option v-for="(v, i) in supplierOptions" :key="i" :label="v.supplierName"
                         :value="v.supplierManagementId"></el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <div class="section-title">图标与附件</div>
      <el-row :gutter="24">
        <el-col :span="24">
          <el-form-item label="耗材图标">
            <div class="upload-row">
              <el-input v-model="model.consumablesIcon" clearable placeholder="上传后自动填充,也可手动填写"></el-input>
              <el-upload
                :action="action"
                :headers="uploadHeader"
                :on-success="onSuccessIcon"
                :show-file-list="false"
              >
                <el-button :icon="$icon('el-icon-upload2')" type="primary" plain>上传</el-button>
              </el-upload>
            </div>
          </el-form-item>
        </el-col>
        <el-col :span="24">
          <el-form-item label="耗材附件">
            <div class="upload-row">
              <el-input v-model="model.attachment" clearable placeholder="上传后自动填充,也可手动填写"></el-input>
              <el-upload
                :action="action"
                :headers="uploadHeader"
                :on-success="onSuccessFile"
                :show-file-list="false"
              >
                <el-button :icon="$icon('el-icon-upload2')" type="primary" plain>上传</el-button>
              </el-upload>
            </div>
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item class="remark-item" label-width="0">
        <el-input v-model="model.remark" :rows="4" placeholder="请输入备注" type="textarea"></el-input>
      </el-form-item>
    </el-form>
    <template #footer><span  class="dialog-footer">
            <el-button @click="resetForm">取 消</el-button>
            <el-button type="primary" @click="submit">保 存</el-button>
        </span></template>
    <template #footer><span class="dialog-footer">
      <el-button @click="resetForm">取 消</el-button>
      <el-button type="primary" @click="submit">保 存</el-button>
    </span></template>
  </el-dialog>
</template>
@@ -130,7 +128,6 @@
  addProcurementSuppliesList,
  updateProcurementSuppliesList,
  selectSupplierManagementAll,
  getProcurementSuppliesContentsNodeNames,
  selectUserCondition
} from '@/api/cnas/externalService/serviceAndSupplyPro/serviceAndSupplyPro'
export default {
@@ -142,7 +139,6 @@
        consumablesIcon: undefined,
        attachment: undefined,
      },
      procurementSuppliesContentOptions: [],
      userOptions: [],
      supplierOptions: [],
      rules: {
@@ -163,7 +159,6 @@
    }
  },
  mounted() {
    this.findProcurementSuppliesContentOptions()
    this.findUserOptions()
    this.findSupplierOptions()
  },
@@ -178,16 +173,10 @@
        console.log("row", row)
        this.model = row
      } else {
        this.model = {}
        // 新增时挂在当前左树选中的目录节点下,存放位置改为手动填写
        this.model = { contentId: this.contentsId }
      }
      this.dialogVisible = true
    },
    findProcurementSuppliesContentOptions() {
      getProcurementSuppliesContentsNodeNames().then(res => {
        if (res.code === 200) {
          this.procurementSuppliesContentOptions = res.data
        }
      })
    },
    findUserOptions() {
      selectUserCondition().then(res => {
@@ -212,6 +201,11 @@
    submit() {
      this.$refs.modelForm.validate((valid) => {
        if (valid) {
          // 新增的耗材挂在左侧选中的目录节点下,未选节点会保存成列表里看不到的数据
          if (!this.model.id && !this.contentsId) {
            this.$message.warning('请先在左侧选择耗材目录节点')
            return
          }
          if (this.model.id) {
            updateProcurementSuppliesList(this.model).then(res => {
              if (res.code === 200) {
@@ -238,15 +232,45 @@
  margin: 6vh auto 50px !important;
}
:deep(.el-dialog__body) {
  max-height: 42em;
  max-height: 70vh;
  overflow-y: auto;
  padding: 24px 32px;
}
.rows {
  width: 100%;
.section-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 15px;
  font-weight: 600;
  color: var(--lims-text);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--lims-divider);
  margin: 8px 0 20px;
}
:deep(.el-form-item__content) {
  line-height: 39px;
.section-title:not(:first-child) {
  margin-top: 28px;
}
.section-title::before {
  content: '';
  display: inline-block;
  width: 3px;
  height: 15px;
  border-radius: 2px;
  background: var(--el-color-primary);
  margin-right: 8px;
}
.upload-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}
.upload-row .el-input {
  flex: 1;
}
:deep(.el-form-item) {
  margin-bottom: 20px;
}
.remark-item {
  margin-top: 28px;
}
</style>
src/views/CNAS/externalService/serviceAndSupplyPro/component/Store.vue
@@ -63,7 +63,8 @@
          <div>
            <div>
              <span>耗材名称:</span>
              <el-select v-model="formTwo.id" filterable placeholder="请选择耗材名称" size="small" style="width: 70%">
              <el-select v-model="formTwo.id" filterable placeholder="请选择耗材名称" size="small" style="width: 70%"
                @change="handleConsumableChange">
                <el-option v-for="(v, i) in consumableOptions" :key="i" :label="v.consumablesName"
                  :value="v.id"></el-option>
              </el-select>
@@ -296,6 +297,11 @@
      console.log(val)
      return this.options.find(item => item.value === val).label
    },
    // 选择耗材名称后,单价自动带出耗材列表里的参考价格(仍可手动修改)
    handleConsumableChange(id) {
      const item = this.consumableOptions.find(v => v.id === id)
      this.formTwo.unitPrice = item && item.referencePrice != null ? item.referencePrice : null
    },
    // 添加表格数据
    addTableData() {
      this.$nextTick()
src/views/CNAS/externalService/serviceAndSupplyPro/index.vue
@@ -33,21 +33,11 @@
        <el-tab-pane label="耗材入库" name="third" :lazy="true">
          <Store v-if="activeName == 'third'" :contentsId="contentsId"></Store>
        </el-tab-pane>
        <el-tab-pane label="目录维护" name="fourth" :lazy="true">
          <Contents
            v-if="activeName == 'fourth'"
            :id="contentsId"
            @contentsUpdate="contentsUpdate"
            :treeData="treeData"
            from="耗材树"
          ></Contents>
        </el-tab-pane>
      </el-tabs>
    </div>
  </div>
</template>
<script>
import Contents from "./component/contents.vue";
import Store from "./component/Store.vue";
import ConsumableOverview from "./component/ConsumableOverview.vue";
import ConsumableList from "./component/ConsumableList.vue"
@@ -74,7 +64,6 @@
    };
  },
  components: {
    Contents,
    Store,
    ConsumableOverview,
    ConsumableList
@@ -106,31 +95,6 @@
      findNodesWithFiber(this.treeData);
    },
    // 目录维护更新
    contentsUpdate(val, flag = false) {
      if (val) {
        this.getTreeData();
        this.expandedKeys = [];
        if (flag) {
          const findNodesWithFiber = (nodes) => {
            nodes.forEach((item) => {
              if (item.parentId == val) {
                this.expandedKeys.push(item.id);
              }
              if(item.children && item.children.length > 0) {
                findNodesWithFiber(item.children);
              }
            });
          };
          findNodesWithFiber(this.treeData);
          this.expandedKeys = this.expandedKeys.filter((item) => item !== val);
        } else {
          this.expandedKeys.push(val);
        }
      }else {
        this.getTreeData();
      }
    },
    // 查询所有目录
    getTreeData() {
      directoryListing().then((res) => {
src/views/CNAS/resourceDemand/device/component/checkRecord.vue
@@ -349,23 +349,16 @@
        return
      }
      const isMonthOnly = /^\d{4}-\d{2}$/.test(checkTime)
      const nowYear = nowMonth.slice(0, 4)
      const todayStr = `${nowMonth}-${String(now.getDate()).padStart(2, '0')}`
      const plannedText = isMonthOnly
        ? (this.planDueDate ? `${this.planDueDate}(计划月份 ${checkTime})` : `${checkTime}(系统计划只登记到月份)`)
        ? (this.planDueDate ? `${this.planDueDate}(计划月份 ${checkTime})` : `${checkTime}(系统登记只到月份)`)
        : checkTime
      const head = `<div class="er-head">该条记录的计划核查时间为 <b>${plannedText}</b>,当前审核日期是 <b>${todayStr}</b>,属于<b>提前审核</b>。</div>`
      const items = isMonthOnly
        ? [
          `本轮记录按实际审核月份归档:核查时间登记为 <b>${nowMonth}</b>(实际审核日期 ${todayStr}),并归入 <b>${nowYear} 年设备核查计划</b>(切到 ${nowYear} 年可看到,状态显示“通过”)。`,
          `系统会按设备的核查周期自动生成下一轮待核查任务(在下次核查日期所属年份的计划中),初始状态“未开始”——这是正常现象,不是审核没生效。`,
          `设备档案的最近核查日期更新为 <b>${todayStr}</b>,下次核查日期按核查周期自动计算。`
        ]
        : [
          `该记录保留原核查日期 <b>${checkTime}</b>,审核状态显示“通过”。`,
          `系统会按设备的核查周期自动生成下一轮待核查任务(在下次核查日期所属年份的计划中),初始状态“未开始”,这是正常现象。`,
          `设备档案的最近核查日期更新为 <b>${todayStr}</b>,下次核查日期按核查周期自动计算。`
        ]
      const head = `<div class="er-head">该条记录的计划检查日期为 <b>${plannedText}</b>,当前审核日期是 <b>${todayStr}</b>,属于<b>提前审核</b>。</div>`
      const items = [
        `本轮记录的计划检查日期保留为 <b>${plannedText}</b> 不变;审核通过后,「核查时间」记录为实际审核日期 <b>${todayStr}</b>。`,
        `设备档案的最近核查日期更新为 <b>${todayStr}</b>,下次核查日期按核查周期自动计算。`,
        `系统会按设备的核查周期自动生成下一轮待核查任务(在下次核查日期所属年份的计划中),初始状态“未开始”,这是正常现象,不是审核没生效。`
      ]
      const content = `${head}<div class="er-title">通过后系统会做三件事:</div>` +
        `<ol class="er-list">${items.map(item => `<li>${item}</li>`).join('')}</ol>` +
        `<div class="er-question">是否确认通过?</div>`
@@ -376,11 +369,11 @@
        dangerouslyUseHTMLString: true,
        customClass: 'early-review-message',
      }).then(() => {
        this.submitReview(statue, isMonthOnly)
        this.submitReview(statue)
      }).catch(() => {})
    },
    // 真正提交审核
    submitReview(statue, isMonthOnly = false) {
    submitReview(statue) {
      this.submitFormLoading = true
      const params = {
        planDetailsId: this.form.planDetailsId,
@@ -388,12 +381,7 @@
      }
      reviewExamineRecord(params).then(res => {
        if (res.code == 200) {
          if (statue === 1 && isMonthOnly) {
            const now = new Date()
            this.$message.success(`审核通过,本轮已归入 ${now.getFullYear()} 年设备核查计划`)
          } else {
            this.$message.success(statue === 1 ? '审核通过' : '已退回')
          }
          this.$message.success(statue === 1 ? '审核通过' : '审核不通过')
          this.resetForm()
        }
        this.submitFormLoading = false
src/views/CNAS/resourceDemand/device/component/equipmentMaintenance.vue
@@ -1,62 +1,110 @@
<template>
  <!--  设备维护保养(只读台账,记录由保养计划生成)-->
  <div>
    <div class="search">
      <el-button size="small" type="primary" @click="refreshTableList">刷新</el-button>
      <el-button size="small" type="primary" @click="openDia('add')">新 建</el-button>
      <el-button :loading="outLoading" size="small" type="primary" @click="openHandleOut">导 出</el-button>
    </div>
    <div>
      <lims-table :tableData="tableData" :column="column"
                  height="calc(100vh - 20em)" @pagination="pagination"
                  :page="page" :tableLoading="tableLoading"></lims-table>
      <el-table v-loading="tableLoading" :data="tableData" border height="calc(100vh - 20em)" style="width: 100%;">
        <el-table-column align="center" header-align="center" label="序号" prop="prop" type="index"
          width="60"></el-table-column>
        <el-table-column align="center" header-align="center" label="维护日期" min-width="110">
          <template #default="scope">{{ formatDate(scope.row.maintenanceDate) }}</template>
        </el-table-column>
        <el-table-column align="center" header-align="center" label="维护保养内容">
          <el-table-column v-for="item in checkColumns" :key="item.prop" align="center" header-align="center"
            :label="item.label" width="110">
            <template #default="scope">{{ scope.row[item.prop] || '—' }}</template>
          </el-table-column>
        </el-table-column>
        <el-table-column align="center" header-align="center" label="维护人" min-width="100">
          <template #default="scope">{{ scope.row.maintenanceUserName || '—' }}</template>
        </el-table-column>
        <el-table-column label="审核状态" min-width="100">
          <template #default="scope">
            <el-tag v-if="scope.row.recordStatus === 1" type="warning" disable-transitions>待审核</el-tag>
            <el-tag v-else-if="scope.row.recordStatus === 2" type="success" disable-transitions>通过</el-tag>
            <el-tag v-else-if="scope.row.recordStatus === 3" type="danger" disable-transitions>不通过</el-tag>
            <el-tag v-else type="info" disable-transitions>历史</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="备注" min-width="140" show-overflow-tooltip>
          <template #default="scope">{{ scope.row.remark || '—' }}</template>
        </el-table-column>
        <el-table-column align="center" fixed="right" label="操作" min-width="120">
          <template #default="scope">
            <el-button size="small" link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button v-if="scope.row.recordStatus === 1 || scope.row.recordStatus === 3" size="small" link
              type="danger" @click="handleDeleteClick(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination :current-page="pagination.current" :page-size="pagination.size"
        :page-sizes="[10, 20, 30, 50, 100]" background :total="pagination.total"
        layout="->,total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
        @current-change="handleCurrentChange">
      </el-pagination>
    </div>
    <el-dialog v-model="dialogVisible" title="仪器设备使用维护保养表" width="55%">
      <el-form ref="form" :model="form" label-width="130px" :rules="rules">
    <el-dialog :close-on-click-modal="false" v-model="dialogVisible" title="查看保养记录" width="880px">
      <el-form :model="form" label-width="150px" size="small">
        <el-row>
          <el-col :span="12">
            <el-form-item label="维护日期:" prop="maintenanceDate">
              <el-date-picker v-model="form.maintenanceDate" :disabled="operationType === 'view'"
                              format="YYYY-MM-DD" size="small" placeholder="选择日期" style="width:100%"
                              type="date" value-format="YYYY-MM-DD">
              </el-date-picker>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="维护内容:" prop="maintenanceContentArr">
              <el-checkbox-group v-if="qualificationList.length" v-model="form.maintenanceContentArr"
                                 :disabled="operationType === 'view'">
                <el-checkbox v-for="city in qualificationList" :key="city.label" :label="city.label"
                             :value="city.label"></el-checkbox>
              </el-checkbox-group>
              <el-input v-else v-model="form.maintenanceContentText" :disabled="operationType === 'view'"
                        size="small" placeholder="请输入维护内容" style="width: 100%"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintenanceUserId">
              <el-select v-model="form.maintenanceUserId"
                         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>
              </el-select>
            <el-form-item label="设备名称:">
              <el-input v-model="form.deviceName" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="备注:" prop="remark">
              <el-input v-model="form.remark" :disabled="operationType === 'view'" size="small"
                        style="width: 100%;"></el-input>
            <el-form-item label="设备编号:">
              <el-input v-model="form.managementNumber" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护日期:">
              <el-input v-model="form.maintenanceDate" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护人:">
              <el-input v-model="form.maintenanceUserName" disabled></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <div class="form-section-title">维护保养内容</div>
        <el-row>
          <el-col v-for="item in checkColumns" :key="item.prop" :span="12">
            <el-form-item :label="item.label + ':'">
              <el-input v-model="form[item.prop]" disabled></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="保养备注:">
          <el-input v-model="form.remark" :rows="2" disabled type="textarea"></el-input>
        </el-form-item>
        <el-form-item label="审核状态:">
          <el-tag v-if="form.recordStatus === 1" type="warning" disable-transitions>待审核</el-tag>
          <el-tag v-else-if="form.recordStatus === 2" type="success" disable-transitions>通过</el-tag>
          <el-tag v-else-if="form.recordStatus === 3" type="danger" disable-transitions>不通过</el-tag>
          <el-tag v-else type="info" disable-transitions>历史</el-tag>
        </el-form-item>
        <el-row v-if="form.reviewUserName || form.reviewTime">
          <el-col :span="12">
            <el-form-item label="审核人:">
              <el-input v-model="form.reviewUserName" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="审核时间:">
              <el-input v-model="form.reviewTime" disabled></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item v-if="form.reviewRemark" label="审核意见:">
          <el-input v-model="form.reviewRemark" :rows="2" disabled type="textarea"></el-input>
        </el-form-item>
      </el-form>
      <template #footer><span  class="dialog-footer">
        <el-button @click="closeDia">取 消</el-button>
        <el-button v-if="operationType !== 'view'" type="primary" @click="saveRecord" :loading="buttonLoading">确 定</el-button>
      <template #footer><span class="dialog-footer">
        <el-button @click="dialogVisible = false">关 闭</el-button>
      </span></template>
    </el-dialog>
  </div>
@@ -64,18 +112,28 @@
<script>
import {
  addDeviceMaintenance,
  deleteDeviceMaintenance, exportDeviceMaintenance,
  selectDeviceMaintenancePage
} from "@/api/cnas/resourceDemand/device";
import limsTable from "@/components/Table/lims-table.vue";
import {selectUserCondition} from "@/api/system/user";
import {saveBlobOrShowError} from '@/utils/saveBlob'
  deleteDeviceMaintenance,
  exportDeviceMaintenance,
  selectDeviceMaintenancePage,
} from '@/api/cnas/resourceDemand/device'
import { formatDateOnly, formatListDate } from './dateFormat'
import { saveBlobOrShowError } from '@/utils/saveBlob'
// 维护保养检查项,与仪器设备维护保养记录表一致
const CHECK_ITEMS = [
  { label: '外观是否良好', prop: 'appearanceGood' },
  { label: '是否可以正常使用', prop: 'normalUse' },
  { label: '是否清洁仪器表面', prop: 'surfaceClean' },
  { label: '标识是否完好', prop: 'labelIntact' },
  { label: '显示器灯是否正常', prop: 'displayLightNormal' },
  { label: '仪器是否生锈', prop: 'noRust' },
  { label: '校准是否正常', prop: 'calibrationNormal' },
  { label: '配件是否需要更换', prop: 'partsReplace' },
  { label: '维护结果是否正常', prop: 'resultNormal' },
]
export default {
  name: '',
  // import 引入的组件需要注入到对象中才能使用
  components: {limsTable},
  name: 'equipment-maintenance',
  props: {
    clickNodeVal: {
      type: Object,
@@ -85,170 +143,97 @@
    },
  },
  data() {
    // 这里存放数据
    return {
      search: {
        maintenanceDate: '',
      },
      outLoading: false,
      tableData: [],
      tableLoading: false,
      page: {
      pagination: {
        total: 0,
        size: 20,
        current: 1
        current: 1,
      },
      column: [
        { label: '维护日期', prop: 'maintenanceDate' },
        { label: '维护内容', prop: 'maintenanceContent' },
        { label: '维护人', prop: 'maintenanceUserName' },
        { label: '备注', prop: 'remark' },
        {
          dataType: 'action',
          label: '操作',
          operation: [
            {
              name: '编辑',
              type: 'text',
              clickFun: (row) => {
                this.openDia('edit', row);
              },
            },
            {
              name: '删除',
              type: 'text',
              clickFun: (row) => {
                this.handleDeleteClick(row);
              },
            },
          ]
        }
      ],
      checkColumns: CHECK_ITEMS.map(item => ({ ...item })),
      dialogVisible: false,
      form: {
        id: '',
        deviceId: '',
        maintenanceDate: '',
        maintenanceContentArr: [],
        maintenanceContent: '',
        maintenanceContentText: '',
        maintenanceUserId: '',
        remark: '',
      },
      operationType: '',
      buttonLoading: false,
      qualificationList: [],
      rules: {
        maintenanceDate: [{ required: true, message: '请选择维护日期', trigger: 'change' }],
        maintenanceContentArr: [{
          validator: (rule, value, callback) => {
            if (this.qualificationList.length) {
              if (!value || value.length === 0) {
                return callback(new Error('请选择维护内容'))
              }
            } else if (!this.form.maintenanceContentText || !this.form.maintenanceContentText.trim()) {
              return callback(new Error('请输入维护内容'))
            }
            callback()
          },
          trigger: 'change'
        }],
        maintenanceUserId: [{ required: true, message: '请选择维护人', trigger: 'change' }],
      },
      responsibleOptions: [],
      form: {},
    };
  },
  mounted() {
    this.refreshTableList()
  },
  // 方法集合
  methods: {
    // 点击刷新
    refreshTableList () {
      this.page.current = 1;
    refreshTableList() {
      this.pagination.current = 1
      this.getTableList()
    },
    // 分页切换
    pagination(page) {
      this.page.current = page.page
      this.page.size = page.limit
    formatDate(value) {
      return formatDateOnly(value) || '—'
    },
    // 分页
    handleSizeChange(val) {
      this.pagination.size = val
      this.getTableList()
    },
    getTableList () {
    // 分页
    handleCurrentChange(val) {
      this.pagination.current = val
      this.getTableList()
    },
    getTableList() {
      if (!this.clickNodeVal.value) {
        this.tableData = []
        this.pagination.total = 0
        return
      }
      this.tableLoading = true
      selectDeviceMaintenancePage({
        deviceId: this.clickNodeVal.value,
        ...this.page
        current: this.pagination.current,
        size: this.pagination.size,
      }).then(res => {
        this.tableLoading = false
        this.tableData = res.data.records
        this.page.total = res.data.total
      }).catch(err => {
        if (res.code == 200) {
          this.tableData = res.data.records || []
          this.pagination.total = res.data.total || 0
        }
      }).catch(() => {
        this.tableLoading = false
      })
    },
    // 打开操作弹框
    openDia (type, row) {
      this.operationType = type
      this.dialogVisible = true
      this.$nextTick(() => {
        this.$refs['form'].resetFields()
        this.form.id = ''
        if (this.operationType === 'edit') {
          this.form = {...row}
          this.form['maintenanceContentArr'] = this.form.maintenanceContent ? this.form.maintenanceContent.split(',') : []
          this.form['maintenanceContentText'] = this.form.maintenanceContent || ''
        }
      })
      this.getDictS()
      this.getUserList()
    },
    // 查询维护内容选择框内容
    getDictS () {
      // 定义实验室名称与维护内容类型的映射关系
      const labTypeMapping = {
        '老化室': 'aging_maintenance_content',
        '老化实验室': 'aging_maintenance_content',
        '电性能实验室': 'electrical_maintenance_content',
        '环境实验室': 'environmental_maintenance_content',
        '燃烧实验室': 'burn_maintenance_content',
        '混炼实验室': 'mixing_maintenance_content',
        '检测中心电性能实验室': 'center_electrical_maintenance_content',
        '恒温二': 'temperature2_maintenance_content',
        '恒温一': 'temperature1_maintenance_content',
        '化学实验室': 'chemistry_maintenance_content',
        '制样室': 'sample_maintenance_content',
        '低温实验室': 'hypothermia_maintenance_content',
      };
      // 取设备存放点名称作为映射键
      const name = this.clickNodeVal.storagePoint || '其他';
      // 根据映射关系获取对应的类型
      const type = labTypeMapping[name];
      if (!type) {
        this.qualificationList = [];
        return;
    // 查看保养记录(只读)
    handleView(row) {
      this.form = {
        deviceName: row.deviceName || '',
        managementNumber: row.managementNumber || '',
        maintenanceDate: formatDateOnly(row.maintenanceDate),
        maintenanceUserName: row.maintenanceUserName || '',
        remark: row.remark || '',
        reviewUserName: row.reviewUserName || '',
        reviewTime: formatListDate(row.reviewTime) || '',
        reviewRemark: row.reviewRemark || '',
        recordStatus: row.recordStatus,
      }
      this.getDicts(type).then((response) => {
        this.qualificationList = response.data.map((m) => {
          return {
            label: m.dictLabel,
            value: m.dictValue,
          };
        });
      });
      this.checkColumns.forEach(item => {
        this.form[item.prop] = row[item.prop] || ''
      })
      this.dialogVisible = true
    },
    openHandleOut () {
    openHandleOut() {
      if (!this.clickNodeVal.value) {
        this.$message.warning('请先选择设备')
        return
      }
      this.outLoading = true
      exportDeviceMaintenance({
        deviceId: this.clickNodeVal.value
      }).then(res => {
        this.outLoading = false
        saveBlobOrShowError(res, '设备维护保养.doc', 'application/msword')
        saveBlobOrShowError(res, '仪器设备维护保养记录.docx', 'application/msword')
      }).catch(() => {
        this.outLoading = false
      })
    },
    // 只允许删除待审核/不通过的记录(历史与审核通过记录留档)
    handleDeleteClick(row) {
      this.$confirm('此操作将永久删除该条保养记录, 是否继续?', '提示', {
        confirmButtonText: '确定',
@@ -260,7 +245,7 @@
            type: 'success',
            message: '删除成功!'
          });
          this.refreshTableList()
          this.getTableList()
        })
      }).catch(() => {
        this.$message({
@@ -269,46 +254,11 @@
        });
      });
    },
    // 提交新增和保存
    saveRecord () {
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.buttonLoading = true;
          this.form.maintenanceContent = this.qualificationList.length
            ? this.form.maintenanceContentArr.join(',')
            : (this.form.maintenanceContentText || '').trim()
          this.form.deviceId = this.clickNodeVal.value
          addDeviceMaintenance(this.form).then(res => {
            this.buttonLoading = false;
            this.$message.success('新增成功')
            this.closeDia()
            this.getTableList()
          }).catch(err => {
            this.buttonLoading = false;
          })
        }
      })
    },
    closeDia () {
      this.$refs['form'].resetFields();
      this.dialogVisible = false
      this.refreshTableList()
    },
    // 获取负责人信息接口
    getUserList() {
      selectUserCondition({type: 2}).then(res => {
        if (res.code == 200) {
          this.responsibleOptions = res.data
        }
      })
    },
  },
  watch: {
    // 监听点击el-tree的数据,进行数据刷新
    clickNodeVal(newVal) {
      if (newVal.value) {
        this.refreshTableList();
      }
      this.refreshTableList();
    },
  }
};
@@ -316,8 +266,19 @@
<style scoped>
.search {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 46px;
  text-align: right;
  margin-top: 10px;
}
.form-section-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--lims-text);
  margin: 4px 0 16px;
  padding-left: 8px;
  border-left: 4px solid var(--el-color-primary);
}
</style>
src/views/CNAS/resourceDemand/device/component/equipmentMaintenancePlan.vue
@@ -10,7 +10,7 @@
          </el-select>
        </div>
        <div>
          <el-button size="small" type="primary" @click="loadPlan">刷新</el-button>
          <el-button size="small" type="primary" @click="loadYearOptions(true)">刷新</el-button>
          <el-button size="small" type="primary" :loading="outLoading" @click="handleDownOne">导出</el-button>
        </div>
      </div>
@@ -47,25 +47,45 @@
        :row-class-name="tableRowClassName" :height="'calc(100vh - 29em)'" style="width: 100%;">
        <el-table-column align="center" header-align="center" label="序号" prop="prop" type="index"
          width="70"></el-table-column>
        <el-table-column label="设备名称及型号" min-width="180" show-overflow-tooltip>
        <el-table-column label="设备名称" min-width="150" prop="deviceName" show-overflow-tooltip></el-table-column>
        <el-table-column label="设备型号" min-width="130" prop="specificationModel" show-overflow-tooltip></el-table-column>
        <el-table-column label="设备编号" min-width="140" prop="deviceNumber"></el-table-column>
        <el-table-column label="存放点" min-width="120" prop="storagePoint"></el-table-column>
        <el-table-column align="center" header-align="center" label="审核日期" min-width="110">
          <template #default="scope">{{ formatReviewDate(scope.row) }}</template>
        </el-table-column>
        <el-table-column align="center" header-align="center" label="维护保养内容">
          <el-table-column v-for="item in checkColumns" :key="item.prop" align="center" header-align="center"
            :label="item.label" width="110">
            <template #default="scope">{{ scope.row[item.prop] || '—' }}</template>
          </el-table-column>
        </el-table-column>
        <el-table-column align="center" header-align="center" label="维护人" min-width="100">
          <template #default="scope">{{ scope.row.maintenanceUserName || '—' }}</template>
        </el-table-column>
        <el-table-column label="审核状态" min-width="100">
          <template #default="scope">
            {{ scope.row.deviceName }}{{ scope.row.specificationModel ? ' ' + scope.row.specificationModel : '' }}
            <el-tag v-if="!scope.row.recordStatus" type="info" disable-transitions>未开始</el-tag>
            <el-tag v-else-if="scope.row.recordStatus === 1" type="warning" disable-transitions>待审核</el-tag>
            <el-tag v-else-if="scope.row.recordStatus === 2" type="success" disable-transitions>通过</el-tag>
            <el-tag v-else-if="scope.row.recordStatus === 3" type="danger" disable-transitions>不通过</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="设备编号" min-width="140" prop="deviceNumber"></el-table-column>
        <el-table-column label="归属实验室" min-width="120" prop="storagePoint"></el-table-column>
        <el-table-column label="保养关键部位" min-width="150" prop="maintenanceSite" show-overflow-tooltip></el-table-column>
        <el-table-column label="保养内容" min-width="180" prop="maintenanceContent" show-overflow-tooltip></el-table-column>
        <el-table-column label="保养周期" min-width="100" prop="maintenanceIntervals"></el-table-column>
        <el-table-column label="计划保养时间" min-width="130" prop="maintenanceDate"
          :formatter="formatDateCell"></el-table-column>
        <el-table-column align="center" fixed="right" label="操作" min-width="160">
        <el-table-column label="备注" min-width="140" show-overflow-tooltip>
          <template #default="scope">{{ scope.row.recordRemark || '—' }}</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"
              :disabled="scope.row.recordStatus === 2 || scope.row.recordStatus === 3"
              @click="handleForm('edit', scope.row)">编辑</el-button>
            <el-button size="small" link type="primary" @click="handleForm('view', scope.row)">查看</el-button>
            <el-button size="small" link type="danger"
              @click="deleteRow(scope.row)">删除</el-button>
            <el-button size="small" link type="primary" :disabled="!scope.row.recordId"
              @click="handleForm('view', scope.row)">查看</el-button>
            <el-button v-if="scope.row.recordStatus === 0 || scope.row.recordStatus === 1"
              size="small" link type="primary" :disabled="!scope.row.maintenanceUserName"
              @click="handleReview(scope.row)">审核</el-button>
            <el-button v-if="scope.row.recordStatus !== 2"
              size="small" link type="danger" @click="deleteRow(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -76,55 +96,76 @@
      </el-pagination>
    </div>
    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible"
      title="设备保养计划明细" width="60%" @close="resetForm">
      <el-form ref="modelForm" :model="form" :rules="rules" label-width="130px">
      :title="dialogTitle" width="880px" @close="resetForm">
      <el-form ref="modelForm" :model="form" :rules="rules" label-width="150px" size="small">
        <el-row>
          <el-col :span="12">
            <el-form-item label="设备名称:">
              <el-input v-model="form.deviceName" disabled size="small"></el-input>
              <el-input v-model="form.deviceName" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="设备型号:">
              <el-input v-model="form.specificationModel" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="设备编号:">
              <el-input v-model="form.deviceNumber" disabled size="small"></el-input>
              <el-input v-model="form.deviceNumber" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="归属实验室:">
              <el-input v-model="form.storagePoint" disabled size="small"></el-input>
            <el-form-item label="存放点:">
              <el-input v-model="form.storagePoint" disabled></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="保养周期:">
              <el-input v-model="form.maintenanceIntervals" :disabled="operationType === 'view'" clearable
                size="small"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="保养关键部位:">
              <el-input v-model="form.maintenanceSite" :disabled="operationType === 'view'" clearable size="small"
                type="textarea"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="保养内容:">
              <el-input v-model="form.maintenanceContent" :disabled="operationType === 'view'" clearable size="small"
                type="textarea"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="计划保养时间:" prop="maintenanceDate">
              <el-date-picker v-model="form.maintenanceDate" :disabled="operationType === 'view'" clearable
                format="YYYY-MM-DD" placeholder="选择日期" size="small" style="width:100%" type="date"
            <el-form-item label="维护日期:" prop="maintenanceDate">
              <el-date-picker v-model="form.maintenanceDate" :disabled="!isEdit" clearable
                format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" type="date"
                value-format="YYYY-MM-DD"></el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintenanceUserId">
              <el-select v-model="form.maintenanceUserId" :disabled="!isEdit" clearable filterable
                placeholder="请选择" style="width: 100%">
                <el-option v-for="item in maintainerOptions" :key="item.id" :label="item.name"
                  :value="item.id"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <div class="form-section-title">维护保养内容</div>
        <el-row>
          <el-col v-for="item in checkColumns" :key="item.prop" :span="12">
            <el-form-item :label="item.label + ':'" :prop="item.prop">
              <el-radio-group v-model="form[item.prop]" :disabled="!isEdit">
                <el-radio value="√">√</el-radio>
                <el-radio value="×">×</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="保养备注:">
          <el-input v-model="form.remark" :disabled="!isEdit" :rows="2" placeholder="可填写保养备注"
            type="textarea"></el-input>
        </el-form-item>
        <el-form-item v-if="operationType === 'review'" label="审核备注:">
          <el-input v-model="form.reviewRemark" :rows="2" placeholder="可填写审核意见"
            type="textarea"></el-input>
        </el-form-item>
        <div class="check-note">备注:正常、良好打“√”,不正常及否打“×”。</div>
        <div v-if="cycleNote" class="check-note">{{ cycleNote }}</div>
      </el-form>
      <template #footer><span class="dialog-footer">
        <el-button @click="resetForm">取 消</el-button>
        <el-button :loading="submitFormLoading" type="primary" @click="submitForm"
          v-if="operationType !== 'view'">确 认</el-button>
        <template v-if="operationType === 'review'">
          <el-button :loading="reviewLoading" @click="submitReview(0)">不通过</el-button>
          <el-button :loading="reviewLoading" type="primary" @click="submitReview(1)">通 过</el-button>
        </template>
        <el-button v-else-if="operationType === 'edit'" :loading="submitFormLoading" type="primary"
          @click="submitForm">确 认</el-button>
      </span></template>
    </el-dialog>
  </div>
@@ -132,15 +173,32 @@
<script>
import {
  selectDeviceMaintenancePlanByPage,
  getMaintenancePlanYears,
  getMaintenancePlanByYear,
  pageMaintenancePlanDetail,
  updateMaintenancePlanDetail,
  deleteMaintenancePlanDetail,
  exportDeviceMaintenancePlan,
  addDeviceMaintenance,
  reviewDeviceMaintenance,
} from '@/api/cnas/resourceDemand/device.js'
import { selectUserCondition } from '@/api/system/user'
import dayjs from 'dayjs'
import { formatDateOnly } from './dateFormat'
import { saveBlobOrShowError } from '@/utils/saveBlob'
// 维护保养检查项,与仪器设备维护保养记录表一致
const CHECK_ITEMS = [
  { label: '外观是否良好', prop: 'appearanceGood' },
  { label: '是否可以正常使用', prop: 'normalUse' },
  { label: '是否清洁仪器表面', prop: 'surfaceClean' },
  { label: '标识是否完好', prop: 'labelIntact' },
  { label: '显示器灯是否正常', prop: 'displayLightNormal' },
  { label: '仪器是否生锈', prop: 'noRust' },
  { label: '校准是否正常', prop: 'calibrationNormal' },
  { label: '配件是否需要更换', prop: 'partsReplace' },
  { label: '维护结果是否正常', prop: 'resultNormal' },
]
export default {
  name: "equipment-maintenance-plan",
  props: {
@@ -152,6 +210,10 @@
    }
  },
  data() {
    const rules = {
      maintenanceDate: [{ required: true, message: '请选择维护日期', trigger: 'change' }],
      maintenanceUserId: [{ required: true, message: '请选择维护人', trigger: 'change' }],
    }
    return {
      planYear: '',
      yearOptions: [],
@@ -167,40 +229,38 @@
        current: 1,
        total: 0,
      },
      checkColumns: CHECK_ITEMS.map(item => ({ ...item })),
      dialogVisible: false,
      operationType: '',
      form: {},
      rules: {
        maintenanceDate: [{ required: true, message: '请选择计划保养时间', trigger: 'change' }],
      },
      rules,
      submitFormLoading: false,
      outLoading: false,
      reviewLoading: false,
      userOptions: [],
    }
  },
  mounted() {
    this.loadYearOptions()
    this.getUserList()
  },
  methods: {
    // 年份下拉选项:已有计划年份 + 当前年份
    loadYearOptions() {
    // 年份下拉:只列出有明细的年份(空年份计划由后端删除),默认显示本年度(本年度无计划时退回最新年份)
    loadYearOptions(preserveYear = false) {
      const currentYear = String(new Date().getFullYear())
      const finish = (years) => {
        if (years.indexOf(currentYear) === -1) {
          years.push(currentYear)
        let list = [...new Set(years.map(y => String(y)).filter(Boolean))]
        list.sort((a, b) => b.localeCompare(a))
        if (!list.length) {
          list = [currentYear]
        }
        this.yearOptions = [...new Set(years)].sort((a, b) => b.localeCompare(a))
        this.planYear = currentYear
        this.yearOptions = list
        const keep = preserveYear && this.planYear && list.includes(String(this.planYear))
        this.planYear = keep ? String(this.planYear) : (list.includes(currentYear) ? currentYear : list[0])
        this.loadPlan()
      }
      selectDeviceMaintenancePlanByPage({
        current: 1,
        size: 99,
      }).then(res => {
        let years = []
        if (res.code == 200) {
          years = (res.data.records || []).map(r => r.planYear).filter(Boolean)
        }
        finish(years)
      getMaintenancePlanYears().then(res => {
        finish(res.code == 200 ? (res.data || []) : [])
      }).catch(() => {
        finish([])
      })
@@ -251,9 +311,10 @@
        this.tableLoading = false
      })
    },
    // 到点需保养的行标红
    formatDateCell(row, column, cellValue) {
      return formatDateOnly(cellValue)
    // 未审核显示计划保养时间;审核通过后显示审核当天(与核查计划一致)
    formatReviewDate(row) {
      const date = row.reviewStatus === 1 ? row.reviewTime : row.maintenanceDate
      return formatDateOnly(date) || '—'
    },
    tableRowClassName({ row }) {
      return row.redFlag === 1 ? 'red-row' : ''
@@ -276,10 +337,10 @@
      this.pagination1.current = val
      this.getTableList()
    },
    // 打开明细弹框
    // 打开明细弹框:edit 填写/修改保养记录,view 查看,review 审核
    handleForm(type, row) {
      this.operationType = type
      this.form = this.HaveJson(row)
      this.form = this.buildForm(row)
      this.dialogVisible = true
      this.$nextTick(() => {
        if (this.$refs.modelForm) {
@@ -287,31 +348,60 @@
        }
      })
    },
    // 提交表单(只回传明细表字段)
    buildForm(row) {
      const form = {
        id: row.recordId || null,
        maintenancePlanDetailId: row.maintenancePlanDetailId,
        deviceId: row.deviceId,
        deviceName: row.deviceName,
        specificationModel: row.specificationModel,
        deviceNumber: row.deviceNumber,
        storagePoint: row.storagePoint,
        // 已有记录用记录日期;还没记录时默认今天,避免把计划日期当成实际保养日期
        maintenanceDate: row.recordId
          ? formatDateOnly(row.recordMaintenanceDate)
          : dayjs().format('YYYY-MM-DD'),
        maintenanceCycle: row.maintenanceCycle || row.maintenanceIntervals || '',
        maintenanceUserId: row.maintenanceUserId || '',
        maintenanceUserName: row.maintenanceUserName || '',
        remark: row.recordRemark || '',
        reviewRemark: '',
      }
      this.checkColumns.forEach(item => {
        form[item.prop] = row[item.prop] || ''
      })
      return form
    },
    // 保存保养记录(新增或修改后进入待审核)
    submitForm() {
      this.$refs.modelForm.validate(valid => {
        if (valid) {
          this.submitFormLoading = true
          const payload = {
            maintenancePlanDetailId: this.form.maintenancePlanDetailId,
            maintenancePlanId: this.form.maintenancePlanId,
            deviceId: this.form.deviceId,
            maintenanceSite: this.form.maintenanceSite,
            maintenanceContent: this.form.maintenanceContent,
            maintenanceIntervals: this.form.maintenanceIntervals,
            maintenanceDate: this.form.maintenanceDate,
          }
          updateMaintenancePlanDetail(payload).then(res => {
            if (res.code == 200) {
              this.$message.success('修改成功')
              this.dialogVisible = false
              this.getTableList()
            }
            this.submitFormLoading = false
          }).catch(() => {
            this.submitFormLoading = false
          })
        if (!valid) {
          return
        }
        this.submitFormLoading = true
        const payload = {
          maintenancePlanDetailId: this.form.maintenancePlanDetailId,
          deviceId: this.form.deviceId,
          maintenanceDate: this.form.maintenanceDate,
          maintenanceUserId: this.form.maintenanceUserId,
          remark: this.form.remark,
        }
        if (this.form.id) {
          payload.id = this.form.id
        }
        this.checkColumns.forEach(item => {
          payload[item.prop] = this.form[item.prop]
        })
        addDeviceMaintenance(payload).then(res => {
          this.submitFormLoading = false
          if (res.code == 200) {
            this.$message.success(this.form.id ? '修改成功' : '保存成功')
            this.dialogVisible = false
            this.loadPlan()
          }
        }).catch(() => {
          this.submitFormLoading = false
        })
      })
    },
    resetForm() {
@@ -319,14 +409,15 @@
    },
    // 明细-删除
    deleteRow(row) {
      this.$confirm('此操作将永久删除该条明细, 是否继续?', '提示', {
      this.$confirm('删除后,该行的保养记录(未审核或审核不通过)会一并删除,是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteMaintenancePlanDetail({ maintenancePlanDetailId: row.maintenancePlanDetailId }).then(() => {
          this.$message.success('删除成功!');
          this.getTableList()
          // 删完可能是该年份最后一条,重新取年份,空年份会一并清掉
          this.loadYearOptions(true)
        });
      }).catch(() => {
        this.$message({
@@ -344,12 +435,91 @@
      }).catch(() => {
        this.outLoading = false
      })
    }
    },
    // 打开保养记录审核弹框
    handleReview(row) {
      this.handleForm('review', row)
    },
    // 提交审核结果:1 通过,0 不通过
    submitReview(status) {
      this.reviewLoading = true
      reviewDeviceMaintenance({
        maintenancePlanDetailId: this.form.maintenancePlanDetailId,
        reviewStatus: status,
        reviewRemark: this.form.reviewRemark,
      }).then(res => {
        this.reviewLoading = false
        if (res.code == 200) {
          this.$message.success(status === 1 ? '审核通过' : '审核不通过')
          this.dialogVisible = false
          // 审核通过后明细会归到审核年份的计划,重新取年份避免停在不存在的年份
          this.loadYearOptions(true)
        }
      }).catch(() => {
        this.reviewLoading = false
      })
    },
    // 维护人下拉
    getUserList() {
      selectUserCondition({ type: 2 }).then(res => {
        if (res.code == 200) {
          this.userOptions = res.data || []
        }
      })
    },
  },
  computed: {
    // 是否已完成审核(通过/不通过)
    isAudited() {
      return this.planInfo.status === 0 || this.planInfo.status === 1;
    },
    // 仅编辑状态可填写表单
    isEdit() {
      return this.operationType === 'edit'
    },
    // 按设备档案的保养周期(月)生成底部说明
    cycleNote() {
      const months = parseInt((this.form && this.form.maintenanceCycle) || '', 10)
      if (!months || months <= 0) {
        return ''
      }
      if (months === 1) {
        return '此设备维护保养每月 1 次。'
      }
      if (months === 3) {
        return '此设备维护保养每季度 1 次。'
      }
      if (months === 6) {
        return '此设备维护保养每半年 1 次。'
      }
      if (months === 12) {
        return '此设备维护保养每年 1 次。'
      }
      if (months % 12 === 0) {
        return `此设备维护保养每 ${months / 12} 年 1 次。`
      }
      return `此设备维护保养每 ${months} 个月 1 次。`
    },
    dialogTitle() {
      if (this.operationType === 'view') {
        return '查看保养记录'
      }
      if (this.operationType === 'review') {
        return '保养记录审核'
      }
      return '编辑保养记录'
    },
    // 已选维护人不在当前人员列表时补一条,保证下拉能显示姓名
    maintainerOptions() {
      const list = this.userOptions.slice()
      const form = this.form || {}
      if (form.maintenanceUserId && !list.some(item => item.id === form.maintenanceUserId)) {
        list.unshift({
          id: form.maintenanceUserId,
          name: form.maintenanceUserName || String(form.maintenanceUserId),
        })
      }
      return list
    },
  },
}
@@ -407,6 +577,22 @@
  margin-bottom: 10px;
}
.form-section-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--lims-text);
  margin: 4px 0 16px;
  padding-left: 8px;
  border-left: 4px solid var(--el-color-primary);
}
.check-note {
  font-size: 13px;
  color: #909399;
  line-height: 22px;
  padding-left: 150px;
}
:deep(.red-row td) {
  background: #ffcccc !important;
}
src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue
@@ -52,8 +52,11 @@
        <el-table-column label="设备编号" min-width="170" prop="deviceNumber"></el-table-column>
        <el-table-column label="设备名称" min-width="180" prop="deviceName" show-overflow-tooltip></el-table-column>
        <el-table-column label="设备型号" min-width="120" prop="deviceModel" show-overflow-tooltip></el-table-column>
        <el-table-column label="核查时间" min-width="120" prop="checkTime"
          :formatter="formatCheckMonth"></el-table-column>
        <el-table-column label="核查时间" min-width="120">
          <template #default="scope">
            {{ scope.row.completeDate || formatCheckMonth(null, null, scope.row.checkTime) }}
          </template>
        </el-table-column>
        <el-table-column label="核查指标" min-width="140" prop="checkIndex"> </el-table-column>
        <el-table-column label="核查方法" min-width="140" prop="checkMethod" show-overflow-tooltip></el-table-column>
        <el-table-column label="结果如何判定" min-width="140" prop="howResults" show-overflow-tooltip></el-table-column>
@@ -90,7 +93,7 @@
              @click="handleRecord(scope.row)">审核</el-button>
            <el-button v-if="scope.row.recordStatus === 2 || scope.row.recordStatus === 3"
              size="small" link type="primary" @click="handleDown(scope.row)">导出</el-button>
            <el-button v-if="scope.row.recordStatus === 0 || scope.row.recordStatus === 1"
            <el-button v-if="scope.row.recordStatus !== 2"
              size="small" link type="danger" @click="deleteFun1(scope.row.planDetailsId)">删除</el-button>
          </template>
        </el-table-column>
@@ -239,7 +242,7 @@
            <span class="export-panel-label">设备名称</span>{{ downRow.deviceName || '—' }}
          </div>
          <div class="export-panel-item">
            <span class="export-panel-label">核查时间</span>{{ formatCheckMonth(null, null, downRow.checkTime) || '—' }}
            <span class="export-panel-label">核查时间</span>{{ downRow.completeDate || formatCheckMonth(null, null, downRow.checkTime) || '—' }}
          </div>
        </div>
        <div v-if="!canExport" class="export-panel-tip">
@@ -264,8 +267,8 @@
import CheckRecord from "./checkRecord.vue";
import LimsAttachment from '@/components/Attachment/lims-attachment.vue'
import {
  pageDeviceExaminePlan,
  getExaminePlanByYear,
  getExaminePlanYears,
  exportDeviceExaminePlanDetail,
  delDeviceExaminePlanDetail,
  pageDeviceExaminePlanDetail,
@@ -345,30 +348,24 @@
  },
  // 方法集合
  methods: {
    // 年份下拉只保留有计划明细的年份,空年份自动消失
    // 年份下拉只保留有计划明细的年份,空年份计划自动删除,默认显示本年度(本年度无计划时退回最新年份)
    loadYearOptions() {
      pageDeviceExaminePlan({
        current: 1,
        size: 99,
      }).then(res => {
        let years = []
        if (res.code == 200) {
          years = (res.data.records || []).map(r => r.planYear).filter(Boolean)
        }
        const options = [...new Set(years)].sort((a, b) => b.localeCompare(a))
        this.yearOptions = options
      const currentYear = String(new Date().getFullYear())
      const finish = (years) => {
        let options = [...new Set((years || []).map(y => String(y)).filter(Boolean))]
        options.sort((a, b) => b.localeCompare(a))
        if (options.length === 0) {
          this.planYear = ''
          this.planInfo = {}
          this.yearTableDetailData = []
          this.pagination1.total = 0
          return
          options = [currentYear]
        }
        if (options.indexOf(this.planYear) === -1) {
          const currentYear = String(new Date().getFullYear())
          this.planYear = options.indexOf(currentYear) > -1 ? currentYear : options[0]
        }
        this.yearOptions = options
        const keep = options.indexOf(String(this.planYear)) > -1
        this.planYear = keep ? String(this.planYear) : (options.includes(currentYear) ? currentYear : options[0])
        this.loadPlan()
      }
      getExaminePlanYears().then(res => {
        finish(res.code == 200 ? (res.data || []) : [])
      }).catch(() => {
        finish([])
      })
    },
    // 切换年份
@@ -504,7 +501,7 @@
    },
    // 明细-删除
    deleteFun1(id) {
      this.$confirm('删除后,该行未审核的核查记录及附件会一并删除;若该设备还有审核通过的核查记录,档案的最近核查日期将回退到最近一次完成记录;若没有,则清空档案的核查日期和核查周期。是否继续?', '提示', {
      this.$confirm('删除后,该行的核查记录(未审核或审核不通过)及附件会一并删除;若该设备还有审核通过的核查记录,档案的最近核查日期将回退到最近一次完成记录;若没有,则清空档案的核查日期和核查周期。是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
@@ -520,10 +517,15 @@
        });
      });
    },
    // 导出年度计划表
    // 导出年度计划表(Excel):有筛选条件导出筛选结果,没有筛选导出该年份全部
    downLoadPost() {
      exportDeviceExaminePlanDetail({ planId: this.planInfo.planId }).then(res => {
        this.saveDocx(res, '仪器设备期间核查计划表.docx')
      exportDeviceExaminePlanDetail({
        planId: this.planInfo.planId,
        deviceName: this.searchForm.deviceName,
        deviceNumber: this.searchForm.deviceNumber,
      }).then(res => {
        const planName = this.planInfo.planName || (this.planYear ? this.planYear + '年度设备核查计划' : '设备核查计划')
        this.saveDocx(res, planName + '.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
      }).catch(() => { })
    },
    // 本次是否有改动(含打开时恢复的草稿)
@@ -586,7 +588,7 @@
      this.downloadDialog = true
    },
    // 导出结果拦截: 后端返回错误 JSON 时提示错误, 不存成坏文件
    saveDocx(res, filename) {
    saveDocx(res, filename, mime = 'application/msword') {
      const reader = new FileReader();
      reader.readAsText(res, 'utf-8');
      reader.onload = () => {
@@ -596,11 +598,11 @@
          try { msg = JSON.parse(text).msg || msg; } catch (e) { }
          this.$message.error(msg);
        } else {
          this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename);
          this.$download.saveAs(new Blob([res], { type: mime }), filename);
        }
      };
      reader.onerror = () => {
        this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename);
        this.$download.saveAs(new Blob([res], { type: mime }), filename);
      };
    },
    // 核查记录导出
src/views/CNAS/resourceDemand/device/component/usageAuthorization.vue
@@ -90,7 +90,7 @@
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column label="设备名称" prop="label" width="190"></el-table-column>
        <el-table-column prop="value" label="设备编号" width="130"></el-table-column>
        <el-table-column prop="storagePoint" label="归属实验室"></el-table-column>
        <el-table-column prop="storagePoint" label="存放点"></el-table-column>
      </el-table>
      <template #footer><span  class="dialog-footer">
        <el-button @click="addEquipDia = false">取 消</el-button>
src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue
@@ -136,6 +136,17 @@
            </el-input>
          </td>
        </tr>
        <tr>
          <td class="td-title">
            <p>照片:</p>
          </td>
          <td class="td-info" colspan="5">
            <lims-attachment ref="photoAttachment" inline defer-upload accept=".jpg,.jpeg,.png,.gif"
              :main-id="form.clientSatisfactionId || ''" upload-url="/clientSatisfaction/uploadAttachment"
              select-url="/clientSatisfaction/selectAttachments" delete-url="/clientSatisfaction/deleteAttachment"
              id-param="clientSatisfactionId" tip="支持 jpg / png / gif 照片,可预览,提交时一并上传"></lims-attachment>
          </td>
        </tr>
      </table>
      <template #footer><span  class="dialog-footer">
        <el-button @click="closeFormDia">取 消</el-button>
@@ -151,10 +162,11 @@
  updateClientSatisfaction
} from '@/api/cnas/systemManagement/customerSatisfaction.js'
import { selectCompletedOrders } from '@/api/business/productOrder'
import limsAttachment from '@/components/Attachment/lims-attachment.vue'
export default {
  name: 'formDialog',
  // import 引入的组件需要注入到对象中才能使用
  components: {},
  components: { limsAttachment },
  data() {
    // 这里存放数据
    return {
@@ -287,24 +299,45 @@
      }
      if (this.operationType === 'add') {
        addClientSatisfaction(payload).then(res => {
          this.editLoad = false
          this.$message.success('提交成功')
          this.closeFormDia()
          // 后端返回保存后的记录(含自增id),用于把新增时暂存的照片传上去
          const savedId = res.data && res.data.clientSatisfactionId
          this.finishSave(savedId)
        }).catch(err => {
          console.log('err---', err);
          this.editLoad = false
        })
      } else {
        updateClientSatisfaction(payload).then(res => {
          this.editLoad = false
          this.$message.success('提交成功')
          this.closeFormDia()
        updateClientSatisfaction(payload).then(() => {
          this.finishSave(this.form.clientSatisfactionId)
        }).catch(err => {
          console.log('err---', err);
          this.editLoad = false
        })
      }
    },
    // 记录保存成功后上传暂存照片;照片上传失败不影响记录本身
    finishSave(clientSatisfactionId) {
      this.uploadPendingPhotos(clientSatisfactionId).then(() => {
        this.editLoad = false
        this.$message.success('提交成功')
        this.closeFormDia()
      }).catch(err => {
        console.log('照片上传失败', err)
        this.editLoad = false
        this.$message.warning('记录已保存,但照片上传失败,请回到列表用“附件”补充上传')
        this.closeFormDia()
      })
    },
    uploadPendingPhotos(clientSatisfactionId) {
      const attachment = this.$refs.photoAttachment
      if (!attachment || !attachment.pendingFiles.length) {
        return Promise.resolve()
      }
      if (!clientSatisfactionId) {
        return Promise.reject(new Error('未获取到记录id,无法上传照片'))
      }
      return attachment.flushPending(clientSatisfactionId)
    },
    closeFormDia() {
      this.formDia = false;
      this.$emit('closeFormDia')