diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/controller/PgRegionController.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/controller/PgRegionController.java index 05941ce..2758f51 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/controller/PgRegionController.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/controller/PgRegionController.java @@ -51,6 +51,15 @@ public class PgRegionController extends BaseController { return R.ok(regionService.selectById(regionId)); } + /** + * 获取区域路径(从根到当前区域的ID列表) + * 用于级联选择器回显 + */ + @GetMapping("/{regionId}/path") + public R> getRegionPath(@PathVariable Long regionId) { + return R.ok(regionService.getRegionPath(regionId)); + } + @SaCheckPermission("business:region:add") @Log(title = "区域管理", businessType = BusinessType.INSERT) @PostMapping diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/domain/PgEducation.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/domain/PgEducation.java new file mode 100644 index 0000000..decb5b8 --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/domain/PgEducation.java @@ -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; +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/mapper/PgEducationMapper.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/mapper/PgEducationMapper.java new file mode 100644 index 0000000..2f91542 --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/mapper/PgEducationMapper.java @@ -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 { +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/IPgRegionService.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/IPgRegionService.java index d784a08..1608d79 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/IPgRegionService.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/IPgRegionService.java @@ -14,6 +14,7 @@ public interface IPgRegionService { List selectTree(); PgRegion selectById(Long regionId); List selectByParentId(Long parentId); + List getRegionPath(Long regionId); int insert(PgRegion region); int update(PgRegion region); int deleteByIds(Long[] regionIds); diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/impl/PgRegionServiceImpl.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/impl/PgRegionServiceImpl.java index c91cdf3..3a35b04 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/impl/PgRegionServiceImpl.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/base/service/impl/PgRegionServiceImpl.java @@ -69,6 +69,30 @@ public class PgRegionServiceImpl implements IPgRegionService { .orderByAsc(PgRegion::getOrderNum)); } + @Override + public List getRegionPath(Long regionId) { + if (regionId == null) { + return new ArrayList<>(); + } + PgRegion region = baseMapper.selectById(regionId); + if (region == null) { + return new ArrayList<>(); + } + List 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); diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/controller/H5MemberController.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/controller/H5MemberController.java index d513dd4..36478d1 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/controller/H5MemberController.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/controller/H5MemberController.java @@ -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 saveEducation(@Valid @RequestBody H5EducationDto dto) { - memberService.saveEducation(dto); + @PostMapping("/educations") + public R 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 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 getEducation() { - return R.ok(memberService.getEducation()); + @GetMapping("/educations") + public R> 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 deleteEducation() { - memberService.deleteEducation(); + @Parameters({ + @Parameter(name = "educationId", description = "教育身份ID", required = true, in = ParameterIn.PATH) + }) + @DeleteMapping("/educations/{educationId}") + public R 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 setDefaultEducation(@PathVariable Long educationId) { + memberService.setDefaultEducation(educationId); return R.ok(); } diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/dto/H5EducationDto.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/dto/H5EducationDto.java index f8176a9..8d86fff 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/dto/H5EducationDto.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/dto/H5EducationDto.java @@ -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; diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5EducationVo.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5EducationVo.java index d03fb5a..05a681e 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5EducationVo.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5EducationVo.java @@ -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; } diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5LoginVo.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5LoginVo.java index 815d49e..5d78ae3 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5LoginVo.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5LoginVo.java @@ -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; } diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5MemberInfoVo.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5MemberInfoVo.java index 097a947..7ca3de2 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5MemberInfoVo.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/domain/vo/H5MemberInfoVo.java @@ -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 educations; @Schema(description = "绑定的学生列表") private List students; diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/H5MemberService.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/H5MemberService.java index 8e23dcd..20e2862 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/H5MemberService.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/H5MemberService.java @@ -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 getEducations(); /** * 删除教育身份 */ - void deleteEducation(); + void deleteEducation(Long educationId); + + /** + * 设置默认教育身份 + */ + void setDefaultEducation(Long educationId); /** * 绑定学生 diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5AuthServiceImpl.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5AuthServiceImpl.java index 1d7fdfb..487134a 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5AuthServiceImpl.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5AuthServiceImpl.java @@ -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; } diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5MemberServiceImpl.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5MemberServiceImpl.java index 6d379b7..3497c95 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5MemberServiceImpl.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/h5/service/impl/H5MemberServiceImpl.java @@ -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() + .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 getEducations() { + Long memberId = getCurrentMemberId(); + + List educations = educationMapper.selectList( + new LambdaQueryWrapper() + .eq(PgEducation::getMemberId, memberId) + .eq(PgEducation::getDelFlag, "0") + .orderByDesc(PgEducation::getIsDefault) + .orderByDesc(PgEducation::getCreateTime) + ); + + List 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() + .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()); } } diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/controller/PgMemberController.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/controller/PgMemberController.java index c6c5bd6..afdf0f2 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/controller/PgMemberController.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/controller/PgMemberController.java @@ -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 add(@Validated @RequestBody PgMember member) { - return toAjax(memberService.insert(member)); + public R add(@Validated @RequestBody MemberSaveDto dto) { + memberService.insertWithEducations(dto); + return R.ok(); } /** - * 修改会员 + * 修改会员(支持同时保存教育身份) */ @SaCheckPermission("business:member:edit") @Log(title = "会员管理", businessType = BusinessType.UPDATE) @PutMapping - public R edit(@Validated @RequestBody PgMember member) { - return toAjax(memberService.update(member)); + public R edit(@Validated @RequestBody MemberSaveDto dto) { + memberService.updateWithEducations(dto); + return R.ok(); } /** @@ -139,4 +146,59 @@ public class PgMemberController extends BaseController { public R unbindStudent(@PathVariable Long studentId) { return toAjax(studentService.unbindStudent(studentId)); } + + // ==================== 教育身份管理 ==================== + + /** + * 获取会员教育身份列表 + */ + @GetMapping("/{memberId}/educations") + public R> getEducations(@PathVariable Long memberId) { + return R.ok(educationService.getEducationsByMemberId(memberId)); + } + + /** + * 添加教育身份 + */ + @SaCheckPermission("business:member:edit") + @Log(title = "会员管理-添加教育身份", businessType = BusinessType.INSERT) + @PostMapping("/{memberId}/educations") + public R 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 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 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 setDefaultEducation(@PathVariable Long memberId, @PathVariable Long educationId) { + educationService.setDefaultEducation(memberId, educationId); + return R.ok(); + } } diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/PgMember.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/PgMember.java index d24fa45..6e84e59 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/PgMember.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/PgMember.java @@ -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 regionIds; - - private Long schoolId; - - private Long schoolGradeId; - - private Long schoolClassId; - /** * 注册来源(1小程序 2H5 3后台 4导入) */ diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/dto/EducationDto.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/dto/EducationDto.java new file mode 100644 index 0000000..bc05149 --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/dto/EducationDto.java @@ -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; +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/dto/MemberSaveDto.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/dto/MemberSaveDto.java new file mode 100644 index 0000000..aceec2e --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/dto/MemberSaveDto.java @@ -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 educations; + + @Schema(description = "绑定的学生ID列表(亲子关系)") + private List studentIds; +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/vo/EducationVo.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/vo/EducationVo.java new file mode 100644 index 0000000..e7b495d --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/domain/vo/EducationVo.java @@ -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; +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgEducationService.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgEducationService.java new file mode 100644 index 0000000..e4c3092 --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgEducationService.java @@ -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 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); +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgMemberService.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgMemberService.java index 5a5f5b0..452822f 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgMemberService.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/IPgMemberService.java @@ -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); + /** * 修改会员 */ diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgEducationServiceImpl.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgEducationServiceImpl.java new file mode 100644 index 0000000..ec2cf86 --- /dev/null +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgEducationServiceImpl.java @@ -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 getEducationsByMemberId(Long memberId) { + List educations = educationMapper.selectList( + new LambdaQueryWrapper() + .eq(PgEducation::getMemberId, memberId) + .eq(PgEducation::getDelFlag, "0") + .orderByDesc(PgEducation::getIsDefault) + .orderByDesc(PgEducation::getCreateTime) + ); + + List 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() + .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() + .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(); + } +} diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgMemberServiceImpl.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgMemberServiceImpl.java index 23545c8..a66c7fe 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgMemberServiceImpl.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/member/service/impl/PgMemberServiceImpl.java @@ -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 getRegionPath(Long regionId) { - List 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 buildQueryWrapper(PgMember member) { LambdaQueryWrapper 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; diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/controller/PgStudentController.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/controller/PgStudentController.java index 07354f4..403d27d 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/controller/PgStudentController.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/controller/PgStudentController.java @@ -89,7 +89,7 @@ public class PgStudentController extends BaseController { * @param schoolId 学校ID(教师身份时传入,限制只能选本校学生) */ @GetMapping("/available") - public TableDataInfo availableStudents( + public TableDataInfo availableStudents( @RequestParam(required = false) String studentName, @RequestParam(required = false) String studentNo, @RequestParam(required = false) Long memberId, diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/IPgStudentService.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/IPgStudentService.java index d9a72ff..459f3e7 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/IPgStudentService.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/IPgStudentService.java @@ -30,7 +30,7 @@ public interface IPgStudentService { * @param schoolId 学校ID(教师身份时必传,限制只能选本校学生) * @param pageQuery 分页参数 */ - TableDataInfo selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery); + TableDataInfo selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery); /** * 查询会员已绑定的学生列表(包含学校、年级、班级名称) diff --git a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/impl/PgStudentServiceImpl.java b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/impl/PgStudentServiceImpl.java index d2d44a0..f335ed4 100644 --- a/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/impl/PgStudentServiceImpl.java +++ b/backend/ruoyi-modules/pangu-business/src/main/java/org/dromara/pangu/student/service/impl/PgStudentServiceImpl.java @@ -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 selectPageList(PgStudent student, PageQuery pageQuery) { @@ -211,7 +214,7 @@ public class PgStudentServiceImpl implements IPgStudentService { } @Override - public TableDataInfo selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery) { + public TableDataInfo selectAvailableStudents(String studentName, String studentNo, Long memberId, Long schoolId, PageQuery pageQuery) { LambdaQueryWrapper 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 page = baseMapper.selectPage(pageQuery.build(), lqw); - return TableDataInfo.build(page); + // 转换为 VO,填充学校、年级、班级名称 + List 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 educations = educationMapper.selectList( + new LambdaQueryWrapper() + .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位随机数) diff --git a/frontend/ruoyi-ui/src/views/business/member/components/EducationDialog.vue b/frontend/ruoyi-ui/src/views/business/member/components/EducationDialog.vue new file mode 100644 index 0000000..29010a9 --- /dev/null +++ b/frontend/ruoyi-ui/src/views/business/member/components/EducationDialog.vue @@ -0,0 +1,347 @@ + + + + diff --git a/frontend/ruoyi-ui/src/views/business/member/components/MemberDialog.vue b/frontend/ruoyi-ui/src/views/business/member/components/MemberDialog.vue index 8891175..d9ab447 100644 --- a/frontend/ruoyi-ui/src/views/business/member/components/MemberDialog.vue +++ b/frontend/ruoyi-ui/src/views/business/member/components/MemberDialog.vue @@ -53,14 +53,6 @@ - - - - 家长 - 教师 - - - - - - - - 绑定学生 - + + 教育身份 - 添加学生 + 添加教育身份 - + + + + + + + + + + + + + + + 亲子关系 + + 添加亲子关系 + + + @@ -141,9 +113,9 @@ @@ -156,7 +128,10 @@ - + + + + @@ -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('新增成功') diff --git a/frontend/ruoyi-ui/src/views/business/member/components/StudentSelectDialog.vue b/frontend/ruoyi-ui/src/views/business/member/components/StudentSelectDialog.vue index a24d112..d63d351 100644 --- a/frontend/ruoyi-ui/src/views/business/member/components/StudentSelectDialog.vue +++ b/frontend/ruoyi-ui/src/views/business/member/components/StudentSelectDialog.vue @@ -24,16 +24,6 @@ - - - 已选择 {{ selectedStudents.length }} 名学生 取消 - 确定绑定 + 确定 @@ -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) diff --git a/frontend/ruoyi-ui/src/views/business/member/index.vue b/frontend/ruoyi-ui/src/views/business/member/index.vue index 0e4149f..7ebe013 100644 --- a/frontend/ruoyi-ui/src/views/business/member/index.vue +++ b/frontend/ruoyi-ui/src/views/business/member/index.vue @@ -9,12 +9,6 @@ - - - - - - @@ -72,13 +66,6 @@ {{ formatBirthday(row.birthday) }} - - -