From fc36e9f71f1238e0c4d65ebb897bb54a68204463 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期四, 02 四月 2026 14:50:11 +0800
Subject: [PATCH] 生产报表联调,并优化页面

---
 src/views/productionManagement/largeScreen/index.vue |  324 ++++++++++++++++++++++++++++++---------------
 src/api/productionManagement/productionStatistic.js  |   50 +++++++
 2 files changed, 263 insertions(+), 111 deletions(-)

diff --git a/src/api/productionManagement/productionStatistic.js b/src/api/productionManagement/productionStatistic.js
new file mode 100644
index 0000000..7722d71
--- /dev/null
+++ b/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
+    });
+}
\ No newline at end of file
diff --git a/src/views/productionManagement/largeScreen/index.vue b/src/views/productionManagement/largeScreen/index.vue
index eece56d..4815bc5 100644
--- a/src/views/productionManagement/largeScreen/index.vue
+++ b/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>
\ No newline at end of file

--
Gitblit v1.9.3