diff --git a/src/app/(dashboard)/parent/children/[studentId]/page.tsx b/src/app/(dashboard)/parent/children/[studentId]/page.tsx index ce25626..48d4977 100644 --- a/src/app/(dashboard)/parent/children/[studentId]/page.tsx +++ b/src/app/(dashboard)/parent/children/[studentId]/page.tsx @@ -3,10 +3,10 @@ import { notFound } from "next/navigation" import { requireAuth } from "@/shared/lib/auth-guard" import { getSearchParam } from "@/shared/lib/utils" import { - verifyParentChildRelation, - getChildDashboardData, - getChildNameList, -} from "@/modules/parent/data-access" + verifyParentChildRelationAction, + getChildDashboardDataAction, + getChildNameListAction, +} from "@/modules/parent/actions" import { ChildDetailHeader } from "@/modules/parent/components/child-detail-header" import { ChildDetailPanel, @@ -31,7 +31,8 @@ export default async function ChildDetailPage({ const t = await getTranslations("common") // 校验当前家长与该子女存在关系(同时按 parentId + studentId 过滤,防止跨家庭信息泄露) - const relation = await verifyParentChildRelation(studentId, ctx.userId) + // 审计 v1 P0 修复(G4-002):改调 Server Action,统一权限校验入口 + const relation = await verifyParentChildRelationAction(studentId) // dataScope 二次校验:admin/其他角色可能通过 requireAuth,但需确认 dataScope 包含该子女 const isInScope = @@ -52,8 +53,8 @@ export default async function ChildDetailPage({ } const [child, siblings] = await Promise.all([ - getChildDashboardData(studentId, relation), - getChildNameList(ctx.userId), + getChildDashboardDataAction(studentId), + getChildNameListAction(), ]) if (!child) { notFound() diff --git a/src/app/(dashboard)/parent/elective/page.tsx b/src/app/(dashboard)/parent/elective/page.tsx index 09bd0b3..70cb4b5 100644 --- a/src/app/(dashboard)/parent/elective/page.tsx +++ b/src/app/(dashboard)/parent/elective/page.tsx @@ -4,9 +4,9 @@ import { getTranslations } from "next-intl/server" import { requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import { - getChildren, - getChildBasicInfo, -} from "@/modules/parent/data-access" + getChildrenAction, + getChildBasicInfoAction, +} from "@/modules/parent/actions" import { getStudentSelections } from "@/modules/elective/data-access-selections" import { ParentChildrenDataPage, @@ -25,7 +25,6 @@ interface ChildSelectionData { export default async function ParentElectivePage() { const t = await getTranslations("elective") const ctx = await requirePermission(Permissions.ELECTIVE_READ) - const parentId = ctx.userId // dataScope 校验:家长必须有关联子女才能查看选课信息 if (ctx.dataScope.type !== "children" || ctx.dataScope.childrenIds.length === 0) { @@ -41,11 +40,12 @@ export default async function ParentElectivePage() { } // 并行拉取每个子女的选课记录,使用 allSettled 容错 - const relations = await getChildren(parentId) + // 审计 v1 P0 修复(G4-002):改调 Server Action,统一权限校验入口 + const relations = await getChildrenAction() const results = await Promise.allSettled( relations.map(async (r): Promise => { // 双重校验:parentId + studentId 必须存在关联关系,防止跨家庭信息泄露 - const basicInfo = await getChildBasicInfo(r.studentId, r.relation) + const basicInfo = await getChildBasicInfoAction(r.studentId, r.relation) const selections = await getStudentSelections(r.studentId) return { studentId: r.studentId, diff --git a/src/app/(dashboard)/parent/leave/page.tsx b/src/app/(dashboard)/parent/leave/page.tsx index e36357c..7f345d4 100644 --- a/src/app/(dashboard)/parent/leave/page.tsx +++ b/src/app/(dashboard)/parent/leave/page.tsx @@ -5,7 +5,7 @@ import { ArrowLeft, CalendarDays } from "lucide-react" import { getAuthContext } from "@/shared/lib/auth-guard" import { Button } from "@/shared/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" -import { getChildren, getChildBasicInfo } from "@/modules/parent/data-access" +import { getChildrenAction, getChildBasicInfoAction } from "@/modules/parent/actions" import { getLeaveRequests } from "@/modules/leave-requests/data-access" import { LeaveRequestForm, @@ -46,8 +46,9 @@ export default async function ParentLeavePage() { } // 并行:子女关系列表 + 已有请假申请列表 + // 审计 v1 P0 修复(G4-002):改调 Server Action,统一权限校验入口 const [relations, leaveResult] = await Promise.all([ - getChildren(ctx.userId), + getChildrenAction(), getLeaveRequests({ scope: ctx.dataScope, currentUserId: ctx.userId, @@ -59,7 +60,7 @@ export default async function ParentLeavePage() { // 构造子女选项(含 activeClass),供表单下拉使用 const childOptions: ChildOption[] = [] for (const r of relations) { - const basic = await getChildBasicInfo(r.studentId, r.relation) + const basic = await getChildBasicInfoAction(r.studentId, r.relation) if (basic && basic.classId && basic.className) { childOptions.push({ id: basic.id, diff --git a/src/app/(dashboard)/student/error-book/student-error-book-list-client.tsx b/src/app/(dashboard)/student/error-book/student-error-book-list-client.tsx index 559d46b..e8695bd 100644 --- a/src/app/(dashboard)/student/error-book/student-error-book-list-client.tsx +++ b/src/app/(dashboard)/student/error-book/student-error-book-list-client.tsx @@ -3,7 +3,7 @@ import { useTransition } from "react" import type { JSX } from "react" import { useRouter } from "next/navigation" -import { toast } from "sonner" +import { notify } from "@/shared/lib/notify" import { useTranslations } from "next-intl" import { ErrorBookList } from "@/modules/error-book/components/error-book-list" @@ -102,10 +102,10 @@ export function StudentErrorBookListClient({ ) const res = await createPracticeSessionAction(undefined, formData) if (res.success && res.data) { - toast.success(res.message ?? tPractice("starter.title")) + notify.success(res.message ?? tPractice("starter.title")) router.push(`/student/practice/${res.data.sessionId}`) } else { - toast.error(res.message ?? t("messages.saveFailed")) + notify.error(res.message ?? t("messages.saveFailed")) } }) } diff --git a/src/app/(dashboard)/teacher/classes/schedule/schedule-filters.tsx b/src/app/(dashboard)/teacher/classes/schedule/schedule-filters.tsx index 82d8e2d..2078926 100644 --- a/src/app/(dashboard)/teacher/classes/schedule/schedule-filters.tsx +++ b/src/app/(dashboard)/teacher/classes/schedule/schedule-filters.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/navigation" import { useTranslations } from "next-intl" import { useQueryState, parseAsString } from "nuqs" import { Plus } from "lucide-react" -import { toast } from "sonner" +import { notify } from "@/shared/lib/notify" import { Button } from "@/shared/components/ui/button" import { @@ -57,14 +57,14 @@ export function ScheduleFilters({ classes }: { classes: TeacherClass[] }) { formData.set("classId", createClassId) const res = await createClassScheduleItemAction(null, formData) if (res.success) { - toast.success(res.message) + notify.success(res.message) setOpen(false) router.refresh() } else { - toast.error(res.message || t("list.failedCreate")) + notify.error(res.message || t("list.failedCreate")) } } catch { - toast.error(t("list.failedCreate")) + notify.error(t("list.failedCreate")) } finally { setIsWorking(false) } diff --git a/src/app/(dashboard)/teacher/classes/students/students-filters.tsx b/src/app/(dashboard)/teacher/classes/students/students-filters.tsx index 2042797..9870696 100644 --- a/src/app/(dashboard)/teacher/classes/students/students-filters.tsx +++ b/src/app/(dashboard)/teacher/classes/students/students-filters.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/navigation" import { useTranslations } from "next-intl" import { useQueryState, parseAsString } from "nuqs" import { UserPlus, ChevronDown, Check } from "lucide-react" -import { toast } from "sonner" +import { notify } from "@/shared/lib/notify" import { Input } from "@/shared/components/ui/input" import { Button } from "@/shared/components/ui/button" @@ -65,14 +65,14 @@ export function StudentsFilters({ classes, defaultClassId }: { classes: TeacherC try { const res = await enrollStudentByEmailAction(enrollClassId, null, formData) if (res.success) { - toast.success(res.message) + notify.success(res.message) setOpen(false) router.refresh() } else { - toast.error(res.message || t("list.failedCreate")) + notify.error(res.message || t("list.failedCreate")) } } catch { - toast.error(t("list.failedCreate")) + notify.error(t("list.failedCreate")) } finally { setIsWorking(false) } diff --git a/src/app/globals.css b/src/app/globals.css index 6e8f650..2392fc2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,6 +4,11 @@ @plugin "@tailwindcss/typography"; @custom-variant dark (&:where(.dark, .dark *)); +/* 排除非源码目录,防止文档中的 Tailwind 任意值语法字符串被误识别为类名 */ +@source not "../../docs"; +@source not "../../scripts"; +@source not "../../tests"; + /* Reduced Motion */ @layer base { @media (prefers-reduced-motion: reduce) {