Explorar o código

feat(dashboard): 更新首页仪表板展示实时统计数据

- 更改供应商图标为办公楼图标,地址图标为商品图标,采购订单图标为时钟图标
- 实现供应商总数、商品总数、采购订单数和待审核商品数的动态加载功能
- 添加骨架屏动画提升用户体验,避免数据加载时的空白显示
- 集成供应商、商品和订单API接口实现真实数据展示
- 将采购订单趋势图表替换为订单状态分布柱状图
- 更新供应商企业类型分布图为商品状态分布饼图
- 添加数据格式化功能支持千分位分隔符显示
- 优化图表响应式布局确保窗口大小调整时正确重绘
肖路 hai 1 día
pai
achega
013367ec1b
Modificáronse 1 ficheiros con 227 adicións e 130 borrados
  1. 227 130
      src/views/index.vue

+ 227 - 130
src/views/index.vue

@@ -4,22 +4,28 @@
       <el-col :span="6" class="card-panel-col">
         <el-card shadow="hover" class="box-card">
           <div class="card-panel-icon-wrapper icon-blue">
-            <el-icon :size="45"><User /></el-icon>
+            <el-icon :size="45"><OfficeBuilding /></el-icon>
           </div>
           <div class="card-panel-description">
             <div class="card-panel-text">供应商总数</div>
-            <div class="card-panel-num">1,245</div>
+            <div class="card-panel-num">
+              <span v-if="!loadingSupplier">{{ formatNum(supplierTotal) }}</span>
+              <el-skeleton v-else animated :rows="1" style="width: 80px;" />
+            </div>
           </div>
         </el-card>
       </el-col>
       <el-col :span="6" class="card-panel-col">
         <el-card shadow="hover" class="box-card">
           <div class="card-panel-icon-wrapper icon-green">
-            <el-icon :size="45"><Location /></el-icon>
+            <el-icon :size="45"><Goods /></el-icon>
           </div>
           <div class="card-panel-description">
-            <div class="card-panel-text">地址总数</div>
-            <div class="card-panel-num">3,892</div>
+            <div class="card-panel-text">商品总数</div>
+            <div class="card-panel-num">
+              <span v-if="!loadingProduct">{{ formatNum(productTotal) }}</span>
+              <el-skeleton v-else animated :rows="1" style="width: 80px;" />
+            </div>
           </div>
         </el-card>
       </el-col>
@@ -30,18 +36,24 @@
           </div>
           <div class="card-panel-description">
             <div class="card-panel-text">采购订单</div>
-            <div class="card-panel-num">8,460</div>
+            <div class="card-panel-num">
+              <span v-if="!loadingOrder">{{ formatNum(orderTotal) }}</span>
+              <el-skeleton v-else animated :rows="1" style="width: 80px;" />
+            </div>
           </div>
         </el-card>
       </el-col>
       <el-col :span="6" class="card-panel-col">
         <el-card shadow="hover" class="box-card">
           <div class="card-panel-icon-wrapper icon-red">
-            <el-icon :size="45"><WarnTriangleFilled /></el-icon>
+            <el-icon :size="45"><Clock /></el-icon>
           </div>
           <div class="card-panel-description">
-            <div class="card-panel-text">售后退货单</div>
-            <div class="card-panel-num">125</div>
+            <div class="card-panel-text">待审核商品</div>
+            <div class="card-panel-num">
+              <span v-if="!loadingProduct">{{ formatNum(waitAuditCount) }}</span>
+              <el-skeleton v-else animated :rows="1" style="width: 80px;" />
+            </div>
           </div>
         </el-card>
       </el-col>
@@ -49,20 +61,20 @@
 
     <el-row :gutter="20" style="margin-top: 20px;">
       <el-col :span="16">
-        <el-card shadow="hover">
+        <el-card shadow="hover" v-loading="loadingOrderStats">
           <template #header>
             <div class="clearfix">
-              <span>采购订单趋势 (2025-2026)</span>
+              <span>订单状态分布</span>
             </div>
           </template>
-          <div ref="lineChartRef" style="height: 350px; width: 100%;"></div>
+          <div ref="barChartRef" style="height: 350px; width: 100%;"></div>
         </el-card>
       </el-col>
       <el-col :span="8">
-        <el-card shadow="hover">
+        <el-card shadow="hover" v-loading="loadingProduct">
           <template #header>
             <div class="clearfix">
-              <span>供应商企业类型分布</span>
+              <span>商品状态分布</span>
             </div>
           </template>
           <div ref="pieChartRef" style="height: 350px; width: 100%;"></div>
@@ -75,146 +87,227 @@
 <script setup name="Index" lang="ts">
 import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
 import * as echarts from 'echarts';
