spring
7 小时以前 4069e8544bb2e4ec8022d3f23a7ba60a5fef05c5
src/views/productionManagement/workOrder/index.vue
@@ -166,17 +166,32 @@
    </el-dialog>
    <el-dialog v-model="reportDialogVisible"
               title="报工"
               width="500px">
               width="1400px">
      <el-form ref="reportFormRef"
               :model="reportForm"
               :rules="reportFormRules"
               label-width="120px">
        <el-form-item label="待生产数量">
          <el-input v-model="reportForm.planQuantity"
                    readonly
                    style="width: 300px" />
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="待生产数量">
              <el-input v-model="reportForm.planQuantity"
                        readonly
                        style="width: 300px" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="投入总量(kg)"
                      prop="totalInvestment">
          <el-input v-model.number="reportForm.totalInvestment"
                    type="number"
                    min="1"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入投入总量"
                    @input="handleTotalInvestmentInput" />
        </el-form-item>
        <el-form-item label="本次生产数量"
          </el-col>
          <el-col :span="12"><el-form-item label="本次生产数量"
                      prop="quantity">
          <el-input v-model.number="reportForm.quantity"
                    type="number"
@@ -185,8 +200,8 @@
                    style="width: 300px"
                    placeholder="请输入本次生产数量"
                    @input="handleQuantityInput" />
        </el-form-item>
        <el-form-item label="报废数量"
        </el-form-item></el-col>
          <el-col :span="12"><el-form-item label="报废数量"
                      prop="scrapQty">
          <el-input v-model.number="reportForm.scrapQty"
                    type="number"
@@ -195,8 +210,21 @@
                    style="width: 300px"
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
        </el-form-item></el-col>
        <el-col :span="12">
          <el-form-item label="检品数量"
                      prop="inspectedQuantity">
          <el-input v-model.number="reportForm.inspectedQuantity"
                    type="number"
                    min="0"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入检品数量"
                    @input="handleInspectedQuantity"/>
        </el-form-item>
        <el-form-item label="班组信息">
        </el-col>
        <el-col :span="12">
          <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
                     style="width: 300px"
                     placeholder="请选择班组信息"
@@ -209,12 +237,99 @@
                       :value="user.userId" />
          </el-select>
        </el-form-item>
        </el-col>
        </el-row>
        <ProductionRecordForm
          ref="productionRecordFormRef"
          :list="processParamList"
          :device-options="deviceOptions"
          :selected-device-id="reportForm.deviceId"
        />
        <div style="margin-top: 20px">
          <div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
            <el-button type="primary" @click="openAddMaterialDialog">新增原材料</el-button>
          </div>
          <el-table
            :data="reportForm.drawMaterialList"
            border
            style="width: 100%"
            height="220px"
            empty-text="暂无原材料领用明细"
          >
            <el-table-column type="index" label="序号" width="60" align="center" />
            <el-table-column prop="productName" label="产品名称" min-width="160" />
            <el-table-column prop="model" label="型号" min-width="150" />
            <el-table-column prop="unit" label="单位" width="90" align="center" />
            <el-table-column prop="reportQty" label="领用数量" width="160" align="center">
              <template #default="{ row }">
                <el-input-number
                  v-model.number="row.reportQty"
                  :min="0"
                  :precision="2"
                  :controls="false"
                  :max="row.requisitionQty || 0"
                  :disabled="!row.requisitionQty"
                  style="width: 100%"
                />
              </template>
            </el-table-column>
            <el-table-column prop="remark" label="备注" min-width="160">
              <template #default="{ row }">
                <el-input v-model="row.remark" placeholder="请输入备注" clearable />
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80" align="center">
              <template #default="{ $index }">
                <el-button type="danger" link @click="removeDrawMaterialRow($index)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleReport">确定</el-button>
          <el-button @click="reportDialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog
      v-model="addMaterialDialogVisible"
      title="选择原材料"
      width="1000px"
      top="5vh"
      :close-on-click-modal="false"
      append-to-body
      destroy-on-close
    >
      <div>
        <el-table
          :data="availableMaterials"
          border
          style="width: 100%"
          height="45vh"
          row-key="id"
          @selection-change="handleAddMaterialSelectionChange"
        >
          <el-table-column type="selection" width="55" align="center" />
          <el-table-column type="index" label="序号" width="60" align="center" />
          <el-table-column prop="productName" label="产品名称" min-width="160" />
          <el-table-column prop="model" label="型号" min-width="150" />
          <el-table-column prop="unit" label="单位" width="90" align="center" />
          <el-table-column prop="requisitionQty" label="可领用数量" width="140" align="center" />
        </el-table>
        <!-- 已选择明细展示放在报工弹框下方的 reportForm.drawMaterialList 表格里 -->
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="addMaterialDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleConfirmAddMaterial">确定</el-button>
        </span>
      </template>
    </el-dialog>
