feat: 账号测试状态持久化、分页选择器、点击账号名复制

- Account 结构加 TestStatus 字段,测试后写入 config.json
- listAccounts 接口返回 test_status,前端根据结果显示红/绿/黄状态点
- 分页选择器支持 10/20/50/100/500/1000/2000/5000
- 点击账号名自动复制到剪贴板,hover 显示复制图标,复制后显示绿色对勾
This commit is contained in:
root
2026-02-27 20:58:18 +08:00
parent 4b73315df0
commit f6f6a651fd
10 changed files with 83 additions and 14 deletions

View File

@@ -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}
/>
<AddKeyModal

View File

@@ -1,4 +1,5 @@
import { ChevronLeft, ChevronRight, Play, Plus, Trash2 } from 'lucide-react'
import { useState } from 'react'
import { ChevronLeft, ChevronRight, Check, Copy, Play, Plus, Trash2 } from 'lucide-react'
import clsx from 'clsx'
export default function AccountsTable({
@@ -10,6 +11,7 @@ export default function AccountsTable({
batchProgress,
totalAccounts,
page,
pageSize,
totalPages,
resolveAccountIdentifier,
onTestAll,
@@ -18,7 +20,16 @@ export default function AccountsTable({
onDeleteAccount,
onPrevPage,
onNextPage,
onPageSizeChange,
}) {
const [copiedId, setCopiedId] = useState(null)
const copyId = (id) => {
navigator.clipboard.writeText(id).then(() => {
setCopiedId(id)
setTimeout(() => setCopiedId(null), 1500)
})
}
return (
<div className="bg-card border border-border rounded-xl overflow-hidden shadow-sm">
<div className="p-6 border-b border-border flex flex-col md:flex-row md:items-center justify-between gap-4">
@@ -83,12 +94,23 @@ export default function AccountsTable({
<div className="flex items-center gap-3 min-w-0">
<div className={clsx(
"w-2 h-2 rounded-full shrink-0",
acc.has_token ? "bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]" : "bg-amber-500"
acc.test_status === 'failed' ? "bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]" :
(acc.test_status === 'ok' || acc.has_token) ? "bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]" :
"bg-amber-500"
)} />
<div className="min-w-0">
<div className="font-medium truncate">{id || '-'}</div>
<div
className="font-medium truncate flex items-center gap-1.5 cursor-pointer hover:text-primary transition-colors group"
onClick={() => copyId(id)}
>
<span className="truncate">{id || '-'}</span>
{copiedId === id
? <Check className="w-3 h-3 text-emerald-500 shrink-0" />
: <Copy className="w-3 h-3 opacity-0 group-hover:opacity-50 shrink-0 transition-opacity" />
}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-0.5">
<span>{acc.has_token ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')}</span>
<span>{acc.test_status === 'failed' ? t('accountManager.testStatusFailed') : (acc.test_status === 'ok' || acc.has_token) ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')}</span>
{acc.token_preview && (
<span className="font-mono bg-muted px-1.5 py-0.5 rounded text-[10px]">
{acc.token_preview}
@@ -122,8 +144,19 @@ export default function AccountsTable({
{totalPages > 1 && (
<div className="p-4 border-t border-border flex items-center justify-between">
<div className="text-sm text-muted-foreground">
{t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })}
<div className="flex items-center gap-3">
<div className="text-sm text-muted-foreground">
{t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })}
</div>
<select
value={pageSize}
onChange={e => onPageSizeChange(Number(e.target.value))}
className="text-sm border border-border rounded-md px-2 py-1 bg-background text-foreground"
>
{[10, 20, 50, 100, 500, 1000, 2000, 5000].map(s => (
<option key={s} value={s}>{s}</option>
))}
</select>
</div>
<div className="flex items-center gap-2">
<button

View File

@@ -6,7 +6,7 @@ export function useAccountsData({ apiFetch }) {
const [accounts, setAccounts] = useState([])
const [page, setPage] = useState(1)
const [pageSize] = useState(10)
const [pageSize, setPageSize] = useState(10)
const [totalPages, setTotalPages] = useState(1)
const [totalAccounts, setTotalAccounts] = useState(0)
const [loadingAccounts, setLoadingAccounts] = useState(false)
@@ -16,10 +16,10 @@ export function useAccountsData({ apiFetch }) {
return String(acc.identifier || acc.email || acc.mobile || '').trim()
}
const fetchAccounts = async (targetPage = page) => {
const fetchAccounts = async (targetPage = page, targetPageSize = pageSize) => {
setLoadingAccounts(true)
try {
const res = await apiFetch(`/admin/accounts?page=${targetPage}&page_size=${pageSize}`)
const res = await apiFetch(`/admin/accounts?page=${targetPage}&page_size=${targetPageSize}`)
if (res.ok) {
const data = await res.json()
setAccounts(data.items || [])
@@ -34,6 +34,11 @@ export function useAccountsData({ apiFetch }) {
}
}
const changePageSize = (newSize) => {
setPageSize(newSize)
fetchAccounts(1, newSize)
}
const fetchQueueStatus = async () => {
try {
const res = await apiFetch('/admin/queue/status')
@@ -59,10 +64,12 @@ export function useAccountsData({ apiFetch }) {
setKeysExpanded,
accounts,
page,
pageSize,
totalPages,
totalAccounts,
loadingAccounts,
fetchAccounts,
changePageSize,
resolveAccountIdentifier,
}
}

View File

@@ -113,6 +113,7 @@
"testingAllAccounts": "Testing all accounts...",
"sessionActive": "Session active",
"reauthRequired": "Re-auth required",
"testStatusFailed": "Last test failed",
"noAccounts": "No accounts found.",
"modalAddKeyTitle": "Add API key",
"newKeyLabel": "New key value",

View File

@@ -113,6 +113,7 @@
"testingAllAccounts": "正在测试所有账号...",
"sessionActive": "已建立会话",
"reauthRequired": "需重新登录",
"testStatusFailed": "上次测试失败",
"noAccounts": "未找到任何账号",
"modalAddKeyTitle": "添加 API 密钥",
"newKeyLabel": "新密钥值",