Files
OJ2/apps/web/src/routes.ts
T
xuyueandClaude Opus 5.5 80106eaa04 feat(课堂): 老师的课堂看板——布置这节课的题、看全班谁还没开始谁卡住了,布置的题直接出现在学生首页
2025 秋平均每节课有 14 个平时在用的学生一道都没交(三分之一),交了 3 次以上还没过的
平均不到 1 个;而老师原来只能一道题一道题打开统计弹窗看。

- 新表 class_lesson(迁移 0023,仅建表):一个班一天一行,存老师布置的 problem.id 与顺序;
  day 存东八区日历日文本,created_by CASCADE
- GET /classroom/board:这个班今天的题 × 全班学生(通过与否不限今天、今天交了几次、
  最后提交时间);不给班级时猜最近两小时提交人数最多的班,不记老师上次选的
- PUT /classroom/lesson:按展示题号布置,对不上的题号点名拒绝,空数组 = 清掉
- /me/class-activity 优先返回老师布置的题(source = teacher),否则照旧推断
- 前端 /classroom:没交过的名单置顶,按 没交过 → 卡住 → 在做 → 做完 排序,15 秒刷新;
  投影模式只显示每题完成人数、不出名字;用户菜单给老师加入口
- 学生首页:老师布置的标题写「今天老师布置的题」,没人交时不显示 0/0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:37:59 +08:00

