mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-18 09:16:39 +08:00
feat: the dashboard page is ready for admin console
This commit is contained in:
parent
3529649ba9
commit
b09d23f97f
55
api/handler/admin/dashboard_handler.go
Normal file
55
api/handler/admin/dashboard_handler.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core"
|
||||||
|
"chatplus/handler"
|
||||||
|
"chatplus/store/model"
|
||||||
|
"chatplus/utils/resp"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DashboardHandler struct {
|
||||||
|
handler.BaseHandler
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
|
||||||
|
h := DashboardHandler{db: db}
|
||||||
|
h.App = app
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
type statsVo struct {
|
||||||
|
Users int64 `json:"users"`
|
||||||
|
Chats int64 `json:"chats"`
|
||||||
|
Tokens int64 `json:"tokens"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) Stats(c *gin.Context) {
|
||||||
|
stats := statsVo{}
|
||||||
|
// new users statistic
|
||||||
|
var userCount int64
|
||||||
|
now := time.Now()
|
||||||
|
zeroTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||||
|
res := h.db.Model(&model.User{}).Where("created_at > ?", zeroTime).Count(&userCount)
|
||||||
|
if res.Error == nil {
|
||||||
|
stats.Users = userCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// new chats statistic
|
||||||
|
var chatCount int64
|
||||||
|
res = h.db.Model(&model.ChatItem{}).Where("created_at > ?", zeroTime).Count(&chatCount)
|
||||||
|
if res.Error == nil {
|
||||||
|
stats.Chats = chatCount
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokens took stats
|
||||||
|
var tokenCount int64
|
||||||
|
res = h.db.Model(&model.HistoryMessage{}).Select("sum(tokens) as tokens_total").Where("created_at > ?", zeroTime).Scan(&tokenCount)
|
||||||
|
if res.Error == nil {
|
||||||
|
stats.Tokens = tokenCount
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c, stats)
|
||||||
|
}
|
@ -141,6 +141,7 @@ func main() {
|
|||||||
fx.Provide(admin.NewUserHandler),
|
fx.Provide(admin.NewUserHandler),
|
||||||
fx.Provide(admin.NewChatRoleHandler),
|
fx.Provide(admin.NewChatRoleHandler),
|
||||||
fx.Provide(admin.NewRewardHandler),
|
fx.Provide(admin.NewRewardHandler),
|
||||||
|
fx.Provide(admin.NewDashboardHandler),
|
||||||
|
|
||||||
// 创建服务
|
// 创建服务
|
||||||
fx.Provide(service.NewAliYunSmsService),
|
fx.Provide(service.NewAliYunSmsService),
|
||||||
@ -231,6 +232,10 @@ func main() {
|
|||||||
group := s.Engine.Group("/api/admin/reward/")
|
group := s.Engine.Group("/api/admin/reward/")
|
||||||
group.GET("list", h.List)
|
group.GET("list", h.List)
|
||||||
}),
|
}),
|
||||||
|
fx.Invoke(func(s *core.AppServer, h *admin.DashboardHandler) {
|
||||||
|
group := s.Engine.Group("/api/admin/dashboard/")
|
||||||
|
group.GET("stats", h.Stats)
|
||||||
|
}),
|
||||||
|
|
||||||
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||||
err := s.Run(db)
|
err := s.Run(db)
|
||||||
|
29
web/package-lock.json
generated
29
web/package-lock.json
generated
@ -25,7 +25,8 @@
|
|||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.0",
|
||||||
"vant": "^4.5.0",
|
"vant": "^4.5.0",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-router": "^4.0.15"
|
"vue-router": "^4.0.15",
|
||||||
|
"vue-schart": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.18.6",
|
"@babel/core": "7.18.6",
|
||||||
@ -9366,6 +9367,11 @@
|
|||||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/schart.js": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/schart.js/-/schart.js-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-uylb2u9rrHX1jyAuSAJUQON8XTfyDKI9kWj1J3fUlCQCkLVZ4HG4+IiV8qm//Z71dqvLI78QZ/fCBw0reB22Zw=="
|
||||||
|
},
|
||||||
"node_modules/schema-utils": {
|
"node_modules/schema-utils": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
|
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
|
||||||
@ -10694,6 +10700,14 @@
|
|||||||
"vue": "^3.2.0"
|
"vue": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-schart": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-schart/-/vue-schart-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-qAu3e5wfMcq26wK1xeHExEWfGpnjfoN1R/9QXblNi+AsU/p52X7tTwhi+Fw7H/otfEufhEY2X7z7emaoF4QO+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"schart.js": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-style-loader": {
|
"node_modules/vue-style-loader": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||||
@ -18621,6 +18635,11 @@
|
|||||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"schart.js": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/schart.js/-/schart.js-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-uylb2u9rrHX1jyAuSAJUQON8XTfyDKI9kWj1J3fUlCQCkLVZ4HG4+IiV8qm//Z71dqvLI78QZ/fCBw0reB22Zw=="
|
||||||
|
},
|
||||||
"schema-utils": {
|
"schema-utils": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
|
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz",
|
||||||
@ -19686,6 +19705,14 @@
|
|||||||
"@vue/devtools-api": "^6.0.0"
|
"@vue/devtools-api": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-schart": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-schart/-/vue-schart-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-qAu3e5wfMcq26wK1xeHExEWfGpnjfoN1R/9QXblNi+AsU/p52X7tTwhi+Fw7H/otfEufhEY2X7z7emaoF4QO+g==",
|
||||||
|
"requires": {
|
||||||
|
"schart.js": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-style-loader": {
|
"vue-style-loader": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||||
|
@ -71,7 +71,7 @@ httpGet('/api/admin/config/get?key=system').then(res => {
|
|||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
index: '/admin/welcome',
|
index: '/admin/dashboard',
|
||||||
title: '仪表盘',
|
title: '仪表盘',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -35,15 +35,15 @@ const routes = [
|
|||||||
{
|
{
|
||||||
name: 'admin',
|
name: 'admin',
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
redirect: '/admin/welcome',
|
redirect: '/admin/dashboard',
|
||||||
component: () => import("@/views/admin/Home.vue"),
|
component: () => import("@/views/admin/Home.vue"),
|
||||||
meta: {title: 'ChatGPT-Plus 管理后台'},
|
meta: {title: 'ChatGPT-Plus 管理后台'},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/admin/welcome',
|
path: '/admin/dashboard',
|
||||||
name: 'admin-home',
|
name: 'admin-dashboard',
|
||||||
meta: {title: '系统首页'},
|
meta: {title: '仪表盘'},
|
||||||
component: () => import('@/views/admin/Welcome.vue'),
|
component: () => import('@/views/admin/Dashboard.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/system',
|
path: '/admin/system',
|
||||||
|
120
web/src/views/admin/Dashboard.vue
Normal file
120
web/src/views/admin/Dashboard.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard">
|
||||||
|
<el-row class="mgb20" :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-1">
|
||||||
|
<el-icon class="grid-con-icon">
|
||||||
|
<User/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">{{ stats.users }}</div>
|
||||||
|
<div>今日新增用户</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-2">
|
||||||
|
<el-icon class="grid-con-icon">
|
||||||
|
<ChatDotRound/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">{{ stats.chats }}</div>
|
||||||
|
<div>今日新增对话</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-3">
|
||||||
|
<el-icon class="grid-con-icon">
|
||||||
|
<TrendCharts/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">{{ stats.tokens }}</div>
|
||||||
|
<div>今日消耗 Tokens</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue';
|
||||||
|
import {ChatDotRound, TrendCharts, User} from "@element-plus/icons-vue";
|
||||||
|
import {httpGet} from "@/utils/http";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
|
const stats = ref({users: 0, chats: 0, tokens: 0})
|
||||||
|
|
||||||
|
httpGet('/api/admin/dashboard/stats').then((res) => {
|
||||||
|
stats.value.users = res.data.users
|
||||||
|
stats.value.chats = res.data.chats
|
||||||
|
stats.value.tokens = res.data.tokens
|
||||||
|
}).catch((e) => {
|
||||||
|
ElMessage.error("获取统计数据失败:" + e.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="stylus">
|
||||||
|
.dashboard {
|
||||||
|
.grid-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cont-right {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-num {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-icon {
|
||||||
|
font-size: 50px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-1 .grid-con-icon {
|
||||||
|
background: rgb(45, 140, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-1 .grid-num {
|
||||||
|
color: rgb(45, 140, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-2 .grid-con-icon {
|
||||||
|
background: rgb(100, 213, 114);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-2 .grid-num {
|
||||||
|
color: rgb(100, 213, 114);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-3 .grid-con-icon {
|
||||||
|
background: rgb(242, 94, 67);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-3 .grid-num {
|
||||||
|
color: rgb(242, 94, 67);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="welcome">
|
|
||||||
<h1>ChatGPT-PLUS 控制台</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup></script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.welcome {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: #202020;
|
|
||||||
background-color: #282c34;
|
|
||||||
height 100%;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 300%;
|
|
||||||
font-weight: bold;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
text-shadow: -1px -1px 1px #111111, 2px 2px 1px #363636;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
Reference in New Issue
Block a user