@@ -233,10 +348,17 @@
    downProductWorkOrder,
  } from "@/api/productionManagement/workOrder.js";
  import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
  import { getBindDevices } from "@/api/productionManagement/productionProcess.js";
  import QRCode from "qrcode";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  import FilesDia from "./components/filesDia.vue";
  import {
    listPage as listProcessParamPage,
  } from "@/api/productionManagement/productProcessParameter.js";
  const { proxy } = getCurrentInstance();
  const ProductionRecordForm = defineAsyncComponent(() => import("./components/ProductionRecordForm.vue"));
  const tableColumn = ref([
    {
@@ -264,12 +386,20 @@
      prop: "model",
    },
    {
      label: "UID码",
      prop: "uidNo",
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "工序名称",
      prop: "processName",
    },
    {
      label: "投入总量(kg)",
      prop: "totalInvestment",
    },
    {
      label: "预计生产数量",
@@ -338,7 +468,7 @@
          clickFun: row => {
            showReportDialog(row);
          },
          disabled: row => row.planQuantity <= 0,
          disabled: row => row.planQuantity <= 0 || row.productOrderIsEnd,
        },
      ],
    },
@@ -356,17 +486,52 @@
  const workOrderFilesRef = ref(null);
  const reportFormRef = ref(null);
  const userOptions = ref([]);
  const deviceOptions = ref([]);
  const reportForm = reactive({
    planQuantity: 0,
    totalInvestment: 0,
    quantity: null,
    scrapQty: null,
    inspectedQuantity: null,
    userName: "",
    workOrderId: "",
    reportWork: "",
    productProcessRouteItemId: "",
    userId: "",
    productMainId: null,
    deviceId: null,
    // 报工时选择的原材料领用明细
    drawMaterialList: [],
    otherData: {
      rows: []
    },
  });
  const productionRecordFormRef = ref();
  // 原材料领用(新增/选择弹框)
  const availableMaterials = ref([]); // 来自当前行的 drawMaterials(json解析成list)
  const addMaterialDialogVisible = ref(false);
  const selectedAddMaterials = ref([]); // 弹框里用户选中的条目(可编辑 reportQty)
  // 投入总量验证规则
  const validateTotalInvestment = (rule, value, callback) => {
    if (value === null || value === undefined || value === "") {
      callback(new Error("请输入投入总量"));
      return;
    }
    const num = Number(value);
    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
      callback(new Error("投入总量必须大于等于1"));
      return;
    }
    if (reportForm.quantity !== null && reportForm.quantity !== undefined && reportForm.quantity !== "") {
      if (num < Number(reportForm.quantity)) {
        callback(new Error("投入总量必须大于本次生产数量"));
        return;
      }
    }
    callback();
  };
  // 本次生产数量验证规则
  const validateQuantity = (rule, value, callback) => {
@@ -375,10 +540,15 @@
      return;
    }
    const num = Number(value);
    // 整数且大于等于1
    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
      callback(new Error("本次生产数量必须大于等于1"));
      return;
    }
    if (reportForm.totalInvestment !== null && reportForm.totalInvestment !== undefined && reportForm.totalInvestment !== "") {
      if (num > Number(reportForm.totalInvestment)) {
        callback(new Error("本次生产数量必须小于投入总量"));
        return;
      }
    }
    callback();
  };
