zhangwencui
2 天以前 2b3d9f38998fa0a3903e7789c690e62cf957d4b9
追踪进度接口对接,主生产计划右侧操作栏透光问题修改
已修改4个文件
452 ■■■■■ 文件已修改
src/api/productionPlan/productionPlan.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/productionPlan/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionPlan/trackProgress/index.vue 365 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionPlan/productionPlan.js
@@ -69,3 +69,13 @@
    data: query,
  });
}
// 追踪进度
export function trackProgressByNo(query) {
  return request({
    url: "/track/trackProgressByNo",
    method: "get",
    params: query,
  });
}
src/views/index.vue
@@ -16,9 +16,9 @@
                   type="primary"
                   plain
                   @click="refreshDashboardData">刷新数据</el-button>
        <el-button size="small"
        <!-- <el-button size="small"
                   plain
                   @click="configDialogVisible = true">首页配置</el-button>
                   @click="configDialogVisible = true">首页配置</el-button> -->
      </div>
    </div>
    <div class="content-grid">
@@ -40,7 +40,7 @@
            </el-button>
          </div>
        </section>
        <section class="section-card">
        <!-- <section class="section-card">
          <div class="section-title">重点待办</div>
          <div class="todo-row"
               v-for="todo in todos"
@@ -49,7 +49,7 @@
                    :type="todo.type">{{ todo.level }}</el-tag>
            <span>{{ todo.title }}</span>
          </div>
        </section>
        </section> -->
        <section class="section-card">
          <div class="section-title">经营关注</div>
          <div class="focus-row"
@@ -157,11 +157,10 @@
          </section>
          <section class="section-card"
                   v-if="isSectionVisible('costChart')">
            <div class="section-title">能耗与成本结构</div>
            <div class="section-title">能耗类型占比</div>
            <Echarts :chartStyle="chartStyle"
                     :legend="costLegend"
                     :tooltip="pieTooltip"
                     :series="costSeries"
                     :series="energyTypeSeries"
                     style="height: 260px" />
          </section>
        </div>
@@ -542,6 +541,61 @@
      data: [],
    },
  ]);
  // 能耗类型占比数据
  const energyTypeSeries = reactive([
    {
      type: "pie",
      radius: ["40%", "70%"],
      center: ["50%", "50%"],
      avoidLabelOverlap: false,
      itemStyle: {
        borderRadius: 10,
        borderColor: "#fff",
        borderWidth: 2,
      },
      label: {
        show: true,
        formatter: "{b}: {d}%",
      },
      data: [
        { value: 0, name: "水", itemStyle: { color: "#409EFF" } },
        { value: 0, name: "电", itemStyle: { color: "#E6A23C" } },
        { value: 0, name: "气", itemStyle: { color: "#F56C6C" } },
      ],
    },
  ]);
  // 模拟能耗数据
  const energyData = reactive({
    water: 120,
    electricity: 350,
    gas: 80,
  });
  // 更新能耗类型占比图表
  const updateEnergyTypeChart = () => {
    const { water, electricity, gas } = energyData;
    const total = water + electricity + gas;
    energyTypeSeries[0].data = [
      {
        value: total > 0 ? ((water / total) * 100).toFixed(2) : 0,
        name: "水",
        itemStyle: { color: "#409EFF" },
      },
      {
        value: total > 0 ? ((electricity / total) * 100).toFixed(2) : 0,
        name: "电",
        itemStyle: { color: "#E6A23C" },
      },
      {
        value: total > 0 ? ((gas / total) * 100).toFixed(2) : 0,
        name: "气",
        itemStyle: { color: "#F56C6C" },
      },
    ];
  };
  const planTable = reactive([]);
  const recentTrendCards = reactive([
@@ -982,6 +1036,7 @@
    loadQualityData();
    loadCostComposition();
    loadWarningCenter();
    updateEnergyTypeChart();
    lastUpdatedAt.value = new Date().toLocaleString();
  };
src/views/productionPlan/productionPlan/index.vue
@@ -674,7 +674,10 @@
    router.push({
      path: "/productionPlan/trackProgress",
      query: {
        applyNo: encodeURIComponent(row.applyNo),
        id: row.id,
        applyNo: row.applyNo,
        productName: row.productName,
        model: row.model,
      },
    });
  };
