mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-19 01:36:38 +08:00
refactor: user login log list for admin is ready
This commit is contained in:
parent
feff1684c4
commit
0e6606e469
@ -8,9 +8,10 @@ import (
|
|||||||
"chatplus/store/vo"
|
"chatplus/store/vo"
|
||||||
"chatplus/utils"
|
"chatplus/utils"
|
||||||
"chatplus/utils/resp"
|
"chatplus/utils/resp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApiKeyHandler struct {
|
type ApiKeyHandler struct {
|
||||||
@ -61,7 +62,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
|
|||||||
|
|
||||||
func (h *ApiKeyHandler) List(c *gin.Context) {
|
func (h *ApiKeyHandler) List(c *gin.Context) {
|
||||||
userId := h.GetInt(c, "user_id", -1)
|
userId := h.GetInt(c, "user_id", -1)
|
||||||
query := h.db.Debug().Session(&gorm.Session{})
|
query := h.db.Session(&gorm.Session{})
|
||||||
if userId >= 0 {
|
if userId >= 0 {
|
||||||
query = query.Where("user_id", userId)
|
query = query.Where("user_id", userId)
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,32 @@ func (h *UserHandler) Remove(c *gin.Context) {
|
|||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *UserHandler) LoginLog(c *gin.Context) {
|
||||||
|
page := h.GetInt(c, "page", 1)
|
||||||
|
pageSize := h.GetInt(c, "page_size", 20)
|
||||||
|
var total int64
|
||||||
|
h.db.Model(&model.UserLoginLog{}).Count(&total)
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
var items []model.UserLoginLog
|
||||||
|
res := h.db.Offset(offset).Limit(pageSize).Find(&items)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "获取数据失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var logs []vo.UserLoginLog
|
||||||
|
for _, v := range items {
|
||||||
|
var log vo.UserLoginLog
|
||||||
|
err := utils.CopyObject(v, &log)
|
||||||
|
if err == nil {
|
||||||
|
log.Id = v.Id
|
||||||
|
log.CreatedAt = v.CreatedAt.Unix()
|
||||||
|
logs = append(logs, log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, vo.NewPage(total, page, pageSize, logs))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *UserHandler) InitUser(c *gin.Context) {
|
func (h *UserHandler) InitUser(c *gin.Context) {
|
||||||
var users []model.User
|
var users []model.User
|
||||||
h.db.Find(&users)
|
h.db.Find(&users)
|
||||||
|
@ -137,6 +137,7 @@ func main() {
|
|||||||
group.GET("list", h.List)
|
group.GET("list", h.List)
|
||||||
group.POST("update", h.Update)
|
group.POST("update", h.Update)
|
||||||
group.GET("remove", h.Remove)
|
group.GET("remove", h.Remove)
|
||||||
|
group.GET("loginLog", h.LoginLog)
|
||||||
group.GET("test", h.InitUser)
|
group.GET("test", h.InitUser)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *admin.ChatRoleHandler) {
|
fx.Invoke(func(s *core.AppServer, h *admin.ChatRoleHandler) {
|
||||||
|
9
api/go/store/vo/user_login_log.go
Normal file
9
api/go/store/vo/user_login_log.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package vo
|
||||||
|
|
||||||
|
type UserLoginLog struct {
|
||||||
|
BaseVo
|
||||||
|
UserId uint `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
LoginIp string `json:"login_ip"`
|
||||||
|
LoginAddress string `json:"login_address"`
|
||||||
|
}
|
@ -87,6 +87,9 @@
|
|||||||
<el-tab-pane label="API KEY" name="apikey" v-if="arrayContains(tabs, 'apikey')">
|
<el-tab-pane label="API KEY" name="apikey" v-if="arrayContains(tabs, 'apikey')">
|
||||||
<api-key v-if="curTab==='apikey'"/>
|
<api-key v-if="curTab==='apikey'"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="登录日志" name="loginLog" v-if="arrayContains(tabs, 'loginLog')">
|
||||||
|
<login-log v-if="curTab==='loginLog'"/>
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -108,10 +111,10 @@ import {httpGet} from "@/utils/http";
|
|||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import ApiKey from "@/views/admin/ApiKey.vue";
|
import ApiKey from "@/views/admin/ApiKey.vue";
|
||||||
|
import LoginLog from "@/views/admin/LoginLog.vue";
|
||||||
|
|
||||||
const title = ref('Chat-Plus 控制台')
|
const title = ref('Chat-Plus 控制台')
|
||||||
const logo = ref('images/logo.png')
|
const logo = ref('images/logo.png')
|
||||||
const user = ref({})
|
|
||||||
const navs = ref([
|
const navs = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -136,6 +139,12 @@ const navs = ref([
|
|||||||
title: 'API KEY',
|
title: 'API KEY',
|
||||||
tab: 'apikey',
|
tab: 'apikey',
|
||||||
active: false,
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: '登录日志',
|
||||||
|
tab: 'loginLog',
|
||||||
|
active: false,
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const tabs = ref([])
|
const tabs = ref([])
|
||||||
@ -155,22 +164,22 @@ const nodeListPaddingLeft = computed(() => {
|
|||||||
})
|
})
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
onMounted(() => {
|
// 获取会话信息
|
||||||
window.addEventListener("resize", function () {
|
httpGet("/api/admin/session").then(() => {
|
||||||
winHeight.value = window.innerHeight
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取会话信息
|
|
||||||
httpGet("/api/admin/session").catch(() => {
|
|
||||||
router.push('/admin/login')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载系统配置
|
// 加载系统配置
|
||||||
httpGet('/api/admin/config/get?key=system').then(res => {
|
httpGet('/api/admin/config/get?key=system').then(res => {
|
||||||
title.value = res.data['admin_title'];
|
title.value = res.data['admin_title'];
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("加载系统配置失败: " + e.message)
|
ElMessage.error("加载系统配置失败: " + e.message)
|
||||||
})
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
router.push('/admin/login')
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("resize", function () {
|
||||||
|
winHeight.value = window.innerHeight
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const logout = function () {
|
const logout = function () {
|
||||||
@ -354,3 +363,12 @@ $borderColor = #4676d0;
|
|||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
.pagination {
|
||||||
|
padding 20px;
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
width 100%
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="list" v-loading="loading">
|
<div class="list" v-loading="loading">
|
||||||
<el-row class="opt-box">
|
<el-row class="opt-box">
|
||||||
<el-button type="primary" @click="add">
|
<el-button type="primary" @click="add" size="small">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Plus/>
|
<Plus/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
新增角色
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
@ -150,6 +150,8 @@ const remove = function (row) {
|
|||||||
|
|
||||||
.opt-box {
|
.opt-box {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
display flex;
|
||||||
|
justify-content end
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
92
web/src/views/admin/LoginLog.vue
Normal file
92
web/src/views/admin/LoginLog.vue
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list" v-loading="loading">
|
||||||
|
<el-row>
|
||||||
|
<el-table :data="items" :row-key="row => row.id">
|
||||||
|
<el-table-column label="用户名" prop="username"/>
|
||||||
|
<el-table-column label="登录IP" prop="login_ip"/>
|
||||||
|
<el-table-column label="登录地址" prop="login_address"/>
|
||||||
|
<el-table-column label="登录时间">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="180">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row)">
|
||||||
|
<template #reference>
|
||||||
|
<el-button size="small" type="danger">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<el-pagination v-if="total > 0" background
|
||||||
|
layout="prev, pager, next"
|
||||||
|
:hide-on-single-page="true"
|
||||||
|
v-model:current-page="page"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
@current-change="fetchList(page, pageSize)"
|
||||||
|
:total="total"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
import {httpGet} from "@/utils/http";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
import {dateFormat} from "@/utils/libs";
|
||||||
|
|
||||||
|
// 用户登录日志
|
||||||
|
const items = ref([])
|
||||||
|
const loading = ref(true)
|
||||||
|
const total = ref(0)
|
||||||
|
const page = ref(0)
|
||||||
|
const pageSize = ref(0)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchList(1, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const fetchList = function (_page, _pageSize) {
|
||||||
|
console.log(_page, _pageSize)
|
||||||
|
httpGet(`/api/admin/user/loginLog?page=${_page}&page_size=${_pageSize}`).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
items.value = res.data.items
|
||||||
|
total.value = res.data.total
|
||||||
|
page.value = res.data.page
|
||||||
|
pageSize.value = res.data.page_size
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error("获取数据失败");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.list {
|
||||||
|
|
||||||
|
.opt-box {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
display flex;
|
||||||
|
justify-content end
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="role-list">
|
<div class="role-list">
|
||||||
<el-row class="opt-box">
|
<el-row class="opt-box">
|
||||||
<el-button type="primary" @click="addRole">
|
<el-button type="primary" @click="addRole" size="small">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Plus/>
|
<Plus/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
新增角色
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
@ -292,6 +292,8 @@ const removeContext = function (index) {
|
|||||||
.role-list {
|
.role-list {
|
||||||
.opt-box {
|
.opt-box {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
display flex;
|
||||||
|
justify-content end
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
margin-right 5px;
|
margin-right 5px;
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
:hide-on-single-page="true"
|
:hide-on-single-page="true"
|
||||||
v-model:current-page="users.page"
|
v-model:current-page="users.page"
|
||||||
v-model:page-size="users.page_size"
|
v-model:page-size="users.page_size"
|
||||||
@update:current-change="fetchUserList(users.page, users.page_size)"
|
@current-change="fetchUserList(users.page, users.page_size)"
|
||||||
:total="users.total"/>
|
:total="users.total"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -126,7 +126,7 @@ const loading = ref(true)
|
|||||||
const userEditFormRef = ref(null)
|
const userEditFormRef = ref(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchUserList(1, 20)
|
fetchUserList(1, 10)
|
||||||
// 获取角色列表
|
// 获取角色列表
|
||||||
httpGet('/api/admin/role/list').then((res) => {
|
httpGet('/api/admin/role/list').then((res) => {
|
||||||
roles.value = res.data;
|
roles.value = res.data;
|
||||||
@ -219,13 +219,6 @@ const handleSelectionChange = function (rows) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
|
||||||
padding-top 20px;
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
width 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-select {
|
.el-select {
|
||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user