feat: API billing dashboard — DeepSeek + SiliconFlow + MiniMax + Baidu
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 5s

This commit is contained in:
WangDL 2026-05-22 15:21:57 +08:00
parent 1a55fce7e4
commit cab92155e5
5 changed files with 88 additions and 1 deletions

View File

@ -10,6 +10,7 @@ import PageLoading from './components/PageLoading'
import AdminLayout from './layouts/AdminLayout'
const Login = lazy(() => import('./pages/Login'))
const BillingPage = lazy(() => import('./pages/Billing'))
const GiteaEmbed = lazy(() => import('./pages/GiteaEmbed'))
const ServersPage = lazy(() => import("./pages/Servers"))
const AuditLogPage = lazy(() => import("./pages/AuditLog"))
@ -82,6 +83,14 @@ function App() {
</PermissionGuard>
}
/>
<Route
path="billing"
element={
<PermissionGuard requiredRole="SUPER_ADMIN">
<Suspense fallback={<PageLoading />}><BillingPage /></Suspense>
</PermissionGuard>
}
/>
<Route
path="git"
element={

View File

@ -1,5 +1,5 @@
import type React from 'react'
import { CodeOutlined, CloudServerOutlined, RobotOutlined, DashboardOutlined,
import { DollarOutlined, CodeOutlined, CloudServerOutlined, RobotOutlined, DashboardOutlined,
UserOutlined,
BookOutlined,
ImportOutlined,
@ -47,6 +47,7 @@ export const adminMenuItems: AdminMenuItem[] = [
{ path: '/ai-costs', name: 'AI 调用与成本', icon: <CloudOutlined /> },
{ path: '/files', name: '文件与 COS', icon: <FileOutlined /> },
{ path: '/settings', name: '系统配置', icon: <SettingOutlined />, requiredRole: 'ADMIN' },
{ path: '/billing', name: 'API 用量', icon: <DollarOutlined />, requiredRole: 'SUPER_ADMIN' },
{ path: '/git', name: '代码仓库', icon: <CodeOutlined /> },
{ path: '/servers', name: '服务器运维', icon: <CloudServerOutlined />, requiredRole: 'SUPER_ADMIN' },
{ path: '/audit', name: '审计日志', icon: <SafetyOutlined />, requiredRole: 'ADMIN' },

View File

@ -21,6 +21,7 @@ const breadcrumbMap: Record<string, string> = {
'/ai-costs': 'AI 调用与成本',
'/files': '文件与 COS',
'/settings': '系统配置',
'/billing': 'API 用量',
'/git': '代码仓库',
'/servers': '服务器运维',
'/audit': '审计日志',

61
src/pages/Billing.tsx Normal file
View File

@ -0,0 +1,61 @@
import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Card, Row, Col, Statistic, Button, Tag, Space, Typography, App } from 'antd'
import { DollarOutlined, ReloadOutlined, LinkOutlined } from '@ant-design/icons'
import { getBilling, type BillingInfo } from '@/services/billing-api'
const { Text } = Typography
function BillingCard({ p }: { p: BillingInfo }) {
const color = p.status === 'ok' ? (p.currency === 'CNY' && parseFloat(p.balance) < 10 ? '#faad14' : '#52c41a') : '#999'
return (
<Card
title={<Space><DollarOutlined />{p.name}<Tag color={p.status === 'ok' ? 'green' : 'default'}>{p.status === 'ok' ? '正常' : '未知'}</Tag></Space>}
extra={<Button type="link" size="small" icon={<LinkOutlined />} href={p.consoleUrl} target="_blank"></Button>}
>
<Statistic title="余额" value={p.balance} valueStyle={{ color, fontSize: 28 }} suffix={<Text type="secondary" style={{ fontSize: 14 }}>{p.currency}</Text>} />
<div style={{ marginTop: 8 }}>
<Text type="secondary" style={{ fontSize: 12 }}>: {p.model}</Text>
<br />
<Text type="secondary" style={{ fontSize: 12 }}>{p.note}</Text>
</div>
</Card>
)
}
function BillingContent() {
const qc = useQueryClient()
const [refreshing, setRefreshing] = useState(false)
const { data } = useQuery({
queryKey: ['billing'],
queryFn: getBilling,
staleTime: 60_000,
})
const refresh = async () => {
setRefreshing(true)
await qc.invalidateQueries({ queryKey: ['billing'] })
setTimeout(() => setRefreshing(false), 800)
}
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<Typography.Title level={5} style={{ margin: 0 }}><DollarOutlined /> API </Typography.Title>
<Button icon={<ReloadOutlined spin={refreshing} />} onClick={refresh} loading={refreshing}></Button>
</div>
<Row gutter={[16, 16]}>
{(data?.providers || []).map(p => (
<Col xs={24} sm={12} lg={6} key={p.name}>
<BillingCard p={p} />
</Col>
))}
</Row>
</div>
)
}
export default function BillingPage() {
return <App><BillingContent /></App>
}

View File

@ -0,0 +1,15 @@
import { api } from './http-client'
export interface BillingInfo {
name: string
model: string
balance: string
currency: string
status: 'ok' | 'unknown'
consoleUrl: string
note: string
}
export function getBilling(): Promise<{ providers: BillingInfo[] }> {
return api.get('/admin-api/billing')
}