feat: newapi-dashboard 全栈项目初始化

为 new-api (LLM API 网关) 构建独立前端管理面板:
- React 18 + TypeScript + Vite + Ant Design 5 前端
- Node.js + Express + better-sqlite3 BFF 后端
- 登录页: 站点选择器 + UserID + AccessToken 认证
- 仪表盘: 用户信息、额度环形图、7天趋势图、模型饼图、令牌概览、日志时间线
- 账单页: 筛选日志表格、模型消耗柱状图、充值记录、CSV/PDF 导出(HMAC签名)
- 管理员站点管理: 站点 CRUD
- API 代理: 多站点切换,会话管理

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
LAMCLOD
2026-03-08 18:00:28 +08:00
commit f6036cab66
36 changed files with 10579 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
import { Card, Table, Tag, Typography } from 'antd'
import { KeyOutlined } from '@ant-design/icons'
import { quotaToUsd, getTokenStatusTag, formatTimestamp } from '@/utils/quota'
const { Text } = Typography
interface TokenItem {
id: number
name: string
status: number
remain_quota: number
used_quota: number
unlimited_quota: boolean
created_time: number
expired_time: number
}
interface Props {
tokens: TokenItem[]
loading: boolean
}
export default function TokenOverview({ tokens, loading }: Props) {
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
render: (status: number) => {
const tag = getTokenStatusTag(status)
return <Tag color={tag.color}>{tag.text}</Tag>
},
},
{
title: '剩余额度',
key: 'remain',
width: 120,
render: (_: any, record: TokenItem) =>
record.unlimited_quota ? <Text type="success"></Text> : `$${quotaToUsd(record.remain_quota)}`,
},
{
title: '已用额度',
dataIndex: 'used_quota',
key: 'used',
width: 120,
render: (val: number) => `$${quotaToUsd(val)}`,
},
{
title: '过期时间',
dataIndex: 'expired_time',
key: 'expired',
width: 120,
render: (val: number) => val === -1 ? '永不过期' : formatTimestamp(val),
},
]
return (
<Card title={<><KeyOutlined /> </>} hoverable>
<Table
dataSource={tokens}
columns={columns}
rowKey="id"
loading={loading}
pagination={false}
size="small"
scroll={{ y: 240 }}
/>
</Card>
)
}