已添加2个文件
已修改5个文件
2272 ■■■■ 文件已修改
src/router/index.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/Detail/index.vue 499 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/components/CopperPrintingForm.vue 443 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrder/index.vue 1212 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js
@@ -106,6 +106,21 @@
      },
    ],
  },
  // ç”Ÿäº§è®¢å•-生产详情(工序进度)
  {
    path: "/productionManagement/productionOrder/detail",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "",
        component: () => import("@/views/productionManagement/productionOrder/Detail/index.vue"),
        name: "ProductionOrderDetail",
        meta: { title: "生产详情", activeMenu: "/productionManagement/productionOrder" },
      },
    ],
  },
];
// åŠ¨æ€è·¯ç”±ï¼ŒåŸºäºŽç”¨æˆ·æƒé™åŠ¨æ€åŽ»åŠ è½½
src/views/productionManagement/productionOrder/Detail/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,499 @@
<template>
  <div class="app-container production-order-detail">
    <PageHeader content="生产详情">
    </PageHeader>
    <el-card shadow="never" class="mb12">
      <div class="header">
        <div class="title">基础信息</div>
        <div class="sub">
          <span class="mr12">生产订单号:{{ header.npsNo || "-" }}</span>
          <span class="mr12">生产批号:{{ header.lotNo || "-" }}</span>
          <span class="mr12">产品名称:{{ header.productCategory || "-" }}</span>
          <span class="mr12">规格:{{ header.specificationModel || "-" }}</span>
        </div>
      </div>
    </el-card>
    <el-card shadow="never" class="mb12">
      <div class="steps-head">
        <div class="steps-title">工序执行进度</div>
      </div>
      <div class="steps-body">
        <div class="steps-left">
          <div class="steps-wrap">
            <el-steps
              class="process-steps"
              :active="active"
              finish-status="success"
              direction="vertical"
            >
              <el-step
                v-for="(p, idx) in processes"
                :key="p.processCode || idx"
              >
                <template #title>
                  <div
                    class="step-title"
                    :class="{ selected: idx === selectedIndex }"
                    @click="selectProcess(idx)"
                  >
                    {{ `${idx + 1}. ${p.processName || "-"}` }}
                  </div>
                </template>
                <template #description>
                  <div class="step-panel">
                    <div v-if="idx === active" class="current-progress">
                      <div class="current-progress-head">
                        <span class="current-progress-title">当前工序进度</span>
                        <!-- <span class="current-progress-value">{{ currentProcessPercentage }}%</span> -->
                      </div>
                      <el-progress
                        :percentage="currentProcessPercentage"
                        :status="currentProcessPercentage >= 100 ? 'success' : ''"
                        :stroke-width="10"
                      />
                    </div>
                    <div class="step-meta">
                      <span class="meta-item">
                        <span class="meta-label">工序编号</span>
                        <span class="meta-value">{{ p.processCode || "-" }}</span>
                      </span>
                      <span class="meta-item">
                        <span class="meta-label">不良率</span>
                        <span class="meta-value danger">{{ defectRateText(p) }}</span>
                      </span>
                    </div>
                    <div class="step-grid">
                      <div class="grid-item">
                        <div class="grid-label">投入数量</div>
                        <div class="grid-value">{{ p.inputQty ?? 0 }}</div>
                      </div>
                      <div class="grid-item">
                        <div class="grid-label">产出数量</div>
                        <div class="grid-value">{{ p.outputQty ?? 0 }}</div>
                      </div>
                      <div class="grid-item">
                        <div class="grid-label">合格数量</div>
                        <div class="grid-value success">{{ p.qualifiedQty ?? 0 }}</div>
                      </div>
                      <div class="grid-item">
                        <div class="grid-label">不良数量</div>
                        <div class="grid-value danger">{{ p.badQty ?? 0 }}</div>
                      </div>
                    </div>
                  </div>
                </template>
              </el-step>
            </el-steps>
          </div>
        </div>
        <div class="steps-right">
          <div class="right-panel">
            <div class="right-panel-head">
              <div class="right-title">报工信息</div>
              <div class="right-sub" v-if="selectedProcess">
                å½“前工序:{{ selectedProcess.processName }}({{ selectedProcess.processCode }})
              </div>
            </div>
            <div v-if="!selectedProcess" class="right-empty">
              ç‚¹å‡»å·¦ä¾§æŸä¸ªå·¥åºï¼Œå³ä¾§å±•示该工序的报工明细。
            </div>
            <div v-else class="right-content">
              <el-table :data="mockReports" border height="420">
                <el-table-column label="序号" type="index" width="60" align="center" />
                <el-table-column label="报工单号" prop="reportNo" min-width="140" show-overflow-tooltip />
                <el-table-column label="报工人员" prop="reportUser" min-width="120" show-overflow-tooltip />
                <el-table-column label="报工时间" prop="reportTime" min-width="160" show-overflow-tooltip />
                <el-table-column label="产出数量" prop="outputQty" min-width="110" />
                <el-table-column label="不良数量" prop="badQty" min-width="110" />
                <el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
              </el-table>
            </div>
          </div>
        </div>
      </div>
    </el-card>
  </div>
