feat: 会员教育信息重构 - 支持多教育身份管理
主要变更: 1. 去掉身份类型字段,会员与教育信息改为一对多关系 2. 新增 pg_education 表及相关实体、服务 3. 管理后台支持添加、编辑、删除多个教育身份 4. 新增会员时支持一次性保存会员信息、教育身份、亲子关系 5. 修复学生选择弹窗学校年级班级不显示问题 6. 添加区域路径接口用于级联选择器回显
This commit is contained in:
parent
acdade6725
commit
729f2c71f1
|
|
@ -51,6 +51,15 @@ public class PgRegionController extends BaseController {
|
|||
return R.ok(regionService.selectById(regionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域路径(从根到当前区域的ID列表)
|
||||
* 用于级联选择器回显
|
||||
*/
|
||||
@GetMapping("/{regionId}/path")
|
||||
public R<List<Long>> getRegionPath(@PathVariable Long regionId) {
|
||||
return R.ok(regionService.getRegionPath(regionId));
|
||||
}
|
||||
|
||||
@SaCheckPermission("business:region:add")
|
||||
@Log(title = "区域管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package org.dromara.pangu.base.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;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 教育信息表(会员教育身份)
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pg_education")
|
||||
public class PgEducation extends BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long educationId;
|
||||
|
||||
private Long memberId;
|
||||
|
||||
private Long regionId;
|
||||
|
||||
private Long schoolId;
|
||||
|
||||
private Long schoolGradeId;
|
||||
|
||||
private Long schoolClassId;
|
||||
|
||||
private Long subjectId;
|
||||
|
||||
/**
|
||||
* 是否默认身份(0否 1是)
|
||||
*/
|
||||
private String isDefault;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
private String tenantId;
|
||||
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
// 非数据库字段(用于VO展示)
|
||||
@TableField(exist = false)
|
||||
private String regionName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String schoolName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String gradeName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String className;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String subjectName;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package org.dromara.pangu.base.mapper;
|
||||
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.pangu.base.domain.PgEducation;
|
||||
|
||||
/**
|
||||
* 教育信息 Mapper 接口
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
public interface PgEducationMapper extends BaseMapperPlus<PgEducation, PgEducation> {
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ public interface IPgRegionService {
|
|||
List<PgRegion> selectTree();
|
||||
PgRegion selectById(Long regionId);
|
||||
List<PgRegion> selectByParentId(Long parentId);
|
||||
List<Long> getRegionPath(Long regionId);
|
||||
int insert(PgRegion region);
|
||||
int update(PgRegion region);
|
||||
int deleteByIds(Long[] regionIds);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,30 @@ public class PgRegionServiceImpl implements IPgRegionService {
|
|||
.orderByAsc(PgRegion::getOrderNum));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getRegionPath(Long regionId) {
|
||||
if (regionId == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
PgRegion region = baseMapper.selectById(regionId);
|
||||
if (region == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<Long> path = new ArrayList<>();
|
||||
// ancestors 格式: "0,42,4201" 表示从根到父的路径
|
||||
if (StrUtil.isNotBlank(region.getAncestors())) {
|
||||
String[] ancestorIds = region.getAncestors().split(",");
|
||||
for (String id : ancestorIds) {
|
||||
if (StrUtil.isNotBlank(id) && !"0".equals(id)) {
|
||||
path.add(Long.parseLong(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 最后加上当前区域ID
|
||||
path.add(regionId);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insert(PgRegion region) {
|
||||
return baseMapper.insert(region);
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ public class H5MemberController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 添加/修改教育身份
|
||||
* 添加教育身份
|
||||
*/
|
||||
@Operation(
|
||||
summary = "设置教育身份(教师)",
|
||||
summary = "添加教育身份",
|
||||
description = """
|
||||
设置或修改会员的教育身份信息,设置后身份类型变为"教师"。
|
||||
添加一个教育身份。一个会员可以有多个教育身份(每班一条记录)。
|
||||
|
||||
**必填信息:**
|
||||
- 学校
|
||||
|
|
@ -143,31 +143,52 @@ public class H5MemberController {
|
|||
"""
|
||||
)
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "设置成功"),
|
||||
@ApiResponse(responseCode = "200", description = "添加成功"),
|
||||
@ApiResponse(responseCode = "401", description = "未登录或Token已过期"),
|
||||
@ApiResponse(responseCode = "500", description = "设置失败,可能原因:学校/年级/班级/学科不存在")
|
||||
@ApiResponse(responseCode = "500", description = "添加失败,可能原因:学校/年级/班级/学科不存在")
|
||||
})
|
||||
@PostMapping("/education")
|
||||
public R<Void> saveEducation(@Valid @RequestBody H5EducationDto dto) {
|
||||
memberService.saveEducation(dto);
|
||||
@PostMapping("/educations")
|
||||
public R<Void> addEducation(@Valid @RequestBody H5EducationDto dto) {
|
||||
memberService.addEducation(dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取教育身份
|
||||
* 修改教育身份
|
||||
*/
|
||||
@Operation(
|
||||
summary = "获取教育身份信息",
|
||||
description = "获取当前会员的教育身份信息(学校、年级、班级、学科)。如果未设置教育身份,返回null。"
|
||||
summary = "修改教育身份",
|
||||
description = "修改指定的教育身份信息。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "修改成功"),
|
||||
@ApiResponse(responseCode = "401", description = "未登录或Token已过期"),
|
||||
@ApiResponse(responseCode = "500", description = "修改失败,可能原因:教育身份不存在或无权限")
|
||||
})
|
||||
@Parameters({
|
||||
@Parameter(name = "educationId", description = "教育身份ID", required = true, in = ParameterIn.PATH)
|
||||
})
|
||||
@PutMapping("/educations/{educationId}")
|
||||
public R<Void> updateEducation(@PathVariable Long educationId, @Valid @RequestBody H5EducationDto dto) {
|
||||
memberService.updateEducation(educationId, dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取教育身份列表
|
||||
*/
|
||||
@Operation(
|
||||
summary = "获取教育身份列表",
|
||||
description = "获取当前会员的所有教育身份信息(学校、年级、班级、学科)。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "获取成功",
|
||||
content = @Content(schema = @Schema(implementation = H5EducationVo.class))),
|
||||
@ApiResponse(responseCode = "401", description = "未登录或Token已过期")
|
||||
})
|
||||
@GetMapping("/education")
|
||||
public R<H5EducationVo> getEducation() {
|
||||
return R.ok(memberService.getEducation());
|
||||
@GetMapping("/educations")
|
||||
public R<List<H5EducationVo>> getEducations() {
|
||||
return R.ok(memberService.getEducations());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -175,15 +196,40 @@ public class H5MemberController {
|
|||
*/
|
||||
@Operation(
|
||||
summary = "删除教育身份",
|
||||
description = "清除当前会员的教育身份信息,身份类型将变为空。"
|
||||
description = "删除指定的教育身份。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "删除成功"),
|
||||
@ApiResponse(responseCode = "401", description = "未登录或Token已过期")
|
||||
@ApiResponse(responseCode = "401", description = "未登录或Token已过期"),
|
||||
@ApiResponse(responseCode = "500", description = "删除失败,可能原因:教育身份不存在或无权限")
|
||||
})
|
||||
@DeleteMapping("/education")
|
||||
public R<Void> deleteEducation() {
|
||||
memberService.deleteEducation();
|
||||
@Parameters({
|
||||
@Parameter(name = "educationId", description = "教育身份ID", required = true, in = ParameterIn.PATH)
|
||||
})
|
||||
@DeleteMapping("/educations/{educationId}")
|
||||
public R<Void> deleteEducation(@PathVariable Long educationId) {
|
||||
memberService.deleteEducation(educationId);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认教育身份
|
||||
*/
|
||||
@Operation(
|
||||
summary = "设置默认教育身份",
|
||||
description = "将指定教育身份设为默认。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "设置成功"),
|
||||
@ApiResponse(responseCode = "401", description = "未登录或Token已过期"),
|
||||
@ApiResponse(responseCode = "500", description = "设置失败,可能原因:教育身份不存在或无权限")
|
||||
})
|
||||
@Parameters({
|
||||
@Parameter(name = "educationId", description = "教育身份ID", required = true, in = ParameterIn.PATH)
|
||||
})
|
||||
@PutMapping("/educations/{educationId}/default")
|
||||
public R<Void> setDefaultEducation(@PathVariable Long educationId) {
|
||||
memberService.setDefaultEducation(educationId);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import lombok.Data;
|
|||
@Schema(description = "教育身份设置请求参数(教师身份)")
|
||||
public class H5EducationDto {
|
||||
|
||||
@Schema(description = "教育身份ID(新增时不传,编辑时必传)", example = "1")
|
||||
private Long educationId;
|
||||
|
||||
@Schema(description = "学校ID(从/h5/base/schools获取)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "请选择学校")
|
||||
private Long schoolId;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import lombok.Data;
|
|||
@Schema(description = "教育身份信息(教师)")
|
||||
public class H5EducationVo {
|
||||
|
||||
@Schema(description = "教育身份ID", example = "1")
|
||||
private Long educationId;
|
||||
|
||||
@Schema(description = "省份ID", example = "420000")
|
||||
private Long provinceId;
|
||||
|
||||
|
|
@ -53,4 +56,7 @@ public class H5EducationVo {
|
|||
|
||||
@Schema(description = "学科名称", example = "语文")
|
||||
private String subjectName;
|
||||
|
||||
@Schema(description = "是否默认身份(0否 1是)", example = "1")
|
||||
private String isDefault;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,4 @@ public class H5LoginVo {
|
|||
|
||||
@Schema(description = "昵称", example = "user_5678")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "身份类型:1-家长,2-教师,null-未设置", example = "1")
|
||||
private String identityType;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,11 +39,8 @@ public class H5MemberInfoVo {
|
|||
@Schema(description = "注册时间", example = "2024-01-01 12:00:00")
|
||||
private Date registerTime;
|
||||
|
||||
@Schema(description = "身份类型:1-家长,2-教师", example = "2")
|
||||
private String identityType;
|
||||
|
||||
@Schema(description = "教育身份信息(仅教师身份有值)")
|
||||
private H5EducationVo education;
|
||||
@Schema(description = "教育身份列表")
|
||||
private List<H5EducationVo> educations;
|
||||
|
||||
@Schema(description = "绑定的学生列表")
|
||||
private List<H5StudentVo> students;
|
||||
|
|
|
|||
|
|
@ -33,19 +33,29 @@ public interface H5MemberService {
|
|||
void updatePassword(H5PasswordUpdateDto dto);
|
||||
|
||||
/**
|
||||
* 添加/修改教育身份
|
||||
* 添加教育身份
|
||||
*/
|
||||
void saveEducation(H5EducationDto dto);
|
||||
void addEducation(H5EducationDto dto);
|
||||
|
||||
/**
|
||||
* 获取教育身份
|
||||
* 修改教育身份
|
||||
*/
|
||||
H5EducationVo getEducation();
|
||||
void updateEducation(Long educationId, H5EducationDto dto);
|
||||
|
||||
/**
|
||||
* 获取教育身份列表
|
||||
*/
|
||||
List<H5EducationVo> getEducations();
|
||||
|
||||
/**
|
||||
* 删除教育身份
|
||||
*/
|
||||
void deleteEducation();
|
||||
void deleteEducation(Long educationId);
|
||||
|
||||
/**
|
||||
* 设置默认教育身份
|
||||
*/
|
||||
void setDefaultEducation(Long educationId);
|
||||
|
||||
/**
|
||||
* 绑定学生
|
||||
|
|
|
|||
|
|
@ -409,7 +409,6 @@ public class H5AuthServiceImpl implements H5AuthService {
|
|||
vo.setMemberCode(member.getMemberCode());
|
||||
vo.setPhone(maskPhone(member.getPhone()));
|
||||
vo.setNickname(member.getNickname());
|
||||
vo.setIdentityType(member.getIdentityType());
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
|
@ -671,7 +670,6 @@ public class H5AuthServiceImpl implements H5AuthService {
|
|||
vo.setMemberCode(loginVo.getMemberCode());
|
||||
vo.setPhone(loginVo.getPhone());
|
||||
vo.setNickname(loginVo.getNickname());
|
||||
vo.setIdentityType(loginVo.getIdentityType());
|
||||
|
||||
log.info("微信登录成功: openId={}, memberId={}", openId, member.getMemberId());
|
||||
} else {
|
||||
|
|
@ -777,7 +775,6 @@ public class H5AuthServiceImpl implements H5AuthService {
|
|||
vo.setMemberCode(loginVo.getMemberCode());
|
||||
vo.setPhone(loginVo.getPhone());
|
||||
vo.setNickname(loginVo.getNickname());
|
||||
vo.setIdentityType(loginVo.getIdentityType());
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.pangu.base.domain.PgClass;
|
||||
import org.dromara.pangu.base.domain.PgEducation;
|
||||
import org.dromara.pangu.base.domain.PgGrade;
|
||||
import org.dromara.pangu.base.domain.PgRegion;
|
||||
import org.dromara.pangu.base.domain.PgSubject;
|
||||
import org.dromara.pangu.base.mapper.PgClassMapper;
|
||||
import org.dromara.pangu.base.mapper.PgEducationMapper;
|
||||
import org.dromara.pangu.base.mapper.PgGradeMapper;
|
||||
import org.dromara.pangu.base.mapper.PgRegionMapper;
|
||||
import org.dromara.pangu.base.mapper.PgSubjectMapper;
|
||||
|
|
@ -59,6 +61,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
private final PgClassMapper classMapper;
|
||||
private final PgSubjectMapper subjectMapper;
|
||||
private final PgRegionMapper regionMapper;
|
||||
private final PgEducationMapper educationMapper;
|
||||
|
||||
@Override
|
||||
public H5MemberInfoVo getMemberInfo() {
|
||||
|
|
@ -77,12 +80,9 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
vo.setGender(member.getGender());
|
||||
vo.setBirthday(member.getBirthday());
|
||||
vo.setRegisterTime(member.getRegisterTime());
|
||||
vo.setIdentityType(member.getIdentityType());
|
||||
|
||||
// 获取教育身份信息
|
||||
if ("2".equals(member.getIdentityType()) && member.getSchoolId() != null) {
|
||||
vo.setEducation(buildEducationVo(member));
|
||||
}
|
||||
// 获取教育身份列表
|
||||
vo.setEducations(getEducations());
|
||||
|
||||
// 获取绑定的学生列表
|
||||
vo.setStudents(getStudents());
|
||||
|
|
@ -139,14 +139,125 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveEducation(H5EducationDto dto) {
|
||||
public void addEducation(H5EducationDto dto) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
PgMember member = memberMapper.selectById(memberId);
|
||||
if (member == null) {
|
||||
throw new ServiceException("会员不存在");
|
||||
|
||||
// 校验学校、年级、班级、学科是否存在
|
||||
PgSchool school = validateAndGetSchool(dto);
|
||||
|
||||
// 创建教育身份记录
|
||||
PgEducation education = new PgEducation();
|
||||
education.setMemberId(memberId);
|
||||
education.setRegionId(school.getRegionId());
|
||||
education.setSchoolId(dto.getSchoolId());
|
||||
education.setSchoolGradeId(dto.getSchoolGradeId());
|
||||
education.setSchoolClassId(dto.getSchoolClassId());
|
||||
education.setSubjectId(dto.getSubjectId());
|
||||
education.setStatus("0");
|
||||
|
||||
// 如果是第一个教育身份,设为默认
|
||||
long count = educationMapper.selectCount(
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getDelFlag, "0")
|
||||
);
|
||||
education.setIsDefault(count == 0 ? "1" : "0");
|
||||
|
||||
educationMapper.insert(education);
|
||||
log.info("H5添加教育身份: memberId={}, educationId={}", memberId, education.getEducationId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateEducation(Long educationId, H5EducationDto dto) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
// 查询并校验教育身份归属
|
||||
PgEducation education = educationMapper.selectById(educationId);
|
||||
if (education == null || !memberId.equals(education.getMemberId())) {
|
||||
throw new ServiceException("教育身份不存在或无权限修改");
|
||||
}
|
||||
|
||||
// 校验学校、年级、班级、学科是否存在
|
||||
PgSchool school = validateAndGetSchool(dto);
|
||||
|
||||
// 更新教育身份
|
||||
education.setRegionId(school.getRegionId());
|
||||
education.setSchoolId(dto.getSchoolId());
|
||||
education.setSchoolGradeId(dto.getSchoolGradeId());
|
||||
education.setSchoolClassId(dto.getSchoolClassId());
|
||||
education.setSubjectId(dto.getSubjectId());
|
||||
educationMapper.updateById(education);
|
||||
|
||||
log.info("H5修改教育身份: memberId={}, educationId={}", memberId, educationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<H5EducationVo> getEducations() {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
List<PgEducation> educations = educationMapper.selectList(
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getDelFlag, "0")
|
||||
.orderByDesc(PgEducation::getIsDefault)
|
||||
.orderByDesc(PgEducation::getCreateTime)
|
||||
);
|
||||
|
||||
List<H5EducationVo> voList = new ArrayList<>();
|
||||
for (PgEducation education : educations) {
|
||||
voList.add(buildEducationVo(education));
|
||||
}
|
||||
return voList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteEducation(Long educationId) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
// 查询并校验教育身份归属
|
||||
PgEducation education = educationMapper.selectById(educationId);
|
||||
if (education == null || !memberId.equals(education.getMemberId())) {
|
||||
throw new ServiceException("教育身份不存在或无权限删除");
|
||||
}
|
||||
|
||||
// 逻辑删除
|
||||
educationMapper.deleteById(educationId);
|
||||
log.info("H5删除教育身份: memberId={}, educationId={}", memberId, educationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void setDefaultEducation(Long educationId) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
|
||||
// 查询并校验教育身份归属
|
||||
PgEducation education = educationMapper.selectById(educationId);
|
||||
if (education == null || !memberId.equals(education.getMemberId())) {
|
||||
throw new ServiceException("教育身份不存在或无权限操作");
|
||||
}
|
||||
|
||||
// 取消其他默认
|
||||
PgEducation updateEntity = new PgEducation();
|
||||
updateEntity.setIsDefault("0");
|
||||
educationMapper.update(updateEntity,
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getIsDefault, "1")
|
||||
);
|
||||
|
||||
// 设置当前为默认
|
||||
education.setIsDefault("1");
|
||||
educationMapper.updateById(education);
|
||||
|
||||
log.info("H5设置默认教育身份: memberId={}, educationId={}", memberId, educationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并获取学校信息
|
||||
*/
|
||||
private PgSchool validateAndGetSchool(H5EducationDto dto) {
|
||||
PgSchool school = schoolMapper.selectById(dto.getSchoolId());
|
||||
if (school == null) {
|
||||
throw new ServiceException("学校不存在");
|
||||
|
|
@ -167,68 +278,13 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
throw new ServiceException("学科不存在");
|
||||
}
|
||||
|
||||
// 更新会员教育信息
|
||||
member.setIdentityType("2"); // 教师
|
||||
member.setSchoolId(dto.getSchoolId());
|
||||
member.setSchoolGradeId(dto.getSchoolGradeId());
|
||||
member.setSchoolClassId(dto.getSchoolClassId());
|
||||
// 从学校获取区域信息并保存
|
||||
if (school.getRegionId() != null) {
|
||||
member.setRegionId(school.getRegionId());
|
||||
}
|
||||
// 学科信息需要在member表添加字段,这里先用remark暂存
|
||||
member.setRemark("subjectId:" + dto.getSubjectId());
|
||||
memberMapper.updateById(member);
|
||||
|
||||
log.info("H5教育身份保存: memberId={}, schoolId={}", memberId, dto.getSchoolId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public H5EducationVo getEducation() {
|
||||
Long memberId = getCurrentMemberId();
|
||||
PgMember member = memberMapper.selectById(memberId);
|
||||
if (member == null) {
|
||||
throw new ServiceException("会员不存在");
|
||||
}
|
||||
|
||||
if (!"2".equals(member.getIdentityType()) || member.getSchoolId() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
H5EducationVo vo = buildEducationVo(member);
|
||||
log.info("H5获取教育身份: memberId={}, provinceId={}, cityId={}, districtId={}, schoolId={}",
|
||||
memberId, vo.getProvinceId(), vo.getCityId(), vo.getDistrictId(), vo.getSchoolId());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteEducation() {
|
||||
Long memberId = getCurrentMemberId();
|
||||
PgMember member = memberMapper.selectById(memberId);
|
||||
if (member == null) {
|
||||
throw new ServiceException("会员不存在");
|
||||
}
|
||||
|
||||
// 清除教育信息
|
||||
member.setIdentityType(null);
|
||||
member.setSchoolId(null);
|
||||
member.setSchoolGradeId(null);
|
||||
member.setSchoolClassId(null);
|
||||
member.setRemark(null);
|
||||
memberMapper.updateById(member);
|
||||
|
||||
log.info("H5教育身份删除: memberId={}", memberId);
|
||||
return school;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void bindStudent(H5StudentBindDto dto) {
|
||||
Long memberId = getCurrentMemberId();
|
||||
PgMember member = memberMapper.selectById(memberId);
|
||||
if (member == null) {
|
||||
throw new ServiceException("会员不存在");
|
||||
}
|
||||
|
||||
// 校验学校、年级、班级是否存在
|
||||
validateSchoolInfo(dto.getSchoolId(), dto.getSchoolGradeId(), dto.getSchoolClassId());
|
||||
|
|
@ -247,12 +303,6 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
student.setStatus("0");
|
||||
studentMapper.insert(student);
|
||||
|
||||
// 如果会员没有身份,默认设为家长
|
||||
if (StringUtils.isBlank(member.getIdentityType())) {
|
||||
member.setIdentityType("1"); // 家长
|
||||
memberMapper.updateById(member);
|
||||
}
|
||||
|
||||
log.info("H5绑定学生: memberId={}, studentId={}", memberId, student.getStudentId());
|
||||
}
|
||||
|
||||
|
|
@ -348,14 +398,17 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
/**
|
||||
* 构建教育身份VO
|
||||
*/
|
||||
private H5EducationVo buildEducationVo(PgMember member) {
|
||||
private H5EducationVo buildEducationVo(PgEducation education) {
|
||||
H5EducationVo vo = new H5EducationVo();
|
||||
vo.setSchoolId(member.getSchoolId());
|
||||
vo.setSchoolGradeId(member.getSchoolGradeId());
|
||||
vo.setSchoolClassId(member.getSchoolClassId());
|
||||
vo.setEducationId(education.getEducationId());
|
||||
vo.setSchoolId(education.getSchoolId());
|
||||
vo.setSchoolGradeId(education.getSchoolGradeId());
|
||||
vo.setSchoolClassId(education.getSchoolClassId());
|
||||
vo.setSubjectId(education.getSubjectId());
|
||||
vo.setIsDefault(education.getIsDefault());
|
||||
|
||||
// 获取学校名称和区域信息
|
||||
PgSchool school = schoolMapper.selectById(member.getSchoolId());
|
||||
PgSchool school = schoolMapper.selectById(education.getSchoolId());
|
||||
if (school != null) {
|
||||
vo.setSchoolName(school.getSchoolName());
|
||||
|
||||
|
|
@ -366,12 +419,9 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
vo.setDistrictId(district.getRegionId());
|
||||
vo.setDistrictName(district.getRegionName());
|
||||
|
||||
// 根据ancestors获取省市信息,格式如 "0,42,4201"
|
||||
// 根据ancestors获取省市信息
|
||||
if (StringUtils.isNotBlank(district.getAncestors())) {
|
||||
String[] ancestorIds = district.getAncestors().split(",");
|
||||
// ancestorIds[0] = "0" (根节点)
|
||||
// ancestorIds[1] = 省份ID
|
||||
// ancestorIds[2] = 城市ID
|
||||
if (ancestorIds.length >= 2 && !"0".equals(ancestorIds[1])) {
|
||||
try {
|
||||
Long provinceId = Long.parseLong(ancestorIds[1].trim());
|
||||
|
|
@ -402,7 +452,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
}
|
||||
|
||||
// 获取年级名称
|
||||
PgSchoolGrade schoolGrade = schoolGradeMapper.selectById(member.getSchoolGradeId());
|
||||
PgSchoolGrade schoolGrade = schoolGradeMapper.selectById(education.getSchoolGradeId());
|
||||
if (schoolGrade != null && schoolGrade.getGradeId() != null) {
|
||||
PgGrade grade = gradeMapper.selectById(schoolGrade.getGradeId());
|
||||
if (grade != null) {
|
||||
|
|
@ -411,7 +461,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
}
|
||||
|
||||
// 获取班级名称
|
||||
PgSchoolClass schoolClass = schoolClassMapper.selectById(member.getSchoolClassId());
|
||||
PgSchoolClass schoolClass = schoolClassMapper.selectById(education.getSchoolClassId());
|
||||
if (schoolClass != null && schoolClass.getClassId() != null) {
|
||||
PgClass cls = classMapper.selectById(schoolClass.getClassId());
|
||||
if (cls != null) {
|
||||
|
|
@ -419,17 +469,11 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
}
|
||||
}
|
||||
|
||||
// 获取学科信息(从remark解析)
|
||||
if (StringUtils.isNotBlank(member.getRemark()) && member.getRemark().startsWith("subjectId:")) {
|
||||
try {
|
||||
Long subjectId = Long.parseLong(member.getRemark().replace("subjectId:", ""));
|
||||
vo.setSubjectId(subjectId);
|
||||
PgSubject subject = subjectMapper.selectById(subjectId);
|
||||
if (subject != null) {
|
||||
vo.setSubjectName(subject.getSubjectName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略解析错误
|
||||
// 获取学科名称
|
||||
if (education.getSubjectId() != null) {
|
||||
PgSubject subject = subjectMapper.selectById(education.getSubjectId());
|
||||
if (subject != null) {
|
||||
vo.setSubjectName(subject.getSubjectName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
|||
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.domain.dto.EducationDto;
|
||||
import org.dromara.pangu.member.domain.dto.MemberSaveDto;
|
||||
import org.dromara.pangu.member.domain.vo.EducationVo;
|
||||
import org.dromara.pangu.member.service.IPgMemberService;
|
||||
import org.dromara.pangu.member.service.IPgEducationService;
|
||||
import org.dromara.pangu.student.domain.vo.StudentVo;
|
||||
import org.dromara.pangu.student.service.IPgStudentService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
|
@ -32,6 +36,7 @@ public class PgMemberController extends BaseController {
|
|||
|
||||
private final IPgMemberService memberService;
|
||||
private final IPgStudentService studentService;
|
||||
private final IPgEducationService educationService;
|
||||
|
||||
/**
|
||||
* 查询会员列表
|
||||
|
|
@ -52,23 +57,25 @@ public class PgMemberController extends BaseController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 新增会员
|
||||
* 新增会员(支持同时保存教育身份)
|
||||
*/
|
||||
@SaCheckPermission("business:member:add")
|
||||
@Log(title = "会员管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<Void> add(@Validated @RequestBody PgMember member) {
|
||||
return toAjax(memberService.insert(member));
|
||||
public R<Void> add(@Validated @RequestBody MemberSaveDto dto) {
|
||||
memberService.insertWithEducations(dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会员
|
||||
* 修改会员(支持同时保存教育身份)
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public R<Void> edit(@Validated @RequestBody PgMember member) {
|
||||
return toAjax(memberService.update(member));
|
||||
public R<Void> edit(@Validated @RequestBody MemberSaveDto dto) {
|
||||
memberService.updateWithEducations(dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,4 +146,59 @@ public class PgMemberController extends BaseController {
|
|||
public R<Void> unbindStudent(@PathVariable Long studentId) {
|
||||
return toAjax(studentService.unbindStudent(studentId));
|
||||
}
|
||||
|
||||
// ==================== 教育身份管理 ====================
|
||||
|
||||
/**
|
||||
* 获取会员教育身份列表
|
||||
*/
|
||||
@GetMapping("/{memberId}/educations")
|
||||
public R<List<EducationVo>> getEducations(@PathVariable Long memberId) {
|
||||
return R.ok(educationService.getEducationsByMemberId(memberId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加教育身份
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-添加教育身份", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/{memberId}/educations")
|
||||
public R<Void> addEducation(@PathVariable Long memberId, @Validated @RequestBody EducationDto dto) {
|
||||
educationService.addEducation(memberId, dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改教育身份
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-修改教育身份", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{memberId}/educations/{educationId}")
|
||||
public R<Void> updateEducation(@PathVariable Long memberId, @PathVariable Long educationId,
|
||||
@Validated @RequestBody EducationDto dto) {
|
||||
educationService.updateEducation(memberId, educationId, dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教育身份
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-删除教育身份", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{memberId}/educations/{educationId}")
|
||||
public R<Void> deleteEducation(@PathVariable Long memberId, @PathVariable Long educationId) {
|
||||
educationService.deleteEducation(memberId, educationId);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认教育身份
|
||||
*/
|
||||
@SaCheckPermission("business:member:edit")
|
||||
@Log(title = "会员管理-设置默认身份", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{memberId}/educations/{educationId}/default")
|
||||
public R<Void> setDefaultEducation(@PathVariable Long memberId, @PathVariable Long educationId) {
|
||||
educationService.setDefaultEducation(memberId, educationId);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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;
|
||||
|
|
@ -10,7 +9,6 @@ import lombok.EqualsAndHashCode;
|
|||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会员表
|
||||
|
|
@ -42,29 +40,10 @@ public class PgMember extends BaseEntity {
|
|||
|
||||
private Date birthday;
|
||||
|
||||
/**
|
||||
* 身份类型(1家长 2教师)
|
||||
*/
|
||||
private String identityType;
|
||||
|
||||
private String openId;
|
||||
|
||||
private String unionId;
|
||||
|
||||
private Long regionId;
|
||||
|
||||
/**
|
||||
* 区域ID路径(非数据库字段,用于级联选择器回显)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> regionIds;
|
||||
|
||||
private Long schoolId;
|
||||
|
||||
private Long schoolGradeId;
|
||||
|
||||
private Long schoolClassId;
|
||||
|
||||
/**
|
||||
* 注册来源(1小程序 2H5 3后台 4导入)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package org.dromara.pangu.member.domain.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 教育身份请求DTO
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "教育身份请求")
|
||||
public class EducationDto {
|
||||
|
||||
@Schema(description = "区域ID")
|
||||
private Long regionId;
|
||||
|
||||
@NotNull(message = "学校不能为空")
|
||||
@Schema(description = "学校ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long schoolId;
|
||||
|
||||
@NotNull(message = "年级不能为空")
|
||||
@Schema(description = "年级ID(school_grade关联ID)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long schoolGradeId;
|
||||
|
||||
@NotNull(message = "班级不能为空")
|
||||
@Schema(description = "班级ID(school_class关联ID)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long schoolClassId;
|
||||
|
||||
@Schema(description = "学科ID")
|
||||
private Long subjectId;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package org.dromara.pangu.member.domain.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会员保存请求DTO(支持同时保存教育身份)
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "会员保存请求")
|
||||
public class MemberSaveDto {
|
||||
|
||||
@Schema(description = "会员ID(新增时不传,编辑时必传)")
|
||||
private Long memberId;
|
||||
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "性别(0未知 1男 2女)")
|
||||
private String gender;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private Date birthday;
|
||||
|
||||
@Schema(description = "状态(0正常 1停用)")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "教育身份列表")
|
||||
private List<EducationDto> educations;
|
||||
|
||||
@Schema(description = "绑定的学生ID列表(亲子关系)")
|
||||
private List<Long> studentIds;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.dromara.pangu.member.domain.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 教育身份响应VO
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "教育身份信息")
|
||||
public class EducationVo {
|
||||
|
||||
@Schema(description = "教育身份ID")
|
||||
private Long educationId;
|
||||
|
||||
@Schema(description = "区域ID")
|
||||
private Long regionId;
|
||||
|
||||
@Schema(description = "区域名称(省/市/区)")
|
||||
private String regionName;
|
||||
|
||||
@Schema(description = "学校ID")
|
||||
private Long schoolId;
|
||||
|
||||
@Schema(description = "学校名称")
|
||||
private String schoolName;
|
||||
|
||||
@Schema(description = "年级ID(school_grade关联ID)")
|
||||
private Long schoolGradeId;
|
||||
|
||||
@Schema(description = "年级名称")
|
||||
private String gradeName;
|
||||
|
||||
@Schema(description = "班级ID(school_class关联ID)")
|
||||
private Long schoolClassId;
|
||||
|
||||
@Schema(description = "班级名称")
|
||||
private String className;
|
||||
|
||||
@Schema(description = "学科ID")
|
||||
private Long subjectId;
|
||||
|
||||
@Schema(description = "学科名称")
|
||||
private String subjectName;
|
||||
|
||||
@Schema(description = "是否默认身份(0否 1是)")
|
||||
private String isDefault;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.dromara.pangu.member.service;
|
||||
|
||||
import org.dromara.pangu.member.domain.dto.EducationDto;
|
||||
import org.dromara.pangu.member.domain.vo.EducationVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教育身份管理 Service 接口
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
public interface IPgEducationService {
|
||||
|
||||
/**
|
||||
* 获取会员的教育身份列表
|
||||
*/
|
||||
List<EducationVo> getEducationsByMemberId(Long memberId);
|
||||
|
||||
/**
|
||||
* 添加教育身份
|
||||
*/
|
||||
void addEducation(Long memberId, EducationDto dto);
|
||||
|
||||
/**
|
||||
* 修改教育身份
|
||||
*/
|
||||
void updateEducation(Long memberId, Long educationId, EducationDto dto);
|
||||
|
||||
/**
|
||||
* 删除教育身份
|
||||
*/
|
||||
void deleteEducation(Long memberId, Long educationId);
|
||||
|
||||
/**
|
||||
* 设置默认教育身份
|
||||
*/
|
||||
void setDefaultEducation(Long memberId, Long educationId);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package org.dromara.pangu.member.service;
|
|||
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.dto.MemberSaveDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -33,6 +34,16 @@ public interface IPgMemberService {
|
|||
*/
|
||||
int insert(PgMember member);
|
||||
|
||||
/**
|
||||
* 新增会员(支持同时保存教育身份)
|
||||
*/
|
||||
void insertWithEducations(MemberSaveDto dto);
|
||||
|
||||
/**
|
||||
* 修改会员(支持同时保存教育身份)
|
||||
*/
|
||||
void updateWithEducations(MemberSaveDto dto);
|
||||
|
||||
/**
|
||||
* 修改会员
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,267 @@
|
|||
package org.dromara.pangu.member.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.pangu.base.domain.PgClass;
|
||||
import org.dromara.pangu.base.domain.PgEducation;
|
||||
import org.dromara.pangu.base.domain.PgGrade;
|
||||
import org.dromara.pangu.base.domain.PgRegion;
|
||||
import org.dromara.pangu.base.domain.PgSubject;
|
||||
import org.dromara.pangu.base.mapper.PgClassMapper;
|
||||
import org.dromara.pangu.base.mapper.PgEducationMapper;
|
||||
import org.dromara.pangu.base.mapper.PgGradeMapper;
|
||||
import org.dromara.pangu.base.mapper.PgRegionMapper;
|
||||
import org.dromara.pangu.base.mapper.PgSubjectMapper;
|
||||
import org.dromara.pangu.member.domain.dto.EducationDto;
|
||||
import org.dromara.pangu.member.domain.vo.EducationVo;
|
||||
import org.dromara.pangu.member.service.IPgEducationService;
|
||||
import org.dromara.pangu.school.domain.PgSchool;
|
||||
import org.dromara.pangu.school.domain.PgSchoolClass;
|
||||
import org.dromara.pangu.school.domain.PgSchoolGrade;
|
||||
import org.dromara.pangu.school.mapper.PgSchoolClassMapper;
|
||||
import org.dromara.pangu.school.mapper.PgSchoolGradeMapper;
|
||||
import org.dromara.pangu.school.mapper.PgSchoolMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教育身份管理 Service 实现
|
||||
*
|
||||
* @author 湖北新华业务中台研发团队
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class PgEducationServiceImpl implements IPgEducationService {
|
||||
|
||||
private final PgEducationMapper educationMapper;
|
||||
private final PgSchoolMapper schoolMapper;
|
||||
private final PgSchoolGradeMapper schoolGradeMapper;
|
||||
private final PgSchoolClassMapper schoolClassMapper;
|
||||
private final PgGradeMapper gradeMapper;
|
||||
private final PgClassMapper classMapper;
|
||||
private final PgSubjectMapper subjectMapper;
|
||||
private final PgRegionMapper regionMapper;
|
||||
|
||||
@Override
|
||||
public List<EducationVo> getEducationsByMemberId(Long memberId) {
|
||||
List<PgEducation> educations = educationMapper.selectList(
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getDelFlag, "0")
|
||||
.orderByDesc(PgEducation::getIsDefault)
|
||||
.orderByDesc(PgEducation::getCreateTime)
|
||||
);
|
||||
|
||||
List<EducationVo> voList = new ArrayList<>();
|
||||
for (PgEducation education : educations) {
|
||||
voList.add(buildEducationVo(education));
|
||||
}
|
||||
return voList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void addEducation(Long memberId, EducationDto dto) {
|
||||
// 校验学校信息
|
||||
PgSchool school = validateAndGetSchool(dto);
|
||||
|
||||
// 创建教育身份
|
||||
PgEducation education = new PgEducation();
|
||||
education.setMemberId(memberId);
|
||||
education.setRegionId(school.getRegionId());
|
||||
education.setSchoolId(dto.getSchoolId());
|
||||
education.setSchoolGradeId(dto.getSchoolGradeId());
|
||||
education.setSchoolClassId(dto.getSchoolClassId());
|
||||
education.setSubjectId(dto.getSubjectId());
|
||||
education.setStatus("0");
|
||||
|
||||
// 如果是第一个教育身份,设为默认
|
||||
long count = educationMapper.selectCount(
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getDelFlag, "0")
|
||||
);
|
||||
education.setIsDefault(count == 0 ? "1" : "0");
|
||||
|
||||
educationMapper.insert(education);
|
||||
log.info("添加教育身份: memberId={}, educationId={}", memberId, education.getEducationId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateEducation(Long memberId, Long educationId, EducationDto dto) {
|
||||
// 查询并校验归属
|
||||
PgEducation education = educationMapper.selectById(educationId);
|
||||
if (education == null || !memberId.equals(education.getMemberId())) {
|
||||
throw new ServiceException("教育身份不存在或无权限修改");
|
||||
}
|
||||
|
||||
// 校验学校信息
|
||||
PgSchool school = validateAndGetSchool(dto);
|
||||
|
||||
// 更新
|
||||
education.setRegionId(school.getRegionId());
|
||||
education.setSchoolId(dto.getSchoolId());
|
||||
education.setSchoolGradeId(dto.getSchoolGradeId());
|
||||
education.setSchoolClassId(dto.getSchoolClassId());
|
||||
education.setSubjectId(dto.getSubjectId());
|
||||
educationMapper.updateById(education);
|
||||
|
||||
log.info("修改教育身份: memberId={}, educationId={}", memberId, educationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteEducation(Long memberId, Long educationId) {
|
||||
PgEducation education = educationMapper.selectById(educationId);
|
||||
if (education == null || !memberId.equals(education.getMemberId())) {
|
||||
throw new ServiceException("教育身份不存在或无权限删除");
|
||||
}
|
||||
|
||||
educationMapper.deleteById(educationId);
|
||||
log.info("删除教育身份: memberId={}, educationId={}", memberId, educationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void setDefaultEducation(Long memberId, Long educationId) {
|
||||
PgEducation education = educationMapper.selectById(educationId);
|
||||
if (education == null || !memberId.equals(education.getMemberId())) {
|
||||
throw new ServiceException("教育身份不存在或无权限操作");
|
||||
}
|
||||
|
||||
// 取消其他默认
|
||||
PgEducation updateEntity = new PgEducation();
|
||||
updateEntity.setIsDefault("0");
|
||||
educationMapper.update(updateEntity,
|
||||
new LambdaQueryWrapper<PgEducation>()
|
||||
.eq(PgEducation::getMemberId, memberId)
|
||||
.eq(PgEducation::getIsDefault, "1")
|
||||
);
|
||||
|
||||
// 设置当前为默认
|
||||
education.setIsDefault("1");
|
||||
educationMapper.updateById(education);
|
||||
|
||||
log.info("设置默认教育身份: memberId={}, educationId={}", memberId, educationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验学校信息
|
||||
*/
|
||||
private PgSchool validateAndGetSchool(EducationDto dto) {
|
||||
PgSchool school = schoolMapper.selectById(dto.getSchoolId());
|
||||
if (school == null) {
|
||||
throw new ServiceException("学校不存在");
|
||||
}
|
||||
|
||||
PgSchoolGrade schoolGrade = schoolGradeMapper.selectById(dto.getSchoolGradeId());
|
||||
if (schoolGrade == null || !schoolGrade.getSchoolId().equals(dto.getSchoolId())) {
|
||||
throw new ServiceException("年级不存在或不属于该学校");
|
||||
}
|
||||
|
||||
PgSchoolClass schoolClass = schoolClassMapper.selectById(dto.getSchoolClassId());
|
||||
if (schoolClass == null || !schoolClass.getSchoolGradeId().equals(dto.getSchoolGradeId())) {
|
||||
throw new ServiceException("班级不存在或不属于该年级");
|
||||
}
|
||||
|
||||
if (dto.getSubjectId() != null) {
|
||||
PgSubject subject = subjectMapper.selectById(dto.getSubjectId());
|
||||
if (subject == null) {
|
||||
throw new ServiceException("学科不存在");
|
||||
}
|
||||
}
|
||||
|
||||
return school;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建教育身份VO
|
||||
*/
|
||||
private EducationVo buildEducationVo(PgEducation education) {
|
||||
EducationVo vo = new EducationVo();
|
||||
vo.setEducationId(education.getEducationId());
|
||||
vo.setSchoolId(education.getSchoolId());
|
||||
vo.setSchoolGradeId(education.getSchoolGradeId());
|
||||
vo.setSchoolClassId(education.getSchoolClassId());
|
||||
vo.setSubjectId(education.getSubjectId());
|
||||
vo.setIsDefault(education.getIsDefault());
|
||||
|
||||
// 获取学校和区域信息
|
||||
PgSchool school = schoolMapper.selectById(education.getSchoolId());
|
||||
if (school != null) {
|
||||
vo.setSchoolName(school.getSchoolName());
|
||||
vo.setRegionId(school.getRegionId());
|
||||
|
||||
// 构建区域全名
|
||||
if (school.getRegionId() != null) {
|
||||
vo.setRegionName(buildRegionFullName(school.getRegionId()));
|
||||
}
|
||||
}
|
||||
|
||||
// 获取年级名称
|
||||
PgSchoolGrade schoolGrade = schoolGradeMapper.selectById(education.getSchoolGradeId());
|
||||
if (schoolGrade != null && schoolGrade.getGradeId() != null) {
|
||||
PgGrade grade = gradeMapper.selectById(schoolGrade.getGradeId());
|
||||
if (grade != null) {
|
||||
vo.setGradeName(grade.getGradeName());
|
||||
}
|
||||
}
|
||||
|
||||
// 获取班级名称
|
||||
PgSchoolClass schoolClass = schoolClassMapper.selectById(education.getSchoolClassId());
|
||||
if (schoolClass != null && schoolClass.getClassId() != null) {
|
||||
PgClass cls = classMapper.selectById(schoolClass.getClassId());
|
||||
if (cls != null) {
|
||||
vo.setClassName(cls.getClassName());
|
||||
}
|
||||
}
|
||||
|
||||
// 获取学科名称
|
||||
if (education.getSubjectId() != null) {
|
||||
PgSubject subject = subjectMapper.selectById(education.getSubjectId());
|
||||
if (subject != null) {
|
||||
vo.setSubjectName(subject.getSubjectName());
|
||||
}
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建区域全名(省/市/区)
|
||||
*/
|
||||
private String buildRegionFullName(Long regionId) {
|
||||
PgRegion district = regionMapper.selectById(regionId);
|
||||
if (district == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (StringUtils.isNotBlank(district.getAncestors())) {
|
||||
String[] ancestorIds = district.getAncestors().split(",");
|
||||
for (String idStr : ancestorIds) {
|
||||
if (!"0".equals(idStr.trim())) {
|
||||
try {
|
||||
PgRegion ancestor = regionMapper.selectById(Long.parseLong(idStr.trim()));
|
||||
if (ancestor != null) {
|
||||
if (sb.length() > 0) sb.append("/");
|
||||
sb.append(ancestor.getRegionName());
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) sb.append("/");
|
||||
sb.append(district.getRegionName());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,22 +5,23 @@ import cn.hutool.core.util.StrUtil;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.domain.dto.EducationDto;
|
||||
import org.dromara.pangu.member.domain.dto.MemberSaveDto;
|
||||
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
||||
import org.dromara.pangu.member.service.IPgEducationService;
|
||||
import org.dromara.pangu.member.service.IPgMemberService;
|
||||
import org.dromara.pangu.student.mapper.PgStudentMapper;
|
||||
import org.dromara.pangu.student.service.IPgStudentService;
|
||||
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;
|
||||
|
||||
|
|
@ -29,13 +30,15 @@ import java.util.List;
|
|||
*
|
||||
* @author pangu
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class PgMemberServiceImpl implements IPgMemberService {
|
||||
|
||||
private final PgMemberMapper baseMapper;
|
||||
private final PgStudentMapper studentMapper;
|
||||
private final PgRegionMapper regionMapper;
|
||||
private final IPgEducationService educationService;
|
||||
private final IPgStudentService studentService;
|
||||
|
||||
private static final String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
|
|
@ -53,29 +56,7 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
|
||||
@Override
|
||||
public PgMember selectById(Long 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;
|
||||
return baseMapper.selectById(memberId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -112,12 +93,63 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
member.setStatus("0");
|
||||
}
|
||||
|
||||
// 教师身份校验必填字段
|
||||
validateTeacherInfo(member);
|
||||
|
||||
return baseMapper.insert(member);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void insertWithEducations(MemberSaveDto dto) {
|
||||
log.info("新增会员: phone={}, educations={}, studentIds={}",
|
||||
dto.getPhone(),
|
||||
dto.getEducations() != null ? dto.getEducations().size() : 0,
|
||||
dto.getStudentIds() != null ? dto.getStudentIds() : "null");
|
||||
|
||||
// 创建会员
|
||||
PgMember member = new PgMember();
|
||||
member.setPhone(dto.getPhone());
|
||||
member.setNickname(dto.getNickname());
|
||||
member.setGender(dto.getGender());
|
||||
member.setBirthday(dto.getBirthday());
|
||||
member.setStatus(dto.getStatus());
|
||||
insert(member);
|
||||
|
||||
Long memberId = member.getMemberId();
|
||||
|
||||
// 保存教育身份
|
||||
if (dto.getEducations() != null && !dto.getEducations().isEmpty()) {
|
||||
log.info("保存教育身份: memberId={}, count={}", memberId, dto.getEducations().size());
|
||||
for (EducationDto eduDto : dto.getEducations()) {
|
||||
educationService.addEducation(memberId, eduDto);
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定学生(亲子关系)
|
||||
if (dto.getStudentIds() != null && !dto.getStudentIds().isEmpty()) {
|
||||
log.info("绑定学生: memberId={}, studentIds={}", memberId, dto.getStudentIds());
|
||||
studentService.bindStudentsToMember(memberId, dto.getStudentIds());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateWithEducations(MemberSaveDto dto) {
|
||||
if (dto.getMemberId() == null) {
|
||||
throw new ServiceException("会员ID不能为空");
|
||||
}
|
||||
|
||||
// 更新会员基本信息
|
||||
PgMember member = new PgMember();
|
||||
member.setMemberId(dto.getMemberId());
|
||||
member.setPhone(dto.getPhone());
|
||||
member.setNickname(dto.getNickname());
|
||||
member.setGender(dto.getGender());
|
||||
member.setBirthday(dto.getBirthday());
|
||||
member.setStatus(dto.getStatus());
|
||||
update(member);
|
||||
|
||||
// 注意:编辑时教育身份通过单独的接口管理,这里不处理
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int update(PgMember member) {
|
||||
|
|
@ -126,17 +158,6 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
throw new ServiceException("手机号已存在");
|
||||
}
|
||||
|
||||
// 教师身份校验必填字段
|
||||
validateTeacherInfo(member);
|
||||
|
||||
// 家长身份清空学校信息
|
||||
if ("1".equals(member.getIdentityType())) {
|
||||
member.setRegionId(null);
|
||||
member.setSchoolId(null);
|
||||
member.setSchoolGradeId(null);
|
||||
member.setSchoolClassId(null);
|
||||
}
|
||||
|
||||
// 不更新密码(密码通过重置接口更新)
|
||||
member.setPassword(null);
|
||||
|
||||
|
|
@ -223,31 +244,10 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验教师身份必填字段
|
||||
*/
|
||||
private void validateTeacherInfo(PgMember member) {
|
||||
if ("2".equals(member.getIdentityType())) {
|
||||
if (member.getRegionId() == null) {
|
||||
throw new ServiceException("教师身份必须选择所属区域");
|
||||
}
|
||||
if (member.getSchoolId() == null) {
|
||||
throw new ServiceException("教师身份必须选择所属学校");
|
||||
}
|
||||
if (member.getSchoolGradeId() == null) {
|
||||
throw new ServiceException("教师身份必须选择所属年级");
|
||||
}
|
||||
if (member.getSchoolClassId() == null) {
|
||||
throw new ServiceException("教师身份必须选择所属班级");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<PgMember> buildQueryWrapper(PgMember member) {
|
||||
LambdaQueryWrapper<PgMember> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.like(StrUtil.isNotBlank(member.getNickname()), PgMember::getNickname, member.getNickname());
|
||||
lqw.like(StrUtil.isNotBlank(member.getPhone()), PgMember::getPhone, member.getPhone());
|
||||
lqw.eq(StrUtil.isNotBlank(member.getIdentityType()), PgMember::getIdentityType, member.getIdentityType());
|
||||
lqw.eq(StrUtil.isNotBlank(member.getStatus()), PgMember::getStatus, member.getStatus());
|
||||
lqw.orderByDesc(PgMember::getCreateTime);
|
||||
return lqw;
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public class PgStudentController extends BaseController {
|
|||
* @param schoolId 学校ID(教师身份时传入,限制只能选本校学生)
|
||||
*/
|
||||
@GetMapping("/available")
|
||||
public TableDataInfo<PgStudent> availableStudents(
|
||||
public TableDataInfo<StudentVo> availableStudents(
|
||||
@RequestParam(required = false) String studentName,
|
||||
@RequestParam(required = false) String studentNo,
|
||||
@RequestParam(required = false) Long memberId,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public interface IPgStudentService {
|
|||
* @param schoolId 学校ID(教师身份时必传,限制只能选本校学生)
|
||||
* @param pageQuery 分页参数
|
||||
*/
|
||||
TableDataInfo<PgStudent> selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery);
|
||||
TableDataInfo<StudentVo> selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询会员已绑定的学生列表(包含学校、年级、班级名称)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ 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.mapper.PgMemberMapper;
|
||||
import org.dromara.pangu.school.domain.PgSchool;
|
||||
|
|
@ -52,6 +54,7 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
private final PgGradeMapper gradeMapper;
|
||||
private final PgClassMapper classMapper;
|
||||
private final PgMemberMapper memberMapper;
|
||||
private final PgEducationMapper educationMapper;
|
||||
|
||||
@Override
|
||||
public TableDataInfo<StudentVo> selectPageList(PgStudent student, PageQuery pageQuery) {
|
||||
|
|
@ -211,7 +214,7 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<PgStudent> selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery) {
|
||||
public TableDataInfo<StudentVo> 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);
|
||||
|
|
@ -224,7 +227,9 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
lqw.orderByDesc(PgStudent::getCreateTime);
|
||||
|
||||
Page<PgStudent> page = baseMapper.selectPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(page);
|
||||
// 转换为 VO,填充学校、年级、班级名称
|
||||
List<StudentVo> voList = convertToVoList(page.getRecords());
|
||||
return new TableDataInfo<>(voList, page.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -344,11 +349,18 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
PgMember member = findOrCreateMember(dto.getMemberPhone().trim());
|
||||
Long memberId = member.getMemberId();
|
||||
|
||||
// 6. 教师身份校验:教师的区域/学校/年级/班级必须与学生一致
|
||||
if ("2".equals(member.getIdentityType())) {
|
||||
String teacherError = validateTeacherStudent(member, school, schoolGrade, schoolClass);
|
||||
if (teacherError != null) {
|
||||
failList.add(createFailItem(rowNum, teacherError));
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -445,12 +457,11 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
return member;
|
||||
}
|
||||
|
||||
// 不存在则创建新会员(身份为家长,初始密码123456)
|
||||
// 不存在则创建新会员(初始密码123456)
|
||||
PgMember newMember = new PgMember();
|
||||
newMember.setMemberCode(generateMemberCode()); // 生成会员编码
|
||||
newMember.setPhone(phone);
|
||||
newMember.setNickname("家长" + phone.substring(7)); // 默认昵称
|
||||
newMember.setIdentityType("1"); // 家长
|
||||
newMember.setPassword(BCrypt.hashpw("123456")); // 初始密码
|
||||
newMember.setStatus("0"); // 正常
|
||||
newMember.setRegisterSource("4"); // 批量导入
|
||||
|
|
@ -460,44 +471,6 @@ public class PgStudentServiceImpl implements IPgStudentService {
|
|||
return newMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验教师与学生的归属关系
|
||||
* 教师的区域/学校/年级/班级必须与学生一致
|
||||
*/
|
||||
private String validateTeacherStudent(PgMember teacher, PgSchool studentSchool,
|
||||
PgSchoolGrade studentGrade, PgSchoolClass studentClass) {
|
||||
String teacherInfo = "教师\"" + (teacher.getNickname() != null ? teacher.getNickname() : "未知")
|
||||
+ "\"(" + teacher.getPhone() + ")";
|
||||
|
||||
// 检查教师是否设置了学校信息
|
||||
if (teacher.getSchoolId() == null || teacher.getSchoolGradeId() == null || teacher.getSchoolClassId() == null) {
|
||||
return teacherInfo + "未设置学校信息,无法绑定学生";
|
||||
}
|
||||
|
||||
// 校验区域(通过学校的区域ID间接校验)
|
||||
if (teacher.getRegionId() != null && studentSchool.getRegionId() != null) {
|
||||
if (!teacher.getRegionId().equals(studentSchool.getRegionId())) {
|
||||
return teacherInfo + "所属区域与学生不一致";
|
||||
}
|
||||
}
|
||||
|
||||
// 校验学校
|
||||
if (!teacher.getSchoolId().equals(studentSchool.getSchoolId())) {
|
||||
return teacherInfo + "所属学校与学生不一致";
|
||||
}
|
||||
|
||||
// 校验年级
|
||||
if (!teacher.getSchoolGradeId().equals(studentGrade.getId())) {
|
||||
return teacherInfo + "所属年级与学生不一致";
|
||||
}
|
||||
|
||||
// 校验班级
|
||||
if (!teacher.getSchoolClassId().equals(studentClass.getId())) {
|
||||
return teacherInfo + "所属班级与学生不一致";
|
||||
}
|
||||
|
||||
return null; // 校验通过
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成会员编码(M + 年月日时分秒毫秒 + 4位随机数)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,347 @@
|
|||
<!--
|
||||
教育身份编辑弹窗
|
||||
@author 湖北新华业务中台研发团队
|
||||
-->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="isEdit ? '编辑教育身份' : '添加教育身份'"
|
||||
width="550px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="区域" prop="regionId">
|
||||
<el-cascader
|
||||
v-model="regionIds"
|
||||
:options="regionTree"
|
||||
:props="{ value: 'regionId', label: 'regionName', checkStrictly: true }"
|
||||
placeholder="请选择区域"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleRegionChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="学校" prop="schoolId">
|
||||
<el-select v-model="form.schoolId" placeholder="请选择学校" clearable style="width: 100%" @change="handleSchoolChange">
|
||||
<el-option v-for="item in schoolList" :key="item.schoolId" :label="item.schoolName" :value="item.schoolId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="年级" prop="schoolGradeId">
|
||||
<el-select v-model="form.schoolGradeId" placeholder="请选择年级" clearable style="width: 100%" @change="handleGradeChange">
|
||||
<el-option v-for="item in gradeList" :key="item.id" :label="item.gradeName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="班级" prop="schoolClassId">
|
||||
<el-select v-model="form.schoolClassId" placeholder="请选择班级" clearable style="width: 100%">
|
||||
<el-option v-for="item in classList" :key="item.id" :label="item.className" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="学科" prop="subjectId">
|
||||
<el-select v-model="form.subjectId" placeholder="请选择学科(可选)" clearable style="width: 100%">
|
||||
<el-option v-for="item in subjectList" :key="item.subjectId" :label="item.subjectName" :value="item.subjectId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import request from '@/utils/request'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
const emit = defineEmits(['success', 'add', 'update'])
|
||||
|
||||
const visible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref(null)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 会员ID和教育身份ID(memberId 为 null 表示本地模式)
|
||||
const memberId = ref(null)
|
||||
const educationId = ref(null)
|
||||
// 本地模式下的临时索引
|
||||
const localIndex = ref(null)
|
||||
|
||||
// 区域ID数组(用于级联选择器)
|
||||
const regionIds = ref([])
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
regionId: null,
|
||||
schoolId: null,
|
||||
schoolGradeId: null,
|
||||
schoolClassId: null,
|
||||
subjectId: null
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
schoolId: [{ required: true, message: '请选择学校', trigger: 'change' }],
|
||||
schoolGradeId: [{ required: true, message: '请选择年级', trigger: 'change' }],
|
||||
schoolClassId: [{ required: true, message: '请选择班级', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 下拉选项数据
|
||||
const regionTree = ref([])
|
||||
const schoolList = ref([])
|
||||
const gradeList = ref([])
|
||||
const classList = ref([])
|
||||
const subjectList = ref([])
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
* @param {Long} mId 会员ID(null 表示本地模式)
|
||||
* @param {Object} row 编辑时传入教育身份数据
|
||||
* @param {Number} index 本地模式下的数组索引
|
||||
*/
|
||||
const open = async (mId, row, index) => {
|
||||
resetForm()
|
||||
memberId.value = mId
|
||||
isEdit.value = !!row
|
||||
localIndex.value = index ?? null
|
||||
visible.value = true
|
||||
|
||||
// 加载基础数据
|
||||
await Promise.all([loadRegionTree(), loadSubjectList()])
|
||||
|
||||
// 编辑模式
|
||||
if (row) {
|
||||
educationId.value = row.educationId
|
||||
form.schoolId = row.schoolId
|
||||
form.schoolGradeId = row.schoolGradeId
|
||||
form.schoolClassId = row.schoolClassId
|
||||
form.subjectId = row.subjectId
|
||||
|
||||
// 加载关联数据
|
||||
if (row.regionId) {
|
||||
// 本地模式下 regionIds 可能已有值
|
||||
if (row.regionIds) {
|
||||
regionIds.value = row.regionIds
|
||||
} else {
|
||||
regionIds.value = await getRegionPath(row.regionId)
|
||||
}
|
||||
form.regionId = row.regionId
|
||||
await loadSchoolList(row.regionId)
|
||||
}
|
||||
if (row.schoolId) {
|
||||
await loadGradeList(row.schoolId)
|
||||
}
|
||||
if (row.schoolGradeId) {
|
||||
await loadClassList(row.schoolGradeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
const resetForm = () => {
|
||||
educationId.value = null
|
||||
regionIds.value = []
|
||||
form.regionId = null
|
||||
form.schoolId = null
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
form.subjectId = null
|
||||
schoolList.value = []
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载区域树
|
||||
*/
|
||||
const loadRegionTree = async () => {
|
||||
try {
|
||||
const res = await request.get('/business/region/tree')
|
||||
regionTree.value = res.data || []
|
||||
} catch (e) {
|
||||
regionTree.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载学科列表
|
||||
*/
|
||||
const loadSubjectList = async () => {
|
||||
try {
|
||||
const res = await request.get('/business/subject/listAll')
|
||||
subjectList.value = res.data || []
|
||||
} catch (e) {
|
||||
subjectList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载学校列表
|
||||
*/
|
||||
const loadSchoolList = async (regionId) => {
|
||||
try {
|
||||
const res = await request.get('/business/school/listAll', { params: { regionId } })
|
||||
schoolList.value = res.data || []
|
||||
} catch (e) {
|
||||
schoolList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载年级列表
|
||||
*/
|
||||
const loadGradeList = async (schoolId) => {
|
||||
try {
|
||||
const res = await request.get(`/business/school/${schoolId}/grades`)
|
||||
gradeList.value = res.data || []
|
||||
} catch (e) {
|
||||
gradeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载班级列表
|
||||
*/
|
||||
const loadClassList = async (schoolGradeId) => {
|
||||
try {
|
||||
const res = await request.get(`/business/school/grade/${schoolGradeId}/classes`)
|
||||
classList.value = res.data || []
|
||||
} catch (e) {
|
||||
classList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域路径(用于回显)
|
||||
*/
|
||||
const getRegionPath = async (regionId) => {
|
||||
try {
|
||||
const res = await request.get(`/business/region/${regionId}/path`)
|
||||
return res.data || []
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域变更
|
||||
*/
|
||||
const handleRegionChange = (val) => {
|
||||
form.regionId = val && val.length ? val[val.length - 1] : null
|
||||
form.schoolId = null
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
schoolList.value = []
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
if (form.regionId) {
|
||||
loadSchoolList(form.regionId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 学校变更
|
||||
*/
|
||||
const handleSchoolChange = () => {
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
if (form.schoolId) {
|
||||
loadGradeList(form.schoolId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 年级变更
|
||||
*/
|
||||
const handleGradeChange = () => {
|
||||
form.schoolClassId = null
|
||||
classList.value = []
|
||||
if (form.schoolGradeId) {
|
||||
loadClassList(form.schoolGradeId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
// 构建数据(包含名称用于本地展示)
|
||||
const data = {
|
||||
regionId: form.regionId,
|
||||
regionIds: [...regionIds.value],
|
||||
schoolId: form.schoolId,
|
||||
schoolGradeId: form.schoolGradeId,
|
||||
schoolClassId: form.schoolClassId,
|
||||
subjectId: form.subjectId,
|
||||
// 添加名称用于列表展示
|
||||
schoolName: schoolList.value.find(s => s.schoolId === form.schoolId)?.schoolName || '',
|
||||
gradeName: gradeList.value.find(g => g.id === form.schoolGradeId)?.gradeName || '',
|
||||
className: classList.value.find(c => c.id === form.schoolClassId)?.className || '',
|
||||
subjectName: subjectList.value.find(s => s.subjectId === form.subjectId)?.subjectName || ''
|
||||
}
|
||||
|
||||
// 本地模式:返回数据给父组件
|
||||
if (!memberId.value) {
|
||||
visible.value = false
|
||||
if (isEdit.value) {
|
||||
emit('update', data, localIndex.value)
|
||||
} else {
|
||||
emit('add', data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 远程模式:调用 API
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const apiData = {
|
||||
regionId: form.regionId,
|
||||
schoolId: form.schoolId,
|
||||
schoolGradeId: form.schoolGradeId,
|
||||
schoolClassId: form.schoolClassId,
|
||||
subjectId: form.subjectId
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
const res = await request.put(`/business/member/${memberId.value}/educations/${educationId.value}`, apiData)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('修改成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
} else {
|
||||
const res = await request.post(`/business/member/${memberId.value}/educations`, apiData)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('添加成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -53,14 +53,6 @@
|
|||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份类型" prop="identityType">
|
||||
<el-radio-group v-model="form.identityType" @change="handleIdentityChange">
|
||||
<el-radio value="1">家长</el-radio>
|
||||
<el-radio value="2">教师</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-switch
|
||||
|
|
@ -74,65 +66,45 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 教师身份时显示学校信息 -->
|
||||
<template v-if="form.identityType === '2'">
|
||||
<el-divider content-position="left">学校信息(教师必填)</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="区域" prop="regionId" :rules="teacherRules.regionId">
|
||||
<el-cascader
|
||||
v-model="form.regionIds"
|
||||
:options="regionTree"
|
||||
:props="{ value: 'regionId', label: 'regionName', checkStrictly: true }"
|
||||
placeholder="请选择区域"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleRegionChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学校" prop="schoolId" :rules="teacherRules.schoolId">
|
||||
<el-select v-model="form.schoolId" placeholder="请选择学校" clearable style="width: 100%" @change="handleSchoolChange">
|
||||
<el-option v-for="item in schoolList" :key="item.schoolId" :label="item.schoolName" :value="item.schoolId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年级" prop="schoolGradeId" :rules="teacherRules.schoolGradeId">
|
||||
<el-select v-model="form.schoolGradeId" placeholder="请选择年级" clearable style="width: 100%" @change="handleGradeChange">
|
||||
<el-option v-for="item in gradeList" :key="item.id" :label="item.gradeName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="schoolClassId" :rules="teacherRules.schoolClassId">
|
||||
<el-select v-model="form.schoolClassId" placeholder="请选择班级" clearable style="width: 100%">
|
||||
<el-option v-for="item in classList" :key="item.id" :label="item.className" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 绑定学生 -->
|
||||
<el-divider content-position="left">绑定学生</el-divider>
|
||||
<el-alert
|
||||
v-if="form.identityType === '2'"
|
||||
title="教师只能绑定本校学生"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
<!-- 教育身份 -->
|
||||
<el-divider content-position="left">教育身份</el-divider>
|
||||
<el-row style="margin-bottom: 12px;">
|
||||
<el-button type="primary" size="small" :icon="Plus" @click="handleAddStudent">添加学生</el-button>
|
||||
<el-button type="primary" size="small" :icon="Plus" @click="handleAddEducation">添加教育身份</el-button>
|
||||
</el-row>
|
||||
<el-table :data="form.students" border size="small" max-height="200">
|
||||
<el-table :data="educations" border size="small" max-height="180">
|
||||
<template #empty>
|
||||
<el-empty description="暂无绑定学生" :image-size="60" />
|
||||
<el-empty description="暂无教育身份" :image-size="60" />
|
||||
</template>
|
||||
<el-table-column prop="schoolName" label="学校" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="gradeName" label="年级" width="70" />
|
||||
<el-table-column prop="className" label="班级" width="60" />
|
||||
<el-table-column prop="subjectName" label="学科" width="60" />
|
||||
<el-table-column label="默认" width="60" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isDefault === '1'" type="success" size="small">默认</el-tag>
|
||||
<el-button v-else link type="primary" size="small" @click="handleSetDefault(row)">设为</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="handleEditEducation(row)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除该教育身份?" @confirm="handleDeleteEducation(row)">
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 亲子关系 -->
|
||||
<el-divider content-position="left">亲子关系</el-divider>
|
||||
<el-row style="margin-bottom: 12px;">
|
||||
<el-button type="primary" size="small" :icon="Plus" @click="handleAddStudent">添加亲子关系</el-button>
|
||||
</el-row>
|
||||
<el-table :data="form.students" border size="small" max-height="180">
|
||||
<template #empty>
|
||||
<el-empty description="暂无亲子关系" :image-size="60" />
|
||||
</template>
|
||||
<el-table-column prop="studentName" label="姓名" min-width="80" />
|
||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||
|
|
@ -141,9 +113,9 @@
|
|||
<el-table-column prop="className" label="班级" width="60" />
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-popconfirm title="确定解绑该学生?" @confirm="handleRemoveStudent(row)">
|
||||
<el-popconfirm :title="isEdit ? '确定解绑该学生?' : '确定移除该学生?'" @confirm="handleRemoveStudent(row)">
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="small">解绑</el-button>
|
||||
<el-button link type="danger" size="small">{{ isEdit ? '解绑' : '移除' }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
|
|
@ -156,7 +128,10 @@
|
|||
</template>
|
||||
|
||||
<!-- 学生选择器 -->
|
||||
<StudentSelectDialog ref="studentSelectRef" @success="loadBoundStudents" />
|
||||
<StudentSelectDialog ref="studentSelectRef" @success="loadBoundStudents" @add="handleLocalAddStudents" />
|
||||
|
||||
<!-- 教育身份编辑弹窗 -->
|
||||
<EducationDialog ref="educationDialogRef" @success="loadEducations" @add="handleLocalAddEducation" @update="handleLocalUpdateEducation" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
|
|
@ -166,6 +141,7 @@ import { Plus } from '@element-plus/icons-vue'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import StudentSelectDialog from './StudentSelectDialog.vue'
|
||||
import EducationDialog from './EducationDialog.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
|
|
@ -174,6 +150,10 @@ const isEdit = ref(false)
|
|||
const formRef = ref(null)
|
||||
const submitLoading = ref(false)
|
||||
const studentSelectRef = ref(null)
|
||||
const educationDialogRef = ref(null)
|
||||
|
||||
// 教育身份列表
|
||||
const educations = ref([])
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
|
|
@ -182,13 +162,7 @@ const form = reactive({
|
|||
nickname: '',
|
||||
gender: '0',
|
||||
birthday: '',
|
||||
identityType: '1',
|
||||
status: '0',
|
||||
regionIds: [],
|
||||
regionId: null,
|
||||
schoolId: null,
|
||||
schoolGradeId: null,
|
||||
schoolClassId: null,
|
||||
students: []
|
||||
})
|
||||
|
||||
|
|
@ -197,26 +171,9 @@ const rules = {
|
|||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
identityType: [
|
||||
{ required: true, message: '请选择身份类型', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 教师身份额外校验规则
|
||||
const teacherRules = {
|
||||
regionId: [{ required: true, message: '请选择所属区域', trigger: 'change' }],
|
||||
schoolId: [{ required: true, message: '请选择所属学校', trigger: 'change' }],
|
||||
schoolGradeId: [{ required: true, message: '请选择所属年级', trigger: 'change' }],
|
||||
schoolClassId: [{ required: true, message: '请选择所属班级', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 下拉选项数据
|
||||
const regionTree = ref([])
|
||||
const schoolList = ref([])
|
||||
const gradeList = ref([])
|
||||
const classList = ref([])
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
*/
|
||||
|
|
@ -225,28 +182,14 @@ const open = async (row) => {
|
|||
isEdit.value = !!row
|
||||
visible.value = true
|
||||
|
||||
// 加载区域树
|
||||
await loadRegionTree()
|
||||
|
||||
// 编辑模式加载会员数据
|
||||
if (row && row.memberId) {
|
||||
try {
|
||||
const res = await request.get(`/business/member/${row.memberId}`)
|
||||
if (res.code === 200 && res.data) {
|
||||
Object.assign(form, res.data)
|
||||
// 后端已返回 regionIds 数组用于级联选择器回显
|
||||
// 加载学校信息的下拉选项
|
||||
if (form.regionId) {
|
||||
await loadSchoolList(form.regionId)
|
||||
}
|
||||
if (form.schoolId) {
|
||||
await loadGradeList(form.schoolId)
|
||||
}
|
||||
if (form.schoolGradeId) {
|
||||
await loadClassList(form.schoolGradeId)
|
||||
}
|
||||
// 加载已绑定的学生列表
|
||||
await loadBoundStudents()
|
||||
// 加载教育身份和学生列表
|
||||
await Promise.all([loadEducations(), loadBoundStudents()])
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载会员数据失败', e)
|
||||
|
|
@ -254,6 +197,22 @@ const open = async (row) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载教育身份列表
|
||||
*/
|
||||
const loadEducations = async () => {
|
||||
if (!form.memberId) {
|
||||
educations.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await request.get(`/business/member/${form.memberId}/educations`)
|
||||
educations.value = res.data || []
|
||||
} catch (e) {
|
||||
educations.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载已绑定的学生列表
|
||||
*/
|
||||
|
|
@ -279,143 +238,147 @@ const resetForm = () => {
|
|||
form.nickname = ''
|
||||
form.gender = '0'
|
||||
form.birthday = ''
|
||||
form.identityType = '1'
|
||||
form.status = '0'
|
||||
form.regionIds = []
|
||||
form.regionId = null
|
||||
form.schoolId = null
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
form.students = []
|
||||
schoolList.value = []
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
educations.value = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载区域树
|
||||
* 添加教育身份
|
||||
*/
|
||||
const loadRegionTree = async () => {
|
||||
try {
|
||||
const res = await request.get('/business/region/tree')
|
||||
regionTree.value = res.data || []
|
||||
} catch (e) {
|
||||
regionTree.value = []
|
||||
const handleAddEducation = () => {
|
||||
// 新增模式:本地模式(memberId 传 null)
|
||||
// 编辑模式:远程模式(传实际 memberId)
|
||||
educationDialogRef.value?.open(isEdit.value ? form.memberId : null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑教育身份
|
||||
*/
|
||||
const handleEditEducation = (row) => {
|
||||
const index = educations.value.indexOf(row)
|
||||
educationDialogRef.value?.open(isEdit.value ? form.memberId : null, row, index)
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地添加教育身份(新增会员模式)
|
||||
*/
|
||||
const handleLocalAddEducation = (data) => {
|
||||
// 如果是第一条,设为默认
|
||||
if (educations.value.length === 0) {
|
||||
data.isDefault = '1'
|
||||
} else {
|
||||
data.isDefault = '0'
|
||||
}
|
||||
educations.value.push(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地更新教育身份(新增会员模式)
|
||||
*/
|
||||
const handleLocalUpdateEducation = (data, index) => {
|
||||
if (index !== null && index >= 0) {
|
||||
// 保留原来的默认状态
|
||||
data.isDefault = educations.value[index].isDefault
|
||||
educations.value[index] = data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载学校列表
|
||||
* 删除教育身份
|
||||
*/
|
||||
const loadSchoolList = async (regionId) => {
|
||||
try {
|
||||
const res = await request.get('/business/school/listAll', { params: { regionId } })
|
||||
schoolList.value = res.data || []
|
||||
} catch (e) {
|
||||
schoolList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载年级列表
|
||||
*/
|
||||
const loadGradeList = async (schoolId) => {
|
||||
try {
|
||||
const res = await request.get(`/business/school/${schoolId}/grades`)
|
||||
gradeList.value = res.data || []
|
||||
} catch (e) {
|
||||
gradeList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载班级列表
|
||||
*/
|
||||
const loadClassList = async (schoolGradeId) => {
|
||||
try {
|
||||
const res = await request.get(`/business/school/grade/${schoolGradeId}/classes`)
|
||||
classList.value = res.data || []
|
||||
} catch (e) {
|
||||
classList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份类型变更
|
||||
*/
|
||||
const handleIdentityChange = () => {
|
||||
// 切换为家长时清空学校信息
|
||||
if (form.identityType === '1') {
|
||||
form.regionIds = []
|
||||
form.regionId = null
|
||||
form.schoolId = null
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
schoolList.value = []
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域变更
|
||||
*/
|
||||
const handleRegionChange = (val) => {
|
||||
form.regionId = val && val.length ? val[val.length - 1] : null
|
||||
form.schoolId = null
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
schoolList.value = []
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
if (form.regionId) {
|
||||
loadSchoolList(form.regionId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 学校变更
|
||||
*/
|
||||
const handleSchoolChange = () => {
|
||||
form.schoolGradeId = null
|
||||
form.schoolClassId = null
|
||||
gradeList.value = []
|
||||
classList.value = []
|
||||
if (form.schoolId) {
|
||||
loadGradeList(form.schoolId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 年级变更
|
||||
*/
|
||||
const handleGradeChange = () => {
|
||||
form.schoolClassId = null
|
||||
classList.value = []
|
||||
if (form.schoolGradeId) {
|
||||
loadClassList(form.schoolGradeId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加学生
|
||||
*/
|
||||
const handleAddStudent = () => {
|
||||
if (!isEdit.value || !form.memberId) {
|
||||
ElMessage.warning('请先保存会员信息后再绑定学生')
|
||||
const handleDeleteEducation = async (row) => {
|
||||
// 新增模式:本地删除
|
||||
if (!isEdit.value) {
|
||||
const index = educations.value.indexOf(row)
|
||||
if (index > -1) {
|
||||
educations.value.splice(index, 1)
|
||||
// 如果删除的是默认项且还有其他项,将第一项设为默认
|
||||
if (row.isDefault === '1' && educations.value.length > 0) {
|
||||
educations.value[0].isDefault = '1'
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
studentSelectRef.value?.open({
|
||||
memberId: form.memberId,
|
||||
identityType: form.identityType,
|
||||
schoolId: form.schoolId
|
||||
})
|
||||
|
||||
// 编辑模式:远程删除
|
||||
try {
|
||||
const res = await request.delete(`/business/member/${form.memberId}/educations/${row.educationId}`)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
await loadEducations()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('删除失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑学生
|
||||
* 设为默认教育身份
|
||||
*/
|
||||
const handleSetDefault = async (row) => {
|
||||
// 新增模式:本地设置
|
||||
if (!isEdit.value) {
|
||||
educations.value.forEach(e => e.isDefault = '0')
|
||||
row.isDefault = '1'
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑模式:远程设置
|
||||
try {
|
||||
const res = await request.put(`/business/member/${form.memberId}/educations/${row.educationId}/default`)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('设置成功')
|
||||
await loadEducations()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('设置失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加亲子关系
|
||||
*/
|
||||
const handleAddStudent = () => {
|
||||
// 新增模式:本地模式(传 excludeIds 排除已选)
|
||||
// 编辑模式:远程模式(传 memberId)
|
||||
if (isEdit.value) {
|
||||
studentSelectRef.value?.open({
|
||||
memberId: form.memberId
|
||||
})
|
||||
} else {
|
||||
studentSelectRef.value?.open({
|
||||
excludeIds: form.students.map(s => s.studentId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地添加学生(新增会员模式)
|
||||
*/
|
||||
const handleLocalAddStudents = (students) => {
|
||||
// 添加到本地列表(去重)
|
||||
for (const student of students) {
|
||||
if (!form.students.some(s => s.studentId === student.studentId)) {
|
||||
form.students.push(student)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑/移除学生
|
||||
*/
|
||||
const handleRemoveStudent = async (row) => {
|
||||
// 新增模式:本地移除
|
||||
if (!isEdit.value) {
|
||||
const index = form.students.findIndex(s => s.studentId === row.studentId)
|
||||
if (index > -1) {
|
||||
form.students.splice(index, 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑模式:远程解绑
|
||||
try {
|
||||
const res = await request.post(`/business/member/unbindStudent/${row.studentId}`)
|
||||
if (res.code === 200) {
|
||||
|
|
@ -439,10 +402,14 @@ const handleSubmit = async () => {
|
|||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const data = { ...form }
|
||||
// 移除不需要提交的字段
|
||||
delete data.regionIds
|
||||
delete data.students
|
||||
const data = {
|
||||
memberId: form.memberId,
|
||||
phone: form.phone,
|
||||
nickname: form.nickname,
|
||||
gender: form.gender,
|
||||
birthday: form.birthday,
|
||||
status: form.status
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
const res = await request.put('/business/member', data)
|
||||
|
|
@ -452,6 +419,15 @@ const handleSubmit = async () => {
|
|||
emit('success')
|
||||
}
|
||||
} else {
|
||||
// 新增时,将教育身份和亲子关系一起提交
|
||||
data.educations = educations.value.map(e => ({
|
||||
regionId: e.regionId,
|
||||
schoolId: e.schoolId,
|
||||
schoolGradeId: e.schoolGradeId,
|
||||
schoolClassId: e.schoolClassId,
|
||||
subjectId: e.subjectId
|
||||
}))
|
||||
data.studentIds = form.students.map(s => s.studentId)
|
||||
const res = await request.post('/business/member', data)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('新增成功')
|
||||
|
|
|
|||
|
|
@ -24,16 +24,6 @@
|
|||
</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"
|
||||
|
|
@ -74,7 +64,7 @@
|
|||
<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>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
|
@ -86,7 +76,7 @@ import { Refresh, Search } from '@element-plus/icons-vue'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const emit = defineEmits(['success', 'add'])
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
|
|
@ -96,10 +86,12 @@ const total = ref(0)
|
|||
const selectedStudents = ref([])
|
||||
const tableRef = ref(null)
|
||||
|
||||
// 会员信息
|
||||
// 会员信息(memberId 为 null 表示本地模式)
|
||||
const memberId = ref(null)
|
||||
const isTeacher = ref(false)
|
||||
const schoolId = ref(null)
|
||||
// 本地模式:已选中的学生ID(用于排除)
|
||||
const excludeStudentIds = ref([])
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
|
|
@ -111,12 +103,13 @@ const queryParams = reactive({
|
|||
|
||||
/**
|
||||
* 打开弹窗
|
||||
* @param options { memberId: Long, identityType: String, schoolId: Long }
|
||||
* @param options { memberId: Long, excludeIds: Long[] } - memberId 为 null 表示本地模式
|
||||
*/
|
||||
const open = (options = {}) => {
|
||||
memberId.value = options.memberId
|
||||
isTeacher.value = options.identityType === '2'
|
||||
schoolId.value = options.schoolId
|
||||
memberId.value = options.memberId || null
|
||||
isTeacher.value = false
|
||||
schoolId.value = null
|
||||
excludeStudentIds.value = options.excludeIds || []
|
||||
|
||||
resetQuery()
|
||||
visible.value = true
|
||||
|
|
@ -134,11 +127,15 @@ const getList = async () => {
|
|||
pageSize: queryParams.pageSize,
|
||||
studentName: queryParams.studentName || undefined,
|
||||
studentNo: queryParams.studentNo || undefined,
|
||||
memberId: memberId.value,
|
||||
schoolId: isTeacher.value ? schoolId.value : undefined
|
||||
memberId: memberId.value || undefined
|
||||
}
|
||||
const res = await request.get('/business/student/available', { params })
|
||||
studentList.value = res.rows || []
|
||||
let list = res.rows || []
|
||||
// 本地模式:过滤掉已选中的学生
|
||||
if (!memberId.value && excludeStudentIds.value.length > 0) {
|
||||
list = list.filter(s => !excludeStudentIds.value.includes(s.studentId))
|
||||
}
|
||||
studentList.value = list
|
||||
total.value = res.total || 0
|
||||
} catch (e) {
|
||||
console.error('获取学生列表失败', e)
|
||||
|
|
@ -195,11 +192,18 @@ const handleSelectionChange = (selection) => {
|
|||
*/
|
||||
const handleConfirm = async () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择要绑定的学生')
|
||||
ElMessage.warning('请选择要添加的学生')
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤出需要绑定的学生(排除已绑定当前会员的)
|
||||
// 本地模式:返回选中的学生数据
|
||||
if (!memberId.value) {
|
||||
visible.value = false
|
||||
emit('add', selectedStudents.value)
|
||||
return
|
||||
}
|
||||
|
||||
// 远程模式:调用 API 绑定
|
||||
const studentIds = selectedStudents.value
|
||||
.filter(s => s.memberId !== memberId.value)
|
||||
.map(s => s.studentId)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@
|
|||
<el-form-item label="昵称">
|
||||
<el-input v-model="queryParams.nickname" placeholder="请输入昵称" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="身份类型">
|
||||
<el-select v-model="queryParams.identityType" placeholder="全部" clearable style="width: 120px">
|
||||
<el-option label="家长" value="1" />
|
||||
<el-option label="教师" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
|
||||
<el-option label="正常" value="0" />
|
||||
|
|
@ -72,13 +66,6 @@
|
|||
{{ formatBirthday(row.birthday) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="identityType" label="身份类型" width="85" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.identityType === '1' ? 'success' : 'warning'">
|
||||
{{ row.identityType === '1' ? '家长' : '教师' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="registerTime" label="注册时间" width="165" />
|
||||
<el-table-column prop="registerSource" label="注册来源" width="85" align="center">
|
||||
<template #default="{ row }">
|
||||
|
|
@ -157,7 +144,6 @@ const queryParams = ref({
|
|||
pageSize: 10,
|
||||
phone: '',
|
||||
nickname: '',
|
||||
identityType: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
|
|
@ -235,7 +221,6 @@ const resetQuery = () => {
|
|||
pageSize: 10,
|
||||
phone: '',
|
||||
nickname: '',
|
||||
identityType: '',
|
||||
status: ''
|
||||
}
|
||||
dateRange.value = []
|
||||
|
|
|
|||
Loading…
Reference in New Issue