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/donor.vue |  580 ++++++++++++++++++++++++---------------------------------
 1 files changed, 242 insertions(+), 338 deletions(-)

diff --git a/src/views/statistics/donor.vue b/src/views/statistics/donor.vue
index 192e13c..a2e8bb8 100644
--- a/src/views/statistics/donor.vue
+++ b/src/views/statistics/donor.vue
@@ -67,23 +67,27 @@
             <span>涓浗鍒嗙被鍒嗗竷</span>
           </div>
           <EChartsWrapper
-            :id="'category-chart'"
-            :options="categoryChartOptions"
+            v-if="useMock"
+            :id="'china-category-chart'"
+            :options="chinaCategoryOptions"
             height="400px"
           />
+          <el-empty v-else description="鍚庣鏆傛湭鎻愪緵涓浗鍒嗙被缁熻鎺ュ彛"></el-empty>
         </el-card>
       </el-col>
 
       <el-col :span="12">
         <el-card>
           <div slot="header" class="clearfix">
-            <span>姝讳骸鍘熷洜鍒嗘瀽</span>
+            <span>姝讳骸鍘熷洜鍒嗗竷</span>
           </div>
           <EChartsWrapper
-            :id="'death-chart'"
-            :options="deathChartOptions"
+            v-if="useMock"
+            :id="'death-cause-chart'"
+            :options="deathCauseOptions"
             height="400px"
           />
+          <el-empty v-else description="鍚庣鏆傛湭鎻愪緵姝讳骸鍘熷洜缁熻鎺ュ彛"></el-empty>
         </el-card>
       </el-col>
     </el-row>
@@ -94,71 +98,54 @@
         <span>鎹愮尞鑰呰缁嗕俊鎭�</span>
       </div>
 
