From 3df460667d74868d0a54fce12d5e881bbd6c0c1a Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期四, 17 九月 2026 17:11:18 +0800
Subject: [PATCH] 青岛维护统计

---
 src/views/statistics/case.vue |  369 +++++++++++++++++++++++++---------------------------
 1 files changed, 175 insertions(+), 194 deletions(-)

diff --git a/src/views/statistics/case.vue b/src/views/statistics/case.vue
index 0b7f00b..8bc4386 100644
--- a/src/views/statistics/case.vue
+++ b/src/views/statistics/case.vue
@@ -35,16 +35,25 @@
         </el-form-item>
 
         <el-form-item>
-          <el-button type="primary" icon="el-icon-search" @click="handleSearch">鏌ヨ</el-button>
-          <el-button icon="el-icon-refresh" @click="handleReset">閲嶇疆</el-button>
-          <el-button type="success" icon="el-icon-download" @click="handleExport">瀵煎嚭</el-button>
+          <el-button type="primary" icon="el-icon-search" @click="handleSearch"
+            >鏌ヨ</el-button
+          >
+          <el-button icon="el-icon-refresh" @click="handleReset"
+            >閲嶇疆</el-button
+          >
+          <el-button
+            type="success"
+            icon="el-icon-download"
+            @click="handleExport"
+            >瀵煎嚭</el-button
+          >
         </el-form-item>
       </el-form>
     </el-card>
 
     <!-- 鏁版嵁姒傝 -->
     <el-row :gutter="20" class="overview-row">
-      <el-col :span="6" v-for="item in overviewData" :key="item.title">
+      <el-col :span="8" v-for="item in overviewData" :key="item.title">
         <el-card class="overview-card">
           <div class="overview-content">
             <div class="overview-icon" :style="{ backgroundColor: item.color }">
@@ -71,7 +80,7 @@
               type="text"
               @click="toggleChartType('trend')"
             >
-              {{ chartTypes.trend === 'line' ? '鏌辩姸鍥�' : '鎶樼嚎鍥�' }}
+              {{ chartTypes.trend === "line" ? "鏌辩姸鍥�" : "鎶樼嚎鍥�" }}
             </el-button>
           </div>
           <EChartsWrapper
@@ -117,15 +126,23 @@
         border
         stripe
       >
-        <el-table-column label="鍦板尯" prop="region" align="center"  />
-        <el-table-column label="鏈堜唤" prop="month" align="center" >
+        <el-table-column label="鍦板尯" prop="region" align="center" />
+        <el-table-column label="鏈堜唤" prop="month" align="center">
           <template slot-scope="{ row }">
             {{ formatMonth(row.month) }}
           </template>
         </el-table-column>
-        <el-table-column label="娼滃湪鎹愮尞妗堜緥鏁�" prop="potentialCount" align="center"  />
-        <el-table-column label="瀹屾垚鎹愮尞鏁伴噺" prop="completedCount" align="center"  />
-        <el-table-column label="瀹屾垚鐜�" prop="completionRate" align="center" >
+        <el-table-column
+          label="娼滃湪鎹愮尞妗堜緥鏁�"
+          prop="potentialCount"
+          align="center"
+        />
+        <el-table-column
+          label="瀹屾垚鎹愮尞鏁伴噺"
+          prop="completedCount"
+          align="center"
+        />
+        <el-table-column label="瀹屾垚鐜�" prop="completionRate" align="center">
           <template slot-scope="{ row }">
             <el-progress
               :percentage="row.completionRate"
@@ -135,13 +152,9 @@
             <span>{{ row.completionRate }}%</span>
           </template>
         </el-table-column>
-        <el-table-column label="妗堜緥鏄庣粏" align="center" >
+        <el-table-column label="妗堜緥鏄庣粏" align="center">
           <template slot-scope="{ row }">
-            <el-button
-              type="text"
-              size="mini"
-              @click="viewCaseDetail(row)"
-            >
+            <el-button type="text" size="mini" @click="viewCaseDetail(row)">
               鏌ョ湅({{ row.caseCount || 0 }})
             </el-button>
           </template>
@@ -161,26 +174,21 @@
     </el-card>
 
     <!-- 妗堜緥鏄庣粏寮圭獥 -->
