实验成绩计算

This commit is contained in:
biss committed 2026-08-09 22:40:21 +08:00
1 parent d4abdd3db4
commit c25f16ec20
12 files changed
+7653 -290

No files matched your search

+94 -44
View File
@@ -376,7 +376,7 @@ async function publishSheet() {
}
}
function scoreTone(result: any) {
function projectScoreTone(result: any) {
if (result.totalScore == null) return 'is-pending'
return result.isPassed ? 'is-passed' : 'is-failed'
}
@@ -404,13 +404,13 @@ onMounted(async () => {
<div>
<span class="section-kicker">LAB ASSESSMENT LEDGER</span>
<h2>{{ isStudent ? '我的实验成绩' : '实验成绩管理' }}</h2>
<p v-if="isStudent">实验成绩独立记录操作、结果、报告和到场情况,不与课程总评混在一起。</p>
<p v-if="isStudent">按课程查看各实验项目;实验课总分为项目贡献权重加权平均,也是课程成绩引用的实验部分分数。</p>
<p v-else>按实验项目建立独立评分方案,审核发布后可选择汇总到课程成绩单。</p>
</div>
<el-button :icon="Refresh" @click="load">刷新</el-button>
</section>
<section class="assessment-flow" aria-label="实验成绩流程">
<section v-if="!isStudent" class="assessment-flow" aria-label="实验成绩流程">
<div><span>PROJECT</span><b>实验项目</b><small>集中安排 / 自主预约</small></div>
<i>→</i>
<div><span>RUBRIC</span><b>独立评分</b><small>操作 · 结果 · 报告 · 规范</small></div>
@@ -419,39 +419,76 @@ onMounted(async () => {
</section>
<template v-if="isStudent">
<section v-loading="loading" class="student-result-grid">
<article
v-for="result in studentResults"
:key="result.id"
class="student-result-card"
:class="scoreTone(result)"
>
<header>
<section v-loading="loading" class="student-course-results">
<article v-for="course in studentResults" :key="course.teachingTaskId" class="student-course-result">
<header class="student-course-head">
<div>
<span>{{ result.projectCode }} · {{ modeLabels[result.arrangementMode] }}</span>
<h3>{{ result.projectName }}</h3>
<p>{{ result.courseCode }} · {{ result.courseName }} · {{ result.termName }}</p>
<span>{{ course.termName }} · {{ course.taskNumber }}</span>
<h3>{{ course.courseCode }} · {{ course.courseName }}</h3>
<p>{{ course.publishedProjectCount }} 个已发布实验项目 · 按项目贡献权重计算</p>
</div>
<div class="score-seal">
<strong>{{ result.totalScore ?? '—' }}</strong>
<small>{{ result.totalScore == null ? participationLabels[result.participationStatus] : result.isPassed ? '通过' : '未通过' }}</small>
<div class="course-score-seal" :class="{ 'has-score': course.experimentCourseScore != null }">
<span>实验课总分</span>
<strong>{{ course.experimentCourseScore ?? '—' }}</strong>
<small>{{ course.experimentCourseScore == null ? '成绩尚未完整' : '加权平均分' }}</small>
</div>
</header>
<div class="result-facts">
<span>参与状态<b>{{ participationLabels[result.participationStatus] }}</b></span>
<span>合格线<b>{{ result.passScore }}</b></span>
<span>实验次数<b>{{ result.attemptNumber }}</b></span>
<span>提交情况<b>{{ result.isLate ? '迟交' : result.submittedAt ? '已提交' : '未登记' }}</b></span>
</div>
<div class="result-rubric">
<div v-for="item in result.items" :key="item.experimentGradeItemId">
<span>{{ item.name }} · {{ item.weight }}%</span>
<b>{{ item.score ?? '—' }}</b>
<small v-if="item.comment">{{ item.comment }}</small>
</div>
</div>
<p v-if="result.safetyViolation" class="safety-alert">已登记安全违规,本次实验按规则判定为 0 分。</p>
<p v-if="result.teacherComment" class="teacher-comment">{{ result.teacherComment }}</p>
<el-table :data="course.projects" class="student-grade-table" size="small">
<el-table-column type="expand" width="44">
<template #default="{ row }">
<div class="student-grade-detail" :class="projectScoreTone(row)">
<div class="result-facts">
<span>参与状态<b>{{ participationLabels[row.participationStatus] }}</b></span>
<span>合格线<b>{{ row.passScore }}</b></span>
<span>实验次数<b>{{ row.attemptNumber }}</b></span>
<span>提交情况<b>{{ row.isLate ? '迟交' : row.submittedAt ? '已提交' : '未登记' }}</b></span>
</div>
<div class="result-rubric">
<div v-for="item in row.items" :key="item.experimentGradeItemId">
<span>{{ item.name }} · {{ item.weight }}%</span>
<b>{{ item.score ?? '—' }}</b>
<small v-if="item.comment">{{ item.comment }}</small>
</div>
</div>
<p v-if="row.safetyViolation" class="safety-alert">已登记安全违规,本次实验按规则判定为 0 分。</p>
<p v-if="row.teacherComment" class="teacher-comment">{{ row.teacherComment }}</p>
</div>
</template>
</el-table-column>
<el-table-column label="实验项目" min-width="220">
<template #default="{ row }">
<div class="student-grade-project">
<b>{{ row.projectCode }} · {{ row.projectName }}</b>
<small>{{ modeLabels[row.arrangementMode] }} · 贡献权重 {{ row.contributionWeight }}</small>
</div>
</template>
</el-table-column>
<el-table-column label="分项成绩" min-width="300">
<template #default="{ row }">
<div class="rubric-inline">
<span v-for="item in row.items" :key="item.experimentGradeItemId">
{{ item.name }} <b>{{ item.score ?? '—' }}</b>
</span>
</div>
</template>
</el-table-column>
<el-table-column label="参与状态" width="100" align="center">
<template #default="{ row }">{{ participationLabels[row.participationStatus] }}</template>
</el-table-column>
<el-table-column label="项目成绩" width="100" align="center">
<template #default="{ row }">
<strong class="project-total" :class="projectScoreTone(row)">{{ row.totalScore ?? '—' }}</strong>
</template>
</el-table-column>
<el-table-column label="结果" width="88" align="center">
<template #default="{ row }">
<el-tag v-if="row.totalScore != null" :type="row.isPassed ? 'success' : 'danger'" size="small">
{{ row.isPassed ? '通过' : '未通过' }}
</el-tag>
<span v-else class="table-muted">待完整</span>
</template>
</el-table-column>
</el-table>
</article>
<el-empty v-if="!loading && !studentResults.length" description="暂无已发布的实验成绩" />
</section>
@@ -785,18 +822,31 @@ onMounted(async () => {
.sheet-progress, .sheet-empty { display: grid; gap: 5px; color: #607681; font-size: 12px; }
.sheet-progress b { color: var(--grade-teal); }
.sheet-empty b { color: #5d7480; }
.student-result-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 15px; min-height: 180px; }
.student-result-card { overflow: hidden; border: 1px solid #d8e4e6; border-top: 5px solid #80939b; background: #fff; }
.student-result-card.is-passed { border-top-color: var(--grade-teal); }
.student-result-card.is-failed { border-top-color: #b5543d; }
.student-result-card > header { padding: 18px; display: flex; justify-content: space-between; gap: 16px; border-bottom: 1px solid #e4eaed; }
.student-result-card header span { color: #607d89; font: 700 10px/1.3 Consolas, monospace; }
.student-result-card h3 { margin: 5px 0; color: var(--grade-ink); font-size: 18px; }
.student-result-card header p { margin: 0; color: var(--muted); font-size: 12px; }
.score-seal { width: 72px; flex: 0 0 72px; display: grid; place-content: center; border-left: 1px solid #dce6e8; text-align: center; }
.score-seal strong { color: var(--grade-teal); font: 750 28px/1 Consolas, monospace; }
.is-failed .score-seal strong { color: #b5543d; }
.score-seal small { margin-top: 5px; color: var(--muted); }
.student-course-results { display: grid; gap: 12px; min-height: 180px; }
.student-course-result { overflow: hidden; border: 1px solid #cfdee3; background: #fff; }
.student-course-head { min-height: 76px; padding: 11px 14px 11px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-left: 5px solid var(--grade-blue); background: #edf4f6; }
.student-course-head > div:first-child { min-width: 0; }
.student-course-head span { color: #617b87; font: 700 10px/1.3 Consolas, monospace; letter-spacing: .05em; }
.student-course-head h3 { margin: 4px 0; color: var(--grade-ink); font-size: 16px; }
.student-course-head p { margin: 0; color: #647b85; font-size: 11px; }
.course-score-seal { min-width: 132px; display: grid; grid-template-columns: auto auto; align-items: center; gap: 2px 12px; padding: 7px 11px; border-left: 1px solid #cbdade; text-align: right; }
.course-score-seal span { grid-column: 1; color: #59717d; font: 600 11px/1.2 inherit; letter-spacing: 0; }
.course-score-seal strong { grid-column: 2; grid-row: 1 / span 2; color: #768b94; font: 750 28px/1 Consolas, monospace; }
.course-score-seal.has-score strong { color: var(--grade-teal); }
.course-score-seal small { grid-column: 1; color: var(--muted); font-size: 10px; }
.student-grade-table { --el-table-border-color: #dce5e8; --el-table-header-bg-color: #f7fafb; width: 100%; }
.student-grade-table :deep(th.el-table__cell) { padding: 7px 0; color: #637985; font-size: 11px; }
.student-grade-table :deep(td.el-table__cell) { padding: 8px 0; }
.student-grade-project { display: grid; gap: 3px; }
.student-grade-project b { color: var(--grade-ink); font-size: 12px; }
.student-grade-project small { color: #6a8089; font-size: 10px; }
.rubric-inline { display: flex; flex-wrap: wrap; gap: 4px 10px; }
.rubric-inline span { color: #6a7f88; font-size: 10px; }
.rubric-inline b { color: #294e5d; font-size: 11px; }
.project-total { font: 750 15px/1 Consolas, monospace; color: #74858c; }
.project-total.is-passed { color: var(--grade-teal); }
.project-total.is-failed { color: #b5543d; }
.student-grade-detail { padding: 12px 16px 14px; background: #f7faf9; }
.result-facts { padding: 12px 18px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; background: #f5f8f8; }
.result-facts span { display: grid; gap: 3px; color: #758991; font-size: 10px; }
.result-facts b { color: #365762; font-size: 12px; }