Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
bc1abb6a
Unverified
Commit
bc1abb6a
authored
Feb 11, 2026
by
Wesley Liddick
Committed by
GitHub
Feb 11, 2026
Browse files
Merge pull request #557 from james-6-23/main
feat(admin): 为账户和兑换码新增邮箱搜索及限流过滤功能
parents
d307d48d
04a1a7c2
Changes
8
Show whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/redeem_handler.go
View file @
bc1abb6a
...
...
@@ -202,7 +202,7 @@ func (h *RedeemHandler) Export(c *gin.Context) {
writer
:=
csv
.
NewWriter
(
&
buf
)
// Write header
if
err
:=
writer
.
Write
([]
string
{
"id"
,
"code"
,
"type"
,
"value"
,
"status"
,
"used_by"
,
"used_at"
,
"created_at"
});
err
!=
nil
{
if
err
:=
writer
.
Write
([]
string
{
"id"
,
"code"
,
"type"
,
"value"
,
"status"
,
"used_by"
,
"used_by_email"
,
"used_at"
,
"created_at"
});
err
!=
nil
{
response
.
InternalError
(
c
,
"Failed to export redeem codes: "
+
err
.
Error
())
return
}
...
...
@@ -213,6 +213,10 @@ func (h *RedeemHandler) Export(c *gin.Context) {
if
code
.
UsedBy
!=
nil
{
usedBy
=
fmt
.
Sprintf
(
"%d"
,
*
code
.
UsedBy
)
}
usedByEmail
:=
""
if
code
.
User
!=
nil
{
usedByEmail
=
code
.
User
.
Email
}
usedAt
:=
""
if
code
.
UsedAt
!=
nil
{
usedAt
=
code
.
UsedAt
.
Format
(
"2006-01-02 15:04:05"
)
...
...
@@ -224,6 +228,7 @@ func (h *RedeemHandler) Export(c *gin.Context) {
fmt
.
Sprintf
(
"%.2f"
,
code
.
Value
),
code
.
Status
,
usedBy
,
usedByEmail
,
usedAt
,
code
.
CreatedAt
.
Format
(
"2006-01-02 15:04:05"
),
});
err
!=
nil
{
...
...
backend/internal/repository/account_repo.go
View file @
bc1abb6a
...
...
@@ -448,8 +448,13 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
q
=
q
.
Where
(
dbaccount
.
TypeEQ
(
accountType
))
}
if
status
!=
""
{
switch
status
{
case
"rate_limited"
:
q
=
q
.
Where
(
dbaccount
.
RateLimitResetAtGT
(
time
.
Now
()))
default
:
q
=
q
.
Where
(
dbaccount
.
StatusEQ
(
status
))
}
}
if
search
!=
""
{
q
=
q
.
Where
(
dbaccount
.
NameContainsFold
(
search
))
}
...
...
backend/internal/repository/redeem_code_repo.go
View file @
bc1abb6a
...
...
@@ -6,6 +6,7 @@ import (
dbent
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
)
...
...
@@ -106,7 +107,12 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin
q
=
q
.
Where
(
redeemcode
.
StatusEQ
(
status
))
}
if
search
!=
""
{
q
=
q
.
Where
(
redeemcode
.
CodeContainsFold
(
search
))
q
=
q
.
Where
(
redeemcode
.
Or
(
redeemcode
.
CodeContainsFold
(
search
),
redeemcode
.
HasUserWith
(
user
.
EmailContainsFold
(
search
)),
),
)
}
total
,
err
:=
q
.
Count
(
ctx
)
...
...
backend/internal/repository/user_repo.go
View file @
bc1abb6a
...
...
@@ -10,6 +10,7 @@ import (
"time"
dbent
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/apikey"
dbuser
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
...
...
@@ -191,6 +192,7 @@ func (r *userRepository) ListWithFilters(ctx context.Context, params pagination.
dbuser
.
EmailContainsFold
(
filters
.
Search
),
dbuser
.
UsernameContainsFold
(
filters
.
Search
),
dbuser
.
NotesContainsFold
(
filters
.
Search
),
dbuser
.
HasAPIKeysWith
(
apikey
.
KeyContainsFold
(
filters
.
Search
)),
),
)
}
...
...
frontend/src/components/admin/account/AccountTableFilters.vue
View file @
bc1abb6a
...
...
@@ -21,5 +21,5 @@ const updateType = (value: string | number | boolean | null) => { emit('update:f
const
updateStatus
=
(
value
:
string
|
number
|
boolean
|
null
)
=>
{
emit
(
'
update:filters
'
,
{
...
props
.
filters
,
status
:
value
})
}
const
pOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allPlatforms
'
)
},
{
value
:
'
anthropic
'
,
label
:
'
Anthropic
'
},
{
value
:
'
openai
'
,
label
:
'
OpenAI
'
},
{
value
:
'
gemini
'
,
label
:
'
Gemini
'
},
{
value
:
'
antigravity
'
,
label
:
'
Antigravity
'
}])
const
tOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allTypes
'
)
},
{
value
:
'
oauth
'
,
label
:
t
(
'
admin.accounts.oauthType
'
)
},
{
value
:
'
setup-token
'
,
label
:
t
(
'
admin.accounts.setupToken
'
)
},
{
value
:
'
apikey
'
,
label
:
t
(
'
admin.accounts.apiKey
'
)
}])
const
sOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allStatus
'
)
},
{
value
:
'
active
'
,
label
:
t
(
'
admin.accounts.status.active
'
)
},
{
value
:
'
inactive
'
,
label
:
t
(
'
admin.accounts.status.inactive
'
)
},
{
value
:
'
error
'
,
label
:
t
(
'
admin.accounts.status.error
'
)
}])
const
sOpts
=
computed
(()
=>
[{
value
:
''
,
label
:
t
(
'
admin.accounts.allStatus
'
)
},
{
value
:
'
active
'
,
label
:
t
(
'
admin.accounts.status.active
'
)
},
{
value
:
'
inactive
'
,
label
:
t
(
'
admin.accounts.status.inactive
'
)
},
{
value
:
'
error
'
,
label
:
t
(
'
admin.accounts.status.error
'
)
},
{
value
:
'
rate_limited
'
,
label
:
t
(
'
admin.accounts.status.rateLimited
'
)
}])
</
script
>
frontend/src/i18n/locales/en.ts
View file @
bc1abb6a
...
...
@@ -841,7 +841,7 @@ export default {
createUser
:
'
Create User
'
,
editUser
:
'
Edit User
'
,
deleteUser
:
'
Delete User
'
,
searchUsers
:
'
Search
users
...
'
,
searchUsers
:
'
Search
by email, username, notes, or API key
...
'
,
allRoles
:
'
All Roles
'
,
allStatus
:
'
All Status
'
,
admin
:
'
Admin
'
,
...
...
@@ -2136,7 +2136,7 @@ export default {
title
:
'
Redeem Code Management
'
,
description
:
'
Generate and manage redeem codes
'
,
generateCodes
:
'
Generate Codes
'
,
searchCodes
:
'
Search codes...
'
,
searchCodes
:
'
Search codes
or email
...
'
,
allTypes
:
'
All Types
'
,
allStatus
:
'
All Status
'
,
balance
:
'
Balance
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
bc1abb6a
...
...
@@ -865,8 +865,8 @@ export default {
editUser
:
'
编辑用户
'
,
deleteUser
:
'
删除用户
'
,
deleteConfirmMessage
:
"
确定要删除用户 '{email}' 吗?此操作无法撤销。
"
,
searchPlaceholder
:
'
搜索用户
邮箱
或
用户名
、
备注
、支持模糊查询
...
'
,
searchUsers
:
'
搜索用户
邮箱
或
用户名
、
备注
、支持模糊查询
'
,
searchPlaceholder
:
'
邮箱
/
用户名
/
备注
/API Key 模糊搜索
...
'
,
searchUsers
:
'
邮箱
/
用户名
/
备注
/API Key 模糊搜索
'
,
roleFilter
:
'
角色筛选
'
,
allRoles
:
'
全部角色
'
,
allStatus
:
'
全部状态
'
,
...
...
@@ -2300,7 +2300,7 @@ export default {
allStatus
:
'
全部状态
'
,
unused
:
'
未使用
'
,
used
:
'
已使用
'
,
searchCodes
:
'
搜索兑换码...
'
,
searchCodes
:
'
搜索兑换码
或邮箱
...
'
,
exportCsv
:
'
导出 CSV
'
,
deleteAllUnused
:
'
删除全部未使用
'
,
deleteCodeConfirm
:
'
确定要删除此兑换码吗?此操作无法撤销。
'
,
...
...
frontend/src/views/admin/RedeemView.vue
View file @
bc1abb6a
...
...
@@ -117,9 +117,9 @@
</span>
</
template
>
<
template
#cell-used_by=
"{ value }"
>
<
template
#cell-used_by=
"{ value
, row
}"
>
<span
class=
"text-sm text-gray-500 dark:text-dark-400"
>
{{
value
?
t
(
'
admin.redeem.userPrefix
'
,
{
id
:
value
}
)
:
'
-
'
}}
{{
row
.
user
?.
email
||
(
value
?
t
(
'
admin.redeem.userPrefix
'
,
{
id
:
value
}
)
:
'
-
'
)
}}
<
/span
>
<
/template
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment