- Vite + React 18 + TypeScript - TailwindCSS 暗色主题 - 仪表板、分析、事件浏览页面 - Recharts 图表组件 - Zustand 状态管理 - TanStack Query 数据请求
121 lines
5.1 KiB
TypeScript
121 lines
5.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { api } from '@/services/api';
|
|
import { cn } from '@/lib/utils';
|
|
import { Search, Filter, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
|
|
export default function Events() {
|
|
const [page, setPage] = useState(1);
|
|
const [search, setSearch] = useState('');
|
|
const limit = 20;
|
|
|
|
const { data: events, isLoading } = useQuery({
|
|
queryKey: ['events', page, limit],
|
|
queryFn: () => api.getEvents({ skip: (page - 1) * limit, limit }),
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-6 animate-fade-in">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-display font-bold">事件浏览</h1>
|
|
<p className="text-zinc-500 text-sm mt-1">查看所有采集的事件数据</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative flex-1 max-w-md">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" />
|
|
<input
|
|
type="text"
|
|
placeholder="搜索事件..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="w-full pl-10 pr-4 py-2 bg-[var(--card)] border border-[var(--border)] rounded-lg text-sm placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-primary-500/50"
|
|
/>
|
|
</div>
|
|
<button className="flex items-center gap-2 px-4 py-2 bg-[var(--card)] border border-[var(--border)] rounded-lg text-sm hover:bg-zinc-800 transition-colors">
|
|
<Filter className="w-4 h-4" />
|
|
筛选
|
|
</button>
|
|
</div>
|
|
|
|
{/* Events Table */}
|
|
<div className="bg-[var(--card)] rounded-xl border border-[var(--border)] overflow-hidden">
|
|
<table className="w-full">
|
|
<thead className="bg-zinc-800/50">
|
|
<tr>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-zinc-400 uppercase">事件ID</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-zinc-400 uppercase">类型</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-zinc-400 uppercase">语言</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-zinc-400 uppercase">IDE</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-zinc-400 uppercase">时间</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-[var(--border)]">
|
|
{isLoading ? (
|
|
[...Array(10)].map((_, i) => (
|
|
<tr key={i}>
|
|
<td colSpan={5} className="px-6 py-4">
|
|
<div className="h-4 bg-zinc-800 rounded animate-pulse" />
|
|
</td>
|
|
</tr>
|
|
))
|
|
) : (
|
|
(events ?? []).map((event) => (
|
|
<tr key={event.eventId} className="hover:bg-zinc-800/30 transition-colors">
|
|
<td className="px-6 py-4 text-sm font-mono text-zinc-300">
|
|
{event.eventId.substring(0, 8)}...
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span className={cn(
|
|
'px-2 py-1 text-xs rounded-full',
|
|
event.eventType.includes('accepted')
|
|
? 'bg-emerald-500/10 text-emerald-400'
|
|
: event.eventType.includes('rejected')
|
|
? 'bg-rose-500/10 text-rose-400'
|
|
: 'bg-zinc-500/10 text-zinc-400'
|
|
)}>
|
|
{event.eventType.replace(/_/g, ' ')}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-zinc-300">{event.language ?? '-'}</td>
|
|
<td className="px-6 py-4 text-sm text-zinc-300">{event.ideType}</td>
|
|
<td className="px-6 py-4 text-sm text-zinc-500">
|
|
{new Date(event.timestamp).toLocaleString()}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
|
|
{/* Pagination */}
|
|
<div className="flex items-center justify-between px-6 py-4 border-t border-[var(--border)]">
|
|
<p className="text-sm text-zinc-500">
|
|
显示 {(page - 1) * limit + 1} - {page * limit} 条
|
|
</p>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => setPage(Math.max(1, page - 1))}
|
|
disabled={page === 1}
|
|
className="p-2 rounded-lg border border-[var(--border)] disabled:opacity-50 hover:bg-zinc-800 transition-colors"
|
|
>
|
|
<ChevronLeft className="w-4 h-4" />
|
|
</button>
|
|
<span className="text-sm text-zinc-400">第 {page} 页</span>
|
|
<button
|
|
onClick={() => setPage(page + 1)}
|
|
className="p-2 rounded-lg border border-[var(--border)] hover:bg-zinc-800 transition-colors"
|
|
>
|
|
<ChevronRight className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|