</template>
<script setup>
import { computed, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const header = computed(() => ({
  orderId: route.query.orderId,
  npsNo: route.query.npsNo,
  lotNo: route.query.lotNo,
  productCategory: route.query.productCategory,
  specificationModel: route.query.specificationModel,
}));
// æ¨¡æ‹Ÿå·¥åºæ•°æ®ï¼ˆåŽç»­ç”¨æŽ¥å£æ›¿æ¢ï¼‰
const processes = computed(() => [
  {
    processCode: "GX-001",
    processName: "备料",
    inputQty: 1000,
    outputQty: 980,
    qualifiedQty: 970,
    badQty: 10,
    status: "success",
  },
  {
    processCode: "GX-002",
    processName: "成型",
    inputQty: 980,
    outputQty: 960,
    qualifiedQty: 948,
    badQty: 12,
    status: "process",
  },
  {
    processCode: "GX-003",
    processName: "烘干",
    inputQty: 960,
    outputQty: 950,
    qualifiedQty: 948,
    badQty: 2,
    status: "wait",
  },
  {
    processCode: "GX-004",
    processName: "包装入库",
    inputQty: 950,
    outputQty: 920,
    qualifiedQty: 918,
    badQty: 2,
    status: "wait",
  },
]);
const selectedIndex = ref(null);
const selectProcess = (idx) => {
  selectedIndex.value = idx;
};
const selectedProcess = computed(() => {
  if (selectedIndex.value === null || selectedIndex.value === undefined) return null;
  return (processes.value || [])[selectedIndex.value] || null;
});
// æ¨¡æ‹ŸæŠ¥å·¥ä¿¡æ¯ï¼ˆåŽç»­ç”¨æŽ¥å£æ›¿æ¢ï¼‰
const mockReports = computed(() => {
  const p = selectedProcess.value;
  if (!p) return [];
  const code = p.processCode || "GX";
  return [
    {
      reportNo: `${code}-BG-0001`,
      reportUser: "张三",
      reportTime: "2026-03-14 09:20",
      outputQty: Math.floor((p.outputQty ?? 0) * 0.4),
      badQty: Math.floor((p.badQty ?? 0) * 0.4),
      remark: "正常报工",
    },
    {
      reportNo: `${code}-BG-0002`,
      reportUser: "李四",
      reportTime: "2026-03-14 13:45",
      outputQty: Math.floor((p.outputQty ?? 0) * 0.35),
      badQty: Math.floor((p.badQty ?? 0) * 0.35),
      remark: "设备调试后恢复",
    },
    {
      reportNo: `${code}-BG-0003`,
      reportUser: "王五",
      reportTime: "2026-03-14 17:10",
      outputQty: Math.max(0, (p.outputQty ?? 0) - Math.floor((p.outputQty ?? 0) * 0.75)),
      badQty: Math.max(0, (p.badQty ?? 0) - Math.floor((p.badQty ?? 0) * 0.75)),
      remark: "收尾",
    },
  ];
});
const clampPercentage = (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);
};
// el-steps: active ä¸ºå½“前进行中的步骤下标(模拟)
const active = computed(() => {
  const list = processes.value || [];
  const idx = list.findIndex((p) => p.status === "process");
  return idx >= 0 ? idx : 0;
});
const currentProcess = computed(() => {
  const list = processes.value || [];
  return list[active.value] || null;
});
// å½“前工序进度:用产出/投入估算(UI å…ˆè·‘通,后续按真实规则替换)
const currentProcessPercentage = computed(() => {
  const p = currentProcess.value;
  if (!p) return 0;
  const input = Number(p.inputQty ?? 0);
  const output = Number(p.outputQty ?? 0);
  if (!Number.isFinite(input) || input <= 0) return 0;
  return clampPercentage((output / input) * 100);
});
// ä¸è‰¯çŽ‡ï¼šä¸è‰¯æ•°é‡ / äº§å‡ºæ•°é‡ï¼ˆå…ˆæŒ‰æ­¤å£å¾„,后续对接接口可调整)
const defectRateText = (p) => {
  const bad = Number(p?.badQty ?? 0);
  const output = Number(p?.outputQty ?? 0);
  if (!Number.isFinite(bad) || bad <= 0) return "0%";
  if (!Number.isFinite(output) || output <= 0) return "0%";
  const rate = (bad / output) * 100;
  return `${rate.toFixed(2)}%`;
};
</script>
<style scoped lang="scss">
.production-order-detail {
  // å·¦ä¾§æ­¥éª¤åŒºçš„可视高度:随屏幕高度自适应
  // è¿™é‡Œå‡åŽ»é¡µé¢é¡¶éƒ¨ï¼ˆPageHeader + åŸºç¡€ä¿¡æ¯å¡ç‰‡ + è¾¹è·ç­‰ï¼‰çš„大致高度
  --steps-left-height: calc(100vh - 320px);
  .header {
    display: flex;
    flex-direction: column;
    gap: 8px;
    .title {
      font-size: 16px;
      font-weight: 600;
      color: #303133;
    }
    .sub {
      color: #606266;
      display: flex;
      flex-wrap: wrap;
      row-gap: 6px;
    }
  }
  .steps-head {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 12px;
    .steps-title {
      font-size: 14px;
      font-weight: 600;
      color: #303133;
    }
    .steps-desc {
      font-size: 12px;
      color: #909399;
    }
  }
  .steps-wrap {
    padding: 4px 0 0;
    height: 100%;
    overflow-y: auto;
  }
  .steps-body {
    display: flex;
    gap: 16px;
    align-items: flex-start;
  }
  .steps-left {
    flex: 0 0 50%;
    max-width: 50%;
    min-width: 0;
    height: var(--steps-left-height);
    overflow: hidden;
  }
  .steps-right {
    flex: 1;
    min-width: 0;
  }
  .step-title {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    padding: 2px 6px;
    border-radius: 6px;
    transition: background-color 0.15s ease;
    &:hover {
      background: #f5f7fa;
    }
    &.selected {
      background: rgba(64, 158, 255, 0.12);
      color: #409eff;
    }
  }
  .process-steps {
    width: 100%;
    :deep(.el-step__title) {
      font-weight: 600;
    }
    :deep(.el-step__description) {
      font-size: 12px;
      color: #606266;
      line-height: 18px;
      margin-top: 8px;
      width: 100%;
    }
    :deep(.el-step__main) {
      padding-bottom: 20px;
      width: 100%;
    }
    :deep(.el-step__icon.is-text) {
      border-color: #dcdfe6;
    }
    :deep(.el-step.is-vertical) {
      align-items: flex-start;
    }
    :deep(.el-step__head) {
      width: 28px;
      flex: 0 0 28px;
    }
    :deep(.el-step__main) {
      flex: 1;
      min-width: 0;
      padding-right: 6px;
    }
  }
  .step-panel {
    background: #f6f8fb;
    border: 1px solid #ebeef5;
    border-radius: 10px;
    padding: 12px 12px 10px;
    width: 100%;
    max-width: none;
    box-sizing: border-box;
  }
  .right-panel {
    border: 1px solid #ebeef5;
    border-radius: 10px;
    background: #ffffff;
    padding: 12px;
    min-height: 520px;
    box-sizing: border-box;
  }
  .right-panel-head {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 12px;
  }
  .right-title {
    font-size: 14px;
    font-weight: 700;
    color: #303133;
  }
  .right-sub {
    font-size: 12px;
    color: #909399;
  }
  .right-empty {
    height: 100%;
    min-height: 460px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #909399;
    background: #fafafa;
    border: 1px dashed #dcdfe6;
    border-radius: 10px;
  }
  .current-progress {
    background: #ffffff;
    border: 1px dashed #dcdfe6;
    border-radius: 10px;
    padding: 10px 12px 8px;
    margin-bottom: 10px;
    .current-progress-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 6px;
    }
    .current-progress-title {
      color: #303133;
      font-weight: 600;
      font-size: 12px;
    }
    .current-progress-value {
      color: #606266;
      font-size: 12px;
      font-weight: 600;
    }
  }
  .step-meta {
    display: flex;
    gap: 16px;
    margin-bottom: 10px;
    .meta-item {
      display: inline-flex;
      gap: 8px;
      align-items: center;
    }
    .meta-label {
      color: #909399;
    }
    .meta-value {
      color: #303133;
      font-weight: 600;
      &.danger {
        color: #f56c6c;
      }
    }
  }
  .step-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 10px;
    .grid-item {
      background: #ffffff;
      border: 1px solid #ebeef5;
      border-radius: 10px;
      padding: 10px 10px 8px;
    }
    .grid-label {
      font-size: 12px;
      color: #909399;
      margin-bottom: 6px;
    }
    .grid-value {
      font-size: 16px;
      font-weight: 700;
      color: #303133;
      &.success {
        color: #67c23a;
      }
      &.danger {
        color: #f56c6c;
      }
    }
  }
  .mb12 {
    margin-bottom: 12px;
  }
  .mr12 {
    margin-right: 12px;
  }
}
</style>
src/views/productionManagement/productionOrder/index.vue
@@ -58,11 +58,13 @@
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #completionStatus="{ row }">
          <el-progress
            :percentage="toProgressPercentage(row?.completionStatus)"
            :color="progressColor(toProgressPercentage(row?.completionStatus))"
            :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
          />
          <div class="progress-link" @click="goProductionDetail(row)">
            <el-progress
              :percentage="toProgressPercentage(row?.completionStatus)"
              :color="progressColor(toProgressPercentage(row?.completionStatus))"
              :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
            />
          </div>
        </template>
      </PIMTable>
    </div>
