feat(settings): 设置与个人信息模块审计重构 — i18n + 服务注入解耦 + Error Boundary + 流式渲染

- 新增 SettingsService 接口 + Context 注入,组件层不再直接 import users/messaging actions

- 新增 resolveRoleSettingsConfig 配置驱动角色路由,删除 parent/student/teacher-settings-view 冗余文件

- 新增 SettingsSectionErrorBoundary,每个 TabsContent + profile 角色概览区块均包裹

- 新增 ProfileStudentOverview/ProfileTeacherOverview 异步 Server Component + 骨架屏,支持流式渲染

- 抽取 buildStudentOverviewData 等纯函数到 lib/student-overview-data.ts,便于单元测试

- 新增 settings.json 翻译文件(zh-CN + en),所有组件改用 useTranslations/getTranslations

- 重构 profile/page.tsx:i18n 适配 + Suspense 分区加载 + 业务逻辑抽离

- 同步更新架构图 004/005
This commit is contained in:
SpecialX
2026-06-22 16:15:36 +08:00
parent 21c7e65fee
commit 5d42495480
29 changed files with 2445 additions and 1094 deletions

View File

@@ -0,0 +1,93 @@
import type { ReactElement } from "react"
import { getTranslations } from "next-intl/server"
import { StudentGradesCard } from "@/modules/dashboard/components/student-dashboard/student-grades-card"
import { StudentStatsGrid } from "@/modules/dashboard/components/student-dashboard/student-stats-grid"
import { StudentTodayScheduleCard } from "@/modules/dashboard/components/student-dashboard/student-today-schedule-card"
import { StudentUpcomingAssignmentsCard } from "@/modules/dashboard/components/student-dashboard/student-upcoming-assignments-card"
import { getStudentClasses, getStudentSchedule } from "@/modules/classes/data-access"
import { getStudentDashboardGrades, getStudentHomeworkAssignments } from "@/modules/homework/data-access"
import { buildStudentOverviewData } from "@/modules/settings/lib/student-overview-data"
import { Separator } from "@/shared/components/ui/separator"
interface ProfileStudentOverviewProps {
userId: string
}
/**
* 学生概览区块Server Component
*
* 独立获取学生数据并渲染,可被 Suspense + ErrorBoundary 包裹实现流式渲染与局部容错。
*/
export async function ProfileStudentOverview({
userId,
}: ProfileStudentOverviewProps): Promise<ReactElement> {
const t = await getTranslations("settings.profilePage.studentOverview")
const [classes, schedule, assignmentsAll, grades] = await Promise.all([
getStudentClasses(userId),
getStudentSchedule(userId),
getStudentHomeworkAssignments(userId),
getStudentDashboardGrades(userId),
])
const data = buildStudentOverviewData({
classes,
schedule,
assignments: assignmentsAll,
grades,
})
return (
<div className="space-y-6">
<Separator />
<div className="space-y-1">
<h2 className="text-xl font-semibold tracking-tight">{t("title")}</h2>
<div className="text-sm text-muted-foreground">{t("description")}</div>
</div>
<StudentStatsGrid
enrolledClassCount={data.enrolledClassCount}
dueSoonCount={data.dueSoonCount}
overdueCount={data.overdueCount}
gradedCount={data.gradedCount}
ranking={data.grades.ranking}
/>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<StudentUpcomingAssignmentsCard upcomingAssignments={data.upcomingAssignments} />
<StudentGradesCard grades={data.grades} />
</div>
<div className="space-y-6">
<StudentTodayScheduleCard items={data.todayScheduleItems} />
</div>
</div>
</div>
)
}
/**
* 学生概览骨架屏
*/
export function ProfileStudentOverviewSkeleton(): ReactElement {
return (
<div className="space-y-6">
<Separator />
<div className="space-y-2">
<div className="h-6 w-40 animate-pulse rounded bg-muted" />
<div className="h-4 w-64 animate-pulse rounded bg-muted" />
</div>
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="h-24 animate-pulse rounded-lg bg-muted" />
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 h-64 animate-pulse rounded-lg bg-muted" />
<div className="h-64 animate-pulse rounded-lg bg-muted" />
</div>
</div>
)
}