src/views/productionManagement/workOrder/index.vue
@@ -13,7 +13,8 @@
        </div>
        <div class="search-item">
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
                     @click="handleQuery">搜索
          </el-button>
        </div>
      </div>
    </div>
@@ -24,10 +25,12 @@
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination">
                <template #completionStatus="{ row }">
                  <el-progress :percentage="toProgressPercentage(row?.completionStatus)" :color="progressColor(toProgressPercentage(row?.completionStatus))" :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
                </template>
              </PIMTable>
        <template #completionStatus="{ row }">
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
        </template>
      </PIMTable>
    </div>
    <el-dialog v-model="editDialogVisible"
               title="编辑时间"
@@ -36,30 +39,34 @@
               label-width="120px">
        <el-form-item label="计划开始时间">
          <el-date-picker v-model="editrow.planStartTime"
                          type="date"
                          type="datetime"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          value-format="YYYY-MM-DD HH:mm"
                          format="YYYY-MM-DD HH:mm"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="计划结束时间">
          <el-date-picker v-model="editrow.planEndTime"
                          type="date"
                          type="datetime"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          value-format="YYYY-MM-DD HH:mm"
                          format="YYYY-MM-DD HH:mm"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="实际开始时间">
          <el-date-picker v-model="editrow.actualStartTime"
                          type="date"
                          type="datetime"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          value-format="YYYY-MM-DD HH:mm"
                          format="YYYY-MM-DD HH:mm"
                          style="width: 300px" />
        </el-form-item>
        <el-form-item label="实际结束时间">
          <el-date-picker v-model="editrow.actualEndTime"
                          type="date"
                          type="datetime"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          value-format="YYYY-MM-DD HH:mm"
                          format="YYYY-MM-DD HH:mm"
                          style="width: 300px" />
        </el-form-item>
      </el-form>
@@ -96,15 +103,14 @@
            </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">
              <span class="info-label">计划开始时间</span>
              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
@@ -160,46 +166,128 @@
      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 :model="reportForm"
               :title="`报工(工单编号:${currentReportRowData?.workOrderNo || '-'})`"
               width="1000px">
      <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="本次生产数量">
          <el-input v-model.number="reportForm.quantity"
                    type="number"
                    min="1"
                    style="width: 300px"
                    placeholder="请输入本次生产数量" />
        </el-form-item>
        <el-form-item label="报废数量">
          <el-input v-model.number="reportForm.scrapQty"
                    type="number"
                    min="1"
                    style="width: 300px"
                    placeholder="请输入报废数量" />
        </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.userName"
                       :value="user.userId" />
          </el-select>
        </el-form-item>
        <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%"
                        :class="{ 'over-limit': reportForm.quantity > reportForm.planQuantity }"
                        @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="addQty">
              <el-input v-model.number="reportForm.addQty"
                        type="number"
                        min="0"
                        step="1"
                        placeholder="请输入加放数" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="班组信息"
                          prop="teamList">
              <el-select v-model="reportForm.teamList"
                         ref="teamSelectRef"
                         multiple
                         filterable
                         allow-create
                         default-first-option
                         clearable
                         collapse-tags
                         value-key="userName"
                         placeholder="请选择或输入班组成员"
                         @change="handleTeamListChange">
                <el-option v-for="user in teamSelectOptions"
                           :key="user.userId || `custom-${user.nickName}`"
                           :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">
