gaoluyang
2026-05-06 7179a06d3a8c824ee711a701e99114205fce9bcb
src/views/productionManagement/processStatistics/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,268 @@
<template>
  <div class="app-container">
    <div class="search-bar">
      <el-form :model="searchForm"
               inline>
        <el-form-item label="日期区间:">
          <el-date-picker v-model="searchForm.dateRange"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                          value-format="YYYY-MM-DD"
                          style="width: 240px" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     icon="Search"
                     @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="stats-grid"
         v-loading="loading">
      <el-row :gutter="16"
              v-if="statsData.length > 0">
        <el-col v-for="(item, index) in statsData"
                :key="index"
                :xs="24"
                :sm="12"
                :md="8"
                :lg="4.8"
                :xl="4.8"
                class="mb-16">
          <div class="stats-card">
            <div class="card-header">
              <span class="process-name">{{ item.name }}</span>
              <div class="header-stats">
                <div class="stat-row">
                  <span class="label">计划数</span>
                  <span class="value">{{ item.planned }}</span>
                </div>
                <div class="stat-row">
                  <span class="label">良品数</span>
                  <span class="value">{{ item.good }}</span>
                </div>
                <div class="stat-row">
                  <span class="label">不良品数</span>
                  <span class="value">{{ item.bad }}</span>
                </div>
              </div>
            </div>
            <div class="card-body">
              <div class="main-stat">
                <div class="big-number">{{ item.total }}</div>
                <div class="sub-label">生产任务数</div>
              </div>
            </div>
            <div class="card-footer">
              <div class="progress-info">
                <span class="progress-label">进度:</span>
                <el-progress :percentage="Math.min(item.percentage, 100)"
                             :color="getProgressColor(item.percentage)"
                             :stroke-width="10"
                             :show-text="false"
                             class="flex-1" />
                <span class="percentage-text">{{ item.percentage }}%</span>
              </div>
            </div>
          </div>
        </el-col>
      </el-row>
      <el-empty v-else
                description="暂无数据" />
    </div>
  </div>
</template>
<script setup>
  import { reactive, ref, onMounted } from "vue";
  import dayjs from "dayjs";
  import { getOperationStatistics } from "@/api/productionManagement/workOrder.js";
  const loading = ref(false);
  const searchForm = reactive({
    dateRange: [],
  });
  const statsData = ref([]);
  const getProgressColor = percentage => {
    if (percentage >= 100) return "#67c23a";
    if (percentage >= 50) return "#409eff";
    if (percentage >= 25) return "#e6a23c";
    return "red";
  };
  const getList = () => {
    loading.value = true;
    const params = {
      startDate: searchForm.dateRange?.[0] || "",
      endDate: searchForm.dateRange?.[1] || "",
    };
    getOperationStatistics(params)
      .then(res => {
        // æ ¹æ®å®žé™…接口返回的字段进行映射
        statsData.value = (res.data || []).map(item => ({
          name: item.operationName || "-",
          total: item.productionTaskCount || 0,
          planned: item.planQuantity || 0,
          good: item.goodQuantity || 0,
          bad: item.scrapQty || 0,
          percentage: Number(item.completionStatus || 0),
        }));
      })
      .finally(() => {
        loading.value = false;
      });
  };
  const handleQuery = () => {
    getList();
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  .app-container {
    padding: 20px;
    background-color: #f0f2f5;
    min-height: calc(100vh - 84px);
  }
  .search-bar {
    background: #fff;
    padding: 15px 20px 0;
    border-radius: 4px;
    margin-bottom: 20px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
  }
  .mb-16 {
    margin-bottom: 16px;
  }
  // æ¨¡æ‹Ÿ lg="4.8" å› ä¸º element ä¸æ”¯æŒ 24/5
  @media only screen and (min-width: 1200px) {
    .el-col-lg-4-8 {
      width: 20%;
      max-width: 20%;
      flex: 0 0 20%;
    }
  }
  .stats-card {
    background: #fff;
    border-radius: 8px;
    padding: 16px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
    transition: transform 0.3s;
    &:hover {
      transform: translateY(-2px);
    }
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 12px;
      .process-name {
        background-color: #e6f7ff;
        color: #1890ff;
        padding: 2px 8px;
        border-radius: 4px;
        font-size: 14px;
        font-weight: 500;
      }
      .header-stats {
        text-align: right;
        .stat-row {
          display: flex;
          justify-content: flex-end;
          align-items: center;
          gap: 8px;
          margin-bottom: 2px;
          .label {
            font-size: 12px;
            color: #909399;
          }
          .value {
            font-size: 13px;
            color: #303133;
            font-weight: bold;
            min-width: 24px;
          }
        }
      }
    }
    .card-body {
      padding: 10px 0;
      .main-stat {
        .big-number {
          font-size: 28px;
          font-weight: bold;
          color: #303133;
          line-height: 1;
        }
        .sub-label {
          font-size: 14px;
          color: #606266;
          margin-top: 8px;
          font-weight: 500;
        }
      }
    }
    .card-footer {
      margin-top: 16px;
      .progress-info {
        display: flex;
        align-items: center;
        gap: 8px;
        .progress-label {
          font-size: 12px;
          color: #909399;
          white-space: nowrap;
        }
        .flex-1 {
          flex: 1;
        }
        .percentage-text {
          font-size: 12px;
          color: #606266;
          min-width: 45px;
          text-align: right;
        }
      }
    }
  }
  // ä¿®æ­£ el-col å¸ƒå±€é€‚配 5 åˆ—
  :deep(.el-row) {
    display: flex;
    flex-wrap: wrap;
  }
  @media only screen and (min-width: 1200px) {
    .el-col-lg-4\.8 {
      flex: 0 0 20%;
      max-width: 20%;
    }
  }
</style>