feat(teacher-portal): 完成参考项目差距闭环 P3-P7 全量实现

- P3 考试/作业/成绩 mutation + 详情页 + 批改界面 + 乐观更新 + 多 Tab 同步
- P4 知识图谱 SVG 可视化 + 学情分析仪表盘 + parent-portal Remote
- P5 WebSocket 通知中心 + AI 出题(SSE) + AI 教案 + AI 学情报告
- P6 可观测性硬化:Sentry + WebVitals + OTel + A11y + 性能配置 + Cookie 迁移
- P7 参考项目差距闭环:新增 35 个页面覆盖 13 个缺失模块
  - attendance(考勤 4 页)/questions(题库)/textbooks(教材 2 页)
  - classes/[id] 详情 + classes/schedule 课表
  - course-plans(2 页)/diagnostic(2 页)/error-book/practice
  - exams/[id]/build 组卷 + exams/[id]/analytics 考后分析
  - exams/[id]/edit-rich 富文本编辑 + exams/[id]/proctoring 监考
  - grades/entry 批量录入 + grades/stats 统计 + grades/analytics 分析 + grades/report-card 报告卡
  - homework/submissions 列表 + assignments/[id]/submissions 批量批改
  - homework/submissions/[submissionId] 单份批改 + scan-grading 扫描批改
  - lesson-plans 编辑器 + library + calendar + heatmap 5 页
  - elective 选修课 3 页 /leave 请假 /schedule-changes 调课
- P7 基础设施:61 GraphQL operations + 5 handlers + 13 fixtures + 11 viewports
- 集成 browser.ts/server.ts 注册所有 p7 handlers(fallthrough 顺序)
- viewports.ts 扩展 11 个新导航项
- 验证:tsc --noEmit 零错误 + eslint 零错误
- 文档:workline.md 新增 §5 P7 参考项目差距闭环(含完整文件清单)
This commit is contained in:
SpecialX
2026-07-13 14:27:04 +08:00
parent f13ca612e6
commit d49d211425
117 changed files with 30867 additions and 108 deletions

View File

@@ -26,6 +26,10 @@ import { usePermission } from "@edu/hooks";
import { ViewportsQuery, MeQuery } from "@/lib/graphql";
import type { ViewportItem, User } from "@/lib/graphql";
import { getToken, getUser, logout } from "@/lib/auth";
import {
useCrossTabSync,
broadcastCrossTabEvent,
} from "@/hooks/use-cross-tab-sync";
/**
* 权限上下文:从 localStorage 读取(登录时由 iam 返回并存储)。
@@ -54,6 +58,9 @@ export default function AppShell({ children }: { children: React.ReactNode }) {
}
}, [router]);
// 多 Tab 会话同步:监听 logout / role-change / token-refresh 事件
useCrossTabSync();
// GraphQL: viewports query替代 REST /api/v1/teacher/viewports
const [viewportsResult] = useQuery({
query: ViewportsQuery,
@@ -95,6 +102,12 @@ export default function AppShell({ children }: { children: React.ReactNode }) {
</p>
) : null;
// 退出登录:先广播给其他 Tab再执行本地登出
const handleLogout = () => {
broadcastCrossTabEvent("logout");
logout();
};
return (
<div className="min-h-screen flex bg-paper">
{/* 左侧栏:导航树 */}
@@ -140,7 +153,7 @@ export default function AppShell({ children }: { children: React.ReactNode }) {
</div>
)}
<button
onClick={logout}
onClick={handleLogout}
className="text-tiny uppercase tracking-wide text-ink-muted hover:opacity-70"
>
退