huminmin
2026-05-28 8ef070c84a703c4a8b838bf9320d68d00a7d6dca
src/views/productionManagement/workOrder/index.vue
@@ -192,6 +192,7 @@
                  step="1"
                  placeholder="请输入本次生产数量"
                  style="width: 100%"
                  :class="{ 'over-limit': reportForm.quantity > reportForm.planQuantity }"
                  @input="handleQuantityInput"
              />
            </el-form-item>
@@ -218,6 +219,18 @@
                  step="1"
                  placeholder="请输入报废数量"
                  @input="handleScrapQtyInput"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="加放数" prop="addQty">
              <el-input
                  v-model.number="reportForm.addQty"
                  type="number"
                  min="0"
                  step="1"
                  placeholder="请输入加放数"
              />
            </el-form-item>
          </el-col>
@@ -263,24 +276,24 @@
<!--            </el-form-item>-->
<!--          </el-col>-->
          <el-col :span="12">
            <el-form-item label="审核人" prop="auditUserId">
              <el-select
                  v-model="reportForm.auditUserId"
                  placeholder="请选择审核人"
                  clearable
                  filterable
                  @change="handleReviewerIdChange"
              >
                <el-option
                    v-for="user in userOptions"
                    :key="user.userId"
                    :label="user.nickName"
                    :value="user.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
<!--          <el-col :span="12">-->
<!--            <el-form-item label="审核人" prop="auditUserId">-->
<!--              <el-select-->
<!--                  v-model="reportForm.auditUserId"-->
<!--                  placeholder="请选择审核人"-->
<!--                  clearable-->
<!--                  filterable-->
<!--                  @change="handleReviewerIdChange"-->
<!--              >-->
<!--                <el-option-->
<!--                    v-for="user in userOptions"-->
<!--                    :key="user.userId"-->
<!--                    :label="user.nickName"-->
<!--                    :value="user.userId"-->
<!--                />-->
<!--              </el-select>-->
<!--            </el-form-item>-->
<!--          </el-col>-->
        </el-row>
      </el-form>
@@ -326,11 +339,11 @@
      </template>
    </el-dialog>
    <el-dialog v-model="scheduleDialogVisible"
               :title="`生产排产(工单编号:${currentReportRowData?.workOrderNo || '-'})`"
               :title="scheduleDialogTitle"
               width="1000px"
               :close-on-click-modal="false">
      <div class="schedule-panel">
        <el-row style="margin-bottom: 12px;">
        <el-row v-if="!isScheduleHistoryMode" style="margin-bottom: 12px;">
          <el-col>
            <el-button type="primary" plain :disabled="scheduleLoading || scheduleSaving" @click="addScheduleRow">
              新增一行
@@ -347,7 +360,7 @@
                  filterable
                  clearable
                  style="width: 100%"
                  :disabled="scheduleSaving"
                  :disabled="scheduleSaving || isScheduleHistoryMode"
                  @change="val => handleScheduleDeviceChange(val, row)"
              >
                <el-option
@@ -362,7 +375,18 @@
          <el-table-column label="本次上机人" min-width="220">
            <template #default="{ row }">
              <div v-if="isScheduleHistoryMode" class="schedule-user-tags">
                <el-tag
                    v-for="(userId, index) in row.userIds"
                    :key="`${userId}-${index}`"
                    type="primary"
                >
                  {{ resolveScheduleUserName(userId) }}
                </el-tag>
                <span v-if="!row.userIds?.length">-</span>
              </div>
              <el-select
                  v-else
                  v-model="row.userIds"
                  placeholder="请选择上机人"
                  filterable
@@ -392,12 +416,12 @@
                  format="YYYY-MM-DD HH:mm:ss"
                  placeholder="请选择上机时间"
                  style="width: 100%"
                  :disabled="scheduleSaving"
                  :disabled="scheduleSaving || isScheduleHistoryMode"
              />
            </template>
          </el-table-column>
          <el-table-column label="操作" width="110" align="center">
          <el-table-column v-if="!isScheduleHistoryMode" label="操作" width="110" align="center">
            <template #default="{ row }">
              <el-button
                  link
@@ -413,7 +437,7 @@
        </el-table>
        <Pagination
            v-show="schedulePage.total > 0"
            v-show="isScheduleHistoryMode && schedulePage.total > 0"
            style="margin-top: 12px"
            :total="schedulePage.total"
            :page="schedulePage.current"
@@ -424,8 +448,13 @@
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="scheduleSaving" @click="handleSaveSchedule">保存排产</el-button>
          <el-button :disabled="scheduleSaving" @click="scheduleDialogVisible = false">取消</el-button>
          <template v-if="isScheduleHistoryMode">
            <el-button @click="scheduleDialogVisible = false">关闭</el-button>
          </template>
          <template v-else>
            <el-button type="primary" :loading="scheduleSaving" @click="handleSaveSchedule">保存排产</el-button>
            <el-button :disabled="scheduleSaving" @click="scheduleDialogVisible = false">取消</el-button>
          </template>
        </span>
      </template>
    </el-dialog>
