feat: add data statistic chart
This commit is contained in:
@@ -101,3 +101,66 @@ func DeleteOutDateLogs(keepNum int) (int, error) {
|
||||
affectedRows = int(result.RowsAffected)
|
||||
return affectedRows, nil
|
||||
}
|
||||
|
||||
type StatisticData struct {
|
||||
TodaySuccNum int `json:"today_succ_num"`
|
||||
TodayFailedNum int `json:"today_failed_num"`
|
||||
TodayTotalNum int `json:"today_total_num"`
|
||||
|
||||
LatestSendData []LatestSendData `json:"latest_send_data"`
|
||||
WayCateData []WayCateData `json:"way_cate_data"`
|
||||
}
|
||||
|
||||
type LatestSendData struct {
|
||||
Day string `json:"day"`
|
||||
Num int `json:"num"`
|
||||
}
|
||||
|
||||
type WayCateData struct {
|
||||
WayName string `json:"way_name"`
|
||||
CountNum int `json:"count_num"`
|
||||
}
|
||||
|
||||
// GetStatisticData 获取统计数据
|
||||
func GetStatisticData() (StatisticData, error) {
|
||||
var statistic StatisticData
|
||||
var latestData []LatestSendData
|
||||
var wayCateData []WayCateData
|
||||
logt := db.NewScope(SendTasksLogs{}).TableName()
|
||||
inst := db.NewScope(SendTasksIns{}).TableName()
|
||||
wayst := db.NewScope(SendWays{}).TableName()
|
||||
|
||||
// 今日统计数据
|
||||
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 = 0 THEN 1 ELSE 0 END) AS today_failed_num`).
|
||||
Where("DATE(created_on) = CURDATE()")
|
||||
query.First(&statistic)
|
||||
|
||||
// 最近30天数据
|
||||
days := 30
|
||||
queryData := db.
|
||||
Table(logt).
|
||||
Select(`
|
||||
CAST(DATE(created_on) AS CHAR) AS day,
|
||||
COUNT(*) AS num`).
|
||||
Where(" created_on >= CURDATE() - INTERVAL ? DAY", days).
|
||||
Group("DATE(created_on)").
|
||||
Order("created_on")
|
||||
queryData.Scan(&latestData)
|
||||
|
||||
// 消息实例分类数目
|
||||
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.LatestSendData = latestData
|
||||
statistic.WayCateData = wayCateData
|
||||
return statistic, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"message-nest/pkg/app"
|
||||
"message-nest/service/statistic_service"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GetStatisticData 获取发送统计数据
|
||||
func GetStatisticData(c *gin.Context) {
|
||||
var (
|
||||
appG = app.Gin{C: c}
|
||||
)
|
||||
|
||||
msgService := statistic_service.StatisticService{}
|
||||
data, err := msgService.GetStatisticData()
|
||||
if err != nil {
|
||||
appG.CResponse(http.StatusInternalServerError, fmt.Sprintf("获取失败!原因:%s", err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
appG.CResponse(http.StatusOK, "获取成功", data)
|
||||
}
|
||||
@@ -81,6 +81,9 @@ func InitRouter(f embed.FS) *gin.Engine {
|
||||
apiV1.POST("/settings/reset", v1.RestDefaultSettings)
|
||||
apiV1.GET("/settings/getsetting", v1.GetUserSetting)
|
||||
|
||||
// statistic
|
||||
apiV1.GET("/statistic", v1.GetStatisticData)
|
||||
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package statistic_service
|
||||
|
||||
import (
|
||||
"message-nest/models"
|
||||
)
|
||||
|
||||
type StatisticService struct {
|
||||
}
|
||||
|
||||
func (sw *StatisticService) GetStatisticData() (models.StatisticData, error) {
|
||||
return models.GetStatisticData()
|
||||
}
|
||||
Generated
+59
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"axios": "^1.6.2",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.4.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.7",
|
||||
@@ -1772,6 +1773,20 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
|
||||
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.616",
|
||||
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz",
|
||||
@@ -3745,6 +3760,19 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
|
||||
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -4960,6 +4988,22 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"echarts": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
|
||||
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
|
||||
"requires": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.616",
|
||||
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz",
|
||||
@@ -6277,6 +6321,21 @@
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"zrender": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
|
||||
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
|
||||
"requires": {
|
||||
"tslib": "2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"axios": "^1.6.2",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.4.2",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.7",
|
||||
|
||||
@@ -11,6 +11,12 @@ const router = createRouter({
|
||||
name: 'login',
|
||||
component: LoginInex
|
||||
},
|
||||
{
|
||||
path: '/statistic',
|
||||
name: 'statistic',
|
||||
alias: '/',
|
||||
component: () => import('../views/tabsTools/statistic/statistic.vue')
|
||||
},
|
||||
{
|
||||
path: '/sendways',
|
||||
name: 'sendWays',
|
||||
@@ -23,7 +29,6 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: '/sendlogs',
|
||||
alias: '/',
|
||||
name: 'sendlogs',
|
||||
component: () => import('../views/tabsTools/sendLogs/sendLogs.vue')
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="inex-title-bar" v-if="pageState.isLogin">
|
||||
<el-menu :collapse="isCollapse" breakpoint="768px" mode="horizontal" @select="handleSelect()"
|
||||
:default-active="currActivate()" :ellipsis="false" :menu-width="'auto'">
|
||||
<el-menu-item index="0" :disabled="false">
|
||||
<el-menu-item index="" :disabled="false">
|
||||
<img style="width: 60px" class="title-logo" :src="titleLogo" alt="Message logo" />
|
||||
</el-menu-item>
|
||||
|
||||
@@ -33,6 +33,7 @@ export default {
|
||||
titleLogo: '',
|
||||
});
|
||||
const menuData = reactive([
|
||||
{ id: '0', title: '数据统计', path: '/statistic' },
|
||||
{ id: '1', title: '发信日志', path: '/sendlogs' },
|
||||
{ id: '2', title: '发信任务', path: '/sendtasks' },
|
||||
{ id: '3', title: '发信渠道', path: '/sendways' },
|
||||
@@ -54,7 +55,7 @@ export default {
|
||||
|
||||
const currActivate = () => {
|
||||
const cur_path = useRoute().path;
|
||||
let result = '1';
|
||||
let result = '0';
|
||||
menuData.forEach(element => {
|
||||
if (element.path == cur_path) {
|
||||
result = element.id;
|
||||
|
||||
@@ -42,9 +42,7 @@
|
||||
import { defineComponent, reactive, toRefs, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
import { request } from '@/api/api'
|
||||
import { CONSTANT } from '@/constant'
|
||||
import { LocalStieConfigUtils } from '@/util/localSiteConfig'
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div class="main-center-body">
|
||||
<div class="container">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="data.today_total_num">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
今日发送总数
|
||||
</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="data.today_succ_num">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
今日发送成功数
|
||||
</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="data.today_failed_num">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
今日发送失败数
|
||||
</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="echarts-box">
|
||||
<div id="daily-chart" style="width: 65%; height: 350px;"></div>
|
||||
<div id="send-cate-chart" style="width: 35%; height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
// import {
|
||||
// ArrowRight,
|
||||
// CaretBottom,
|
||||
// CaretTop,
|
||||
// Warning,
|
||||
// } from '@element-plus/icons-vue'
|
||||
import { reactive, toRefs, onMounted, onUnmounted } from 'vue'
|
||||
import { request } from '@/api/api'
|
||||
import * as echarts from "echarts"
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const state = reactive({
|
||||
data: {},
|
||||
dailyChart: {},
|
||||
sendCateChart: {},
|
||||
});
|
||||
let echart = echarts;
|
||||
|
||||
const getStatisticData = async () => {
|
||||
const rsp = await request.get('/statistic');
|
||||
if (await rsp.data.code == 200) {
|
||||
let data = await rsp.data.data;
|
||||
state.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getStatisticData();
|
||||
initDailyChart();
|
||||
initSendCateChart();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
state.dailyChart.dispose();
|
||||
state.sendCateChart.dispose();
|
||||
});
|
||||
|
||||
// 最近30天数据图
|
||||
function initDailyChart() {
|
||||
state.dailyChart = echart.init(document.getElementById("daily-chart"));
|
||||
|
||||
let xAxisdata = [];
|
||||
state.data.latest_send_data.forEach(element => {
|
||||
xAxisdata.push(element.day);
|
||||
});
|
||||
let yAxisdata = [];
|
||||
state.data.latest_send_data.forEach(element => {
|
||||
yAxisdata.push(element.num);
|
||||
});
|
||||
|
||||
state.dailyChart.setOption({
|
||||
title: {
|
||||
subtext: '最近消息30天发送数据',
|
||||
top: 0,
|
||||
textStyle: {
|
||||
color: '#333',
|
||||
fontSize: 18,
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: xAxisdata
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis"
|
||||
},
|
||||
yAxis: {
|
||||
type: "value"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: yAxisdata,
|
||||
type: "line",
|
||||
smooth: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
window.onresize = function () {
|
||||
state.dailyChart.resize();
|
||||
};
|
||||
}
|
||||
|
||||
// 发送消息实例类别图
|
||||
function initSendCateChart() {
|
||||
state.sendCateChart = echart.init(document.getElementById("send-cate-chart"));
|
||||
let data = [];
|
||||
state.data.way_cate_data.forEach(element => {
|
||||
data.push({ name: element.way_name, value: element.count_num });
|
||||
});
|
||||
state.sendCateChart.setOption({
|
||||
grid: {
|
||||
width: '60%',
|
||||
height: '60%',
|
||||
},
|
||||
title: {
|
||||
subtext: '发送消息实例渠道',
|
||||
top: 0,
|
||||
textStyle: {
|
||||
color: '#333',
|
||||
fontSize: 18,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
data: data,
|
||||
radius: '40%',
|
||||
// roseType: 'area'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
window.onresize = function () {
|
||||
state.sendCateChart.resize();
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state)
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
|
||||
.el-statistic {
|
||||
--el-statistic-content-font-size: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.statistic-card {
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
}
|
||||
|
||||
.echarts-box {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--el-color-error);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user