diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..965a996 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +南邮工会羽毛球报名 \ No newline at end of file diff --git a/backend/src/main/java/com/budwk/app/client/auth/service/ClientAuthServiceImpl.java b/backend/src/main/java/com/budwk/app/client/auth/service/ClientAuthServiceImpl.java index b3e1863..aee9076 100644 --- a/backend/src/main/java/com/budwk/app/client/auth/service/ClientAuthServiceImpl.java +++ b/backend/src/main/java/com/budwk/app/client/auth/service/ClientAuthServiceImpl.java @@ -52,14 +52,16 @@ public class ClientAuthServiceImpl implements ClientAuthService { } if (loginBody.getLoginMethod().equals("sms")) { - // 测试阶段允许已注册手机号直接登录;恢复正式登录时必须重新启用短信验证码校验。 -// validateService.checkSMSCode(loginBody.getPhone(), loginBody.getSmsCode()); + validateService.checkSMSCode(loginBody.getPhone(), loginBody.getSmsCode()); } if (loginBody.getLoginMethod().equals("password")) { + if (Strings.isBlank(loginBody.getPassword())) { + throw new BaseException("请输入密码"); + } String hashedPassword = PwdUtil.getPassword(loginBody.getPassword(), user.getSalt()); if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) { -// throw new BaseException("密码不正确"); + throw new BaseException("密码不正确"); } } diff --git a/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientActivityController.java b/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientActivityController.java index 898dd2f..8bf9552 100644 --- a/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientActivityController.java +++ b/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientActivityController.java @@ -59,6 +59,7 @@ public class ClientActivityController { t1.signStartTime, t1.signEndTime, t1.coHostUnit, + t1.hostUnit, t1.cover FROM activity t1 diff --git a/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientGroupController.java b/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientGroupController.java index 5a91c4f..e3534e1 100644 --- a/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientGroupController.java +++ b/backend/src/main/java/com/budwk/app/client/module/activity/controller/ClientGroupController.java @@ -66,7 +66,6 @@ public class ClientGroupController { LEFT JOIN act_user t2 ON t1.id = t2.activityId WHERE t2.userId = @userId - AND t2.isSignIn =1 """).setParam("userId", StpClientUtil.STP.getLoginIdAsLong()); List list = clientActivityService.listMap(sql); return Result.success(list); diff --git a/backend/src/main/java/com/budwk/app/client/module/activity/service/impl/ClientActivityServiceImpl.java b/backend/src/main/java/com/budwk/app/client/module/activity/service/impl/ClientActivityServiceImpl.java index d833abd..5fd12fe 100644 --- a/backend/src/main/java/com/budwk/app/client/module/activity/service/impl/ClientActivityServiceImpl.java +++ b/backend/src/main/java/com/budwk/app/client/module/activity/service/impl/ClientActivityServiceImpl.java @@ -1,5 +1,6 @@ package com.budwk.app.client.module.activity.service.impl; +import cn.hutool.core.util.IdcardUtil; import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.client.auth.models.Client_user; @@ -144,6 +145,11 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme oldUser = oldUserByPhone.get(user.getPhone()); } mergeHiddenFields(user, oldUser, requiredMap); + // 无手机号人员再次提交时沿用本人旧记录关联的账号,不信任客户端传入的账号ID。 + user.setUserId(oldUser == null ? null : oldUser.getUserId()); + if (!conditionMatches(user, RegistrationFieldDefinition.PHONE)) { + user.setPhone(null); + } } if (requiredMap.containsKey("personType") || requiredMap.containsKey("projectType")) { @@ -175,6 +181,7 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme // 检查是否有重复的手机号 boolean hasDuplicatePhone = userList.stream() + .filter(item -> Strings.isNotBlank(item.getPhone())) .collect(Collectors.groupingBy(ActUser::getPhone, Collectors.counting())) .values().stream() .anyMatch(count -> count > 1); @@ -191,7 +198,10 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme // 首先检查报名人员是否已经在用户表中 for (ActUser user : userList) { - Client_user clientUser = dao().fetch(Client_user.class, Cnd.where(Client_user::getPhone, "=", user.getPhone())); + // 未兼任教练或领队的运动员允许不填手机号,不能用空手机号查找并错误关联其他人的账号。 + Client_user clientUser = Strings.isNotBlank(user.getPhone()) + ? dao().fetch(Client_user.class, Cnd.where(Client_user::getPhone, "=", user.getPhone())) + : (user.getUserId() == null ? null : dao().fetch(Client_user.class, user.getUserId())); if (clientUser == null) { // 创建用户 clientUser = new Client_user(); @@ -259,6 +269,11 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme if (Boolean.TRUE.equals(entry.getValue()) && isEmptyValue(value)) { throw new BaseException("请填写" + definition.getFieldLabel()); } + // 服务端复核身份证,防止绕过页面校验提交无效号码。 + if ("idCard".equals(entry.getKey()) && !isEmptyValue(value) + && (String.valueOf(value).length() != 18 || !IdcardUtil.isValidCard18(String.valueOf(value)))) { + throw new BaseException("请输入正确的18位身份证号码"); + } if (!isEmptyValue(value) && !activityRegistrationFieldService.isValidDictValue(activityId, entry.getKey(), String.valueOf(value))) { throw new BaseException(definition.getFieldLabel() + "不属于当前可用的数据字典选项"); @@ -274,6 +289,10 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme if (Strings.isBlank(definition.getParentField())) { return true; } + // 人员类型为字典多选,按名称解析编码后匹配任意目标类型。 + if ("personType".equals(definition.getParentField())) { + return activityRegistrationFieldService.matchesPersonTypes(user.getPersonType(), definition.getShowWhenValue()); + } Object parentValue = getFieldValue(user, definition.getParentField()); return parentValue != null && definition.getShowWhenValue().equals(String.valueOf(parentValue)); } @@ -292,6 +311,7 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme case "unitName" -> user.getUnitName(); case "position" -> user.getPosition(); case "phone" -> user.getPhone(); + case "idCard" -> user.getIdCard(); case "birthday" -> user.getBirthday(); case "isStay" -> user.getIsStay(); case "isContacts" -> user.getIsContacts(); @@ -321,6 +341,7 @@ public class ClientActivityServiceImpl extends BaseServiceImpl impleme case "projectType" -> target.setProjectType(source == null ? null : source.getProjectType()); case "weight" -> target.setWeight(source == null ? null : source.getWeight()); case "position" -> target.setPosition(source == null ? null : source.getPosition()); + case "idCard" -> target.setIdCard(source == null ? null : source.getIdCard()); case "birthday" -> target.setBirthday(source == null ? null : source.getBirthday()); case "isStay" -> target.setIsStay(source == null ? null : source.getIsStay()); case "isContacts" -> target.setIsContacts(source == null ? false : source.getIsContacts()); diff --git a/backend/src/main/java/com/budwk/app/module/activity/controller/ActGroupController.java b/backend/src/main/java/com/budwk/app/module/activity/controller/ActGroupController.java index 79e9b99..9aabb67 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/controller/ActGroupController.java +++ b/backend/src/main/java/com/budwk/app/module/activity/controller/ActGroupController.java @@ -187,6 +187,20 @@ public class ActGroupController { } + /** + * 按单位对活动全部有效报名人员进行分组。 + * + * @param activityId 活动ID + * @return 分组成功结果;数据不完整时由业务异常返回错误提示 + */ + @At + @SaCheckPermission("activity.group") + @SLog(tag = "活动分组", msg = "按单位分组") + public Result groupByUnit(Long activityId) { + actGroupService.groupByUnit(activityId); + return Result.success(); + } + @At @SaCheckPermission("activity.group") public Result queryUnGroupUser(String activityId) { diff --git a/backend/src/main/java/com/budwk/app/module/activity/controller/ActUserController.java b/backend/src/main/java/com/budwk/app/module/activity/controller/ActUserController.java index 1286967..c86c62a 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/controller/ActUserController.java +++ b/backend/src/main/java/com/budwk/app/module/activity/controller/ActUserController.java @@ -40,6 +40,9 @@ import javax.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -255,10 +258,53 @@ public class ActUserController { Sql sql = actUserService.getSignUpSql(pageForm); List nutMaps = actUserService.listMap(sql); + // 历史表列名为ProjectType,统一导出键名供项目排序及名称转换使用。 + for (NutMap row : nutMaps) { + if (row.getString("projectType", "").isEmpty()) { + row.put("projectType", row.getString("ProjectType", "")); + } + } + // 导出按单位、领队、教练、项目、男女分组;人员类型按字典名称解析,兼任类型也参与排序。 + Map personTypeOrder = nutMaps.stream() + .map(row -> row.getString("personType", "")).distinct() + .collect(Collectors.toMap(type -> type, type -> + activityRegistrationFieldService.matchesPersonTypes(type, "领队") ? 0 + : activityRegistrationFieldService.matchesPersonTypes(type, "教练") ? 1 : 2)); + nutMaps.sort(Comparator.comparing((NutMap row) -> row.getString("unitName", "")) + .thenComparing(row -> row.getString("unitId", "")) + .thenComparingInt(row -> personTypeOrder.get(row.getString("personType", ""))) + .thenComparing(row -> Arrays.stream(row.getString("projectType", "").split(",")) + .map(String::trim).filter(value -> !value.isEmpty()).distinct().sorted() + .collect(Collectors.joining(","))) + .thenComparingInt(row -> "男".equals(row.getString("sex")) ? 0 + : "女".equals(row.getString("sex")) ? 1 : 2)); + // 多选字典编码转换为名称;保留无法匹配的旧编码,避免历史报名信息在导出时丢失。 + for (NutMap field : activityRegistrationFieldService.getFieldConfig(pageForm.getActivityId(), false, true)) { + String fieldKey = field.getString("fieldKey"); + if (!"personType".equals(fieldKey) && !"projectType".equals(fieldKey)) { + continue; + } + Map optionNames = new HashMap<>(); + if (field.get("options") instanceof List options) { + for (Object option : options) { + if (option instanceof Map optionMap) { + optionNames.put(String.valueOf(optionMap.get("value")), String.valueOf(optionMap.get("label"))); + } + } + } + for (NutMap row : nutMaps) { + row.put(fieldKey + "Desc", Arrays.stream(row.getString(fieldKey, "").split(",")) + .map(String::trim).filter(value -> !value.isEmpty()) + .map(value -> optionNames.getOrDefault(value, value)) + .collect(Collectors.joining("、"))); + } + } List entityList = new ArrayList<>() {{ // add(new ExcelExportEntity("报名号", "actUserNo", 20)); add(new ExcelExportEntity("姓名", "userName", 20)); add(new ExcelExportEntity("性别", "sex", 20)); + add(new ExcelExportEntity("人员类型", "personTypeDesc", 20)); + add(new ExcelExportEntity("项目类型", "projectTypeDesc", 30)); add(new ExcelExportEntity("身份证号码", "idCard", 20)); add(new ExcelExportEntity("联系方式", "phone", 20)); add(new ExcelExportEntity("职务", "position", 20)); diff --git a/backend/src/main/java/com/budwk/app/module/activity/enums/RegistrationFieldDefinition.java b/backend/src/main/java/com/budwk/app/module/activity/enums/RegistrationFieldDefinition.java index 05a5120..df583c3 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/enums/RegistrationFieldDefinition.java +++ b/backend/src/main/java/com/budwk/app/module/activity/enums/RegistrationFieldDefinition.java @@ -14,11 +14,11 @@ public enum RegistrationFieldDefinition { USER_NAME("userName", "姓名", "input", true, true, true, null, null, null), SEX("sex", "性别", "radio", true, true, false, null, null, null), PERSON_TYPE("personType", "人员类型", "dict-checkbox", false, false, false, null, null, "person_type"), - PROJECT_TYPE("projectType", "项目类型", "dict-checkbox", false, false, false, null, null, "project_type"), + PROJECT_TYPE("projectType", "项目类型", "dict-checkbox", false, true, false, "personType", "运动员", "project_type"), WEIGHT("weight", "体重(KG)", "number", true, true, false, null, null, null), UNIT_NAME("unitName", "单位名称", "readonly", true, true, true, null, null, null), POSITION("position", "职务", "input", true, true, false, null, null, null), - PHONE("phone", "联系电话", "tel", true, true, true, null, null, null), + PHONE("phone", "联系电话", "tel", true, true, true, "personType", "教练,领队", null), BIRTHDAY("birthday", "出生日期", "date", false, false, false, null, null, null), IS_STAY("isStay", "是否住宿", "boolean-radio", true, true, false, null, null, null), IS_CONTACTS("isContacts", "是否联络员", "boolean-radio", true, true, false, null, null, null), @@ -29,7 +29,8 @@ public enum RegistrationFieldDefinition { IS_SHOW_SKILL("isShowSkill", "是否展示才艺", "boolean-radio", false, false, false, null, null, null), SKILL_NAME("skillName", "才艺名称", "input", false, false, false, "isShowSkill", "true", null), EQUIP("equip", "所需器材", "input", false, false, false, "isShowSkill", "true", null), - IS_PARTY("isParty", "参加茶叙会", "boolean-radio", true, true, false, null, null, null); + IS_PARTY("isParty", "参加茶叙会", "boolean-radio", true, true, false, null, null, null), + ID_CARD("idCard", "身份证", "input", true, true, true, "personType", "运动员", null); private final String fieldKey; private final String fieldLabel; diff --git a/backend/src/main/java/com/budwk/app/module/activity/models/ActUser.java b/backend/src/main/java/com/budwk/app/module/activity/models/ActUser.java index a39e172..dd53727 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/models/ActUser.java +++ b/backend/src/main/java/com/budwk/app/module/activity/models/ActUser.java @@ -71,10 +71,10 @@ public class ActUser extends BaseModel { @ColDefine(type = ColType.VARCHAR, width = 100) private String position; -// @Column -// @Comment("身份证号码") -// @ColDefine(type = ColType.VARCHAR, width = 20) -// private String idCard; + @Column + @Comment("身份证号码") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String idCard; @Column @Comment("出生日期") diff --git a/backend/src/main/java/com/budwk/app/module/activity/service/ActGroupService.java b/backend/src/main/java/com/budwk/app/module/activity/service/ActGroupService.java index 0bf63d0..32f802b 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/service/ActGroupService.java +++ b/backend/src/main/java/com/budwk/app/module/activity/service/ActGroupService.java @@ -24,6 +24,14 @@ public interface ActGroupService extends BaseService { */ void groupSetting(ActGroupVo groupVo); + /** + * 按单位重建活动全部有效报名人员的分组,同一单位只生成一个组。 + * + * @param activityId 活动ID;不限制人员签到状态 + * @throws com.budwk.app.base.exception.BaseException 活动或报名数据不满足分组条件时抛出 + */ + void groupByUnit(Long activityId); + /** * 设置未分组人员 * @param unGroupUserVo diff --git a/backend/src/main/java/com/budwk/app/module/activity/service/ActivityRegistrationFieldService.java b/backend/src/main/java/com/budwk/app/module/activity/service/ActivityRegistrationFieldService.java index 30f711a..bcc7c9c 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/service/ActivityRegistrationFieldService.java +++ b/backend/src/main/java/com/budwk/app/module/activity/service/ActivityRegistrationFieldService.java @@ -54,6 +54,15 @@ public interface ActivityRegistrationFieldService extends BaseService implements Ac } + /** + * 将活动全部有效报名人员按单位ID重建为一单位一组,并初始化各项目得分记录。 + * + * @param activityId 活动ID + * @throws BaseException 活动不存在、无有效报名人员或人员缺少单位、关联账号时抛出 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void groupByUnit(Long activityId) { + if (activityId == null || dao().fetch(Activity.class, + Cnd.where(Activity::getId, "=", activityId).and(Activity::getDelFlag, "=", false)) == null) { + throw new BaseException("请选择有效活动"); + } + List users = dao().query(ActUser.class, + Cnd.where(ActUser::getActivityId, "=", activityId).and(ActUser::getDelFlag, "=", false) + .asc(ActUser::getUnitName).asc(ActUser::getId)); + if (users.isEmpty()) { + throw new BaseException("当前活动暂无有效报名人员"); + } + Map> unitUsers = new LinkedHashMap<>(); + for (ActUser user : users) { + if (Strings.isBlank(user.getUnitId()) || user.getUserId() == null) { + throw new BaseException("报名人员【" + user.getUserName() + "】缺少所属单位或关联账号,请补齐后再分组"); + } + unitUsers.computeIfAbsent(user.getUnitId(), key -> new ArrayList<>()).add(user); + } + List items = dao().query(ActivityItem.class, Cnd.where(ActivityItem::getActivityId, "=", activityId)); + // 完成数据检查后再覆盖旧分组,整个重建过程在同一事务内完成。 + dao().clear(ActItemGroupScore.class, Cnd.where(ActItemGroupScore::getActivityId, "=", activityId)); + dao().clear(ActGroupUser.class, Cnd.where(ActGroupUser::getActivityId, "=", activityId)); + dao().clear(ActUnGroupUser.class, Cnd.where(ActUnGroupUser::getActivityId, "=", activityId)); + dao().clear(ActGroup.class, Cnd.where(ActGroup::getActivityId, "=", activityId)); + int sort = 1; + for (List members : unitUsers.values()) { + ActGroup group = new ActGroup(); + group.setActivityId(activityId); + group.setGroupName(Strings.sBlank(members.get(0).getUnitName(), members.get(0).getUnitId())); + group.setSort(sort++); + group.setIsOpen(false); + group.setGroupUsers(members.stream().map(ActUser::getUserId).distinct().map(userId -> { + ActGroupUser member = new ActGroupUser(); + member.setActivityId(activityId); + member.setUserId(userId); + return member; + }).collect(Collectors.toList())); + dao().insertWith(group, "groupUsers"); + for (ActivityItem item : items) { + ActItemGroupScore score = new ActItemGroupScore(); + score.setActivityId(activityId); + score.setItemId(item.getId()); + score.setGroupId(group.getId()); + dao().insert(score); + } + } + } + @Override @Aop(TransAop.READ_COMMITTED) public void unGroupUserSetting(ActUnGroupUserVo unGroupUserVo) { diff --git a/backend/src/main/java/com/budwk/app/module/activity/service/impl/ActivityRegistrationFieldServiceImpl.java b/backend/src/main/java/com/budwk/app/module/activity/service/impl/ActivityRegistrationFieldServiceImpl.java index d003d8b..6c5f68f 100644 --- a/backend/src/main/java/com/budwk/app/module/activity/service/impl/ActivityRegistrationFieldServiceImpl.java +++ b/backend/src/main/java/com/budwk/app/module/activity/service/impl/ActivityRegistrationFieldServiceImpl.java @@ -51,12 +51,41 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl item, (left, right) -> left, LinkedHashMap::new)); boolean useDefault = storedFields.isEmpty(); + ActRegistrationField identityField = storedMap.get("idCard"); + boolean legacyIdentitySort = identityField == null || identityField.getSort() == null + || identityField.getSort() == 0; + // 兼容身份证原来排在0的活动配置,仅在旧顺序下为第6位腾出位置,避免重复顺延。 + if (legacyIdentitySort) { + for (ActRegistrationField field : storedMap.values()) { + if (field.getSort() != null && field.getSort() >= 6) { + field.setSort(field.getSort() + 1); + } + } + if (identityField != null) { + identityField.setSort(6); + } + if (storedMap.containsKey("phone")) { + storedMap.get("phone").setSort(7); + } + if (storedMap.containsKey("position")) { + storedMap.get("position").setSort(8); + } + } + // 旧活动也补齐新增固定字段及其父字段,使人员类型联动无需重新保存即可生效。 + for (RegistrationFieldDefinition definition : RegistrationFieldDefinition.values()) { + if (definition.isLocked() || useDefault && definition.isDefaultSelected()) { + storedMap.putIfAbsent(definition.getFieldKey(), buildDefaultField(definition)); + } + } + for (String fieldKey : new LinkedHashSet<>(storedMap.keySet())) { + addParentField(storedMap, RegistrationFieldDefinition.fromKey(fieldKey)); + } List result = new ArrayList<>(); int defaultSort = 1; for (RegistrationFieldDefinition definition : RegistrationFieldDefinition.values()) { boolean projectTypeField = RegistrationFieldDefinition.PROJECT_TYPE == definition; ActRegistrationField stored = storedMap.get(definition.getFieldKey()); - boolean selected = useDefault ? definition.isDefaultSelected() : stored != null; + boolean selected = stored != null; // 多项目、无项目或唯一项目未配置有效类型时,历史配置也不再向客户端开放。 if (projectTypeField && !projectTypeContext.configurable) { selected = false; @@ -66,13 +95,14 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl selected = splitMultiValue(personType); + List expectedLabels = splitMultiValue(labels); + return getEnabledDictOptions(RegistrationFieldDefinition.PERSON_TYPE.getDictCode()).stream() + .anyMatch(option -> expectedLabels.contains(option.getString("label")) + && selected.contains(option.getString("value"))); + } + @Override public boolean isValidPersonTypeCombination(String value) { if (Strings.isBlank(value)) { @@ -366,7 +409,17 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl mixedFemaleCount ? "女" : "男"; + int missingCount = Math.abs(mixedMaleCount - mixedFemaleCount); + throw new BaseException("混合双打:" + currentSelection + ",按现有人数配对还缺少" + + missingCount + "名" + missingSex + "选手。请调整报名选择,确保每对为一男一女"); } validateProjectTypeRegistrationLimits(activityId, users, options); } @@ -541,7 +594,14 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl 5; + case ID_CARD -> 6; + case PHONE -> 7; + case POSITION -> 8; + default -> definition.ordinal() >= RegistrationFieldDefinition.BIRTHDAY.ordinal() + ? definition.ordinal() + 2 : definition.ordinal() + 1; + }); return field; } diff --git a/backend/src/main/java/com/budwk/app/sys/controller/SysUserController.java b/backend/src/main/java/com/budwk/app/sys/controller/SysUserController.java index 8739af3..526bcaa 100644 --- a/backend/src/main/java/com/budwk/app/sys/controller/SysUserController.java +++ b/backend/src/main/java/com/budwk/app/sys/controller/SysUserController.java @@ -129,7 +129,7 @@ public class SysUserController { try { Sys_user user = sysUserService.fetch(id); String salt = R.UU32(); - String pwd = R.captchaNumber(6); + String pwd = "wdxxmm@123"; String hashedPasswordBase64 = PwdUtil.getPassword(pwd, salt); sysUserService.update(Chain.make("salt", salt).add("password", hashedPasswordBase64), Cnd.where("id", "=", id)); sysUserService.deleteCache(user.getId()); diff --git a/backend/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java b/backend/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java index 7d55847..ae19238 100644 --- a/backend/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java +++ b/backend/src/main/java/com/budwk/app/sys/services/impl/SysUserServiceImpl.java @@ -261,9 +261,12 @@ public class SysUserServiceImpl extends BaseServiceImpl implements Sys if (user.isDisabled()) { throw new BaseException("用户被禁用"); } + if (Strings.isBlank(passowrd)) { + throw new BaseException("请输入密码"); + } String hashedPassword = PwdUtil.getPassword(passowrd, user.getSalt()); if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) { -// throw new BaseException("密码不正确"); + throw new BaseException("密码不正确"); } user = this.fetchLinks(user, "unit"); return user; diff --git a/backend/src/main/resources/views/platform/activity/group/index.html b/backend/src/main/resources/views/platform/activity/group/index.html index b3d4f01..cd30154 100644 --- a/backend/src/main/resources/views/platform/activity/group/index.html +++ b/backend/src/main/resources/views/platform/activity/group/index.html @@ -62,6 +62,7 @@ layout("/layouts/platform_1.html"){ 开始分组 + 按单位分组 @@ -139,7 +140,8 @@ layout("/layouts/platform_1.html"){ year: new Date().getFullYear() + "" }, activityList: [], - unGroupUser: [] + unGroupUser: [], + unitGrouping: false } }, methods: { @@ -223,6 +225,36 @@ layout("/layouts/platform_1.html"){ }) }) }, + // 按单位重建会清空旧分组及得分,操作前明确提示并防止重复提交。 + async groupByUnit() { + if (this.unitGrouping) return + if (!this.pageForm.activityId) { + this.$message.error("请选择活动") + return + } + const activityId = this.pageForm.activityId + try { + await this.$confirm('将全部有效报名人员按单位分组,同一单位生成一个组。此操作会清空该活动现有分组及项目得分,是否继续?', '按单位分组', { + type: 'warning', confirmButtonText: '确定分组', cancelButtonText: '取消' + }) + } catch (error) { + return + } + this.unitGrouping = true + try { + const resp = await $.post('/platform/activity/group/groupByUnit', {activityId}) + if (resp.code === 0) { + this.$message.success('按单位分组成功') + await this.successGroup() + } else { + this.$message.error(resp.msg || '按单位分组失败') + } + } catch (error) { + this.$message.error('按单位分组失败') + } finally { + this.unitGrouping = false + } + }, // 开始分组 openGrouping(){ if (!this.pageForm.activityId) { diff --git a/backend/src/main/resources/views/platform/activity/manage/registrationFieldSetting.js b/backend/src/main/resources/views/platform/activity/manage/registrationFieldSetting.js index 5f9e556..8035b3c 100644 --- a/backend/src/main/resources/views/platform/activity/manage/registrationFieldSetting.js +++ b/backend/src/main/resources/views/platform/activity/manage/registrationFieldSetting.js @@ -1,33 +1,33 @@ const registrationFieldSetting = { template: /*language=HTML*/ ` + title="设置报名信息" + :visible.sync="visible" + size="58%" + :wrapper-closable="false" + :before-close="handleClose">
+ title="身份证、姓名、联系电话和单位名称为系统固定字段;关联字段会自动保持完整的父子关系。" + type="info" + :closable="false" + show-icon> + v-if="projectTypeRuleMessage" + :title="projectTypeRuleMessage" + :type="projectTypeConfigurable ? 'success' : 'warning'" + class="mt-3" + :closable="false" + show-icon> @@ -45,20 +45,20 @@ const registrationFieldSetting = { @@ -121,6 +121,11 @@ const registrationFieldSetting = { this.selectParent(row) return } + if (this.fieldList.some((item) => item.parentField === row.fieldKey && item.locked)) { + this.$set(row, "selected", true) + this.$message.warning("系统固定字段依赖此字段,不能取消显示") + return + } this.unselectChildren(row.fieldKey) }, selectParent(row) { @@ -223,6 +228,9 @@ const registrationFieldSetting = { return row.dictCode ? "数据字典:" + row.dictCode : "固定表单字段" }, dependencyLabel(row) { + if (row.parentField === "personType") { + return "当人员类型为" + row.showWhenValue.split(",").join("或者") + "时才需要填写" + } if (!row.parentField) { return "-" } diff --git a/backend/src/main/resources/views/platform/activity/user/index.html b/backend/src/main/resources/views/platform/activity/user/index.html index dd8151b..f856109 100644 --- a/backend/src/main/resources/views/platform/activity/user/index.html +++ b/backend/src/main/resources/views/platform/activity/user/index.html @@ -683,13 +683,13 @@ layout("/layouts/platform_1.html"){ }) }) }, - // 查询面板和代报名弹窗共用启用单位列表,并按单位名称排序,便于快速检索。 + // 查询面板和代报名弹窗共用启用单位列表,按数据库序号location升序排列。 getUnits(){ this.unitLoading = true $.post('/open/common/units').then((resp) => { if (resp.code === 0) { const units = (resp.data || []).slice().sort((left, right) => { - return String(left.name || '').localeCompare(String(right.name || ''), 'zh-CN') + return Number(left.location ?? 0) - Number(right.location ?? 0) }) this.$set(this, 'units', units) } else { diff --git a/frontend/src/App.vue b/frontend/src/App.vue index da8bcf2..ab15df8 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -27,6 +27,10 @@ export default { margin: 0; padding: 0; box-sizing: border-box; +} + +/* 活动详情富文本保留自身字体及继承关系,其他区域沿用原有默认字体。 */ +:where(*:not(.activity-rich-content *)) { font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; } diff --git a/frontend/src/manifest.json b/frontend/src/manifest.json index d2f59ab..8c23cad 100644 --- a/frontend/src/manifest.json +++ b/frontend/src/manifest.json @@ -69,7 +69,7 @@ "enable": false }, "h5": { - "title": "趣味运动会", + "title": "赛事管理", "router": { "mode": "history" } diff --git a/frontend/src/pages.json b/frontend/src/pages.json index dc3e7d6..c4321ce 100644 --- a/frontend/src/pages.json +++ b/frontend/src/pages.json @@ -107,7 +107,7 @@ }, "tabBar": { "color": "#000", - "selectedColor": "#f45a5a", + "selectedColor": "#1989fa", "borderStyle": "white", "backgroundColor": "#ffffff", "height": "60px", @@ -124,7 +124,7 @@ "selectedText": "\ue64d", "fontSize": "17px", "color": "#333", - "selectedColor": "#f45a5a" + "selectedColor": "#1989fa" } }, { @@ -135,7 +135,7 @@ "selectedText": "\ue8b3", "fontSize": "17px", "color": "#333", - "selectedColor": "#f45a5a" + "selectedColor": "#1989fa" } }, { @@ -146,7 +146,7 @@ "selectedText": "\ue600", "fontSize": "17px", "color": "#333", - "selectedColor": "#f45a5a" + "selectedColor": "#1989fa" } } ] diff --git a/frontend/src/pages/activity/detail/index.vue b/frontend/src/pages/activity/detail/index.vue index 50a4646..912de89 100644 --- a/frontend/src/pages/activity/detail/index.vue +++ b/frontend/src/pages/activity/detail/index.vue @@ -18,7 +18,7 @@ - + 报名时间 {{ formatDate(detailData.signStartTime) }} 至 @@ -29,7 +29,7 @@ - + 活动时间 {{ formatDate(detailData.startTime) }} 至 {{ @@ -41,7 +41,7 @@ - + 活动地点 {{ detailData.address }} @@ -52,7 +52,7 @@ 活动详情 - + @@ -76,7 +76,7 @@ - + 比赛方法 {{ item.form }} @@ -84,7 +84,7 @@ - + 评分标准 {{ item.rule }} @@ -92,7 +92,7 @@ - + 比赛规则 {{ item.ruleForm }} @@ -125,9 +125,16 @@ diff --git a/frontend/src/pages/login/index.vue b/frontend/src/pages/login/index.vue index 873ed84..bbb6dbd 100644 --- a/frontend/src/pages/login/index.vue +++ b/frontend/src/pages/login/index.vue @@ -6,15 +6,15 @@ - + - + - + + - + - + @@ -89,8 +89,7 @@ /> - - - --> @@ -260,8 +258,7 @@ const startCountDown = () => { // 在页面挂载时恢复倒计时状态 onMounted(() => { - // 测试阶段不启用登录验证码,因此不再恢复验证码倒计时状态。 - // restoreCountdownState() + restoreCountdownState() getLatestActivityOrganizer() }) @@ -274,7 +271,6 @@ onBeforeUnmount(() => { }) // 获取登录短信验证码:手机号通过路径参数传递,接口返回 code 和 msg 表示发送结果。 -/* 测试阶段临时关闭登录验证码发送;恢复正式登录时需同时恢复验证码输入和后端校验。 const getSmsCode = () => { if (!loginForm.phone) { Toast('请输入手机号码'); @@ -295,21 +291,19 @@ const getSmsCode = () => { Toast({message: msg, theme: 'error'}) } }).catch((error) => { - Toast({message: error.message, theme: 'error'}) + Toast({message: error?.message || error?.msg || error?.errMsg || '验证码发送失败,请稍后重试', theme: 'error'}) }) } -*/ const handleLogin = () => { if (!loginForm.phone) { Toast('请输入手机号码'); return } - // 测试阶段允许已注册手机号直接登录,暂不校验验证码是否填写。 - // if (!loginForm.smsCode && loginForm.loginMethod === 'sms') { - // Toast('请输入验证码'); - // return - // } + if (!loginForm.smsCode.trim() && loginForm.loginMethod === 'sms') { + Toast('请输入验证码'); + return + } if (!loginForm.password && loginForm.loginMethod === 'password') { Toast('请输入密码'); return @@ -354,32 +348,25 @@ function navigateToRegister() { diff --git a/frontend/src/pages/profile/act.vue b/frontend/src/pages/profile/act.vue index aa57558..fbd5c80 100644 --- a/frontend/src/pages/profile/act.vue +++ b/frontend/src/pages/profile/act.vue @@ -56,7 +56,7 @@ - {{ item.coHostUnit }} + {{ item.hostUnit }} diff --git a/frontend/src/pages/profile/index.vue b/frontend/src/pages/profile/index.vue index d81b19d..ebf5137 100644 --- a/frontend/src/pages/profile/index.vue +++ b/frontend/src/pages/profile/index.vue @@ -123,7 +123,7 @@ async function updateAvatar(avatar) { - 退出登录 + 退出登录 diff --git a/frontend/src/static/img/index4-old.png b/frontend/src/static/img/index4-old.png new file mode 100644 index 0000000..f242a5e Binary files /dev/null and b/frontend/src/static/img/index4-old.png differ diff --git a/frontend/src/static/img/index4.jpg b/frontend/src/static/img/index4.jpg new file mode 100644 index 0000000..2be332c Binary files /dev/null and b/frontend/src/static/img/index4.jpg differ diff --git a/frontend/src/static/img/index4.png b/frontend/src/static/img/index4.png deleted file mode 100644 index b65a056..0000000 Binary files a/frontend/src/static/img/index4.png and /dev/null differ diff --git a/frontend/src/theme.css b/frontend/src/theme.css index f309878..73a5e5b 100644 --- a/frontend/src/theme.css +++ b/frontend/src/theme.css @@ -1,14 +1,14 @@ :root, :root[theme-mode="light"] { - --td-brand-color-1: #fff0ed; - --td-brand-color-2: #ffd8d3; - --td-brand-color-3: #ffb8b1; - --td-brand-color-4: #ff928c; - --td-brand-color-5: #ff5f5c; - --td-brand-color-6: #df3d3f; - --td-brand-color-7: #b6292d; - --td-brand-color-8: #8f121b; - --td-brand-color-9: #6b000a; - --td-brand-color-10: #490005; + --td-brand-color-1: #e8f3ff; + --td-brand-color-2: #cfe7ff; + --td-brand-color-3: #a4d2ff; + --td-brand-color-4: #70b7ff; + --td-brand-color-5: #1989fa; + --td-brand-color-6: #0873d9; + --td-brand-color-7: #005bb5; + --td-brand-color-8: #00468c; + --td-brand-color-9: #003568; + --td-brand-color-10: #002447; --td-brand-color-light: var(--td-brand-color-1); --td-brand-color-focus: var(--td-brand-color-2); --td-brand-color-disabled: var(--td-brand-color-3); @@ -119,16 +119,16 @@ } :root[theme-mode="dark"] { - --td-brand-color-1: #ff505020; - --td-brand-color-2: #6b000a; - --td-brand-color-3: #8f121b; - --td-brand-color-4: #b6292d; - --td-brand-color-5: #df3d3f; - --td-brand-color-6: #ff5050; - --td-brand-color-7: #ff928c; - --td-brand-color-8: #ffb8b1; - --td-brand-color-9: #ffd8d3; - --td-brand-color-10: #fff0ed; + --td-brand-color-1: #1989fa20; + --td-brand-color-2: #003568; + --td-brand-color-3: #00468c; + --td-brand-color-4: #005bb5; + --td-brand-color-5: #0873d9; + --td-brand-color-6: #1989fa; + --td-brand-color-7: #70b7ff; + --td-brand-color-8: #a4d2ff; + --td-brand-color-9: #cfe7ff; + --td-brand-color-10: #e8f3ff; --td-brand-color-light: var(--td-brand-color-1); --td-brand-color-focus: var(--td-brand-color-2); --td-brand-color-disabled: var(--td-brand-color-3);