@@ -209,27 +297,744 @@
        </span>
      </template>
    </el-dialog>
    <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="scheduleDialogTitle"
               width="1000px"
               :close-on-click-modal="false">
      <div class="schedule-panel">
        <el-row v-if="!isScheduleHistoryMode"
                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"
                           :index="indexMethod" />
          <el-table-column label="本次上机机台"
                           min-width="220">
            <template #default="{ row }">
              <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"
                           :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 }">
              <div v-if="isScheduleHistoryMode"
                   class="schedule-user-tags">
                <el-tag v-for="(userId, index) in row.userIds"
                        :key="`${userId}-${index}`"
                        type="primary">
                  {{ resolveScheduleUserName(userId) }}
                </el-tag>
                <span v-if="!row.userIds?.length">-</span>
              </div>
              <el-select v-else
                         v-model="row.userIds"
                         placeholder="请选择上机人"
                         filterable
                         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 || isScheduleHistoryMode" />
            </template>
          </el-table-column>
          <el-table-column v-if="!isScheduleHistoryMode"
                           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="isScheduleHistoryMode && 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">
          <template v-if="isScheduleHistoryMode">
            <el-button @click="scheduleDialogVisible = false">关闭</el-button>
          </template>
          <template v-else>
            <el-button type="primary"
                       :loading="scheduleSaving"
                       @click="handleSaveSchedule">保存排产</el-button>
            <el-button :disabled="scheduleSaving"
                       @click="scheduleDialogVisible = false">取消</el-button>
          </template>
        </span>
      </template>
    </el-dialog>
    <FilesDia ref="workOrderFilesRef" />
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { ElMessageBox } from "element-plus";
  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 { processList } from "@/api/productionManagement/productionProcess.js";
  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 { proxy } = getCurrentInstance();
  const currentUserId = ref("");
  const deviceOptions = ref([]);
  const currentUserName = ref("");
  const teamSelectRef = ref(null);
  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 isCurrentUserInUserIds = row => {
    const uid = String(currentUserId.value || "");
    if (!uid) return false;
    const ids = normalizeArray(row?.userIds)
      .map(id => String(id))
      .filter(Boolean);
    return ids.includes(uid);
  };
  // 判断当前用户是否在工序报工人中
  const isCurrentUserInProcessUserIds = row => {
    const uid = String(currentUserId.value || "");
    if (!uid) return false;
    const ids = normalizeArray(row?.processUserIds)
      .map(id => String(id))
      .filter(Boolean);
    return ids.includes(uid);
  };
  // 判断当前用户是否可以报工(工单报工人 或 工序报工人)
  const canCurrentUserReport = row => {
    return isCurrentUserInUserIds(row) || isCurrentUserInProcessUserIds(row);
  };
  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;
    }
    scheduleRows.value.push(
      createScheduleRow({
        id: "",
        deviceId: "",
        deviceName: "",
        userIds: [],
        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 = [];
      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 = records.map(record => mapMachineRecordToScheduleRow(record));
      scheduleRows.value = rows;
    } catch (error) {
      console.error("获取排产记录失败", error);
      schedulePage.total = 0;
      scheduleRows.value = [];
      ElMessage.error("获取排产记录失败");
    } finally {
      scheduleLoading.value = false;
    }
  };
  const removeScheduleRow = async row => {
    if (!row || isScheduleHistoryMode.value) return;
    if (!row.id) {
      scheduleRows.value = scheduleRows.value.filter(item => item !== row);
      if (!scheduleRows.value.length) {
        addScheduleRow();
      }
      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 }) => {
    if (!isScheduleHistoryMode.value) return;
    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 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 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;
    });
    return Array.from(grouped.values()).sort(
      (a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf()
    );
  };
  const resetCreateScheduleRows = () => {
    schedulePage.current = 1;
    schedulePage.total = 0;
    scheduleRows.value = [];
    addScheduleRow();
  };
  const openScheduleDialog = async row => {
    scheduleDialogMode.value = "create";
    currentReportRowData.value = row;
    baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
    userTemp.value = [...baseScheduleUsers.value];
    scheduleDialogVisible.value = true;
    resetCreateScheduleRows();
  };
  const openHistoryTimelineDialog = async row => {
    scheduleDialogMode.value = "history";
    currentReportRowData.value = row;
    baseScheduleUsers.value = buildBaseScheduleUsersByRow(row);
    userTemp.value = [...baseScheduleUsers.value];
    schedulePage.current = 1;
    schedulePage.total = 0;
    scheduleRows.value = [];
    scheduleDialogVisible.value = true;
    await refreshScheduleRows();
  };
  const handleSaveSchedule = async () => {
    if (isScheduleHistoryMode.value) return;
    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;
      }
      proxy.$modal.msgSuccess("排产已保存");
      scheduleDialogVisible.value = false;
      resetCreateScheduleRows();
      await getList();
    } catch (error) {
      console.error("保存排产失败", error);
      ElMessage.error("保存排产失败,请重试");
    } finally {
      scheduleSaving.value = false;
    }
  };
  const tableColumn = ref([
    {
      label: "工单类型",
      prop: "workOrderType",
      width: "80",
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
@@ -239,20 +1044,27 @@
      label: "生产订单号",
      prop: "productOrderNpsNo",
      width: "140",
      formatData: val => (val && val.length > 4 ? val.slice(0, -4) : val || ""),
    },
    {
      label: "产品名称",
      prop: "productName",
      width: "140",
      label: "成品名称",
      prop: "finalProductModel",
      minWidth: 200,
      overHidden: false,
    },
    {
      label: "规格",
      prop: "model",
    },
    {
      label: "单位",
      prop: "unit",
    },
    // {
    //   label: "加工品名称",
    //   prop: "productName",
    //   width: "140",
    // },
    // {
    //   label: "加工品规格",
    //   prop: "model",
    // },
    // {
    //   label: "加工品单位",
    //   prop: "unit",
    // },
    {
      label: "工序名称",
      prop: "processName",
@@ -296,7 +1108,7 @@
    },
    {
      label: "操作",
      width: "200",
      width: "220",
      align: "center",
      dataType: "action",
      fixed: "right",
@@ -324,33 +1136,265 @@
          clickFun: row => {
            showReportDialog(row);
          },
          disabled: row => row.planQuantity <= 0,
          // 用户当前id在工单报工人或工序报工人中
          disabled: row =>
            row.completeQuantity >= row.planQuantity ||
            !canCurrentUserReport(row) ||
            row.hasUnreportedMachine,
        },
        {
          name: "生产排产",
          clickFun: row => {
            if (!row.canSchedule) {
              ElMessage.warning("当前用户不在该工序人员中,不能生产排产");
              return;
            }
            openScheduleDialog(row);
          },
          disabled: row =>
            !row.canSchedule || row.completeQuantity >= row.planQuantity,
        },
        {
          name: "排产记录",
          clickFun: row => {
            if (!row.canSchedule) {
              ElMessage.warning("当前用户不在该工序人员中,不能查看排产记录");
              return;
            }
            openHistoryTimelineDialog(row);
          },
          disabled: row => !row.canSchedule,
        },
        // {
        //   name:"审核",
        //   color: "#f56c6c",
        //   clickFun: row => {
        //     handleAudit(row);
        //   },
        //   disabled: row => Number(row?.auditStatus) === 1,
        // }
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const qrCodeUrl = ref("");
  const qrRowData = ref(null);
  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 transferCardData = ref([]);
  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: 0,
    quantity: null,
    scrapQty: null,
    addQty: 0,
    startTime: "",
    endTime: "",
    userName: "",
    workOrderId: "",
    reportWork: "",
    productProcessRouteItemId: "",
    userId: "",
    productMainId: null,
    teamList: [],
    deviceId: null,
  });
  const teamSelectOptions = computed(() => {
    const base =
      Array.isArray(reportForm.userIdsList) && reportForm.userIdsList.length > 0
        ? reportForm.userIdsList.map(u => ({
            userId: String(u.userId ?? ""),
            nickName: String(u.nickName ?? "").trim(),
          }))
        : [];
    const baseNameSet = new Set(base.map(u => u.nickName));
    const selected = Array.isArray(reportForm.teamList)
      ? reportForm.teamList
      : [];
    const extraNames = selected
      .map(item => {
        if (typeof item === "string") return String(item).trim();
        const name = item?.userName ?? item?.nickName ?? "";
        return String(name).trim();
      })
      .filter(Boolean)
      .filter(name => !baseNameSet.has(name));
    const extras = extraNames.map(name => ({ userId: "", nickName: name }));
    return [...base, ...extras];
  });
  function removeLastFour(str) {
    if (!str) return ""; // 空值保护
    return str.toString().slice(0, -4); // 核心:截取 0 到 倒数第4位
  }
  // 本次生产数量验证规则
  const validateQuantity = (rule, value, callback) => {
    if (value === null || value === undefined || value === "") {
      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 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 submitAudit = async result => {
    const current = auditRowData.value;
    if (!current) return;
    if (auditLoading.value) return;
    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;
      await 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;
      }
      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,
@@ -391,18 +1435,27 @@
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
  const getList = async () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    productWorkOrderPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
    try {
      await ensureCurrentUser();
      await processLists();
      const params = { ...searchForm.value, ...page };
      const res = await productWorkOrderPage(params);
      const records = Array.isArray(res?.data?.records) ? res.data.records : [];
      tableData.value = records.map(row => ({
        ...row,
        canSchedule: canScheduleByWorkOrderNo(row),
      }));
      page.total = res?.data?.total || 0;
    } finally {
      tableLoading.value = false;
    }
  };
  // 下载并打印工单流转卡(文件流)
@@ -425,7 +1478,9 @@
      // 创建 Blob URL
      const fileBlob =
        blob instanceof Blob ? blob : new Blob([blob], { type: blob.type || "application/octet-stream" });
        blob instanceof Blob
          ? blob
          : new Blob([blob], { type: blob.type || "application/octet-stream" });
      const url = window.URL.createObjectURL(fileBlob);
      // 创建隐藏 iframe,用于触发浏览器打印
@@ -468,6 +1523,10 @@
  };
  const handleEdit = row => {
    // if (!isCurrentUserReportWorker(row)) {
    //   ElMessage.warning("当前用户不是该工单的报工人,无法编辑");
    //   return;
    // }
    editrow.value = JSON.parse(JSON.stringify(row));
    editDialogVisible.value = true;
  };
@@ -488,24 +1547,44 @@
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.planQuantity = row.planQuantity;
    reportForm.quantity = row.quantity;
    reportForm.planQuantity = row.planQuantity - row.completeQuantity;
    reportForm.quantity = row.planQuantity - row.completeQuantity;
    reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
    reportForm.workOrderId = row.id;
    reportForm.reportWork = row.reportWork;
    reportForm.productMainId = row.productMainId;
    reportForm.scrapQty = row.scrapQty;
    // 获取当前登录用户信息,设置为默认选中
    getUserProfile()
      .then(res => {
        if (res.code === 200) {
          reportForm.userId = res.data.userId;
          reportForm.userName = res.data.userName;
        }
      })
      .catch(err => {
        console.error("获取用户信息失败", err);
      });
    reportForm.startTime = "";
    reportForm.endTime = "";
    reportForm.replenishQty = 0;
    reportForm.teamList = [];
    reportForm.scrapQty = 0;
    reportForm.addQty = 0;
    reportForm.userIds = row.userIds || [];
    // 合并工单报工人和工序报工人,去重
    const workOrderUserIds = normalizeArray(row.userIds)
      .map(id => String(id).trim())
      .filter(Boolean);
    const processUserIds = normalizeArray(row.processUserIds)
      .map(id => String(id).trim())
      .filter(Boolean);
    const allowedUserIds = [...new Set([...workOrderUserIds, ...processUserIds])];
    // 只允许选择工单报工人和工序报工人
    reportForm.userIdsList = userTeamOptions.value
      .filter(item => allowedUserIds.includes(String(item.userId)))
      .map(item => ({
        userId: item.userId,
        nickName: item.nickName,
      }));
    nextTick(() => {
      reportFormRef.value?.clearValidate();
    });
    ensureCurrentUser().then(() => {
      reportForm.userId = currentUserId.value;
      reportForm.userName = currentUserName.value;
    });
    reportDialogVisible.value = true;
  };
