成绩录入页面优化
This commit is contained in:
1 parent
53bee58a84
commit
7124011da9
3 files changed
+234
-54
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
Check,
|
||||
ArrowRight,
|
||||
@@ -18,9 +18,11 @@ import { downloadApiFile, importExcel } from '../api/excel'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'
|
||||
import { useScoreGridNavigation } from '../utils/scoreGridNavigation'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const router = useRouter()
|
||||
const { handleScoreGridKeydown } = useScoreGridNavigation('course-grade-entry')
|
||||
const isStudent = computed(() => auth.user?.roles.includes('Student') &&
|
||||
!auth.user.roles.some((role) =>
|
||||
['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher'].includes(role)))
|
||||
@@ -53,6 +55,10 @@ const createForm = reactive({
|
||||
const newItemName = ref('')
|
||||
const newItemWeight = ref(0)
|
||||
const returnComment = ref('')
|
||||
const autoSaveState = ref<'idle' | 'saving' | 'saved' | 'error'>('idle')
|
||||
let autoSaveTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let savedRecordSnapshot = ''
|
||||
let autoSaving = false
|
||||
|
||||
const presetItems = ['实验', '实习', '课程设计', '作业', '课堂表现', '期中']
|
||||
|
||||
@@ -199,6 +205,7 @@ async function loadDetail() {
|
||||
detail.value.canEdit = response.canEdit
|
||||
detail.value.canReview = response.canReview
|
||||
detail.value.canPublish = response.canPublish
|
||||
savedRecordSnapshot = gradeRecordSnapshot()
|
||||
if (!detail.value.items?.some((item: any) => item.id === experimentGradeItemId.value)) {
|
||||
experimentGradeItemId.value =
|
||||
detail.value.items?.find((item: any) => item.name.includes('实验'))?.id
|
||||
@@ -260,28 +267,62 @@ async function createSheet() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRecords() {
|
||||
function gradeRecordPayload() {
|
||||
return detail.value.records.map((record: any) => ({
|
||||
id: record.id,
|
||||
regularScore: record.regularScore,
|
||||
finalScore: record.finalScore,
|
||||
itemScores: record.itemScores?.map((itemScore: any) => ({
|
||||
gradeItemId: itemScore.gradeItemId,
|
||||
score: itemScore.score,
|
||||
})),
|
||||
examStatus: record.examStatus,
|
||||
notes: record.notes,
|
||||
}))
|
||||
}
|
||||
|
||||
function gradeRecordSnapshot() {
|
||||
return detail.value ? JSON.stringify(gradeRecordPayload()) : ''
|
||||
}
|
||||
|
||||
function scheduleAutoSave() {
|
||||
if (!detail.value?.canEdit || gradeRecordSnapshot() === savedRecordSnapshot) return
|
||||
autoSaveState.value = 'idle'
|
||||
clearTimeout(autoSaveTimer)
|
||||
autoSaveTimer = setTimeout(() => { void saveRecords(true) }, 60_000)
|
||||
}
|
||||
|
||||
async function saveRecords(automatic = false) {
|
||||
if (!detail.value || (automatic && autoSaving)) return
|
||||
const records = gradeRecordPayload()
|
||||
const snapshot = JSON.stringify(records)
|
||||
if (automatic) {
|
||||
autoSaving = true
|
||||
autoSaveState.value = 'saving'
|
||||
}
|
||||
try {
|
||||
await http.put(`/grades/sheets/${detail.value.id}/records`, {
|
||||
records: detail.value.records.map((record: any) => ({
|
||||
id: record.id,
|
||||
regularScore: record.regularScore,
|
||||
finalScore: record.finalScore,
|
||||
itemScores: record.itemScores?.map((itemScore: any) => ({
|
||||
gradeItemId: itemScore.gradeItemId,
|
||||
score: itemScore.score,
|
||||
})),
|
||||
examStatus: record.examStatus,
|
||||
notes: record.notes,
|
||||
})),
|
||||
records,
|
||||
})
|
||||
ElMessage.success('成绩已保存并重新计算总评')
|
||||
await loadDetail()
|
||||
savedRecordSnapshot = snapshot
|
||||
if (automatic) autoSaveState.value = 'saved'
|
||||
else {
|
||||
ElMessage.success('成绩已保存并重新计算总评')
|
||||
await loadDetail()
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
if (automatic) autoSaveState.value = 'error'
|
||||
else ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
if (automatic) {
|
||||
autoSaving = false
|
||||
if (gradeRecordSnapshot() !== savedRecordSnapshot) scheduleAutoSave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => detail.value?.records, scheduleAutoSave, { deep: true })
|
||||
|
||||
async function importExperimentScores() {
|
||||
if (!experimentGradeItemId.value) {
|
||||
ElMessage.warning('请先选择用于承接实验成绩的课程成绩项。')
|
||||
@@ -636,7 +677,14 @@ onMounted(async () => {
|
||||
</el-table-column>
|
||||
<el-table-column :label="`平时 ${detail.regularWeight}%`" width="110">
|
||||
<template #default="{ row }">
|
||||
<div class="score-cell" :class="{ 'is-failed': isFailingScore(row.regularScore) }">
|
||||
<div
|
||||
class="score-cell"
|
||||
:class="{ 'is-failed': isFailingScore(row.regularScore) }"
|
||||
data-score-grid="course-grade-entry"
|
||||
:data-score-row="detail.records.indexOf(row)"
|
||||
data-score-column="0"
|
||||
@keydown="handleScoreGridKeydown"
|
||||
>
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && ['Normal', 'Deferred'].includes(row.examStatus)"
|
||||
v-model="row.regularScore"
|
||||
@@ -653,7 +701,14 @@ onMounted(async () => {
|
||||
:width="item.sourceType === 'ExperimentSummary' ? 150 : 110"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="score-cell" :class="{ 'is-failed': isFailingScore(itemScore(row, item.id)?.score) }">
|
||||
<div
|
||||
class="score-cell"
|
||||
:class="{ 'is-failed': isFailingScore(itemScore(row, item.id)?.score) }"
|
||||
data-score-grid="course-grade-entry"
|
||||
:data-score-row="detail.records.indexOf(row)"
|
||||
:data-score-column="detail.items.indexOf(item) + 1"
|
||||
@keydown="handleScoreGridKeydown"
|
||||
>
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && ['Normal', 'Deferred'].includes(row.examStatus) && item.sourceType !== 'ExperimentSummary'"
|
||||
:model-value="itemScore(row, item.id)?.score"
|
||||
@@ -669,7 +724,14 @@ onMounted(async () => {
|
||||
</el-table-column>
|
||||
<el-table-column :label="`期末 ${detail.finalWeight}%`" width="110">
|
||||
<template #default="{ row }">
|
||||
<div class="score-cell" :class="{ 'is-failed': isFailingScore(row.finalScore) }">
|
||||
<div
|
||||
class="score-cell"
|
||||
:class="{ 'is-failed': isFailingScore(row.finalScore) }"
|
||||
data-score-grid="course-grade-entry"
|
||||
:data-score-row="detail.records.indexOf(row)"
|
||||
:data-score-column="detail.items.length + 1"
|
||||
@keydown="handleScoreGridKeydown"
|
||||
>
|
||||
<el-input-number
|
||||
v-if="detail.canEdit && row.examStatus === 'Normal'"
|
||||
v-model="row.finalScore"
|
||||
@@ -730,6 +792,9 @@ onMounted(async () => {
|
||||
<small v-if="detail.needsCollegeReview && (detail.status === 'Submitted' || detail.status === 'Approved')" class="college-hint">
|
||||
开课学院:{{ detail.needsCollegeReview }}
|
||||
</small>
|
||||
<small v-if="detail.canEdit" class="autosave-state">
|
||||
{{ autoSaveState === 'saving' ? '正在自动保存…' : autoSaveState === 'saved' ? '已自动保存' : autoSaveState === 'error' ? '自动保存失败,请手动保存' : '停止编辑 1 分钟后自动保存' }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="grade-actions-buttons">
|
||||
<div v-if="detail.canEdit && detail.items?.length" class="experiment-import">
|
||||
@@ -749,7 +814,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
<el-button v-if="detail.canEdit" :icon="Download" @click="downloadTemplate">下载模板</el-button>
|
||||
<el-button v-if="detail.canEdit" :icon="Upload" @click="chooseImportFile">Excel导入</el-button>
|
||||
<el-button v-if="detail.canEdit" :icon="EditPen" @click="saveRecords">保存成绩</el-button>
|
||||
<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>
|
||||
|
||||
Reference in new issue
Block a user