瀏覽代碼

Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project

zlwu 1 年之前
父節點
當前提交
94fdca480f

+ 253 - 1
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<StglActivityMapper, StglActivity> 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<StglActivityMapper, Stg
      */
     @Override
     public TableDataInfo<StglActivityVo> queryPageList(StglActivityBo bo, PageQuery pageQuery) {
+        // 应用数据权限控制
+        applyDataScope(bo);
+        
         // 转换为实体对象,使用自定义SQL查询(包含关联信息)
         StglActivity activity = BeanUtil.toBean(bo, StglActivity.class);
         Page<StglActivityVo> result = baseMapper.selectVoPage(pageQuery.build(), activity);
@@ -68,6 +86,9 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
      */
     @Override
     public List<StglActivityVo> queryList(StglActivityBo bo) {
+        // 应用数据权限控制
+        applyDataScope(bo);
+        
         // 转换为实体对象,使用自定义SQL查询(包含关联信息)
         StglActivity activity = BeanUtil.toBean(bo, StglActivity.class);
         List<StglActivityVo> list = baseMapper.selectVoList(activity);
@@ -215,6 +236,8 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
         boolean flag = baseMapper.insert(add) > 0;
         if (flag) {
             bo.setId(add.getId());
+            // 为指导老师分配角色
+            assignAdvisorRoles(add);
         }
         return flag;
     }
@@ -227,7 +250,21 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
     public Boolean updateByBo(StglActivityBo bo) {
         StglActivity update = MapstructUtils.convert(bo, StglActivity.class);
         validEntityBeforeSave(update);
-        return baseMapper.updateById(update) > 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<StglActivityMapper, Stg
         return baseMapper.update(null, wrapper) > 0;
     }
 
+    /**
+     * 为指导老师分配角色
+     *
+     * @param activity 活动信息
+     */
+    private void assignAdvisorRoles(StglActivity activity) {
+        try {
+            List<Long> 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<Long> oldAdvisorUserIds = oldActivity != null ? extractAdvisorUserIds(oldActivity) : new ArrayList<>();
+            List<Long> newAdvisorUserIds = extractAdvisorUserIds(newActivity);
+
+            // 找出新增的指导老师
+            List<Long> addedAdvisorUserIds = newAdvisorUserIds.stream()
+                .filter(id -> !oldAdvisorUserIds.contains(id))
+                .collect(Collectors.toList());
+
+            // 找出移除的指导老师
+            List<Long> 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<Long> extractAdvisorUserIds(StglActivity activity) {
+        List<Long> advisorUserIds = new ArrayList<>();
+
+        try {
+            // 从JSON字段读取指导老师数据
+            if (StringUtils.isNotBlank(activity.getInstructors())) {
+                List<Map<String, Object>> instructors = JsonUtils.parseObject(activity.getInstructors(), new TypeReference<List<Map<String, Object>>>() {});
+                for (Map<String, Object> 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<String, Object> teacher = (Map<String, Object>) 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<String> 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<Long> 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<Long> getUserAdvisorClubIds(Long userId) {
+        List<Long> clubIds = new ArrayList<>();
+        
+        try {
+            // 从stgl_club表的advisors_json字段查询
+            List<Long> 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<Long> getJsonAdvisorClubIds(Long userId) {
+        List<Long> clubIds = new ArrayList<>();
+        
+        try {
+            // 查询社团表中advisors_json字段包含该用户的记录
+            LambdaQueryWrapper<StglClub> lqw = new LambdaQueryWrapper<>();
+            lqw.select(StglClub::getId);
+            lqw.like(StglClub::getAdvisorsJson, userId.toString());
+            
+            List<StglClub> 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;
+    }
 } 

+ 237 - 2
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<Long> userAdvisorClubIds = getUserAdvisorClubIds(currentUserId);
+                
+                if (userAdvisorClubIds.isEmpty()) {
+                    // 如果不是任何社团的指导老师,设置一个不存在的ID,返回空结果
+                    bo.setClubIds(Collections.singletonList(-1L));
+                } else {
+                    // 如果之前已经设置了clubIds(比如stgl_president角色),则取交集
+                    if (CollectionUtils.isNotEmpty(bo.getClubIds())) {
+                        List<Long> 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<StglMember> lqw = Wrappers.lambdaQuery();
             lqw.eq(StglMember::getMemberType, 1)      // 社团成员
                .eq(StglMember::getUserId, userId)      // 当前用户
-               .eq(StglMember::getRoleType, 1)        // 社长角色
+               .eq(StglMember::getRoleType, "社长")    // 社长角色 - 修复:使用字符串而不是数值
                .eq(StglMember::getStatus, 2);         // 已加入状态
             
             List<StglMember> members = memberMapper.selectList(lqw);
@@ -178,6 +208,91 @@ public class StglClubServiceImpl implements IStglClubService {
         }
     }
 
+    /**
+     * 获取用户作为指导老师的社团ID列表
+     *
+     * @param userId 用户ID
+     * @return 社团ID列表
+     */
+    private List<Long> getUserAdvisorClubIds(Long userId) {
+        if (userId == null) {
+            return Collections.emptyList();
+        }
+
+        try {
+            // 方法1:从stgl_member表查询(如果指导老师作为成员记录)
+            LambdaQueryWrapper<StglMember> memberLqw = Wrappers.lambdaQuery();
+            memberLqw.eq(StglMember::getMemberType, 1)      // 社团成员
+                     .eq(StglMember::getUserId, userId)      // 当前用户
+                     .eq(StglMember::getRoleType, "指导老师") // 指导老师角色
+                     .eq(StglMember::getStatus, 2);         // 已加入状态
+            
+            List<StglMember> memberAdvisors = memberMapper.selectList(memberLqw);
+            List<Long> memberClubIds = memberAdvisors.stream()
+                                                    .map(StglMember::getRelationId)
+                                                    .distinct()
+                                                    .collect(Collectors.toList());
+            
+            // 方法2:从社团表的advisors_json字段查询(主要方法)
+            LambdaQueryWrapper<StglClub> clubLqw = Wrappers.lambdaQuery();
+            clubLqw.select(StglClub::getId, StglClub::getAdvisorsJson);
+            clubLqw.isNotNull(StglClub::getAdvisorsJson);
+            clubLqw.ne(StglClub::getAdvisorsJson, "");
+            
+            List<StglClub> clubs = baseMapper.selectList(clubLqw);
+            List<Long> jsonClubIds = new ArrayList<>();
+            
+            for (StglClub club : clubs) {
+                try {
+                    if (StringUtils.isNotBlank(club.getAdvisorsJson())) {
+                        List<Map<String, Object>> advisors = JsonUtils.parseObject(
+                            club.getAdvisorsJson(), 
+                            new TypeReference<List<Map<String, Object>>>() {}
+                        );
+                        
+                        for (Map<String, Object> 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<String, Object> teacher = (Map<String, Object>) 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<Long> 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<StglClub> buildQueryWrapper(StglClubBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<StglClub> 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<Long> 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<Long> oldAdvisorUserIds = oldClub != null ? extractAdvisorUserIds(oldClub) : new ArrayList<>();
+            List<Long> newAdvisorUserIds = extractAdvisorUserIds(newClub);
+
+            // 找出新增的指导老师
+            List<Long> addedAdvisorUserIds = newAdvisorUserIds.stream()
+                .filter(id -> !oldAdvisorUserIds.contains(id))
+                .collect(Collectors.toList());
+
+            // 找出移除的指导老师
+            List<Long> 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<Long> extractAdvisorUserIds(StglClub club) {
+        List<Long> advisorUserIds = new ArrayList<>();
+
+        try {
+            // 优先从JSON字段读取指导老师数据
+            if (StringUtils.isNotBlank(club.getAdvisorsJson())) {
+                List<Map<String, Object>> advisors = JsonUtils.parseObject(club.getAdvisorsJson(), new TypeReference<List<Map<String, Object>>>() {});
+                for (Map<String, Object> 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<String, Object> teacher = (Map<String, Object>) 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;
+    }
+
 } 

+ 88 - 0
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;
+    }
+
 } 

+ 20 - 5
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() &lt; a.registration_start THEN '0'
-                    WHEN NOW() &gt;= a.registration_start AND NOW() &lt;= a.registration_end THEN '1'  
-                    WHEN NOW() &gt; 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
+                            <![CDATA[WHEN NOW() < a.registration_start THEN '0']]>
+                            <![CDATA[WHEN NOW() >= a.registration_start AND NOW() <= a.registration_end THEN '1']]>
+                            <![CDATA[WHEN NOW() > a.registration_end THEN '2']]>
+                            ELSE a.registration_status
+                        END
+                    
+                    -- 其他情况保持原状态
                     ELSE a.registration_status
                 END AS CHAR
             ) as registration_status,

+ 77 - 17
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() &lt; sa.registration_start THEN '0'
-                WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'  
-                WHEN NOW() &gt; 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
+                        <![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
+                        <![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
+                        <![CDATA[WHEN NOW() > 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 @@
             <if test="registrationStatus != null">
                 AND (
                     CASE 
-                        WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
-                        WHEN NOW() &lt; sa.registration_start THEN '0'
-                        WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'  
-                        WHEN NOW() &gt; 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
+                                <![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
+                                <![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
+                                <![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
+                                ELSE sa.registration_status
+                            END
+                        
+                        -- 其他情况保持原状态
                         ELSE sa.registration_status
                     END
                 ) = #{registrationStatus}
@@ -187,10 +217,25 @@
             <if test="activity.registrationStatus != null and activity.registrationStatus != ''">
                 AND (
                     CASE 
-                        WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
-                        WHEN NOW() &lt; sa.registration_start THEN '0'
-                        WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'  
-                        WHEN NOW() &gt; 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
+                                <![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
+                                <![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
+                                <![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
+                                ELSE sa.registration_status
+                            END
+                        
+                        -- 其他情况保持原状态
                         ELSE sa.registration_status
                     END
                 ) = #{activity.registrationStatus}
@@ -199,10 +244,25 @@
             <if test="activity.params != null and activity.params.registrationStatusList != null and activity.params.registrationStatusList.size() > 0">
                 AND (
                     CASE 
-                        WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
-                        WHEN NOW() &lt; sa.registration_start THEN '0'
-                        WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'  
-                        WHEN NOW() &gt; 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
+                                <![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
+                                <![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
+                                <![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
+                                ELSE sa.registration_status
+                            END
+                        
+                        -- 其他情况保持原状态
                         ELSE sa.registration_status
                     END
                 ) IN

+ 5 - 0
web/src/api/stgl/activity/types.ts

@@ -478,6 +478,11 @@ export interface StglActivityQuery extends PageQuery {
    */
   isFinished?: number;
 
+  /**
+   * 创建者ID
+   */
+  createBy?: string | number;
+
   /**
    * 日期范围参数
    */

+ 313 - 27
web/src/views/stgl/activity/index.vue

@@ -84,19 +84,28 @@
           <el-col :span="1.5">
             <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activity:add']">新增</el-button>
           </el-col>
-          <el-col :span="1.5" v-if="!single && selectedActivity && !['2', '3', '4', '5'].includes(selectedActivity.activityStatus)">
-            <el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
-          </el-col>
+                      <el-col :span="1.5" v-if="!single && selectedActivity && ['0', '7', '6'].includes(selectedActivity.activityStatus) && hasActivityOperationPermission(selectedActivity)">
+              <el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
+            </el-col>
+            <el-col :span="1.5" v-if="!single && selectedActivity && selectedActivity.activityStatus === '0' && hasActivityOperationPermission(selectedActivity)">
+              <el-button type="success" icon="Upload" @click="handleSubmit()" v-hasPermi="['stgl:activity:edit']">提交</el-button>
+            </el-col>
+            <el-col :span="1.5" v-if="!single && selectedActivity && selectedActivity.activityStatus === '2' && hasActivityOperationPermission(selectedActivity)">
+              <el-button type="warning" icon="Check" @click="handleApprove()" v-hasPermi="['stgl:activity:approve']">审批</el-button>
+            </el-col>
+            <el-col :span="1.5" v-if="!single && selectedActivity && ['3', '4'].includes(selectedActivity.activityStatus) && hasActivityOperationPermission(selectedActivity)">
+              <el-button type="danger" icon="Close" @click="handleCancel()" v-hasPermi="['stgl:activity:edit']">取消活动</el-button>
+            </el-col>
           <el-col :span="1.5">
             <el-button icon="CopyDocument" :disabled="single" @click="handleCopy()" v-hasPermi="['stgl:activity:add']">复制</el-button>
           </el-col>
           <el-col :span="1.5">
             <el-button
               icon="Delete"
-              :disabled="multiple || (selectedActivity && selectedActivity.activityStatus !== '1')"
+              :disabled="multiple || !['0', '7'].includes(selectedActivity?.activityStatus || '')"
               @click="handleDelete()"
               v-hasPermi="['stgl:activity:remove']"
-              :title="selectedActivity && selectedActivity.activityStatus !== '1' ? '只有筹备中的活动可以删除' : ''"
+              :title="selectedActivity && !['0', '7'].includes(selectedActivity.activityStatus) ? '只有草稿和已驳回状态的活动可以删除' : ''"
               >删除</el-button
             >
           </el-col>
@@ -195,7 +204,7 @@
                 v-hasPermi="['stgl:activity:query']"
               ></el-button>
             </el-tooltip>
-            <el-tooltip content="修改" placement="top" v-if="!['2', '3', '4', '5'].includes(scope.row.activityStatus)">
+            <el-tooltip content="修改" placement="top" v-if="['0', '7', '6'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
               <el-button
                 link
                 type="primary"
@@ -206,6 +215,39 @@
                 v-hasPermi="['stgl:activity:edit']"
               ></el-button>
             </el-tooltip>
+            <el-tooltip content="提交" placement="top" v-if="scope.row.activityStatus === '0' && hasActivityOperationPermission(scope.row)">
+              <el-button
+                link
+                type="success"
+                icon="Upload"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleSubmit(scope.row)"
+                v-hasPermi="['stgl:activity:edit']"
+              ></el-button>
+            </el-tooltip>
+            <el-tooltip content="审批" placement="top" v-if="scope.row.activityStatus === '2' && hasActivityOperationPermission(scope.row)">
+              <el-button
+                link
+                type="warning"
+                icon="Check"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleApprove(scope.row)"
+                v-hasPermi="['stgl:activity:approve']"
+              ></el-button>
+            </el-tooltip>
+            <el-tooltip content="取消活动" placement="top" v-if="['3', '4'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
+              <el-button
+                link
+                type="danger"
+                icon="Close"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleCancel(scope.row)"
+                v-hasPermi="['stgl:activity:edit']"
+              ></el-button>
+            </el-tooltip>
             <el-tooltip content="复制" placement="top">
               <el-button
                 link
@@ -217,7 +259,7 @@
                 v-hasPermi="['stgl:activity:add']"
               ></el-button>
             </el-tooltip>
-            <el-tooltip content="活动总结" placement="top" v-if="shouldShowSummaryButton(scope.row)">
+            <el-tooltip content="活动总结" placement="top" v-if="shouldShowSummaryButton(scope.row) && hasActivityOperationPermission(scope.row)">
               <el-button
                 link
                 type="success"
@@ -239,7 +281,7 @@
                 v-hasPermi="['stgl:activity:query']"
               ></el-button>
             </el-tooltip>
-            <el-tooltip content="删除" placement="top" v-if="scope.row.activityStatus === '1'">
+            <el-tooltip content="删除" placement="top" v-if="['0', '7'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
               <el-button
                 link
                 type="danger"
@@ -315,16 +357,9 @@
               </el-col>
             </el-row>
             <el-row :gutter="20">
-              <el-col :span="12">
-                <el-form-item label="活动状态" prop="activityStatus">
-                  <el-select v-model="form.activityStatus" placeholder="请选择活动状态" style="width: 100%">
-                    <el-option v-for="dict in stgl_activity_status" :key="dict.value" :label="dict.label" :value="dict.value" />
-                  </el-select>
-                </el-form-item>
-              </el-col>
               <el-col :span="12">
                 <el-form-item label="关联社团" prop="relatedClubId">
-                  <ClubSelector v-model="form.relatedClub" @change="handleRelatedClubChange" />
+                  <ClubSelector v-model="form.relatedClub" @change="handleRelatedClubChange" :disabled="isRelatedClubDisabled" />
                 </el-form-item>
               </el-col>
             </el-row>
@@ -519,6 +554,37 @@
       :activity-name="selectedActivity?.activityName"
       ref="membersDialogRef"
     />
+
+    <!-- 审批对话框 -->
+    <el-dialog v-model="approveDialog.visible" title="审批活动" width="500px" append-to-body>
+      <el-form ref="approveFormRef" :model="approveForm" label-width="100px">
+        <el-form-item label="活动名称">
+          <el-input v-model="approveForm.activityName" readonly />
+        </el-form-item>
+        <el-form-item label="审批结果" prop="approvalResult">
+          <el-radio-group v-model="approveForm.approvalResult">
+            <el-radio label="approve">通过</el-radio>
+            <el-radio label="reject">驳回</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="驳回原因" prop="rejectReason" v-if="approveForm.approvalResult === 'reject'">
+          <el-input 
+            v-model="approveForm.rejectReason" 
+            type="textarea" 
+            placeholder="请输入驳回原因" 
+            :rows="3"
+            :maxlength="200"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="approveDialog.visible = false">取消</el-button>
+          <el-button type="primary" @click="confirmApprove" :loading="approveLoading">确认审批</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -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<any>(
   proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'sys_yes_no')
 );
@@ -570,6 +638,7 @@ const queryFormRef = ref<ElFormInstance>();
 const activityFormRef = ref<ElFormInstance>();
 const copyFormRef = ref<ElFormInstance>();
 const summaryFormRef = ref<ElFormInstance>();
+const approveFormRef = ref<ElFormInstance>();
 const membersDialogRef = ref();
 
 const dialog = reactive<DialogOption>({
@@ -597,8 +666,26 @@ const membersDialog = reactive<DialogOption>({
   title: ''
 });
 
+// 审批对话框
+const approveDialog = reactive<DialogOption>({
+  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<PageData<StglActivityForm, StglActivityQuery & { relatedCl
     activityType: [{ required: true, message: '活动类型不能为空', trigger: 'change' }],
     startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
     endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
-    organizerName: [{ required: true, message: '主办方名称不能为空', trigger: 'blur' }],
-    activityStatus: [{ required: true, message: '活动状态不能为空', trigger: 'change' }]
+    organizerName: [{ required: true, message: '主办方名称不能为空', trigger: 'blur' }]
   }
 });
 
@@ -753,6 +839,11 @@ const getList = async () => {
     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();

+ 26 - 7
web/src/views/stgl/club/info/index.vue

@@ -26,6 +26,8 @@
               </el-col>
             </el-row>
 
+
+
             <el-row>
               <el-col :span="24" class="search-btn-container">
                 <el-form-item>
@@ -619,6 +621,18 @@
 </template>
 
 <script setup name="StglClub" lang="ts">
+/**
+ * 社团信息管理
+ * 
+ * 权限控制说明:
+ * - stgl_president角色:可以操作自己担任社长的社团(无论是否为创建者)
+ * - stgl_instructor角色:指导老师,有部分字段的只读权限
+ * - 管理员角色:可以操作所有社团
+ * 
+ * 业务逻辑:
+ * 管理员创建社团后选择社长,被选中的社长(stgl_president角色)
+ * 需要能够看到和操作自己担任社长的社团,不再受限于创建者身份
+ */
 import { useRouter } from 'vue-router';
 import {
   listStglClub,
@@ -646,9 +660,15 @@ const { stgl_club_type, sys_normal_disable, stgl_funding_source, sys_yes_no } =
 
 // 检查是否拥有指导老师或管理员权限
 const hasInstructorPermission = computed(() => {
-  return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_admin', 'superadmin']);
+  return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_stgl_admin', 'superadmin']);
 });
 
+
+
+// 社长权限控制 - 检查用户是否为各社团的社长(无论是否为创建者)
+// 用于控制转让按钮显示和社长相关操作权限
+const presidentRoles = ref<Record<string | number, boolean>>({});
+
 const clubList = ref<StglClubVO[]>([]);
 const buttonLoading = ref(false);
 const loading = ref(true);
@@ -660,9 +680,6 @@ const total = ref(0);
 const activeTab = ref('basic');
 const charterDownloading = ref(false);
 
-// 社长权限控制
-const presidentRoles = ref<Record<string | number, boolean>>({});
-
 const queryFormRef = ref<ElFormInstance>();
 const clubFormRef = ref<ElFormInstance>();
 
@@ -1281,7 +1298,7 @@ const handleAdvisorsChange = (advisors: AdvisorVO[]) => {
   });
 };
 
-/** 检查用户是否为指定社团的社长 */
+/** 检查用户是否为指定社团的社长(无论是否为创建者) */
 const checkIsPresident = async (clubId: string | number): Promise<boolean> => {
   try {
     console.log('🔍 checkIsPresident: 开始检查社长权限, clubId:', clubId);
@@ -1296,7 +1313,7 @@ const checkIsPresident = async (clubId: string | number): Promise<boolean> => {
   }
 };
 
-/** 批量检查社长权限 */
+/** 批量检查社长权限(检查用户是否为各社团的社长,无论是否为创建者) */
 const checkBatchPresidentRoles = async (clubs: StglClubVO[]) => {
   console.log('🔍 checkBatchPresidentRoles: 开始批量检查权限, clubs数量:', clubs.length);
   const checks = clubs.map(async (club) => {
@@ -1334,7 +1351,7 @@ const shouldShowTransferButton = (club: StglClubVO): boolean => {
       console.log('⏳ shouldShowTransferButton: 权限检查未完成,返回false避免闪现');
       return false;
     }
-    // 只有社长才能看到转让按钮
+    // 社长可以看到转让按钮(无论是否为创建者)
     const result = isPresident === true;
     console.log('🔍 shouldShowTransferButton: stgl_president角色最终结果:', result);
     return result;
@@ -1689,6 +1706,8 @@ onMounted(() => {
   }
 }
 
+
+
 /* 人员信息部分样式 */
 .personnel-section {
   margin-bottom: 24px;