From f6f6a651fdb1a75f6dd49418b092bd0e9cfb5962 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 27 Feb 2026 20:58:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B4=A6=E5=8F=B7=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=8C=81=E4=B9=85=E5=8C=96=E3=80=81=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E9=80=89=E6=8B=A9=E5=99=A8=E3=80=81=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=90=8D=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Account 结构加 TestStatus 字段,测试后写入 config.json - listAccounts 接口返回 test_status,前端根据结果显示红/绿/黄状态点 - 分页选择器支持 10/20/50/100/500/1000/2000/5000 - 点击账号名自动复制到剪贴板,hover 显示复制图标,复制后显示绿色对勾 --- internal/admin/deps.go | 1 + internal/admin/handler_accounts_crud.go | 1 + internal/admin/handler_accounts_testing.go | 10 ++++- internal/config/config.go | 9 ++-- internal/config/store.go | 12 +++++ .../account/AccountManagerContainer.jsx | 4 ++ webui/src/features/account/AccountsTable.jsx | 45 ++++++++++++++++--- webui/src/features/account/useAccountsData.js | 13 ++++-- webui/src/locales/en.json | 1 + webui/src/locales/zh.json | 1 + 10 files changed, 83 insertions(+), 14 deletions(-) diff --git a/internal/admin/deps.go b/internal/admin/deps.go index e92c37b..7debcf0 100644 --- a/internal/admin/deps.go +++ b/internal/admin/deps.go @@ -16,6 +16,7 @@ type ConfigStore interface { Accounts() []config.Account FindAccount(identifier string) (config.Account, bool) UpdateAccountToken(identifier, token string) error + UpdateAccountTestStatus(identifier, status string) error Update(mutator func(*config.Config) error) error ExportJSONAndBase64() (string, string, error) IsEnvBacked() bool diff --git a/internal/admin/handler_accounts_crud.go b/internal/admin/handler_accounts_crud.go index daaa434..a0d64df 100644 --- a/internal/admin/handler_accounts_crud.go +++ b/internal/admin/handler_accounts_crud.go @@ -56,6 +56,7 @@ func (h *Handler) listAccounts(w http.ResponseWriter, r *http.Request) { "has_password": acc.Password != "", "has_token": token != "", "token_preview": preview, + "test_status": acc.TestStatus, }) } writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize, "total_pages": totalPages}) diff --git a/internal/admin/handler_accounts_testing.go b/internal/admin/handler_accounts_testing.go index 2bd7706..7a7430d 100644 --- a/internal/admin/handler_accounts_testing.go +++ b/internal/admin/handler_accounts_testing.go @@ -88,7 +88,15 @@ func runAccountTestsConcurrently(accounts []config.Account, maxConcurrency int, func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, message string) map[string]any { start := time.Now() - result := map[string]any{"account": acc.Identifier(), "success": false, "response_time": 0, "message": "", "model": model} + identifier := acc.Identifier() + result := map[string]any{"account": identifier, "success": false, "response_time": 0, "message": "", "model": model} + defer func() { + status := "failed" + if ok, _ := result["success"].(bool); ok { + status = "ok" + } + _ = h.Store.UpdateAccountTestStatus(identifier, status) + }() token := strings.TrimSpace(acc.Token) if token == "" { newToken, err := h.DS.Login(ctx, acc) diff --git a/internal/config/config.go b/internal/config/config.go index 4b281a2..b8d59d6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,10 +18,11 @@ type Config struct { } type Account struct { - Email string `json:"email,omitempty"` - Mobile string `json:"mobile,omitempty"` - Password string `json:"password,omitempty"` - Token string `json:"token,omitempty"` + Email string `json:"email,omitempty"` + Mobile string `json:"mobile,omitempty"` + Password string `json:"password,omitempty"` + Token string `json:"token,omitempty"` + TestStatus string `json:"test_status,omitempty"` } type CompatConfig struct { diff --git a/internal/config/store.go b/internal/config/store.go index 2e6fcaf..7a09cdc 100644 --- a/internal/config/store.go +++ b/internal/config/store.go @@ -97,6 +97,18 @@ func (s *Store) FindAccount(identifier string) (Account, bool) { return Account{}, false } +func (s *Store) UpdateAccountTestStatus(identifier, status string) error { + identifier = strings.TrimSpace(identifier) + s.mu.Lock() + defer s.mu.Unlock() + idx, ok := s.findAccountIndexLocked(identifier) + if !ok { + return errors.New("account not found") + } + s.cfg.Accounts[idx].TestStatus = status + return s.saveLocked() +} + func (s *Store) UpdateAccountToken(identifier, token string) error { identifier = strings.TrimSpace(identifier) s.mu.Lock() diff --git a/webui/src/features/account/AccountManagerContainer.jsx b/webui/src/features/account/AccountManagerContainer.jsx index f5f9324..558739d 100644 --- a/webui/src/features/account/AccountManagerContainer.jsx +++ b/webui/src/features/account/AccountManagerContainer.jsx @@ -17,10 +17,12 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage, setKeysExpanded, accounts, page, + pageSize, totalPages, totalAccounts, loadingAccounts, fetchAccounts, + changePageSize, resolveAccountIdentifier, } = useAccountsData({ apiFetch }) @@ -79,6 +81,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage, batchProgress={batchProgress} totalAccounts={totalAccounts} page={page} + pageSize={pageSize} totalPages={totalPages} resolveAccountIdentifier={resolveAccountIdentifier} onTestAll={testAllAccounts} @@ -87,6 +90,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage, onDeleteAccount={deleteAccount} onPrevPage={() => fetchAccounts(page - 1)} onNextPage={() => fetchAccounts(page + 1)} + onPageSizeChange={changePageSize} /> { + navigator.clipboard.writeText(id).then(() => { + setCopiedId(id) + setTimeout(() => setCopiedId(null), 1500) + }) + } return (
@@ -83,12 +94,23 @@ export default function AccountsTable({
-
{id || '-'}
+
copyId(id)} + > + {id || '-'} + {copiedId === id + ? + : + } +
- {acc.has_token ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')} + {acc.test_status === 'failed' ? t('accountManager.testStatusFailed') : (acc.test_status === 'ok' || acc.has_token) ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')} {acc.token_preview && ( {acc.token_preview} @@ -122,8 +144,19 @@ export default function AccountsTable({ {totalPages > 1 && (
-
- {t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })} +
+
+ {t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })} +
+