huminmin
4 天以前 fc36e9f71f1238e0c4d65ebb897bb54a68204463
生产报表联调,并优化页面
已添加1个文件
已修改1个文件
374 ■■■■ 文件已修改
src/api/productionManagement/productionStatistic.js 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/largeScreen/index.vue 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionStatistic.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
import request from "@/utils/request.js";
// èŽ·å–ç”Ÿäº§å·¥å•æ•°é‡ç»Ÿè®¡æ•°æ®
export function findProductWorkOrderCountStatistics() {
    return request({
        url: "/productStatistics/workOrderCount",
        method: "get",
    });
}
// èŽ·å–ç”Ÿäº§è´¨é‡ç»Ÿè®¡æ•°æ®
export function findProductQualityStatistics() {
    return request({
        url: "/productStatistics/qualityStatistics",
        method: "get",
    });
}
// èŽ·å–ç”Ÿäº§æ•°é‡ç»Ÿè®¡æ•°æ®
export function findProductProductionStatistics() {
    return request({
        url: "/productStatistics/productionStatistics",
        method: "get",
    });
}
// èŽ·å–äº§å“äº§å‡ºåˆ†æžæ•°æ®
export function findProductOutputCategoryPieData() {
    return request({
        url: "/productStatistics/productOutputCategoryPieData",
        method: "get",
    });
}
// èŽ·å–ä¸è‰¯åŽŸå› åˆ†æžç»Ÿè®¡æ•°æ®
export function findProductDefectReasonAnalysis() {
    return request({
        url: "/productStatistics/defectReasonAnalysis",
        method: "get",
    });
}
// èŽ·å–å·¥åºä¸è‰¯çŽ‡åˆ†æž
export function findProductProcessDefectRateAnalysis(params) {
    return request({
        url: "/productStatistics/processDefectRateAnalysis",
        method: "get",
        params
    });
}
src/views/productionManagement/largeScreen/index.vue
@@ -146,8 +146,8 @@
            />
          </div>
          <div class="category-cards">
            <div v-for="(it, idx) in categoryPieData" :key="it.name" class="category-card">
              <div class="category-name">产品大类{{ idx + 1 }}</div>
            <div v-for="(it, idx) in categoryPieData" :key="idx" class="category-card">
              <div class="category-name">{{ it.name }}</div>
              <div class="category-val">{{ it.value }}</div>
            </div>
          </div>
@@ -205,22 +205,26 @@
    <div class="bottom-row">
      <div class="panel panel-full">
        <div class="panel-header">
          <div class="panel-title">工序不良原因分析</div>
          <div class="panel-title">工序不良率分析</div>
          <div class="panel-actions">
            <div class="panel-action">年份</div>
            <div class="panel-action">筛选时间范围</div>
            <el-date-picker
              v-model="currentYear"
              type="year"
              format="YYYYå¹´"
              value-format="YYYY"
              :clearable="false"
              class="panel-year-picker"
              v-model="dateRange"
              type="daterange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              :clearable="true"
              class="panel-date-picker"
              size="small"
              @change="handleDateChange"
            />
          </div>
        </div>
        <div class="panel-body">
          <div class="bottom-chart-wrap">
          <div class="bottom-chart-wrap" v-loading="isLoading" element-loading-text="加载中...">
            <div class="chart-unit">单位:%</div>
            <Echarts
              :chartStyle="{ width: '100%', height: '260px' }"
