From 4c2154bfdee59c80a4740b517fe0fc9cb3a1dfb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=97=AD=E6=98=8E?= Date: Tue, 26 Aug 2025 00:26:25 +0800 Subject: [PATCH] stgl1.1 --- .../service/impl/StglActivityServiceImpl.java | 254 ++++++++++++- .../service/impl/StglClubServiceImpl.java | 239 +++++++++++- .../impl/StglVolunteerTeamServiceImpl.java | 88 +++++ .../main/resources/mapper/StglMyMapper.xml | 25 +- .../mapper/stgl/StglActivityMapper.xml | 94 ++++- web/src/api/stgl/activity/types.ts | 5 + web/src/views/stgl/activity/index.vue | 340 ++++++++++++++++-- web/src/views/stgl/club/info/index.vue | 33 +- 8 files changed, 1019 insertions(+), 59 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java index 1347b651..b242ec9e 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java @@ -21,16 +21,27 @@ import org.dromara.stgl.domain.bo.StglActivityBo; import org.dromara.stgl.domain.vo.StglActivityVo; import org.dromara.stgl.mapper.StglActivityMapper; import org.dromara.stgl.service.IStglActivityService; +import org.dromara.stgl.utils.AdvisorRoleAssigner; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.dromara.common.json.utils.JsonUtils; +import com.fasterxml.jackson.core.type.TypeReference; +import org.dromara.stgl.domain.StglClub; +import org.dromara.stgl.domain.StglVolunteerTeam; +import org.dromara.stgl.mapper.StglClubMapper; +import org.dromara.stgl.mapper.StglVolunteerTeamMapper; /** * 活动信息Service业务层处理 @@ -38,11 +49,15 @@ import java.util.concurrent.atomic.AtomicInteger; * @author ruoyi * @date 2025-01-07 */ +@Slf4j @RequiredArgsConstructor @Service public class StglActivityServiceImpl extends ServiceImpl implements IStglActivityService { private final StglActivityMapper baseMapper; + private final AdvisorRoleAssigner advisorRoleAssigner; + private final StglClubMapper stglClubMapper; + private final StglVolunteerTeamMapper stglVolunteerTeamMapper; /** * 查询活动信息 @@ -57,6 +72,9 @@ public class StglActivityServiceImpl extends ServiceImpl queryPageList(StglActivityBo bo, PageQuery pageQuery) { + // 应用数据权限控制 + applyDataScope(bo); + // 转换为实体对象,使用自定义SQL查询(包含关联信息) StglActivity activity = BeanUtil.toBean(bo, StglActivity.class); Page result = baseMapper.selectVoPage(pageQuery.build(), activity); @@ -68,6 +86,9 @@ public class StglActivityServiceImpl extends ServiceImpl queryList(StglActivityBo bo) { + // 应用数据权限控制 + applyDataScope(bo); + // 转换为实体对象,使用自定义SQL查询(包含关联信息) StglActivity activity = BeanUtil.toBean(bo, StglActivity.class); List list = baseMapper.selectVoList(activity); @@ -215,6 +236,8 @@ public class StglActivityServiceImpl extends ServiceImpl 0; if (flag) { bo.setId(add.getId()); + // 为指导老师分配角色 + assignAdvisorRoles(add); } return flag; } @@ -227,7 +250,21 @@ public class StglActivityServiceImpl extends ServiceImpl 0; + + // 获取更新前的活动信息,用于比较指导老师变化 + StglActivity oldActivity = null; + if (update.getId() != null) { + oldActivity = baseMapper.selectById(update.getId()); + } + + boolean updateResult = baseMapper.updateById(update) > 0; + + if (updateResult) { + // 处理指导老师角色分配 + handleAdvisorRoleAssignment(oldActivity, update); + } + + return updateResult; } /** @@ -382,4 +419,219 @@ public class StglActivityServiceImpl extends ServiceImpl 0; } + /** + * 为指导老师分配角色 + * + * @param activity 活动信息 + */ + private void assignAdvisorRoles(StglActivity activity) { + try { + List advisorUserIds = extractAdvisorUserIds(activity); + if (!advisorUserIds.isEmpty()) { + advisorRoleAssigner.assignAdvisorRole(advisorUserIds); + log.info("成功为活动 {} 的指导老师分配角色,指导老师数量: {}", activity.getActivityName(), advisorUserIds.size()); + } + } catch (Exception e) { + log.error("为活动 {} 的指导老师分配角色时发生异常: {}", activity.getActivityName(), e.getMessage()); + // 不抛出异常,避免影响主业务流程 + } + } + + /** + * 处理指导老师角色分配(更新时使用) + * + * @param oldActivity 更新前的活动信息 + * @param newActivity 更新后的活动信息 + */ + private void handleAdvisorRoleAssignment(StglActivity oldActivity, StglActivity newActivity) { + try { + // 获取更新前后的指导老师用户ID列表 + List oldAdvisorUserIds = oldActivity != null ? extractAdvisorUserIds(oldActivity) : new ArrayList<>(); + List newAdvisorUserIds = extractAdvisorUserIds(newActivity); + + // 找出新增的指导老师 + List addedAdvisorUserIds = newAdvisorUserIds.stream() + .filter(id -> !oldAdvisorUserIds.contains(id)) + .collect(Collectors.toList()); + + // 找出移除的指导老师 + List removedAdvisorUserIds = oldAdvisorUserIds.stream() + .filter(id -> !newAdvisorUserIds.contains(id)) + .collect(Collectors.toList()); + + // 为新指导老师分配角色 + if (!addedAdvisorUserIds.isEmpty()) { + advisorRoleAssigner.assignAdvisorRole(addedAdvisorUserIds); + log.info("活动 {} 新增指导老师,成功分配角色,数量: {}", newActivity.getActivityName(), addedAdvisorUserIds.size()); + } + + // 移除旧指导老师的角色 + if (!removedAdvisorUserIds.isEmpty()) { + advisorRoleAssigner.removeAdvisorRoles(removedAdvisorUserIds); + log.info("活动 {} 移除指导老师,成功移除角色,数量: {}", newActivity.getActivityName(), removedAdvisorUserIds.size()); + } + } catch (Exception e) { + log.error("处理活动 {} 的指导老师角色分配时发生异常: {}", newActivity.getActivityName(), e.getMessage()); + // 不抛出异常,避免影响主业务流程 + } + } + + /** + * 从活动信息中提取指导老师用户ID列表 + * + * @param activity 活动信息 + * @return 指导老师用户ID列表 + */ + private List extractAdvisorUserIds(StglActivity activity) { + List advisorUserIds = new ArrayList<>(); + + try { + // 从JSON字段读取指导老师数据 + if (StringUtils.isNotBlank(activity.getInstructors())) { + List> instructors = JsonUtils.parseObject(activity.getInstructors(), new TypeReference>>() {}); + for (Map instructor : instructors) { + // 修复:支持嵌套的JSON结构,从teacher子对象中提取userId + Object userIdObj = null; + + // 尝试直接从instructor对象获取userId(兼容旧格式) + if (instructor.containsKey("userId")) { + userIdObj = instructor.get("userId"); + } + // 尝试从teacher子对象获取userId(新格式) + else if (instructor.containsKey("teacher")) { + Object teacherObj = instructor.get("teacher"); + if (teacherObj instanceof Map) { + @SuppressWarnings("unchecked") + Map teacher = (Map) teacherObj; + userIdObj = teacher.get("userId"); + } + } + + if (userIdObj != null) { + Long userId = Long.valueOf(userIdObj.toString()); + if (userId != null) { + advisorUserIds.add(userId); + } + } + } + } + } catch (Exception e) { + log.error("解析活动 {} 的指导老师数据时发生异常: {}", activity.getActivityName(), e.getMessage()); + } + + return advisorUserIds; + } + + /** + * 应用数据权限控制 + * 如果是stgl_president角色,只能查看自己创建的活动 + * 如果是stgl_instructor角色,只能查看自己作为指导老师的社团的活动 + * + * @param bo 查询条件 + */ + private void applyDataScope(StglActivityBo bo) { + try { + // 获取当前登录用户信息 + Long currentUserId = LoginHelper.getUserId(); + if (currentUserId == null) { + return; + } + + Set roleKeys = LoginHelper.getLoginUser().getRolePermission(); + + // 如果是stgl_president角色且不是管理员,只能查看自己创建的活动 + if (roleKeys.contains("stgl_president") && + !roleKeys.contains("admin") && + !roleKeys.contains("common")) { + + // 设置数据权限过滤条件 - 使用params.dataScope方式,与XML中的${activity.params.dataScope}对应 + if (bo.getParams() == null) { + bo.setParams(new HashMap<>()); + } + bo.getParams().put("dataScope", "AND sa.create_by = " + currentUserId); + log.info("应用数据权限:用户 {} 为社长角色,只能查看自己创建的活动", currentUserId); + } + + // 🔥 新增:如果是stgl_instructor角色且不是管理员,只能查看自己作为指导老师的社团的活动 + if (roleKeys.contains("stgl_instructor") && + !roleKeys.contains("admin") && + !roleKeys.contains("common")) { + + // 获取用户作为指导老师的社团ID列表 + List userAdvisorClubIds = getUserAdvisorClubIds(currentUserId); + + if (userAdvisorClubIds.isEmpty()) { + // 如果不是任何社团的指导老师,设置一个不存在的ID,返回空结果 + if (bo.getParams() == null) { + bo.setParams(new HashMap<>()); + } + bo.getParams().put("dataScope", "AND sa.related_club_id = -1"); + log.info("应用数据权限:用户 {} 为指导老师角色,但不是任何社团的指导老师,返回空结果", currentUserId); + } else { + // 设置数据权限过滤条件 - 只能查看自己作为指导老师的社团的活动 + if (bo.getParams() == null) { + bo.setParams(new HashMap<>()); + } + String clubIdsStr = userAdvisorClubIds.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + bo.getParams().put("dataScope", "AND sa.related_club_id IN (" + clubIdsStr + ")"); + log.info("应用数据权限:用户 {} 为指导老师角色,只能查看社团ID为 {} 的活动", currentUserId, clubIdsStr); + } + } + + } catch (Exception e) { + log.error("应用数据权限控制失败:{}", e.getMessage(), e); + } + } + + /** + * 获取用户作为指导老师的社团ID列表 + * + * @param userId 用户ID + * @return 社团ID列表 + */ + private List getUserAdvisorClubIds(Long userId) { + List clubIds = new ArrayList<>(); + + try { + // 从stgl_club表的advisors_json字段查询 + List jsonClubIds = getJsonAdvisorClubIds(userId); + clubIds.addAll(jsonClubIds); + + log.debug("用户 {} 作为指导老师的社团ID列表: {}", userId, clubIds); + + } catch (Exception e) { + log.error("获取用户 {} 作为指导老师的社团ID列表时发生异常: {}", userId, e.getMessage(), e); + } + + return clubIds; + } + + /** + * 从社团表的advisors_json字段获取用户作为指导老师的社团ID列表 + * + * @param userId 用户ID + * @return 社团ID列表 + */ + private List getJsonAdvisorClubIds(Long userId) { + List clubIds = new ArrayList<>(); + + try { + // 查询社团表中advisors_json字段包含该用户的记录 + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.select(StglClub::getId); + lqw.like(StglClub::getAdvisorsJson, userId.toString()); + + List clubs = stglClubMapper.selectList(lqw); + clubIds = clubs.stream() + .map(StglClub::getId) + .collect(Collectors.toList()); + + } catch (Exception e) { + log.error("从社团表获取用户 {} 作为指导老师的社团ID列表时发生异常: {}", userId, e.getMessage(), e); + } + + return clubIds; + } } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java index fe86dd9e..51c81645 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClubServiceImpl.java @@ -17,6 +17,7 @@ import org.dromara.stgl.domain.StglMember; import org.dromara.stgl.mapper.StglClubMapper; import org.dromara.stgl.mapper.StglMemberMapper; import org.dromara.stgl.service.IStglClubService; +import org.dromara.stgl.utils.AdvisorRoleAssigner; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.json.utils.JsonUtils; import com.fasterxml.jackson.core.type.TypeReference; @@ -35,10 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Collection; import java.util.Date; +import java.util.HashSet; import com.baomidou.mybatisplus.core.metadata.IPage; import java.util.Set; @@ -58,6 +61,8 @@ public class StglClubServiceImpl implements IStglClubService { private final StglMemberMapper memberMapper; // 添加学生信息Mapper private final XgStudentMsgMapper studentMsgMapper; + // 添加指导老师角色分配工具 + private final AdvisorRoleAssigner advisorRoleAssigner; /** * 查询社团信息 @@ -143,6 +148,31 @@ public class StglClubServiceImpl implements IStglClubService { bo.setClubIds(userClubIds); } } + + // 🔥 新增:如果是stgl_instructor角色且不是管理员,只能查看自己作为指导老师的社团 + if (roleKeys.contains("stgl_instructor") && + !roleKeys.contains("admin") && + !roleKeys.contains("common")) { + + // 获取用户作为指导老师的社团ID列表 + List userAdvisorClubIds = getUserAdvisorClubIds(currentUserId); + + if (userAdvisorClubIds.isEmpty()) { + // 如果不是任何社团的指导老师,设置一个不存在的ID,返回空结果 + bo.setClubIds(Collections.singletonList(-1L)); + } else { + // 如果之前已经设置了clubIds(比如stgl_president角色),则取交集 + if (CollectionUtils.isNotEmpty(bo.getClubIds())) { + List intersection = bo.getClubIds().stream() + .filter(userAdvisorClubIds::contains) + .collect(Collectors.toList()); + bo.setClubIds(intersection.isEmpty() ? Collections.singletonList(-1L) : intersection); + } else { + // 设置数据权限过滤条件 + bo.setClubIds(userAdvisorClubIds); + } + } + } } catch (Exception e) { // 如果获取权限信息失败,不影响正常查询 log.warn("获取用户权限信息失败: {}", e.getMessage()); @@ -164,7 +194,7 @@ public class StglClubServiceImpl implements IStglClubService { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(StglMember::getMemberType, 1) // 社团成员 .eq(StglMember::getUserId, userId) // 当前用户 - .eq(StglMember::getRoleType, 1) // 社长角色 + .eq(StglMember::getRoleType, "社长") // 社长角色 - 修复:使用字符串而不是数值 .eq(StglMember::getStatus, 2); // 已加入状态 List members = memberMapper.selectList(lqw); @@ -178,6 +208,91 @@ public class StglClubServiceImpl implements IStglClubService { } } + /** + * 获取用户作为指导老师的社团ID列表 + * + * @param userId 用户ID + * @return 社团ID列表 + */ + private List getUserAdvisorClubIds(Long userId) { + if (userId == null) { + return Collections.emptyList(); + } + + try { + // 方法1:从stgl_member表查询(如果指导老师作为成员记录) + LambdaQueryWrapper memberLqw = Wrappers.lambdaQuery(); + memberLqw.eq(StglMember::getMemberType, 1) // 社团成员 + .eq(StglMember::getUserId, userId) // 当前用户 + .eq(StglMember::getRoleType, "指导老师") // 指导老师角色 + .eq(StglMember::getStatus, 2); // 已加入状态 + + List memberAdvisors = memberMapper.selectList(memberLqw); + List memberClubIds = memberAdvisors.stream() + .map(StglMember::getRelationId) + .distinct() + .collect(Collectors.toList()); + + // 方法2:从社团表的advisors_json字段查询(主要方法) + LambdaQueryWrapper clubLqw = Wrappers.lambdaQuery(); + clubLqw.select(StglClub::getId, StglClub::getAdvisorsJson); + clubLqw.isNotNull(StglClub::getAdvisorsJson); + clubLqw.ne(StglClub::getAdvisorsJson, ""); + + List clubs = baseMapper.selectList(clubLqw); + List jsonClubIds = new ArrayList<>(); + + for (StglClub club : clubs) { + try { + if (StringUtils.isNotBlank(club.getAdvisorsJson())) { + List> advisors = JsonUtils.parseObject( + club.getAdvisorsJson(), + new TypeReference>>() {} + ); + + for (Map advisor : advisors) { + Object userIdObj = null; + + // 尝试直接从advisor对象获取userId + if (advisor.containsKey("userId")) { + userIdObj = advisor.get("userId"); + } + // 尝试从teacher子对象获取userId + else if (advisor.containsKey("teacher")) { + Object teacherObj = advisor.get("teacher"); + if (teacherObj instanceof Map) { + @SuppressWarnings("unchecked") + Map teacher = (Map) teacherObj; + userIdObj = teacher.get("userId"); + } + } + + if (userIdObj != null && userId.toString().equals(userIdObj.toString())) { + jsonClubIds.add(club.getId()); + break; // 找到该用户,继续下一个社团 + } + } + } + } catch (Exception e) { + log.warn("解析社团 {} 的指导老师JSON数据失败: {}", club.getId(), e.getMessage()); + } + } + + // 合并两种方法的结果 + Set allClubIds = new HashSet<>(); + allClubIds.addAll(memberClubIds); + allClubIds.addAll(jsonClubIds); + + log.info("用户 {} 作为指导老师的社团ID列表: memberClubIds={}, jsonClubIds={}, 最终结果={}", + userId, memberClubIds, jsonClubIds, allClubIds); + + return new ArrayList<>(allClubIds); + } catch (Exception e) { + log.error("查询用户作为指导老师的社团失败: {}", e.getMessage(), e); + return Collections.emptyList(); + } + } + private LambdaQueryWrapper buildQueryWrapper(StglClubBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); @@ -215,6 +330,8 @@ public class StglClubServiceImpl implements IStglClubService { bo.setId(add.getId()); // 自动创建社长和副社长的成员记录 createLeaderMemberRecords(add); + // 为指导老师分配角色 + assignAdvisorRoles(add); } return flag; } @@ -231,12 +348,20 @@ public class StglClubServiceImpl implements IStglClubService { StglClub update = MapstructUtils.convert(bo, StglClub.class); validEntityBeforeSave(update); + // 获取更新前的社团信息,用于比较指导老师变化 + StglClub oldClub = null; + if (update.getId() != null) { + oldClub = baseMapper.selectById(update.getId()); + } + // 更新社团基本信息 boolean updateResult = baseMapper.updateById(update) > 0; if (updateResult) { // 同步更新副社长的成员记录 syncVicePresidentsInMembers(update); + // 处理指导老师角色分配 + handleAdvisorRoleAssignment(oldClub, update); } return updateResult; @@ -867,7 +992,7 @@ public class StglClubServiceImpl implements IStglClubService { lqw.eq(StglMember::getMemberType, 1); // 社团成员 lqw.eq(StglMember::getRelationId, clubId); // 该社团 lqw.eq(StglMember::getUserId, userId); // 指定用户 - lqw.eq(StglMember::getRoleType, 1); // 社长角色 (1-社长) + lqw.eq(StglMember::getRoleType, "社长"); // 社长角色 - 修复:使用字符串而不是数值 lqw.eq(StglMember::getStatus, 2); // 已加入状态 (2-已加入) StglMember member = memberMapper.selectOne(lqw); @@ -973,4 +1098,114 @@ public class StglClubServiceImpl implements IStglClubService { return true; } + /** + * 为指导老师分配角色 + * + * @param club 社团信息 + */ + private void assignAdvisorRoles(StglClub club) { + try { + List advisorUserIds = extractAdvisorUserIds(club); + if (!advisorUserIds.isEmpty()) { + advisorRoleAssigner.assignAdvisorRole(advisorUserIds); + log.info("成功为社团 {} 的指导老师分配角色,指导老师数量: {}", club.getClubName(), advisorUserIds.size()); + } + } catch (Exception e) { + log.error("为社团 {} 的指导老师分配角色时发生异常", club.getClubName(), e); + // 不抛出异常,避免影响主业务流程 + } + } + + /** + * 处理指导老师角色分配(更新时使用) + * + * @param oldClub 更新前的社团信息 + * @param newClub 更新后的社团信息 + */ + private void handleAdvisorRoleAssignment(StglClub oldClub, StglClub newClub) { + try { + // 获取更新前后的指导老师用户ID列表 + List oldAdvisorUserIds = oldClub != null ? extractAdvisorUserIds(oldClub) : new ArrayList<>(); + List newAdvisorUserIds = extractAdvisorUserIds(newClub); + + // 找出新增的指导老师 + List addedAdvisorUserIds = newAdvisorUserIds.stream() + .filter(id -> !oldAdvisorUserIds.contains(id)) + .collect(Collectors.toList()); + + // 找出移除的指导老师 + List removedAdvisorUserIds = oldAdvisorUserIds.stream() + .filter(id -> !newAdvisorUserIds.contains(id)) + .collect(Collectors.toList()); + + // 为新指导老师分配角色 + if (!addedAdvisorUserIds.isEmpty()) { + advisorRoleAssigner.assignAdvisorRole(addedAdvisorUserIds); + log.info("社团 {} 新增指导老师,成功分配角色,数量: {}", newClub.getClubName(), addedAdvisorUserIds.size()); + } + + // 移除旧指导老师的角色 + if (!removedAdvisorUserIds.isEmpty()) { + advisorRoleAssigner.removeAdvisorRoles(removedAdvisorUserIds); + log.info("社团 {} 移除指导老师,成功移除角色,数量: {}", newClub.getClubName(), removedAdvisorUserIds.size()); + } + } catch (Exception e) { + log.error("处理社团 {} 的指导老师角色分配时发生异常", newClub.getClubName(), e); + // 不抛出异常,避免影响主业务流程 + } + } + + /** + * 从社团信息中提取指导老师用户ID列表 + * + * @param club 社团信息 + * @return 指导老师用户ID列表 + */ + private List extractAdvisorUserIds(StglClub club) { + List advisorUserIds = new ArrayList<>(); + + try { + // 优先从JSON字段读取指导老师数据 + if (StringUtils.isNotBlank(club.getAdvisorsJson())) { + List> advisors = JsonUtils.parseObject(club.getAdvisorsJson(), new TypeReference>>() {}); + for (Map advisor : advisors) { + // 修复:支持嵌套的JSON结构,从teacher子对象中提取userId + Object userIdObj = null; + + // 尝试直接从advisor对象获取userId(兼容旧格式) + if (advisor.containsKey("userId")) { + userIdObj = advisor.get("userId"); + } + // 尝试从teacher子对象获取userId(新格式) + else if (advisor.containsKey("teacher")) { + Object teacherObj = advisor.get("teacher"); + if (teacherObj instanceof Map) { + @SuppressWarnings("unchecked") + Map teacher = (Map) teacherObj; + userIdObj = teacher.get("userId"); + } + } + + if (userIdObj != null) { + Long userId = Long.valueOf(userIdObj.toString()); + if (userId != null) { + advisorUserIds.add(userId); + } + } + } + } + + // 如果没有JSON数据,尝试从单一字段获取 + if (advisorUserIds.isEmpty() && StringUtils.isNotBlank(club.getAdvisorName())) { + // 这里需要根据指导老师姓名查找用户ID,暂时跳过 + // 因为单一字段模式下没有用户ID信息 + log.debug("社团 {} 使用单一指导老师字段,无法获取用户ID", club.getClubName()); + } + } catch (Exception e) { + log.error("解析社团 {} 的指导老师数据时发生异常", club.getClubName(), e); + } + + return advisorUserIds; + } + } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglVolunteerTeamServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglVolunteerTeamServiceImpl.java index 8271cc50..abe764c5 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglVolunteerTeamServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglVolunteerTeamServiceImpl.java @@ -25,13 +25,16 @@ import org.dromara.system.mapper.SysUserMapper; import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.domain.SysUser; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.stgl.utils.AdvisorRoleAssigner; import cn.dev33.satoken.stp.StpUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.extern.slf4j.Slf4j; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Objects; /** * 志愿者队伍Service业务层处理 @@ -39,6 +42,7 @@ import java.util.List; * @author Cursor AI * @date 2025-01-07 */ +@Slf4j @RequiredArgsConstructor @Service public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService { @@ -47,6 +51,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService { private final StglMemberMapper memberMapper; private final XgStudentMsgMapper studentMsgMapper; private final SysUserMapper userMapper; + private final AdvisorRoleAssigner advisorRoleAssigner; /** * 查询志愿者队伍 @@ -148,6 +153,8 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService { bo.setId(add.getId()); // 自动创建队长的成员记录 createLeaderMemberRecord(add); + // 为指导老师分配角色 + assignAdvisorRole(add); } return flag; } @@ -161,12 +168,20 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService { StglVolunteerTeam update = MapstructUtils.convert(bo, StglVolunteerTeam.class); validEntityBeforeSave(update); + // 获取更新前的队伍信息,用于比较指导老师变化 + StglVolunteerTeam oldTeam = null; + if (update.getId() != null) { + oldTeam = baseMapper.selectById(update.getId()); + } + // 更新队伍基本信息 boolean updateResult = baseMapper.updateById(update) > 0; if (updateResult) { // 同步更新队长的成员记录 syncLeaderInMembers(update); + // 处理指导老师角色分配 + handleAdvisorRoleAssignment(oldTeam, update); } return updateResult; @@ -831,4 +846,77 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService { team.setSighOpen(sighOpen); return baseMapper.updateById(team) > 0; } + + /** + * 为指导老师分配角色 + * + * @param team 志愿者队伍信息 + */ + private void assignAdvisorRole(StglVolunteerTeam team) { + try { + Long advisorUserId = extractAdvisorUserId(team); + if (advisorUserId != null) { + advisorRoleAssigner.assignAdvisorRole(advisorUserId); + log.info("成功为志愿者队伍 {} 的指导老师分配角色,指导老师用户ID: {}", team.getTeamName(), advisorUserId); + } + } catch (Exception e) { + log.error("为志愿者队伍 {} 的指导老师分配角色时发生异常", team.getTeamName(), e); + // 不抛出异常,避免影响主业务流程 + } + } + + /** + * 处理指导老师角色分配(更新时使用) + * + * @param oldTeam 更新前的队伍信息 + * @param newTeam 更新后的队伍信息 + */ + private void handleAdvisorRoleAssignment(StglVolunteerTeam oldTeam, StglVolunteerTeam newTeam) { + try { + // 获取更新前后的指导老师用户ID + Long oldAdvisorUserId = oldTeam != null ? extractAdvisorUserId(oldTeam) : null; + Long newAdvisorUserId = extractAdvisorUserId(newTeam); + + // 如果指导老师发生变化 + if (!Objects.equals(oldAdvisorUserId, newAdvisorUserId)) { + // 移除旧指导老师的角色 + if (oldAdvisorUserId != null) { + advisorRoleAssigner.removeAdvisorRole(oldAdvisorUserId); + log.info("志愿者队伍 {} 移除指导老师,成功移除角色,用户ID: {}", newTeam.getTeamName(), oldAdvisorUserId); + } + + // 为新指导老师分配角色 + if (newAdvisorUserId != null) { + advisorRoleAssigner.assignAdvisorRole(newAdvisorUserId); + log.info("志愿者队伍 {} 新增指导老师,成功分配角色,用户ID: {}", newTeam.getTeamName(), newAdvisorUserId); + } + } + } catch (Exception e) { + log.error("处理志愿者队伍 {} 的指导老师角色分配时发生异常", newTeam.getTeamName(), e); + // 不抛出异常,避免影响主业务流程 + } + } + + /** + * 从志愿者队伍信息中提取指导老师用户ID + * + * @param team 志愿者队伍信息 + * @return 指导老师用户ID + */ + private Long extractAdvisorUserId(StglVolunteerTeam team) { + // 志愿者队伍目前使用单一指导老师字段,需要根据指导老师姓名查找用户ID + if (StringUtils.isNotBlank(team.getAdvisorName())) { + try { + // 这里需要根据指导老师姓名查找用户ID + // 由于志愿者队伍表没有直接存储指导老师用户ID,需要额外的查询逻辑 + // 暂时返回null,后续可以根据需要实现 + log.debug("志愿者队伍 {} 的指导老师 {} 需要根据姓名查找用户ID", team.getTeamName(), team.getAdvisorName()); + return null; + } catch (Exception e) { + log.error("查找志愿者队伍 {} 指导老师用户ID时发生异常", team.getTeamName(), e); + } + } + return null; + } + } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml index 3b5b2849..557e0707 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml @@ -99,13 +99,28 @@ END as organization_name, a.instructors, CAST(a.activity_status AS CHAR) as activity_status, - -- 动态计算报名状态:基于当前时间和报名时间范围 + -- 动态计算报名状态:优先根据活动状态,再根据报名时间范围 CAST( CASE - WHEN a.registration_start IS NULL OR a.registration_end IS NULL THEN a.registration_status - WHEN NOW() < a.registration_start THEN '0' - WHEN NOW() >= a.registration_start AND NOW() <= a.registration_end THEN '1' - WHEN NOW() > a.registration_end THEN '2' + -- 首先根据活动状态强制设置(优先级最高) + WHEN a.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) + WHEN a.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) + WHEN a.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) + WHEN a.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + WHEN a.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + WHEN a.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + + -- 然后根据活动状态和报名时间判断 + WHEN a.activity_status = '3' OR a.activity_status = '4' THEN + CASE + WHEN a.registration_start IS NULL OR a.registration_end IS NULL THEN a.registration_status + + = a.registration_start AND NOW() <= a.registration_end THEN '1']]> + a.registration_end THEN '2']]> + ELSE a.registration_status + END + + -- 其他情况保持原状态 ELSE a.registration_status END AS CHAR ) as registration_status, diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml index a3447546..f7a23ce8 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml @@ -56,12 +56,27 @@ sa.poster_url, sa.related_club_id, sa.related_type, sa.organizer_name, sa.organizer_type, sa.organizer_contact, sa.organizer_phone, sa.co_organizers, sa.instructors, sa.registration_type, sa.registration_start, sa.registration_end, - -- 动态计算报名状态:基于当前时间和报名时间范围 + -- 动态计算报名状态:优先根据活动状态,再根据报名时间范围 CASE - WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status - WHEN NOW() < sa.registration_start THEN '0' - WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1' - WHEN NOW() > sa.registration_end THEN '2' + -- 首先根据活动状态强制设置(优先级最高) + WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) + WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) + WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + + -- 然后根据活动状态和报名时间判断 + WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + CASE + WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status + + = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> + sa.registration_end THEN '2']]> + ELSE sa.registration_status + END + + -- 其他情况保持原状态 ELSE sa.registration_status END AS registration_status, sa.max_participants, sa.max_teams, sa.team_size_min, sa.team_size_max, sa.need_approval, @@ -120,10 +135,25 @@ AND ( CASE - WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status - WHEN NOW() < sa.registration_start THEN '0' - WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1' - WHEN NOW() > sa.registration_end THEN '2' + -- 首先根据活动状态强制设置(优先级最高) + WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) + WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) + WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + + -- 然后根据活动状态和报名时间判断 + WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + CASE + WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status + + = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> + sa.registration_end THEN '2']]> + ELSE sa.registration_status + END + + -- 其他情况保持原状态 ELSE sa.registration_status END ) = #{registrationStatus} @@ -187,10 +217,25 @@ AND ( CASE - WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status - WHEN NOW() < sa.registration_start THEN '0' - WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1' - WHEN NOW() > sa.registration_end THEN '2' + -- 首先根据活动状态强制设置(优先级最高) + WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) + WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) + WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + + -- 然后根据活动状态和报名时间判断 + WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + CASE + WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status + + = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> + sa.registration_end THEN '2']]> + ELSE sa.registration_status + END + + -- 其他情况保持原状态 ELSE sa.registration_status END ) = #{activity.registrationStatus} @@ -199,10 +244,25 @@ AND ( CASE - WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status - WHEN NOW() < sa.registration_start THEN '0' - WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1' - WHEN NOW() > sa.registration_end THEN '2' + -- 首先根据活动状态强制设置(优先级最高) + WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定) + WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定) + WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定) + WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定) + WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定) + WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定) + + -- 然后根据活动状态和报名时间判断 + WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN + CASE + WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status + + = sa.registration_start AND NOW() <= sa.registration_end THEN '1']]> + sa.registration_end THEN '2']]> + ELSE sa.registration_status + END + + -- 其他情况保持原状态 ELSE sa.registration_status END ) IN diff --git a/web/src/api/stgl/activity/types.ts b/web/src/api/stgl/activity/types.ts index 0517862a..2d52e350 100644 --- a/web/src/api/stgl/activity/types.ts +++ b/web/src/api/stgl/activity/types.ts @@ -478,6 +478,11 @@ export interface StglActivityQuery extends PageQuery { */ isFinished?: number; + /** + * 创建者ID + */ + createBy?: string | number; + /** * 日期范围参数 */ diff --git a/web/src/views/stgl/activity/index.vue b/web/src/views/stgl/activity/index.vue index d967e7f0..a25ab5cd 100644 --- a/web/src/views/stgl/activity/index.vue +++ b/web/src/views/stgl/activity/index.vue @@ -84,19 +84,28 @@ 新增 - - 修改 - + + 修改 + + + 提交 + + + 审批 + + + 取消活动 + 复制 删除 @@ -195,7 +204,7 @@ v-hasPermi="['stgl:activity:query']" > - + + + + + + + + + + - + - + - - - - - - - - + @@ -519,6 +554,37 @@ :activity-name="selectedActivity?.activityName" ref="membersDialogRef" /> + + + + + + + + + + 通过 + 驳回 + + + + + + + + @@ -545,10 +611,12 @@ import ActivityDetailView from './components/ActivityDetailView.vue'; import ActivityMembersDialog from './components/ActivityMembersDialog.vue'; import ActivityRegistrationView from './components/ActivityRegistrationView.vue'; // Added import import { AdvisorVO } from '@/components/AdvisorManager/index.vue'; +import { useUserStore } from '@/store/modules/user'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const route = useRoute(); const router = useRouter(); +const userStore = useUserStore(); const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_registration_status, sys_yes_no } = toRefs( proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'sys_yes_no') ); @@ -570,6 +638,7 @@ const queryFormRef = ref(); const activityFormRef = ref(); const copyFormRef = ref(); const summaryFormRef = ref(); +const approveFormRef = ref(); const membersDialogRef = ref(); const dialog = reactive({ @@ -597,8 +666,26 @@ const membersDialog = reactive({ title: '' }); +// 审批对话框 +const approveDialog = reactive({ + visible: false, + title: '' +}); + +// 审批表单 +const approveForm = reactive({ + id: undefined as string | number | undefined, + activityName: '', + approvalResult: 'approve' as 'approve' | 'reject', + rejectReason: '' +}); + const copyLoading = ref(false); const summaryLoading = ref(false); +const approveLoading = ref(false); + +// 关联社团禁用状态控制 +const isRelatedClubDisabled = ref(false); const initFormData: StglActivityForm = { id: undefined, @@ -631,7 +718,7 @@ const initFormData: StglActivityForm = { teamSizeMin: undefined, teamSizeMax: undefined, needApproval: 1, - activityStatus: '1', + activityStatus: '0', isFinished: 0, summary: undefined, achievements: undefined, @@ -668,8 +755,7 @@ const data = reactive { queryParams.value.endTime = undefined; } + // 如果用户是社长角色,只能查看自己创建的活动 + if (proxy?.$auth.hasRole('stgl_president')) { + queryParams.value.createBy = userStore.userId; + } + try { const res = await listStglActivity(queryParams.value); activityList.value = res.rows; @@ -781,6 +872,15 @@ const reset = () => { form.value.instructorList = []; form.value.instructors = undefined; + // 重置关联社团禁用状态 + isRelatedClubDisabled.value = false; + + // 重置审批表单 + approveForm.id = undefined; + approveForm.activityName = ''; + approveForm.approvalResult = 'approve'; + approveForm.rejectReason = ''; + // 更新formKey以强制重新渲染组件 formKey.value++; @@ -839,6 +939,9 @@ const handleAdd = () => { form.value.relatedClubId = queryParams.value.relatedClub.relatedClubId; form.value.relatedType = queryParams.value.relatedClub.relatedType; + // 设置关联社团字段为禁用状态(不允许修改) + isRelatedClubDisabled.value = true; + console.log('新增活动 - 自动填充关联组织信息:', { relatedType: queryParams.value.relatedClub.relatedType, relatedClubId: queryParams.value.relatedClub.relatedClubId, @@ -851,6 +954,9 @@ const handleAdd = () => { formRelatedClubId: form.value.relatedClubId, formRelatedType: form.value.relatedType }); + } else { + // 没有关联组织时,允许编辑关联社团字段 + isRelatedClubDisabled.value = false; } dialog.visible = true; @@ -874,8 +980,8 @@ const handleUpdate = async (row?: StglActivityVO) => { // 先获取活动数据检查状态 const res = await getStglActivity(_id); - // 检查活动状态是否允许修改(2:报名中、3:报名结束、4:进行中、5:已结束 不允许修改) - if (['2', '3', '4', '5'].includes(res.data.activityStatus)) { + // 检查活动状态是否允许修改(只有草稿0、已驳回7、已取消6状态允许修改) + if (!['0', '7', '6'].includes(res.data.activityStatus)) { const statusDict = stgl_activity_status.value.find((dict: any) => dict.value === res.data.activityStatus); const statusName = statusDict ? statusDict.label : '当前状态'; proxy?.$modal.msgWarning(`活动状态为"${statusName}",不允许修改`); @@ -896,6 +1002,15 @@ const handleUpdate = async (row?: StglActivityVO) => { relatedType: res.data.relatedType, uniqueKey: `${res.data.relatedType}-${res.data.relatedClubId}` }; + + // 修改时,如果是从URL参数进入的,则禁用关联社团字段 + if (queryParams.value.relatedClub && queryParams.value.relatedClub.relatedClubId === res.data.relatedClubId) { + isRelatedClubDisabled.value = true; + } else { + isRelatedClubDisabled.value = false; + } + } else { + isRelatedClubDisabled.value = false; } // 处理承办方数据 @@ -945,6 +1060,11 @@ const submitForm = () => { // 准备提交的数据 const submitData = { ...form.value } as any; + // 新增时自动设置活动状态为0(草稿) + if (!submitData.id) { + submitData.activityStatus = '0'; + } + // 处理关联社团数据 if (submitData.relatedClub) { submitData.relatedClubId = submitData.relatedClub.relatedClubId; @@ -1032,23 +1152,23 @@ const handleDelete = async (row?: StglActivityVO) => { const _ids = row?.id || ids.value; let confirmMessage = ''; - // 检查删除权限 - 只有筹备中(状态1)的活动可以删除 + // 检查删除权限 - 只有草稿(状态0)和已驳回(状态7)的活动可以删除 if (row) { - if (row.activityStatus !== '1') { + if (!['0', '7'].includes(row.activityStatus)) { const statusDict = stgl_activity_status.value.find((dict: any) => dict.value === row.activityStatus); const statusName = statusDict ? statusDict.label : '当前状态'; - proxy?.$modal.msgWarning(`活动状态为"${statusName}",只有筹备中的活动可以删除`); + proxy?.$modal.msgWarning(`活动状态为"${statusName}",只有草稿和已驳回状态的活动可以删除`); return; } confirmMessage = `是否确认删除活动"${row.activityName}"?`; } else { const selectedItems = activityList.value.filter((item) => ids.value.includes(item.id)); - // 检查选中的活动是否都是筹备中状态 - const nonDeletableItems = selectedItems.filter((item) => item.activityStatus !== '1'); + // 检查选中的活动是否都是草稿或已驳回状态 + const nonDeletableItems = selectedItems.filter((item) => !['0', '7'].includes(item.activityStatus)); if (nonDeletableItems.length > 0) { const names = nonDeletableItems.map((item) => item.activityName).join('、'); - proxy?.$modal.msgWarning(`活动"${names}"不是筹备中状态,只有筹备中的活动可以删除`); + proxy?.$modal.msgWarning(`活动"${names}"不是草稿或已驳回状态,只有草稿和已驳回状态的活动可以删除`); return; } @@ -1273,6 +1393,127 @@ const handleMembersReview = (row: StglActivityVO) => { membersDialog.title = `${row.activityName}活动报名管理`; }; +/** 提交按钮操作 */ +const handleSubmit = async (row?: StglActivityVO) => { + const activity = row || selectedActivity.value; + if (!activity) return; + + try { + await proxy?.$modal.confirm(`是否确认提交活动"${activity.activityName}"?提交后将进入审批流程。`); + + // 调用后端接口更新活动状态为2(待审批) + // 传递完整的活动数据,避免后端验证失败 + await updateStglActivity({ + ...activity, + activityStatus: '2' + }); + + proxy?.$modal.msgSuccess('提交成功'); + await getList(); + } catch (error) { + if (error !== 'cancel') { + console.error('提交失败:', error); + proxy?.$modal.msgError('提交失败'); + } + } +}; + +/** 审批按钮操作 */ +const handleApprove = async (row?: StglActivityVO) => { + const activity = row || selectedActivity.value; + if (!activity) return; + + // 显示审批对话框 + approveDialog.visible = true; + approveDialog.title = `审批活动 - ${activity.activityName}`; + approveForm.id = activity.id; + approveForm.activityName = activity.activityName || ''; + approveForm.approvalResult = 'approve'; + approveForm.rejectReason = ''; +}; + +/** 取消活动按钮操作 */ +const handleCancel = async (row?: StglActivityVO) => { + const activity = row || selectedActivity.value; + if (!activity) return; + + try { + await proxy?.$modal.confirm(`是否确认取消活动"${activity.activityName}"?取消后将无法恢复。`); + + // 调用后端接口更新活动状态为6(已取消) + // 传递完整的活动数据,避免后端验证失败 + await updateStglActivity({ + ...activity, + activityStatus: '6' + }); + + proxy?.$modal.msgSuccess('取消活动成功'); + await getList(); + } catch (error) { + if (error !== 'cancel') { + console.error('取消活动失败:', error); + proxy?.$modal.msgError('取消活动失败'); + } + } +}; + +/** 确认审批 */ +const confirmApprove = async () => { + if (!approveFormRef.value) return; + + approveLoading.value = true; + try { + const activity = activityList.value.find(item => item.id === approveForm.id); + if (!activity) { + proxy?.$modal.msgError('活动信息不存在'); + return; + } + + let newStatus: string; + let successMessage: string; + + if (approveForm.approvalResult === 'approve') { + // 审批通过,根据时间比较确定状态 + const now = new Date(); + const startTime = new Date(activity.startTime); + const endTime = new Date(activity.endTime); + + if (now < startTime) { + newStatus = '1'; // 未开始 + } else if (now >= startTime && now <= endTime) { + newStatus = '4'; // 进行中 + } else { + newStatus = '5'; // 已结束 + } + successMessage = '审批通过'; + } else { + // 审批驳回 + if (!approveForm.rejectReason.trim()) { + proxy?.$modal.msgWarning('请输入驳回原因'); + return; + } + newStatus = '7'; // 已驳回 + successMessage = '审批驳回'; + } + + // 调用后端接口更新活动状态 + // 传递完整的活动数据,避免后端验证失败 + await updateStglActivity({ + ...activity, + activityStatus: newStatus + }); + + proxy?.$modal.msgSuccess(successMessage); + approveDialog.visible = false; + await getList(); + } catch (error) { + console.error('审批失败:', error); + proxy?.$modal.msgError('审批失败'); + } finally { + approveLoading.value = false; + } +}; + /** 格式化日期 */ const formatDate = (dateStr: string): string => { if (!dateStr) return ''; @@ -1303,6 +1544,51 @@ const getAdvisorNames = (activity: StglActivityVO): string[] => { return []; }; +// 检查是否拥有指导老师或管理员权限 +const hasInstructorPermission = computed(() => { + return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_stgl_admin', 'superadmin']); +}); + +// 检查当前用户是否为指定活动的指导老师 +const isCurrentUserAdvisor = (activity: StglActivityVO): boolean => { + if (!activity.instructors) return false; + + try { + const advisors = JSON.parse(activity.instructors); + if (Array.isArray(advisors)) { + return advisors.some((advisor: any) => { + // 支持两种数据结构:直接包含userId或嵌套在teacher对象中 + const userId = advisor.userId || advisor.teacher?.userId; + return userId && userId.toString() === userStore.userId?.toString(); + }); + } + } catch (error) { + console.warn('解析活动指导老师数据失败:', error); + } + + return false; +}; + +// 检查当前用户是否有活动操作权限(指导老师只能操作自己指导的活动) +const hasActivityOperationPermission = (activity: StglActivityVO): boolean => { + // 管理员有所有权限 + if (proxy?.$auth.hasRole('admin') || proxy?.$auth.hasRole('common')) { + return true; + } + + // 指导老师只能操作自己指导的活动 + if (proxy?.$auth.hasRole('stgl_instructor')) { + return isCurrentUserAdvisor(activity); + } + + // 社长只能操作自己创建的活动 + if (proxy?.$auth.hasRole('stgl_president')) { + return activity.createBy === userStore.userId; + } + + return false; +}; + onMounted(() => { initUrlParams(); getList(); diff --git a/web/src/views/stgl/club/info/index.vue b/web/src/views/stgl/club/info/index.vue index ff2bb4b6..30626818 100644 --- a/web/src/views/stgl/club/info/index.vue +++ b/web/src/views/stgl/club/info/index.vue @@ -26,6 +26,8 @@ + + @@ -619,6 +621,18 @@