@@ -213,6 +215,7 @@
        {
          name: "工艺路线",
          type: "text",
          showHide: row => row.processRouteCode,
          clickFun: row => {
            showRouteItemModal(row);
          },
@@ -422,6 +425,20 @@
    });
  };
  const goProductionDetail = (row) => {
    if (!row) return;
    router.push({
      path: "/productionManagement/productionOrder/detail",
      query: {
        orderId: row.id,
        npsNo: row.npsNo || "",
        lotNo: row.lotNo || "",
        productCategory: row.productCategory || "",
        specificationModel: row.specificationModel || "",
      },
    });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
@@ -491,4 +508,8 @@
::v-deep .purple{
  background-color: #F4DEFA;
}
.progress-link {
  cursor: pointer;
}
</style>
src/views/productionManagement/workOrder/components/CopperPrintingForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,443 @@
<script setup lang="ts">
import {computed, onMounted, reactive, ref} from "vue";
import dayjs from "dayjs";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {ElMessage} from "element-plus";
import {addProductMain} from "@/api/productionManagement/workOrder.js";
defineOptions({
  name: "CopperPrintingForm"
});
const props = defineProps({
  isShow: {
    type: Boolean,
    required: true
  },
  isEdit: {
    type: Boolean,
    default: false
  },
  detailData: {
    type: Object,
    default: () => ({}),
  },
  row: {
    type: Object,
    default: () => ({}),
  }
});
const emits = defineEmits(["update:isShow", "refreshData"]);
const visible = computed({
  get: () => props.isShow,
  set: (value: boolean) => emits("update:isShow", value),
});
const formData = reactive({
  productProcessRouteItemId: undefined,
  workOrderId: undefined,
  planQuantity: undefined,
  reportWork: undefined,
  productMainId: undefined,
  quantity: undefined, // çƒ§é“œäº§å‡ºæ¿€
  userId: undefined, // ä½œä¸šå‘˜
  userName: undefined, // ä½œä¸šå‘˜
  otherData: {
    dryingTemperature: '', // çƒ˜å¹²æ¸©åº¦
    startTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), // å¼€å§‹æ—¶é—´
    endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), // ç»“束时间
    userId: undefined, // ä½œä¸šå‘˜
    userName: undefined, // ä½œä¸šå‘˜
    underlyingCopperPaste: undefined, // åº•层铜浆
    underlyingCopperPastePrintingQuantity: undefined, // åº•层铜浆印刷次数
    underlyingCopperPasteMachineNumber: undefined, // åº•层铜浆机台号
    underlyingCopperPasteSilkScreenFamilyNumber: undefined, // åº•层铜浆丝网族号
    underlyingCopperPasteNumberOfEyes: undefined, // åº•层铜浆目数
    underlyingCopperPasteUserId: undefined, // åº•层铜浆作业员
    underlyingCopperPasteUserName: undefined, // åº•层铜浆作业员
    underlyingCopperPasteDryingTemperature: undefined, // åº•层铜浆烘干温度
    surfaceCopperPaste: undefined, // è¡¨å±‚铜浆
    surfaceCopperPastePrintingQuantity: undefined, // è¡¨å±‚铜浆印刷次数
    surfaceCopperPasteMachineNumber: undefined, // è¡¨å±‚铜浆机台号
    surfaceCopperPasteSilkScreenFamilyNumber: undefined, // è¡¨å±‚铜浆丝网族号
    surfaceCopperPasteNumberOfEyes: undefined, // è¡¨å±‚铜浆目数
    surfaceCopperPasteUserId: undefined, // è¡¨å±‚铜浆作业员
    surfaceCopperPasteUserName: undefined, // è¡¨å±‚铜浆作业员
    surfaceCopperPasteDryingTemperature: undefined, // è¡¨å±‚铜浆烘干温度,
    steelBurningDate: undefined, // çƒ§é’¢æ—¥æœŸ
    copperFiringTime: undefined, // çƒ§é“œè¿›ç‚‰æ—¶é—´
    steelFiringTime: undefined, // çƒ§é’¢å‡ºç‚‰æ—¶é—´
    weight: undefined, // é‡é‡ï¼ˆkg/pos)
    copperSmeltingTemperatureProfile: undefined, // çƒ§é“œæ¸©åº¦æ›²çº¿
    remark: undefined, // å¤‡æ³¨
  }
})
const userOptions = ref([]);
const getUserList = () => {
  userListNoPageByTenantId()
      .then(res => {
        if (res.code === 200) {
          userOptions.value = res.data || [];
        }
      })
      .catch(err => {
        console.error("获取用户列表失败", err);
      });
};
// ç”¨æˆ·é€‰æ‹©å˜åŒ–æ—¶æ›´æ–° userName
const handleUserChange = (userId: any, reportType: string) => {
  if (userId) {
    const selectedUser = userOptions.value.find(user => user.userId === userId);
    switch (reportType) {
      case 'userId':
        formData.otherData.userName = selectedUser.userName;
        break;
      case 'underlyingCopperPasteUserId':
        formData.otherData.underlyingCopperPasteUserName = selectedUser.userName;
        break;
      case 'surfaceCopperPasteUserId':
        formData.otherData.surfaceCopperPasteUserName = selectedUser.userName;
        break;
    }
  } else {
    switch (reportType) {
      case 'userId':
        formData.otherData.userName = "";
        break;
      case 'underlyingCopperPasteUserId':
        formData.otherData.underlyingCopperPasteUserName = "";
        break;
      case 'surfaceCopperPasteUserId':
        formData.otherData.surfaceCopperPasteUserName = "";
        break;
    }
  }
};
const handleReport = () => {
  if (!formData.otherData.userId && !formData.otherData.surfaceCopperPasteUserId && !formData.otherData.underlyingCopperPasteUserId) {
    ElMessage.error('请选择作业员')
    return;
  }
  if (!formData.quantity || formData.quantity <= 0) {
    ElMessage.error('请输入生产数量')
    return;
  }
  formData.userId = formData.otherData.surfaceCopperPasteUserId || formData.otherData.underlyingCopperPasteUserId;
  const otherData = JSON.stringify(formData.otherData);
  const submitData = {
    ...formData,
    otherData: otherData
  };
  addProductMain(submitData).then(res => {
    if (res.code === 200) {
      ElMessage({
        message: '报工成功',
        type: 'success',
      })
      emits("refreshData")
      visible.value = false;
    } else {
      ElMessage.error('报工失败')
    }
  });
};
const initData = () => {
  if (!props.isEdit) {
    formData.otherData = JSON.parse(props.detailData.otherData || '{}');
    formData.quantity = props.detailData.quantity;
  } else {
    const row = props.row;
    formData.planQuantity = row.planQuantity
    formData.productProcessRouteItemId = row.productProcessRouteItemId
    formData.workOrderId = row.id
    formData.reportWork = row.reportWork
    formData.productMainId = row.productMainId
  }
}
const displayValue = (value: any) => {
  return value === undefined || value === null || value === "" ? "-" : value;
};
onMounted(() => {
  getUserList();
  initData()
})
</script>
<template>
  <el-dialog v-model="visible"
             title="印铜报工"
             width="90%">
    <el-form :model="formData">
      <table class="report-table">
        <tbody>
        <tr>
          <td class="tip" colspan="4">瓷片清洗后放置时间超过72H,重新烘片后才能印刷并填写本栏</td>
          <td class="label" colspan="3">烘干温度</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.dryingTemperature" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.dryingTemperature) }}</span>
          </td>
          <td class="label">开始时间</td>
          <td colspan="2">
            <el-date-picker
                v-if="props.isEdit"
                v-model="formData.otherData.startTime"
                type="datetime"
                value-format="YYYY-MM-DD HH:mm:ss"
                format="YYYY-MM-DD HH:mm:ss"
                placeholder="请选择"
                style="width: 100%"
            />
            <span v-else class="view-value">{{ displayValue(formData.otherData.startTime) }}</span>
          </td>
          <td class="label"  colspan="3">结束时间</td>
          <td colspan="2">
            <el-date-picker
                v-if="props.isEdit"
                v-model="formData.otherData.endTime"
                type="datetime"
                value-format="YYYY-MM-DD HH:mm:ss"
                format="YYYY-MM-DD HH:mm:ss"
                placeholder="请选择"
                style="width: 100%"
            />
            <span v-else class="view-value">{{ displayValue(formData.otherData.endTime) }}</span>
          </td>
          <td class="label" colspan="2">作业员</td>
          <td  colspan="2">
            <el-select v-model="formData.otherData.userId"
                       v-if="props.isEdit"
                       style="width: 100%"
                       placeholder="请选择作业员"
                       clearable
                       filterable
                       @change="handleUserChange($event, 'userId')">
              <el-option v-for="user in userOptions"
                         :key="user.userId"
                         :label="user.userName"
                         :value="user.userId"/>
            </el-select>
            <span v-else class="view-value">{{ displayValue(formData.otherData.userName || formData.otherData.userId) }}</span>
          </td>
        </tr>
        <tr>
          <td class="label" colspan="2">底层铜浆</td>
          <td>
            <el-input v-if="props.isEdit" v-model="formData.otherData.underlyingCopperPaste" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPaste) }}</span>
          </td>
          <td class="label">印刷次数</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.underlyingCopperPastePrintingQuantity" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPastePrintingQuantity) }}</span>
          </td>
          <td class="label">机台号</td>
          <td>
            <el-input v-if="props.isEdit" v-model="formData.otherData.underlyingCopperPasteMachineNumber" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPasteMachineNumber) }}</span>
          </td>
          <td class="label">丝网族号</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.underlyingCopperPasteSilkScreenFamilyNumber" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPasteSilkScreenFamilyNumber) }}</span>
          </td>
          <td class="label">目数</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.underlyingCopperPasteNumberOfEyes" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPasteNumberOfEyes) }}</span>
          </td>
          <td class="label">作业员</td>
          <td colspan="2">
            <el-select v-model="formData.otherData.underlyingCopperPasteUserId"
                       v-if="props.isEdit"
                       style="width: 100%"
                       placeholder="请选择作业员"
                       clearable
                       filterable
                       @change="handleUserChange($event, 'underlyingCopperPasteUserId')">
              <el-option v-for="user in userOptions"
                         :key="user.userId"
                         :label="user.userName"
                         :value="user.userId"/>
            </el-select>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPasteUserName || formData.otherData.underlyingCopperPasteUserId) }}</span>
          </td>
          <td class="label" colspan="2">烘干温度</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.underlyingCopperPasteDryingTemperature" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.underlyingCopperPasteDryingTemperature) }}</span>
          </td>
        </tr>
        <tr>
          <td class="label" colspan="2">表层铜浆</td>
          <td>
            <el-input v-if="props.isEdit" v-model="formData.otherData.surfaceCopperPaste" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPaste) }}</span>
          </td>
          <td class="label">印刷次数</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.surfaceCopperPastePrintingQuantity" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPastePrintingQuantity) }}</span>
          </td>
          <td class="label">机台号</td>
          <td>
            <el-input v-if="props.isEdit" v-model="formData.otherData.surfaceCopperPasteMachineNumber" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPasteMachineNumber) }}</span>
          </td>
          <td class="label">丝网族号</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.surfaceCopperPasteSilkScreenFamilyNumber" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPasteSilkScreenFamilyNumber) }}</span>
          </td>
          <td class="label">目数</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.surfaceCopperPasteNumberOfEyes" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPasteNumberOfEyes) }}</span>
          </td>
          <td class="label">作业员</td>
          <td colspan="2">
            <el-select v-model="formData.otherData.surfaceCopperPasteUserId"
                       v-if="props.isEdit"
                       style="width: 100%"
                       placeholder="请选择作业员"
                       clearable
                       filterable
                       @change="handleUserChange($event, 'surfaceCopperPasteUserId')">
              <el-option v-for="user in userOptions"
                         :key="user.userId"
                         :label="user.userName"
                         :value="user.userId"/>
            </el-select>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPasteUserName || formData.otherData.surfaceCopperPasteUserId) }}</span>
          </td>
          <td class="label" colspan="2">烘干温度</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.surfaceCopperPasteDryingTemperature" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.surfaceCopperPasteDryingTemperature) }}</span>
          </td>
        </tr>
        <tr>
          <td class="label" colspan="2">烧钢日期</td>
          <td colspan="2">
            <el-date-picker
                v-if="props.isEdit"
                v-model="formData.otherData.steelBurningDate"
                type="date"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                placeholder="请选择"
                style="width: 100%"
            />
            <span v-else class="view-value">{{ displayValue(formData.otherData.steelBurningDate) }}</span>
          </td>
          <td class="label" colspan="3">烧铜进炉时间</td>
          <td colspan="2">
            <el-date-picker
                v-if="props.isEdit"
                v-model="formData.otherData.copperFiringTime"
                type="datetime"
                value-format="YYYY-MM-DD HH:mm:ss"
                format="YYYY-MM-DD HH:mm:ss"
                placeholder="请选择"
                style="width: 100%"
            />
            <span v-else class="view-value">{{ displayValue(formData.otherData.copperFiringTime) }}</span>
          </td>
          <td class="label">烧钢出炉时间</td>
          <td colspan="2">
            <el-date-picker
                v-if="props.isEdit"
                v-model="formData.otherData.steelFiringTime"
                type="datetime"
                value-format="YYYY-MM-DD HH:mm:ss"
                format="YYYY-MM-DD HH:mm:ss"
                placeholder="请选择"
                style="width: 100%"
            />
            <span v-else class="view-value">{{ displayValue(formData.otherData.steelFiringTime) }}</span>
          </td>
          <td class="label" colspan="3">烧铜产出激</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.quantity" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.quantity) }}</span>
          </td>
          <td class="label" colspan="2">重量(kg/pos)</td>
          <td colspan="2">
            <el-input v-if="props.isEdit" v-model="formData.otherData.weight" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.weight) }}</span>
          </td>
        </tr>
        <tr>
          <td class="label" colspan="2">烧铜温度曲线</td>
          <td colspan="7">
            <el-input
                v-if="props.isEdit"
                v-model="formData.otherData.copperSmeltingTemperatureProfile"
                type="textarea"
                :rows="3"
                placeholder="请输入"
            />
            <span v-else class="view-value">{{ displayValue(formData.otherData.copperSmeltingTemperatureProfile) }}</span>
          </td>
          <td class="label">备注</td>
          <td colspan="11">
            <el-input v-if="props.isEdit" v-model="formData.otherData.remark" type="textarea" :rows="3" placeholder="请输入"/>
            <span v-else class="view-value">{{ displayValue(formData.otherData.remark) }}</span>
          </td>
        </tr>
        </tbody>
      </table>
    </el-form>
    <template #footer>
        <span class="dialog-footer">
          <el-button v-if="props.isEdit" type="primary"
                     @click="handleReport">确定</el-button>
          <el-button @click="visible = false">{{ props.isEdit ? "取消" : "关闭" }}</el-button>
        </span>
    </template>
  </el-dialog>