@@ -1559,4 +1562,7 @@
  //     margin-bottom: 0px !important;
  //   }
  // }
  :deep(.el-table .el-table__body-wrapper tr td) {
    background-color: #fff;
  }
</style>
src/views/productionPlan/trackProgress/index.vue
@@ -10,92 +10,118 @@
                   :model="searchForm"
                   class="search-form">
            <el-form-item label="申请单编号">
              <el-input v-model="searchForm.applyNo"
              <el-select v-model="selectedApplyNo"
                         filterable
                         remote
                         reserve-keyword
                        placeholder="请输入申请单编号"
                        style="width: 400px;"></el-input>
            </el-form-item>
            <el-form-item>
              <el-button type="primary"
                         @click="handleSearch">搜索</el-button>
                         :loading="applyNoLoading"
                         :remote-method="handleApplyNoSearch"
                         @change="handleSearch"
                         style="width: 400px;">
                <el-option v-for="option in applyNoOptions"
                           :key="option.id"
                           :label="option.applyNo+'-'+option.productName+'-'+option.model"
                           :value="option.id" />
              </el-select>
            </el-form-item>
          </el-form>
        </div>
      </template>
      <!-- 基础信息 -->
      <div class="detail-section">
      <div v-if="rowData.productionPlanDto"
           class="detail-section">
        <h3 class="section-title">基础信息</h3>
        <el-skeleton :loading="loading"
                     animated>
          <template #template>
            <el-skeleton-item variant="p"
                              style="width: 80%" />
            <el-skeleton-item variant="p"
                              style="width: 60%" />
            <el-skeleton-item variant="p"
                              style="width: 40%" />
          </template>
        <el-descriptions :column="3"
                         border>
          <el-descriptions-item label="申请单编号">{{ rowData.applyNo || '-' }}</el-descriptions-item>
          <el-descriptions-item label="产品名称">{{ rowData.productName || '-' }}</el-descriptions-item>
          <el-descriptions-item label="产品规格">{{ rowData.model || '-' }}</el-descriptions-item>
          <el-descriptions-item label="物料编码">{{ rowData.materialCode || '-' }}</el-descriptions-item>
          <el-descriptions-item label="下发数量">{{ rowData.assignedQuantity || 0 }} <span class="unit">方</span></el-descriptions-item>
            <el-descriptions-item label="申请单编号">{{ rowData.productionPlanDto?.applyNo || '-' }}</el-descriptions-item>
            <el-descriptions-item label="产品名称">{{ rowData.productionPlanDto?.productName || '-' }}</el-descriptions-item>
            <el-descriptions-item label="产品规格">{{ rowData.productionPlanDto?.model || '-' }}</el-descriptions-item>
            <el-descriptions-item label="物料编码">{{ rowData.productionPlanDto?.materialCode || '-' }}</el-descriptions-item>
            <el-descriptions-item label="下发数量">{{ rowData.productionPlanDto?.assignedQuantity || 0 }} <span class="unit">方</span></el-descriptions-item>
          <el-descriptions-item label="当前状态">
            <el-tag :type="getStatusType(rowData.status)">
              {{ getStatusText(rowData.status) }}
              <el-tag :type="getStatusType(rowData.productionPlanDto?.status)">
                {{ getStatusText(rowData.productionPlanDto?.status) }}
            </el-tag>
          </el-descriptions-item>
        </el-descriptions>
        </el-skeleton>
      </div>
      <div class="progress-container">
      <el-empty v-else
                description="暂无数据" />
      <div v-if="rowData.orderDtoList"
           class="progress-container">
        <div class="progress-section">
          <h3 class="section-title">订单信息</h3>
          <div v-for="item in rowData.orderList"
               :key="item.orderNo"
          <el-skeleton :loading="loading"
                       animated>
            <template #template>
              <el-skeleton-item variant="p"
                                style="width: 80%" />
              <el-skeleton-item variant="p"
                                style="width: 60%" />
              <el-skeleton-item variant="p"
                                style="width: 40%" />
              <el-skeleton-item variant="p"
                                style="width: 100%" />
            </template>
            <div v-for="(item, index) in rowData.orderDtoList"
                 :key="item.productOrderDto?.npsNo || index"
               class="order-item">
            <el-descriptions :column="3"
                             border>
              <el-descriptions-item label="订单编号">{{ item.orderNo || '-' }}</el-descriptions-item>
              <!-- <el-descriptions-item label="订单状态">
                <el-tag :type="getStatusType(item.status)">{{ getStatusText(item.status) }}</el-tag>
              </el-descriptions-item> -->
              <el-descriptions-item label="开始日期">{{ item.startTime || '-' }}</el-descriptions-item>
                <el-descriptions-item label="订单编号">{{ item.productOrderDto?.npsNo || '-' }}</el-descriptions-item>
                <el-descriptions-item label="开始日期">{{ item.productOrderDto?.startTime || '-' }}</el-descriptions-item>
              <el-descriptions-item label="完成进度">
                <el-progress :percentage="item.completionRate"
                             :color="customColors(item.completionRate)"
                             :status="item.completionRate === 100 ? 'success' : ''"
                  <el-progress :percentage="item.productOrderDto?.completionStatus"
                               :color="customColors(item.productOrderDto?.completionStatus)"
                               :status="item.productOrderDto?.completionStatus === 100 ? 'success' : ''"
                             style="width: 120px;" />
              </el-descriptions-item>
              <el-descriptions-item label="需求数量">{{ item.quantity || 0 }} <span class="unit">方</span></el-descriptions-item>
              <el-descriptions-item label="完成数量">{{ item.completeQuantity || 0 }} <span class="unit">方</span></el-descriptions-item>
              <el-descriptions-item label="剩余数量">{{ item.remainingQuantity || 0 }} <span class="unit">方</span></el-descriptions-item>
                <el-descriptions-item label="需求数量">{{ item.productOrderDto?.quantity || 0 }} <span class="unit">方</span></el-descriptions-item>
                <el-descriptions-item label="完成数量">{{ item.productOrderDto?.completeQuantity || 0 }} <span class="unit">方</span></el-descriptions-item>
                <el-descriptions-item label="剩余数量">{{ (item.productOrderDto?.quantity - item.productOrderDto?.completeQuantity) || 0 }} <span class="unit">方</span></el-descriptions-item>
            </el-descriptions>
            <el-table :data="trackProgressForm.progressDetails"
              <el-table :data="item.productionProductMainDtos"
                      border
                      style="width: auto; height: 200px">
                        style="width: auto; max-height: 200px">
              <el-table-column prop="step"
                               label="步骤(点击查看详情)"
                                 label="报工(点击查看详情)"
                               align="center">
                <template #default="{ row, $index }">
                  <el-link v-if="$index!=0"
                           @click="handleClickStep(row)"
                           type="primary">{{ row.step }}</el-link>
                  <span v-else>{{ row.step }}</span>
                  <template #default="{ row }">
                    <el-link @click="handleClickStep(row)"
                             type="primary">{{ row.productNo }}</el-link>
                </template>
              </el-table-column>
              <!-- <el-table-column prop="status"
                               label="状态"
                               align="center">
                <template #default="scope">
                  <el-tag :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'">
                    {{ scope.row.status === 'completed' ? '已完成' : scope.row.status === 'processing' ? '进行中' : '待开始' }}
                  </el-tag>
                </template>
              </el-table-column> -->
              <el-table-column prop="quantity"
                               label="数量"
                                 label="数量(方)"
                               align="center" />
              <el-table-column prop="startTime"
                <el-table-column prop="reportingTime"
                               label="时间"
                               align="center" />
              <el-table-column prop="startTime1"
                <el-table-column prop="schedule"
                                 label="班次"
                                 align="center" />
                <el-table-column prop="postName"
                               label="岗位人员"
                               align="center" />
            </el-table>
          </div>
          </el-skeleton>
        </div>
      </div>
      <el-empty v-else-if="rowData.productionPlanDto"
                description="暂无进度" />
    </el-card>
    <!-- 生产报工详情弹窗 -->
    <el-dialog v-model="detailDialogVisible"
