gongchunyi
2026-05-15 c6c1caafba75438cc0eff215f19e9e0b900778d7
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -14,6 +14,7 @@
                  v-model="form.supplier"
                  placeholder="请选择"
                  clearable
                  :disabled="supplierQuantityDisabled"
              >
                <el-option
                    v-for="item in supplierList"
@@ -71,28 +72,40 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="form.unit" disabled/>
              <el-input v-model="form.unit" placeholder="自动带出" readonly class="readonly-display"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入"
                               clearable :precision="2"/>
            <el-form-item label="厚度:" prop="thickness">
              <el-input v-model="form.thickness" placeholder="自动带出" readonly class="readonly-display"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="总数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入"
                               clearable :precision="2" :disabled="supplierQuantityDisabled"
                               @change="onTotalQuantityChange"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测单位:" prop="checkCompany">
              <el-input v-model="form.checkCompany" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult">
                <el-option label="合格" value="合格"/>
                <el-option label="不合格" value="不合格"/>
              </el-select>
            <el-form-item label="合格数量:" prop="qualifiedQuantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入"
                               clearable :precision="2" @change="onQualifiedQuantityChange"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不合格数量:" prop="unqualifiedQuantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入"
                               clearable :precision="2" @change="onUnqualifiedQuantityChange"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -104,6 +117,8 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测日期:" prop="checkTime">
              <el-date-picker
@@ -115,6 +130,47 @@
                  clearable
                  style="width: 100%"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item>
              <template #label>
                <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                  <span>审批人选择:</span>
                  <el-button type="primary"
                             size="small"
                             @click="addApproverNode"
                             icon="Plus">新增节点</el-button>
                </div>
              </template>
              <div class="approver-nodes-container">
                <div v-for="(node, index) in approverNodes"
                     :key="node.id"
                     class="approver-node-item">
                  <div class="approver-node-header">
                    <span class="approver-node-label">审批节点 {{ index + 1 }}</span>
                    <el-button v-if="approverNodes.length > 1"
                               type="danger"
                               size="small"
                               text
                               @click="removeApproverNode(index)"
                               icon="Delete">删除</el-button>
                  </div>
                  <el-select v-model="node.userId"
                             placeholder="请选择审批人"
                             filterable
                             style="width: 100%">
                    <el-option
                      v-for="item in approverList"
                      :key="item.userId"
                      :label="item.userName"
                      :value="item.userId"
                    />
                  </el-select>
                </div>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