</template>
<style scoped>
.report-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 13px;
}
.report-table td {
  border: 1px solid #dcdfe6;
  padding: 6px;
  vertical-align: middle;
}
.report-table .label {
  width: 90px;
  background: #f5f7fa;
  text-align: center;
  font-weight: 500;
}
.report-table .tip {
  text-align: center;
  font-weight: 500;
  line-height: 1.4;
  background: #fafafa;
}
.view-value {
  display: inline-block;
  min-height: 32px;
  line-height: 32px;
}
</style>
src/views/productionManagement/workOrder/index.vue
@@ -10,11 +10,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>
@@ -25,10 +26,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="编辑时间"
@@ -40,28 +43,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>
@@ -105,7 +108,7 @@
                transferCardRowData.status 
              }}</span>
            </div> -->
            <div class="info-item">
              <span class="info-label">计划开始时间</span>
              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
@@ -150,7 +153,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,7 +164,8 @@
      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"
@@ -174,7 +178,7 @@
        <el-form-item label="待生产数量">
          <el-input v-model="reportForm.planQuantity"
                    readonly
                    style="width: 300px" />
                    style="width: 300px"/>
        </el-form-item>
        <el-form-item label="本次生产数量" prop="quantity">
          <el-input v-model.number="reportForm.quantity"