@@ -241,7 +245,16 @@
</template>
<script setup>
import { computed, ref } from 'vue'
import {
  findProductQualityStatistics,
  findProductWorkOrderCountStatistics,
  findProductProductionStatistics,
  findProductOutputCategoryPieData,
  findProductDefectReasonAnalysis,
  findProductProcessDefectRateAnalysis
} from '@/api/productionManagement/productionStatistic.js'
import {computed, onMounted, ref} from 'vue'
import * as echarts from 'echarts'
import Echarts from '@/components/Echarts/echarts.vue'
import { ElSelect, ElOption, ElDatePicker } from 'element-plus'
@@ -253,24 +266,20 @@
}
const kpi = ref({
  workOrderTotal: 378,
  workOrderDoing: 108,
  workOrderDone: 238,
  passRate: 98,
  ngRate: 8,
  scrapTotal: 38,
  productionAmount: 989878,
  productionAmountTrend: 16,
  productionQty: 19878,
  productionQtyTrend: 16,
  productionCost: 69878,
  productionCostTrend: -16,
  supplierCount: 48,
  supplierCountTrend: 16,
  totalOutput: 1000,
  workOrderTotal: 0,
  workOrderDoing: 0,
  workOrderDone: 0,
  passRate: 0,
  ngRate: 0,
  scrapTotal: 0,
  productionAmount: 0, // æ€»ç”Ÿäº§æ€»é‡
  productionAmountTrend: 0, // æ€»ç”Ÿäº§æ€»é‡è¾ƒä¸Šæœˆè¶‹åŠ¿
  productionCost: 0, // ç”Ÿäº§æ€»æ¶ˆè€—
  productionCostTrend: 0, // ç”Ÿäº§æ€»æ¶ˆè€—较上月趋势
  supplierCount: 0, // æ€»ä¾›åº”商
  supplierCountTrend: 0, // æ€»ä¾›åº”商较上月趋势
})
const currentYear = ref('2025')
const dateRange = ref([])
function formatMoney(n) {
  const num = Number(n || 0)
@@ -280,6 +289,10 @@
const axisTooltip = {
  trigger: 'axis',
  axisPointer: { type: 'shadow' },
  confine: true, // é™åˆ¶åœ¨å›¾è¡¨åŒºåŸŸå†…显示
  position: 'top', // å›ºå®šæ˜¾ç¤ºåœ¨é¡¶éƒ¨
  enterable: true, // å…è®¸é¼ æ ‡è¿›å…¥æç¤ºæ¡†åŒºåŸŸ
  extraCssText: 'max-height: 200px; overflow-y: auto; padding: 10px;', // æ·»åŠ æœ€å¤§é«˜åº¦å’Œæ»šåŠ¨æ¡
  formatter: (params) => {
    const first = params?.[0]
    const x = first?.axisValueLabel || ''
@@ -296,13 +309,7 @@
// ä¸­éƒ¨å·¦ä¾§ï¼šé¥¼çŠ¶å›¾ï¼ˆæŒ‰äº§å“å¤§ç±»ç»Ÿè®¡äº§å‡ºæ•°é‡ï¼‰
const categoryPieColors = ['#2D5BFF', '#4E8AFF', '#00A4ED', '#26C6DA', '#7C3AED', '#F59E0B', '#EF4444', '#10B981']
const categoryPieData = ref([
  { name: '产品大类1', value: 600 },
  { name: '产品大类2', value: 200 },
  { name: '产品大类3', value: 200 },
  { name: '产品大类4', value: 200 },
  { name: '产品大类5', value: 200 },
])
const categoryPieData = ref([])
const categoryPieLegend = computed(() => {
  // è®¾è®¡å›¾ä¸­é¥¼å›¾æœ¬èº«æ˜¾ç¤ºå æ¯”标签,不额外展示图例
@@ -347,13 +354,9 @@
// ä¸­éƒ¨å³ä¾§ï¼šçޝ形饼图
const ngColors = ['#2D5BFF', '#26C6DA', '#F59E0B', '#7C3AED', '#60A5FA', '#10B981']
const ngReasonData = ref([
  { name: '不良原因1', value: 300 },
  { name: '不良原因3', value: 200 },
  { name: '不良原因4', value: 100 },
  { name: '不良原因5', value: 100 },
  { name: '不良原因6', value: 100 },
])
const ngReasonData = ref([])
const ngRateData = ref([])
const isLoading = ref(false)
const ngTotal = computed(() => ngReasonData.value.reduce((s, it) => s + Number(it.value || 0), 0))
const ngRows = computed(() => {
@@ -385,40 +388,59 @@
// åº•部:折线图(年份联动的每个工序不良率)
const lineGrid = { left: '4%', right: '4%', top: 60, bottom: 30, containLabel: true }
const lineLegend = computed(() => {
  const processes = chartData.value.processes || []
  const processNames = processes.map(p => p.name)
  return {
    show: true,
    top: 8,
    type: 'scroll',
    orient: 'horizontal',
    textStyle: { color: '#666' },
    data: ['平均不良率', '工序A', '工序B', '工序C'],
    data: ['平均不良率', ...processNames],
    pageIconSize: 10,
    pageTextStyle: { color: '#666' },
    pageButtonItemGap: 5,
    pageButtonGap: 10
  }
})
const chartDataByYear = {
  '2023': {
    x: ['2023/02/21', '2023/03/02', '2023/03/13', '2023/03/24', '2023/04/04', '2023/04/15', '2023/04/28'],
    bar: [30, 32, 35, 40, 38, 42, 39],
    lineA: [45, 48, 52, 60, 55, 62, 58],
    lineB: [30, 32, 38, 45, 40, 48, 42],
    lineC: [20, 24, 30, 35, 32, 38, 33],
  },
  '2024': {
    x: ['2024/02/21', '2024/03/02', '2024/03/13', '2024/03/24', '2024/04/04', '2024/04/15', '2024/04/28'],
    bar: [28, 30, 33, 37, 35, 39, 36],
    lineA: [42, 46, 50, 58, 53, 60, 56],
    lineB: [28, 31, 36, 43, 38, 46, 40],
    lineC: [18, 21, 27, 33, 29, 35, 30],
  },
  '2025': {
    x: ['2025/02/21', '2025/03/02', '2025/03/13', '2025/03/24', '2025/04/04', '2025/04/15', '2025/04/28'],
    bar: [32, 34, 37, 42, 40, 45, 41],
    lineA: [45, 49, 54, 61, 57, 64, 60],
    lineB: [31, 33, 39, 47, 41, 50, 44],
    lineC: [21, 25, 31, 37, 34, 40, 35],
  },
}
const chartData = computed(() => {
  return chartDataByYear[currentYear.value] || chartDataByYear['2025']
  const data = ngRateData.value
  if (!data || data.length === 0) {
    return {
      x: [],
      bar: [],
      processes: []
    }
  }
  // Extract data from the API response
  const x = data.map(item => item.date)
  const bar = data.map(item => item.averageDefectRate) // Convert to percentage
  // Extract process names and their data
  const processNames = new Set()
  data.forEach(item => {
    item.processes.forEach(process => {
      Object.keys(process).forEach(name => processNames.add(name))
    })
  })
  const processes = Array.from(processNames).map(name => {
    return {
      name,
      data: data.map(item => {
        const process = item.processes.find(p => p[name] !== undefined)
        return process ? process[name] : 0 // Convert to percentage
      })
    }
  })
  return {
    x,
    bar,
    processes
  }
})
const lineXAxis = computed(() => {
@@ -443,48 +465,118 @@
  ]
})
const lineSeries = computed(() => [
  {
    name: '平均不良率',
    type: 'bar',
    barWidth: 18,
    data: chartData.value.bar,
    itemStyle: {
      color: 'rgba(59, 130, 246, 0.15)',
      borderRadius: [4, 4, 0, 0],
const lineSeries = computed(() => {
  const series = [
    {
      name: '平均不良率',
      type: 'bar',
      barWidth: 18,
      data: chartData.value.bar,
      itemStyle: {
        color: 'rgba(59, 130, 246, 0.15)',
        borderRadius: [4, 4, 0, 0],
      },
    },
  },
  {
    name: '工序A',
    type: 'line',
    smooth: true,
    data: chartData.value.lineA,
    symbol: 'circle',
    symbolSize: 6,
    lineStyle: { width: 2, color: '#3b82f6' },
    itemStyle: { color: '#3b82f6' },
  },
  {
    name: '工序B',
    type: 'line',
    smooth: true,
    data: chartData.value.lineB,
    symbol: 'circle',
    symbolSize: 6,
    lineStyle: { width: 2, color: '#f59e0b' },
    itemStyle: { color: '#f59e0b' },
  },
  {
    name: '工序C',
    type: 'line',
    smooth: true,
    data: chartData.value.lineC,
    symbol: 'circle',
    symbolSize: 6,
    lineStyle: { width: 2, color: '#10b981' },
    itemStyle: { color: '#10b981' },
  },
])
  ]
  // Add process lines with different colors
  const colors = ['#3b82f6', '#f59e0b', '#10b981', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316']
  chartData.value.processes.forEach((process, index) => {
    series.push({
      name: process.name,
      type: 'line',
      smooth: true,
      data: process.data,
      symbol: 'circle',
      symbolSize: 6,
      lineStyle: { width: 2, color: colors[index % colors.length] },
      itemStyle: { color: colors[index % colors.length] },
    })
  })
  return series
})
// èŽ·å–ç”Ÿäº§å·¥å•æ•°é‡ç»Ÿè®¡æ•°æ®
const fetchProductWorkOrderCountStatistic = () => {
  findProductWorkOrderCountStatistics().then((res) => {
    const data = res.data
    kpi.value.workOrderTotal = data.totalCount
    kpi.value.workOrderDoing = data.inProgressCount
    kpi.value.workOrderDone = data.completedCount
  })
}
// èŽ·å–ç”Ÿäº§è´¨é‡ç»Ÿè®¡æ•°æ®
const fetchProductQualityStatistics = () => {
  findProductQualityStatistics().then((res) => {
    const data = res.data
    kpi.value.passRate = data.qualifiedRate
    kpi.value.ngRate = data.defectRate
    kpi.value.scrapTotal = data.scrapCount
  })
}
// èŽ·å–ç”Ÿäº§æ•°é‡ç»Ÿè®¡æ•°æ®
const fetchProductProductionStatistics = () => {
  findProductProductionStatistics().then((res) => {
    const data = res.data
    kpi.value.productionAmount = data.productionOutput
    kpi.value.productionAmountTrend = data.productionOutputMonthlyChange
    kpi.value.productionCost = data.productionConsumption
    kpi.value.productionCostTrend = data.productionConsumptionMonthlyChange
    kpi.value.supplierCount = data.supplierCount
    kpi.value.supplierCountTrend = data.supplierCountMonthlyChange
  })
}
// èŽ·å–äº§å“äº§å‡ºåˆ†æžæ•°æ®
const fetchProductOutputCategoryPieData = () => {
  findProductOutputCategoryPieData().then((res) => {
    categoryPieData.value = res.data
  })
}
// èŽ·å–ä¸è‰¯åŽŸå› åˆ†æžç»Ÿè®¡æ•°æ®
const fetchProductDefectReasonAnalysis = () => {
  findProductDefectReasonAnalysis().then((res) => {
    ngReasonData.value = res.data
  })
}
// èŽ·å–å·¥åºä¸è‰¯çŽ‡åˆ†æž
const fetchProductProcessDefectRateAnalysis = (dateRange) => {
  isLoading.value = true
  const params = dateRange ? {
    startDate: dateRange[0],
    endDate: dateRange[1]
  } : {}
  findProductProcessDefectRateAnalysis(params).then((res) => {
    ngRateData.value = res.data
  }).finally(() => {
    isLoading.value = false
  })
}
// å¤„理日期选择变化
const handleDateChange = (range) => {
  fetchProductProcessDefectRateAnalysis(range)
}
onMounted(() => {
  // åˆå§‹åŒ–时获取生产工单数量统计数据
  fetchProductWorkOrderCountStatistic()
  // åˆå§‹åŒ–时获取生产质量统计数据
  fetchProductQualityStatistics()
  // åˆå§‹åŒ–时获取生产数量统计数据
  fetchProductProductionStatistics()
  // åˆå§‹åŒ–时获取产品产出分析数据
  fetchProductOutputCategoryPieData()
  // åˆå§‹åŒ–时获取不良原因分析统计数据
  fetchProductDefectReasonAnalysis()
  // åˆå§‹åŒ–时获取工序不良率分析
  fetchProductProcessDefectRateAnalysis()
})
</script>
<style scoped>
@@ -858,6 +950,16 @@
  white-space: nowrap;
}
.panel-date-picker {
  width: 240px;
  margin-left: 6px;
}
.panel-date-picker {
  width: 240px;
  margin-left: 6px;
}
.panel-title {
  font-weight: 800;
  color: #030303;
@@ -1086,4 +1188,4 @@
    grid-template-columns: 1fr;
  }
}
</style>
</style>