refactor(grades,diagnostic): 成绩和学情诊断模块审计修复
P0-1: 10 个页面补充 requirePermission 权限校验 P0-2: diagnostic/data-access-reports.ts 移除直查 users 表,改用 getUserNamesByIds P0-3: 新增 grade/grades/diagnostic 三组 i18n 翻译文件(zh-CN/en) P0-4: 新增 /management/grade 重定向页面 P1-2: 抽取 toNumber/normalize/buildScopeClassFilter 到 lib/grade-utils.ts P1-3: 为 12 个 Action 新增 Zod safeParse 校验(schema.ts +12 查询 schema) P1-4: 修复 as 断言违规,改用类型守卫函数 P2-2: 移除 diagnostic 组件中 Tailwind 任意值 同步更新架构图文档 004 和 005
This commit is contained in:
@@ -131,7 +131,7 @@ export function ClassDiagnosticView({ summary }: ClassDiagnosticViewProps) {
|
||||
className={`flex flex-col items-center justify-center rounded-md px-3 py-2 text-white ${masteryColor(kp.averageMastery)}`}
|
||||
title={`${kp.knowledgePointName}: ${kp.averageMastery.toFixed(1)}% (mastered ${kp.masteredCount}/${kp.totalStudents})`}
|
||||
>
|
||||
<span className="max-w-[120px] truncate text-xs font-medium">
|
||||
<span className="max-w-32 truncate text-xs font-medium">
|
||||
{kp.knowledgePointName}
|
||||
</span>
|
||||
<span className="text-sm font-bold">{kp.averageMastery.toFixed(0)}%</span>
|
||||
@@ -252,7 +252,7 @@ export function ClassDiagnosticView({ summary }: ClassDiagnosticViewProps) {
|
||||
type="month"
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value)}
|
||||
className="w-[180px]"
|
||||
className="w-44"
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleGenerate} disabled={isGenerating}>
|
||||
|
||||
@@ -42,7 +42,7 @@ export function MasteryRadarChart({ data }: MasteryRadarChartProps) {
|
||||
domain={[0, 100]}
|
||||
tickCount={5}
|
||||
showLegend={hasClassAverage}
|
||||
heightClassName="mx-auto h-[360px] w-full max-w-[520px]"
|
||||
heightClassName="mx-auto h-96 w-full max-w-lg"
|
||||
gridStrokeDasharray="4 4"
|
||||
series={[
|
||||
{
|
||||
|
||||
@@ -195,7 +195,7 @@ export function StudentDiagnosticView({ summary, reports, classAverageMastery }:
|
||||
<span className="text-sm font-medium">
|
||||
{r.period ?? "Untitled period"}
|
||||
</span>
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{r.reportType}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import "server-only"
|
||||
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { and, desc, eq, inArray, type SQL } from "drizzle-orm"
|
||||
import { and, desc, eq, type SQL } from "drizzle-orm"
|
||||
import { cache } from "react"
|
||||
|
||||
import { db } from "@/shared/db"
|
||||
import { learningDiagnosticReports, users } from "@/shared/db/schema"
|
||||
import { learningDiagnosticReports } from "@/shared/db/schema"
|
||||
import { getUserNamesByIds } from "@/modules/users/data-access"
|
||||
|
||||
import { getClassMasterySummary, getStudentMasterySummary } from "./data-access"
|
||||
import type {
|
||||
@@ -132,31 +133,25 @@ export const getDiagnosticReports = cache(
|
||||
if (filters.period) conditions.push(eq(learningDiagnosticReports.period, filters.period))
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
report: learningDiagnosticReports,
|
||||
studentName: users.name,
|
||||
})
|
||||
.select({ report: learningDiagnosticReports })
|
||||
.from(learningDiagnosticReports)
|
||||
.leftJoin(users, eq(users.id, learningDiagnosticReports.studentId))
|
||||
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
||||
.orderBy(desc(learningDiagnosticReports.createdAt))
|
||||
|
||||
const generatorIds = Array.from(
|
||||
new Set(rows.map((r) => r.report.generatedBy).filter((id): id is string => id !== null))
|
||||
)
|
||||
const generatorMap = new Map<string, string>()
|
||||
if (generatorIds.length > 0) {
|
||||
const generators = await db
|
||||
.select({ id: users.id, name: users.name })
|
||||
.from(users)
|
||||
.where(inArray(users.id, generatorIds))
|
||||
for (const g of generators) generatorMap.set(g.id, g.name ?? "Unknown")
|
||||
// 收集所有需要查询姓名的用户 ID(学生 + 生成者),通过 users data-access 统一获取
|
||||
const userIds = new Set<string>()
|
||||
for (const r of rows) {
|
||||
userIds.add(r.report.studentId)
|
||||
if (r.report.generatedBy) userIds.add(r.report.generatedBy)
|
||||
}
|
||||
const userMap = await getUserNamesByIds(Array.from(userIds))
|
||||
|
||||
return rows.map((r) => ({
|
||||
...serializeReport(r.report),
|
||||
studentName: r.studentName ?? "Unknown",
|
||||
generatedByName: r.report.generatedBy ? generatorMap.get(r.report.generatedBy) ?? "Unknown" : null,
|
||||
studentName: userMap.get(r.report.studentId)?.name ?? "Unknown",
|
||||
generatedByName: r.report.generatedBy
|
||||
? userMap.get(r.report.generatedBy)?.name ?? "Unknown"
|
||||
: null,
|
||||
}))
|
||||
},
|
||||
)
|
||||
@@ -165,26 +160,23 @@ export const getDiagnosticReports = cache(
|
||||
export const getDiagnosticReportById = cache(
|
||||
async (id: string): Promise<DiagnosticReportWithDetails | null> => {
|
||||
const [row] = await db
|
||||
.select({ report: learningDiagnosticReports, studentName: users.name })
|
||||
.select({ report: learningDiagnosticReports })
|
||||
.from(learningDiagnosticReports)
|
||||
.leftJoin(users, eq(users.id, learningDiagnosticReports.studentId))
|
||||
.where(eq(learningDiagnosticReports.id, id))
|
||||
.limit(1)
|
||||
if (!row) return null
|
||||
|
||||
let generatedByName: string | null = null
|
||||
if (row.report.generatedBy) {
|
||||
const [gen] = await db
|
||||
.select({ name: users.name })
|
||||
.from(users)
|
||||
.where(eq(users.id, row.report.generatedBy))
|
||||
.limit(1)
|
||||
generatedByName = gen?.name ?? null
|
||||
}
|
||||
// 通过 users data-access 获取学生姓名和生成者姓名
|
||||
const userIds = [row.report.studentId]
|
||||
if (row.report.generatedBy) userIds.push(row.report.generatedBy)
|
||||
const userMap = await getUserNamesByIds(userIds)
|
||||
|
||||
return {
|
||||
...serializeReport(row.report),
|
||||
studentName: row.studentName ?? "Unknown",
|
||||
generatedByName,
|
||||
studentName: userMap.get(row.report.studentId)?.name ?? "Unknown",
|
||||
generatedByName: row.report.generatedBy
|
||||
? userMap.get(row.report.generatedBy)?.name ?? null
|
||||
: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user