src/views/business/inspectionTask/inspection.vue
@@ -1,28 +1,13 @@
<template>
  <div v-loading="loading" class="inspection" style="background-color: rgb(245, 247, 251);">
    <el-row class="title">
      <el-col v-if="insOrder.ifsOrderType!=='02wg'" :span="8" style="text-align: left">
        <el-form :inline="true" :model="otherForm" class="form-inline" label-width="50px"
          style="text-align: right; padding-top: 0; display: inline">
          <el-form-item label="温度:" style="margin-bottom: 0">
            <el-input v-model="otherForm.temperature" :disabled="state > 1" placeholder="" size="small"
              style="width: 90px; line-height: 60px" @change="(m) => subOtherForm(m, 'temperature')"></el-input>
            <span style="margin-left: 4px">℃</span>
          </el-form-item>
          <el-form-item label="湿度:" style="margin-bottom: 0">
            <el-input v-model="otherForm.humidity" :disabled="state > 1" placeholder="" size="small"
              style="width: 90px; line-height: 60px" @change="(m) => subOtherForm(m, 'humidity')"></el-input>
            <span style="margin-left: 4px">%</span>
          </el-form-item>
        </el-form>
      </el-col>
      <el-col :span="insOrder.ifsOrderType!=='02wg'?16:24" style="text-align: right">
      <el-col :span="24" style="text-align: right">
        <el-button v-if="insOrder.ifsOrderType && insOrder.ifsOrderType==='02wg'" size="small" type="primary" @click="showMaterialPropsDialog">IFS物料属性更新</el-button>
        <el-button size="small" type="primary" @click="refreshView">刷新</el-button>
        <el-button v-if="typeSource == 1" size="small" type="primary" @click="openPurchase">进货验证</el-button>
        <el-button v-if="state == 1 && [0,1].includes(Number(typeSource))" size="small" type="primary"
          @click="openUnPassDialog('add')">不合格处理</el-button>
        <el-button size="small" type="primary" @click="sampleVisible = true; uploadSample();">样品切换</el-button>
        <!-- <el-button size="small" type="primary" @click="sampleVisible = true; uploadSample();">样品切换</el-button> -->
        <!--        <el-button v-if="state == 1" size="small" type="primary" @click="taskVisible = true">任务切换</el-button>-->
        <el-button v-if="state == 1" size="small" type="primary" @click="addVerifyDia = true">提交</el-button>
        <!-- 复核 -->
@@ -92,7 +77,6 @@
          <el-descriptions-item label="零件号">{{insOrder.partNo}}</el-descriptions-item>
          <el-descriptions-item label="零件名称">{{insOrder.partDetail}}</el-descriptions-item>
          <el-descriptions-item label="批次号">{{ insOrder.lotBatchNo }}</el-descriptions-item>
          <el-descriptions-item label="样品型号" >{{currentSample.model}}</el-descriptions-item>
          <el-descriptions-item label="抽检数量">{{insOrder.testQuantity}}</el-descriptions-item>
          <el-descriptions-item label="厂家密度">{{supplierDensity}}</el-descriptions-item>
          <template v-if="insOrder.ifsOrderType && insOrder.ifsOrderType==='02wg'">
@@ -107,10 +91,10 @@
        </template>
        <template v-else>
          <el-descriptions-item label="委托编号">{{ insOrder.entrustCode }}</el-descriptions-item>
          <el-descriptions-item label="检测依据">{{ insOrder.sample || '-' }}</el-descriptions-item>
          <el-descriptions-item label="样品名称">{{ insOrder.sampleView || '-' }}</el-descriptions-item>
          <el-descriptions-item label="样品编号">{{ currentSample.sampleCode }}</el-descriptions-item>
          <el-descriptions-item label="样品名称">{{ currentSample.sample }}</el-descriptions-item>
          <el-descriptions-item label="样品数量">{{ sampleProduct.length }}</el-descriptions-item>
          <el-descriptions-item label="样品型号">{{ currentSample.model }}</el-descriptions-item>
          <el-descriptions-item label="样品数量">{{ insOrder.testQuantity }}</el-descriptions-item>
          <el-descriptions-item label="下发时间">{{ insOrder.sendTime }}</el-descriptions-item>
          <el-descriptions-item label="紧急程度">{{ insOrder.typeName }}</el-descriptions-item>
          <el-descriptions-item label="约定时间">{{ insOrder.appointed }}</el-descriptions-item>
