This commit is contained in:
weizhong committed 2025-08-26 00:46:44 +08:00
commit d5d264fc29
196 files changed
+6214 -1116

No files matched your search

+1
View File
@@ -325,3 +325,4 @@ docs/变更管理/
.playwright-mcp/
查找界面中涉及到的权限字符.md
docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf
首页完善的提示词.md
+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,48 +145,134 @@ 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());
// 通过学号查找用户ID
Long userId = getUserIdByStudentNo(bo.getStudentNo());
if (userId == null) {
throw new RuntimeException("未找到对应的用户信息");
}
if (existingRecord != null) {
// 如果已存在记录,更新签到信息
bo.setId(existingRecord.getId());
bo.setSignInTime(new Date());
bo.setSignInStatus(1);
bo.setOverallStatus(1); // 已签到
return updateByBo(bo);
} else {
// 如果不存在记录,新增签到记录
// 移除重复签到检查,允许每次签到都生成新记录
// 无论之前是否有签到记录,都创建新的签到记录
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);
}
}
/**
* 用户签退
*/
@Override
public Boolean signOut(StglActivityAttendanceBo bo) {
// 必须先签到才能签退
StglActivityAttendanceVo existingRecord = queryByActivityAndUser(bo.getActivityId(), bo.getUserId());
// 通过学号查找用户ID
Long userId = getUserIdByStudentNo(bo.getStudentNo());
if (userId == null) {
throw new RuntimeException("未找到对应的用户信息");
}
if (existingRecord == null || existingRecord.getSignInStatus() != 1) {
throw new RuntimeException("请先完成签到");
// 如果提供了记录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.setId(existingRecord.getId());
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;
}
}
@@ -21,16 +21,27 @@ import org.dromara.stgl.domain.bo.StglActivityBo;
import org.dromara.stgl.domain.vo.StglActivityVo;
import org.dromara.stgl.mapper.StglActivityMapper;
import org.dromara.stgl.service.IStglActivityService;
import org.dromara.stgl.utils.AdvisorRoleAssigner;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.dromara.common.json.utils.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.dromara.stgl.domain.StglClub;
import org.dromara.stgl.domain.StglVolunteerTeam;
import org.dromara.stgl.mapper.StglClubMapper;
import org.dromara.stgl.mapper.StglVolunteerTeamMapper;
/**
* 活动信息Service业务层处理
@@ -38,11 +49,15 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author ruoyi
* @date 2025-01-07
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, StglActivity> implements IStglActivityService {
private final StglActivityMapper baseMapper;
private final AdvisorRoleAssigner advisorRoleAssigner;
private final StglClubMapper stglClubMapper;
private final StglVolunteerTeamMapper stglVolunteerTeamMapper;
/**
* 查询活动信息
@@ -57,6 +72,9 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
*/
@Override
public TableDataInfo<StglActivityVo> queryPageList(StglActivityBo bo, PageQuery pageQuery) {
// 应用数据权限控制
applyDataScope(bo);
// 转换为实体对象,使用自定义SQL查询(包含关联信息)
StglActivity activity = BeanUtil.toBean(bo, StglActivity.class);
Page<StglActivityVo> result = baseMapper.selectVoPage(pageQuery.build(), activity);
@@ -68,6 +86,9 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
*/
@Override
public List<StglActivityVo> queryList(StglActivityBo bo) {
// 应用数据权限控制
applyDataScope(bo);
// 转换为实体对象,使用自定义SQL查询(包含关联信息)
StglActivity activity = BeanUtil.toBean(bo, StglActivity.class);
List<StglActivityVo> list = baseMapper.selectVoList(activity);
@@ -215,6 +236,8 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 为指导老师分配角色
assignAdvisorRoles(add);
}
return flag;
}
@@ -227,7 +250,21 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
public Boolean updateByBo(StglActivityBo bo) {
StglActivity update = MapstructUtils.convert(bo, StglActivity.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
// 获取更新前的活动信息,用于比较指导老师变化
StglActivity oldActivity = null;
if (update.getId() != null) {
oldActivity = baseMapper.selectById(update.getId());
}
boolean updateResult = baseMapper.updateById(update) > 0;
if (updateResult) {
// 处理指导老师角色分配
handleAdvisorRoleAssignment(oldActivity, update);
}
return updateResult;
}
/**
@@ -382,4 +419,219 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
return baseMapper.update(null, wrapper) > 0;
}
/**
* 为指导老师分配角色
*
* @param activity 活动信息
*/
private void assignAdvisorRoles(StglActivity activity) {
try {
List<Long> advisorUserIds = extractAdvisorUserIds(activity);
if (!advisorUserIds.isEmpty()) {
advisorRoleAssigner.assignAdvisorRole(advisorUserIds);
log.info("成功为活动 {} 的指导老师分配角色,指导老师数量: {}", activity.getActivityName(), advisorUserIds.size());
}
} catch (Exception e) {
log.error("为活动 {} 的指导老师分配角色时发生异常: {}", activity.getActivityName(), e.getMessage());
// 不抛出异常,避免影响主业务流程
}
}
/**
* 处理指导老师角色分配(更新时使用)
*
* @param oldActivity 更新前的活动信息
* @param newActivity 更新后的活动信息
*/
private void handleAdvisorRoleAssignment(StglActivity oldActivity, StglActivity newActivity) {
try {
// 获取更新前后的指导老师用户ID列表
List<Long> oldAdvisorUserIds = oldActivity != null ? extractAdvisorUserIds(oldActivity) : new ArrayList<>();
List<Long> newAdvisorUserIds = extractAdvisorUserIds(newActivity);
// 找出新增的指导老师
List<Long> addedAdvisorUserIds = newAdvisorUserIds.stream()
.filter(id -> !oldAdvisorUserIds.contains(id))
.collect(Collectors.toList());
// 找出移除的指导老师
List<Long> removedAdvisorUserIds = oldAdvisorUserIds.stream()
.filter(id -> !newAdvisorUserIds.contains(id))
.collect(Collectors.toList());
// 为新指导老师分配角色
if (!addedAdvisorUserIds.isEmpty()) {
advisorRoleAssigner.assignAdvisorRole(addedAdvisorUserIds);
log.info("活动 {} 新增指导老师,成功分配角色,数量: {}", newActivity.getActivityName(), addedAdvisorUserIds.size());
}
// 移除旧指导老师的角色
if (!removedAdvisorUserIds.isEmpty()) {
advisorRoleAssigner.removeAdvisorRoles(removedAdvisorUserIds);
log.info("活动 {} 移除指导老师,成功移除角色,数量: {}", newActivity.getActivityName(), removedAdvisorUserIds.size());
}
} catch (Exception e) {
log.error("处理活动 {} 的指导老师角色分配时发生异常: {}", newActivity.getActivityName(), e.getMessage());
// 不抛出异常,避免影响主业务流程
}
}
/**
* 从活动信息中提取指导老师用户ID列表
*
* @param activity 活动信息
* @return 指导老师用户ID列表
*/
private List<Long> extractAdvisorUserIds(StglActivity activity) {
List<Long> advisorUserIds = new ArrayList<>();
try {
// 从JSON字段读取指导老师数据
if (StringUtils.isNotBlank(activity.getInstructors())) {
List<Map<String, Object>> instructors = JsonUtils.parseObject(activity.getInstructors(), new TypeReference<List<Map<String, Object>>>() {});
for (Map<String, Object> instructor : instructors) {
// 修复:支持嵌套的JSON结构,从teacher子对象中提取userId
Object userIdObj = null;
// 尝试直接从instructor对象获取userId(兼容旧格式)
if (instructor.containsKey("userId")) {
userIdObj = instructor.get("userId");
}
// 尝试从teacher子对象获取userId(新格式)
else if (instructor.containsKey("teacher")) {
Object teacherObj = instructor.get("teacher");
if (teacherObj instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> teacher = (Map<String, Object>) teacherObj;
userIdObj = teacher.get("userId");
}
}
if (userIdObj != null) {
Long userId = Long.valueOf(userIdObj.toString());
if (userId != null) {
advisorUserIds.add(userId);
}
}
}
}
} catch (Exception e) {
log.error("解析活动 {} 的指导老师数据时发生异常: {}", activity.getActivityName(), e.getMessage());
}
return advisorUserIds;
}
/**
* 应用数据权限控制
* 如果是stgl_president角色,只能查看自己创建的活动
* 如果是stgl_instructor角色,只能查看自己作为指导老师的社团的活动
*
* @param bo 查询条件
*/
private void applyDataScope(StglActivityBo bo) {
try {
// 获取当前登录用户信息
Long currentUserId = LoginHelper.getUserId();
if (currentUserId == null) {
return;
}
Set<String> roleKeys = LoginHelper.getLoginUser().getRolePermission();
// 如果是stgl_president角色且不是管理员,只能查看自己创建的活动
if (roleKeys.contains("stgl_president") &&
!roleKeys.contains("admin") &&
!roleKeys.contains("common")) {
// 设置数据权限过滤条件 - 使用params.dataScope方式,与XML中的${activity.params.dataScope}对应
if (bo.getParams() == null) {
bo.setParams(new HashMap<>());
}
bo.getParams().put("dataScope", "AND sa.create_by = " + currentUserId);
log.info("应用数据权限:用户 {} 为社长角色,只能查看自己创建的活动", currentUserId);
}
// 🔥 新增:如果是stgl_instructor角色且不是管理员,只能查看自己作为指导老师的社团的活动
if (roleKeys.contains("stgl_instructor") &&
!roleKeys.contains("admin") &&
!roleKeys.contains("common")) {
// 获取用户作为指导老师的社团ID列表
List<Long> userAdvisorClubIds = getUserAdvisorClubIds(currentUserId);
if (userAdvisorClubIds.isEmpty()) {
// 如果不是任何社团的指导老师,设置一个不存在的ID,返回空结果
if (bo.getParams() == null) {
bo.setParams(new HashMap<>());
}
bo.getParams().put("dataScope", "AND sa.related_club_id = -1");
log.info("应用数据权限:用户 {} 为指导老师角色,但不是任何社团的指导老师,返回空结果", currentUserId);
} else {
// 设置数据权限过滤条件 - 只能查看自己作为指导老师的社团的活动
if (bo.getParams() == null) {
bo.setParams(new HashMap<>());
}
String clubIdsStr = userAdvisorClubIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
bo.getParams().put("dataScope", "AND sa.related_club_id IN (" + clubIdsStr + ")");
log.info("应用数据权限:用户 {} 为指导老师角色,只能查看社团ID为 {} 的活动", currentUserId, clubIdsStr);
}
}
} catch (Exception e) {
log.error("应用数据权限控制失败:{}", e.getMessage(), e);
}
}
/**
* 获取用户作为指导老师的社团ID列表
*
* @param userId 用户ID
* @return 社团ID列表
*/
private List<Long> getUserAdvisorClubIds(Long userId) {
List<Long> clubIds = new ArrayList<>();
try {
// 从stgl_club表的advisors_json字段查询
List<Long> jsonClubIds = getJsonAdvisorClubIds(userId);
clubIds.addAll(jsonClubIds);
log.debug("用户 {} 作为指导老师的社团ID列表: {}", userId, clubIds);
} catch (Exception e) {
log.error("获取用户 {} 作为指导老师的社团ID列表时发生异常: {}", userId, e.getMessage(), e);
}
return clubIds;
}
/**
* 从社团表的advisors_json字段获取用户作为指导老师的社团ID列表
*
* @param userId 用户ID
* @return 社团ID列表
*/
private List<Long> getJsonAdvisorClubIds(Long userId) {
List<Long> clubIds = new ArrayList<>();
try {
// 查询社团表中advisors_json字段包含该用户的记录
LambdaQueryWrapper<StglClub> lqw = new LambdaQueryWrapper<>();
lqw.select(StglClub::getId);
lqw.like(StglClub::getAdvisorsJson, userId.toString());
List<StglClub> clubs = stglClubMapper.selectList(lqw);
clubIds = clubs.stream()
.map(StglClub::getId)
.collect(Collectors.toList());
} catch (Exception e) {
log.error("从社团表获取用户 {} 作为指导老师的社团ID列表时发生异常: {}", userId, e.getMessage(), e);
}
return clubIds;
}
}
@@ -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("社长和副社长不能直接退出,请先转让职务");
}
@@ -17,6 +17,7 @@ import org.dromara.stgl.domain.StglMember;
import org.dromara.stgl.mapper.StglClubMapper;
import org.dromara.stgl.mapper.StglMemberMapper;
import org.dromara.stgl.service.IStglClubService;
import org.dromara.stgl.utils.AdvisorRoleAssigner;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.json.utils.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -35,10 +36,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.Set;
@@ -58,6 +61,8 @@ public class StglClubServiceImpl implements IStglClubService {
private final StglMemberMapper memberMapper;
// 添加学生信息Mapper
private final XgStudentMsgMapper studentMsgMapper;
// 添加指导老师角色分配工具
private final AdvisorRoleAssigner advisorRoleAssigner;
/**
* 查询社团信息
@@ -143,6 +148,31 @@ public class StglClubServiceImpl implements IStglClubService {
bo.setClubIds(userClubIds);
}
}
// 🔥 新增:如果是stgl_instructor角色且不是管理员,只能查看自己作为指导老师的社团
if (roleKeys.contains("stgl_instructor") &&
!roleKeys.contains("admin") &&
!roleKeys.contains("common")) {
// 获取用户作为指导老师的社团ID列表
List<Long> userAdvisorClubIds = getUserAdvisorClubIds(currentUserId);
if (userAdvisorClubIds.isEmpty()) {
// 如果不是任何社团的指导老师,设置一个不存在的ID,返回空结果
bo.setClubIds(Collections.singletonList(-1L));
} else {
// 如果之前已经设置了clubIds(比如stgl_president角色),则取交集
if (CollectionUtils.isNotEmpty(bo.getClubIds())) {
List<Long> intersection = bo.getClubIds().stream()
.filter(userAdvisorClubIds::contains)
.collect(Collectors.toList());
bo.setClubIds(intersection.isEmpty() ? Collections.singletonList(-1L) : intersection);
} else {
// 设置数据权限过滤条件
bo.setClubIds(userAdvisorClubIds);
}
}
}
} catch (Exception e) {
// 如果获取权限信息失败,不影响正常查询
log.warn("获取用户权限信息失败: {}", e.getMessage());
@@ -164,7 +194,7 @@ public class StglClubServiceImpl implements IStglClubService {
LambdaQueryWrapper<StglMember> lqw = Wrappers.lambdaQuery();
lqw.eq(StglMember::getMemberType, 1) // 社团成员
.eq(StglMember::getUserId, userId) // 当前用户
.eq(StglMember::getRoleType, 1) // 社长角色
.eq(StglMember::getRoleType, "社长") // 社长角色 - 修复:使用字符串而不是数值
.eq(StglMember::getStatus, 2); // 已加入状态
List<StglMember> members = memberMapper.selectList(lqw);
@@ -178,6 +208,91 @@ public class StglClubServiceImpl implements IStglClubService {
}
}
/**
* 获取用户作为指导老师的社团ID列表
*
* @param userId 用户ID
* @return 社团ID列表
*/
private List<Long> getUserAdvisorClubIds(Long userId) {
if (userId == null) {
return Collections.emptyList();
}
try {
// 方法1:从stgl_member表查询(如果指导老师作为成员记录)
LambdaQueryWrapper<StglMember> memberLqw = Wrappers.lambdaQuery();
memberLqw.eq(StglMember::getMemberType, 1) // 社团成员
.eq(StglMember::getUserId, userId) // 当前用户
.eq(StglMember::getRoleType, "指导老师") // 指导老师角色
.eq(StglMember::getStatus, 2); // 已加入状态
List<StglMember> memberAdvisors = memberMapper.selectList(memberLqw);
List<Long> memberClubIds = memberAdvisors.stream()
.map(StglMember::getRelationId)
.distinct()
.collect(Collectors.toList());
// 方法2:从社团表的advisors_json字段查询(主要方法)
LambdaQueryWrapper<StglClub> clubLqw = Wrappers.lambdaQuery();
clubLqw.select(StglClub::getId, StglClub::getAdvisorsJson);
clubLqw.isNotNull(StglClub::getAdvisorsJson);
clubLqw.ne(StglClub::getAdvisorsJson, "");
List<StglClub> clubs = baseMapper.selectList(clubLqw);
List<Long> jsonClubIds = new ArrayList<>();
for (StglClub club : clubs) {
try {
if (StringUtils.isNotBlank(club.getAdvisorsJson())) {
List<Map<String, Object>> advisors = JsonUtils.parseObject(
club.getAdvisorsJson(),
new TypeReference<List<Map<String, Object>>>() {}
);
for (Map<String, Object> advisor : advisors) {
Object userIdObj = null;
// 尝试直接从advisor对象获取userId
if (advisor.containsKey("userId")) {
userIdObj = advisor.get("userId");
}
// 尝试从teacher子对象获取userId
else if (advisor.containsKey("teacher")) {
Object teacherObj = advisor.get("teacher");
if (teacherObj instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> teacher = (Map<String, Object>) teacherObj;
userIdObj = teacher.get("userId");
}
}
if (userIdObj != null && userId.toString().equals(userIdObj.toString())) {
jsonClubIds.add(club.getId());
break; // 找到该用户,继续下一个社团
}
}
}
} catch (Exception e) {
log.warn("解析社团 {} 的指导老师JSON数据失败: {}", club.getId(), e.getMessage());
}
}
// 合并两种方法的结果
Set<Long> allClubIds = new HashSet<>();
allClubIds.addAll(memberClubIds);
allClubIds.addAll(jsonClubIds);
log.info("用户 {} 作为指导老师的社团ID列表: memberClubIds={}, jsonClubIds={}, 最终结果={}",
userId, memberClubIds, jsonClubIds, allClubIds);
return new ArrayList<>(allClubIds);
} catch (Exception e) {
log.error("查询用户作为指导老师的社团失败: {}", e.getMessage(), e);
return Collections.emptyList();
}
}
private LambdaQueryWrapper<StglClub> buildQueryWrapper(StglClubBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<StglClub> lqw = Wrappers.lambdaQuery();
@@ -215,6 +330,8 @@ public class StglClubServiceImpl implements IStglClubService {
bo.setId(add.getId());
// 自动创建社长和副社长的成员记录
createLeaderMemberRecords(add);
// 为指导老师分配角色
assignAdvisorRoles(add);
}
return flag;
}
@@ -231,12 +348,20 @@ public class StglClubServiceImpl implements IStglClubService {
StglClub update = MapstructUtils.convert(bo, StglClub.class);
validEntityBeforeSave(update);
// 获取更新前的社团信息,用于比较指导老师变化
StglClub oldClub = null;
if (update.getId() != null) {
oldClub = baseMapper.selectById(update.getId());
}
// 更新社团基本信息
boolean updateResult = baseMapper.updateById(update) > 0;
if (updateResult) {
// 同步更新副社长的成员记录
syncVicePresidentsInMembers(update);
// 处理指导老师角色分配
handleAdvisorRoleAssignment(oldClub, update);
}
return updateResult;
@@ -358,6 +483,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 +513,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 +622,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 +688,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 +729,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 +783,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 +817,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 +842,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 +862,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. 处理单个副社长字段(向后兼容)
@@ -851,7 +992,7 @@ public class StglClubServiceImpl implements IStglClubService {
lqw.eq(StglMember::getMemberType, 1); // 社团成员
lqw.eq(StglMember::getRelationId, clubId); // 该社团
lqw.eq(StglMember::getUserId, userId); // 指定用户
lqw.eq(StglMember::getRoleType, 1); // 社长角色 (1-社长)
lqw.eq(StglMember::getRoleType, "社长"); // 社长角色 - 修复:使用字符串而不是数值
lqw.eq(StglMember::getStatus, 2); // 已加入状态 (2-已加入)
StglMember member = memberMapper.selectOne(lqw);
@@ -897,7 +1038,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 +1077,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 +1089,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);
@@ -957,4 +1098,114 @@ public class StglClubServiceImpl implements IStglClubService {
return true;
}
/**
* 为指导老师分配角色
*
* @param club 社团信息
*/
private void assignAdvisorRoles(StglClub club) {
try {
List<Long> advisorUserIds = extractAdvisorUserIds(club);
if (!advisorUserIds.isEmpty()) {
advisorRoleAssigner.assignAdvisorRole(advisorUserIds);
log.info("成功为社团 {} 的指导老师分配角色,指导老师数量: {}", club.getClubName(), advisorUserIds.size());
}
} catch (Exception e) {
log.error("为社团 {} 的指导老师分配角色时发生异常", club.getClubName(), e);
// 不抛出异常,避免影响主业务流程
}
}
/**
* 处理指导老师角色分配(更新时使用)
*
* @param oldClub 更新前的社团信息
* @param newClub 更新后的社团信息
*/
private void handleAdvisorRoleAssignment(StglClub oldClub, StglClub newClub) {
try {
// 获取更新前后的指导老师用户ID列表
List<Long> oldAdvisorUserIds = oldClub != null ? extractAdvisorUserIds(oldClub) : new ArrayList<>();
List<Long> newAdvisorUserIds = extractAdvisorUserIds(newClub);
// 找出新增的指导老师
List<Long> addedAdvisorUserIds = newAdvisorUserIds.stream()
.filter(id -> !oldAdvisorUserIds.contains(id))
.collect(Collectors.toList());
// 找出移除的指导老师
List<Long> removedAdvisorUserIds = oldAdvisorUserIds.stream()
.filter(id -> !newAdvisorUserIds.contains(id))
.collect(Collectors.toList());
// 为新指导老师分配角色
if (!addedAdvisorUserIds.isEmpty()) {
advisorRoleAssigner.assignAdvisorRole(addedAdvisorUserIds);
log.info("社团 {} 新增指导老师,成功分配角色,数量: {}", newClub.getClubName(), addedAdvisorUserIds.size());
}
// 移除旧指导老师的角色
if (!removedAdvisorUserIds.isEmpty()) {
advisorRoleAssigner.removeAdvisorRoles(removedAdvisorUserIds);
log.info("社团 {} 移除指导老师,成功移除角色,数量: {}", newClub.getClubName(), removedAdvisorUserIds.size());
}
} catch (Exception e) {
log.error("处理社团 {} 的指导老师角色分配时发生异常", newClub.getClubName(), e);
// 不抛出异常,避免影响主业务流程
}
}
/**
* 从社团信息中提取指导老师用户ID列表
*
* @param club 社团信息
* @return 指导老师用户ID列表
*/
private List<Long> extractAdvisorUserIds(StglClub club) {
List<Long> advisorUserIds = new ArrayList<>();
try {
// 优先从JSON字段读取指导老师数据
if (StringUtils.isNotBlank(club.getAdvisorsJson())) {
List<Map<String, Object>> advisors = JsonUtils.parseObject(club.getAdvisorsJson(), new TypeReference<List<Map<String, Object>>>() {});
for (Map<String, Object> advisor : advisors) {
// 修复:支持嵌套的JSON结构,从teacher子对象中提取userId
Object userIdObj = null;
// 尝试直接从advisor对象获取userId(兼容旧格式)
if (advisor.containsKey("userId")) {
userIdObj = advisor.get("userId");
}
// 尝试从teacher子对象获取userId(新格式)
else if (advisor.containsKey("teacher")) {
Object teacherObj = advisor.get("teacher");
if (teacherObj instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> teacher = (Map<String, Object>) teacherObj;
userIdObj = teacher.get("userId");
}
}
if (userIdObj != null) {
Long userId = Long.valueOf(userIdObj.toString());
if (userId != null) {
advisorUserIds.add(userId);
}
}
}
}
// 如果没有JSON数据,尝试从单一字段获取
if (advisorUserIds.isEmpty() && StringUtils.isNotBlank(club.getAdvisorName())) {
// 这里需要根据指导老师姓名查找用户ID,暂时跳过
// 因为单一字段模式下没有用户ID信息
log.debug("社团 {} 使用单一指导老师字段,无法获取用户ID", club.getClubName());
}
} catch (Exception e) {
log.error("解析社团 {} 的指导老师数据时发生异常", club.getClubName(), e);
}
return advisorUserIds;
}
}
@@ -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()
@@ -25,13 +25,16 @@ import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.domain.SysUser;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.stgl.utils.AdvisorRoleAssigner;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 志愿者队伍Service业务层处理
@@ -39,6 +42,7 @@ import java.util.List;
* @author Cursor AI
* @date 2025-01-07
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
@@ -47,6 +51,7 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
private final StglMemberMapper memberMapper;
private final XgStudentMsgMapper studentMsgMapper;
private final SysUserMapper userMapper;
private final AdvisorRoleAssigner advisorRoleAssigner;
/**
* 查询志愿者队伍
@@ -148,6 +153,8 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
bo.setId(add.getId());
// 自动创建队长的成员记录
createLeaderMemberRecord(add);
// 为指导老师分配角色
assignAdvisorRole(add);
}
return flag;
}
@@ -161,12 +168,20 @@ public class StglVolunteerTeamServiceImpl implements IStglVolunteerTeamService {
StglVolunteerTeam update = MapstructUtils.convert(bo, StglVolunteerTeam.class);
validEntityBeforeSave(update);
// 获取更新前的队伍信息,用于比较指导老师变化
StglVolunteerTeam oldTeam = null;
if (update.getId() != null) {
oldTeam = baseMapper.selectById(update.getId());
}
// 更新队伍基本信息
boolean updateResult = baseMapper.updateById(update) > 0;
if (updateResult) {
// 同步更新队长的成员记录
syncLeaderInMembers(update);
// 处理指导老师角色分配
handleAdvisorRoleAssignment(oldTeam, update);
}
return updateResult;
@@ -220,7 +235,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 +271,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 +282,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 +318,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 +448,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 +555,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 +672,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 +690,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 +831,92 @@ 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;
}
/**
* 为指导老师分配角色
*
* @param team 志愿者队伍信息
*/
private void assignAdvisorRole(StglVolunteerTeam team) {
try {
Long advisorUserId = extractAdvisorUserId(team);
if (advisorUserId != null) {
advisorRoleAssigner.assignAdvisorRole(advisorUserId);
log.info("成功为志愿者队伍 {} 的指导老师分配角色,指导老师用户ID: {}", team.getTeamName(), advisorUserId);
}
} catch (Exception e) {
log.error("为志愿者队伍 {} 的指导老师分配角色时发生异常", team.getTeamName(), e);
// 不抛出异常,避免影响主业务流程
}
}
/**
* 处理指导老师角色分配(更新时使用)
*
* @param oldTeam 更新前的队伍信息
* @param newTeam 更新后的队伍信息
*/
private void handleAdvisorRoleAssignment(StglVolunteerTeam oldTeam, StglVolunteerTeam newTeam) {
try {
// 获取更新前后的指导老师用户ID
Long oldAdvisorUserId = oldTeam != null ? extractAdvisorUserId(oldTeam) : null;
Long newAdvisorUserId = extractAdvisorUserId(newTeam);
// 如果指导老师发生变化
if (!Objects.equals(oldAdvisorUserId, newAdvisorUserId)) {
// 移除旧指导老师的角色
if (oldAdvisorUserId != null) {
advisorRoleAssigner.removeAdvisorRole(oldAdvisorUserId);
log.info("志愿者队伍 {} 移除指导老师,成功移除角色,用户ID: {}", newTeam.getTeamName(), oldAdvisorUserId);
}
// 为新指导老师分配角色
if (newAdvisorUserId != null) {
advisorRoleAssigner.assignAdvisorRole(newAdvisorUserId);
log.info("志愿者队伍 {} 新增指导老师,成功分配角色,用户ID: {}", newTeam.getTeamName(), newAdvisorUserId);
}
}
} catch (Exception e) {
log.error("处理志愿者队伍 {} 的指导老师角色分配时发生异常", newTeam.getTeamName(), e);
// 不抛出异常,避免影响主业务流程
}
}
/**
* 从志愿者队伍信息中提取指导老师用户ID
*
* @param team 志愿者队伍信息
* @return 指导老师用户ID
*/
private Long extractAdvisorUserId(StglVolunteerTeam team) {
// 志愿者队伍目前使用单一指导老师字段,需要根据指导老师姓名查找用户ID
if (StringUtils.isNotBlank(team.getAdvisorName())) {
try {
// 这里需要根据指导老师姓名查找用户ID
// 由于志愿者队伍表没有直接存储指导老师用户ID,需要额外的查询逻辑
// 暂时返回null,后续可以根据需要实现
log.debug("志愿者队伍 {} 的指导老师 {} 需要根据姓名查找用户ID", team.getTeamName(), team.getAdvisorName());
return null;
} catch (Exception e) {
log.error("查找志愿者队伍 {} 指导老师用户ID时发生异常", team.getTeamName(), e);
}
}
return null;
}
}
@@ -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 != ''">
@@ -99,13 +99,28 @@
END as organization_name,
a.instructors,
CAST(a.activity_status AS CHAR) as activity_status,
-- 动态计算报名状态:基于当前时间和报名时间范围
-- 动态计算报名状态:优先根据活动状态,再根据报名时间范围
CAST(
CASE
-- 首先根据活动状态强制设置(优先级最高)
WHEN a.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定)
WHEN a.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定)
WHEN a.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定)
WHEN a.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定)
WHEN a.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定)
WHEN a.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定)
-- 然后根据活动状态和报名时间判断
WHEN a.activity_status = '3' OR a.activity_status = '4' THEN
CASE
WHEN a.registration_start IS NULL OR a.registration_end IS NULL THEN a.registration_status
WHEN NOW() &lt; a.registration_start THEN '0'
WHEN NOW() &gt;= a.registration_start AND NOW() &lt;= a.registration_end THEN '1'
WHEN NOW() &gt; a.registration_end THEN '2'
<![CDATA[WHEN NOW() < a.registration_start THEN '0']]>
<![CDATA[WHEN NOW() >= a.registration_start AND NOW() <= a.registration_end THEN '1']]>
<![CDATA[WHEN NOW() > a.registration_end THEN '2']]>
ELSE a.registration_status
END
-- 其他情况保持原状态
ELSE a.registration_status
END AS CHAR
) as registration_status,
@@ -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"/>
@@ -56,12 +56,27 @@
sa.poster_url, sa.related_club_id, sa.related_type, sa.organizer_name, sa.organizer_type,
sa.organizer_contact, sa.organizer_phone, sa.co_organizers, sa.instructors,
sa.registration_type, sa.registration_start, sa.registration_end,
-- 动态计算报名状态:基于当前时间和报名时间范围
-- 动态计算报名状态:优先根据活动状态,再根据报名时间范围
CASE
-- 首先根据活动状态强制设置(优先级最高)
WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定)
WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定)
WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定)
WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定)
WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定)
WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定)
-- 然后根据活动状态和报名时间判断
WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN
CASE
WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
WHEN NOW() &lt; sa.registration_start THEN '0'
WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'
WHEN NOW() &gt; sa.registration_end THEN '2'
<![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
<![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
<![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
ELSE sa.registration_status
END
-- 其他情况保持原状态
ELSE sa.registration_status
END AS registration_status,
sa.max_participants, sa.max_teams, sa.team_size_min, sa.team_size_max, sa.need_approval,
@@ -119,11 +134,26 @@
</if>
<if test="registrationStatus != null">
AND (
CASE
-- 首先根据活动状态强制设置(优先级最高)
WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定)
WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定)
WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定)
WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定)
WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定)
WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定)
-- 然后根据活动状态和报名时间判断
WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN
CASE
WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
WHEN NOW() &lt; sa.registration_start THEN '0'
WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'
WHEN NOW() &gt; sa.registration_end THEN '2'
<![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
<![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
<![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
ELSE sa.registration_status
END
-- 其他情况保持原状态
ELSE sa.registration_status
END
) = #{registrationStatus}
@@ -186,11 +216,26 @@
</if>
<if test="activity.registrationStatus != null and activity.registrationStatus != ''">
AND (
CASE
-- 首先根据活动状态强制设置(优先级最高)
WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定)
WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定)
WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定)
WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定)
WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定)
WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定)
-- 然后根据活动状态和报名时间判断
WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN
CASE
WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
WHEN NOW() &lt; sa.registration_start THEN '0'
WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'
WHEN NOW() &gt; sa.registration_end THEN '2'
<![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
<![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
<![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
ELSE sa.registration_status
END
-- 其他情况保持原状态
ELSE sa.registration_status
END
) = #{activity.registrationStatus}
@@ -198,11 +243,26 @@
<!-- 支持报名状态IN查询(用于活动报名大厅) -->
<if test="activity.params != null and activity.params.registrationStatusList != null and activity.params.registrationStatusList.size() > 0">
AND (
CASE
-- 首先根据活动状态强制设置(优先级最高)
WHEN sa.activity_status = '0' THEN '0' -- 草稿 → 未开始(固定)
WHEN sa.activity_status = '1' THEN '0' -- 未开始 → 未开始(固定)
WHEN sa.activity_status = '2' THEN '0' -- 待审批 → 未开始(固定)
WHEN sa.activity_status = '5' THEN '2' -- 已结束 → 报名结束(固定)
WHEN sa.activity_status = '6' THEN '3' -- 已取消 → 已关闭(固定)
WHEN sa.activity_status = '7' THEN '0' -- 已驳回 → 未开始(固定)
-- 然后根据活动状态和报名时间判断
WHEN sa.activity_status = '3' OR sa.activity_status = '4' THEN
CASE
WHEN sa.registration_start IS NULL OR sa.registration_end IS NULL THEN sa.registration_status
WHEN NOW() &lt; sa.registration_start THEN '0'
WHEN NOW() &gt;= sa.registration_start AND NOW() &lt;= sa.registration_end THEN '1'
WHEN NOW() &gt; sa.registration_end THEN '2'
<![CDATA[WHEN NOW() < sa.registration_start THEN '0']]>
<![CDATA[WHEN NOW() >= sa.registration_start AND NOW() <= sa.registration_end THEN '1']]>
<![CDATA[WHEN NOW() > sa.registration_end THEN '2']]>
ELSE sa.registration_status
END
-- 其他情况保持原状态
ELSE sa.registration_status
END
) IN
+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 '备注',
+25
View File
@@ -395,6 +395,31 @@ export const api = {
url: '/auth/logout',
method: 'POST',
}),
// 修改密码
resetPassword: (data) =>
request({
url: '/system/user/profile/updatePwd',
method: 'PUT',
header: {
isEncrypt: 'true', // 修复:使用字符串 'true' 而不是布尔值 true
repeatSubmit: false,
},
data: {
oldPassword: data.oldPassword,
newPassword: data.newPassword,
},
}),
// 更新用户头像
// updateUserAvatar: (data) =>
// request({
// url: '/system/user/profile/updateAvatar',
// method: 'PUT',
// data: {
// avatar: data.avatar,
// },
// }),
};
// 便捷请求方法封装
+8
View File
@@ -294,6 +294,14 @@
"navigationBarBackgroundColor": "#1890ff",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/user/resetPasswd",
"style": {
"navigationBarTitleText": "修改密码",
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
+38 -4
View File
@@ -460,7 +460,7 @@
</Card> -->
<!-- 系统管理 -->
<!-- <Card v-if="hasSystemServices">
<Card>
<template #header>
<view class="section-header">
<u-icon name="setting" size="18" color="#f5222d" />
@@ -468,7 +468,27 @@
</view>
</template>
<GridLayout :columns="3" gap="16px">
<ServiceItem
<ServiceItem title="修改密码" @click="gotoResetPassword">
<template #icon>
<image
src="/static/00118.png"
mode="aspectFit"
style="width: 24px; height: 24px"
/>
</template>
</ServiceItem>
<!-- <ServiceItem title="修改头像" @click="gotoChangeAvatar">
<template #icon>
<image
src="/static/password.png"
mode="aspectFit"
style="width: 24px; height: 24px"
/>
</template>
</ServiceItem> -->
<!-- <ServiceItem
v-if="hasPermi(['system:mobileuser:list'])"
title="用户管理"
@click="handleServiceClick"
@@ -530,9 +550,9 @@
<template #icon>
<u-icon name="wifi" size="24" />
</template>
</ServiceItem>
</ServiceItem> -->
</GridLayout>
</Card> -->
</Card>
<!-- 工作流 -->
<Card v-if="hasWorkflowServices">
@@ -946,6 +966,20 @@ export default {
});
},
// 跳转到修改密码页面
gotoResetPassword() {
uni.navigateTo({
url: '/pages/user/resetPasswd',
});
},
// 跳转到修改头像页面
gotoChangeAvatar() {
uni.navigateTo({
url: '/pages/user/changeaAvatar',
});
},
// 图片加载成功处理
handleImageLoad(event) {
console.log('✅ 轮播图片加载成功:', event.detail);
+128 -79
View File
@@ -11,7 +11,9 @@
></u-avatar>
</view>
<view class="info-section">
<view class="user-name">{{ userInfo.nickName || userInfo.userName || '学生' }}</view>
<view class="user-name">
{{ userInfo.nickName || userInfo.userName || '学生' }}
</view>
<view class="user-detail">学号:{{ userInfo.userName || '暂无' }}</view>
<view class="user-detail">部门:{{ userInfo.deptName || '暂无' }}</view>
</view>
@@ -35,11 +37,22 @@
</view>
<view class="info-row">
<text class="label">实习期间:</text>
<text class="value">{{ formatDateRange(internshipInfo.internshipStartDate, internshipInfo.internshipEndDate) }}</text>
<text class="value">
{{
formatDateRange(
internshipInfo.internshipStartDate,
internshipInfo.internshipEndDate,
)
}}
</text>
</view>
<view class="info-row">
<text class="label">状态:</text>
<u-tag :text="getStatusText(internshipInfo.internshipStatus)" :type="getStatusType(internshipInfo.internshipStatus)" size="mini"></u-tag>
<u-tag
:text="getStatusText(internshipInfo.internshipStatus)"
:type="getStatusType(internshipInfo.internshipStatus)"
size="mini"
></u-tag>
</view>
</view>
</view>
@@ -78,7 +91,7 @@
@click="handleCheckin"
:custom-style="checkinButtonStyle"
>
<u-icon name="map" size="20" color="#fff" style="margin-right: 8px;"></u-icon>
<u-icon name="map" size="20" color="#fff" style="margin-right: 8px"></u-icon>
{{ checkinLoading ? '签到中...' : '立即签到' }}
</u-button>
@@ -93,7 +106,6 @@
<view class="location-card">
<view class="card-header">
<view class="header-left">
<text class="card-title">签到位置</text>
</view>
</view>
@@ -120,15 +132,24 @@
<!-- 坐标信息 -->
<view class="location-coords-container">
<text class="location-coords">
经纬度:{{ currentLocation.latitude.toFixed(6) }},{{ currentLocation.longitude.toFixed(6) }}
经纬度:{{ currentLocation.latitude.toFixed(6) }},{{
currentLocation.longitude.toFixed(6)
}}
</text>
</view>
<!-- 精度信息 -->
<view class="accuracy-section" v-if="currentLocation.accuracy">
<view class="accuracy-info">
<u-icon name="target" size="14" color="#67c23a" class="accuracy-icon"></u-icon>
<text class="accuracy-text">定位精度 {{ currentLocation.accuracy.toFixed(0) }}米</text>
<u-icon
name="target"
size="14"
color="#67c23a"
class="accuracy-icon"
></u-icon>
<text class="accuracy-text">
定位精度 {{ currentLocation.accuracy.toFixed(0) }}米
</text>
<u-tag
:text="getAccuracyStatus(currentLocation.accuracy).text"
:type="getAccuracyStatus(currentLocation.accuracy).type"
@@ -173,7 +194,12 @@
<view class="error-content">
<view class="error-header">
<u-icon name="warning-circle" size="28" color="#e6a23c" class="error-icon"></u-icon>
<u-icon
name="warning-circle"
size="28"
color="#e6a23c"
class="error-icon"
></u-icon>
<text class="error-title">无法获取位置信息</text>
</view>
@@ -183,15 +209,30 @@
<text class="tips-title">签到需要位置信息,请确保:</text>
<view class="tips-list">
<view class="tip-item">
<u-icon name="checkmark-circle" size="14" color="#67c23a" class="tip-icon"></u-icon>
<u-icon
name="checkmark-circle"
size="14"
color="#67c23a"
class="tip-icon"
></u-icon>
<text class="tip-text">已开启位置权限</text>
</view>
<view class="tip-item">
<u-icon name="checkmark-circle" size="14" color="#67c23a" class="tip-icon"></u-icon>
<u-icon
name="checkmark-circle"
size="14"
color="#67c23a"
class="tip-icon"
></u-icon>
<text class="tip-text">GPS/网络定位已开启</text>
</view>
<view class="tip-item">
<u-icon name="checkmark-circle" size="14" color="#67c23a" class="tip-icon"></u-icon>
<u-icon
name="checkmark-circle"
size="14"
color="#67c23a"
class="tip-icon"
></u-icon>
<text class="tip-text">网络连接正常</text>
</view>
</view>
@@ -226,7 +267,7 @@
</view>
<!-- 最近签到记录 -->
<view class="records-card" style="display: none;">
<view class="records-card" style="display: none">
<view class="card-header">
<u-icon name="list" size="18" color="#909399"></u-icon>
<text class="card-title">最近签到</text>
@@ -259,8 +300,18 @@
</template>
<script>
import { getInternshipInfo, getStudentInternshipInfo, checkIn, getDailyList, getStatistics } from '@/api/internship.js';
import { getCurrentLocation, checkLocationPermission, openLocationSettings } from '@/utils/location.js';
import {
getInternshipInfo,
getStudentInternshipInfo,
checkIn,
getDailyList,
getStatistics,
} from '@/api/internship.js';
import {
getCurrentLocation,
checkLocationPermission,
openLocationSettings,
} from '@/utils/location.js';
import { userManager } from '@/utils/user.js';
import weChatSDK from '@/utils/wechat.js';
import { request } from '@/common/request.js';
@@ -295,7 +346,7 @@ export default {
totalDays: 0,
checkedDays: 0,
missedDays: 0,
exceptionDays: 0
exceptionDays: 0,
},
// 签到记录
@@ -312,8 +363,8 @@ export default {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none',
fontSize: '16px',
fontWeight: 'bold'
}
fontWeight: 'bold',
},
};
},
@@ -386,7 +437,7 @@ export default {
if (!this.userInfo.userId) {
uni.showToast({
title: '请先登录',
icon: 'none'
icon: 'none',
});
return;
}
@@ -404,19 +455,18 @@ export default {
this.loadInternshipInfo(),
this.loadTodayRecord(),
this.loadRecentRecords(),
this.loadStatistics()
this.loadStatistics(),
]);
// 初始化位置信息(不阻塞页面加载)
this.initLocation().catch(error => {
this.initLocation().catch((error) => {
console.error('位置信息初始化失败:', error);
});
} catch (error) {
console.error('初始化页面失败:', error);
uni.showToast({
title: '数据加载失败',
icon: 'none'
icon: 'none',
});
}
},
@@ -468,7 +518,10 @@ export default {
if (response.code === 200) {
this.internshipInfo = response.data;
console.log('✅ 实习信息获取成功:', JSON.stringify(this.internshipInfo, null, 2));
console.log(
'✅ 实习信息获取成功:',
JSON.stringify(this.internshipInfo, null, 2),
);
// 详细字段检查
if (this.internshipInfo) {
@@ -510,7 +563,7 @@ export default {
uni.showToast({
title: message,
icon: 'none',
duration: 3000
duration: 3000,
});
},
@@ -524,7 +577,7 @@ export default {
studentId: this.userInfo.userId,
checkDate: this.formatDate(today),
pageNum: 1,
pageSize: 1
pageSize: 1,
};
const response = await getDailyList(params);
@@ -551,7 +604,7 @@ export default {
const params = {
studentId: this.userInfo.userId,
pageNum: 1,
pageSize: 5
pageSize: 5,
};
const response = await getDailyList(params);
@@ -572,7 +625,7 @@ export default {
const params = {
studentId: this.userInfo.userId,
year: now.getFullYear(),
month: now.getMonth() + 1
month: now.getMonth() + 1,
};
const response = await getStatistics(params);
@@ -684,7 +737,6 @@ export default {
// 优先级4:uni-app通用定位(最后的回退方案)
await this.handleFallbackLocation();
} catch (error) {
console.error('智能定位流程出错:', error);
this.updateLocationError(error.message || '定位失败');
@@ -701,7 +753,7 @@ export default {
// 用户选择重试
this.handleGetLocation();
}
}
},
});
} finally {
this.locationLoading = false;
@@ -730,7 +782,9 @@ export default {
latitude: parseFloat(result.latitude),
longitude: parseFloat(result.longitude),
accuracy: result.accuracy || 50,
address: `${parseFloat(result.latitude).toFixed(6)}, ${parseFloat(result.longitude).toFixed(6)}`
address: `${parseFloat(result.latitude).toFixed(6)}, ${parseFloat(
result.longitude,
).toFixed(6)}`,
};
this.updateLocationSuccess();
@@ -793,7 +847,7 @@ export default {
latitude: latitude,
longitude: longitude,
accuracy: accuracy || 100,
address: `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`
address: `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`,
};
this.updateLocationSuccess();
@@ -865,7 +919,9 @@ export default {
latitude: res.latitude,
longitude: res.longitude,
accuracy: res.accuracy || 100,
address: res.address || `${res.latitude.toFixed(6)}, ${res.longitude.toFixed(6)}`
address:
res.address ||
`${res.latitude.toFixed(6)}, ${res.longitude.toFixed(6)}`,
};
this.updateLocationSuccess();
@@ -873,7 +929,7 @@ export default {
uni.showToast({
title: '定位成功',
icon: 'success',
duration: 2000
duration: 2000,
});
resolve(true);
},
@@ -893,7 +949,7 @@ export default {
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
duration: 3000,
});
resolve(false);
},
@@ -901,10 +957,6 @@ export default {
});
},
/**
* 更新定位成功状态
*/
@@ -935,8 +987,6 @@ export default {
this.handleGetLocation();
},
/**
* 打开位置信息在地图中查看(参考请假页面实现)
*/
@@ -1029,7 +1079,7 @@ export default {
// 首先尝试使用uni.openLocation(原生地图)
if (typeof uni.openLocation === 'function') {
uni.showLoading({
title: '打开地图中...'
title: '打开地图中...',
});
uni.openLocation({
@@ -1046,7 +1096,7 @@ export default {
console.error('uni.openLocation打开地图失败:', error);
uni.hideLoading();
this.showCopyCoordinatesOption(latitude, longitude);
}
},
});
} else {
// 如果uni.openLocation不可用,显示复制坐标选项
@@ -1058,7 +1108,8 @@ export default {
showCopyCoordinatesOption(latitude, longitude) {
uni.showModal({
title: '无法打开地图',
content: '当前环境不支持地图功能,是否复制坐标到剪贴板?\n您可以在其他地图应用中粘贴坐标查看位置。',
content:
'当前环境不支持地图功能,是否复制坐标到剪贴板?\n您可以在其他地图应用中粘贴坐标查看位置。',
showCancel: true,
cancelText: '取消',
confirmText: '复制坐标',
@@ -1069,12 +1120,12 @@ export default {
success: () => {
uni.showToast({
title: '坐标已复制',
icon: 'success'
icon: 'success',
});
},
});
}
});
}
}
},
});
},
@@ -1090,7 +1141,7 @@ export default {
if (!this.internshipInfo) {
uni.showToast({
title: '暂无实习信息',
icon: 'none'
icon: 'none',
});
return;
}
@@ -1105,7 +1156,7 @@ export default {
if (res.confirm) {
this.handleGetLocation();
}
}
},
});
return;
}
@@ -1115,11 +1166,13 @@ export default {
const confirmResult = await new Promise((resolve) => {
uni.showModal({
title: '定位精度提醒',
content: `当前定位精度较低(${this.currentLocation.accuracy.toFixed(0)}米),可能影响签到准确性,是否继续签到?`,
content: `当前定位精度较低(${this.currentLocation.accuracy.toFixed(
0,
)}米),可能影响签到准确性,是否继续签到?`,
confirmText: '继续签到',
cancelText: '重新定位',
success: (res) => resolve(res.confirm),
fail: () => resolve(false)
fail: () => resolve(false),
});
});
@@ -1147,14 +1200,14 @@ export default {
dailySummary: '移动端签到打卡', // 后端映射为 workContent
status: '1', // 正常状态
isException: '0' // 正常,无异常
isException: '0', // 正常,无异常
};
console.log('🔍 签到前检查:', {
internshipInfo: this.internshipInfo,
userInfo: this.userInfo,
currentLocation: this.currentLocation,
canCheckin: this.canCheckin
canCheckin: this.canCheckin,
});
console.log('📤 发送签到数据:', JSON.stringify(checkinData, null, 2));
@@ -1163,27 +1216,25 @@ export default {
if (response.code === 200) {
uni.showToast({
title: '签到成功',
icon: 'success'
icon: 'success',
});
// 刷新今日记录
await this.loadTodayRecord();
await this.loadRecentRecords();
await this.loadStatistics();
} else {
throw new Error(response.msg || '签到失败');
}
} catch (error) {
console.error('❌ 签到详细错误:', {
message: error.message,
response: error.response,
stack: error.stack
stack: error.stack,
});
uni.showToast({
title: error.message || '签到失败',
icon: 'none'
icon: 'none',
});
} finally {
this.checkinLoading = false;
@@ -1197,7 +1248,7 @@ export default {
// 可以跳转到详细记录页面
uni.showToast({
title: '功能开发中',
icon: 'none'
icon: 'none',
});
},
@@ -1226,10 +1277,10 @@ export default {
*/
getStatusText(status) {
const statusMap = {
'1': '进行中',
'2': '已完成',
'3': '已暂停',
'4': '已终止'
1: '进行中',
2: '已完成',
3: '已暂停',
4: '已终止',
};
return statusMap[status] || '未实习';
},
@@ -1239,10 +1290,10 @@ export default {
*/
getStatusType(status) {
const typeMap = {
'1': 'success', // 进行中
'2': 'info', // 已完成
'3': 'warning', // 已暂停
'4': 'error' // 已终止
1: 'success', // 进行中
2: 'info', // 已完成
3: 'warning', // 已暂停
4: 'error', // 已终止
};
return typeMap[status] || 'default';
},
@@ -1265,15 +1316,14 @@ export default {
*/
async loadUserInfo() {
try {
const userInfo = userManager.getUserInfo();
const userInfo = userManager.getCurrentUser();
if (userInfo) {
this.userInfo = userInfo;
console.log('用户信息加载成功:', userInfo);
} else {
console.warn('未获取到用户信息');
// 尝试重新获取
await userManager.refreshUserInfo();
this.userInfo = userManager.getUserInfo() || {};
// 用户信息为空,使用默认值
this.userInfo = {};
}
} catch (error) {
console.error('加载用户信息失败:', error);
@@ -1327,11 +1377,10 @@ export default {
// 检查今日签到状态
await this.checkTodayCheckInStatus();
} catch (error) {
console.error('数据刷新失败:', error);
}
}
},
},
/**
@@ -1361,25 +1410,25 @@ export default {
// this.checkIsWechatBrowser() 在需要时调用
// 获取用户信息(不阻塞)
this.loadUserInfo().catch(error => {
this.loadUserInfo().catch((error) => {
console.error('用户信息加载失败:', error);
});
// 获取实习信息(不阻塞)
this.loadInternshipInfo().catch(error => {
this.loadInternshipInfo().catch((error) => {
console.error('实习信息加载失败:', error);
});
// 初始化位置权限检查(不阻塞页面显示)
this.initLocation().catch(error => {
this.initLocation().catch((error) => {
console.error('位置信息初始化失败:', error);
});
// 检查今日签到状态(不阻塞)
this.checkTodayCheckInStatus().catch(error => {
this.checkTodayCheckInStatus().catch((error) => {
console.error('签到状态检查失败:', error);
});
}
},
};
</script>
+932
View File
@@ -0,0 +1,932 @@
<template>
<view class="page-container">
<!-- 页面头部 -->
<u-navbar :title="pageTitle" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
<view slot="left" @click="goBack">
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
</view>
</u-navbar>
<!-- 加载状态 -->
<u-loading-page :loading="loading" loading-text="加载中..."></u-loading-page>
<!-- 主要内容 -->
<view v-if="!loading" class="main-content">
<view class="avatar-container">
<!-- 当前头像预览 -->
<view class="current-avatar-section">
<text class="section-title">当前头像</text>
<view class="avatar-preview">
<image
:src="currentAvatar"
mode="aspectFill"
class="avatar-image"
@error="handleAvatarError"
/>
</view>
</view>
<!-- 新头像预览区域 -->
<view class="new-avatar-section">
<text class="section-title">新头像预览</text>
<!-- 加载状态 -->
<view v-if="avatarLoading || wechatImageLoading" class="avatar-loading">
<u-loading-icon mode="circle" size="32"></u-loading-icon>
<text class="loading-text">
{{ wechatImageLoading ? '正在处理图片...' : '加载中...' }}
</text>
<text v-if="uploadProgress > 0" class="progress-text">
上传进度: {{ uploadProgress }}%
</text>
</view>
<!-- 已有新头像 - 点击可预览或重新选择 -->
<view v-else-if="newAvatar" class="avatar-wrapper" @click="handleAvatarClick">
<image
:src="newAvatar"
mode="aspectFill"
class="avatar-image"
@error="handleAvatarError"
/>
<!-- 头像操作遮罩 -->
<view class="avatar-overlay">
<view class="overlay-content">
<u-icon
name="camera-fill"
color="rgba(255,255,255,0.9)"
size="24"
></u-icon>
<text class="overlay-text">点击更换头像</text>
</view>
</view>
<!-- 头像角标 -->
<view class="avatar-badge">
<u-icon name="checkmark-circle-fill" color="#52c41a" size="20"></u-icon>
</view>
</view>
<!-- 空状态 - 点击选择头像 -->
<view v-else class="avatar-placeholder" @click="handlePhotoUpload">
<text class="placeholder-text">选择新头像</text>
<text class="placeholder-note">点击选择图片</text>
</view>
</view>
<!-- 快捷操作按钮 -->
<view class="avatar-quick-actions">
<view class="placeholder-tips">
<text class="tip-item">
• 建议尺寸:200x200像素 • 支持格式:JPG、PNG • 文件大小:不超过10MB
</text>
</view>
</view>
<!-- 提交按钮 -->
<view v-if="newAvatar" class="submit-section">
<button
class="submit-btn"
:class="{ 'btn-loading': isSubmitting }"
:disabled="isSubmitting"
@click="handleSubmit"
>
<u-icon v-if="isSubmitting" name="loading" size="16" color="#ffffff" />
<text class="btn-text">{{ isSubmitting ? '上传中...' : '确认修改' }}</text>
</button>
</view>
<!-- 微信环境提示 -->
<view v-if="!isWechatBrowser" class="wechat-tip">
<u-icon name="info-circle" size="16" color="#fa8c16" />
<text class="tip-text">建议在微信中使用以获得最佳体验</text>
</view>
</view>
</view>
</view>
</template>
<script>
import { api, request } from '@/common/request';
import { userManager } from '@/utils/user';
import { REQUEST_CONFIG } from '@/common/config';
import weChatSDK from '@/utils/wechat.js';
export default {
name: 'ChangeAvatar',
data() {
return {
loading: false,
avatarLoading: false,
currentAvatar: '',
newAvatar: '',
isSubmitting: false,
pageTitle: '修改头像',
// 微信SDK相关
wechatSDK: weChatSDK,
isWechatBrowser: false,
wechatInitializing: false,
wechatImageLoading: false,
uploadProgress: 0,
};
},
onLoad() {
this.loadCurrentAvatar();
this.checkWechatEnvironment();
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
},
// 加载当前头像
loadCurrentAvatar() {
const userInfo = userManager.getCurrentUser();
this.currentAvatar = userInfo?.avatar || '/static/111.png';
},
// 检查微信环境并自动初始化SDK
async checkWechatEnvironment() {
this.isWechatBrowser = this.wechatSDK.isWeChatBrowser();
if (this.isWechatBrowser) {
console.log('检测到微信环境,自动初始化微信SDK...');
await this.initWechatSDK();
} else {
console.log('非微信环境,使用标准图片选择功能');
}
},
// 初始化微信SDK
async initWechatSDK() {
if (this.wechatInitializing) return;
this.wechatInitializing = true;
console.log('开始初始化微信SDK...');
try {
// 检查微信环境
if (!this.isWechatBrowser) {
throw new Error('当前不在微信浏览器环境中');
}
// 使用微信SDK工具类初始化
const configData = await this.wechatSDK.init({
debug: false,
});
console.log('✅ 微信SDK初始化成功!', configData);
// 验证图片相关API可用性
try {
const checkResult = await this.wechatSDK.checkJsApi([
'chooseImage',
'previewImage',
'uploadImage',
'downloadImage',
]);
console.log('图片API可用性检查完成:', checkResult);
} catch (checkError) {
console.warn('API可用性检查失败:', checkError.message);
}
} catch (error) {
console.error('微信SDK初始化失败:', error);
uni.showToast({
title: '微信SDK初始化失败',
icon: 'none',
duration: 2000,
});
} finally {
this.wechatInitializing = false;
}
},
// === 头像处理相关方法 ===
// 头像上传处理(主要入口)
handlePhotoUpload() {
if (this.isWechatBrowser) {
if (this.wechatSDK.isInitialized) {
this.chooseWechatImage();
} else {
this.showWechatInitDialog();
}
} else {
this.chooseImage();
}
},
// 头像点击处理(已有新头像时)
handleAvatarClick() {
uni.showActionSheet({
itemList: ['预览头像', '更换头像', '删除头像'],
success: (res) => {
switch (res.tapIndex) {
case 0:
this.handleAvatarPreview();
break;
case 1:
this.handleAvatarReselect();
break;
case 2:
this.handleAvatarDelete();
break;
}
},
});
},
// 预览头像
handleAvatarPreview() {
if (this.newAvatar) {
uni.previewImage({
urls: [this.newAvatar],
current: this.newAvatar,
success: () => {
console.log('头像预览打开成功');
},
fail: (error) => {
console.error('预览头像失败:', error);
uni.showToast({
title: '预览失败',
icon: 'none',
});
},
});
}
},
// 重新选择头像
handleAvatarReselect() {
uni.showModal({
title: '确认操作',
content: '确定要重新选择头像吗?当前头像将被替换。',
success: (res) => {
if (res.confirm) {
this.handlePhotoUpload();
}
},
});
},
// 删除头像
handleAvatarDelete() {
uni.showModal({
title: '确认删除',
content: '确定要删除当前头像吗?删除后无法恢复。',
confirmColor: '#ff5722',
success: (res) => {
if (res.confirm) {
this.clearAvatarData();
uni.showToast({
title: '头像已删除',
icon: 'success',
});
}
},
});
},
// 清空头像数据
clearAvatarData() {
this.newAvatar = '';
this.uploadProgress = 0;
},
// 显示微信初始化对话框
showWechatInitDialog() {
uni.showModal({
title: '提示',
content: '微信SDK未初始化,是否立即初始化?',
success: (res) => {
if (res.confirm) {
this.initWechatSDK();
}
},
});
},
// 显示非微信环境提示
showNonWechatTip() {
uni.showModal({
title: '环境提示',
content:
'建议在微信中使用此功能以获得最佳体验。\n\n在微信中打开此页面可以:\n• 直接选择相册图片\n• 拍照上传\n• 图片预览和编辑',
showCancel: false,
confirmText: '我知道了',
});
},
// 选择微信图片
async chooseWechatImage() {
if (!this.wechatSDK.isInitialized) {
console.log('请先初始化微信SDK');
await this.initWechatSDK();
return;
}
this.wechatImageLoading = true;
this.uploadProgress = 0;
console.log('正在打开微信图片选择器...');
try {
const res = await this.wechatSDK.chooseImage({
count: 1, // 头像只选择一张
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
});
console.log('✅ 微信图片选择成功,共', res.localIds.length, '张');
// 将选择的图片设置为头像预览
if (res.localIds.length > 0) {
this.newAvatar = res.localIds[0];
console.log('📷 已设置头像预览');
// 显示上传提示
uni.showToast({
title: '正在上传头像...',
icon: 'loading',
duration: 2000,
});
// 自动上传图片到微信服务器并保存到OSS
await this.uploadWechatImageToOSS(res.localIds[0]);
// 上传成功提示
uni.showToast({
title: '头像上传成功!',
icon: 'success',
duration: 1500,
});
}
} catch (error) {
console.error('选择微信图片失败:', error);
// 显示错误提示
uni.showModal({
title: '选择图片失败',
content: error.message || '请检查微信环境或网络连接',
showCancel: false,
confirmText: '我知道了',
});
// 重置头像预览
this.newAvatar = '';
} finally {
this.wechatImageLoading = false;
this.uploadProgress = 0;
}
},
// 上传微信图片到OSS
async uploadWechatImageToOSS(localId) {
try {
console.log('📤 开始上传微信图片,localId:', localId);
// 步骤1:上传到微信服务器(20%进度)
this.uploadProgress = 10;
console.log('📤 正在上传到微信服务器...');
const wxUploadRes = await new Promise((resolve, reject) => {
wx.uploadImage({
localId: localId,
isShowProgressTips: 0, // 关闭微信自带的进度提示
success: resolve,
fail: reject,
});
});
const mediaId = wxUploadRes.serverId;
this.uploadProgress = 30;
console.log('✅ 微信服务器上传成功,mediaId:', mediaId);
// 步骤2:保存到OSS(80%进度)
this.uploadProgress = 50;
console.log('💾 正在保存到云端存储...');
const ossResponse = await request({
url: '/system/wechat/downloadToOss',
method: 'GET',
params: {
mediaId: mediaId,
},
timeout: 30000, // 30秒超时
});
if (ossResponse.code === 200) {
const ossResult = ossResponse.data;
this.uploadProgress = 90;
console.log('🎉 头像保存成功!', ossResult);
// 更新新头像URL
this.newAvatar = ossResult.url;
// 上传进度完成
this.uploadProgress = 100;
return ossResult;
} else {
throw new Error(ossResponse.msg || '保存头像到云端存储失败');
}
} catch (error) {
console.error('上传微信图片失败:', error);
// 上传失败时重置进度
this.uploadProgress = 0;
// 显示详细错误信息
let errorMsg = '头像上传失败,请重试';
if (error.message.includes('timeout')) {
errorMsg = '上传超时,请检查网络连接后重试';
} else if (error.message.includes('mediaId')) {
errorMsg = '微信服务器上传失败,请重试';
} else if (error.message.includes('OSS')) {
errorMsg = '保存到云端存储失败,请重试';
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 2000,
});
throw error;
}
},
// 选择图片(非微信环境)
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album'],
success: (res) => {
this.handleImageSelected(res.tempFilePaths[0]);
},
fail: (error) => {
console.error('选择图片失败:', error);
uni.showToast({
title: '选择图片失败',
icon: 'none',
duration: 2000,
});
},
});
},
// 拍照
takePhoto() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'],
success: (res) => {
this.handleImageSelected(res.tempFilePaths[0]);
},
fail: (error) => {
console.error('拍照失败:', error);
uni.showToast({
title: '拍照失败',
icon: 'none',
duration: 2000,
});
},
});
},
// 处理选中的图片
handleImageSelected(filePath) {
// 检查文件大小
uni.getFileInfo({
filePath: filePath,
success: (res) => {
const fileSizeInMB = res.size / (1024 * 1024);
if (fileSizeInMB > 10) {
uni.showToast({
title: '图片大小不能超过10MB',
icon: 'none',
duration: 2000,
});
return;
}
// 压缩图片
this.compressImage(filePath);
},
fail: (error) => {
console.error('获取文件信息失败:', error);
// 直接使用原图
this.newAvatar = filePath;
},
});
},
// 压缩图片
compressImage(filePath) {
// 检查是否支持压缩图片API
if (typeof uni.compressImage === 'function' && uni.compressImage) {
try {
uni.compressImage({
src: filePath,
quality: 80,
success: (res) => {
console.log('图片压缩成功:', res.tempFilePath);
this.newAvatar = res.tempFilePath;
},
fail: (error) => {
console.warn('压缩图片失败,使用原图:', error);
// 使用原图
this.newAvatar = filePath;
},
complete: () => {
// 确保有值
if (!this.newAvatar) {
this.newAvatar = filePath;
}
},
});
} catch (error) {
console.warn('compressImage API调用异常,使用原图:', error);
this.newAvatar = filePath;
}
} else {
console.log('当前环境不支持图片压缩API,使用原图');
// 不支持压缩时直接使用原图
this.newAvatar = filePath;
}
},
// 处理头像加载错误
handleAvatarError() {
this.currentAvatar = '/static/111.png';
},
// 提交头像
async handleSubmit() {
if (!this.newAvatar) {
uni.showToast({
title: '请先选择头像',
icon: 'none',
duration: 2000,
});
return;
}
this.isSubmitting = true;
try {
let avatarUrl = this.newAvatar;
// 如果不是微信上传的图片,需要先上传到服务器
if (!this.newAvatar.startsWith('http')) {
const uploadResult = await this.uploadAvatar(this.newAvatar);
avatarUrl = uploadResult.url;
}
// 更新用户信息
await api.updateUserAvatar({
avatar: avatarUrl,
});
// 更新本地用户信息
userManager.updateUserInfo({
avatar: avatarUrl,
});
uni.showToast({
title: '头像修改成功',
icon: 'success',
duration: 2000,
});
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack();
}, 2000);
} catch (error) {
console.error('修改头像失败:', error);
uni.showToast({
title: error.message || '修改头像失败',
icon: 'none',
duration: 2000,
});
} finally {
this.isSubmitting = false;
}
},
// 上传头像到服务器(非微信环境使用)
uploadAvatar(filePath) {
return new Promise((resolve, reject) => {
const token = uni.getStorageSync('access_token');
const clientid = uni.getStorageSync('clientid') || 'mobile';
// 构建完整URL
let fullUrl;
if (REQUEST_CONFIG.baseURL.startsWith('/')) {
// 开发环境,baseURL是'/api',需要使用完整的服务器URL
fullUrl = `http://localhost:8080${REQUEST_CONFIG.baseURL}/system/user/profile/avatar`;
} else {
// 生产环境,直接拼接
fullUrl = REQUEST_CONFIG.baseURL + '/system/user/profile/avatar';
}
uni.uploadFile({
url: fullUrl,
filePath: filePath,
name: 'avatarfile',
header: {
authorization: token ? `Bearer ${token}` : '',
clientid: clientid,
},
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.code === 200) {
resolve(data.data);
} else {
reject(new Error(data.msg || '上传失败'));
}
} catch (error) {
reject(new Error('解析响应失败'));
}
},
fail: (error) => {
reject(new Error('上传失败'));
},
});
});
},
},
};
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f6fa;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding: 20rpx;
padding-bottom: 40rpx;
}
.avatar-container {
background-color: #ffffff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.current-avatar-section,
.new-avatar-section {
padding: 30rpx;
border-bottom: 1px solid #f0f0f0;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #303133;
margin-bottom: 20rpx;
display: block;
}
.avatar-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #d9d9d9;
border-radius: 50%;
background-color: #fafafa;
margin: 0 auto;
}
.loading-text {
font-size: 24rpx;
margin-top: 16rpx;
color: #666;
}
.progress-text {
font-size: 20rpx;
color: #1890ff;
margin-top: 12rpx;
font-weight: 500;
}
.avatar-wrapper {
position: relative;
display: inline-block;
cursor: pointer;
margin: 0 auto;
display: block;
&:hover .avatar-overlay {
opacity: 1;
}
}
.avatar-image {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
border: 3rpx solid #ffffff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
display: block;
margin: 0 auto;
}
.avatar-overlay {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 200rpx;
height: 200rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 0.3s ease;
}
.overlay-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.overlay-text {
color: rgba(255, 255, 255, 0.9);
font-size: 20rpx;
margin-top: 8rpx;
}
.avatar-badge {
position: absolute;
top: -6rpx;
right: calc(50% - 100rpx + 6rpx);
width: 32rpx;
height: 32rpx;
background: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #d9d9d9;
border-radius: 50%;
background-color: #fafafa;
margin: 0 auto;
}
.placeholder-text {
font-size: 24rpx;
color: #8c939d;
margin-top: 16rpx;
}
.placeholder-note {
font-size: 20rpx;
color: #c0c4cc;
margin-top: 8rpx;
}
.placeholder-tips {
padding: 16rpx 20rpx;
background: rgba(24, 144, 255, 0.05);
border-radius: 8rpx;
border: 1px solid rgba(24, 144, 255, 0.1);
}
.tip-item {
display: block;
font-size: 20rpx;
color: #666;
margin-bottom: 6rpx;
&:last-child {
margin-bottom: 0;
}
}
.avatar-quick-actions {
padding: 20rpx 30rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-top: 1px solid #f0f0f0;
}
.submit-section {
padding: 30rpx;
border-top: 1px solid #f0f0f0;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #52c41a, #389e0d);
border: none;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
&:disabled {
background: #cccccc;
opacity: 0.6;
}
&.btn-loading {
opacity: 0.8;
}
}
.btn-text {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.wechat-tip {
margin: 20rpx 30rpx;
padding: 16rpx 20rpx;
background-color: #fffbe6;
border: 1px solid #ffe58f;
border-radius: 8rpx;
display: flex;
align-items: center;
gap: 16rpx;
font-size: 28rpx;
color: #faad14;
}
.tip-text {
font-size: 28rpx;
color: #faad14;
}
/* 响应式设计 */
@media (max-width: 375px) {
.main-content {
padding: 16rpx 12rpx;
}
.avatar-container {
padding: 20rpx 16rpx;
}
.avatar-image {
width: 160rpx;
height: 160rpx;
}
.avatar-wrapper {
width: 160rpx;
height: 160rpx;
}
.avatar-overlay {
width: 160rpx;
height: 160rpx;
}
.avatar-placeholder {
width: 160rpx;
height: 160rpx;
}
}
</style>
+418
View File
@@ -0,0 +1,418 @@
<template>
<view class="page-container">
<!-- 页面头部 -->
<!-- <view class="page-header">
<view class="header-content">
<view class="back-btn" @click="goBack">
<u-icon name="arrow-left" size="20" color="#ffffff" />
</view>
<text class="page-title">修改密码</text>
<view class="placeholder"></view>
</view>
</view> -->
<!-- 主要内容 -->
<view class="main-content">
<view class="form-container">
<!-- 旧密码 -->
<view class="form-item">
<view class="input-label">
<u-icon name="lock" size="16" color="#666666" />
<text class="label-text">旧密码</text>
</view>
<view class="input-wrapper">
<input
v-model="formData.oldPassword"
type="password"
placeholder="请输入旧密码"
class="input-field"
:class="{ 'input-error': errors.oldPassword }"
@blur="validateOldPassword"
/>
<view v-if="errors.oldPassword" class="error-message">
{{ errors.oldPassword }}
</view>
</view>
</view>
<!-- 新密码 -->
<view class="form-item">
<view class="input-label">
<u-icon name="lock-open" size="16" color="#666666" />
<text class="label-text">新密码</text>
</view>
<view class="input-wrapper">
<input
v-model="formData.newPassword"
type="password"
placeholder="请输入新密码"
class="input-field"
:class="{ 'input-error': errors.newPassword }"
@blur="validateNewPassword"
/>
<view v-if="errors.newPassword" class="error-message">
{{ errors.newPassword }}
</view>
</view>
</view>
<!-- 确认新密码 -->
<view class="form-item">
<view class="input-label">
<u-icon name="checkmark-circle" size="16" color="#666666" />
<text class="label-text">确认新密码</text>
</view>
<view class="input-wrapper">
<input
v-model="formData.confirmPassword"
type="password"
placeholder="请再次输入新密码"
class="input-field"
:class="{ 'input-error': errors.confirmPassword }"
@blur="validateConfirmPassword"
/>
<view v-if="errors.confirmPassword" class="error-message">
{{ errors.confirmPassword }}
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<button
class="submit-btn"
:class="{ 'btn-loading': isSubmitting }"
:disabled="isSubmitting || !isFormValid"
@click="handleSubmit"
>
<u-icon v-if="isSubmitting" name="loading" size="16" color="#ffffff" />
<text class="btn-text">{{ isSubmitting ? '提交中...' : '确认修改' }}</text>
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import { api } from '@/common/request';
export default {
name: 'ResetPassword',
data() {
return {
formData: {
oldPassword: '',
newPassword: '',
confirmPassword: '',
},
errors: {
oldPassword: '',
newPassword: '',
confirmPassword: '',
},
isSubmitting: false,
};
},
computed: {
isFormValid() {
return (
this.formData.oldPassword.trim() !== '' &&
this.formData.newPassword.trim() !== '' &&
this.formData.confirmPassword.trim() !== '' &&
!this.errors.oldPassword &&
!this.errors.newPassword &&
!this.errors.confirmPassword
);
},
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
},
// 验证旧密码
validateOldPassword() {
if (!this.formData.oldPassword.trim()) {
this.errors.oldPassword = '请输入旧密码';
return false;
}
this.errors.oldPassword = '';
return true;
},
// 验证新密码
validateNewPassword() {
const password = this.formData.newPassword.trim();
if (!password) {
this.errors.newPassword = '请输入新密码';
return false;
}
if (password.length < 6) {
this.errors.newPassword = '密码长度不能少于6位';
return false;
}
if (password.length > 20) {
this.errors.newPassword = '密码长度不能超过20位';
return false;
}
// 检查是否包含数字和字母
const hasNumber = /\d/.test(password);
const hasLetter = /[a-zA-Z]/.test(password);
if (!hasNumber || !hasLetter) {
this.errors.newPassword = '密码必须包含数字和字母';
return false;
}
this.errors.newPassword = '';
return true;
},
// 验证确认密码
validateConfirmPassword() {
if (!this.formData.confirmPassword.trim()) {
this.errors.confirmPassword = '请确认新密码';
return false;
}
if (this.formData.newPassword !== this.formData.confirmPassword) {
this.errors.confirmPassword = '两次输入的密码不一致';
return false;
}
this.errors.confirmPassword = '';
return true;
},
// 提交表单
async handleSubmit() {
// 验证所有字段
const isOldValid = this.validateOldPassword();
const isNewValid = this.validateNewPassword();
const isConfirmValid = this.validateConfirmPassword();
if (!isOldValid || !isNewValid || !isConfirmValid) {
return;
}
if (!this.isFormValid) {
uni.showToast({
title: '请完善表单信息',
icon: 'none',
duration: 2000,
});
return;
}
this.isSubmitting = true;
try {
// 调用修改密码API
await api.resetPassword({
oldPassword: this.formData.oldPassword,
newPassword: this.formData.newPassword,
});
uni.showToast({
title: '密码修改成功',
icon: 'success',
duration: 2000,
});
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack();
}, 2000);
} catch (error) {
console.error('修改密码失败:', error);
uni.showToast({
title: error.message || '修改密码失败',
icon: 'none',
duration: 2000,
});
} finally {
this.isSubmitting = false;
}
},
},
};
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 页面头部 */
.page-header {
background: linear-gradient(135deg, #007aff, #0051d9);
padding: calc(var(--status-bar-height) + 12px) 16px 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.back-btn {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
&:active {
background: rgba(255, 255, 255, 0.3);
transform: scale(0.95);
}
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #ffffff;
flex: 1;
text-align: center;
}
.placeholder {
width: 40px;
}
/* 主要内容 */
.main-content {
flex: 1;
padding: 20px 16px;
}
.form-container {
background-color: #ffffff;
border-radius: 12px;
padding: 24px 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.form-item {
margin-bottom: 24px;
}
.input-label {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.label-text {
font-size: 16px;
font-weight: 500;
color: #333333;
}
.input-wrapper {
position: relative;
}
.input-field {
width: 100%;
height: 48px;
padding: 0 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
background-color: #ffffff;
transition: all 0.3s ease;
box-sizing: border-box;
&:focus {
border-color: #007aff;
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
}
&.input-error {
border-color: #ff4d4f;
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.1);
}
&::placeholder {
color: #999999;
}
}
.error-message {
font-size: 14px;
color: #ff4d4f;
margin-top: 4px;
padding-left: 4px;
}
.submit-section {
margin-top: 32px;
}
.submit-btn {
width: 100%;
height: 48px;
background: linear-gradient(135deg, #007aff, #0051d9);
border: none;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
&:disabled {
background: #cccccc;
opacity: 0.6;
}
&.btn-loading {
opacity: 0.8;
}
}
.btn-text {
font-size: 16px;
font-weight: 600;
color: #ffffff;
}
/* 响应式设计 */
@media (max-width: 375px) {
.main-content {
padding: 16px 12px;
}
.form-container {
padding: 20px 16px;
}
.input-field {
height: 44px;
font-size: 15px;
}
.submit-btn {
height: 44px;
}
}
</style>
+262 -2
View File
@@ -581,8 +581,30 @@
</view>
</view>
<view class="info-item">
<text class="label">宿舍房间号</text>
<text class="value">{{ form.dormitoryRoom || '-' }}</text>
<text class="label">宿舍房间</text>
<view class="value">
<view v-if="roomDisplayName" class="room-info">
<u-tag
:text="roomDisplayName"
:type="getRoomTagType(form.dormitoryRoom)"
size="mini"
></u-tag>
</view>
<text v-else>-</text>
</view>
</view>
<view class="info-item">
<text class="label">床位</text>
<view class="value">
<view v-if="bedDisplayName" class="bed-info">
<u-tag
:text="bedDisplayName"
:type="getBedTagType(form.extField1)"
size="mini"
></u-tag>
</view>
<text v-else>-</text>
</view>
</view>
</view>
</view>
@@ -724,6 +746,7 @@ export default {
classTeacherName: '',
dormitoryBuilding: '',
dormitoryRoom: '',
extField1: '', // 新增床位信息字段
// 银行卡信息字段
bankCardNo: '',
@@ -751,6 +774,14 @@ export default {
// 行政区划和宿舍楼栋数据
regionOptions: [],
dormBuildingOptions: [],
// 房间和床位数据
dormRoomOptions: [],
dormBedOptions: [],
// 显示名称(异步加载)
roomDisplayName: '',
bedDisplayName: '',
};
},
@@ -770,6 +801,13 @@ export default {
console.error('区域和宿舍数据加载出错:', error);
}
try {
await this.loadDormRoomAndBedData();
console.log('✓ 房间和床位数据加载完成');
} catch (error) {
console.error('房间和床位数据加载出错:', error);
}
try {
await this.getPersonalInfoData();
console.log('✓ 个人信息加载完成');
@@ -1224,6 +1262,33 @@ export default {
});
},
// 加载房间和床位数据(改为按需加载)
async loadDormRoomAndBedData() {
console.log('房间和床位数据改为按需加载模式,不再预加载所有数据');
// 初始化空数组,数据将在需要时按需查询
this.dormRoomOptions = [];
this.dormBedOptions = [];
},
// 异步加载房间和床位显示名称
async loadRoomAndBedDisplayNames() {
try {
// 加载房间名称
if (this.form.dormitoryRoom) {
this.roomDisplayName = await this.getRoomName(this.form.dormitoryRoom);
console.log('房间名称加载完成:', this.roomDisplayName);
}
// 加载床位名称
if (this.form.extField1) {
this.bedDisplayName = await this.getBedName(this.form.extField1);
console.log('床位名称加载完成:', this.bedDisplayName);
}
} catch (error) {
console.error('加载房间和床位显示名称失败:', error);
}
},
// 获取学生个人信息
async getPersonalInfoData() {
try {
@@ -1257,6 +1322,17 @@ export default {
// 将API返回的数据映射到表单
Object.assign(this.form, data);
// 调试日志:检查宿舍相关字段
console.log('宿舍信息字段检查:', {
dormitoryBuilding: this.form.dormitoryBuilding,
dormitoryRoom: this.form.dormitoryRoom,
extField1: this.form.extField1,
extField1Type: typeof this.form.extField1,
});
// 异步加载房间和床位信息
await this.loadRoomAndBedDisplayNames();
} else {
uni.showToast({
title: response.msg || '获取个人信息失败',
@@ -1468,6 +1544,178 @@ export default {
return findBuilding(this.dormBuildingOptions, buildingId);
},
// 根据房间ID获取房间名称(含性别标识)
async getRoomName(roomId) {
// 防护性检查
if (!roomId) {
return '';
}
// 如果本地缓存中没有数据,则按需查询
if (!this.dormRoomOptions || this.dormRoomOptions.length === 0) {
try {
const response = await request({
url: `/xgss/dormRoom/${roomId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
const roomData = response.data;
// 缓存房间数据
if (!this.dormRoomOptions) {
this.dormRoomOptions = [];
}
this.dormRoomOptions.push(roomData);
// 返回房间名称
return roomData.roomName || roomData.name || `房间${roomId}`;
} else {
console.warn(`查询房间信息失败,房间ID: ${roomId}`);
return `房间${roomId}`;
}
} catch (error) {
console.error(`查询房间信息异常,房间ID: ${roomId}`, error);
return `房间${roomId}`;
}
}
// 从本地缓存中查找
const room = this.dormRoomOptions.find((item) => String(item.id) === String(roomId));
if (room) {
return room.roomName || room.name || `房间${roomId}`;
}
// 如果本地缓存中没有找到,则按需查询
try {
const response = await request({
url: `/xgss/dormRoom/${roomId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
const roomData = response.data;
// 缓存房间数据
this.dormRoomOptions.push(roomData);
// 返回房间名称
return roomData.roomName || roomData.name || `房间${roomId}`;
} else {
console.warn(`查询房间信息失败,房间ID: ${roomId}`);
return `房间${roomId}`;
}
} catch (error) {
console.error(`查询房间信息异常,房间ID: ${roomId}`, error);
return `房间${roomId}`;
}
},
// 根据房间ID获取房间标签类型
getRoomTagType(roomId) {
if (!roomId) {
return 'info'; // 默认类型
}
// 从本地缓存中查找
const room = this.dormRoomOptions.find((item) => String(item.id) === String(roomId));
if (room) {
// 根据房间类型设置标签类型
if (room.roomType === '1') {
// 男生房间
return 'warning';
} else if (room.roomType === '2') {
// 女生房间
return 'error';
}
}
return 'info'; // 默认类型
},
// 根据床位ID获取床位名称(含性别标识)
async getBedName(bedId) {
// 防护性检查
if (!bedId) {
return '';
}
// 如果本地缓存中没有数据,则按需查询
if (!this.dormBedOptions || this.dormBedOptions.length === 0) {
try {
const response = await request({
url: `/dormbed/dormBed/${bedId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
const bedData = response.data;
// 缓存床位数据
if (!this.dormBedOptions) {
this.dormBedOptions = [];
}
this.dormBedOptions.push(bedData);
// 返回床位名称
return bedData.bedName || bedData.name || `床位${bedId}`;
} else {
console.warn(`查询床位信息失败,床位ID: ${bedId}`);
return `床位${bedId}`;
}
} catch (error) {
console.error(`查询床位信息异常,床位ID: ${bedId}`, error);
return `床位${bedId}`;
}
}
// 从本地缓存中查找
const bed = this.dormBedOptions.find((item) => String(item.id) === String(bedId));
if (bed) {
return bed.bedName || bed.name || `床位${bedId}`;
}
// 如果本地缓存中没有找到,则按需查询
try {
const response = await request({
url: `/dormbed/dormBed/${bedId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
const bedData = response.data;
// 缓存床位数据
this.dormBedOptions.push(bedData);
// 返回床位名称
return bedData.bedName || bedData.name || `床位${bedId}`;
} else {
console.warn(`查询床位信息失败,床位ID: ${bedId}`);
return `床位${bedId}`;
}
} catch (error) {
console.error(`查询床位信息异常,床位ID: ${bedId}`, error);
return `床位${bedId}`;
}
},
// 根据床位ID获取床位标签类型
getBedTagType(bedId) {
if (!bedId) {
return 'info'; // 默认类型
}
// 从本地缓存中查找
const bed = this.dormBedOptions.find((item) => String(item.id) === String(bedId));
if (bed) {
// 根据床位类型设置标签类型
if (bed.bedType === '1') {
// 男生床位
return 'warning';
} else if (bed.bedType === '2') {
// 女生床位
return 'error';
}
}
return 'info'; // 默认类型
},
},
};
</script>
@@ -1655,6 +1903,18 @@ export default {
gap: 8rpx;
}
.room-info {
display: flex;
align-items: center;
gap: 8rpx;
}
.bed-info {
display: flex;
align-items: center;
gap: 8rpx;
}
// uview组件样式调整
::v-deep .u-tag {
margin: 0;
@@ -747,9 +747,15 @@
</text>
</view>
<view class="info-item">
<text class="label">宿舍房间号:</text>
<text class="label">宿舍房间:</text>
<text class="value">
{{ studentInfo.dormitoryRoom || '-' }}
{{ dormRoomName || '-' }}
</text>
</view>
<view class="info-item">
<text class="label">床位:</text>
<text class="value">
{{ dormBedName || '-' }}
</text>
</view>
</view>
@@ -881,6 +887,10 @@ export default {
// 新增:行政区划和宿舍楼栋数据
regionOptions: [], // 行政区划选项
dormBuildingOptions: [], // 宿舍楼栋选项
// 宿舍房间和床位显示名称
dormRoomName: '',
dormBedName: '',
};
},
@@ -1293,6 +1303,42 @@ export default {
}
},
// 获取宿舍房间名称
async getDormRoomName(roomId) {
if (!roomId) return '-';
try {
const response = await request({
url: `/xgss/dormRoom/${roomId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
return response.data.roomName || '-';
}
return roomId; // 如果没有找到,返回原始ID
} catch (error) {
console.error('获取宿舍房间名称失败:', error);
return roomId;
}
},
// 获取床位名称
async getDormBedName(bedId) {
if (!bedId) return '-';
try {
const response = await request({
url: `/dormbed/dormBed/${bedId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
return response.data.bedName || '-';
}
return bedId; // 如果没有找到,返回原始ID
} catch (error) {
console.error('获取床位名称失败:', error);
return bedId;
}
},
// 根据行政区划代码获取完整路径(省/市/区)
getRegionFullPath(code) {
if (!code || !this.regionOptions) return code || '';
@@ -1336,6 +1382,21 @@ export default {
return findBuilding(this.dormBuildingOptions, buildingId) || buildingId;
},
// 设置宿舍房间和床位显示名称
async setDormDisplayNames() {
if (this.studentInfo.dormitoryRoom) {
this.dormRoomName = await this.getDormRoomName(this.studentInfo.dormitoryRoom);
} else {
this.dormRoomName = '-';
}
if (this.studentInfo.extField1) {
this.dormBedName = await this.getDormBedName(this.studentInfo.extField1);
} else {
this.dormBedName = '-';
}
},
// 加载数据
async loadData() {
if (!this.batchInfo || !this.batchInfo.batchId) {
@@ -1407,6 +1468,9 @@ export default {
if (this.studentInfo.photoPath) {
this.loadPhoto();
}
// 设置宿舍房间和床位显示名称
await this.setDormDisplayNames();
} else {
console.log('未获取到学生信息或数据为空');
}
+109 -12
View File
@@ -77,7 +77,7 @@
</view>
</view>
<!-- 快捷操作按钮 v-if="isWechatBrowser"-->
<!-- 快捷操作按钮 -->
<view class="photo-quick-actions">
<view class="placeholder-tips">
<text class="tip-item">• 建议尺寸:2寸证件照 • 支持格式:JPG、PNG • 文件大小:不超过10MB</text>
@@ -1086,20 +1086,21 @@
<u-picker
:columns="[trainingModeOptions]"
keyName="label"
:disabled="true"
:defaultIndex="trainingModeIndex"
:show="showTrainingModePicker"
:closeOnClickOverlay="true"
@confirm="onTrainingModeConfirm"
@cancel="onTrainingModeClose"
></u-picker>
<view class="picker-input" @click="showTrainingModePicker = true">
<view class="picker-input disabled">
<text class="picker-text">
{{
getDictLabel(trainingModeOptions, form.trainingType) ||
'请选择培养方式'
}}
</text>
<u-icon name="arrow-down" size="16" color="#c0c4cc"></u-icon>
<u-icon name="arrow-down" size="16" color="#e4e7ed"></u-icon>
</view>
</view>
</view>
@@ -1109,13 +1110,14 @@
<u-picker
:columns="[admissionCategoryOptions]"
keyName="label"
:disabled="true"
:defaultIndex="admissionCategoryIndex"
:show="showAdmissionCategoryPicker"
:closeOnClickOverlay="true"
@confirm="onAdmissionCategoryConfirm"
@cancel="onAdmissionCategoryClose"
></u-picker>
<view class="picker-input" @click="showAdmissionCategoryPicker = true">
<view class="picker-input disabled">
<text class="picker-text">
{{
getDictLabel(
@@ -1124,7 +1126,7 @@
) || '请选择录取类别'
}}
</text>
<u-icon name="arrow-down" size="16" color="#c0c4cc"></u-icon>
<u-icon name="arrow-down" size="16" color="#e4e7ed"></u-icon>
</view>
</view>
<view class="form-item-half">
@@ -1132,24 +1134,26 @@
<u-picker
:columns="[studentStatusOptions]"
keyName="label"
:disabled="true"
:defaultIndex="studentStatusIndex"
:show="showStudentStatusPicker"
:closeOnClickOverlay="true"
@confirm="onStudentStatusConfirm"
@cancel="onStudentStatusClose"
></u-picker>
<view class="picker-input" @click="showStudentStatusPicker = true">
<view class="picker-input disabled">
<text class="picker-text">
{{
getDictLabel(studentStatusOptions, form.studentStatus) ||
'请选择学籍状态'
}}
</text>
<u-icon name="arrow-down" size="16" color="#c0c4cc"></u-icon>
<u-icon name="arrow-down" size="16" color="#e4e7ed"></u-icon>
</view>
</view>
</view>
<view class="form-item">
<view class="form-row">
<view class="form-item-half">
<text class="form-label">是否住校</text>
<u-radio-group v-model="form.isBoarder">
<u-radio
@@ -1162,7 +1166,6 @@
</u-radio>
</u-radio-group>
</view>
<view class="form-row">
<view class="form-item-half">
<text class="form-label">宿舍楼栋</text>
<u-picker
@@ -1185,11 +1188,22 @@
<u-icon name="arrow-down" size="16" color="#e4e7ed"></u-icon>
</view>
</view>
</view>
<view class="form-row">
<view class="form-item-half">
<text class="form-label">宿舍房间号</text>
<text class="form-label">宿舍房间</text>
<u-input
v-model="form.dormitoryRoom"
placeholder="请输入宿舍房间号"
:value="dormRoomName"
placeholder="宿舍房间"
:disabled="true"
border-bottom
></u-input>
</view>
<view class="form-item-half">
<text class="form-label">床位</text>
<u-input
:value="dormBedName"
placeholder="床位"
:disabled="true"
border-bottom
></u-input>
@@ -1387,6 +1401,7 @@ export default {
classTeacherName: '',
dormitoryBuilding: '',
dormitoryRoom: '',
extField1: '',
// 银行卡信息字段
bankCardNo: '',
@@ -1453,6 +1468,14 @@ export default {
showStudentStatusPicker: false,
showDormBuildingPicker: false,
showSourceRegionPicker: false,
// 缓存
roomNameCache: {},
bedNameCache: {},
// 宿舍房间和床位显示名称
dormRoomName: '',
dormBedName: '',
};
},
@@ -2106,6 +2129,9 @@ export default {
// 同步picker索引
this.syncPickerIndexes();
// 设置宿舍房间和床位显示名称
await this.setDormDisplayNames();
} else {
uni.showToast({
title: response.msg || '获取个人信息失败',
@@ -3127,6 +3153,77 @@ export default {
this.debugMode = !this.debugMode;
this.addWechatLog(`调试模式已${this.debugMode ? '开启' : '关闭'}`, 'info');
},
// 获取宿舍房间名称
async getDormRoomName(roomId) {
if (!roomId) return '-';
// 检查缓存
if (this.roomNameCache && this.roomNameCache[roomId]) {
return this.roomNameCache[roomId];
}
try {
const response = await request({
url: `/xgss/dormRoom/${roomId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
const roomName = response.data.roomName || response.data.roomCode || String(roomId);
// 缓存结果
if (!this.roomNameCache) this.roomNameCache = {};
this.roomNameCache[roomId] = roomName;
return roomName;
}
return String(roomId); // 如果没有找到,返回原始ID
} catch (error) {
console.error('获取宿舍房间名称失败:', error);
return String(roomId);
}
},
// 获取床位名称
async getDormBedName(bedId) {
if (!bedId) return '-';
// 检查缓存
if (this.bedNameCache && this.bedNameCache[bedId]) {
return this.bedNameCache[bedId];
}
try {
const response = await request({
url: `/dormbed/dormBed/${bedId}`,
method: 'GET',
});
if (response.code === 200 && response.data) {
const bedName = response.data.bedName || response.data.bedCode || String(bedId);
// 缓存结果
if (!this.bedNameCache) this.bedNameCache = {};
this.bedNameCache[bedId] = bedName;
return bedName;
}
return String(bedId); // 如果没有找到,返回原始ID
} catch (error) {
console.error('获取床位名称失败:', error);
return String(bedId);
}
},
// 设置宿舍房间和床位显示名称
async setDormDisplayNames() {
if (this.form.dormitoryRoom) {
this.dormRoomName = await this.getDormRoomName(this.form.dormitoryRoom);
} else {
this.dormRoomName = '-';
}
if (this.form.extField1) {
this.dormBedName = await this.getDormBedName(this.form.extField1);
} else {
this.dormBedName = '-';
}
},
},
};
</script>
File renamed without changes.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="3182" height="3197">
<g transform="matrix(1.3333334 0 0 1.3333334 0 0)">
<image x="0" y="0" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAADG4AAAx9CAYAAACOo659AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAP+lSURBVHhe7L0HgGRFufbfk2d2lyw5Y7zfTd93w/9eA+xO6O7pPGl3Qa6RqCISJUqSpICgCCiSFAQURQwYAEEkqAQlSZacd2HjzHR+/+/z1qnuMz09y+Aq6T4/eLbq1KlTp845dapqz75vV0RedypSqZTkwgvOk+223Vq2324bufeeuywN+wghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQ8r+XaqBpVMvy46uulB222VLeue1W8tvf/lYqFeeHUK02PeJNAR03CCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBDypoGOG2sNHTcIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEENIcOm6sNXTcIIQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEJIc+i4sdbQcYMQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIc2h48ZaQ8cNQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQ0h44baw0dNwghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQ0hw6bqw1dNwghBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQkhz6Lix1tBxgxBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghzaHjxlpDxw1CCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhDSHjhtrDR03CCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBDSHDpurDV03CCEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCSHPouLHW0HGDEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCHNoePGWkPHDUIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGENIeOG2sNHTcIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEENIcOm6sNXTcIIQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEJIc+i4sdbQcYMQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIc2h48ZaQ8cNQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQ0h44baw0dNwghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQ0hw6bqw1dNwghBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQkhz6Lix1tBxgxBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghzaHjxlpDxw1CCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhDSHjhtrDR03CCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBDSHDpurDV03CCEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCSHPouLHW0HGDEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCHNoePGWkPHDUIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGENIeOG2sNHTcIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEENIcOm6sNXTcIIQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEJIc+i4sdbQcYMQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIc2h48ZaQ8cNQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQ0h44baw0dNwghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQ0hw6bqw1dNwghBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQkhz6Lix1tBxgxBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghzaHjxlpDxw1CCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhDSHjhtrDR03CCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBDSHDpurDV03CCEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCSHPouLHW0HGDEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCHNoePGWkPHDUIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGENIeOG2sNHTcIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEENIcOm6sNXTcIIQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEJIc+i4sdbQcYMQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIc2h48ZaQ8cNQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQ0h44baw0dNwghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQ0hw6bqw1dNwghBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQkhz6Lix1tBxgxBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghzaHjxlpDxw1CCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhDSHjhtrDR03CCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBDSHDpurDV03CCEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCSHPeho4bqOTfT1W9MZDfrpSLdrMu/Na58q5ttpF37rCd3H3nHZZWrZSm5KUoiqIoiqIoiqIoiqIoiqL+lloD/ut3WJWq+2br5cG+AMumf06RJuKnekrBtosD1MHFXHmN9Zsq/70YpbhtLRiFAwReAeEkyH4uSCO+Lv5jvftzzSCPOzPq79R4PPbNVBbSG/f5+1GRciC/XU/z39T9tdfzBvdLC0U9anUJ4uG0aQTJ/jr886hvV6VYrUzZdqrnK+DcuJmBXLvQnfXHVRfSw/LPLAD/eOL/AcVTLuP6Gph6mDH1eoNEBeVVy1pmqI5WT00rFYpuWysXvreNarbPnk6QjhJMOLeGniCXpeFe+XuJtHIQR5rHl2H1C66lEaRYmbrPl1+7puBYTVyjrFjE/SUpSPIgHt7253HP3l9PvQ34/f64sGr4BK0fqog6TL3GUAVfi/T+l+w9CNfDxWrvxquo/nw9zfL5q8W/ZTVKj7WL0jBQvcxmZVEURVFro3r/ql2ujiONc4XGuQQhhBBCCCGEEELefuDLsv+6PIUqHTfWqPCH61IxL+d94xzZfsst5T07bC/33fUnTeaHbYqiKIqiKIqiKIqiKIqiqL+r/BfuQN6gulHOKBwZ6obJpqmHT5GZUGtkmjQdptAI8adX+Jvx7BQCRTUByV7An9cbndu1NWZaA96A3ZfxGg51NByAcmDA7xV2yoCm3GsV0srVUmh//cyNdbBLW4PwLOpG+CKFJioG8vF6fnf9WgmHRq1AHwYKO05Y3O9HGOCj/lrM4SKIN4LUpnv8juBctXOUtE0h1HNXCkVXLraDPP6eYgVwCPc2rMb77e65b6f100Ief3+R1ug3gnvnQ+y3y4SAD2cgnBXgWlAVL7ue0LZLm1l27hAznR7paKdhhd+DsBrrCKyegWo3IpQRR9Yq9Rrlnkm9PuF99ec0s/x71WyfU73F4w2A88YUNR6PuNeUciiKoqi1Vbi/tTElwMcRYg5B5w1CCCGEEEIIIeTtTejz8lSqb1HHjXKpIGuj0qsonKdQmDSnjaKGl1x0oWy3lXPcuOuO20V3SH71KluRI3wsRVEURVEURVEURVEURVEU9TdSsVRTNRSHSqVGFaRYrqtUnhQpqvLjIoWJpqoWxqfJ8k8iPmHljFdLMlkpSaFUtHCirCG+D5dLut+psS6oH9JRZ0srFE1Nr3FGTdo1hFUuTaxRuN6qqoLrw7UXCjVVi3Wh/GbftevbOJcry8uX7ctvJtwzU5CnPLnahLSy3lcI+8JxyxfE/TZCKeXtWDyLSh6hK8fL6hPa9mle7jyT9p0fwnd+yH/zh+yag22E/p5gtW2k5fMTMjGx2vYhr6VpfSC/HS4PwjkR4px5rcOE3kevcYTlvIVQvloUPaO2q4KsLk7IpO7z2+O6Xda2hn+DKOL8oX+LsOcUpCOO0MetPoHcsy/W20DoXlX0nuMeh9O8rP3rdVe1TVWKeq0FfY5BvLbdRCjTP9uivku4HlwLhOv28UblQ/JpePfQBsPPMyzfRqHG98TL7fdt+tXl7631H/re4v3VF9/Cxv4H8uleyBvOD60pf1ha4WlptX5D42sqxz/fsMLve/i9n+maZ1KzYyiKoqiZhb6zoP2wjSOBswbijbyZDTIIIYQQQgghhBDy1/O2c9zYY/dPyuuh3VV77rGbabdPflxS8Zg5bkC7Ll4ku3/8Y7LP3ns1PZaiKIqiKIqiKIqiKIqiKIpae+212+5TtEdIu+/u5b7n7raHk48j3Hu3j8mnP+n18VB8Zu29m8uHY3fbY3f5hCocTpc73yf2RFhPR35cg6/fTApf79S0j2uZTrvv8XEtsy5sh4W8EOqMcC+9BlyHhZ/U+6Daa7e6Gs/nzxkuD+Heu3/iNcvOqeGn9J74sBZHHt2P8NOatpfeZ8jqingQ7qna4xMflc/suZvlw/EILR4c6+N+2+J7On1GnwWO2XOPT8gee+wme+2p7Ue19157TIn7fwPANkJ/X1y6lrW3pu/l7sseWu7ee+1m2kvL9/LbCJHXpyO+296flE9+yoUW3+sTtfjun9rNtu35ahxCG/qYnvcTWtbHNdxtN5W2W/+sLNQ0X09fbx+iznYdQTzclj+lz/QzeiyEd8Hin/i47PPJUBjsx/ZntZ3geHuuPlyD/HPfW+8VzoXzf1KvA9eC0Me9Pq7X6oXrDgtpeKd8m95r93q79sJ2o1w+lxey9hzcO3//wgrvC8v3Line truncated
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+1 -1
View File
@@ -107,7 +107,7 @@ export const userManager = {
return {
name: this.getDisplayName(),
avatar: user.avatar || '/static/logo-2.png',
avatar: user.avatar || '/static/logo.svg',
deptName: user.deptName || '未设置部门',
status: user.status,
loginDate: user.loginDate,
+9 -1
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';
/**
* 查询活动信息列表
+5
View File
@@ -478,6 +478,11 @@ export interface StglActivityQuery extends PageQuery {
*/
isFinished?: number;
/**
* 创建者ID
*/
createBy?: string | number;
/**
* 日期范围参数
*/
+11 -11
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
}
});
};
@@ -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
});
};
+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;
/**
* 状态
+11 -1
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';
/**
* 查询我参与的社团列表
+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'
}
});
}
+11
View File
@@ -11,6 +11,17 @@ export const listMenu = (query?: MenuQuery): AxiosPromise<MenuVO[]> => {
});
};
// 获取首页菜单数据(无权限限制,用于首页展示)
export const getHomeMenus = (): AxiosPromise<MenuVO[]> => {
return request({
url: '/system/menu/list',
method: 'get',
params: {
status: '0' // 只获取正常状态的菜单
}
});
};
// 查询菜单详细
export const getMenu = (menuId: string | number): AxiosPromise<MenuVO> => {
return request({
+25 -1
View File
@@ -52,10 +52,34 @@ export function getStudentPersonalInfo(userId?: number): AxiosPromise<any> {
});
}
/**
* 获取宿舍房间信息
* @param roomId 房间ID
*/
export function getDormRoomInfo(roomId: number): AxiosPromise<R<any>> {
return request({
url: `/xgss/dormRoom/${roomId}`,
method: 'get'
});
}
/**
* 获取宿舍床位信息
* @param bedId 床位ID
*/
export function getDormBedInfo(bedId: number): AxiosPromise<R<any>> {
return request({
url: `/dormbed/dormBed/${bedId}`,
method: 'get'
});
}
export default {
XgDeptTreeSelect,
deptTreeSelect,
listStudentRoster,
exportStudentRoster,
getStudentPersonalInfo
getStudentPersonalInfo,
getDormRoomInfo,
getDormBedInfo
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756114168869" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="37747" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M768 0H256C114.6112 0 0 114.6112 0 256v512c0 141.3376 114.6112 256 256 256h512c141.3376 0 256-114.6624 256-256V256c0-141.3888-114.6624-256-256-256zM447.6416 230.4h123.4688c15.1552 0 27.2384 8.9088 27.2384 25.5488 0.0256 16.6912-12.1088 25.6512-26.496 25.6512h-124.2624c-15.104 0-27.2384-8.96-27.2384-25.6512 0.0256-16.64 12.16-25.5488 27.2896-25.5488zM768 725.0688C768 774.3232 749.9008 793.6 701.3888 793.6H316.5952C268.1344 793.6 256 768.9984 256 725.0432v-405.248c0-40.9344 6.8096-63.7952 56.832-63.7952h62.2592c-0.6144 0-0.9984 2.0736-1.024 5.4272 0 25.3696 18.432 45.7728 41.4976 45.7728h188.8768c21.8624 0 40.2432-20.3776 40.2432-45.7728 0-3.3792-0.384-5.4272-0.9984-5.4272h61.4912c46.2336 0 62.8224 22.8864 62.8224 63.7952v405.2736z" fill="#6EE9DF" p-id="37748"></path><path d="M695.4752 665.6H328.5248a21.3248 21.3248 0 0 0-21.3248 21.3248v8.5504c0 11.776 9.5488 21.3248 21.3248 21.3248h366.9504c11.776 0 21.3248-9.5488 21.3248-21.3248v-8.5504a21.3248 21.3248 0 0 0-21.3248-21.3248zM672.8192 354.8928l-182.0672 224.6656-103.5264-93.6192a31.4368 31.4368 0 0 0-44.2368 2.0992 30.9248 30.9248 0 0 0 2.1248 43.904l118.7072 107.2896c8.0128 7.1936 18.4576 11.2384 29.1584 11.2384 1.0752 0 2.1504-0.1024 3.2512-0.2048 11.776-0.896 22.8608-6.656 30.464-15.9488l194.8928-240.4352a30.9504 30.9504 0 0 0-4.7872-43.776 31.488 31.488 0 0 0-43.9808 4.7872z" fill="#6EE9DF" p-id="37749"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756113838264" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1618" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M720.96 240.746667A85.333333 85.333333 0 0 1 725.333333 267.733333v482.197334a85.333333 85.333333 0 0 1-116.266666 79.530666l-186.133334-72.384a127.786667 127.786667 0 0 1-51.733333-37.12A85.013333 85.013333 0 0 1 341.333333 725.333333h-128a128 128 0 0 1-128-128v-192a128 128 0 0 1 128-128h128c11.669333 0 22.805333 2.346667 32.938667 6.592a127.445333 127.445333 0 0 1 54.592-35.776l184.149333-61.376a85.333333 85.333333 0 0 1 107.946667 53.973334z" fill="#FAB007" p-id="1619"></path><path d="M768 469.333333m42.666667 0l85.333333 0q42.666667 0 42.666667 42.666667l0 0q0 42.666667-42.666667 42.666667l-85.333333 0q-42.666667 0-42.666667-42.666667l0 0q0-42.666667 42.666667-42.666667Z" fill="#FEE789" p-id="1620"></path><path d="M758.099166 325.716249m36.950417-21.333333l73.900834-42.666667q36.950417-21.333333 58.283751 15.617084l0 0q21.333333 36.950417-15.617084 58.283751l-73.900835 42.666667q-36.950417 21.333333-58.28375-15.617084l0 0q-21.333333-36.950417 15.617084-58.283751Z" fill="#FEE789" p-id="1621"></path><path d="M758.098241 698.2872m36.950418 21.333333l73.900834 42.666667q36.950417 21.333333 58.283751-15.617084l0 0q21.333333-36.950417-15.617084-58.283751l-73.900835-42.666666q-36.950417-21.333333-58.28375 15.617083l0 0q-21.333333 36.950417 15.617084 58.283751Z" fill="#FEE789" p-id="1622"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756113949131" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M516.8128 427.4688a46.08 46.08 0 1 0-46.08-46.08 46.3872 46.3872 0 0 0 46.08 46.08z" fill="#FFA937" p-id="1214"></path><path d="M938.8544 830.2592h-58.5216V381.1328a60.8768 60.8768 0 0 0-60.8768-60.8768h-111.7696V272.384a36.9152 36.9152 0 0 0-12.032-27.2896l-152.5248-139.1616a37.0176 37.0176 0 0 0-49.5616 0L338.176 245.0944a36.864 36.864 0 0 0-12.288 27.4944v47.6672H214.1184a60.8768 60.8768 0 0 0-60.8768 60.8768v449.1264H92.16a30.1056 30.1056 0 0 0 0 60.16h846.6944a30.1056 30.1056 0 1 0 0-60.16z m-422.0416-555.52a106.4448 106.4448 0 1 1-106.4448 106.3936 106.496 106.496 0 0 1 106.4448-106.3936z m80.8448 525.4656v-142.2336a19.3536 19.3536 0 0 0-19.3536-19.3536h-122.88a19.4048 19.4048 0 0 0-19.3536 19.4048v142.1824a30.0544 30.0544 0 0 1-30.0544 30.0544 30.0544 30.0544 0 0 1-30.1056-30.0544v-142.2336A79.4624 79.4624 0 0 1 455.2704 578.56h122.88a79.4624 79.4624 0 0 1 79.5136 79.4624v142.2336a30.0544 30.0544 0 0 1-30.1056 30.0544 30.0544 30.0544 0 0 1-29.9008-30.1056z" fill="#FFA937" p-id="1215"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096800268" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6149" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M958.83 512c0 321.16-126.32 447.5-447.48 447.5S63.83 833.15 63.83 512 190.18 64.51 511.36 64.51 958.81 190.83 958.81 512z" fill="#03A9F5" p-id="6150"></path><path d="M391.16 697.63h240.47a16.39 16.39 0 1 1 0 32.79H391.16A17.63 17.63 0 0 1 374.84 714a15.74 15.74 0 0 1 16.32-16.39z m275.67-404.05H355.75A63.54 63.54 0 0 0 293 356.46v229.25a63.54 63.54 0 0 0 62.81 62.81h311a64.13 64.13 0 0 0 62.81-65.58V356.46a63.54 63.54 0 0 0-62.81-62.89zM615 577.76a20.33 20.33 0 0 1-24.56 0l-38.24-38.18a98.22 98.22 0 0 1-54.58 16.32 95.6 95.6 0 1 1 95.53-95.68 98.37 98.37 0 0 1-16.39 54.58L615 553a20.33 20.33 0 0 1 0 24.56z m0 0" fill="#FFFFFF" p-id="6151"></path><path d="M500.39 400.18a60 60 0 1 0 60 60 60 60 0 0 0-60-60z m0 0" fill="#FFFFFF" p-id="6152"></path><path d="M560.44 460.23a60 60 0 1 1-60-60 60 60 0 0 1 60 60z m0 0" fill="#FFFFFF" p-id="6153"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756113963939" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M154.7264 150.528m89.2928 0l148.224 0q89.2928 0 89.2928 89.2928l0 148.224q0 89.2928-89.2928 89.2928l-148.224 0q-89.2928 0-89.2928-89.2928l0-148.224q0-89.2928 89.2928-89.2928Z" fill="#5097FF" p-id="1718"></path><path d="M154.7264 541.44m89.2928 0l148.224 0q89.2928 0 89.2928 89.2928l0 148.224q0 89.2928-89.2928 89.2928l-148.224 0q-89.2928 0-89.2928-89.2928l0-148.224q0-89.2928 89.2928-89.2928Z" fill="#5097FF" p-id="1719"></path><path d="M547.4816 541.44m89.2928 0l148.224 0q89.2928 0 89.2928 89.2928l0 148.224q0 89.2928-89.2928 89.2928l-148.224 0q-89.2928 0-89.2928-89.2928l0-148.224q0-89.2928 89.2928-89.2928Z" fill="#5097FF" p-id="1720"></path><path d="M710.906623 97.648519m63.139544 63.139545l90.002814 90.002814q63.139544 63.139544 0 126.279088l-90.002814 90.002814q-63.139544 63.139544-126.279089 0l-90.002814-90.002814q-63.139544-63.139544 0-126.279088l90.002814-90.002814q63.139544-63.139544 126.279089 0Z" fill="#5097FF" p-id="1721"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096945880" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2454" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M711.104 0c47.104 0 85.312 38.208 85.312 85.312v853.376c0 47.104-38.208 85.312-85.312 85.312H312.96a85.312 85.312 0 0 1-85.312-85.312V85.312C227.584 38.208 265.792 0 312.896 0h398.208zM113.792 142.208c31.36 0 56.896 25.472 56.896 56.96v682.624c0 31.36-25.472 56.896-56.96 56.896H0V142.208h113.792z m910.208 0v796.48h-113.792a56.896 56.896 0 0 1-56.896-56.96V199.168c0-31.36 25.472-56.896 56.96-56.896H1024z" fill="#25DCA2" p-id="2455"></path></svg>

After

Width:  |  Height:  |  Size: 781 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096967537" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3118" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1011.512149 85.354138V1023.999905H180.011738C87.414674 1023.999905 12.487851 947.199912 12.487851 852.292604V171.707301C12.487851 76.799993 87.414674 0 180.011738 0h664.975548v682.64579H181.073202c-46.829264 0-85.354138 39.46146-85.354139 87.477065v81.108285c0 47.953166 38.524874 87.414626 85.354139 87.414626h747.207735V85.354138H1011.512149zM844.987286 767.999929H179.012714v85.354138h665.974572V767.999929z" fill="#FE9C27" p-id="3119"></path></svg>

After

Width:  |  Height:  |  Size: 786 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096958321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2784" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M864.097437 0H159.841877A127.67992 127.67992 0 0 0 31.969957 127.42392v769.08752C31.969957 966.911396 89.249922 1023.99936 159.841877 1023.99936h704.25556a127.61592 127.61592 0 0 0 127.80792-127.35992V127.35992C991.969357 57.023964 934.689393 0 864.097437 0z m-563.199648 277.439827h39.807975c30.591981 0 55.359965 19.135988 55.359966 42.687973 0 23.679985-24.767985 42.751973-55.359966 42.751973H300.769789c-30.591981 0-55.359965-19.135988-55.359965-42.751973 0-23.551985 24.767985-42.687973 55.359965-42.687973z m422.143736 490.623693H300.833789c-30.591981 0-55.359965-19.135988-55.359965-42.687973 0-23.679985 24.767985-42.751973 55.359965-42.751974h422.207736c30.655981 0 55.423965 19.135988 55.423966 42.751974 0 23.551985-24.767985 42.687973-55.423966 42.687973z m0-192.31988H300.833789c-30.591981 0-55.359965-19.007988-55.359965-42.431973 0-23.487985 24.767985-42.495973 55.359965-42.495974h422.207736c30.655981 0 55.423965 19.007988 55.423966 42.495974 0 23.423985-24.767985 42.431973-55.423966 42.431973z m0-212.863867H552.033632c-30.591981 0-55.423965-19.135988-55.423965-42.687973 0-23.679985 24.831984-42.751973 55.423965-42.751973h171.007893c30.655981 0 55.423965 19.135988 55.423966 42.751973 0 23.551985-24.767985 42.687973-55.423966 42.687973z" fill="#FF7400" p-id="2785"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756113811295" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="943" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M648.746667 241.130667l-90.176-90.816a65.706667 65.706667 0 0 0-93.397334 0l-90.197333 90.816a65.728 65.728 0 0 1-65.344 16.789333l-113.066667-33.536c-42.304-12.522667-84.693333 19.413333-84.693333 63.808v489.152c0 36.736 29.568 66.496 66.048 66.496h667.904c36.48 0 66.048-29.76 66.048-66.496V288.192c0-44.394667-42.389333-76.352-84.693333-63.786667l-113.066667 33.493334a65.728 65.728 0 0 1-65.344-16.768z" fill="#FE7840" p-id="944"></path><path d="M585.301333 373.909333h-146.858666c-16.746667 0-32.832 7.082667-44.672 19.669334l-101.162667 107.562666a26.794667 26.794667 0 0 0 0 36.245334l196.693333 209.109333a30.613333 30.613333 0 0 0 45.141334 0l196.693333-209.109333c9.408-10.026667 9.408-26.24 0-36.266667L629.973333 393.6a61.333333 61.333333 0 0 0-44.672-19.669333z" fill="#FFFFFF" opacity=".56" p-id="945"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756113951989" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1382" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M931.1232 465.3056l-94.72-87.6544V216.064a50.0224 50.0224 0 1 0-99.9424 0v69.0176l-175.9744-162.9184C532.992 98.8672 481.792 104.6528 457.728 128L98.9184 465.0496a49.9712 49.9712 0 1 0 68.4544 72.8064L467.2 256A66.56 66.56 0 0 1 558.08 256l305.3568 282.7776a50.0224 50.0224 0 0 0 67.9424-73.3696z" fill="#FF5A3E" p-id="1383"></path><path d="M546.816 301.4144a50.2272 50.2272 0 0 0-66.56 0L189.44 579.072v289.536a47.616 47.616 0 0 0 48.9472 46.08H788.48a47.616 47.616 0 0 0 48.9472-46.08v-290.816z" fill="#FF5A3E" p-id="1384"></path></svg>

After

Width:  |  Height:  |  Size: 871 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096961327" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2952" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M929.6 134.336c-20.16-20.8-43.52-31.168-69.952-31.168H652.16A159.68 159.68 0 0 0 598.4 28.8C573.44 9.6 544.704 0 512.064 0c-32.704 0-61.44 9.536-86.4 28.736-24.832 19.2-42.752 43.968-53.632 74.368H164.352c-26.432 0-49.792 10.432-70.016 31.168-20.224 20.864-30.336 44.8-30.336 72v714.56c0 27.136 10.176 51.136 30.336 71.936 20.224 20.864 43.584 31.232 70.016 31.232h695.296c26.432 0 49.792-10.432 69.952-31.168 20.288-20.8 30.4-44.736 30.4-71.936V206.272c0-27.136-10.176-51.136-30.4-71.936z m-452.672-16.832A47.104 47.104 0 0 1 512 103.104c14.08 0 25.664 4.8 35.072 14.4 9.344 9.6 13.952 21.568 13.952 35.968a53.824 53.824 0 0 1-13.952 37.184 45.184 45.184 0 0 1-35.072 15.616 45.312 45.312 0 0 1-35.072-15.552 53.568 53.568 0 0 1-13.952-37.184c0-14.4 4.608-26.368 13.952-36.032z m135.424 702.656H262.336v-103.168h349.952v103.168zM761.6 613.888H262.336V513.216h499.328v100.672z m0-203.84H262.336V306.944h499.328v103.104z" fill="#5290FB" p-id="2953"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096928165" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1282" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M716.270137 257.088L473.710137 27.328a99.328 99.328 0 0 0-136.832 0.064L95.214137 257.152A100.736 100.736 0 0 0 63.982137 329.92v593.664C63.982137 979.072 108.590137 1024 163.694137 1024h484.16c55.104 0 99.712-44.864 99.712-100.288V330.112c0-27.648-11.264-54.08-31.36-72.96zM408.238137 178.624c31.616 0 57.152 25.792 57.152 57.6 0 31.744-25.6 57.472-57.152 57.472a57.344 57.344 0 0 1-57.216-57.536c0-31.744 25.6-57.536 57.216-57.536z m-100.608 650.88l63.104-183.552H254.318137l196.352-225.28-12.608 167.424 104.32 5.248-234.752 236.16z m498.176-358.912l146.432 290.88c17.344 34.56 4.608 76.8-28.992 95.68l-119.68 67.328 2.24-453.888z" fill="#FF8E12" p-id="1283"></path></svg>

After

Width:  |  Height:  |  Size: 1008 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1756096832904" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7620" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M959.6 512.3c0 321.16-126.32 447.5-447.48 447.5S64.6 833.46 64.6 512.3 190.94 64.82 512.12 64.82s447.45 126.32 447.45 447.5z m0 0" fill="#F76969" p-id="7621"></path><path d="M721.15 452.57c-232.91 104.54-418.14 0-418.14 0v65.63c200.61 121.12 418.14 0 418.14 0z" fill="#FFFFFF" p-id="7622"></path><path d="M303 607.85c200.61 121.13 418.14 0 418.14 0v-65.68c-232.91 104.55-418.14 0-418.14 0zM512.1 273.35c-115.48 0-209.1 46.82-209.1 104.54 0 37.34 39.8 71.87 104.55 90.54s144.38 18.67 209.09 0 104.55-53.17 104.55-90.54c0-57.72-93.6-104.54-209.09-104.54z" fill="#FFFFFF" p-id="7623"></path><path d="M303 697.46c200.61 121.09 418.14 0 418.14 0v-65.67c-232.91 104.54-418.14 0-418.14 0z" fill="#FFFFFF" p-id="7624"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Loaded 100 of 196 files, more files were not shown because too many files have changed in this diff. Show more