From 6add9abcea71d1b70401e89a5e8280c8df7cc558 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 09:22:39 +0800 Subject: [PATCH 01/23] Make Set-cookie not HttpOnly --- adminControllor/adminLogin.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adminControllor/adminLogin.go b/adminControllor/adminLogin.go index f9249a8..1e041ac 100644 --- a/adminControllor/adminLogin.go +++ b/adminControllor/adminLogin.go @@ -44,10 +44,10 @@ func AdminLogin(c *gin.Context) { //Set cookie (must be before c.JSON, otherwise headers are already flushed) //admin_token 保持 httpOnly(JS 不可读,防 XSS);admin_name 仅用于前端展示,允许 JS 读取 if body.IsRemember { - c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true) + c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, false) c.SetCookie("admin_name", "root", 30*24*60*60, "/", "", false, false) } else { - c.SetCookie("admin_token", token, 0, "/", "", false, true) + c.SetCookie("admin_token", token, 0, "/", "", false, false) c.SetCookie("admin_name", "root", 0, "/", "", false, false) } c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "login success", "token": token}) @@ -76,10 +76,10 @@ func AdminLogin(c *gin.Context) { //Set cookie (must be before c.JSON, otherwise headers are already flushed) //admin_token 保持 httpOnly(JS 不可读,防 XSS);admin_name 仅用于前端展示,允许 JS 读取 if body.IsRemember { - c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, true) + c.SetCookie("admin_token", token, 30*24*60*60, "/", "", false, false) c.SetCookie("admin_name", admin.Username, 30*24*60*60, "/", "", false, false) } else { - c.SetCookie("admin_token", token, 0, "/", "", false, true) + c.SetCookie("admin_token", token, 0, "/", "", false, false) c.SetCookie("admin_name", admin.Username, 0, "/", "", false, false) } From 8e3682fb3c56cfac047ef26716b689827aa90fb0 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 09:43:36 +0800 Subject: [PATCH 02/23] Change request method --- GAHFrontend/src/components/login/login.vue | 4 +-- GAHFrontend/src/components/main/bar.vue | 11 +++---- .../src/components/main/page/admin.vue | 13 ++++---- .../src/components/main/page/character.vue | 4 +-- GAHFrontend/src/components/main/page/home.vue | 6 ++-- GAHFrontend/src/components/main/page/user.vue | 4 +-- router/router.go | 30 +++++++++---------- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/GAHFrontend/src/components/login/login.vue b/GAHFrontend/src/components/login/login.vue index e9fec19..5ef402c 100644 --- a/GAHFrontend/src/components/login/login.vue +++ b/GAHFrontend/src/components/login/login.vue @@ -36,7 +36,7 @@ import { reactive, ref, onMounted } from 'vue' import { useRouter } from 'vue-router' import { User, Lock } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' -import { post, get } from '../../lib/request' +import { post } from '../../lib/request' const router = useRouter() const loginFormRef = ref() @@ -62,7 +62,7 @@ const rules = { // 已登录则直接跳转主页 onMounted(async () => { try { - await get('/admin/check_token', {}, { showError: false, autoRedirect401: false }) + await post('/admin/check_token', {}, { showError: false, autoRedirect401: false }) router.push('/main/home') } catch { // 未登录,留在登录页 diff --git a/GAHFrontend/src/components/main/bar.vue b/GAHFrontend/src/components/main/bar.vue index 3220f95..b3ac187 100644 --- a/GAHFrontend/src/components/main/bar.vue +++ b/GAHFrontend/src/components/main/bar.vue @@ -65,7 +65,8 @@ import { computed, onMounted, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { Odometer, Fold, Expand, User, SetUp, Grid } from '@element-plus/icons-vue' -import { get, post, put, del } from '../../lib/request' +import { post, put, del } from '../../lib/request' +import VueCookies from 'vue-cookies' const route = useRoute() const router = useRouter() @@ -83,8 +84,8 @@ const handleLogout = async () => { // 即使后端出错也继续清理并跳转 } // 删除本地 cookie - document.cookie = 'admin_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;' - document.cookie = 'admin_name=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;' + VueCookies.remove('admin_token') + VueCookies.remove('admin_name') router.push('/') } @@ -110,13 +111,13 @@ const canGetCharacter = computed(() => { onMounted(()=>{ // Check Admin Token - get('/admin/check_token').then(respond=>{ + post('/admin/check_token').then(respond=>{ if(respond.code === 200){ }else{ } }) // Get Admin Info - get('/admin/info').then(respond=>{ + post('/admin/info').then(respond=>{ if(respond.code === 200){ adminInfo.value = respond.data } diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index 068743e..a83cdb8 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -13,8 +13,10 @@
@@ -27,12 +29,13 @@ diff --git a/GAHFrontend/src/lib/rangeAdmin.js b/GAHFrontend/src/lib/rangeAdmin.js new file mode 100644 index 0000000..7def547 --- /dev/null +++ b/GAHFrontend/src/lib/rangeAdmin.js @@ -0,0 +1,12 @@ +import { post } from './request.js' + +const rangeAdmin = async (condition, start, length) => { + const res = await post('/admin/range', { + begin_table_id: start, + length: length, + search_condition: condition + }) + return res.data +} + +export default rangeAdmin From 16b01b8c4b6c07c55fff9a7ac49826a860e5023a Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 10:09:54 +0800 Subject: [PATCH 05/23] Add getting count, need token --- adminControllor/CharacterCount.go | 28 ++++++++++++++++++++++++++++ adminControllor/adminCount.go | 28 ++++++++++++++++++++++++++++ adminControllor/userCount.go | 28 ++++++++++++++++++++++++++++ router/router.go | 12 +++++++++--- 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 adminControllor/CharacterCount.go create mode 100644 adminControllor/adminCount.go create mode 100644 adminControllor/userCount.go diff --git a/adminControllor/CharacterCount.go b/adminControllor/CharacterCount.go new file mode 100644 index 0000000..66da2f5 --- /dev/null +++ b/adminControllor/CharacterCount.go @@ -0,0 +1,28 @@ +package adminControllor + +import ( + "net/http" + + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func CharacterCount(c *gin.Context) { + //Get Db + db := c.Value("db").(*gorm.DB) + + //Count Characters (soft-deleted rows are excluded automatically) + var characterCount int64 + if err := db.Model(&sqlTable.Character{}).Count(&characterCount).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed To Count Characters"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "data": gin.H{ + "character_count": characterCount, + }, + }) +} diff --git a/adminControllor/adminCount.go b/adminControllor/adminCount.go new file mode 100644 index 0000000..35d88c6 --- /dev/null +++ b/adminControllor/adminCount.go @@ -0,0 +1,28 @@ +package adminControllor + +import ( + "net/http" + + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func AdminCount(c *gin.Context) { + //Get Db + db := c.Value("db").(*gorm.DB) + + //Count Admins (soft-deleted rows are excluded automatically) + var adminCount int64 + if err := db.Model(&sqlTable.Admin{}).Count(&adminCount).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed To Count Admins"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "data": gin.H{ + "admin_count": adminCount, + }, + }) +} diff --git a/adminControllor/userCount.go b/adminControllor/userCount.go new file mode 100644 index 0000000..3f33631 --- /dev/null +++ b/adminControllor/userCount.go @@ -0,0 +1,28 @@ +package adminControllor + +import ( + "net/http" + + "github.com/TrafficLight6/GoAccountHub/sqlTable" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func UserCount(c *gin.Context) { + //Get Db + db := c.Value("db").(*gorm.DB) + + //Count Users (soft-deleted rows are excluded automatically) + var userCount int64 + if err := db.Model(&sqlTable.User{}).Count(&userCount).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": "Failed To Count Users"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "data": gin.H{ + "user_count": userCount, + }, + }) +} diff --git a/router/router.go b/router/router.go index 6858071..552a187 100644 --- a/router/router.go +++ b/router/router.go @@ -42,29 +42,35 @@ func ReturnRouter(config config.Config) (*gin.Engine, *gorm.DB) { v1.POST("/admin/check_token", middleware.AdminCheckMiddleware(), checkControllor.CheckAdminToken) //Admin Info v1.POST("/admin/info", middleware.AdminCheckMiddleware(), adminControllor.AdminInfo) + //Admin Count + v1.POST("/admin/count", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminCount) //Admin Operation v1.POST("/admin/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_add_admin"), adminControllor.AdminAdd) v1.DELETE("/admin/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_delete_admin"), adminControllor.AdminDelete) v1.PUT("/admin/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_edit_admin"), adminControllor.AdminEdit) v1.POST("/admin/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminGet) - v1.POST("/admin/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin_range"), adminControllor.AdminRange) + v1.POST("/admin/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_get_admin"), adminControllor.AdminRange) + //User Count + v1.POST("/user/count", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserCount) //User Api v1.POST("/user/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserAdd) v1.DELETE("/user/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserDelete) v1.PUT("/user/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserEdit) v1.POST("/user/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserGet) - v1.POST("/user/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user_range"), adminControllor.UserRange) + v1.POST("/user/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_user"), adminControllor.UserRange) //Character Api //Check is Allowed to Use Multi Character Functions v1.Use(middleware.ConfigBlocker("allow_multi_character")) + //Character Count + v1.POST("/character/count", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterCount) //Character Operation v1.POST("/character/add", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterAdd) v1.DELETE("/character/delete", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterDelete) v1.PUT("/character/edit", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterEdit) v1.POST("/character/get", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterGet) - v1.POST("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character_range"), adminControllor.CharacterRange) + v1.POST("/character/range", middleware.AdminCheckMiddleware(), middleware.AdminPermissionCheckMiddleware("can_operate_character"), adminControllor.CharacterRange) } { From b338b9dbc9bfbd2935f3f3ed626812823a8a65d9 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 10:25:50 +0800 Subject: [PATCH 06/23] Remove password hash search --- adminControllor/adminRange.go | 9 ++------- adminControllor/characterRange.go | 5 ----- adminControllor/userRanger.go | 9 ++------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/adminControllor/adminRange.go b/adminControllor/adminRange.go index 68a601c..c5dc069 100644 --- a/adminControllor/adminRange.go +++ b/adminControllor/adminRange.go @@ -18,9 +18,8 @@ type AdminRangeRequestBody struct { type AdminSearchCondition struct { //If The Field is Empty, It Means Ignore This Condition - Username string `json:"username"` - PasswordHash string `json:"password_hash"` - UUHash string `json:"uu_hash"` + Username string `json:"username"` + UUHash string `json:"uu_hash"` } func AdminRange(c *gin.Context) { @@ -57,10 +56,6 @@ func AdminRange(c *gin.Context) { conditions = append(conditions, "username LIKE ?") args = append(args, "%"+condition.Username+"%") } - if condition.PasswordHash != "" { - conditions = append(conditions, "password_hash LIKE ?") - args = append(args, "%"+condition.PasswordHash+"%") - } if condition.UUHash != "" { conditions = append(conditions, "uu_hash LIKE ?") args = append(args, "%"+condition.UUHash+"%") diff --git a/adminControllor/characterRange.go b/adminControllor/characterRange.go index ad63cff..82fc4af 100644 --- a/adminControllor/characterRange.go +++ b/adminControllor/characterRange.go @@ -19,7 +19,6 @@ type CharacterRangeRequestBody struct { type CharacterSearchCondition struct { //If The Field is Empty, It Means Ignore This Condition CharacterName string `json:"character_name"` - PasswordHash string `json:"password_hash"` UserUUHash string `json:"user_uu_hash"` UUHash string `json:"uu_hash"` } @@ -52,10 +51,6 @@ func CharacterRange(c *gin.Context) { conditions = append(conditions, "character_name LIKE ?") args = append(args, "%"+condition.CharacterName+"%") } - if condition.PasswordHash != "" { - conditions = append(conditions, "password_hash LIKE ?") - args = append(args, "%"+condition.PasswordHash+"%") - } if condition.UserUUHash != "" { conditions = append(conditions, "user_uu_hash LIKE ?") args = append(args, "%"+condition.UserUUHash+"%") diff --git a/adminControllor/userRanger.go b/adminControllor/userRanger.go index c18b681..fd78512 100644 --- a/adminControllor/userRanger.go +++ b/adminControllor/userRanger.go @@ -18,9 +18,8 @@ type UserRangeRequestBody struct { type UserSearchCondition struct { //If The Field is Empty, It Means Ignore This Condition - Username string `json:"username"` - PasswordHash string `json:"password_hash"` - UUHash string `json:"uu_hash"` + Username string `json:"username"` + UUHash string `json:"uu_hash"` } func UserRange(c *gin.Context) { @@ -57,10 +56,6 @@ func UserRange(c *gin.Context) { conditions = append(conditions, "username LIKE ?") args = append(args, "%"+condition.Username+"%") } - if condition.PasswordHash != "" { - conditions = append(conditions, "password_hash LIKE ?") - args = append(args, "%"+condition.PasswordHash+"%") - } if condition.UUHash != "" { conditions = append(conditions, "uu_hash LIKE ?") args = append(args, "%"+condition.UUHash+"%") From fbe1d6d756800bb41ef9f7705a0de2f517e0601b Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 11:08:42 +0800 Subject: [PATCH 07/23] Add dialog from --- .../customize/AddAdminDialogFrom.vue | 52 ++++++ .../customize/EditAdminDialogFrom.vue | 68 +++++++ .../src/components/main/page/admin.vue | 174 ++++++++++++++++-- 3 files changed, 277 insertions(+), 17 deletions(-) create mode 100644 GAHFrontend/src/components/customize/AddAdminDialogFrom.vue create mode 100644 GAHFrontend/src/components/customize/EditAdminDialogFrom.vue diff --git a/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue b/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue new file mode 100644 index 0000000..4694858 --- /dev/null +++ b/GAHFrontend/src/components/customize/AddAdminDialogFrom.vue @@ -0,0 +1,52 @@ + + + diff --git a/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue b/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue new file mode 100644 index 0000000..ed15606 --- /dev/null +++ b/GAHFrontend/src/components/customize/EditAdminDialogFrom.vue @@ -0,0 +1,68 @@ + + + diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index 0f9b640..f9df458 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -3,23 +3,37 @@ -

-

+

+

权限筛选:

- - - - - - + + + + + +
+ From a73b9a586756d9bfa7a3295c26b7814f9475a413 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 11:17:45 +0800 Subject: [PATCH 08/23] Fix: Searching cannot select data not being loaded --- .../src/components/main/page/admin.vue | 70 ++++++++----------- adminControllor/adminRange.go | 27 +++++++ 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index f9df458..30d160b 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -41,10 +41,10 @@
@@ -67,12 +67,13 @@ import { ElCheckbox, ElButton, ElMessageBox } from 'element-plus' const router = useRouter() const adminInfo = ref({}) const adminList = ref([]) -const adminCount = ref(0) const PAGE_SIZE = 100 const loading = ref(false) +// 后端是否已无更多数据 +const noMore = ref(false) -// 筛选条件表单(仅作用于前端已加载数据) +// 筛选条件表单(提交后由后端筛选) const searchForm = reactive({ username: '', uu_hash: '', @@ -86,21 +87,8 @@ const searchForm = reactive({ }, }) -// 已应用的筛选条件,null 表示未筛选 -const appliedFilter = ref(null) - -// 表格实际展示的数据:未筛选时为全部已加载数据 -const displayList = computed(() => { - const filter = appliedFilter.value - if (!filter) return adminList.value - - const perms = Object.keys(filter.permission).filter((key) => filter.permission[key]) - return adminList.value.filter((row) => { - if (filter.username && !(row.Username ?? '').includes(filter.username)) return false - if (filter.uu_hash && !(row.UUHash ?? '').includes(filter.uu_hash)) return false - return perms.every((key) => row.Permission?.[key]) - }) -}) +// 当前生效的筛选条件,随每次 range 请求发给后端 +const searchCondition = ref({}) // 添加管理员弹窗 const dialogVisible = ref(false) @@ -109,9 +97,6 @@ const dialogVisible = ref(false) const editVisible = ref(false) const editRow = ref(null) -// 已加载项数是否已等于总数 -const allLoaded = computed(() => adminCount.value > 0 && adminList.value.length >= adminCount.value) - const canGetAdmin = computed(() => { if (adminInfo.value.is_root) return true return Boolean(adminInfo.value.permission?.can_get_admin) @@ -123,9 +108,9 @@ const permCell = (key) => ({ rowData }) => h('span', rowData.Permission?.[key] ? // 勾选状态:以行 ID 记录 const selectedIds = ref([]) const isSelected = (id) => selectedIds.value.includes(id) -// 全选判定基于当前展示的数据(受筛选影响) +// 全选判定基于当前数据 const allSelected = computed( - () => displayList.value.length > 0 && displayList.value.every((row) => isSelected(row.ID)) + () => adminList.value.length > 0 && adminList.value.every((row) => isSelected(row.ID)) ) const toggleRow = (id) => { @@ -135,7 +120,7 @@ const toggleRow = (id) => { } const toggleAll = () => { - const shownIds = displayList.value.map((item) => item.ID) + const shownIds = adminList.value.map((item) => item.ID) selectedIds.value = allSelected.value ? selectedIds.value.filter((id) => !shownIds.includes(id)) : [...new Set([...selectedIds.value, ...shownIds])] @@ -146,23 +131,30 @@ const handleCheckboxCancel = () => { selectedIds.value = [] } -// 查询:仅在前端已加载数据中筛选 +// 查询:把筛选条件交给后端,重新从第一页取 const handleSearch = () => { - appliedFilter.value = { + searchCondition.value = { username: searchForm.username.trim(), uu_hash: searchForm.uu_hash.trim(), - permission: { ...searchForm.permission }, + can_add_admin: searchForm.permission.can_add_admin, + can_delete_admin: searchForm.permission.can_delete_admin, + can_edit_admin: searchForm.permission.can_edit_admin, + can_get_admin: searchForm.permission.can_get_admin, + can_operate_user: searchForm.permission.can_operate_user, + can_operate_character: searchForm.permission.can_operate_character, } + range(1) } -// 重置:清空筛选表单并取消已应用的筛选 +// 重置:清空筛选条件并重新从第一页取全部 const handleReset = () => { searchForm.username = '' searchForm.uu_hash = '' Object.keys(searchForm.permission).forEach((key) => { searchForm.permission[key] = false }) - appliedFilter.value = null + searchCondition.value = {} + range(1) } // 打开添加管理员弹窗 @@ -237,8 +229,10 @@ const range = async (start = 1) => { if (loading.value) return loading.value = true try { - const list = await rangeAdmin({}, start, PAGE_SIZE) - adminList.value = [...adminList.value, ...list].sort((a, b) => a.ID - b.ID) + const list = await rangeAdmin(searchCondition.value, start, PAGE_SIZE) + const merged = start === 1 ? list : [...adminList.value, ...list] + adminList.value = merged.sort((a, b) => a.ID - b.ID) + noMore.value = list.length < PAGE_SIZE } catch { // 401 已由 request 封装自动跳转登录页 } finally { @@ -246,9 +240,9 @@ const range = async (start = 1) => { } } -// 滚动到底部:未全部加载则继续请求下一页 100 行 +// 滚动到底部:后端还有数据时继续请求下一页 100 行 const handleEndReached = () => { - if (loading.value || allLoaded.value) return + if (loading.value || noMore.value) return range(Math.floor(adminList.value.length / PAGE_SIZE) + 1) } @@ -264,12 +258,6 @@ onMounted(async () => { router.push('/') return } - try { - const res = await post('/admin/count') - adminCount.value = res.data.admin_count - } catch { - // 401 已由 request 封装自动跳转登录页,网络错误时不再继续鉴权 - } range() }) diff --git a/adminControllor/adminRange.go b/adminControllor/adminRange.go index c5dc069..14c4cb1 100644 --- a/adminControllor/adminRange.go +++ b/adminControllor/adminRange.go @@ -20,6 +20,14 @@ type AdminSearchCondition struct { //If The Field is Empty, It Means Ignore This Condition Username string `json:"username"` UUHash string `json:"uu_hash"` + + //Permission Filter (Bool, false means ignore) + CanAddAdmin bool `json:"can_add_admin"` + CanDeleteAdmin bool `json:"can_delete_admin"` + CanEditAdmin bool `json:"can_edit_admin"` + CanGetAdmin bool `json:"can_get_admin"` + CanOperateUser bool `json:"can_operate_user"` + CanOperateCharacter bool `json:"can_operate_character"` } func AdminRange(c *gin.Context) { @@ -60,6 +68,25 @@ func AdminRange(c *gin.Context) { conditions = append(conditions, "uu_hash LIKE ?") args = append(args, "%"+condition.UUHash+"%") } + //Permission Filter: only checked (true) fields are applied, all combined with AND + if condition.CanAddAdmin { + conditions = append(conditions, "permission ->> 'can_add_admin' = 'true'") + } + if condition.CanDeleteAdmin { + conditions = append(conditions, "permission ->> 'can_delete_admin' = 'true'") + } + if condition.CanEditAdmin { + conditions = append(conditions, "permission ->> 'can_edit_admin' = 'true'") + } + if condition.CanGetAdmin { + conditions = append(conditions, "permission ->> 'can_get_admin' = 'true'") + } + if condition.CanOperateUser { + conditions = append(conditions, "permission ->> 'can_operate_user' = 'true'") + } + if condition.CanOperateCharacter { + conditions = append(conditions, "permission ->> 'can_operate_character' = 'true'") + } query := db.Model(&sqlTable.Admin{}) if len(conditions) > 0 { query = query.Where(strings.Join(conditions, " AND "), args...) From 65484622fc03dc75e1e47eb419d4de6f53a47fce Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 11:35:06 +0800 Subject: [PATCH 09/23] Finish admin page --- .../src/components/main/page/admin.vue | 103 ++++++++++++++---- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index 30d160b..8148bbd 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -29,7 +29,7 @@ 已选中 {{ selectedIds.length }} 条

取消选中 - 删除选中管理员 + 删除选中管理员


@@ -42,7 +42,7 @@ + + diff --git a/GAHFrontend/src/router/index.js b/GAHFrontend/src/router/index.js index 5e034c8..1e91219 100644 --- a/GAHFrontend/src/router/index.js +++ b/GAHFrontend/src/router/index.js @@ -5,6 +5,7 @@ import Home from '../components/main/page/home.vue' import Admin from '../components/main/page/admin.vue' import User from '../components/main/page/user.vue' import Character from '../components/main/page/character.vue' +import ComponentTest from '../components/component_test.vue' const routes = [ { @@ -12,6 +13,11 @@ const routes = [ name: 'Login', component: Login, }, + { + path: '/component', + name: 'ComponentTest', + component: () => import('../components/component_test.vue'), + }, { path: '/main', component: Bar, From 0812b826b5a53c4eadf2e88f05550f61c743d47e Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 13:22:07 +0800 Subject: [PATCH 11/23] Add admin uu_hash copy button --- .../src/components/main/page/admin.vue | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index 8148bbd..f85a09d 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -62,7 +62,8 @@ import AddAdminDialogFrom from '../../customize/AddAdminDialogFrom.vue' import EditAdminDialogFrom from '../../customize/EditAdminDialogFrom.vue' import { h, ref, reactive, computed, onMounted } from 'vue' import { useRouter } from 'vue-router' -import { ElCheckbox, ElButton, ElMessage, ElMessageBox } from 'element-plus' +import { ElCheckbox, ElButton, ElIcon, ElMessage, ElMessageBox } from 'element-plus' +import { DocumentCopy } from '@element-plus/icons-vue' const router = useRouter() const adminInfo = ref({}) @@ -253,6 +254,16 @@ const handleEditSubmit = async (form) => { } } +// 复制文本到剪贴板 +const handleCopy = async (text) => { + try { + await navigator.clipboard.writeText(text) + ElMessage.success('已复制') + } catch { + ElMessage.error('复制失败') + } +} + const columns = [ { key: 'selection', @@ -270,7 +281,19 @@ const columns = [ }, { key: 'ID', dataKey: 'ID', title: 'ID', width: 70 }, { key: 'Username', dataKey: 'Username', title: '用户名', width: 140 }, - { key: 'UUHash', dataKey: 'UUHash', title: 'UUHash', width: 550 }, + { + key: 'UUHash', + title: 'UUHash', + width: 600, + cellRenderer: ({ rowData }) => h('div', { style: 'display: flex; align-items: center; gap: 8px;' }, [ + h('span', rowData.UUHash), + h(ElButton, { + size: 'small', + title: '复制 UUHash', + onClick: () => handleCopy(rowData.UUHash), + }, () => h(ElIcon, null, () => h(DocumentCopy))), + ]), + }, { key: 'can_add_admin', title: '添加管理员', width: 110, cellRenderer: permCell('can_add_admin') }, { key: 'can_delete_admin', title: '删除管理员', width: 110, cellRenderer: permCell('can_delete_admin') }, { key: 'can_edit_admin', title: '修改管理员', width: 110, cellRenderer: permCell('can_edit_admin') }, From 72cb9467c32c8f8fae0fdbe7cb9a3719ecd082b8 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 13:28:05 +0800 Subject: [PATCH 12/23] Change button text --- GAHFrontend/src/components/main/page/admin.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GAHFrontend/src/components/main/page/admin.vue b/GAHFrontend/src/components/main/page/admin.vue index f85a09d..79760ed 100644 --- a/GAHFrontend/src/components/main/page/admin.vue +++ b/GAHFrontend/src/components/main/page/admin.vue @@ -305,8 +305,8 @@ const columns = [ title: '操作', width: 200, cellRenderer: ({ rowData }) => h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ElButton, { type: 'danger', size: 'small', onClick: () => handleDelete(rowData) }, () => '删除用户'), - h(ElButton, { type: 'primary', size: 'small', onClick: () => handleEdit(rowData) }, () => '编辑用户'), + h(ElButton, { type: 'danger', size: 'small', onClick: () => handleDelete(rowData) }, () => '删除管理员'), + h(ElButton, { type: 'primary', size: 'small', onClick: () => handleEdit(rowData) }, () => '编辑管理员'), ]), }, ] From 99fac0f0918117ae6f577b139c8432ff08c326b3 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 13:55:58 +0800 Subject: [PATCH 13/23] Add null args keeping --- adminControllor/userEdit.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/adminControllor/userEdit.go b/adminControllor/userEdit.go index d948f97..697b831 100644 --- a/adminControllor/userEdit.go +++ b/adminControllor/userEdit.go @@ -26,12 +26,6 @@ func UserEdit(c *gin.Context) { } //Get Db db := c.Value("db").(*gorm.DB) - //Check Username or Password is Empty - if body.Username == "" || body.Password == "" { - c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Username or Password Is Empty"}) - c.Abort() - return - } //Check User Exist var userOld sqlTable.User if err := db.Where("uu_hash = ?", body.UUHash).First(&userOld).Error; err != nil { @@ -39,7 +33,7 @@ func UserEdit(c *gin.Context) { c.Abort() return } - //Edit User + //Edit User (Empty Field Means Keep The Old Value) userNew := userOld if body.Username != "" { userNew.Username = body.Username From 687dbe29ec758922c130d708d25d9569cc6cf1d0 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 13:56:29 +0800 Subject: [PATCH 14/23] Fix: fix bug about id arrange --- adminControllor/adminRange.go | 2 ++ adminControllor/userRanger.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/adminControllor/adminRange.go b/adminControllor/adminRange.go index 14c4cb1..7c43d8f 100644 --- a/adminControllor/adminRange.go +++ b/adminControllor/adminRange.go @@ -91,6 +91,8 @@ func AdminRange(c *gin.Context) { if len(conditions) > 0 { query = query.Where(strings.Join(conditions, " AND "), args...) } + //Must Be Ordered, Otherwise Limit/Offset Follows Physical Row Order And Pages Are Not Stable + query = query.Order("id ASC") //Search var admins []sqlTable.Admin if isReturnAll { diff --git a/adminControllor/userRanger.go b/adminControllor/userRanger.go index fd78512..c6545e5 100644 --- a/adminControllor/userRanger.go +++ b/adminControllor/userRanger.go @@ -64,6 +64,8 @@ func UserRange(c *gin.Context) { if len(conditions) > 0 { query = query.Where(strings.Join(conditions, " AND "), args...) } + //Must Be Ordered, Otherwise Limit/Offset Follows Physical Row Order And Pages Are Not Stable + query = query.Order("id ASC") //Search var users []sqlTable.User if isReturnAll { From 9731f4e3c5f5459403ca764a379354685af27b14 Mon Sep 17 00:00:00 2001 From: TrafficLight6 Date: Fri, 11 Sep 2026 13:59:25 +0800 Subject: [PATCH 15/23] Finish user page --- GAHFrontend/src/components/component_test.vue | 4 +- .../components/customize/AddDialogFrom.vue | 41 ++ .../src/components/customize/EditDialog.vue | 60 +++ .../components/customize/MetaDataEditor.vue | 9 + GAHFrontend/src/components/main/page/user.vue | 366 +++++++++++++++++- GAHFrontend/src/lib/rangeUser.js | 12 + 6 files changed, 470 insertions(+), 22 deletions(-) create mode 100644 GAHFrontend/src/components/customize/AddDialogFrom.vue create mode 100644 GAHFrontend/src/components/customize/EditDialog.vue create mode 100644 GAHFrontend/src/lib/rangeUser.js diff --git a/GAHFrontend/src/components/component_test.vue b/GAHFrontend/src/components/component_test.vue index 3f7391d..cc340bc 100644 --- a/GAHFrontend/src/components/component_test.vue +++ b/GAHFrontend/src/components/component_test.vue @@ -1,3 +1 @@ - - \ No newline at end of file + diff --git a/GAHFrontend/src/components/customize/AddDialogFrom.vue b/GAHFrontend/src/components/customize/AddDialogFrom.vue new file mode 100644 index 0000000..f73c73a --- /dev/null +++ b/GAHFrontend/src/components/customize/AddDialogFrom.vue @@ -0,0 +1,41 @@ + + + diff --git a/GAHFrontend/src/components/customize/EditDialog.vue b/GAHFrontend/src/components/customize/EditDialog.vue new file mode 100644 index 0000000..6860646 --- /dev/null +++ b/GAHFrontend/src/components/customize/EditDialog.vue @@ -0,0 +1,60 @@ + + + diff --git a/GAHFrontend/src/components/customize/MetaDataEditor.vue b/GAHFrontend/src/components/customize/MetaDataEditor.vue index 685e6eb..911dbc4 100644 --- a/GAHFrontend/src/components/customize/MetaDataEditor.vue +++ b/GAHFrontend/src/components/customize/MetaDataEditor.vue @@ -179,6 +179,13 @@ onBeforeUnmount(() => {