浏览代码

feat(statistics): 添加业务统计数据展示功能

- 新增统计信息API接口调用
- 实现时间筛选功能(今日、近三天、近七天、本月)
- 添加自定义时间范围查询功能
- 创建统计卡片展示比赛场次、充值数、充值金额等数据
- 集成ECharts实现折线图数据可视化
- 添加图表数据加载和更新逻辑
- 实现页面初始化时自动加载统计数据
- 添加重置筛选条件功能
- 优化图表数据显示格式和样式- 增加数据验证和错误处理机制
fugui001 2 月之前
父节点
当前提交
59b7ce35c9
共有 2 个文件被更改,包括 341 次插入141 次删除
  1. 17 0
      src/api/system/business/statistics/index.ts
  2. 324 141
      src/views/index.vue

+ 17 - 0
src/api/system/business/statistics/index.ts

@@ -0,0 +1,17 @@
+import request from '@/utils/request';
+
+export const getStatisticsInfo = (params: any) => {
+  return request({
+    url: '/business/statistics/getStatisticsInfo',
+    method: 'get',
+    params
+  });
+};
+
+export const getStatisticsGraphInfo = (params: any) => {
+  return request({
+    url: '/business/statistics/getStatisticsGraphInfo',
+    method: 'get',
+    params
+  });
+};

+ 324 - 141
src/views/index.vue

@@ -1,165 +1,348 @@
 <template>
   <div class="app-container home">
