From 89187eb5dcc77d66bd590898d92a98633e813b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Tue, 28 Apr 2026 19:18:55 +0800 Subject: [PATCH] up --- backend/my_work/routers/apiSysAdmin.go | 264 +++++++- frontend/ops_vue_js/src/api/auth.js | 20 + .../ops_vue_js/src/components/AppHeader.vue | 4 +- frontend/ops_vue_js/src/i18n/en.json | 2 - frontend/ops_vue_js/src/i18n/zh-CN.json | 2 - frontend/ops_vue_js/src/router/index.js | 2 +- frontend/ops_vue_js/src/stores/users.js | 2 +- .../ops_vue_js/src/views/SysAdminView.vue | 590 +++++++++++++++++- 8 files changed, 854 insertions(+), 32 deletions(-) diff --git a/backend/my_work/routers/apiSysAdmin.go b/backend/my_work/routers/apiSysAdmin.go index 9c3676f..6266c79 100644 --- a/backend/my_work/routers/apiSysAdmin.go +++ b/backend/my_work/routers/apiSysAdmin.go @@ -1,7 +1,10 @@ package routers import ( + "ops/models" + "github.com/gin-gonic/gin" + "github.com/mitchellh/mapstructure" ) // InitSysAdminRouter 初始化系统管理员路由 @@ -25,8 +28,265 @@ func ApiSysAdmin(r *gin.RouterGroup) { ReturnJson(ctx, "apiOK", redata) }) - // TODO: 其他系统管理员接口可在此添加 - // 例如:用户管理、用户组管理、登录日志查询等 + // 获取用户列表(仅系统管理员可访问) + r.POST("/users", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + // 检查是否为系统管理员 + if !SysAdminCheck(user.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + + // 解析分页和搜索参数 + var params struct { + Page int `json:"page"` + PageSize int `json:"page_size"` + Search string `json:"search"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil { + params.Page = 1 + params.PageSize = 20 + } + if params.Page < 1 { + params.Page = 1 + } + if params.PageSize < 1 || params.PageSize > 100 { + params.PageSize = 20 + } + + offset := (params.Page - 1) * params.PageSize + + // 构建查询 + var users []TabUser + var total int64 + query := models.DB.Model(&TabUser{}) + + // 搜索条件 + if params.Search != "" { + search := "%" + params.Search + "%" + query = query.Where("name LIKE ? OR email LIKE ?", search, search) + } + + // 获取总数 + query.Count(&total) + + // 获取分页数据 + query.Order("id DESC").Offset(offset).Limit(params.PageSize).Find(&users) + + // 获取用户详细信息(包括头像) + var userList []map[string]interface{} + for _, u := range users { + userInfo := GetUserInfoFromUserID(u.ID) + userData := map[string]interface{}{ + "id": u.ID, + "name": u.Name, + "email": u.Email, + "type": u.Type, + "date": u.Date, + "username": "", + "avatarPath": "", + } + if userInfo != nil { + userData["username"] = userInfo.Username + userData["avatarPath"] = userInfo.AvatarPath + } + userList = append(userList, userData) + } + + var redata map[string]interface{} = make(map[string]interface{}) + redata["users"] = userList + redata["total"] = total + redata["page"] = params.Page + redata["page_size"] = params.PageSize + + ReturnJson(ctx, "apiOK", redata) + }) + + // 获取所有用户组列表(仅系统管理员可访问) + r.POST("/groups", func(ctx *gin.Context) { + isAuth, _, _ := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + var groups []TabUserGroups + models.DB.Order("id ASC").Find(&groups) + + var list []map[string]interface{} + for _, g := range groups { + // 统计成员数量 + var memberCount int64 + models.DB.Model(&TabUserGroupBinds{}).Where("group_id = ?", g.ID).Count(&memberCount) + + // 获取部分成员(最多5个) + var binds []TabUserGroupBinds + models.DB.Where("group_id = ?", g.ID).Limit(5).Find(&binds) + var memberIDs []uint + for _, b := range binds { + memberIDs = append(memberIDs, b.UserID) + } + + list = append(list, map[string]interface{}{ + "id": g.ID, + "name": g.Name, + "email": g.Email, + "type": g.Type, + "date": g.Date, + "memberCount": memberCount, + "memberIDs": memberIDs, + }) + } + + var redata map[string]interface{} = make(map[string]interface{}) + redata["groups"] = list + ReturnJson(ctx, "apiOK", redata) + }) + + // 获取用户组成员列表(仅系统管理员可访问) + r.POST("/group_members", func(ctx *gin.Context) { + isAuth, _, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + var params struct { + GroupID uint `json:"group_id"` + Page int `json:"page"` + PageSize int `json:"page_size"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil { + params.Page = 1 + params.PageSize = 20 + } + if params.Page < 1 { + params.Page = 1 + } + if params.PageSize < 1 || params.PageSize > 100 { + params.PageSize = 20 + } + + // 验证用户组是否存在 + var group TabUserGroups + if models.DB.First(&group, params.GroupID).Error != nil { + ReturnJson(ctx, "groupNotFound", nil) + return + } + + offset := (params.Page - 1) * params.PageSize + + var binds []TabUserGroupBinds + var total int64 + models.DB.Model(&TabUserGroupBinds{}).Where("group_id = ?", params.GroupID).Count(&total) + models.DB.Where("group_id = ?", params.GroupID).Order("id ASC").Offset(offset).Limit(params.PageSize).Find(&binds) + + // 获取成员用户信息 + var members []map[string]interface{} + for _, b := range binds { + var u TabUser + if models.DB.First(&u, b.UserID).Error == nil { + userInfo := GetUserInfoFromUserID(u.ID) + member := map[string]interface{}{ + "id": u.ID, + "name": u.Name, + "email": u.Email, + "type": u.Type, + "avatarPath": "", + "username": "", + } + if userInfo != nil { + member["username"] = userInfo.Username + member["avatarPath"] = userInfo.AvatarPath + } + members = append(members, member) + } + } + + var redata map[string]interface{} = make(map[string]interface{}) + redata["group_id"] = params.GroupID + redata["group_name"] = group.Name + redata["members"] = members + redata["total"] = total + redata["page"] = params.Page + redata["page_size"] = params.PageSize + ReturnJson(ctx, "apiOK", redata) +}) + + // 获取登录失败日志(仅系统管理员可访问) + r.POST("/login_fail_logs", func(ctx *gin.Context) { + isAuth, _, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + // 解析分页和搜索参数 + var params struct { + Page int `json:"page"` + PageSize int `json:"page_size"` + Search string `json:"search"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil { + params.Page = 1 + params.PageSize = 20 + } + if params.Page < 1 { + params.Page = 1 + } + if params.PageSize < 1 || params.PageSize > 100 { + params.PageSize = 20 + } + + offset := (params.Page - 1) * params.PageSize + + // 构建查询 + var logs []TabUserLoginFailLog + var total int64 + query := models.DB.Model(&TabUserLoginFailLog{}) + + // 搜索条件(用户名或IP) + if params.Search != "" { + search := "%" + params.Search + "%" + query = query.Where("username LIKE ? OR ip LIKE ?", search, search) + } + + // 获取总数 + query.Count(&total) + + // 获取分页数据,按时间倒序 + query.Order("updated_at DESC").Offset(offset).Limit(params.PageSize).Find(&logs) + + // 构建返回数据 + var logList []map[string]interface{} + for _, log := range logs { + logData := map[string]interface{}{ + "id": log.ID, + "username": log.Username, + "user_id": log.UserID, + "ip": log.IP, + "user_agent": log.UserAgent, + "reason": log.Reason, + "count": log.Count, + "created_at": log.CreatedAt, + "updated_at": log.UpdatedAt, + } + logList = append(logList, logData) + } + + var redata map[string]interface{} = make(map[string]interface{}) + redata["logs"] = logList + redata["total"] = total + redata["page"] = params.Page + redata["page_size"] = params.PageSize + + ReturnJson(ctx, "apiOK", redata) + }) } // SysAdminCheck 检查当前用户是否为系统管理员 diff --git a/frontend/ops_vue_js/src/api/auth.js b/frontend/ops_vue_js/src/api/auth.js index 131d6d9..47752b9 100644 --- a/frontend/ops_vue_js/src/api/auth.js +++ b/frontend/ops_vue_js/src/api/auth.js @@ -21,6 +21,26 @@ export const authApi = { return api.post('/admin/sysadmins', {}) }, + /** 获取用户列表(仅管理员可访问) */ + getUsers(params = {}) { + return api.post('/admin/users', params) + }, + + /** 获取用户组列表(仅管理员可访问) */ + getGroups() { + return api.post('/admin/groups', {}) + }, + + /** 获取用户组成员列表(仅管理员可访问) */ + getGroupMembers(groupId, params = {}) { + return api.post('/admin/group_members', { group_id: groupId, ...params }) + }, + + /** 获取登录失败日志(仅管理员可访问) */ + getLoginFailLogs(params = {}) { + return api.post('/admin/login_fail_logs', params) + }, + /** 修改密码 */ changePassword(oldPass, newPass) { return api.post('/users/changePassword', { oldpass: oldPass, newpass: newPass }) diff --git a/frontend/ops_vue_js/src/components/AppHeader.vue b/frontend/ops_vue_js/src/components/AppHeader.vue index 6fb7f73..df38777 100644 --- a/frontend/ops_vue_js/src/components/AppHeader.vue +++ b/frontend/ops_vue_js/src/components/AppHeader.vue @@ -153,7 +153,7 @@ const navItems = computed(() => [ @@ -228,7 +228,7 @@ const navItems = computed(() => [ diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index db57124..60a3b9b 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -203,7 +203,6 @@ "back_to_list": "Back to List", "root": "Warehouse", "item_detail": "Item Detail", - "edit_item": "Edit Item", "move_item": "Move Item", "move_history": "Move History", "work_orders": "Work Orders", @@ -364,7 +363,6 @@ "old_pass_incorrect": "Old password is incorrect", "confirm_password_incorrect": "Confirm password is incorrect", "confirm": "Confirm", - "cancel": "Cancel", "save_success": "Saved successfully", "submit": "Submit", "submitting": "Submitting...", diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index 476a3c9..fba8882 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -203,7 +203,6 @@ "back_to_list": "返回列表", "root": "仓库", "item_detail": "物品详情", - "edit_item": "编辑物品", "move_item": "移动物品", "move_history": "移动历史", "work_orders": "关联工单", @@ -363,7 +362,6 @@ "old_pass_incorrect": "旧密码不正确", "confirm_password_incorrect": "确认密码不正确", "confirm": "确认", - "cancel": "取消", "delete_ok": "删除成功", "save_success": "保存成功", "submit": "提交", diff --git a/frontend/ops_vue_js/src/router/index.js b/frontend/ops_vue_js/src/router/index.js index 734fec5..cab747b 100644 --- a/frontend/ops_vue_js/src/router/index.js +++ b/frontend/ops_vue_js/src/router/index.js @@ -109,7 +109,7 @@ const router = createRouter({ component: () => import('@/views/AdminView.vue'), }, { - path: 'admin', + path: 'sysadmin', name: 'sysadmin', component: () => import('@/views/SysAdminView.vue'), meta: { requireSysAdmin: true }, diff --git a/frontend/ops_vue_js/src/stores/users.js b/frontend/ops_vue_js/src/stores/users.js index 988d1a9..8e22d20 100644 --- a/frontend/ops_vue_js/src/stores/users.js +++ b/frontend/ops_vue_js/src/stores/users.js @@ -64,7 +64,7 @@ export const useUsersStore = defineStore('users', () => { } return { - usersInfo, getUsernameFromUserID, getAvatarUrlFromUserID, + usersInfo, getUsernameFromUserID, getAvatarUrlFromUserID, fetchUser, } }) diff --git a/frontend/ops_vue_js/src/views/SysAdminView.vue b/frontend/ops_vue_js/src/views/SysAdminView.vue index 883c9f1..1a0c0d6 100644 --- a/frontend/ops_vue_js/src/views/SysAdminView.vue +++ b/frontend/ops_vue_js/src/views/SysAdminView.vue @@ -1,18 +1,47 @@ @@ -73,35 +263,384 @@ onMounted(() => {
-