@@ -190,6 +174,50 @@
            @click="dataAcquisitionEidtAble = !dataAcquisitionEidtAble">{{ dataAcquisitionEidtAble ? "关闭编辑" : "编辑数采"
            }}</el-button>
        </div>
      </div>
      <!-- 当前检验模板的检测条件与检验结论 -->
      <div class="template-panel">
        <el-form v-if="showTemplateConditions" :inline="true" :model="currentOtherForm" class="form-inline"
          size="small" label-width="84px">
          <el-form-item v-if="insOrder.ifsOrderType!=='02wg'" label="温度:">
            <el-input v-model="currentOtherForm.temperature" :disabled="state > 1" placeholder="" size="small"
              style="width: 90px" @change="(m) => subOtherForm(m, 'temperature')"></el-input>
            <span class="template-panel__unit">℃</span>
          </el-form-item>
          <el-form-item v-if="insOrder.ifsOrderType!=='02wg'" label="湿度:">
            <el-input v-model="currentOtherForm.humidity" :disabled="state > 1" placeholder="" size="small"
              style="width: 90px" @change="(m) => subOtherForm(m, 'humidity')"></el-input>
            <span class="template-panel__unit">%RH</span>
          </el-form-item>
          <el-form-item v-if="insOrder.ifsOrderType!=='02wg'" label="检测时间:">
            <el-date-picker v-model="currentOtherForm.detectionTime" :disabled="state > 1" type="date"
              value-format="yyyy-MM-dd" placeholder="请选择日期" size="small" style="width: 160px"
              @change="(m) => subOtherForm(m, 'detectionTime')"></el-date-picker>
          </el-form-item>
          <el-form-item v-if="insOrder.ifsOrderType!=='02wg'" label="检测地点:">
            <el-input v-model="currentOtherForm.detectionPlace" :disabled="state > 1" clearable placeholder="请输入检测地点"
              size="small" style="width: 200px" @change="(m) => subOtherForm(m, 'detectionPlace')"></el-input>
          </el-form-item>
          <el-form-item v-if="isDirectTemplateOrder()" label="设备:">
            <el-select v-model="currentOtherForm.deviceIds" :disabled="state != 1" multiple filterable clearable
              size="small" placeholder="请选择设备" style="width: 360px"
              @change="(m) => subOtherForm(m, 'deviceIds')">
              <el-option v-for="device in orderDeviceOptions" :key="device.id" :label="device.label" :value="device.id" />
            </el-select>
          </el-form-item>
        </el-form>
        <!-- 复核时才能选 -->
        <el-form :inline="true" :model="currentOtherForm"
          :class="['form-inline', { 'template-panel__conclusion--divided': showTemplateConditions }]"
          size="small" label-width="84px">
          <el-form-item label="检验结论:">
            <el-radio-group v-model="currentOtherForm.insResult" :disabled="state != 2"
              @change="(m) => subOtherForm(m, 'insResult')">
              <el-radio :label="1">合格</el-radio>
              <el-radio :label="0">不合格</el-radio>
            </el-radio-group>
          </el-form-item>
        </el-form>
      </div>
      <!-- 常规检验原始记录 -->
      <div id="nav" v-loading="tableLoading" class="center-box">
@@ -291,12 +319,12 @@
                        align-items: center;
                        width: 100%;
                      ">
                        <i class="el-icon-caret-left table_caret" style="width: 16px" @click="caretSample(-1)"></i>
                        <!-- <i class="el-icon-caret-left table_caret" style="width: 16px" @click="caretSample(-1)"></i> -->
                        <div
                          :style="`font-family:${n.v.ff} !important;overflow: hidden;white-space: nowrap;width: calc(100% - 32px);`">
                          {{ currentSample.sampleCode }}
                        </div>
                        <i class="el-icon-caret-right table_caret" style="width: 16px" @click="caretSample(1)"></i>
                        <!-- <i class="el-icon-caret-right table_caret" style="width: 16px" @click="caretSample(1)"></i> -->
                      </div>
                    </template>
                    <template v-else-if="