354 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { RouteRecordRaw } from "vue-router"
export const ojs: RouteRecordRaw = {
path: "/",
component: () => import("shared/layout/default.vue"),
children: [
// 登录后是个人首页,没登录还是题目列表(见 oj/home/index.vue)
{ path: "", component: () => import("oj/home/index.vue"), name: "home" },
{ path: "problem", component: () => import("oj/problem/list.vue"), name: "problems" },
{
path: "problem/:problemID",
component: () => import("oj/problem/detail.vue"),
props: true,
name: "problem",
},
{
path: "submission",
component: () => import("oj/submission/list.vue"),
name: "submissions",
},
{
path: "submission/:submissionID",
component: () => import("oj/submission/detail.vue"),
props: true,
meta: { requiresAuth: true },
},
{
path: "contest",
component: () => import("oj/contest/list.vue"),
name: "contests",
},
{
path: "contest/:contestID",
component: () => import("oj/contest/detail.vue"),
props: true,
meta: { requiresAuth: true },
children: [
{
path: "",
component: () => import("oj/contest/pages/problems.vue"),
props: true,
meta: { requiresAuth: true },
name: "contest problems",
},
{
path: "submission",
component: () => import("oj/submission/list.vue"),
meta: { requiresAuth: true },
name: "contest submissions",
},
{
path: "rank",
component: () => import("oj/contest/pages/rank.vue"),
props: true,
meta: { requiresAuth: true },
name: "contest rank",
},
],
},
{
path: "contest/:contestID/problem/:problemID",
component: () => import("oj/problem/detail.vue"),
props: true,
name: "contest problem",
meta: { requiresAuth: true },
},
{
path: "rank",
name: "rank",
component: () => import("oj/rank/list.vue"),
},
{
path: "classroom",
name: "classroom board",
component: () => import("oj/classroom/board.vue"),
meta: { requiresAuth: true, requiresTeacherAdmin: true },
},
{
path: "class",
name: "class",
component: () => import("oj/class/pk.vue"),
},
{
path: "announcement",
name: "announcements",
component: () => import("oj/announcement/list.vue"),
},
{
path: "user",
component: () => import("oj/user/index.vue"),
meta: { requiresAuth: true },
},
{
path: "achievement",
name: "achievement",
component: () => import("oj/achievement/index.vue"),
meta: { requiresAuth: true },
},
{
path: "setting",
component: () => import("oj/user/setting.vue"),
meta: { requiresAuth: true },
},
{
path: "message",
component: () => import("oj/user/message.vue"),
meta: { requiresAuth: true },
},
{
path: "learn/:type/:step",
name: "learn",
component: () => import("oj/learn/index.vue"),
props: true,
},
{
path: "ai-analysis",
name: "ai",
component: () => import("oj/ai/analysis.vue"),
meta: { requiresAuth: true },
},
{
path: "flowchart",
name: "flowchart",
component: () => import("oj/flowchart/index.vue"),
},
{
path: "problemset",
component: () => import("oj/problemset/list.vue"),
name: "problemsets",
},
{
path: "problemset/:problemSetId",
component: () => import("oj/problemset/detail.vue"),
props: true,
name: "problemset",
},
{
path: "problemset/:problemSetId/problem/:problemID",
component: () => import("oj/problem/detail.vue"),
props: true,
name: "problemset problem",
meta: { requiresAuth: true },
},
],
}
export const admins: RouteRecordRaw = {
path: "/admin",
component: () => import("shared/layout/admin.vue"),
children: [
{
path: "",
name: "admin home",
component: () => import("admin/setting/home.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "config",
name: "admin config",
component: () => import("admin/setting/config.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "user/list",
name: "admin user list",
component: () => import("admin/user/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "user/generate",
name: "admin user generate",
component: () => import("admin/user/generate.vue"),
meta: { requiresSuperAdmin: true },
},
// admin和super_admin都可以访问的路由 (需要题目权限)
{
path: "problem/list",
name: "admin problem list",
component: () => import("admin/problem/list.vue"),
meta: { requiresProblemPermission: true },
},
{
path: "problem/tags",
name: "admin tag list",
component: () => import("admin/problem/tags.vue"),
meta: { requiresProblemPermission: true },
},
{
path: "problem/create",
name: "admin problem create",
component: () => import("admin/problem/detail.vue"),
meta: { requiresProblemPermission: true },
},
{
path: "problem/edit/:problemID",
name: "admin problem edit",
component: () => import("admin/problem/detail.vue"),
props: true,
meta: { requiresProblemPermission: true },
},
{
path: "contest/list",
name: "admin contest list",
component: () => import("admin/contest/list.vue"),
meta: { requiresTeacherAdmin: true },
},
{
path: "contest/create",
name: "admin contest create",
component: () => import("admin/contest/detail.vue"),
meta: { requiresTeacherAdmin: true },
},
{
path: "contest/edit/:contestID",
name: "admin contest edit",
component: () => import("admin/contest/detail.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
{
path: "contest/:contestID/problem/list",
name: "admin contest problem list",
component: () => import("admin/problem/list.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
{
path: "contest/:contestID/problem/create",
name: "admin contest problem create",
component: () => import("admin/problem/detail.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
{
path: "contest/:contestID/problem/edit/:problemID",
name: "admin contest problem edit",
component: () => import("admin/problem/detail.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
{
path: "contest/:contestID/helper",
name: "admin contest helper",
component: () => import("admin/contest/helper.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
// 只有super_admin可以访问的路由
{
path: "announcement/list",
name: "admin announcement list",
component: () => import("admin/announcement/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "announcement/create",
name: "admin announcement create",
component: () => import("admin/announcement/detail.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "announcement/edit/:announcementID",
name: "admin announcement edit",
component: () => import("admin/announcement/detail.vue"),
props: true,
meta: { requiresSuperAdmin: true },
},
{
path: "message/list",
name: "admin message list",
component: () => import("admin/communication/messages.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "tutorial/list",
name: "admin tutorial list",
component: () => import("admin/tutorial/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "tutorial/create",
name: "admin tutorial create",
component: () => import("admin/tutorial/detail.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "tutorial/edit/:tutorialID",
name: "admin tutorial edit",
component: () => import("admin/tutorial/detail.vue"),
props: true,
meta: { requiresSuperAdmin: true },
},
{
path: "problem/stuck",
name: "admin stuck problems",
component: () => import("admin/problem/Stuck.vue"),
meta: { requiresTeacherAdmin: true },
},
{
path: "problem/top_ac_trend",
name: "admin top ac trend",
component: () => import("admin/problem/TopACTrend.vue"),
meta: { requiresTeacherAdmin: true },
},
// 题单管理路由
{
path: "problemset/list",
name: "admin problemset list",
component: () => import("admin/problemset/list.vue"),
meta: { requiresTeacherAdmin: true },
},
{
path: "achievement/list",
name: "admin achievement list",
// 后端 admin 接口是 super_admin_required,这里必须跟着用
// requiresSuperAdmin,否则教师能进页面但每个请求都 403
component: () => import("admin/achievement/list.vue"),
meta: { requiresSuperAdmin: true },
},
{
path: "problemset/create",
name: "admin problemset create",
component: () => import("admin/problemset/edit.vue"),
meta: { requiresTeacherAdmin: true },
},
{
path: "problemset/edit/:problemSetId",
name: "admin problemset edit",
component: () => import("admin/problemset/edit.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
{
path: "problemset/:problemSetId",
name: "admin problemset detail",
component: () => import("admin/problemset/detail.vue"),
props: true,
meta: { requiresTeacherAdmin: true },
},
{
path: "ai/reports",
name: "admin ai reports",
component: () => import("admin/ai/list.vue"),
meta: { requiresTeacherAdmin: true },
},
{
path: "learn",
name: "admin learn analytics",
component: () => import("admin/learn/index.vue"),
meta: { requiresTeacherAdmin: true },
},
],
}