From 378b7a0b79227a597428dd3840b1c2d94255dac8 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 27 Jun 2026 09:12:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E5=85=8D?= =?UTF-8?q?=E6=8A=BC=E9=A2=9D=E5=BA=A6=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/adminuser/dto.go | 5 +- backend/internal/modules/adminuser/handler.go | 14 +++++- .../internal/modules/adminuser/repository.go | 3 ++ .../modules/adminuser/repository_test.go | 50 ++++++++++++++++++- frontend/src/features/admin/api/adminUsers.ts | 2 + .../features/admin/views/AdminUsersView.vue | 7 +++ 6 files changed, 76 insertions(+), 5 deletions(-) diff --git a/backend/internal/modules/adminuser/dto.go b/backend/internal/modules/adminuser/dto.go index 345f41d..d7022fa 100644 --- a/backend/internal/modules/adminuser/dto.go +++ b/backend/internal/modules/adminuser/dto.go @@ -34,8 +34,9 @@ type DepositFreeQuotaRequest struct { // ListQuery 用户列表筛选条件。Keyword 同时匹配手机号/昵称(模糊)与用户 ID(精确)。 type ListQuery struct { - Keyword string - Status string + Keyword string + Status string + HasDepositFreeQuota bool } type PaginatedResult struct { diff --git a/backend/internal/modules/adminuser/handler.go b/backend/internal/modules/adminuser/handler.go index 1778160..7c1361f 100644 --- a/backend/internal/modules/adminuser/handler.go +++ b/backend/internal/modules/adminuser/handler.go @@ -38,8 +38,9 @@ func parsePagination(c *gin.Context) (int, int) { func (h *Handler) List(c *gin.Context) { page, pageSize := parsePagination(c) query := ListQuery{ - Keyword: strings.TrimSpace(c.Query("keyword")), - Status: strings.TrimSpace(c.Query("status")), + Keyword: strings.TrimSpace(c.Query("keyword")), + Status: strings.TrimSpace(c.Query("status")), + HasDepositFreeQuota: parseBoolQuery(c.Query("has_deposit_free_quota")), } result, err := h.service.List(c.Request.Context(), page, pageSize, query) if err != nil { @@ -49,6 +50,15 @@ func (h *Handler) List(c *gin.Context) { response.OK(c, result) } +func parseBoolQuery(value string) bool { + switch strings.ToLower(strings.TrimSpace(value)) { + case "1", "true", "yes", "on": + return true + default: + return false + } +} + func (h *Handler) Freeze(c *gin.Context) { adminID, ok := currentAdminID(c) if !ok { diff --git a/backend/internal/modules/adminuser/repository.go b/backend/internal/modules/adminuser/repository.go index 7965660..ab88a5e 100644 --- a/backend/internal/modules/adminuser/repository.go +++ b/backend/internal/modules/adminuser/repository.go @@ -61,6 +61,9 @@ func applyUserFilter(tx *gorm.DB, query ListQuery) *gorm.DB { if query.Status != "" { tx = tx.Where("u.status = ?", query.Status) } + if query.HasDepositFreeQuota { + tx = tx.Where("u.deposit_free_quota_cent > 0") + } if keyword := strings.TrimSpace(query.Keyword); keyword != "" { like := "%" + keyword + "%" // 关键词为纯数字时一并按用户 ID 精确匹配 diff --git a/backend/internal/modules/adminuser/repository_test.go b/backend/internal/modules/adminuser/repository_test.go index 1dafee9..cdbe73e 100644 --- a/backend/internal/modules/adminuser/repository_test.go +++ b/backend/internal/modules/adminuser/repository_test.go @@ -1,6 +1,12 @@ package adminuser -import "testing" +import ( + "strings" + "testing" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) func TestDepositFreeQuotaAmountCent(t *testing.T) { tests := []struct { @@ -28,3 +34,45 @@ func TestDepositFreeQuotaAmountCent(t *testing.T) { }) } } + +func TestParseBoolQuery(t *testing.T) { + tests := []struct { + name string + value string + want bool + }{ + {name: "true 开启", value: "true", want: true}, + {name: "数字 1 开启", value: "1", want: true}, + {name: "on 开启", value: "on", want: true}, + {name: "空值关闭", value: "", want: false}, + {name: "false 关闭", value: "false", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseBoolQuery(tt.value); got != tt.want { + t.Fatalf("parseBoolQuery(%q) = %v, want %v", tt.value, got, tt.want) + } + }) + } +} + +func TestApplyUserFilterHasDepositFreeQuota(t *testing.T) { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{DryRun: true}) + if err != nil { + t.Fatalf("打开测试数据库失败:%v", err) + } + + stmt := applyUserFilter(db.Table("users AS u"), ListQuery{ + Status: "active", + HasDepositFreeQuota: true, + }).Find(&[]struct{}{}).Statement + sql := stmt.SQL.String() + + if !strings.Contains(sql, "u.deposit_free_quota_cent > 0") { + t.Fatalf("SQL 未包含免押额度过滤:%s", sql) + } + if len(stmt.Vars) != 1 || stmt.Vars[0] != "active" { + t.Fatalf("筛选变量不正确:%v", stmt.Vars) + } +} diff --git a/frontend/src/features/admin/api/adminUsers.ts b/frontend/src/features/admin/api/adminUsers.ts index 47d2d90..85d39a4 100644 --- a/frontend/src/features/admin/api/adminUsers.ts +++ b/frontend/src/features/admin/api/adminUsers.ts @@ -25,12 +25,14 @@ export interface AdminUserItem { export interface AdminUserQuery { keyword?: string status?: '' | UserStatus + has_deposit_free_quota?: boolean } export async function fetchAdminUsers(page = 1, pageSize = 20, query: AdminUserQuery = {}) { const params: Record = { page, page_size: pageSize } if (query.keyword) params.keyword = query.keyword if (query.status) params.status = query.status + if (query.has_deposit_free_quota) params.has_deposit_free_quota = true const { data } = await apiClient.get>>( '/admin/users', { diff --git a/frontend/src/features/admin/views/AdminUsersView.vue b/frontend/src/features/admin/views/AdminUsersView.vue index db5e17d..5279e3e 100644 --- a/frontend/src/features/admin/views/AdminUsersView.vue +++ b/frontend/src/features/admin/views/AdminUsersView.vue @@ -30,6 +30,7 @@ const quotaAmount = ref(0) const filters = reactive({ keyword: '', status: '', + has_deposit_free_quota: false, }) const { @@ -51,6 +52,7 @@ function queryUsers() { function resetFilters() { filters.keyword = '' filters.status = '' + filters.has_deposit_free_quota = false queryUsers() } @@ -168,6 +170,11 @@ function moneyCent(value: number | string | undefined) { + + + 仅看有额度 + + 查询