社团管理1.0

This commit is contained in:
李旭明 committed 2025-08-25 15:53:02 +08:00
1 parent 4d8267e561
commit d256028f8a
83 files changed
+2040 -1096

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.5.0</snailjob.version>
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.36</lombok.version>
<bouncycastle.version>1.80</bouncycastle.version>
+2 -2
View File
@@ -157,11 +157,11 @@
</dependency>
<!-- 社团管理模块 -->
<!-- <dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-stgl</artifactId>
<version>${revision}</version>
</dependency> -->
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
+1 -1
View File
@@ -25,7 +25,7 @@
<module>ruoyi-jcbt</module>
<module>ruoyi-bzrkh</module>
<module>ruoyi-xskh</module>
<!-- <module>ruoyi-stgl</module> -->
<module>ruoyi-stgl</module>
</modules>
<artifactId>ruoyi-modules</artifactId>
@@ -22,6 +22,7 @@ import org.dromara.stgl.domain.bo.StglActivityAttendanceBo;
import org.dromara.stgl.service.IStglActivityAttendanceService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.apache.commons.lang3.StringUtils;
/**
* 活动考勤签到
@@ -120,6 +121,28 @@ public class StglActivityAttendanceController extends BaseController {
return R.ok(stglActivityAttendanceService.queryByActivityAndUser(activityId, userId));
}
/**
* 根据活动ID和用户ID查询所有签到记录(支持多次签到)
*/
@SaCheckPermission("stgl:activityAttendance:query")
@GetMapping("/getAllByActivityAndUser")
public R<List<StglActivityAttendanceVo>> getAllByActivityAndUser(
@RequestParam("activityId") Long activityId,
@RequestParam(value = "userId", required = false) Long userId,
@RequestParam(value = "studentNo", required = false) String studentNo) {
List<StglActivityAttendanceVo> list;
if (userId != null) {
list = stglActivityAttendanceService.queryAllByActivityAndUser(activityId, userId);
} else if (StringUtils.isNotBlank(studentNo)) {
list = stglActivityAttendanceService.queryAllByActivityAndStudentNo(activityId, studentNo);
} else {
return R.fail("请提供用户ID或学号");
}
return R.ok(list);
}
/**
* 用户签到
*/
@@ -128,8 +151,7 @@ public class StglActivityAttendanceController extends BaseController {
@RepeatSubmit()
@PostMapping("/signIn")
public R<Void> signIn(@Validated @RequestBody StglActivityAttendanceBo bo) {
// 设置当前登录用户ID
bo.setUserId(LoginHelper.getUserId());
// 移除重复设置userId,让Service层通过学号查找用户ID
return toAjax(stglActivityAttendanceService.signIn(bo));
}
@@ -141,8 +163,7 @@ public class StglActivityAttendanceController extends BaseController {
@RepeatSubmit()
@PostMapping("/signOut")
public R<Void> signOut(@Validated @RequestBody StglActivityAttendanceBo bo) {
// 设置当前登录用户ID
bo.setUserId(LoginHelper.getUserId());
// 移除重复设置userId,让Service层通过学号查找用户ID
return toAjax(stglActivityAttendanceService.signOut(bo));
}
}
@@ -15,7 +15,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.stgl.domain.bo.BatchApprovalBo;
import org.dromara.stgl.domain.bo.StglBatchApprovalBo;
import org.dromara.stgl.domain.bo.StglActivityRegistrationBo;
import org.dromara.stgl.domain.vo.StglActivityRegistrationVo;
import org.dromara.stgl.service.IStglActivityRegistrationService;
@@ -120,7 +120,7 @@ public class StglActivityRegisterController extends BaseController {
@SaCheckPermission("stgl:activityRegister:approve")
@Log(title = "活动报名批量审核", businessType = BusinessType.UPDATE)
@PostMapping("/batchApprove")
public R<Void> batchApprove(@Validated @RequestBody BatchApprovalBo batchApprovalBo) {
public R<Void> batchApprove(@Validated @RequestBody StglBatchApprovalBo batchApprovalBo) {
return toAjax(stglActivityRegistrationService.batchApprove(batchApprovalBo.getIds(), batchApprovalBo.getApprovalStatus(), batchApprovalBo.getApprovalRemark(), LoginHelper.getUserId()));
}
@@ -228,6 +228,21 @@ public class StglClubController extends BaseController {
return toAjax(stglClubService.changeStatus(id, status));
}
/**
* 修改社团报名开启状态
*/
@Operation(summary = "修改社团报名开启状态")
@SaCheckPermission("stgl:club:edit")
@Log(title = "社团信息", businessType = BusinessType.UPDATE)
@PutMapping("/changeSighOpenStatus")
@Parameters({
@Parameter(name = "id", description = "社团ID", required = true),
@Parameter(name = "sighOpen", description = "报名开启状态(Y/N)", required = true)
})
public R<Void> changeSighOpenStatus(@RequestParam Long id, @RequestParam String sighOpen) {
return toAjax(stglClubService.changeSighOpenStatus(id, sighOpen));
}
/**
* 修复已存在社团的成员记录
* 为缺失社长、副社长成员记录的社团补充相应记录
@@ -92,7 +92,7 @@ public class StglVolunteerMemberController extends BaseController {
bo.setMemberType(2); // 志愿者成员
// 如果前端没有传递roleType,设置默认值为1(队员)
if (bo.getRoleType() == null) {
bo.setRoleType(1);
bo.setRoleType("1");
}
return toAjax(stglMemberService.insertByBo(bo));
}
@@ -302,4 +302,14 @@ public class StglVolunteerTeamController extends BaseController {
return toAjax(stglVolunteerTeamService.transferVolunteerTeam(id, newLeaderUserId));
}
/**
* 修改志愿者团队报名开启状态
*/
@SaCheckPermission("stgl:volunteerTeam:edit")
@Log(title = "志愿者团队报名状态变更", businessType = BusinessType.UPDATE)
@PutMapping("/changeSighOpenStatus")
public R<Void> changeSighOpenStatus(@RequestParam Long id, @RequestParam String sighOpen) {
return toAjax(stglVolunteerTeamService.changeSighOpenStatus(id, sighOpen));
}
}
@@ -126,4 +126,10 @@ public class StglActivityAttendance extends TenantEntity {
* 签到状态(0-未签到 1-已签到 2-已签退)
*/
private Integer overallStatus;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic(value = "0", delval = "1")
private Integer deleted;
}
@@ -192,6 +192,11 @@ public class StglClub extends TenantEntity {
*/
private Integer status;
/**
* 开启报名(Y是 N否)
*/
private String sighOpen;
/**
* 备注
*/
@@ -53,9 +53,9 @@ public class StglMember extends TenantEntity {
private String studentName;
/**
* 角色类型(1-社长 2-副社长 3-部长 4-普通成员)
* 角色类型(社长、副社长、部长、普通成员)
*/
private Integer roleType;
private String roleType;
/**
* 加入时间
@@ -127,4 +127,9 @@ public class StglVolunteerTeam extends TenantEntity {
*/
private Integer status;
/**
* 报名开启状态(Y-开启 N-关闭)
*/
private String sighOpen;
}
@@ -1,40 +0,0 @@
package org.dromara.stgl.domain.bo;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Size;
import java.util.List;
/**
* 批量审核请求对象
*
* @author ruoyi
* @date 2025-01-31
*/
@Data
public class BatchApprovalBo {
/**
* 报名ID数组
*/
@NotEmpty(message = "审核ID列表不能为空")
private List<Long> ids;
/**
* 审核状态 1-通过 2-驳回
*/
@NotNull(message = "审核状态不能为空")
@Min(value = 1, message = "审核状态值无效")
@Max(value = 2, message = "审核状态值无效")
private Integer approvalStatus;
/**
* 审核意见
*/
@Size(max = 500, message = "审核意见不能超过500个字符")
private String approvalRemark;
}
@@ -230,6 +230,11 @@ public class StglClubBo extends BaseEntity {
*/
private Integer status;
/**
* 开启报名(Y是 N否)
*/
private String sighOpen;
/**
* 备注
*/
@@ -64,9 +64,7 @@ public class StglMemberBo extends BaseEntity {
/**
* 角色类型(社团:1-社长 2-副社长 3-部长 4-普通成员;志愿者:0-队长 1-队员)
*/
@Min(value = 0, message = "角色类型值无效")
@Max(value = 4, message = "角色类型值无效")
private Integer roleType;
private String roleType;
/**
* 加入时间
@@ -76,7 +74,7 @@ public class StglMemberBo extends BaseEntity {
/**
* 状态(0-正常 1-停用)
*/
private Integer status;
private String status;
/**
* 备注
@@ -150,4 +150,10 @@ public class StglVolunteerTeamBo extends BaseEntity {
*/
private Integer status;
/**
* 报名开启状态(Y-开启 N-关闭)
*/
@Pattern(regexp = "^[YN]$", message = "报名开启状态只能为Y或N")
private String sighOpen;
}
@@ -238,4 +238,9 @@ public class StglClubRegisterVo implements Serializable {
*/
private String remark;
/**
* 开启报名(Y是 N否)
*/
private String sighOpen;
}
@@ -226,6 +226,12 @@ public class StglClubVo implements Serializable {
@ExcelProperty(value = "状态")
private Integer status;
/**
* 开启报名(Y是 N否)
*/
@ExcelProperty(value = "开启报名")
private String sighOpen;
/**
* 备注
*/
@@ -112,7 +112,7 @@ public class StglMemberVo implements Serializable {
/**
* 角色类型(1-社长 2-副社长 3-成员)
*/
private Integer roleType;
private String roleType;
/**
* 角色类型名称
@@ -130,7 +130,7 @@ public class StglMemberVo implements Serializable {
/**
* 状态(0-正常 1-停用)
*/
private Integer status;
private String status;
/**
* 状态名称
@@ -149,6 +149,12 @@ public class StglVolunteerTeamVo implements Serializable {
@ExcelProperty(value = "状态")
private Integer status;
/**
* 报名开启状态(Y-开启 N-关闭)
*/
@ExcelProperty(value = "报名开启状态")
private String sighOpen;
/**
* 创建者
*/
@@ -273,10 +273,10 @@ public class StglMemberImportListener extends AnalysisEventListener<StglMemberIm
member.setMemberType(1); // 社团成员
}
if (member.getRoleType() == null) {
member.setRoleType(4); // 普通成员
member.setRoleType("4"); // 普通成员
}
if (member.getStatus() == null) {
member.setStatus(2); // 已加入
member.setStatus("2"); // 已加入
}
// 🔑 关键验证:确保userId不为空
if (member.getUserId() == null) {
@@ -238,8 +238,8 @@ public class StglVolunteerImportListener extends AnalysisEventListener<StglVolun
newMember.setStudentName(volunteerVo.getStudentName());
// 🔑 关键修复:设置用户ID(学生ID就是用户ID)
newMember.setUserId(student.getId());
newMember.setRoleType(1); // 志愿者角色 stgl_volunteer_role=1 队员
newMember.setStatus(2); // 成员状态 stgl_member_status=2 已加入
newMember.setRoleType("1"); // 志愿者角色 stgl_volunteer_role=1 队员
newMember.setStatus("2"); // 成员状态 stgl_member_status=2 已加入
newMember.setJoinTime(volunteerVo.getJoinTime() != null ? volunteerVo.getJoinTime() : new Date());
newMember.setCreateBy(operUserId);
@@ -39,6 +39,15 @@ public interface StglActivityAttendanceMapper extends BaseMapperPlus<StglActivit
*/
StglActivityAttendanceVo selectByActivityAndUser(@Param("activityId") Long activityId, @Param("userId") Long userId);
/**
* 根据活动ID和用户ID查询所有签到记录(支持多次签到)
*
* @param activityId 活动ID
* @param userId 用户ID
* @return 签到记录列表
*/
List<StglActivityAttendanceVo> selectAllByActivityAndUser(@Param("activityId") Long activityId, @Param("userId") Long userId);
/**
* 根据ID查询活动考勤签到详情(关联查询活动信息)
*
@@ -36,17 +36,19 @@ public interface StglMemberMapper extends BaseMapperPlus<StglMember, StglMemberV
* @param clubName 社团名称(用于社团成员查询)
* @param teamName 队伍名称(用于志愿者查询)
* @param userId 用户ID(用于权限过滤)
* @param params 其他查询参数(包含statusList等)
* @return 成员列表
*/
List<StglMemberVo> selectMemberWithUserInfo(@Param("memberType") Integer memberType,
@Param("relationId") Long relationId,
@Param("studentNo") String studentNo,
@Param("studentName") String studentName,
@Param("roleType") Integer roleType,
@Param("status") Integer status,
@Param("roleType") String roleType,
@Param("status") String status,
@Param("clubName") String clubName,
@Param("teamName") String teamName,
@Param("userId") Long userId);
@Param("userId") Long userId,
@Param("params") java.util.Map<String, Object> params);
/**
* 检查用户是否已加入某个社团/志愿者队伍
@@ -56,6 +56,16 @@ public interface IStglActivityAttendanceService {
*/
StglActivityAttendanceVo queryByActivityAndUser(Long activityId, Long userId);
/**
* 根据活动ID和用户ID查询所有签到记录(支持多次签到)
*/
List<StglActivityAttendanceVo> queryAllByActivityAndUser(Long activityId, Long userId);
/**
* 根据活动ID和学号查询所有签到记录(支持多次签到)
*/
List<StglActivityAttendanceVo> queryAllByActivityAndStudentNo(Long activityId, String studentNo);
/**
* 用户签到
*/
@@ -106,6 +106,15 @@ public interface IStglClubService {
*/
Boolean changeStatus(Long id, Integer status);
/**
* 修改社团报名开启状态
*
* @param id 社团ID
* @param sighOpen 报名开启状态(Y/N)
* @return 是否操作成功
*/
Boolean changeSighOpenStatus(Long id, String sighOpen);
/**
* 修复所有已存在社团的成员记录
* 为那些没有对应成员记录的社长和副社长补充成员记录
@@ -130,4 +130,13 @@ public interface IStglVolunteerTeamService {
*/
Integer fixExistingTeamLeaderRecords();
/**
* 修改志愿者团队报名开启状态
*
* @param id 团队ID
* @param sighOpen 报名开启状态(Y/N)
* @return 是否修改成功
*/
Boolean changeSighOpenStatus(Long id, String sighOpen);
}
@@ -15,11 +15,14 @@ import org.dromara.stgl.domain.vo.StglActivityAttendanceVo;
import org.dromara.stgl.domain.StglActivityAttendance;
import org.dromara.stgl.mapper.StglActivityAttendanceMapper;
import org.dromara.stgl.service.IStglActivityAttendanceService;
import org.dromara.system.domain.SysUser;
import org.dromara.system.mapper.SysUserMapper;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Date;
import java.util.ArrayList;
/**
* 活动考勤签到Service业务层处理
@@ -32,6 +35,7 @@ import java.util.Date;
public class StglActivityAttendanceServiceImpl implements IStglActivityAttendanceService {
private final StglActivityAttendanceMapper baseMapper;
private final SysUserMapper sysUserMapper;
/**
* 查询活动考勤签到
@@ -141,29 +145,51 @@ public class StglActivityAttendanceServiceImpl implements IStglActivityAttendanc
return baseMapper.selectByActivityAndUser(activityId, userId);
}
/**
* 根据活动ID和用户ID查询所有签到记录(支持多次签到)
*/
@Override
public List<StglActivityAttendanceVo> queryAllByActivityAndUser(Long activityId, Long userId) {
return baseMapper.selectAllByActivityAndUser(activityId, userId);
}
/**
* 根据活动ID和学号查询所有签到记录(支持多次签到)
*/
@Override
public List<StglActivityAttendanceVo> queryAllByActivityAndStudentNo(Long activityId, String studentNo) {
// 先通过学号获取用户ID
Long userId = getUserIdByStudentNo(studentNo);
if (userId == null) {
return new ArrayList<>();
}
return baseMapper.selectAllByActivityAndUser(activityId, userId);
}
/**
* 用户签到
*/
@Override
public Boolean signIn(StglActivityAttendanceBo bo) {
// 检查是否已存在签到记录
StglActivityAttendanceVo existingRecord = queryByActivityAndUser(bo.getActivityId(), bo.getUserId());
if (existingRecord != null) {
// 如果已存在记录,更新签到信息
bo.setId(existingRecord.getId());
bo.setSignInTime(new Date());
bo.setSignInStatus(1);
bo.setOverallStatus(1); // 已签到
return updateByBo(bo);
} else {
// 如果不存在记录,新增签到记录
bo.setSignInTime(new Date());
bo.setSignInStatus(1);
bo.setSignOutStatus(0);
bo.setOverallStatus(1); // 已签到
return insertByBo(bo);
// 通过学号查找用户ID
Long userId = getUserIdByStudentNo(bo.getStudentNo());
if (userId == null) {
throw new RuntimeException("未找到对应的用户信息");
}
// 移除重复签到检查,允许每次签到都生成新记录
// 无论之前是否有签到记录,都创建新的签到记录
bo.setUserId(userId);
bo.setSignInTime(new Date());
bo.setSignInStatus(1);
bo.setSignOutStatus(0);
bo.setOverallStatus(1); // 已签到
// 设置基础字段
bo.setCreateTime(new Date());
bo.setUpdateTime(new Date());
return insertByBo(bo);
}
/**
@@ -171,18 +197,82 @@ public class StglActivityAttendanceServiceImpl implements IStglActivityAttendanc
*/
@Override
public Boolean signOut(StglActivityAttendanceBo bo) {
// 必须先签到才能签退
StglActivityAttendanceVo existingRecord = queryByActivityAndUser(bo.getActivityId(), bo.getUserId());
if (existingRecord == null || existingRecord.getSignInStatus() != 1) {
throw new RuntimeException("请先完成签到");
// 通过学号查找用户ID
Long userId = getUserIdByStudentNo(bo.getStudentNo());
if (userId == null) {
throw new RuntimeException("未找到对应的用户信息");
}
// 更新签退信息
bo.setId(existingRecord.getId());
bo.setSignOutTime(new Date());
bo.setSignOutStatus(1);
bo.setOverallStatus(2); // 已签退
return updateByBo(bo);
// 如果提供了记录ID,直接更新该记录
if (bo.getId() != null) {
StglActivityAttendance record = baseMapper.selectById(bo.getId());
if (record == null) {
throw new RuntimeException("没有找到指定的签到记录");
}
// 检查是否可以签退
if (record.getSignInStatus() != 1) {
throw new RuntimeException("您还未完成签到,不能签退");
}
if (record.getSignOutStatus() == 1) {
throw new RuntimeException("您已经完成签退,无需重复签退");
}
// 更新签退信息
bo.setUserId(userId);
bo.setSignOutTime(new Date());
bo.setSignOutStatus(1);
bo.setOverallStatus(2); // 已签退
return updateByBo(bo);
} else {
// 如果没有提供记录ID,查找最新的未完成签到记录
LambdaQueryWrapper<StglActivityAttendance> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(StglActivityAttendance::getActivityId, bo.getActivityId());
queryWrapper.eq(StglActivityAttendance::getUserId, userId);
queryWrapper.orderByDesc(StglActivityAttendance::getCreateTime);
queryWrapper.last("LIMIT 1");
StglActivityAttendance record = baseMapper.selectOne(queryWrapper);
if (record == null) {
throw new RuntimeException("没有找到签到记录,请先完成签到");
}
// 检查是否可以签退
if (record.getSignInStatus() != 1) {
throw new RuntimeException("您还未完成签到,不能签退");
}
if (record.getSignOutStatus() == 1) {
throw new RuntimeException("您已经完成签退,无需重复签退");
}
// 更新签退信息
bo.setId(record.getId());
bo.setUserId(userId);
bo.setSignOutTime(new Date());
bo.setSignOutStatus(1);
bo.setOverallStatus(2); // 已签退
return updateByBo(bo);
}
}
/**
* 根据学号获取用户ID
*/
private Long getUserIdByStudentNo(String studentNo) {
if (StringUtils.isBlank(studentNo)) {
return null;
}
// 这里需要根据实际的用户表结构来查询
// 假设用户表是sys_user,学号字段是user_name
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUserName, studentNo);
wrapper.eq(SysUser::getDelFlag, "0");
SysUser user = sysUserMapper.selectOne(wrapper);
return user != null ? user.getUserId() : null;
}
}
@@ -79,6 +79,7 @@ public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
private LambdaQueryWrapper<StglClub> buildQueryWrapper(String clubType, String clubName) {
LambdaQueryWrapper<StglClub> lqw = Wrappers.lambdaQuery();
lqw.eq(StglClub::getStatus, 0); // 只查询正常状态的社团(0-正常,1-停用)
lqw.eq(StglClub::getSighOpen, "Y"); // 只查询报名开启的社团
lqw.eq(StringUtils.isNotBlank(clubType), StglClub::getClubType, clubType);
lqw.like(StringUtils.isNotBlank(clubName), StglClub::getClubName, clubName);
lqw.orderByDesc(StglClub::getCreateTime);
@@ -137,6 +138,7 @@ public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
// ========== 新增:章程文件和备注信息 ==========
vo.setCharterUrl(club.getCharterUrl());
vo.setRemark(club.getRemark());
vo.setSighOpen(club.getSighOpen()); // 设置报名开启状态
// 查询社团成员数量(所有已加入的成员)
LambdaQueryWrapper<StglMember> memberCountLqw = Wrappers.lambdaQuery();
@@ -243,7 +245,7 @@ public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
member.setUserId(userId);
member.setStudentNo(studentNo);
member.setStudentName(studentName != null ? studentName : user.getNickName()); // 如果没有学生信息,使用用户昵称
member.setRoleType(4); // 普通成员
member.setRoleType("4"); // 普通成员
member.setJoinTime(new Date());
member.setStatus(0); // 待审核状态(stgl_member_status字典,新报名需审核)
member.setRemark(reason); // 将申请理由存储到备注字段
@@ -272,7 +274,7 @@ public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
}
// 检查是否是社长或副社长
if (member.getRoleType() != null && member.getRoleType() <= 2) {
if (member.getRoleType() != null && Integer.parseInt(member.getRoleType()) <= 2) {
throw new RuntimeException("社长和副社长不能直接退出,请先转让职务");
}
@@ -358,6 +358,21 @@ public class StglClubServiceImpl implements IStglClubService {
return baseMapper.updateById(club) > 0;
}
/**
* 修改社团报名开启状态
*
* @param id 社团ID
* @param sighOpen 报名开启状态(Y/N)
* @return 是否操作成功
*/
@Override
public Boolean changeSighOpenStatus(Long id, String sighOpen) {
StglClub club = new StglClub();
club.setId(id);
club.setSighOpen(sighOpen);
return baseMapper.updateById(club) > 0;
}
/**
* 自动创建社长和副社长的成员记录
*
@@ -373,7 +388,7 @@ public class StglClubServiceImpl implements IStglClubService {
president.setRelationId(club.getId());
president.setStudentNo(club.getPresidentStudentNo());
president.setStudentName(club.getPresidentName());
president.setRoleType(1); // 社长
president.setRoleType("社长"); // 社长
president.setJoinTime(now);
president.setStatus(2); // 已加入状态(stgl_member_status字典)
president.setRemark("社团创建时自动添加");
@@ -482,7 +497,7 @@ public class StglClubServiceImpl implements IStglClubService {
vicePresident.setRelationId(clubId);
vicePresident.setStudentNo(studentNo);
vicePresident.setStudentName(name);
vicePresident.setRoleType(2); // 副社长
vicePresident.setRoleType("副社长"); // 副社长
vicePresident.setJoinTime(joinTime);
vicePresident.setStatus(2); // 已加入状态(stgl_member_status字典)
vicePresident.setRemark("社团创建时自动添加");
@@ -548,14 +563,14 @@ public class StglClubServiceImpl implements IStglClubService {
// 检查并补充社长成员记录
if (StringUtils.isNotBlank(club.getPresidentStudentNo())) {
boolean presidentExists = checkMemberExists(club.getId(), club.getPresidentStudentNo(), 1);
boolean presidentExists = checkMemberExists(club.getId(), club.getPresidentStudentNo(), "社长");
if (!presidentExists) {
StglMember president = new StglMember();
president.setMemberType(1); // 社团成员
president.setRelationId(club.getId());
president.setStudentNo(club.getPresidentStudentNo());
president.setStudentName(club.getPresidentName());
president.setRoleType(1); // 社长
president.setRoleType("社长"); // 社长
president.setJoinTime(club.getCreateTime() != null ? club.getCreateTime() : now);
president.setStatus(2); // 已加入状态(stgl_member_status字典)
president.setRemark("补充缺失的社长成员记录");
@@ -589,7 +604,7 @@ public class StglClubServiceImpl implements IStglClubService {
// 1. 处理单个副社长字段(向后兼容)
if (StringUtils.isNotBlank(club.getVicePresidentStudentNo())) {
boolean vicePresidentExists = checkMemberExists(club.getId(), club.getVicePresidentStudentNo(), 2);
boolean vicePresidentExists = checkMemberExists(club.getId(), club.getVicePresidentStudentNo(), "副社长");
if (!vicePresidentExists) {
fixedCount += createFixedVicePresidentRecord(
club.getId(),
@@ -643,7 +658,7 @@ public class StglClubServiceImpl implements IStglClubService {
if (StringUtils.isBlank(club.getVicePresidentStudentNo()) ||
!studentNo.equals(club.getVicePresidentStudentNo())) {
boolean exists = checkMemberExists(club.getId(), studentNo, 2);
boolean exists = checkMemberExists(club.getId(), studentNo, "副社长");
if (!exists) {
System.out.println("修复副社长成员记录: " + name + "(" + studentNo + ")");
fixedCount += createFixedVicePresidentRecord(club.getId(), studentNo, name, joinTime) ? 1 : 0;
@@ -677,7 +692,7 @@ public class StglClubServiceImpl implements IStglClubService {
vicePresident.setRelationId(clubId);
vicePresident.setStudentNo(studentNo);
vicePresident.setStudentName(name);
vicePresident.setRoleType(2); // 副社长
vicePresident.setRoleType("副社长"); // 副社长
vicePresident.setJoinTime(joinTime);
vicePresident.setStatus(2); // 已加入状态(stgl_member_status字典)
vicePresident.setRemark("补充缺失的副社长成员记录");
@@ -702,10 +717,11 @@ public class StglClubServiceImpl implements IStglClubService {
* @param roleType 角色类型
* @return 是否存在
*/
private boolean checkMemberExists(Long clubId, String studentNo, Integer roleType) {
private boolean checkMemberExists(Long clubId, String studentNo, String roleType) {
LambdaQueryWrapper<StglMember> query = Wrappers.lambdaQuery();
query.eq(StglMember::getMemberType, 1);
query.eq(StglMember::getRelationId, clubId);
query.eq(StglMember::getMemberType, 1);
query.eq(StglMember::getStudentNo, studentNo);
query.eq(StglMember::getRoleType, roleType);
query.eq(StglMember::getStatus, 1);
@@ -721,11 +737,11 @@ public class StglClubServiceImpl implements IStglClubService {
*/
private void syncVicePresidentsInMembers(StglClub club) {
try {
// 1. 删除现有的所有副社长记录(roleType = 2)
// 1. 删除现有的所有副社长记录(roleType = "副社长")
LambdaQueryWrapper<StglMember> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(StglMember::getRelationId, club.getId())
.eq(StglMember::getMemberType, 1) // 社团成员
.eq(StglMember::getRoleType, 2); // 副社长角色
.eq(StglMember::getRoleType, "副社长"); // 副社长角色
memberMapper.delete(deleteWrapper);
// 2. 处理单个副社长字段(向后兼容)
@@ -897,7 +913,7 @@ public class StglClubServiceImpl implements IStglClubService {
LambdaQueryWrapper<StglMember> oldPresidentLqw = Wrappers.lambdaQuery();
oldPresidentLqw.eq(StglMember::getMemberType, 1); // 社团成员
oldPresidentLqw.eq(StglMember::getRelationId, clubId);
oldPresidentLqw.eq(StglMember::getRoleType, 1); // 社长
oldPresidentLqw.eq(StglMember::getRoleType, "社长"); // 社长
oldPresidentLqw.eq(StglMember::getStatus, 2); // 已加入状态
StglMember oldPresidentMember = memberMapper.selectOne(oldPresidentLqw);
@@ -936,7 +952,7 @@ public class StglClubServiceImpl implements IStglClubService {
// 如果新社长已经是成员,更新角色和状态
StglMember updateNewPresident = new StglMember();
updateNewPresident.setId(newPresidentMember.getId());
updateNewPresident.setRoleType(1); // 社长
updateNewPresident.setRoleType("社长"); // 社长
updateNewPresident.setStatus(2); // 已加入状态
updateNewPresident.setJoinTime(new Date());
memberMapper.updateById(updateNewPresident);
@@ -948,7 +964,7 @@ public class StglClubServiceImpl implements IStglClubService {
newMember.setUserId(newPresidentUserId);
newMember.setStudentNo(newPresidentStudent.getStudentNo());
newMember.setStudentName(newPresidentStudent.getName());
newMember.setRoleType(1); // 社长
newMember.setRoleType("社长"); // 社长
newMember.setJoinTime(new Date());
newMember.setStatus(2); // 已加入状态
memberMapper.insert(newMember);
@@ -85,7 +85,8 @@ public class StglMemberServiceImpl implements IStglMemberService {
bo.getStatus(),
bo.getClubName(),
bo.getTeamName(),
bo.getUserId()
bo.getUserId(),
bo.getParams()
);
log.info("查询结果: total={}", allMembers.size());
@@ -131,7 +132,8 @@ public class StglMemberServiceImpl implements IStglMemberService {
bo.getStatus(),
bo.getClubName(),
bo.getTeamName(),
bo.getUserId()
bo.getUserId(),
bo.getParams()
);
}
@@ -144,7 +146,35 @@ public class StglMemberServiceImpl implements IStglMemberService {
lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), StglMember::getStudentNo, bo.getStudentNo());
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), StglMember::getStudentName, bo.getStudentName());
lqw.eq(bo.getRoleType() != null, StglMember::getRoleType, bo.getRoleType());
lqw.eq(bo.getStatus() != null, StglMember::getStatus, bo.getStatus());
// 处理状态查询:优先使用statusList多状态查询,如果没有则使用单个status
if (params != null && params.get("statusList") != null) {
Object statusList = params.get("statusList");
if (statusList instanceof List) {
List<?> statuses = (List<?>) statusList;
if (!statuses.isEmpty()) {
lqw.in(StglMember::getStatus, statuses);
}
} else if (statusList instanceof String) {
String statusStr = (String) statusList;
if (statusStr.contains(",")) {
// 处理 "0,3" 格式的字符串
String[] statuses = statusStr.split(",");
List<String> statusList2 = new ArrayList<>();
for (String status : statuses) {
statusList2.add(status.trim());
}
lqw.in(StglMember::getStatus, statusList2);
} else {
// 单个状态值
lqw.eq(StglMember::getStatus, statusStr);
}
}
} else if (bo.getStatus() != null) {
// 如果没有statusList,则使用单个status字段
lqw.eq(StglMember::getStatus, bo.getStatus());
}
lqw.between(params.get("beginJoinTime") != null && params.get("endJoinTime") != null,
StglMember::getJoinTime, params.get("beginJoinTime"), params.get("endJoinTime"));
lqw.orderByDesc(StglMember::getJoinTime);
@@ -208,7 +238,7 @@ public class StglMemberServiceImpl implements IStglMemberService {
entity.setStatus(2); // 默认为已加入状态(stgl_member_status字典)
}
if (entity.getRoleType() == null) {
entity.setRoleType(3); // 默认为普通成员
entity.setRoleType("3"); // 默认为普通成员
}
}
@@ -289,7 +319,7 @@ public class StglMemberServiceImpl implements IStglMemberService {
member.setMemberType(memberType);
member.setRelationId(relationId);
member.setUserId(userId);
member.setRoleType(roleType != null ? roleType : 3);
member.setRoleType(roleType != null ? roleType.toString() : "3");
member.setJoinTime(now);
member.setStatus(2); // 已加入状态(stgl_member_status字典)
return member;
@@ -309,7 +339,7 @@ public class StglMemberServiceImpl implements IStglMemberService {
public Boolean updateMemberRole(Long id, Integer roleType) {
StglMember member = new StglMember();
member.setId(id);
member.setRoleType(roleType);
member.setRoleType(roleType.toString());
return baseMapper.updateById(member) > 0;
}
@@ -395,8 +425,8 @@ public class StglMemberServiceImpl implements IStglMemberService {
memberBo.setStudentNo(memberVo.getStudentNo());
memberBo.setStudentName(memberVo.getStudentName());
memberBo.setRelationId(memberVo.getRelationId());
memberBo.setRoleType(memberVo.getRoleType() != null ? memberVo.getRoleType() : 1); // 默认普通成员
memberBo.setStatus(memberVo.getStatus() != null ? memberVo.getStatus() : 1); // 默认正常状态
memberBo.setRoleType(memberVo.getRoleType() != null ? memberVo.getRoleType() : "1"); // 默认普通成员
memberBo.setStatus(memberVo.getStatus() != null ? memberVo.getStatus() : "1"); // 默认正常状态
memberBo.setJoinTime(memberVo.getJoinTime() != null ? memberVo.getJoinTime() : new Date());
// 检查是否已存在
@@ -469,16 +499,17 @@ public class StglMemberServiceImpl implements IStglMemberService {
null, // status
null, // clubName
null, // teamName
bo.getUserId()
bo.getUserId(),
null // params
);
// 基于权限过滤后的数据进行统计
long totalCount = allMembers.size();
long activeCount = allMembers.stream()
.mapToLong(member -> member.getStatus() != null && member.getStatus() == 2 ? 1 : 0)
.mapToLong(member -> member.getStatus() != null && "2".equals(member.getStatus()) ? 1 : 0)
.sum();
long pendingCount = allMembers.stream()
.mapToLong(member -> member.getStatus() != null && member.getStatus() == 1 ? 1 : 0)
.mapToLong(member -> member.getStatus() != null && "1".equals(member.getStatus()) ? 1 : 0)
.sum();
Map<String, Object> stats = new HashMap<>();
@@ -597,7 +628,7 @@ public class StglMemberServiceImpl implements IStglMemberService {
// 查询用户作为队长的志愿者队伍
StglMemberBo captainBo = new StglMemberBo();
captainBo.setMemberType(2); // 志愿者成员
captainBo.setRoleType(0); // 队长角色
captainBo.setRoleType("0"); // 队长角色
List<StglMemberVo> captainRecords = baseMapper.selectMemberWithUserInfo(
captainBo.getMemberType(),
@@ -608,7 +639,8 @@ public class StglMemberServiceImpl implements IStglMemberService {
null, // status
null, // clubName
null, // teamName
userId // userId 权限过滤
userId, // userId 权限过滤
null // params
);
return captainRecords.stream()
@@ -220,7 +220,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
leader.setUserId(team.getLeaderUserId());
leader.setStudentNo(team.getLeaderStudentNo());
leader.setStudentName(team.getLeaderName());
leader.setRoleType(0); // 队长 (stgl_volunteer_role=0)
leader.setRoleType("队长"); // 队长角色
leader.setJoinTime(new Date());
leader.setStatus(2); // 已加入状态
leader.setRemark("志愿者队伍创建时自动添加");
@@ -256,7 +256,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
LambdaQueryWrapper<StglMember> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(StglMember::getRelationId, team.getId())
.eq(StglMember::getMemberType, 2) // 志愿者成员
.eq(StglMember::getRoleType, 0); // 队长角色
.eq(StglMember::getRoleType, "队长"); // 队长角色
memberMapper.delete(deleteWrapper);
// 2. 创建新的队长成员记录
@@ -267,7 +267,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
leader.setUserId(team.getLeaderUserId());
leader.setStudentNo(team.getLeaderStudentNo());
leader.setStudentName(team.getLeaderName());
leader.setRoleType(0); // 队长 (stgl_volunteer_role=0)
leader.setRoleType("队长"); // 队长角色
leader.setJoinTime(new Date());
leader.setStatus(2); // 已加入状态
leader.setRemark("志愿者队伍修改时同步更新");
@@ -303,7 +303,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
query.eq(StglMember::getMemberType, 2); // 志愿者成员
query.eq(StglMember::getRelationId, teamId); // 指定队伍
query.eq(StglMember::getStudentNo, studentNo); // 指定学号
query.eq(StglMember::getRoleType, 0); // 队长角色
query.eq(StglMember::getRoleType, "0"); // 队长角色
query.eq(StglMember::getStatus, 2); // 已加入状态
return memberMapper.selectCount(query) > 0;
@@ -433,7 +433,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
member.setUserId(LoginHelper.getUserId()); // 设置当前登录用户ID
member.setStudentName(applicationBo.getStudentName());
member.setStudentNo(applicationBo.getStudentNo());
member.setRoleType(1); // 1-队员(志愿者角色)
member.setRoleType("1"); // 1-队员(志愿者角色)
member.setStatus(0); // 0-待审核
member.setJoinTime(new Date());
@@ -540,7 +540,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
leader.setUserId(team.getLeaderUserId());
leader.setStudentNo(team.getLeaderStudentNo());
leader.setStudentName(team.getLeaderName());
leader.setRoleType(0); // 队长 (stgl_volunteer_role=0)
leader.setRoleType("0"); // 队长 (stgl_volunteer_role=0)
leader.setJoinTime(team.getCreateTime() != null ? team.getCreateTime() : new Date());
leader.setStatus(2); // 已加入状态
leader.setRemark("补充缺失的队长成员记录");
@@ -657,7 +657,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
// 如果新队长已经是成员,更新角色和状态
StglMember updateNewLeader = new StglMember();
updateNewLeader.setId(newLeaderMember.getId());
updateNewLeader.setRoleType(0); // 队长角色
updateNewLeader.setRoleType("0"); // 队长角色
updateNewLeader.setStatus(2); // 已加入状态
updateNewLeader.setJoinTime(new Date());
updateNewLeader.setUpdateBy(LoginHelper.getUserId());
@@ -675,7 +675,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
newMember.setUserId(newLeaderUserId);
newMember.setStudentNo(newLeaderStudent.getStudentNo());
newMember.setStudentName(newLeaderStudent.getName());
newMember.setRoleType(0); // 队长角色
newMember.setRoleType("0"); // 队长角色
newMember.setJoinTime(new Date());
newMember.setStatus(2); // 已加入状态
newMember.setCreateBy(LoginHelper.getUserId());
@@ -816,4 +816,19 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
List<StglMember> members = memberMapper.selectList(wrapper);
return members.isEmpty() ? null : members.get(0);
}
/**
* 修改志愿者团队报名开启状态
*
* @param id 团队ID
* @param sighOpen 报名开启状态(Y/N)
* @return 是否操作成功
*/
@Override
public Boolean changeSighOpenStatus(Long id, String sighOpen) {
StglVolunteerTeam team = new StglVolunteerTeam();
team.setId(id);
team.setSighOpen(sighOpen);
return baseMapper.updateById(team) > 0;
}
}
@@ -39,6 +39,7 @@
<result property="fundingSources" column="funding_sources"/>
<result property="charterUrl" column="charter_url"/>
<result property="status" column="status"/>
<result property="sighOpen" column="sigh_open"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
@@ -56,7 +57,7 @@
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.advisors_json,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time, c.activity_frequency, c.funding_sources,
c.charter_url, c.status, c.remark, c.create_by, c.create_time, c.update_by, c.update_time, c.tenant_id, c.deleted,
c.charter_url, c.status, c.sigh_open, c.remark, c.create_by, c.create_time, c.update_by, c.update_time, c.tenant_id, c.deleted,
COALESCE(m.member_count, 0) as memberCount
from stgl_club c
LEFT JOIN (
@@ -76,7 +77,7 @@
c.vice_president_name, c.vice_president_student_no, c.vice_president_dept, c.vice_president_major, c.vice_president_class, c.vice_president_phone,
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.remark,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.sigh_open, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time,
COALESCE(m.member_count, 0) as memberCount
FROM stgl_club c
@@ -98,7 +99,7 @@
c.vice_president_name, c.vice_president_student_no, c.vice_president_dept, c.vice_president_major, c.vice_president_class, c.vice_president_phone,
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.remark,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.sigh_open, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time,
COALESCE(m.member_count, 0) as memberCount
FROM stgl_club c
@@ -129,7 +130,7 @@
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.advisors_json,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.remark,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.sigh_open, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time,
COALESCE(m.member_count, 0) as memberCount
FROM stgl_club c
@@ -160,7 +160,35 @@
<if test="roleType != null">
AND m.role_type = #{roleType}
</if>
<if test="status != null">
<!-- 状态过滤:优先使用statusList多状态查询,如果没有则使用单个status -->
<if test="params != null and params.statusList != null">
<choose>
<when test="params.statusList instanceof java.util.List">
<if test="params.statusList.size() > 0">
AND m.status IN
<foreach collection="params.statusList" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</if>
</when>
<otherwise>
<if test="params.statusList != null and params.statusList != ''">
<choose>
<when test="params.statusList.indexOf(',') > -1">
AND m.status IN
<foreach collection="params.statusList.split(',')" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</when>
<otherwise>
AND m.status = #{params.statusList}
</otherwise>
</choose>
</if>
</otherwise>
</choose>
</if>
<if test="status != null and (params == null or params.statusList == null)">
AND m.status = #{status}
</if>
<if test="clubName != null and clubName != ''">
@@ -130,13 +130,7 @@
AND sar.user_id = #{userId}
AND sar.approval_status = 1 -- 报名成功(审核通过)
AND sa.activity_status = '4' -- 进行中
-- 排除已有签到记录的活动
AND NOT EXISTS (
SELECT 1 FROM stgl_activity_sign sas2
WHERE sas2.activity_id = sa.id
AND sas2.user_id = #{userId}
AND sas2.deleted = 0
)
-- 移除NOT EXISTS子查询,允许用户重复签到
</where>
ORDER BY sa.start_time ASC
</select>
@@ -152,6 +146,18 @@
</where>
</select>
<!-- 根据活动ID和用户ID查询所有签到记录(支持多次签到) -->
<select id="selectAllByActivityAndUser" resultMap="StglActivityAttendanceResult">
<include refid="selectStglActivityAttendanceVo"/>
<where>
sas.deleted = 0
AND sa.deleted = 0
AND sas.activity_id = #{activityId}
AND sas.user_id = #{userId}
</where>
ORDER BY sas.create_time DESC
</select>
<!-- 根据ID查询活动考勤签到详情(关联查询活动信息) -->
<select id="selectVoByIdWithActivity" parameterType="Long" resultMap="StglActivityAttendanceResult">
<include refid="selectStglActivityAttendanceVo"/>
+1
View File
@@ -97,6 +97,7 @@
| 日期 | 版本 | 更新内容 | 更新人 |
|------|------|---------|-------|
| 2025-01-03 | 1.1 | 社团管理API更新:删除副社长信息卡片,role_type字段改为字符串类型 | 系统管理员 |
| 2025-07-01 | 1.0 | 初始版本 | 系统管理员 |
| 2025-07-15 | 1.1 | 添加心理健康测评API | 张工 |
| 2025-07-30 | 1.2 | 添加预约限制功能API | Cursor |
@@ -107,7 +107,7 @@ CREATE TABLE `stgl_member` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`student_no` varchar(20) COMMENT '学号',
`student_name` varchar(50) COMMENT '学生姓名',
`role_type` tinyint COMMENT '角色类型(1-社长 2-副社长 3-成员)',
`role_type` varchar(20) COMMENT '角色类型(社长、副社长、部长、普通成员)',
`join_time` datetime COMMENT '加入时间',
`status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-退出)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
+10 -2
View File
@@ -1,6 +1,14 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { StglActivityVO, StglActivityForm, StglActivityQuery, ClubOption, ActivityCopyParams, ActivitySummaryParams, ActivityRatingVO } from './types';
import {
StglActivityVO,
StglActivityForm,
StglActivityQuery,
ClubOption,
ActivityCopyParams,
ActivitySummaryParams,
ActivityRatingVO
} from './types';
/**
* 查询活动信息列表
@@ -137,4 +145,4 @@ export const getActivityRatings = (activityId: string | number): AxiosPromise<Ac
url: '/stgl/activity/' + activityId + '/ratings',
method: 'get'
});
};
};
+1 -1
View File
@@ -118,4 +118,4 @@ export const signOut = (data: StglActivityAttendanceForm) => {
method: 'post',
data: data
});
};
};
+1 -1
View File
@@ -329,4 +329,4 @@ export interface StglActivityAttendanceQuery {
* 日期范围参数
*/
params?: any;
}
}
+1 -1
View File
@@ -299,4 +299,4 @@ export function batchReviewSubmissions(ids: string, reviewStatus: number, review
method: 'put',
params: params
});
}
}
+1 -1
View File
@@ -533,4 +533,4 @@ export interface MaterialTypeOption {
* 是否选中
*/
checked?: boolean;
}
}
+12 -12
View File
@@ -12,7 +12,7 @@ export const listActivityRegister = (query?: any): AxiosPromise<StglActivityRegi
return request({
url: '/stgl/activity/register/list',
method: 'get',
params: query,
params: query
});
};
@@ -23,7 +23,7 @@ export const listActivityRegister = (query?: any): AxiosPromise<StglActivityRegi
export const getActivityRegister = (id: string | number): AxiosPromise<StglActivityRegistrationVo> => {
return request({
url: '/stgl/activity/register/' + id,
method: 'get',
method: 'get'
});
};
@@ -35,7 +35,7 @@ export const addActivityRegister = (data: StglActivityRegistrationForm) => {
return request({
url: '/stgl/activity/register',
method: 'post',
data: data,
data: data
});
};
@@ -47,7 +47,7 @@ export const updateActivityRegister = (data: StglActivityRegistrationForm) => {
return request({
url: '/stgl/activity/register',
method: 'put',
data: data,
data: data
});
};
@@ -58,7 +58,7 @@ export const updateActivityRegister = (data: StglActivityRegistrationForm) => {
export const delActivityRegister = (id: string | number | Array<string | number>) => {
return request({
url: '/stgl/activity/register/' + id,
method: 'delete',
method: 'delete'
});
};
@@ -74,8 +74,8 @@ export const approveActivityRegister = (id: string | number, approvalStatus: num
method: 'post',
params: {
approvalStatus,
approvalRemark,
},
approvalRemark
}
});
};
@@ -92,8 +92,8 @@ export const batchApproveActivityRegister = (ids: Array<string | number>, approv
data: {
ids,
approvalStatus,
approvalRemark,
},
approvalRemark
}
});
};
@@ -121,7 +121,7 @@ export const recordActivityScore = (id: string | number, score: string) => {
url: `/stgl/activity/register/score/${id}`,
method: 'post',
params: {
score,
},
score
}
});
};
};
+1 -1
View File
@@ -341,4 +341,4 @@ export interface TeamMemberInfo {
export interface PageQuery {
pageNum?: number;
pageSize?: number;
}
}
@@ -11,7 +11,7 @@ export const listRegistrableActivities = (query?: any): AxiosPromise<StglActivit
return request({
url: '/stgl/activity/registerhall/list',
method: 'get',
params: query,
params: query
});
};
@@ -22,7 +22,7 @@ export const listRegistrableActivities = (query?: any): AxiosPromise<StglActivit
export const getActivityDetail = (id: string | number): AxiosPromise<StglActivityVo> => {
return request({
url: '/stgl/activity/registerhall/' + id,
method: 'get',
method: 'get'
});
};
@@ -34,7 +34,7 @@ export const registerActivity = (data: StglActivityRegistrationForm) => {
return request({
url: '/stgl/activity/registerhall/register',
method: 'post',
data: data,
data: data
});
};
@@ -45,7 +45,7 @@ export const registerActivity = (data: StglActivityRegistrationForm) => {
export const checkRegistration = (activityId: string | number): AxiosPromise<boolean> => {
return request({
url: `/stgl/activity/registerhall/checkRegistration/${activityId}`,
method: 'get',
method: 'get'
});
};
@@ -57,6 +57,6 @@ export const getMyRegistrations = (query?: any): AxiosPromise<StglActivityRegist
return request({
url: '/stgl/activity/registerhall/myRegistrations',
method: 'get',
params: query,
params: query
});
};
};
@@ -422,4 +422,4 @@ export interface TeamMemberInfo {
* 是否为队长(0-否 1-是)
*/
isLeader: number;
}
}
+19
View File
@@ -120,3 +120,22 @@ export const checkPresidentRole = (id: string | number) => {
}
});
};
/**
* 修改社团报名开启状态
* @param id 社团ID
* @param sighOpen 报名开启状态(Y/N)
*/
export const changeSighOpenStatus = (id: string | number, sighOpen: string) => {
return request({
url: '/stgl/club/changeSighOpenStatus',
method: 'put',
params: {
id,
sighOpen
},
headers: {
isEncrypt: 'true'
}
});
};
+10
View File
@@ -189,6 +189,11 @@ export interface StglClubVO {
*/
status: string;
/**
* 是否开启报名
*/
sighOpen: string;
/**
* 备注
*/
@@ -381,6 +386,11 @@ export interface StglClubForm {
*/
status?: string;
/**
* 是否开启报名
*/
sighOpen?: string;
/**
* 备注
*/
+6 -6
View File
@@ -67,9 +67,9 @@ export interface StglMemberVO {
email: string;
/**
* 角色类型
* 成员角色
*/
roleType: number;
roleType: string;
/**
* 角色名称
@@ -164,9 +164,9 @@ export interface StglMemberForm {
email?: string;
/**
* 角色类型
* 成员角色
*/
roleType?: number;
roleType?: string;
/**
* 角色名称
@@ -216,9 +216,9 @@ export interface StglMemberQuery extends PageQuery {
studentName?: string;
/**
* 角色类型
* 成员角色
*/
roleType?: number;
roleType?: string;
/**
* 状态
+13 -3
View File
@@ -1,6 +1,16 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MyClubVO, MyVolunteerTeamVO, MyActivityVO, MyRegistrationVO, ActivityRatingForm, MyRegistrationQuery, MyClubQuery, MyVolunteerQuery, MyActivityQuery } from './types';
import {
MyClubVO,
MyVolunteerTeamVO,
MyActivityVO,
MyRegistrationVO,
ActivityRatingForm,
MyRegistrationQuery,
MyClubQuery,
MyVolunteerQuery,
MyActivityQuery
} from './types';
/**
* 查询我参与的社团列表
@@ -18,7 +28,7 @@ export function getMyClubs(query?: MyClubQuery): AxiosPromise<MyClubVO[]> {
*/
export function getMyVolunteerTeams(query?: MyVolunteerQuery): AxiosPromise<MyVolunteerTeamVO[]> {
return request({
url: '/stgl/my/volunteerTeams',
url: '/stgl/my/volunteerTeams',
method: 'get',
params: query
});
@@ -65,4 +75,4 @@ export function canRateActivity(activityId: string | number): AxiosPromise<boole
url: `/stgl/my/activities/${activityId}/canRate`,
method: 'get'
});
}
}
+1 -1
View File
@@ -211,4 +211,4 @@ export interface MyRegistrationQuery {
registrationType?: string;
/** 审核状态 */
approvalStatus?: string;
}
}
+10
View File
@@ -49,6 +49,11 @@ export interface StglClubRegisterVO {
*/
canRegister: boolean;
/**
* 报名开启状态
*/
sighOpen?: string;
/**
* 招新周期
*/
@@ -242,6 +247,11 @@ export interface StglClubRegisterQuery extends PageQuery {
*/
canRegister?: boolean;
/**
* 报名开启状态
*/
sighOpen?: string;
/**
* 日期范围参数
*/
+23 -3
View File
@@ -4,6 +4,16 @@ export interface VolunteerMemberVO {
*/
id: string | number;
/**
* 用户ID
*/
userId?: string | number;
/**
* 成员类型(1-社团成员,2-志愿者成员)
*/
memberType?: number;
/**
* 学号
*/
@@ -65,7 +75,7 @@ export interface VolunteerMemberVO {
joinTime: string;
/**
* 状态(0正常 1停用)
* 状态(0待审核 1审核中 2已加入 3已驳回 4已退出)
*/
status: string;
@@ -101,6 +111,16 @@ export interface VolunteerMemberForm {
*/
id?: string | number;
/**
* 用户ID
*/
userId?: string | number;
/**
* 成员类型(1-社团成员,2-志愿者成员)
*/
memberType?: number;
/**
* 学号
*/
@@ -157,7 +177,7 @@ export interface VolunteerMemberForm {
joinTime?: string;
/**
* 状态(0正常 1停用)
* 状态(0待审核 1审核中 2已加入 3已驳回 4已退出)
*/
status?: string;
@@ -209,7 +229,7 @@ export interface VolunteerMemberQuery {
relationId?: string | number;
/**
* 状态(0正常 1停用)
* 状态(0待审核 1审核中 2已加入 3已驳回 4已退出)
*/
status?: string;
+19
View File
@@ -212,3 +212,22 @@ export function listVolunteerTeamForRegistration(query) {
params: query
});
}
/**
* 修改志愿者团队报名开启状态
* @param id 团队ID
* @param sighOpen 报名开启状态(Y/N)
*/
export function changeSighOpenStatus(id, sighOpen) {
return request({
url: '/stgl/volunteerTeam/changeSighOpenStatus',
method: 'put',
params: {
id,
sighOpen
},
headers: {
isEncrypt: 'true'
}
});
}
+33 -46
View File
@@ -1,33 +1,16 @@
<template>
<div class="activity-selector">
<el-input
v-model="selectedActivityName"
placeholder="请选择活动"
readonly
@click="openSelector"
style="cursor: pointer"
>
<el-input v-model="selectedActivityName" placeholder="请选择活动" readonly @click="openSelector" style="cursor: pointer">
<template #suffix>
<el-icon class="el-input__icon"><Search /></el-icon>
</template>
</el-input>
<!-- 活动选择对话框 -->
<el-dialog
v-model="dialogVisible"
title="选择活动"
width="800px"
append-to-body
>
<el-dialog v-model="dialogVisible" title="选择活动" width="800px" append-to-body>
<!-- 搜索框 -->
<div class="mb-4">
<el-input
v-model="searchKeyword"
placeholder="请输入活动名称搜索"
clearable
@input="handleSearch"
style="width: 300px"
>
<el-input v-model="searchKeyword" placeholder="请输入活动名称搜索" clearable @input="handleSearch" style="width: 300px">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
@@ -158,10 +141,10 @@ const confirmSelect = () => {
if (selectedActivity.value) {
const activityId = selectedActivity.value.id;
selectedActivityName.value = selectedActivity.value.activityName;
emit('update:modelValue', activityId);
emit('change', activityId);
dialogVisible.value = false;
}
};
@@ -169,13 +152,13 @@ const confirmSelect = () => {
/** 获取状态类型 */
const getStatusType = (status: string): string => {
const statusMap: Record<string, string> = {
'1': 'info', // 筹备中
'2': 'warning', // 待审批
'3': 'success', // 已通过
'4': 'primary', // 进行中
'5': 'info', // 已结束
'6': 'danger', // 已取消
'7': 'danger' // 已驳回
'1': 'info', // 筹备中
'2': 'warning', // 待审批
'3': 'success', // 已通过
'4': 'primary', // 进行中
'5': 'info', // 已结束
'6': 'danger', // 已取消
'7': 'danger' // 已驳回
};
return statusMap[status] || 'info';
};
@@ -202,24 +185,28 @@ const formatDate = (dateStr: string): string => {
};
/** 监听外部值变化 */
watch(() => props.modelValue, async (newVal) => {
if (newVal) {
try {
// 这里可以根据ID获取活动名称
const res = await listStglActivity({
pageNum: 1,
pageSize: 1
});
if (res.rows && res.rows.length > 0) {
selectedActivityName.value = res.rows[0].activityName;
watch(
() => props.modelValue,
async (newVal) => {
if (newVal) {
try {
// 这里可以根据ID获取活动名称
const res = await listStglActivity({
pageNum: 1,
pageSize: 1
});
if (res.rows && res.rows.length > 0) {
selectedActivityName.value = res.rows[0].activityName;
}
} catch (error) {
console.error('获取活动信息失败:', error);
}
} catch (error) {
console.error('获取活动信息失败:', error);
} else {
selectedActivityName.value = '';
}
} else {
selectedActivityName.value = '';
}
}, { immediate: true });
},
{ immediate: true }
);
</script>
<style scoped>
@@ -235,4 +222,4 @@ watch(() => props.modelValue, async (newVal) => {
.dialog-footer {
text-align: right;
}
</style>
</style>
+11 -3
View File
@@ -93,24 +93,32 @@ const currentHash = computed(() => {
watch(
() => props.modelValue,
(newValue) => {
console.log('AdvisorManager - 外部值变化:', newValue);
if (!updateLock.value) {
updateLock.value = true;
nextTick(() => {
advisors.value = newValue.map((item) => ({ ...item }));
// 确保深拷贝,避免引用问题
advisors.value = newValue ? newValue.map((item) => ({ ...item })) : [];
console.log('AdvisorManager - 更新内部值:', advisors.value);
updateLock.value = false;
});
}
},
{ immediate: true }
{ immediate: true, deep: false }
);
// 监听内部值变化 - 使用hash比较避免深度比较
watch(currentHash, (newHash) => {
watch(currentHash, (newHash, oldHash) => {
// 避免初始化时的重复触发
if (oldHash === undefined) return;
console.log('AdvisorManager - 内部值变化检测到:', newHash);
if (!updateLock.value) {
updateLock.value = true;
// 延迟emit,避免在TabPane环境下的立即更新冲突
nextTick(() => {
const newValue = advisors.value.map((advisor) => ({ ...advisor }));
console.log('AdvisorManager - 发送更新事件:', newValue);
emit('update:modelValue', newValue);
// 重新添加change事件,但添加防护机制
emit('change', newValue);
+27 -31
View File
@@ -1,7 +1,7 @@
<template>
<div class="club-selector">
<el-select
:model-value="modelValue"
<el-select
:model-value="modelValue"
@update:model-value="handleChange"
:placeholder="placeholder"
:clearable="clearable"
@@ -14,12 +14,7 @@
value-key="uniqueKey"
style="width: 100%"
>
<el-option
v-for="option in options"
:key="`${option.relatedType}-${option.relatedClubId}`"
:label="formatOptionLabel(option)"
:value="option"
>
<el-option v-for="option in options" :key="option.uniqueKey || `${option.relatedType}-${option.relatedClubId}`" :label="formatOptionLabel(option)" :value="option">
<div class="option-content">
<span class="option-name">{{ option.relatedClubName }}</span>
<span class="option-type">{{ getTypeLabel(option.relatedType) }}</span>
@@ -31,12 +26,7 @@
<el-dialog v-model="dialogVisible" title="选择社团/志愿者队伍" width="800px" append-to-body>
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="社团" name="club">
<el-table
:data="clubOptions"
@row-click="handleRowClick"
highlight-current-row
height="400"
>
<el-table :data="clubOptions" @row-click="handleRowClick" highlight-current-row height="400">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="社团名称" prop="relatedClubName" />
<el-table-column label="类型" width="80" align="center">
@@ -47,12 +37,7 @@
</el-table>
</el-tab-pane>
<el-tab-pane label="志愿者队伍" name="volunteer">
<el-table
:data="volunteerOptions"
@row-click="handleRowClick"
highlight-current-row
height="400"
>
<el-table :data="volunteerOptions" @row-click="handleRowClick" highlight-current-row height="400">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="队伍名称" prop="relatedClubName" />
<el-table-column label="类型" width="100" align="center">
@@ -63,7 +48,7 @@
</el-table>
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
@@ -111,6 +96,12 @@ const clubOptions = ref<ClubOption[]>([]);
const volunteerOptions = ref<ClubOption[]>([]);
const selectedOption = ref<ClubOption | null>(null);
// 监听 modelValue 变化
import { watch } from 'vue';
watch(() => props.modelValue, (newValue) => {
console.log('ClubSelector - modelValue changed:', newValue);
}, { immediate: true });
// 格式化选项标签
const formatOptionLabel = (option: ClubOption): string => {
const typeText = option.relatedType === 1 ? '社团' : '志愿者队伍';
@@ -124,6 +115,7 @@ const getTypeLabel = (type: number): string => {
// 处理值变化
const handleChange = (value: ClubOption | null) => {
console.log('ClubSelector - handleChange:', value);
emit('update:modelValue', value);
emit('change', value);
};
@@ -134,10 +126,10 @@ const remoteMethod = async (query: string) => {
loading.value = true;
try {
// 根据查询条件过滤选项
const filteredOptions = [...clubOptions.value, ...volunteerOptions.value].filter(option =>
const filteredOptions = [...clubOptions.value, ...volunteerOptions.value].filter((option) =>
option.relatedClubName.toLowerCase().includes(query.toLowerCase())
);
options.value = filteredOptions.map(item => ({
options.value = filteredOptions.map((item) => ({
...item,
uniqueKey: item.uniqueKey || `${item.relatedType}-${item.relatedClubId}`
}));
@@ -168,21 +160,25 @@ const confirmSelection = () => {
const loadOptions = async () => {
loading.value = true;
try {
const [clubRes, volunteerRes] = await Promise.all([
getClubOptions(),
getVolunteerTeamOptions()
]);
const [clubRes, volunteerRes] = await Promise.all([getClubOptions(), getVolunteerTeamOptions()]);
// 为每个选项添加唯一标识
clubOptions.value = (clubRes.data || []).map(item => ({
clubOptions.value = (clubRes.data || []).map((item) => ({
...item,
uniqueKey: `${item.relatedType}-${item.relatedClubId}`
}));
volunteerOptions.value = (volunteerRes.data || []).map(item => ({
volunteerOptions.value = (volunteerRes.data || []).map((item) => ({
...item,
uniqueKey: `${item.relatedType}-${item.relatedClubId}`
}));
options.value = [...clubOptions.value, ...volunteerOptions.value];
console.log('ClubSelector - 加载选项数据完成:', {
clubCount: clubOptions.value.length,
volunteerCount: volunteerOptions.value.length,
totalCount: options.value.length,
options: options.value
});
} catch (error) {
console.error('加载选项失败:', error);
} finally {
@@ -223,4 +219,4 @@ onMounted(() => {
justify-content: flex-end;
gap: 10px;
}
</style>
</style>
+25 -60
View File
@@ -13,11 +13,7 @@
</div>
<div v-else class="organizer-list">
<div
v-for="(organizer, index) in coOrganizers"
:key="index"
class="organizer-item"
>
<div v-for="(organizer, index) in coOrganizers" :key="index" class="organizer-item">
<div class="organizer-content">
<div class="organizer-info">
<div class="info-row">
@@ -30,13 +26,7 @@
</div>
</div>
<div class="organizer-actions">
<el-button
type="danger"
circle
size="small"
@click="handleDelete(index)"
title="删除"
>
<el-button type="danger" circle size="small" @click="handleDelete(index)" title="删除">
<el-icon><Delete /></el-icon>
</el-button>
</div>
@@ -45,43 +35,20 @@
</div>
<!-- 添加/编辑承办方对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
append-to-body
>
<el-form
ref="formRef"
:model="currentOrganizer"
:rules="rules"
label-width="100px"
>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" append-to-body>
<el-form ref="formRef" :model="currentOrganizer" :rules="rules" label-width="100px">
<el-form-item label="承办方名称" prop="name">
<el-input
v-model="currentOrganizer.name"
placeholder="请输入承办方名称"
maxlength="100"
show-word-limit
/>
<el-input v-model="currentOrganizer.name" placeholder="请输入承办方名称" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="联系人" prop="contact">
<el-input
v-model="currentOrganizer.contact"
placeholder="请输入联系人姓名"
maxlength="50"
/>
<el-input v-model="currentOrganizer.contact" placeholder="请输入联系人姓名" maxlength="50" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input
v-model="currentOrganizer.phone"
placeholder="请输入联系电话"
maxlength="20"
/>
<el-input v-model="currentOrganizer.phone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
@@ -138,9 +105,7 @@ const rules: FormRules = {
{ min: 1, max: 100, message: '承办方名称长度在1到100个字符', trigger: 'blur' }
],
contact: [
{ max: 50, message: '联系人姓名长度不能超过50个字符', trigger: 'blur' }
],
contact: [{ max: 50, message: '联系人姓名长度不能超过50个字符', trigger: 'blur' }],
phone: [
{ max: 20, message: '联系电话长度不能超过20个字符', trigger: 'blur' },
{ pattern: /^[0-9\-\s\+\(\)]*$/, message: '请输入正确的电话号码格式', trigger: 'blur' }
@@ -177,42 +142,42 @@ const handleAdd = () => {
dialogVisible.value = true;
};
// 删除承办方
const handleDelete = (index: number) => {
ElMessageBox.confirm('确定要删除这个承办方吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
coOrganizers.value.splice(index, 1);
ElMessage.success('删除成功');
}).catch(() => {
// 用户取消删除
});
})
.then(() => {
coOrganizers.value.splice(index, 1);
ElMessage.success('删除成功');
})
.catch(() => {
// 用户取消删除
});
};
// 确认添加/编辑
const handleConfirm = () => {
if (!formRef.value) return;
formRef.value.validate((valid) => {
if (valid) {
// 保存当前操作类型和数据,避免状态重置后丢失
const isEdit = editIndex.value >= 0;
const currentData = { ...currentOrganizer.value };
const currentIndex = editIndex.value;
// 立即关闭弹窗,避免后续操作影响弹窗状态
dialogVisible.value = false;
// 重置编辑状态
editIndex.value = -1;
// 重置表单
formRef.value?.resetFields();
// 延迟一帧更新数据,确保弹窗已完全关闭
setTimeout(() => {
if (isEdit) {
@@ -233,10 +198,10 @@ const handleConfirm = () => {
const handleCancel = () => {
// 立即关闭弹窗
dialogVisible.value = false;
// 重置编辑状态
editIndex.value = -1;
// 重置表单
formRef.value?.resetFields();
};
@@ -328,4 +293,4 @@ const handleCancel = () => {
justify-content: flex-end;
gap: 10px;
}
</style>
</style>
+21 -60
View File
@@ -13,11 +13,7 @@
</div>
<div v-else class="instructor-list">
<div
v-for="(instructor, index) in instructors"
:key="index"
class="instructor-item"
>
<div v-for="(instructor, index) in instructors" :key="index" class="instructor-item">
<div class="instructor-content">
<div class="instructor-info">
<div class="info-row">
@@ -56,58 +52,25 @@
</div>
<!-- 添加/编辑指导老师对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
append-to-body
>
<el-form
ref="formRef"
:model="currentInstructor"
:rules="rules"
label-width="100px"
>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" append-to-body>
<el-form ref="formRef" :model="currentInstructor" :rules="rules" label-width="100px">
<el-form-item label="老师姓名" prop="name">
<el-input
v-model="currentInstructor.name"
placeholder="请输入老师姓名"
maxlength="50"
show-word-limit
/>
<el-input v-model="currentInstructor.name" placeholder="请输入老师姓名" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="所属部门" prop="dept">
<el-input
v-model="currentInstructor.dept"
placeholder="请输入所属部门"
maxlength="100"
/>
<el-input v-model="currentInstructor.dept" placeholder="请输入所属部门" maxlength="100" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input
v-model="currentInstructor.phone"
placeholder="请输入联系电话"
maxlength="20"
/>
<el-input v-model="currentInstructor.phone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="currentInstructor.email"
placeholder="请输入邮箱地址"
maxlength="100"
/>
<el-input v-model="currentInstructor.email" placeholder="请输入邮箱地址" maxlength="100" />
</el-form-item>
<el-form-item label="指导学时" prop="hours">
<el-input-number
v-model="currentInstructor.hours"
:min="0"
:max="9999"
placeholder="请输入指导学时"
style="width: 100%"
/>
<el-input-number v-model="currentInstructor.hours" :min="0" :max="9999" placeholder="请输入指导学时" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
@@ -165,9 +128,7 @@ const rules: FormRules = {
{ required: true, message: '请输入老师姓名', trigger: 'blur' },
{ min: 1, max: 50, message: '老师姓名长度在1到50个字符', trigger: 'blur' }
],
dept: [
{ max: 100, message: '所属部门长度不能超过100个字符', trigger: 'blur' }
],
dept: [{ max: 100, message: '所属部门长度不能超过100个字符', trigger: 'blur' }],
phone: [
{ max: 20, message: '联系电话长度不能超过20个字符', trigger: 'blur' },
{ pattern: /^[0-9\-\s\+\(\)]*$/, message: '请输入正确的电话号码格式', trigger: 'blur' }
@@ -176,9 +137,7 @@ const rules: FormRules = {
{ max: 100, message: '邮箱长度不能超过100个字符', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
hours: [
{ type: 'number', min: 0, max: 9999, message: '指导学时必须在0到9999之间', trigger: 'blur' }
]
hours: [{ type: 'number', min: 0, max: 9999, message: '指导学时必须在0到9999之间', trigger: 'blur' }]
};
// 监听props变化
@@ -226,18 +185,20 @@ const handleDelete = (index: number) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
instructors.value.splice(index, 1);
ElMessage.success('删除成功');
}).catch(() => {
// 用户取消删除
});
})
.then(() => {
instructors.value.splice(index, 1);
ElMessage.success('删除成功');
})
.catch(() => {
// 用户取消删除
});
};
// 确认添加/编辑
const handleConfirm = async () => {
if (!formRef.value) return;
await formRef.value.validate((valid) => {
if (valid) {
if (editIndex.value >= 0) {
@@ -350,4 +311,4 @@ const handleCancel = () => {
justify-content: flex-end;
gap: 10px;
}
</style>
</style>
@@ -2,12 +2,7 @@
<div class="material-type-selector">
<div class="type-options">
<el-checkbox-group v-model="selectedTypes" @change="handleChange">
<el-checkbox
v-for="option in materialTypes"
:key="option.value"
:label="option.value"
class="type-checkbox"
>
<el-checkbox v-for="option in materialTypes" :key="option.value" :label="option.value" class="type-checkbox">
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
@@ -29,9 +24,7 @@ const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_material_type } = toRefs<any>(
proxy?.useDict('stgl_material_type')
);
const { stgl_material_type } = toRefs<any>(proxy?.useDict('stgl_material_type'));
const selectedTypes = ref<string[]>([]);
@@ -48,13 +41,17 @@ const handleChange = (value: string[]) => {
};
/** 监听外部值变化 */
watch(() => props.modelValue, (newVal) => {
if (newVal) {
selectedTypes.value = newVal.split(',').filter(item => item.trim());
} else {
selectedTypes.value = [];
}
}, { immediate: true });
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
selectedTypes.value = newVal.split(',').filter((item) => item.trim());
} else {
selectedTypes.value = [];
}
},
{ immediate: true }
);
</script>
<style scoped>
@@ -89,4 +86,4 @@ watch(() => props.modelValue, (newVal) => {
color: #409eff;
font-weight: 500;
}
</style>
</style>
+12 -19
View File
@@ -6,17 +6,10 @@
<template #header>
<div class="card-header">
<span>成员 {{ index + 1 }}</span>
<el-button
type="danger"
size="small"
icon="Delete"
circle
@click="removeMember(index)"
:disabled="memberList.length <= 1"
/>
<el-button type="danger" size="small" icon="Delete" circle @click="removeMember(index)" :disabled="memberList.length <= 1" />
</div>
</template>
<el-form :model="member" label-width="80px" size="small">
<el-row :gutter="20">
<el-col :span="12">
@@ -30,7 +23,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="性别">
@@ -46,7 +39,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="院系">
@@ -59,7 +52,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="班级">
@@ -76,7 +69,7 @@
</el-card>
</div>
</div>
<div class="add-member">
<el-button type="primary" icon="Plus" @click="addMember">添加成员</el-button>
</div>
@@ -179,18 +172,18 @@ nextTick(() => {
.team-member-manager {
.member-list {
margin-bottom: 20px;
.member-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.member-card {
border-radius: 8px;
.card-header {
display: flex;
justify-content: space-between;
@@ -200,11 +193,11 @@ nextTick(() => {
}
}
}
.add-member {
text-align: center;
padding: 16px 0;
border-top: 1px dashed #e4e7ed;
}
}
</style>
</style>
@@ -239,7 +239,7 @@
<!-- 签到操作区域 -->
<div v-if="form.activityId" class="sign-action-section">
<h4 class="section-title">签到操作</h4>
<el-row :gutter="20" style="text-align: center;">
<el-row :gutter="20" style="text-align: center">
<el-col :span="12">
<el-button
type="primary"
@@ -284,7 +284,7 @@
<el-descriptions-item label="活动地点">{{ viewForm.location }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ parseTime(viewForm.startTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ parseTime(viewForm.endTime) }}</el-descriptions-item>
<!-- 用户信息 -->
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
@@ -293,7 +293,7 @@
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewForm.phone }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ viewForm.email }}</el-descriptions-item>
<!-- 签到信息 -->
<el-descriptions-item label="签到状态">
<dict-tag :options="stgl_activity_sign_status" :value="viewForm.overallStatus" />
@@ -331,9 +331,7 @@ import {
import { StglActivityAttendanceVO, StglActivityAttendanceQuery, StglActivityAttendanceForm } from '@/api/stgl/activityattendance/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_sign_status } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_sign_status')
);
const { stgl_activity_type, stgl_activity_sign_status } = toRefs<any>(proxy?.useDict('stgl_activity_type', 'stgl_activity_sign_status'));
const attendanceList = ref<StglActivityAttendanceVO[]>([]);
const availableActivities = ref<StglActivityAttendanceVO[]>([]);
@@ -489,10 +487,10 @@ const handleActivityChange = async (activityId: string | number) => {
}
// 设置选中的活动信息
const activity = availableActivities.value.find(item => item.activityId === activityId);
const activity = availableActivities.value.find((item) => item.activityId === activityId);
if (activity) {
selectedActivity.value = activity;
// 填充用户信息
form.value.studentName = activity.studentName;
form.value.studentNo = activity.studentNo;
@@ -537,7 +535,7 @@ const handleSignIn = async () => {
await signIn(signInData);
proxy?.$modal.msgSuccess('签到成功');
// 刷新当前签到状态
await handleActivityChange(form.value.activityId);
await getList();
@@ -571,7 +569,7 @@ const handleSignOut = async () => {
await signOut(signOutData);
proxy?.$modal.msgSuccess('签退成功');
// 刷新当前签到状态
await handleActivityChange(form.value.activityId);
await getList();
@@ -701,4 +699,4 @@ onMounted(() => {
display: flex;
align-items: center;
}
</style>
</style>
@@ -19,7 +19,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -34,7 +34,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -49,7 +49,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -66,7 +66,7 @@
</div>
</el-col>
</el-row>
<div class="detail-item">
<label class="detail-label">活动海报</label>
<div class="detail-value">
@@ -78,7 +78,7 @@
</div>
</div>
</div>
<div class="detail-item">
<label class="detail-label">活动简介</label>
<div class="detail-value">
@@ -208,7 +208,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -223,7 +223,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20" v-if="activity.maxParticipants">
<el-col :span="12">
<div class="detail-item">
@@ -232,7 +232,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20" v-if="activity.maxTeams || activity.teamSizeMin || activity.teamSizeMax">
<el-col :span="8" v-if="activity.maxTeams">
<div class="detail-item">
@@ -253,7 +253,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -272,6 +272,11 @@
</el-row>
</el-tab-pane>
<!-- 报名查看标签页 -->
<el-tab-pane label="报名查看" name="registration-view">
<ActivityRegistrationView :activity-id="activity.id" :is-view-mode="true" />
</el-tab-pane>
<!-- 其他信息标签页(仅已办活动显示) -->
<el-tab-pane label="其他信息" name="other" v-if="activity.isFinished === 1">
<div class="detail-item">
@@ -282,7 +287,7 @@
</div>
</div>
</div>
<div class="detail-item" v-if="summaryAttachmentList.length > 0">
<label class="detail-label">总结报告附件</label>
<div class="detail-value">
@@ -301,7 +306,7 @@
</div>
</div>
</div>
<div class="detail-item">
<label class="detail-label">活动成果</label>
<div class="detail-value">
@@ -310,7 +315,7 @@
</div>
</div>
</div>
<div class="detail-item" v-if="achievementsAttachmentList.length > 0">
<label class="detail-label">成果附件</label>
<div class="detail-value">
@@ -334,7 +339,7 @@
<!-- 活动评分标签页 -->
<el-tab-pane label="活动评分" name="rating" v-if="activity.id">
<ActivityRatingView :activity-id="activity.id" />
<!-- 统计信息区域(只在活动评分标签页显示) -->
<div class="statistics-section">
<el-divider content-position="left">统计信息</el-divider>
@@ -362,6 +367,7 @@
import { computed, ref, onMounted, watch } from 'vue';
import { StglActivityVO, CoOrganizerInfo, InstructorInfo } from '@/api/stgl/activity/types';
import ActivityRatingView from './ActivityRatingView.vue';
import ActivityRegistrationView from './ActivityRegistrationView.vue';
import { listByIds } from '@/api/system/oss';
interface Props {
@@ -404,11 +410,10 @@ const instructorList = computed<InstructorInfo[]>(() => {
// 加载附件信息
const loadAttachments = async () => {
// 重置附件列表
summaryAttachmentList.value = [];
achievementsAttachmentList.value = [];
// 加载总结报告附件
if (props.activity.summaryAttachments) {
// 检查数据格式:如果以 [ 开头,说明是JSON格式;否则是ossId格式
@@ -423,7 +428,7 @@ const loadAttachments = async () => {
}
} else {
// ossId格式:通过API获取真实附件信息
const ossIds = attachmentStr.split(',').filter(id => id.trim());
const ossIds = attachmentStr.split(',').filter((id) => id.trim());
if (ossIds.length > 0) {
try {
const res = await listByIds(ossIds.join(','));
@@ -454,7 +459,7 @@ const loadAttachments = async () => {
}
} else {
// ossId格式:通过API获取真实附件信息
const ossIds = attachmentStr.split(',').filter(id => id.trim());
const ossIds = attachmentStr.split(',').filter((id) => id.trim());
if (ossIds.length > 0) {
try {
const res = await listByIds(ossIds.join(','));
@@ -480,11 +485,15 @@ const downloadAttachment = (file: any) => {
};
// 监听props变化,重新加载附件信息
watch(() => props.activity, (newActivity) => {
if (newActivity && newActivity.id) {
loadAttachments();
}
}, { immediate: true });
watch(
() => props.activity,
(newActivity) => {
if (newActivity && newActivity.id) {
loadAttachments();
}
},
{ immediate: true }
);
// 组件挂载时加载附件信息
onMounted(() => {
@@ -657,4 +666,4 @@ onMounted(() => {
.attachment-icon {
font-size: 14px;
}
</style>
</style>
@@ -3,7 +3,7 @@
<div v-if="ratings.length === 0" class="empty-state">
<el-empty description="暂无评分信息" :image-size="80" />
</div>
<div v-else>
<div class="rating-summary">
<el-row :gutter="20">
@@ -18,9 +18,9 @@
</el-col>
</el-row>
</div>
<el-divider />
<el-table :data="ratings" height="400" border>
<el-table-column label="评分人" prop="userName" width="120" />
<el-table-column label="评分" prop="ratingScore" width="120" align="center">
@@ -59,13 +59,13 @@ const averageRating = computed(() => {
// 计算最高评分
const maxRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.max(...ratings.value.map(rating => rating.ratingScore));
return Math.max(...ratings.value.map((rating) => rating.ratingScore));
});
// 加载评分数据
const loadRatings = async () => {
if (!props.activityId) return;
loading.value = true;
try {
const res = await getActivityRatings(props.activityId);
@@ -101,4 +101,4 @@ onMounted(() => {
text-align: center;
padding: 60px 20px;
}
</style>
</style>
+34 -46
View File
@@ -124,24 +124,10 @@
></el-button>
</el-tooltip>
<el-tooltip content="已报名" placement="top" v-else-if="isRegistered(scope.row)">
<el-button
link
type="warning"
icon="Check"
size="default"
style="font-size: 16px"
disabled
></el-button>
<el-button link type="warning" icon="Check" size="default" style="font-size: 16px" disabled></el-button>
</el-tooltip>
<el-tooltip content="报名已结束" placement="top" v-else>
<el-button
link
type="info"
icon="Clock"
size="default"
style="font-size: 16px"
disabled
></el-button>
<el-button link type="info" icon="Clock" size="default" style="font-size: 16px" disabled></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -169,7 +155,7 @@
<el-form-item label="活动名称">
<el-input v-model="selectedActivity.activityName" disabled />
</el-form-item>
<el-form-item label="报名类型" prop="registrationType">
<el-radio-group v-model="registerForm.registrationType" @change="handleRegistrationTypeChange">
<el-radio v-if="isRegistrationTypeAllowed(1)" :value="1">个人报名</el-radio>
@@ -240,7 +226,7 @@
<el-form-item label="团队描述" prop="teamDescription">
<el-input v-model="registerForm.teamDescription" type="textarea" :rows="3" placeholder="请输入团队描述" />
</el-form-item>
<!-- 团队负责人信息 -->
<el-divider content-position="left">团队负责人</el-divider>
<el-row :gutter="20">
@@ -301,18 +287,18 @@
</el-row>
<!-- 团队成员管理 -->
<el-divider content-position="left" style="margin-left: 0px;">团队成员</el-divider>
<VicePresidentManager
:model-value="registerForm.teamMembersNew"
<el-divider content-position="left" style="margin-left: 0px">团队成员</el-divider>
<VicePresidentManager
:model-value="registerForm.teamMembersNew"
title="团队管理"
addButtonText="添加团队成员"
emptyTip="暂无团队成员,点击上方按钮添加"
selectPlaceholder="选择团队成员"
@change="handleTeamMembersNewChange"
@change="handleTeamMembersNewChange"
/>
</div>
<el-form-item label="申请理由" prop="remark" style="margin-top: 24px;">
<el-form-item label="申请理由" prop="remark" style="margin-top: 24px">
<el-input v-model="registerForm.remark" type="textarea" :rows="4" placeholder="请简要说明您的申请理由和个人优势(选填)" />
</el-form-item>
</el-form>
@@ -398,10 +384,12 @@ const data = reactive<PageData<StglActivityRegistrationForm, StglActivityQuery>>
const { queryParams } = toRefs(data);
// 报名表单
const registerForm = reactive<StglActivityRegistrationForm & {
presidentStudent?: StudentVO | null;
teamMembersNew?: VicePresidentVO[];
}>({
const registerForm = reactive<
StglActivityRegistrationForm & {
presidentStudent?: StudentVO | null;
teamMembersNew?: VicePresidentVO[];
}
>({
activityId: undefined,
registrationType: undefined,
studentName: undefined,
@@ -426,10 +414,10 @@ const registerRules = reactive({
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
phone: [
{ required: true, message: '联系电话不能为空', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
} as any
],
teamName: [{ required: true, message: '团队名称不能为空', trigger: 'blur' }]
@@ -442,7 +430,7 @@ const getList = async () => {
const res = await listRegistrableActivities(queryParams.value);
activityList.value = res.rows;
total.value = res.total;
// 批量检查用户报名状态
await checkUserRegistrationStatus();
} catch (error) {
@@ -464,7 +452,7 @@ const checkUserRegistrationStatus = async () => {
userRegistrationStatus.value[activity.id as number] = false;
}
});
await Promise.all(promises);
};
@@ -492,7 +480,7 @@ const handleView = async (row: StglActivityVO) => {
const handleRegister = async (row: StglActivityVO) => {
selectedActivity.value = row;
resetRegisterForm();
registerDialog.visible = true;
};
@@ -516,7 +504,7 @@ const resetRegisterForm = () => {
remark: undefined,
presidentStudent: null
});
nextTick(() => {
registerFormRef.value?.resetFields();
});
@@ -527,7 +515,7 @@ const loadUserInfoToForm = async () => {
try {
const response = await getPersonalInfo();
const studentInfo: StudentMsgForm = response.data;
if (studentInfo) {
registerForm.studentName = studentInfo.name || '';
registerForm.studentNo = studentInfo.studentNo || '';
@@ -555,7 +543,7 @@ const handleRegistrationTypeChange = async (value: number) => {
registerForm.teamMembers = [];
registerForm.teamMembersNew = [];
registerForm.presidentStudent = null;
// 自动填充当前用户信息
await loadUserInfoToForm();
} else if (value === 2) {
@@ -583,7 +571,7 @@ const handlePresidentChange = async (student: StudentVO | null) => {
registerForm.department = student.collegeName || '';
registerForm.major = student.majorName || '';
registerForm.className = student.className || '';
// 清空email,让用户手动填写或通过其他方式获取
registerForm.email = undefined;
} else {
@@ -602,9 +590,9 @@ const handlePresidentChange = async (student: StudentVO | null) => {
/** 新版团队成员变化处理 */
const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
registerForm.teamMembersNew = members;
// 同步到原有字段,用于兼容后端
registerForm.teamMembers = members.map(member => ({
registerForm.teamMembers = members.map((member) => ({
userId: member.student?.id || 0,
studentNo: member.student?.studentNo || '',
studentName: member.name || member.student?.name || '',
@@ -616,7 +604,7 @@ const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
className: member.class || member.student?.className || '',
isLeader: 0 // 团队成员,非负责人
}));
// 验证团队人数限制
const totalMembers = members.length + 1; // 包含负责人
if (selectedActivity.value.teamSizeMin && totalMembers < selectedActivity.value.teamSizeMin) {
@@ -630,7 +618,7 @@ const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
/** 团队成员变化处理(兼容原有方法) */
const handleTeamMembersChange = (members: TeamMemberInfo[]) => {
registerForm.teamMembers = members;
// 验证团队人数限制
const totalMembers = members.length + 1; // 包含负责人
if (selectedActivity.value.teamSizeMin && totalMembers < selectedActivity.value.teamSizeMin) {
@@ -679,7 +667,7 @@ const submitRegister = () => {
if (registerForm.registrationType === 2) {
submitData.teamName = registerForm.teamName;
submitData.teamDescription = registerForm.teamDescription;
// 将团队成员数组转换为JSON字符串(后端期望String类型)
if (registerForm.teamMembers && Array.isArray(registerForm.teamMembers)) {
submitData.teamMembers = JSON.stringify(registerForm.teamMembers);
@@ -688,11 +676,11 @@ const submitRegister = () => {
}
}
// 个人报名时不发送任何团队相关字段
await registerActivity(submitData);
proxy?.$modal.msgSuccess('报名成功,请等待审核');
registerDialog.visible = false;
// 更新报名状态
userRegistrationStatus.value[selectedActivity.value.id as number] = true;
} catch (error) {
@@ -792,4 +780,4 @@ onMounted(() => {
margin-bottom: 0 !important;
}
}
</style>
</style>
+198 -44
View File
@@ -45,6 +45,11 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="关联社团" prop="relatedClubId">
<ClubSelector v-model="queryParams.relatedClub" @change="handleQueryRelatedClubChange" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="活动时间">
<el-date-picker
@@ -80,23 +85,20 @@
<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-button icon="Edit" @click="handleUpdate()" 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')"
@click="handleDelete()"
<el-button
icon="Delete"
:disabled="multiple || (selectedActivity && selectedActivity.activityStatus !== '1')"
@click="handleDelete()"
v-hasPermi="['stgl:activity:remove']"
:title="selectedActivity && selectedActivity.activityStatus !== '1' ? '只有筹备中的活动可以删除' : ''"
>删除</el-button>
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activity:export']">导出</el-button>
@@ -180,7 +182,7 @@
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createByName" width="100" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="320" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
@@ -226,6 +228,17 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="成员审核" placement="top">
<el-button
link
type="info"
icon="User"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleMembersReview(scope.row)"
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.activityStatus === '1'">
<el-button
link
@@ -356,7 +369,11 @@
<div class="section-divider">
<h4 class="section-title">指导老师信息</h4>
</div>
<AdvisorManager v-model="form.advisors" @change="handleAdvisorsChange" />
<AdvisorManager
:key="`advisor-manager-${form.id || 'new'}-${formKey}`"
v-model="form.advisors"
@change="handleAdvisorsChange"
/>
</el-tab-pane>
<el-tab-pane label="报名设置" name="registration">
@@ -427,24 +444,9 @@
</el-row>
</el-tab-pane>
<el-tab-pane label="其他信息" name="other" v-if="form.isFinished === 1">
<el-form-item label="活动总结报告" prop="summary">
<el-input v-model="form.summary" type="textarea" placeholder="请输入活动总结报告" resize="none" :rows="6" />
</el-form-item>
<el-form-item label="总结报告附件" prop="summaryAttachments">
<file-upload v-model="form.summaryAttachments" />
</el-form-item>
<el-form-item label="活动成果" prop="achievements">
<el-input v-model="form.achievements" type="textarea" placeholder="请输入活动成果" resize="none" :rows="6" />
</el-form-item>
<el-form-item label="成果附件" prop="achievementsAttachments">
<file-upload v-model="form.achievementsAttachments" />
</el-form-item>
</el-tab-pane>
<el-tab-pane label="活动评分" name="rating" v-if="form.id && !dialog.title.includes('新增')">
<ActivityRatingView :activity-id="form.id" />
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -509,11 +511,20 @@
</div>
</template>
</el-dialog>
<!-- 成员审核弹窗 -->
<ActivityMembersDialog
v-model:visible="membersDialog.visible"
:activity-id="selectedActivity?.id"
:activity-name="selectedActivity?.activityName"
ref="membersDialogRef"
/>
</div>
</template>
<script setup name="StglActivity" lang="ts">
import { nextTick } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
listStglActivity,
getStglActivity,
@@ -531,9 +542,13 @@ import InstructorManager from '@/components/InstructorManager/index.vue';
import AdvisorManager from '@/components/AdvisorManager/index.vue';
import ActivityRatingView from './components/ActivityRatingView.vue';
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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const router = useRouter();
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')
);
@@ -549,11 +564,13 @@ const total = ref(0);
const activeTab = ref('basic');
const dateRange = ref<[string, string]>();
const selectedActivity = ref<StglActivityVO | null>(null);
const formKey = ref(0);
const queryFormRef = ref<ElFormInstance>();
const activityFormRef = ref<ElFormInstance>();
const copyFormRef = ref<ElFormInstance>();
const summaryFormRef = ref<ElFormInstance>();
const membersDialogRef = ref();
const dialog = reactive<DialogOption>({
visible: false,
@@ -575,6 +592,11 @@ const summaryDialog = reactive<DialogOption>({
title: ''
});
const membersDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const copyLoading = ref(false);
const summaryLoading = ref(false);
@@ -619,7 +641,7 @@ const initFormData: StglActivityForm = {
remark: undefined
};
const data = reactive<PageData<StglActivityForm, StglActivityQuery>>({
const data = reactive<PageData<StglActivityForm, StglActivityQuery & { relatedClub?: ClubOption | null }>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
@@ -631,6 +653,7 @@ const data = reactive<PageData<StglActivityForm, StglActivityQuery>>({
endTime: undefined,
location: undefined,
relatedClubId: undefined,
relatedClub: null,
organizerName: undefined,
registrationType: undefined,
registrationStart: undefined,
@@ -654,6 +677,45 @@ const { queryParams, form, rules } = toRefs(data);
const viewForm = ref<StglActivityVO>({} as StglActivityVO);
/** 初始化URL参数 */
const initUrlParams = () => {
const { relatedClubId, relatedClubName, relatedTeamId, relatedTeamName, from } = route.query;
if (relatedClubId && relatedClubName && from === 'club') {
// 设置社团查询参数
queryParams.value.relatedClubId = relatedClubId as string | number;
queryParams.value.relatedClub = {
relatedClubId: relatedClubId as string | number,
relatedClubName: relatedClubName as string,
relatedType: 1, // 社团类型
uniqueKey: `1-${relatedClubId}`
};
console.log('初始化URL参数 - 社团过滤:', {
relatedClubId,
relatedClubName,
queryParams: queryParams.value.relatedClub
});
} else if (relatedTeamId && relatedTeamName && from === 'team') {
// 设置志愿者队伍查询参数
queryParams.value.relatedClubId = relatedTeamId as string | number;
queryParams.value.relatedClub = {
relatedClubId: relatedTeamId as string | number,
relatedClubName: relatedTeamName as string,
relatedType: 2, // 志愿者队伍类型
uniqueKey: `2-${relatedTeamId}`
};
console.log('初始化URL参数 - 志愿者队伍过滤:', {
relatedTeamId,
relatedTeamName,
queryParams: queryParams.value.relatedClub
});
}
};
// 复制表单
const copyForm = reactive({
sourceId: undefined as string | number | undefined,
@@ -714,6 +776,14 @@ const reset = () => {
// 使用nextTick来避免递归更新
nextTick(() => {
form.value = { ...initFormData };
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = undefined;
// 更新formKey以强制重新渲染组件
formKey.value++;
activeTab.value = 'basic';
nextTick(() => {
activityFormRef.value?.resetFields();
@@ -744,19 +814,66 @@ const handleSelectionChange = (selection: StglActivityVO[]) => {
/** 新增按钮操作 */
const handleAdd = () => {
reset();
// 先重置表单
form.value = { ...initFormData };
activeTab.value = 'basic';
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = undefined;
// 更新formKey以强制重新渲染组件
formKey.value++;
console.log('新增活动 - 初始化表单数据:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors,
formKey: formKey.value
});
// 如果有查询条件中的关联组织(社团或志愿者队伍),自动填充关联信息
if (queryParams.value.relatedClub) {
form.value.relatedClub = queryParams.value.relatedClub;
form.value.relatedClubId = queryParams.value.relatedClub.relatedClubId;
form.value.relatedType = queryParams.value.relatedClub.relatedType;
console.log('新增活动 - 自动填充关联组织信息:', {
relatedType: queryParams.value.relatedClub.relatedType,
relatedClubId: queryParams.value.relatedClub.relatedClubId,
relatedClubName: queryParams.value.relatedClub.relatedClubName
});
// 确保表单数据正确设置
console.log('新增活动 - 表单数据设置后:', {
formRelatedClub: form.value.relatedClub,
formRelatedClubId: form.value.relatedClubId,
formRelatedType: form.value.relatedType
});
}
dialog.visible = true;
dialog.title = '添加活动信息';
// 确保对话框完全打开后再检查表单状态
nextTick(() => {
console.log('新增活动 - 对话框打开后的表单状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors
});
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: StglActivityVO) => {
reset();
const _id = row?.id || ids.value[0];
// 先获取活动数据检查状态
const res = await getStglActivity(_id);
// 检查活动状态是否允许修改(2:报名中、3:报名结束、4:进行中、5:已结束 不允许修改)
if (['2', '3', '4', '5'].includes(res.data.activityStatus)) {
const statusDict = stgl_activity_status.value.find((dict: any) => dict.value === res.data.activityStatus);
@@ -857,17 +974,23 @@ const submitForm = () => {
// 处理AdvisorManager组件的指导老师数据
if (submitData.advisors && submitData.advisors.length > 0) {
console.log('提交前 - 原始advisors数据:', submitData.advisors);
// 确保advisors数据完整
submitData.advisors = submitData.advisors.map((advisor: any) => {
return {
const processedAdvisor = {
...advisor,
name: advisor.name || advisor.teacher?.nickName || '',
dept: advisor.dept || advisor.teacher?.deptName || '',
phone: advisor.phone || advisor.teacher?.phonenumber || '',
email: advisor.email || advisor.teacher?.email || ''
};
console.log('处理后的指导老师数据:', processedAdvisor);
return processedAdvisor;
});
submitData.instructors = JSON.stringify(submitData.advisors);
console.log('提交的指导老师JSON数据:', submitData.instructors);
// 同步第一个指导老师信息到instructorList(保持兼容性)
if (submitData.advisors.length > 0) {
@@ -877,7 +1000,13 @@ const submitForm = () => {
phone: advisor.phone,
email: advisor.email
}));
console.log('同步的instructorList数据:', submitData.instructorList);
}
} else {
console.log('提交时 - 没有指导老师数据或数据为空');
submitData.advisors = [];
submitData.instructors = '';
submitData.instructorList = [];
}
try {
@@ -902,7 +1031,7 @@ const submitForm = () => {
const handleDelete = async (row?: StglActivityVO) => {
const _ids = row?.id || ids.value;
let confirmMessage = '';
// 检查删除权限 - 只有筹备中(状态1)的活动可以删除
if (row) {
if (row.activityStatus !== '1') {
@@ -914,15 +1043,15 @@ const handleDelete = async (row?: StglActivityVO) => {
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) => item.activityStatus !== '1');
if (nonDeletableItems.length > 0) {
const names = nonDeletableItems.map(item => item.activityName).join('、');
const names = nonDeletableItems.map((item) => item.activityName).join('、');
proxy?.$modal.msgWarning(`活动"${names}"不是筹备中状态,只有筹备中的活动可以删除`);
return;
}
if (selectedItems.length === 1) {
confirmMessage = `是否确认删除活动"${selectedItems[0].activityName}"?`;
} else {
@@ -1009,12 +1138,12 @@ const handleSummary = async (row?: StglActivityVO) => {
summaryForm.achievements = fullActivity.achievements || '';
summaryForm.summaryAttachments = fullActivity.summaryAttachments || '';
summaryForm.achievementsAttachments = fullActivity.achievementsAttachments || '';
console.log('handleSummary - 设置到表单的数据:', {
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
summaryDialog.visible = true;
};
@@ -1039,7 +1168,7 @@ const confirmSummary = async () => {
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
await summarizeActivity({
id: summaryForm.id!,
summary: summaryForm.summary,
@@ -1067,8 +1196,6 @@ const handleAchievementsAttachmentsChange = (attachments: string) => {
summaryForm.achievementsAttachments = attachments;
};
/** 导出按钮操作 */
const handleExport = async () => {
try {
@@ -1082,11 +1209,26 @@ const handleExport = async () => {
/** 关联社团变化处理 */
const handleRelatedClubChange = (club: ClubOption | null) => {
console.log('活动表单 - handleRelatedClubChange:', club);
form.value.relatedClub = club;
form.value.relatedClubId = club?.relatedClubId;
form.value.relatedType = club?.relatedType; // 修复:同时设置关联类型
console.log('活动表单 - 更新后的表单数据:', {
relatedClub: form.value.relatedClub,
relatedClubId: form.value.relatedClubId,
relatedType: form.value.relatedType
});
};
/** 查询关联社团变化处理 */
const handleQueryRelatedClubChange = (club: ClubOption | null) => {
queryParams.value.relatedClub = club;
queryParams.value.relatedClubId = club?.relatedClubId;
};
/** 承办方变化处理 */
const handleCoOrganizersChange = (coOrganizers: CoOrganizerInfo[]) => {
form.value.coOrganizerList = coOrganizers;
@@ -1099,7 +1241,9 @@ const handleInstructorsChange = (instructors: InstructorInfo[]) => {
/** 指导老师变化处理 */
const handleAdvisorsChange = (advisors: AdvisorVO[]) => {
console.log('handleAdvisorsChange - 指导老师数据变化:', advisors);
form.value.advisors = advisors;
console.log('handleAdvisorsChange - 更新后的form.advisors:', form.value.advisors);
};
/** 报名方式变化处理 */
@@ -1122,6 +1266,13 @@ const shouldShowSummaryButton = (row: StglActivityVO): boolean => {
return row.activityStatus === '4' || row.activityStatus === '5';
};
/** 成员审核按钮操作 */
const handleMembersReview = (row: StglActivityVO) => {
selectedActivity.value = row;
membersDialog.visible = true;
membersDialog.title = `${row.activityName}活动报名管理`;
};
/** 格式化日期 */
const formatDate = (dateStr: string): string => {
if (!dateStr) return '';
@@ -1153,6 +1304,7 @@ const getAdvisorNames = (activity: StglActivityVO): string[] => {
};
onMounted(() => {
initUrlParams();
getList();
});
</script>
@@ -1269,4 +1421,6 @@ onMounted(() => {
border: 1px solid #dcdfe6;
}
}
</style>
@@ -18,18 +18,10 @@
<el-descriptions-item label="截止时间">
{{ formatDate(task.deadline) || '未设置' }}
</el-descriptions-item>
<el-descriptions-item label="文件大小限制">
{{ task.fileSizeLimit || 0 }} MB
</el-descriptions-item>
<el-descriptions-item label="文件数量限制">
{{ task.fileCountLimit || 0 }} 个
</el-descriptions-item>
<el-descriptions-item label="总参与人数">
{{ task.totalParticipants || 0 }} 人
</el-descriptions-item>
<el-descriptions-item label="已提交人数">
{{ task.submittedCount || 0 }} 人
</el-descriptions-item>
<el-descriptions-item label="文件大小限制"> {{ task.fileSizeLimit || 0 }} MB </el-descriptions-item>
<el-descriptions-item label="文件数量限制"> {{ task.fileCountLimit || 0 }} 个 </el-descriptions-item>
<el-descriptions-item label="总参与人数"> {{ task.totalParticipants || 0 }} 人 </el-descriptions-item>
<el-descriptions-item label="已提交人数"> {{ task.submittedCount || 0 }} 人 </el-descriptions-item>
<el-descriptions-item label="创建者">
{{ task.createByName || '未知' }}
</el-descriptions-item>
@@ -38,13 +30,7 @@
</el-descriptions-item>
<el-descriptions-item label="材料收集类型" :span="2">
<div class="collection-types">
<el-tag
v-for="type in parseCollectionTypes(task.collectionTypes)"
:key="type"
type="info"
size="small"
class="mr-2"
>
<el-tag v-for="type in parseCollectionTypes(task.collectionTypes)" :key="type" type="info" size="small" class="mr-2">
{{ getMaterialTypeLabel(type) }}
</el-tag>
</div>
@@ -96,13 +82,7 @@
</el-card>
<!-- 提交记录列表 -->
<el-table
v-loading="submissionsLoading"
:data="submissions"
border
style="width: 100%"
max-height="400px"
>
<el-table v-loading="submissionsLoading" :data="submissions" border style="width: 100%" max-height="400px">
<el-table-column label="学号" prop="studentNo" width="120" />
<el-table-column label="学生姓名" prop="studentName" width="100" />
<el-table-column label="提交类型" width="100">
@@ -120,10 +100,7 @@
</el-table-column>
<el-table-column label="审核状态" width="100">
<template #default="scope">
<el-tag
:type="getReviewStatusType(scope.row.reviewStatus) as any"
size="small"
>
<el-tag :type="getReviewStatusType(scope.row.reviewStatus) as any" size="small">
{{ getReviewStatusText(scope.row.reviewStatus) }}
</el-tag>
</template>
@@ -140,14 +117,7 @@
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click="viewSubmissionDetail(scope.row)"
>
查看详情
</el-button>
<el-button link type="primary" size="small" @click="viewSubmissionDetail(scope.row)"> 查看详情 </el-button>
</template>
</el-table-column>
</el-table>
@@ -169,12 +139,7 @@
</el-tabs>
<!-- 提交详情对话框 -->
<el-dialog
v-model="submissionDetailVisible"
title="提交详情"
width="600px"
append-to-body
>
<el-dialog v-model="submissionDetailVisible" title="提交详情" width="600px" append-to-body>
<SubmissionDetailView :submission="selectedSubmission" />
</el-dialog>
</div>
@@ -192,9 +157,7 @@ interface Props {
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_material_type } = toRefs<any>(
proxy?.useDict('stgl_material_type')
);
const { stgl_material_type } = toRefs<any>(proxy?.useDict('stgl_material_type'));
const activeTab = ref('task');
const submissionsLoading = ref(false);
@@ -213,7 +176,7 @@ const submissionQuery = reactive({
/** 获取提交记录 */
const getSubmissions = async () => {
if (!props.task.id) return;
submissionsLoading.value = true;
try {
submissionQuery.taskId = String(props.task.id);
@@ -230,7 +193,7 @@ const getSubmissions = async () => {
/** 获取统计信息 */
const getStatistics = async () => {
if (!props.task.id) return;
try {
const res = await getSubmissionStatistics(props.task.id);
statistics.value = res.data || {};
@@ -248,7 +211,7 @@ const viewSubmissionDetail = (submission: StglMaterialSubmissionVO) => {
/** 解析收集类型 */
const parseCollectionTypes = (types: string): string[] => {
if (!types) return [];
return types.split(',').filter(type => type.trim());
return types.split(',').filter((type) => type.trim());
};
/** 获取材料类型标签 */
@@ -273,9 +236,9 @@ const formatDate = (dateStr: string): string => {
/** 获取审核状态类型 */
const getReviewStatusType = (status: number): string => {
const statusMap: Record<number, string> = {
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
};
return statusMap[status] || 'info';
};
@@ -299,12 +262,16 @@ watch(activeTab, (newTab) => {
});
/** 监听任务变化 */
watch(() => props.task.id, (newId) => {
if (newId && activeTab.value === 'submissions') {
getSubmissions();
getStatistics();
}
}, { immediate: true });
watch(
() => props.task.id,
(newId) => {
if (newId && activeTab.value === 'submissions') {
getSubmissions();
getStatistics();
}
},
{ immediate: true }
);
</script>
<style scoped>
@@ -370,4 +337,4 @@ watch(() => props.task.id, (newId) => {
display: flex;
justify-content: center;
}
</style>
</style>
@@ -7,21 +7,16 @@
<el-radio :label="2">团队提交</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本内容" prop="contentText">
<el-input
v-model="form.contentText"
type="textarea"
placeholder="请输入文本内容"
:rows="4"
/>
<el-input v-model="form.contentText" type="textarea" placeholder="请输入文本内容" :rows="4" />
</el-form-item>
<el-form-item label="附件上传">
<file-upload v-model="form.fileUrls" />
</el-form-item>
</el-form>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
@@ -95,4 +90,4 @@ const handleCancel = () => {
gap: 12px;
margin-top: 24px;
}
</style>
</style>
@@ -68,9 +68,9 @@ const formatDate = (dateStr: string): string => {
/** 获取审核状态类型 */
const getReviewStatusType = (status: number): string => {
const statusMap: Record<number, string> = {
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
};
return statusMap[status] || 'info';
};
@@ -98,4 +98,4 @@ const getReviewStatusText = (status: number): string => {
max-height: 200px;
overflow-y: auto;
}
</style>
</style>
+33 -79
View File
@@ -67,45 +67,26 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activitymaterial:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['stgl:activitymaterial:edit']"
>修改</el-button>
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activitymaterial:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['stgl:activitymaterial:remove']"
>删除</el-button>
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activitymaterial:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Open"
:disabled="single"
@click="handleStatusChange(1)"
v-hasPermi="['stgl:activitymaterial:status']"
>开启</el-button>
<el-button icon="Open" :disabled="single" @click="handleStatusChange(1)" v-hasPermi="['stgl:activitymaterial:status']">开启</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="TurnOff"
:disabled="single"
@click="handleStatusChange(0)"
v-hasPermi="['stgl:activitymaterial:status']"
>关闭</el-button>
<el-button icon="TurnOff" :disabled="single" @click="handleStatusChange(0)" v-hasPermi="['stgl:activitymaterial:status']">关闭</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
icon="Upload"
:disabled="single || !canUpload"
@click="handleUpload()"
<el-button
type="success"
icon="Upload"
:disabled="single || !canUpload"
@click="handleUpload()"
v-hasPermi="['stgl:activitymaterial:upload']"
>上传</el-button>
>上传</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activitymaterial:export']">导出</el-button>
@@ -213,18 +194,10 @@
<ActivitySelector v-model="form.activityId" @change="handleActivityChange" />
</el-form-item>
<el-form-item label="任务名称" prop="taskName">
<el-input
v-model="form.taskName"
placeholder="请输入任务名称"
/>
<el-input v-model="form.taskName" placeholder="请输入任务名称" />
</el-form-item>
<el-form-item label="收集说明" prop="taskDescription">
<el-input
v-model="form.taskDescription"
type="textarea"
placeholder="请输入收集说明"
:rows="3"
/>
<el-input v-model="form.taskDescription" type="textarea" placeholder="请输入收集说明" :rows="3" />
</el-form-item>
<el-form-item label="截止时间" prop="deadline">
<el-date-picker
@@ -239,30 +212,13 @@
<MaterialTypeSelector v-model="form.collectionTypes" />
</el-form-item>
<el-form-item label="文件大小要求(MB)" prop="fileSizeLimit">
<el-input-number
v-model="form.fileSizeLimit"
:min="1"
:max="1024"
style="width: 100%"
controls-position="right"
/>
<el-input-number v-model="form.fileSizeLimit" :min="1" :max="1024" style="width: 100%" controls-position="right" />
</el-form-item>
<el-form-item label="上传数量上限" prop="fileCountLimit">
<el-input-number
v-model="form.fileCountLimit"
:min="1"
:max="100"
style="width: 100%"
controls-position="right"
/>
<el-input-number v-model="form.fileCountLimit" :min="1" :max="100" style="width: 100%" controls-position="right" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注"
:rows="2"
/>
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" :rows="2" />
</el-form-item>
</el-form>
<template #footer>
@@ -280,10 +236,10 @@
<!-- 上传材料对话框 -->
<el-dialog title="上传材料" v-model="uploadDialog.visible" width="600px" append-to-body>
<MaterialUploadForm
:task-id="uploadDialog.taskId"
<MaterialUploadForm
:task-id="uploadDialog.taskId"
:activity-id="uploadDialog.activityId"
@success="handleUploadSuccess"
@success="handleUploadSuccess"
@cancel="uploadDialog.visible = false"
v-if="uploadDialog.visible"
/>
@@ -292,11 +248,11 @@
</template>
<script setup lang="ts">
import {
listStglMaterialTask,
getStglMaterialTask,
delStglMaterialTask,
addStglMaterialTask,
import {
listStglMaterialTask,
getStglMaterialTask,
delStglMaterialTask,
addStglMaterialTask,
updateStglMaterialTask,
updateTaskStatus,
exportStglMaterialTask
@@ -387,7 +343,7 @@ const getList = async () => {
params.deadlineStart = '';
params.deadlineEnd = '';
}
try {
const res = await listStglMaterialTask(params);
taskList.value = res.rows;
@@ -412,7 +368,7 @@ const resetQuery = () => {
/** 多选框选中数据 */
const handleSelectionChange = (selection: StglMaterialTaskVO[]) => {
ids.value = selection.map(item => item.id);
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
@@ -420,7 +376,7 @@ const handleSelectionChange = (selection: StglMaterialTaskVO[]) => {
/** 计算是否可以上传 */
const canUpload = computed(() => {
if (single.value) return false;
const selectedTask = taskList.value.find(item => ids.value.includes(item.id));
const selectedTask = taskList.value.find((item) => ids.value.includes(item.id));
return selectedTask?.taskStatus === 1;
});
@@ -487,13 +443,11 @@ const handleStatusChange = async (status: number, row?: StglMaterialTaskVO) => {
const taskIds = row?.id ? [row.id] : ids.value;
const statusText = status === 1 ? '开启' : '关闭';
await proxy?.$modal.confirm(`确认要${statusText}所选的材料收集任务吗?`);
loading.value = true;
try {
// 处理单个或多个任务状态更新
const promises = Array.isArray(taskIds)
? taskIds.map(id => updateTaskStatus(id, status))
: [updateTaskStatus(taskIds, status)];
const promises = Array.isArray(taskIds) ? taskIds.map((id) => updateTaskStatus(id, status)) : [updateTaskStatus(taskIds, status)];
await Promise.all(promises);
await getList();
proxy?.$modal.msgSuccess(`${statusText}成功`);
@@ -512,15 +466,15 @@ const handleView = (row: StglMaterialTaskVO) => {
const handleUpload = (row?: StglMaterialTaskVO) => {
const taskId = row?.id || ids.value[0];
let activityId = '';
if (row) {
activityId = String(row.activityId);
} else {
// 如果没有传row,从选中的任务中获取activityId
const selectedTask = taskList.value.find(item => item.id === ids.value[0]);
const selectedTask = taskList.value.find((item) => item.id === ids.value[0]);
activityId = selectedTask ? String(selectedTask.activityId) : '';
}
uploadDialog.taskId = String(taskId);
uploadDialog.activityId = activityId;
uploadDialog.visible = true;
@@ -606,4 +560,4 @@ onMounted(() => {
.dialog-footer {
text-align: right;
}
</style>
</style>
+34 -33
View File
@@ -53,7 +53,15 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="info" plain icon="Check" :disabled="multiple" @click="handleBatchApprove()" v-hasPermi="['stgl:activityRegister:approve']">批量审核</el-button>
<el-button
type="info"
plain
icon="Check"
:disabled="multiple"
@click="handleBatchApprove()"
v-hasPermi="['stgl:activityRegister:approve']"
>批量审核</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出</el-button>
@@ -108,7 +116,7 @@
v-hasPermi="['stgl:activityRegister:query']"
></el-button>
</el-tooltip>
<!-- 待审核状态显示审核按钮 -->
<template v-if="Number(scope.row.approvalStatus) === 0">
<el-tooltip content="审核" placement="top">
@@ -123,7 +131,7 @@
></el-button>
</el-tooltip>
</template>
<!-- 审核通过状态显示成绩录入按钮 -->
<template v-if="Number(scope.row.approvalStatus) === 1">
<el-tooltip content="录入成绩" placement="top">
@@ -138,7 +146,6 @@
></el-button>
</el-tooltip>
</template>
</template>
</el-table-column>
</el-table>
@@ -162,11 +169,15 @@
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ viewForm.approvalTime ? parseTime(viewForm.approvalTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{
viewForm.approvalTime ? parseTime(viewForm.approvalTime, '{y}-{m}-{d} {h}:{i}') : '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请理由" :span="2">{{ viewForm.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="审核意见" :span="2">{{ viewForm.approvalRemark || '-' }}</el-descriptions-item>
<el-descriptions-item label="获得成绩">{{ viewForm.score || '-' }}</el-descriptions-item>
<el-descriptions-item label="成绩录入时间">{{ viewForm.scoreTime ? parseTime(viewForm.scoreTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</el-descriptions-item>
<el-descriptions-item label="成绩录入时间">{{
viewForm.scoreTime ? parseTime(viewForm.scoreTime, '{y}-{m}-{d} {h}:{i}') : '-'
}}</el-descriptions-item>
</el-descriptions>
<template #footer>
<div class="dialog-footer">
@@ -181,20 +192,14 @@
<!-- 显示状态选择 -->
<el-form-item label="审核结果" prop="approvalStatus" v-if="approvalTitle === '审核' || approvalTitle === '批量审核'">
<el-select v-model="approvalForm.approvalStatus" placeholder="请选择审核结果" style="width: 100%">
<el-option
:value="1"
label="审核通过"
>
<span style="display: flex; align-items: center;">
<el-option :value="1" label="审核通过">
<span style="display: flex; align-items: center">
<el-icon style="color: #67c23a; margin-right: 8px"><Check /></el-icon>
审核通过
</span>
</el-option>
<el-option
:value="2"
label="审核不通过"
>
<span style="display: flex; align-items: center;">
<el-option :value="2" label="审核不通过">
<span style="display: flex; align-items: center">
<el-icon style="color: #f56c6c; margin-right: 8px"><Close /></el-icon>
审核不通过
</span>
@@ -364,15 +369,13 @@ function handleApprove(row) {
approvalOpen.value = true;
}
/** 批量审核操作 */
function handleBatchApprove() {
if (ids.value.length === 0) {
proxy.$modal.msgError('请选择要审核的数据');
return;
}
approvalForm.id = undefined; // 标识为批量审核
approvalForm.approvalStatus = undefined;
approvalForm.approvalRemark = '';
@@ -384,25 +387,23 @@ function handleBatchApprove() {
function submitApproval() {
// 所有审核都需要验证审核状态和审核意见
const validateFields = ['approvalStatus', 'approvalRemark'];
approvalRef.value.validateField(validateFields, (valid) => {
if (valid) {
if (approvalForm.id) {
// 单个审核(包括重新审核)
approveActivityRegister(approvalForm.id, approvalForm.approvalStatus, approvalForm.approvalRemark)
.then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
});
approveActivityRegister(approvalForm.id, approvalForm.approvalStatus, approvalForm.approvalRemark).then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
});
} else {
// 批量审核
batchApproveActivityRegister(ids.value, approvalForm.approvalStatus, approvalForm.approvalRemark)
.then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
});
batchApproveActivityRegister(ids.value, approvalForm.approvalStatus, approvalForm.approvalRemark).then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
});
}
}
});
@@ -454,4 +455,4 @@ onMounted(() => {
<style lang="scss" scoped>
/* 样式可以参考志愿者成员管理的样式 */
</style>
</style>
+14 -13
View File
@@ -124,7 +124,7 @@
</div>
<div class="detail-item">
<i class="el-icon-s-data"></i>
<span>成员:{{ club.memberCount || 0 }}{{ club.memberLimit ? '/' + club.memberLimit : '' }}人</span>
<span>成员:{{ club.currentMemberCount || 0 }}{{ club.memberLimit ? '/' + club.memberLimit : '' }}人</span>
</div>
<div class="detail-item">
<i class="el-icon-time"></i>
@@ -310,7 +310,7 @@
<span class="date-value">{{ selectedClub.establishDate }}</span>
</div>
<div class="member-info">
<span class="member-count">成员:{{ selectedClub.memberCount || 0 }}</span>
<span class="member-count">成员:{{ selectedClub.currentMemberCount || 0 }}</span>
<span v-if="selectedClub.memberLimit" class="member-limit">/ {{ selectedClub.memberLimit }}人</span>
</div>
</div>
@@ -365,7 +365,7 @@
<span class="info-item"><strong>姓名:</strong>{{ vp.name }}</span>
<span v-if="vp.dept" class="info-item"><strong>院系:</strong>{{ vp.dept }}</span>
<span v-if="vp.major" class="info-item"><strong>专业:</strong>{{ vp.major }}</span>
<span v-if="vp.class" class="info-item"><strong>班级:</strong>{{ vp.class }}</span>
<span v-if="vp.className" class="info-item"><strong>班级:</strong>{{ vp.className }}</span>
<span v-if="vp.phone" class="info-item"><strong>联系方式:</strong>{{ vp.phone }}</span>
<span v-if="vp.studentNo" class="info-item"><strong>学号:</strong>{{ vp.studentNo }}</span>
</div>
@@ -497,6 +497,7 @@ const queryParams = reactive<StglClubRegisterQuery>({
clubName: undefined,
clubType: undefined,
canRegister: undefined,
sighOpen: 'Y', // 只显示报名开启的社团
params: {}
});
@@ -572,14 +573,13 @@ const handleTabChange = (tabName: string) => {
const handleViewDetail = (club: StglClubRegisterVO) => {
// 🔥 调试信息:检查数据结构
console.log('handleViewDetail: 社团数据详情:', club);
console.log('handleViewDetail: advisorsJson字段:', club.advisorsJson);
selectedClub.value = club;
// 解析副社长JSON数据
if (club.vicePresidentsJson) {
// 解析副社长JSON数据(如果存在)
if ((club as any).vicePresidentsJson) {
try {
const vicePresidents = JSON.parse(club.vicePresidentsJson);
const vicePresidents = JSON.parse((club as any).vicePresidentsJson);
(club as any).vicePresidents = vicePresidents;
console.log('handleViewDetail: 解析的副社长数据:', vicePresidents);
} catch (error) {
@@ -588,7 +588,7 @@ const handleViewDetail = (club: StglClubRegisterVO) => {
}
}
// 解析指导老师JSON数据(如果有的话)
// 解析指导老师JSON数据(如果存在)
if ((club as any).advisorsJson) {
try {
const advisors = JSON.parse((club as any).advisorsJson);
@@ -598,8 +598,6 @@ const handleViewDetail = (club: StglClubRegisterVO) => {
console.warn('解析指导老师JSON数据失败:', error);
(club as any).advisors = [];
}
} else {
console.warn('handleViewDetail: 未找到advisorsJson字段');
}
detailDialog.visible = true;
@@ -667,7 +665,10 @@ const downloadCharter = async () => {
charterDownloading.value = true;
try {
// 先获取OSS文件信息
const ossResponse = await proxy?.$api.get(`/resource/oss/${charterUrl}`);
const ossResponse = await proxy?.$http.request({
url: `/resource/oss/${charterUrl}`,
method: 'get'
});
if (ossResponse && ossResponse.code === 200) {
const ossInfo = ossResponse.data;
// 使用原始文件名,如果没有则使用社团名称 + 原始文件扩展名
@@ -745,9 +746,9 @@ const getAdvisorNames = (row: any): string[] => {
console.log('getAdvisorNames: 处理行数据:', row);
// 优先从JSON字段读取指导老师数据
if (row.advisorsJson) {
if ((row as any).advisorsJson) {
try {
const advisors = JSON.parse(row.advisorsJson);
const advisors = JSON.parse((row as any).advisorsJson);
console.log('getAdvisorNames: 从JSON字段解析的指导老师数据:', advisors);
if (Array.isArray(advisors)) {
const names = advisors.map((advisor: any) => advisor.name || advisor.teacher?.nickName || '').filter((name) => name);
+189 -243
View File
@@ -96,13 +96,26 @@
</div>
</template>
</el-table-column>
<el-table-column label="成员数量" align="center" width="100">
<template #default="scope">
<span class="member-count">{{ scope.row.memberCount || 0 }}</span>
<span v-if="scope.row.memberLimit" class="member-limit">/ {{ scope.row.memberLimit }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<el-table-column label="报名开启" align="center" prop="sighOpen" width="90">
<template #default="scope">
<el-switch
v-model="scope.row.sighOpen"
active-value="Y"
inactive-value="N"
@change="handleSighOpenStatusChange(scope.row)"
v-hasPermi="['stgl:club:edit']"
></el-switch>
</template>
</el-table-column>
<el-table-column label="社团状态" align="center" prop="status" width="90">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
@@ -112,7 +125,7 @@
<div class="description-cell">{{ scope.row.description }}</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
@@ -136,6 +149,28 @@
v-hasPermi="['stgl:club:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="成员管理" placement="top">
<el-button
link
type="info"
icon="User"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleMemberManagement(scope.row)"
v-hasPermi="['stgl:clubmember:list']"
></el-button>
</el-tooltip>
<el-tooltip content="活动管理" placement="top">
<el-button
link
type="success"
icon="Calendar"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleActivityManagement(scope.row)"
v-hasPermi="['stgl:activity:list']"
></el-button>
</el-tooltip>
<el-tooltip content="转让" placement="top">
<el-button
link
@@ -175,12 +210,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="社团名称" prop="clubName">
<el-input v-model="form.clubName" placeholder="请输入社团名称" />
<el-input v-model="form.clubName" placeholder="请输入社团名称" :disabled="!hasInstructorPermission" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="社团类型" prop="clubType">
<el-select v-model="form.clubType" placeholder="请选择社团类型" style="width: 100%">
<el-select v-model="form.clubType" placeholder="请选择社团类型" style="width: 100%" :disabled="!hasInstructorPermission">
<el-option v-for="dict in stgl_club_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
@@ -195,12 +230,13 @@
placeholder="请选择成立日期"
value-format="YYYY-MM-DD"
style="width: 100%"
:disabled="!hasInstructorPermission"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成员规模上限" prop="memberLimit">
<el-input-number v-model="form.memberLimit" :min="1" :max="9999" placeholder="请输入成员规模上限" style="width: 100%" />
<el-form-item label="成员规模上限" prop="memberLimit" required>
<el-input-number v-model="form.memberLimit" :min="1" :max="9999" placeholder="请输入成员规模上限" style="width: 100%" :disabled="!hasInstructorPermission" />
</el-form-item>
</el-col>
</el-row>
@@ -269,16 +305,12 @@
</el-row>
</div>
<!-- 副社长信息 -->
<div class="personnel-section">
<h4 class="section-title">副社长信息</h4>
<VicePresidentManager :model-value="form.vicePresidents" @change="handleVicePresidentsChange" />
</div>
<!-- 指导老师信息 -->
<div class="personnel-section">
<h4 class="section-title">指导老师信息</h4>
<AdvisorManager v-model="form.advisors" @change="handleAdvisorsChange" />
<AdvisorManager v-model="form.advisors" @change="handleAdvisorsChange" :disabled="!hasInstructorPermission" />
</div>
</el-tab-pane>
@@ -320,11 +352,22 @@
</el-tab-pane>
<el-tab-pane label="其他信息" name="other">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开启报名" prop="sighOpen">
<el-radio-group v-model="form.sighOpen">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" resize="none" :rows="4" />
</el-form-item>
@@ -394,31 +437,7 @@
</div>
</div>
<!-- 副社长信息 -->
<div class="position-group">
<div class="position-title">副社长:</div>
<div class="position-content">
<template v-if="viewForm.vicePresidents && viewForm.vicePresidents.length > 0">
<div v-for="(vp, index) in viewForm.vicePresidents" :key="index" class="vice-president-item">
<span class="info-item"><strong>姓名:</strong>{{ vp.name }}</span>
<span class="info-item"><strong>院系:</strong>{{ vp.dept || '暂无' }}</span>
<span class="info-item"><strong>专业:</strong>{{ vp.major || '暂无' }}</span>
<span class="info-item"><strong>班级:</strong>{{ vp.class || '暂无' }}</span>
<span class="info-item"><strong>联系方式:</strong>{{ vp.phone || '暂无' }}</span>
</div>
</template>
<template v-else-if="viewForm.vicePresidentName">
<span class="info-item"><strong>姓名:</strong>{{ viewForm.vicePresidentName }}</span>
<span class="info-item"><strong>院系:</strong>{{ viewForm.vicePresidentDept || '暂无' }}</span>
<span class="info-item"><strong>专业:</strong>{{ viewForm.vicePresidentMajor || '暂无' }}</span>
<span class="info-item"><strong>班级:</strong>{{ viewForm.vicePresidentClass || '暂无' }}</span>
<span class="info-item"><strong>联系方式:</strong>{{ viewForm.vicePresidentPhone || '暂无' }}</span>
</template>
<template v-else>
<span class="no-data">暂无副社长信息</span>
</template>
</div>
</div>
<!-- 指导老师信息 -->
<div class="position-group">
@@ -585,25 +604,51 @@
</div>
</template>
</el-dialog>
<!-- 成员管理弹窗 -->
<MemberManagementDialog
v-model:visible="memberDialog.visible"
:title="memberDialog.title"
:club-id="currentClub?.id"
:club-name="currentClub?.clubName"
:member-limit="currentClub?.memberLimit"
:current-member-count="currentClub?.memberCount"
@member-count-changed="handleMemberCountChanged"
/>
</div>
</template>
<script setup name="StglClub" lang="ts">
import { listStglClub, getStglClub, delStglClub, addStglClub, updateStglClub, transferClub, checkPresidentRole } from '@/api/stgl/club';
import { useRouter } from 'vue-router';
import {
listStglClub,
getStglClub,
delStglClub,
addStglClub,
updateStglClub,
transferClub,
checkPresidentRole,
changeSighOpenStatus
} from '@/api/stgl/club';
import { StglClubVO, StglClubQuery, StglClubForm } from '@/api/stgl/club/types';
import StudentSelector from '@/components/StudentSelector/index.vue';
import VicePresidentManager from '@/components/VicePresidentManager/index.vue';
import AdvisorManager from '@/components/AdvisorManager/index.vue';
import MemberManagementDialog from './components/MemberManagementDialog.vue';
import { StudentVO } from '@/api/xg/student/types';
import { VicePresidentVO } from '@/components/VicePresidentManager/index.vue';
import { AdvisorVO } from '@/components/AdvisorManager/index.vue';
import { Document } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_club_type, sys_normal_disable, stgl_funding_source } = toRefs<any>(
proxy?.useDict('stgl_club_type', 'sys_normal_disable', 'stgl_funding_source')
const router = useRouter();
const { stgl_club_type, sys_normal_disable, stgl_funding_source, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_club_type', 'sys_normal_disable', 'stgl_funding_source', 'sys_yes_no')
);
// 检查是否拥有指导老师或管理员权限
const hasInstructorPermission = computed(() => {
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_admin', 'superadmin']);
});
const clubList = ref<StglClubVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
@@ -640,6 +685,15 @@ const transferDialog = reactive<DialogOption>({
const transferLoading = ref(false);
const transferFormRef = ref<ElFormInstance>();
// 成员管理相关
const memberDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const currentClub = ref<StglClubVO | null>(null);
const memberActiveTab = ref('joined');
interface TransferForm {
clubId?: string | number;
oldPresidentName?: string;
@@ -710,6 +764,7 @@ const viewForm = reactive<StglClubForm>({
fundingSources: [],
charterUrl: undefined,
status: '0',
sighOpen: 'Y',
remark: undefined
});
@@ -729,9 +784,7 @@ const initFormData: StglClubForm = {
presidentMajor: undefined,
presidentClass: undefined,
presidentPhone: undefined,
vicePresidents: [],
vicePresidentName: undefined,
vicePresidentStudentNo: undefined,
advisors: [],
advisorName: undefined,
advisorDept: undefined,
@@ -743,6 +796,7 @@ const initFormData: StglClubForm = {
fundingSources: [],
charterUrl: undefined,
status: '0',
sighOpen: 'N',
remark: undefined
};
@@ -763,6 +817,7 @@ const data = reactive<PageData<StglClubForm, StglClubQuery>>({
presidentName: [{ required: true, message: '社长姓名不能为空', trigger: 'blur' }],
presidentStudentNo: [{ required: true, message: '社长学号不能为空', trigger: 'blur' }],
advisorName: [{ required: true, message: '指导老师姓名不能为空', trigger: 'blur' }],
memberLimit: [{ required: true, message: '成员规模上限不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
}
});
@@ -791,6 +846,9 @@ const getList = async () => {
if (item.status !== undefined && item.status !== null) {
item.status = String(item.status);
}
if (item.sighOpen !== undefined && item.sighOpen !== null) {
item.sighOpen = String(item.sighOpen);
}
});
}
@@ -870,6 +928,9 @@ const handleUpdate = async (row?: StglClubVO) => {
if (res.data.status !== undefined && res.data.status !== null) {
(res.data as any).status = String(res.data.status);
}
if (res.data.sighOpen !== undefined && res.data.sighOpen !== null) {
(res.data as any).sighOpen = String(res.data.sighOpen);
}
// 处理社长信息:将简单字段转换为复杂对象结构
if (res.data.presidentName) {
@@ -896,39 +957,7 @@ const handleUpdate = async (row?: StglClubVO) => {
vicePresidentPhone: res.data.vicePresidentPhone
});
// 🔥 优先从JSON字段读取副社长数据
if (res.data.vicePresidentsJson) {
try {
(res.data as any).vicePresidents = JSON.parse(res.data.vicePresidentsJson);
console.log('handleUpdate: 从JSON字段读取的副社长数据:', (res.data as any).vicePresidents);
} catch (error) {
console.warn('解析副社长JSON数据失败:', error);
(res.data as any).vicePresidents = [];
}
} else if (res.data.vicePresidentName) {
// 回退到单一字段
(res.data as any).vicePresidents = [
{
name: res.data.vicePresidentName,
dept: res.data.vicePresidentDept,
major: res.data.vicePresidentMajor,
class: res.data.vicePresidentClass,
phone: res.data.vicePresidentPhone,
student: {
studentNo: res.data.vicePresidentStudentNo,
name: res.data.vicePresidentName,
collegeName: res.data.vicePresidentDept,
majorName: res.data.vicePresidentMajor,
className: res.data.vicePresidentClass,
phone: res.data.vicePresidentPhone
}
}
];
} else {
(res.data as any).vicePresidents = [];
}
console.log('handleUpdate: 处理后的副社长数据:', (res.data as any).vicePresidents);
// 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
// 🔥 优先从JSON字段读取指导老师数据
@@ -994,45 +1023,15 @@ const submitForm = () => {
});
}
// 确保副社长数据完整
if (submitData.vicePresidents && Array.isArray(submitData.vicePresidents)) {
submitData.vicePresidents = submitData.vicePresidents.map((vp: any) => {
return {
...vp,
// 确保name等字段有值
name: vp.name || vp.student?.name || '',
dept: vp.dept || vp.student?.collegeName || '',
major: vp.major || vp.student?.majorName || '',
class: vp.class || vp.student?.className || '',
phone: vp.phone || vp.student?.phone || ''
};
});
// 同步第一个副社长信息到旧字段(为了兼容后端)
if (submitData.vicePresidents.length > 0) {
const firstVP = submitData.vicePresidents[0];
submitData.vicePresidentName = firstVP.name;
submitData.vicePresidentStudentNo = firstVP.student?.studentNo;
submitData.vicePresidentDept = firstVP.dept;
submitData.vicePresidentMajor = firstVP.major;
submitData.vicePresidentClass = firstVP.class;
submitData.vicePresidentPhone = firstVP.phone;
}
}
// 🔥 新增:同时保存JSON字段和兼容性字段
submitData.vicePresidentsJson = JSON.stringify(submitData.vicePresidents || []);
submitData.advisorsJson = JSON.stringify(submitData.advisors || []);
// 调试日志:检查指导老师数据
console.log('提交数据 - advisors:', submitData.advisors);
console.log('提交数据 - advisorName:', submitData.advisorName);
console.log('提交数据 - advisorDept:', submitData.advisorDept);
// 调试日志:检查副社长数据
console.log('提交数据 - vicePresidents:', submitData.vicePresidents);
console.log('提交数据 - vicePresidentName:', submitData.vicePresidentName);
console.log('提交数据 - vicePresidentStudentNo:', submitData.vicePresidentStudentNo);
console.log('提交数据 - vicePresidentDept:', submitData.vicePresidentDept);
console.log('完整提交数据:', submitData);
if (form.value.id) {
@@ -1089,44 +1088,12 @@ const handleView = async (row: StglClubVO) => {
if (viewData.status !== undefined && viewData.status !== null) {
(viewData as any).status = String(viewData.status);
}
// 🔥 处理副社长信息:优先从JSON字段读取,如果没有则使用单一字段
console.log('handleView: 副社长原始数据:', {
vicePresidentsJson: viewData.vicePresidentsJson,
vicePresidentName: viewData.vicePresidentName
});
if (viewData.vicePresidentsJson) {
try {
(viewData as any).vicePresidents = JSON.parse(viewData.vicePresidentsJson);
console.log('handleView: 从JSON字段读取的副社长数据:', (viewData as any).vicePresidents);
} catch (error) {
console.warn('解析副社长JSON数据失败:', error);
(viewData as any).vicePresidents = [];
}
} else if (viewData.vicePresidentName) {
// 回退到单一字段
(viewData as any).vicePresidents = [
{
name: viewData.vicePresidentName,
dept: viewData.vicePresidentDept,
major: viewData.vicePresidentMajor,
class: viewData.vicePresidentClass,
phone: viewData.vicePresidentPhone,
student: {
studentNo: viewData.vicePresidentStudentNo,
name: viewData.vicePresidentName,
collegeName: viewData.vicePresidentDept,
majorName: viewData.vicePresidentMajor,
className: viewData.vicePresidentClass,
phone: viewData.vicePresidentPhone
}
}
];
} else {
(viewData as any).vicePresidents = [];
if (viewData.sighOpen !== undefined && viewData.sighOpen !== null) {
(viewData as any).sighOpen = String(viewData.sighOpen);
}
// 🔥 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
console.log('handleView: 指导老师原始数据:', {
advisorsJson: viewData.advisorsJson,
@@ -1162,7 +1129,6 @@ const handleView = async (row: StglClubVO) => {
}
console.log('handleView: 处理后的数据:', {
vicePresidents: (viewData as any).vicePresidents,
advisors: (viewData as any).advisors
});
@@ -1185,75 +1151,31 @@ const downloadCharter = async () => {
charterDownloading.value = true;
try {
// 先获取OSS文件信息
const ossResponse = await proxy?.$api.get(`/resource/oss/${viewForm.charterUrl}`);
if (ossResponse && ossResponse.code === 200) {
const ossInfo = ossResponse.data;
// 使用原始文件名,如果没有则使用社团名称 + 原始文件扩展名
let fileName = ossInfo.originalName;
if (!fileName) {
// 如果没有原始文件名,使用文件后缀和社团名称构造
const fileSuffix = ossInfo.fileSuffix || '';
fileName = `${viewForm.clubName || '社团'}_章程文档${fileSuffix}`;
}
// 使用secureOss方法下载,传递正确的文件名
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} else {
// 如果获取文件信息失败,尝试从URL中提取文件扩展名
let fileExtension = '';
try {
// 从URL中提取文件扩展名
const urlParts = viewForm.charterUrl.split('.');
if (urlParts.length > 1) {
const lastPart = urlParts[urlParts.length - 1];
// 检查是否为常见的文件扩展名(去除可能的查询参数)
const ext = lastPart.split('?')[0].toLowerCase();
if (['doc', 'docx', 'pdf', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
fileExtension = '.' + ext;
}
// 尝试从URL中提取文件扩展名
let fileExtension = '';
try {
const urlParts = viewForm.charterUrl.split('.');
if (urlParts.length > 1) {
const lastPart = urlParts[urlParts.length - 1];
const ext = lastPart.split('?')[0].toLowerCase();
if (['doc', 'docx', 'pdf', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
fileExtension = '.' + ext;
}
} catch (e) {
console.log('无法从URL提取扩展名:', e);
}
// 如果没有提取到扩展名,默认使用.docx(因为大部分章程文档是Word格式)
if (!fileExtension) {
fileExtension = '.docx';
}
const fileName = `${viewForm.clubName || '社团'}_章程文档${fileExtension}`;
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} catch (e) {
console.log('无法从URL提取扩展名:', e);
}
// 如果没有提取到扩展名,默认使用.docx
if (!fileExtension) {
fileExtension = '.docx';
}
const fileName = `${viewForm.clubName || '社团'}_章程文档${fileExtension}`;
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} catch (error) {
console.error('下载章程文档失败:', error);
// 如果获取文件信息失败,尝试使用默认文件名下载
try {
// 尝试从URL中提取文件扩展名
let fileExtension = '';
try {
const urlParts = viewForm.charterUrl.split('.');
if (urlParts.length > 1) {
const lastPart = urlParts[urlParts.length - 1];
const ext = lastPart.split('?')[0].toLowerCase();
if (['doc', 'docx', 'pdf', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
fileExtension = '.' + ext;
}
}
} catch (e) {
console.log('无法从URL提取扩展名:', e);
}
// 如果没有提取到扩展名,默认使用.docx
if (!fileExtension) {
fileExtension = '.docx';
}
const fileName = `${viewForm.clubName || '社团'}_章程文档${fileExtension}`;
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} catch (downloadError) {
proxy?.$modal.msgError('下载章程文档失败,请稍后重试');
}
proxy?.$modal.msgError('下载章程文档失败,请稍后重试');
} finally {
charterDownloading.value = false;
}
@@ -1324,36 +1246,7 @@ const handlePresidentChange = (student: StudentVO | null) => {
});
};
/** 副社长列表变化处理 */
const handleVicePresidentsChange = (vicePresidents: VicePresidentVO[]) => {
// 添加防护锁,避免递归更新
if (syncLock.value) return;
syncLock.value = true;
nextTick(() => {
// 同步副社长数据到表单
form.value.vicePresidents = vicePresidents;
// 为了兼容旧版本,同步第一个副社长的信息到旧字段
if (vicePresidents.length > 0) {
const firstVP = vicePresidents[0];
form.value.vicePresidentName = firstVP.name;
form.value.vicePresidentStudentNo = firstVP.student?.studentNo;
form.value.vicePresidentDept = firstVP.student?.collegeName;
form.value.vicePresidentMajor = firstVP.student?.majorName;
form.value.vicePresidentClass = firstVP.student?.className;
form.value.vicePresidentPhone = firstVP.student?.phone;
} else {
form.value.vicePresidentName = undefined;
form.value.vicePresidentStudentNo = undefined;
form.value.vicePresidentDept = undefined;
form.value.vicePresidentMajor = undefined;
form.value.vicePresidentClass = undefined;
form.value.vicePresidentPhone = undefined;
}
syncLock.value = false;
});
};
// 防止递归更新的锁
const syncLock = ref(false);
@@ -1534,6 +1427,58 @@ const handleNewPresidentSelected = (student: StudentVO | null) => {
}
};
/** 报名开启状态修改 */
const handleSighOpenStatusChange = async (row: StglClubVO) => {
const text = row.sighOpen === 'Y' ? '开启' : '关闭';
await proxy?.$modal
.confirm('确认要"' + text + '""' + row.clubName + '"社团报名吗?')
.then(async () => {
await changeSighOpenStatus(row.id!, row.sighOpen!).then(() => {
proxy?.$modal.msgSuccess(text + '成功');
});
})
.catch(() => {
row.sighOpen = row.sighOpen === 'Y' ? 'N' : 'Y';
});
};
/** 活动管理按钮操作 */
const handleActivityManagement = (row: StglClubVO) => {
router.push({
path: '/stgl/activity/info',
query: {
relatedClubId: row.id,
relatedClubName: row.clubName,
from: 'club'
}
});
};
/** 成员管理按钮操作 */
const handleMemberManagement = (row: StglClubVO) => {
console.log('handleMemberManagement: 打开成员管理弹窗, row:', row);
currentClub.value = row;
memberDialog.visible = true;
memberDialog.title = `${row.clubName} - 成员管理`;
memberActiveTab.value = 'joined';
};
/** 处理成员数量变化 */
const handleMemberCountChanged = (newCount: number) => {
console.log('handleMemberCountChanged: 成员数量变化:', newCount, '当前社团:', currentClub.value);
if (currentClub.value) {
// 更新当前社团的成员数量
currentClub.value.memberCount = newCount;
// 同时更新列表中的对应社团数据
const clubIndex = clubList.value.findIndex(club => club.id === currentClub.value?.id);
if (clubIndex !== -1) {
clubList.value[clubIndex].memberCount = newCount;
console.log('handleMemberCountChanged: 已更新列表中的成员数量:', clubList.value[clubIndex]);
}
}
};
onMounted(() => {
getList();
});
@@ -1590,6 +1535,8 @@ onMounted(() => {
text-align: center;
}
.member-count {
font-weight: bold;
color: #409eff;
@@ -1662,7 +1609,6 @@ onMounted(() => {
}
}
.vice-president-item,
.advisor-item {
display: flex;
flex-wrap: wrap;
+495 -3
View File
@@ -121,7 +121,7 @@
<span v-else class="text-gray-400">暂无评分</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
<el-button
@@ -133,6 +133,16 @@
@click="handleView(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip content="签到/签退" placement="top">
<el-button
link
type="success"
icon="Check"
size="default"
style="font-size: 16px; margin-right: 8px; color: green"
@click="handleAttendance(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip content="活动评分" placement="top">
<el-button
link
@@ -267,14 +277,95 @@
</div>
</template>
</el-dialog>
<!-- 签到对话框 -->
<el-dialog title="活动签到" v-model="attendanceOpen" width="560px" append-to-body>
<div class="attendance-content">
<div class="attendance-actions">
<el-button
type="primary"
size="large"
:loading="signInLoading"
:disabled="!canSignIn"
@click="handleSignIn"
class="action-btn"
>
{{ signInButtonText }}
</el-button>
<el-button
type="warning"
size="large"
:loading="signOutLoading"
:disabled="!canSignOut"
@click="handleSignOut"
class="action-btn"
>
签 退
</el-button>
</div>
<!-- 签到记录 -->
<div class="attendance-records">
<div class="records-header">
<span class="records-title">签到记录</span>
<el-button
type="text"
size="small"
@click="refreshRecords"
:loading="recordsLoading"
>
刷新
</el-button>
</div>
<el-table
:data="attendanceRecords"
stripe
size="small"
class="records-table"
v-loading="recordsLoading"
>
<el-table-column prop="signInTime" label="签到时间" width="200">
<template #default="{ row }">
{{ formatDateTime(row.signInTime) }}
</template>
</el-table-column>
<el-table-column prop="signOutTime" label="签退时间" width="200">
<template #default="{ row }">
{{ row.signOutTime ? formatDateTime(row.signOutTime) : '-' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag
:type="row.signOutTime ? 'success' : 'warning'"
size="small"
>
{{ row.signOutTime ? '已完成' : '进行中' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div v-if="attendanceRecords.length === 0" class="no-records">
<el-empty description="暂无签到记录" :image-size="60" />
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup name="MyActivity" lang="ts">
import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, computed, nextTick } from 'vue';
import { getMyActivities, rateActivity } from '@/api/stgl/my';
import type { MyActivityVO, ActivityRatingForm, MyActivityQuery } from '@/api/stgl/my/types';
import { Calendar } from '@element-plus/icons-vue';
import { Calendar, Location, Clock, Check, Close } from '@element-plus/icons-vue';
import { getInfo } from '@/api/login'; // 添加获取用户详细信息的API
import request from '@/utils/request';
import type { FormInstance } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_status, stgl_registration_status, stgl_my_registration_status } = toRefs<any>(
@@ -294,7 +385,7 @@ const detailOpen = ref(false);
const detailForm = ref<MyActivityVO>({} as MyActivityVO);
const ratingOpen = ref(false);
const ratingLoading = ref(false);
const ratingFormRef = ref<ElFormInstance>();
const ratingFormRef = ref<FormInstance>();
const ratingForm = ref<ActivityRatingForm & { activityName?: string; studentName?: string; studentNo?: string }>({
activityId: '',
@@ -429,6 +520,359 @@ const formatDate = (dateStr: string): string => {
});
};
// 签到相关API调用
const createSignInRecord = async (signInData: any) => {
try {
const response = await request({
url: '/stgl/activityAttendance/signIn',
method: 'post',
data: signInData
});
return response;
} catch (error) {
console.error('签到失败:', error);
throw error;
}
};
const updateSignOutRecord = async (signOutData: any) => {
try {
const response = await request({
url: '/stgl/activityAttendance/signOut',
method: 'post',
data: signOutData
});
return response;
} catch (error) {
console.error('签退失败:', error);
throw error;
}
};
const getAllAttendanceRecords = async (activityId: number, studentNo: string) => {
try {
const response = await request({
url: `/stgl/activityAttendance/getAllByActivityAndUser?activityId=${activityId}&studentNo=${studentNo}`,
method: 'get'
});
return response;
} catch (error) {
console.error('获取所有签到记录失败:', error);
throw error;
}
};
// 签到相关变量
const attendanceOpen = ref(false);
const attendanceForm = ref<any>({
activityId: '',
activityName: '',
activityType: '',
location: '',
startTime: '',
endTime: '',
studentName: '',
studentNo: '',
department: '',
major: '',
className: '',
phone: '',
email: '',
signInStatus: 0,
signOutStatus: 0,
signInTime: '',
signOutTime: ''
});
const signInLoading = ref(false);
const signOutLoading = ref(false);
const attendanceFormRef = ref<FormInstance>();
// 签到表单验证规则
const attendanceRules = reactive({
// 可以添加验证规则
});
// 新增签到相关变量
const attendanceHistory = ref<any[]>([]);
const currentAttendance = ref<any>(null);
// 签到记录相关数据
const attendanceRecords = ref<any[]>([])
const recordsLoading = ref(false)
// 计算属性 - 简化状态判断
const canSignIn = computed(() => {
// 如果没有记录,或者最新记录已签到已签退,则可以签到
if (!currentAttendance.value) {
return true; // 没有记录时可以签到
}
// 如果最新记录已签到已签退,则可以重新签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 1) {
return true;
}
// 如果最新记录已签到未签退,则不能签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return false;
}
// 其他情况允许签到
return true;
});
const canSignOut = computed(() => {
// 如果有记录且已签到未签退,则可以签退
return currentAttendance.value &&
currentAttendance.value.signInStatus === 1 &&
currentAttendance.value.signOutStatus === 0;
});
const signInButtonText = computed(() => {
if (!currentAttendance.value) {
return '签到';
}
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return '已签到';
}
return '签到';
});
/** 打开签到对话框 */
const handleAttendance = async (row: MyActivityVO) => {
try {
const userInfo = await getCurrentUserDetailInfo();
// 填充活动信息
attendanceForm.value = {
activityId: row.id,
activityName: row.activityName,
activityType: row.activityType,
location: row.location,
startTime: row.startTime,
endTime: row.endTime,
studentName: userInfo?.nickName || userInfo?.userName || '',
studentNo: userInfo?.userName || '',
department: userInfo?.deptName || '',
major: '',
className: '',
phone: userInfo?.phonenumber || '',
email: userInfo?.email || '',
signInStatus: 0,
signOutStatus: 0,
signInTime: '',
signOutTime: ''
};
// 加载签到历史记录
await refreshAttendanceData();
attendanceOpen.value = true;
} catch (error) {
console.error('获取用户信息失败:', error);
proxy?.$modal.msgError('获取用户信息失败,请稍后重试');
}
};
/** 签到操作 */
const handleSignIn = async () => {
if (!attendanceForm.value.activityId) {
proxy?.$modal.msgError('请先选择活动');
return;
}
// 检查是否可以签到
if (!canSignIn.value) {
proxy?.$modal.msgError('当前状态不允许签到');
return;
}
signInLoading.value = true;
try {
const signInData = {
activityId: attendanceForm.value.activityId,
studentName: attendanceForm.value.studentName,
studentNo: attendanceForm.value.studentNo,
department: attendanceForm.value.department,
major: attendanceForm.value.major,
className: attendanceForm.value.className,
phone: attendanceForm.value.phone,
email: attendanceForm.value.email,
signInLocation: '前端签到',
signInDevice: navigator.userAgent.substring(0, 50)
};
await createSignInRecord(signInData);
// 签到成功消息
proxy?.$modal.msgSuccess('签到成功');
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签到后状态更新:', {
currentAttendance: currentAttendance.value,
canSignIn: canSignIn.value,
canSignOut: canSignOut.value,
signInButtonText: signInButtonText.value
});
});
} catch (error) {
console.error('签到失败:', error);
proxy?.$modal.msgError('签到失败');
} finally {
signInLoading.value = false;
}
};
/** 签退操作 */
const handleSignOut = async () => {
if (!attendanceForm.value.activityId) {
proxy?.$modal.msgError('请先选择活动');
return;
}
if (!canSignOut.value) {
proxy?.$modal.msgError('当前状态不允许签退');
return;
}
signOutLoading.value = true;
try {
const signOutData = {
id: currentAttendance.value.id, // 使用当前记录的ID进行更新
activityId: attendanceForm.value.activityId,
studentNo: attendanceForm.value.studentNo,
signOutLocation: '前端签退',
signOutDevice: navigator.userAgent.substring(0, 50)
};
await updateSignOutRecord(signOutData);
proxy?.$modal.msgSuccess('签退成功');
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签退后状态更新:', {
currentAttendance: currentAttendance.value,
canSignIn: canSignIn.value,
canSignOut: canSignOut.value,
signInButtonText: signInButtonText.value
});
});
} catch (error) {
console.error('签退失败:', error);
proxy?.$modal.msgError('签退失败');
} finally {
signOutLoading.value = false;
}
};
/** 刷新签到数据 */
const refreshAttendanceData = async () => {
try {
// 获取最新的签到记录
const recordRes = await getAllAttendanceRecords(attendanceForm.value.activityId, attendanceForm.value.studentNo);
if (recordRes.data && recordRes.data.length > 0) {
// 按创建时间排序,取最新的一条作为当前记录
const sortedRecords = recordRes.data.sort((a: any, b: any) =>
new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
);
currentAttendance.value = sortedRecords[0];
attendanceHistory.value = sortedRecords;
// 同时更新签到记录表格数据
attendanceRecords.value = sortedRecords;
console.log('获取到签到记录:', {
total: sortedRecords.length,
current: currentAttendance.value,
history: attendanceHistory.value
});
} else {
currentAttendance.value = null;
attendanceHistory.value = [];
attendanceRecords.value = [];
console.log('没有找到签到记录');
}
// 强制触发计算属性重新计算
await nextTick();
console.log('刷新签到数据完成:', {
history: attendanceHistory.value,
current: currentAttendance.value,
canSignIn: canSignIn.value,
canSignOut: canSignOut.value,
signInButtonText: signInButtonText.value
});
} catch (error) {
console.error('刷新签到数据失败:', error);
// 如果获取失败,重置状态
currentAttendance.value = null;
attendanceHistory.value = [];
attendanceRecords.value = [];
}
};
/** 刷新签到记录 */
const refreshRecords = async () => {
if (!attendanceForm.value.activityId || !attendanceForm.value.studentNo) return;
recordsLoading.value = true;
try {
await refreshAttendanceData();
proxy?.$modal.msgSuccess('签到记录已刷新');
} catch (error) {
console.error('刷新签到记录失败:', error);
proxy?.$modal.msgError('刷新签到记录失败');
} finally {
recordsLoading.value = false;
}
};
/** 格式化日期时间 */
const formatDateTime = (dateStr: string): string => {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
/** 计算参与时长 */
const calculateDuration = (signInTime: string, signOutTime: string): string => {
if (!signInTime) return '-';
if (!signOutTime) return '进行中';
const start = new Date(signInTime);
const end = new Date(signOutTime);
const diffMs = end.getTime() - start.getTime();
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}小时${minutes}分钟`;
} else {
return `${minutes}分钟`;
}
};
onMounted(() => {
getList();
});
@@ -521,4 +965,52 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
/* 签到弹窗样式 */
.attendance-content {
padding: 20px 0;
}
.attendance-actions {
display: flex;
justify-content: center;
gap: 16px;
}
.action-btn {
min-width: 120px;
height: 44px;
font-size: 16px;
font-weight: 500;
}
/* 签到记录样式 */
.attendance-records {
margin-top: 24px;
border-top: 1px solid #e4e7ed;
padding-top: 20px;
}
.records-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.records-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.records-table {
margin-bottom: 16px;
}
.no-records {
text-align: center;
padding: 40px 0;
color: #909399;
}
</style>
+4 -14
View File
@@ -12,23 +12,13 @@
</el-col>
<el-col :span="6">
<el-form-item label="社团类型" prop="clubType">
<el-select
v-model="queryParams.clubType"
placeholder="请选择社团类型"
clearable
style="width: 100%"
>
<el-option
v-for="dict in stgl_club_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-select v-model="queryParams.clubType" placeholder="请选择社团类型" clearable style="width: 100%">
<el-option v-for="dict in stgl_club_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -339,4 +329,4 @@ onMounted(() => {
background-color: #fff;
}
}
</style>
</style>
+15 -49
View File
@@ -7,12 +7,7 @@
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="报名类型" prop="registrationType">
<el-select
v-model="queryParams.registrationType"
placeholder="请选择报名类型"
clearable
style="width: 100%"
>
<el-select v-model="queryParams.registrationType" placeholder="请选择报名类型" clearable style="width: 100%">
<el-option label="全部" value="" />
<el-option label="社团" value="1" />
<el-option label="志愿团队" value="2" />
@@ -22,12 +17,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="审核状态" prop="approvalStatus">
<el-select
v-model="queryParams.approvalStatus"
placeholder="请选择审核状态"
clearable
style="width: 100%"
>
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审核状态" clearable style="width: 100%">
<el-option label="全部" value="" />
<el-option label="待审核" value="待审核" />
<el-option label="已通过" value="已通过" />
@@ -36,7 +26,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -65,10 +55,7 @@
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column label="报名类型" align="center" prop="registrationType" width="100">
<template #default="scope">
<el-tag
:type="getRegistrationTypeTag(scope.row.registrationType)"
size="small"
>
<el-tag :type="getRegistrationTypeTag(scope.row.registrationType)" size="small">
{{ getRegistrationTypeText(scope.row.registrationType) }}
</el-tag>
</template>
@@ -84,20 +71,14 @@
</el-table-column>
<el-table-column label="审核状态" align="center" prop="approvalStatus" width="100">
<template #default="scope">
<el-tag
:type="getApprovalStatusTag(scope.row.approvalStatus)"
size="small"
>
<el-tag :type="getApprovalStatusTag(scope.row.approvalStatus)" size="small">
{{ scope.row.approvalStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="当前状态" align="center" prop="currentStatus" width="100">
<template #default="scope">
<el-tag
:type="getCurrentStatusTag(scope.row.currentStatus)"
size="small"
>
<el-tag :type="getCurrentStatusTag(scope.row.currentStatus)" size="small">
{{ scope.row.currentStatus }}
</el-tag>
</template>
@@ -132,23 +113,14 @@
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 查看详情对话框 -->
<el-dialog title="报名记录详情" v-model="detailOpen" width="800px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="报名类型">
<el-tag
:type="getRegistrationTypeTag(detailForm.registrationType)"
size="small"
>
<el-tag :type="getRegistrationTypeTag(detailForm.registrationType)" size="small">
{{ getRegistrationTypeText(detailForm.registrationType) }}
</el-tag>
</el-descriptions-item>
@@ -158,18 +130,12 @@
<el-descriptions-item label="组织者">{{ detailForm.organizer || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ parseTime(detailForm.registrationTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="审核状态">
<el-tag
:type="getApprovalStatusTag(detailForm.approvalStatus)"
size="small"
>
<el-tag :type="getApprovalStatusTag(detailForm.approvalStatus)" size="small">
{{ detailForm.approvalStatus }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="当前状态">
<el-tag
:type="getCurrentStatusTag(detailForm.currentStatus)"
size="small"
>
<el-tag :type="getCurrentStatusTag(detailForm.currentStatus)" size="small">
{{ detailForm.currentStatus }}
</el-tag>
</el-descriptions-item>
@@ -254,7 +220,7 @@ const handleView = (row: MyRegistrationVO) => {
const getRegistrationTypeText = (type: string): string => {
const typeMap: Record<string, string> = {
'1': '社团',
'2': '志愿团队',
'2': '志愿团队',
'3': '活动'
};
return typeMap[type] || '未知';
@@ -267,7 +233,7 @@ const getRegistrationTypeTag = (type: string) => {
'2': 'success' as const,
'3': 'warning' as const
};
return tagMap[type as keyof typeof tagMap] || 'info' as const;
return tagMap[type as keyof typeof tagMap] || ('info' as const);
};
/** 获取审核状态标签样式 */
@@ -277,7 +243,7 @@ const getApprovalStatusTag = (status: string) => {
'已拒绝': 'danger' as const,
'待审核': 'warning' as const
};
return tagMap[status as keyof typeof tagMap] || 'info' as const;
return tagMap[status as keyof typeof tagMap] || ('info' as const);
};
/** 获取当前状态标签样式 */
@@ -287,7 +253,7 @@ const getCurrentStatusTag = (status: string) => {
'已报名': 'primary' as const,
'待审核': 'warning' as const
};
return tagMap[status as keyof typeof tagMap] || 'info' as const;
return tagMap[status as keyof typeof tagMap] || ('info' as const);
};
onMounted(() => {
@@ -359,4 +325,4 @@ onMounted(() => {
font-size: 14px;
line-height: 1.6;
}
</style>
</style>
+2 -2
View File
@@ -11,7 +11,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -291,4 +291,4 @@ onMounted(() => {
background-color: #fff;
}
}
</style>
</style>
@@ -125,7 +125,7 @@
v-hasPermi="['stgl:volunteerMember:query']"
></el-button>
</el-tooltip>
<!-- 待审核状态显示审核按钮 -->
<template v-if="scope.row.status === 0">
<el-tooltip content="通过" placement="top">
@@ -149,7 +149,7 @@
></el-button>
</el-tooltip>
</template>
<!-- 非待审核状态显示常规操作按钮 -->
<template v-else>
<el-tooltip content="修改" placement="top">
@@ -679,7 +679,7 @@ function handleDelete(row) {
function handleApprove(row, approved) {
const action = approved ? '通过' : '驳回';
const memberName = row.studentName || '该成员';
proxy.$modal
.confirm(`是否确认${action}${memberName}的申请?`)
.then(function () {
@@ -18,6 +18,7 @@
<el-option label="已满员" value="full" />
<el-option label="已申请" value="applied" />
<el-option label="已加入" value="joined" />
<el-option label="报名关闭" value="closed" />
</el-select>
</el-form-item>
</el-col>
@@ -114,7 +115,10 @@
<!-- 3. 已满员 - 红色 -->
<el-tag v-else-if="calculateRegisterStatus(scope.row) === 'full'" type="danger" size="small">已满员</el-tag>
<!-- 4. 可报名 - 绿色(默认状态) -->
<!-- 4. 报名关闭 - 灰色 -->
<el-tag v-else-if="calculateRegisterStatus(scope.row) === 'closed'" type="info" size="small">报名关闭</el-tag>
<!-- 5. 可报名 - 绿色(默认状态) -->
<el-tag v-else type="success" size="small">可报名</el-tag>
</template>
</el-table-column>
@@ -141,6 +145,11 @@
<el-button link type="danger" icon="Close" disabled />
</el-tooltip>
<!-- 报名关闭状态 - 灰色禁用按钮 -->
<el-tooltip content="报名已关闭" placement="top" v-else-if="calculateRegisterStatus(scope.row) === 'closed'">
<el-button link type="info" icon="Lock" disabled />
</el-tooltip>
<!-- 查看详情按钮 - 始终显示 -->
<el-tooltip content="查看详情" placement="top">
<el-button link type="primary" icon="View" @click="handleViewDetail(scope.row)" />
@@ -171,6 +180,7 @@
<el-tag v-if="calculateRegisterStatus(detailForm) === 'joined'" type="primary">已加入队伍</el-tag>
<el-tag v-else-if="calculateRegisterStatus(detailForm) === 'applied'" type="warning">申请审核中</el-tag>
<el-tag v-else-if="calculateRegisterStatus(detailForm) === 'full'" type="danger">队伍已满员</el-tag>
<el-tag v-else-if="calculateRegisterStatus(detailForm) === 'closed'" type="info">报名已关闭</el-tag>
<el-tag v-else type="success">可申请报名</el-tag>
</el-descriptions-item>
<el-descriptions-item label="剩余名额">
@@ -362,6 +372,7 @@ interface TeamInfo {
advisorEmail?: string;
email?: string;
status?: number | string; // 队伍状态(sys_normal_disable字典:0-正常 1-停用)
sighOpen?: string; // 报名开启状态(Y-开启 N-关闭)
// 用户申请状态字段(后端已支持)
hasApplied?: boolean; // 是否已申请
@@ -448,19 +459,24 @@ function calculateRegisterStatus(row: TeamInfo): string {
if (row.hasJoined) {
return 'joined'; // 已加入
}
if (row.hasApplied) {
return 'applied'; // 已申请
}
// 优先级2:队伍成员数量检查
const currentCount = row.currentMemberCount || 0;
const limit = row.memberLimit || 0;
if (limit > 0 && currentCount >= limit) {
return 'full'; // 已满员
}
// 优先级3:报名开关检查
if (row.sighOpen === 'N') {
return 'closed'; // 报名已关闭
}
return 'available'; // 可报名
}
@@ -559,7 +575,7 @@ async function loadUserInfoToForm() {
// 获取当前用户的学生信息
const response = await getPersonalInfo();
const studentInfo: StudentMsgForm = response.data;
if (studentInfo) {
// 自动填充用户基本信息
applyForm.value.studentName = studentInfo.name || '';
@@ -584,16 +600,18 @@ function submitApply() {
(proxy!.$refs['applyRef'] as any).validate((valid: boolean) => {
if (valid) {
applyForm.value.teamId = selectedTeam.value.id;
applyJoinVolunteerTeam(applyForm.value).then((response) => {
proxy!.$modal.msgSuccess('申请提交成功,请等待审核');
applyOpen.value = false;
getList(); // 刷新列表
}).catch((error) => {
console.error('申请提交失败:', error);
// 显示具体的错误信息
const errorMsg = error?.response?.data?.msg || error?.message || '申请提交失败,请稍后重试';
proxy!.$modal.msgError(errorMsg);
});
applyJoinVolunteerTeam(applyForm.value)
.then((response) => {
proxy!.$modal.msgSuccess('申请提交成功,请等待审核');
applyOpen.value = false;
getList(); // 刷新列表
})
.catch((error) => {
console.error('申请提交失败:', error);
// 显示具体的错误信息
const errorMsg = error?.response?.data?.msg || error?.message || '申请提交失败,请稍后重试';
proxy!.$modal.msgError(errorMsg);
});
}
});
}
+131 -8
View File
@@ -43,7 +43,7 @@
type="warning"
icon="Switch"
:disabled="single || isTransferDisabled(teamList.find((t) => ids.includes(t.id)))"
@click="handleTransfer()"
@click="handleTransfer"
v-hasPermi="['stgl:volunteerTeam:transfer']"
>转让</el-button
>
@@ -78,6 +78,17 @@
<span v-if="scope.row.memberLimit" class="member-limit">/ {{ scope.row.memberLimit }}</span>
</template>
</el-table-column>
<el-table-column label="报名开启" align="center" prop="sighOpen" width="90">
<template #default="scope">
<el-switch
v-model="scope.row.sighOpen"
active-value="Y"
inactive-value="N"
@change="handleSighOpenStatusChange(scope.row)"
v-hasPermi="['stgl:volunteerTeam:edit']"
></el-switch>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
@@ -91,7 +102,7 @@
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="380" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
@@ -115,6 +126,28 @@
v-hasPermi="['stgl:volunteerTeam:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="成员管理" placement="top">
<el-button
link
type="info"
icon="User"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleMemberManagement(scope.row)"
v-hasPermi="['stgl:volunteermember:list']"
></el-button>
</el-tooltip>
<el-tooltip content="活动管理" placement="top">
<el-button
link
type="success"
icon="Calendar"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleActivityManagement(scope.row)"
v-hasPermi="['stgl:activity:list']"
></el-button>
</el-tooltip>
<el-tooltip content="转让" placement="top">
<el-button
link
@@ -197,7 +230,7 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
@@ -206,6 +239,15 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开启报名" prop="sighOpen">
<el-radio-group v-model="form.sighOpen">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
@@ -296,6 +338,9 @@
<el-descriptions-item label="状态">
<dict-tag :options="sys_normal_disable" :value="detailForm.status" />
</el-descriptions-item>
<el-descriptions-item label="报名开启">
<dict-tag :options="sys_yes_no" :value="detailForm.sighOpen" />
</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detailForm.phone }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ detailForm.email }}</el-descriptions-item>
<el-descriptions-item label="队伍简介" :span="2">{{ detailForm.description }}</el-descriptions-item>
@@ -413,6 +458,16 @@
</div>
</template>
</el-dialog>
<!-- 成员管理弹窗 -->
<MemberManagementDialog
v-model:visible="memberDialog.visible"
:title="memberDialog.title"
:team-id="currentTeam?.id"
:team-name="currentTeam?.teamName"
:member-limit="currentTeam?.memberLimit"
:current-member-count="currentTeam?.currentMemberCount"
/>
</div>
</template>
@@ -429,14 +484,18 @@ import {
searchTeacherByName,
getTeacherById,
checkTeamLeader,
transferVolunteerTeam
transferVolunteerTeam,
changeSighOpenStatus
} from '@/api/stgl/volunteerteam';
import StudentSelector from '@/components/StudentSelector/index.vue';
import TeacherSelector from '@/components/TeacherSelector/index.vue';
import { onMounted, getCurrentInstance, ref, reactive, watch, toRefs } from 'vue';
import MemberManagementDialog from './components/MemberManagementDialog.vue';
import { onMounted, getCurrentInstance, ref, reactive, watch, toRefs, nextTick } from 'vue';
import { useRouter } from 'vue-router';
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = toRefs(proxy.useDict('sys_normal_disable'));
const { sys_normal_disable, sys_yes_no } = toRefs(proxy.useDict('sys_normal_disable', 'sys_yes_no'));
const router = useRouter();
const teamList = ref([]);
const open = ref(false);
@@ -480,6 +539,14 @@ const transferForm = reactive({
newLeaderPhone: ''
});
// 成员管理相关
const memberDialog = reactive({
visible: false,
title: ''
});
const currentTeam = ref(null);
const transferRules = {
newLeaderStudent: [{ required: true, message: '请选择新队长', trigger: 'change' }]
};
@@ -520,7 +587,7 @@ const checkIsTeamLeader = async (teamId) => {
const result = response.data === true;
console.log(`✅ 队长检查结果: ${result} (response.data: ${response.data}, type: ${typeof response.data})`);
return result;
} catch (error) {
} catch (error: any) {
console.error('❌ 检查队长权限失败:', error);
console.error('❌ 错误详情:', {
message: error.message,
@@ -703,6 +770,7 @@ const data = reactive({
advisorEmail: null,
advisorUserId: null,
status: '0',
sighOpen: 'Y',
remark: null
} as any,
detailForm: {} as any,
@@ -747,6 +815,9 @@ async function getList() {
if (item.status !== undefined && item.status !== null) {
item.status = String(item.status);
}
if (item.sighOpen !== undefined && item.sighOpen !== null) {
item.sighOpen = String(item.sighOpen);
}
});
}
@@ -793,6 +864,7 @@ function reset() {
advisorEmail: null,
advisorUserId: null,
status: '0',
sighOpen: 'Y',
remark: null
};
@@ -861,7 +933,9 @@ function handleUpdate(row) {
// 指导老师信息字段映射
advisorDepartment: data.advisorDept, // advisorDept -> advisorDepartment
// 状态字段类型转换:确保与字典组件匹配(Integer -> String)
status: data.status !== undefined && data.status !== null ? String(data.status) : '0'
status: data.status !== undefined && data.status !== null ? String(data.status) : '0',
// 报名开关字段类型转换:确保与字典组件匹配
sighOpen: data.sighOpen !== undefined && data.sighOpen !== null ? String(data.sighOpen) : 'Y'
};
// 设置队长选择器的值 - 先尝试用延迟设置的方式
@@ -1109,6 +1183,48 @@ const getAdvisorNames = (row: any): string[] => {
return [];
};
/** 成员管理按钮操作 */
const handleMemberManagement = (row) => {
console.log('handleMemberManagement: 打开成员管理弹窗, row:', row);
currentTeam.value = row;
memberDialog.visible = true;
memberDialog.title = `${row.teamName} - 成员管理`;
};
/** 活动管理按钮操作 */
const handleActivityManagement = (row) => {
console.log('handleActivityManagement: 跳转到活动管理页面, row:', row);
console.log('handleActivityManagement: 传递参数:', {
relatedTeamId: row.id,
relatedTeamName: row.teamName,
from: 'team'
});
router.push({
path: '/stgl/activity/info',
query: {
relatedTeamId: row.id,
relatedTeamName: row.teamName,
from: 'team'
}
});
};
/** 报名开启状态修改 */
const handleSighOpenStatusChange = async (row) => {
const text = row.sighOpen === 'Y' ? '开启' : '关闭';
await proxy?.$modal
.confirm('确认要"' + text + '""' + row.teamName + '"志愿者团队报名吗?')
.then(async () => {
await changeSighOpenStatus(row.id, row.sighOpen).then(() => {
proxy?.$modal.msgSuccess(text + '成功');
});
})
.catch(() => {
row.sighOpen = row.sighOpen === 'Y' ? 'N' : 'Y';
});
};
onMounted(() => {
getList();
});
@@ -1172,4 +1288,11 @@ onMounted(() => {
min-width: 200px !important;
}
}
/* 操作列自适应样式 - 只确保不换行 */
:deep(.el-table) {
.el-table__body-wrapper .el-table__body td:has(.el-button) {
white-space: nowrap !important;
}
}
</style>
+23 -2
View File
@@ -180,7 +180,7 @@ const { evaluation_status, sys_user_sex } = toRefs<any>(proxy?.useDict('evaluati
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const evaluationId = ref(route.query.id as string);
const loading = ref(false);
const statisticsLoading = ref(false);
@@ -219,7 +219,7 @@ let pieChart: echarts.ECharts | null = null;
const queryParams = ref<XljkEvaluationDetailQuery>({
pageNum: 1,
pageSize: 10,
evaluationId: Number(evaluationId.value),
evaluationId: Number(evaluationId.value) || 0,
mentalStatus: '',
studentName: '',
studentNo: '',
@@ -260,7 +260,15 @@ const getStatistics = async () => {
const getDetailList = async () => {
try {
loading.value = true;
// 确保有有效的evaluationId
if (!evaluationId.value) {
console.error('evaluationId为空,无法查询详情');
return;
}
console.log('查询参数:', {
evaluationId: evaluationId.value,
filterStatus: filterStatus.value,
searchKeyword: searchKeyword.value,
studentNo: queryParams.value.studentNo,
@@ -441,6 +449,19 @@ const resetFilter = () => {
};
onMounted(() => {
console.log('详情页面路由参数:', {
params: route.params,
query: route.query,
evaluationId: evaluationId.value
});
// 验证ID是否存在
if (!evaluationId.value) {
proxy?.$modal.msgError('缺少测评ID参数');
router.push('/xljk/evaluation');
return;
}
getEvaluationInfo();
getStatistics();
getDetailList();
+29 -6
View File
@@ -134,13 +134,15 @@ import {
import { XljkEvaluationForm, StudentInfo, XljkEvaluationDetailForm } from '@/api/xljk/evaluation/types';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useTagsViewStore } from '@/store/modules/tagsView';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const tagsViewStore = useTagsViewStore();
const evaluationId = ref(route.query.id as string);
const formRef = ref<ElFormInstance>();
const submitLoading = ref(false);
const studentsLoading = ref(false);
@@ -166,6 +168,13 @@ const rules = {
evaluationName: [{ required: true, message: '测评名称不能为空', trigger: 'blur' }]
};
// 心理状态字典映射
const mentalStatusMap: Record<string, string> = {
'1': '健康',
'2': '良好',
'3': '重点关注'
};
// 学生列表
const studentList = ref<StudentInfo[]>([]);
// 测评明细列表
@@ -273,7 +282,7 @@ const applyBatchMentalStatus = async () => {
} as XljkEvaluationDetailForm;
});
await batchUpdateXljkMentalStatus(detailsToUpdate);
ElMessage.success(`已批量设置 ${selectedStudents.value.length} 名学生的心理状态为 ${status}`);
ElMessage.success(`已批量设置 ${selectedStudents.value.length} 名学生的心理状态为 ${mentalStatusMap[status]}`);
// 刷新学生列表以更新状态
await loadStudentList();
await getEvaluationDetails();
@@ -297,7 +306,7 @@ const applyBatchMentalStatusToAll = async () => {
} as XljkEvaluationDetailForm;
});
await batchUpdateXljkMentalStatus(detailsToUpdate);
ElMessage.success(`已批量设置 ${studentList.value.length} 名学生的心理状态为 ${status}`);
ElMessage.success(`已批量设置 ${studentList.value.length} 名学生的心理状态为 ${mentalStatusMap[status]}`);
// 刷新学生列表以更新状态
await loadStudentList();
await getEvaluationDetails();
@@ -366,7 +375,11 @@ const submitForm = () => {
await batchUpdateXljkMentalStatus(detailsToUpdate);
ElMessage.success('修改成功');
router.replace('/xljk/evaluation');
// 关闭当前标签页并跳转到列表页面
setTimeout(async () => {
await tagsViewStore.delView(route);
router.push('/xljk/evaluation');
}, 1000);
} catch (error) {
console.error('修改测评失败', error);
} finally {
@@ -377,11 +390,21 @@ const submitForm = () => {
};
/** 取消按钮 */
const cancel = () => {
router.replace('/xljk/evaluation');
const cancel = async () => {
await tagsViewStore.delView(route);
router.push('/xljk/evaluation');
};
onMounted(() => {
console.log('Edit页面路由参数:', route.query);
console.log('获取到的测评ID:', evaluationId.value);
if (!evaluationId.value) {
proxy?.$modal.msgError('缺少测评ID参数');
router.push('/xljk/evaluation');
return;
}
getEvaluationInfo();
});
</script>
+20 -3
View File
@@ -343,18 +343,35 @@ const handleSelectionChange = (selection: XljkEvaluationVO[]) => {
/** 新增按钮操作 */
const handleAdd = async () => {
proxy?.$router.push({ path: '/xljk/evaluation/add' });
proxy.$router.push({
path: '/xljk/evaluation/add',
query: {
type: 'add'
}
});
};
/** 查看按钮操作 */
const handleView = (row: XljkEvaluationVO) => {
proxy?.$router.push({ path: `/xljk/evaluation/detail/${row.id}` });
proxy.$router.push({
path: `/xljk/evaluation/detail`,
query: {
id: row.id,
type: 'detail'
}
});
};
/** 修改按钮操作 */
const handleUpdate = (row?: XljkEvaluationVO) => {
const id = row?.id || ids.value[0];
proxy?.$router.push({ path: `/xljk/evaluation/edit/${id}` });
proxy.$router.push({
path: `/xljk/evaluation/edit`,
query: {
id: row.id,
type: 'edit'
}
});
};
/** 删除按钮操作 */