fix(dashboard): v3 审计修复 — 数据完整性、i18n、类型安全、死代码清理
P0 修复(严重):
- admin ContentRow 标签与值错配(stats.users→textbooks 等 6 处)
- admin/error.tsx 硬编码中文替换为 useTranslations
- UserGrowthChart 空数据时渲染 EmptyState(userGrowth/homeworkTrend 永远为空数组)
P1 修复(高):
- 新增 admin/dashboard 和 student/dashboard 的 loading.tsx + error.tsx
- 抽取 DashboardLoadingSkeleton 和 DashboardErrorFallback 共享组件,消除 5 套重复文件
- formatDate/formatLongDate 传入用户 locale(admin/teacher/student 共 6 个组件)
- 移除死代码:getCachedAdminDashboard、AvatarImage src={undefined}、TeacherStats isLoading prop
- filterTodaySchedule 改为泛型函数,消除 as 类型断言
- 辅助函数 getStatus/getDueUrgency 新增显式返回类型
- UserGrowthChart 新增 labelKey prop 区分用户增长/作业提交趋势标签
P2 修复(中):
- 4 个组件从客户端转为服务端组件(DashboardGreetingHeader、TeacherQuickActions、TeacherDashboardHeader、StudentDashboardHeader)
- Student dashboard 空状态新增 CTA(viewSchedule、viewAll)
- TeacherHomeworkCard 图标按钮新增 aria-label
- TeacherTodoCard 排序逻辑重写为可读的 if/return 模式
同步更新:
- docs/architecture/005_architecture_data.json 新增 DashboardLoadingSkeleton、DashboardErrorFallback 条目
- 新增 docs/architecture/audit/dashboard-audit-report-v3.md 审计报告
- dashboard.json 新增 6 个 i18n 键(textbooks/chapters/questions/exams/totalAssignments/totalSubmissions)
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
persistExamDraft,
|
||||
resolveSubjectGradeNames,
|
||||
updateExamWithQuestions,
|
||||
type ExamModeConfig,
|
||||
} from "./data-access"
|
||||
import {
|
||||
AiGeneratedStructureSchema,
|
||||
@@ -58,6 +59,30 @@ const getStringValue = (formData: FormData, key: string) => {
|
||||
return typeof value === "string" ? value : undefined
|
||||
}
|
||||
|
||||
const getBoolValue = (formData: FormData, key: string, fallback = false): boolean => {
|
||||
const value = formData.get(key)
|
||||
if (typeof value !== "string") return fallback
|
||||
return value === "true"
|
||||
}
|
||||
|
||||
const parseExamModeConfig = (formData: FormData): ExamModeConfig => {
|
||||
const rawMode = getStringValue(formData, "examMode")
|
||||
const examMode: ExamModeConfig["examMode"] =
|
||||
rawMode === "timed" || rawMode === "proctored" ? rawMode : "homework"
|
||||
const rawDuration = getStringValue(formData, "durationMinutes")
|
||||
const durationMinutes = rawDuration && Number.isFinite(Number(rawDuration))
|
||||
? Number(rawDuration)
|
||||
: null
|
||||
return {
|
||||
examMode,
|
||||
durationMinutes,
|
||||
shuffleQuestions: getBoolValue(formData, "shuffleQuestions", false),
|
||||
allowLateStart: getBoolValue(formData, "allowLateStart", false),
|
||||
lateStartGraceMinutes: Number(getStringValue(formData, "lateStartGraceMinutes") ?? "0") || 0,
|
||||
antiCheatEnabled: getBoolValue(formData, "antiCheatEnabled", false),
|
||||
}
|
||||
}
|
||||
|
||||
const failState = <T>(message: string, errors?: Record<string, string[]>): ActionState<T> => ({
|
||||
success: false,
|
||||
message,
|
||||
@@ -317,6 +342,7 @@ export async function createExamAction(
|
||||
gradeId: input.grade,
|
||||
scheduledAt: context.scheduled,
|
||||
description,
|
||||
examModeConfig: parseExamModeConfig(formData),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to create exam:", error)
|
||||
@@ -436,6 +462,7 @@ export async function createAiExamAction(
|
||||
description,
|
||||
structure,
|
||||
generated,
|
||||
examModeConfig: parseExamModeConfig(formData),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to create exam:", error)
|
||||
|
||||
Reference in New Issue
Block a user