"use client" import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts" import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig, } from "@/shared/components/ui/chart" import { cn } from "@/shared/lib/utils" /** * 趋势折线图:统一的 LineChart 配置(CartesianGrid + XAxis + YAxis + ChartTooltip + Line)。 * * 覆盖以下重复模式(4 个文件几乎逐行相同): * - GradeTrendChart(dataKey=normalizedScore, height=h-[280px]) * - TeacherGradeTrends(dataKey=score, height=h-[200px]) * - StudentGradesCard(dataKey=score, height=h-[200px]) * - ChildGradeSummary(dataKey=score, xKey=date, height=h-[160px]) * * 默认配置: * - CartesianGrid: vertical=false, strokeDasharray="4 4", strokeOpacity=0.4 * - XAxis: tickLine=false, axisLine=false, tickMargin=8, 默认截断到 10 字符 * - YAxis: domain=[0,100], tickLine=false, axisLine=false, 默认百分比格式化, width=36 * - ChartTooltip: cursor 虚线, content=ChartTooltipContent indicator="line" labelKey="fullTitle" * - Line: type="monotone", strokeWidth=2 */ export interface TrendLineSeries { /** 数据字段名(对应 data 中的 key) */ dataKey: string /** 图例名称 */ name: string /** 颜色(CSS 变量或 hsl 值,如 "hsl(var(--primary))") */ color: string /** 数据点半径(默认 3) */ dotRadius?: number /** 激活数据点半径(默认 5) */ activeDotRadius?: number } interface TrendLineChartProps { /** 图表数据 */ data: Array> /** 折线系列配置(支持单条或多条) */ series: TrendLineSeries[] /** X 轴数据字段名(默认 "title") */ xKey?: string /** Y 轴定义域(默认 [0, 100]) */ yDomain?: [number, number] /** Y 轴刻度格式化(默认百分比 `${value}%`) */ yTickFormatter?: (value: number) => string /** X 轴刻度格式化(默认截断到 10 字符;设为 null 则不格式化) */ xTickFormatter?: ((value: string) => string) | null /** 图表高度类名(默认 "h-[280px]") */ heightClassName?: string /** 图表 margin(默认 { left: 8, right: 8, top: 8, bottom: 8 }) */ margin?: { left: number; right: number; top: number; bottom: number } /** Y 轴宽度(默认 36) */ yWidth?: number /** Tooltip 内容宽度类名(默认 "w-[220px]") */ tooltipClassName?: string /** Tooltip labelKey(默认 "fullTitle") */ tooltipLabelKey?: string /** 容器额外类名 */ className?: string } const DEFAULT_X_TICK_TRUNCATE_LENGTH = 10 function defaultXTickFormatter(value: string): string { return value.length > DEFAULT_X_TICK_TRUNCATE_LENGTH ? `${value.slice(0, DEFAULT_X_TICK_TRUNCATE_LENGTH)}...` : value } function defaultYTickFormatter(value: number): string { return `${value}%` } export function TrendLineChart({ data, series, xKey = "title", yDomain = [0, 100], yTickFormatter = defaultYTickFormatter, xTickFormatter = defaultXTickFormatter, heightClassName = "h-[280px]", margin = { left: 8, right: 8, top: 8, bottom: 8 }, yWidth = 36, tooltipClassName = "w-[220px]", tooltipLabelKey = "fullTitle", className, }: TrendLineChartProps) { const chartConfig: ChartConfig = {} for (const s of series) { chartConfig[s.dataKey] = { label: s.name, color: s.color, } } return ( } /> {series.map((s) => ( ))} ) }