- 在 class.vue 中增加“详情”按钮,可跳转至对应学生详情页 - 使用 vue-router 的 useRouter 实现页面跳转功能 - 添加 /student/:id 路由,绑定学生详情组件 student.vue - 新增 student.vue 组件,展示学生详细信息 - 精简 Header.vue, 移除多余导航链接,优化界面展示
411 lines
17 KiB
Vue
411 lines
17 KiB
Vue
<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-column label="操作" width="120" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="danger" size="small" @click.stop="onDeleteClass(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</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 class="mt-3 flex justify-end">
|
||
<el-button type="primary" @click="showAddClassDialog = true">新增班级</el-button>
|
||
</div>
|
||
<AddClassDialog v-model="showAddClassDialog" :default-grade-id="selectedGradeId"
|
||
@success="fetchClasses" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 lg:col-span-1 lg:row-span-1">
|
||
<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>
|
||
<el-button type="warning" :disabled="selectedStudentIds.length !== 1"
|
||
@click="showLessonPlanDialog = 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-column label="操作" width="180" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" size="small" @click.stop="onViewStudent(row)">详情</el-button>
|
||
<el-button type="danger" size="small" @click.stop="onDeleteStudent(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</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 class="mt-3 flex justify-end">
|
||
<el-button type="primary" @click="showAddStudentDialog = true">新增学生</el-button>
|
||
</div>
|
||
<AddStudentDialog
|
||
v-model="showAddStudentDialog"
|
||
:default-class-id="selectedClassId"
|
||
:default-grade-id="selectedGradeId"
|
||
@success="fetchStudents"
|
||
/>
|
||
<LessonPlanDialog
|
||
v-model="showLessonPlanDialog"
|
||
:student-id="selectedStudentIds[0]"
|
||
/>
|
||
</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-column label="操作" width="120" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="danger" size="small"
|
||
@click.stop="onDeleteGrade(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</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 class="mt-3 flex justify-end">
|
||
<el-button type="primary" @click="showAddGradeDialog = true">新增年级</el-button>
|
||
</div>
|
||
<AddGradeDialog v-model="showAddGradeDialog" @success="fetchGrades" />
|
||
</div>
|
||
|
||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="unitLoading">
|
||
<div class="text-lg font-semibold mb-4">单元列表</div>
|
||
<el-table ref="unitTableRef" :data="units" border class="w-full">
|
||
<el-table-column prop="id" label="ID" width="80" />
|
||
<el-table-column prop="title" label="单元名称" min-width="200" />
|
||
<el-table-column prop="version" label="版本" min-width="120" />
|
||
<el-table-column prop="createAt" label="创建时间" min-width="160" />
|
||
<el-table-column label="操作" width="120" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="danger" size="small" @click.stop="onDeleteUnit(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="mt-4 flex justify-end">
|
||
<el-pagination
|
||
background
|
||
layout="prev, pager, next, sizes, total"
|
||
:total="unitTotalCount"
|
||
:page-size="unitPageSize"
|
||
:current-page="unitPageNo"
|
||
@current-change="handleUnitPageChange"
|
||
@size-change="handleUnitSizeChange"
|
||
/>
|
||
</div>
|
||
<div class="mt-3 flex justify-end">
|
||
<el-button type="primary" :disabled="!selectedGradeId" @click="showAddUnitDialog = true">新增单元</el-button>
|
||
</div>
|
||
<AddUnitDialog
|
||
v-model="showAddUnitDialog"
|
||
:default-grade-id="selectedGradeId"
|
||
@success="fetchUnits"
|
||
/>
|
||
</div>
|
||
|
||
</div>
|
||
</el-main>
|
||
|
||
</el-container>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import Header from '@/layouts/components/Header.vue'
|
||
import { ref, onMounted } from 'vue'
|
||
import { getClassList, deleteClass } from '@/api/class'
|
||
import { getGradeList, deleteGrade } from '@/api/grade'
|
||
import { getStudentList, deleteStudent } from '@/api/student'
|
||
import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue'
|
||
import AddClassDialog from '@/layouts/components/AddClassDialog.vue'
|
||
import AddGradeDialog from '@/layouts/components/AddGradeDialog.vue'
|
||
import AddStudentDialog from '@/layouts/components/AddStudentDialog.vue'
|
||
import LessonPlanDialog from '@/layouts/components/LessonPlanDialog.vue'
|
||
import { getUnitList, deleteUnit } from '@/api/unit'
|
||
import AddUnitDialog from '@/layouts/components/AddUnitDialog.vue'
|
||
import { useRouter } from 'vue-router'
|
||
|
||
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 showAddClassDialog = ref(false)
|
||
|
||
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 showAddGradeDialog = ref(false)
|
||
|
||
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)
|
||
const showAddStudentDialog = ref(false)
|
||
const showLessonPlanDialog = ref(false)
|
||
|
||
const units = ref([])
|
||
const unitPageNo = ref(1)
|
||
const unitPageSize = ref(10)
|
||
const unitTotalCount = ref(0)
|
||
const unitLoading = ref(false)
|
||
const unitTableRef = ref(null)
|
||
const showAddUnitDialog = ref(false)
|
||
const router = useRouter()
|
||
|
||
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 onViewStudent(row) {
|
||
router.push(`/student/${row.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()
|
||
}
|
||
async function onDeleteStudent(row) {
|
||
try {
|
||
await deleteStudent(row.id)
|
||
ElMessage.success('删除成功')
|
||
if (selectedStudentIds.value?.length) {
|
||
selectedStudentIds.value = selectedStudentIds.value.filter(id => id !== row.id)
|
||
studentTableRef.value?.toggleRowSelection(row, false)
|
||
}
|
||
await fetchStudents()
|
||
} catch (e) {
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}
|
||
async function onDeleteClass(row) {
|
||
try {
|
||
await deleteClass(row.id)
|
||
ElMessage.success('删除成功')
|
||
if (selectedClassId.value === row.id) {
|
||
selectedClassId.value = null
|
||
selectedClassTitle.value = ''
|
||
classTableRef.value?.setCurrentRow()
|
||
}
|
||
await fetchClasses()
|
||
} catch (e) {
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}
|
||
function resetStudentFilters() {
|
||
studentName.value = ''
|
||
selectedClassId.value = null
|
||
selectedClassTitle.value = ''
|
||
selectedGradeId.value = null
|
||
selectedGradeTitle.value = ''
|
||
classTableRef.value?.setCurrentRow()
|
||
gradeTableRef.value?.setCurrentRow()
|
||
studentPageNo.value = 1
|
||
fetchStudents()
|
||
}
|
||
|
||
async function onDeleteGrade(row) {
|
||
try {
|
||
await deleteGrade(row.id)
|
||
ElMessage.success('删除成功')
|
||
if (selectedGradeId.value === row.id) {
|
||
selectedGradeId.value = null
|
||
selectedGradeTitle.value = ''
|
||
gradeTableRef.value?.setCurrentRow()
|
||
}
|
||
await fetchGrades()
|
||
} catch (e) {
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}
|
||
|
||
async function fetchUnits() {
|
||
unitLoading.value = true
|
||
try {
|
||
const res = await getUnitList(unitPageNo.value, unitPageSize.value)
|
||
const d = res.data
|
||
units.value = Array.isArray(d.data) ? d.data : []
|
||
unitTotalCount.value = d.totalCount || 0
|
||
unitPageNo.value = d.pageNo || unitPageNo.value
|
||
unitPageSize.value = d.pageSize || unitPageSize.value
|
||
} finally {
|
||
unitLoading.value = false
|
||
}
|
||
}
|
||
function handleUnitPageChange(p) {
|
||
unitPageNo.value = p
|
||
fetchUnits()
|
||
}
|
||
function handleUnitSizeChange(s) {
|
||
unitPageSize.value = s
|
||
unitPageNo.value = 1
|
||
fetchUnits()
|
||
}
|
||
async function onDeleteUnit(row) {
|
||
try {
|
||
const res = await deleteUnit(row.id)
|
||
const data = res.data
|
||
console.log(data)
|
||
if (data.success) {
|
||
ElMessage.success('删除成功')
|
||
} else {
|
||
ElMessage.error(data.message || '删除失败')
|
||
}
|
||
await fetchUnits()
|
||
} catch (e) {
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchClasses()
|
||
fetchGrades()
|
||
fetchStudents()
|
||
fetchUnits()
|
||
})
|
||
</script>
|