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
62dc0b95
Commit
62dc0b95
authored
Jan 09, 2026
by
shaw
Browse files
Merge branch 'fix/table-pagination-and-features'
parents
f060db0b
7c3d5cad
Changes
26
Show whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/account_handler.go
View file @
62dc0b95
...
...
@@ -116,6 +116,7 @@ type BulkUpdateAccountsRequest struct {
Concurrency
*
int
`json:"concurrency"`
Priority
*
int
`json:"priority"`
Status
string
`json:"status" binding:"omitempty,oneof=active inactive error"`
Schedulable
*
bool
`json:"schedulable"`
GroupIDs
*
[]
int64
`json:"group_ids"`
Credentials
map
[
string
]
any
`json:"credentials"`
Extra
map
[
string
]
any
`json:"extra"`
...
...
@@ -136,6 +137,11 @@ func (h *AccountHandler) List(c *gin.Context) {
accountType
:=
c
.
Query
(
"type"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
search
=
search
[
:
100
]
}
accounts
,
total
,
err
:=
h
.
adminService
.
ListAccounts
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
accountType
,
status
,
search
)
if
err
!=
nil
{
...
...
@@ -655,6 +661,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
req
.
Concurrency
!=
nil
||
req
.
Priority
!=
nil
||
req
.
Status
!=
""
||
req
.
Schedulable
!=
nil
||
req
.
GroupIDs
!=
nil
||
len
(
req
.
Credentials
)
>
0
||
len
(
req
.
Extra
)
>
0
...
...
@@ -671,6 +678,7 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) {
Concurrency
:
req
.
Concurrency
,
Priority
:
req
.
Priority
,
Status
:
req
.
Status
,
Schedulable
:
req
.
Schedulable
,
GroupIDs
:
req
.
GroupIDs
,
Credentials
:
req
.
Credentials
,
Extra
:
req
.
Extra
,
...
...
backend/internal/handler/admin/group_handler.go
View file @
62dc0b95
...
...
@@ -2,6 +2,7 @@ package admin
import
(
"strconv"
"strings"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
...
...
@@ -67,6 +68,12 @@ func (h *GroupHandler) List(c *gin.Context) {
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
platform
:=
c
.
Query
(
"platform"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
search
=
search
[
:
100
]
}
isExclusiveStr
:=
c
.
Query
(
"is_exclusive"
)
var
isExclusive
*
bool
...
...
@@ -75,7 +82,7 @@ func (h *GroupHandler) List(c *gin.Context) {
isExclusive
=
&
val
}
groups
,
total
,
err
:=
h
.
adminService
.
ListGroups
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
status
,
isExclusive
)
groups
,
total
,
err
:=
h
.
adminService
.
ListGroups
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
status
,
search
,
isExclusive
)
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/admin/proxy_handler.go
View file @
62dc0b95
...
...
@@ -51,6 +51,11 @@ func (h *ProxyHandler) List(c *gin.Context) {
protocol
:=
c
.
Query
(
"protocol"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
search
=
search
[
:
100
]
}
proxies
,
total
,
err
:=
h
.
adminService
.
ListProxiesWithAccountCount
(
c
.
Request
.
Context
(),
page
,
pageSize
,
protocol
,
status
,
search
)
if
err
!=
nil
{
...
...
backend/internal/handler/admin/redeem_handler.go
View file @
62dc0b95
...
...
@@ -5,6 +5,7 @@ import (
"encoding/csv"
"fmt"
"strconv"
"strings"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
...
...
@@ -41,6 +42,11 @@ func (h *RedeemHandler) List(c *gin.Context) {
codeType
:=
c
.
Query
(
"type"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
search
=
search
[
:
100
]
}
codes
,
total
,
err
:=
h
.
adminService
.
ListRedeemCodes
(
c
.
Request
.
Context
(),
page
,
pageSize
,
codeType
,
status
,
search
)
if
err
!=
nil
{
...
...
backend/internal/handler/admin/user_handler.go
View file @
62dc0b95
...
...
@@ -2,6 +2,7 @@ package admin
import
(
"strconv"
"strings"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
...
...
@@ -63,10 +64,17 @@ type UpdateBalanceRequest struct {
func
(
h
*
UserHandler
)
List
(
c
*
gin
.
Context
)
{
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
search
:=
c
.
Query
(
"search"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
search
=
search
[
:
100
]
}
filters
:=
service
.
UserListFilters
{
Status
:
c
.
Query
(
"status"
),
Role
:
c
.
Query
(
"role"
),
Search
:
c
.
Query
(
"
search
"
)
,
Search
:
search
,
Attributes
:
parseAttributeFilters
(
c
),
}
...
...
backend/internal/repository/account_repo.go
View file @
62dc0b95
...
...
@@ -831,6 +831,11 @@ func (r *accountRepository) BulkUpdate(ctx context.Context, ids []int64, updates
args
=
append
(
args
,
*
updates
.
Status
)
idx
++
}
if
updates
.
Schedulable
!=
nil
{
setClauses
=
append
(
setClauses
,
"schedulable = $"
+
itoa
(
idx
))
args
=
append
(
args
,
*
updates
.
Schedulable
)
idx
++
}
// JSONB 需要合并而非覆盖,使用 raw SQL 保持旧行为。
if
len
(
updates
.
Credentials
)
>
0
{
payload
,
err
:=
json
.
Marshal
(
updates
.
Credentials
)
...
...
backend/internal/repository/group_repo.go
View file @
62dc0b95
...
...
@@ -112,10 +112,10 @@ func (r *groupRepository) Delete(ctx context.Context, id int64) error {
}
func
(
r
*
groupRepository
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
)
([]
service
.
Group
,
*
pagination
.
PaginationResult
,
error
)
{
return
r
.
ListWithFilters
(
ctx
,
params
,
""
,
""
,
nil
)
return
r
.
ListWithFilters
(
ctx
,
params
,
""
,
""
,
""
,
nil
)
}
func
(
r
*
groupRepository
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
service
.
Group
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
r
*
groupRepository
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
service
.
Group
,
*
pagination
.
PaginationResult
,
error
)
{
q
:=
r
.
client
.
Group
.
Query
()
if
platform
!=
""
{
...
...
@@ -124,6 +124,12 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
if
status
!=
""
{
q
=
q
.
Where
(
group
.
StatusEQ
(
status
))
}
if
search
!=
""
{
q
=
q
.
Where
(
group
.
Or
(
group
.
NameContainsFold
(
search
),
group
.
DescriptionContainsFold
(
search
),
))
}
if
isExclusive
!=
nil
{
q
=
q
.
Where
(
group
.
IsExclusiveEQ
(
*
isExclusive
))
}
...
...
backend/internal/repository/group_repo_integration_test.go
View file @
62dc0b95
...
...
@@ -131,6 +131,7 @@ func (s *GroupRepoSuite) TestListWithFilters_Platform() {
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
service
.
PlatformOpenAI
,
""
,
""
,
nil
,
)
s
.
Require
()
.
NoError
(
err
,
"ListWithFilters base"
)
...
...
@@ -152,7 +153,7 @@ func (s *GroupRepoSuite) TestListWithFilters_Platform() {
SubscriptionType
:
service
.
SubscriptionTypeStandard
,
}))
groups
,
_
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
service
.
PlatformOpenAI
,
""
,
nil
)
groups
,
_
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
service
.
PlatformOpenAI
,
""
,
""
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
groups
,
len
(
baseGroups
)
+
1
)
// Verify all groups are OpenAI platform
...
...
@@ -179,7 +180,7 @@ func (s *GroupRepoSuite) TestListWithFilters_Status() {
SubscriptionType
:
service
.
SubscriptionTypeStandard
,
}))
groups
,
_
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
""
,
service
.
StatusDisabled
,
nil
)
groups
,
_
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
""
,
service
.
StatusDisabled
,
""
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
groups
,
1
)
s
.
Require
()
.
Equal
(
service
.
StatusDisabled
,
groups
[
0
]
.
Status
)
...
...
@@ -204,12 +205,117 @@ func (s *GroupRepoSuite) TestListWithFilters_IsExclusive() {
}))
isExclusive
:=
true
groups
,
_
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
""
,
""
,
&
isExclusive
)
groups
,
_
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
""
,
""
,
""
,
&
isExclusive
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
groups
,
1
)
s
.
Require
()
.
True
(
groups
[
0
]
.
IsExclusive
)
}
func
(
s
*
GroupRepoSuite
)
TestListWithFilters_Search
()
{
newRepo
:=
func
()
(
*
groupRepository
,
context
.
Context
)
{
tx
:=
testEntTx
(
s
.
T
())
return
newGroupRepositoryWithSQL
(
tx
.
Client
(),
tx
),
context
.
Background
()
}
containsID
:=
func
(
groups
[]
service
.
Group
,
id
int64
)
bool
{
for
i
:=
range
groups
{
if
groups
[
i
]
.
ID
==
id
{
return
true
}
}
return
false
}
mustCreate
:=
func
(
repo
*
groupRepository
,
ctx
context
.
Context
,
g
*
service
.
Group
)
*
service
.
Group
{
s
.
Require
()
.
NoError
(
repo
.
Create
(
ctx
,
g
))
s
.
Require
()
.
NotZero
(
g
.
ID
)
return
g
}
newGroup
:=
func
(
name
string
)
*
service
.
Group
{
return
&
service
.
Group
{
Name
:
name
,
Platform
:
service
.
PlatformAnthropic
,
RateMultiplier
:
1.0
,
IsExclusive
:
false
,
Status
:
service
.
StatusActive
,
SubscriptionType
:
service
.
SubscriptionTypeStandard
,
}
}
s
.
Run
(
"search_name_should_match"
,
func
()
{
repo
,
ctx
:=
newRepo
()
target
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-name-target"
))
other
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-name-other"
))
groups
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
50
},
""
,
""
,
"name-target"
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
True
(
containsID
(
groups
,
target
.
ID
),
"expected target group to match by name"
)
s
.
Require
()
.
False
(
containsID
(
groups
,
other
.
ID
),
"expected other group to be filtered out"
)
})
s
.
Run
(
"search_description_should_match"
,
func
()
{
repo
,
ctx
:=
newRepo
()
target
:=
newGroup
(
"it-group-search-desc-target"
)
target
.
Description
=
"something about desc-needle in here"
target
=
mustCreate
(
repo
,
ctx
,
target
)
other
:=
newGroup
(
"it-group-search-desc-other"
)
other
.
Description
=
"nothing to see here"
other
=
mustCreate
(
repo
,
ctx
,
other
)
groups
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
50
},
""
,
""
,
"desc-needle"
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
True
(
containsID
(
groups
,
target
.
ID
),
"expected target group to match by description"
)
s
.
Require
()
.
False
(
containsID
(
groups
,
other
.
ID
),
"expected other group to be filtered out"
)
})
s
.
Run
(
"search_nonexistent_should_return_empty"
,
func
()
{
repo
,
ctx
:=
newRepo
()
_
=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-nonexistent-baseline"
))
search
:=
s
.
T
()
.
Name
()
+
"__no_such_group__"
groups
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
50
},
""
,
""
,
search
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Empty
(
groups
)
})
s
.
Run
(
"search_should_be_case_insensitive"
,
func
()
{
repo
,
ctx
:=
newRepo
()
target
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"MiXeDCaSe-Needle"
))
other
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-case-other"
))
groups
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
50
},
""
,
""
,
"mixedcase-needle"
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
True
(
containsID
(
groups
,
target
.
ID
),
"expected case-insensitive match"
)
s
.
Require
()
.
False
(
containsID
(
groups
,
other
.
ID
),
"expected other group to be filtered out"
)
})
s
.
Run
(
"search_should_escape_like_wildcards"
,
func
()
{
repo
,
ctx
:=
newRepo
()
percentTarget
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-100%-target"
))
percentOther
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-100X-other"
))
groups
,
_
,
err
:=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
50
},
""
,
""
,
"100%"
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
True
(
containsID
(
groups
,
percentTarget
.
ID
),
"expected literal %% match"
)
s
.
Require
()
.
False
(
containsID
(
groups
,
percentOther
.
ID
),
"expected %% not to act as wildcard"
)
underscoreTarget
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-ab_cd-target"
))
underscoreOther
:=
mustCreate
(
repo
,
ctx
,
newGroup
(
"it-group-search-abXcd-other"
))
groups
,
_
,
err
=
repo
.
ListWithFilters
(
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
50
},
""
,
""
,
"ab_cd"
,
nil
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
True
(
containsID
(
groups
,
underscoreTarget
.
ID
),
"expected literal _ match"
)
s
.
Require
()
.
False
(
containsID
(
groups
,
underscoreOther
.
ID
),
"expected _ not to act as wildcard"
)
})
}
func
(
s
*
GroupRepoSuite
)
TestListWithFilters_AccountCount
()
{
g1
:=
&
service
.
Group
{
Name
:
"g1"
,
...
...
@@ -244,7 +350,7 @@ func (s *GroupRepoSuite) TestListWithFilters_AccountCount() {
s
.
Require
()
.
NoError
(
err
)
isExclusive
:=
true
groups
,
page
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
service
.
PlatformAnthropic
,
service
.
StatusActive
,
&
isExclusive
)
groups
,
page
,
err
:=
s
.
repo
.
ListWithFilters
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
service
.
PlatformAnthropic
,
service
.
StatusActive
,
""
,
&
isExclusive
)
s
.
Require
()
.
NoError
(
err
,
"ListWithFilters"
)
s
.
Require
()
.
Equal
(
int64
(
1
),
page
.
Total
)
s
.
Require
()
.
Len
(
groups
,
1
)
...
...
backend/internal/server/api_contract_test.go
View file @
62dc0b95
...
...
@@ -587,7 +587,7 @@ func (stubGroupRepo) List(ctx context.Context, params pagination.PaginationParam
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
func
(
stubGroupRepo
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
service
.
Group
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
stubGroupRepo
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
service
.
Group
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
...
...
backend/internal/service/account_service.go
View file @
62dc0b95
...
...
@@ -66,6 +66,7 @@ type AccountBulkUpdate struct {
Concurrency
*
int
Priority
*
int
Status
*
string
Schedulable
*
bool
Credentials
map
[
string
]
any
Extra
map
[
string
]
any
}
...
...
backend/internal/service/admin_service.go
View file @
62dc0b95
...
...
@@ -24,7 +24,7 @@ type AdminService interface {
GetUserUsageStats
(
ctx
context
.
Context
,
userID
int64
,
period
string
)
(
any
,
error
)
// Group management
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
Group
,
int64
,
error
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
int64
,
error
)
GetAllGroups
(
ctx
context
.
Context
)
([]
Group
,
error
)
GetAllGroupsByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Group
,
error
)
GetGroup
(
ctx
context
.
Context
,
id
int64
)
(
*
Group
,
error
)
...
...
@@ -168,6 +168,7 @@ type BulkUpdateAccountsInput struct {
Concurrency
*
int
Priority
*
int
Status
string
Schedulable
*
bool
GroupIDs
*
[]
int64
Credentials
map
[
string
]
any
Extra
map
[
string
]
any
...
...
@@ -478,9 +479,9 @@ func (s *adminServiceImpl) GetUserUsageStats(ctx context.Context, userID int64,
}
// Group management implementations
func
(
s
*
adminServiceImpl
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
Group
,
int64
,
error
)
{
func
(
s
*
adminServiceImpl
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
int64
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
groups
,
result
,
err
:=
s
.
groupRepo
.
ListWithFilters
(
ctx
,
params
,
platform
,
status
,
isExclusive
)
groups
,
result
,
err
:=
s
.
groupRepo
.
ListWithFilters
(
ctx
,
params
,
platform
,
status
,
search
,
isExclusive
)
if
err
!=
nil
{
return
nil
,
0
,
err
}
...
...
@@ -910,6 +911,9 @@ func (s *adminServiceImpl) BulkUpdateAccounts(ctx context.Context, input *BulkUp
if
input
.
Status
!=
""
{
repoUpdates
.
Status
=
&
input
.
Status
}
if
input
.
Schedulable
!=
nil
{
repoUpdates
.
Schedulable
=
input
.
Schedulable
}
// Run bulk update for column/jsonb fields first.
if
_
,
err
:=
s
.
accountRepo
.
BulkUpdate
(
ctx
,
input
.
AccountIDs
,
repoUpdates
);
err
!=
nil
{
...
...
backend/internal/service/admin_service_delete_test.go
View file @
62dc0b95
...
...
@@ -124,7 +124,7 @@ func (s *groupRepoStub) List(ctx context.Context, params pagination.PaginationPa
panic
(
"unexpected List call"
)
}
func
(
s
*
groupRepoStub
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
s
*
groupRepoStub
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
panic
(
"unexpected ListWithFilters call"
)
}
...
...
backend/internal/service/admin_service_group_test.go
View file @
62dc0b95
...
...
@@ -16,6 +16,16 @@ type groupRepoStubForAdmin struct {
updated
*
Group
// 记录 Update 调用的参数
getByID
*
Group
// GetByID 返回值
getErr
error
// GetByID 返回的错误
listWithFiltersCalls
int
listWithFiltersParams
pagination
.
PaginationParams
listWithFiltersPlatform
string
listWithFiltersStatus
string
listWithFiltersSearch
string
listWithFiltersIsExclusive
*
bool
listWithFiltersGroups
[]
Group
listWithFiltersResult
*
pagination
.
PaginationResult
listWithFiltersErr
error
}
func
(
s
*
groupRepoStubForAdmin
)
Create
(
_
context
.
Context
,
g
*
Group
)
error
{
...
...
@@ -47,8 +57,28 @@ func (s *groupRepoStubForAdmin) List(_ context.Context, _ pagination.PaginationP
panic
(
"unexpected List call"
)
}
func
(
s
*
groupRepoStubForAdmin
)
ListWithFilters
(
_
context
.
Context
,
_
pagination
.
PaginationParams
,
_
,
_
string
,
_
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
panic
(
"unexpected ListWithFilters call"
)
func
(
s
*
groupRepoStubForAdmin
)
ListWithFilters
(
_
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listWithFiltersCalls
++
s
.
listWithFiltersParams
=
params
s
.
listWithFiltersPlatform
=
platform
s
.
listWithFiltersStatus
=
status
s
.
listWithFiltersSearch
=
search
s
.
listWithFiltersIsExclusive
=
isExclusive
if
s
.
listWithFiltersErr
!=
nil
{
return
nil
,
nil
,
s
.
listWithFiltersErr
}
result
:=
s
.
listWithFiltersResult
if
result
==
nil
{
result
=
&
pagination
.
PaginationResult
{
Total
:
int64
(
len
(
s
.
listWithFiltersGroups
)),
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
}
}
return
s
.
listWithFiltersGroups
,
result
,
nil
}
func
(
s
*
groupRepoStubForAdmin
)
ListActive
(
_
context
.
Context
)
([]
Group
,
error
)
{
...
...
@@ -195,3 +225,68 @@ func TestAdminService_UpdateGroup_PartialImagePricing(t *testing.T) {
require
.
InDelta
(
t
,
0.15
,
*
repo
.
updated
.
ImagePrice2K
,
0.0001
)
// 原值保持
require
.
Nil
(
t
,
repo
.
updated
.
ImagePrice4K
)
}
func
TestAdminService_ListGroups_WithSearch
(
t
*
testing
.
T
)
{
// 测试:
// 1. search 参数正常传递到 repository 层
// 2. search 为空字符串时的行为
// 3. search 与其他过滤条件组合使用
t
.
Run
(
"search 参数正常传递到 repository 层"
,
func
(
t
*
testing
.
T
)
{
repo
:=
&
groupRepoStubForAdmin
{
listWithFiltersGroups
:
[]
Group
{{
ID
:
1
,
Name
:
"alpha"
}},
listWithFiltersResult
:
&
pagination
.
PaginationResult
{
Total
:
1
},
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
1
,
20
,
""
,
""
,
"alpha"
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
1
),
total
)
require
.
Equal
(
t
,
[]
Group
{{
ID
:
1
,
Name
:
"alpha"
}},
groups
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
"alpha"
,
repo
.
listWithFiltersSearch
)
require
.
Nil
(
t
,
repo
.
listWithFiltersIsExclusive
)
})
t
.
Run
(
"search 为空字符串时传递空字符串"
,
func
(
t
*
testing
.
T
)
{
repo
:=
&
groupRepoStubForAdmin
{
listWithFiltersGroups
:
[]
Group
{},
listWithFiltersResult
:
&
pagination
.
PaginationResult
{
Total
:
0
},
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
2
,
10
,
""
,
""
,
""
,
nil
)
require
.
NoError
(
t
,
err
)
require
.
Empty
(
t
,
groups
)
require
.
Equal
(
t
,
int64
(
0
),
total
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
2
,
PageSize
:
10
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
""
,
repo
.
listWithFiltersSearch
)
require
.
Nil
(
t
,
repo
.
listWithFiltersIsExclusive
)
})
t
.
Run
(
"search 与其他过滤条件组合使用"
,
func
(
t
*
testing
.
T
)
{
isExclusive
:=
true
repo
:=
&
groupRepoStubForAdmin
{
listWithFiltersGroups
:
[]
Group
{{
ID
:
2
,
Name
:
"beta"
}},
listWithFiltersResult
:
&
pagination
.
PaginationResult
{
Total
:
42
},
}
svc
:=
&
adminServiceImpl
{
groupRepo
:
repo
}
groups
,
total
,
err
:=
svc
.
ListGroups
(
context
.
Background
(),
3
,
50
,
PlatformAntigravity
,
StatusActive
,
"beta"
,
&
isExclusive
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
42
),
total
)
require
.
Equal
(
t
,
[]
Group
{{
ID
:
2
,
Name
:
"beta"
}},
groups
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
3
,
PageSize
:
50
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
PlatformAntigravity
,
repo
.
listWithFiltersPlatform
)
require
.
Equal
(
t
,
StatusActive
,
repo
.
listWithFiltersStatus
)
require
.
Equal
(
t
,
"beta"
,
repo
.
listWithFiltersSearch
)
require
.
NotNil
(
t
,
repo
.
listWithFiltersIsExclusive
)
require
.
True
(
t
,
*
repo
.
listWithFiltersIsExclusive
)
})
}
backend/internal/service/admin_service_search_test.go
0 → 100644
View file @
62dc0b95
//go:build unit
package
service
import
(
"context"
"testing"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/stretchr/testify/require"
)
type
accountRepoStubForAdminList
struct
{
accountRepoStub
listWithFiltersCalls
int
listWithFiltersParams
pagination
.
PaginationParams
listWithFiltersPlatform
string
listWithFiltersType
string
listWithFiltersStatus
string
listWithFiltersSearch
string
listWithFiltersAccounts
[]
Account
listWithFiltersResult
*
pagination
.
PaginationResult
listWithFiltersErr
error
}
func
(
s
*
accountRepoStubForAdminList
)
ListWithFilters
(
_
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
accountType
,
status
,
search
string
)
([]
Account
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listWithFiltersCalls
++
s
.
listWithFiltersParams
=
params
s
.
listWithFiltersPlatform
=
platform
s
.
listWithFiltersType
=
accountType
s
.
listWithFiltersStatus
=
status
s
.
listWithFiltersSearch
=
search
if
s
.
listWithFiltersErr
!=
nil
{
return
nil
,
nil
,
s
.
listWithFiltersErr
}
result
:=
s
.
listWithFiltersResult
if
result
==
nil
{
result
=
&
pagination
.
PaginationResult
{
Total
:
int64
(
len
(
s
.
listWithFiltersAccounts
)),
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
}
}
return
s
.
listWithFiltersAccounts
,
result
,
nil
}
type
proxyRepoStubForAdminList
struct
{
proxyRepoStub
listWithFiltersCalls
int
listWithFiltersParams
pagination
.
PaginationParams
listWithFiltersProtocol
string
listWithFiltersStatus
string
listWithFiltersSearch
string
listWithFiltersProxies
[]
Proxy
listWithFiltersResult
*
pagination
.
PaginationResult
listWithFiltersErr
error
listWithFiltersAndAccountCountCalls
int
listWithFiltersAndAccountCountParams
pagination
.
PaginationParams
listWithFiltersAndAccountCountProtocol
string
listWithFiltersAndAccountCountStatus
string
listWithFiltersAndAccountCountSearch
string
listWithFiltersAndAccountCountProxies
[]
ProxyWithAccountCount
listWithFiltersAndAccountCountResult
*
pagination
.
PaginationResult
listWithFiltersAndAccountCountErr
error
}
func
(
s
*
proxyRepoStubForAdminList
)
ListWithFilters
(
_
context
.
Context
,
params
pagination
.
PaginationParams
,
protocol
,
status
,
search
string
)
([]
Proxy
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listWithFiltersCalls
++
s
.
listWithFiltersParams
=
params
s
.
listWithFiltersProtocol
=
protocol
s
.
listWithFiltersStatus
=
status
s
.
listWithFiltersSearch
=
search
if
s
.
listWithFiltersErr
!=
nil
{
return
nil
,
nil
,
s
.
listWithFiltersErr
}
result
:=
s
.
listWithFiltersResult
if
result
==
nil
{
result
=
&
pagination
.
PaginationResult
{
Total
:
int64
(
len
(
s
.
listWithFiltersProxies
)),
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
}
}
return
s
.
listWithFiltersProxies
,
result
,
nil
}
func
(
s
*
proxyRepoStubForAdminList
)
ListWithFiltersAndAccountCount
(
_
context
.
Context
,
params
pagination
.
PaginationParams
,
protocol
,
status
,
search
string
)
([]
ProxyWithAccountCount
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listWithFiltersAndAccountCountCalls
++
s
.
listWithFiltersAndAccountCountParams
=
params
s
.
listWithFiltersAndAccountCountProtocol
=
protocol
s
.
listWithFiltersAndAccountCountStatus
=
status
s
.
listWithFiltersAndAccountCountSearch
=
search
if
s
.
listWithFiltersAndAccountCountErr
!=
nil
{
return
nil
,
nil
,
s
.
listWithFiltersAndAccountCountErr
}
result
:=
s
.
listWithFiltersAndAccountCountResult
if
result
==
nil
{
result
=
&
pagination
.
PaginationResult
{
Total
:
int64
(
len
(
s
.
listWithFiltersAndAccountCountProxies
)),
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
}
}
return
s
.
listWithFiltersAndAccountCountProxies
,
result
,
nil
}
type
redeemRepoStubForAdminList
struct
{
redeemRepoStub
listWithFiltersCalls
int
listWithFiltersParams
pagination
.
PaginationParams
listWithFiltersType
string
listWithFiltersStatus
string
listWithFiltersSearch
string
listWithFiltersCodes
[]
RedeemCode
listWithFiltersResult
*
pagination
.
PaginationResult
listWithFiltersErr
error
}
func
(
s
*
redeemRepoStubForAdminList
)
ListWithFilters
(
_
context
.
Context
,
params
pagination
.
PaginationParams
,
codeType
,
status
,
search
string
)
([]
RedeemCode
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listWithFiltersCalls
++
s
.
listWithFiltersParams
=
params
s
.
listWithFiltersType
=
codeType
s
.
listWithFiltersStatus
=
status
s
.
listWithFiltersSearch
=
search
if
s
.
listWithFiltersErr
!=
nil
{
return
nil
,
nil
,
s
.
listWithFiltersErr
}
result
:=
s
.
listWithFiltersResult
if
result
==
nil
{
result
=
&
pagination
.
PaginationResult
{
Total
:
int64
(
len
(
s
.
listWithFiltersCodes
)),
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
}
}
return
s
.
listWithFiltersCodes
,
result
,
nil
}
func
TestAdminService_ListAccounts_WithSearch
(
t
*
testing
.
T
)
{
t
.
Run
(
"search 参数正常传递到 repository 层"
,
func
(
t
*
testing
.
T
)
{
repo
:=
&
accountRepoStubForAdminList
{
listWithFiltersAccounts
:
[]
Account
{{
ID
:
1
,
Name
:
"acc"
}},
listWithFiltersResult
:
&
pagination
.
PaginationResult
{
Total
:
10
},
}
svc
:=
&
adminServiceImpl
{
accountRepo
:
repo
}
accounts
,
total
,
err
:=
svc
.
ListAccounts
(
context
.
Background
(),
1
,
20
,
PlatformGemini
,
AccountTypeOAuth
,
StatusActive
,
"acc"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
10
),
total
)
require
.
Equal
(
t
,
[]
Account
{{
ID
:
1
,
Name
:
"acc"
}},
accounts
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
PlatformGemini
,
repo
.
listWithFiltersPlatform
)
require
.
Equal
(
t
,
AccountTypeOAuth
,
repo
.
listWithFiltersType
)
require
.
Equal
(
t
,
StatusActive
,
repo
.
listWithFiltersStatus
)
require
.
Equal
(
t
,
"acc"
,
repo
.
listWithFiltersSearch
)
})
}
func
TestAdminService_ListProxies_WithSearch
(
t
*
testing
.
T
)
{
t
.
Run
(
"search 参数正常传递到 repository 层"
,
func
(
t
*
testing
.
T
)
{
repo
:=
&
proxyRepoStubForAdminList
{
listWithFiltersProxies
:
[]
Proxy
{{
ID
:
2
,
Name
:
"p1"
}},
listWithFiltersResult
:
&
pagination
.
PaginationResult
{
Total
:
7
},
}
svc
:=
&
adminServiceImpl
{
proxyRepo
:
repo
}
proxies
,
total
,
err
:=
svc
.
ListProxies
(
context
.
Background
(),
3
,
50
,
"http"
,
StatusActive
,
"p1"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
7
),
total
)
require
.
Equal
(
t
,
[]
Proxy
{{
ID
:
2
,
Name
:
"p1"
}},
proxies
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
3
,
PageSize
:
50
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
"http"
,
repo
.
listWithFiltersProtocol
)
require
.
Equal
(
t
,
StatusActive
,
repo
.
listWithFiltersStatus
)
require
.
Equal
(
t
,
"p1"
,
repo
.
listWithFiltersSearch
)
})
}
func
TestAdminService_ListProxiesWithAccountCount_WithSearch
(
t
*
testing
.
T
)
{
t
.
Run
(
"search 参数正常传递到 repository 层"
,
func
(
t
*
testing
.
T
)
{
repo
:=
&
proxyRepoStubForAdminList
{
listWithFiltersAndAccountCountProxies
:
[]
ProxyWithAccountCount
{{
Proxy
:
Proxy
{
ID
:
3
,
Name
:
"p2"
},
AccountCount
:
5
}},
listWithFiltersAndAccountCountResult
:
&
pagination
.
PaginationResult
{
Total
:
9
},
}
svc
:=
&
adminServiceImpl
{
proxyRepo
:
repo
}
proxies
,
total
,
err
:=
svc
.
ListProxiesWithAccountCount
(
context
.
Background
(),
2
,
10
,
"socks5"
,
StatusDisabled
,
"p2"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
9
),
total
)
require
.
Equal
(
t
,
[]
ProxyWithAccountCount
{{
Proxy
:
Proxy
{
ID
:
3
,
Name
:
"p2"
},
AccountCount
:
5
}},
proxies
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersAndAccountCountCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
2
,
PageSize
:
10
},
repo
.
listWithFiltersAndAccountCountParams
)
require
.
Equal
(
t
,
"socks5"
,
repo
.
listWithFiltersAndAccountCountProtocol
)
require
.
Equal
(
t
,
StatusDisabled
,
repo
.
listWithFiltersAndAccountCountStatus
)
require
.
Equal
(
t
,
"p2"
,
repo
.
listWithFiltersAndAccountCountSearch
)
})
}
func
TestAdminService_ListRedeemCodes_WithSearch
(
t
*
testing
.
T
)
{
t
.
Run
(
"search 参数正常传递到 repository 层"
,
func
(
t
*
testing
.
T
)
{
repo
:=
&
redeemRepoStubForAdminList
{
listWithFiltersCodes
:
[]
RedeemCode
{{
ID
:
4
,
Code
:
"ABC"
}},
listWithFiltersResult
:
&
pagination
.
PaginationResult
{
Total
:
3
},
}
svc
:=
&
adminServiceImpl
{
redeemCodeRepo
:
repo
}
codes
,
total
,
err
:=
svc
.
ListRedeemCodes
(
context
.
Background
(),
1
,
20
,
RedeemTypeBalance
,
StatusUnused
,
"ABC"
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
int64
(
3
),
total
)
require
.
Equal
(
t
,
[]
RedeemCode
{{
ID
:
4
,
Code
:
"ABC"
}},
codes
)
require
.
Equal
(
t
,
1
,
repo
.
listWithFiltersCalls
)
require
.
Equal
(
t
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
20
},
repo
.
listWithFiltersParams
)
require
.
Equal
(
t
,
RedeemTypeBalance
,
repo
.
listWithFiltersType
)
require
.
Equal
(
t
,
StatusUnused
,
repo
.
listWithFiltersStatus
)
require
.
Equal
(
t
,
"ABC"
,
repo
.
listWithFiltersSearch
)
})
}
backend/internal/service/gemini_multiplatform_test.go
View file @
62dc0b95
...
...
@@ -166,7 +166,7 @@ func (m *mockGroupRepoForGemini) DeleteCascade(ctx context.Context, id int64) ([
func
(
m
*
mockGroupRepoForGemini
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
nil
}
func
(
m
*
mockGroupRepoForGemini
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
m
*
mockGroupRepoForGemini
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
nil
}
func
(
m
*
mockGroupRepoForGemini
)
ListActive
(
ctx
context
.
Context
)
([]
Group
,
error
)
{
return
nil
,
nil
}
...
...
backend/internal/service/group_service.go
View file @
62dc0b95
...
...
@@ -21,7 +21,7 @@ type GroupRepository interface {
DeleteCascade
(
ctx
context
.
Context
,
id
int64
)
([]
int64
,
error
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
Group
,
*
pagination
.
PaginationResult
,
error
)
ListActive
(
ctx
context
.
Context
)
([]
Group
,
error
)
ListActiveByPlatform
(
ctx
context
.
Context
,
platform
string
)
([]
Group
,
error
)
...
...
frontend/src/api/admin/groups.ts
View file @
62dc0b95
...
...
@@ -16,7 +16,7 @@ import type {
* List all groups with pagination
* @param page - Page number (default: 1)
* @param pageSize - Items per page (default: 20)
* @param filters - Optional filters (platform, status, is_exclusive)
* @param filters - Optional filters (platform, status, is_exclusive
, search
)
* @returns Paginated list of groups
*/
export
async
function
list
(
...
...
@@ -26,6 +26,7 @@ export async function list(
platform
?:
GroupPlatform
status
?:
'
active
'
|
'
inactive
'
is_exclusive
?:
boolean
search
?:
string
},
options
?:
{
signal
?:
AbortSignal
...
...
frontend/src/components/admin/account/AccountBulkActionsBar.vue
View file @
62dc0b95
<
template
>
<div
v-if=
"selectedIds.length > 0"
class=
"mb-4 flex items-center justify-between p-3 bg-primary-50 rounded-lg"
>
<span
class=
"text-sm font-medium"
>
{{
t
(
'
admin.accounts.bulkActions.selected
'
,
{
count
:
selectedIds
.
length
}
)
}}
<
/span
>
<div
v-if=
"selectedIds.length > 0"
class=
"mb-4 flex items-center justify-between p-3 bg-primary-50 rounded-lg dark:bg-primary-900/20"
>
<div
class=
"flex flex-wrap items-center gap-2"
>
<span
class=
"text-sm font-medium text-primary-900 dark:text-primary-100"
>
{{
t
(
'
admin.accounts.bulkActions.selected
'
,
{
count
:
selectedIds
.
length
}
)
}}
<
/span
>
<
button
@
click
=
"
$emit('select-page')
"
class
=
"
text-xs font-medium text-primary-700 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200
"
>
{{
t
(
'
admin.accounts.bulkActions.selectCurrentPage
'
)
}}
<
/button
>
<
span
class
=
"
text-gray-300 dark:text-primary-800
"
>
•
<
/span
>
<
button
@
click
=
"
$emit('clear')
"
class
=
"
text-xs font-medium text-primary-700 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200
"
>
{{
t
(
'
admin.accounts.bulkActions.clear
'
)
}}
<
/button
>
<
/div
>
<
div
class
=
"
flex gap-2
"
>
<
button
@
click
=
"
$emit('delete')
"
class
=
"
btn btn-danger btn-sm
"
>
{{
t
(
'
admin.accounts.bulkActions.delete
'
)
}}
<
/button
>
<
button
@
click
=
"
$emit('toggle-schedulable', true)
"
class
=
"
btn btn-success btn-sm
"
>
{{
t
(
'
admin.accounts.bulkActions.enableScheduling
'
)
}}
<
/button
>
<
button
@
click
=
"
$emit('toggle-schedulable', false)
"
class
=
"
btn btn-warning btn-sm
"
>
{{
t
(
'
admin.accounts.bulkActions.disableScheduling
'
)
}}
<
/button
>
<
button
@
click
=
"
$emit('edit')
"
class
=
"
btn btn-primary btn-sm
"
>
{{
t
(
'
admin.accounts.bulkActions.edit
'
)
}}
<
/button
>
<
/div
>
<
/div
>
...
...
@@ -10,5 +29,5 @@
<
script
setup
lang
=
"
ts
"
>
import
{
useI18n
}
from
'
vue-i18n
'
defineProps
([
'
selectedIds
'
]);
defineEmits
([
'
delete
'
,
'
edit
'
]);
const
{
t
}
=
useI18n
()
defineProps
([
'
selectedIds
'
]);
defineEmits
([
'
delete
'
,
'
edit
'
,
'
clear
'
,
'
select-page
'
,
'
toggle-schedulable
'
]);
const
{
t
}
=
useI18n
()
<
/script>
\ No newline at end of file
frontend/src/composables/useTableLoader.ts
View file @
62dc0b95
...
...
@@ -43,7 +43,8 @@ export function useTableLoader<T, P extends Record<string, any>>(options: TableL
if
(
abortController
)
{
abortController
.
abort
()
}
abortController
=
new
AbortController
()
const
currentController
=
new
AbortController
()
abortController
=
currentController
loading
.
value
=
true
try
{
...
...
@@ -51,7 +52,7 @@ export function useTableLoader<T, P extends Record<string, any>>(options: TableL
pagination
.
page
,
pagination
.
page_size
,
toRaw
(
params
)
as
P
,
{
signal
:
abor
tController
.
signal
}
{
signal
:
curren
tController
.
signal
}
)
items
.
value
=
response
.
items
||
[]
...
...
@@ -63,7 +64,7 @@ export function useTableLoader<T, P extends Record<string, any>>(options: TableL
throw
error
}
}
finally
{
if
(
abortController
&&
!
abor
tController
.
signal
.
aborted
)
{
if
(
abortController
===
curren
tController
)
{
loading
.
value
=
false
}
}
...
...
@@ -77,7 +78,9 @@ export function useTableLoader<T, P extends Record<string, any>>(options: TableL
const
debouncedReload
=
useDebounceFn
(
reload
,
debounceMs
)
const
handlePageChange
=
(
page
:
number
)
=>
{
pagination
.
page
=
page
// 确保页码在有效范围内
const
validPage
=
Math
.
max
(
1
,
Math
.
min
(
page
,
pagination
.
pages
||
1
))
pagination
.
page
=
validPage
load
()
}
...
...
frontend/src/i18n/locales/en.ts
View file @
62dc0b95
...
...
@@ -1085,12 +1085,16 @@ export default {
tokenRefreshed
:
'
Token refreshed successfully
'
,
accountDeleted
:
'
Account deleted successfully
'
,
rateLimitCleared
:
'
Rate limit cleared successfully
'
,
bulkSchedulableEnabled
:
'
Successfully enabled scheduling for {count} account(s)
'
,
bulkSchedulableDisabled
:
'
Successfully disabled scheduling for {count} account(s)
'
,
bulkActions
:
{
selected
:
'
{count} account(s) selected
'
,
selectCurrentPage
:
'
Select this page
'
,
clear
:
'
Clear selection
'
,
edit
:
'
Bulk Edit
'
,
delete
:
'
Bulk Delete
'
delete
:
'
Bulk Delete
'
,
enableScheduling
:
'
Enable Scheduling
'
,
disableScheduling
:
'
Disable Scheduling
'
},
bulkEdit
:
{
title
:
'
Bulk Edit Accounts
'
,
...
...
Prev
1
2
Next
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