gaoluyang
2026-03-18 74aa4abd22f1eff79009931d2772dd0168072152
天津德瑞斯
1.添加一个计划生产排班
已添加3个文件
1250 ■■■■■ 文件已修改
src/views/productionManagement/workOrderEdit/index.vue 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderManagement/components/filesDia.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderManagement/index.vue 789 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderEdit/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,260 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div class="search-row">
        <div class="search-item">
          <span class="search_title">工单编号:</span>
          <el-input v-model="searchForm.workOrderNo"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search" />
        </div>
        <div class="search-item">
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </div>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :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>
    </div>
    <!-- ç¼–辑时间弹窗 -->
    <el-dialog v-model="editDialogVisible"
               title="编辑计划时间"
               width="500px">
      <el-form :model="editrow"
               label-width="120px">
        <el-form-item label="计划开始时间">
          <el-date-picker v-model="editrow.planStartTime"
                          type="date"
                          placeholder="请选择"
                          value-format="YYYY-MM-DD"
                          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" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleUpdate">确定</el-button>
          <el-button @click="editDialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref, nextTick } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
    productWorkOrderPage,
    updateProductWorkOrder,
  } from "@/api/productionManagement/workOrder.js";
  import { getCurrentInstance, reactive, toRefs } from "vue";
  const { proxy } = getCurrentInstance();
  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: "100",
      align: "center",
      dataType: "action",
      fixed: "right",
      operation: [
        {
          name: "计划时间",
          clickFun: row => {
            handleEdit(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const editDialogVisible = ref(false);
  let editrow = 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";
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  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;
        page.total = res.data.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  const handleEdit = row => {
    editrow.value = JSON.parse(JSON.stringify(row));
    editDialogVisible.value = true;
  };
  const handleUpdate = () => {
    updateProductWorkOrder(editrow.value)
      .then(res => {
        proxy.$modal.msgSuccess("提交成功");
        editDialogVisible.value = false;
        getList();
      })
      .catch(() => {
        ElMessageBox.alert("修改失败", "提示", {
          confirmButtonText: "确定",
        });
      });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  .search-row {
    display: flex;
    align-items: center;
    gap: 12px;
  }
  .search-item {
    display: flex;
    align-items: center;
  }
  .search_title {
    margin-right: 8px;
    font-size: 14px;
    color: #606266;
  }
</style>
src/views/productionManagement/workOrderManagement/components/filesDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,201 @@
<template>
  <div>
    <el-dialog v-model="dialogVisible" title="工单附件" width="50%" @close="closeDia">
      <div style="margin-bottom: 10px; text-align: right">
        <el-upload
          v-model:file-list="fileList"
          class="upload-demo"
          :action="uploadUrl"
          :on-success="handleUploadSuccess"
          :on-error="handleUploadError"
          :before-upload="beforeUpload"
          name="file"
          :show-file-list="false"
          :headers="headers"
          accept="image/*"
          style="display: inline; margin-right: 10px"
        >
          <el-button type="primary">上传图片</el-button>
        </el-upload>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :total="page.total"
        :tableLoading="tableLoading"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        @pagination="paginationSearch"
        height="500"
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDia">关闭</el-button>
        </div>
      </template>
    </el-dialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
import { getToken } from "@/utils/auth.js";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import filePreview from "@/components/filePreview/index.vue";
import {
  productWorkOrderFileAdd,
  productWorkOrderFileDel,
  productWorkOrderFileListPage,
} from "@/api/productionManagement/productWorkOrderFile.js";
const { proxy } = getCurrentInstance();
const emit = defineEmits(["close"]);
const dialogVisible = ref(false);
const currentWorkOrderId = ref("");
const selectedRows = ref([]);
const filePreviewRef = ref();
const tableColumn = ref([
  {
    label: "文件名称",
    prop: "name",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    width: 120,
    operation: [
      {
        name: "下载",
        type: "text",
        clickFun: row => {
          proxy.$download.name(row.url);
        },
      },
      {
        name: "预览",
        type: "text",
        clickFun: row => {
          filePreviewRef.value?.open(row.url);
        },
      },
    ],
  },
]);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
const tableData = ref([]);
const fileList = ref([]);
const tableLoading = ref(false);
const headers = ref({
  Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload");
const beforeUpload = file => {
  const isImage = file?.type?.startsWith("image/");
  if (!isImage) {
    proxy.$modal.msgError("只能上传图片文件");
  }
  return isImage;
};
const openDialog = row => {
  dialogVisible.value = true;
  currentWorkOrderId.value = row.id;
  page.current = 1;
  getList();
};
const closeDia = () => {
  dialogVisible.value = false;
  emit("close");
};
const paginationSearch = obj => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  productWorkOrderFileListPage({
    workOrderId: currentWorkOrderId.value,
    current: page.current,
    size: page.size,
  })
    .then(res => {
      tableData.value = res.data.records || [];
      page.total = res.data.total || 0;
    })
    .finally(() => {
      tableLoading.value = false;
    });
};
const handleSelectionChange = selection => {
  selectedRows.value = selection;
};
function handleUploadSuccess(res) {
  if (res.code == 200) {
    const fileRow = {
      name: res.data.originalName,
      url: res.data.tempPath,
      workOrderId: currentWorkOrderId.value,
    };
    productWorkOrderFileAdd(fileRow).then(() => {
      proxy.$modal.msgSuccess("文件上传成功");
      getList();
    });
  } else {
    proxy.$modal.msgError("文件上传失败");
  }
}
function handleUploadError() {
  proxy.$modal.msgError("文件上传失败");
}
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  const ids = selectedRows.value.map(item => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      productWorkOrderFileDel(ids).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
defineExpose({
  openDialog,
});
</script>
<style scoped></style>
src/views/productionManagement/workOrderManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,789 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div class="search-row">
        <div class="search-item">
          <span class="search_title">工单编号:</span>
          <el-input v-model="searchForm.workOrderNo"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search" />
        </div>
        <div class="search-item">
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </div>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :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>
    </div>
    <!-- æµè½¬å¡å¼¹çª— -->
    <el-dialog v-model="transferCardVisible"
               title="流转卡"
               width="1000px">
      <div class="transfer-card-title">工单流转卡</div>
      <div class="transfer-card-container">
        <div class="transfer-card-info">
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">工单编号</span>
              <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">产品名称</span>
              <span class="info-value">{{ transferCardRowData.productName }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">产品规格</span>
              <span class="info-value">{{ transferCardRowData.model }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划开始时间</span>
              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">计划结束时间</span>
              <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">备注</span>
              <span class="info-value">{{ transferCardRowData.remark }}</span>
            </div>
          </div>
          <div class="info-group">
            <div class="info-item">
              <span class="info-label">需求数量</span>
              <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-value">{{ transferCardRowData.completeQuantity }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">良品数量</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">不良品数</span>
              <span class="info-value">0</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际开始时间</span>
              <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
            </div>
            <div class="info-item">
              <span class="info-label">实际结束时间</span>
              <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
            </div>
          </div>
        </div>
        <div class="transfer-card-qr">
          <div class="qr-container">
            <img :src="transferCardQrUrl"
                 alt="流转卡二维码"
                 style="width: 200px; height: 200px;" />
          </div>
        </div>
      </div>
      <div class="print-button-container"
           style=" text-align: center;
      margin-bottom: 40px;">
        <el-button type="primary"
                   style="margin-top: 20px;"
                   @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>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary"
                     @click="handleReport">确定</el-button>
          <el-button @click="reportDialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <FilesDia ref="workOrderFilesRef" />
  </div>
</template>
<script setup>
  import { onMounted, ref, nextTick } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import {
    productWorkOrderPage,
    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();
  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 => {
            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 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 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 reportFormRules = {
    quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }],
    scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
  };
  // å¤„理本次生产数量输入,限制必须大于等于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,
    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";
  };
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  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;
        page.total = res.data.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  // ä¸‹è½½å¹¶æ‰“印工单流转卡(文件流)
  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);
      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("下载工单流转卡失败");
    }
  };
  const showTransferCard = async row => {
    transferCardRowData.value = row;
    const qrContent = String(row.id);
    transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
    transferCardVisible.value = true;
  };
  const printTransferCard = () => {
    window.print();
  };
  const openWorkOrderFiles = row => {
    workOrderFilesRef.value?.openDialog(row);
  };
  const showReportDialog = row => {
    currentReportRowData.value = row;
    reportForm.planQuantity = row.planQuantity;
    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);
      });
    reportDialogVisible.value = true;
  };
  const handleReport = () => {
    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);
      if (isNaN(quantity) || quantity <= 0) {
        ElMessageBox.alert("本次生产数量必须大于0", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      if (quantity > reportForm.planQuantity) {
        ElMessageBox.alert("本次生产数量不能超过待生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      // éªŒè¯æŠ¥åºŸæ•°é‡
      const scrapQty = Number(reportForm.scrapQty);
      if (!isNaN(scrapQty) && scrapQty < 0) {
        ElMessageBox.alert("报废数量不能小于0", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      if (!isNaN(scrapQty) && scrapQty > quantity) {
        ElMessageBox.alert("报废数量不能大于本次生产数量", "提示", {
          confirmButtonText: "确定",
        });
        return;
      }
      const params = {
        quantity: quantity,
        scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
        userId: reportForm.userId,
        userName: reportForm.userName,
        workOrderId: reportForm.workOrderId,
        productProcessRouteItemId: reportForm.productProcessRouteItemId,
        reportWork: reportForm.reportWork,
        productMainId: reportForm.productMainId,
      };
      addProductMain(params)
        .then(res => {
          proxy.$modal.msgSuccess("报工成功");
          reportDialogVisible.value = false;
          getList();
        })
        .catch(() => {
          ElMessageBox.alert("报工失败", "提示", {
            confirmButtonText: "确定",
          });
        });
    });
  };
  const handleUserChange = val => {
    const user = userOptions.value.find(item => item.userId === val);
    reportForm.userName = user ? user.nickName : "";
  };
  onMounted(() => {
    getList();
    // èŽ·å–ç”¨æˆ·åˆ—è¡¨
    userListNoPageByTenantId().then(res => {
      if (res.code === 200) {
        userOptions.value = res.data;
      }
    });
  });
</script>
<style scoped lang="scss">
  .search-row {
    display: flex;
    align-items: center;
    gap: 12px;
  }
  .search-item {
    display: flex;
    align-items: center;
  }
  .search_title {
    margin-right: 8px;
    font-size: 14px;
    color: #606266;
  }
  .transfer-card-title {
    text-align: center;
    font-size: 24px;
    font-weight: bold;
    margin-bottom: 20px;
    color: #303133;
  }
  .transfer-card-container {
    display: flex;
    justify-content: space-between;
    padding: 20px;
  }
  .transfer-card-info {
    flex: 1;
    margin-right: 20px;
  }
  .info-group {
    margin-bottom: 20px;
  }
  .info-item {
    display: flex;
    margin-bottom: 10px;
  }
  .info-label {
    width: 100px;
    font-weight: bold;
    color: #606266;
  }
  .info-value {
    flex: 1;
    color: #303133;
  }
  .transfer-card-qr {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .qr-container {
    text-align: center;
  }
  .print-button-container {
    text-align: center;
    margin-top: 20px;
  }
</style>
<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;
      }
      .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;
        }
      }
    }
    .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>