feat: 会员管理-添加学生绑定功能
1. 后端新增接口:
- GET /business/student/available - 查询可绑定的学生列表
- GET /business/student/byMember/{memberId} - 查询会员已绑定的学生
- GET /business/member/{memberId}/students - 获取会员已绑定的学生列表
- POST /business/member/{memberId}/bindStudents - 批量绑定学生到会员
- POST /business/member/unbindStudent/{studentId} - 解绑学生
- GET /business/school/grade/{schoolGradeId}/classes - 获取年级下的班级列表
2. 业务规则实现:
- 教师身份只能绑定同校学生(schoolId相同)
- 家长身份可绑定任意学生
- 一个学生只能归属一个会员
3. 前端功能:
- 新增StudentSelectDialog学生选择器组件
- 支持按姓名、学号搜索
- 支持多选绑定
- MemberDialog集成学生选择器
4. 修复问题:
- 修复会员编辑时学校信息(区域/年级/班级)回显问题
- 修复班级列表接口404问题
This commit is contained in:
parent
d28b68ef46
commit
2baf792159
|
|
@ -10,10 +10,13 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.pangu.member.domain.PgMember;
|
||||
import org.dromara.pangu.member.service.IPgMemberService;
|
||||
import org.dromara.pangu.student.domain.PgStudent;
|
||||
import org.dromara.pangu.student.service.IPgStudentService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
|
@ -28,6 +31,7 @@ import java.util.Map;
|
|||
public class PgMemberController extends BaseController {
|
||||
|
||||
private final IPgMemberService memberService;
|
||||
private final IPgStudentService studentService;
|
||||
|
||||
/**
|
||||
* 查询会员列表
|
||||
|
|
@ -107,4 +111,32 @@ public class PgMemberController extends BaseController {
|
|||
public R<Boolean> checkPhoneUnique(@RequestParam String phone, @RequestParam(required = false) Long memberId) {
|
||||
return R.ok(memberService.checkPhoneUnique(phone, memberId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员已绑定的学生列表
|
||||
*/
|
||||
@GetMapping("/{memberId}/students")
|
||||
public R<List<PgStudent>> getMemberStudents(@PathVariable Long memberId) {
|
||||
return R.ok(studentService.selectByMemberId(memberId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量绑定学生到会员
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-绑定学生", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/{memberId}/bindStudents")
|
||||
public R<Void> bindStudents(@PathVariable Long memberId, @RequestBody List<Long> studentIds) {
|
||||
return toAjax(studentService.bindStudentsToMember(memberId, studentIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑学生
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-解绑学生", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/unbindStudent/{studentId}")
|
||||
public R<Void> unbindStudent(@PathVariable Long studentId) {
|
||||
return toAjax(studentService.unbindStudent(studentId));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.dromara.pangu.member.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
|
@ -9,6 +10,7 @@ import lombok.EqualsAndHashCode;
|
|||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会员表
|
||||
|
|
@ -51,6 +53,12 @@ public class PgMember extends BaseEntity {
|
|||
|
||||
private Long regionId;
|
||||
|
||||
/**
|
||||
* 区域ID路径(非数据库字段,用于级联选择器回显)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> regionIds;
|
||||
|
||||
private Long schoolId;
|
||||
|
||||
private Long schoolGradeId;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import lombok.RequiredArgsConstructor;
|
|||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.pangu.base.domain.PgRegion;
|
||||
import org.dromara.pangu.base.mapper.PgRegionMapper;
|
||||
import org.dromara.pangu.member.domain.PgMember;
|
||||
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
||||
import org.dromara.pangu.member.service.IPgMemberService;
|
||||
|
|
@ -16,7 +18,9 @@ import cn.hutool.crypto.digest.BCrypt;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -31,6 +35,7 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
|
||||
private final PgMemberMapper baseMapper;
|
||||
private final PgStudentMapper studentMapper;
|
||||
private final PgRegionMapper regionMapper;
|
||||
|
||||
private static final String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
|
|
@ -48,7 +53,29 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
|
||||
@Override
|
||||
public PgMember selectById(Long memberId) {
|
||||
return baseMapper.selectById(memberId);
|
||||
PgMember member = baseMapper.selectById(memberId);
|
||||
if (member != null && member.getRegionId() != null) {
|
||||
// 查询区域的完整路径用于级联选择器回显
|
||||
member.setRegionIds(getRegionPath(member.getRegionId()));
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域的完整路径(从根到当前节点的ID列表)
|
||||
*/
|
||||
private List<Long> getRegionPath(Long regionId) {
|
||||
List<Long> path = new ArrayList<>();
|
||||
Long currentId = regionId;
|
||||
while (currentId != null && currentId > 0) {
|
||||
path.add(0, currentId);
|
||||
PgRegion region = regionMapper.selectById(currentId);
|
||||
if (region == null) {
|
||||
break;
|
||||
}
|
||||
currentId = region.getParentId();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -87,6 +87,14 @@ public class PgSchoolController extends BaseController {
|
|||
return R.ok(schoolService.selectGradesBySchoolId(schoolId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年级下的班级列表
|
||||
*/
|
||||
@GetMapping("/grade/{schoolGradeId}/classes")
|
||||
public R<List<PgSchoolClass>> getGradeClasses(@PathVariable Long schoolGradeId) {
|
||||
return R.ok(schoolService.selectClassesBySchoolGradeId(schoolGradeId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 为学校添加年级(批量挂载)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.dromara.pangu.school.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
|
@ -33,4 +34,10 @@ public class PgSchoolClass implements Serializable {
|
|||
private Long createBy;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 班级名称(非数据库字段,用于前端显示)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String className;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,9 @@ public interface IPgSchoolService {
|
|||
* 删除年级下的班级
|
||||
*/
|
||||
int removeGradeClass(Long schoolClassId);
|
||||
|
||||
/**
|
||||
* 获取年级下的班级列表
|
||||
*/
|
||||
List<org.dromara.pangu.school.domain.PgSchoolClass> selectClassesBySchoolGradeId(Long schoolGradeId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,22 @@ public class PgSchoolServiceImpl implements IPgSchoolService {
|
|||
return schoolClassMapper.deleteById(schoolClassId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PgSchoolClass> selectClassesBySchoolGradeId(Long schoolGradeId) {
|
||||
List<PgSchoolClass> schoolClasses = schoolClassMapper.selectList(
|
||||
new LambdaQueryWrapper<PgSchoolClass>()
|
||||
.eq(PgSchoolClass::getSchoolGradeId, schoolGradeId)
|
||||
);
|
||||
// 关联查询班级名称
|
||||
for (PgSchoolClass sc : schoolClasses) {
|
||||
PgClass cls = classMapper.selectById(sc.getClassId());
|
||||
if (cls != null) {
|
||||
sc.setClassName(cls.getClassName());
|
||||
}
|
||||
}
|
||||
return schoolClasses;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<PgSchool> buildQueryWrapper(PgSchool school) {
|
||||
LambdaQueryWrapper<PgSchool> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.like(StrUtil.isNotBlank(school.getSchoolName()), PgSchool::getSchoolName, school.getSchoolName());
|
||||
|
|
|
|||
|
|
@ -58,4 +58,29 @@ public class PgStudentController extends BaseController {
|
|||
public R<Void> remove(@PathVariable Long[] studentIds) {
|
||||
return toAjax(studentService.deleteByIds(studentIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询可绑定的学生列表(用于会员绑定学生)
|
||||
* @param studentName 学生姓名(模糊查询)
|
||||
* @param studentNo 学号(模糊查询)
|
||||
* @param memberId 当前会员ID
|
||||
* @param schoolId 学校ID(教师身份时传入,限制只能选本校学生)
|
||||
*/
|
||||
@GetMapping("/available")
|
||||
public TableDataInfo<PgStudent> availableStudents(
|
||||
@RequestParam(required = false) String studentName,
|
||||
@RequestParam(required = false) String studentNo,
|
||||
@RequestParam(required = false) Long memberId,
|
||||
@RequestParam(required = false) Long schoolId,
|
||||
PageQuery pageQuery) {
|
||||
return studentService.selectAvailableStudents(studentName, studentNo, memberId, schoolId, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询会员已绑定的学生列表
|
||||
*/
|
||||
@GetMapping("/byMember/{memberId}")
|
||||
public R<java.util.List<PgStudent>> listByMemberId(@PathVariable Long memberId) {
|
||||
return R.ok(studentService.selectByMemberId(memberId));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,29 @@ public interface IPgStudentService {
|
|||
int insert(PgStudent student);
|
||||
int update(PgStudent student);
|
||||
int deleteByIds(Long[] studentIds);
|
||||
|
||||
/**
|
||||
* 查询可绑定的学生列表(分页)
|
||||
* @param studentName 学生姓名(模糊查询)
|
||||
* @param studentNo 学号(模糊查询)
|
||||
* @param memberId 当前会员ID(排除已绑定其他会员的学生)
|
||||
* @param schoolId 学校ID(教师身份时必传,限制只能选本校学生)
|
||||
* @param pageQuery 分页参数
|
||||
*/
|
||||
TableDataInfo<PgStudent> selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询会员已绑定的学生列表
|
||||
*/
|
||||
List<PgStudent> selectByMemberId(Long memberId);
|
||||
|
||||
/**
|
||||
* 批量绑定学生到会员
|
||||
*/
|
||||
int bindStudentsToMember(Long memberId, List<Long> studentIds);
|
||||
|
||||
/**
|
||||
* 解绑学生
|
||||
*/
|
||||
int unbindStudent(Long studentId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,4 +68,65 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
lqw.orderByDesc(PgStudent::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<PgStudent> selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<PgStudent> lqw = new LambdaQueryWrapper<>();
|
||||
// 姓名模糊查询
|
||||
lqw.like(StrUtil.isNotBlank(studentName), PgStudent::getStudentName, studentName);
|
||||
// 学号模糊查询
|
||||
lqw.like(StrUtil.isNotBlank(studentNo), PgStudent::getStudentNo, studentNo);
|
||||
// 可绑定条件:未被绑定 或 已绑定当前会员
|
||||
lqw.and(wrapper -> wrapper
|
||||
.isNull(PgStudent::getMemberId)
|
||||
.or()
|
||||
.eq(memberId != null, PgStudent::getMemberId, memberId)
|
||||
);
|
||||
// 教师身份限制:只能选本校学生
|
||||
lqw.eq(schoolId != null, PgStudent::getSchoolId, schoolId);
|
||||
lqw.orderByDesc(PgStudent::getCreateTime);
|
||||
|
||||
Page<PgStudent> page = baseMapper.selectPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PgStudent> selectByMemberId(Long memberId) {
|
||||
if (memberId == null) {
|
||||
return List.of();
|
||||
}
|
||||
return baseMapper.selectList(
|
||||
new LambdaQueryWrapper<PgStudent>()
|
||||
.eq(PgStudent::getMemberId, memberId)
|
||||
.orderByDesc(PgStudent::getCreateTime)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bindStudentsToMember(Long memberId, List<Long> studentIds) {
|
||||
if (memberId == null || studentIds == null || studentIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Long studentId : studentIds) {
|
||||
PgStudent student = new PgStudent();
|
||||
student.setStudentId(studentId);
|
||||
student.setMemberId(memberId);
|
||||
count += baseMapper.updateById(student);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int unbindStudent(Long studentId) {
|
||||
if (studentId == null) {
|
||||
return 0;
|
||||
}
|
||||
// 使用原生SQL更新memberId为null
|
||||
return baseMapper.update(null,
|
||||
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<PgStudent>()
|
||||
.eq(PgStudent::getStudentId, studentId)
|
||||
.set(PgStudent::getMemberId, null)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@
|
|||
<el-table-column prop="gradeName" label="年级" width="80" />
|
||||
<el-table-column prop="className" label="班级" width="60" />
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-popconfirm title="确定解绑该学生?" @confirm="handleRemoveStudent($index)">
|
||||
<template #default="{ row }">
|
||||
<el-popconfirm title="确定解绑该学生?" @confirm="handleRemoveStudent(row)">
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="small">解绑</el-button>
|
||||
</template>
|
||||
|
|
@ -154,6 +154,9 @@
|
|||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 学生选择器 -->
|
||||
<StudentSelectDialog ref="studentSelectRef" @success="loadBoundStudents" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
|
|
@ -162,6 +165,7 @@ import { Plus } from '@element-plus/icons-vue'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import request from '@/utils/request'
|
||||
import StudentSelectDialog from './StudentSelectDialog.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
|
|
@ -169,6 +173,7 @@ const visible = ref(false)
|
|||
const isEdit = ref(false)
|
||||
const formRef = ref(null)
|
||||
const submitLoading = ref(false)
|
||||
const studentSelectRef = ref(null)
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
|
|
@ -229,10 +234,9 @@ const open = async (row) => {
|
|||
const res = await request.get(`/business/member/${row.memberId}`)
|
||||
if (res.code === 200 && res.data) {
|
||||
Object.assign(form, res.data)
|
||||
// 处理区域级联
|
||||
// 后端已返回 regionIds 数组用于级联选择器回显
|
||||
// 加载学校信息的下拉选项
|
||||
if (form.regionId) {
|
||||
// 构建regionIds数组用于级联选择器回显
|
||||
form.regionIds = [form.regionId]
|
||||
await loadSchoolList(form.regionId)
|
||||
}
|
||||
if (form.schoolId) {
|
||||
|
|
@ -241,6 +245,8 @@ const open = async (row) => {
|
|||
if (form.schoolGradeId) {
|
||||
await loadClassList(form.schoolGradeId)
|
||||
}
|
||||
// 加载已绑定的学生列表
|
||||
await loadBoundStudents()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载会员数据失败', e)
|
||||
|
|
@ -248,6 +254,22 @@ const open = async (row) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载已绑定的学生列表
|
||||
*/
|
||||
const loadBoundStudents = async () => {
|
||||
if (!form.memberId) {
|
||||
form.students = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await request.get(`/business/member/${form.memberId}/students`)
|
||||
form.students = res.data || []
|
||||
} catch (e) {
|
||||
form.students = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
|
|
@ -287,8 +309,8 @@ const loadRegionTree = async () => {
|
|||
*/
|
||||
const loadSchoolList = async (regionId) => {
|
||||
try {
|
||||
const res = await request.get('/business/school/list', { params: { regionId } })
|
||||
schoolList.value = res.rows || []
|
||||
const res = await request.get('/business/school/listAll', { params: { regionId } })
|
||||
schoolList.value = res.data || []
|
||||
} catch (e) {
|
||||
schoolList.value = []
|
||||
}
|
||||
|
|
@ -299,7 +321,7 @@ const loadSchoolList = async (regionId) => {
|
|||
*/
|
||||
const loadGradeList = async (schoolId) => {
|
||||
try {
|
||||
const res = await request.get('/business/school/grades', { params: { schoolId } })
|
||||
const res = await request.get(`/business/school/${schoolId}/grades`)
|
||||
gradeList.value = res.data || []
|
||||
} catch (e) {
|
||||
gradeList.value = []
|
||||
|
|
@ -311,7 +333,7 @@ const loadGradeList = async (schoolId) => {
|
|||
*/
|
||||
const loadClassList = async (schoolGradeId) => {
|
||||
try {
|
||||
const res = await request.get('/business/school/classes', { params: { schoolGradeId } })
|
||||
const res = await request.get(`/business/school/grade/${schoolGradeId}/classes`)
|
||||
classList.value = res.data || []
|
||||
} catch (e) {
|
||||
classList.value = []
|
||||
|
|
@ -379,14 +401,30 @@ const handleGradeChange = () => {
|
|||
* 添加学生
|
||||
*/
|
||||
const handleAddStudent = () => {
|
||||
ElMessage.info('学生选择功能待开发')
|
||||
if (!isEdit.value || !form.memberId) {
|
||||
ElMessage.warning('请先保存会员信息后再绑定学生')
|
||||
return
|
||||
}
|
||||
studentSelectRef.value?.open({
|
||||
memberId: form.memberId,
|
||||
identityType: form.identityType,
|
||||
schoolId: form.schoolId
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除学生
|
||||
* 解绑学生
|
||||
*/
|
||||
const handleRemoveStudent = (index) => {
|
||||
form.students.splice(index, 1)
|
||||
const handleRemoveStudent = async (row) => {
|
||||
try {
|
||||
const res = await request.post(`/business/member/unbindStudent/${row.studentId}`)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('解绑成功')
|
||||
await loadBoundStudents()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解绑失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
<!--
|
||||
学生选择器弹窗
|
||||
@author 湖北新华业务中台研发团队
|
||||
-->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="选择学生"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<!-- 搜索条件 -->
|
||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||
<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>
|
||||
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 教师身份提示 -->
|
||||
<el-alert
|
||||
v-if="isTeacher"
|
||||
title="教师只能绑定本校学生"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="studentList"
|
||||
border
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="studentName" label="姓名" min-width="80" />
|
||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||
<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="60" />
|
||||
<el-table-column label="绑定状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.memberId === memberId" type="success" size="small">已绑定</el-tag>
|
||||
<el-tag v-else-if="row.memberId" type="warning" size="small">已被绑定</el-tag>
|
||||
<el-tag v-else type="info" size="small">未绑定</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
:current-page="queryParams.pageNum"
|
||||
:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
style="margin-top: 12px; justify-content: flex-end"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<span style="margin-right: 16px; color: #909399">已选择 {{ selectedStudents.length }} 名学生</span>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleConfirm">确定绑定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Search, Refresh } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const studentList = ref([])
|
||||
const total = ref(0)
|
||||
const selectedStudents = ref([])
|
||||
const tableRef = ref(null)
|
||||
|
||||
// 会员信息
|
||||
const memberId = ref(null)
|
||||
const isTeacher = ref(false)
|
||||
const schoolId = ref(null)
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNo: ''
|
||||
})
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
* @param options { memberId: Long, identityType: String, schoolId: Long }
|
||||
*/
|
||||
const open = (options = {}) => {
|
||||
memberId.value = options.memberId
|
||||
isTeacher.value = options.identityType === '2'
|
||||
schoolId.value = options.schoolId
|
||||
|
||||
resetQuery()
|
||||
visible.value = true
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生列表
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
pageNum: queryParams.pageNum,
|
||||
pageSize: queryParams.pageSize,
|
||||
studentName: queryParams.studentName || undefined,
|
||||
studentNo: queryParams.studentNo || undefined,
|
||||
memberId: memberId.value,
|
||||
schoolId: isTeacher.value ? schoolId.value : undefined
|
||||
}
|
||||
const res = await request.get('/business/student/available', { params })
|
||||
studentList.value = res.rows || []
|
||||
total.value = res.total || 0
|
||||
} catch (e) {
|
||||
console.error('获取学生列表失败', e)
|
||||
studentList.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置搜索
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryParams.pageNum = 1
|
||||
queryParams.pageSize = 10
|
||||
queryParams.studentName = ''
|
||||
queryParams.studentNo = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页大小变化
|
||||
*/
|
||||
const handleSizeChange = (val) => {
|
||||
queryParams.pageSize = val
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 页码变化
|
||||
*/
|
||||
const handleCurrentChange = (val) => {
|
||||
queryParams.pageNum = val
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择变化
|
||||
*/
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectedStudents.value = selection
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定绑定
|
||||
*/
|
||||
const handleConfirm = async () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择要绑定的学生')
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤出需要绑定的学生(排除已绑定当前会员的)
|
||||
const studentIds = selectedStudents.value
|
||||
.filter(s => s.memberId !== memberId.value)
|
||||
.map(s => s.studentId)
|
||||
|
||||
if (studentIds.length === 0) {
|
||||
ElMessage.info('所选学生已全部绑定')
|
||||
visible.value = false
|
||||
return
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const res = await request.post(`/business/member/${memberId.value}/bindStudents`, studentIds)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`成功绑定 ${studentIds.length} 名学生`)
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-form {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue