Files
en-edu/enlish-vue/src/pages/class.vue
lbw e3b993dd27 feat(exam): 实现按学生批量生成并下载试题功能
- 增加学生多选功能和生成试题按钮,支持批量操作
- 新增ExamGenerateDialog组件,提供选择年级和难度界面
- 设计后端接口支持多个学生ID,生成对应的试题文档
- 在后端实现批量生成Word文档并压缩打包下载
- 新增StudentDetail业务对象,完善学生信息展示
- 优化了Mapper接口及XML,支持批量查询学生和班级数据
- 提供前端API封装用于调用试题生成和下载服务
- 实现下载失败时的错误处理与提示机制
2025-12-15 14:32:53 +08:00

238 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="common-layout">
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-main class="p-4">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="lg:col-span-1 flex flex-col gap-6">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">班级列表</div>
<el-table ref="classTableRef" :data="classes" border class="w-full" v-loading="loading" highlight-current-row
row-key="id" :current-row-key="selectedClassId" @row-click="onClassRowClick">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="班级名称" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount"
:page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange"
@size-change="handleSizeChange" />
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 lg:col-span-1 lg:row-span-2">
<div class="text-lg font-semibold mb-4">学生查询</div>
<div class="flex flex-wrap items-center gap-3 mb-4">
<el-input v-model="studentName" placeholder="按姓名查询" clearable style="max-width: 220px" />
<el-tag v-if="selectedClassId" effect="plain">班级{{ selectedClassTitle }} (ID: {{
selectedClassId }})</el-tag>
<el-tag v-if="selectedGradeId" effect="plain">年级{{ selectedGradeTitle }} (ID: {{
selectedGradeId }})</el-tag>
<el-button type="primary" @click="fetchStudents">查询</el-button>
<el-button @click="resetStudentFilters">重置</el-button>
<el-button type="success" :disabled="selectedStudentIds.length === 0" @click="showGenerateDialog = true">
生成试题
</el-button>
</div>
<el-table
ref="studentTableRef"
:data="students"
border
class="w-full"
v-loading="studentLoading"
@selection-change="onStudentSelectionChange"
>
<el-table-column type="selection" width="48" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="classId" label="班级ID" width="100" />
<el-table-column prop="gradeId" label="年级ID" width="100" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total"
:total="studentTotalCount" :page-size="studentPageSize" :current-page="studentPageNo"
@current-change="handleStudentPageChange" @size-change="handleStudentSizeChange" />
</div>
<ExamGenerateDialog
v-model="showGenerateDialog"
:student-ids="selectedStudentIds"
:default-grade-id="selectedGradeId"
/>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
<div class="text-lg font-semibold mb-4">年级列表</div>
<el-table ref="gradeTableRef" :data="grades" border class="w-full" highlight-current-row row-key="id"
:current-row-key="selectedGradeId" @row-click="onGradeRowClick">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="年级名称" min-width="160" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="gradeTotalCount"
:page-size="gradePageSize" :current-page="gradePageNo"
@current-change="handleGradePageChange" @size-change="handleGradeSizeChange" />
</div>
</div>
</div>
</el-main>
</el-container>
</div>
</template>
<script setup>
import Header from '@/layouts/components/Header.vue'
import { ref, onMounted } from 'vue'
import { getClassList } from '@/api/class'
import { getGradeList } from '@/api/grade'
import { getStudentList } from '@/api/student'
import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue'
const classes = ref([])
const pageNo = ref(1)
const pageSize = ref(10)
const totalCount = ref(0)
const loading = ref(false)
const classTableRef = ref(null)
const selectedClassId = ref(null)
const selectedClassTitle = ref('')
const grades = ref([])
const gradePageNo = ref(1)
const gradePageSize = ref(10)
const gradeTotalCount = ref(0)
const gradeLoading = ref(false)
const gradeTableRef = ref(null)
const selectedGradeId = ref(null)
const selectedGradeTitle = ref('')
const students = ref([])
const studentPageNo = ref(1)
const studentPageSize = ref(10)
const studentTotalCount = ref(0)
const studentLoading = ref(false)
const studentName = ref('')
const studentTableRef = ref(null)
const selectedStudentIds = ref([])
const showGenerateDialog = ref(false)
async function fetchClasses() {
loading.value = true
try {
const res = await getClassList(pageNo.value, pageSize.value)
const d = res.data
classes.value = Array.isArray(d.data) ? d.data : []
totalCount.value = d.totalCount || 0
pageNo.value = d.pageNo || pageNo.value
pageSize.value = d.pageSize || pageSize.value
} finally {
loading.value = false
}
}
async function fetchGrades() {
gradeLoading.value = true
try {
const res = await getGradeList(gradePageNo.value, gradePageSize.value)
const d = res.data
grades.value = Array.isArray(d.data) ? d.data : []
gradeTotalCount.value = d.totalCount || 0
gradePageNo.value = d.pageNo || gradePageNo.value
gradePageSize.value = d.pageSize || gradePageSize.value
} finally {
gradeLoading.value = false
}
}
async function fetchStudents() {
studentLoading.value = true
try {
const payload = {
name: studentName.value || null,
classId: selectedClassId.value ?? null,
gradeId: selectedGradeId.value ?? null,
pageNo: studentPageNo.value,
pageSize: studentPageSize.value
}
const res = await getStudentList(payload)
const d = res.data
students.value = Array.isArray(d.data) ? d.data : []
studentTotalCount.value = d.totalCount || 0
studentPageNo.value = d.pageNo || studentPageNo.value
studentPageSize.value = d.pageSize || studentPageSize.value
} finally {
studentLoading.value = false
}
}
function handlePageChange(p) {
pageNo.value = p
fetchClasses()
}
function handleSizeChange(s) {
pageSize.value = s
pageNo.value = 1
fetchClasses()
}
function handleGradePageChange(p) {
gradePageNo.value = p
fetchGrades()
}
function handleGradeSizeChange(s) {
gradePageSize.value = s
gradePageNo.value = 1
fetchGrades()
}
function handleStudentPageChange(p) {
studentPageNo.value = p
fetchStudents()
}
function handleStudentSizeChange(s) {
studentPageSize.value = s
studentPageNo.value = 1
fetchStudents()
}
function onStudentSelectionChange(rows) {
selectedStudentIds.value = rows.map(r => r.id)
}
function onClassRowClick(row) {
selectedClassId.value = row.id
selectedClassTitle.value = row.title
classTableRef.value?.setCurrentRow(row)
studentPageNo.value = 1
fetchStudents()
}
function onGradeRowClick(row) {
selectedGradeId.value = row.id
selectedGradeTitle.value = row.title
gradeTableRef.value?.setCurrentRow(row)
studentPageNo.value = 1
fetchStudents()
}
function resetStudentFilters() {
studentName.value = ''
selectedClassId.value = null
selectedClassTitle.value = ''
selectedGradeId.value = null
selectedGradeTitle.value = ''
classTableRef.value?.setCurrentRow()
gradeTableRef.value?.setCurrentRow()
studentPageNo.value = 1
fetchStudents()
}
onMounted(() => {
fetchClasses()
fetchGrades()
fetchStudents()
})
</script>