@@ -183,7 +187,7 @@
                    step="1"
                    style="width: 300px"
                    placeholder="请输入本次生产数量"
                    @input="handleQuantityInput" />
                    @input="handleQuantityInput"/>
        </el-form-item>
        <el-form-item label="报废数量" prop="scrapQty">
          <el-input v-model.number="reportForm.scrapQty"
@@ -192,7 +196,7 @@
                    step="1"
                    style="width: 300px"
                    placeholder="请输入报废数量"
                    @input="handleScrapQtyInput" />
                    @input="handleScrapQtyInput"/>
        </el-form-item>
        <el-form-item label="班组信息">
          <el-select v-model="reportForm.userId"
@@ -204,7 +208,7 @@
            <el-option v-for="user in userOptions"
                       :key="user.userId"
                       :label="user.userName"
                       :value="user.userId" />
                       :value="user.userId"/>
          </el-select>
        </el-form-item>
      </el-form>
@@ -216,308 +220,318 @@
        </span>
      </template>
    </el-dialog>
    <FilesDia ref="workOrderFilesRef" />
    <FilesDia ref="workOrderFilesRef"/>
    <CopperPrintingForm
        v-if="copperPrintingFormVisible"
        v-model:isShow="copperPrintingFormVisible"
        :isEdit="true"
        :row="currentReportRowData"
        @refreshData="getList"/>
  </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();
  const { priority_type } = proxy.useDict("priority_type");
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 tableColumn = ref([
    {
      label: "优先级",
      prop: "priority",
      width: '100px',
      dataType: "tag",
      formatData: val => proxy.selectDictLabel(priority_type.value, val),
      formatType: val => {
        const v = Number(val);
        if (v === 0) return "danger";   // çº¢è‰²
        if (v === 1) return "warning";  // é»„色
        if (v === 2) return "success";  // ç»¿è‰²
        return "";
const {proxy} = getCurrentInstance();
const {priority_type} = proxy.useDict("priority_type");
const CopperPrintingForm = defineAsyncComponent(() => import("./components/CopperPrintingForm.vue"));
const tableColumn = ref([
  {
    label: "优先级",
    prop: "priority",
    width: '100px',
    dataType: "tag",
    formatData: val => proxy.selectDictLabel(priority_type.value, val),
    formatType: val => {
      const v = Number(val);
      if (v === 0) return "danger";   // çº¢è‰²
      if (v === 1) return "warning";  // é»„色
      if (v === 2) return "success";  // ç»¿è‰²
      return "";
    },
  },
  // {
  //   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);
          handleEdit(row);
        },
      },
    },
    // {
    //   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 => {
            downloadAndPrintWorkOrder(row);
          },
      },
      {
        name: "附件",
        clickFun: row => {
          openWorkOrderFiles(row);
        },
        {
          name: "附件",
          clickFun: row => {
            openWorkOrderFiles(row);
          },
      },
      {
        name: "报工",
        clickFun: row => {
          showReportDialog(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 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' }
        disabled: row => row.planQuantity <= 0,
      },
    ],
    scrapQty: [
      { validator: validateScrapQty, trigger: 'blur' }
    ]
  };
  // å¤„理本次生产数量输入,限制必须大于等于1
  const handleQuantityInput = (value) => {
    if (value === '' || value === null || value === undefined) {
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const qrCodeUrl = ref("");
const qrRowData = ref(null);
const editDialogVisible = ref(false);
const copperPrintingFormVisible = 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;
    }
    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,
  });
    reportForm.quantity = intValue;
    return;
  }
  reportForm.quantity = num;
};
  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 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 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 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)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
