268 lines
7.9 KiB
TypeScript
268 lines
7.9 KiB
TypeScript
"use client";
|
|
|
|
import { type ReactNode } from "react";
|
|
import { useDashboard } from "@/hooks/use-dashboard";
|
|
import {
|
|
PageHeader,
|
|
PaperCard,
|
|
LoadingState,
|
|
ErrorState,
|
|
Badge,
|
|
} from "@/components/ui";
|
|
import { NotificationPanel } from "@/components/notification-panel";
|
|
import { t } from "@/lib/i18n";
|
|
|
|
interface StatCardProps {
|
|
label: string;
|
|
value: number | string;
|
|
suffix?: string;
|
|
}
|
|
|
|
function StatCard({ label, value, suffix }: StatCardProps): ReactNode {
|
|
return (
|
|
<PaperCard className="p-4">
|
|
<p
|
|
style={{
|
|
fontSize: 11,
|
|
color: "var(--color-ink-muted)",
|
|
textTransform: "uppercase",
|
|
letterSpacing: "0.05em",
|
|
}}
|
|
>
|
|
{label}
|
|
</p>
|
|
<p
|
|
style={{
|
|
fontFamily: "var(--font-serif)",
|
|
fontSize: 28,
|
|
color: "var(--color-ink)",
|
|
margin: "8px 0 0 0",
|
|
}}
|
|
>
|
|
{value}
|
|
{suffix && (
|
|
<span
|
|
style={{
|
|
fontSize: 14,
|
|
color: "var(--color-ink-muted)",
|
|
marginLeft: 4,
|
|
}}
|
|
>
|
|
{suffix}
|
|
</span>
|
|
)}
|
|
</p>
|
|
</PaperCard>
|
|
);
|
|
}
|
|
|
|
export default function DashboardPage(): ReactNode {
|
|
const { data, loading, error } = useDashboard();
|
|
|
|
return (
|
|
<div style={{ padding: 32, maxWidth: 1200, margin: "0 auto" }}>
|
|
<PageHeader
|
|
title={t("admin.dashboard.title")}
|
|
description="全局概览与系统健康"
|
|
/>
|
|
|
|
<NotificationPanel />
|
|
|
|
{loading && <LoadingState />}
|
|
{error && <ErrorState message={error.message} />}
|
|
{data && (
|
|
<div className="flex flex-col gap-6">
|
|
{/* 统计卡片 */}
|
|
<div className="grid grid-cols-4 gap-4">
|
|
<StatCard
|
|
label={t("admin.dashboard.totalTeachers")}
|
|
value={data.totalTeachers}
|
|
/>
|
|
<StatCard
|
|
label={t("admin.dashboard.totalStudents")}
|
|
value={data.totalStudents}
|
|
/>
|
|
<StatCard
|
|
label={t("admin.dashboard.totalClasses")}
|
|
value={data.totalClasses}
|
|
/>
|
|
<StatCard
|
|
label={t("admin.dashboard.totalSchools")}
|
|
value={data.totalSchools}
|
|
/>
|
|
<StatCard
|
|
label={t("admin.dashboard.schoolAvgScore")}
|
|
value={data.schoolAvgScore.toFixed(1)}
|
|
/>
|
|
<StatCard
|
|
label={t("admin.dashboard.activeUsersToday")}
|
|
value={data.activeUsersToday}
|
|
/>
|
|
<StatCard
|
|
label={t("admin.dashboard.auditEventsToday")}
|
|
value={data.auditEventsToday}
|
|
/>
|
|
</div>
|
|
|
|
{/* 趋势图(简化的表格) */}
|
|
<PaperCard className="p-4">
|
|
<h3
|
|
style={{
|
|
fontFamily: "var(--font-serif)",
|
|
fontSize: 16,
|
|
color: "var(--color-ink)",
|
|
marginBottom: 12,
|
|
}}
|
|
>
|
|
{t("admin.dashboard.trend")}
|
|
</h3>
|
|
<div style={{ overflowX: "auto" }}>
|
|
<table
|
|
style={{
|
|
width: "100%",
|
|
borderCollapse: "collapse",
|
|
fontSize: 12,
|
|
}}
|
|
>
|
|
<thead>
|
|
<tr style={{ borderBottom: "1px solid var(--color-rule)" }}>
|
|
<th
|
|
style={{
|
|
textAlign: "left",
|
|
padding: "6px 8px",
|
|
color: "var(--color-ink-muted)",
|
|
}}
|
|
>
|
|
日期
|
|
</th>
|
|
<th
|
|
style={{
|
|
textAlign: "right",
|
|
padding: "6px 8px",
|
|
color: "var(--color-ink-muted)",
|
|
}}
|
|
>
|
|
教师
|
|
</th>
|
|
<th
|
|
style={{
|
|
textAlign: "right",
|
|
padding: "6px 8px",
|
|
color: "var(--color-ink-muted)",
|
|
}}
|
|
>
|
|
学生
|
|
</th>
|
|
<th
|
|
style={{
|
|
textAlign: "right",
|
|
padding: "6px 8px",
|
|
color: "var(--color-ink-muted)",
|
|
}}
|
|
>
|
|
平均分
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.trend.map((row) => (
|
|
<tr
|
|
key={row.date}
|
|
style={{ borderBottom: "1px solid var(--color-rule)" }}
|
|
>
|
|
<td
|
|
style={{
|
|
padding: "6px 8px",
|
|
color: "var(--color-ink)",
|
|
}}
|
|
>
|
|
{row.date}
|
|
</td>
|
|
<td
|
|
style={{
|
|
padding: "6px 8px",
|
|
textAlign: "right",
|
|
color: "var(--color-ink)",
|
|
}}
|
|
>
|
|
{row.teachers}
|
|
</td>
|
|
<td
|
|
style={{
|
|
padding: "6px 8px",
|
|
textAlign: "right",
|
|
color: "var(--color-ink)",
|
|
}}
|
|
>
|
|
{row.students}
|
|
</td>
|
|
<td
|
|
style={{
|
|
padding: "6px 8px",
|
|
textAlign: "right",
|
|
color: "var(--color-ink)",
|
|
fontFamily: "var(--font-mono)",
|
|
}}
|
|
>
|
|
{row.avgScore.toFixed(1)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</PaperCard>
|
|
|
|
{/* 服务健康 */}
|
|
<PaperCard className="p-4">
|
|
<h3
|
|
style={{
|
|
fontFamily: "var(--font-serif)",
|
|
fontSize: 16,
|
|
color: "var(--color-ink)",
|
|
marginBottom: 12,
|
|
}}
|
|
>
|
|
{t("admin.dashboard.serviceHealth")}
|
|
</h3>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{data.serviceHealth.map((svc) => (
|
|
<div
|
|
key={svc.serviceName}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
padding: "8px 12px",
|
|
borderBottom: "1px solid var(--color-rule)",
|
|
}}
|
|
>
|
|
<span
|
|
style={{
|
|
fontFamily: "var(--font-mono)",
|
|
fontSize: 12,
|
|
color: "var(--color-ink)",
|
|
}}
|
|
>
|
|
{svc.serviceName}
|
|
</span>
|
|
<div
|
|
style={{ display: "flex", alignItems: "center", gap: 8 }}
|
|
>
|
|
<span
|
|
style={{ fontSize: 11, color: "var(--color-ink-muted)" }}
|
|
>
|
|
{svc.latencyMs}ms
|
|
</span>
|
|
<Badge status={svc.status} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</PaperCard>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|