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:
SpecialX
2026-06-17 13:44:37 +08:00
parent 125f7ec54c
commit 3b6272c99d
195 changed files with 27274 additions and 416 deletions

View File

@@ -0,0 +1,94 @@
"use client"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/shared/components/ui/table"
import { AlertTriangle, CheckCircle2 } from "lucide-react"
import type { AutoScheduleResult } from "../types"
const WEEKDAY_LABELS = ["", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
export function AutoScheduleResultView({ result }: { result: AutoScheduleResult }) {
const sortedSchedules = [...result.schedules].sort(
(a, b) => a.weekday - b.weekday || a.startTime.localeCompare(b.startTime)
)
return (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Generated Schedule</span>
<div className="flex items-center gap-2 text-sm font-normal">
<Badge variant="secondary">{result.scheduledCount} sessions</Badge>
<Badge variant={result.conflictCount > 0 ? "destructive" : "default"}>
{result.conflictCount} conflicts
</Badge>
</div>
</CardTitle>
</CardHeader>
<CardContent>
{sortedSchedules.length === 0 ? (
<p className="text-muted-foreground text-sm">No sessions generated.</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Day</TableHead>
<TableHead>Start</TableHead>
<TableHead>End</TableHead>
<TableHead>Course</TableHead>
<TableHead>Location</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedSchedules.map((s, idx) => (
<TableRow key={`${s.weekday}-${s.startTime}-${idx}`}>
<TableCell>{WEEKDAY_LABELS[s.weekday] ?? `Day ${s.weekday}`}</TableCell>
<TableCell>{s.startTime}</TableCell>
<TableCell>{s.endTime}</TableCell>
<TableCell className="font-medium">{s.course}</TableCell>
<TableCell>{s.location ?? "-"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
{result.conflicts.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertTriangle className="text-destructive h-5 w-5" />
<span>Conflicts & Warnings</span>
</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm">
{result.conflicts.map((c, idx) => (
<li key={idx} className="text-muted-foreground">
<Badge variant="outline" className="mr-2">
{c.type}
</Badge>
{c.description}
</li>
))}
</ul>
</CardContent>
</Card>
)}
{result.conflicts.length === 0 && result.scheduledCount > 0 && (
<Card>
<CardContent className="flex items-center gap-2 py-4 text-sm">
<CheckCircle2 className="text-primary h-5 w-5" />
<span>No conflicts detected. The schedule is ready to apply.</span>
</CardContent>
</Card>
)}
</div>
)
}