This commit is contained in:
李旭明 committed 2025-08-26 00:26:25 +08:00
1 parent d5316b3d37
commit 4c2154bfde
8 files changed
+1019 -59

No files matched your search

@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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,
@@ -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
View File
@@ -478,6 +478,11 @@ export interface StglActivityQuery extends PageQuery {
*/
isFinished?: number;
/**
* 创建者ID
*/
createBy?: string | number;
/**
* 日期范围参数
*/
+313 -27
View File
@@ -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
View File
@@ -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;