@@ -326,6 +354,7 @@
      <lims-table :tableData="tableData" :column="column" :page="page" :tableLoading="tableLoading"
        :height="'calc(100vh - 290px)'" @pagination="pagination" style="height: 100%; margin-top: 16px"></lims-table>
    </div>
    <!-- 样品切换已停用
    <el-drawer :size="550" :visible.sync="sampleVisible" title="样品切换">
      <el-table v-if="sampleVisible" ref="productTable" :current-row-key="currentKey" :data="sampleProduct"
                :header-cell-style="{ background: '#f8f8f9', color: '#515a6e' }" border
@@ -346,7 +375,9 @@
            <span>{{ scope.row.isLeave == 0 ? "否" : "是" }}</span>
          </template>
        </el-table-column>
    -->
        <!-- 检验单会有一个是否有配套样品字段控制显示 -->
    <!--
        <el-table-column v-if="insOrder.mating > 0" label="配套样品型号" min-width="130px" prop="joinModel"
          show-overflow-tooltip></el-table-column>
        <el-table-column v-if="insOrder.mating > 0" label="配套样品名称" min-width="130px" prop="joinName"
@@ -355,6 +386,7 @@
          show-overflow-tooltip></el-table-column>
      </el-table>
    </el-drawer>
    -->
    <el-drawer :size="500" :visible.sync="taskVisible" title="任务切换">
      <lims-table :tableData="tableData0" :column="column0" :page="page0" :tableLoading="tableLoading0"
        :height="'calc(100vh - 90px)'" :currentChange="handleChangeTask"></lims-table>
@@ -382,17 +414,6 @@
          <el-select v-model="verifyUser" clearable filterable placeholder="请选择" size="small" style="width: 100%">
            <el-option v-for="(item, i) in personList" :key="i" :label="item.label" :value="item.value">
            </el-option>
          </el-select>
        </div>
      </div>
      <div class="body" style="display: flex; padding: 10px; align-items: center">
        <div class="search_label" style="width: 150px">
          <span class="required-span">*</span>报告类型
        </div>
        <div class="search_input" style="width: 100%">
          <el-select v-model="reportType" clearable placeholder="请选择" size="small" style="width: 100%">
            <el-option label="检测报告" :value="0"></el-option>
            <el-option label="检验报告" :value="1"></el-option>
          </el-select>
        </div>
      </div>
@@ -579,6 +600,7 @@
  checkSubmitPlan,
  submitPlan,
  saveInsContext,
  saveTemplateContext,
  selectUserCondition,
  downFile,
  getFileList,
@@ -661,6 +683,7 @@
      comparisonList: [],
      excelMethodList: [],
      equipOptions: [],
      orderDeviceOptions: [],
      reviewLoading: false,
      reviewDia: false,
      noReason: "",
@@ -670,7 +693,6 @@
      widthList: [],
      addVerifyDia: false,
      verifyUser: null,
      reportType: null,
      personList: [],
      enumList: [],
      tableLoading: false,
@@ -681,10 +703,8 @@
        inspectionItem: 1,
        inspectionItemSubclass: "20(常温)",
      },
      otherForm: {
        humidity: null,
        temperature: null,
      },
      // 检测条件与检验结论按模板 id 分组:{ [templateId]: { temperature, humidity, detectionTime, detectionPlace, deviceIds, insResult } }
      otherForm: {},
      equipForm: {
        value0: null,
        code0: null,
@@ -696,6 +716,8 @@
      worker0: null,
      // 检验值按触发顺序串行保存,避免同一检验项的旧请求覆盖新请求。
      saveQueue: null,
      calculationRevision: 0,
      completedCalculationRevision: 0,
      wareLength: [],
      dataAcquisitionInfo: {},
      dataAcquisitionInfoNew: {},
@@ -827,7 +849,6 @@
      },
      tableLoading0: false,
      // 任务切换列表--结束
      isSplit: 0, // 是否是拆单过来的
    };
  },
  // 用于上传文件的信息
