feat(student): 实现学生的新增与删除功能

- 新增AddStudentReqVO和DeleteStudentReqVO请求对象
- 在StudentController中添加新增和删除学生接口
- StudentService及其实现类增加新增和删除学生方法
- 通过StudentDOMapper新增插入和逻辑删除方法
- 新增AddStudentDialog组件,实现学生添加的表单及交互
- 在class.vue页面添加新增学生按钮及删除学生操作列
- API层新增addStudent和deleteStudent接口调用
- 删除学生时更新选中状态及重新加载学生列表
- 初始化新增学生时词汇掌握记录相关数据
This commit is contained in:
lbw
2025-12-15 16:08:42 +08:00
parent 7aebff5f6a
commit f8169b453e
10 changed files with 283 additions and 8 deletions

View File

@@ -8,4 +8,19 @@ export function getStudentDetail(id) {
export function getStudentList(data) {
return axios.post('/student/list', data)
}
export function addStudent(name, classId, gradeId, startDate) {
return axios.post('/student/add', {
name: name,
classId: classId,
gradeId: gradeId,
createTime: startDate
})
}
export function deleteStudent(id) {
return axios.post('/student/delete', {
studentId: id
})
}

View File

@@ -0,0 +1,134 @@
<template>
<el-dialog v-model="visible" title="新增学生" width="560px" :close-on-click-modal="false">
<div class="space-y-4" v-loading="loading">
<el-form label-width="90px">
<el-form-item label="姓名">
<el-input v-model="name" placeholder="请输入学生姓名" clearable />
</el-form-item>
<el-form-item label="年级">
<el-select v-model="gradeId" placeholder="请选择年级" style="width: 260px" @change="handleGradeChange">
<el-option v-for="g in gradeOptions" :key="g.id" :label="g.title" :value="g.id" />
</el-select>
</el-form-item>
<el-form-item label="班级">
<el-select v-model="classId" placeholder="请选择班级" style="width: 260px">
<el-option v-for="c in filteredClassOptions" :key="c.id" :label="c.title" :value="c.id" />
</el-select>
<div v-if="gradeId && filteredClassOptions.length === 0" class="mt-2 flex items-center gap-2">
<span class="text-xs text-gray-500">当前年级暂无班级</span>
<el-button type="primary" size="small" @click="showAddClassDialog = true">新增班级</el-button>
</div>
</el-form-item>
<el-form-item label="入学时间">
<el-date-picker v-model="startDate" type="datetime" placeholder="选择日期时间"
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :disabled="!canSubmit" @click="handleSubmit">确定</el-button>
</div>
</template>
</el-dialog>
<AddClassDialog v-model="showAddClassDialog" :default-grade-id="gradeId" @success="onAddClassSuccess" />
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { getGradeList } from '@/api/grade'
import { getClassList } from '@/api/class'
import { addStudent } from '@/api/student'
import AddClassDialog from '@/layouts/components/AddClassDialog.vue'
const props = defineProps({
modelValue: { type: Boolean, default: false },
defaultClassId: { type: [Number, String], default: null },
defaultGradeId: { type: [Number, String], default: null }
})
const emit = defineEmits(['update:modelValue', 'success'])
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const loading = ref(false)
const name = ref('')
const gradeId = ref(null)
const classId = ref(null)
const startDate = ref('')
const gradeOptions = ref([])
const classOptions = ref([])
const filteredClassOptions = computed(() => {
if (!gradeId.value) return []
return classOptions.value.filter(c => String(c.gradeId) === String(gradeId.value))
})
const showAddClassDialog = ref(false)
function onAddClassSuccess() {
fetchBaseOptions().then(() => {
handleGradeChange()
})
}
const canSubmit = computed(() =>
name.value.trim().length > 0 &&
!!gradeId.value &&
!!classId.value &&
!!startDate.value
)
async function fetchBaseOptions() {
loading.value = true
try {
const [gr, cl] = await Promise.all([
getGradeList(1, 100),
getClassList(1, 100)
])
const gd = gr?.data
const cd = cl?.data
gradeOptions.value = Array.isArray(gd?.data) ? gd.data : []
classOptions.value = Array.isArray(cd?.data) ? cd.data : []
} finally {
loading.value = false
}
}
function handleGradeChange() {
if (classId.value) {
const match = filteredClassOptions.value.some(c => String(c.id) === String(classId.value))
if (!match) classId.value = null
}
}
async function handleSubmit() {
if (!canSubmit.value) return
loading.value = true
try {
await addStudent(name.value.trim(), Number(classId.value), Number(gradeId.value), startDate.value)
ElMessage.success('新增学生成功')
emit('success')
visible.value = false
} finally {
loading.value = false
}
}
watch(
() => props.modelValue,
async (v) => {
if (v) {
name.value = ''
gradeId.value = props.defaultGradeId ? Number(props.defaultGradeId) : null
classId.value = props.defaultClassId ? Number(props.defaultClassId) : null
startDate.value = ''
await fetchBaseOptions()
if (gradeId.value) handleGradeChange()
}
}
)
</script>
<style scoped></style>

View File

@@ -57,6 +57,11 @@
<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="120" fixed="right">
<template #default="{ row }">
<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"
@@ -65,6 +70,15 @@
</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"
/>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
@@ -103,10 +117,11 @@ import Header from '@/layouts/components/Header.vue'
import { ref, onMounted } from 'vue'
import { getClassList, deleteClass } from '@/api/class'
import { getGradeList } from '@/api/grade'
import { getStudentList } from '@/api/student'
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'
const classes = ref([])
const pageNo = ref(1)
@@ -137,6 +152,7 @@ const studentName = ref('')
const studentTableRef = ref(null)
const selectedStudentIds = ref([])
const showGenerateDialog = ref(false)
const showAddStudentDialog = ref(false)
async function fetchClasses() {
loading.value = true
@@ -233,6 +249,19 @@ function onGradeRowClick(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)