@@ -400,8 +570,48 @@
  // 验证规则
  const reportFormRules = {
    totalInvestment: [{ required: true, validator: validateTotalInvestment, trigger: "blur" }],
    quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }],
    scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
  };
  // 处理投入总量输入
  const handleTotalInvestmentInput = value => {
    if (value === "" || value === null || value === undefined) {
      reportForm.totalInvestment = null;
      reportForm.scrapQty = null;
      return;
    }
    const num = Number(value);
    if (isNaN(num)) {
      return;
    }
    if (num < 1) {
      reportForm.totalInvestment = null;
      reportForm.scrapQty = null;
      return;
    }
    if (!Number.isInteger(num)) {
      const intValue = Math.floor(num);
      if (intValue < 1) {
        reportForm.totalInvestment = null;
        reportForm.scrapQty = null;
        return;
      }
      reportForm.totalInvestment = intValue;
    } else {
      reportForm.totalInvestment = num;
    }
    // 如果 quantity 有值,自动计算 scrapQty = totalInvestment - quantity
    if (reportForm.quantity !== null && reportForm.quantity !== undefined && reportForm.quantity !== "") {
      const total = Number(reportForm.totalInvestment);
      const qty = Number(reportForm.quantity);
      if (total > qty) {
        reportForm.scrapQty = total - qty;
      } else {
        reportForm.scrapQty = null;
      }
    }
  };
  // 处理本次生产数量输入,限制必须大于等于1
@@ -414,23 +624,20 @@
    if (isNaN(num)) {
      return;
    }
    // 如果小于1,清除
    if (num < 1) {
      reportForm.quantity = null;
      return;
    }
    // 如果是小数取整数部分
    if (!Number.isInteger(num)) {
      const intValue = Math.floor(num);
      // 如果取整后小于1,清除
      if (intValue < 1) {
        reportForm.quantity = null;
        return;
      }
      reportForm.quantity = intValue;
      return;
    } else {
      reportForm.quantity = num;
    }
    reportForm.quantity = num;
  };
  // 处理报废数量
