chenhj
5 天以前 2f71101ad75ce34a9f5f9de6e0d0902093b36ce3
Merge branch 'dev_衡阳_鹏创电子' of http://114.132.189.42:9002/r/product-inventory-management into dev_衡阳_鹏创电子
已添加1个文件
已修改4个文件
617 ■■■■■ 文件已修改
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/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/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",
    },
  };
});