@@ -836,10 +857,27 @@
    action() {
      return this.javaApi + "/insOrderPlan/uploadFile";
    },
    // 当前模板对应的检测条件与检验结论
    currentOtherForm() {
      return (
        this.otherForm[this.currentTable] || {
          temperature: null,
          humidity: null,
          detectionTime: null,
          detectionPlace: null,
          deviceIds: [],
          insResult: null,
        }
      );
    },
    // 02wg 单没有温湿度;新聚直接绑定模板的单只有设备
    showTemplateConditions() {
      return this.insOrder.ifsOrderType !== "02wg" || this.isDirectTemplateOrder();
    },
  },
  created() {
    this.saveQueue = Promise.resolve();
    let { sonLaboratory, orderId, state, inspectorList, typeSource,isSplit } =
    let { sonLaboratory, orderId, state, inspectorList, typeSource } =
      this.$route.query;
    this.sonLaboratory = sonLaboratory;
    this.orderId = orderId;
@@ -847,7 +885,6 @@
    this.state = state;
    this.inspectorList = inspectorList;
    this.typeSource = typeSource;
    this.isSplit = isSplit;
  },
  mounted() {
    this.getTypeDicts(); // 获取紧急程度下拉框选项
@@ -911,6 +948,8 @@
              this.tableList = this.tableLists.filter(
                (m) => m.templateId == val1
              );
              // 模板集合可能变化,补齐检测条件槽位
              this.tableLists.forEach((m) => this.ensureOtherForm(m.templateId));
              // 对模板进行处理
              this.handleTableData();
            }
@@ -1097,18 +1136,27 @@
        this.sampleProduct = res.data.sampleProduct;
        this.currentSample = this.HaveJson(this.sampleProduct[0]);
        let insProduct = this.HaveJson(this.currentSample.insProduct);
        // 温度、湿度赋值
        this.otherForm = {
          temperature: this.insOrder.temperature
            ? this.insOrder.temperature
            : null,
          humidity: this.insOrder.humidity ? this.insOrder.humidity : null,
        };
        // 检测条件与检验结论赋值:按模板 id 分组回显
        this.otherForm = {};
        (this.insOrder.templateConditions || []).forEach((item) => {
          this.$set(this.otherForm, item.templateId, {
            temperature: item.temperature != null ? item.temperature : null,
            humidity: item.humidity != null ? item.humidity : null,
            detectionTime: item.detectionTime != null ? item.detectionTime : null,
            detectionPlace: item.detectionPlace != null ? item.detectionPlace : null,
            deviceIds: Array.isArray(item.deviceIds) ? [...item.deviceIds] : [],
            insResult: item.insResult != null ? item.insResult : null,
          });
        });
        this.tableLists.forEach((m) => this.ensureOtherForm(m.templateId));
        if (this.typeSource == "1") {
          this.getRawMaterialTag(this.currentSample.id); // 原材料的检验任务查询批数
          this.rawMaterialTag = "1";
        }
        this.getEquipOptions(1);
        if (this.sonLaboratory === "新聚") {
          this.loadDeviceOptions();
        }
        // 获取当前样品的检验项
        let list = await this.getCurrentProduct(this.currentSample.id, 0);
        this.currentSample.insProduct = this.HaveJson(list);