-      <el-table
-        v-loading="loading"
-        :data="tableData"
-        style="width: 100%"
-        border
-        stripe
-      >
-        <el-table-column label="鎹愮尞鑰呯紪鍙�" prop="donorNo" align="center"  />
-        <el-table-column label="濮撳悕" prop="name" align="center"  />
-        <el-table-column label="鎬у埆" prop="gender" align="center" >
-          <template slot-scope="{ row }">
-            <el-tag :type="row.gender === '鐢�' ? 'primary' : 'danger'" size="small">
-              {{ row.gender }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="骞撮緞" prop="age" align="center"  />
-        <el-table-column label="琛�鍨�" prop="bloodType" align="center" >
-          <template slot-scope="{ row }">
-            <el-tag :type="getBloodTypeTag(row.bloodType)" size="small">
-              {{ row.bloodType }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="鍦板尯" prop="region" align="center"  />
-        <el-table-column label="鎹愮尞鏃堕棿" prop="donationTime" align="center"  />
-        <el-table-column label="涓浗鍒嗙被" prop="chinaCategory" align="center"  />
-        <el-table-column label="姝讳骸鍘熷洜" prop="deathCause" align="center"  />
-        <el-table-column label="鎹愮尞鍣ㄥ畼" prop="organs" align="center" >
-          <template slot-scope="{ row }">
-            <span>{{ row.organs.join('銆�') }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="鎿嶄綔" align="center" " fixed="right">
-          <template slot-scope="{ row }">
-            <el-button
-              type="text"
-              size="mini"
-              @click="viewDonorDetail(row)"
-            >
-              璇︽儏
-            </el-button>
-          </template>
-        </el-table-column>
-      </el-table>
+      <template v-if="useMock">
+        <el-table
+          v-loading="loading"
+          :data="tableData"
+          style="width: 100%"
+          border
+          stripe
+        >
+          <el-table-column label="鎹愮尞鑰呯紪鍙�" prop="donorNo" align="center" />
+          <el-table-column label="濮撳悕" prop="name" align="center" />
+          <el-table-column label="鎬у埆" prop="gender" align="center" />
+          <el-table-column label="骞撮緞" prop="age" align="center" />
+          <el-table-column label="琛�鍨�" prop="bloodType" align="center" />
+          <el-table-column label="鍦板尯" prop="region" align="center" />
+          <el-table-column label="鎹愮尞鏃堕棿" prop="donationTime" align="center" />
+        </el-table>
 
-      <el-pagination
-        v-show="total > 0"
-        :total="total"
-        :page.sync="query.page"
-        :limit.sync="query.limit"
-        @current-change="handlePagination"
-        @size-change="handlePageSizeChange"
-        style="margin-top: 20px;"
-        layout="total, sizes, prev, pager, next, jumper"
-        :page-sizes="[10, 20, 50, 100]"
-      />
+        <el-pagination
+          v-show="total > 0"
+          :total="total"
+          :page.sync="query.page"
+          :limit.sync="query.limit"
+          @current-change="handlePagination"
+          @size-change="handlePageSizeChange"
+          style="margin-top: 20px;"
+          layout="total, sizes, prev, pager, next, jumper"
+          :page-sizes="[10, 20, 50, 100]"
+        />
+      </template>
+      <el-empty v-else description="鍚庣鏆傛湭鎻愪緵鎹愮尞鑰呰缁嗕俊鎭帴鍙�"></el-empty>
     </el-card>
   </div>
 </template>
 
 <script>
-// 淇瀵煎叆璺緞
 import FilterPanel from '@/components/charts/FilterPanel.vue';
 import EChartsWrapper from '@/components/charts/EChartsWrapper.vue';
+import { caseSex, caseBloodType, caseAvgAge } from '@/api/businessApi';
+import { toTotalsList, toNum } from '@/utils/totals';
+import statisticsMode, { isStatisticsMock } from '@/utils/statisticsMode';
+import { COLORS, PALETTE, axisTooltip, grid, legend } from '@/utils/chartStyle';
+
+const AGE_GROUP_MAP = {
+  '1': '<17宀�',
+  '2': '18-49宀�',
+  '3': '50-69宀�',
+  '4': '>69宀�'
+};
 
 export default {
   name: 'DonorStatistics',
@@ -187,13 +174,13 @@
           type: 'select',
           multiple: true,
           options: [
-            { 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: '涓存矀鍦板尯' }
           ],
           width: '300px'
         },
@@ -206,19 +193,6 @@
           endPlaceholder: '缁撴潫鏈堜唤',
           valueFormat: 'yyyy-MM',
           width: '300px'
-        },
-        {
-          label: '骞撮緞娈�',
-          prop: 'ageRange',
-          type: 'select',
-          multiple: true,
-          options: [
-            { label: '<17宀�', value: '0-17' },
-            { label: '18-49宀�', value: '18-49' },
-            { label: '50-69宀�', value: '50-69' },
-            { label: '>69宀�', value: '70-100' }
-          ],
-          width: '200px'
         },
         {
           label: '鎬у埆',
@@ -258,115 +232,109 @@
       // 鍥捐〃閰嶇疆
       ageChartOptions: {},
       genderBloodChartOptions: {},
-      categoryChartOptions: {},
-      deathChartOptions: {},
+
+      // 鎺ュ彛杩斿洖鏁版嵁
+      sexData: [],
+      bloodData: [],
+
+      // 鏃犳帴鍙e浘琛ㄧ殑妯℃嫙鏁版嵁锛堜粎 mock 妯″紡灞曠ず锛�
+      chinaCategoryData: [
+        { name: '涓浗涓�绫�(DBD)', value: 45 },
+        { name: '涓浗浜岀被(DCD)', value: 35 },
+        { name: '涓浗涓夌被(DBCD)', value: 20 }
+      ],
+      deathCauseData: [
+        { name: '鑴戣绠$柧鐥�', value: 40 },
+        { name: '棰呰剳鎹熶激', value: 30 },
+        { name: '蹇冭剰鐤剧梾', value: 15 },
+        { name: '鍏朵粬鍘熷洜', value: 15 }
+      ],
+
+      // 鎹愮尞鑰呰缁嗕俊鎭ā鎷熸暟鎹紙鏃犲悗绔帴鍙o紝浠� mock 妯″紡灞曠ず锛�
+      donorList: [
+        { donorNo: 'D2024001', name: '寮犱紵', gender: '鐢�', age: 45, bloodType: 'A', region: '闈掑矝鍦板尯', donationTime: '2024-03-15' },
+        { donorNo: 'D2024002', name: '鐜嬭姵', gender: '濂�', age: 38, bloodType: 'B', region: '闈掑矝鍦板尯', donationTime: '2024-03-22' },
+        { donorNo: 'D2024003', name: '鏉庡', gender: '濂�', age: 52, bloodType: 'O', region: '鏃ョ収鍦板尯', donationTime: '2024-04-02' },
+        { donorNo: 'D2024004', name: '鍒樻磱', gender: '鐢�', age: 29, bloodType: 'AB', region: '闈掑矝鍦板尯', donationTime: '2024-04-18' },
+        { donorNo: 'D2024005', name: '闄堥潤', gender: '濂�', age: 47, bloodType: 'A', region: '娴庡崡鍦板尯', donationTime: '2024-05-06' },
+        { donorNo: 'D2024006', name: '鏉ㄥ己', gender: '鐢�', age: 33, bloodType: 'B', region: '鐑熷彴鍦板尯', donationTime: '2024-05-21' },
+        { donorNo: 'D2024007', name: '璧垫晱', gender: '濂�', age: 41, bloodType: 'O', region: '闈掑矝鍦板尯', donationTime: '2024-06-03' },
+        { donorNo: 'D2024008', name: '榛勭', gender: '鐢�', age: 55, bloodType: 'A', region: '娼嶅潑鍦板尯', donationTime: '2024-06-17' },
+        { donorNo: 'D2024009', name: '鍛ㄦ澃', gender: '鐢�', age: 62, bloodType: 'O', region: '涓存矀鍦板尯', donationTime: '2024-07-01' },
+        { donorNo: 'D2024010', name: '鍚撮湠', gender: '濂�', age: 26, bloodType: 'AB', region: '鏃ョ収鍦板尯', donationTime: '2024-07-15' },
+        { donorNo: 'D2024011', name: '寰愮', gender: '鐢�', age: 48, bloodType: 'B', region: '闈掑矝鍦板尯', donationTime: '2024-08-09' },
+        { donorNo: 'D2024012', name: '瀛欎附', gender: '濂�', age: 35, bloodType: 'O', region: '濞佹捣鍦板尯', donationTime: '2024-08-24' },
+        { donorNo: 'D2024013', name: '椹秴', gender: '鐢�', age: 44, bloodType: 'A', region: '娴庡崡鍦板尯', donationTime: '2024-09-05' },
+        { donorNo: 'D2024014', name: '鏈卞┓', gender: '濂�', age: 58, bloodType: 'B', region: '闈掑矝鍦板尯', donationTime: '2024-09-19' },
+        { donorNo: 'D2024015', name: '鑳″啗', gender: '鐢�', age: 31, bloodType: 'O', region: '鐑熷彴鍦板尯', donationTime: '2024-10-08' },
+        { donorNo: 'D2024016', name: '閮稕', gender: '鐢�', age: 68, bloodType: 'A', region: '闈掑矝鍦板尯', donationTime: '2024-10-22' },
+        { donorNo: 'D2024017', name: '浣曞钩', gender: '濂�', age: 39, bloodType: 'AB', region: '娼嶅潑鍦板尯', donationTime: '2024-11-06' },
+        { donorNo: 'D2024018', name: '楂樼繑', gender: '鐢�', age: 50, bloodType: 'B', region: '涓存矀鍦板尯', donationTime: '2024-11-20' },
+        { donorNo: 'D2024019', name: '鏋楄姵', gender: '濂�', age: 27, bloodType: 'O', region: '闈掑矝鍦板尯', donationTime: '2024-12-04' },
+        { donorNo: 'D2024020', name: '缃楀垰', gender: '鐢�', age: 42, bloodType: 'A', region: '鏃ョ収鍦板尯', donationTime: '2024-12-18' },
+        { donorNo: 'D2023021', name: '閮戠埥', gender: '濂�', age: 34, bloodType: 'B', region: '闈掑矝鍦板尯', donationTime: '2023-06-10' },
+        { donorNo: 'D2023022', name: '姊佸畤', gender: '鐢�', age: 57, bloodType: 'O', region: '娴庡崡鍦板尯', donationTime: '2023-07-25' },
+        { donorNo: 'D2023023', name: '璋㈠', gender: '濂�', age: 49, bloodType: 'A', region: '濞佹捣鍦板尯', donationTime: '2023-08-14' },
+        { donorNo: 'D2023024', name: '闊╁啺', gender: '鐢�', age: 23, bloodType: 'AB', region: '闈掑矝鍦板尯', donationTime: '2023-09-30' }
+      ],
 
       // 琛ㄦ牸鏁版嵁
       tableData: [],
-      total: 0,
-
-      // 妯℃嫙鏁版嵁
-      mockData: {
-        table: [
-          { id: 1, donorNo: 'D2024001', name: '寮犱笁', gender: '鐢�', age: 45, bloodType: 'A', region: '闈掑矝鍦板尯', donationTime: '2024-01-15', chinaCategory: '涓浗涓�绫�(DBD)', deathCause: '鑴戝浼�', organs: ['鍏ㄨ倽', '宸﹁偩', '鍙宠偩'] },
-          { id: 2, donorNo: 'D2024002', name: '鏉庡洓', gender: '濂�', age: 32, bloodType: 'B', region: '闈掑矝鍦板尯', donationTime: '2024-01-20', chinaCategory: '涓浗浜岀被(DCD)', deathCause: '蹇冩悘楠ゅ仠', organs: ['蹇冭剰', '鐪艰鑶�'] },
-          { id: 3, donorNo: 'D2024003', name: '鐜嬩簲', gender: '鐢�', age: 58, bloodType: 'O', region: '鏃ョ収鍦板尯', donationTime: '2024-02-10', chinaCategory: '涓浗涓夌被(DBCD)', deathCause: '鑴戣绠℃剰澶�', organs: ['鍏ㄨ倽', '鑳拌吅'] },
-          { id: 4, donorNo: 'D2024004', name: '璧靛叚', gender: '鐢�', age: 28, bloodType: 'AB', region: '娴庡崡鍦板尯', donationTime: '2024-02-15', chinaCategory: '涓浗涓�绫�(DBD)', deathCause: '浜ら�氫簨鏁�', organs: ['鍏ㄨ偤', '宸﹁偩', '鍙宠偩'] },
-          { id: 5, donorNo: 'D2024005', name: '閽变竷', gender: '濂�', age: 65, bloodType: 'A', region: '鐑熷彴鍦板尯', donationTime: '2024-02-20', chinaCategory: '涓浗浜岀被(DCD)', deathCause: '蹇冭倢姊楁', organs: ['蹇冭剰'] },
-          { id: 6, donorNo: 'D2024006', name: '瀛欏叓', gender: '鐢�', age: 42, bloodType: 'B', region: '濞佹捣鍦板尯', donationTime: '2024-03-05', chinaCategory: '涓浗涓�绫�(DBD)', deathCause: '鑴戣偪鐦�', organs: ['宸﹁偩', '鍙宠偩', '鐪艰鑶�'] },
-          { id: 7, donorNo: 'D2024007', name: '鍛ㄤ節', gender: '鐢�', age: 37, bloodType: 'O', region: '娼嶅潑鍦板尯', donationTime: '2024-03-10', chinaCategory: '涓浗涓夌被(DBCD)', deathCause: '鑴戝浼�', organs: ['鑲濊剰', '鑳拌吅', '鐪艰鑶�'] },
-          { id: 8, donorNo: 'D2024008', name: '鍚村崄', gender: '濂�', age: 25, bloodType: 'AB', region: '涓存矀鍦板尯', donationTime: '2024-03-15', chinaCategory: '涓浗浜岀被(DCD)', deathCause: '浜ら�氫簨鏁�', organs: ['蹇冭剰', '鑲�'] }
-        ],
-        age: [
-          { ageRange: '<17宀�', male: 5, female: 3 },
-          { ageRange: '18-49宀�', male: 45, female: 30 },
-          { ageRange: '50-69宀�', male: 35, female: 25 },
-          { ageRange: '>69宀�', male: 10, female: 8 }
-        ],
-        gender: [
-          { name: '鐢锋��', value: 95 },
-          { name: '濂虫��', value: 66 }
-        ],
-        blood: [
-          { name: 'A鍨�', value: 45 },
-          { name: 'B鍨�', value: 38 },
-          { name: 'O鍨�', value: 52 },
-          { name: 'AB鍨�', value: 26 }
-        ],
-        category: [
-          { name: '涓浗涓�绫�(DBD)', value: 85 },
-          { name: '涓浗浜岀被(DCD)', value: 55 },
-          { name: '涓浗涓夌被(DBCD)', value: 21 }
-        ],
-        death: [
-          { name: '鑴戝浼�', value: 45 },
-          { name: '蹇冩悘楠ゅ仠', value: 35 },
-          { name: '鑴戣绠℃剰澶�', value: 35 },
-          { name: '浜ら�氫簨鏁�', value: 20 },
-          { name: '蹇冭倢姊楁', value: 25 },
-          { name: '鍏朵粬', value: 15 }
-        ]
-      }
+      total: 0
     };
+  },
+  computed: {
+    useMock() {
+      return isStatisticsMock();
+    },
+    chinaCategoryOptions() {
+      return this.buildPieOptions('涓浗鍒嗙被', this.chinaCategoryData);
+    },
+    deathCauseOptions() {
+      return this.buildPieOptions('姝讳骸鍘熷洜', this.deathCauseData);
+    }
   },
   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];
+      }
+      if (this.query.sex) q.sex = this.query.sex;
+      if (this.query.bloodType) q.boodType = this.query.bloodType;
+      return q;
+    },
+
     // 鍔犺浇鏁版嵁
     async loadData() {
       this.loading = true;
       try {
-        await new Promise(resolve => setTimeout(resolve, 500));
+        const q = this.buildQuery();
+        const [sexRes, bloodRes, avgRes] = await Promise.all([
+          caseSex(q),
+          caseBloodType(q),
+          caseAvgAge(q)
+        ]);
 
-        // 澶勭悊琛ㄦ牸鏁版嵁
-        let filteredData = [...this.mockData.table];
+        this.sexData = toTotalsList(sexRes);
+        this.bloodData = toTotalsList(bloodRes);
+        const avgList = toTotalsList(avgRes);
 
-        if (this.query.region.length > 0) {
-          filteredData = filteredData.filter(item =>
-            this.query.region.includes(item.region.replace('鍦板尯', '').toLowerCase())
-          );
-        }
-
-        if (this.query.monthRange && this.query.monthRange.length === 2) {
-          const [start, end] = this.query.monthRange;
-          filteredData = filteredData.filter(item =>
-            item.donationTime >= start && item.donationTime <= end
-          );
-        }
-
-        if (this.query.ageRange.length > 0) {
-          filteredData = filteredData.filter(item => {
-            const age = item.age;
-            return this.query.ageRange.some(range => {
-              if (range === '0-17') return age < 18;
-              if (range === '18-49') return age >= 18 && age <= 49;
-              if (range === '50-69') return age >= 50 && age <= 69;
-              if (range === '70-100') return age >= 70;
-              return false;
-            });
-          });
-        }
-
-        if (this.query.gender) {
-          filteredData = filteredData.filter(item => item.gender === this.query.gender);
-        }
-
-        if (this.query.bloodType) {
-          filteredData = filteredData.filter(item => item.bloodType === this.query.bloodType);
-        }
-
-        // 鍒嗛〉
-        const start = (this.query.page - 1) * this.query.limit;
-        this.tableData = filteredData.slice(start, start + this.query.limit);
-        this.total = filteredData.length;
-
-        // 鏇存柊姒傝鏁版嵁
-        this.updateOverviewData(filteredData);
-
-        // 鏇存柊鍥捐〃
+        this.updateOverviewData(avgList);
         this.updateCharts();
 
+        // 鎹愮尞鑰呰缁嗕俊鎭棤 /opo/totals 鑱氬悎鎺ュ彛锛屼娇鐢ㄦā鎷熸暟鎹ˉ鍏�
+        this.updateDonorTable();
       } catch (error) {
         console.error('鍔犺浇鏁版嵁澶辫触:', error);
         this.$message.error('鏁版嵁鍔犺浇澶辫触');
@@ -376,220 +344,164 @@
     },
 
     // 鏇存柊姒傝鏁版嵁
-    updateOverviewData(data) {
-      if (data.length === 0) return;
-
-      const total = data.length;
-      const males = data.filter(item => item.gender === '鐢�').length;
-      const females = data.filter(item => item.gender === '濂�').length;
-      const avgAge = data.reduce((sum, item) => sum + item.age, 0) / total;
+    updateOverviewData(avgList) {
+      const males = this.sexData.reduce((sum, item) => sum + toNum(item.manCount), 0);
+      const females = this.sexData.reduce((sum, item) => sum + toNum(item.womanCount), 0);
+      const total = males + females;
+      const avgAge = avgList.length
+        ? toNum(avgList.reduce((sum, item) => sum + toNum(item.avgAge), 0) / avgList.length)
+        : 0;
 
       this.overviewData[0].value = total;
-      this.overviewData[1].value = `${((males / total) * 100).toFixed(1)}%`;
-      this.overviewData[2].value = `${((females / total) * 100).toFixed(1)}%`;
+      this.overviewData[1].value = total > 0 ? `${((males / total) * 100).toFixed(1)}%` : '0%';
+      this.overviewData[2].value = total > 0 ? `${((females / total) * 100).toFixed(1)}%` : '0%';
       this.overviewData[3].value = avgAge.toFixed(1);
     },
 
     // 鏇存柊鍥捐〃
     updateCharts() {
-      // 骞撮緞鍒嗗竷鍥�
+      // 骞撮緞鍒嗗竷鍥撅紙宸︾敺鍙冲コ锛屾竻鐖藉彂鏁i噾瀛楀锛�
       this.ageChartOptions = {
-        tooltip: {
-          trigger: 'axis',
-          axisPointer: {
-            type: 'shadow'
-          }
+        tooltip: axisTooltip(),
+        legend: legend(['鐢锋��', '濂虫��']),
+        grid: grid(),
+        xAxis: {
+          type: 'value',
+          axisLine: { show: false },
+          axisTick: { show: false },
+          axisLabel: { color: '#8a919f', fontSize: 12, formatter: value => Math.abs(value) },
+          splitLine: { lineStyle: { type: 'dashed', color: '#f0f1f5' } }
         },
-        legend: {
-          data: ['鐢锋��', '濂虫��']
+        yAxis: {
+          type: 'category',
+          data: this.sexData.map(item => AGE_GROUP_MAP[item.ageGroup] || item.ageGroup || '鏈煡'),
+          axisLine: { show: false },
+          axisTick: { show: false },
+          axisLabel: { color: '#606266', fontSize: 13 }
         },
-        grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
-          containLabel: true
-        },
-        xAxis: [
-          {
-            type: 'value',
-            position: 'bottom',
-            axisLabel: {
-              formatter: function(value) {
-                return Math.abs(value);
-              }
-            }
-          }
-        ],
-        yAxis: [
-          {
-            type: 'category',
-            axisTick: {
-              show: false
-            },
-            data: this.mockData.age.map(item => item.ageRange)
-          }
-        ],
         series: [
           {
             name: '鐢锋��',
             type: 'bar',
             stack: '鎬у埆',
-            data: this.mockData.age.map(item => item.male),
-            itemStyle: { color: '#409EFF' }
+            barMaxWidth: 18,
+            data: this.sexData.map(item => toNum(item.manCount)),
+            itemStyle: { color: COLORS.blue, borderRadius: [4, 0, 0, 4] }
           },
           {
             name: '濂虫��',
             type: 'bar',
             stack: '鎬у埆',
-            data: this.mockData.age.map(item => -item.female),
-            itemStyle: { color: '#F56C6C' }
+            barMaxWidth: 18,
+            data: this.sexData.map(item => -toNum(item.womanCount)),
+            itemStyle: { color: COLORS.coral, borderRadius: [0, 4, 4, 0] }
           }
         ]
       };
 
-      // 鎬у埆涓庤鍨嬬粍鍚堝浘
+      // 鎬у埆涓庤鍨嬪垎甯冨浘锛堝悓蹇冪幆锛岄厤鑹叉竻鐖斤級
+      const males = this.bloodData.reduce((sum, item) => sum + toNum(item.manCount), 0);
+      const females = this.bloodData.reduce((sum, item) => sum + toNum(item.womanCount), 0);
+      const bloodPie = this.bloodData.map(item => ({
+        name: item.boodType || '鏈煡',
+        value: toNum(item.boodTypeCount) || (toNum(item.manCount) + toNum(item.womanCount))
+      }));
+
       this.genderBloodChartOptions = {
         tooltip: {
           trigger: 'item',
           formatter: '{a} <br/>{b} : {c} ({d}%)'
         },
-        legend: {
-          data: [
-            '鐢锋��', '濂虫��',
-            'A鍨�', 'B鍨�', 'O鍨�', 'AB鍨�'
-          ],
-          top: 20
-        },
-        grid: {
-          top: '20%',
-          bottom: '20%',
-          left: '10%',
-          right: '10%'
-        },
+        legend: { data: ['鐢锋��', '濂虫��', ...this.bloodData.map(item => item.boodType || '鏈煡')], top: 20 },
         series: [
           {
             name: '鎬у埆鍒嗗竷',
             type: 'pie',
-            radius: [0, '30%'],
+            radius: [0, '40%'],
             center: ['25%', '50%'],
-            label: {
-              position: 'inner',
-              fontSize: 12
-            },
-            data: this.mockData.gender
+            label: { position: 'inner', fontSize: 12 },
+            data: [
+              { name: '鐢锋��', value: males },
+              { name: '濂虫��', value: females }
+            ],
+            color: [COLORS.blue, COLORS.coral]
           },
           {
             name: '琛�鍨嬪垎甯�',
             type: 'pie',
-            radius: ['40%', '55%'],
+            radius: ['55%', '75%'],
             center: ['25%', '50%'],
-            data: this.mockData.blood
-          },
-          {
-            name: '鎬у埆琛�鍨嬪姣�',
-            type: 'pie',
-            radius: [0, '30%'],
-            center: ['75%', '50%'],
-            label: {
-              position: 'inner',
-              fontSize: 12
-            },
-            data: [
-              { name: '鐢�-A', value: 20 },
-              { name: '鐢�-B', value: 15 },
-              { name: '鐢�-O', value: 25 },
-              { name: '鐢�-AB', value: 10 },
-              { name: '濂�-A', value: 12 },
-              { name: '濂�-B', value: 8 },
-              { name: '濂�-O', value: 10 },
-              { name: '濂�-AB', value: 5 }
-            ]
+            label: { fontSize: 14, color: '#606266' },
+            data: bloodPie,
+            color: [COLORS.amber, COLORS.teal, COLORS.purple, COLORS.orange]
           }
         ]
       };
+    },
 
-      // 涓浗鍒嗙被鍒嗗竷鍥�
-      this.categoryChartOptions = {
+    // 鎹愮尞鑰呰缁嗕俊鎭紙鏃犳帴鍙o紝妯℃嫙鏁版嵁琛ュ叏 + 杩囨护鍒嗛〉锛�
+    updateDonorTable() {
+      let list = this.donorList;
+      if (this.query.region && this.query.region.length) {
+        list = list.filter(item => this.query.region.includes(item.region));
+      }
+      if (this.query.monthRange && this.query.monthRange.length === 2) {
+        const start = this.query.monthRange[0];
+        const end = this.query.monthRange[1];
+        list = list.filter(item => {
+          const m = item.donationTime.slice(0, 7);
+          return m >= start && m <= end;
+        });
+      }
+      if (this.query.gender) {
+        list = list.filter(item => item.gender === this.query.gender);
+      }
+      if (this.query.bloodType) {
+        list = list.filter(item => item.bloodType === this.query.bloodType);
+      }
+      this.total = list.length;
+      const start = (this.query.page - 1) * this.query.limit;
+      this.tableData = list.slice(start, start + this.query.limit);
+    },
+
+    // 鐢熸垚楗煎浘閰嶇疆锛堟棤鎺ュ彛鍥捐〃鐨� mock 灞曠ず锛�
+    buildPieOptions(name, data) {
+      return {
+        color: PALETTE,
         tooltip: {
           trigger: 'item',
           formatter: '{a} <br/>{b} : {c} ({d}%)'
         },
         legend: {
           orient: 'vertical',
-          right: 10,
-          top: 'center',
-          data: this.mockData.category.map(item => item.name)
+          left: 'left',
+          top: 'middle',
+          itemWidth: 10,
+          itemHeight: 10,
+          itemGap: 16,
+          textStyle: { fontSize: 14, color: '#606266' },
+          data: data.map(item => item.name)
         },
         series: [
           {
-            name: '涓浗鍒嗙被',
+            name,
             type: 'pie',
-            radius: ['30%', '70%'],
-            center: ['40%', '50%'],
-            roseType: 'radius',
-            label: {
-              show: true
-            },
+            radius: '55%',
+            center: ['55%', '50%'],
+            itemStyle: { borderColor: '#fff', borderWidth: 2 },
+            label: { fontSize: 15, color: '#606266', formatter: '{b} {d}%' },
+            labelLine: { lineStyle: { color: '#c0c4cc' } },
+            data,
             emphasis: {
-              label: {
-                show: true
+              itemStyle: {
+                shadowBlur: 10,
+                shadowOffsetX: 0,
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
               }
-            },
-            data: this.mockData.category
+            }
           }
         ]
       };
-
-      // 姝讳骸鍘熷洜鍒嗘瀽鍥�
-      this.deathChartOptions = {
-        tooltip: {
-          trigger: 'item',
-          formatter: '{b}: {c} ({d}%)'
-        },
-        legend: {
-          orient: 'vertical',
-          left: 10,
-          top: 'center',
-          data: this.mockData.death.map(item => item.name)
-        },
-        series: [
-          {
-            name: '姝讳骸鍘熷洜',
-            type: 'pie',
-            radius: ['30%', '60%'],
-            center: ['60%', '50%'],
-            avoidLabelOverlap: false,
-            label: {
-              show: true,
-              formatter: '{b|{b}}\n{c|{c} ({d}%)}',
-              rich: {
-                b: {
-                  fontSize: 12,
-                  lineHeight: 20
-                },
-                c: {
-                  fontSize: 10,
-                  color: '#999'
-                }
-              }
-            },
-            emphasis: {
-              label: {
-                show: true,
-                fontSize: '16',
-                fontWeight: 'bold'
-              }
-            },
-            data: this.mockData.death
-          }
-        ]
-      };
-    },
-
-    // 鑾峰彇琛�鍨嬫爣绛剧被鍨�
-    getBloodTypeTag(type) {
-      const map = { 'A': 'danger', 'B': 'primary', 'O': 'success', 'AB': 'warning' };
-      return map[type] || 'info';
     },
 
     // 澶勭悊鏌ヨ
@@ -628,14 +540,6 @@
     // 澶勭悊瀵煎嚭
     handleExport() {
       this.$message.info('瀵煎嚭鍔熻兘寮�鍙戜腑...');
-    },
-
-    // 鏌ョ湅鎹愮尞鑰呰鎯�
-    viewDonorDetail(row) {
-      this.$router.push({
-        path: '/donor/detail',
-        query: { id: row.id }
-      });
     }
   }
 };

--
Gitblit v1.9.3