feat: 添加令牌重置已用额度功能

- 后端新增 POST /api/admin/token/:key/reset 接口
- 前端令牌列表添加重置额度按钮(桌面端和移动端菜单)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
dreamhunter2333andClaude Opus 4.5 committed 2026-01-30 21:52:22 +08:00
1 parent bdb4825cdc
commit 98522e7678
4 files changed
+67 -3

No files matched your search

+3
View File
@@ -161,6 +161,9 @@ export const apiClient = {
deleteToken: (key: string) =>
request(`/api/admin/token/${encodeURIComponent(key)}`, { method: 'DELETE' }),
resetTokenUsage: (key: string) =>
request(`/api/admin/token/${encodeURIComponent(key)}/reset`, { method: 'POST' }),
// Pricing APIs
getPricing: () => request<ApiResponse>('/api/admin/pricing', { method: 'GET' }),
+32 -2
View File
@@ -25,6 +25,7 @@ import {
ChevronRight,
AlertCircle,
Search,
RotateCcw,
} from 'lucide-react'
import { Checkbox } from '@/components/ui/checkbox'
import { PageContainer } from '@/components/ui/page-container'
@@ -106,6 +107,19 @@ export function Tokens() {
},
})
const resetUsageMutation = useMutation({
mutationFn: async (key: string) => {
return apiClient.resetTokenUsage(key)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tokens'] })
addToast('已用额度已重置', 'success')
},
onError: (error: any) => {
addToast('重置失败:' + error.message, 'error')
},
})
const resetForm = () => {
setFormData({ name: '', channel_keys: [], total_quota: 0 })
setTokenKey('')
@@ -137,6 +151,12 @@ export function Tokens() {
}
}
const handleResetUsage = (key: string) => {
if (confirm(`确定要重置此令牌的已用额度吗?`)) {
resetUsageMutation.mutate(key)
}
}
const handleCopy = async (text: string) => {
try {
await copyToClipboard(text)
@@ -318,6 +338,13 @@ export function Tokens() {
<Pencil className="h-4 w-4" />
编辑
</button>
<button
className="w-full px-3 py-2 text-sm text-left hover:bg-muted flex items-center gap-2"
onClick={() => handleResetUsage(token.key)}
>
<RotateCcw className="h-4 w-4" />
重置额度
</button>
<button
className="w-full px-3 py-2 text-sm text-left hover:bg-muted flex items-center gap-2 text-destructive"
onClick={() => handleDelete(token.key)}
@@ -389,10 +416,13 @@ export function Tokens() {
</div>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => handleEdit(token)}>
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => handleEdit(token)} title="编辑">
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-destructive hover:text-destructive" onClick={() => handleDelete(token.key)}>
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => handleResetUsage(token.key)} title="重置额度">
<RotateCcw className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-destructive hover:text-destructive" onClick={() => handleDelete(token.key)} title="删除">
<Trash2 className="h-4 w-4" />
</Button>
</div>
+2 -1
View File
@@ -5,7 +5,7 @@ import {
ChannelGetEndpoint, ChannelUpsertEndpoint, ChannelDeleteEndpoint
} from "./channel_api"
import {
TokenListEndpoint, TokenUpsertEndpoint, TokenDeleteEndpoint
TokenListEndpoint, TokenUpsertEndpoint, TokenDeleteEndpoint, TokenResetUsageEndpoint
} from "./token_api"
import {
PricingGetEndpoint, PricingUpdateEndpoint
@@ -34,6 +34,7 @@ api.delete("/api/admin/channel/:key", ChannelDeleteEndpoint)
// Token management routes
api.get("/api/admin/token", TokenListEndpoint)
api.post("/api/admin/token/:key", TokenUpsertEndpoint)
api.post("/api/admin/token/:key/reset", TokenResetUsageEndpoint)
api.delete("/api/admin/token/:key", TokenDeleteEndpoint)
// Pricing management routes
+30
View File
@@ -98,6 +98,36 @@ export class TokenUpsertEndpoint extends OpenAPIRoute {
}
}
// Token 重置额度 API
export class TokenResetUsageEndpoint extends OpenAPIRoute {
schema = {
tags: ['Admin API'],
summary: 'Reset token usage to zero',
request: {
params: z.object({
key: z.string().describe('Token key'),
}),
},
responses: {
...CommonSuccessfulResponse(z.boolean()),
...CommonErrorResponse,
},
};
async handle(c: Context<HonoCustomType>) {
const { key } = c.req.param();
const result = await c.env.DB.prepare(
`UPDATE api_token SET usage = 0, updated_at = datetime('now') WHERE key = ?`
).bind(key).run();
return {
success: true,
data: result.success
} as CommonResponse;
}
}
// Token 删除 API
export class TokenDeleteEndpoint extends OpenAPIRoute {
schema = {