diff --git a/.workbuddy/memory/2026-04-28.md b/.workbuddy/memory/2026-04-28.md
index 349c0aa..bf93ab6 100644
--- a/.workbuddy/memory/2026-04-28.md
+++ b/.workbuddy/memory/2026-04-28.md
@@ -49,3 +49,12 @@
2. 查询该组所有成员的 `TabUserGroupBinds` 记录
3. 提取所有 `UserID` 更新到 `sysAdmins` 缓存切片
4. 组不存在或查询失败时清空缓存
+
+## Web 前端系统管理员入口
+
+- 后端 `getinfo` 接口返回 `isSysAdmin` 布尔值(不再暴露完整管理员列表)
+- 后端新增 `POST /users/sysadmins` 接口,仅系统管理员可访问,返回完整 `sysAdmins` 数组
+- 前端 `userStore`:`isSysAdmin` 改为 ref,直接从后端获取
+- `AppHeader.vue` 用户菜单:当 `isSysAdmin` 为 true 时显示「系统管理」入口(琥珀色盾牌图标)
+- 新建 `SysAdminView.vue`:4 个标签页占位符(用户管理、用户组、登录日志、系统配置),页面内调用 `authApi.sysAdmins()` 获取管理员列表
+- 路由 `/sysadmin`:添加 `requireSysAdmin` 元信息,路由守卫拦截非管理员访问
diff --git a/backend/my_work/routers/apiUsers.go b/backend/my_work/routers/apiUsers.go
index 0317331..e1a706f 100644
--- a/backend/my_work/routers/apiUsers.go
+++ b/backend/my_work/routers/apiUsers.go
@@ -575,8 +575,6 @@ func ApiUser(r *gin.RouterGroup) {
isAuth, user, _ := AuthenticationAuthority(ctx)
if isAuth {
//载入用户info
-
- //fmt.Println(userInfo)
var redata map[string]interface{} = make(map[string]interface{})
info := GetUserInfoFromUserID(user.ID)
@@ -586,10 +584,47 @@ func ApiUser(r *gin.RouterGroup) {
user.Salt = ""
redata["user"] = user
+ // 只返回当前用户是否为系统管理员,不暴露完整列表
+ isSysAdmin := false
+ for _, adminID := range sysAdmins {
+ if adminID == user.ID {
+ isSysAdmin = true
+ break
+ }
+ }
+ redata["isSysAdmin"] = isSysAdmin
+
ReturnJson(ctx, "apiOK", redata)
}
})
+
+ // 获取系统管理员列表(仅系统管理员可访问)
+ r.POST("/sysadmins", func(ctx *gin.Context) {
+ isAuth, user, _ := AuthenticationAuthority(ctx)
+ if !isAuth {
+ ReturnJson(ctx, "userNoLogin", nil)
+ return
+ }
+
+ // 检查是否为系统管理员
+ isSysAdmin := false
+ for _, adminID := range sysAdmins {
+ if adminID == user.ID {
+ isSysAdmin = true
+ break
+ }
+ }
+ if !isSysAdmin {
+ ReturnJson(ctx, "permission_denied", nil)
+ return
+ }
+
+ var redata map[string]interface{} = make(map[string]interface{})
+ redata["sysAdmins"] = sysAdmins
+ ReturnJson(ctx, "apiOK", redata)
+ })
+
//用户登陆
r.POST("/login", func(ctx *gin.Context) {
var loginuser From_user_login
diff --git a/frontend/ops_vue_js/src/api/auth.js b/frontend/ops_vue_js/src/api/auth.js
index 489ab6b..0e23db7 100644
--- a/frontend/ops_vue_js/src/api/auth.js
+++ b/frontend/ops_vue_js/src/api/auth.js
@@ -16,6 +16,11 @@ export const authApi = {
return api.post('/users/getinfo', {})
},
+ /** 获取系统管理员列表(仅管理员可访问) */
+ sysAdmins() {
+ return api.post('/users/sysadmins', {})
+ },
+
/** 修改密码 */
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 b35ce43..6e4dd73 100644
--- a/frontend/ops_vue_js/src/components/AppHeader.vue
+++ b/frontend/ops_vue_js/src/components/AppHeader.vue
@@ -11,6 +11,7 @@ import {
IconSettings,
IconMenu2,
IconX,
+ IconShield,
} from "@tabler/icons-vue";
const { t, locale } = useI18n();
@@ -149,6 +150,15 @@ const navItems = computed(() => [