@@ -440,22 +647,38 @@
      return;
    }
    const num = Number(value);
    // 如果是NaN,保持原值
    if (isNaN(num)) {
      return;
    }
    // 如果是负数,清除输入
    if (num < 0) {
      reportForm.scrapQty = null;
      return;
    }
    // 如果是小数,取整数部分
    if (!Number.isInteger(num)) {
      reportForm.scrapQty = Math.floor(num);
      return;
    }
    // 有效的非负整数(包括0)
    reportForm.scrapQty = num;
  };
  const handleInspectedQuantity = value => {
    if (value === "" || value === null || value === undefined) {
      reportForm.inspectedQuantity = null;
      return;
    }
    const num = Number(value);
    if (isNaN(num)) {
      return;
    }
    if (num < 0) {
      reportForm.inspectedQuantity = null;
      return;
    }
    if (!Number.isInteger(num)) {
      reportForm.inspectedQuantity = Math.floor(num);
      return;
    }
    reportForm.inspectedQuantity = num;
  };
  const currentReportRowData = ref(null);
  const page = reactive({
@@ -594,17 +817,115 @@
      });
  };
  const showReportDialog = row => {
  const processParamPage = reactive({
    current: 1,
    size: 9999,
    total: 0,
  });
  const getProcessParamList = async (row) => {
    const params = {
      processId: row.processId,
      ...processParamPage,
    };
    const res = await listProcessParamPage(params)
    return res.data.records
  };
  // 原材料领用:drawMaterials(json -> list)与 drawMaterialList(最终提交)
  const parseMaybeJsonList = (val) => {
    if (!val) return [];
    if (Array.isArray(val)) return val;
    if (typeof val === 'string') {
      try {
        const parsed = JSON.parse(val);
        return Array.isArray(parsed) ? parsed : [];
      } catch (e) {
        return [];
      }
    }
    return [];
  };
  const normalizeDrawMaterialItem = (item) => {
    if (!item) return null;
    return {
      ...item,
      reportQty: item.reportQty ?? item.requisitionQty ?? 0,
      remark: item.remark ?? '',
    };
  };
  const materialKey = (item) => String(item?.id ?? item?.productModelId ?? '');
  const openAddMaterialDialog = () => {
    if (!availableMaterials.value || availableMaterials.value.length === 0) {
      proxy.$modal.msgWarning("当前工单没有可领用的原材料");
      return;
    }
    selectedAddMaterials.value = [];
    addMaterialDialogVisible.value = true;
  };
  const handleAddMaterialSelectionChange = (selection) => {
    selectedAddMaterials.value = (selection || []).map((x) => normalizeDrawMaterialItem({ ...x }));
  };
  const handleConfirmAddMaterial = () => {
    if (!selectedAddMaterials.value || selectedAddMaterials.value.length === 0) {
      proxy.$modal.msgWarning("请先选择原材料");
      return;
    }
    const existingKeys = new Set((reportForm.drawMaterialList || []).map(materialKey));
    const newItems = selectedAddMaterials.value
      .filter((it) => !existingKeys.has(materialKey(it)))
      .map(normalizeDrawMaterialItem)
      .filter(Boolean);
    if (newItems.length === 0) {
      proxy.$modal.msgWarning("所选原材料已存在,无需重复添加");
      addMaterialDialogVisible.value = false;
      return;
    }
    reportForm.drawMaterialList = [...(reportForm.drawMaterialList || []), ...newItems];
    addMaterialDialogVisible.value = false;
  };
  const removeDrawMaterialRow = (index) => {
    if (!Array.isArray(reportForm.drawMaterialList)) return;
    reportForm.drawMaterialList.splice(index, 1);
  };
  const processParamList = ref([])
  const showReportDialog = async row => {
    currentReportRowData.value = row;
    processParamList.value = await getProcessParamList(row)
    reportForm.planQuantity = row.planQuantity;
    reportForm.totalInvestment = row.totalInvestment;
    reportForm.quantity =
      row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
    reportForm.productMainId = row.productMainId;
    reportForm.inspectedQuantity = row.inspectedQuantity;
    reportForm.scrapQty =
      row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
    reportForm.deviceId = row.deviceId || null;
    // 获取工序绑定设备列表
    getDeviceList(row.processId);
    // 原材料:可选项来自当前行 drawMaterials(json -> list)
    availableMaterials.value = parseMaybeJsonList(row.drawMaterials);
    // 最终领用清单:优先回显 drawMaterialList(若后端已返回);否则为空
    const existingDrawList = parseMaybeJsonList(row.drawMaterialList);
    reportForm.drawMaterialList = (existingDrawList || []).map(normalizeDrawMaterialItem).filter(Boolean);
    selectedAddMaterials.value = [];
    addMaterialDialogVisible.value = false;
    nextTick(() => {
      reportFormRef.value?.clearValidate();
    });
@@ -627,7 +948,18 @@
    workOrderFilesRef.value?.openDialog(row);
  };
  const handleReport = () => {
  const handleReport = async () => {
    try {
      const data = await productionRecordFormRef.value.submitData();
      console.log("生产记录表单数据:", data);
      reportForm.otherData.rows = data || [];
      // 机台选择由 ProductionRecordForm 的“机台选择”参数决定
      reportForm.deviceId = getDeviceFromRecordParams(data) ?? reportForm.deviceId;
    } catch (error) {
      console.error("获取生产记录表单数据失败", error);
      return;
    }
    reportFormRef.value?.validate(valid => {
      if (!valid) {
        return false;
@@ -676,20 +1008,22 @@
        return;
      }
      if (quantity > reportForm.planQuantity) {
        ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // if (quantity > reportForm.planQuantity) {
      //   ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
      //     confirmButtonText: "确定",
      //   });
      //   return;
      // }
      const submitData = {
        ...reportForm,
        quantity: quantity,
        scrapQty: scrapQty,
        // drawMaterialList 直接传数组(不做 JSON.stringify)
        drawMaterialList: reportForm.drawMaterialList || [],
        otherData: JSON.stringify(reportForm.otherData)
      };
      // console.log(submitData);
      addProductMain(submitData).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("报工成功");
@@ -700,6 +1034,11 @@
            confirmButtonText: "确定",
          });
        }
      }).catch(err => {
        console.error("报工失败", err);
        ElMessageBox.alert("报工失败", "提示", {
          confirmButtonText: "确定",
        });
      });
    });
  };
@@ -717,6 +1056,24 @@
      });
  };
  // 获取设备列表
  const getDeviceList = (processId) => {
    if (!processId) {
      deviceOptions.value = [];
      return;
    }
    getBindDevices(processId)
      .then(res => {
        if (res.code === 200) {
          deviceOptions.value = res.data || [];
        }
      })
      .catch(err => {
        console.error("获取设备列表失败", err);
        deviceOptions.value = [];
      });
  };
  // 用户选择变化时更新 userName
  const handleUserChange = userId => {
    if (userId) {
@@ -729,6 +1086,12 @@
    }
  };
  const getDeviceFromRecordParams = (rows) => {
    if (!Array.isArray(rows)) return null;
    const machineRow = rows.find(r => r?.type === '机台选择');
    return machineRow?.value ?? null;
  };
  onMounted(() => {
    getList();
    getUserList();