修复其他考试提交
This commit is contained in:
@@ -384,6 +384,10 @@ function scoreClass(score: number | null) {
|
||||
return ''
|
||||
}
|
||||
|
||||
function isFailingScore(score: number | string | null | undefined) {
|
||||
return score != null && score !== '' && Number(score) < 60
|
||||
}
|
||||
|
||||
function itemScore(record: any, gradeItemId: string) {
|
||||
return record.itemScores?.find((itemScore: any) => itemScore.gradeItemId === gradeItemId)
|
||||
}
|
||||
@@ -473,7 +477,7 @@ onMounted(async () => {
|
||||
:style="{ left: `${record.totalScore}%` }"
|
||||
>{{ record.totalScore }}</span>
|
||||
</div>
|
||||
<div class="transcript-result">
|
||||
<div class="transcript-result" :class="{ 'is-failed': isFailingScore(record.totalScore) }">
|
||||
<span>{{ examStatusLabels[record.examStatus] }}</span>
|
||||
<b v-if="record.totalScore != null">{{ record.totalScore }}</b>
|
||||
<b v-else>—</b>
|
||||
@@ -602,12 +606,14 @@ onMounted(async () => {
|
||||
</el-table-column>
|
||||
<el-table-column :label="`平时 ${detail.regularWeight}%`" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal'"
|
||||
v-model="row.regularScore"
|
||||
:min="0" :max="100" :controls="false" size="small"
|
||||
/>
|
||||
<span v-else>{{ row.regularScore ?? '—' }}</span>
|
||||
<div class="score-cell" :class="{ 'is-failed': isFailingScore(row.regularScore) }">
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal'"
|
||||
v-model="row.regularScore"
|
||||
:min="0" :max="100" :controls="false" size="small"
|
||||
/>
|
||||
<span v-else>{{ row.regularScore ?? '—' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -617,36 +623,45 @@ onMounted(async () => {
|
||||
:width="item.sourceType === 'ExperimentSummary' ? 150 : 110"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal' && item.sourceType !== 'ExperimentSummary'"
|
||||
:model-value="itemScore(row, item.id)?.score"
|
||||
@update:model-value="(val: number | undefined) => { const found = itemScore(row, item.id); if (found) found.score = val }"
|
||||
:min="0" :max="100" :controls="false" size="small"
|
||||
/>
|
||||
<span v-else class="item-score-readonly">
|
||||
{{ itemScore(row, item.id)?.score ?? '—' }}
|
||||
<small v-if="item.sourceType === 'ExperimentSummary'">已锁定</small>
|
||||
</span>
|
||||
<div class="score-cell" :class="{ 'is-failed': isFailingScore(itemScore(row, item.id)?.score) }">
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal' && item.sourceType !== 'ExperimentSummary'"
|
||||
:model-value="itemScore(row, item.id)?.score"
|
||||
@update:model-value="(val: number | undefined) => { const found = itemScore(row, item.id); if (found) found.score = val }"
|
||||
:min="0" :max="100" :controls="false" size="small"
|
||||
/>
|
||||
<span v-else class="item-score-readonly">
|
||||
{{ itemScore(row, item.id)?.score ?? '—' }}
|
||||
<small v-if="item.sourceType === 'ExperimentSummary'">已锁定</small>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="`期末 ${detail.finalWeight}%`" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal'"
|
||||
v-model="row.finalScore"
|
||||
:min="0" :max="100" :controls="false" size="small"
|
||||
/>
|
||||
<span v-else>{{ row.finalScore ?? '—' }}</span>
|
||||
<div class="score-cell" :class="{ 'is-failed': isFailingScore(row.finalScore) }">
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal'"
|
||||
v-model="row.finalScore"
|
||||
:min="0" :max="100" :controls="false" size="small"
|
||||
/>
|
||||
<span v-else>{{ row.finalScore ?? '—' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总评" width="90">
|
||||
<template #default="{ row }">
|
||||
<template v-if="detail.canEdit && row.examStatus === 'Normal'">
|
||||
<b class="total-score preview" :class="scoreClass(calcPreviewTotal(row))">
|
||||
{{ calcPreviewTotal(row) ?? '—' }}
|
||||
</b>
|
||||
</template>
|
||||
<b v-else class="total-score" :class="scoreClass(row.totalScore)">{{ row.totalScore ?? '—' }}</b>
|
||||
<div
|
||||
class="score-cell"
|
||||
:class="{ 'is-failed': isFailingScore(detail.canEdit && row.examStatus === 'Normal' ? calcPreviewTotal(row) : row.totalScore) }"
|
||||
>
|
||||
<template v-if="detail.canEdit && row.examStatus === 'Normal'">
|
||||
<b class="total-score preview" :class="scoreClass(calcPreviewTotal(row))">
|
||||
{{ calcPreviewTotal(row) ?? '—' }}
|
||||
</b>
|
||||
</template>
|
||||
<b v-else class="total-score" :class="scoreClass(row.totalScore)">{{ row.totalScore ?? '—' }}</b>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考试状态" width="115">
|
||||
@@ -795,6 +810,32 @@ onMounted(async () => {
|
||||
.item-row > span:first-child { flex: 1; font-size: 13px; font-weight: 650; }
|
||||
.add-item-row { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
.score-cell {
|
||||
min-height: 30px;
|
||||
margin: -5px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.score-cell.is-failed {
|
||||
color: #b42318;
|
||||
background: #fdecec;
|
||||
border-color: #f4b8b2;
|
||||
font-weight: 700;
|
||||
}
|
||||
.score-cell.is-failed :deep(input) { color: #b42318; font-weight: 700; }
|
||||
.transcript-result.is-failed {
|
||||
color: #b42318;
|
||||
background: #fdecec;
|
||||
border: 1px solid #f4b8b2;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
.transcript-result.is-failed b { color: #b42318; }
|
||||
|
||||
/* Preview total score */
|
||||
.total-score.preview {
|
||||
text-decoration: underline dashed;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Download, Plus, Promotion, Upload } from '@element-plus/icons-vue'
|
||||
import http, { apiErrorMessage } from '../api/http'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
@@ -14,6 +14,7 @@ const selected = ref<any>(null)
|
||||
const results = ref<ResultRow[]>([])
|
||||
const mine = reactive({ best: [] as any[], history: [] as any[] })
|
||||
const dialog = ref(false)
|
||||
const publishDialog = ref(false)
|
||||
const saving = ref(false)
|
||||
const form = reactive({ examCode: '', name: '', organizer: '', examDate: new Date().toISOString().slice(0, 10), metricKind: 3, maxScore: 100, levelOptions: '' })
|
||||
const selectedMetricKind = computed(() => Number(selected.value?.metricKind ?? 0))
|
||||
@@ -54,11 +55,19 @@ async function saveResults() {
|
||||
} catch (error) { ElMessage.error(apiErrorMessage(error)) } finally { saving.value = false }
|
||||
}
|
||||
async function publish() {
|
||||
publishDialog.value = true
|
||||
}
|
||||
async function confirmPublish() {
|
||||
saving.value = true
|
||||
try {
|
||||
await ElMessageBox.confirm('确认发布本批次成绩?发布后学生可查看;修改后可再次发布。', '发布其他考试成绩')
|
||||
await http.post(`/other-exams/batches/${selected.value.id}/publish`)
|
||||
ElMessage.success('成绩已发布'); await openBatch(selected.value); await load()
|
||||
} catch (error: any) { if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error)) }
|
||||
publishDialog.value = false
|
||||
ElMessage.success('成绩已发布')
|
||||
await openBatch(selected.value)
|
||||
await load()
|
||||
} catch (error) {
|
||||
ElMessage.error(`发布失败:${apiErrorMessage(error)}`)
|
||||
} finally { saving.value = false }
|
||||
}
|
||||
async function createBatch() {
|
||||
try { await http.post('/other-exams/batches', form); dialog.value = false; ElMessage.success('考试批次已建立'); await load() }
|
||||
@@ -94,6 +103,7 @@ onMounted(load)
|
||||
<section v-if="selected" class="editor-panel"><div class="editor-heading"><div><span class="kicker">{{ selected.examCode }}</span><h2>{{ selected.name }}</h2><p>{{ selected.organizer || '未填写组织方' }} · {{ selected.examDate }} · {{ metricName(selected.metricKind) }}{{ selected.metricKind === 3 ? ` · 满分 ${selected.maxScore}` : '' }}</p></div><div class="editor-actions"><el-button :icon="Download" @click="downloadTemplate">下载导入模板</el-button><label class="upload-button"><Upload />批量导入<input type="file" accept=".xlsx,.xls" @change="importExcel" /></label><el-button :icon="Plus" @click="addResult">新增一行</el-button><el-button type="primary" :loading="saving" @click="saveResults">保存记录</el-button><el-button type="success" :icon="Promotion" @click="publish">发布</el-button></div></div><div class="editor-hint">学号输入完成后离开输入框,系统自动检索姓名、学院和班级;参加次数不需要填写,由系统按考试编码和考试日期自动计算。</div><el-table :data="results" class="score-table"><el-table-column label="学号" min-width="150" fixed><template #default="{ row }"><el-input v-model="row.studentNumber" placeholder="输入学号" @blur="lookupStudent(row)" /></template></el-table-column><el-table-column label="姓名" width="110"><template #default="{ row }"><span :class="{ 'unresolved': row.studentNumber && !row.studentName }">{{ row.studentName || '待检索' }}</span></template></el-table-column><el-table-column label="学院" min-width="150"><template #default="{ row }">{{ row.collegeName || '—' }}</template></el-table-column><el-table-column label="班级" min-width="150"><template #default="{ row }">{{ row.className || '—' }}</template></el-table-column><el-table-column v-if="selected.metricKind === 3" label="成绩" width="150"><template #default="{ row }"><el-input-number v-model="row.score" :min="0" :max="selected.maxScore" :precision="2" controls-position="right" placeholder="请输入分数" /></template></el-table-column><el-table-column v-if="selected.metricKind === 2" label="等级" width="150"><template #default="{ row }"><el-select v-model="row.level" placeholder="选择等级"><el-option v-for="level in (selected.levelOptions || '').split(',').filter(Boolean)" :key="level" :label="level" :value="level" /></el-select></template></el-table-column><el-table-column v-if="selected.metricKind === 1" label="考试结果" width="150"><template #default="{ row }"><el-select v-model="row.isPassed" placeholder="选择结果"><el-option label="合格" :value="true"/><el-option label="不合格" :value="false"/></el-select></template></el-table-column><el-table-column label="参加次数" width="100"><template #default="{ row }"><span class="auto-attempt">{{ row.attemptNumber || '自动' }}</span></template></el-table-column><el-table-column label="备注" min-width="180"><template #default="{ row }"><el-input v-model="row.notes" placeholder="可选" /></template></el-table-column></el-table><el-empty v-if="!results.length" description="还没有成绩记录,点击“新增一行”或使用批量导入" /></section>
|
||||
</template>
|
||||
<el-dialog v-model="dialog" title="新建其他考试场次" width="600px"><el-form label-width="100px"><el-form-item label="考试编码" required><el-input v-model="form.examCode" placeholder="如 CET4、IELTS、计算机二级;同一考试始终使用相同编码" /></el-form-item><el-form-item label="考试名称" required><el-input v-model="form.name" placeholder="如 大学英语四级" /></el-form-item><el-form-item label="组织方"><el-input v-model="form.organizer" /></el-form-item><el-form-item label="考试日期"><el-date-picker v-model="form.examDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="评价方式"><el-select v-model="form.metricKind"><el-option label="分数制" :value="3"/><el-option label="等级制" :value="2"/><el-option label="合格/不合格" :value="1"/></el-select></el-form-item><el-form-item v-if="form.metricKind === 3" label="满分"><el-input-number v-model="form.maxScore" :min="1" /></el-form-item><el-form-item v-if="form.metricKind === 2" label="等级顺序"><el-input v-model="form.levelOptions" placeholder="按最优到最差填写,如 A+,A,B,C,D" /></el-form-item></el-form><template #footer><el-button @click="dialog = false">取消</el-button><el-button type="primary" @click="createBatch">建立场次</el-button></template></el-dialog>
|
||||
<el-dialog v-model="publishDialog" title="发布其他考试成绩" width="460px" :close-on-click-modal="false"><div class="publish-confirm"><strong>确认发布“{{ selected?.name }}”成绩?</strong><p>发布后学生可以查看本批次成绩;如需更正,可以修改后再次发布。</p></div><template #footer><el-button @click="publishDialog = false">取消</el-button><el-button type="success" :loading="saving" @click="confirmPublish">确认发布</el-button></template></el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -105,4 +115,5 @@ onMounted(load)
|
||||
.editor-heading > div:first-child { min-width: 0; }
|
||||
.editor-actions { flex: 0 0 auto; }
|
||||
.upload-button { flex: 0 0 auto; min-width: 112px; white-space: nowrap; justify-content: center; line-height: 1.4; }
|
||||
.publish-confirm { color:var(--ink); line-height:1.7; }.publish-confirm p { margin:8px 0 0; color:var(--muted); font-size:13px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user