From a3331a1def8c0d0cc282fa973d42aa247c0f8e08 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:59:02 +0800 Subject: [PATCH] up --- backend/my_work/routers/apiSysAdmin.go | 116 +++++++++- frontend/ops_vue_js/src/api/auth.js | 10 + .../ops_vue_js/src/views/SysAdminView.vue | 198 +++++++++++++++++- 3 files changed, 314 insertions(+), 10 deletions(-) diff --git a/backend/my_work/routers/apiSysAdmin.go b/backend/my_work/routers/apiSysAdmin.go index 6266c79..757da2b 100644 --- a/backend/my_work/routers/apiSysAdmin.go +++ b/backend/my_work/routers/apiSysAdmin.go @@ -157,7 +157,7 @@ func ApiSysAdmin(r *gin.RouterGroup) { } var params struct { - GroupID uint `json:"group_id"` + GroupID uint `json:"group_id"` Page int `json:"page"` PageSize int `json:"page_size"` } @@ -209,14 +209,112 @@ func ApiSysAdmin(r *gin.RouterGroup) { } 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) -}) + 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("/user_detail", func(ctx *gin.Context) { + isAuth, _, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + var params struct { + UserID uint `json:"user_id"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil || params.UserID == 0 { + ReturnJson(ctx, "parameErr", nil) + return + } + + // 获取用户基本信息 + var user TabUser + if models.DB.First(&user, params.UserID).Error != nil { + ReturnJson(ctx, "userNotFound", nil) + return + } + + // 获取用户扩展信息 + userInfo := GetUserInfoFromUserID(user.ID) + + // 构建返回数据 + redata := map[string]interface{}{ + "user": map[string]interface{}{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "type": user.Type, + "date": user.Date, + }, + "userinfo": userInfo, + } + + ReturnJson(ctx, "apiOK", redata) + }) + + // 重置用户密码(仅系统管理员可访问) + r.POST("/reset_user_password", func(ctx *gin.Context) { + isAuth, adminUser, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + // 检查是否为系统管理员 + if !SysAdminCheck(adminUser.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + + var params struct { + UserID float64 `json:"user_id" mapstructure:"user_id"` + Password string `json:"password" mapstructure:"password"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil || params.UserID == 0 || params.Password == "" { + ReturnJson(ctx, "parameErr", map[string]interface{}{"decode_err": err != nil, "user_id": params.UserID, "pass_empty": params.Password == ""}) + return + } + + // 查找目标用户 + var targetUser TabUser + if models.DB.First(&targetUser, uint(params.UserID)).Error != nil { + ReturnJson(ctx, "userNotFound", nil) + return + } + + // 生成新盐值并哈希密码 + newSalt := models.RandStr32() + tempUser := TabUser{ + Pass: params.Password, + Salt: newSalt, + } + HashUserPass(&tempUser) + + // 更新密码和盐值 + updates := TabUser{ + Pass: tempUser.Pass, + Salt: newSalt, + } + if err := models.DB.Model(&targetUser).Updates(&updates).Error; err != nil { + ReturnJson(ctx, "dbErr", nil) + return + } + + // 注销该用户的所有 cookie(强制重新登录) + if err := models.DB.Where("user_id = ?", targetUser.ID).Delete(&TabUserCookie{}).Error; err != nil { + // 删除 cookie 失败不影响密码修改结果,仅记录 + //fmt.Println("删除用户 cookie 失败:", err) + } + + ReturnJson(ctx, "apiOK", nil) + }) // 获取登录失败日志(仅系统管理员可访问) r.POST("/login_fail_logs", func(ctx *gin.Context) { diff --git a/frontend/ops_vue_js/src/api/auth.js b/frontend/ops_vue_js/src/api/auth.js index 47752b9..f657fee 100644 --- a/frontend/ops_vue_js/src/api/auth.js +++ b/frontend/ops_vue_js/src/api/auth.js @@ -36,6 +36,16 @@ export const authApi = { return api.post('/admin/group_members', { group_id: groupId, ...params }) }, + /** 获取用户详细信息(仅管理员可访问) */ + getUserDetail(userId) { + return api.post('/admin/user_detail', { user_id: userId }) + }, + + /** 重置用户密码(仅管理员可访问) */ + resetUserPassword(userId, password) { + return api.post('/admin/reset_user_password', { user_id: userId, password }) + }, + /** 获取登录失败日志(仅管理员可访问) */ getLoginFailLogs(params = {}) { return api.post('/admin/login_fail_logs', params) diff --git a/frontend/ops_vue_js/src/views/SysAdminView.vue b/frontend/ops_vue_js/src/views/SysAdminView.vue index 1a0c0d6..2fca75b 100644 --- a/frontend/ops_vue_js/src/views/SysAdminView.vue +++ b/frontend/ops_vue_js/src/views/SysAdminView.vue @@ -2,9 +2,12 @@ import { ref, onMounted, watch, computed } from 'vue' import { useUserStore } from '@/stores/user' import { useUsersStore } from '@/stores/users' +import { useToastStore } from '@/stores/toast' import { authApi } from '@/api/auth' import { IconSearch, IconRefresh, IconChevronLeft, IconChevronRight } from '@tabler/icons-vue' +const toast = useToastStore() + const usersStore = useUsersStore() const userStore = useUserStore() @@ -38,6 +41,14 @@ const loginFailLogPage = ref(1) const loginFailLogPageSize = ref(20) const loginFailLogTotal = ref(0) +// 用户详情相关 +const showUserDetail = ref(false) +const userDetail = ref(null) +const userDetailInfo = ref(null) +const userDetailLoading = ref(false) +const newPassword = ref('') +const resetPasswordLoading = ref(false) + const tabs = [ { id: 'users', label: '用户管理' }, { id: 'groups', label: '用户组' }, @@ -204,6 +215,64 @@ function getReasonClass(reason) { return 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300' } +async function openUserDetail(user) { + userDetail.value = user + showUserDetail.value = true + userDetailLoading.value = true + try { + // 获取用户详细信息 + const res = await authApi.getUserDetail(user.id) + if (res.errCode === 0) { + userDetail.value = res.data.user || user + userDetailInfo.value = res.data.userinfo || null + } + } catch { + // 错误已由拦截器处理 + } finally { + userDetailLoading.value = false + } +} + +function closeUserDetail() { + showUserDetail.value = false + userDetail.value = null + userDetailInfo.value = null + newPassword.value = '' +} + +async function resetUserPassword() { + if (!newPassword.value || newPassword.value.length < 6) { + toast.warning('密码长度至少为6位') + return + } + if (!userDetail.value) return + + resetPasswordLoading.value = true + try { + const res = await authApi.resetUserPassword(userDetail.value.id, newPassword.value) + if (res.errCode === 0) { + toast.success('密码修改成功') + newPassword.value = '' + } else { + toast.error(res.raw?.err_msg || '密码修改失败') + } + } catch { + // 错误已由拦截器处理 + } finally { + resetPasswordLoading.value = false + } +} + +function formatDate(dateStr) { + if (!dateStr) return '-' + return new Date(dateStr).toLocaleDateString() +} + +function formatGender(gender) { + const map = { 'M': '男', 'F': '女', 'U': '未知' } + return map[gender] || '未知' +} + // 监听 Tab 切换 watch(activeTab, (tab) => { if (tab === 'users') { @@ -344,7 +413,7 @@ onMounted(() => { {{ new Date(user.date).toLocaleString() }} - + @@ -677,4 +746,131 @@ onMounted(() => { + + +
+
+
+

用户详情

+ +
+ +
+ 加载中... +
+ +
+ +
+ avatar +
+
+ {{ usersStore.getUsernameFromUserID(userDetail.id) || userDetail.name }} +
+
{{ userDetail.email }}
+ + {{ userDetail.type }} + +
+
+ +
+ + +
+
+ 用户ID + {{ userDetail.id }} +
+
+ 用户名 + {{ userDetail.name }} +
+
+ 注册时间 + {{ new Date(userDetail.date).toLocaleString() }} +
+ + + +
+
+ + +
+
重置密码
+
+ + +
+
+ +
+ +
+
+