|
|
@@ -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">¥免费开源</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">¥免费开源</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>
|