@@ -623,17 +652,12 @@
    return;
  }
  const first = scheduleRows.value[0] || {};
  const deviceId = first.deviceId || currentReportRowData.value?.deviceId || "";
  const userOptions = deviceId ? buildScheduleUserOptionsByDeviceId(deviceId) : [...baseScheduleUsers.value];
  const defaultUserId = userOptions[0]?.userId ? String(userOptions[0].userId) : "";
  scheduleRows.value.push(
    createScheduleRow({
      id: "",
      deviceId,
      deviceName: first.deviceName || currentReportRowData.value?.deviceName || "",
      userIds: defaultUserId ? [defaultUserId] : [],
      deviceId:"",
      deviceName:"",
      userIds: [],
      startTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    })
  );
@@ -645,7 +669,7 @@
  if (!workOrderRow?.id) {
    schedulePage.current = 1;
    schedulePage.total = 0;
    scheduleRows.value = [createScheduleRow({})];
    scheduleRows.value = [];
    return;
  }
@@ -672,11 +696,11 @@
    }
    const rows = records.map(record => mapMachineRecordToScheduleRow(record));
    scheduleRows.value = rows.length > 0 ? rows : [createScheduleRow({})];
    scheduleRows.value = rows;
  } catch (error) {
    console.error("获取排产记录失败", error);
    schedulePage.total = 0;
    scheduleRows.value = [createScheduleRow({})];
    scheduleRows.value = [];
    ElMessage.error("获取排产记录失败");
  } finally {
    scheduleLoading.value = false;
@@ -684,12 +708,12 @@
};
const removeScheduleRow = async (row) => {
  if (!row) return;
  if (!row || isScheduleHistoryMode.value) return;
  if (!row.id) {
    scheduleRows.value = scheduleRows.value.filter((item) => item !== row);
    if (!scheduleRows.value.length) {
      scheduleRows.value = [createScheduleRow({})];
      addScheduleRow();
    }
    return;
  }
@@ -740,6 +764,7 @@
};
const handleSchedulePagination = ({page, limit}) => {
  if (!isScheduleHistoryMode.value) return;
  schedulePage.current = page;
  schedulePage.size = limit;
  refreshScheduleRows();
@@ -818,6 +843,12 @@
  return (schedulePage.current - 1) * schedulePage.size + index + 1;
};
const scheduleDialogMode = ref("create");
const isScheduleHistoryMode = computed(() => scheduleDialogMode.value === "history");
const scheduleDialogTitle = computed(() =>
  `${isScheduleHistoryMode.value ? "排产记录" : "生产排产"}(工单编号:${currentReportRowData.value?.workOrderNo || "-"})`
);
const mapMachineRecordToScheduleRow = (record) => {
  const id = record?.id ?? "";
  const deviceId = record?.machineId ?? record?.deviceId ?? "";
@@ -863,11 +894,29 @@
  );
};
const resetCreateScheduleRows = () => {
  schedulePage.current = 1;
  schedulePage.total = 0;
  scheduleRows.value = [];
  addScheduleRow();
};
const openScheduleDialog = async (row) => {
  scheduleDialogMode.value = "create";
  currentReportRowData.value = row;
  baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
  userTemp.value = [...baseScheduleUsers.value];
  scheduleDialogVisible.value = true;
  resetCreateScheduleRows();
};
const openHistoryTimelineDialog = async (row) => {
  scheduleDialogMode.value = "history";
  currentReportRowData.value = row;
  baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
  userTemp.value = [...baseScheduleUsers.value];
  schedulePage.current = 1;
  schedulePage.total = 0;
  scheduleRows.value = [];
  scheduleDialogVisible.value = true;
@@ -875,6 +924,7 @@
};
const handleSaveSchedule = async () => {
  if (isScheduleHistoryMode.value) return;
  if (scheduleSaving.value) return;
  if (!validateScheduleRows()) return;
@@ -899,8 +949,9 @@
    }
    proxy.$modal.msgSuccess("排产已保存");
    await refreshScheduleRows();
    getList();
    scheduleDialogVisible.value = false;
    resetCreateScheduleRows();
    await getList();
  } catch (error) {
    console.error("保存排产失败", error);
    ElMessage.error("保存排产失败,请重试");
@@ -924,20 +975,27 @@
    label: "生产订单号",
    prop: "productOrderNpsNo",
    width: "140",
    formatData: val => (val && val.length > 4 ? val.slice(0, -4) : val || ""),
  },
  {
    label: "产品名称",
    prop: "productName",
    width: "140",
    label: "成品名称",
    prop: "finalProductModel",
    minWidth: 200,
    overHidden: false
  },
  {
    label: "规格",
    prop: "model",
  },
  {
    label: "单位",
    prop: "unit",
  },
  // {
  //   label: "加工品名称",
  //   prop: "productName",
  //   width: "140",
  // },
  // {
  //   label: "加工品规格",
  //   prop: "model",
  // },
  // {
  //   label: "加工品单位",
  //   prop: "unit",
  // },
  {
    label: "工序名称",
    prop: "processName",
@@ -981,7 +1039,7 @@
  },
  {
    label: "操作",
    width: "200",
    width: "220",
    align: "center",
    dataType: "action",
    fixed: "right",
@@ -1009,9 +1067,9 @@
        clickFun: row => {
          showReportDialog(row);
        },
        // 用户当前id
        disabled: row => row.completeQuantity !==0 ||
            !isCurrentUserInUserIds(row)
        // // 用户当前id
        disabled: row => row.completeQuantity >= row.planQuantity ||
            !isCurrentUserInUserIds(row) || row.hasUnreportedMachine
      },
      {
        name: "生产排产",
@@ -1021,6 +1079,17 @@
            return;
          }
          openScheduleDialog(row);
        },
        disabled: row => !row.canSchedule || row.completeQuantity >= row.planQuantity
      },
      {
        name: "排产记录",
        clickFun: row => {
          if (!row.canSchedule) {
            ElMessage.warning("当前用户不在该工序人员中,不能查看排产记录");
            return;
          }
          openHistoryTimelineDialog(row);
        },
        disabled: row => !row.canSchedule
      }
