考试排考
This commit is contained in:
@@ -45,6 +45,13 @@ const statusLabels: Record<string, string> = {
|
||||
Published: '已发布',
|
||||
Returned: '已退回',
|
||||
}
|
||||
const statusMeta: Record<string, { icon: string; description: string }> = {
|
||||
Draft: { icon: '📝', description: '教师正在录入成绩' },
|
||||
Submitted: { icon: '📤', description: '已提交,等待开课学院审核' },
|
||||
Approved: { icon: '✅', description: '学院审核通过,待校级发布' },
|
||||
Published: { icon: '📢', description: '成绩已向学生发布' },
|
||||
Returned: { icon: '↩️', description: '学院退回,需教师修改后重新提交' },
|
||||
}
|
||||
const examStatusLabels: Record<string, string> = {
|
||||
Normal: '正常',
|
||||
Absent: '缺考',
|
||||
@@ -281,6 +288,32 @@ function itemScore(record: any, gradeItemId: string) {
|
||||
return record.itemScores?.find((itemScore: any) => itemScore.gradeItemId === gradeItemId)
|
||||
}
|
||||
|
||||
/** Client-side preview calculation — mirrors backend GradeCalculator.CalculateTotal */
|
||||
function calcPreviewTotal(record: any): number | null {
|
||||
if (!detail.value) return null
|
||||
if (record.examStatus !== 'Normal') return null
|
||||
|
||||
const { regularWeight, finalWeight, items } = detail.value
|
||||
const itemWeightById: Record<string, number> = {}
|
||||
for (const item of items ?? []) itemWeightById[item.id] = item.weight
|
||||
|
||||
// Check required scores are present
|
||||
if (regularWeight > 0 && (record.regularScore == null || record.regularScore === '')) return null
|
||||
if (finalWeight > 0 && (record.finalScore == null || record.finalScore === '')) return null
|
||||
for (const itemScore of record.itemScores ?? []) {
|
||||
const weight = itemWeightById[itemScore.gradeItemId]
|
||||
if (weight > 0 && (itemScore.score == null || itemScore.score === '')) return null
|
||||
}
|
||||
|
||||
let total = (Number(record.regularScore) || 0) * regularWeight / 100
|
||||
for (const itemScore of record.itemScores ?? []) {
|
||||
const weight = itemWeightById[itemScore.gradeItemId] || 0
|
||||
total += (Number(itemScore.score) || 0) * weight / 100
|
||||
}
|
||||
total += (Number(record.finalScore) || 0) * finalWeight / 100
|
||||
return Math.round(total * 10) / 10
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
terms.value = (await http.get('/base-data/terms')).data
|
||||
@@ -395,8 +428,14 @@ onMounted(async () => {
|
||||
<span>{{ detail.courseCode }} · {{ detail.taskNumber }}</span>
|
||||
<h3>{{ detail.courseName }}成绩登记册</h3>
|
||||
<p>{{ detail.termName }} · {{ detail.teacherNames.join('、') }} · {{ detail.classNames.join('、') }}</p>
|
||||
<p v-if="detail.courseCollegeName" class="college-meta">开课学院:{{ detail.courseCollegeName }}</p>
|
||||
</div>
|
||||
<div class="status-badge-group">
|
||||
<i :class="detail.status.toLowerCase()">{{ statusLabels[detail.status] }}</i>
|
||||
<small v-if="detail.status === 'Submitted' && detail.needsCollegeReview">
|
||||
需 {{ detail.needsCollegeReview }} 审核
|
||||
</small>
|
||||
</div>
|
||||
<i :class="detail.status.toLowerCase()">{{ statusLabels[detail.status] }}</i>
|
||||
</header>
|
||||
|
||||
<section class="grade-register-ruler">
|
||||
@@ -465,9 +504,14 @@ onMounted(async () => {
|
||||
<span v-else>{{ row.finalScore ?? '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总评" width="80">
|
||||
<el-table-column label="总评" width="110">
|
||||
<template #default="{ row }">
|
||||
<b class="total-score" :class="scoreClass(row.totalScore)">{{ row.totalScore ?? '—' }}</b>
|
||||
<div class="total-score-cell">
|
||||
<b class="total-score" :class="scoreClass(row.totalScore)">{{ row.totalScore ?? '—' }}</b>
|
||||
<span v-if="detail.canEdit && row.examStatus === 'Normal'" class="preview-score" :class="scoreClass(calcPreviewTotal(row))">
|
||||
参考 {{ calcPreviewTotal(row) ?? '—' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考试状态" width="115">
|
||||
@@ -488,15 +532,20 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<footer class="grade-actions">
|
||||
<div>
|
||||
<span>当前流程</span>
|
||||
<b>{{ statusLabels[detail.status] }}</b>
|
||||
<div class="workflow-status">
|
||||
<span>{{ statusMeta[detail.status]?.icon }} {{ statusLabels[detail.status] }}</span>
|
||||
<small>{{ statusMeta[detail.status]?.description }}</small>
|
||||
<small v-if="detail.needsCollegeReview && (detail.status === 'Submitted' || detail.status === 'Approved')" class="college-hint">
|
||||
开课学院:{{ detail.needsCollegeReview }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="grade-actions-buttons">
|
||||
<el-button v-if="detail.canEdit" :icon="EditPen" @click="saveRecords">保存成绩</el-button>
|
||||
<el-button v-if="detail.canEdit" type="primary" :icon="Promotion" @click="submitSheet">提交审核</el-button>
|
||||
<el-button v-if="detail.canReview" type="danger" plain @click="openReturn">退回修改</el-button>
|
||||
<el-button v-if="detail.canReview" type="success" :icon="Check" @click="approveSheet">审核通过</el-button>
|
||||
<el-button v-if="detail.canPublish" type="primary" :icon="Promotion" @click="publishSheet">发布成绩</el-button>
|
||||
</div>
|
||||
<el-button v-if="detail.canEdit" :icon="EditPen" @click="saveRecords">保存成绩</el-button>
|
||||
<el-button v-if="detail.canEdit" type="primary" :icon="Promotion" @click="submitSheet">提交审核</el-button>
|
||||
<el-button v-if="detail.canReview" type="danger" plain @click="openReturn">退回修改</el-button>
|
||||
<el-button v-if="detail.canReview" type="success" :icon="Check" @click="approveSheet">审核通过</el-button>
|
||||
<el-button v-if="detail.canPublish" type="primary" :icon="Promotion" @click="publishSheet">发布成绩</el-button>
|
||||
</footer>
|
||||
</template>
|
||||
</main>
|
||||
@@ -581,4 +630,21 @@ onMounted(async () => {
|
||||
.item-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: #eef5f0; border-radius: 4px; }
|
||||
.item-row > span:first-child { flex: 1; font-size: 13px; font-weight: 650; }
|
||||
.add-item-row { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
/* Preview score */
|
||||
.total-score-cell { display: flex; flex-direction: column; align-items: center; gap: 2px; }
|
||||
.preview-score { font-size: 10px; opacity: .7; white-space: nowrap; }
|
||||
.preview-score.failed { color: #b34e48; }
|
||||
.preview-score.excellent { color: #2d8975; }
|
||||
|
||||
/* College review */
|
||||
.college-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||
.status-badge-group { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
|
||||
.status-badge-group small { font-size: 11px; color: var(--muted); }
|
||||
.workflow-status { display: flex; flex-direction: column; gap: 2px; }
|
||||
.workflow-status span { font-weight: 650; font-size: 13px; }
|
||||
.workflow-status small { color: var(--muted); font-size: 11px; }
|
||||
.workflow-status .college-hint { color: #6b4e16; font-weight: 600; }
|
||||
.grade-actions { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; padding: 12px 16px; border-top: 1px solid #e4e7ed; background: #fafbfc; }
|
||||
.grade-actions-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user