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

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

+40 -6
View File
@@ -1,13 +1,19 @@
<script setup lang="ts">
// 一次提交的自动评分结果:每项通过与否、得分和不通过的原因;老师评分项只列出名称和分值。
// 一次提交的评分结果:自动评分每项通过与否、得分和不通过的原因;
// 老师评分项:老师已评阅时显示各项得分和评语,还没评阅时只列出名称和分值。
import { computed } from 'vue'
import { NSpin } from 'naive-ui'
import type { Assignment, Submission } from '@iot/shared'
import type { Assignment, Review, Submission } from '@iot/shared'
const props = defineProps<{ assignment: Assignment; submission: Submission }>()
const props = withDefaults(defineProps<{ assignment: Assignment; submission: Submission; review?: Review | null; showManual?: boolean }>(), {
review: null,
showManual: true,
})
const autoTotal = computed(() => props.assignment.scoring.auto.reduce((a, b) => a + b, 0))
const manual = computed(() => props.assignment.scoring.manual)
const manualTotal = computed(() => manual.value.reduce((a, m) => a + m.points, 0))
const manualScore = computed(() => props.review?.scores.reduce<number>((a, s) => a + (s ?? 0), 0) ?? 0)
</script>
<template>
@@ -34,9 +40,22 @@ const manual = computed(() => props.assignment.scoring.manual)
</li>
</ul>
</template>
<div v-if="manual.length" class="manual">
由老师评分:<span v-for="m in manual" :key="m.title">{{ m.title }}({{ m.points }} 分)</span>
</div>
<template v-if="showManual && manual.length">
<div v-if="review" class="reviewed">
<div class="summary">
老师评分 <b>{{ manualScore }}</b> / {{ manualTotal }} 分
</div>
<ul class="items">
<li v-for="(m, i) in manual" :key="i">
<span class="title">{{ m.title }}<span class="points">{{ review.scores[i] ?? '未打分' }} / {{ m.points }} 分</span></span>
</li>
</ul>
<div v-if="review.comment" class="comment">老师评语:{{ review.comment }}</div>
</div>
<div v-else class="manual">
由老师评分:<span v-for="m in manual" :key="m.title">{{ m.title }}({{ m.points }} 分)</span>
</div>
</template>
</div>
</template>
@@ -99,6 +118,21 @@ const manual = computed(() => props.assignment.scoring.manual)
color: #b91c1c;
}
.reviewed {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 10px;
border-top: 1px solid #f0f0f0;
}
.comment {
padding: 8px 10px;
background: #f9fafb;
border-radius: 4px;
white-space: pre-wrap;
}
.manual {
color: #6b7280;
}