@@ -1606,12 +1654,36 @@
        this.worker0 = null;
      }
    },
    // 保存湿度、温度数据
    // 保证模板的检测条件与检验结论槽位存在
    ensureOtherForm(templateId) {
      if (templateId == null) return;
      if (!this.otherForm[templateId]) {
        this.$set(this.otherForm, templateId, {
          temperature: null,
          humidity: null,
          detectionTime: null,
          detectionPlace: null,
          deviceIds: [],
          insResult: null,
        });
      }
    },
    // 保存当前模板的检测条件与检验结论;备注是单据级,不带模板
    subOtherForm(m, type) {
      write({
        [type]: type === "remark" ? m : Number(m),
        id: this.insOrder.id,
      }).then((res) => {
      const payload = { id: this.insOrder.id };
      if (type === "remark") {
        payload.remark = m;
      } else {
        payload.templateId = this.currentTable;
        if (type === "deviceIds") {
          payload.deviceIds = Array.isArray(m) ? [...m] : [];
        } else if (type === "detectionTime" || type === "detectionPlace") {
          payload[type] = m === "" || m == null ? null : m;
        } else {
          payload[type] = Number(m);
        }
      }
      write(payload).then((res) => {
        this.$message.success("保存成功");
      });
    },
@@ -1778,6 +1850,8 @@
        this.tableList = null;
        this.tableList = [this.tableLists[0]];
        this.currentTable = this.tableLists[0].templateId;
        // 补齐每个模板的检测条件槽位
        this.tableLists.forEach((m) => this.ensureOtherForm(m.templateId));
        // 处理页面列表数据
        this.handleTableData();
      }
@@ -2003,6 +2077,7 @@
        }
      }
      try {
        const revision = ++this.calculationRevision;
        // 向 Worker 发送消息,开始处理逻辑
        this.worker.postMessage(
          JSON.stringify({
@@ -2017,6 +2092,7 @@
            getDataTypeId: this.getDataTypeId,
            modelType: this.sampleProduct[0].model,
            currentInsItem: n,
            revision: revision,
          })
        );
        // 先保存主线程中已经输入的原始值。Worker 完成计算后会再次入队保存计算结果。
@@ -2031,6 +2107,9 @@
      // 监听 Worker 返回的结果
      this.worker.onmessage = (event) => {
        const workerResult = JSON.parse(event.data);
        if (workerResult.revision != null && workerResult.revision < this.calculationRevision) {
          return;
        }
        this.result = workerResult;
        switch (workerResult.method) {
          case "saveInsContext":
@@ -2043,6 +2122,10 @@
              );
              this.param = resultValue.param;
              this.saveInsContext();
              this.completedCalculationRevision = Math.max(
                this.completedCalculationRevision,
                workerResult.revision || 0
              );
            });
            break;
          case "tableList":
