feat: 添加令牌重置已用额度功能
- 后端新增 POST /api/admin/token/:key/reset 接口 - 前端令牌列表添加重置额度按钮(桌面端和移动端菜单) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bdb4825cdc
commit
98522e7678
4 files changed
+67
-3
No files matched your search
@@ -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' }),
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user