feat: 学生多会员绑定重构 - 支持一个学生被多个会员绑定
主要变更: 1. 新建 pg_member_student 关联表,实现学生与会员的多对多关系 2. 移除 pg_student.member_id 字段 3. 后端服务层全部改用关联表查询和操作 4. H5 接口支持 relation 字段(父亲/母亲/其他) 5. 前端学生选择弹窗显示已绑定会员数量 6. 更新需求文档
This commit is contained in:
parent
729f2c71f1
commit
df1f2932c4
|
|
@ -47,4 +47,7 @@ public class H5StudentBindDto {
|
|||
@Schema(description = "学校班级关联ID(从/h5/base/classes获取的schoolClassId)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "请选择班级")
|
||||
private Long schoolClassId;
|
||||
|
||||
@Schema(description = "与学生的关系(父亲/母亲/其他)", example = "父亲")
|
||||
private String relation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,4 +52,7 @@ public class H5StudentVo {
|
|||
|
||||
@Schema(description = "班级名称", example = "1班")
|
||||
private String className;
|
||||
|
||||
@Schema(description = "与学生的关系(父亲/母亲/其他)", example = "父亲")
|
||||
private String relation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ import org.dromara.pangu.h5.domain.vo.H5MemberInfoVo;
|
|||
import org.dromara.pangu.h5.domain.vo.H5StudentVo;
|
||||
import org.dromara.pangu.h5.service.H5MemberService;
|
||||
import org.dromara.pangu.member.domain.PgMember;
|
||||
import org.dromara.pangu.member.domain.PgMemberStudent;
|
||||
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
||||
import org.dromara.pangu.member.mapper.PgMemberStudentMapper;
|
||||
import org.dromara.pangu.school.domain.PgSchool;
|
||||
import org.dromara.pangu.school.domain.PgSchoolClass;
|
||||
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
||||
|
|
@ -62,6 +64,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
private final PgSubjectMapper subjectMapper;
|
||||
private final PgRegionMapper regionMapper;
|
||||
private final PgEducationMapper educationMapper;
|
||||
private final PgMemberStudentMapper memberStudentMapper;
|
||||
|
||||
@Override
|
||||
public H5MemberInfoVo getMemberInfo() {
|
||||
|
|
@ -299,10 +302,16 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
student.setSchoolId(dto.getSchoolId());
|
||||
student.setSchoolGradeId(dto.getSchoolGradeId());
|
||||
student.setSchoolClassId(dto.getSchoolClassId());
|
||||
student.setMemberId(memberId);
|
||||
student.setStatus("0");
|
||||
studentMapper.insert(student);
|
||||
|
||||
// 创建会员学生关联
|
||||
PgMemberStudent ms = new PgMemberStudent();
|
||||
ms.setMemberId(memberId);
|
||||
ms.setStudentId(student.getStudentId());
|
||||
ms.setRelation(dto.getRelation());
|
||||
memberStudentMapper.insert(ms);
|
||||
|
||||
log.info("H5绑定学生: memberId={}, studentId={}", memberId, student.getStudentId());
|
||||
}
|
||||
|
||||
|
|
@ -310,9 +319,31 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
public List<H5StudentVo> getStudents() {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
// 通过关联表查询学生ID
|
||||
List<PgMemberStudent> relations = memberStudentMapper.selectList(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
);
|
||||
|
||||
if (relations.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<Long> studentIds = relations.stream()
|
||||
.map(PgMemberStudent::getStudentId)
|
||||
.toList();
|
||||
|
||||
// 构建关系映射
|
||||
java.util.Map<Long, String> relationMap = relations.stream()
|
||||
.collect(java.util.stream.Collectors.toMap(
|
||||
PgMemberStudent::getStudentId,
|
||||
ms -> ms.getRelation() != null ? ms.getRelation() : "",
|
||||
(a, b) -> a
|
||||
));
|
||||
|
||||
List<PgStudent> students = studentMapper.selectList(
|
||||
new LambdaQueryWrapper<PgStudent>()
|
||||
.eq(PgStudent::getMemberId, memberId)
|
||||
.in(PgStudent::getStudentId, studentIds)
|
||||
.eq(PgStudent::getDelFlag, "0")
|
||||
);
|
||||
|
||||
|
|
@ -320,6 +351,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
for (PgStudent student : students) {
|
||||
H5StudentVo vo = new H5StudentVo();
|
||||
BeanUtil.copyProperties(student, vo);
|
||||
vo.setRelation(relationMap.get(student.getStudentId()));
|
||||
|
||||
// 填充学校、年级、班级名称
|
||||
fillStudentNames(vo, student);
|
||||
|
|
@ -334,12 +366,22 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
public void updateStudent(Long studentId, H5StudentBindDto dto) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
// 查询学生并校验归属
|
||||
PgStudent student = studentMapper.selectById(studentId);
|
||||
if (student == null || !memberId.equals(student.getMemberId())) {
|
||||
// 校验会员是否绑定了该学生
|
||||
PgMemberStudent relation = memberStudentMapper.selectOne(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
.eq(PgMemberStudent::getStudentId, studentId)
|
||||
);
|
||||
if (relation == null) {
|
||||
throw new ServiceException("学生不存在或无权限修改");
|
||||
}
|
||||
|
||||
// 查询学生
|
||||
PgStudent student = studentMapper.selectById(studentId);
|
||||
if (student == null) {
|
||||
throw new ServiceException("学生不存在");
|
||||
}
|
||||
|
||||
// 校验学校、年级、班级是否存在
|
||||
validateSchoolInfo(dto.getSchoolId(), dto.getSchoolGradeId(), dto.getSchoolClassId());
|
||||
|
||||
|
|
@ -354,6 +396,12 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
student.setSchoolClassId(dto.getSchoolClassId());
|
||||
studentMapper.updateById(student);
|
||||
|
||||
// 更新关系
|
||||
if (dto.getRelation() != null) {
|
||||
relation.setRelation(dto.getRelation());
|
||||
memberStudentMapper.updateById(relation);
|
||||
}
|
||||
|
||||
log.info("H5修改学生: memberId={}, studentId={}", memberId, studentId);
|
||||
}
|
||||
|
||||
|
|
@ -362,15 +410,18 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
public void unbindStudent(Long studentId) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
// 查询学生并校验归属
|
||||
PgStudent student = studentMapper.selectById(studentId);
|
||||
if (student == null || !memberId.equals(student.getMemberId())) {
|
||||
// 校验会员是否绑定了该学生
|
||||
PgMemberStudent relation = memberStudentMapper.selectOne(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
.eq(PgMemberStudent::getStudentId, studentId)
|
||||
);
|
||||
if (relation == null) {
|
||||
throw new ServiceException("学生不存在或无权限操作");
|
||||
}
|
||||
|
||||
// 解绑(清除会员关联)
|
||||
student.setMemberId(null);
|
||||
studentMapper.updateById(student);
|
||||
// 删除关联关系
|
||||
memberStudentMapper.deleteById(relation.getId());
|
||||
|
||||
log.info("H5解绑学生: memberId={}, studentId={}", memberId, studentId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,13 +138,13 @@ public class PgMemberController extends BaseController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 解绑学生
|
||||
* 解绑学生(解绑指定会员与学生的关系)
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-解绑学生", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/unbindStudent/{studentId}")
|
||||
public R<Void> unbindStudent(@PathVariable Long studentId) {
|
||||
return toAjax(studentService.unbindStudent(studentId));
|
||||
@PostMapping("/{memberId}/unbindStudent/{studentId}")
|
||||
public R<Void> unbindStudent(@PathVariable Long memberId, @PathVariable Long studentId) {
|
||||
return toAjax(studentService.unbindStudentFromMember(memberId, studentId));
|
||||
}
|
||||
|
||||
// ==================== 教育身份管理 ====================
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package org.dromara.pangu.member.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 会员学生关联表
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Data
|
||||
@TableName("pg_member_student")
|
||||
public class PgMemberStudent {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 关系(父亲/母亲/其他)
|
||||
*/
|
||||
private String relation;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package org.dromara.pangu.member.mapper;
|
||||
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.pangu.member.domain.PgMemberStudent;
|
||||
|
||||
/**
|
||||
* 会员学生关联 Mapper
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
public interface PgMemberStudentMapper extends BaseMapperPlus<PgMemberStudent, PgMemberStudent> {
|
||||
}
|
||||
|
|
@ -10,9 +10,11 @@ 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.member.domain.PgMember;
|
||||
import org.dromara.pangu.member.domain.PgMemberStudent;
|
||||
import org.dromara.pangu.member.domain.dto.EducationDto;
|
||||
import org.dromara.pangu.member.domain.dto.MemberSaveDto;
|
||||
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
||||
import org.dromara.pangu.member.mapper.PgMemberStudentMapper;
|
||||
import org.dromara.pangu.member.service.IPgEducationService;
|
||||
import org.dromara.pangu.member.service.IPgMemberService;
|
||||
import org.dromara.pangu.student.mapper.PgStudentMapper;
|
||||
|
|
@ -37,6 +39,7 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
|
||||
private final PgMemberMapper baseMapper;
|
||||
private final PgStudentMapper studentMapper;
|
||||
private final PgMemberStudentMapper memberStudentMapper;
|
||||
private final IPgEducationService educationService;
|
||||
private final IPgStudentService studentService;
|
||||
|
||||
|
|
@ -230,9 +233,9 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
@Override
|
||||
public boolean checkCanDelete(Long memberId) {
|
||||
// 检查是否有绑定的学生
|
||||
Long count = studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<org.dromara.pangu.student.domain.PgStudent>()
|
||||
.eq(org.dromara.pangu.student.domain.PgStudent::getMemberId, memberId)
|
||||
Long count = memberStudentMapper.selectCount(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
);
|
||||
return count == 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ public class PgStudent extends BaseEntity {
|
|||
|
||||
private Long schoolClassId;
|
||||
|
||||
private Long memberId;
|
||||
|
||||
private String status;
|
||||
|
||||
private String tenantId;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package org.dromara.pangu.student.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 会员简要信息(用于学生关联展示)
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Data
|
||||
public class MemberSimpleVo {
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 会员昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 会员手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 关系(父亲/母亲/其他)
|
||||
*/
|
||||
private String relation;
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package org.dromara.pangu.student.domain.vo;
|
|||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生视图对象(包含关联数据)
|
||||
|
|
@ -50,17 +51,15 @@ public class StudentVo {
|
|||
*/
|
||||
private String className;
|
||||
|
||||
private Long memberId;
|
||||
/**
|
||||
* 绑定的会员数量
|
||||
*/
|
||||
private Integer memberCount;
|
||||
|
||||
/**
|
||||
* 会员昵称
|
||||
* 绑定的会员列表
|
||||
*/
|
||||
private String memberNickname;
|
||||
|
||||
/**
|
||||
* 会员手机号
|
||||
*/
|
||||
private String memberPhone;
|
||||
private List<MemberSimpleVo> members;
|
||||
|
||||
private String status;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,10 +43,15 @@ public interface IPgStudentService {
|
|||
int bindStudentsToMember(Long memberId, List<Long> studentIds);
|
||||
|
||||
/**
|
||||
* 解绑学生
|
||||
* 解绑学生(解绑所有会员与该学生的关系)
|
||||
*/
|
||||
int unbindStudent(Long studentId);
|
||||
|
||||
/**
|
||||
* 解绑指定会员与学生的关系
|
||||
*/
|
||||
int unbindStudentFromMember(Long memberId, Long studentId);
|
||||
|
||||
/**
|
||||
* 批量导入学生
|
||||
* @param dataList Excel 导入的数据列表
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import org.dromara.pangu.base.domain.PgClass;
|
|||
import org.dromara.pangu.base.domain.PgGrade;
|
||||
import org.dromara.pangu.base.mapper.PgClassMapper;
|
||||
import org.dromara.pangu.base.mapper.PgGradeMapper;
|
||||
import org.dromara.pangu.base.domain.PgEducation;
|
||||
import org.dromara.pangu.base.mapper.PgEducationMapper;
|
||||
import org.dromara.pangu.member.domain.PgMember;
|
||||
import org.dromara.pangu.member.domain.PgMemberStudent;
|
||||
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
||||
import org.dromara.pangu.member.mapper.PgMemberStudentMapper;
|
||||
import org.dromara.pangu.school.domain.PgSchool;
|
||||
import org.dromara.pangu.school.domain.PgSchoolClass;
|
||||
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
||||
|
|
@ -26,6 +26,7 @@ import org.dromara.pangu.school.mapper.PgSchoolGradeMapper;
|
|||
import org.dromara.pangu.school.mapper.PgSchoolMapper;
|
||||
import org.dromara.pangu.student.domain.PgStudent;
|
||||
import org.dromara.pangu.student.domain.dto.StudentImportDto;
|
||||
import org.dromara.pangu.student.domain.vo.MemberSimpleVo;
|
||||
import org.dromara.pangu.student.domain.vo.StudentVo;
|
||||
import org.dromara.pangu.student.mapper.PgStudentMapper;
|
||||
import org.dromara.pangu.student.service.IPgStudentService;
|
||||
|
|
@ -54,7 +55,7 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
private final PgGradeMapper gradeMapper;
|
||||
private final PgClassMapper classMapper;
|
||||
private final PgMemberMapper memberMapper;
|
||||
private final PgEducationMapper educationMapper;
|
||||
private final PgMemberStudentMapper memberStudentMapper;
|
||||
|
||||
@Override
|
||||
public TableDataInfo<StudentVo> selectPageList(PgStudent student, PageQuery pageQuery) {
|
||||
|
|
@ -123,13 +124,13 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
Set<Long> schoolIds = new HashSet<>();
|
||||
Set<Long> schoolGradeIds = new HashSet<>();
|
||||
Set<Long> schoolClassIds = new HashSet<>();
|
||||
Set<Long> memberIds = new HashSet<>();
|
||||
Set<Long> studentIds = new HashSet<>();
|
||||
|
||||
for (PgStudent s : students) {
|
||||
if (s.getSchoolId() != null) schoolIds.add(s.getSchoolId());
|
||||
if (s.getSchoolGradeId() != null) schoolGradeIds.add(s.getSchoolGradeId());
|
||||
if (s.getSchoolClassId() != null) schoolClassIds.add(s.getSchoolClassId());
|
||||
if (s.getMemberId() != null) memberIds.add(s.getMemberId());
|
||||
studentIds.add(s.getStudentId());
|
||||
}
|
||||
|
||||
// 批量查询关联数据
|
||||
|
|
@ -145,6 +146,19 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
schoolClassMapper.selectByIds(schoolClassIds).stream()
|
||||
.collect(Collectors.toMap(PgSchoolClass::getId, Function.identity()));
|
||||
|
||||
// 查询会员学生关联关系
|
||||
List<PgMemberStudent> memberStudents = memberStudentMapper.selectList(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.in(PgMemberStudent::getStudentId, studentIds)
|
||||
);
|
||||
// 按学生ID分组
|
||||
Map<Long, List<PgMemberStudent>> studentMemberMap = memberStudents.stream()
|
||||
.collect(Collectors.groupingBy(PgMemberStudent::getStudentId));
|
||||
|
||||
// 收集所有会员ID并批量查询
|
||||
Set<Long> memberIds = memberStudents.stream()
|
||||
.map(PgMemberStudent::getMemberId)
|
||||
.collect(Collectors.toSet());
|
||||
Map<Long, PgMember> memberMap = memberIds.isEmpty() ? Collections.emptyMap() :
|
||||
memberMapper.selectByIds(memberIds).stream()
|
||||
.collect(Collectors.toMap(PgMember::getMemberId, Function.identity()));
|
||||
|
|
@ -192,11 +206,26 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
vo.setClassName(classNameMap.get(schoolClass.getClassId()));
|
||||
}
|
||||
|
||||
// 填充会员信息
|
||||
PgMember member = memberMap.get(s.getMemberId());
|
||||
if (member != null) {
|
||||
vo.setMemberNickname(member.getNickname());
|
||||
vo.setMemberPhone(member.getPhone());
|
||||
// 填充会员信息(多对多)
|
||||
List<PgMemberStudent> relations = studentMemberMap.get(s.getStudentId());
|
||||
if (relations != null && !relations.isEmpty()) {
|
||||
List<MemberSimpleVo> members = new ArrayList<>();
|
||||
for (PgMemberStudent rel : relations) {
|
||||
PgMember member = memberMap.get(rel.getMemberId());
|
||||
if (member != null) {
|
||||
MemberSimpleVo msv = new MemberSimpleVo();
|
||||
msv.setMemberId(member.getMemberId());
|
||||
msv.setNickname(member.getNickname());
|
||||
msv.setPhone(member.getPhone());
|
||||
msv.setRelation(rel.getRelation());
|
||||
members.add(msv);
|
||||
}
|
||||
}
|
||||
vo.setMembers(members);
|
||||
vo.setMemberCount(members.size());
|
||||
} else {
|
||||
vo.setMembers(Collections.emptyList());
|
||||
vo.setMemberCount(0);
|
||||
}
|
||||
|
||||
voList.add(vo);
|
||||
|
|
@ -218,17 +247,34 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
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);
|
||||
// 转换为 VO,填充学校、年级、班级名称
|
||||
// 转换为 VO,填充学校、年级、班级、会员信息
|
||||
List<StudentVo> voList = convertToVoList(page.getRecords());
|
||||
|
||||
// 标记当前会员是否已绑定该学生
|
||||
if (memberId != null) {
|
||||
Set<Long> boundStudentIds = memberStudentMapper.selectList(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
).stream().map(PgMemberStudent::getStudentId).collect(Collectors.toSet());
|
||||
|
||||
for (StudentVo vo : voList) {
|
||||
// 判断当前会员是否已绑定该学生
|
||||
boolean isBound = boundStudentIds.contains(vo.getStudentId());
|
||||
// 在 members 列表中找到当前会员并标记
|
||||
if (vo.getMembers() != null) {
|
||||
for (MemberSimpleVo m : vo.getMembers()) {
|
||||
if (memberId.equals(m.getMemberId())) {
|
||||
m.setRelation("已绑定");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TableDataInfo<>(voList, page.getTotal());
|
||||
}
|
||||
|
||||
|
|
@ -237,39 +283,71 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
if (memberId == null) {
|
||||
return List.of();
|
||||
}
|
||||
// 通过关联表查询学生ID
|
||||
List<Long> studentIds = memberStudentMapper.selectList(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
).stream().map(PgMemberStudent::getStudentId).collect(Collectors.toList());
|
||||
|
||||
if (studentIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<PgStudent> students = baseMapper.selectList(
|
||||
new LambdaQueryWrapper<PgStudent>()
|
||||
.eq(PgStudent::getMemberId, memberId)
|
||||
.in(PgStudent::getStudentId, studentIds)
|
||||
.orderByDesc(PgStudent::getCreateTime)
|
||||
);
|
||||
// 转换为 VO,包含学校、年级、班级名称
|
||||
// 转换为 VO,包含学校、年级、班级、会员名称
|
||||
return convertToVoList(students);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
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);
|
||||
// 检查是否已绑定
|
||||
Long existCount = memberStudentMapper.selectCount(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
.eq(PgMemberStudent::getStudentId, studentId)
|
||||
);
|
||||
if (existCount == 0) {
|
||||
PgMemberStudent ms = new PgMemberStudent();
|
||||
ms.setMemberId(memberId);
|
||||
ms.setStudentId(studentId);
|
||||
ms.setCreateTime(new Date());
|
||||
memberStudentMapper.insert(ms);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int unbindStudent(Long studentId) {
|
||||
if (studentId == null) {
|
||||
// 解绑所有会员与该学生的关系
|
||||
return memberStudentMapper.delete(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getStudentId, studentId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑指定会员与学生的关系
|
||||
*/
|
||||
public int unbindStudentFromMember(Long memberId, Long studentId) {
|
||||
if (memberId == null || studentId == null) {
|
||||
return 0;
|
||||
}
|
||||
return baseMapper.update(null,
|
||||
new LambdaUpdateWrapper<PgStudent>()
|
||||
.eq(PgStudent::getStudentId, studentId)
|
||||
.set(PgStudent::getMemberId, null)
|
||||
return memberStudentMapper.delete(
|
||||
new LambdaQueryWrapper<PgMemberStudent>()
|
||||
.eq(PgMemberStudent::getMemberId, memberId)
|
||||
.eq(PgMemberStudent::getStudentId, studentId)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -349,23 +427,7 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
PgMember member = findOrCreateMember(dto.getMemberPhone().trim());
|
||||
Long memberId = member.getMemberId();
|
||||
|
||||
// 6. 教师身份校验:检查会员是否有匹配的教育身份
|
||||
List<PgEducation> educations = educationMapper.selectList(
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getDelFlag, "0")
|
||||
);
|
||||
if (!educations.isEmpty()) {
|
||||
// 会员有教育身份,检查是否包含学生所在的班级
|
||||
boolean hasMatchingClass = educations.stream()
|
||||
.anyMatch(e -> schoolClass.getId().equals(e.getSchoolClassId()));
|
||||
if (!hasMatchingClass) {
|
||||
failList.add(createFailItem(rowNum, "该教师未管理学生所在班级"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 检查学号是否重复
|
||||
// 6. 检查学号是否重复
|
||||
if (StrUtil.isNotBlank(dto.getStudentNo())) {
|
||||
PgStudent existStudent = baseMapper.selectOne(
|
||||
new LambdaQueryWrapper<PgStudent>()
|
||||
|
|
@ -377,7 +439,7 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
}
|
||||
}
|
||||
|
||||
// 8. 创建学生
|
||||
// 7. 创建学生
|
||||
PgStudent student = new PgStudent();
|
||||
student.setStudentName(dto.getStudentName().trim());
|
||||
student.setStudentNo(StrUtil.isNotBlank(dto.getStudentNo()) ? dto.getStudentNo().trim() : null);
|
||||
|
|
@ -387,10 +449,16 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
student.setSchoolId(school.getSchoolId());
|
||||
student.setSchoolGradeId(schoolGrade.getId());
|
||||
student.setSchoolClassId(schoolClass.getId());
|
||||
student.setMemberId(memberId);
|
||||
student.setStatus("0"); // 默认正常
|
||||
|
||||
baseMapper.insert(student);
|
||||
|
||||
// 8. 创建会员学生关联
|
||||
PgMemberStudent ms = new PgMemberStudent();
|
||||
ms.setMemberId(memberId);
|
||||
ms.setStudentId(student.getStudentId());
|
||||
ms.setCreateTime(new Date());
|
||||
memberStudentMapper.insert(ms);
|
||||
|
||||
successCount++;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
```
|
||||
pg_member(会员)
|
||||
├── 一对一:教育信息(存在会员表中) ← 需要改为一对多
|
||||
└── 一对多:pg_student(学生) ← 保持不变
|
||||
└── 一对多:pg_student(学生) ← 已改为多对多
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -70,8 +70,8 @@ pg_member(会员)
|
|||
|
||||
```
|
||||
pg_member(会员)- 只存基础信息
|
||||
├── 一对多:pg_member_education(会员教育信息) ← 新建
|
||||
└── 一对多:pg_student(学生) ← 保持不变
|
||||
├── 一对多:pg_education(会员教育信息) ← 新建
|
||||
└── 多对多:pg_member_student ↔ pg_student ← 已重构(支持多会员绑定同一学生)
|
||||
```
|
||||
|
||||
### 3.2 新建表:pg_member_education
|
||||
|
|
@ -647,7 +647,7 @@ educations: any[] // 多个
|
|||
|
||||
---
|
||||
|
||||
## 附录:现有数据统计(上线前执行)
|
||||
## 附录A:现有数据统计(上线前执行)
|
||||
|
||||
```sql
|
||||
-- 统计需要迁移的教育数据
|
||||
|
|
@ -658,3 +658,68 @@ WHERE identity_type = '2' AND school_id IS NOT NULL AND del_flag = '0';
|
|||
SELECT COUNT(*) FROM pg_member
|
||||
WHERE identity_type = '2' AND school_class_id IS NOT NULL AND del_flag = '0';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录B:学生多会员绑定重构(2026-02-03)
|
||||
|
||||
### B.1 需求说明
|
||||
|
||||
**原设计**:一个学生只能被一个会员绑定(`pg_student.member_id` 单值字段)
|
||||
|
||||
**新设计**:一个学生可以被多个会员绑定(如爸爸和妈妈都能绑定同一个孩子)
|
||||
|
||||
### B.2 数据库变更
|
||||
|
||||
#### 新建关联表 `pg_member_student`
|
||||
|
||||
```sql
|
||||
CREATE TABLE pg_member_student (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
||||
member_id BIGINT NOT NULL COMMENT '会员ID',
|
||||
student_id BIGINT NOT NULL COMMENT '学生ID',
|
||||
relation VARCHAR(20) COMMENT '关系(父亲/母亲/其他)',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_member_student (member_id, student_id),
|
||||
KEY idx_member_id (member_id),
|
||||
KEY idx_student_id (student_id)
|
||||
) COMMENT='会员学生关联表';
|
||||
```
|
||||
|
||||
#### 数据迁移
|
||||
|
||||
```sql
|
||||
-- 迁移现有绑定关系
|
||||
INSERT IGNORE INTO pg_member_student (member_id, student_id)
|
||||
SELECT member_id, student_id FROM pg_student WHERE member_id IS NOT NULL;
|
||||
|
||||
-- 移除 pg_student.member_id 字段
|
||||
ALTER TABLE pg_student DROP COLUMN member_id;
|
||||
```
|
||||
|
||||
### B.3 后端变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| PgMemberStudent.java | 新建关联实体 |
|
||||
| PgMemberStudentMapper.java | 新建 Mapper |
|
||||
| PgStudent.java | 删除 memberId 字段 |
|
||||
| StudentVo.java | memberCount + List<MemberSimpleVo> members |
|
||||
| PgStudentServiceImpl.java | 绑定/解绑/查询改用关联表 |
|
||||
| H5MemberServiceImpl.java | 绑定/解绑/查询改用关联表 |
|
||||
| H5StudentVo.java | 新增 relation 字段 |
|
||||
| H5StudentBindDto.java | 新增 relation 字段 |
|
||||
|
||||
### B.4 前端变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| MemberDialog.vue | 解绑接口路径调整 |
|
||||
| StudentSelectDialog.vue | 绑定状态显示多会员数量 |
|
||||
|
||||
### B.5 关系模型
|
||||
|
||||
```
|
||||
变更前:会员 --1:N--> 学生(通过 pg_student.member_id)
|
||||
变更后:会员 --N:M--> 学生(通过 pg_member_student 关联表)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ const handleRemoveStudent = async (row) => {
|
|||
|
||||
// 编辑模式:远程解绑
|
||||
try {
|
||||
const res = await request.post(`/business/member/unbindStudent/${row.studentId}`)
|
||||
const res = await request.post(`/business/member/${form.memberId}/unbindStudent/${row.studentId}`)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('解绑成功')
|
||||
await loadBoundStudents()
|
||||
|
|
|
|||
|
|
@ -38,11 +38,17 @@
|
|||
<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">
|
||||
<el-table-column label="绑定状态" width="120" 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 v-if="isBoundToMe(row)">
|
||||
<el-tag type="success" size="small">已绑定</el-tag>
|
||||
</template>
|
||||
<template v-else-if="row.memberCount > 0">
|
||||
<el-tag type="warning" size="small">{{ row.memberCount }}人已绑</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="info" size="small">未绑定</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
@ -187,6 +193,14 @@ const handleSelectionChange = (selection) => {
|
|||
selectedStudents.value = selection
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断学生是否已被当前会员绑定
|
||||
*/
|
||||
const isBoundToMe = (row) => {
|
||||
if (!memberId.value || !row.members) return false
|
||||
return row.members.some(m => m.memberId === memberId.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定绑定
|
||||
*/
|
||||
|
|
@ -203,9 +217,9 @@ const handleConfirm = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
// 远程模式:调用 API 绑定
|
||||
// 远程模式:调用 API 绑定(过滤掉已被当前会员绑定的)
|
||||
const studentIds = selectedStudents.value
|
||||
.filter(s => s.memberId !== memberId.value)
|
||||
.filter(s => !isBoundToMe(s))
|
||||
.map(s => s.studentId)
|
||||
|
||||
if (studentIds.length === 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue