pangu-user-platform/frontend/src/views/business/student/index.vue

298 lines
9.8 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="app-container">
<el-row :gutter="16">
<!-- 左侧学校树 -->
<el-col :span="5">
<el-card shadow="never">
<template #header>
<span>学校筛选</span>
</template>
<el-input v-model="treeFilterText" placeholder="输入关键字过滤" clearable style="margin-bottom: 12px" />
<el-scrollbar height="calc(100vh - 260px)">
<el-tree
ref="treeRef"
:data="schoolTree"
:props="{ label: 'name', children: 'children' }"
node-key="id"
highlight-current
:filter-node-method="filterNode"
@node-click="handleNodeClick"
/>
</el-scrollbar>
</el-card>
</el-col>
<!-- 右侧列表 -->
<el-col :span="19">
<!-- 搜索区域 -->
<el-card shadow="never" class="search-wrapper">
<el-form :model="queryParams" :inline="true">
<el-form-item label="学生姓名">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 150px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学号">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable style="width: 150px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="性别">
<el-select v-model="queryParams.gender" placeholder="全部" clearable style="width: 100px">
<el-option label="男" value="1" />
<el-option label="女" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 表格区域 -->
<el-card shadow="never" style="margin-top: 12px">
<el-row :gutter="10" style="margin-bottom: 12px">
<el-col :span="1.5">
<el-button type="primary" :icon="Plus" @click="handleAdd" v-hasPermi="['business:student:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" :icon="Upload" @click="handleImport" v-hasPermi="['business:student:import']">导入</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
<el-table-column prop="studentNo" label="学号" width="130" />
<el-table-column prop="studentName" label="姓名" width="100" />
<el-table-column prop="gender" label="性别" width="60" align="center">
<template #default="{ row }">
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
</template>
</el-table-column>
<el-table-column prop="birthday" label="出生日期" width="110">
<template #default="{ row }">
{{ row.birthday ? formatDate(row.birthday) : '' }}
</template>
</el-table-column>
<el-table-column prop="schoolName" label="学校" min-width="150" show-overflow-tooltip />
<el-table-column prop="gradeName" label="年级" width="80" />
<el-table-column prop="className" label="班级" width="80" />
<el-table-column label="归属用户" width="180">
<template #default="{ row }">
<template v-if="row.members && row.members.length > 0">
<el-tooltip v-if="row.members.length > 1" placement="top">
<template #content>
<div v-for="(m, idx) in row.members" :key="idx" style="line-height: 1.6">
{{ m.nickname || m.phone }}{{ m.relation ? `${m.relation}` : '' }}
</div>
</template>
<el-tag size="small">{{ row.memberCount }}人绑定</el-tag>
</el-tooltip>
<span v-else>
{{ row.members[0].nickname || row.members[0].phone }}
<span v-if="row.members[0].relation" style="color: #909399">{{ row.members[0].relation }}</span>
</span>
</template>
<span v-else style="color: #909399">未绑定</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column label="操作" width="150" fixed="right" align="center">
<template #default="{ row }">
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)" v-hasPermi="['business:student:edit']">编辑</el-button>
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)" v-hasPermi="['business:student:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top: 16px; justify-content: flex-end"
@size-change="handleQuery"
@current-change="handleQuery"
/>
</el-card>
</el-col>
</el-row>
<!-- 新增/编辑弹窗 -->
<StudentDialog ref="studentDialogRef" @success="handleQuery" />
<!-- 导入弹窗 -->
<ImportDialog ref="importDialogRef" @success="handleQuery" />
</div>
</template>
<script setup>
/**
* 学生管理页面
* @author pangu
*/
import { Delete, Edit, Plus, Refresh, Search, Upload } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, ref, watch } from 'vue'
import request from '@/utils/request'
import ImportDialog from './components/ImportDialog.vue'
import StudentDialog from './components/StudentDialog.vue'
// 学校树相关
const treeRef = ref()
const treeFilterText = ref('')
const schoolTree = ref([])
// 表格相关
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
// 查询参数
const queryParams = ref({
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
gender: '',
schoolId: null,
schoolGradeId: null,
schoolClassId: null
})
// 弹窗引用
const studentDialogRef = ref()
const importDialogRef = ref()
// 监听树过滤
watch(treeFilterText, (val) => {
treeRef.value?.filter(val)
})
// 树节点过滤
const filterNode = (value, data) => {
if (!value) return true
return data.name?.includes(value)
}
// 获取学校树
const getSchoolTree = async () => {
const res = await request.get('/business/student/schoolTree')
if (res.code === 200) {
schoolTree.value = res.data
}
}
// 获取学生列表
const getList = async () => {
loading.value = true
try {
const res = await request.get('/business/student/list', { params: queryParams.value })
if (res.code === 200) {
tableData.value = res.rows
total.value = res.total
}
} finally {
loading.value = false
}
}
// 树节点点击
const handleNodeClick = (data) => {
// 根据节点层级设置筛选条件,子节点需要带上父级条件
// 注意:使用专门的 ID 字段,不是 idid 是带前缀的唯一标识)
if (data.type === 'school') {
// 点击学校:只筛选学校
queryParams.value.schoolId = data.schoolId
queryParams.value.schoolGradeId = null
queryParams.value.schoolClassId = null
} else if (data.type === 'grade') {
// 点击年级:筛选学校 + 年级
queryParams.value.schoolId = data.schoolId
queryParams.value.schoolGradeId = data.schoolGradeId
queryParams.value.schoolClassId = null
} else if (data.type === 'class') {
// 点击班级:筛选学校 + 年级 + 班级
queryParams.value.schoolId = data.schoolId
queryParams.value.schoolGradeId = data.schoolGradeId
queryParams.value.schoolClassId = data.classId // 使用 classIdpg_school_class 表的 ID
}
queryParams.value.pageNum = 1
getList()
}
// 搜索
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
// 重置
const resetQuery = () => {
queryParams.value = {
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
gender: '',
schoolId: null,
schoolGradeId: null,
schoolClassId: null
}
treeRef.value?.setCurrentKey(null)
getList()
}
// 新增
const handleAdd = () => {
studentDialogRef.value?.open()
}
// 编辑
const handleEdit = (row) => {
studentDialogRef.value?.open(row)
}
// 导入
const handleImport = () => {
importDialogRef.value?.open()
}
// 格式化日期
const formatDate = (date) => {
if (!date) return ''
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 删除
const handleDelete = (row) => {
ElMessageBox.confirm(`确定要删除学生"${row.studentName}"吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await request.delete(`/business/student/${row.studentId}`)
if (res.code === 200) {
ElMessage.success('删除成功')
getList()
}
}).catch(() => {})
}
onMounted(() => {
getSchoolTree()
getList()
})
</script>
<style scoped>
.app-container {
padding: 16px;
}
.search-wrapper {
margin-bottom: 0;
}
</style>