feat(app,styles): update dashboard pages and global styles
- Update parent: children/[studentId]/page, elective/page, leave/page - Update student: error-book/student-error-book-list-client - Update teacher: classes/schedule/schedule-filters, classes/students/students-filters - Update src/app/globals.css
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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<ChildSelectionData> => {
|
||||
// 双重校验: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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user