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,64 @@
import { Card, Timeline, Typography, Tag, Empty } from 'antd'
import { HistoryOutlined } from '@ant-design/icons'
import { formatTimestamp, quotaToUsd } from '@/utils/quota'
const { Text } = Typography
interface LogItem {
id: number
created_at: number
model_name: string
token_name: string
quota: number
type: number
}
interface Props {
logs: LogItem[]
loading: boolean
}
const logTypeMap: Record<number, { color: string; label: string }> = {
1: { color: 'green', label: '充值' },
2: { color: 'blue', label: '消费' },
3: { color: 'orange', label: '管理' },
4: { color: 'purple', label: '系统' },
5: { color: 'red', label: '错误' },
6: { color: 'cyan', label: '退款' },
}
export default function RecentLogs({ logs, loading }: Props) {
if (!loading && logs.length === 0) {
return <Card title={<><HistoryOutlined /> </>}><Empty description="暂无日志" /></Card>
}
return (
<Card title={<><HistoryOutlined /> </>} loading={loading} hoverable>
<Timeline
items={logs.slice(0, 10).map((log) => {
const typeInfo = logTypeMap[log.type] || { color: 'default', label: '未知' }
return {
color: typeInfo.color,
children: (
<div>
<div>
<Tag color={typeInfo.color} style={{ marginRight: 8 }}>{typeInfo.label}</Tag>
<Text strong>{log.model_name || '-'}</Text>
{log.quota > 0 && (
<Text type="secondary" style={{ marginLeft: 8 }}>
${quotaToUsd(log.quota)}
</Text>
)}
</div>
<Text type="secondary" style={{ fontSize: 12 }}>
{formatTimestamp(log.created_at)}
{log.token_name && ` · ${log.token_name}`}
</Text>
</div>
),
}
})}
/>
</Card>
)
}