@@ -2115,14 +2198,18 @@
    // 对输入值进行格式校验
    handleInput(n) {
      try {
        n.v.v = n.v.v.replace(/[^\d.^e>>≥≤<<断裂脆化\-/+]/g, "");
        n.v.v = n.v.v.replace(/\.{2,}/g, "."); //只保留第一个. 清除多余的
        n.v.v = n.v.v
        const v = n.v.v;
        // 能力范围已废弃,检验值类型不再作为判断依据。整串只由算式字符组成时才规范化格式,
        // 出现中文等其它字符即按文本处理原样保留,否则文字会被逐字删掉
        if (typeof v !== "string" || /[^\d.^e>>≥≤<<断裂脆化\-/+]/.test(v)) {
          return;
        }
        n.v.v = v
          .replace(/\.{2,}/g, ".") //只保留第一个. 清除多余的
          .replace(".", "$#$")
          .replace(/\./g, "")
          .replace("$#$", ".");
        n.v.v = n.v.v.replace(/\/{2,}/g, "/"); //只保留第一个/清除多余的
        n.v.v = n.v.v
          .replace("$#$", ".")
          .replace(/\/{2,}/g, "/") //只保留第一个/清除多余的
          .replace("/", "$#$")
          .replace(/\//g, "")
          .replace("$#$", "/");
@@ -2162,6 +2249,17 @@
    upInsReview(e) {
      if (e == 1) {
        // 通过
        // 每个模板都要有结论;全部合格才算整单合格
        const insResults = this.tableLists.map(
          (m) => (this.otherForm[m.templateId] || {}).insResult
        );
        const missingIndex = insResults.findIndex((r) => r !== 0 && r !== 1);
        if (missingIndex > -1) {
          this.$message.error(
            `请选择「${this.tableLists[missingIndex].templateName}」的检验结论`
          );
          return;
        }
        this.reviewLoading = true;
        verifyPlan({
          orderId: this.orderId,
@@ -2169,6 +2267,7 @@
          laboratory: this.sonLaboratory,
          tell: null,
          userId: this.checkUser,
          insResult: insResults.every((r) => r === 1) ? 1 : 0,
        }).then((res) => {
          if (res.code === 200) {
            this.$message.success("操作成功");
@@ -2214,17 +2313,15 @@
      this.$refs.viewManHourDia.showDialog(this.id, 99);
    },
    openAddVerifyDia() {
      this.reportType = null;
      this.addVerifyDia = true;
    },
    confirmSubmit(registerInsResults){
    confirmSubmit(){
      submitPlan({
        orderId: this.orderId,
        laboratory: this.sonLaboratory,
        verifyUser: this.verifyUser,
        entrustCode: this.insOrder.entrustCode,
        registerInsResults: this.reportType === 1 && registerInsResults,
        reportType: this.reportType
        registerInsResults: false,
      }).then((res) => {
        if (res.code === 200) {
          this.$message.success("操作成功");
@@ -2243,16 +2340,21 @@
        this.$message.error("请指定复核人员");
        return;
      }
      if (this.reportType === null || this.reportType === "") {
        this.$message.error("请选择报告类型");
        return;
      }
      if (!this.otherForm.humidity && this.insOrder.ifsOrderType!=='02wg') {
        this.$message.error("请输入湿度");
        return;
      }
      if (!this.otherForm.temperature && this.insOrder.ifsOrderType!=='02wg') {
        this.$message.error("请输入温度");
      const missing = [];
      this.tableLists.forEach((m) => {
        const form = this.otherForm[m.templateId] || {};
        if (this.insOrder.ifsOrderType !== "02wg") {
          if (!form.temperature) missing.push(`请填写「${m.templateName}」的温度`);
          if (!form.humidity) missing.push(`请填写「${m.templateName}」的湿度`);
          if (!form.detectionTime) missing.push(`请选择「${m.templateName}」的检测时间`);
          if (!form.detectionPlace) missing.push(`请填写「${m.templateName}」的检测地点`);
        }
        if (this.isDirectTemplateOrder() && (!form.deviceIds || form.deviceIds.length === 0)) {
          missing.push(`请选择「${m.templateName}」的设备`);
        }
      });
      if (missing.length > 0) {
        this.$message.error(missing[0]);
        return;
      }
      if (!this.insOrder.lotPartId && this.insOrder.ifsOrderType==='02wg') {
@@ -2264,27 +2366,11 @@
      checkSubmitPlan({
        orderId: this.orderId,
        laboratory: this.sonLaboratory,
        reportType: this.reportType,
      }).then((res) => {
        if (res.code === 200) {
          if (!res.data || res.data.errorMsg.length == 0) {
            this.submitLoading = true;
            //检验类型为原材料
            if(this.reportType === 1 && this.typeSource==1 && res.data.unInsOrderCount==0 && this.isSplit==1){
              const htmlStr = "是否登记IFS采购检验结果并移库?该操作会登记<span style='color:#ff4949'>同一零件同一订单的所有拆分批次</span>,请谨慎选择。"
              this.$confirm(htmlStr, '提示', {
                confirmButtonText: '是',
                cancelButtonText: '否',
                type: 'warning',
                dangerouslyUseHTMLString: true
              }).then(() => {
                this.confirmSubmit(true)
              }).catch(() => {
                this.confirmSubmit(false)
              });
            }else{
              this.confirmSubmit(true)
            }
            this.confirmSubmit();
          } else {
            let newData = [];
            const h = this.$createElement;
@@ -2320,7 +2406,7 @@
            })
              .then(() => {
                this.submitLoading = true;
                this.confirmSubmit(false)
                this.confirmSubmit()
                // submitPlan({
                //   orderId: this.orderId,
                //   laboratory: this.sonLaboratory,
@@ -2353,7 +2439,17 @@
      const param = {};
      this.tableList.forEach((a) => {
        param[a.templateId] = {};
        a.template.forEach((cell) => {
        // Worker 回传后 arr 是页面上的最新对象,template 仍可能保留计算前的旧值。
        const cells = a.arr && a.arr.length
          ? a.arr.reduce((all, row) => all.concat(row), [])
          : a.template;
        const resultHeader = cells.find((cell) =>
          cell.v && String(cell.v.v || "").trim() === "检验结果"
        );
        const exportRows = new Set(cells.filter((cell) =>
          cell.v && cell.v.ps && cell.v.ps.value === "导出值"
        ).map((cell) => cell.r));
        cells.forEach((cell) => {
          const isEditable =
            cell.v.ps != undefined &&
            typeof cell.v.ps.value === "string" &&
@@ -2361,10 +2457,12 @@
              cell.v.ps.value === "计算值" ||
              cell.v.ps.value === "最终值" ||
              cell.v.ps.value === "设备名称");
          if (!isEditable) {
          const isExportResult = resultHeader && exportRows.has(cell.r) && cell.c === resultHeader.c;
          if (!isEditable && !isExportResult) {
            return;
          }
          if (
            isEditable &&
            cell.v.ps.value.includes("检验值") &&
            (cell.v.v === "" || cell.v.v === null || cell.v.v === undefined)
          ) {
@@ -2410,7 +2508,14 @@
        const previousSave = this.saveQueue || Promise.resolve();
        this.saveQueue = previousSave
          .catch(() => undefined)
          .then(() => saveInsContext(payload))
          .then(() => this.isDirectTemplateOrder()
            ? saveTemplateContext({
                orderId: this.orderId,
                sampleId: this.currentSample.id,
                templateId: this.currentTable,
                values: param[this.currentTable] || {},
              })
            : saveInsContext(payload))
          .then(() => {
            this.$message.success("已保存");
          })
@@ -2433,6 +2538,20 @@
      }
    },
    async waitForPendingSaves() {
      const targetRevision = this.calculationRevision;
      if (this.completedCalculationRevision < targetRevision) {
        await new Promise((resolve) => {
          const startedAt = Date.now();
          const waitWorker = () => {
            if (this.completedCalculationRevision >= targetRevision || Date.now() - startedAt > 5000) {
              resolve();
              return;
            }
            setTimeout(waitWorker, 20);
          };
          waitWorker();
        });
      }
      if (this.saveQueue) {
        await this.saveQueue;
      }
@@ -2457,6 +2576,18 @@
      });
      // 选择、移除、清空均由 change 事件触发,立即保存。
      this.saveInsContext();
    },
    isDirectTemplateOrder() {
      return this.sonLaboratory === "新聚" && this.currentSample &&
        (this.currentSample.insProduct || []).some((product) => product.templateRowIndex != null);
    },
    loadDeviceOptions() {
      search({ status: 0 }).then((devices) => {
        this.orderDeviceOptions = (devices.data || []).map((device) => ({
          id: device.id,
          label: `${device.deviceName}--${device.managementNumber}`,
        }));
      });
    },
    getAuthorizedPerson() {
      selectUserCondition({ type: 1 }).then((res) => {
@@ -2629,6 +2760,37 @@
  text-align: left;
}
/* 当前检验模板的检测条件与检验结论 */
.template-panel {
  background-color: #f8f9fb;
  border: 1px solid #ebeef5;
  border-radius: 3px;
  padding: 16px 16px 4px;
  margin-bottom: 12px;
  text-align: left;
}
/* form-item 用 inline-flex 保证 label 与控件同行,不依赖行盒宽度推算 */
.template-panel>>>.el-form-item {
  display: inline-flex;
  align-items: center;
  margin-bottom: 12px;
}
.template-panel>>>.el-form-item__label {
  flex: none;
}
.template-panel__unit {
  margin-left: 4px;
  color: #606266;
}
.template-panel__conclusion--divided {
  border-top: 1px dashed #e4e7ed;
  padding-top: 12px;
}
.center {
  width: 100%;
  /* max-height: 580px; */