整改亲子活动报名规则
This commit is contained in:
+7
-31
@@ -521,6 +521,12 @@ public class FamilyActivityApplyController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交当前登录教职工的报名,不接受客户端指定报名用户或已有报名ID。
|
||||
* @param familyUser courseId为子活动ID,mobile为联系方式,mobileColumnsValue为家属动态字段二维列表,
|
||||
* 独立报名课程还需activityCourseId时段ID;活动归属及用户由service确定
|
||||
* @return Result JSON,code为0表示正常或候补报名成功,msg说明结果或未通过的规则
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("活动报名")
|
||||
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
|
||||
@@ -529,37 +535,7 @@ public class FamilyActivityApplyController {
|
||||
public Result doSignUp(FamilyUser familyUser) {
|
||||
try {
|
||||
lock.lock();
|
||||
FamilyActivity activity = dao.fetch(FamilyActivity.class, familyUser.getActivityId());
|
||||
if(Lang.isEmpty(familyUser.getMobileColumnsValue())) {
|
||||
return Result.error(99,"请填写" + activity.getKeyWord() + "信息");
|
||||
}
|
||||
boolean courseByUser = familyActivityService.isSignCourseByUser(familyUser.getCourseId(), SecurityUtil.getUserId());
|
||||
if(courseByUser) {
|
||||
return Result.error(99,"您已报过该活动");
|
||||
}
|
||||
|
||||
Map<Boolean, String> validFamilyCount = familyActivityService.validFamilyCount(familyUser);
|
||||
if(validFamilyCount.containsKey(false)) {
|
||||
return Result.error(99,validFamilyCount.get(false));
|
||||
}
|
||||
// 判断人数
|
||||
int number = 0;
|
||||
FamilyCourse course = familyActivityService.dao().fetch(FamilyCourse.class, familyUser.getCourseId());
|
||||
FamilyType type = familyActivityService.dao().fetch(FamilyType.class, course.getCourseType());
|
||||
if(type != null) {
|
||||
if(type.getIsBringFamily() && type.getIsAddFamily()) {
|
||||
List<List<NutMap>> mobileColumnsValue = familyUser.getMobileColumnsValue();
|
||||
number = Lang.isNotEmpty(mobileColumnsValue) ? mobileColumnsValue.size() : 0;
|
||||
}
|
||||
}
|
||||
boolean signFull = familyActivityService.isSignFull(course, number);
|
||||
if(signFull) {
|
||||
return Result.error(99,"当前报名人数已满");
|
||||
}
|
||||
boolean signFullByUnionId = familyActivityService.isSignFullByUnionId(course, number);
|
||||
if(signFullByUnionId) {
|
||||
return Result.error(99,"该活动您所在的分工会名额不足");
|
||||
}
|
||||
// 家属、名额和时间冲突由service在数据库锁及事务内统一校验,避免普通报名与快速报名规则分叉。
|
||||
FamilyUser finishUser = familyActivityService.doSignUp(familyUser);
|
||||
if(finishUser.getState() == 2) {
|
||||
return Result.success("正式报名已满,您已列为替补,如有老师放弃报名,将会按先后顺序依次替补。请您自行关注后续结果。谢谢!");
|
||||
|
||||
+9
@@ -13,6 +13,8 @@ import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
@@ -144,11 +146,18 @@ public class FamilyActivityController {
|
||||
return Result.success().addData(dataMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或发布活动配置,业务校验由service执行。
|
||||
* @param activity 活动及子活动列表;familyCountScope为1按类型、2按活动,timeConflictCheck为重叠校验开关,
|
||||
* onlyKey为各类型共有且必填的字段编码,familyMaxCount为活动累计上限
|
||||
* @return Result JSON,code为0表示保存成功,配置不完整时返回具体业务错误
|
||||
*/
|
||||
@At
|
||||
@Ok("json:full")
|
||||
@ApiOperation("亲子活动新增/修改")
|
||||
@SaCheckPermission("family.manage")
|
||||
@SLog(tag = "亲子活动-活动管理", msg = "新增/修改活动")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result doHandle(FamilyActivity activity) {
|
||||
if (StrUtil.isBlank(activity.getId())) {
|
||||
familyActivityManageService.add(activity, null);
|
||||
|
||||
+30
-44
@@ -1,8 +1,10 @@
|
||||
package com.budwk.app.zhgh.activity.family.controller.manage;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -24,7 +26,6 @@ import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -118,59 +119,44 @@ public class FamilyAdjustController {
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activityId 当前活动ID
|
||||
* @param oldCourseId 人员原报名子活动ID
|
||||
* @param newCourseId 同活动内的目标子活动ID
|
||||
* @param userId 被调整教职工ID,按该人员而非管理员校验家属和时间规则
|
||||
* @return Result JSON;code为0表示调整成功,失败时msg说明原因且保留原报名
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("人员调整")
|
||||
@SaCheckPermission("family.adjust")
|
||||
@SLog(tag = "亲子活动-人员调整", msg = "人员调整")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result adjust(String activityId, String oldCourseId, String newCourseId, String userId) {
|
||||
|
||||
Cnd oldCnd = Cnd.where("activityId", "=", activityId)
|
||||
.and("courseId", "=", oldCourseId).and("userId", "=", userId);
|
||||
|
||||
Cnd newCnd = Cnd.where("activityId", "=", activityId)
|
||||
.and("courseId", "=", newCourseId).and("userId", "=", userId);
|
||||
|
||||
//旧的报名信息
|
||||
FamilyUser oldfamilyUser = dao.fetch(FamilyUser.class, oldCnd);
|
||||
oldfamilyUser.setCourseId(newCourseId);
|
||||
oldfamilyUser.setSignUpTime(DateUtil.date());
|
||||
dao.update(oldfamilyUser);
|
||||
|
||||
//新的课程的信息,上课时间
|
||||
List<FamilyActivityCourse> activityCourseList = dao.query(FamilyActivityCourse.class, Cnd.where("activityId", "=", activityId)
|
||||
.and("courseId", "=", newCourseId));
|
||||
//先清楚旧的信息
|
||||
dao.clear(FamilyUserCourse.class, oldCnd);
|
||||
//添加新的信息
|
||||
List<FamilyUserCourse> familyUserCourseList = new ArrayList<>();
|
||||
activityCourseList.forEach(item -> {
|
||||
FamilyUserCourse course = new FamilyUserCourse();
|
||||
course.setActivityCourseId(activityId);
|
||||
course.setCourseId(newCourseId);
|
||||
course.setUserId(userId);
|
||||
course.setCourseStartTime(item.getCourseStartTime());
|
||||
course.setCourseEndTime(item.getCourseEndTime());
|
||||
course.setActivityCourseId(item.getId());
|
||||
familyUserCourseList.add(course);
|
||||
});
|
||||
dao.insert(familyUserCourseList);
|
||||
return Result.success();
|
||||
try {
|
||||
familyActivityManageService.adjustSignUp(activityId, oldCourseId, newCourseId, userId);
|
||||
return Result.success();
|
||||
} catch (BaseException e) {
|
||||
return Result.error(99, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activityId 当前活动ID
|
||||
* @param courseId 待删除报名所属子活动ID
|
||||
* @param userId 被删除报名的教职工ID
|
||||
* @return Result JSON;code为0表示对应报名及其时段记录已删除
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("删除报名人员")
|
||||
@SaCheckPermission("family.adjust")
|
||||
@SLog(tag = "亲子活动-人员调整", msg = "删除报名人员")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result deleteSignUser(String activityId, String courseId, String userId) {
|
||||
|
||||
//删除
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("activityId", "=", activityId);
|
||||
cnd.and("courseId", "=", courseId);
|
||||
cnd.and("userId", "=", userId);
|
||||
|
||||
dao.clear(FamilyUser.class, cnd);
|
||||
dao.clear(FamilyUserCourse.class, cnd);
|
||||
return Result.success();
|
||||
try {
|
||||
familyActivityManageService.deleteSignUser(activityId, courseId, userId);
|
||||
return Result.success();
|
||||
} catch (BaseException e) {
|
||||
return Result.error(99, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,18 @@ public class FamilyActivity extends BaseModel implements Serializable, SysHomeCo
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer familyMaxCount;
|
||||
|
||||
@Column
|
||||
@Comment("家属人数限制范围:1按子活动类型累计,2按整个活动累计;历史空值按1处理")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Default("1")
|
||||
private Integer familyCountScope;
|
||||
|
||||
@Column
|
||||
@Comment("是否禁止同一教职工报名本活动内时间重叠的子活动,含候补报名")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean timeConflictCheck;
|
||||
|
||||
@Column
|
||||
@Comment("判断家属数量的唯一标识")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
|
||||
+22
-2
@@ -76,6 +76,25 @@ public interface FamilyActivityService extends BaseService<FamilyActivity> {
|
||||
*/
|
||||
void cancelSignUp(String activityId, String courseId);
|
||||
|
||||
/**
|
||||
* 后台将指定教职工的正常报名调整到同一活动的另一子活动,复用家属及时间冲突规则。
|
||||
* @param activityId 活动ID,原课程与目标课程必须属于该活动
|
||||
* @param oldCourseId 原课程ID
|
||||
* @param newCourseId 目标课程ID;独立时段课程须只有一个可确定的时段
|
||||
* @param userId 被调整教职工的用户ID,不使用管理员身份参与统计
|
||||
* @return 无返回值;不满足限制时抛出业务异常,原报名不变
|
||||
*/
|
||||
void adjustSignUp(String activityId, String oldCourseId, String newCourseId, String userId);
|
||||
|
||||
/**
|
||||
* 后台删除指定教职工的报名,与报名和调班共用数据库锁,避免并发统计读取到中间状态。
|
||||
* @param activityId 活动ID
|
||||
* @param courseId 课程ID
|
||||
* @param userId 被删除报名的教职工ID
|
||||
* @return 无返回值;仅删除匹配的报名及其课程时段记录
|
||||
*/
|
||||
void deleteSignUser(String activityId, String courseId, String userId);
|
||||
|
||||
/**
|
||||
* 查询当前用户在指定课程中的预填信息。
|
||||
*
|
||||
@@ -120,7 +139,8 @@ public interface FamilyActivityService extends BaseService<FamilyActivity> {
|
||||
FamilyUser getSignUpInfo(String courseId, String userId);
|
||||
|
||||
/**
|
||||
* 校验当前用户是否可以修改指定课程的报名信息。
|
||||
* 打开编辑表单前校验当前用户报名归属、报名时间和人员范围。
|
||||
* 家属、人数和时间冲突在保存新数据时重新校验,允许历史冲突记录进入表单整改。
|
||||
*
|
||||
* @param courseId 课程ID;当前用户必须已经报名该课程
|
||||
* @return 无返回值;校验不通过时抛出包含具体原因的业务异常
|
||||
@@ -166,7 +186,7 @@ public interface FamilyActivityService extends BaseService<FamilyActivity> {
|
||||
boolean isSignFull(FamilyCourse course, Integer currentFamilyNumber);
|
||||
|
||||
/**
|
||||
* 校验本次提交的家属数量、唯一标识及分场冲突。
|
||||
* 校验本次提交的类型家属上限、活动累计家属上限、唯一标识及分场冲突。
|
||||
* 当参数包含报名记录ID时,会排除该记录后再校验,用于修改报名。
|
||||
*
|
||||
* @param user 待校验的报名信息
|
||||
|
||||
+322
-12
@@ -48,10 +48,74 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增、沿用和编辑活动共用配置校验。activity 为页面提交的完整活动,oldActivity 为编辑前记录。
|
||||
* 唯一标识必须使用所有课程类型共有且必填的字段,不限定验证规则,不改写历史字段编码。
|
||||
* 返回值为空;字段缺失、编码不一致或历史数据可能失联时抛出可定位类型的业务异常。
|
||||
*/
|
||||
private void validateActivityRuleConfig(FamilyActivity activity, FamilyActivity oldActivity) {
|
||||
if (activity.getFamilyCountScope() == null) {
|
||||
activity.setFamilyCountScope(1);
|
||||
}
|
||||
if (activity.getFamilyCountScope() != 1 && activity.getFamilyCountScope() != 2) {
|
||||
throw new BaseException("家属人数限制范围不正确");
|
||||
}
|
||||
boolean activityScope = Objects.equals(activity.getFamilyCountScope(), 2);
|
||||
if (activityScope && (activity.getFamilyMaxCount() == null || activity.getFamilyMaxCount() < 1)) {
|
||||
throw new BaseException("按整个活动累计时,家属最多数必须为正整数");
|
||||
}
|
||||
boolean keyChanged = oldActivity == null || !Objects.equals(oldActivity.getOnlyKey(), activity.getOnlyKey());
|
||||
if (oldActivity != null && keyChanged
|
||||
&& (dao().count(FamilyUser.class, Cnd.where("activityId", "=", activity.getId())) > 0
|
||||
|| dao().count(FamilyApplyPrefill.class, Cnd.where("activityId", "=", activity.getId())) > 0)) {
|
||||
throw new BaseException("活动已有报名或预填信息,不能直接更换唯一标识,请先核对历史数据");
|
||||
}
|
||||
List<FamilyCourse> courses = activity.getCourseList();
|
||||
if (Lang.isEmpty(courses)) {
|
||||
if (activityScope || Boolean.TRUE.equals(activity.getTimeConflictCheck())) {
|
||||
throw new BaseException("启用报名规则前请先配置子活动");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 已选唯一标识即检查所有参与类型,即使编码未更换也必须满足共有与必填要求。
|
||||
boolean checkKey = activityScope || StrUtil.isNotBlank(activity.getOnlyKey());
|
||||
if (activityScope && StrUtil.isBlank(activity.getOnlyKey())) {
|
||||
throw new BaseException("请选择各子活动类型共有且必填的字段作为唯一标识");
|
||||
}
|
||||
for (FamilyCourse course : courses) {
|
||||
if (checkKey) {
|
||||
FamilyType type = StrUtil.isBlank(course.getCourseType()) ? null
|
||||
: dao().fetch(FamilyType.class, course.getCourseType());
|
||||
if (type == null) {
|
||||
throw new BaseException("请为子活动配置有效类型:" + course.getCourseName());
|
||||
}
|
||||
List<FamilyMobileSignColumn> columns = dao().query(FamilyMobileSignColumn.class,
|
||||
Cnd.where("typeId", "=", type.getId()).and("columnCode", "=", activity.getOnlyKey()));
|
||||
if (columns.size() != 1 || !Boolean.TRUE.equals(columns.get(0).getIsRequired())) {
|
||||
throw new BaseException("类型【" + type.getTypeName() + "】必须配置同编码且必填的唯一标识字段:"
|
||||
+ activity.getOnlyKey());
|
||||
}
|
||||
}
|
||||
if (Boolean.TRUE.equals(activity.getTimeConflictCheck())) {
|
||||
if (Lang.isEmpty(course.getCourseTimeList())) {
|
||||
throw new BaseException("启用时间冲突校验前请配置子活动时间:" + course.getCourseName());
|
||||
}
|
||||
for (FamilyActivityCourse time : course.getCourseTimeList()) {
|
||||
if (time == null || time.getCourseDate() == null || time.getCourseStartTime() == null
|
||||
|| time.getCourseEndTime() == null
|
||||
|| DateUtil.format(time.getCourseStartTime(), "HH:mm").compareTo(
|
||||
DateUtil.format(time.getCourseEndTime(), "HH:mm")) >= 0) {
|
||||
throw new BaseException("子活动日期或起止时间不完整、无效:" + course.getCourseName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void add(FamilyActivity activity, FamilyCourse course) {
|
||||
|
||||
validateActivityRuleConfig(activity, null);
|
||||
dao().insert(activity);
|
||||
|
||||
//插入类型限制
|
||||
@@ -81,7 +145,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void edit(FamilyActivity activity) {
|
||||
|
||||
validateActivityRuleConfig(activity, dao().fetch(FamilyActivity.class, activity.getId()));
|
||||
//修改活动
|
||||
update(activity);
|
||||
|
||||
@@ -155,6 +219,8 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
}
|
||||
}
|
||||
|
||||
/** 将页面的课程日期与时分合成为实际上课时间,保存到同一事务的课程时段表。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void setCourseTimeAndInsert(FamilyCourse course) {
|
||||
course.getCourseTimeList().forEach(courseTime -> {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
@@ -254,7 +320,13 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public FamilyUser doSignUp(FamilyUser familyUser) throws Exception {
|
||||
String userId = SecurityUtil.getUserId();
|
||||
|
||||
if (familyUser == null) {
|
||||
throw new BaseException("报名信息不能为空");
|
||||
}
|
||||
// 先锁教职工,再锁课程;跨课程报名也必须串行校验同一活动的家属总数和时间。
|
||||
lockSignUpUser(userId);
|
||||
familyUser.setId(null);
|
||||
familyUser.setUserId(userId);
|
||||
// 对课程行加数据库锁,使名额校验和正式写入在多节点部署时仍按顺序执行。
|
||||
lockCourseForSignUp(familyUser.getCourseId());
|
||||
//查询课程
|
||||
@@ -272,6 +344,10 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
|
||||
validateSignUpTimeAndScope(activity, userId, false);
|
||||
familyUser.setActivityId(course.getActivityId());
|
||||
if (!Boolean.TRUE.equals(course.getCourseIsLimitApply())) {
|
||||
// 非独立时段课程必须参加全部时段,忽略客户端多传的单个时段ID。
|
||||
familyUser.setActivityCourseId(null);
|
||||
}
|
||||
if (getSignUpInfo(course.getId(), userId) != null) {
|
||||
throw new BaseException("抱歉,您已经报名");
|
||||
}
|
||||
@@ -298,6 +374,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
: "您选择的类型已达上限,不能再报该类型的了");
|
||||
}
|
||||
validateCourseTime(course, familyUser.getActivityCourseId(), null);
|
||||
validateTimeConflict(familyUser, course, activity, userId);
|
||||
//如果这个课程的预留名额方式为报名人数不变
|
||||
if (course.getReserveMode() == 2) {
|
||||
//如果当前报名+已报小于这个课程限制人数
|
||||
@@ -345,6 +422,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
throw new BaseException("取消报名参数不完整");
|
||||
}
|
||||
|
||||
lockSignUpUser(SecurityUtil.getUserId());
|
||||
// 与正式报名共用课程行锁,保证删除、重新统计和候补转正在同一串行区间内完成。
|
||||
lockCourseForSignUp(courseId);
|
||||
FamilyCourse course = dao().fetch(FamilyCourse.class, courseId);
|
||||
@@ -378,6 +456,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
* 按报名时间顺序从全部分工会候补中查找当前可容纳的人员。
|
||||
* 本人和家属是否计入总人数,严格按课程类型配置计算。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void promoteWaitingUsers(FamilyCourse course) {
|
||||
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
|
||||
if (type == null) {
|
||||
@@ -459,6 +538,78 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
&& DateUtil.compare(now, activity.getActivitySignUpEndTime()) < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void adjustSignUp(String activityId, String oldCourseId, String newCourseId, String userId) {
|
||||
if (StrUtil.hasBlank(activityId, oldCourseId, newCourseId, userId)) {
|
||||
throw new BaseException("人员调整参数不完整");
|
||||
}
|
||||
lockSignUpUser(userId);
|
||||
// 调班涉及两门课,固定按ID排序加锁,避免不同教职工反向调班时死锁。
|
||||
List.of(oldCourseId, newCourseId).stream().distinct().sorted().forEach(this::lockCourseForSignUp);
|
||||
FamilyUser original = dao().fetch(FamilyUser.class, Cnd.where("activityId", "=", activityId)
|
||||
.and("courseId", "=", oldCourseId).and("userId", "=", userId));
|
||||
if (original == null || (!Objects.equals(original.getState(), 1) && !Objects.equals(original.getState(), 3))) {
|
||||
throw new BaseException("只允许调整当前有效的正常报名");
|
||||
}
|
||||
FamilyCourse source = dao().fetch(FamilyCourse.class, oldCourseId);
|
||||
FamilyCourse target = dao().fetch(FamilyCourse.class, newCourseId);
|
||||
if (!Objects.equals(source.getActivityId(), activityId) || !Objects.equals(target.getActivityId(), activityId)) {
|
||||
throw new BaseException("原子活动和目标子活动必须属于当前活动");
|
||||
}
|
||||
if (Objects.equals(oldCourseId, newCourseId)) {
|
||||
return;
|
||||
}
|
||||
if (getSignUpInfo(newCourseId, userId) != null) {
|
||||
throw new BaseException("该教职工已经报名目标子活动");
|
||||
}
|
||||
FamilyActivity activity = dao().fetch(FamilyActivity.class, activityId);
|
||||
FamilyType type = dao().fetch(FamilyType.class, target.getCourseType());
|
||||
if (activity == null || type == null) {
|
||||
throw new BaseException("活动或目标子活动类型不存在");
|
||||
}
|
||||
original.setCourseId(newCourseId);
|
||||
original.setActivityCourseId(null);
|
||||
if (Boolean.TRUE.equals(target.getCourseIsLimitApply())) {
|
||||
List<FamilyActivityCourse> times = dao().query(FamilyActivityCourse.class, Cnd.where("courseId", "=", newCourseId));
|
||||
if (times.size() != 1) {
|
||||
throw new BaseException("目标子活动包含多个独立报名时段,请通过报名表单明确选择时段");
|
||||
}
|
||||
original.setActivityCourseId(times.get(0).getId());
|
||||
}
|
||||
original.setMobileColumnsValue(normalizeAndValidateFamilyColumns(original.getMobileColumnsValue(), type, target, activity));
|
||||
Map<Boolean, String> result = validFamilyCount(original, userId);
|
||||
if (result.containsKey(false)) {
|
||||
throw new BaseException(result.get(false));
|
||||
}
|
||||
validateTimeConflict(original, target, activity, userId);
|
||||
validateCourseTime(target, original.getActivityCourseId(), null);
|
||||
int contribution = getFamilyContribution(type, original.getMobileColumnsValue().size());
|
||||
if (statisticsService.queryCourseCount(newCourseId, target.getCourseType()) + contribution
|
||||
+ target.getCourseReservedNumber() > target.getCoursePeopleNumber() || target.getCoursePeopleNumber() == 0) {
|
||||
throw new BaseException("目标子活动名额不足");
|
||||
}
|
||||
original.setSignUpTime(new Date());
|
||||
dao().update(original);
|
||||
// 调班后的所有校验通过才删除旧时段,异常时事务回滚,保留原报名。
|
||||
dao().clear(FamilyUserCourse.class, Cnd.where("activityId", "=", activityId)
|
||||
.and("courseId", "=", oldCourseId).and("userId", "=", userId));
|
||||
rebuildUserCourseTimes(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteSignUser(String activityId, String courseId, String userId) {
|
||||
if (StrUtil.hasBlank(activityId, courseId, userId)) {
|
||||
throw new BaseException("删除报名参数不完整");
|
||||
}
|
||||
lockSignUpUser(userId);
|
||||
lockCourseForSignUp(courseId);
|
||||
Cnd cnd = Cnd.where("activityId", "=", activityId).and("courseId", "=", courseId).and("userId", "=", userId);
|
||||
dao().clear(FamilyUserCourse.class, cnd);
|
||||
dao().clear(FamilyUser.class, cnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FamilyApplyPrefill getPrefill(String courseId, String userId) {
|
||||
if (StrUtil.isBlank(courseId) || StrUtil.isBlank(userId)) {
|
||||
@@ -581,7 +732,13 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
if (currentSignUp == null) {
|
||||
throw new BaseException("未找到本人在该课程的报名信息");
|
||||
}
|
||||
validateUpdateSignUpData(currentSignUp, currentSignUp);
|
||||
// 打开编辑表单只校验归属和报名时间,让历史冲突数据仍可进入表单整改;保存时校验新数据。
|
||||
FamilyCourse course = dao().fetch(FamilyCourse.class, courseId);
|
||||
if (course == null || !Objects.equals(course.getActivityId(), currentSignUp.getActivityId())) {
|
||||
throw new BaseException("报名课程不存在或已变更");
|
||||
}
|
||||
validateSignUpTimeAndScope(dao().fetch(FamilyActivity.class, course.getActivityId()),
|
||||
SecurityUtil.getUserId(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -592,6 +749,8 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
}
|
||||
|
||||
String currentUserId = SecurityUtil.getUserId();
|
||||
lockSignUpUser(currentUserId);
|
||||
lockCourseForSignUp(familyUser.getCourseId());
|
||||
FamilyUser currentSignUp = getSignUpInfo(familyUser.getCourseId(), currentUserId);
|
||||
if (currentSignUp == null || !Objects.equals(currentSignUp.getId(), familyUser.getId())) {
|
||||
throw new BaseException("未找到本人在该课程的报名信息");
|
||||
@@ -617,6 +776,9 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
}
|
||||
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
|
||||
validateSignUpTimeAndScope(activity, SecurityUtil.getUserId(), true);
|
||||
if (!Boolean.TRUE.equals(course.getCourseIsLimitApply())) {
|
||||
familyUser.setActivityCourseId(null);
|
||||
}
|
||||
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
|
||||
if (type == null) {
|
||||
throw new BaseException("报名类型不存在或已变更");
|
||||
@@ -651,6 +813,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
: "您选择的类型已达上限,不能再报该类型的了");
|
||||
}
|
||||
validateCourseTime(course, familyUser.getActivityCourseId(), currentSignUp);
|
||||
validateTimeConflict(familyUser, course, activity, SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -701,6 +864,22 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定实际报名教职工的账号行,覆盖多节点、PC/H5和快速报名的跨课程并发。
|
||||
* userId 必须由登录态或已校验的后台人员调整参数提供,所有写入口统一先锁账号再锁课程。
|
||||
*/
|
||||
private void lockSignUpUser(String userId) {
|
||||
if (StrUtil.isBlank(userId)) {
|
||||
throw new BaseException("报名人员不能为空");
|
||||
}
|
||||
Sql sql = Sqls.create("SELECT id FROM sys_user WHERE id = @userId FOR UPDATE");
|
||||
sql.setParam("userId", userId);
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
if (dao().execute(sql).getResult() == null) {
|
||||
throw new BaseException("报名人员不存在或已删除");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预填新增、修改和删除共用时间及人员范围校验。
|
||||
* 预填只允许发生在报名开始时间之前,到达开始时间后数据立即冻结。
|
||||
@@ -775,6 +954,14 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
for (FamilyMobileSignColumn config : columnConfigs) {
|
||||
NutMap submittedColumn = submittedValueMap.get(config.getColumnCode());
|
||||
Object columnValue = submittedColumn == null ? null : submittedColumn.get("columnValue");
|
||||
if (Objects.equals(activity.getFamilyCountScope(), 2)
|
||||
&& Objects.equals(activity.getOnlyKey(), config.getColumnCode())) {
|
||||
if (!Boolean.TRUE.equals(config.getIsRequired())) {
|
||||
throw new BaseException("活动累计家属人数需要同编码且必填的唯一标识字段,请联系管理员核对类型配置");
|
||||
}
|
||||
// 姓名等普通字段保留大小写;仅有效身份证号统一末位X,历史统计采用相同规则。
|
||||
columnValue = normalizeFamilyIdentityValue(columnValue == null ? null : String.valueOf(columnValue));
|
||||
}
|
||||
validateDynamicColumnValue(config, columnValue, familyIndex, course, activity);
|
||||
|
||||
NutMap normalizedColumn = NutMap.NEW();
|
||||
@@ -986,13 +1173,104 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
}
|
||||
}
|
||||
|
||||
/** 提取活动指定字段的值;活动级累计去除首尾空格,普通字段保留大小写,历史按类型规则保持原值。 */
|
||||
private String getFamilyIdentity(List<NutMap> family, FamilyActivity activity) {
|
||||
if (family == null) {
|
||||
return null;
|
||||
}
|
||||
String value = family.stream().filter(Objects::nonNull)
|
||||
.filter(column -> Objects.equals(activity.getOnlyKey(), column.getString("columnCode")))
|
||||
.map(column -> column.getString("columnValue")).filter(Objects::nonNull).findFirst().orElse(null);
|
||||
return value != null && Objects.equals(activity.getFamilyCountScope(), 2)
|
||||
? normalizeFamilyIdentityValue(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一本次提交与历史记录的唯一标识比较值,不要求值必须为身份证。
|
||||
* value 为所选字段的文本值,返回去除首尾空格的文本;有效身份证号末位x统一为X,其他文本保持原大小写。
|
||||
*/
|
||||
private String normalizeFamilyIdentityValue(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String identity = value.trim();
|
||||
return IdcardUtil.isValidCard(identity) ? identity.toUpperCase(Locale.ROOT) : identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一条报名实际参加的课程时段。独立报名仅取所选时段,否则取全部时段。
|
||||
* course 为所属子活动,activityCourseId 为用户所选时段ID;返回完整有效的日期时间区间列表。
|
||||
* 缺失、越权或无效时间不允许跳过冲突检查,避免缺配置成为绕过入口。
|
||||
*/
|
||||
private List<FamilyActivityCourse> getSignUpCourseTimes(FamilyCourse course, String activityCourseId) {
|
||||
Cnd cnd = Cnd.where(FamilyActivityCourse::getCourseId, "=", course.getId());
|
||||
if (Boolean.TRUE.equals(course.getCourseIsLimitApply())) {
|
||||
if (StrUtil.isBlank(activityCourseId)) {
|
||||
throw new BaseException("请选择报名时段:" + course.getCourseName());
|
||||
}
|
||||
cnd.and(FamilyActivityCourse::getId, "=", activityCourseId);
|
||||
}
|
||||
List<FamilyActivityCourse> times = dao().query(FamilyActivityCourse.class, cnd);
|
||||
if (Lang.isEmpty(times)) {
|
||||
throw new BaseException("子活动报名时段不存在或未配置,请联系管理员:" + course.getCourseName());
|
||||
}
|
||||
for (FamilyActivityCourse time : times) {
|
||||
if (time.getCourseStartTime() == null || time.getCourseEndTime() == null
|
||||
|| !time.getCourseStartTime().before(time.getCourseEndTime())) {
|
||||
throw new BaseException("子活动起止时间配置无效,请联系管理员:" + course.getCourseName());
|
||||
}
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在同一活动、同一教职工内校验时间重叠,含候补,修改和调班排除当前报名记录。
|
||||
* 参数为本次报名、所属课程、活动配置和实际报名用户ID;无返回值,冲突时提示已有课程和时间。
|
||||
* 使用半开区间:一场结束时刻等于另一场开始时刻允许报名;不同日期自然不会冲突。
|
||||
*/
|
||||
private void validateTimeConflict(FamilyUser user, FamilyCourse course, FamilyActivity activity, String userId) {
|
||||
if (!Boolean.TRUE.equals(activity.getTimeConflictCheck())) {
|
||||
return;
|
||||
}
|
||||
List<FamilyActivityCourse> currentTimes = getSignUpCourseTimes(course, user.getActivityCourseId());
|
||||
Cnd cnd = Cnd.where(FamilyUser::getActivityId, "=", activity.getId())
|
||||
.and(FamilyUser::getUserId, "=", userId).and(FamilyUser::getState, "in", List.of(1, 2, 3));
|
||||
if (StrUtil.isNotBlank(user.getId())) {
|
||||
cnd.and(FamilyUser::getId, "!=", user.getId());
|
||||
}
|
||||
for (FamilyUser existing : dao().query(FamilyUser.class, cnd)) {
|
||||
FamilyCourse oldCourse = dao().fetch(FamilyCourse.class, existing.getCourseId());
|
||||
if (oldCourse == null || !Objects.equals(oldCourse.getActivityId(), activity.getId())) {
|
||||
throw new BaseException("已有报名的子活动不存在或已变更,请联系管理员核对");
|
||||
}
|
||||
for (FamilyActivityCourse oldTime : getSignUpCourseTimes(oldCourse, existing.getActivityCourseId())) {
|
||||
for (FamilyActivityCourse currentTime : currentTimes) {
|
||||
if (currentTime.getCourseStartTime().before(oldTime.getCourseEndTime())
|
||||
&& oldTime.getCourseStartTime().before(currentTime.getCourseEndTime())) {
|
||||
throw new BaseException("您已报名【" + oldCourse.getCourseName() + "】("
|
||||
+ DateUtil.format(oldTime.getCourseStartTime(), "yyyy-MM-dd HH:mm") + "至"
|
||||
+ DateUtil.format(oldTime.getCourseEndTime(), "yyyy-MM-dd HH:mm")
|
||||
+ "),与当前报名时间重叠,不能兼报");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅在报名时段发生变化时重建本人课程时段记录,未变化时保留原签到和领取信息。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void syncUpdatedUserCourse(FamilyUser currentSignUp, String oldActivityCourseId) {
|
||||
if (Objects.equals(oldActivityCourseId, currentSignUp.getActivityCourseId())) {
|
||||
return;
|
||||
}
|
||||
rebuildUserCourseTimes(currentSignUp);
|
||||
}
|
||||
|
||||
/** 修改所选时段或后台调班后,按本次报名实际参加的时段重建课程记录。 */
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void rebuildUserCourseTimes(FamilyUser currentSignUp) {
|
||||
dao().clear(FamilyUserCourse.class, Cnd.where(FamilyUserCourse::getActivityId, "=", currentSignUp.getActivityId())
|
||||
.and(FamilyUserCourse::getCourseId, "=", currentSignUp.getCourseId())
|
||||
.and(FamilyUserCourse::getUserId, "=", currentSignUp.getUserId()));
|
||||
@@ -1023,6 +1301,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
|
||||
@Async
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void asyncInsertUserCourse(String activityId, String courseId, String userId) {
|
||||
log.info("异步插入{}的上课信息,课程ID为{},活动ID为{}", userId, courseId, activityId);
|
||||
insertUserCourse(activityId, courseId, userId);
|
||||
@@ -1031,6 +1310,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
/**
|
||||
* 同步写入报名人员的全部课程时段,用于保证正式报名主记录与时段记录处于同一事务。
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
private void insertUserCourse(String activityId, String courseId, String userId) {
|
||||
List<FamilyActivityCourse> courseList = dao().query(FamilyActivityCourse.class, Cnd.where("courseId", "=", courseId));
|
||||
List<FamilyUserCourse> list = new ArrayList<>();
|
||||
@@ -1112,6 +1392,11 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
|
||||
@Override
|
||||
public Map<Boolean, String> validFamilyCount(FamilyUser user) {
|
||||
return validFamilyCount(user, SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
/** 按实际报名用户校验家属集合,后台调班不能使用当前管理员身份查询报名。 */
|
||||
private Map<Boolean, String> validFamilyCount(FamilyUser user, String userId) {
|
||||
String activityId = user.getActivityId();
|
||||
FamilyActivity activity = dao().fetch(FamilyActivity.class, activityId);
|
||||
if (activity == null) {
|
||||
@@ -1137,7 +1422,11 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
|
||||
// 查询本人本活动的其他报名,修改报名时排除当前记录,后续冲突校验仍覆盖全部子活动类型。
|
||||
Cnd signUpCnd = Cnd.where(FamilyUser::getActivityId, "=", activityId)
|
||||
.and(FamilyUser::getUserId, "=", SecurityUtil.getUserId());
|
||||
.and(FamilyUser::getUserId, "=", userId);
|
||||
if (Objects.equals(activity.getFamilyCountScope(), 2) || Boolean.TRUE.equals(activity.getTimeConflictCheck())) {
|
||||
// 正常、候补及候补转正占用规则;废弃报名不占用新规则的家属名额。
|
||||
signUpCnd.and(FamilyUser::getState, "in", List.of(1, 2, 3));
|
||||
}
|
||||
if (StrUtil.isNotBlank(user.getId())) {
|
||||
signUpCnd.and(FamilyUser::getId, "!=", user.getId());
|
||||
}
|
||||
@@ -1146,6 +1435,31 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
Map<String, String> courseMap = courseList.stream()
|
||||
.collect(Collectors.toMap(FamilyCourse::getId, FamilyCourse::getCourseType));
|
||||
|
||||
if (Objects.equals(activity.getFamilyCountScope(), 2)) {
|
||||
if (activity.getFamilyMaxCount() == null || activity.getFamilyMaxCount() < 1) {
|
||||
return Map.of(false, "活动未配置有效的家属最多数,请联系管理员");
|
||||
}
|
||||
Set<String> identities = new HashSet<>();
|
||||
List<FamilyUser> allSignUps = new ArrayList<>(list);
|
||||
allSignUps.add(user);
|
||||
for (FamilyUser signUp : allSignUps) {
|
||||
if (Lang.isEmpty(signUp.getMobileColumnsValue())) {
|
||||
return Map.of(false, "报名记录缺少家属信息,请联系管理员核对历史数据");
|
||||
}
|
||||
for (List<NutMap> family : signUp.getMobileColumnsValue()) {
|
||||
String identity = getFamilyIdentity(family, activity);
|
||||
if (StrUtil.isBlank(identity)) {
|
||||
return Map.of(false, "报名记录缺少统一的家属唯一标识,请联系管理员核对字段编码和历史数据");
|
||||
}
|
||||
identities.add(identity);
|
||||
}
|
||||
}
|
||||
if (identities.size() > activity.getFamilyMaxCount()) {
|
||||
return Map.of(false, "本活动累计" + activity.getKeyWord() + "人数最多为" + activity.getFamilyMaxCount()
|
||||
+ ",请使用已报名的家属,不能更换为其他人");
|
||||
}
|
||||
}
|
||||
|
||||
// 家属最多数按当前子活动类型独立累计,同类型多个子活动按家属唯一标识去重。
|
||||
List<List<NutMap>> allMobileColumnsValue = new ArrayList<>(list.stream()
|
||||
.filter(item -> Objects.equals(currentCourse.getCourseType(), courseMap.get(item.getCourseId())))
|
||||
@@ -1157,11 +1471,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
allMobileColumnsValue.addAll(user.getMobileColumnsValue());
|
||||
|
||||
long count = allMobileColumnsValue.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(List::stream)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(nutMap -> Objects.equals(activity.getOnlyKey(), nutMap.getString("columnCode")))
|
||||
.map(nutMap -> nutMap.getString("columnValue"))
|
||||
.map(family -> getFamilyIdentity(family, activity))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.count();
|
||||
@@ -1190,7 +1500,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
continue;
|
||||
}
|
||||
// 家属匹配唯一标识的值
|
||||
String onlyKeyValue = nutMap.getString("columnValue");
|
||||
String onlyKeyValue = getFamilyIdentity(listMap, activity);
|
||||
if(Objects.equals(tempValue, onlyKeyValue)) {
|
||||
return Map.of(false, activity.getKeyWord() + "不能重复报同一分场");
|
||||
}
|
||||
@@ -1212,7 +1522,7 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
|
||||
if(hisNutMap == null) {
|
||||
continue;
|
||||
}
|
||||
String hisKeyValue = hisNutMap.getString("columnValue");
|
||||
String hisKeyValue = getFamilyIdentity(hisValue, activity);
|
||||
// 判断是不是同一个孩子
|
||||
if(Objects.equals(onlyKeyValue, hisKeyValue)) {
|
||||
oneFamilyCount ++;
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
.field .van-field__error-message {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 与测量节点共用排版,确保滚轮每一行都能容纳完整选项。 */
|
||||
.train-picker-option-text {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
@@ -89,8 +100,11 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<van-picker
|
||||
:key="column.columnCode + '-' + (pickerItemHeights[column.columnCode] || 44)"
|
||||
:columns="column.selectValues"
|
||||
:title="column.columnName"
|
||||
:item-height="pickerItemHeights[column.columnCode] || 44"
|
||||
:visible-item-count="pickerVisibleItemCounts[column.columnCode] || 6"
|
||||
show-toolbar
|
||||
@cancel="ordinaryPickerCancel(column)"
|
||||
@confirm="
|
||||
@@ -98,7 +112,11 @@
|
||||
ordinaryPickerConfirm(value, column)
|
||||
}
|
||||
"
|
||||
/>
|
||||
>
|
||||
<template v-if="fullOptionText" #option="option">
|
||||
<div class="train-picker-option-text">{{ option }}</div>
|
||||
</template>
|
||||
</van-picker>
|
||||
</template>
|
||||
</van-popup>
|
||||
</template>
|
||||
@@ -140,7 +158,12 @@ module.exports = {
|
||||
name: "TrainDynamicForm",
|
||||
components: {},
|
||||
props: {
|
||||
dynamicData: []
|
||||
dynamicData: [],
|
||||
// 由亲子活动报名页单独启用,其余引用页面保留原有选项展示。
|
||||
fullOptionText: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: "dynamicData",
|
||||
@@ -160,6 +183,8 @@ module.exports = {
|
||||
return {
|
||||
//所有的picker状态
|
||||
pickerStates: {},
|
||||
pickerItemHeights: {},
|
||||
pickerVisibleItemCounts: {},
|
||||
fileList: [],
|
||||
addressStates: {},
|
||||
addressAreaList: window.trainSignUpAreaList || { province_list: {}, city_list: {}, county_list: {} }
|
||||
@@ -444,7 +469,44 @@ module.exports = {
|
||||
const addressValue = (state.displayArea || "") + (state.detail || "")
|
||||
this.$set(column, "columnValue", addressValue)
|
||||
},
|
||||
/**
|
||||
* 按当前屏幕宽度测量最长选项,统一行高以保持滚轮定位准确。
|
||||
* @param column 动态字段配置,selectValues 为后台配置的字符串选项数组
|
||||
* @return 无返回值;将像素行高和屏内可见行数写入对应字段的展示状态
|
||||
*/
|
||||
measurePickerOptions(column) {
|
||||
if (!this.fullOptionText || column.columnFormType !== "SELECT"
|
||||
|| ["DATE", "DATETIME"].includes(column.columnType)) {
|
||||
return
|
||||
}
|
||||
const measureElement = document.createElement("div")
|
||||
measureElement.className = "train-picker-option-text"
|
||||
// Vant 选项左右各有 4px 内边距,测量宽度必须与实际文字区域一致。
|
||||
Object.assign(measureElement.style, {
|
||||
position: "fixed",
|
||||
visibility: "hidden",
|
||||
pointerEvents: "none",
|
||||
width: Math.max(document.documentElement.clientWidth - 8, 1) + "px",
|
||||
fontFamily: window.getComputedStyle(this.$el).fontFamily
|
||||
})
|
||||
document.body.appendChild(measureElement)
|
||||
let itemHeight = 44
|
||||
try {
|
||||
(column.selectValues || []).forEach((option) => {
|
||||
measureElement.textContent = option
|
||||
itemHeight = Math.max(itemHeight, Math.ceil(measureElement.getBoundingClientRect().height) + 16)
|
||||
})
|
||||
} finally {
|
||||
document.body.removeChild(measureElement)
|
||||
}
|
||||
this.$set(this.pickerItemHeights, column.columnCode, itemHeight)
|
||||
// 多行选项减少同时展示的行数,避免滚轮随行高增加而撑出屏幕。
|
||||
const availableHeight = Math.min(264, Math.max(window.innerHeight - 44, 0))
|
||||
this.$set(this.pickerVisibleItemCounts, column.columnCode,
|
||||
Math.max(1, Math.floor(availableHeight / itemHeight)))
|
||||
},
|
||||
selectFieldClick(column) {
|
||||
this.measurePickerOptions(column)
|
||||
this.$set(this.pickerStates, column.columnCode + "Picker", true)
|
||||
if (!["DATE", "DATETIME"].includes(column.columnType)) {
|
||||
//说明是普通的下拉框
|
||||
|
||||
@@ -3,7 +3,7 @@ const signForm = {
|
||||
<div>
|
||||
<el-dialog :close-on-click-modal="false" :visible.sync="signDialog"
|
||||
:title="prefillMode ? (formData.id ? '修改预填信息' : '预填报名信息') : (editMode ? '修改报名' : '信息填写')" width="50%"
|
||||
append-to-body>
|
||||
class="family-sign-dialog" top="5vh" append-to-body>
|
||||
<el-form :model="formData" ref="form" label-width="120px">
|
||||
<div class="left-span-label">个人信息</div>
|
||||
<el-row>
|
||||
@@ -72,6 +72,9 @@ const signForm = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activity.familyCountScope === 2" style="color: #c64120; margin-bottom: 10px">
|
||||
本活动累计最多报名{{activity.familyMaxCount}}名不同{{activity.keyWord}},含候补;多个子活动请使用已报名的家属。
|
||||
</div>
|
||||
<div v-if="formData.mobileColumnsValue && formData.mobileColumnsValue.length > 0">
|
||||
<el-tabs v-model="active" type="card">
|
||||
<el-tab-pane v-for="(item, index) in formData.mobileColumnsValue"
|
||||
@@ -137,7 +140,7 @@ const signForm = {
|
||||
|
||||
</el-form>
|
||||
|
||||
<el-row class="mt20" justify="end" type="flex">
|
||||
<el-row slot="footer" justify="end" type="flex">
|
||||
<el-button @click="signDialog = false">取 消</el-button>
|
||||
<el-button :loading="formLoading" @click="onSubmit" type="primary">{{prefillMode ? '保存预填' : '提 交'}}</el-button>
|
||||
</el-row>
|
||||
@@ -158,7 +161,7 @@ const signForm = {
|
||||
courseRow: {},
|
||||
courseTypeRow: {},
|
||||
courseTimeSelectList: [],
|
||||
active: '',
|
||||
active: '0',
|
||||
|
||||
activity: {},
|
||||
}
|
||||
@@ -173,13 +176,23 @@ const signForm = {
|
||||
this.$message.warning('家属最多人数为' + this.courseTypeRow.familyMaxCount)
|
||||
return
|
||||
}
|
||||
// 类型上限与整个活动上限同时生效;跨课程已用家属集合由后端在最终提交时校验。
|
||||
if (this.activity.familyCountScope === 2
|
||||
&& this.formData.mobileColumnsValue.length >= Number(this.activity.familyMaxCount)) {
|
||||
this.$message.warning('本活动累计家属人数最多为' + this.activity.familyMaxCount)
|
||||
return
|
||||
}
|
||||
const list = clone(this.courseTypeRow.familyMobileSignColumnList)
|
||||
this.formData.mobileColumnsValue.push(list)
|
||||
// 新增儿童后选中对应的字符串索引,确保标签和填写区域同步显示。
|
||||
this.$set(this, "active", String(this.formData.mobileColumnsValue.length - 1))
|
||||
},
|
||||
delFamily() {
|
||||
// 无儿童时不执行删除;删完后保留首项索引,兼容重新添加儿童。
|
||||
if (this.formData.mobileColumnsValue.length === 0) return
|
||||
this.formData.mobileColumnsValue.splice(this.active, 1)
|
||||
const ac = (Number(this.active) - 1)
|
||||
this.active = '' + (ac >= 0 ? ac : 0)
|
||||
this.$set(this, "active", String(ac >= 0 ? ac : 0))
|
||||
},
|
||||
validSignUp() {
|
||||
//获取家属是多少人
|
||||
@@ -478,12 +491,33 @@ const signForm = {
|
||||
this.mergeMobileColumnsValue(courseType, signUpData.mobileColumnsValue))
|
||||
}
|
||||
this.$set(this, "formData", formData)
|
||||
// 不同活动共用报名组件,每次打开都选中首个儿童,避免沿用上次的标签状态。
|
||||
this.$set(this, "active", "0")
|
||||
this.$nextTick(() => {
|
||||
this.$refs.form.clearValidate()
|
||||
})
|
||||
},
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
/* 报名弹框保留上下可视边距,仅正文滚动,标题和操作按钮始终可见。 */
|
||||
.family-sign-dialog {
|
||||
overflow: hidden;
|
||||
}
|
||||
.family-sign-dialog > .el-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.family-sign-dialog > .el-dialog > .el-dialog__header,
|
||||
.family-sign-dialog > .el-dialog > .el-dialog__footer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.family-sign-dialog > .el-dialog > .el-dialog__body {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
::v-deep .el-tabs__header {
|
||||
margin: 0 0 15px !important;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,24 @@ const basicForm = {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="家属人数范围" prop="familyCountScope">
|
||||
<el-radio-group :value="formData.familyCountScope" size="small"
|
||||
@input="(val) => { this.$set(formData, 'familyCountScope', val) }">
|
||||
<el-radio :label="1" border>按子活动类型累计</el-radio>
|
||||
<el-radio :label="2" border>按整个活动累计</el-radio>
|
||||
</el-radio-group>
|
||||
<div style="color: #c64120; font-size: 12px" v-if="formData.familyCountScope === 2">
|
||||
同一教职工在本活动所有子活动中,家属按唯一标识去重后的累计人数不得超过家属最多数;含候补报名。
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="报名时间冲突" prop="timeConflictCheck">
|
||||
<el-switch :value="formData.timeConflictCheck"
|
||||
@input="(val) => { this.$set(formData, 'timeConflictCheck', val) }"
|
||||
active-text="禁止时间重叠报名"></el-switch>
|
||||
<div style="color: #c64120; font-size: 12px">
|
||||
按子活动实际日期及起止时间判断,含候补报名。上午与同日全天互斥,上午与下午、不同日期可兼报。
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :rules="{required:true,message: '请输入活动介绍', trigger: 'blur'}" label="活动介绍" prop="introduce">
|
||||
<text-editor v-model="formData.introduce"></text-editor>
|
||||
</el-form-item>
|
||||
@@ -280,9 +298,14 @@ const basicForm = {
|
||||
|
||||
<div class="left-span-label" style="margin-top: 20px">判断家属数量唯一标识</div>
|
||||
<el-form-item label="唯一标识" prop="onlyKey">
|
||||
<el-select @focus="keyFocus" v-model="formData.onlyKey" placeholder="请选择唯一标识">
|
||||
<el-option v-for="item in typeArray" :key="item.columnCode" :label="item.columnName" :value="item.columnCode"></el-option>
|
||||
<el-select @focus="keyFocus" :value="formData.onlyKey" placeholder="请选择唯一标识" style="width: 420px"
|
||||
@input="(val) => { this.$set(formData, 'onlyKey', val) }">
|
||||
<el-option v-for="item in typeArray" :key="item.columnCode" :label="item.optionLabel"
|
||||
:value="item.columnCode" :disabled="item.legacy"></el-option>
|
||||
</el-select>
|
||||
<div style="color: #c64120; font-size: 12px">
|
||||
请选择本活动所有子活动类型共有且均设为必填的字段,各类型字段编码必须一致。有报名或预填后禁止直接更换。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -290,8 +313,8 @@ const basicForm = {
|
||||
<el-button @click="$emit('back')">取消</el-button>
|
||||
<el-button v-if="step === 2" type="primary" @click="step = 1">上一步</el-button>
|
||||
<el-button v-if="step === 1" type="primary" @click="step = 2">下一步</el-button>
|
||||
<el-button type="primary" @click="onSave">保存</el-button>
|
||||
<el-button type="primary" @click="onSubmit">提交</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="onSave">保存</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="onSubmit">提交</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog :close-on-click-modal="false" :visible.sync="signUpDialog" title="设置报名限制" append-to-body>
|
||||
@@ -325,6 +348,7 @@ const basicForm = {
|
||||
data() {
|
||||
return {
|
||||
signUpDialog: false,
|
||||
formLoading: false,
|
||||
step: 1,
|
||||
formData: {
|
||||
notice: false,
|
||||
@@ -348,7 +372,9 @@ const basicForm = {
|
||||
restrictLimit: 1,
|
||||
limitNum: 1,
|
||||
typeLimits: [],
|
||||
keyWord: '家属'
|
||||
keyWord: '家属',
|
||||
familyCountScope: 1,
|
||||
timeConflictCheck: false,
|
||||
},
|
||||
historicalActList: [],
|
||||
trainTypeList: [],
|
||||
@@ -372,28 +398,47 @@ const basicForm = {
|
||||
},
|
||||
methods: {
|
||||
keyFocus() {
|
||||
const types = this.formData.courseList.filter(o => o.courseType !== undefined).map(o => o.courseType)
|
||||
const types = [...new Set(this.formData.courseList.map(o => o.courseType).filter(Boolean))]
|
||||
if(types.length === 0) {
|
||||
this.$set(this, 'typeArray', [])
|
||||
this.$message.warning('请先设置' + this.activityType + '类型')
|
||||
return
|
||||
}
|
||||
const typeList = this.courseTypeList.filter(o => types.includes(o.id))
|
||||
let array = []
|
||||
typeList.forEach(item => {
|
||||
array = array.concat(item.familyMobileSignColumnList)
|
||||
})
|
||||
// 根据 columnCode 去重
|
||||
const uniqueMap = new Map()
|
||||
array.forEach(item => {
|
||||
if (item.columnCode) {
|
||||
uniqueMap.set(item.columnCode, item)
|
||||
}
|
||||
})
|
||||
// 可选:保留没有 columnCode 的项
|
||||
const withoutCode = array.filter(item => !item.columnCode)
|
||||
this.typeArray = [...uniqueMap.values(), ...withoutCode]
|
||||
// 唯一标识必须在每种类型中各出现一次且均为必填,不限定字段的验证规则。
|
||||
const firstColumns = typeList[0] ? (typeList[0].familyMobileSignColumnList || []) : []
|
||||
const options = firstColumns.filter(column => column.columnCode && typeList.length === types.length
|
||||
&& typeList.every(type => {
|
||||
const matches = (type.familyMobileSignColumnList || []).filter(item => item.columnCode === column.columnCode)
|
||||
return matches.length === 1 && matches[0].isRequired === true
|
||||
})).map(column => ({
|
||||
columnCode: column.columnCode,
|
||||
optionLabel: column.columnName,
|
||||
legacy: false
|
||||
}))
|
||||
// 历史选择仍可回显,但不能作为新选择;不改写历史编码,也不静默清空原配置。
|
||||
if (this.formData.onlyKey && !options.some(item => item.columnCode === this.formData.onlyKey)) {
|
||||
const oldColumn = typeList.flatMap(type => type.familyMobileSignColumnList || [])
|
||||
.find(column => column.columnCode === this.formData.onlyKey)
|
||||
options.push({
|
||||
columnCode: this.formData.onlyKey,
|
||||
optionLabel: oldColumn ? oldColumn.columnName : '原字段',
|
||||
legacy: true
|
||||
})
|
||||
}
|
||||
this.$set(this, 'typeArray', options)
|
||||
},
|
||||
async validNumber(row, old) {
|
||||
// 老活动空配置按原来的按类型累计、不检查重叠回显,沿用活动时也保留所选规则。
|
||||
applyRuleDefaults() {
|
||||
this.$set(this.formData, 'familyCountScope', Number(this.formData.familyCountScope) === 2 ? 2 : 1)
|
||||
this.$set(this.formData, 'timeConflictCheck', this.formData.timeConflictCheck === true)
|
||||
if (this.formData.courseList.some(course => course.courseType)) {
|
||||
this.keyFocus()
|
||||
} else {
|
||||
this.$set(this, 'typeArray', [])
|
||||
}
|
||||
},
|
||||
validNumber(row, old) {
|
||||
if (GetQueryString("id") === "") {
|
||||
if (row.courseReservedNumber > row.coursePeopleNumber && row.reserveMode === 1) {
|
||||
this.$alert("预留人数不能大于" + this.activityType + "人数!", "提示", {
|
||||
@@ -402,31 +447,32 @@ const basicForm = {
|
||||
row.courseReservedNumber = 0
|
||||
}
|
||||
} else {
|
||||
const registerUserCount = await this.getRegisterUserCount(row.id)
|
||||
if (row.courseReservedNumber + registerUserCount > row.coursePeopleNumber) {
|
||||
if (row.reserveMode === 1) {
|
||||
let num = row.coursePeopleNumber - registerUserCount
|
||||
let str =
|
||||
"您设置的" +
|
||||
this.activityType +
|
||||
"人数为" +
|
||||
row.coursePeopleNumber +
|
||||
"人,当前报名人数为" +
|
||||
registerUserCount +
|
||||
"人,预留名额上限为" +
|
||||
num +
|
||||
"人!"
|
||||
this.$alert(str, "提示", {
|
||||
confirmButtonText: "确定"
|
||||
})
|
||||
row.courseReservedNumber = 0
|
||||
return this.getRegisterUserCount(row.id).then((registerUserCount) => {
|
||||
if (row.courseReservedNumber + registerUserCount > row.coursePeopleNumber) {
|
||||
if (row.reserveMode === 1) {
|
||||
let num = row.coursePeopleNumber - registerUserCount
|
||||
let str =
|
||||
"您设置的" +
|
||||
this.activityType +
|
||||
"人数为" +
|
||||
row.coursePeopleNumber +
|
||||
"人,当前报名人数为" +
|
||||
registerUserCount +
|
||||
"人,预留名额上限为" +
|
||||
num +
|
||||
"人!"
|
||||
this.$alert(str, "提示", {
|
||||
confirmButtonText: "确定"
|
||||
})
|
||||
row.courseReservedNumber = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async getRegisterUserCount(courseId) {
|
||||
const resp = await this.$axios.post("/platform/family/manage/getRegisterUserCount", { courseId })
|
||||
return resp.code === 0 ? resp.data : 0
|
||||
getRegisterUserCount(courseId) {
|
||||
return this.$axios.post("/platform/family/manage/getRegisterUserCount", { courseId })
|
||||
.then((resp) => resp.code === 0 ? resp.data : 0)
|
||||
},
|
||||
courseTypeChange(val, row) {
|
||||
row.reserveMode = 1
|
||||
@@ -497,30 +543,32 @@ const basicForm = {
|
||||
addCourse() {
|
||||
this.formData.courseList.push({ courseTimeList: [], isMobileSign: false, isReceiveGift: false, reserveMode: 1, courseIsLimitApply: false })
|
||||
},
|
||||
async historicalActChange(val) {
|
||||
const resp = await this.$axios.post("/platform/family/manage/findOne", {id: val})
|
||||
if (resp.code === 0) {
|
||||
this.formData = resp.data
|
||||
this.typeChange(this.formData.trainType)
|
||||
this.formData.id = ""
|
||||
this.formData.courseList.forEach((course) => {
|
||||
course.id = ""
|
||||
course.activityId = ""
|
||||
if (course.courseTimeList) {
|
||||
course.courseTimeList.forEach((courseTime) => {
|
||||
courseTime.id = ""
|
||||
courseTime.activityId = ""
|
||||
courseTime.courseId = ""
|
||||
historicalActChange(val) {
|
||||
return this.$axios.post("/platform/family/manage/findOne", {id: val}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$set(this, "formData", resp.data)
|
||||
this.applyRuleDefaults()
|
||||
this.typeChange(this.formData.trainType)
|
||||
this.formData.id = ""
|
||||
this.formData.courseList.forEach((course) => {
|
||||
course.id = ""
|
||||
course.activityId = ""
|
||||
if (course.courseTimeList) {
|
||||
course.courseTimeList.forEach((courseTime) => {
|
||||
courseTime.id = ""
|
||||
courseTime.activityId = ""
|
||||
courseTime.courseId = ""
|
||||
})
|
||||
}
|
||||
})
|
||||
if (this.formData.typeLimits) {
|
||||
this.formData.typeLimits.forEach((typeLimit) => {
|
||||
typeLimit.id = ""
|
||||
typeLimit.activityId = ""
|
||||
})
|
||||
}
|
||||
})
|
||||
if (this.formData.typeLimits) {
|
||||
this.formData.typeLimits.forEach((typeLimit) => {
|
||||
typeLimit.id = ""
|
||||
typeLimit.activityId = ""
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
typeChange(val) {
|
||||
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find(o => o.code === val)
|
||||
@@ -534,15 +582,13 @@ const basicForm = {
|
||||
this.$set(this.formData, "activityGroupName", "全部教职工")
|
||||
}
|
||||
},
|
||||
async getActivityGroup() {
|
||||
const { data } = await this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
|
||||
return data
|
||||
getActivityGroup() {
|
||||
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((resp) => resp.data)
|
||||
},
|
||||
async getHistoricalActList() {
|
||||
const resp = await this.$axios.post("/platform/family/manage/getHistoricalActList", {})
|
||||
return resp.data
|
||||
getHistoricalActList() {
|
||||
return this.$axios.post("/platform/family/manage/getHistoricalActList", {}).then((resp) => resp.data)
|
||||
},
|
||||
async onSave() {
|
||||
onSave() {
|
||||
let valid = false
|
||||
this.$refs.form.validateField("activityName", (errMsg) => {
|
||||
valid = errMsg === ""
|
||||
@@ -551,10 +597,10 @@ const basicForm = {
|
||||
this.$message.warning("请输入活动名称")
|
||||
return
|
||||
}
|
||||
await this.doHandle('保存')
|
||||
return this.doHandle('保存')
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs["form"].validate(async (valid, errMsg) => {
|
||||
this.$refs["form"].validate((valid, errMsg) => {
|
||||
if (valid) {
|
||||
const courseValid = this.formData.courseList.some((v, i) => {
|
||||
const basicValid = v.courseName && v.coursePeopleNumber && v.courseLocation && v.courseInstructor && v.courseType
|
||||
@@ -599,7 +645,7 @@ const basicForm = {
|
||||
return
|
||||
}
|
||||
this.formData.isDisabled = false
|
||||
await this.doHandle('提交')
|
||||
this.doHandle('提交')
|
||||
} else {
|
||||
if(Object.keys(errMsg).length > 0) {
|
||||
this.$message.warning(errMsg[Object.keys(errMsg)[0]][0].message)
|
||||
@@ -609,7 +655,7 @@ const basicForm = {
|
||||
}
|
||||
})
|
||||
},
|
||||
async doHandle(type) {
|
||||
doHandle(type) {
|
||||
const cloneData = clone(this.formData)
|
||||
cloneData.activitySignUpStartTime = cloneData.activitySignTime !== undefined ? cloneData.activitySignTime[0] : null
|
||||
cloneData.activitySignUpEndTime = cloneData.activitySignTime !== undefined ? cloneData.activitySignTime[1] : null
|
||||
@@ -620,41 +666,50 @@ const basicForm = {
|
||||
}
|
||||
cloneData.typeLimits = JSON.stringify(this.formData.typeLimits)
|
||||
cloneData.courseList = JSON.stringify(cloneData.courseList)
|
||||
const confirm = await this.$confirm("您确定要" + type + "吗?", "提示", {
|
||||
return this.$confirm("您确定要" + type + "吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
if (confirm !== "confirm") return
|
||||
const resp = await this.$axios.post("/platform/family/manage/doHandle", cloneData)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.step = 1
|
||||
this.$emit('refresh')
|
||||
this.$emit('back')
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}).then(() => {
|
||||
this.formLoading = true
|
||||
return this.$axios.post("/platform/family/manage/doHandle", cloneData).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.step = 1
|
||||
this.$emit('refresh')
|
||||
this.$emit('back')
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
getAllType() {
|
||||
return this.$axios.post("/platform/family/type/getAllType", {}).then((resp) => resp.data)
|
||||
},
|
||||
init(row) {
|
||||
if (!row || !row.id) {
|
||||
this.applyRuleDefaults()
|
||||
return Promise.resolve()
|
||||
}
|
||||
},
|
||||
async getAllType() {
|
||||
const resp = await this.$axios.post("/platform/family/type/getAllType", {})
|
||||
return resp.data
|
||||
},
|
||||
async init(row) {
|
||||
if (row && row.id) {
|
||||
const resp = await this.$axios.post("/platform/family/manage/findOne", {id: row.id})
|
||||
return this.$axios.post("/platform/family/manage/findOne", {id: row.id}).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.formData = resp.data
|
||||
this.$set(this, "formData", resp.data)
|
||||
this.typeChange(this.formData.trainType)
|
||||
if(this.formData.onlyKey) this.keyFocus()
|
||||
this.applyRuleDefaults()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
async initData(row) {
|
||||
this.activityGroupList = await this.getActivityGroup()
|
||||
this.historicalActList = await this.getHistoricalActList()
|
||||
this.courseTypeList = await this.getAllType()
|
||||
await this.init(row)
|
||||
initData(row) {
|
||||
return Promise.all([this.getActivityGroup(), this.getHistoricalActList(), this.getAllType()])
|
||||
.then(([groups, activities, types]) => {
|
||||
this.$set(this, "activityGroupList", groups)
|
||||
this.$set(this, "historicalActList", activities)
|
||||
this.$set(this, "courseTypeList", types)
|
||||
return this.init(row)
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -664,9 +719,11 @@ const basicForm = {
|
||||
}
|
||||
},
|
||||
'formData.activityGroupId': {
|
||||
async handler(newVal, oldVal) {
|
||||
this.activityGroupList = await this.getActivityGroup()
|
||||
this.userScopeDialog = false
|
||||
handler(newVal, oldVal) {
|
||||
this.getActivityGroup().then((groups) => {
|
||||
this.$set(this, 'activityGroupList', groups)
|
||||
this.$set(this, 'userScopeDialog', false)
|
||||
})
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
@@ -11,6 +11,10 @@ const info = {
|
||||
<el-descriptions-item label="活动结束时间">{{viewData.activityEndTime}}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名开始时间">{{viewData.activitySignUpStartTime}}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名结束时间">{{viewData.activitySignUpEndTime}}</el-descriptions-item>
|
||||
<el-descriptions-item label="家属人数范围">{{viewData.familyCountScope === 2 ? '按整个活动累计' : '按子活动类型累计'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="活动家属最多数">{{viewData.familyMaxCount}}</el-descriptions-item>
|
||||
<el-descriptions-item label="禁止时间重叠报名">{{viewData.timeConflictCheck === true ? '是(含候补)' : '否'}}</el-descriptions-item>
|
||||
<el-descriptions-item label="家属唯一标识编码">{{viewData.onlyKey}}</el-descriptions-item>
|
||||
<el-descriptions-item label="详细信息" :span="2">
|
||||
<div v-html="viewData.introduce"></div>
|
||||
</el-descriptions-item>
|
||||
@@ -54,6 +58,7 @@ const info = {
|
||||
<el-dialog :visible.sync="viewMapDialog" title="地点" width="60%">
|
||||
<div id="viewMap" style="width: 100%; height: 500px"></div>
|
||||
</el-dialog>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
dicts: ["FAMILY_SIGNUP_TYPE"],
|
||||
@@ -72,7 +77,7 @@ const info = {
|
||||
this.$axios.post("/platform/family/manage/findOne", { id: id })
|
||||
.then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.viewData = resp.data
|
||||
this.$set(this, 'viewData', resp.data)
|
||||
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find((o) => o.code === resp.data.trainType)
|
||||
this.activityType = type?.name || "子活动"
|
||||
}
|
||||
|
||||
@@ -52,13 +52,16 @@ const applyForm = {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="activity.familyCountScope === 2" style="color: #c64120; padding: 10px 16px">
|
||||
本活动累计最多报名{{activity.familyMaxCount}}名不同{{activity.keyWord}},含候补;多个子活动请使用已报名的家属。
|
||||
</div>
|
||||
<div v-if="formData.mobileColumnsValue && formData.mobileColumnsValue.length > 0" class="mt10">
|
||||
<van-tabs v-model="familyActive" type="card" color="#0e78c5" animated>
|
||||
<van-tab v-for="item, index in formData.mobileColumnsValue"
|
||||
:name="index + ''"
|
||||
:title="activity.keyWord + (index + 1)"
|
||||
:key="index">
|
||||
<train-dynamic-form v-model="formData.mobileColumnsValue[index]" :key="index"></train-dynamic-form>
|
||||
<train-dynamic-form v-model="formData.mobileColumnsValue[index]" :key="index" full-option-text></train-dynamic-form>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
@@ -111,6 +114,12 @@ const applyForm = {
|
||||
this.$toast(this.activity.keyWord + '最多人数为' + this.courseType.familyMaxCount)
|
||||
return
|
||||
}
|
||||
// 与PC一致,单次添加同时受类型和活动上限约束,跨课程累计由后端负责。
|
||||
if (this.activity.familyCountScope === 2
|
||||
&& this.formData.mobileColumnsValue.length >= Number(this.activity.familyMaxCount)) {
|
||||
this.$toast('本活动累计家属人数最多为' + this.activity.familyMaxCount)
|
||||
return
|
||||
}
|
||||
const list = clone(this.courseType.familyMobileSignColumnList)
|
||||
this.formData.mobileColumnsValue.push(list)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user