feat: 完成 P1 全部功能 + 修复 proxy 导出 + 切换 MySQL 端口至 14013
## P1 功能(20 项) - 站内消息系统、家长仪表盘、学生考勤管理 - Excel 导入导出、用户批量导入、成绩导出 - 排课规则+自动排课+课表调整 - 成绩趋势+对比分析、密码安全策略、速率限制 - 数据变更日志、文件预览+存储策略、全文检索 - 依赖审计集成 CI、数据库定时备份、E2E 测试完善 - 通知偏好管理 ## 基础设施修复 - src/proxy.ts: 将 middleware 导出重命名为 proxy(Next.js 16 要求) - .env: MySQL 端口从 13002 切换至 14013 - scripts/create-db.ts: 新增数据库初始化脚本 ## 架构文档同步 - 004_architecture_impact_map.md 和 005_architecture_data.json 完整记录所有新增表、模块、路由、权限、依赖关系
This commit is contained in:
132
src/modules/grades/components/class-comparison-chart.tsx
Normal file
132
src/modules/grades/components/class-comparison-chart.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
"use client"
|
||||
|
||||
import { BarChart3 } from "lucide-react"
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts"
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/shared/components/ui/card"
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/shared/components/ui/chart"
|
||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
import type { ClassComparisonItem } from "@/modules/grades/types"
|
||||
|
||||
const chartConfig = {
|
||||
averageScore: { label: "Average (%)", color: "hsl(var(--primary))" },
|
||||
passRate: { label: "Pass Rate (%)", color: "hsl(var(--chart-2))" },
|
||||
excellentRate: { label: "Excellent (%)", color: "hsl(var(--chart-3))" },
|
||||
}
|
||||
|
||||
interface ClassComparisonChartProps {
|
||||
data: ClassComparisonItem[]
|
||||
}
|
||||
|
||||
export function ClassComparisonChart({ data }: ClassComparisonChartProps) {
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Class Comparison
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Compare average, pass rate, and excellent rate across classes.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EmptyState
|
||||
icon={BarChart3}
|
||||
title="No comparison data"
|
||||
description="Select a grade and subject to compare classes."
|
||||
className="border-none h-60"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const chartData = data.map((d) => ({
|
||||
name: d.className,
|
||||
averageScore: d.averageScore,
|
||||
passRate: d.passRate,
|
||||
excellentRate: d.excellentRate,
|
||||
count: d.count,
|
||||
studentCount: d.studentCount,
|
||||
}))
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Class Comparison
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Average score, pass rate (≥60%), and excellent rate (≥85%) per class.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig} className="h-[300px] w-full">
|
||||
<BarChart
|
||||
data={chartData}
|
||||
margin={{ left: 8, right: 8, top: 8, bottom: 8 }}
|
||||
>
|
||||
<CartesianGrid
|
||||
vertical={false}
|
||||
strokeDasharray="4 4"
|
||||
strokeOpacity={0.4}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value: string) =>
|
||||
value.length > 8 ? `${value.slice(0, 8)}...` : value
|
||||
}
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value: number) => `${value}%`}
|
||||
width={36}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent className="w-[240px]" />} />
|
||||
<Legend />
|
||||
<Bar
|
||||
dataKey="averageScore"
|
||||
fill="var(--color-averageScore)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="passRate"
|
||||
fill="var(--color-passRate)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="excellentRate"
|
||||
fill="var(--color-excellentRate)"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user