-import { User, Location, Document, WarnTriangleFilled } from '@element-plus/icons-vue';
+import { OfficeBuilding, Goods, Document, Clock } from '@element-plus/icons-vue';
+import { listInfo } from '@/api/customer/supplierInfo';
+import { getProductStatusCount } from '@/api/product/base';
+import { listOrderMain, queryOrderStatusStats } from '@/api/order/orderMain';
 
-const lineChartRef = ref<HTMLElement | null>(null);
+const barChartRef = ref<HTMLElement | null>(null);
 const pieChartRef = ref<HTMLElement | null>(null);
 
-let lineChart: echarts.ECharts | null = null;
+let barChart: echarts.ECharts | null = null;
 let pieChart: echarts.ECharts | null = null;
 
-const initLineChart = () => {
-  if (lineChartRef.value) {
-    lineChart = echarts.init(lineChartRef.value);
-    const option = {
-      tooltip: {
-        trigger: 'axis'
-      },
-      legend: {
-        data: ['采购订单量', '售后退货量']
-      },
-      grid: {
-        left: '3%',
-        right: '4%',
-        bottom: '3%',
-        containLabel: true
-      },
-      xAxis: {
-        type: 'category',
-        boundaryGap: false,
-        data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
-      },
-      yAxis: {
-        type: 'value'
-      },
-      series: [
-        {
-          name: '采购订单量',
-          type: 'line',
-          smooth: true,
-          itemStyle: { color: '#409EFF' },
-          areaStyle: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              { offset: 0, color: 'rgba(64,158,255,0.5)' },
-              { offset: 1, color: 'rgba(64,158,255,0.1)' }
-            ])
-          },
-          data: [120, 180, 250, 230, 300, 450, 420, 500, 600, 550, 650, 800]
+// 统计数据
+const supplierTotal = ref(0);
+const productTotal = ref(0);
+const orderTotal = ref(0);
+const waitAuditCount = ref(0);
+
+// 加载状态
+const loadingSupplier = ref(true);
+const loadingProduct = ref(true);
+const loadingOrder = ref(true);
+const loadingOrderStats = ref(true);
+
+// 格式化数字
+const formatNum = (num: number): string => {
+  if (num === 0) return '0';
+  return num.toLocaleString('zh-CN');
+};
+
+/** 获取供应商总数 */
+const fetchSupplierTotal = async () => {
+  try {
+    const res: any = await listInfo({ pageNum: 1, pageSize: 1 });
+    supplierTotal.value = res.total || 0;
+  } catch (error) {
+    console.error('获取供应商总数失败:', error);
+  } finally {
+    loadingSupplier.value = false;
+  }
+};
+
+/** 获取商品统计数据 */
+const fetchProductStats = async () => {
+  try {
+    const res: any = await getProductStatusCount();
+    if (res.data) {
+      productTotal.value = res.data.total || 0;
+      waitAuditCount.value = res.data.waitAudit || 0;
+      // 初始化饼图
+      initPieChart(res.data);
+    }
+  } catch (error) {
+    console.error('获取商品统计失败:', error);
+  } finally {
+    loadingProduct.value = false;
+  }
+};
+
+/** 获取订单总数 */
+const fetchOrderTotal = async () => {
+  try {
+    const res: any = await listOrderMain({ pageNum: 1, pageSize: 1 });
+    orderTotal.value = res.total || 0;
+  } catch (error) {
+    console.error('获取订单总数失败:', error);
+  } finally {
+    loadingOrder.value = false;
+  }
+};
+
+/** 获取订单状态分布 */
+const fetchOrderStats = async () => {
+  try {
+    const res: any = await queryOrderStatusStats();
+    initBarChart(res);
+  } catch (error) {
+    console.error('获取订单状态统计失败:', error);
+  } finally {
+    loadingOrderStats.value = false;
+  }
+};
+
+/** 初始化订单状态柱状图 */
+const initBarChart = (data: any) => {
+  if (!barChartRef.value) return;
+  barChart = echarts.init(barChartRef.value);
+  const categories = ['待付款', '待发货', '已发货', '已完成', '已关闭'];
+  const values = [
+    data?.pendingPaymentCount || 0,
+    data?.pendingShipmentCount || 0,
+    data?.shippedCount || 0,
+    data?.completedCount || 0,
+    data?.closedCount || 0
+  ];
+  const option: echarts.EChartsOption = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' }
+    },
+    legend: {
+      data: ['订单数量']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: categories,
+      axisLabel: { fontSize: 12 }
+    },
+    yAxis: {
+      type: 'value',
+      minInterval: 1
+    },
+    series: [
+      {
+        name: '订单数量',
+        type: 'bar',
+        barWidth: '50%',
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#409EFF' },
+            { offset: 1, color: '#79bbff' }
+          ]),
+          borderRadius: [6, 6, 0, 0]
         },
-        {
-          name: '售后退货量',
-          type: 'line',
-          smooth: true,
-          itemStyle: { color: '#F56C6C' },
-          areaStyle: {
+        emphasis: {
+          itemStyle: {
             color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              { offset: 0, color: 'rgba(245,108,108,0.5)' },
-              { offset: 1, color: 'rgba(245,108,108,0.1)' }
+              { offset: 0, color: '#337ecc' },
+              { offset: 1, color: '#5ba5e8' }
             ])
-          },
-          data: [5, 12, 11, 15, 10, 22, 18, 25, 30, 28, 35, 40]
-        }
-      ]
-    };
-    lineChart.setOption(option);
-  }
+          }
+        },
+        label: {
+          show: true,
+          position: 'top',
+          fontSize: 12,
+          color: '#333'
+        },
+        data: values
+      }
+    ]
+  };
+  barChart.setOption(option);
 };
 