用户管理

-
-

sysadmin_users_placeholder

+
+

用户管理

+ + 共 {{ userTotal }} 位用户 + +
+ + +
+
+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID用户名邮箱类型注册时间操作
加载中...
暂无用户
{{ user.id }} +
+ avatar + + {{ usersStore.getUsernameFromUserID(user.id) || user.name }} + +
+
{{ user.email }} + + {{ user.type }} + + {{ new Date(user.date).toLocaleString() }} + +
+
+ + +
+
+ 第 {{ userPage }} 页,共 {{ totalPages }} 页 +
+
+ + +
-

用户组管理

-
-

sysadmin_groups_placeholder

+
+

用户组管理

+ +
+ +
+ +
+
+
+ 用户组列表 +
+
+ 加载中... +
+
+ 暂无用户组 +
+
+ +
+
+
+ + +
+
+

请选择一个用户组查看成员

+
+
+
+
+

{{ selectedGroup.name }}

+

共 {{ groupMemberTotal }} 位成员

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
用户邮箱类型
加载中...
暂无成员
+
+ avatar + + {{ usersStore.getUsernameFromUserID(member.id) || member.name }} + +
+
{{ member.email }} + + {{ member.type }} + +
+
+ + +
+
+ 第 {{ groupMemberPage }} 页,共 {{ groupMemberTotalPages }} 页 +
+
+ + +
+
+
+
-

登录失败日志

-
-

sysadmin_logs_placeholder

+
+

登录失败日志

+ + 共 {{ loginFailLogTotal }} 条记录 + +
+ + +
+
+ + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
用户名失败原因连续次数IP地址最后时间首次时间
加载中...
暂无登录失败记录
+
+ avatar +
+ + {{ log.username }} + +
+
+ + {{ formatReason(log.reason) }} + + + + {{ log.count }} + + {{ log.ip }}{{ new Date(log.updated_at).toLocaleString() }}{{ new Date(log.created_at).toLocaleString() }}
+
+ + +
+
+ 第 {{ loginFailLogPage }} 页,共 {{ loginFailLogTotalPages }} 页 +
+
+ + +
- -
-

系统配置

-
-

sysadmin_config_placeholder

-
-
@@ -117,13 +656,20 @@ onMounted(() => {
- - ID: {{ adminId }} - + avatar + + {{ usersStore.getUsernameFromUserID(adminId) || 'ID: ' + adminId }} + +
暂无系统管理员