zhangwencui
2026-06-03 9696cd7b0754672889dcab70cb5a30fab69d326a
生产报工班组信息可支持手动输入,若系统中无对应人员,也能输入
已修改1个文件
474 ■■■■■ 文件已修改
src/views/productionManagement/workOrder/index.vue 474 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue
@@ -173,93 +173,82 @@
    <el-dialog v-model="reportDialogVisible"
               :title="`报工(工单编号:${currentReportRowData?.workOrderNo || '-'})`"
               width="1000px">
      <el-form
          ref="reportFormRef"
      <el-form ref="reportFormRef"
          :model="reportForm"
          :rules="reportFormRules"
          label-width="120px"
      >
               label-width="120px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="待生产数量">
              <el-input v-model="reportForm.planQuantity" readonly disabled/>
              <el-input v-model="reportForm.planQuantity"
                        readonly
                        disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="本次生产数量" prop="quantity">
              <el-input
                  v-model.number="reportForm.quantity"
            <el-form-item label="本次生产数量"
                          prop="quantity">
              <el-input v-model.number="reportForm.quantity"
                  type="number"
                  min="1"
                  step="1"
                  placeholder="请输入本次生产数量"
                  style="width: 100%"
                  :class="{ 'over-limit': reportForm.quantity > reportForm.planQuantity }"
                  @input="handleQuantityInput"
              />
                        @input="handleQuantityInput" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="补产数量" prop="replenishQty">
              <el-input
                  v-model.number="reportForm.replenishQty"
            <el-form-item label="补产数量"
                          prop="replenishQty">
              <el-input v-model.number="reportForm.replenishQty"
                  type="number"
                  min="0"
                  step="1"
                  placeholder="请输入补产数量"
              />
                        placeholder="请输入补产数量" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="报废数量" prop="scrapQty">
              <el-input
                  v-model.number="reportForm.scrapQty"
            <el-form-item label="报废数量"
                          prop="scrapQty">
              <el-input v-model.number="reportForm.scrapQty"
                  type="number"
                  min="0"
                  step="1"
                  placeholder="请输入报废数量"
                  @input="handleScrapQtyInput"
              />
                        @input="handleScrapQtyInput" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="加放数" prop="addQty">
              <el-input
                  v-model.number="reportForm.addQty"
            <el-form-item label="加放数"
                          prop="addQty">
              <el-input v-model.number="reportForm.addQty"
                  type="number"
                  min="0"
                  step="1"
                  placeholder="请输入加放数"
              />
                        placeholder="请输入加放数" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="班组信息" prop="teamList">
              <el-select
                  v-model="reportForm.teamList"
            <el-form-item label="班组信息"
                          prop="teamList">
              <el-select v-model="reportForm.teamList"
                  multiple
                  filterable
                         allow-create
                         default-first-option
                  clearable
                  collapse-tags
                  value-key="userId"
                  placeholder="请选择班组成员"
              >
                <el-option
                    v-for="user in reportForm.userIdsList"
                         value-key="userName"
                         placeholder="请选择或输入班组成员"
                         @change="handleTeamListChange">
                <el-option v-for="user in reportForm.userIdsList"
                    :key="user.userId"
                    :label="user.nickName"
                    :value="{ userId: user.userId, userName: user.nickName }"
                />
                           :value="{ userId: user.userId, userName: user.nickName }" />
              </el-select>
            </el-form-item>
          </el-col>
<!--          <el-col :span="12">-->
<!--            <el-form-item label="机台" prop="deviceId">-->
<!--              <el-select-->
@@ -279,7 +268,6 @@
<!--              </el-select>-->
<!--            </el-form-item>-->
<!--          </el-col>-->
<!--          <el-col :span="12">-->
<!--            <el-form-item label="审核人" prop="auditUserId">-->
<!--              <el-select-->
@@ -298,7 +286,6 @@
<!--              </el-select>-->
<!--            </el-form-item>-->
<!--          </el-col>-->
        </el-row>
      </el-form>
      <template #footer>