-    <el-dialog
-      title="妗堜緥鏄庣粏"
-      :visible.sync="showDetail"
-
-      append-to-body
-    >
-      <case-detail
-        :query="detailQuery"
-        @close="showDetail = false"
-      />
+    <el-dialog title="妗堜緥鏄庣粏" :visible.sync="showDetail" append-to-body>
+      <case-detail :query="detailQuery" @close="showDetail = false" />
     </el-dialog>
   </div>
 </template>
 
 <script>
-import EChartsWrapper from '@/components/charts/EChartsWrapper.vue';
-import CaseDetail from './components/CaseDetail.vue';
-
+import EChartsWrapper from "@/components/charts/EChartsWrapper.vue";
+import CaseDetail from "./components/CaseDetail.vue";
+import { caseTrendArea, caseTrend, caseTrendMoon, totals,trendList } from "@/api/businessApi";
+import { toTotalsList, toMonth, toNum } from "@/utils/totals";
+import statisticsMode from "@/utils/statisticsMode";
+import { COLORS, axisTooltip, grid, xAxis, yAxis, legend, barSeries, lineSeries } from "@/utils/chartStyle";
 export default {
-  name: 'CaseStatistics',
+  name: "CaseStatistics",
   components: {
     EChartsWrapper,
     CaseDetail
@@ -195,15 +203,15 @@
         limit: 10
       },
 
-      // 鍦板尯閫夐」
+      // 鍦板尯閫夐」锛坴alue 鐩存帴浼犲湴鍖哄悕绉扮粰鎺ュ彛 area 瀛楁锛�
       regionOptions: [
-        { label: '闈掑矝鍦板尯', value: 'qingdao' },
-        { label: '鏃ョ収鍦板尯', value: 'rizhao' },
-        { label: '娴庡崡鍦板尯', value: 'jinan' },
-        { label: '鐑熷彴鍦板尯', value: 'yantai' },
-        { label: '濞佹捣鍦板尯', value: 'weihai' },
-        { label: '娼嶅潑鍦板尯', value: 'weifang' },
-        { label: '涓存矀鍦板尯', value: 'linyi' }
+        { label: "闈掑矝鍦板尯", value: "闈掑矝鍦板尯" },
+        { label: "鏃ョ収鍦板尯", value: "鏃ョ収鍦板尯" },
+        { label: "娴庡崡鍦板尯", value: "娴庡崡鍦板尯" },
+        { label: "鐑熷彴鍦板尯", value: "鐑熷彴鍦板尯" },
+        { label: "濞佹捣鍦板尯", value: "濞佹捣鍦板尯" },
+        { label: "娼嶅潑鍦板尯", value: "娼嶅潑鍦板尯" },
+        { label: "涓存矀鍦板尯", value: "涓存矀鍦板尯" }
       ],
 
       // 鍔犺浇鐘舵��
@@ -215,15 +223,32 @@
 
       // 姒傝鏁版嵁
       overviewData: [
-        { title: '鎬绘綔鍦ㄦ崘鐚渚�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-flag', color: '#409EFF' },
-        { title: '鎬诲畬鎴愭崘鐚暟閲�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-circle-check', color: '#67C23A' },
-        { title: '骞冲潎瀹屾垚鐜�', value: '0%', desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-s-data', color: '#E6A23C' },
-        { title: '娑夊強妗堜緥鏁�', value: 0, desc: '缁熻鍛ㄦ湡鍐�', icon: 'el-icon-document', color: '#F56C6C' }
+        {
+          title: "鎬绘綔鍦ㄦ崘鐚渚�",
+          value: 0,
+          desc: "缁熻鍛ㄦ湡鍐�",
+          icon: "el-icon-s-flag",
+          color: "#5B8FF9"
+        },
+        {
+          title: "鎬诲畬鎴愭崘鐚暟閲�",
+          value: 0,
+          desc: "缁熻鍛ㄦ湡鍐�",
+          icon: "el-icon-circle-check",
+          color: "#5AD8A6"
+        },
+        {
+          title: "骞冲潎瀹屾垚鐜�",
+          value: "0%",
+          desc: "缁熻鍛ㄦ湡鍐�",
+          icon: "el-icon-s-data",
+          color: "#F6BD16"
+        }
       ],
 
       // 鍥捐〃绫诲瀷
       chartTypes: {
-        trend: 'line'
+        trend: "line"
       },
 
       // 鍥捐〃鏁版嵁
@@ -234,200 +259,157 @@
 
       // 寮圭獥鎺у埗
       showDetail: false,
-      detailQuery: {},
-
-      // 妯℃嫙鏁版嵁
-      mockData: {
-        table: [
-          { id: 1, region: '闈掑矝鍦板尯', month: '2024-01', potentialCount: 15, completedCount: 8, completionRate: 53.3, caseCount: 5 },
-          { id: 2, region: '闈掑矝鍦板尯', month: '2024-02', potentialCount: 18, completedCount: 10, completionRate: 55.6, caseCount: 6 },
-          { id: 3, region: '鏃ョ収鍦板尯', month: '2024-01', potentialCount: 8, completedCount: 4, completionRate: 50.0, caseCount: 3 },
-          { id: 4, region: '鏃ョ収鍦板尯', month: '2024-02', potentialCount: 10, completedCount: 6, completionRate: 60.0, caseCount: 4 },
-          { id: 5, region: '娴庡崡鍦板尯', month: '2024-01', potentialCount: 20, completedCount: 12, completionRate: 60.0, caseCount: 7 },
-          { id: 6, region: '娴庡崡鍦板尯', month: '2024-02', potentialCount: 22, completedCount: 14, completionRate: 63.6, caseCount: 8 }
-        ],
-        trend: [
-          { month: '2024-01', potential: 55, completed: 31 },
-          { month: '2024-02', potential: 64, completed: 39 },
-          { month: '2024-03', potential: 58, completed: 35 },
-          { month: '2024-04', potential: 62, completed: 40 },
-          { month: '2024-05', potential: 68, completed: 45 },
-          { month: '2024-06', potential: 72, completed: 50 }
-        ],
-        region: [
-          { region: '闈掑矝鍦板尯', potential: 150, completed: 85 },
-          { region: '鏃ョ収鍦板尯', potential: 80, completed: 45 },
-          { region: '娴庡崡鍦板尯', potential: 200, completed: 120 },
-          { region: '鐑熷彴鍦板尯', potential: 100, completed: 60 }
-        ]
-      }
+      detailQuery: {}
     };
   },
   created() {
     this.loadData();
+    // 鏁版嵁婧�(mock/鐪熷疄鎺ュ彛)鍒囨崲鏃堕噸鏂板姞杞�
+    this.$watch(() => statisticsMode.useMock, () => this.loadData());
   },
   methods: {
+    // 缁勮鎺ュ彛鏌ヨ鏉′欢
+    buildQuery() {
+      const q = {};
+      if (this.query.region && this.query.region.length) {
+        q.area = this.query.region.join(",");
+      }
+      if (this.query.monthRange && this.query.monthRange.length === 2) {
+        q.startTime = this.query.monthRange[0];
+        q.endTime = this.query.monthRange[1];
+      }
+      return q;
+    },
+
     // 鍔犺浇鏁版嵁
     async loadData() {
       this.loading = true;
       try {
-        // 妯℃嫙鎺ュ彛璋冪敤
-        await new Promise(resolve => setTimeout(resolve, 500));
+        const q = this.buildQuery();
+        const [areaRes, trendRes, moonRes, totalsRes] = await Promise.all([
+          caseTrendArea(q),
+          caseTrend(q),
+          caseTrendMoon(q),
+          totals(q)
+        ]);
 
-        // 澶勭悊琛ㄦ牸鏁版嵁
-        let filteredData = [...this.mockData.table];
+        // 鍦板尯鍒嗗竷瀵规瘮
+        this.chartData.region = toTotalsList(areaRes).map(item => ({
+          region: item.area || item.region || "",
+          potential: toNum(item.potentialCount),
+          completed: toNum(item.completeCount)
+        }));
 
-        if (this.query.region.length > 0) {
-          filteredData = filteredData.filter(item =>
-            this.query.region.includes(item.region.replace('鍦板尯', '').toLowerCase())
-          );
-        }
+        // 妗堜緥瓒嬪娍
+        this.chartData.trend = toTotalsList(trendRes).map(item => ({
+          month: toMonth(item.donateTime) || toMonth(item.month),
+          potential: toNum(item.potentialCount),
+          completed: toNum(item.completeCount)
+        }));
 
-        if (this.query.monthRange && this.query.monthRange.length === 2) {
-          const [start, end] = this.query.monthRange;
-          filteredData = filteredData.filter(item =>
-            item.month >= start && item.month <= end
-          );
-        }
+        // 鏄庣粏琛紙鍦板尯 x 鏈堜唤锛�
+        const all = toTotalsList(moonRes).map(item => {
+          const potential = toNum(item.potentialCount);
+          const completed = toNum(item.completeCount);
+          const rate =
+            potential > 0
+              ? Number(((completed / potential) * 100).toFixed(1))
+              : 0;
+          return {
+            region: item.area || item.region || "",
+            month: toMonth(item.donateTime) || toMonth(item.month),
+            potentialCount: potential,
+            completedCount: completed,
+            completionRate: rate,
+            caseCount: toNum(item.totalCount) || potential
+          };
+        });
+        this.total = all.length;
+        const start = (this.query.page - 1) * this.query.limit;
+        this.tableData = all.slice(start, start + this.query.limit);
 
-        this.tableData = filteredData;
-        this.total = filteredData.length;
-
-        // 鏇存柊姒傝鏁版嵁
-        this.updateOverviewData(filteredData);
-
-        // 鏇存柊鍥捐〃鏁版嵁
-        this.chartData.trend = [...this.mockData.trend];
-        this.chartData.region = [...this.mockData.region];
-
+        this.updateOverviewData(toTotalsList(totalsRes));
       } catch (error) {
-        console.error('鍔犺浇鏁版嵁澶辫触:', error);
-        this.$message.error('鏁版嵁鍔犺浇澶辫触');
+        console.error("鍔犺浇鏁版嵁澶辫触:", error);
+        this.$message.error("鏁版嵁鍔犺浇澶辫触");
       } finally {
         this.loading = false;
       }
     },
 
-    // 鏇存柊姒傝鏁版嵁
-    updateOverviewData(data) {
-      if (data.length === 0) return;
-
-      const totalPotential = data.reduce((sum, item) => sum + item.potentialCount, 0);
-      const totalCompleted = data.reduce((sum, item) => sum + item.completedCount, 0);
-      const avgRate = data.length > 0
-        ? (data.reduce((sum, item) => sum + item.completionRate, 0) / data.length).toFixed(1)
-        : 0;
-      const totalCases = data.reduce((sum, item) => sum + (item.caseCount || 0), 0);
+    // 鏇存柊姒傝鏁版嵁锛堟�绘綔鍦�/鎬诲畬鎴愭崘鐚暟鏉ヨ嚜 /opo/totals/totals 鎺ュ彛锛�
+    updateOverviewData(totalsList) {
+      const totalPotential = totalsList.reduce(
+        (sum, item) => sum + toNum(item.totalPotential),
+        0
+      );
+      const totalCompleted = totalsList.reduce(
+        (sum, item) => sum + toNum(item.totalCompletion),
+        0
+      );
+      const avgRate =
+        totalPotential > 0
+          ? ((totalCompleted / totalPotential) * 100).toFixed(1)
+          : 0;
 
       this.overviewData[0].value = totalPotential;
       this.overviewData[1].value = totalCompleted;
       this.overviewData[2].value = `${avgRate}%`;
-      this.overviewData[3].value = totalCases;
     },
 
     // 鑾峰彇瓒嬪娍鍥捐〃閰嶇疆
     getTrendChartOptions() {
-      const isLine = this.chartTypes.trend === 'line';
+      const isLine = this.chartTypes.trend === "line";
+      const xData = this.chartData.trend.map(item => item.month);
+      const potential = this.chartData.trend.map(item => item.potential);
+      const completed = this.chartData.trend.map(item => item.completed);
+
+      const series = isLine
+        ? [
+            lineSeries("娼滃湪鎹愮尞鏁�", potential, COLORS.blue, { area: true }),
+            lineSeries("瀹屾垚鎹愮尞鏁�", completed, COLORS.green, { area: true })
+          ]
+        : [
+            barSeries("娼滃湪鎹愮尞鏁�", potential, COLORS.blue),
+            barSeries("瀹屾垚鎹愮尞鏁�", completed, COLORS.green)
+          ];
 
       return {
-        tooltip: {
-          trigger: 'axis',
-          axisPointer: {
-            type: 'shadow'
-          }
-        },
-        legend: {
-          data: ['娼滃湪鎹愮尞鏁�', '瀹屾垚鎹愮尞鏁�']
-        },
-        grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
-          containLabel: true
-        },
-        xAxis: {
-          type: 'category',
-          data: this.chartData.trend.map(item => this.formatMonth(item.month))
-        },
-        yAxis: {
-          type: 'value',
-          name: '鏁伴噺(涓�)'
-        },
-        series: [
-          {
-            name: '娼滃湪鎹愮尞鏁�',
-            type: isLine ? 'line' : 'bar',
-            data: this.chartData.trend.map(item => item.potential),
-            itemStyle: { color: '#409EFF' },
-            smooth: isLine,
-            lineStyle: isLine ? { width: 3 } : {}
-          },
-          {
-            name: '瀹屾垚鎹愮尞鏁�',
-            type: isLine ? 'line' : 'bar',
-            data: this.chartData.trend.map(item => item.completed),
-            itemStyle: { color: '#67C23A' },
-            smooth: isLine,
-            lineStyle: isLine ? { width: 3 } : {}
-          }
-        ]
+        tooltip: axisTooltip(),
+        legend: legend(["娼滃湪鎹愮尞鏁�", "瀹屾垚鎹愮尞鏁�"]),
+        grid: grid(),
+        xAxis: xAxis(xData),
+        yAxis: yAxis(),
+        series
       };
     },
 
     // 鑾峰彇鍦板尯鍥捐〃閰嶇疆
     getRegionChartOptions() {
       return {
-        tooltip: {
-          trigger: 'axis',
-          axisPointer: {
-            type: 'shadow'
-          }
-        },
-        legend: {
-          data: ['娼滃湪鎹愮尞鏁�', '瀹屾垚鎹愮尞鏁�']
-        },
-        grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
-          containLabel: true
-        },
-        xAxis: {
-          type: 'category',
-          data: this.chartData.region.map(item => item.region)
-        },
-        yAxis: {
-          type: 'value',
-          name: '鏁伴噺(涓�)'
-        },
+        tooltip: axisTooltip(),
+        legend: legend(["娼滃湪鎹愮尞鏁�", "瀹屾垚鎹愮尞鏁�"]),
+        grid: grid(),
+        xAxis: xAxis(this.chartData.region.map(item => item.region)),
+        yAxis: yAxis(),
         series: [
-          {
-            name: '娼滃湪鎹愮尞鏁�',
-            type: 'bar',
-            data: this.chartData.region.map(item => item.potential),
-            itemStyle: { color: '#409EFF' }
-          },
-          {
-            name: '瀹屾垚鎹愮尞鏁�',
-            type: 'bar',
-            data: this.chartData.region.map(item => item.completed),
-            itemStyle: { color: '#67C23A' }
-          }
+          barSeries("娼滃湪鎹愮尞鏁�", this.chartData.region.map(item => item.potential), COLORS.blue),
+          barSeries("瀹屾垚鎹愮尞鏁�", this.chartData.region.map(item => item.completed), COLORS.green)
         ]
       };
     },
 
     // 鑾峰彇杩涘害鏉¢鑹�
     getProgressColor(percentage) {
-      if (percentage >= 60) return '#67C23A';
-      if (percentage >= 50) return '#E6A23C';
-      return '#F56C6C';
+      if (percentage >= 60) return COLORS.green;
+      if (percentage >= 50) return COLORS.amber;
+      return COLORS.coral;
     },
 
     // 鏍煎紡鍖栨湀浠芥樉绀�
     formatMonth(month) {
-      if (!month) return '';
-      return month.replace('-', '骞�') + '鏈�';
+      if (!month) return "";
+      return month.replace("-", "骞�") + "鏈�";
     },
 
     // 澶勭悊鏌ヨ
@@ -456,13 +438,12 @@
 
     // 澶勭悊瀵煎嚭
     handleExport() {
-      this.$message.info('瀵煎嚭鍔熻兘寮�鍙戜腑...');
-      // 杩欓噷鍙互璋冪敤瀵煎嚭鎺ュ彛
+      this.$message.info("瀵煎嚭鍔熻兘寮�鍙戜腑...");
     },
 
     // 鍒囨崲鍥捐〃绫诲瀷
     toggleChartType(type) {
-      this.chartTypes[type] = this.chartTypes[type] === 'line' ? 'bar' : 'line';
+      this.chartTypes[type] = this.chartTypes[type] === "line" ? "bar" : "line";
     },
 
     // 鏌ョ湅妗堜緥璇︽儏
@@ -537,7 +518,7 @@
 
 .overview-desc {
   font-size: 12px;
-  color: #C0C4CC;
+  color: #c0c4cc;
 }
 
 .chart-row {

--
Gitblit v1.9.3