feat:优化初版
This commit is contained in:
@@ -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("密码不正确");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -59,6 +59,7 @@ public class ClientActivityController {
|
||||
t1.signStartTime,
|
||||
t1.signEndTime,
|
||||
t1.coHostUnit,
|
||||
t1.hostUnit,
|
||||
t1.cover
|
||||
FROM
|
||||
activity t1
|
||||
|
||||
-1
@@ -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<NutMap> list = clientActivityService.listMap(sql);
|
||||
return Result.success(list);
|
||||
|
||||
+22
-1
@@ -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<Activity> 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<Activity> 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<Activity> 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<Activity> 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<Activity> 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<Activity> 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<Activity> 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());
|
||||
|
||||
+14
@@ -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) {
|
||||
|
||||
@@ -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<NutMap> nutMaps = actUserService.listMap(sql);
|
||||
// 历史表列名为ProjectType,统一导出键名供项目排序及名称转换使用。
|
||||
for (NutMap row : nutMaps) {
|
||||
if (row.getString("projectType", "").isEmpty()) {
|
||||
row.put("projectType", row.getString("ProjectType", ""));
|
||||
}
|
||||
}
|
||||
// 导出按单位、领队、教练、项目、男女分组;人员类型按字典名称解析,兼任类型也参与排序。
|
||||
Map<String, Integer> 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<String, String> 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<ExcelExportEntity> 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));
|
||||
|
||||
+4
-3
@@ -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;
|
||||
|
||||
@@ -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("出生日期")
|
||||
|
||||
@@ -24,6 +24,14 @@ public interface ActGroupService extends BaseService<ActGroup> {
|
||||
*/
|
||||
void groupSetting(ActGroupVo groupVo);
|
||||
|
||||
/**
|
||||
* 按单位重建活动全部有效报名人员的分组,同一单位只生成一个组。
|
||||
*
|
||||
* @param activityId 活动ID;不限制人员签到状态
|
||||
* @throws com.budwk.app.base.exception.BaseException 活动或报名数据不满足分组条件时抛出
|
||||
*/
|
||||
void groupByUnit(Long activityId);
|
||||
|
||||
/**
|
||||
* 设置未分组人员
|
||||
* @param unGroupUserVo
|
||||
|
||||
+9
@@ -54,6 +54,15 @@ public interface ActivityRegistrationFieldService extends BaseService<ActRegistr
|
||||
*/
|
||||
boolean isValidPersonTypeCombination(String value);
|
||||
|
||||
/**
|
||||
* 根据字典名称判断多选人员类型是否满足字段联动条件。
|
||||
*
|
||||
* @param personType 逗号分隔的人员类型字典编码
|
||||
* @param labels 逗号分隔的人员类型名称,匹配任意一项即满足条件
|
||||
* @return 已选人员类型中包含目标类型时返回true
|
||||
*/
|
||||
boolean matchesPersonTypes(String personType, String labels);
|
||||
|
||||
/**
|
||||
* 获取同一活动、同一单位的人员类型人数限制状态。
|
||||
*
|
||||
|
||||
+58
@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdcardUtil;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.module.activity.models.*;
|
||||
import com.budwk.app.module.activity.service.ActGroupService;
|
||||
import com.budwk.app.module.activity.vo.ActGroupVo;
|
||||
@@ -16,6 +17,7 @@ import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -133,6 +135,62 @@ public class ActGroupServiceImpl extends BaseServiceImpl<ActGroup> 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<ActUser> 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<String, List<ActUser>> 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<ActivityItem> 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<ActUser> 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) {
|
||||
|
||||
+71
-11
@@ -51,12 +51,41 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl<ActReg
|
||||
.collect(Collectors.toMap(ActRegistrationField::getFieldKey, item -> 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<NutMap> 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<ActReg
|
||||
continue;
|
||||
}
|
||||
boolean required = stored == null ? definition.isDefaultRequired() : Boolean.TRUE.equals(stored.getRequired());
|
||||
int sort = stored == null || stored.getSort() == null ? defaultSort : stored.getSort();
|
||||
int sort = stored == null || stored.getSort() == null
|
||||
? buildDefaultField(definition).getSort() : stored.getSort();
|
||||
NutMap item = NutMap.NEW()
|
||||
.addv("fieldKey", definition.getFieldKey())
|
||||
.addv("fieldLabel", definition.getFieldLabel())
|
||||
.addv("componentType", definition.getComponentType())
|
||||
.addv("selected", selected)
|
||||
.addv("required", definition.isLocked() || required)
|
||||
.addv("required", definition.isLocked() || projectTypeField || required)
|
||||
.addv("locked", definition.isLocked())
|
||||
.addv("sort", sort)
|
||||
.addv("parentField", definition.getParentField())
|
||||
@@ -130,16 +160,13 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl<ActReg
|
||||
RegistrationFieldDefinition definition = RegistrationFieldDefinition.fromKey(field.getFieldKey());
|
||||
field.setId(null);
|
||||
field.setActivityId(activityId);
|
||||
field.setRequired(definition.isLocked() || Boolean.TRUE.equals(field.getRequired()));
|
||||
field.setSort(field.getSort() == null ? defaultSort : field.getSort());
|
||||
field.setRequired(definition.isLocked() || definition == RegistrationFieldDefinition.PROJECT_TYPE || Boolean.TRUE.equals(field.getRequired()));
|
||||
field.setSort(field.getSort() == null ? buildDefaultField(definition).getSort() : field.getSort());
|
||||
saveList.add(field);
|
||||
defaultSort++;
|
||||
}
|
||||
saveList.sort(Comparator.comparing(ActRegistrationField::getSort));
|
||||
// 允许管理端用数字快速调整顺序;保存时归一化为连续序号,避免重复排序值导致展示不稳定。
|
||||
for (int index = 0; index < saveList.size(); index++) {
|
||||
saveList.get(index).setSort(index + 1);
|
||||
}
|
||||
// 保留配置序号,避免未启用字段被跳过后将身份证的第6位重新编号。
|
||||
dao().insert(saveList);
|
||||
}
|
||||
|
||||
@@ -167,6 +194,22 @@ public class ActivityRegistrationFieldServiceImpl extends BaseServiceImpl<ActReg
|
||||
return splitMultiValue(value).stream().allMatch(enabledValues::contains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典名称判断多选人员类型是否满足字段联动条件。
|
||||
*
|
||||
* @param personType 逗号分隔的人员类型字典编码
|
||||
* @param labels 逗号分隔的人员类型名称,匹配任意一项即满足条件
|
||||
* @return 已选人员类型中包含目标类型时返回true
|
||||
*/
|
||||
@Override
|
||||
public boolean matchesPersonTypes(String personType, String labels) {
|
||||
List<String> selected = splitMultiValue(personType);
|
||||
List<String> 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<ActReg
|
||||
}
|
||||
}
|
||||
if (mixedMaleCount != mixedFemaleCount) {
|
||||
throw new BaseException("混合双打须按一男一女配对,请按规则重新选择");
|
||||
String currentSelection = "当前已选择" + mixedMaleCount + "名男选手、" + mixedFemaleCount + "名女选手";
|
||||
if (mixedMaleCount == 2 && mixedFemaleCount == 0) {
|
||||
currentSelection = "当前两名选手均为男选手,未选择女选手";
|
||||
} else if (mixedMaleCount == 0 && mixedFemaleCount == 2) {
|
||||
currentSelection = "当前两名选手均为女选手,未选择男选手";
|
||||
}
|
||||
// 差额按当前全部混双报名人数计算,明确未能配对的性别和人数。
|
||||
String missingSex = mixedMaleCount > 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<ActReg
|
||||
ActRegistrationField field = new ActRegistrationField();
|
||||
field.setFieldKey(definition.getFieldKey());
|
||||
field.setRequired(definition.isDefaultRequired());
|
||||
field.setSort(definition.ordinal() + 1);
|
||||
field.setSort(switch (definition) {
|
||||
case UNIT_NAME -> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -261,9 +261,12 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> 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;
|
||||
|
||||
@@ -62,6 +62,7 @@ layout("/layouts/platform_1.html"){
|
||||
<el-card shadow="never" class="mt-3">
|
||||
<el-row type="flex" style="justify-content: flex-start" class="mb-3">
|
||||
<el-button type="primary" size="small" @click="openGrouping">开始分组</el-button>
|
||||
<el-button type="primary" size="small" :loading="unitGrouping" @click="groupByUnit">按单位分组</el-button>
|
||||
<!-- <div v-if="unGroupUser && unGroupUser.length > 0" style="margin-left: 10px; display: inline-block;">-->
|
||||
<!-- <el-button type="warning" size="small" style="position: relative;" @click="doUnGroupUserSetting">-->
|
||||
<!-- 未分组人员-->
|
||||
@@ -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) {
|
||||
|
||||
+34
-26
@@ -1,33 +1,33 @@
|
||||
const registrationFieldSetting = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-drawer
|
||||
title="设置报名信息"
|
||||
:visible.sync="visible"
|
||||
size="58%"
|
||||
:wrapper-closable="false"
|
||||
:before-close="handleClose">
|
||||
title="设置报名信息"
|
||||
:visible.sync="visible"
|
||||
size="58%"
|
||||
:wrapper-closable="false"
|
||||
:before-close="handleClose">
|
||||
<div class="registration-field-setting" v-loading="loading">
|
||||
<el-alert
|
||||
title="姓名、联系电话和单位名称为系统固定字段;关联字段会自动保持完整的父子关系。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon>
|
||||
title="身份证、姓名、联系电话和单位名称为系统固定字段;关联字段会自动保持完整的父子关系。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon>
|
||||
</el-alert>
|
||||
<el-alert
|
||||
v-if="projectTypeRuleMessage"
|
||||
:title="projectTypeRuleMessage"
|
||||
:type="projectTypeConfigurable ? 'success' : 'warning'"
|
||||
class="mt-3"
|
||||
:closable="false"
|
||||
show-icon>
|
||||
v-if="projectTypeRuleMessage"
|
||||
:title="projectTypeRuleMessage"
|
||||
:type="projectTypeConfigurable ? 'success' : 'warning'"
|
||||
class="mt-3"
|
||||
:closable="false"
|
||||
show-icon>
|
||||
</el-alert>
|
||||
<el-table :data="fieldList" class="mt-3" border>
|
||||
<el-table-column label="显示" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-checkbox
|
||||
v-model="scope.row.selected"
|
||||
:disabled="scope.row.locked || scope.row.configurable === false"
|
||||
@change="(val) => onSelectedChange(scope.row, val)">
|
||||
v-model="scope.row.selected"
|
||||
:disabled="scope.row.locked || scope.row.configurable === false"
|
||||
@change="(val) => onSelectedChange(scope.row, val)">
|
||||
</el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -45,20 +45,20 @@ const registrationFieldSetting = {
|
||||
<el-table-column label="必填" width="90" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.required"
|
||||
:disabled="!scope.row.selected || scope.row.locked || scope.row.configurable === false">
|
||||
v-model="scope.row.required"
|
||||
:disabled="!scope.row.selected || scope.row.locked || scope.row.fieldKey === 'projectType' || scope.row.configurable === false">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="顺序" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.sort"
|
||||
:disabled="!scope.row.selected || scope.row.configurable === false"
|
||||
:min="1"
|
||||
:max="99"
|
||||
controls-position="right"
|
||||
size="small">
|
||||
v-model="scope.row.sort"
|
||||
:disabled="!scope.row.selected || scope.row.configurable === false"
|
||||
:min="0"
|
||||
:max="99"
|
||||
controls-position="right"
|
||||
size="small">
|
||||
</el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -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 "-"
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user