评阅与成绩:老师看代码、运行学生程序、打分、写评语;成绩表与导出

- 评阅页:全班学生列表(提交状态、得分、是否已评阅);选中学生的代码(只读)、自动评分结果、
  规范提示(代码里用到的默认名字控件、注释行数)、老师评分项打分和评语,"保存并看下一个"
- "运行他的程序":新标签页里打开学生的提交,可以操作实训台,改动不保存
- 学生能看到老师的各项得分和评语;作业列表显示总分
- 班级管理页"成绩"标签页:学生 × 作业的总分,点分数进入评阅;单个作业和全班成绩导出 CSV
- 老师增删评分项后,已打的分按新的评分项对齐

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 19:13:30 +08:00
1 parent 4e6b0d5cb2
commit 89e9956a22
20 files changed
+999 -127

No files matched your search

+6 -12
View File
@@ -1,11 +1,13 @@
<script setup lang="ts">
// 老师的班级管理:建班、导入学生名单(CSV)、查看和重置密码、导出名单发给学生;布置作业、查看提交情况。
// 老师的班级管理:建班、导入学生名单(CSV)、查看和重置密码、导出名单发给学生;布置作业(评阅在 ReviewPage);全班成绩表。
import { computed, h, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { NButton, NDataTable, NEmpty, NInput, NModal, NPopconfirm, NPopover, NTabPane, NTabs, useDialog, useMessage, type DataTableColumns } from 'naive-ui'
import { api } from '../api'
import { currentUser, logout } from '../auth'
import AssignmentsPanel from '../teacher/AssignmentsPanel.vue'
import GradebookPanel from '../teacher/GradebookPanel.vue'
import { downloadCsv } from '../teacher/csv'
import { decodeCsv, parseCsv, toRoster, type RosterRow } from '../teacher/roster'
interface ClassInfo {
@@ -177,17 +179,6 @@ async function confirmImport() {
})
}
/** 下载 CSV:带 BOM,Excel 打开时中文不乱码;账号前加制表符,长学号不会变成科学计数法 */
function downloadCsv(filename: string, rows: string[][]) {
const quote = (cell: string) => (/[",\n]/.test(cell) ? `"${cell.replaceAll('"', '""')}"` : cell)
const text = '' + rows.map((r) => r.map(quote).join(',')).join('\r\n') + '\r\n'
const link = document.createElement('a')
link.href = URL.createObjectURL(new Blob([text], { type: 'text/csv;charset=utf-8' }))
link.download = filename
link.click()
URL.revokeObjectURL(link.href)
}
function exportRoster() {
if (!selected.value) return
downloadCsv(`${selected.value.name}-名单.csv`, [
@@ -282,6 +273,9 @@ async function signOut() {
<NTabPane name="assignments" tab="作业">
<AssignmentsPanel :class-id="selected.id" />
</NTabPane>
<NTabPane name="grades" tab="成绩" display-directive="if">
<GradebookPanel :class-id="selected.id" :class-name="selected.name" />
</NTabPane>
</NTabs>
</section>
<section v-else class="students">