Skip to content

Commit baf11ec

Browse files
DouDOU-startclaude
andcommitted
feat(account): filter ungrouped accounts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3163d16 commit baf11ec

8 files changed

Lines changed: 41 additions & 5 deletions

File tree

backend/internal/app/account/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ type ListFilter struct {
8888
State string // active / rate_limited / degraded / disabled
8989
AccountType string
9090
GroupID *int
91+
Ungrouped bool
9192
ProxyID *int
9293
IDs []int
9394
}

backend/internal/infra/store/account_store.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ func (s *AccountStore) List(ctx context.Context, filter appaccount.ListFilter) (
4141
}
4242
if filter.GroupID != nil {
4343
query = query.Where(entaccount.HasGroupsWith(entgroup.ID(*filter.GroupID)))
44+
} else if filter.Ungrouped {
45+
query = query.Where(entaccount.Not(entaccount.HasGroups()))
4446
}
4547
if filter.ProxyID != nil {
4648
query = query.Where(entaccount.HasProxyWith(entproxy.IDEQ(*filter.ProxyID)))
@@ -83,6 +85,8 @@ func (s *AccountStore) ListAll(ctx context.Context, filter appaccount.ListFilter
8385
}
8486
if filter.GroupID != nil {
8587
query = query.Where(entaccount.HasGroupsWith(entgroup.ID(*filter.GroupID)))
88+
} else if filter.Ungrouped {
89+
query = query.Where(entaccount.Not(entaccount.HasGroups()))
8690
}
8791
if filter.ProxyID != nil {
8892
query = query.Where(entaccount.HasProxyWith(entproxy.IDEQ(*filter.ProxyID)))

backend/internal/server/handler/account_handler.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ func parseOptionalInt(raw string) *int {
6464
return &value
6565
}
6666

67+
func parseOptionalBool(raw string) bool {
68+
switch strings.ToLower(strings.TrimSpace(raw)) {
69+
case "1", "true", "yes", "y", "on":
70+
return true
71+
default:
72+
return false
73+
}
74+
}
75+
6776
// parseIDList 解析逗号分隔的整数列表(如 "1,2,3"),忽略空项与非法项。
6877
func parseIDList(raw string) []int {
6978
if raw == "" {

backend/internal/server/handler/account_handler_routes.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,18 @@ func (h *AccountHandler) ListAccounts(c *gin.Context) {
2323
return
2424
}
2525

26+
groupID := parseOptionalInt(c.Query("group_id"))
27+
ungrouped := groupID == nil && parseOptionalBool(c.Query("ungrouped"))
28+
2629
result, err := h.service.List(c.Request.Context(), appaccount.ListFilter{
2730
Page: page.Page,
2831
PageSize: page.PageSize,
2932
Keyword: page.Keyword,
3033
Platform: c.Query("platform"),
3134
State: c.Query("state"),
3235
AccountType: c.Query("account_type"),
33-
GroupID: parseOptionalInt(c.Query("group_id")),
36+
GroupID: groupID,
37+
Ungrouped: ungrouped,
3438
ProxyID: parseOptionalInt(c.Query("proxy_id")),
3539
})
3640
if err != nil {
@@ -54,12 +58,16 @@ func (h *AccountHandler) ListAccounts(c *gin.Context) {
5458
// ExportAccounts 按当前筛选条件导出账号(返回 JSON 数据,前端落盘为文件)。
5559
// 当前端传入 ids=1,2,3 时只导出这些账号;否则按查询条件导出全部匹配的账号。
5660
func (h *AccountHandler) ExportAccounts(c *gin.Context) {
61+
groupID := parseOptionalInt(c.Query("group_id"))
62+
ungrouped := groupID == nil && parseOptionalBool(c.Query("ungrouped"))
63+
5764
accounts, err := h.service.ExportAll(c.Request.Context(), appaccount.ListFilter{
5865
Keyword: c.Query("keyword"),
5966
Platform: c.Query("platform"),
6067
State: c.Query("state"),
6168
AccountType: c.Query("account_type"),
62-
GroupID: parseOptionalInt(c.Query("group_id")),
69+
GroupID: groupID,
70+
Ungrouped: ungrouped,
6371
ProxyID: parseOptionalInt(c.Query("proxy_id")),
6472
IDs: parseIDList(c.Query("ids")),
6573
})

web/src/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@
426426
"enable_dispatch": "Enable Dispatch",
427427
"select_groups": "Select groups",
428428
"all_groups": "All groups",
429+
"ungrouped": "Ungrouped",
429430
"all_proxies": "All proxies",
430431
"delete_title": "Delete Account",
431432
"delete_confirm": "Are you sure you want to delete account \"{{name}}\"? It will no longer participate in dispatch.",

web/src/i18n/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@
425425
"enable_dispatch": "启用调度",
426426
"select_groups": "选择分组",
427427
"all_groups": "全部分组",
428+
"ungrouped": "未分组",
428429
"all_proxies": "全部代理",
429430
"delete_title": "删除账号",
430431
"delete_confirm": "确定要删除账号「{{name}}」吗?删除后该账号将不再参与调度。",

web/src/pages/admin/AccountsPage.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ import type {
4848
PagedData,
4949
} from '../../shared/types';
5050

51+
const UNGROUPED_GROUP_FILTER = '__ungrouped__';
52+
5153
// formatCountdown 把剩余毫秒格式化成 "Xd Yh"/"Xh Ym"/"Ym" 样式,
5254
// 与 sub2api 的"限流中 10h 16m 自动恢复"徽标一致。
5355
function formatCountdown(ms: number): string {
@@ -282,7 +284,8 @@ export default function AccountsPage() {
282284
platform: platformFilter || undefined,
283285
state: stateFilter || undefined,
284286
account_type: typeFilter || undefined,
285-
group_id: groupFilter ? Number(groupFilter) : undefined,
287+
group_id: groupFilter && groupFilter !== UNGROUPED_GROUP_FILTER ? Number(groupFilter) : undefined,
288+
ungrouped: groupFilter === UNGROUPED_GROUP_FILTER ? true : undefined,
286289
proxy_id: proxyFilter ? Number(proxyFilter) : undefined,
287290
}),
288291
});
@@ -334,7 +337,8 @@ export default function AccountsPage() {
334337
platform: platformFilter || undefined,
335338
state: stateFilter || undefined,
336339
account_type: typeFilter || undefined,
337-
group_id: groupFilter ? Number(groupFilter) : undefined,
340+
group_id: groupFilter && groupFilter !== UNGROUPED_GROUP_FILTER ? Number(groupFilter) : undefined,
341+
ungrouped: groupFilter === UNGROUPED_GROUP_FILTER ? true : undefined,
338342
proxy_id: proxyFilter ? Number(proxyFilter) : undefined,
339343
});
340344
},
@@ -979,6 +983,7 @@ export default function AccountsPage() {
979983
onChange={(e) => { setGroupFilter(e.target.value); setPage(1); }}
980984
options={[
981985
{ value: '', label: t('accounts.all_groups') },
986+
{ value: UNGROUPED_GROUP_FILTER, label: t('accounts.ungrouped') },
982987
...(allGroupsData?.list ?? []).map((g) => ({ value: String(g.id), label: g.name })),
983988
]}
984989
/>

web/src/shared/api/accounts.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import type {
66
CredentialSchemaResp, ModelInfo, PageReq, PagedData,
77
} from '../types';
88

9-
export type AccountListFilter = { platform?: string; state?: string; account_type?: string; group_id?: number; proxy_id?: number };
9+
export type AccountListFilter = {
10+
platform?: string;
11+
state?: string;
12+
account_type?: string;
13+
group_id?: number;
14+
ungrouped?: boolean;
15+
proxy_id?: number;
16+
};
1017

1118
export const accountsApi = {
1219
list: (params: PageReq & AccountListFilter) =>

0 commit comments

Comments
 (0)