-const initPieChart = () => {
-  if (pieChartRef.value) {
-    pieChart = echarts.init(pieChartRef.value);
-    const option = {
-      tooltip: {
-        trigger: 'item'
-      },
-      legend: {
-        bottom: '0%',
-        left: 'center'
-      },
-      series: [
-        {
-          name: '供应商类型',
-          type: 'pie',
-          radius: ['40%', '70%'],
-          avoidLabelOverlap: false,
-          itemStyle: {
-            borderRadius: 10,
-            borderColor: '#fff',
-            borderWidth: 2
-          },
+/** 初始化商品状态饼图 */
+const initPieChart = (data: any) => {
+  if (!pieChartRef.value) return;
+  pieChart = echarts.init(pieChartRef.value);
+  const option: echarts.EChartsOption = {
+    tooltip: {
+      trigger: 'item',
+      formatter: '{b}: {c} ({d}%)'
+    },
+    legend: {
+      bottom: '0%',
+      left: 'center'
+    },
+    series: [
+      {
+        name: '商品状态',
+        type: 'pie',
+        radius: ['40%', '70%'],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          borderRadius: 10,
+          borderColor: '#fff',
+          borderWidth: 2
+        },
+        label: {
+          show: false,
+          position: 'center'
+        },
+        emphasis: {
           label: {
-            show: false,
-            position: 'center'
-          },
-          emphasis: {
-            label: {
-              show: true,
-              fontSize: 20,
-              fontWeight: 'bold'
-            }
-          },
-          labelLine: {
-            show: false
-          },
-          data: [
-            { value: 1048, name: '国有企业' },
-            { value: 735, name: '民营企业' },
-            { value: 580, name: '外资企业' },
-            { value: 484, name: '合资企业' },
-            { value: 300, name: '个体工商户' }
-          ]
-        }
-      ]
-    };
-    pieChart.setOption(option);
-  }
+            show: true,
+            fontSize: 18,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: [
+          { value: data?.onSale || 0, name: '已上架', itemStyle: { color: '#67C23A' } },
+          { value: data?.offSale || 0, name: '已下架', itemStyle: { color: '#909399' } },
+          { value: data?.waitAudit || 0, name: '待审核', itemStyle: { color: '#E6A23C' } },
+          { value: data?.auditPass || 0, name: '审核通过', itemStyle: { color: '#409EFF' } },
+          { value: data?.auditReject || 0, name: '审核驳回', itemStyle: { color: '#F56C6C' } }
+        ]
+      }
+    ]
+  };
+  pieChart.setOption(option);
 };
 
 const handleResize = () => {
-  if (lineChart) {
-    lineChart.resize();
-  }
-  if (pieChart) {
-    pieChart.resize();
-  }
+  if (barChart) barChart.resize();
+  if (pieChart) pieChart.resize();
 };
 
 onMounted(() => {
   nextTick(() => {
-    initLineChart();
-    initPieChart();
     window.addEventListener('resize', handleResize);
   });
+  // 并行请求所有数据
+  fetchSupplierTotal();
+  fetchProductStats();
+  fetchOrderTotal();
+  fetchOrderStats();
 });
 
 onBeforeUnmount(() => {
   window.removeEventListener('resize', handleResize);
-  if (lineChart) {
-    lineChart.dispose();
-  }
-  if (pieChart) {
-    pieChart.dispose();
-  }
+  if (barChart) barChart.dispose();
+  if (pieChart) pieChart.dispose();
 });
 </script>
 
@@ -294,6 +387,10 @@ onBeforeUnmount(() => {
         .card-panel-num {
           font-size: 24px;
           color: #333;
+          min-height: 32px;
+          display: flex;
+          align-items: center;
+          justify-content: flex-end;
         }
       }
     }