zhangwencui
2026-05-06 8a67b9a356a75423372a4cb62aa1408b134c89d9
工序生产实况模块开发
已添加1个文件
已修改3个文件
399 ■■■■■ 文件已修改
src/api/productionManagement/workOrder.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/productionManagement/processStatistics/index.vue 370 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/workOrder.js
@@ -41,3 +41,12 @@
    responseType: "blob",
  });
}
// èŽ·å–å·¥åºç»Ÿè®¡æ•°æ®
export function getOperationStatistics(query) {
  return request({
    url: "/productionOperationTask/getOperation",
    method: "get",
    params: query,
  });
}
src/pages.json
@@ -873,6 +873,13 @@
      }
    },
    {
      "path": "pages/productionManagement/processStatistics/index",
      "style": {
        "navigationBarTitleText": "工序生产实况",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/inventoryManagement/receiptManagement/index",
      "style": {
        "navigationBarTitleText": "自定义入库",
src/pages/productionManagement/processStatistics/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,370 @@
<template>
  <view class="process-statistics">
    <PageHeader title="工序生产实况"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="date-picker-container"
            @click="showCalendar = true">
        <view class="date-input">
          <up-icon name="calendar"
                   size="20"
                   color="#999"></up-icon>
          <text class="date-text"
                :class="{ 'placeholder': !searchForm.startDate }">{{ dateRangeText }}</text>
          <view v-if="searchForm.startDate"
                class="clear-icon-wrapper"
                @click.stop="handleClearDate">
            <up-icon name="close-circle-fill"
                     size="18"
                     color="#c0c4cc"></up-icon>
          </view>
        </view>
        <view class="search-btn-wrapper">
          <up-button type="primary"
                     size="small"
                     text="搜索"
                     @click.stop="handleQuery"></up-button>
        </view>
      </view>
    </view>
    <!-- ç»Ÿè®¡å¡ç‰‡åˆ—表 -->
    <scroll-view scroll-y
                 class="stats-list">
      <view v-if="loading"
            class="loading-box">
        <up-loading-icon text="加载中..."></up-loading-icon>
      </view>
      <view v-else-if="statsData.length > 0"
            class="card-grid">
        <view v-for="(item, index) in statsData"
              :key="index"
              class="stats-card">
          <view class="card-header">
            <text class="process-tag">{{ item.name }}</text>
            <view class="header-details">
              <view class="detail-row">
                <text class="label">计划数</text>
                <text class="value">{{ item.planned }}</text>
              </view>
              <view class="detail-row">
                <text class="label">良品数</text>
                <text class="value good">{{ item.good }}</text>
              </view>
              <view class="detail-row">
                <text class="label">不良品</text>
                <text class="value bad">{{ item.bad }}</text>
              </view>
            </view>
          </view>
          <view class="card-body">
            <view class="main-stat">
              <text class="big-number">{{ item.total }}</text>
              <text class="sub-label">生产任务数</text>
            </view>
          </view>
          <view class="card-footer">
            <view class="progress-section">
              <view class="progress-header">
                <text class="progress-label">生产进度</text>
                <text class="percentage-text">{{ item.percentage }}%</text>
              </view>
              <up-line-progress :percentage="Math.min(item.percentage, 100)"
                                :activeColor="getProgressColor(item.percentage)"
                                :show-text="false"
                                height="8"></up-line-progress>
            </view>
          </view>
        </view>
      </view>
      <view v-else
            class="no-data">
        <up-empty mode="data"
                  text="暂无工序统计数据"></up-empty>
      </view>
    </scroll-view>
    <!-- æ—¥åŽ†é€‰æ‹©å™¨ -->
    <up-calendar :show="showCalendar"
                 mode="range"
                 :maxDate="maxDate"
                 minDate="2026-01-01"
                 :monthNum="monthNum"
                 @confirm="onDateConfirm"
                 @close="showCalendar = false"></up-calendar>
  </view>
</template>
<script setup>
  import { ref, reactive, onMounted, computed } from "vue";
  import { getOperationStatistics } from "@/api/productionManagement/workOrder.js";
  import PageHeader from "@/components/PageHeader.vue";
  import dayjs from "dayjs";
  const loading = ref(false);
  const showCalendar = ref(false);
  const dateRange = ref([]);
  const maxDate = dayjs().format("YYYY-MM-DD");
  const monthNum = computed(() => {
    const min = dayjs("2022-02-01");
    const max = dayjs(maxDate);
    return max.diff(min, "month") + 1;
  });
  // const minDate = dayjs().subtract(7, "day").format("YYYY-MM-DD");
  const searchForm = reactive({
    startDate: "",
    endDate: "",
  });
  const statsData = ref([]);
  const dateRangeText = computed(() => {
    if (searchForm.startDate && searchForm.endDate) {
      return `${searchForm.startDate} è‡³ ${searchForm.endDate}`;
    }
    return "请选择日期区间";
  });
  const goBack = () => {
    uni.navigateBack();
  };
  const getProgressColor = percentage => {
    if (percentage >= 100) return "#67c23a";
    if (percentage >= 50) return "#3c9cff";
    if (percentage >= 25) return "#e6a23c";
    return "#f56c6c";
  };
  const onDateConfirm = e => {
    searchForm.startDate = e[0];
    searchForm.endDate = e[e.length - 1];
    showCalendar.value = false;
    handleQuery();
  };
  const getList = () => {
    loading.value = true;
    const params = {
      startDate: searchForm.startDate,
      endDate: searchForm.endDate,
    };
    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();
  };
  const handleClearDate = () => {
    searchForm.startDate = "";
    searchForm.endDate = "";
    handleQuery();
  };
  onMounted(() => {
    // é»˜è®¤æ—¶é—´ç½®ç©º
    searchForm.startDate = "";
    searchForm.endDate = "";
    getList();
  });
</script>
<style scoped lang="scss">
  @import "@/styles/procurement-common.scss";
  .process-statistics {
    min-height: 100vh;
    background-color: #f5f7fa;
    display: flex;
    flex-direction: column;
  }
  .search-section {
    background-color: #fff;
    padding: 24rpx 30rpx;
    margin-bottom: 20rpx;
    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
  }
  .date-picker-container {
    display: flex;
    align-items: center;
    width: 100%;
    .date-input {
      flex: 1;
      height: 80rpx;
      background-color: #f5f7fa;
      border: 1rpx solid #e4e7ed;
      border-radius: 12rpx;
      display: flex;
      align-items: center;
      padding: 0 24rpx;
      margin-right: 20rpx;
      transition: all 0.3s;
      &:active {
        background-color: #ebedf0;
      }
      .date-text {
        font-size: 28rpx;
        color: #303133;
        margin-left: 16rpx;
        flex: 1;
        &.placeholder {
          color: #c0c4cc;
        }
      }
      .clear-icon {
        padding: 10rpx;
        margin-right: -10rpx;
      }
    }
    .search-btn-wrapper {
      width: 140rpx;
    }
  }
  .stats-list {
    flex: 1;
    height: 0;
    padding: 0 24rpx 40rpx;
  }
  .loading-box {
    display: flex;
    justify-content: center;
    padding-top: 100rpx;
  }
  .card-grid {
    display: flex;
    flex-direction: column;
    gap: 24rpx;
  }
  .stats-card {
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 30rpx;
      .process-tag {
        background-color: #e6f7ff;
        color: #1890ff;
        padding: 6rpx 16rpx;
        border-radius: 8rpx;
        font-size: 26rpx;
        font-weight: bold;
      }
      .header-details {
        display: flex;
        flex-direction: column;
        gap: 4rpx;
        .detail-row {
          display: flex;
          justify-content: flex-end;
          align-items: center;
          gap: 12rpx;
          .label {
            font-size: 22rpx;
            color: #999;
          }
          .value {
            font-size: 24rpx;
            color: #333;
            font-weight: bold;
            min-width: 60rpx;
            text-align: right;
            &.good {
              color: #52c41a;
            }
            &.bad {
              color: #f56c6c;
            }
          }
        }
      }
    }
    .card-body {
      padding-bottom: 30rpx;
      border-bottom: 1rpx solid #f0f0f0;
      .main-stat {
        display: flex;
        flex-direction: column;
        align-items: center;
        .big-number {
          font-size: 56rpx;
          font-weight: bold;
          color: #333;
          line-height: 1;
        }
        .sub-label {
          font-size: 26rpx;
          color: #666;
          margin-top: 12rpx;
        }
      }
    }
    .card-footer {
      padding-top: 24rpx;
      .progress-section {
        .progress-header {
          display: flex;
          justify-content: space-between;
          margin-bottom: 12rpx;
          .progress-label {
            font-size: 24rpx;
            color: #999;
          }
          .percentage-text {
            font-size: 24rpx;
            font-weight: bold;
            color: #333;
          }
        }
      }
    }
  }
  .no-data {
    padding-top: 100rpx;
  }
</style>
src/pages/works.vue
@@ -601,6 +601,14 @@
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "生产追溯",
    },
    {
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "工序生产实况",
    },
    {
      icon: "/static/images/icon/shengchanbaogong1.svg",
      label: "工序实况",
    },
  ]);
  // è®¾å¤‡ç®¡ç†åŠŸèƒ½æ•°æ®
@@ -879,6 +887,11 @@
          url: "/pages/productionManagement/productionTraceability/index",
        });
        break;
      case "工序生产实况":
        uni.navigateTo({
          url: "/pages/productionManagement/processStatistics/index",
        });
        break;
      case "设备台账":
        uni.navigateTo({
          url: "/pages/equipmentManagement/ledger/index",