feat(portal-shell): homework 模块 7 页迁移(教师域 §9.1 B2)
§9.1 教师域 homework 模块完整迁移(继 exams 之后第二个 B2 模块): 7 页路由结构(与旧 teacher-portal 同构): - /shell/teacher/homework:列表页(?classId/status/q 筛选) - /shell/teacher/homework/new:布置作业表单页 - /shell/teacher/homework/[id]:详情 + 内联批改(含提交列表 + recordGrade 表单) - /shell/teacher/homework/submissions:跨作业提交评审列表 - /shell/teacher/homework/submissions/[submissionId]:单份提交批改 + AI 建议 + 上下份导航 - /shell/teacher/homework/submissions/[submissionId]/scan-grading:扫描批改工作台(三栏) - /shell/teacher/homework/assignments/[id]/submissions:按作业批量批改 + 统计 + AI 批量评分 数据契约(混合): - ✅ homework(id: ID!) 真实查询(schema 已就绪,详情页用) - ❌ 列表/mutation/submissions/grading/aiBatchGrading 全部 @contract-pending MSW 兜底 · 9 个 hook 走 MSW,待后端补齐 mutation 后切换真实 fetcher §11.3 DoD 11 项验收: 1. route-permissions:EXACT + PREFIX 表 /shell/teacher/homework 已配置 2. 页面模板:list/new 用 ListPageShell/FormPageShell;detail/grading 用 DetailPageShell; scan-grading 用 WorkbenchPageShell(三栏,未使用 emptyNode) 3. 三态:loading(Skeleton)/error(errorNode 或 errorSummary)/empty(emptyNode) 全实现 4. lib/api hooks:homework.ts 10 个 hooks(useHomework 真实 + 9 个 MSW) 5. @contract-pending MSW:graphql-data.ts 扩展 6 块 mock + 10 个 switch case 6. i18n:homework 节点扩展 8 个分区共 130+ keys(list/detail/new/submissions/grading/ scan/assignment/error)中英对齐 7. lint:0 errors(4 warnings 在 __generated__) 8. lint:tokens:0 errors 9. notify:mutation 反馈走 @/shared/lib/notify(非 sonner 直引) 10. vitest:transformations 纯函数单测齐全,全量 323/323 通过(新增 ~50 测试) 11. typecheck:0 errors(noUncheckedIndexedAccess 安全访问) 附带修复: - 修复 2 处遗留 broken link: · widgets/sidebar/quick-actions: /homework/new → /shell/teacher/homework/new · widgets/topbar/global-search: /homework → /shell/teacher/homework - scripts/check-page-count.ts baseline 同步 13 → 26(与 exams 6 + homework 7 一致) 剩余模块:grades(5)+lesson-plans(6)+questions(1)+textbooks(2)+attendance(4)+classes(3)+ students(1)+course-plans(2)+elective(3)+error-book(1)+diagnostic(2)+analytics(2)+ai-*(3)+ knowledge-graph(1)+practice(1)+schedule-changes(1)+leave(1) 共 39 页。
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { HomeworkDetailClient } from "@/features/teacher/homework/homework-detail-client";
|
||||
import { DetailPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 作业详情页(ARCHITECTURE.md §7.3 详情页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹。
|
||||
* 业务逻辑在 HomeworkDetailClient(client component)中。
|
||||
*
|
||||
* 数据契约:
|
||||
* - 单查 homework(id: ID!) ✅ schema 真实字段(core-edu 子图)
|
||||
* - 提交列表 homeworkSubmissions(homeworkId) ❌ schema 无此字段 → MSW 兜底
|
||||
* - mutation recordGrade(input) ❌ schema 无 Mutation → MSW 兜底
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.5 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function HomeworkDetailPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<DetailPageSkeleton />}>
|
||||
<HomeworkDetailClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { AssignmentSubmissionsClient } from "@/features/teacher/homework/assignment-submissions-client";
|
||||
import { ListPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 按作业批量批改页(ARCHITECTURE.md §7.3 列表页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹。
|
||||
* 业务逻辑在 AssignmentSubmissionsClient(client component)中。
|
||||
*
|
||||
* 数据契约:
|
||||
* - 查询 assignmentSubmissions(homeworkId) ❌ schema 无此字段 → MSW 兜底(@contract-pending)
|
||||
* - 查询 aiBatchGrading(homeworkId) ❌ schema 无此字段 → MSW 兜底
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function AssignmentSubmissionsPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<ListPageSkeleton rows={6} />}>
|
||||
<AssignmentSubmissionsClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/teacher/homework/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/teacher/homework/error.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 作业路由错误边界(ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD)。
|
||||
* Next.js Route Segment error.tsx,捕获子树未处理异常。
|
||||
*/
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function HomeworkError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}): React.ReactElement {
|
||||
const t = useTranslations("homework");
|
||||
|
||||
useEffect(() => {
|
||||
console.error("[portal-shell] homework route error:", error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
|
||||
<h2 className="text-lg font-semibold text-destructive">
|
||||
{t("error.title")}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{error.message || t("error.unknown")}
|
||||
</p>
|
||||
<Button onClick={reset} variant="outline">
|
||||
{t("error.retry")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
apps/portal-shell/src/app/shell/teacher/homework/loading.tsx
Normal file
12
apps/portal-shell/src/app/shell/teacher/homework/loading.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ListPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 作业路由段加载骨架(ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD)。
|
||||
* Next.js Route Segment loading.tsx,自动包裹页面渲染期间。
|
||||
*
|
||||
* 子页面(详情/批改/扫描批改)的 Skeleton 由各自 server page 的 <Suspense> 兜底,
|
||||
* 本文件仅在 /shell/teacher/homework 列表/重定向期间显示。
|
||||
*/
|
||||
export default function HomeworkLoading(): React.ReactElement {
|
||||
return <ListPageSkeleton rows={5} />;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { NewHomeworkClient } from "@/features/teacher/homework/new-homework-client";
|
||||
import { FormPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 布置作业表单页(ARCHITECTURE.md §7.3 表单页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹(useSearchParams 要求)。
|
||||
* 业务逻辑在 NewHomeworkClient(client component)中。
|
||||
*
|
||||
* 数据契约:mutation assignHomework(input) ❌ schema 无 Mutation → MSW 兜底(@contract-pending)
|
||||
* 契约工单:docs/architecture/issues/contracts/core-edu_contract.md#assign-homework-mutation
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function NewHomeworkPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<FormPageSkeleton />}>
|
||||
<NewHomeworkClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
23
apps/portal-shell/src/app/shell/teacher/homework/page.tsx
Normal file
23
apps/portal-shell/src/app/shell/teacher/homework/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { HomeworkListClient } from "@/features/teacher/homework/homework-list-client";
|
||||
import { ListPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 作业管理列表页(ARCHITECTURE.md §7.3 列表页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹(useSearchParams 要求)。
|
||||
* 业务逻辑在 HomeworkListClient(client component)中。
|
||||
*
|
||||
* 数据契约:列表查询 homeworks(classId) ❌ schema 无此字段 → MSW 兜底(@contract-pending)
|
||||
* 契约工单:docs/architecture/issues/contracts/core-edu_contract.md#homework-list
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function HomeworkListPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<ListPageSkeleton rows={5} />}>
|
||||
<HomeworkListClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { SubmissionGradingClient } from "@/features/teacher/homework/submission-grading-client";
|
||||
import { DetailPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 单份提交批改页(ARCHITECTURE.md §7.3 详情页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹。
|
||||
* 业务逻辑在 SubmissionGradingClient(client component)中。
|
||||
*
|
||||
* 数据契约:
|
||||
* - 单查 submissionDetail(submissionId) ❌ schema 无此字段 → MSW 兜底(@contract-pending)
|
||||
* - mutation gradeSubmission(input) ❌ schema 无 Mutation → MSW 兜底
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function SubmissionGradingPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<DetailPageSkeleton />}>
|
||||
<SubmissionGradingClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { ScanGradingClient } from "@/features/teacher/homework/scan-grading-client";
|
||||
import { WorkbenchPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 扫描批改页(ARCHITECTURE.md §7.3 工作台页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹。
|
||||
* 业务逻辑在 ScanGradingClient(client component)中。
|
||||
*
|
||||
* 数据契约:
|
||||
* - 单查 submissionDetail(submissionId) ❌ schema 无此字段 → MSW 兜底(@contract-pending)
|
||||
* - mutation saveScanGrading(input) ❌ schema 无 Mutation → MSW 兜底
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function ScanGradingPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<WorkbenchPageSkeleton />}>
|
||||
<ScanGradingClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { SubmissionsListClient } from "@/features/teacher/homework/submissions-list-client";
|
||||
import { ListPageSkeleton } from "@/shared/components/page-templates";
|
||||
|
||||
/**
|
||||
* 提交批改列表页(ARCHITECTURE.md §7.3 列表页 / §9.1 / §10 P2)
|
||||
*
|
||||
* Server Component 入口:仅负责 Suspense 边界包裹(useSearchParams 要求)。
|
||||
* 业务逻辑在 SubmissionsListClient(client component)中。
|
||||
*
|
||||
* 数据契约:列表查询 homeworkSubmissions(filter) ❌ schema 无此字段 → MSW 兜底(@contract-pending)
|
||||
* 契约工单:docs/architecture/issues/contracts/core-edu_contract.md#homework-submissions
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.1 / §10 P2 / §11.3 / §11.4
|
||||
*/
|
||||
export default function HomeworkSubmissionsPage(): React.ReactElement {
|
||||
return (
|
||||
<Suspense fallback={<ListPageSkeleton rows={8} />}>
|
||||
<SubmissionsListClient />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user