---
成绩册 Excel 导入/导出 — 已完成
后端 (GradesController.cs)
GET /api/grades/sheets/{id}/template — 下载成绩导入模板
- 根据成绩单的动态分项(GradeItems)生成对应的 Excel 列:学号 | 姓名 | 班级 | 平时成绩 | [分项1] | [分项2] | ... | 期末成绩 | 考试状态 | 备注
- 预填已有学生信息和已有成绩
- 附带填写说明工作表
POST /api/grades/sheets/{id}/import — 从 Excel 导入成绩
- 按学号匹配学生,批量更新平时成绩、分项成绩、期末成绩、考试状态、备注
- 支持留空(不覆盖已有成绩)、0-100 范围校验、考试状态校验(正常/缺考/缓考/免修)
- 导入后自动重新计算总评成绩和绩点
- 仅限 Draft/Returned 状态的成绩单,仅限有编辑权限的用户
- 错误时整批回滚,最多显示 50 条错误详情
前端 (GradesView.vue)
在成绩登记册底部操作栏新增两个按钮(仅在 canEdit 时显示):
- 下载模板 — 调用 downloadApiFile 下载当前成绩单的 Excel 模板
- Excel导入 — 通过隐藏 file input 选择 .xlsx 文件,调用 importExcel 上传
工作流程
1. 教师打开成绩单 → 点击「下载模板」
2. 在 Excel 中填写/修改学生成绩
3. 点击「Excel导入」选择文件上传
4. 系统自动校验、合并成绩、重新计算总评 → 提示更新条数
5. 成绩表刷新显示最新数据
✻ Crunched for 5m 50s
This commit is contained in:
@@ -4,12 +4,15 @@ import {
|
||||
Check,
|
||||
Delete,
|
||||
DocumentChecked,
|
||||
Download,
|
||||
EditPen,
|
||||
Plus,
|
||||
Promotion,
|
||||
Refresh,
|
||||
Upload,
|
||||
} from '@element-plus/icons-vue'
|
||||
import http, { apiErrorMessage } from '../api/http'
|
||||
import { downloadApiFile, importExcel } from '../api/excel'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
|
||||
const auth = useAuthStore()
|
||||
@@ -230,6 +233,37 @@ async function submitSheet() {
|
||||
}
|
||||
}
|
||||
|
||||
const importFileInput = ref<HTMLInputElement>()
|
||||
|
||||
function downloadTemplate() {
|
||||
downloadApiFile(
|
||||
`/grades/sheets/${detail.value.id}/template`,
|
||||
'成绩导入模板.xlsx',
|
||||
)
|
||||
}
|
||||
|
||||
function chooseImportFile() {
|
||||
importFileInput.value?.click()
|
||||
}
|
||||
|
||||
async function handleImport(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
try {
|
||||
const result = await importExcel(
|
||||
`/grades/sheets/${detail.value.id}/import`,
|
||||
file,
|
||||
)
|
||||
ElMessage.success(`导入完成:已更新 ${result.data.updated} 条成绩记录`)
|
||||
await selectTask(selectedTask.value)
|
||||
} catch (error) {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
input.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function approveSheet() {
|
||||
try {
|
||||
await http.post(`/grades/sheets/${detail.value.id}/approve`)
|
||||
@@ -531,6 +565,8 @@ onMounted(async () => {
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<input ref="importFileInput" type="file" accept=".xlsx" style="display:none" @change="handleImport" />
|
||||
|
||||
<footer class="grade-actions">
|
||||
<div class="workflow-status">
|
||||
<span>{{ statusMeta[detail.status]?.icon }} {{ statusLabels[detail.status] }}</span>
|
||||
@@ -540,6 +576,8 @@ onMounted(async () => {
|
||||
</small>
|
||||
</div>
|
||||
<div class="grade-actions-buttons">
|
||||
<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" type="primary" :icon="Promotion" @click="submitSheet">提交审核</el-button>
|
||||
<el-button v-if="detail.canReview" type="danger" plain @click="openReturn">退回修改</el-button>
|
||||
|
||||
Reference in New Issue
Block a user