From fb6e16a425e9ade08dbcca96d6a2f1c9b2f25b1e Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期三, 14 一月 2026 17:51:56 +0800
Subject: [PATCH] fix: 合格率统计调整:铜、铝合并为【导体】统计数据。明细表格字段更改和导出按钮

---
 src/components/Table/lims-table.vue                               |   20 ++
 src/views/statisticalCharts/qualificationRateStatistics/index.vue |  350 +++++++++++++++++++++++++++++++------------------
 src/api/statisticalCharts/dataAnalysis.js                         |   10 +
 3 files changed, 248 insertions(+), 132 deletions(-)

diff --git a/src/api/statisticalCharts/dataAnalysis.js b/src/api/statisticalCharts/dataAnalysis.js
index 09c89e4..2150836 100644
--- a/src/api/statisticalCharts/dataAnalysis.js
+++ b/src/api/statisticalCharts/dataAnalysis.js
@@ -96,3 +96,13 @@
     params: query,
   });
 }
+
+// 瀵煎嚭渚涘簲鍟嗗悎鏍肩巼缁熻
+export function exportSupplierExcel(data) {
+  return request({
+    url: "/dataAnalysis/exportSupplierExcel",
+    method: "post",
+    data: data,
+    responseType: "blob"
+  });
+}
diff --git a/src/components/Table/lims-table.vue b/src/components/Table/lims-table.vue
index 1f4e431..1b698ca 100644
--- a/src/components/Table/lims-table.vue
+++ b/src/components/Table/lims-table.vue
@@ -4,7 +4,7 @@
     <el-table ref="multipleTable" v-loading="tableLoading" :border="border" :data="tableData"
       :header-cell-style="{ background: '#f8f8f9', color: '#515a6e' }" :height="height"
       :highlight-current-row="highlightCurrentRow" :row-class-name="rowClassName" :row-style="rowStyle"
-      :row-key="rowKey" :span-method="spanMethod" stripe style="width: 100%" tooltip-effect="dark" @row-click="rowClick"
+      :row-key="rowKey" :span-method="spanMethod" :show-summary="showSummary" :summary-method="summaryMethod" stripe style="width: 100%" tooltip-effect="dark" @row-click="rowClick"
       @current-change="currentChange" @selection-change="handleSelectionChange" class="lims-table">
       <el-table-column align="center" type="selection" width="55" v-if="isSelection" />
       <el-table-column align="center" label="搴忓彿" type="index" width="60" :index="indexMethod" />
@@ -260,6 +260,14 @@
         };
       },
     },