@@ -309,36 +296,61 @@
        </span>
      </template>
    </el-dialog>
    <el-dialog
        v-model="auditDialogVisible"
    <el-dialog v-model="auditDialogVisible"
        title="审核"
        width="1000px"
        :close-on-click-modal="false"
    >
      <el-table :data="auditTableData" border style="width: 100%" v-loading="auditLoading">
        <el-table-column label="产品名称" prop="productName" min-width="140" show-overflow-tooltip/>
        <el-table-column label="规格" prop="model" min-width="120" show-overflow-tooltip/>
        <el-table-column label="单位" prop="unit" width="80"/>
        <el-table-column label="工序名称" prop="processName" min-width="120" show-overflow-tooltip/>
        <el-table-column label="需求数量" prop="planQuantity" width="110"/>
        <el-table-column label="完成数量" prop="completeQuantity" width="110"/>
        <el-table-column label="完成进度" prop="completionStatus" width="140">
               :close-on-click-modal="false">
      <el-table :data="auditTableData"
                border
                style="width: 100%"
                v-loading="auditLoading">
        <el-table-column label="产品名称"
                         prop="productName"
                         min-width="140"
                         show-overflow-tooltip />
        <el-table-column label="规格"
                         prop="model"
                         min-width="120"
                         show-overflow-tooltip />
        <el-table-column label="单位"
                         prop="unit"
                         width="80" />
        <el-table-column label="工序名称"
                         prop="processName"
                         min-width="120"
                         show-overflow-tooltip />
        <el-table-column label="需求数量"
                         prop="planQuantity"
                         width="110" />
        <el-table-column label="完成数量"
                         prop="completeQuantity"
                         width="110" />
        <el-table-column label="完成进度"
                         prop="completionStatus"
                         width="140">
          <template #default="{ row }">
            <el-progress
                :percentage="toProgressPercentage(row?.completionStatus)"
            <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                :color="progressColor(toProgressPercentage(row?.completionStatus))"
                :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
            />
                         :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
          </template>
        </el-table-column>
        <el-table-column label="计划开始时间" prop="planStartTime" width="140"/>
        <el-table-column label="计划结束时间" prop="planEndTime" width="140"/>
        <el-table-column label="计划开始时间"
                         prop="planStartTime"
                         width="140" />
        <el-table-column label="计划结束时间"
                         prop="planEndTime"
                         width="140" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="auditLoading" @click="submitAudit(1)">通过</el-button>
          <el-button type="danger" :loading="auditLoading" @click="submitAudit(2)">不通过</el-button>
          <el-button :disabled="auditLoading" @click="auditDialogVisible = false">取消</el-button>
          <el-button type="primary"
                     :loading="auditLoading"
                     @click="submitAudit(1)">通过</el-button>
          <el-button type="danger"
                     :loading="auditLoading"
                     @click="submitAudit(2)">不通过</el-button>
          <el-button :disabled="auditLoading"
                     @click="auditDialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
@@ -347,50 +359,56 @@
               width="1000px"
               :close-on-click-modal="false">
      <div class="schedule-panel">
        <el-row v-if="!isScheduleHistoryMode" 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">
            <el-button type="primary"
                       plain
                       :disabled="scheduleLoading || scheduleSaving"
                       @click="addScheduleRow">
              新增一行
            </el-button>
          </el-col>
        </el-row>
        <el-table :data="scheduleRows" border style="width: 100%" v-loading="scheduleLoading">
          <el-table-column type="index" label="序号" width="70" align="center" :index="indexMethod" />
          <el-table-column label="本次上机机台" min-width="220">
        <el-table :data="scheduleRows"
                  border
                  style="width: 100%"
                  v-loading="scheduleLoading">
          <el-table-column type="index"
                           label="序号"
                           width="70"
                           align="center"
                           :index="indexMethod" />
          <el-table-column label="本次上机机台"
                           min-width="220">
            <template #default="{ row }">
              <el-select
                  v-model="row.deviceId"
              <el-select v-model="row.deviceId"
                  placeholder="请选择机台"
                  filterable
                  clearable
                  style="width: 100%"
                  :disabled="scheduleSaving || isScheduleHistoryMode"
                  @change="val => handleScheduleDeviceChange(val, row)"
              >
                <el-option
                    v-for="item in deviceOptions"
                         @change="val => handleScheduleDeviceChange(val, row)">
                <el-option v-for="item in deviceOptions"
                    :key="item.id"
                    :label="item.deviceName"
                    :value="String(item.id)"
                />
                           :value="String(item.id)" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="本次上机人" min-width="220">
          <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"
              <div v-if="isScheduleHistoryMode"
                   class="schedule-user-tags">
                <el-tag v-for="(userId, index) in row.userIds"
                    :key="`${userId}-${index}`"
                    type="primary"
                >
                        type="primary">
                  {{ resolveScheduleUserName(userId) }}
                </el-tag>
                <span v-if="!row.userIds?.length">-</span>
              </div>
              <el-select
                  v-else
              <el-select v-else
                  v-model="row.userIds"
                  placeholder="请选择上机人"
                  filterable