@@ -104,6 +130,22 @@
               :close-on-click-modal="false"
               custom-class="custom-dialog">
      <div class="detail-container">
        <el-skeleton :loading="dialogLoading"
                     animated>
          <template #template>
            <el-skeleton-item variant="p"
                              style="width: 80%" />
            <el-skeleton-item variant="p"
                              style="width: 60%" />
            <el-skeleton-item variant="p"
                              style="width: 40%" />
            <el-skeleton-item variant="h3"
                              style="width: 50%" />
            <el-skeleton-item variant="p"
                              style="width: 100%" />
            <el-skeleton-item variant="p"
                              style="width: 100%" />
          </template>
        <!-- 基础信息 -->
        <div class="detail-section">
          <h3 class="section-title">基础信息</h3>
@@ -240,6 +282,7 @@
            </div>
          </div>
        </div>
        </el-skeleton>
      </div>
      <template #footer>
        <div class="dialog-footer">
@@ -255,6 +298,11 @@
  import { ElMessage } from "element-plus";
  import { useRouter, useRoute } from "vue-router";
  import dayjs from "dayjs";
  import {
    trackProgressByNo,
    productionPlanListPage,
  } from "@/api/productionPlan/productionPlan";
  import { productionReportDetail } from "@/api/productionManagement/productionReporting.js";
  const router = useRouter();
  const route = useRoute();
