feat(admin): 补全 admin 模块核心功能与产品体验优化

修复 v4 报告中的 13 个产品体验问题:新增用户管理列表页和系统设置页,重组导航菜单并补充缺失入口,增加角色切换机制,Dashboard 增加快捷操作和 recharts 趋势图表,考勤增加统计概览,排课增加课表网格视图,统一 Toast 操作反馈,同步更新架构文档
This commit is contained in:
SpecialX
2026-06-22 13:38:07 +08:00
parent 978d9a8309
commit c45b3488c5
23 changed files with 3112 additions and 213 deletions

View File

@@ -0,0 +1,80 @@
import { Users, CheckCircle2, XCircle, Clock, LogOut, FileText } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
interface AttendanceStatsCardsProps {
stats: {
totalRecords: number
presentCount: number
absentCount: number
lateCount: number
earlyLeaveCount: number
excusedCount: number
attendanceRate: number
}
}
export function AttendanceStatsCards({ stats }: AttendanceStatsCardsProps) {
const cards = [
{
title: "总记录数",
value: stats.totalRecords,
icon: FileText,
color: "text-blue-500",
bgColor: "bg-blue-500/10",
},
{
title: "出勤",
value: stats.presentCount,
icon: CheckCircle2,
color: "text-green-500",
bgColor: "bg-green-500/10",
},
{
title: "缺勤",
value: stats.absentCount,
icon: XCircle,
color: "text-red-500",
bgColor: "bg-red-500/10",
},
{
title: "迟到",
value: stats.lateCount,
icon: Clock,
color: "text-yellow-500",
bgColor: "bg-yellow-500/10",
},
{
title: "早退",
value: stats.earlyLeaveCount,
icon: LogOut,
color: "text-orange-500",
bgColor: "bg-orange-500/10",
},
{
title: "出勤率",
value: `${stats.attendanceRate}%`,
icon: Users,
color: "text-primary",
bgColor: "bg-primary/10",
},
]
return (
<div className="grid gap-4 md:grid-cols-3 lg:grid-cols-6">
{cards.map((card) => (
<Card key={card.title} className="shadow-none">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{card.title}</CardTitle>
<div className={`flex h-8 w-8 items-center justify-center rounded-md ${card.bgColor}`}>
<card.icon className={`h-4 w-4 ${card.color}`} />
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold tabular-nums">{card.value}</div>
</CardContent>
</Card>
))}
</div>
)
}

View File

@@ -271,3 +271,39 @@ export async function upsertAttendanceRules(data: AttendanceRuleInput): Promise<
})
return id
}
export type AttendanceOverviewStats = {
totalRecords: number
presentCount: number
absentCount: number
lateCount: number
earlyLeaveCount: number
excusedCount: number
attendanceRate: number
}
export async function getAttendanceStats(params: {
scope: DataScope
currentUserId: string
classId?: string
date?: string
}): Promise<AttendanceOverviewStats> {
// 简化实现:基于已有查询统计
const records = await getAttendanceRecords(params)
const items = records.items
const total = items.length
const present = items.filter((r) => r.status === "present").length
const absent = items.filter((r) => r.status === "absent").length
const late = items.filter((r) => r.status === "late").length
const earlyLeave = items.filter((r) => r.status === "early_leave").length
const excused = items.filter((r) => r.status === "excused").length
return {
totalRecords: total,
presentCount: present,
absentCount: absent,
lateCount: late,
earlyLeaveCount: earlyLeave,
excusedCount: excused,
attendanceRate: total > 0 ? Math.round((present / total) * 1000) / 10 : 0,
}
}