@@ -526,77 +540,77 @@
      .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 => {
  editrow.value = JSON.parse(JSON.stringify(row));
  editDialogVisible.value = true;
};
const handleUpdate = () => {
  updateProductWorkOrder(editrow.value)
      .then(res => {
        proxy.$modal.msgSuccess("提交成功");
        editDialogVisible.value = false;
@@ -607,22 +621,26 @@
          confirmButtonText: "确定",
        });
      });
  };
};
  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()
const showReportDialog = row => {
  currentReportRowData.value = row;
  if (row.processName === '印铜') {
    copperPrintingFormVisible.value = true
    return
  }
  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;
@@ -633,86 +651,86 @@
        console.error("获取用户信息失败", err);
      });
    reportDialogVisible.value = true;
  };
  reportDialogVisible.value = true;
};
  const openWorkOrderFiles = row => {
    workOrderFilesRef.value?.openDialog(row);
  };
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 (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
      };
      // console.log(submitData);
      addProductMain(submitData).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess("报工成功");
          reportDialogVisible.value = false;
          getList();
        } else {
          ElMessageBox.alert(res.msg || "报工失败", "提示", {
            confirmButtonText: "确定",
          });
        }
const handleReport = () => {
  reportFormRef.value?.validate((valid) => {
    if (!valid) {
      return false;
    }
    if (reportForm.planQuantity <= 0) {
      ElMessageBox.alert("待生产数量为0,无法报工", "提示", {
        confirmButtonText: "确定",
      });
    });
  };
      return;
    }
  // èŽ·å–ç”¨æˆ·åˆ—è¡¨
  const getUserList = () => {
    userListNoPageByTenantId()
    // éªŒè¯æœ¬æ¬¡ç”Ÿäº§æ•°é‡
    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
    };
    // console.log(submitData);
    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 || [];
