feat: speed up dashboard page loading
This commit is contained in:
@@ -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
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- 折线图 -->
|
||||
|
||||
Reference in New Issue
Block a user