@@ -515,35 +1594,77 @@
  };
  const handleReport = () => {
    if (reportForm.planQuantity <= 0) {
      ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (!reportForm.quantity || reportForm.quantity <= 0) {
      ElMessageBox.alert("请输入有效的本次生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    if (reportForm.quantity > reportForm.planQuantity) {
      ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
        confirmButtonText: "确定",
      });
      return;
    }
    // console.log(reportForm);
    addProductMain(reportForm).then(res => {
      if (res.code === 200) {
        proxy.$modal.msgSuccess("报工成功");
        reportDialogVisible.value = false;
        getList();
      } else {
        ElMessageBox.alert(res.msg || "报工失败", "提示", {
    reportFormRef.value?.validate(valid => {
      if (!valid) {
        return false;
      }
      if (reportForm.planQuantity <= 0) {
        ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // 验证本次生产数量
      if (
        reportForm.quantity === null ||
        reportForm.quantity === undefined ||
        reportForm.quantity === ""
      ) {
        ElMessageBox.alert("请输入本次生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      const quantity = Number(reportForm.quantity);
      const scrapQty =
        reportForm.scrapQty === null ||
        reportForm.scrapQty === undefined ||
        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,
      };
      addProductMain(submitData).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("报工成功");
          reportDialogVisible.value = false;
          getList();
        } else {
          ElMessageBox.alert(res.msg || "报工失败", "提示", {
            confirmButtonText: "确定",
          });
        }
      });
    });
  };
@@ -552,7 +1673,12 @@
    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 => {
@@ -561,30 +1687,117 @@
  };
  // 用户选择变化时更新 userName
  const handleUserChange = (userId) => {
  const handleUserChange = userId => {
    if (userId) {
      const selectedUser = userOptions.value.find(user => user.userId === userId);
      if (selectedUser) {
        reportForm.userName = selectedUser.userName;
        reportForm.userName = selectedUser.nickName;
      }
    } else {
      reportForm.userName = "";
    }
  };
  onMounted(() => {
    getList();
  const handleTeamListChange = val => {
    if (!Array.isArray(val)) return;
    let hasString = false;
    const newList = val.map(item => {
      if (typeof item === "string") {
        hasString = true;
        return { userName: item };
      }
      return item;
    });
    if (hasString) {
      reportForm.teamList = newList;
    }
    // 解决 allow-create 在 multiple 模式下输入框内容不自动清空的问题
    setTimeout(() => {
      if (teamSelectRef.value) {
        // 1. 清除内部 query 状态
        teamSelectRef.value.query = "";
        if (teamSelectRef.value.states) {
          teamSelectRef.value.states.query = "";
          teamSelectRef.value.states.inputValue = "";
        }
        // 2. 强制清除 DOM 输入框的值
        const input = teamSelectRef.value.$el?.querySelector("input");
        if (input) {
          input.value = "";
        }
        // 3. 针对某些版本,重置选中标签的内部偏移(防止输入框被挤占)
        if (typeof teamSelectRef.value.resetInputState === "function") {
          teamSelectRef.value.resetInputState();
        }
      }
    }, 50);
  };
  // 审核人
  const handleReviewerIdChange = userId => {
    if (userId) {
      const selectedUser = userOptions.value.find(user => user.userId === userId);
      if (selectedUser) {
        reportForm.auditUserName = selectedUser.nickName;
      }
    } else {
      reportForm.auditUserName = "";
    }
  };
  const processData = ref([]);
  // 查询所有工序
  const processLists = async () => {
    console.log(processData.value);
    if (processData.value.length > 0) {
      return processData.value;
    }
    const res = await processList();
    processData.value = Array.isArray(res?.data) ? res.data : [];
    return processData.value;
  };
  // 判断当前用户是否能排产
  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)
    );
    if (!currentProcess) return false;
    const ids = normalizeArray(currentProcess.userIds)
      .map(id => String(id).trim())
      .filter(Boolean);
    return ids.includes(uid);
  };
  onMounted(async () => {
    await ensureCurrentUser();
    await processLists();
    await 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;
@@ -604,26 +1817,32 @@
    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;
@@ -632,9 +1851,17 @@
      justify-content: flex-start;
    }
  }
  .schedule-user-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    min-height: 32px;
    align-items: center;
  }
</style>
<style  lang="scss">
<style lang="scss">
  @media print {
    @page {
      size: landscape;
@@ -682,20 +1909,24 @@
    }
    .transfer-card-info {
      flex: 1;
      .info-group {
        width: 100%;
        float: none;
        margin-bottom: 20px;
      }
      .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;
@@ -714,4 +1945,12 @@
      height: 140px !important;
    }
  }
  .el-table .cell {
    white-space: normal !important;
    word-break: break-all;
  }
  .over-limit .el-input__inner {
    color: #f56c6c !important;
  }
</style>