gaoluyang
2026-09-20 b58f53769f004fb0ba6b5aedf55ef602850c7c97
src/pages/qualityManagement/finalInspection/add.vue
@@ -51,6 +51,7 @@
      </up-form-item>
      <up-form-item label="指标选择"
                    prop="testStandardId"
                    required
                    border-bottom>
        <up-input v-model="testStandardDisplay"
                  placeholder="请选择指标"
@@ -124,7 +125,13 @@
      <view class="inspection-items-container">
        <view class="steps-header">
          <text class="steps-title">检验项目</text>
          <text class="steps-count">共 {{ tableData.length }} 个项目</text>
          <view class="steps-header-right">
            <text class="steps-count">共 {{ tableData.length }} 个项目</text>
            <up-button type="primary"
                       size="mini"
                       text="追加检测项"
                       @click="addTempItem" />
          </view>
        </view>
        <view class="steps-list">
          <view v-for="(item, index) in tableData"
@@ -136,28 +143,106 @@
            <view class="step-content">
              <view class="step-row">
                <text class="step-label">指标:</text>
                <text class="step-value">{{ item.parameterItem }}</text>
                <up-input v-if="item.isTemp"
                          v-model="item.parameterItem"
                          placeholder="请输入指标名称"
                          border-bottom
                          class="step-input" />
                <text v-else
                      class="step-value">{{ item.parameterItem }}</text>
              </view>
              <view class="step-row">
                <text class="step-label">参数类型:</text>
                <view v-if="item.isTemp"
                      class="type-switch">
                  <text class="type-option"
                        :class="{ active: item.parameterType === TEXT_TYPE }"
                        @click="setTempType(item, TEXT_TYPE)">文字</text>
                  <text class="type-option"
                        :class="{ active: item.parameterType === NUMERIC_TYPE }"
                        @click="setTempType(item, NUMERIC_TYPE)">数字</text>
                </view>
                <text v-else
                      class="step-value">{{ isNumeric(item) ? "数字" : "文字" }}</text>
              </view>
              <!-- 数字型按上下限判定,把上下限摆出来才看得懂自动判定的依据 -->
              <template v-if="isNumeric(item)">
                <view class="step-row">
                  <text class="step-label">最大值:</text>
                  <up-input v-if="item.isTemp"
                            v-model="item.maxValue"
                            type="number"
                            placeholder="请输入最大值"
                            border-bottom
                            class="step-input" />
                  <text v-else
                        class="step-value">{{ item.maxValue }}</text>
                </view>
                <view class="step-row">
                  <text class="step-label">最小值:</text>
                  <up-input v-if="item.isTemp"
                            v-model="item.minValue"
                            type="number"
                            placeholder="请输入最小值"
                            border-bottom
                            class="step-input" />
                  <text v-else
                        class="step-value">{{ item.minValue }}</text>
                </view>
              </template>
              <view class="step-row">
                <text class="step-label">单位:</text>
                <text class="step-value">{{ item.unit }}</text>
                <up-input v-if="item.isTemp"
                          v-model="item.unit"
                          placeholder="请输入单位"
                          border-bottom
                          class="step-input" />
                <text v-else
                      class="step-value">{{ item.unit }}</text>
              </view>
              <view class="step-row">
                <text class="step-label">标准值:</text>
                <text class="step-value">{{ item.standardValue }}</text>
                <up-input v-if="item.isTemp"
                          v-model="item.standardValue"
                          placeholder="请输入标准值"
                          border-bottom
                          class="step-input" />
                <text v-else
                      class="step-value">{{ item.standardValue }}</text>
              </view>
              <view class="step-row">
                <text class="step-label">内控值:</text>
                <text class="step-value">{{ item.controlValue }}</text>
                <up-input v-if="item.isTemp"
                          v-model="item.controlValue"
                          placeholder="请输入内控值"
                          border-bottom
                          class="step-input" />
                <text v-else
                      class="step-value">{{ item.controlValue }}</text>
              </view>
              <view class="step-row">
                <text class="step-label">检验值:</text>
                <up-input v-model="item.testValue"
                          :type="isNumeric(item) ? 'number' : 'text'"
                          placeholder="请输入检验值"
                          clearable
                          border-bottom
                          class="step-input" />
              </view>
              <view class="step-row">
                <text class="step-label">判定:</text>
                <text class="step-value judge-text"
                      :class="itemPassClass(item)">
                  {{ judgeText(item) }}
                </text>
              </view>
            </view>
            <view v-if="item.isTemp"
                  class="delete-btn"
                  @click="removeTempItem(index)">
              <up-icon name="close"
                       size="14"
                       color="#ffffff" />
            </view>
          </view>
          <view v-if="tableData.length === 0"
