|
|
|
|
@ -8,44 +8,46 @@
|
|
|
|
|
<div class="filter-bar">
|
|
|
|
|
<el-input v-model="searchKey" placeholder="搜索姓名/学号..." :prefix-icon="Search" clearable size="default" style="width: 220px" />
|
|
|
|
|
<el-select v-model="classFilter" placeholder="班级筛选" clearable size="default" style="width: 180px">
|
|
|
|
|
<el-option label="计算机2021-1班" value="cs1" />
|
|
|
|
|
<el-option label="软件工程2022-2班" value="se2" />
|
|
|
|
|
<el-option label="人工智能2023-1班" value="ai1" />
|
|
|
|
|
<el-option v-for="c in classList" :key="c.id" :label="c.className" :value="c.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-upload action="#" :show-file-list="false" accept=".xlsx,.xls">
|
|
|
|
|
<!-- <el-upload action="#" :show-file-list="false" accept=".xlsx,.xls">
|
|
|
|
|
<el-button :icon="Upload">导入Excel</el-button>
|
|
|
|
|
</el-upload>
|
|
|
|
|
</el-upload> -->
|
|
|
|
|
<el-button type="primary" :icon="Plus" @click="showAddDialog">添加学生</el-button>
|
|
|
|
|
<el-button :icon="Delete" :disabled="!hasSelected" @click="batchDelete">批量删除</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table :data="students" stripe @selection-change="handleSelectionChange">
|
|
|
|
|
<el-table :data="students" stripe v-loading="loading" @selection-change="handleSelectionChange">
|
|
|
|
|
<el-table-column type="selection" width="45" />
|
|
|
|
|
<el-table-column prop="id" label="学号" width="120" />
|
|
|
|
|
<el-table-column prop="studentNo" label="学号" width="120" />
|
|
|
|
|
<el-table-column prop="name" label="姓名" width="100" />
|
|
|
|
|
<el-table-column prop="gender" label="性别" width="70" align="center" />
|
|
|
|
|
<el-table-column prop="gender" label="性别" width="70" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.gender === 1 ? '男' : '女' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="className" label="班级" min-width="160" />
|
|
|
|
|
<el-table-column prop="phone" label="手机号" width="140" />
|
|
|
|
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === '正常' ? 'success' : 'info'" size="small">
|
|
|
|
|
{{ row.status }}
|
|
|
|
|
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
|
|
|
|
{{ row.status === 1 ? '在读' : '离校' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="160" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" :icon="Edit" @click="editStudent(row)">编辑</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" :icon="Delete" @click="deleteStudent(row)">删除</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" :icon="Delete" @click="deleteOne(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="pageCurrent"
|
|
|
|
|
:total="students.length"
|
|
|
|
|
:page-size="10"
|
|
|
|
|
:total="total"
|
|
|
|
|
:page-size="pageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
@ -55,64 +57,142 @@
|
|
|
|
|
|
|
|
|
|
<!-- 添加/编辑弹窗 -->
|
|
|
|
|
<el-dialog v-model="dialogVisible" :title="editing ? '编辑学生信息' : '添加学生'" width="520px" destroy-on-close>
|
|
|
|
|
<el-form :model="form" label-width="80px" label-position="left">
|
|
|
|
|
<el-form ref="formRef" :model="form" :rules="formRules" label-width="80px" label-position="left">
|
|
|
|
|
<el-form-item label="学号" required>
|
|
|
|
|
<el-input v-model="form.id" placeholder="请输入学号" />
|
|
|
|
|
<el-input v-model="form.studentNo" placeholder="请输入学号" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="姓名" required>
|
|
|
|
|
<el-input v-model="form.name" placeholder="请输入姓名" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="性别">
|
|
|
|
|
<el-radio-group v-model="form.gender">
|
|
|
|
|
<el-radio value="男">男</el-radio>
|
|
|
|
|
<el-radio value="女">女</el-radio>
|
|
|
|
|
<el-radio :value="1">男</el-radio>
|
|
|
|
|
<el-radio :value="0">女</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="班级" required>
|
|
|
|
|
<el-select v-model="form.className" placeholder="请选择班级" style="width: 100%">
|
|
|
|
|
<el-option label="计算机2021-1班" value="计算机2021-1班" />
|
|
|
|
|
<el-option label="软件工程2022-2班" value="软件工程2022-2班" />
|
|
|
|
|
<el-select v-model="form.classId" placeholder="请选择班级" style="width: 100%">
|
|
|
|
|
<el-option v-for="c in classList" :key="c.id" :label="c.className" :value="c.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="手机号">
|
|
|
|
|
<el-input v-model="form.phone" placeholder="请输入手机号" />
|
|
|
|
|
<el-form-item label="手机号" prop="phone">
|
|
|
|
|
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态">
|
|
|
|
|
<el-radio-group v-model="form.status">
|
|
|
|
|
<el-radio :value="1">在读</el-radio>
|
|
|
|
|
<el-radio :value="0">离校</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="saveStudent">保存</el-button>
|
|
|
|
|
<el-button type="primary" :loading="submitting" @click="saveStudent">保存</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { getStudentPage, getClassList, addStudent, updateStudent, deleteStudent } from '@/api/info'
|
|
|
|
|
|
|
|
|
|
const searchKey = ref('')
|
|
|
|
|
const classFilter = ref('')
|
|
|
|
|
const pageCurrent = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const submitting = ref(false)
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
const editing = ref(false)
|
|
|
|
|
const selectedRows = ref([])
|
|
|
|
|
const hasSelected = ref(false)
|
|
|
|
|
const classList = ref([])
|
|
|
|
|
|
|
|
|
|
const formRef = ref(null)
|
|
|
|
|
|
|
|
|
|
const formRules = {
|
|
|
|
|
phone: [
|
|
|
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const form = ref({
|
|
|
|
|
id: '',
|
|
|
|
|
id: null,
|
|
|
|
|
studentNo: '',
|
|
|
|
|
name: '',
|
|
|
|
|
gender: '男',
|
|
|
|
|
className: '',
|
|
|
|
|
phone: ''
|
|
|
|
|
gender: 1,
|
|
|
|
|
classId: '',
|
|
|
|
|
phone: '',
|
|
|
|
|
status: 1
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// classId → className 映射
|
|
|
|
|
const classMap = computed(() => {
|
|
|
|
|
const map = {}
|
|
|
|
|
classList.value.forEach(c => { map[c.id] = c.className })
|
|
|
|
|
return map
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const students = ref([])
|
|
|
|
|
|
|
|
|
|
// 获取班级列表(下拉筛选用)
|
|
|
|
|
const fetchClassList = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getClassList()
|
|
|
|
|
if (res?.data) {
|
|
|
|
|
classList.value = res.data
|
|
|
|
|
}
|
|
|
|
|
} catch { /* 班级接口不可用时降级 */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取学生分页数据
|
|
|
|
|
const fetchStudents = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
current: pageCurrent.value,
|
|
|
|
|
size: pageSize.value
|
|
|
|
|
}
|
|
|
|
|
if (searchKey.value) params.keyword = searchKey.value
|
|
|
|
|
if (classFilter.value) params.classId = classFilter.value
|
|
|
|
|
const res = await getStudentPage(params)
|
|
|
|
|
if (res?.code === 200 && res.data) {
|
|
|
|
|
const records = (res.data.records || []).map(item => ({
|
|
|
|
|
...item,
|
|
|
|
|
className: classMap.value[item.classId] || `班级${item.classId}`
|
|
|
|
|
}))
|
|
|
|
|
students.value = records
|
|
|
|
|
total.value = res.data.total || students.value.length
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 搜索防抖
|
|
|
|
|
let searchTimer = null
|
|
|
|
|
watch(searchKey, () => {
|
|
|
|
|
clearTimeout(searchTimer)
|
|
|
|
|
searchTimer = setTimeout(() => {
|
|
|
|
|
pageCurrent.value = 1
|
|
|
|
|
fetchStudents()
|
|
|
|
|
}, 400)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const students = ref([
|
|
|
|
|
{ id: '2021001', name: '张三', gender: '男', className: '计算机2021-1班', phone: '138****6789', status: '正常' },
|
|
|
|
|
{ id: '2021002', name: '李四', gender: '女', className: '计算机2021-1班', phone: '139****7890', status: '正常' },
|
|
|
|
|
{ id: '2021003', name: '王五', gender: '男', className: '计算机2021-1班', phone: '137****8901', status: '正常' },
|
|
|
|
|
{ id: '2022001', name: '赵六', gender: '男', className: '软件工程2022-2班', phone: '136****9012', status: '正常' },
|
|
|
|
|
{ id: '2022002', name: '钱七', gender: '女', className: '软件工程2022-2班', phone: '135****0123', status: '休学' }
|
|
|
|
|
])
|
|
|
|
|
watch(classFilter, () => {
|
|
|
|
|
pageCurrent.value = 1
|
|
|
|
|
fetchStudents()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
watch(pageCurrent, () => fetchStudents())
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await fetchClassList()
|
|
|
|
|
fetchStudents()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleSelectionChange = (rows) => {
|
|
|
|
|
selectedRows.value = rows
|
|
|
|
|
@ -121,30 +201,85 @@ const handleSelectionChange = (rows) => {
|
|
|
|
|
|
|
|
|
|
const showAddDialog = () => {
|
|
|
|
|
editing.value = false
|
|
|
|
|
form.value = { id: '', name: '', gender: '男', className: '', phone: '' }
|
|
|
|
|
form.value = { id: null, studentNo: '', name: '', gender: 1, classId: '', phone: '', status: 1 }
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
formRef.value?.clearValidate()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const editStudent = (row) => {
|
|
|
|
|
editing.value = true
|
|
|
|
|
form.value = { ...row }
|
|
|
|
|
form.value = {
|
|
|
|
|
id: row.id,
|
|
|
|
|
studentNo: row.studentNo,
|
|
|
|
|
name: row.name,
|
|
|
|
|
gender: row.gender,
|
|
|
|
|
classId: row.classId,
|
|
|
|
|
phone: row.phone || '',
|
|
|
|
|
status: row.status
|
|
|
|
|
}
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
formRef.value?.clearValidate()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveStudent = () => {
|
|
|
|
|
ElMessage.success(editing.value ? '编辑成功' : '添加成功')
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
const saveStudent = async () => {
|
|
|
|
|
const valid = await formRef.value?.validate().catch(() => false)
|
|
|
|
|
if (!valid) return
|
|
|
|
|
|
|
|
|
|
submitting.value = true
|
|
|
|
|
const payload = {
|
|
|
|
|
studentNo: form.value.studentNo,
|
|
|
|
|
name: form.value.name,
|
|
|
|
|
gender: form.value.gender,
|
|
|
|
|
classId: form.value.classId,
|
|
|
|
|
phone: form.value.phone,
|
|
|
|
|
status: form.value.status
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (editing.value) {
|
|
|
|
|
payload.id = form.value.id
|
|
|
|
|
await updateStudent(payload)
|
|
|
|
|
} else {
|
|
|
|
|
await addStudent(payload)
|
|
|
|
|
}
|
|
|
|
|
ElMessage.success(editing.value ? '编辑成功' : '添加成功')
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
fetchStudents()
|
|
|
|
|
} catch {
|
|
|
|
|
ElMessage.error(editing.value ? '编辑失败' : '添加失败')
|
|
|
|
|
} finally {
|
|
|
|
|
submitting.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteStudent = (row) => {
|
|
|
|
|
const deleteOne = (row) => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除学生 ${row.name}?`, '提示', { type: 'warning' })
|
|
|
|
|
.then(() => ElMessage.success('删除成功'))
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteStudent([row.id])
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
fetchStudents()
|
|
|
|
|
} catch {
|
|
|
|
|
ElMessage.error('删除失败')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const batchDelete = () => {
|
|
|
|
|
if (selectedRows.value.length === 0) return
|
|
|
|
|
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 名学生?`, '批量删除', { type: 'warning' })
|
|
|
|
|
.then(() => ElMessage.success('批量删除成功'))
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const ids = selectedRows.value.map(r => r.id)
|
|
|
|
|
await deleteStudent(ids)
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
selectedRows.value = []
|
|
|
|
|
hasSelected.value = false
|
|
|
|
|
fetchStudents()
|
|
|
|
|
} catch {
|
|
|
|
|
ElMessage.error('批量删除失败')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|