张诺
8 小时以前 71a260afa397035d3844ab13f2fea5669f7b46ab
src/views/productionManagement/workOrder/index.vue
@@ -9,11 +9,12 @@
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search" />
                    prefix-icon="Search"/>
        </div>
        <div class="search-item">
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
                     @click="handleQuery">搜索
          </el-button>
        </div>
      </div>
    </div>
@@ -27,7 +28,7 @@
        <template #completionStatus="{ row }">
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"/>
        </template>
      </PIMTable>
    </div>
@@ -41,28 +42,28 @@
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
                          style="width: 300px"/>
        </el-form-item>
        <el-form-item label="计划结束时间">
          <el-date-picker v-model="editrow.planEndTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
                          style="width: 300px"/>
        </el-form-item>
        <el-form-item label="实际开始时间">
          <el-date-picker v-model="editrow.actualStartTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
                          style="width: 300px"/>
        </el-form-item>
        <el-form-item label="实际结束时间">
          <el-date-picker v-model="editrow.actualEndTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          style="width: 300px" />
                          style="width: 300px"/>
        </el-form-item>
      </el-form>
      <template #footer>
@@ -98,12 +99,12 @@
            </div>
            <!-- <div class="info-item">
              <span class="info-label">工单状态</span>
              <span class="info-value">{{
                transferCardRowData.status === 1 ? '待确认' :
                transferCardRowData.status === 2 ? '待生产' :
                transferCardRowData.status === 3 ? '生产中' :
                transferCardRowData.status === 4 ? '已生产' :
                transferCardRowData.status
              <span class="info-value">{{
                transferCardRowData.status === 1 ? '待确认' :
                transferCardRowData.status === 2 ? '待生产' :
                transferCardRowData.status === 3 ? '生产中' :
                transferCardRowData.status === 4 ? '已生产' :
                transferCardRowData.status
              }}</span>
            </div> -->
            <div class="info-item">
@@ -150,7 +151,7 @@
          <div class="qr-container">
            <img :src="transferCardQrUrl"
                 alt="流转卡二维码"
                 style="width: 200px; height: 200px;" />
                 style="width: 200px; height: 200px;"/>
            <!-- <div class="qr-tip"
                 style="margin-top: 10px; text-align: center;">流转卡二维码</div> -->
          </div>
@@ -161,54 +162,127 @@
      margin-bottom: 40px;">
        <el-button type="primary"
                   style="margin-top: 20px;"
                   @click="printTransferCard">打印流转卡</el-button>
                   @click="printTransferCard">打印流转卡
        </el-button>
      </div>
    </el-dialog>
    <el-dialog v-model="reportDialogVisible"
               title="报工"
               width="500px">
      <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-form-item>
        <el-form-item label="本次生产数量"
                      prop="quantity">
          <el-input v-model.number="reportForm.quantity"
                    type="number"
                    min="1"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入本次生产数量"
                    @input="handleQuantityInput" />
        </el-form-item>
        <el-form-item label="报废数量"
                      prop="scrapQty">
          <el-input v-model.number="reportForm.scrapQty"
                    type="number"
                    min="0"
                    step="1"
                    style="width: 300px"
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
        </el-form-item>
        <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
                     style="width: 300px"
                     placeholder="请选择班组信息"
                     clearable
                     filterable
                     @change="handleUserChange">
            <el-option v-for="user in userOptions"
                       :key="user.userId"
                       :label="user.nickName"
                       :value="user.userId" />
          </el-select>
        </el-form-item>
               :title="`报工(工单编号:${currentReportRowData?.workOrderNo || '-'})`"
               width="1000px">
      <el-form
          ref="reportFormRef"
          :model="reportForm"
          :rules="reportFormRules"
          label-width="120px"
      >
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="待生产数量">
              <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"
                  type="number"
                  min="1"
                  step="1"
                  placeholder="请输入本次生产数量"
                  style="width: 100%"
                  @input="handleQuantityInput"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="补产数量" prop="replenishQty">
              <el-input
                  v-model.number="reportForm.replenishQty"
                  type="number"
                  min="0"
                  step="1"
                  placeholder="请输入补产数量"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="报废数量" prop="scrapQty">
              <el-input
                  v-model.number="reportForm.scrapQty"
                  type="number"
                  min="0"
                  step="1"
                  placeholder="请输入报废数量"
                  @input="handleScrapQtyInput"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="班组信息" prop="teamList">
              <el-select
                  v-model="reportForm.teamList"
                  multiple
                  filterable
                  clearable
                  collapse-tags
                  value-key="userId"
                  placeholder="请选择班组成员"
              >
                <el-option
                    v-for="user in userTeamOptions"
                    :key="user.userId"
                    :label="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
                  v-model="reportForm.deviceId"
                  placeholder="请选择机台"
                  filterable
                  clearable
                  @change="(val) => handleDeviceChange(val)"
                  :disabled="isDetail"
              >
                <el-option
                    v-for="item in deviceOptions"
                    :key="item.id"
                    :label="item.deviceName"
                    :value="item.id"
                />
              </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>
      <template #footer>
        <span class="dialog-footer">
@@ -218,289 +292,1010 @@
        </span>
      </template>
    </el-dialog>
    <FilesDia ref="workOrderFilesRef" />
    <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">
          <template #default="{ row }">
            <el-progress
                :percentage="toProgressPercentage(row?.completionStatus)"
                :color="progressColor(toProgressPercentage(row?.completionStatus))"
                :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>
      <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>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="scheduleDialogVisible"
               :title="`生产排产(工单编号:${currentReportRowData?.workOrderNo || '-'})`"
               width="1000px"
               :close-on-click-modal="false">
      <div class="schedule-panel">
        <el-row style="margin-bottom: 12px;">
          <el-col>
            <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" />
          <el-table-column label="本次上机机台" min-width="220">
            <template #default="{ row }">
              <el-select
                  v-model="row.deviceId"
                  placeholder="请选择机台"
                  filterable
                  clearable
                  style="width: 100%"
                  :disabled="scheduleSaving"
                  @change="val => handleScheduleDeviceChange(val, row)"
              >
                <el-option
                    v-for="item in deviceOptions"
                    :key="item.id"
                    :label="item.deviceName"
                    :value="String(item.id)"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="本次上机人" min-width="220">
            <template #default="{ row }">
              <el-select
                  v-model="row.userIds"
                  placeholder="请选择上机人"
                  filterable
                  multiple
                  clearable
                  collapse-tags
                  style="width: 100%"
                  :disabled="scheduleSaving"
                  @change="val => handleScheduleUserChange(val, row)"
              >
                <el-option
                    v-for="user in row.userOptions"
                    :key="user.userId"
                    :label="user.nickName"
                    :value="String(user.userId)"
                />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="本次上机时间" min-width="240">
            <template #default="{ row }">
              <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"
              />
            </template>
          </el-table-column>
          <el-table-column label="操作" width="110" align="center">
            <template #default="{ row }">
              <el-button
                  link
                  type="danger"
                  :loading="row.deleting"
                  :disabled="scheduleSaving"
                  @click="removeScheduleRow(row)"
              >
                删除
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <Pagination
            v-show="schedulePage.total > 0"
            style="margin-top: 12px"
            :total="schedulePage.total"
            :page="schedulePage.current"
            :limit="schedulePage.size"
            @pagination="handleSchedulePagination"
        />
      </div>
      <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>
        </span>
      </template>
    </el-dialog>
    <FilesDia ref="workOrderFilesRef"/>
  </div>
</template>
<script setup>
  import { onMounted, ref, nextTick } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
    productWorkOrderPage,
    updateProductWorkOrder,
    addProductMain,
    downProductWorkOrder,
  } from "@/api/productionManagement/workOrder.js";
  import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
  import QRCode from "qrcode";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  import FilesDia from "./components/filesDia.vue";
  const { proxy } = getCurrentInstance();
import {onMounted, ref, nextTick, computed} from "vue";
import {deepClone} from "@/utils/index.js"
import {ElMessageBox, ElMessage} from "element-plus";
import Pagination from "@/components/PIMTable/Pagination.vue";
import dayjs from "dayjs";
import {
  productWorkOrderPage,
  updateProductWorkOrder,
  addProductMain,
  downProductWorkOrder,
  addProductionMachineRecord,
  productionMachineRecordListPage,
  deleteProductionMachineRecord
} from "@/api/productionManagement/workOrder.js";
import {getUserProfile, userListNoPageByTenantId} from "@/api/system/user.js";
import QRCode from "qrcode";
import {getCurrentInstance, reactive, toRefs} from "vue";
import FilesDia from "./components/filesDia.vue";
import {getDeviceLedger} from "@/api/equipmentManagement/ledger.js";
  const tableColumn = ref([
    {
      label: "工单类型",
      prop: "workOrderType",
      width: "80",
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      width: "140",
    },
    {
      label: "生产订单号",
      prop: "productOrderNpsNo",
      width: "140",
    },
    {
      label: "产品名称",
      prop: "productName",
      width: "140",
    },
    {
      label: "规格",
      prop: "model",
    },
    {
      label: "单位",
      prop: "unit",
    },
    {
      label: "工序名称",
      prop: "processName",
    },
    {
      label: "需求数量",
      prop: "planQuantity",
      width: "140",
    },
    {
      label: "完成数量",
      prop: "completeQuantity",
      width: "140",
    },
    {
      label: "完成进度",
      prop: "completionStatus",
      dataType: "slot",
      slot: "completionStatus",
      width: "140",
    },
    {
      label: "计划开始时间",
      prop: "planStartTime",
      width: "140",
    },
    {
      label: "计划结束时间",
      prop: "planEndTime",
      width: "140",
    },
    {
      label: "实际开始时间",
      prop: "actualStartTime",
      width: "140",
    },
    {
      label: "实际结束时间",
      prop: "actualEndTime",
      width: "140",
    },
    {
      label: "操作",
      width: "200",
      align: "center",
      dataType: "action",
      fixed: "right",
      operation: [
        {
          name: "编辑",
          clickFun: row => {
            handleEdit(row);
          },
        },
        {
          name: "流转卡",
          clickFun: row => {
            downloadAndPrintWorkOrder(row);
          },
        },
        {
          name: "附件",
          clickFun: row => {
            openWorkOrderFiles(row);
          },
        },
        {
          name: "报工",
          clickFun: row => {
            showReportDialog(row);
          },
          disabled: row => row.planQuantity <= 0,
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const qrCodeUrl = ref("");
  const qrRowData = ref(null);
  const editDialogVisible = ref(false);
  const transferCardVisible = ref(false);
  const transferCardData = ref([]);
  const transferCardQrUrl = ref("");
  const transferCardRowData = ref(null);
  const reportDialogVisible = ref(false);
  const workOrderFilesRef = ref(null);
  const reportFormRef = ref(null);
  const userOptions = ref([]);
  const reportForm = reactive({
    planQuantity: 0,
    quantity: null,
    scrapQty: null,
    userName: "",
    workOrderId: "",
    reportWork: "",
    productProcessRouteItemId: "",
    userId: "",
    productMainId: null,
const {proxy} = getCurrentInstance();
const currentUserId = ref("");
const deviceOptions = ref([])
const currentUserName = ref("");
const ensureCurrentUser = async () => {
  if (currentUserId.value) return;
  try {
    const res = await getUserProfile();
    if (res?.code === 200) {
      currentUserId.value = String(res?.data?.userId ?? "");
      currentUserName.value = String(res?.data?.nickName ?? "");
    }
  } catch (err) {
    console.error("获取用户信息失败", err);
  }
};
// 机台获取
const getDeviceList = () => {
  getDeviceLedger().then(res => {
    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 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())
        .filter(Boolean);
  }
  return [val];
};
const isCurrentUserReportWorker = (row) => {
  const uid = String(currentUserId.value || "");
  if (!uid) return false;
  if (!row) return false;
  const candidateIds = [
    row.reportUserIds,
    row.reportWorkerIds,
    row.userIdList,
    row.reportUserId,
    row.userId,
  ]
      .flatMap((v) => normalizeArray(v))
      .map((v) => String(v))
      .filter(Boolean);
  if (candidateIds.includes(uid)) return true;
  const candidateNames = [
    row.userNames,
    row.reportUserNames,
    row.reportWorkerNames,
    row.userName,
  ]
      .flatMap((v) => normalizeArray(v))
      .map((v) => String(v))
      .filter(Boolean);
  if (currentUserName.value && candidateNames.includes(currentUserName.value)) {
    return true;
  }
  if (Array.isArray(row.reportWorkerList)) {
    const list = row.reportWorkerList
        .map((item) => String(item?.userId ?? item?.id ?? ""))
        .filter(Boolean);
    if (list.includes(uid)) return true;
    const nameList = row.reportWorkerList
        .map((item) => String(item?.userName ?? item?.nickName ?? ""))
        .filter(Boolean);
    if (currentUserName.value && nameList.includes(currentUserName.value)) return true;
  }
  return false;
};
const canOperateByReportWorker = computed(() => {
  return (row) => isCurrentUserReportWorker(row);
});
const isRowScheduled = (row) => {
  const ids = normalizeArray(row?.userIds)
    .map((val) => String(val))
    .filter(Boolean);
  if (!ids.length) return false;
  return ids.some((val) => val !== "0");
};
const buildBaseScheduleUsersByRow = (row) => {
  if (!row) return [];
  if (Array.isArray(row?.reportWorkerList) && row.reportWorkerList.length > 0) {
    const mapped = row.reportWorkerList
      .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);
    const uniq = new Map();
    mapped.forEach((item) => uniq.set(String(item.userId), item));
    return Array.from(uniq.values());
  }
  const configuredIds = [
    row.reportUserIds,
    row.reportWorkerIds,
    row.userIdList,
    row.reportUserId,
    row.userId,
  ]
    .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 { userId: String(id), nickName: user?.nickName || String(id) };
    });
  }
  return userTeamOptions.value.map((u) => ({
    userId: String(u.userId),
    nickName: u.nickName,
  }));
};
const resolveScheduleUserName = (userId) => {
  const uid = String(userId ?? "").trim();
  if (!uid) return "";
  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);
  return inTeam?.nickName || uid;
};
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())
      .filter(Boolean)
    : [];
  if (!operatorIds.length) {
    return [...baseScheduleUsers.value];
  }
  return baseScheduleUsers.value.filter((user) =>
    operatorIds.includes(String(user.userId))
  );
};
const createScheduleRow = (preset = {}) => {
  const deviceId =
    preset?.deviceId === null || preset?.deviceId === undefined
      ? ""
      : String(preset.deviceId);
  const userOptions = deviceId
    ? buildScheduleUserOptionsByDeviceId(deviceId)
    : [...baseScheduleUsers.value];
  const userIds = normalizeArray(preset?.userIds)
    .map((val) => String(val))
    .filter(Boolean)
    .filter((uid) => userOptions.some((user) => String(user.userId) === String(uid)));
  return {
    id: preset?.id ?? "",
    deviceId,
    deviceName: preset?.deviceName ?? "",
    userIds,
    userOptions,
    startTime: preset?.startTime ?? dayjs().format("YYYY-MM-DD HH:mm:ss"),
    deleting: false,
  };
};
const addScheduleRow = (preset) => {
  if (preset) {
    scheduleRows.value.push(createScheduleRow(preset));
    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] : [],
      startTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    })
  );
};
const refreshScheduleRows = async () => {
  const workOrderRow = currentReportRowData.value;
  if (!workOrderRow?.id) {
    schedulePage.current = 1;
    schedulePage.total = 0;
    scheduleRows.value = [createScheduleRow({})];
    return;
  }
  scheduleLoading.value = true;
  try {
    const res = await productionMachineRecordListPage({
      workOrderId: workOrderRow.id,
      current: schedulePage.current,
      size: schedulePage.size,
    });
    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;
    const lastPage = Math.max(1, Math.ceil((schedulePage.total || 0) / schedulePage.size));
    if (schedulePage.current > lastPage) {
      schedulePage.current = lastPage;
      await refreshScheduleRows();
      return;
    }
    const rows = buildScheduleRowsFromRecords(records);
    scheduleRows.value = rows.length > 0 ? rows : [createScheduleRow({})];
  } catch (error) {
    console.error("获取排产记录失败", error);
    schedulePage.total = 0;
    scheduleRows.value = [createScheduleRow({})];
    ElMessage.error("获取排产记录失败");
  } finally {
    scheduleLoading.value = false;
  }
};
const removeScheduleRow = async (row) => {
  if (!row) return;
  if (!row.id) {
    scheduleRows.value = scheduleRows.value.filter((item) => item !== row);
    if (!scheduleRows.value.length) {
      scheduleRows.value = [createScheduleRow({})];
    }
    return;
  }
  try {
    await ElMessageBox.confirm("确定删除这条排产记录吗?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    });
  } catch {
    return;
  }
  row.deleting = true;
  try {
    const res = await deleteProductionMachineRecord([row.id]);
    if (res?.code !== undefined && res.code !== 200) {
      ElMessage.error(res?.msg || "删除失败");
      return;
    }
    ElMessage.success("删除成功");
    await refreshScheduleRows();
    getList();
  } catch (error) {
    console.error("删除排产记录失败", error);
    ElMessage.error("删除失败,请重试");
  } finally {
    row.deleting = false;
  }
};
const handleScheduleUserChange = (userIds, row) => {
  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));
  row.deviceId = deviceId === null || deviceId === undefined ? "" : String(deviceId);
  row.deviceName = device?.deviceName || "";
  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)));
};
const handleSchedulePagination = ({page, limit}) => {
  schedulePage.current = page;
  schedulePage.size = limit;
  refreshScheduleRows();
};
const validateScheduleRows = () => {
  if (!scheduleRows.value.length) {
    ElMessage.warning("请至少添加一条上机信息");
    return false;
  }
  for (let index = 0; index < scheduleRows.value.length; index += 1) {
    const row = scheduleRows.value[index];
    if (!row.deviceId) {
      ElMessage.warning(`第${index + 1}行请选择机台`);
      return false;
    }
    if (!Array.isArray(row.userIds) || row.userIds.length === 0) {
      ElMessage.warning(`第${index + 1}行请选择上机人`);
      return false;
    }
    if (!row.startTime) {
      ElMessage.warning(`第${index + 1}行请选择上机时间`);
      return false;
    }
    if (!dayjs(row.startTime).isValid()) {
      ElMessage.warning(`第${index + 1}行上机时间格式不正确`);
      return false;
    }
  }
  return true;
};
const buildMachineRecordPayload = (workOrderRow, scheduleRow, sortIndex = 0) => {
  const processId =
    workOrderRow?.processId ??
    workOrderRow?.productProcessRouteItemId ??
    reportForm.productProcessRouteItemId;
  const operatorIds = normalizeArray(scheduleRow?.userIds)
    .map((val) => String(val).trim())
    .filter(Boolean)
    .join(",");
  const nickName = normalizeArray(scheduleRow?.userIds)
    .map((uid) => resolveScheduleUserName(uid))
    .filter(Boolean)
    .join(",");
  const payload = {
    workOrderId: workOrderRow?.id,
    processId,
    machineId: scheduleRow.deviceId ? Number(scheduleRow.deviceId) : undefined,
    deviceName: scheduleRow.deviceName,
    operatorId: operatorIds || undefined,
    nickName: nickName || "",
    machineStartTime: scheduleRow.startTime,
    reportStatus: false,
    remark: `排产序号:${sortIndex + 1}`,
  };
  if (scheduleRow.id) {
    payload.id = scheduleRow.id;
  }
  return payload;
};
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))
    .filter(Boolean);
  return createScheduleRow({
    id,
    deviceId: deviceId === null || deviceId === undefined ? "" : String(deviceId),
    deviceName,
    userIds,
    startTime,
  });
};
const buildScheduleRowsFromRecords = (records) => {
  const list = Array.isArray(records) ? records : [];
  const grouped = new Map();
  list.forEach((record) => {
    const row = mapMachineRecordToScheduleRow(record);
    const key = `${row.deviceId}__${row.startTime}__${row.deviceName}`;
    if (!grouped.has(key)) {
      grouped.set(key, row);
      return;
    }
    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)))
    ).filter(Boolean);
    if (!existing.deviceName && row.deviceName) existing.deviceName = row.deviceName;
  });
  // 本次生产数量验证规则
  const validateQuantity = (rule, value, callback) => {
    if (value === null || value === undefined || value === "") {
      callback(new Error("请输入本次生产数量"));
  return Array.from(grouped.values()).sort(
    (a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf()
  );
};
const openScheduleDialog = async (row) => {
  currentReportRowData.value = row;
  baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
  userTemp.value = [...baseScheduleUsers.value];
  schedulePage.current = 1;
  scheduleRows.value = [];
  scheduleDialogVisible.value = true;
  await refreshScheduleRows();
};
const handleSaveSchedule = async () => {
  if (scheduleSaving.value) return;
  if (!validateScheduleRows()) return;
  const workOrderRow = currentReportRowData.value;
  if (!workOrderRow?.id) {
    ElMessage.warning("缺少工单信息,无法保存排产");
    return;
  }
  const sortedRows = [...scheduleRows.value].sort((a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf());
  scheduleSaving.value = true;
  try {
    const productionMachineRecord = sortedRows.map((scheduleRow, index) =>
      buildMachineRecordPayload(workOrderRow, scheduleRow, index)
    );
    const res = await addProductionMachineRecord({productionMachineRecord});
    if (res?.code !== undefined && res.code !== 200) {
      ElMessage.error(res?.msg || "保存排产失败");
      return;
    }
    const num = Number(value);
    // 整数且大于等于1
    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
      callback(new Error("本次生产数量必须大于等于1"));
      return;
    }
    proxy.$modal.msgSuccess("排产已保存");
    await refreshScheduleRows();
    getList();
  } catch (error) {
    console.error("保存排产失败", error);
    ElMessage.error("保存排产失败,请重试");
  } finally {
    scheduleSaving.value = false;
  }
};
const tableColumn = ref([
  {
    label: "工单类型",
    prop: "workOrderType",
    width: "80",
  },
  {
    label: "工单编号",
    prop: "workOrderNo",
    width: "140",
  },
  {
    label: "生产订单号",
    prop: "productOrderNpsNo",
    width: "140",
  },
  {
    label: "产品名称",
    prop: "productName",
    width: "140",
  },
  {
    label: "规格",
    prop: "model",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "工序名称",
    prop: "processName",
  },
  {
    label: "需求数量",
    prop: "planQuantity",
    width: "140",
  },
  {
    label: "完成数量",
    prop: "completeQuantity",
    width: "140",
  },
  {
    label: "完成进度",
    prop: "completionStatus",
    dataType: "slot",
    slot: "completionStatus",
    width: "140",
  },
  {
    label: "计划开始时间",
    prop: "planStartTime",
    width: "140",
  },
  {
    label: "计划结束时间",
    prop: "planEndTime",
    width: "140",
  },
  {
    label: "实际开始时间",
    prop: "actualStartTime",
    width: "140",
  },
  {
    label: "实际结束时间",
    prop: "actualEndTime",
    width: "140",
  },
  {
    label: "操作",
    width: "200",
    align: "center",
    dataType: "action",
    fixed: "right",
    operation: [
      {
        name: "编辑",
        clickFun: row => {
          handleEdit(row);
        },
      },
      {
        name: "流转卡",
        clickFun: row => {
          downloadAndPrintWorkOrder(row);
        },
      },
      {
        name: "附件",
        clickFun: row => {
          openWorkOrderFiles(row);
        },
      },
      {
        name: "报工",
        clickFun: row => {
          showReportDialog(row);
        },
      },
      {
        name: "生产排产",
        clickFun: row => {
          openScheduleDialog(row);
        },
      },
      // {
      //   name:"审核",
      //   color: "#f56c6c",
      //   clickFun: row => {
      //     handleAudit(row);
      //   },
      //   disabled: row => Number(row?.auditStatus) === 1,
      // }
    ],
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const scheduleRows = ref([]);
const scheduleLoading = ref(false);
const scheduleSaving = ref(false);
const schedulePage = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const editDialogVisible = ref(false);
const transferCardVisible = ref(false);
const transferCardQrUrl = ref("");
const scheduleDialogVisible = ref(false);
const transferCardRowData = ref(null);
const baseScheduleUsers = ref([]);
const userTemp = ref([]);
const reportDialogVisible = ref(false);
const auditDialogVisible = ref(false);
const auditRowData = ref(null);
const auditTableData = ref([]);
const auditLoading = ref(false);
const workOrderFilesRef = ref(null);
const reportFormRef = ref(null);
const userOptions = ref([]);
const userTeamOptions = ref([]);
const reportForm = reactive({
  planQuantity: 0,
  quantity: null,
  scrapQty: null,
  startTime: "",
  endTime: "",
  userName: "",
  workOrderId: "",
  reportWork: "",
  productProcessRouteItemId: "",
  userId: "",
  productMainId: null,
  teamList: [],
  deviceId: null,
});
// 本次生产数量验证规则
const validateQuantity = (rule, value, callback) => {
  if (value === null || value === undefined || value === "") {
    callback(new Error("请输入本次生产数量"));
    return;
  }
  const num = Number(value);
  // 整数且大于等于1
  if (isNaN(num) || !Number.isInteger(num) || num < 1) {
    callback(new Error("本次生产数量必须大于等于1"));
    return;
  }
  callback();
};
// 报废数量验证规则
const validateScrapQty = (rule, value, callback) => {
  if (value === null || value === undefined || value === "") {
    callback();
  };
    return;
  }
  const num = Number(value);
  // 整数且大于等于0
  if (isNaN(num) || !Number.isInteger(num) || num < 0) {
    callback(new Error("报废数量必须大于等于0"));
    return;
  }
  callback();
};
  // 报废数量验证规则
  const validateScrapQty = (rule, value, callback) => {
    if (value === null || value === undefined || value === "") {
      callback();
      return;
    }
    const num = Number(value);
    // 整数且大于等于0
    if (isNaN(num) || !Number.isInteger(num) || num < 0) {
      callback(new Error("报废数量必须大于等于0"));
      return;
    }
    callback();
  };
// 审核
const handleAudit = (row) => {
  if (Number(row?.auditStatus) === 1) {
    ElMessage.warning("该工单已审核");
    return;
  }
  auditRowData.value = row;
  const workOrderNo = row?.workOrderNo;
  const related = workOrderNo
      ? tableData.value.filter(r => r?.workOrderNo === workOrderNo)
      : [];
  auditTableData.value = related.length > 0 ? related : [row];
  auditDialogVisible.value = true;
};
  // 验证规则
  const reportFormRules = {
    quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }],
    scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
  };
const submitAudit = async (result) => {
  const current = auditRowData.value;
  if (!current) return;
  if (auditLoading.value) return;
  // 处理本次生产数量输入,限制必须大于等于1
  const handleQuantityInput = value => {
    if (value === "" || value === null || value === undefined) {
  const confirmText = result === 1 ? "确定审核通过吗?" : "确定审核不通过吗?";
  try {
    await ElMessageBox.confirm(confirmText, "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    });
  } catch {
    return;
  }
  auditLoading.value = true;
  try {
    const updates = auditTableData.value.map(item => {
      const id = item?.id;
      if (!id) return Promise.resolve();
      return updateProductWorkOrder({id, auditStatus: result});
    });
    await Promise.all(updates);
    ElMessage.success("审核成功");
    auditDialogVisible.value = false;
    getList();
  } finally {
    auditLoading.value = false;
  }
};
// 查看详情
const handleView = (row) => {
  const {workOrderId} = row;
  router.push({
    path: "/productionManagement/workOrderDetail",
    query: {workOrderId},
  });
}
// 验证规则
const reportFormRules = {
  quantity: [{required: true, validator: validateQuantity, trigger: "blur"}],
  scrapQty: [{validator: validateScrapQty, trigger: "blur"}],
  startTime: [{required: true, message: "请选择开始时间", trigger: "change"}],
  endTime: [{required: true, message: "请选择结束时间", trigger: "change"}],
  auditUserId: [{required: true, message: "请选择审核人", trigger: "change"}],
  teamList: [{required: true, message: "请选择班组", trigger: "change"}],
  deviceId: [{required: true, message: "请选择设备", trigger: "change"}],
};
// 处理本次生产数量输入,限制必须大于等于1
const handleQuantityInput = value => {
  if (value === "" || value === null || value === undefined) {
    reportForm.quantity = null;
    return;
  }
  const num = Number(value);
  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;
    }
    const num = Number(value);
    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;
    }
    reportForm.quantity = num;
  };
    reportForm.quantity = intValue;
    return;
  }
  reportForm.quantity = num;
};
  // 处理报废数量
  const handleScrapQtyInput = value => {
    if (value === "" || value === null || value === undefined) {
      reportForm.scrapQty = null;
      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 currentReportRowData = ref(null);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
// 处理报废数量
const handleScrapQtyInput = value => {
  if (value === "" || value === null || value === undefined) {
    reportForm.scrapQty = null;
    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 currentReportRowData = ref(null);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
  const data = reactive({
    searchForm: {
      workOrderNo: "",
    },
  });
  const { searchForm } = toRefs(data);
  const toProgressPercentage = val => {
    const n = Number(val);
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return Math.round(n);
  };
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
    if (p < 30) return "#f56c6c";
    if (p < 50) return "#e6a23c";
    if (p < 80) return "#409eff";
    return "#67c23a";
  };
  let editrow = ref(null);
const data = reactive({
  searchForm: {
    workOrderNo: "",
  },
});
const {searchForm} = toRefs(data);
const toProgressPercentage = val => {
  const n = Number(val);
  if (!Number.isFinite(n)) return 0;
  if (n <= 0) return 0;
  if (n >= 100) return 100;
  return Math.round(n);
};
const progressColor = percentage => {
  const p = toProgressPercentage(percentage);
  if (p < 30) return "#f56c6c";
  if (p < 50) return "#e6a23c";
  if (p < 80) return "#409eff";
  return "#67c23a";
};
let editrow = ref(null);
  // 查询列表
  /** 搜索按钮操作 */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    productWorkOrderPage(params)
// 查询列表
/** 搜索按钮操作 */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const params = {...searchForm.value, ...page};
  productWorkOrderPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
@@ -509,79 +1304,83 @@
      .catch(() => {
        tableLoading.value = false;
      });
  };
};
  // 下载并打印工单流转卡(文件流)
  const downloadAndPrintWorkOrder = async row => {
    if (!row || !row.id) {
      proxy.$modal.msgError("缺少工单ID,无法下载流转卡");
      return;
    }
    const fileName = row.workOrderNo
// 下载并打印工单流转卡(文件流)
const downloadAndPrintWorkOrder = async row => {
  if (!row || !row.id) {
    proxy.$modal.msgError("缺少工单ID,无法下载流转卡");
    return;
  }
  const fileName = row.workOrderNo
      ? `工单流转卡_${row.workOrderNo}.xlsx`
      : "工单流转卡.xlsx";
    try {
      // 调用接口,以 responseType: 'blob' 获取文件流
      const blob = await downProductWorkOrder(row.id);
  try {
    // 调用接口,以 responseType: 'blob' 获取文件流
    const blob = await downProductWorkOrder(row.id);
      if (!blob) {
        proxy.$modal.msgError("未获取到流转卡文件");
        return;
      }
      // 创建 Blob URL
      const fileBlob =
        blob instanceof Blob
          ? blob
          : new Blob([blob], { type: blob.type || "application/octet-stream" });
      const url = window.URL.createObjectURL(fileBlob);
      // 创建隐藏 iframe,用于触发浏览器打印
      const iframe = document.createElement("iframe");
      iframe.style.position = "fixed";
      iframe.style.right = "0";
      iframe.style.bottom = "0";
      iframe.style.width = "0";
      iframe.style.height = "0";
      iframe.style.border = "0";
      iframe.src = url;
      document.body.appendChild(iframe);
      iframe.onload = () => {
        try {
          iframe.contentWindow?.focus();
          iframe.contentWindow?.print();
        } catch (e) {
          console.error("自动调用打印失败", e);
          // 退而求其次,打开新窗口由用户手动打印
          window.open(url);
        }
      };
    } catch (e) {
      console.error("下载工单流转卡失败", e);
      proxy.$modal.msgError("下载工单流转卡失败");
    if (!blob) {
      proxy.$modal.msgError("未获取到流转卡文件");
      return;
    }
  };
  const showTransferCard = async row => {
    transferCardRowData.value = row;
    const qrContent = String(row.id);
    // 创建 Blob URL
    const fileBlob =
        blob instanceof Blob
            ? blob
            : new Blob([blob], {type: blob.type || "application/octet-stream"});
    const url = window.URL.createObjectURL(fileBlob);
    transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
    transferCardVisible.value = true;
  };
    // 创建隐藏 iframe,用于触发浏览器打印
    const iframe = document.createElement("iframe");
    iframe.style.position = "fixed";
    iframe.style.right = "0";
    iframe.style.bottom = "0";
    iframe.style.width = "0";
    iframe.style.height = "0";
    iframe.style.border = "0";
    iframe.src = url;
    document.body.appendChild(iframe);
  const printTransferCard = () => {
    window.print();
  };
    iframe.onload = () => {
      try {
        iframe.contentWindow?.focus();
        iframe.contentWindow?.print();
      } catch (e) {
        console.error("自动调用打印失败", e);
        // 退而求其次,打开新窗口由用户手动打印
        window.open(url);
      }
    };
  } catch (e) {
    console.error("下载工单流转卡失败", e);
    proxy.$modal.msgError("下载工单流转卡失败");
  }
};
  const handleEdit = row => {
    editrow.value = JSON.parse(JSON.stringify(row));
    editDialogVisible.value = true;
  };
const showTransferCard = async row => {
  transferCardRowData.value = row;
  const qrContent = String(row.id);
  const handleUpdate = () => {
    updateProductWorkOrder(editrow.value)
  transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
  transferCardVisible.value = true;
};
const printTransferCard = () => {
  window.print();
};
const handleEdit = row => {
  // if (!isCurrentUserReportWorker(row)) {
  //   ElMessage.warning("当前用户不是该工单的报工人,无法编辑");
  //   return;
  // }
  editrow.value = JSON.parse(JSON.stringify(row));
  editDialogVisible.value = true;
};
const handleUpdate = () => {
  updateProductWorkOrder(editrow.value)
      .then(res => {
        proxy.$modal.msgSuccess("提交成功");
        editDialogVisible.value = false;
@@ -592,283 +1391,326 @@
          confirmButtonText: "确定",
        });
      });
  };
};
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.planQuantity = row.planQuantity;
    reportForm.quantity =
const showReportDialog = row => {
  // if (!isCurrentUserReportWorker(row)) {
  //   ElMessage.warning("当前用户不是该工单的报工人,无法报工");
  //   return;
  // }
  currentReportRowData.value = row;
  reportForm.planQuantity = row.planQuantity - row.completeQuantity;
  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.scrapQty =
      row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
    nextTick(() => {
      reportFormRef.value?.clearValidate();
    });
    // 获取当前登录用户信息,设置为默认选中
    getUserProfile()
      .then(res => {
        if (res.code === 200) {
          reportForm.userId = res.data.userId;
          reportForm.userName = res.data.nickName;
        }
      })
      .catch(err => {
        console.error("获取用户信息失败", err);
  reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
  reportForm.workOrderId = row.id;
  reportForm.reportWork = row.reportWork;
  reportForm.productMainId = row.productMainId;
  reportForm.startTime = "";
  reportForm.endTime = "";
  reportForm.replenishQty = 0;
  reportForm.teamList = [];
  reportForm.scrapQty = 0;
  nextTick(() => {
    reportFormRef.value?.clearValidate();
  });
  ensureCurrentUser().then(() => {
    reportForm.userId = currentUserId.value;
    reportForm.userName = currentUserName.value;
  });
  reportDialogVisible.value = true;
};
const openWorkOrderFiles = row => {
  workOrderFilesRef.value?.openDialog(row);
};
const handleReport = () => {
  reportFormRef.value?.validate(valid => {
    if (!valid) {
      return false;
    }
    if (reportForm.planQuantity <= 0) {
      ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    reportDialogVisible.value = true;
  };
  const openWorkOrderFiles = row => {
    workOrderFilesRef.value?.openDialog(row);
  };
  const handleReport = () => {
    reportFormRef.value?.validate(valid => {
      if (!valid) {
        return false;
      }
      if (reportForm.planQuantity <= 0) {
        ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // 验证本次生产数量
      if (
    // 验证本次生产数量
    if (
        reportForm.quantity === null ||
        reportForm.quantity === undefined ||
        reportForm.quantity === ""
      ) {
        ElMessageBox.alert("请输入本次生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
    ) {
      ElMessageBox.alert("请输入本次生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
      const quantity = Number(reportForm.quantity);
      const scrapQty =
    const quantity = Number(reportForm.quantity);
    const scrapQty =
        reportForm.scrapQty === null ||
        reportForm.scrapQty === undefined ||
        reportForm.scrapQty === ""
          ? 0
          : Number(reportForm.scrapQty);
            ? 0
            : Number(reportForm.scrapQty);
      // 本次生产数量
      if (isNaN(quantity) || !Number.isInteger(quantity) || quantity < 1) {
        ElMessageBox.alert("本次生产数量必须大于等于1", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // 报废数量必须是整数且大于等于0
      if (isNaN(scrapQty) || !Number.isInteger(scrapQty) || scrapQty < 0) {
        ElMessageBox.alert("报废数量必须大于等于0", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      if (quantity > reportForm.planQuantity) {
        ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      const submitData = {
        ...reportForm,
        quantity: quantity,
        scrapQty: scrapQty,
      };
      // console.log(submitData);
      addProductMain(submitData).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("报工成功");
          reportDialogVisible.value = false;
          getList();
        } else {
          ElMessageBox.alert(res.msg || "报工失败", "提示", {
            confirmButtonText: "确定",
          });
        }
    // 本次生产数量
    if (isNaN(quantity) || !Number.isInteger(quantity) || quantity < 1) {
      ElMessageBox.alert("本次生产数量必须大于等于1", "提示", {
        confirmButtonText: "确定",
      });
    });
  };
      return;
    }
  // 获取用户列表
  const getUserList = () => {
    userListNoPageByTenantId()
    // 报废数量必须是整数且大于等于0
    if (isNaN(scrapQty) || !Number.isInteger(scrapQty) || scrapQty < 0) {
      ElMessageBox.alert("报废数量必须大于等于0", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (quantity > reportForm.planQuantity) {
      ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (!reportForm.startTime || !reportForm.endTime) {
      ElMessageBox.alert("开始时间和结束时间不能为空", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (dayjs(reportForm.startTime).isSame(dayjs(reportForm.endTime)) || dayjs(reportForm.startTime).isAfter(dayjs(reportForm.endTime))) {
      ElMessageBox.alert("开始时间必须小于结束时间", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    const submitData = {
      ...reportForm,
      quantity: quantity,
      scrapQty: scrapQty,
    };
    addProductMain(submitData).then(res => {
      if (res.code === 200) {
        proxy.$modal.msgSuccess("报工成功");
        reportDialogVisible.value = false;
        getList();
      } else {
        ElMessageBox.alert(res.msg || "报工失败", "提示", {
          confirmButtonText: "确定",
        });
      }
    });
  });
};
// 获取用户列表
const getUserList = () => {
  userListNoPageByTenantId()
      .then(res => {
        if (res.code === 200) {
          userOptions.value = res.data || [];
          const list = Array.isArray(res.data) ? res.data : []
          userOptions.value = [
            {nickName: "任意用户", userId: "-1"},
            ...deepClone(list)
          ]
          userTeamOptions.value = deepClone(list)
        }
      })
      .catch(err => {
        console.error("获取用户列表失败", err);
      });
  };
};
  // 用户选择变化时更新 userName
  const handleUserChange = userId => {
    if (userId) {
      const selectedUser = userOptions.value.find(user => user.userId === userId);
      if (selectedUser) {
        reportForm.userName = selectedUser.nickName;
      }
    } else {
      reportForm.userName = "";
// 用户选择变化时更新 userName
const handleUserChange = userId => {
  if (userId) {
    const selectedUser = userOptions.value.find(user => user.userId === userId);
    if (selectedUser) {
      reportForm.userName = selectedUser.nickName;
    }
  };
  } else {
    reportForm.userName = "";
  }
};
// 审核人
const handleReviewerIdChange = userId => {
  if (userId) {
    const selectedUser = userOptions.value.find(user => user.userId === userId);
    if (selectedUser) {
      reportForm.auditUserName = selectedUser.nickName;
    }
  } else {
    reportForm.auditUserName = "";
  }
}
  onMounted(() => {
    getList();
    getUserList();
  });
onMounted(() => {
  ensureCurrentUser();
  getList();
  getUserList();
  getDeviceList();
});
</script>
<style scoped lang="scss">
  .search_form {
    margin-bottom: 20px;
    .search-row {
      display: flex;
      gap: 20px;
      align-items: center;
      .search-item {
        display: flex;
        align-items: center;
        gap: 10px;
      }
    }
  }
.search_form {
  margin-bottom: 20px;
  .transfer-card-title {
    font-size: 24px;
    font-weight: bold;
    text-align: center;
    margin-bottom: 20px;
  }
  .transfer-card-container {
  .search-row {
    display: flex;
    gap: 20px;
    height: 350px;
    .transfer-card-info {
      flex: 1;
      overflow: auto;
      .info-group {
        width: 50%;
        float: left;
      }
      .info-item {
        display: flex;
        margin-bottom: 15px;
        .info-label {
          width: 120px;
          font-weight: bold;
          margin-right: 20px;
        }
        .info-value {
          flex: 1;
        }
      }
    }
    .transfer-card-qr {
      width: 240px;
    align-items: center;
    .search-item {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
      gap: 10px;
    }
  }
}
.transfer-card-title {
  font-size: 24px;
  font-weight: bold;
  text-align: center;
  margin-bottom: 20px;
}
.transfer-card-container {
  display: flex;
  gap: 20px;
  height: 350px;
  .transfer-card-info {
    flex: 1;
    overflow: auto;
    .info-group {
      width: 50%;
      float: left;
    }
    .info-item {
      display: flex;
      margin-bottom: 15px;
      .info-label {
        width: 120px;
        font-weight: bold;
        margin-right: 20px;
      }
      .info-value {
        flex: 1;
      }
    }
  }
  .transfer-card-qr {
    width: 240px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
  }
}
</style>
<style  lang="scss">
  @media print {
    @page {
      size: landscape;
<style lang="scss">
@media print {
  @page {
    size: landscape;
  }
  body * {
    visibility: hidden;
  }
  .el-dialog__wrapper,
  .el-dialog,
  .el-dialog__body,
  .transfer-card-title,
  .transfer-card-container,
  .transfer-card-container *,
  .info-item,
  .info-label,
  .info-value {
    visibility: visible;
  }
  .print-button-container {
    visibility: hidden;
  }
  .el-dialog__wrapper {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    margin: 0;
  }
  .el-dialog {
    width: 100% !important;
    max-width: 800px;
    margin: 0 auto !important;
  }
  .el-dialog__header,
  .el-dialog__footer {
    display: none;
  }
  .el-dialog__body {
    padding: 20px;
  }
  .transfer-card-container {
    height: auto;
    display: flex;
    gap: 20px;
  }
  .transfer-card-info {
    flex: 1;
    .info-group {
      width: 100%;
      float: none;
      margin-bottom: 20px;
    }
    body * {
      visibility: hidden;
    }
    .el-dialog__wrapper,
    .el-dialog,
    .el-dialog__body,
    .transfer-card-title,
    .transfer-card-container,
    .transfer-card-container *,
    .info-item,
    .info-label,
    .info-value {
      visibility: visible;
    }
    .print-button-container {
      visibility: hidden;
    }
    .el-dialog__wrapper {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      margin: 0;
    }
    .el-dialog {
      width: 100% !important;
      max-width: 800px;
      margin: 0 auto !important;
    }
    .el-dialog__header,
    .el-dialog__footer {
      display: none;
    }
    .el-dialog__body {
      padding: 20px;
    }
    .transfer-card-container {
      height: auto;
    .info-item {
      display: flex;
      gap: 20px;
    }
    .transfer-card-info {
      flex: 1;
      .info-group {
        width: 100%;
        float: none;
        margin-bottom: 20px;
      margin-bottom: 10px;
      .info-label {
        width: 100px;
        font-weight: bold;
        margin-right: 15px;
        white-space: nowrap;
      }
      .info-item {
        display: flex;
        margin-bottom: 10px;
        .info-label {
          width: 100px;
          font-weight: bold;
          margin-right: 15px;
          white-space: nowrap;
        }
        .info-value {
          flex: 1;
          word-break: break-word;
        }
      .info-value {
        flex: 1;
        word-break: break-word;
      }
    }
    .transfer-card-qr {
      width: 160px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    }
    .qr-container img {
      width: 140px !important;
      height: 140px !important;
    }
  }
  .transfer-card-qr {
    width: 160px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
  }
  .qr-container img {
    width: 140px !important;
    height: 140px !important;
  }
}
</style>