@@ -144,13 +200,14 @@
</template>
<script setup>
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectParamDel, qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {userListNoPage} from "@/api/system/user.js";
import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js";
const {proxy} = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -168,9 +225,12 @@
    model: "",
    testStandardId: "",
    unit: "",
    thickness: "",
    quantity: "",
    qualifiedQuantity: null,
    unqualifiedQuantity: null,
    checkCompany: "",
    checkResult: "",
    reviewName: "",
  },
  rules: {
    checkTime: [{required: true, message: "请输入", trigger: "blur"},],
@@ -180,9 +240,11 @@
    productModelId: [{required: true, message: "请选择产品型号", trigger: "change"}],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{required: false, message: "请输入", trigger: "blur"}],
    thickness: [{required: false, message: "请输入", trigger: "blur"}],
    quantity: [{required: true, message: "请输入", trigger: "blur"}],
    checkCompany: [{required: false, message: "请输入", trigger: "blur"}],
    checkResult: [{required: true, message: "请选择检测结果", trigger: "change"}],
    qualifiedQuantity: [{required: true, message: "请输入合格数量", trigger: "blur"}],
    unqualifiedQuantity: [{required: true, message: "请输入不合格数量", trigger: "blur"}],
  },
});
const tableColumn = ref([
@@ -213,12 +275,135 @@
const tableLoading = ref(false);
const {form, rules} = toRefs(data);
function normalizeQuantitiesFromLegacy() {
  const qty = form.value.quantity != null ? Number(form.value.quantity) : null
  if (qty == null || Number.isNaN(qty)) return
  const hasQ = form.value.qualifiedQuantity != null && form.value.qualifiedQuantity !== ''
  const hasU = form.value.unqualifiedQuantity != null && form.value.unqualifiedQuantity !== ''
  if (hasQ && hasU) return
  if (form.value.checkResult === '不合格') {
    form.value.qualifiedQuantity = 0
    form.value.unqualifiedQuantity = qty
  } else {
    form.value.qualifiedQuantity = qty
    form.value.unqualifiedQuantity = 0
  }
}
function assertQtySplitOrError() {
  const total = Number(form.value.quantity)
  const a = form.value.qualifiedQuantity != null ? Number(form.value.qualifiedQuantity) : NaN
  const b = form.value.unqualifiedQuantity != null ? Number(form.value.unqualifiedQuantity) : NaN
  if (!Number.isFinite(total) || total < 0) {
    proxy.$modal.msgError('请先填写有效的总数量')
    return false
  }
  if (!Number.isFinite(a) || !Number.isFinite(b) || a < 0 || b < 0) {
    proxy.$modal.msgError('请填写合格数量与不合格数量')
    return false
  }
  if (a + b - total > 0.001) {
    proxy.$modal.msgError('合格数量与不合格数量之和不能超过总数量')
    return false
  }
  return true
}
function roundQty(n) {
  if (!Number.isFinite(n)) return 0
  return Math.round(n * 100) / 100
}
function parseTotalQty() {
  const t = Number(form.value.quantity)
  return Number.isFinite(t) && t >= 0 ? t : null
}
function onQualifiedQuantityChange(val) {
  const total = parseTotalQty()
  if (total == null) return
  let q = val == null || val === '' ? 0 : Number(val)
  if (!Number.isFinite(q) || q < 0) q = 0
  if (q > total) q = total
  q = roundQty(q)
  form.value.qualifiedQuantity = q
  form.value.unqualifiedQuantity = roundQty(total - q)
}
function onUnqualifiedQuantityChange(val) {
  const total = parseTotalQty()
  if (total == null) return
  let u = val == null || val === '' ? 0 : Number(val)
  if (!Number.isFinite(u) || u < 0) u = 0
  if (u > total) u = total
  u = roundQty(u)
  form.value.unqualifiedQuantity = u
  form.value.qualifiedQuantity = roundQty(total - u)
}
function onTotalQuantityChange() {
  const total = parseTotalQty()
  if (total == null) return
  const q = form.value.qualifiedQuantity != null && form.value.qualifiedQuantity !== '' ? Number(form.value.qualifiedQuantity) : NaN
  const u = form.value.unqualifiedQuantity != null && form.value.unqualifiedQuantity !== '' ? Number(form.value.unqualifiedQuantity) : NaN
  if (!Number.isFinite(q) && !Number.isFinite(u)) {
    form.value.qualifiedQuantity = roundQty(total)
    form.value.unqualifiedQuantity = 0
    return
  }
  if (Number.isFinite(q) && Number.isFinite(u)) {
    const sum = q + u
    if (sum > total + 0.001) {
      const nq = roundQty(Math.min(Math.max(0, q), total))
      form.value.qualifiedQuantity = nq
      form.value.unqualifiedQuantity = roundQty(total - nq)
    }
    return
  }
  if (Number.isFinite(q)) {
    const nq = roundQty(Math.min(Math.max(0, q), total))
    form.value.qualifiedQuantity = nq
    form.value.unqualifiedQuantity = roundQty(total - nq)
  } else if (Number.isFinite(u)) {
    const nu = roundQty(Math.min(Math.max(0, u), total))
    form.value.unqualifiedQuantity = nu
    form.value.qualifiedQuantity = roundQty(total - nu)
  }
}
function ensureQtyPairInitialized() {
  const total = parseTotalQty()
  if (total == null) return
  const qEmpty = form.value.qualifiedQuantity == null || form.value.qualifiedQuantity === ''
  const uEmpty = form.value.unqualifiedQuantity == null || form.value.unqualifiedQuantity === ''
  if (qEmpty && uEmpty) {
    form.value.qualifiedQuantity = roundQty(total)
    form.value.unqualifiedQuantity = 0
  }
}
const supplierList = ref([]);
const productOptions = ref([]);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // 指标选择下拉框数据
const modelOptions = ref([]);
const userList = ref([]); // 检验员下拉列表
const approverList = ref([]); // 审批人下拉列表
const approverNodes = ref([{ id: 1, userId: null }]);
let nextApproverId = 2;
const addApproverNode = () => {
  approverNodes.value.push({ id: nextApproverId++, userId: null });
};
const removeApproverNode = index => {
  approverNodes.value.splice(index, 1);
};
// 编辑时:productMainId 或 purchaseLedgerId 任一有值则供应商、数量置灰
const supplierQuantityDisabled = computed(() => {
  const v = form.value || {};
  return !!(v.productMainId != null || v.purchaseLedgerId != null);
});
// 打开弹框
const openDialog = async (type, row) => {
@@ -230,9 +415,12 @@
  try {
    const userRes = await userListNoPage();
    userList.value = userRes.data || [];
    const approverRes = await approveUserList({ approveType: 9 });
    approverList.value = approverRes.data || [];
  } catch (e) {
    console.error("加载检验员列表失败", e);
    console.error("加载人员列表失败", e);
    userList.value = [];
    approverList.value = [];
  }
  // 先重置表单数据(保持字段完整,避免弹窗首次渲染时触发必填红框“闪一下”)
   form.value = {
@@ -245,10 +433,15 @@
    model: "",
    testStandardId: "",
    unit: "",
    thickness: "",
    quantity: "",
    qualifiedQuantity: null,
    unqualifiedQuantity: null,
    checkCompany: "",
    checkResult: "",
    reviewName: "",
  }
  approverNodes.value = [{ id: 1, userId: null }];
  nextApproverId = 2;
  testStandardOptions.value = [];
  tableData.value = [];
  // 先确保产品树已加载,否则编辑时产品/规格型号无法反显
@@ -257,6 +450,26 @@
    // 先保存 testStandardId,避免被清空
    const savedTestStandardId = row.testStandardId;
    form.value = {...row}
    normalizeQuantitiesFromLegacy()
    if (form.value.approveUserIds) {
      const ids = String(form.value.approveUserIds)
        .split(",")
        .map(id => id.trim())
        .filter(Boolean);
      if (ids.length > 0) {
        approverNodes.value = ids.map((id, index) => ({
          id: index + 1,
          userId: Number(id),
        }));
        nextApproverId = ids.length + 1;
      }
    } else if (form.value.reviewName) {
      const matchedReviewer = approverList.value.find(item => item.userName === form.value.reviewName);
      if (matchedReviewer?.userId) {
        approverNodes.value = [{ id: 1, userId: matchedReviewer.userId }];
        nextApproverId = 2;
      }
    }
    currentProductId.value = row.productId || 0
    // 关键:编辑时加载规格型号下拉选项,才能反显 productModelId
    if (currentProductId.value) {
@@ -327,6 +540,7 @@
const getModels = (value) => {
  form.value.productModelId = undefined;
  form.value.unit = undefined;
  form.value.thickness = undefined;
  modelOptions.value = [];
  currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
@@ -339,8 +553,10 @@
};
const handleChangeModel = (value) => {
  form.value.model = modelOptions.value.find(item => item.id == value)?.model || '';
  form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
  const selectedModel = modelOptions.value.find(item => item.id == value);
  form.value.model = selectedModel?.model || '';
  form.value.unit = selectedModel?.unit || '';
  form.value.thickness = selectedModel?.thickness ?? '';
}
const findNodeById = (nodes, productId) => {
@@ -377,13 +593,33 @@
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      ensureQtyPairInitialized()
      if (!assertQtySplitOrError()) {
        return
      }
      const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
      if (hasEmptyApprover) {
        proxy.$modal.msgError("请为所有审批节点选择审批人!");
        return;
      }
      const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
      const firstApproverName =
        approverList.value.find(item => String(item.userId) === String(approverNodes.value[0]?.userId))
          ?.userName || "";
      form.value.inspectType = 0
         if (operationType.value === "add") {
            tableData.value.forEach((item) => {
               delete item.id
            })
         }
      const data = {...form.value, qualityInspectParams: tableData.value}
      const data = {
        ...form.value,
        reviewName: firstApproverName,
        approveUserIds,
        // 兼容后端不同字段命名
        auditName: firstApproverName,
        qualityInspectParams: tableData.value,
      }
      if (operationType.value === "add") {
        qualityInspectAdd(data).then(res => {
          proxy.$modal.msgSuccess("提交成功");
@@ -444,6 +680,8 @@
// 关闭弹框
const closeDia = () => {
  proxy.resetForm("formRef");
  approverNodes.value = [{ id: 1, userId: null }];
  nextApproverId = 2;
  tableData.value = [];
  testStandardOptions.value = [];
  form.value.testStandardId = '';
@@ -457,4 +695,65 @@
<style scoped>
:deep(.readonly-display .el-input__wrapper) {
  background-color: var(--el-disabled-bg-color);
  box-shadow: 0 0 0 1px var(--el-disabled-border-color) inset;
}
:deep(.readonly-display .el-input__inner) {
  color: var(--el-disabled-text-color);
  -webkit-text-fill-color: var(--el-disabled-text-color);
  cursor: not-allowed;
}
.approver-nodes-container {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding: 16px;
  background-color: #f8f9fa;
  border-radius: 4px;
  border: 1px solid #e4e7ed;
}
.approver-node-item {
  flex: 0 0 calc(33.333% - 12px);
  min-width: 200px;
  padding: 12px;
  background-color: #fff;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  transition: all 0.3s;
}
.approver-node-item:hover {
  border-color: #409eff;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
}
.approver-node-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.approver-node-label {
  font-size: 13px;
  font-weight: 500;
  color: #606266;
}
@media (max-width: 1200px) {
  .approver-node-item {
    flex: 0 0 calc(50% - 8px);
  }
}
@media (max-width: 768px) {
  .approver-node-item {
    flex: 0 0 100%;
  }
}
</style>