Files
Edu/apps/admin-portal/src/app/admin/dashboard/page.tsx
SpecialX b3511910d1 feat(admin-portal): 完整实现 admin-portal 管理端微前端
包含 src 全部实现、Dockerfile、配置文件等
2026-07-10 19:09:12 +08:00

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>
);
}