feat: 改版阶段 3:老师按班级 × 任务批改,学生看本班同学作品

老师
- 新的“批改”页(教学默认页):左边班级 + 任务列表和完成进度,中间作品墙
  (每人最新一版、未交 / 我未评 / 分数),右侧详情面板:各版本、提示词链、打分、
  加入课上展示、加入作品展;J/K 切换学生,1–5 打分,P 课上展示
- “开始讲评”全屏依次播放课上展示的作品;成绩册点分数打开同一个详情面板
- 四色标记和“一键清除全站标记”去掉,flag 只用 present(课上展示);全部提交只给超管

学生
- “同学作品”:只看本班、自己交过这道题才开放,打分前匿名(服务端不返回作者)
- 学生的提交列表只有自己的;别人的提交和提示词链只有本班同学交过同一题、或进了作品展才能看
- 随机互评不再返回作者,打完分由 PUT /:id/score 返回

服务端新增 routes/review.ts(/submission/review:options、progress、class、peers),
查询走 slim 覆盖索引

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-27 00:10:18 +08:00
1 parent 9c6c01eba1
commit df3f842dac
18 files changed
+1616 -275

No files matched your search

+12 -7
View File
@@ -83,9 +83,18 @@
style="flex: 1; min-height: 0;"
/>
</n-flex>
<n-drawer
:show="!!reviewId"
:width="400"
placement="right"
@update:show="(v: boolean) => !v && (reviewId = null)"
>
<ReviewPanel v-if="reviewId" :submission-id="reviewId" @close="reviewId = null" />
</n-drawer>
</template>
<script setup lang="ts">
import ReviewPanel from "../components/review/ReviewPanel.vue"
import { computed, h, onMounted, reactive, ref, watch } from "vue"
import { Icon } from "../icons"
import { watchDebounced } from "@vueuse/core"
@@ -96,7 +105,6 @@ import {
useMessage,
type DataTableColumn,
} from "naive-ui"
import { useRouter } from "vue-router"
import { Account, Gradebook } from "../api"
import { displayGradebookStudentName } from "../utils/gradebook"
import type {
@@ -108,7 +116,6 @@ import type {
GradebookTaskType,
} from "../utils/type"
const router = useRouter()
const message = useMessage()
const classesLoading = ref(false)
const loading = ref(false)
@@ -146,13 +153,11 @@ function taskTitle(task: GradebookTask) {
return `${typeLabel}${task.display}`
}
// 点分数:在右侧打开和批改页一样的详情,能直接打分、看提示词
const reviewId = ref<string | null>(null)
function openSubmission(cell: GradebookCell) {
if (!cell.submitted || !cell.submission_id) return
const { href } = router.resolve({
name: "submission",
params: { id: cell.submission_id },
})
window.open(href, "_blank")
reviewId.value = cell.submission_id
}
function gradeTagType(grade: string) {