@@ -1065,6 +1134,7 @@
  planQuantity: 0,
  quantity: null,
  scrapQty: null,
  addQty: 0,
  startTime: "",
  endTime: "",
  userName: "",
@@ -1076,7 +1146,10 @@
  teamList: [],
  deviceId: null,
});
function removeLastFour(str) {
  if (!str) return ''; // 空值保护
  return str.toString().slice(0, -4); // 核心:截取 0 到 倒数第4位
}
// 本次生产数量验证规则
const validateQuantity = (rule, value, callback) => {
  if (value === null || value === undefined || value === "") {
@@ -1148,7 +1221,7 @@
    await Promise.all(updates);
    ElMessage.success("审核成功");
    auditDialogVisible.value = false;
    getList();
    await getList();
  } finally {
    auditLoading.value = false;
  }
@@ -1169,7 +1242,7 @@
  scrapQty: [{validator: validateScrapQty, trigger: "blur"}],
  startTime: [{required: true, message: "请选择开始时间", trigger: "change"}],
  endTime: [{required: true, message: "请选择结束时间", trigger: "change"}],
  auditUserId: [{required: true, message: "请选择审核人", trigger: "change"}],
  // auditUserId: [{required: true, message: "请选择审核人", trigger: "change"}],
  teamList: [{required: true, message: "请选择班组", trigger: "change"}],
  deviceId: [{required: true, message: "请选择设备", trigger: "change"}],
};
@@ -1260,7 +1333,7 @@
/** 搜索按钮操作 */
const handleQuery = () => {
  page.current = 1;
  getList();
   getList();
};
const pagination = obj => {
  page.current = obj.page;
@@ -1378,9 +1451,10 @@
};
const showReportDialog = row => {
  currentReportRowData.value = row;
  reportForm.planQuantity = row.planQuantity - row.completeQuantity;
  reportForm.quantity = row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
  reportForm.quantity = row.planQuantity - row.completeQuantity;
  reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
  reportForm.workOrderId = row.id;
  reportForm.reportWork = row.reportWork;
@@ -1390,6 +1464,7 @@
  reportForm.replenishQty = 0;
  reportForm.teamList = [];
  reportForm.scrapQty = 0;
  reportForm.addQty = 0;
  reportForm.userIds = row.userIds || [];
  const ids = (row.userIds || "")
@@ -1398,7 +1473,7 @@
      .filter(Boolean);
  reportForm.userIdsList = userTeamOptions.value
      .filter(item => ids.includes(String(item.userId)))
      // .filter(item => ids.includes(String(item.userId)))
      .map(item => ({
        userId: item.userId,
        nickName: item.nickName
@@ -1469,12 +1544,12 @@
      return;
    }
    if (quantity > reportForm.planQuantity) {
      ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    // if (quantity > reportForm.planQuantity) {
    //   ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
    //     confirmButtonText: "确定",
    //   });
    //   return;
    // }
    const submitData = {
      ...reportForm,
@@ -1641,6 +1716,14 @@
    justify-content: flex-start;
  }
}
.schedule-user-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 32px;
  align-items: center;
}
</style>
<style lang="scss">
@@ -1727,4 +1810,12 @@
    height: 140px !important;
  }
}
.el-table .cell {
  white-space: normal !important;
  word-break: break-all;
}
.over-limit .el-input__inner {
  color: #f56c6c !important;
}
</style>