-    <el-row :gutter="20">
-      <el-col :sm="24" :lg="12" style="padding-left: 20px">
-        <h2>RuoYi-Vue-Plus多租户管理系统</h2>
-        <p>
-          RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
-          <br />
-          * 前端开发框架 Vue3、TS、Element Plus<br />
-          * 后端开发框架 Spring Boot<br />
-          * 容器框架 Undertow 基于 Netty 的高性能容器<br />
-          * 权限认证框架 Sa-Token 支持多终端认证系统<br />
-          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
-          * 缓存数据库 Redis 适配 6.X 最低 4.X<br />
-          * 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
-          * 数据库框架 p6spy 更强劲的 SQL 分析<br />
-          * 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
-          * 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
-          * Redis客户端 Redisson 性能强劲、API丰富<br />
-          * 分布式限流 Redisson 全局、请求IP、集群ID 多种限流<br />
-          * 分布式锁 Lock4j 注解锁、工具锁 多种多样<br />
-          * 分布式幂等 Lock4j 基于分布式锁实现<br />
-          * 分布式链路追踪 SkyWalking 支持链路追踪、网格分析、度量聚合、可视化<br />
-          * 分布式任务调度 SnailJob 高性能 高可靠 易扩展<br />
-          * 文件存储 Minio 本地存储<br />
-          * 文件存储 七牛、阿里、腾讯 云存储<br />
-          * 监控框架 SpringBoot-Admin 全方位服务监控<br />
-          * 校验框架 Validation 增强接口安全性 严谨性<br />
-          * Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
-          * 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释<br />
-          * 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性<br />
-          * 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码<br />
-          * 部署方式 Docker 容器编排 一键部署业务集群<br />
-          * 国际化 SpringMessage Spring标准国际化方案<br />
-        </p>
-        <p><b>当前版本:</b> <span>v5.4.0</span></p>
-        <p>
-          <el-tag type="danger">&yen;免费开源</el-tag>
-        </p>
-        <p>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
-            >更新日志</el-button
-          >
-        </p>
-      </el-col>
-
-      <el-col :sm="24" :lg="12" style="padding-left: 20px">
-        <h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
-        <p>
-          RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
-          <br />
-          * 前端开发框架 Vue3、TS、Element UI<br />
-          * 后端开发框架 Spring Boot<br />
-          * 微服务开发框架 Spring Cloud、Spring Cloud Alibaba<br />
-          * 容器框架 Undertow 基于 XNIO 的高性能容器<br />
-          * 权限认证框架 Sa-Token、Jwt 支持多终端认证系统<br />
-          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
-          * 关系数据库 Oracle 适配 11g 12c<br />
-          * 关系数据库 PostgreSQL 适配 13 14<br />
-          * 关系数据库 SQLServer 适配 2017 2019<br />
-          * 缓存数据库 Redis 适配 6.X 最低 5.X<br />
-          * 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
-          * 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
-          * 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
-          * 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
-          * RPC远程调用 Apache Dubbo 原生态使用体验、高性能<br />
-          * 分布式限流熔断 Alibaba Sentinel 无侵入、高扩展<br />
-          * 分布式事务 Alibaba Seata 无侵入、高扩展 支持 四种模式<br />
-          * 分布式消息队列 Apache Kafka 高性能高速度<br />
-          * 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
-          * 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
-          * 分布式搜索引擎 ElasticSearch 业界知名<br />
-          * 分布式链路追踪 Apache SkyWalking 链路追踪、网格分析、度量聚合、可视化<br />
-          * 分布式日志中心 ELK 业界成熟解决方案<br />
-          * 分布式监控 Prometheus、Grafana 全方位性能监控<br />
-          * 其余与 Vue 版本一致<br />
-        </p>
-        <p><b>当前版本:</b> <span>v2.4.0</span></p>
-        <p>
-          <el-tag type="danger">&yen;免费开源</el-tag>
-        </p>
-        <p>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
-            >更新日志</el-button
-          >
-        </p>
-      </el-col>
-    </el-row>
-    <el-divider />
+    <!-- 时间筛选区域 -->
+    <!-- 时间筛选区域 -->
+    <div class="time-filter">
+      <el-button size="small" :type="timeFilter === 'day' ? 'primary' : 'default'" @click="handleTimeFilter('day')"> 今日 </el-button>
+      <el-button size="small" :type="timeFilter === 'three' ? 'primary' : 'default'" @click="handleTimeFilter('three')"> 近三天 </el-button>
+      <el-button size="small" :type="timeFilter === 'week' ? 'primary' : 'default'" @click="handleTimeFilter('week')"> 近七天 </el-button>
+      <el-button size="small" :type="timeFilter === 'month' ? 'primary' : 'default'" @click="handleTimeFilter('month')"> 本月 </el-button>
+
+      <span class="filter-label">选择时间:</span>
+      <el-date-picker v-model="startTime" type="datetime" placeholder="请选择开始时间" value-format="YYYY-MM-DD HH:mm:ss" style="width: 180px" />
+      <span>至</span>
+      <el-date-picker v-model="endTime" type="datetime" placeholder="请选择结束时间" value-format="YYYY-MM-DD HH:mm:ss" style="width: 180px" />
+      <el-button size="small" type="primary" @click="queryData">查询</el-button>
+      <el-button size="small" @click="resetFilter">重置</el-button>
+    </div>
+
+    <!-- 统计卡片区域 -->
+    <div class="statistic-cards">
+      <div class="stat-card">
+        <div class="card-header">比赛场次</div>
+        <div class="card-value">{{ matchCount }}</div>
+      </div>
+      <div class="stat-card">
+        <div class="card-header">充值数</div>
+        <div class="card-value">{{ rechargeCount }}</div>
+      </div>
+      <div class="stat-card">
+        <div class="card-header">充值金额</div>
+        <div class="card-value">{{ rechargeAmount }}</div>
+      </div>
+      <div class="stat-card">
+        <div class="card-header">三湘杯参赛资格发放数</div>
+        <div class="card-value">{{ qualificationIssued }}</div>
+      </div>
+      <div class="stat-card">
+        <div class="card-header">三湘杯参赛资格核销数</div>
+        <div class="card-value">{{ qualificationUsed }}</div>
+      </div>
+    </div>
+
+    <!-- 折线图区域 -->
+    <div class="chart-container">
+      <div class="chart-header">
+        <div class="chart-title">折线图</div>
+      </div>
+      <div class="chart-content">
+        <div ref="chartRef" class="chart"></div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup name="Index" lang="ts">
-const goTarget = (url: string) => {
-  window.open(url, '__blank');
+import { ref, onMounted } from 'vue';
+import * as echarts from 'echarts';
+import { getStatisticsInfo, getStatisticsGraphInfo } from '@/api/system/business/statistics'; // 导入API
+// 时间筛选相关
+const startTime = ref('');
+const endTime = ref('');
+// 修改 timeFilter 的定义
+const timeFilter = ref<'day' | 'three' | 'week' | 'month' | 'otherTime'>('day');
+
+// 统计数据
+const matchCount = ref(0);
+const rechargeCount = ref(0);
+const rechargeAmount = ref('0');
+const qualificationIssued = ref(0);
+const qualificationUsed = ref(0);
+
+// 图表相关
+const chartRef = ref<HTMLDivElement | null>(null);
+let chartInstance: any;
+
+// 时间筛选处理
+const handleTimeFilter = (type: string) => {
+  timeFilter.value = type as 'day' | 'three' | 'week' | 'month' | 'otherTime';
+  startTime.value = '';
+  endTime.value = '';
+  // 这里可以设置对应的时间范围
+  fetchStatisticsData();
+  fetchGraphData(); // 获取图表数据
 };
-</script>
 
-<style lang="scss" scoped>
-.home {
-  blockquote {
-    padding: 10px 20px;
-    margin: 0 0 20px;
-    font-size: 17.5px;
-    border-left: 5px solid #eee;
-  }
-  hr {
-    margin-top: 20px;
-    margin-bottom: 20px;
-    border: 0;
-    border-top: 1px solid #eee;
-  }
-  .col-item {
-    margin-bottom: 20px;
-  }
+// 重置筛选条件
+const resetFilter = () => {
+  startTime.value = '';
+  endTime.value = '';
+  timeFilter.value = 'day';
+};
+
+// 获取图表数据
+const fetchGraphData = async () => {
+  try {
+    const params = {
+      flagType: timeFilter.value,
+      startTime: startTime.value || undefined,
+      endTime: endTime.value || undefined
+    };
+
+    const res = await getStatisticsGraphInfo(params);
+    if (res.code === 200) {
+      const data = res.data;
 
-  ul {
-    padding: 0;
-    margin: 0;
+      // 更新图表数据
+      updateChart(data);
+    }
+  } catch (error) {
+    console.error('获取图表数据失败:', error);
   }
+};
+
+// 在 updateChart 函数中添加数据验证
+const updateChart = (data: any) => {
+  // 使用从后端获取的日期数据
+  const timeData = data.dates || [];
 
-  font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-  font-size: 13px;
-  color: #676a6c;
-  overflow-x: hidden;
+  // 设置图表选项
+  const option = {
+    title: {
+      /* text: 'Stacked Line',*/
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: {
+      data: ['充值数', '充值金额', '三湘杯参赛资格发放数', '三湘杯参赛资格核销数']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: timeData
+    },
+    yAxis: {
+      type: 'value',
+      minInterval: 1 // 设置最小间隔为1
+    },
+    series: [
+      {
+        name: '充值数',
+        type: 'line',
+        stack: 'Total',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series'
+        },
+        data: data.payOrderCountIds || []
+      },
+      {
+        name: '充值金额',
+        type: 'line',
+        stack: 'Total',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series'
+        },
+        data:
+          data.payOrderAmountIds?.map((item: any) => {
+            if (item && typeof item === 'object') {
+              // 使用 BigDecimal 的 toPlainString() 方法转换为字符串
+              return item.toPlainString ? item.toPlainString() : Number(item);
+            }
+            return Number(item);
+          }) || []
+      },
+      {
+        name: '三湘杯参赛资格发放数',
+        type: 'line',
+        stack: 'Total',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series'
+        },
+        data: data.sanXiangCardCountIds || []
+      },
+      {
+        name: '三湘杯参赛资格核销数',
+        type: 'line',
+        stack: 'Total',
+        areaStyle: {},
+        emphasis: {
+          focus: 'series'
+        },
+        data: data.selectCheckRecordSanXiangCountIds || []
+      }
+    ]
+  };
 
-  ul {
-    list-style-type: none;
+  // 设置图表选项
+  chartInstance.setOption(option);
+};
+
+
+// 初始化图表
+const initChart = () => {
+  if (!chartInstance && chartRef.value) {
+    chartInstance = echarts.init(chartRef.value);
+
+    // 初始化时设置基本配置
+    const option = {
+      title: {
+       /* text: '',*/
+        left: 'center'
+      },
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          type: 'shadow'
+        }
+      },
+      legend: {
+        data: ['充值数', '充值金额', '三湘杯参赛资格发放数', '三湘杯参赛资格核销数']
+      },
+      grid: {
+        left: '3%',
+        right: '4%',
+        bottom: '15%',
+        containLabel: true
+      },
+      xAxis: {
+        type: 'category',
+        data: []
+      },
+      yAxis: {
+        type: 'value'
+      },
+      series: []
+    };
+
+    chartInstance.setOption(option);
   }
+};
+// 获取统计数据
+const fetchStatisticsData = async () => {
+  try {
+    const params = {
+      flagType: timeFilter.value, // 根据实际需求调整
+      startTime: startTime.value || undefined,
+      endTime: endTime.value || undefined
+    };
 
-  h4 {
-    margin-top: 0px;
+    const res = await getStatisticsInfo(params);
+    if (res.code === 200) {
+      const data = res.data;
+      matchCount.value = data.tournamentCount || 0;
+      rechargeCount.value = data.payOrderCount || 0;
+      rechargeAmount.value = data.payOrderAmount ? data.payOrderAmount.toFixed(0) : '0';
+      qualificationIssued.value = data.sanXiangCardCount || 0;
+      qualificationUsed.value = data.selectCheckRecordSanXiangCount || 0;
+    }
+  } catch (error) {
+    console.error('获取统计数据失败:', error);
   }
+};
+// 查询数据
+const queryData = () => {
+  timeFilter.value = 'otherTime';
+  fetchStatisticsData();
+  fetchGraphData(); // 获取图表数据
+};
+// 页面加载完成后初始化图表
+onMounted(() => {
+  initChart();
+  timeFilter.value = 'month';
+  fetchStatisticsData();
+  fetchGraphData(); // 获取图表数据
+});
+</script>
+
+<style lang="scss" scoped>
+.home {
+  padding: 20px;
+}
+
+.time-filter {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
 
-  h2 {
-    margin-top: 10px;
-    font-size: 26px;
-    font-weight: 100;
+  .filter-label {
+    margin: 0 10px;
   }
+}
 
-  p {
-    margin-top: 10px;
+.statistic-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 15px;
+  margin-bottom: 20px;
+}
 
-    b {
-      font-weight: 700;
-    }
+.stat-card {
+  background: #f5f5f5;
+  border-radius: 8px;
+  padding: 20px;
+  text-align: center;
+
+  .card-header {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 10px;
   }
 
-  .update-log {
-    ol {
-      display: block;
-      list-style-type: decimal;
-      margin-block-start: 1em;
-      margin-block-end: 1em;
-      margin-inline-start: 0;
-      margin-inline-end: 0;
-      padding-inline-start: 40px;
-    }
+  .card-value {
+    font-size: 36px;
+    font-weight: bold;
+    color: #333;
   }
 }
+
+.chart-container {
+  background: #f5f5f5;
+  border-radius: 8px;
+  padding: 20px;
+  margin-top: 20px;
+}
+
+.chart-header {
+  margin-bottom: 15px;
+}
+
+.chart-title {
+  font-size: 24px;
+  color: #333;
+}
+
+.chart-content {
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+}
+
+.chart {
+  width: 100%;
+  height: 400px;
+}
 </style>