@@ -721,160 +739,172 @@
      .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.userName;
      }
    } else {
      reportForm.userName = "";
// ç”¨æˆ·é€‰æ‹©å˜åŒ–æ—¶æ›´æ–° userName
const handleUserChange = (userId) => {
  if (userId) {
    const selectedUser = userOptions.value.find(user => user.userId === userId);
    if (selectedUser) {
      reportForm.userName = selectedUser.userName;
    }
  };
  } else {
    reportForm.userName = "";
  }
};
  onMounted(() => {
    getList();
    getUserList();
  });
onMounted(() => {
  getList();
  getUserList();
});
</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>
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
@@ -25,6 +25,7 @@
          <el-option label="原材料检验" value="0" />
          <el-option label="过程检验" value="1" />
          <el-option label="出厂检验" value="2" />
          <el-option label="巡检" value="3" />
        </el-select>
      </el-form-item>
      <el-form-item label="工序" prop="processId">
vite.config.js
@@ -7,74 +7,61 @@
  const env = loadEnv(mode, process.cwd());
  const { VITE_APP_ENV } = env;
  const baseUrl =
    env.VITE_APP_ENV === "development"
      ? "http://192.168.1.35:9009"
      : env.VITE_BASE_API;
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9009"
          : env.VITE_BASE_API;
  const javaUrl =
    env.VITE_APP_ENV === "development"
      ? "http://192.168.1.35:9009"
      : env.VITE_JAVA_API;
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9009"
          : env.VITE_JAVA_API;
  return {
    define: {
      __BASE_API__: JSON.stringify(javaUrl),
    define:{
      __BASE_API__: JSON.stringify(javaUrl)
    },
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。
    // é»˜è®¤æƒ…况下,vite ä¼šå‡è®¾ä½ çš„应用是被部署在一个域名的根路径上
    // ä¾‹å¦‚ https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl ä¸º /admin/。
    base: VITE_APP_ENV === "production" ? "/" : "/",
    plugins: createVitePlugins(env, command === "build"),
    resolve: {
      // https://cn.vitejs.dev/config/#resolve-alias
      alias: {
        // è®¾ç½®è·¯å¾„
        "~": path.resolve(__dirname, "./"),
        // è®¾ç½®åˆ«å
        "@": path.resolve(__dirname, "./src"),
      },
      // https://cn.vitejs.dev/config/#resolve-extensions
      extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
      dedupe: ["vue", "axios"], // åŽ»é‡é‡å¤ä¾èµ–
    },
    // å…¨å±€å¼€å¯æž„建缓存(核心)
    cacheDir:
      "/var/jenkins_home/workspace/客户-鹏创电子前端/node_modules/.vite",
    // ä¾èµ–预构建优化
    optimizeDeps: {
      include: ["vue", "axios", "element-plus", "echarts"], // æ ¹æ®é¡¹ç›®ä¾èµ–调整
      esbuildOptions: {
        target: "es2020",
      },
    },
    // æ‰“包配置(核心优化区)
    // æ‰“包配置
    build: {
      sourcemap: false, // å½»åº•关闭生产环境sourcemap
      // https://vite.dev/config/build-options.html
      sourcemap: command === "build" ? false : "inline",
      outDir: "dist",
      assetsDir: "assets",
      chunkSizeWarningLimit: 2000,
      minify: "esbuild", // ä½¿ç”¨ esbuild åŽ‹ç¼©ï¼ˆæ— éœ€é¢å¤–ä¾èµ–ï¼‰
      reportCompressedSize: false, // å…³é—­äº§ç‰©ä½“积报告,减少耗时
      commonjsOptions: {
        include: [/node_modules/, /\.commonjs$/],
      },
      rollupOptions: {
        output: {
          chunkFileNames: "static/js/[name]-[hash].js",
          entryFileNames: "static/js/[name]-[hash].js",
          assetFileNames: "static/[ext]/[name]-[hash].[ext]",
          // åˆ†åŒ…策略(拆分大依赖)
          manualChunks: {
            vendor: ["vue", "vue-router", "pinia", "axios"],
            ui: ["element-plus"], // æ ¹æ®å®žé™…UI库调整
            charts: ["echarts"], // æœ‰å›¾è¡¨åº“则保留,无则删除
          },
        },
        cache: true,
      },
    },
    // vite ç›¸å…³é…ç½®
    server: {
      port: 8001,
      port: 80,
      host: true,
      open: true,
      proxy: {
        // https://cn.vitejs.dev/config/#server-proxy
        "/dev-api": {
          target: baseUrl,
          changeOrigin: true,
          rewrite: (p) => p.replace(/^\/dev-api/, ""),
        },
        // springdoc proxy
        "^/v3/api-docs/(.*)": {
          target: baseUrl,
          changeOrigin: true,
@@ -96,20 +83,6 @@
          },
        ],
      },
      // CSS é¢„编译缓存
      preprocessorOptions: {
        scss: {
          cacheDirectory: path.resolve(
            __dirname,
            "./node_modules/.vite/scss-cache"
          ),
        },
      },
    },
    // esbuild å…¨å±€é…ç½®
    esbuild: {
      logOverride: { "this-is-undefined-in-esm": "silent" },
      target: "es2020",
    },
  };
});