@@ -399,65 +417,59 @@
                  collapse-tags
                  style="width: 100%"
                  :disabled="scheduleSaving"
                  @change="val => handleScheduleUserChange(val, row)"
              >
                <el-option
                    v-for="user in row.userOptions"
                         @change="val => handleScheduleUserChange(val, row)">
                <el-option v-for="user in row.userOptions"
                    :key="user.userId"
                    :label="user.nickName"
                    :value="String(user.userId)"
                />
                           :value="String(user.userId)" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="本次上机时间" min-width="240">
          <el-table-column label="本次上机时间"
                           min-width="240">
            <template #default="{ row }">
              <el-date-picker
                  v-model="row.startTime"
              <el-date-picker v-model="row.startTime"
                  type="datetime"
                  value-format="YYYY-MM-DD HH:mm:ss"
                  format="YYYY-MM-DD HH:mm:ss"
                  placeholder="请选择上机时间"
                  style="width: 100%"
                  :disabled="scheduleSaving || isScheduleHistoryMode"
              />
                              :disabled="scheduleSaving || isScheduleHistoryMode" />
            </template>
          </el-table-column>
          <el-table-column v-if="!isScheduleHistoryMode" label="操作" width="110" align="center">
          <el-table-column v-if="!isScheduleHistoryMode"
                           label="操作"
                           width="110"
                           align="center">
            <template #default="{ row }">
              <el-button
                  link
              <el-button link
                  type="danger"
                  :loading="row.deleting"
                  :disabled="scheduleSaving"
                  @click="removeScheduleRow(row)"
              >
                         @click="removeScheduleRow(row)">
                删除
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <Pagination
            v-show="isScheduleHistoryMode && schedulePage.total > 0"
        <Pagination v-show="isScheduleHistoryMode && schedulePage.total > 0"
            style="margin-top: 12px"
            :total="schedulePage.total"
            :page="schedulePage.current"
            :limit="schedulePage.size"
            @pagination="handleSchedulePagination"
        />
                    @pagination="handleSchedulePagination" />
      </div>
      <template #footer>
        <span class="dialog-footer">
          <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>
            <el-button type="primary"
                       :loading="scheduleSaving"
                       @click="handleSaveSchedule">保存排产</el-button>
            <el-button :disabled="scheduleSaving"
                       @click="scheduleDialogVisible = false">取消</el-button>
          </template>
        </span>
      </template>
@@ -468,11 +480,11 @@
<script setup>
import {onMounted, ref, nextTick, computed} from "vue";
import {deepClone} from "@/utils/index.js"
  import { deepClone } from "@/utils/index.js";
import {ElMessageBox, ElMessage} from "element-plus";
import Pagination from "@/components/PIMTable/Pagination.vue";
import dayjs from "dayjs";
import { processList } from '@/api/productionManagement/productionProcess.js'
  import { processList } from "@/api/productionManagement/productionProcess.js";
import {
  productWorkOrderPage,
  updateProductWorkOrder,
@@ -480,7 +492,7 @@
  downProductWorkOrder,
  addProductionMachineRecord,
  productionMachineRecordListPage,
  deleteProductionMachineRecord
    deleteProductionMachineRecord,
} from "@/api/productionManagement/workOrder.js";
import {getUserProfile, userListNoPageByTenantId} from "@/api/system/user.js";
import QRCode from "qrcode";
@@ -491,7 +503,7 @@
const {proxy} = getCurrentInstance();
const currentUserId = ref("");
const deviceOptions = ref([])
  const deviceOptions = ref([]);
const currentUserName = ref("");
const ensureCurrentUser = async () => {
@@ -510,29 +522,29 @@
// 机台获取
const getDeviceList = () => {
  getDeviceLedger().then(res => {
    deviceOptions.value = Array.isArray(res?.data) ? res.data : []
  })
}
      deviceOptions.value = Array.isArray(res?.data) ? res.data : [];
    });
  };