@@ -271,15 +319,21 @@
    remark: "",
  });
  // 搜索表单
  const searchForm = reactive({
    applyNo: "",
  });
  // 生产报工详情弹窗
  const detailDialogVisible = ref(false);
  const detailData = ref({});
  const baseUrl = import.meta.env.VITE_APP_BASE_API;
  // 加载状态
  const loading = ref(false);
  // 弹窗加载状态
  const dialogLoading = ref(false);
  // 申请单下拉框数据
  const applyNoOptions = ref([]);
  const applyNoLoading = ref(false);
  const applyNoQuery = ref("");
  const selectedApplyNo = ref(null);
  // 获取状态类型
  const getStatusType = status => {
@@ -392,28 +446,52 @@
    router.push("/productionPlan/productionPlan");
  };
  // 处理申请单编号搜索
  const handleApplyNoSearch = query => {
    if (query) {
      applyNoLoading.value = true;
      productionPlanListPage({
        current: -1,
        size: -1,
        applyNo: query,
      })
        .then(res => {
          // 转换数据格式为下拉框所需的格式
          applyNoOptions.value = res.data.records;
        })
        .catch(error => {
          console.error(error);
        })
        .finally(() => {
          applyNoLoading.value = false;
        });
    } else {
      applyNoOptions.value = [];
    }
  };
  // 处理搜索
  const handleSearch = () => {
    const applyNo = searchForm.applyNo.trim();
    if (!applyNo) {
      ElMessage.warning("请输入申请单编号");
    if (!selectedApplyNo.value) {
      ElMessage.warning("请选择申请单编号");
      return;
    }
    // 这里可以添加搜索逻辑,例如调用API获取数据
    // 目前使用模拟数据
    ElMessage.success(`搜索申请单编号: ${applyNo}`);
    // 模拟搜索结果
    rowData.applyNo = applyNo;
    rowData.productName = "搜索结果产品";
    rowData.model = "搜索结果规格";
    rowData.materialCode = "MAT-" + applyNo;
    rowData.assignedQuantity = 100;
    rowData.status = 1;
    trackProgressForm.progressDetails = generateProgressDetails(1);
    trackProgressForm.completionRate = calculateCompletionRate(
      trackProgressForm.progressDetails
    );
    rowData.orderList = generateOrderList();
    // 调用API获取数据
    loading.value = true;
    trackProgressByNo({ productionPlanId: selectedApplyNo.value })
      .then(res => {
        console.log(res, "搜索结果");
        // 合并数据到rowData
        Object.assign(rowData, res.data);
        ElMessage.success("搜索成功");
      })
      .catch(error => {
        ElMessage.error("搜索失败,请稍后重试");
        console.error(error);
      })
      .finally(() => {
        loading.value = false;
      });
  };
  // 生成模拟订单数据
@@ -451,97 +529,23 @@
  // 处理点击步骤查看详情
  const handleClickStep = row => {
    // 这里可以添加获取报工详情数据的逻辑
    // 目前使用模拟数据
    detailData.value = {
      npsNo: "NPS-2026-001",
      schedule: "白班",
      postName: "张三",
      materialCode: rowData.materialCode || "MAT-001",
      productName: rowData.productName || "产品A",
      model: rowData.model || "规格A",
      qualifiedQuantity: 100,
      unqualifiedQuantity: 5,
      quantity: 105,
      reportingTime: new Date(),
      createTime: new Date(),
      updateTime: new Date(),
      productionProductRouteItemDtoList: [
        {
          id: 1,
          processName: "工序1",
          postName: "张三",
          processNo: "PROC-001",
          equipmentMalfunction: "无异常",
          equipmentDisposal: "正常运行",
          processExplained: "按照标准工艺操作",
          productionProductRouteItemParamDtoList: [
            {
              id: 11,
              paramName: "原材料A",
              model: "型号A",
              productValue: "100",
              unit: "kg",
              bomId: 101,
            },
            {
              id: 12,
              paramName: "温度",
              paramValue: "25",
              unit: "°C",
              sourceSort: 1,
              valueMode: 2,
              minValue: 20,
              maxValue: 30,
            },
            {
              id: 13,
              paramName: "压力",
              paramValue: "1.5",
              unit: "MPa",
              sourceSort: 1,
              valueMode: 2,
              minValue: 1.0,
              maxValue: 2.0,
            },
            {
              id: 14,
              paramName: "转速",
              paramValue: "1500",
              unit: "rpm",
              sourceSort: 2,
              valueMode: 1,
              standardValue: "1500",
            },
            {
              id: 15,
              paramName: "电流",
              paramValue: "12",
              unit: "A",
              sourceSort: 2,
              valueMode: 2,
              minValue: 10,
              maxValue: 15,
            },
          ],
          fileList: [
            {
              id: 21,
              fileName: "生产记录1.jpg",
              fileUrl: "/upload/files/20260301/12345.jpg",
              fileSize: 1024000,
            },
            {
              id: 22,
              fileName: "生产记录2.jpg",
              fileUrl: "/upload/files/20260301/67890.jpg",
              fileSize: 2048000,
            },
          ],
        },
      ],
    };
    // 获取报工详情数据
    dialogLoading.value = true;
    productionReportDetail(row.id)
      .then(res => {
        console.log(res, "报工详情");
        // 将API返回的数据赋值给detailData
        detailData.value = res.data;
        // 打开弹窗
    detailDialogVisible.value = true;
      })
      .catch(error => {
        ElMessage.error("获取报工详情失败,请稍后重试");
        console.error(error);
      })
      .finally(() => {
        dialogLoading.value = false;
      });
  };
  // 格式化时间
@@ -637,29 +641,26 @@
  // 页面加载时获取数据
  onMounted(() => {
    // 从路由参数中获取数据
    applyNo.value = route.query.applyNo
      ? decodeURIComponent(route.query.applyNo)
    selectedApplyNo.value = route.query.applyNo
      ? route.query.applyNo +
        "-" +
        route.query.productName +
        "-" +
        route.query.model
      : null;
    searchForm.applyNo = applyNo.value;
    // 生成假数据
    rowData.applyNo = applyNo.value || "APPLY-2026-001";
    rowData.productName = "测试产品";
    rowData.model = "测试规格";
    rowData.materialCode = "MAT-001";
    rowData.assignedQuantity = 233;
    rowData.status = 1;
    // 赋值给表单数据
    trackProgressForm.materialCode = rowData.materialCode;
    trackProgressForm.currentStatus = rowData.status;
    trackProgressForm.progressDetails = generateProgressDetails(rowData.status);
    trackProgressForm.completionRate = calculateCompletionRate(
      trackProgressForm.progressDetails
    );
    trackProgressForm.remark = "";
    // 生成模拟订单数据
    rowData.orderList = generateOrderList();
    if (route.query.id) {
      loading.value = true;
      trackProgressByNo({ productionPlanId: route.query.id })
        .then(res => {
          console.log(res, "追踪进度");
          // 合并数据到rowData
          Object.assign(rowData, res.data);
        })
        .finally(() => {
          loading.value = false;
        });
    }
  });
</script>