feat: speed up dashboard page loading

This commit is contained in:
engigu
2025-09-20 14:09:07 +08:00
parent f51295e945
commit 0ecad7f23d
5 changed files with 235 additions and 34 deletions
+96
View File
@@ -129,6 +129,25 @@ type StatisticData struct {
WayCateData []WayCateData `json:"way_cate_data" gorm:"many2many:way_cate_data;"`
}
// BasicStatisticData 基础统计数据
type BasicStatisticData struct {
TodaySuccNum int `json:"today_succ_num"`
TodayFailedNum int `json:"today_failed_num"`
TodayTotalNum int `json:"today_total_num"`
MessageTotalNum int `json:"message_total_num"`
HostedMessageTotalNum int `json:"hosted_message_total_num"`
}
// TrendStatisticData 趋势统计数据
type TrendStatisticData struct {
LatestSendData []LatestSendData `json:"latest_send_data"`
}
// ChannelStatisticData 渠道统计数据
type ChannelStatisticData struct {
WayCateData []WayCateData `json:"way_cate_data"`
}
type LatestSendData struct {
Day string `json:"day"`
Num int `json:"num"`
@@ -204,3 +223,80 @@ func GetStatisticData() (StatisticData, error) {
statistic.WayCateData = wayCateData
return statistic, nil
}
// GetBasicStatisticData 获取基础统计数据
func GetBasicStatisticData() (BasicStatisticData, error) {
var statistic BasicStatisticData
logt := GetSchema(SendTasksLogs{})
hostedt := GetSchema(HostedMessage{})
currDay := util.GetNowTimeStr()[:10]
// 今日统计数据
query := db.
Table(logt).
Select(`
COUNT(*) AS today_total_num,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS today_succ_num,
SUM(CASE WHEN status != 1 or status is null THEN 1 ELSE 0 END) AS today_failed_num`).
Where("DATE(created_on) = ?", currDay)
query.Take(&statistic)
// 全部消息统计数据
totalQuery := db.Table(logt).Select(`COUNT(*) AS message_total_num`)
totalQuery.Take(&statistic)
// 托管消息统计数据
hostedMessageTotalQuery := db.Table(hostedt).Select(`COUNT(*) AS hosted_message_total_num`)
hostedMessageTotalQuery.Take(&statistic)
return statistic, nil
}
// GetTrendStatisticData 获取趋势统计数据
func GetTrendStatisticData() (TrendStatisticData, error) {
var statistic TrendStatisticData
var latestData []LatestSendData
logt := GetSchema(SendTasksLogs{})
// 最近30天数据
days := 30
now := util.GetNowTime()
past := now.AddDate(0, 0, -days)
pastDate := past.Format("2006-01-02")
next := now.AddDate(0, 0, 1)
nextDate := next.Format("2006-01-02")
queryData := db.
Table(logt).
Select(`
CAST(DATE(created_on) AS CHAR) AS day,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS day_succ_num,
SUM(CASE WHEN status != 1 or status is null THEN 1 ELSE 0 END) AS day_failed_num,
COUNT(*) AS num`).
Where(fmt.Sprintf(" created_on >= '%s' and created_on <= '%s' ", pastDate, nextDate)).
Group("day").
Order("day")
queryData.Scan(&latestData)
statistic.LatestSendData = latestData
return statistic, nil
}
// GetChannelStatisticData 获取渠道统计数据
func GetChannelStatisticData() (ChannelStatisticData, error) {
var statistic ChannelStatisticData
var wayCateData []WayCateData
inst := GetSchema(SendTasksIns{})
wayst := GetSchema(SendWays{})
// 消息实例分类数目
db.
Table(inst).
Select(fmt.Sprintf("%s.name as way_name, count(%s.id) as count_num", wayst, wayst)).
Joins(fmt.Sprintf("JOIN %s ON %s.way_id = %s.id", wayst, inst, wayst)).
Group(fmt.Sprintf("%s.id", wayst)).
Scan(&wayCateData)
statistic.WayCateData = wayCateData
return statistic, nil
}
+3 -1
View File
@@ -2,9 +2,10 @@ package util
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
"message-nest/pkg/setting"
"time"
"github.com/golang-jwt/jwt/v5"
)
type UserClaims struct {
@@ -16,6 +17,7 @@ type UserClaims struct {
func GenerateToken(username, password string) (string, error) {
expHours := 1 * 24 * time.Hour
//expHours := 1 * time.Minute
SetClaims := UserClaims{
Username: username,
Password: EncodeMD5(password),
+35 -6
View File
@@ -14,12 +14,41 @@ func GetStatisticData(c *gin.Context) {
appG = app.Gin{C: c}
)
// 获取类型参数
statisticType := c.Query("type")
msgService := statistic_service.StatisticService{}
data, err := msgService.GetStatisticData()
if err != nil {
appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取失败!原因:%s", err), nil)
return
switch statisticType {
case "basic":
data, err := msgService.GetBasicStatisticData()
if err != nil {
appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取基础统计失败!原因:%s", err), nil)
return
}
appG.CResponse(http.StatusOK, "获取基础统计成功", data)
case "trend":
data, err := msgService.GetTrendStatisticData()
if err != nil {
appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取趋势统计失败!原因:%s", err), nil)
return
}
appG.CResponse(http.StatusOK, "获取趋势统计成功", data)
case "channels":
data, err := msgService.GetChannelStatisticData()
if err != nil {
appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取渠道统计失败!原因:%s", err), nil)
return
}
appG.CResponse(http.StatusOK, "获取渠道统计成功", data)
default:
// 默认返回完整统计数据(保持向后兼容)
data, err := msgService.GetStatisticData()
if err != nil {
appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取失败!原因:%s", err), nil)
return
}
appG.CResponse(http.StatusOK, "获取成功", data)
}
appG.CResponse(http.StatusOK, "获取成功", data)
}
+15
View File
@@ -10,3 +10,18 @@ type StatisticService struct {
func (sw *StatisticService) GetStatisticData() (models.StatisticData, error) {
return models.GetStatisticData()
}
// GetBasicStatisticData 获取基础统计数据
func (sw *StatisticService) GetBasicStatisticData() (models.BasicStatisticData, error) {
return models.GetBasicStatisticData()
}
// GetTrendStatisticData 获取趋势统计数据
func (sw *StatisticService) GetTrendStatisticData() (models.TrendStatisticData, error) {
return models.GetTrendStatisticData()
}
// GetChannelStatisticData 获取渠道统计数据
func (sw *StatisticService) GetChannelStatisticData() (models.ChannelStatisticData, error) {
return models.GetChannelStatisticData()
}
@@ -4,6 +4,7 @@ import { DatabaseIcon, BarChartIcon, SendIcon, CheckCircleIcon, XCircleIcon } fr
// import { LineChart } from "@/components/ui/chart-line"
import { onMounted, reactive } from 'vue';
import { request } from '@/api/api';
import { toast } from 'vue-sonner';
// import VueApexCharts from 'vue3-apexcharts'
import ApexCharts from 'apexcharts'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
@@ -21,54 +22,113 @@ interface CateData {
}
let state = reactive({
data: {
basicData: {
message_total_num: 0,
hosted_message_total_num: 0,
today_total_num: 0,
today_succ_num: 0,
today_failed_num: 0,
way_cate_data: [] as CateData[],
},
trendData: {
latest_send_data: [] as SendData[],
},
channelData: {
way_cate_data: [] as CateData[],
},
loading: {
basic: false,
trend: false,
channels: false,
}
});
const getStatisticData = async () => {
// 获取基础统计数据
const getBasicStatisticData = async () => {
state.loading.basic = true;
try {
const rsp = await request.get('/statistic');
const rsp = await request.get('/statistic?type=basic');
if (rsp && rsp.data && rsp.data.code == 200) {
state.data = rsp.data.data;
// 数据加载完成后重新渲染图表
state.basicData = rsp.data.data;
} else {
toast.error(rsp?.data?.msg || '获取基础统计数据失败');
}
} catch (error) {
toast.error('获取基础统计数据时发生错误');
} finally {
state.loading.basic = false;
}
}
// 获取趋势统计数据
const getTrendStatisticData = async () => {
state.loading.trend = true;
try {
const rsp = await request.get('/statistic?type=trend');
if (rsp && rsp.data && rsp.data.code == 200) {
state.trendData = rsp.data.data;
// 数据加载完成后重新渲染折线图
setTimeout(() => {
renderLineChart();
}, 100);
} else {
toast.error(rsp?.data?.msg || '获取趋势统计数据失败');
}
} catch (error) {
toast.error('获取趋势统计数据时发生错误');
} finally {
state.loading.trend = false;
}
}
// 获取渠道统计数据
const getChannelStatisticData = async () => {
state.loading.channels = true;
try {
const rsp = await request.get('/statistic?type=channels');
if (rsp && rsp.data && rsp.data.code == 200) {
state.channelData = rsp.data.data;
// 数据加载完成后重新渲染饼图
setTimeout(() => {
renderPieChart();
}, 100);
} else {
console.error('Failed to load statistics:', rsp?.data?.msg || 'Unknown error');
toast.error(rsp?.data?.msg || '获取渠道统计数据失败');
}
} catch (error) {
console.error('Error loading statistics:', error);
toast.error('获取渠道统计数据时发生错误');
} finally {
state.loading.channels = false;
}
}
// 并行加载所有统计数据
const loadAllStatisticData = async () => {
await Promise.all([
getBasicStatisticData(),
getTrendStatisticData(),
getChannelStatisticData()
]);
}
const renderLineChart = () => {
const options = {
series: [
{
name: '发送总数',
data: state.data.latest_send_data.length > 0
? state.data.latest_send_data.map(item => item.num || 0)
data: state.trendData.latest_send_data.length > 0
? state.trendData.latest_send_data.map(item => item.num || 0)
: []
},
{
name: '发送成功数',
data: state.data.latest_send_data.length > 0
? state.data.latest_send_data.map(item => item.day_succ_num || 0)
data: state.trendData.latest_send_data.length > 0
? state.trendData.latest_send_data.map(item => item.day_succ_num || 0)
: []
},
{
name: '发送失败数',
data: state.data.latest_send_data.length > 0
? state.data.latest_send_data.map(item => item.day_failed_num || 0)
data: state.trendData.latest_send_data.length > 0
? state.trendData.latest_send_data.map(item => item.day_failed_num || 0)
: []
},
],
@@ -115,8 +175,8 @@ const renderLineChart = () => {
}
},
xaxis: {
categories: state.data.latest_send_data.length > 0
? state.data.latest_send_data.map(item => item.day)
categories: state.trendData.latest_send_data.length > 0
? state.trendData.latest_send_data.map(item => item.day)
: [],
axisBorder: {
show: false
@@ -268,8 +328,8 @@ const renderLineChart = () => {
const renderPieChart = () => {
const options = {
series: state.data.way_cate_data.length > 0
? state.data.way_cate_data.map(item => item.count_num)
series: state.channelData.way_cate_data.length > 0
? state.channelData.way_cate_data.map(item => item.count_num)
: [],
chart: {
type: 'pie',
@@ -290,8 +350,8 @@ const renderPieChart = () => {
}
}
},
labels: state.data.way_cate_data.length > 0
? state.data.way_cate_data.map(item => item.way_name)
labels: state.channelData.way_cate_data.length > 0
? state.channelData.way_cate_data.map(item => item.way_name)
: [],
colors: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'],
legend: {
@@ -353,8 +413,7 @@ const renderPieChart = () => {
}
onMounted(() => {
getStatisticData();
// renderLineChart();
loadAllStatisticData();
})
@@ -363,11 +422,11 @@ onMounted(() => {
<template>
<div class="w-[90%] mx-auto grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
<StatCard title="推送记录留存数" :value="state.data.message_total_num" description="" :icon="DatabaseIcon" />
<StatCard title="托管消息数" :value="state.data.hosted_message_total_num" description="" :icon="BarChartIcon" />
<StatCard title="今日发送总数" :value="state.data.today_total_num" description="" :icon="SendIcon" />
<StatCard title="今日成功数" :value="state.data.today_succ_num" description="" :icon="CheckCircleIcon" />
<StatCard title="今日失败数" :value="state.data.today_failed_num" description="" :icon="XCircleIcon" />
<StatCard title="推送记录留存数" :value="state.basicData.message_total_num" description="" :icon="DatabaseIcon" />
<StatCard title="托管消息数" :value="state.basicData.hosted_message_total_num" description="" :icon="BarChartIcon" />
<StatCard title="今日发送总数" :value="state.basicData.today_total_num" description="" :icon="SendIcon" />
<StatCard title="今日成功数" :value="state.basicData.today_succ_num" description="" :icon="CheckCircleIcon" />
<StatCard title="今日失败数" :value="state.basicData.today_failed_num" description="" :icon="XCircleIcon" />
</div>
<!-- 折线图 -->