feat(attendance,elective): 实现所有 P2 长期改进项

P2 修复(来自审计报告):
- 2.4.4: Server Action 错误消息 i18n 化(attendance/elective 全部 Action)
- 2.5.3: 抽取 AttendancePageLayout 组件复用(admin/teacher 页面)
- 2.5.4: 抽取 ElectivePageLayout 组件复用(admin/teacher 列表页)
- 2.6.3: 考勤月历键盘导航(tabIndex + 方向键 + Home/End + role=grid)
- 2.8.2: getStudentAttendanceSummary 分页优化(SQL 聚合统计 + LIMIT 分页)
- 2.8.3: resolveCourseDisplayNames 缓存优化(React cache 去重)
- 2.1.4: elective data-access 跨模块依赖接口抽象(resolvers.ts 可注入)

P2 建议项:
- 选课时间冲突检测(parseSchedule + isScheduleConflict 纯函数 + checkScheduleConflict)
- 学分上限校验(MAX_CREDIT_PER_TERM + checkCreditLimit)
- 考勤/选课数据导出 Excel(export.ts + API 路由扩展)

新增文件:
- src/modules/attendance/components/attendance-page-layout.tsx
- src/modules/elective/components/elective-page-layout.tsx
- src/modules/elective/resolvers.ts
- src/modules/attendance/export.ts
- src/modules/elective/export.ts

校验:
- npm run lint 通过(exit 0)
- npx tsc --noEmit attendance/elective/parent 相关零错误
This commit is contained in:
SpecialX
2026-06-23 09:02:41 +08:00
parent c766951374
commit e2e0487a3b
50 changed files with 1514 additions and 411 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { PlusCircle, Pencil, Trash2 } from "lucide-react"
import { PlusCircle, Pencil, Trash2, Tag } from "lucide-react"
import { useTranslations } from "next-intl"
import type { KnowledgePoint } from "../types"
@@ -8,6 +8,7 @@ import { cn } from "@/shared/lib/utils"
import { ScrollArea } from "@/shared/components/ui/scroll-area"
import { Badge } from "@/shared/components/ui/badge"
import { Button } from "@/shared/components/ui/button"
import { EmptyState } from "@/shared/components/ui/empty-state"
interface KnowledgePointListProps {
knowledgePoints: KnowledgePoint[]
@@ -35,9 +36,12 @@ export function KnowledgePointList({
if (knowledgePoints.length === 0) {
return (
<div className="flex h-full items-center justify-center text-muted-foreground p-4 text-center text-sm">
{t("reader.emptyKnowledge")}
</div>
<EmptyState
icon={Tag}
title={t("reader.emptyKnowledge")}
description={t("reader.emptyKnowledgeDesc")}
className="h-full border-none shadow-none bg-transparent"
/>
)
}
@@ -57,6 +61,7 @@ export function KnowledgePointList({
<div className="flex items-start justify-between gap-2">
<h4 className="text-sm font-medium leading-none">{kp.name}</h4>
<div className="flex items-center gap-1">
{/* 任意值 text-[10px]紧凑徽章text-xs(12px) 在列表项中过大 */}
<Badge variant="outline" className="text-[10px] h-5 px-1">
{t("panel.level")}
{kp.level}