+    showSummary: {
+      type: Boolean,
+      default: false
+    },
+    summaryMethod: {
+      type: Function,
+      default: null
+    }
   },
   data() {
     return {
@@ -284,9 +292,13 @@
   watch: {
     tableData: {
       handler() {
-        // 褰撹〃鏍兼暟鎹彉鍖栨椂锛屽垵濮嬪寲 uploadKeys
         this.tableData.forEach((_, index) => {
           this.$set(this.uploadKeys, index, Date.now());
+        });
+        this.$nextTick(() => {
+          if (this.$refs.multipleTable) {
+            this.$refs.multipleTable.doLayout();
+          }
         });
       },
       immediate: true
@@ -471,7 +483,7 @@
 </script>
 
 <style scoped>
-.el-table>>>.el-table__empty-text {
+.el-table ::v-deep .el-table__empty-text {
   text-align: center;
 }
 
@@ -479,7 +491,7 @@
   color: rgb(64, 158, 255);
   cursor: pointer;
 }
->>>.cell {
+::v-deep .cell {
   padding: 0 !important;
 }
 .cell {
diff --git a/src/views/statisticalCharts/qualificationRateStatistics/index.vue b/src/views/statisticalCharts/qualificationRateStatistics/index.vue
index 2e4996f..3456973 100644
--- a/src/views/statisticalCharts/qualificationRateStatistics/index.vue
+++ b/src/views/statisticalCharts/qualificationRateStatistics/index.vue
@@ -30,28 +30,22 @@
       </el-col>
     </el-row>
     <el-row :gutter="20">
-      <el-col :span="6">
+      <el-col :span="8">
         <div class="pie-card" :class="{ active: currentMaterialProp === '00Raw' }" @click="updateTitle('鍘熸潗鏂�')">
           <div class="title"><span style="color: #005BAC;font-weight: bold;">鍘熸潗鏂�</span>鍚堟牸鐜�</div>
           <Echarts ref="chart" :legend="pieLegend" :series="rawPieSeries" :tooltip="pieTooltip" style="height: 36vh;">
           </Echarts>
         </div>
       </el-col>
-      <el-col :span="6">
-        <div class="pie-card" :class="{ active: currentMaterialProp === '01Cu' }" @click="updateTitle('閾�')">
-          <div class="title"><span style="color: #005BAC;font-weight: bold;">閾�</span>鍚堟牸鐜�</div>
-          <Echarts ref="chart" :legend="pieLegend" :series="cuPieSeries" :tooltip="pieTooltip" style="height: 36vh;">
+      <el-col :span="8">
+        <div class="pie-card" :class="{ active: currentMaterialProp === '01Cu,02Al' }" @click="updateTitle('瀵间綋')">
+          <div class="title"><span style="color: #005BAC;font-weight: bold;">瀵间綋</span>鍚堟牸鐜�</div>
+          <Echarts ref="chart" :legend="pieLegend" :series="conductorPieSeries" :tooltip="pieTooltip"
+            style="height: 36vh;">
           </Echarts>
         </div>
       </el-col>
-      <el-col :span="6">
-        <div class="pie-card" :class="{ active: currentMaterialProp === '02Al' }" @click="updateTitle('閾�')">
-          <div class="title"><span style="color: #005BAC;font-weight: bold;">閾�</span>鍚堟牸鐜�</div>
-          <Echarts ref="chart" :legend="pieLegend" :series="alPieSeries" :tooltip="pieTooltip" style="height: 36vh;">
-          </Echarts>
-        </div>
-      </el-col>
-      <el-col :span="6">
+      <el-col :span="8">
         <div class="pie-card" :class="{ active: currentMaterialProp === '04Dlan' }" @click="updateTitle('鐢电紗')">
           <div class="title"><span style="color: #005BAC;font-weight: bold;">鐢电紗</span>鍚堟牸鐜�</div>
           <Echarts ref="chart" :legend="pieLegend" :series="dlanPieSeries" :tooltip="pieTooltip" style="height: 36vh;">
@@ -79,9 +73,16 @@
       </el-col>
       <el-col :span="12">
         <div class="inspection-card">
-          <div class="title"><span style="color: #005BAC;font-weight: bold;">{{ inspectionTitle }}</span>鏄庣粏鏁版嵁</div>
+          <div class="title" style="display:flex;align-items:center;justify-content:space-between;">
+            <div>
+              <span style="color: #005BAC;font-weight: bold;">{{ inspectionTitle }}</span>鏄庣粏鏁版嵁
+            </div>
+            <el-button type="text" icon="el-icon-download" @click="downloadTable">
+              涓嬭浇
+            </el-button>
+          </div>
           <lims-table :tableData="tableData" :column="tableColumn" :tableLoading="tableLoading"
-            :height="'calc(40vh - 40px)'"></lims-table>
+            :height="'calc(40vh - 40px)'" :show-summary="true" :summary-method="getSummaries"></lims-table>
         </div>
       </el-col>
     </el-row>
@@ -96,7 +97,8 @@
   getRawPassRateByBarChart,
   getRawPassRateByCake,
   getRawUpMonth,
-  getMaterialPropTable
+  getMaterialPropTable,
+  exportSupplierExcel
 } from "@/api/statisticalCharts/dataAnalysis";
 
 export default {
@@ -243,40 +245,7 @@
           ]
         }
       ],
-      cuPieSeries: [
-        {
-          name: 'Access From',
-          type: 'pie',
-          radius: '70%',
-          center: ['50%', '50%'],
-          avoidLabelOverlap: false,
-          itemStyle: {
-            borderColor: '#fff',
-            borderWidth: 2
-          },
-          label: {
-            alignTo: 'edge',
-            formatter: '{name|{b}}\n{time|{c}}',
-            edgeDistance: 10,
-            lineHeight: 15,
-            rich: {
-              time: {
-                fontSize: 10,
-                color: '#999'
-              }
-            },
-          },
-          labelLine: {
-            length: 20,
-            length2: 40,
-          },
-          data: [
-            { value: 0, name: '涓嶅悎鏍兼暟閲�', itemStyle: { color: '#F56C6C' } },
-            { value: 0, name: '鍚堟牸鏁伴噺', itemStyle: { color: '#67C23A' } },
-          ]
-        }
-      ],
-      alPieSeries: [
+      conductorPieSeries: [
         {
           name: 'Access From',
           type: 'pie',
@@ -420,34 +389,18 @@
       tableData: [],
       tableLoading: false,
       tableColumn: [
-        { label: '鎵瑰彿', prop: 'updateBatchNo', minWidth: '120px' },
-        { label: '鎶佃揪鎴愬搧鏁伴噺', prop: 'qtyArrived', minWidth: '100px' },
-        { label: '闆朵欢鎻忚堪', prop: 'partDesc', minWidth: '200px' },
+        { label: '渚涘簲鍟嗗悕绉�', prop: 'supplierName', minWidth: '200px' },
+        { label: '鍒拌揣鎵规', prop: 'totalBatch', minWidth: '100px' },
+        { label: '涓嶅悎鏍兼壒娆�', prop: 'unqualifiedBatch', minWidth: '100px' },
         {
-          dataType: 'tag',
-          label: '妫�楠岀姸鎬�',
-          prop: 'inspectStatus',
+          label: '鍚堟牸鐜�(%)',
+          prop: 'passRate',
           minWidth: '100px',
-          formatData: (params) => {
-            if (params == 0) return '妫�楠屼腑'
-            if (params == 1) return '鍚堟牸'
-            if (params == 2) return '涓嶅悎鏍�'
-            if (params == 3) return '鏈笅鍗�'
-            if (params == 4) return '璁╂鏀捐'
-            return null
-          },
-          formatType: (params) => {
-            if (params == 1 || params == 4) return 'success'
-            if (params == 0 || params == 2) return 'danger'
-            if (params == 3) return 'warning'
-            return null
-          }
-        },
-        { label: '涓嬪彂鏃堕棿', prop: 'sendTime', minWidth: '150px' }
+          formatData: (val) => (val != null ? val + '%' : '0%')
+        }
       ],
       rawPassRate: '',
-      cuPassRate: '',
-      alPassRate: '',
+      conductorPassRate: '',
       dlanPassRate: '',
       sum: '',
     }
@@ -471,27 +424,51 @@
     },
 
     getBar() {
-      const params = {
-        dateType: this.dateType,
-        beginDate: this.beginDate,
-        endDate: this.endDate,
-        sampleName: this.sampleName,
-        modelName: this.modelName,
-        supplierName: this.supplierName,
-        materialProp: this.currentMaterialProp,
-      };
+      const types = this.currentMaterialProp.split(',');
+      const requests = types.map(t => {
+        const params = {
+          dateType: this.dateType,
+          beginDate: this.beginDate,
+          endDate: this.endDate,
+          sampleName: this.sampleName,
+          modelName: this.modelName,
+          supplierName: this.supplierName,
+          materialProp: t,
+        };
+        return getRawPassRateByBarChart(params);
+      });
 
-      getRawPassRateByBarChart(params).then((res) => {
+      Promise.all(requests).then((responses) => {
+        let dateMap = {}; // { date: { qualified, unQualified } }
+
+        responses.forEach(res => {
+          if (res.data && Array.isArray(res.data)) {
+            res.data.forEach(item => {
+              const date = item.searchTime;
+              if (!dateMap[date]) {
+                dateMap[date] = { qualified: 0, unQualified: 0 };
+              }
+              dateMap[date].qualified += (item.qualified || 0);
+              dateMap[date].unQualified += (item.unQualified || 0);
+            });
+          }
+        });
+
+        const sortedDates = Object.keys(dateMap).sort();
         let qualifiedData = [];
         let unQualifiedData = [];
         let lineData = [];
         let xAxis = [];
 
-        res.data.forEach(item => {
-          qualifiedData.push(item.qualified || 0);
-          unQualifiedData.push(item.unQualified || 0);
-          lineData.push(item.passRate || 0);
-          xAxis.push(item.searchTime);
+        sortedDates.forEach(date => {
+          const { qualified, unQualified } = dateMap[date];
+          const total = qualified + unQualified;
+          const passRate = total > 0 ? (qualified / total * 100).toFixed(2) : 0;
+
+          xAxis.push(date);
+          qualifiedData.push(qualified);
+          unQualifiedData.push(unQualified);
+          lineData.push(parseFloat(passRate));
         });
 
         this.echartsSeries[0].data = qualifiedData;
@@ -505,32 +482,46 @@
     getRawPass() {
       const materials = [
         { type: '00Raw', series: 'rawPieSeries', rate: 'rawPassRate' },
-        { type: '01Cu', series: 'cuPieSeries', rate: 'cuPassRate' },
-        { type: '02Al', series: 'alPieSeries', rate: 'alPassRate' },
+        { type: '01Cu,02Al', series: 'conductorPieSeries', rate: 'conductorPassRate' },
         { type: '04Dlan', series: 'dlanPieSeries', rate: 'dlanPassRate' }
       ];
 
       materials.forEach(item => {
-        const params = {
-          dateType: this.dateType,
-          beginDate: this.beginDate,
-          endDate: this.endDate,
-          sampleName: this.sampleName,
-          modelName: this.modelName,
-          supplierName: this.supplierName,
-          materialProp: item.type
-        }
-        getRawPassRateByCake(params).then((res) => {
-          if (res.data) {
-            this[item.series][0].data[0].value = res.data.unQualified || 0
-            this[item.series][0].data[1].value = res.data.qualified || 0
-            this[item.rate] = res.data.passRate != null ? res.data.passRate + '%' : ''
+        const types = item.type.split(',');
+        const requests = types.map(t => {
+          const params = {
+            dateType: this.dateType,
+            beginDate: this.beginDate,
+            endDate: this.endDate,
+            sampleName: this.sampleName,
+            modelName: this.modelName,
+            supplierName: this.supplierName,
+            materialProp: t
+          };
+          return getRawPassRateByCake(params);
+        });
+
+        Promise.all(requests).then((responses) => {
+          let unQualified = 0;
+          let qualified = 0;
+          responses.forEach(res => {
+            if (res.data) {
+              unQualified += (res.data.unQualified || 0);
+              qualified += (res.data.qualified || 0);
+            }
+          });
+
+          if (qualified + unQualified > 0) {
+            this[item.series][0].data[0].value = unQualified;
+            this[item.series][0].data[1].value = qualified;
+            const passRate = (qualified / (qualified + unQualified) * 100).toFixed(2);
+            this[item.rate] = passRate + '%';
           } else {
-            this[item.series][0].data[0].value = 0
-            this[item.series][0].data[1].value = 0
-            this[item.rate] = ''
+            this[item.series][0].data[0].value = 0;
+            this[item.series][0].data[1].value = 0;
+            this[item.rate] = '';
           }
-        })
+        });
       });
     },
     // 鑾峰彇鏈湀妫�楠岀被鍨嬫暟閲�
@@ -590,8 +581,7 @@
 
       const typeMap = {
         '鍘熸潗鏂�': '00Raw',
-        '閾�': '01Cu',
-        '閾�': '02Al',
+        '瀵间綋': '01Cu,02Al',
         '鐢电紗': '04Dlan',
       };
 
@@ -605,24 +595,49 @@
 
     getTableData() {
       this.tableLoading = true;
-      const params = {
-        dateType: this.dateType,
-        beginDate: this.beginDate,
-        endDate: this.endDate,
-        sampleName: this.sampleName,
-        modelName: this.modelName,
-        supplierName: this.supplierName,
-        materialProp: this.currentMaterialProp,
-      };
+      const types = this.currentMaterialProp.split(',');
+      const requests = types.map(t => {
+        const params = {
+          dateType: this.dateType,
+          beginDate: this.beginDate,
+          endDate: this.endDate,
+          sampleName: this.sampleName,
+          modelName: this.modelName,
+          supplierName: this.supplierName,
+          materialProp: t,
+        };
+        return getMaterialPropTable(params);
+      });
 
-      getMaterialPropTable(params).then((res) => {
-        if (Array.isArray(res.data)) {
-          this.tableData = res.data;
-        } else if (res.data && res.data.records) {
-          this.tableData = res.data.records;
-        } else {
-          this.tableData = [];
-        }
+      Promise.all(requests).then((responses) => {
+        let supplierMap = {}; // { supplierName: { totalBatch, unqualifiedBatch } }
+
+        responses.forEach(res => {
+          const data = Array.isArray(res.data) ? res.data : (res.data && res.data.records ? res.data.records : []);
+          data.forEach(item => {
+            const name = item.supplierName || '鏈煡渚涘簲鍟�';
+            if (!supplierMap[name]) {
+              supplierMap[name] = { totalBatch: 0, unqualifiedBatch: 0 };
+            }
+            supplierMap[name].totalBatch += (item.totalBatch || 0);
+            supplierMap[name].unqualifiedBatch += (item.unqualifiedBatch || 0);
+          });
+        });
+
+        const tableData = Object.keys(supplierMap).map(name => {
+          const { totalBatch, unqualifiedBatch } = supplierMap[name];
+          const passRate = totalBatch > 0 ? ((totalBatch - unqualifiedBatch) / totalBatch * 100).toFixed(2) : 0;
+          return {
+            supplierName: name,
+            totalBatch,
+            unqualifiedBatch,
+            passRate
+          };
+        });
+
+        // Sort by totalBatch descending
+        tableData.sort((a, b) => b.totalBatch - a.totalBatch);
+        this.tableData = tableData;
         this.tableLoading = false;
       }).catch(() => {
         this.tableLoading = false;
@@ -639,6 +654,85 @@
       const title = `${this.inspectionTitle}_鍚堟牸鐜囪秼鍔縛;
       this.$refs.barChart.downloadImage(title);
     },
+
+    // 涓嬭浇琛ㄦ牸鏁版嵁
+    downloadTable() {
+      if (!this.tableData || this.tableData.length === 0) {
+        this.$message.warning("娌℃湁鍙鍑虹殑鏁版嵁");
+        return;
+      }
+
+      exportSupplierExcel(this.tableData)
+        .then(res => {
+          if (res.type && res.type.includes("application/json")) {
+            const fileReader = new FileReader();
+            fileReader.onload = () => {
+              const msg = JSON.parse(fileReader.result);
+              this.$message.error(msg.msg || "瀵煎嚭澶辫触");
+            };
+            fileReader.readAsText(res);
+            return;
+          }
+
+          const blob = new Blob([res], {
+            type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+          });
+
+          const fileName = `${this.inspectionTitle}鍚堟牸鐜囩粺璁�.xlsx`;
+
+          if (window.navigator && window.navigator.msSaveOrOpenBlob) {
+            window.navigator.msSaveOrOpenBlob(blob, fileName);
+          } else {
+            const link = document.createElement("a");
+            link.href = window.URL.createObjectURL(blob);
+            link.download = fileName;
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            window.URL.revokeObjectURL(link.href);
+          }
+        })
+        .catch(() => {
+          this.$message.error("瀵煎嚭澶辫触");
+        });
+    },
+
+    getSummaries(param) {
+      const { columns, data } = param;
+      const sums = [];
+      columns.forEach((column, index) => {
+        if (index === 0) {
+          sums[index] = '鍚堣';
+          return;
+        }
+
+        const property = column.property;
+        if (property === 'supplierName') {
+          sums[index] = '';
+          return;
+        }
+
+        if (property === 'totalBatch' || property === 'unqualifiedBatch') {
+          const values = data.map(item => Number(item[property]));
+          sums[index] = values.reduce((prev, curr) => {
+            const value = Number(curr);
+            if (!isNaN(value)) {
+              return prev + curr;
+            } else {
+              return prev;
+            }
+          }, 0);
+        } else if (property === 'passRate') {
+          const totalBatch = data.reduce((prev, curr) => prev + (Number(curr.totalBatch) || 0), 0);
+          const unqualifiedBatch = data.reduce((prev, curr) => prev + (Number(curr.unqualifiedBatch) || 0), 0);
+          sums[index] = totalBatch > 0 ? ((totalBatch - unqualifiedBatch) / totalBatch * 100).toFixed(2) + '%' : '0%';
+        } else {
+          sums[index] = '';
+        }
+      });
+
+      return sums;
+    },
   },
 }
 </script>

--
Gitblit v1.9.3