@@ -301,7 +386,7 @@
</template>
<script setup>
  import { ref, computed, onMounted, nextTick } from "vue";
  import { ref, computed, onMounted, nextTick, watch } from "vue";
  import { onShow, onLoad } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import dayjs from "dayjs";
@@ -316,6 +401,13 @@
    list,
  } from "@/api/qualityManagement/materialInspection.js";
  import { userListNoPage } from "@/api/system/user.js";
  import {
    NUMERIC_TYPE,
    TEXT_TYPE,
    createTempItem,
    isItemPass,
    judgeInspectionResult,
  } from "./_utils/inspection";
  // 显示提示信息
  const showToast = message => {
@@ -442,7 +534,7 @@
      { required: true, message: "请选择产品型号", trigger: "change" },
    ],
    testStandardId: [
      { required: false, message: "请选择指标", trigger: "change" },
      { required: true, message: "请选择指标", trigger: "change" },
    ],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -602,9 +694,9 @@
  // 获取工序列表
  const getprocessList = () => {
    list().then(res => {
      processList.value = res.data;
    });
    // list().then(res => {
    //   processList.value = res.data;
    // });
  };
  // 获取产品选项
@@ -670,6 +762,54 @@
    });
  };
  // 数字型才有最大/最小值,判定也按上下限走
  const isNumeric = item => item?.parameterType === NUMERIC_TYPE;
  const judgeText = item => {
    const val = item?.testValue;
    if (val === null || val === undefined || val === "") return "-";
    return isItemPass(item) ? "合格" : "不合格";
  };
  const itemPassClass = item => {
    const val = item?.testValue;
    if (val === null || val === undefined || val === "") return "";
    return isItemPass(item) ? "pass" : "fail";
  };
  // 需求九.3:检验值一变就自动判定,口径见 _utils/inspection.js。
  // 与管理端一致,这里无条件覆盖 form.checkResult —— 手工选的检测结果
  // 只是临时兜底,下次改动检验项时会被重新判定覆盖。
  watch(
    tableData,
    rows => {
      const result = judgeInspectionResult(rows);
      if (result) form.value.checkResult = result;
    },
    { deep: true }
  );
  // 需求九.2:临时追加检测项
  const addTempItem = () => {
    tableData.value.push(createTempItem());
  };
  const removeTempItem = index => {
    tableData.value.splice(index, 1);
  };
  // 切类型时清掉不再适用的值,避免文字型残留上下限、数字型残留标准值
  const setTempType = (item, type) => {
    if (item.parameterType === type) return;
    item.parameterType = type;
    if (type === NUMERIC_TYPE) {
      item.standardValue = "";
    } else {
      item.maxValue = null;
      item.minValue = null;
    }
  };
  // 提交表单
  const submitForm = async () => {
    console.log("submitForm", form.value, tableData.value);
@@ -691,19 +831,32 @@
        showToast("请选择产品");
        return;
      }
      if (!form.value.testStandardId) {
        showToast("请选择指标");
        return;
      }
      if (!form.value.checkResult) {
        showToast("请选择检测结果");
        return;
      }
      const missingTemp = tableData.value.find(
        item => item.isTemp && !item.parameterItem
      );
      if (missingTemp) {
        showToast("请填写追加检测项的指标名称");
        return;
      }
      loading.value = true;
      form.value.inspectType = 2;
      // if (isEdit.value) {
      tableData.value.forEach(item => {
        delete item.id;
      });
      // }
      // isTemp 只是前端的行标记,不能提交给后端
      tableData.value = tableData.value.map(({ isTemp, ...rest }) => rest);
      if (!isEdit.value) {
        tableData.value.forEach(item => {
          delete item.id;
        });
      }
      const data = { ...form.value, qualityInspectParams: tableData.value };
      data.quantity = Number(data.quantity);
@@ -926,6 +1079,40 @@
    color: #909399;
  }
  .steps-header-right {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  // 临时追加行的参数类型切换
  .type-switch {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .type-option {
    padding: 4px 14px;
    font-size: 13px;
    color: #606266;
    background: #f4f4f5;
    border-radius: 12px;
  }
  .type-option.active {
    color: #409eff;
    background: #ecf5ff;
  }
  .judge-text.pass {
    color: #67c23a;
  }
  .judge-text.fail {
    color: #f56c6c;
  }
  .steps-list {
    margin-bottom: 20px;
  }