const handleDeviceChange = (val) => {
  const device = deviceOptions.value.find(item => item.id === val)
  reportForm.deviceName = device?.deviceName || ""
  reportForm.deviceId = val || ""
}
  const handleDeviceChange = val => {
    const device = deviceOptions.value.find(item => item.id === val);
    reportForm.deviceName = device?.deviceName || "";
    reportForm.deviceId = val || "";
  };
const normalizeArray = (val) => {
  const normalizeArray = val => {
  if (val === null || val === undefined) return [];
  if (Array.isArray(val)) return val;
  if (typeof val === "string") {
    return val
        .split(/[,,;;\s]+/g)
        .map((s) => s.trim())
        .map(s => s.trim())
        .filter(Boolean);
  }
  return [val];
};
const isCurrentUserInUserIds = (row) => {
  const isCurrentUserInUserIds = row => {
  const uid = String(currentUserId.value || "");
  if (!uid) return false;
@@ -544,7 +556,7 @@
};
// 判断当前用户是否在工序报工人中
const isCurrentUserInProcessUserIds = (row) => {
  const isCurrentUserInProcessUserIds = row => {
  const uid = String(currentUserId.value || "");
  if (!uid) return false;
@@ -556,35 +568,35 @@
};
// 判断当前用户是否可以报工(工单报工人 或 工序报工人)
const canCurrentUserReport = (row) => {
  const canCurrentUserReport = row => {
  return isCurrentUserInUserIds(row) || isCurrentUserInProcessUserIds(row);
};
const canOperateByReportWorker = computed(() => {
  return (row) => isCurrentUserReportWorker(row);
    return row => isCurrentUserReportWorker(row);
});
const isRowScheduled = (row) => {
  const isRowScheduled = row => {
  const ids = normalizeArray(row?.userIds)
    .map((val) => String(val))
      .map(val => String(val))
    .filter(Boolean);
  if (!ids.length) return false;
  return ids.some((val) => val !== "0");
    return ids.some(val => val !== "0");
};
const buildBaseScheduleUsersByRow = (row) => {
  const buildBaseScheduleUsersByRow = row => {
  if (!row) return [];
  if (Array.isArray(row?.reportWorkerList) && row.reportWorkerList.length > 0) {
    const mapped = row.reportWorkerList
      .map((item) => {
        .map(item => {
        const userId = String(item?.userId ?? item?.id ?? "").trim();
        const nickName = String(item?.userName ?? item?.nickName ?? "").trim();
        return { userId, nickName: nickName || userId };
      })
      .filter((item) => item.userId);
        .filter(item => item.userId);
    const uniq = new Map();
    mapped.forEach((item) => uniq.set(String(item.userId), item));
      mapped.forEach(item => uniq.set(String(item.userId), item));
    return Array.from(uniq.values());
  }
@@ -595,40 +607,44 @@
    row.reportUserId,
    row.userId,
  ]
    .flatMap((v) => normalizeArray(v))
    .map((v) => String(v).trim())
      .flatMap(v => normalizeArray(v))
      .map(v => String(v).trim())
    .filter(Boolean);
  if (configuredIds.length > 0) {
    const uniqIds = Array.from(new Set(configuredIds));
    return uniqIds.map((id) => {
      const user = userTeamOptions.value.find((u) => String(u.userId) === String(id));
      return uniqIds.map(id => {
        const user = userTeamOptions.value.find(
          u => String(u.userId) === String(id)
        );
      return { userId: String(id), nickName: user?.nickName || String(id) };
    });
  }
  return userTeamOptions.value.map((u) => ({
    return userTeamOptions.value.map(u => ({
    userId: String(u.userId),
    nickName: u.nickName,
  }));
};
const resolveScheduleUserName = (userId) => {
  const resolveScheduleUserName = userId => {
  const uid = String(userId ?? "").trim();
  if (!uid) return "";
  const inBase = baseScheduleUsers.value.find((u) => String(u.userId) === uid);
    const inBase = baseScheduleUsers.value.find(u => String(u.userId) === uid);
  if (inBase?.nickName) return inBase.nickName;
  const inTeam = userTeamOptions.value.find((u) => String(u.userId) === uid);
    const inTeam = userTeamOptions.value.find(u => String(u.userId) === uid);
  return inTeam?.nickName || uid;
};
const buildScheduleUserOptionsByDeviceId = (deviceId) => {
  const device = deviceOptions.value.find((item) => String(item.id) === String(deviceId));
  const buildScheduleUserOptionsByDeviceId = deviceId => {
    const device = deviceOptions.value.find(
      item => String(item.id) === String(deviceId)
    );
  const operatorIds = device?.operatorId
    ? String(device.operatorId)
      .split(/[,,;;\s]+/g)
      .map((id) => id.trim())
          .map(id => id.trim())
      .filter(Boolean)
    : [];
@@ -636,7 +652,7 @@
    return [...baseScheduleUsers.value];
  }
  return baseScheduleUsers.value.filter((user) =>
    return baseScheduleUsers.value.filter(user =>
    operatorIds.includes(String(user.userId))
  );
};
@@ -652,9 +668,11 @@
    : [...baseScheduleUsers.value];
  const userIds = normalizeArray(preset?.userIds)
    .map((val) => String(val))
      .map(val => String(val))
    .filter(Boolean)
    .filter((uid) => userOptions.some((user) => String(user.userId) === String(uid)));
      .filter(uid =>
        userOptions.some(user => String(user.userId) === String(uid))
      );
  return {
    id: preset?.id ?? "",
@@ -667,7 +685,7 @@
  };
};
const addScheduleRow = (preset) => {
  const addScheduleRow = preset => {
  if (preset) {
    scheduleRows.value.push(createScheduleRow(preset));
    return;
@@ -705,11 +723,12 @@
    const records = Array.isArray(res?.data?.records) ? res.data.records : [];
    const apiTotal = Number(res?.data?.total);
    schedulePage.total =
      Number.isFinite(apiTotal) && apiTotal > 0
        ? apiTotal
        : records.length;
        Number.isFinite(apiTotal) && apiTotal > 0 ? apiTotal : records.length;
    const lastPage = Math.max(1, Math.ceil((schedulePage.total || 0) / schedulePage.size));
      const lastPage = Math.max(
        1,
        Math.ceil((schedulePage.total || 0) / schedulePage.size)
      );
    if (schedulePage.current > lastPage) {
      schedulePage.current = lastPage;
      await refreshScheduleRows();
@@ -728,11 +747,11 @@
  }
};
const removeScheduleRow = async (row) => {
  const removeScheduleRow = async row => {
  if (!row || isScheduleHistoryMode.value) return;
  if (!row.id) {
    scheduleRows.value = scheduleRows.value.filter((item) => item !== row);
      scheduleRows.value = scheduleRows.value.filter(item => item !== row);
    if (!scheduleRows.value.length) {
      addScheduleRow();
    }
@@ -769,19 +788,28 @@
};
const handleScheduleUserChange = (userIds, row) => {
  row.userIds = normalizeArray(userIds).map((val) => String(val)).filter(Boolean);
    row.userIds = normalizeArray(userIds)
      .map(val => String(val))
      .filter(Boolean);
};
const handleScheduleDeviceChange = (deviceId, row) => {
  const device = deviceOptions.value.find((item) => String(item.id) === String(deviceId));
    const device = deviceOptions.value.find(
      item => String(item.id) === String(deviceId)
    );
  row.deviceId = deviceId === null || deviceId === undefined ? "" : String(deviceId);
    row.deviceId =
      deviceId === null || deviceId === undefined ? "" : String(deviceId);
  row.deviceName = device?.deviceName || "";
  row.userOptions = row.deviceId ? buildScheduleUserOptionsByDeviceId(row.deviceId) : [...baseScheduleUsers.value];
    row.userOptions = row.deviceId
      ? buildScheduleUserOptionsByDeviceId(row.deviceId)
      : [...baseScheduleUsers.value];
  row.userIds = normalizeArray(row.userIds)
    .map((uid) => String(uid))
    .filter((uid) => row.userOptions.some((user) => String(user.userId) === String(uid)));
      .map(uid => String(uid))
      .filter(uid =>
        row.userOptions.some(user => String(user.userId) === String(uid))
      );
};
const handleSchedulePagination = ({page, limit}) => {
@@ -821,23 +849,26 @@
    }
  }
  return true;
};
const buildMachineRecordPayload = (workOrderRow, scheduleRow, sortIndex = 0) => {
  const buildMachineRecordPayload = (
    workOrderRow,
    scheduleRow,
    sortIndex = 0
  ) => {
  const processId =
    workOrderRow?.processId ??
    workOrderRow?.productProcessRouteItemId ??
    reportForm.productProcessRouteItemId;
  const operatorIds = normalizeArray(scheduleRow?.userIds)
    .map((val) => String(val).trim())
      .map(val => String(val).trim())
    .filter(Boolean)
    .join(",");
  const nickName = normalizeArray(scheduleRow?.userIds)
    .map((uid) => resolveScheduleUserName(uid))
      .map(uid => resolveScheduleUserName(uid))
    .filter(Boolean)
    .join(",");
@@ -860,39 +891,47 @@
  return payload;
};
const indexMethod = (index) => {
  const indexMethod = index => {
  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 isScheduleHistoryMode = computed(
    () => scheduleDialogMode.value === "history"
  );
  const scheduleDialogTitle = computed(
    () =>
      `${isScheduleHistoryMode.value ? "排产记录" : "生产排产"}(工单编号:${
        currentReportRowData.value?.workOrderNo || "-"
      })`
);
const mapMachineRecordToScheduleRow = (record) => {
  const mapMachineRecordToScheduleRow = record => {
  const id = record?.id ?? "";
  const deviceId = record?.machineId ?? record?.deviceId ?? "";
  const deviceName = record?.deviceName ?? record?.machineName ?? "";
  const startTime = record?.machineStartTime ?? record?.startTime ?? "";
  const userIds = normalizeArray(record?.operatorId ?? record?.operatorIds ?? record?.userId)
    .map((val) => String(val))
    const userIds = normalizeArray(
      record?.operatorId ?? record?.operatorIds ?? record?.userId
    )
      .map(val => String(val))
    .filter(Boolean);
  return createScheduleRow({
    id,
    deviceId: deviceId === null || deviceId === undefined ? "" : String(deviceId),
      deviceId:
        deviceId === null || deviceId === undefined ? "" : String(deviceId),
    deviceName,
    userIds,
    startTime,
  });
};
const buildScheduleRowsFromRecords = (records) => {
  const buildScheduleRowsFromRecords = records => {
  const list = Array.isArray(records) ? records : [];
  const grouped = new Map();
  list.forEach((record) => {
    list.forEach(record => {
    const row = mapMachineRecordToScheduleRow(record);
    const key = `${row.deviceId}__${row.startTime}__${row.deviceName}`;
@@ -904,10 +943,15 @@
    const existing = grouped.get(key);
    existing.ids = Array.from(new Set([existing.id, row.id].filter(Boolean)));
    existing.userIds = Array.from(
      new Set([...(existing?.userIds || []), ...(row?.userIds || [])].map((v) => String(v)))
        new Set(
          [...(existing?.userIds || []), ...(row?.userIds || [])].map(v =>
            String(v)
          )
        )
    ).filter(Boolean);
    if (!existing.deviceName && row.deviceName) existing.deviceName = row.deviceName;
      if (!existing.deviceName && row.deviceName)
        existing.deviceName = row.deviceName;
  });
  return Array.from(grouped.values()).sort(
@@ -922,7 +966,7 @@
  addScheduleRow();
};
const openScheduleDialog = async (row) => {
  const openScheduleDialog = async row => {
  scheduleDialogMode.value = "create";
  currentReportRowData.value = row;
  baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
@@ -931,7 +975,7 @@
  resetCreateScheduleRows();
};
const openHistoryTimelineDialog = async (row) => {
  const openHistoryTimelineDialog = async row => {
  scheduleDialogMode.value = "history";
  currentReportRowData.value = row;
  baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
@@ -955,7 +999,9 @@
    return;
  }
  const sortedRows = [...scheduleRows.value].sort((a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf());
    const sortedRows = [...scheduleRows.value].sort(
      (a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf()
    );
  scheduleSaving.value = true;
  try {
@@ -1002,7 +1048,7 @@
    label: "成品名称",
    prop: "finalProductModel",
    minWidth: 200,
    overHidden: false
      overHidden: false,
  },
  // {
  //   label: "加工品名称",
@@ -1089,8 +1135,10 @@
          showReportDialog(row);
        },
        // 用户当前id在工单报工人或工序报工人中
        disabled: row => row.completeQuantity >= row.planQuantity ||
            !canCurrentUserReport(row) || row.hasUnreportedMachine
          disabled: row =>
            row.completeQuantity >= row.planQuantity ||
            !canCurrentUserReport(row) ||
            row.hasUnreportedMachine,
      },
      {
        name: "生产排产",
@@ -1101,7 +1149,8 @@
          }
          openScheduleDialog(row);
        },
        disabled: row => !row.canSchedule || row.completeQuantity >= row.planQuantity
          disabled: row =>
            !row.canSchedule || row.completeQuantity >= row.planQuantity,
      },
      {
        name: "排产记录",
@@ -1112,8 +1161,8 @@
          }
          openHistoryTimelineDialog(row);
        },
        disabled: row => !row.canSchedule
      }
          disabled: row => !row.canSchedule,
        },
      // {
      //   name:"审核",
      //   color: "#f56c6c",
@@ -1168,7 +1217,7 @@
  deviceId: null,
});
function removeLastFour(str) {
  if (!str) return ''; // 空值保护
    if (!str) return ""; // 空值保护
  return str.toString().slice(0, -4); // 核心:截取 0 到 倒数第4位
}
// 本次生产数量验证规则
@@ -1202,7 +1251,7 @@
};
// 审核
const handleAudit = (row) => {
  const handleAudit = row => {
  if (Number(row?.auditStatus) === 1) {
    ElMessage.warning("该工单已审核");
    return;
@@ -1216,7 +1265,7 @@
  auditDialogVisible.value = true;
};
const submitAudit = async (result) => {
  const submitAudit = async result => {
  const current = auditRowData.value;
  if (!current) return;
  if (auditLoading.value) return;
@@ -1249,13 +1298,13 @@
};
// 查看详情
const handleView = (row) => {
  const handleView = row => {
  const {workOrderId} = row;
  router.push({
    path: "/productionManagement/workOrderDetail",
    query: {workOrderId},
  });
}
  };
// 验证规则
const reportFormRules = {
@@ -1375,7 +1424,7 @@
    tableData.value = records.map(row => ({
      ...row,
      canSchedule: canScheduleByWorkOrderNo(row)
        canSchedule: canScheduleByWorkOrderNo(row),
    }));
    page.total = res?.data?.total || 0;
@@ -1472,7 +1521,6 @@
};
const showReportDialog = row => {
  currentReportRowData.value = row;
  reportForm.planQuantity = row.planQuantity - row.completeQuantity;
  reportForm.quantity = row.planQuantity - row.completeQuantity;
@@ -1502,9 +1550,8 @@
      .filter(item => allowedUserIds.includes(String(item.userId)))
      .map(item => ({
        userId: item.userId,
        nickName: item.nickName
        nickName: item.nickName,
      }));
  nextTick(() => {
    reportFormRef.value?.clearValidate();
@@ -1601,12 +1648,12 @@
  userListNoPageByTenantId()
      .then(res => {
        if (res.code === 200) {
          const list = Array.isArray(res.data) ? res.data : []
          const list = Array.isArray(res.data) ? res.data : [];
          userOptions.value = [
            {nickName: "任意用户", userId: "-1"},
            ...deepClone(list)
          ]
          userTeamOptions.value = deepClone(list)
            ...deepClone(list),
          ];
          userTeamOptions.value = deepClone(list);
        }
      })
      .catch(err => {
@@ -1625,6 +1672,17 @@
    reportForm.userName = "";
  }
};
  const handleTeamListChange = val => {
    if (!Array.isArray(val)) return;
    if (!val.some(item => typeof item === "string")) return;
    reportForm.teamList = val.map(item => {
      if (typeof item === "string") {
        return { userName: item };
      }
      return item;
    });
  };
// 审核人
const handleReviewerIdChange = userId => {
  if (userId) {
@@ -1635,13 +1693,13 @@
  } else {
    reportForm.auditUserName = "";
  }
}
  };
const processData = ref([]);
// 查询所有工序
const processLists = async () => {
  console.log(processData.value)
    console.log(processData.value);
  if (processData.value.length > 0) {
    return processData.value;
  }
@@ -1652,14 +1710,14 @@
};
// 判断当前用户是否能排产
const canScheduleByWorkOrderNo = (row) => {
  const canScheduleByWorkOrderNo = row => {
  if (!row) return false;
  const uid = String(currentUserId.value || "");
  if (!uid) return false;
  const currentProcess = processData.value.find(item =>
      String(item.id) === String(row.processId)
    const currentProcess = processData.value.find(
      item => String(item.id) === String(row.processId)
  );
  if (!currentProcess) return false;