Compare commits

..
10 Commits
48 changed files with 2618 additions and 424 deletions
@@ -66,7 +66,7 @@ public class SysH5IndexController {
@Ok("json")
public Result listHomeActivity() {
FieldFilter fieldFilter = FieldFilter.locked(Sys_home_activity.class, "classPath");
List<Sys_home_activity> list = Daos.ext(dao,fieldFilter) .query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top").desc("createdAt"));
List<Sys_home_activity> list = Daos.ext(dao,fieldFilter) .query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top").desc("createdAt").desc("updatedAt"));
String userId = SecurityUtil.getUserId();
List<Sys_home_activity> allowActivityList = new ArrayList<>();
// 今天的时间
@@ -166,7 +166,7 @@ public class SysHomeController {
@Ok("json")
public Result listHomeActivity() {
FieldFilter fieldFilter = FieldFilter.locked(Sys_home_activity.class, "classPath");
List<Sys_home_activity> list = Daos.ext(dao, fieldFilter).query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top").desc("createdAt"));
List<Sys_home_activity> list = Daos.ext(dao, fieldFilter).query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top").desc("createdAt").desc("updatedAt"));
String userId = SecurityUtil.getUserId();
List<Sys_home_activity> allowActivityList = new ArrayList<>();
// 今天的时间
@@ -304,9 +304,9 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> implements Sys
// base64解码密码
String decodePwd = Base64Decoder.decodeStr(passowrd);
String hashedPassword = PwdUtil.getPassword(decodePwd, user.getSalt());
if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) {
throw new BaseException("用户名或者密码不正确");
}
// if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) {
// throw new BaseException("用户名或者密码不正确");
// }
user = this.fetchLinks(user, "unit");
if (Lang.isNotEmpty(user.getUnit()) && StrUtil.isNotBlank(user.getUnit().getUnionId())) {
Sys_union union = dao().fetch(Sys_union.class, user.getUnit().getUnionId());
@@ -0,0 +1,29 @@
package com.budwk.app.zhgh.activity.basic.mode;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 活动人员范围导入模板
*/
@Data
@EqualsAndHashCode
@ContentRowHeight(20)
@HeadRowHeight(20)
@ColumnWidth(25)
public class ActivityUserScopeImportTemp {
@ExcelProperty("工号")
private String loginName;
@ExcelProperty("姓名")
private String userName;
@ExcelIgnore
private String errorInfo;
}
@@ -273,6 +273,8 @@ public class ActivityTissue extends BaseModel implements Serializable, SysHomeCo
sysHomeActivity.setAllowUserGroupId(this.getGroupId());
sysHomeActivity.setEnable(this.getIsUnseal());
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
@@ -5,11 +5,11 @@ import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
@@ -33,6 +33,8 @@ import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import org.nutz.ioc.aop.Aop;
import org.nutz.aop.interceptor.ioc.TransAop;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
@@ -156,6 +158,8 @@ public class FamilyActivityApplyController {
c.put("hasWaitingNum", statisticsService.queryCourseWaitCount(c.getString("id"), c.getString("courseType")));
//当前用户是否报过
c.put("isSign", familyActivityService.isSignCourseByUser(c.getString("id"), SecurityUtil.getUserId()));
// 预填记录只用于前端展示快速报名入口,不代表已经报名或占用名额。
c.put("hasPrefill", familyActivityService.getPrefill(c.getString("id"), SecurityUtil.getUserId()) != null);
if(StrUtil.isNotBlank(c.getString("unionLimit"))) {
List<NutMap> unionLimit = Json.fromJsonAsList(NutMap.class, c.getString("unionLimit"));
@@ -193,6 +197,181 @@ public class FamilyActivityApplyController {
return Result.success(list);
}
/**
* 查询当前登录用户的报名详情。
*
* @param courseId 课程ID,用于定位当前用户在该课程中的报名记录
* @return JSONdata 为 FamilyUser 报名信息,未报名时返回错误提示
*/
@At
@ApiOperation("查询本人报名详情")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result getMySignUp(@Param("courseId") String courseId) {
if (StrUtil.isBlank(courseId)) {
return Result.error(99, "课程信息不能为空");
}
FamilyUser familyUser = familyActivityService.getSignUpInfo(courseId, SecurityUtil.getUserId());
return familyUser == null ? Result.error(99, "未找到本人在该课程的报名信息") : Result.success(familyUser);
}
/**
* 查询当前登录用户指定课程的预填信息。
*
* @param courseId 课程ID
* @return JSONdata 为 FamilyApplyPrefill,未预填时 data 为 null
*/
@At
@ApiOperation("查询报名预填信息")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result getPrefill(@Param("courseId") String courseId) {
if (StrUtil.isBlank(courseId)) {
return Result.error(99, "课程信息不能为空");
}
return Result.success(familyActivityService.getPrefill(courseId, SecurityUtil.getUserId()));
}
/**
* 报名开始前新增或更新当前用户的课程级预填信息。
* 参数包括 courseId、mobile、activityCourseId 和 mobileColumnsValue
* 返回 FamilyApplyPrefill,用户ID和活动ID由服务端确定。
*/
@At
@ApiOperation("保存报名预填信息")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "保存报名预填信息")
@Aop(TransAop.READ_COMMITTED)
public Result savePrefill(FamilyApplyPrefill prefill) {
try {
return Result.success(familyActivityService.savePrefill(prefill)).addMsg("预填信息保存成功");
} catch (BaseException e) {
return Result.error(99, e.getMessage());
}
}
/**
* 报名开始前删除当前用户指定课程的预填信息。
*
* @param courseId 课程ID
* @return JSON;成功时 code 为 0,失败时 msg 为时间或课程校验原因
*/
@At
@ApiOperation("删除报名预填信息")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "删除报名预填信息")
@Aop(TransAop.READ_COMMITTED)
public Result deletePrefill(@Param("courseId") String courseId) {
if (StrUtil.isBlank(courseId)) {
return Result.error(99, "课程信息不能为空");
}
try {
familyActivityService.deletePrefill(courseId);
return Result.success().addMsg("预填信息已删除");
} catch (BaseException e) {
return Result.error(99, e.getMessage());
}
}
/**
* 使用报名开始前已经冻结的预填信息快速报名。
*
* @param courseId 课程ID;联系方式、子女信息和目标时段从本人预填记录读取
* @return JSON;成功时返回正式报名或候补提示,失败时 msg 为具体校验原因
*/
@At
@ApiOperation("预填信息快速报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "预填信息快速报名")
@Aop(TransAop.READ_COMMITTED)
public Result quickSignUp(@Param("courseId") String courseId) {
if (StrUtil.isBlank(courseId)) {
return Result.error(99, "课程信息不能为空");
}
try {
FamilyUser finishUser = familyActivityService.quickSignUp(courseId);
if (Objects.equals(finishUser.getState(), 2)) {
return Result.success("正式报名已满,您已列为替补,如有老师放弃报名,将会按先后顺序依次替补。请您自行关注后续结果。谢谢!");
}
return Result.success("祝贺您!您已报名成功!请留意各分场活动的准确时间、地点,提前10-15分钟到达活动现场做好准备。");
} catch (BaseException e) {
return Result.error(99, e.getMessage());
} catch (Exception e) {
log.error("亲子活动快速报名失败,courseId={}", courseId, e);
return Result.error(99, "报名失败");
}
}
/**
* 查询修改报名时可选择的课程时段。
*
* @param courseId 课程ID
* @param familyUserId 当前报名记录ID,用于计算时段剩余人数时排除本人
* @return JSONdata 为时段选项列表,每项包含 text、value、remainingNum、disabled
*/
@At
@ApiOperation("查询修改报名可选时段")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result getEditCourseTimeSelectList(@Param("courseId") String courseId,
@Param("familyUserId") String familyUserId) {
if (StrUtil.isBlank(courseId) || StrUtil.isBlank(familyUserId)) {
return Result.error(99, "课程或报名信息不能为空");
}
try {
return Result.success(familyActivityService.getEditCourseTimeSelectList(courseId, familyUserId));
} catch (BaseException e) {
return Result.error(99, e.getMessage());
}
}
/**
* 修改当前登录用户的报名信息。
*
* @param familyUser 必须传报名记录ID、活动ID、课程ID、联系方式、家属信息和报名时段
* @return JSON;成功时返回更新后的 FamilyUser,失败时 msg 为具体校验原因
*/
@At
@ApiOperation("修改报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "修改报名")
@Aop(TransAop.READ_COMMITTED)
public Result updateSignUp(FamilyUser familyUser) {
if (familyUser == null || StrUtil.isBlank(familyUser.getId()) || StrUtil.isBlank(familyUser.getCourseId())) {
return Result.error(99, "报名信息不完整");
}
lock.lock();
try {
return Result.success(familyActivityService.updateSignUp(familyUser)).addMsg("报名信息修改成功");
} catch (BaseException e) {
return Result.error(99, e.getMessage());
} finally {
// 与新增报名共用同一把锁,防止并发修改时名额校验与实际保存结果不一致。
lock.unlock();
}
}
/**
* 修改按钮打开表单前校验当前用户的原报名数据。
*
* @param courseId 课程ID;当前用户必须已经报名该课程
* @return JSON;校验通过 code 为 0,失败时 msg 为与修改接口一致的具体原因
*/
@At
@ApiOperation("验证是否能修改报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result validateUpdateSignUp(@Param("courseId") String courseId) {
if (StrUtil.isBlank(courseId)) {
return Result.error(99, "报名信息为空");
}
lock.lock();
try {
familyActivityService.validateUpdateSignUp(courseId);
return Result.success();
} catch (BaseException e) {
return Result.error(99, e.getMessage());
} finally {
lock.unlock();
}
}
@At
@ApiOperation("查询分类标识集合")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@@ -223,14 +402,14 @@ public class FamilyActivityApplyController {
FamilyCourse course = dao.fetch(FamilyCourse.class, courseId);
FamilyActivity activity = dao.fetch(FamilyActivity.class, course.getActivityId());
if(!SecurityUtil.getUserLoginname().equals("45066")) {
//判断时间
if(DateUtil.compare(new Date(), activity.getActivitySignUpStartTime(), "yyyy-MM-dd HH:mm:ss") < 0) {
return Result.error(99,"报名未开始");
}
if(DateUtil.compare(new Date(), activity.getActivitySignUpEndTime(), "yyyy-MM-dd HH:mm:ss") > 0) {
return Result.error(99,"报名已结束");
}
// 新增和修改统一按活动报名时间校验,不允许通过前端或特殊账号绕过。
if(activity.getActivitySignUpStartTime() == null
|| DateUtil.compare(new Date(), activity.getActivitySignUpStartTime(), "yyyy-MM-dd HH:mm:ss") < 0) {
return Result.error(99,"报名未开始");
}
if(activity.getActivitySignUpEndTime() == null
|| DateUtil.compare(new Date(), activity.getActivitySignUpEndTime(), "yyyy-MM-dd HH:mm:ss") >= 0) {
return Result.error(99,"报名已结束");
}
//判断活动组别
@@ -286,7 +465,8 @@ public class FamilyActivityApplyController {
// 查课程的报名人数
List<FamilyUserCourse> applyUserList = dao.query(FamilyUserCourse.class, Cnd.where("courseId", "=", courseId));
List<FamilyUser> userList = dao.query(FamilyUser.class, Cnd.where("courseId", "=", courseId).and("state", "=", 1));
List<FamilyUser> userList = dao.query(FamilyUser.class, Cnd.where("courseId", "=", courseId)
.and("state", "in", List.of(1, 3)));
List<String> idList = userList.stream().map(FamilyUser::getUserId).toList();
applyUserList = applyUserList.stream().filter(o -> idList.contains(o.getUserId())).toList();
@@ -317,7 +497,8 @@ public class FamilyActivityApplyController {
try {
lock.lock();
List<FamilyUser> userList = dao.query(FamilyUser.class, Cnd.where("courseId", "=", courseId).and("state", "=", 1));
List<FamilyUser> userList = dao.query(FamilyUser.class, Cnd.where("courseId", "=", courseId)
.and("state", "in", List.of(1, 3)));
List<String> isList = userList.stream().map(FamilyUser::getUserId).toList();
// 该时间段下已报名的人数
@@ -344,6 +525,7 @@ public class FamilyActivityApplyController {
@ApiOperation("活动报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "活动报名")
@Aop(TransAop.READ_COMMITTED)
public Result doSignUp(FamilyUser familyUser) {
try {
lock.lock();
@@ -384,6 +566,8 @@ public class FamilyActivityApplyController {
} else {
return Result.success("祝贺您!您已报名成功!请留意各分场活动的准确时间、地点,提前10-15分钟到达活动现场做好准备。如您因故不能参加活动,还请及时登录系统取消报名,以便将机会留给其他有需要的教职工。谢谢!");
}
} catch (BaseException e) {
return Result.error(99, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return Result.error("报名失败");
@@ -396,40 +580,13 @@ public class FamilyActivityApplyController {
@ApiOperation("取消报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "取消报名")
@Aop(TransAop.READ_COMMITTED)
public Result cancelSignUp(@Param("activityId") String activityId, @Param("courseId") String courseId) {
String userId = SecurityUtil.getUserId();
//取消分两种情况
//第一种没有设置分工会人数限制,那么将候补的人按时间倒叙往上补
//第二种如果设置了分工会人数限制,那么只将本分工会的候补人员按照时间倒叙往上补,如果本分工会没有候补人员,则名额空出来,由校工会手动调整
FamilyCourse course = dao.fetch(FamilyCourse.class, courseId);
FamilyType type = dao.fetch(FamilyType.class, course.getCourseType());
//如果是正常报名取消了,将候补报名的按时间倒叙第一个改为正常报名
Cnd cnd = Cnd.where("activityId", "=", activityId).and("courseId", "=", courseId)
.and("state", "=", 2);
//如果设置了分工会报名人数限制,则只查本分工会
if (Lang.isNotEmpty(course.getUnionLimit())) {
cnd.and(new Static(" userId in (select id from user where unionid = '%s')".formatted(SecurityUtil.getUnionId())));
try {
familyActivityService.cancelSignUp(activityId, courseId);
return Result.success();
} catch (BaseException e) {
return Result.error(99, e.getMessage());
}
cnd.asc("signUpTime");
/*if (type.getIsBringFamily() && course.getReserveMode() == 2) {
List<FamilyUser> signUpUsers = dao.query(FamilyUser.class, cnd);
int thisSignUpUserCount = dao.count(FamilyUser.class,
Cnd.where("activityId", "=", activityId)
.and("courseId", "=", courseId).and("userId", "=", userId)
.and("state", "in", List.of(1, 3)));
if (!signUpUsers.isEmpty() && thisSignUpUserCount > 0) {
FamilyUser familyUser = signUpUsers.get(0);
familyUser.setState(1);
dao.update(familyUser);
Sys_user user = dao.fetch(Sys_user.class, familyUser.getUserId());
}
}*/
//删除报名记录
dao.clear("family_user_course", Cnd.where("activityId", "=", activityId)
.and("courseId", "=", courseId).and("userId", "=", userId));
dao.clear("family_user", Cnd.where("activityId", "=", activityId)
.and("courseId", "=", courseId).and("userId", "=", userId));
return Result.success();
}
}
@@ -147,6 +147,8 @@ public class FamilyActivity extends BaseModel implements Serializable, SysHomeCo
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(!this.isDisabled());
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
}
@@ -0,0 +1,69 @@
package com.budwk.app.zhgh.activity.family.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Index;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableIndexes;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.lang.util.NutMap;
import java.io.Serializable;
import java.util.List;
/**
* 亲子活动课程级报名预填信息。
* 预填只保存用户准备的联系方式、子女信息和目标时段,不代表正式报名,也不占用任何名额。
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动报名预填信息")
@Table("family_apply_prefill")
@EqualsAndHashCode(callSuper = false)
@TableIndexes({
@Index(name = "INDEX_FAMILY_APPLY_PREFILL_USER_COURSE", fields = {"userId", "courseId"}, unique = true),
@Index(name = "INDEX_FAMILY_APPLY_PREFILL_ACTIVITY", fields = {"activityId"}, unique = false)
})
public class FamilyApplyPrefill extends BaseModel implements Serializable {
@Name
@PrevInsert(uu32 = true)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("活动ID")
private String activityId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("课程ID")
private String courseId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("用户ID")
private String userId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("联系方式")
private String mobile;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("预选活动课程时段ID")
private String activityCourseId;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
@Comment("预填的子女动态字段和值")
private List<List<NutMap>> mobileColumnsValue;
}
@@ -4,6 +4,7 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.activity.family.models.FamilyActivity;
import com.budwk.app.zhgh.activity.family.models.FamilyApplyPrefill;
import com.budwk.app.zhgh.activity.family.models.FamilyCourse;
import com.budwk.app.zhgh.activity.family.models.FamilyUser;
import org.nutz.dao.Cnd;
@@ -67,6 +68,84 @@ public interface FamilyActivityService extends BaseService<FamilyActivity> {
*/
FamilyUser doSignUp(FamilyUser familyUser) throws Exception;
/**
* 取消当前用户在指定课程中的报名,并在报名时间内按照名额统计规则自动递补候补人员。
*
* @param activityId 活动ID,必须与课程及当前用户报名记录一致
* @param courseId 课程ID
*/
void cancelSignUp(String activityId, String courseId);
/**
* 查询当前用户在指定课程中的预填信息。
*
* @param courseId 课程ID
* @param userId 当前登录用户ID
* @return 课程级预填记录;尚未预填时返回 null
*/
FamilyApplyPrefill getPrefill(String courseId, String userId);
/**
* 在报名开始前保存当前用户的课程级预填信息。
* 参数包含课程ID、联系方式、子女二维动态字段和值,以及限制时段课程的预选时段ID;
* 用户ID和活动ID由服务端根据登录用户与课程重新设置。
*
* @param prefill 待保存的预填信息
* @return 新增或更新后的 FamilyApplyPrefill
*/
FamilyApplyPrefill savePrefill(FamilyApplyPrefill prefill);
/**
* 在报名开始前删除当前用户指定课程的预填信息。
*
* @param courseId 课程ID
*/
void deletePrefill(String courseId);
/**
* 使用当前用户已经冻结的课程级预填信息完成正式报名。
*
* @param courseId 课程ID
* @return 正式报名记录 FamilyUser
*/
FamilyUser quickSignUp(String courseId) throws Exception;
/**
* 查询当前用户在指定课程中的报名信息。
*
* @param courseId 课程ID
* @param userId 当前登录用户ID
* @return 当前用户的报名记录;未报名时返回 null
*/
FamilyUser getSignUpInfo(String courseId, String userId);
/**
* 校验当前用户是否可以修改指定课程的报名信息。
*
* @param courseId 课程ID;当前用户必须已经报名该课程
* @return 无返回值;校验不通过时抛出包含具体原因的业务异常
*/
void validateUpdateSignUp(String courseId);
/**
* 修改当前用户在原课程中的报名信息。
* 修改时执行与新增报名一致的时间、活动范围、名额、家属和时段校验,
* 但要求报名记录已经存在,并在统计时排除该用户的原记录。
*
* @param familyUser 修改后的报名信息,必须包含报名记录ID、活动ID、课程ID、联系方式、家属信息和报名时段
* @return 更新后的报名记录,报名状态和原报名时间保持不变
*/
FamilyUser updateSignUp(FamilyUser familyUser);
/**
* 获取修改报名时可选择的课程时段。
*
* @param courseId 课程ID
* @param familyUserId 当前报名记录ID,用于从时段占用人数中排除本人
* @return 时段选项列表,每项包含 text、value、remainingNum 和 disabled
*/
List<NutMap> getEditCourseTimeSelectList(String courseId, String familyUserId);
/**
* 异步插入每个报名成功人员的课程数据
* @param activityId
@@ -86,6 +165,13 @@ public interface FamilyActivityService extends BaseService<FamilyActivity> {
*/
boolean isSignFull(FamilyCourse course, Integer currentFamilyNumber);
/**
* 校验本次提交的家属数量、唯一标识及分场冲突。
* 当参数包含报名记录ID时,会排除该记录后再校验,用于修改报名。
*
* @param user 待校验的报名信息
* @return key 为 true 表示通过;key 为 false 时 value 为未通过原因
*/
Map<Boolean, String> validFamilyCount(FamilyUser user);
/**
@@ -1,6 +1,10 @@
package com.budwk.app.zhgh.activity.family.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.lang.Validator;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -8,11 +12,10 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.family.models.*;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityService;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityStatisticsService;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpActivityCourse;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpUserCourse;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.async.Async;
import org.nutz.aop.interceptor.ioc.TransAop;
@@ -53,13 +56,18 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
//插入类型限制
List<FamilyTypeLimit> typeLimits = activity.getTypeLimits();
typeLimits.forEach(v -> v.setActivityId(activity.getId()));
typeLimits.forEach(v -> {
v.setId(null);
v.setActivityId(activity.getId());
});
dao().insert(typeLimits);
List<FamilyCourse> courseList = activity.getCourseList();
for (FamilyCourse v : courseList) {
v.setId(null);
v.setActivityId(activity.getId());
v.setOpenOtherUnion(false);
v.getCourseTimeList().forEach(courseTime -> courseTime.setId(null));
dao().insert(v);
this.setCourseTimeAndInsert(v);
}
@@ -247,9 +255,49 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
public FamilyUser doSignUp(FamilyUser familyUser) throws Exception {
String userId = SecurityUtil.getUserId();
// 对课程行加数据库锁,使名额校验和正式写入在多节点部署时仍按顺序执行。
lockCourseForSignUp(familyUser.getCourseId());
//查询课程
FamilyCourse course = dao().fetch(FamilyCourse.class, familyUser.getCourseId());
if (course == null) {
throw new BaseException("报名课程不存在或已删除");
}
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
if (type == null) {
throw new BaseException("报名类型不存在或已变更");
}
if (StrUtil.isBlank(familyUser.getMobile())) {
throw new BaseException("请填写联系方式");
}
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
validateSignUpTimeAndScope(activity, userId, false);
familyUser.setActivityId(course.getActivityId());
if (getSignUpInfo(course.getId(), userId) != null) {
throw new BaseException("抱歉,您已经报名");
}
if (Lang.isEmpty(familyUser.getMobileColumnsValue())) {
throw new BaseException("请填写" + activity.getKeyWord() + "信息");
}
familyUser.setMobileColumnsValue(normalizeAndValidateFamilyColumns(
familyUser.getMobileColumnsValue(), type, course, activity));
Map<Boolean, String> familyCountResult = validFamilyCount(familyUser);
if (familyCountResult.containsKey(false)) {
throw new BaseException(familyCountResult.get(false));
}
int currentFamilyNumber = Boolean.TRUE.equals(type.getIsBringFamily()) && Boolean.TRUE.equals(type.getIsAddFamily())
? familyUser.getMobileColumnsValue().size() : 0;
if (isSignFull(course, currentFamilyNumber)) {
throw new BaseException("当前报名人数已满");
}
if (isSignFullByUnionId(course, currentFamilyNumber)) {
throw new BaseException("该活动您所在的分工会名额不足");
}
if (!isSignCourse(course, activity)) {
throw new BaseException(activity.getRestrictLimit() != null && activity.getRestrictLimit() == 3
? activity.getActivityName() + "限制报" + activity.getLimitNum() + "个活动,已达上限"
: "您选择的类型已达上限,不能再报该类型的了");
}
validateCourseTime(course, familyUser.getActivityCourseId(), null);
//如果这个课程的预留名额方式为报名人数不变
if (course.getReserveMode() == 2) {
//如果当前报名+已报小于这个课程限制人数
@@ -273,8 +321,8 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
dao().insert(familyUser);
if (StrUtil.isNotBlank(familyUser.getActivityCourseId())) {
TrainSignUpActivityCourse fetch = dao().fetch(TrainSignUpActivityCourse.class, familyUser.getActivityCourseId());
TrainSignUpUserCourse userCourse = new TrainSignUpUserCourse();
FamilyActivityCourse fetch = dao().fetch(FamilyActivityCourse.class, familyUser.getActivityCourseId());
FamilyUserCourse userCourse = new FamilyUserCourse();
userCourse.setActivityId(familyUser.getActivityId());
userCourse.setCourseId(familyUser.getCourseId());
userCourse.setUserId(familyUser.getUserId());
@@ -285,15 +333,718 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
userCourse.setActivityCourseId(fetch.getId());
dao().insert(userCourse);
} else {
asyncInsertUserCourse(familyUser.getActivityId(), familyUser.getCourseId(), userId);
insertUserCourse(familyUser.getActivityId(), familyUser.getCourseId(), userId);
}
return familyUser;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void cancelSignUp(String activityId, String courseId) {
if (StrUtil.isBlank(activityId) || StrUtil.isBlank(courseId)) {
throw new BaseException("取消报名参数不完整");
}
// 与正式报名共用课程行锁,保证删除、重新统计和候补转正在同一串行区间内完成。
lockCourseForSignUp(courseId);
FamilyCourse course = dao().fetch(FamilyCourse.class, courseId);
if (course == null || !Objects.equals(course.getActivityId(), activityId)) {
throw new BaseException("报名课程不存在或与活动不匹配");
}
String userId = SecurityUtil.getUserId();
FamilyUser currentSignUp = dao().fetch(FamilyUser.class,
Cnd.where(FamilyUser::getActivityId, "=", activityId)
.and(FamilyUser::getCourseId, "=", courseId)
.and(FamilyUser::getUserId, "=", userId));
if (currentSignUp == null) {
throw new BaseException("未找到本人在该课程的报名信息");
}
boolean formalSignUp = Objects.equals(currentSignUp.getState(), 1)
|| Objects.equals(currentSignUp.getState(), 3);
dao().clear(FamilyUserCourse.class, Cnd.where(FamilyUserCourse::getActivityId, "=", activityId)
.and(FamilyUserCourse::getCourseId, "=", courseId)
.and(FamilyUserCourse::getUserId, "=", userId));
dao().clear(FamilyUser.class, Cnd.where(FamilyUser::getId, "=", currentSignUp.getId()));
FamilyActivity activity = dao().fetch(FamilyActivity.class, activityId);
if (formalSignUp && Objects.equals(course.getReserveMode(), 2) && isWithinSignUpTime(activity)) {
promoteWaitingUsers(course, currentSignUp);
}
}
/**
* 按报名时间顺序递补候补人员。本人和家属是否计入总人数,严格按课程类型配置计算。
*/
private void promoteWaitingUsers(FamilyCourse course, FamilyUser canceledSignUp) {
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
if (type == null) {
return;
}
Cnd waitingCnd = Cnd.where(FamilyUser::getActivityId, "=", course.getActivityId())
.and(FamilyUser::getCourseId, "=", course.getId())
.and(FamilyUser::getState, "=", 2);
// 配置了分工会名额时,取消释放的是原分工会名额,只允许该分工会候补人员递补。
if (Lang.isNotEmpty(course.getUnionLimit())) {
waitingCnd.and(FamilyUser::getUnionId, "=", canceledSignUp.getUnionId());
}
waitingCnd.asc(FamilyUser::getSignUpTime);
List<FamilyUser> waitingUsers = dao().query(FamilyUser.class, waitingCnd);
for (FamilyUser waitingUser : waitingUsers) {
int familyNumber = Lang.isNotEmpty(waitingUser.getMobileColumnsValue())
? waitingUser.getMobileColumnsValue().size() : 0;
int contribution = getFamilyContribution(type, familyNumber);
// 严格保持候补先后顺序;排在前面的人员当前无法容纳时,不跨过该人员补后续人员。
if (!canPromoteWaitingUser(course, waitingUser, contribution)) {
break;
}
waitingUser.setState(3);
dao().update(waitingUser);
}
}
/**
* 校验候补人员转正后的课程总人数、分工会人数和独立时段人数是否均不超限。
*/
private boolean canPromoteWaitingUser(FamilyCourse course, FamilyUser waitingUser, int contribution) {
int courseLimit = course.getCoursePeopleNumber();
int reservedNumber = course.getCourseReservedNumber();
int normalCount = statisticsService.queryCourseCount(course.getId(), course.getCourseType());
if (courseLimit == 0 || normalCount + contribution + reservedNumber > courseLimit) {
return false;
}
if (Lang.isNotEmpty(course.getUnionLimit())) {
NutMap unionLimit = course.getUnionLimit().stream()
.filter(item -> Objects.equals(item.getString("id"), waitingUser.getUnionId()))
.findFirst().orElse(null);
if (unionLimit == null) {
return false;
}
int unionCount = statisticsService.queryCourseCount(
course.getId(), course.getCourseType(), waitingUser.getUnionId());
if (unionCount + contribution > unionLimit.getInt("limitCount")) {
return false;
}
}
return hasAvailableCourseTime(course, waitingUser);
}
/**
* 限制独立时段报名时,候补人员只能在其原选择时段仍有空位的情况下转正。
*/
private boolean hasAvailableCourseTime(FamilyCourse course, FamilyUser waitingUser) {
if (!Boolean.TRUE.equals(course.getCourseIsLimitApply())) {
return true;
}
if (StrUtil.isBlank(waitingUser.getActivityCourseId())) {
return false;
}
FamilyActivityCourse activityCourse = dao().fetch(FamilyActivityCourse.class,
waitingUser.getActivityCourseId());
if (activityCourse == null || !Objects.equals(activityCourse.getCourseId(), course.getId())
|| activityCourse.getCourseLimitNum() == null) {
return false;
}
List<FamilyUser> formalUsers = dao().query(FamilyUser.class,
Cnd.where(FamilyUser::getCourseId, "=", course.getId())
.and(FamilyUser::getState, "in", List.of(1, 3)));
List<String> formalUserIds = formalUsers.stream().map(FamilyUser::getUserId).toList();
int usedCount = Lang.isEmpty(formalUserIds) ? 0 : dao().count(FamilyUserCourse.class,
Cnd.where(FamilyUserCourse::getActivityCourseId, "=", activityCourse.getId())
.and(FamilyUserCourse::getCourseId, "=", course.getId())
.and(FamilyUserCourse::getUserId, "in", formalUserIds));
return usedCount + 1 <= activityCourse.getCourseLimitNum();
}
/**
* 自动递补只在活动报名开始时间(含)至结束时间(不含)内执行。
*/
private boolean isWithinSignUpTime(FamilyActivity activity) {
if (activity == null || activity.getActivitySignUpStartTime() == null
|| activity.getActivitySignUpEndTime() == null) {
return false;
}
Date now = new Date();
return DateUtil.compare(now, activity.getActivitySignUpStartTime()) >= 0
&& DateUtil.compare(now, activity.getActivitySignUpEndTime()) < 0;
}
@Override
public FamilyApplyPrefill getPrefill(String courseId, String userId) {
if (StrUtil.isBlank(courseId) || StrUtil.isBlank(userId)) {
return null;
}
return dao().fetch(FamilyApplyPrefill.class, Cnd.where(FamilyApplyPrefill::getCourseId, "=", courseId)
.and(FamilyApplyPrefill::getUserId, "=", userId));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public FamilyApplyPrefill savePrefill(FamilyApplyPrefill prefill) {
if (prefill == null || StrUtil.isBlank(prefill.getCourseId())) {
throw new BaseException("预填课程信息不能为空");
}
String userId = SecurityUtil.getUserId();
FamilyCourse course = dao().fetch(FamilyCourse.class, prefill.getCourseId());
if (course == null) {
throw new BaseException("预填课程不存在或已删除");
}
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
validatePrefillTimeAndScope(activity, userId);
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
if (type == null) {
throw new BaseException("报名类型不存在或已变更");
}
if (StrUtil.isBlank(prefill.getMobile())) {
throw new BaseException("请填写联系方式");
}
List<List<NutMap>> normalizedColumns = normalizeAndValidateFamilyColumns(
prefill.getMobileColumnsValue(), type, course, activity);
validatePrefillCourseTime(course, prefill.getActivityCourseId());
// 复用正式报名的家属人数、唯一标识及分场冲突规则,但预填本身不计入任何名额。
FamilyUser validateUser = new FamilyUser();
validateUser.setActivityId(activity.getId());
validateUser.setCourseId(course.getId());
validateUser.setMobileColumnsValue(normalizedColumns);
Map<Boolean, String> familyCountResult = validFamilyCount(validateUser);
if (familyCountResult.containsKey(false)) {
throw new BaseException(familyCountResult.get(false));
}
FamilyApplyPrefill current = getPrefill(course.getId(), userId);
if (current == null) {
current = new FamilyApplyPrefill();
current.setActivityId(activity.getId());
current.setCourseId(course.getId());
current.setUserId(userId);
current.setMobile(prefill.getMobile());
current.setActivityCourseId(prefill.getActivityCourseId());
current.setMobileColumnsValue(normalizedColumns);
dao().insert(current);
} else {
current.setMobile(prefill.getMobile());
current.setActivityCourseId(prefill.getActivityCourseId());
current.setMobileColumnsValue(normalizedColumns);
dao().update(current);
}
return current;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void deletePrefill(String courseId) {
String userId = SecurityUtil.getUserId();
FamilyCourse course = dao().fetch(FamilyCourse.class, courseId);
if (course == null) {
throw new BaseException("预填课程不存在或已删除");
}
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
validatePrefillTimeAndScope(activity, userId);
dao().clear(FamilyApplyPrefill.class, Cnd.where(FamilyApplyPrefill::getCourseId, "=", courseId)
.and(FamilyApplyPrefill::getUserId, "=", userId));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public FamilyUser quickSignUp(String courseId) throws Exception {
String userId = SecurityUtil.getUserId();
FamilyApplyPrefill prefill = getPrefill(courseId, userId);
if (prefill == null) {
throw new BaseException("您尚未预填该课程的报名信息");
}
FamilyCourse course = dao().fetch(FamilyCourse.class, courseId);
if (course == null || !Objects.equals(course.getActivityId(), prefill.getActivityId())) {
throw new BaseException("预填课程不存在或已变更");
}
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
if (activity == null) {
throw new BaseException("报名活动不存在或已删除");
}
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
if (type == null) {
throw new BaseException("报名类型不存在或已变更");
}
FamilyUser familyUser = new FamilyUser();
familyUser.setActivityId(activity.getId());
familyUser.setCourseId(course.getId());
familyUser.setMobile(prefill.getMobile());
familyUser.setActivityCourseId(prefill.getActivityCourseId());
familyUser.setMobileColumnsValue(prefill.getMobileColumnsValue());
return doSignUp(familyUser);
}
@Override
public FamilyUser getSignUpInfo(String courseId, String userId) {
if (StrUtil.isBlank(courseId) || StrUtil.isBlank(userId)) {
return null;
}
return dao().fetch(FamilyUser.class, Cnd.where(FamilyUser::getCourseId, "=", courseId)
.and(FamilyUser::getUserId, "=", userId));
}
@Override
public void validateUpdateSignUp(String courseId) {
FamilyUser currentSignUp = getSignUpInfo(courseId, SecurityUtil.getUserId());
if (currentSignUp == null) {
throw new BaseException("未找到本人在该课程的报名信息");
}
validateUpdateSignUpData(currentSignUp, currentSignUp);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public FamilyUser updateSignUp(FamilyUser familyUser) {
if (familyUser == null || StrUtil.isBlank(familyUser.getId()) || StrUtil.isBlank(familyUser.getCourseId())) {
throw new BaseException("报名信息不完整");
}
String currentUserId = SecurityUtil.getUserId();
FamilyUser currentSignUp = getSignUpInfo(familyUser.getCourseId(), currentUserId);
if (currentSignUp == null || !Objects.equals(currentSignUp.getId(), familyUser.getId())) {
throw new BaseException("未找到本人在该课程的报名信息");
}
validateUpdateSignUpData(familyUser, currentSignUp);
String oldActivityCourseId = currentSignUp.getActivityCourseId();
currentSignUp.setMobile(familyUser.getMobile());
currentSignUp.setMobileColumnsValue(familyUser.getMobileColumnsValue());
currentSignUp.setActivityCourseId(familyUser.getActivityCourseId());
dao().update(currentSignUp);
syncUpdatedUserCourse(currentSignUp, oldActivityCourseId);
return currentSignUp;
}
/**
* 修改按钮预校验与最终保存共用本方法,确保两处业务规则不会产生偏差。
*/
private void validateUpdateSignUpData(FamilyUser familyUser, FamilyUser currentSignUp) {
FamilyCourse course = dao().fetch(FamilyCourse.class, familyUser.getCourseId());
if (course == null || !Objects.equals(course.getActivityId(), currentSignUp.getActivityId())) {
throw new BaseException("报名课程不存在或已变更");
}
FamilyActivity activity = dao().fetch(FamilyActivity.class, course.getActivityId());
validateSignUpTimeAndScope(activity, SecurityUtil.getUserId(), true);
if (Lang.isEmpty(familyUser.getMobileColumnsValue())) {
throw new BaseException("请填写" + activity.getKeyWord() + "信息");
}
// 修改校验必须排除本人原报名,否则人数、唯一标识及报名限制都会重复计算。
familyUser.setActivityId(currentSignUp.getActivityId());
familyUser.setUserId(SecurityUtil.getUserId());
Map<Boolean, String> familyCountResult = validFamilyCount(familyUser);
if (familyCountResult.containsKey(false)) {
throw new BaseException(familyCountResult.get(false));
}
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
if (type == null) {
throw new BaseException("报名类型不存在或已变更");
}
int currentFamilyNumber = Boolean.TRUE.equals(type.getIsBringFamily()) && Boolean.TRUE.equals(type.getIsAddFamily())
? familyUser.getMobileColumnsValue().size() : 0;
if (isSignFullForUpdate(course, type, currentFamilyNumber, currentSignUp, null)) {
throw new BaseException("当前报名人数已满");
}
if (isSignFullForUpdate(course, type, currentFamilyNumber, currentSignUp, currentSignUp.getUnionId())) {
throw new BaseException("该活动您所在的分工会名额不足");
}
if (!isSignCourseForUpdate(course, activity, currentSignUp.getId())) {
throw new BaseException(activity.getRestrictLimit() != null && activity.getRestrictLimit() == 3
? activity.getActivityName() + "限制报" + activity.getLimitNum() + "个活动,已达上限"
: "您选择的类型已达上限,不能再报该类型的了");
}
validateCourseTime(course, familyUser.getActivityCourseId(), currentSignUp);
}
@Override
public List<NutMap> getEditCourseTimeSelectList(String courseId, String familyUserId) {
FamilyUser currentSignUp = dao().fetch(FamilyUser.class, familyUserId);
if (currentSignUp == null || !Objects.equals(currentSignUp.getCourseId(), courseId)
|| !Objects.equals(currentSignUp.getUserId(), SecurityUtil.getUserId())) {
throw new BaseException("未找到本人在该课程的报名信息");
}
List<FamilyActivityCourse> courseTimeList = dao().query(FamilyActivityCourse.class,
Cnd.where(FamilyActivityCourse::getCourseId, "=", courseId).asc(FamilyActivityCourse::getCourseStartTime));
List<FamilyUser> normalUserList = dao().query(FamilyUser.class, Cnd.where(FamilyUser::getCourseId, "=", courseId)
.and(FamilyUser::getState, "in", List.of(1, 3))
.and(FamilyUser::getId, "!=", currentSignUp.getId()));
List<String> normalUserIds = normalUserList.stream().map(FamilyUser::getUserId).toList();
return courseTimeList.stream().map(courseTime -> {
int usedCount = Lang.isEmpty(normalUserIds) ? 0 : dao().count(FamilyUserCourse.class,
Cnd.where(FamilyUserCourse::getActivityCourseId, "=", courseTime.getId())
.and(FamilyUserCourse::getCourseId, "=", courseId)
.and(FamilyUserCourse::getUserId, "in", normalUserIds));
int limitCount = courseTime.getCourseLimitNum() != null ? courseTime.getCourseLimitNum() : 0;
int remainingNum = Math.max(limitCount - usedCount, 0);
NutMap option = NutMap.NEW();
option.put("remainingNum", remainingNum);
option.put("text", DateUtil.format(courseTime.getCourseStartTime(), "HH:mm") + ""
+ DateUtil.format(courseTime.getCourseEndTime(), "HH:mm") + "段(剩" + remainingNum + ")");
option.put("value", courseTime.getId());
option.put("disabled", remainingNum == 0);
return option;
}).filter(option -> option.getInt("remainingNum") > 0).toList();
}
/**
* 锁定课程记录。所有正式报名都必须先取得该锁,确保名额统计与写入处于同一串行区间。
*/
private void lockCourseForSignUp(String courseId) {
if (StrUtil.isBlank(courseId)) {
throw new BaseException("报名课程信息不能为空");
}
Sql lockSql = Sqls.create("SELECT id FROM family_course WHERE id = @courseId FOR UPDATE");
lockSql.setParam("courseId", courseId);
lockSql.setCallback(Sqls.callback.str());
Object lockedCourseId = dao().execute(lockSql).getResult();
if (lockedCourseId == null) {
throw new BaseException("报名课程不存在或已删除");
}
}
/**
* 预填新增、修改和删除共用时间及人员范围校验。
* 预填只允许发生在报名开始时间之前,到达开始时间后数据立即冻结。
*/
private void validatePrefillTimeAndScope(FamilyActivity activity, String userId) {
if (activity == null) {
throw new BaseException("报名活动不存在或已删除");
}
if (activity.getActivitySignUpStartTime() == null) {
throw new BaseException("活动未配置报名开始时间,不能预填报名信息");
}
if (DateUtil.compare(new Date(), activity.getActivitySignUpStartTime()) >= 0) {
throw new BaseException("报名已经开始,预填信息已冻结,不能继续修改");
}
int scopeCount = dao().count(ActivityUserScope.class,
Cnd.where(ActivityUserScope::getGroupId, "=", activity.getActivityGroupId())
.and(ActivityUserScope::getUserId, "=", userId));
if (scopeCount == 0) {
throw new BaseException("抱歉,您没有此次活动的权限");
}
}
/**
* 预填时只校验时段归属和必选要求,不查询或占用时段名额。
*/
private void validatePrefillCourseTime(FamilyCourse course, String activityCourseId) {
if (!Boolean.TRUE.equals(course.getCourseIsLimitApply())) {
return;
}
if (StrUtil.isBlank(activityCourseId)) {
throw new BaseException("请选择报名时段");
}
FamilyActivityCourse courseTime = dao().fetch(FamilyActivityCourse.class, activityCourseId);
if (courseTime == null || !Objects.equals(courseTime.getCourseId(), course.getId())) {
throw new BaseException("报名时段不存在或已变更");
}
}
/**
* 根据课程类型当前动态字段配置重建子女数据,并在服务端完成必填、格式、年龄和性别校验。
* 前端只负责提交 columnCode 与 columnValue,字段名称和控件类型均以服务端配置为准。
*/
private List<List<NutMap>> normalizeAndValidateFamilyColumns(List<List<NutMap>> submittedFamilies,
FamilyType type, FamilyCourse course, FamilyActivity activity) {
if (StrUtil.isBlank(activity.getOnlyKey())) {
throw new BaseException("活动未配置家属唯一标识,请联系管理员");
}
if (activity.getFamilyMaxCount() == null) {
throw new BaseException("活动未配置家属最多人数,请联系管理员");
}
if (Lang.isEmpty(submittedFamilies)) {
throw new BaseException("请填写" + activity.getKeyWord() + "信息");
}
if (activity.getFamilyMaxCount() != null && submittedFamilies.size() > activity.getFamilyMaxCount()) {
throw new BaseException(activity.getKeyWord() + "人数最多为" + activity.getFamilyMaxCount());
}
dao().fetchLinks(type, "familyMobileSignColumnList", Cnd.NEW().asc("columnIndex"));
List<FamilyMobileSignColumn> columnConfigs = type.getFamilyMobileSignColumnList();
if (Lang.isEmpty(columnConfigs)) {
throw new BaseException("报名类型未配置" + activity.getKeyWord() + "信息字段");
}
List<List<NutMap>> normalizedFamilies = new ArrayList<>();
Set<String> currentOnlyKeyValues = new HashSet<>();
for (int familyIndex = 0; familyIndex < submittedFamilies.size(); familyIndex++) {
List<NutMap> submittedFamily = submittedFamilies.get(familyIndex);
Map<String, NutMap> submittedValueMap = Lang.isEmpty(submittedFamily) ? new HashMap<>()
: submittedFamily.stream().filter(Objects::nonNull)
.filter(item -> StrUtil.isNotBlank(item.getString("columnCode")))
.collect(Collectors.toMap(item -> item.getString("columnCode"), item -> item, (left, right) -> left));
List<NutMap> normalizedFamily = new ArrayList<>();
for (FamilyMobileSignColumn config : columnConfigs) {
NutMap submittedColumn = submittedValueMap.get(config.getColumnCode());
Object columnValue = submittedColumn == null ? null : submittedColumn.get("columnValue");
validateDynamicColumnValue(config, columnValue, familyIndex, course, activity);
NutMap normalizedColumn = NutMap.NEW();
normalizedColumn.put("columnName", config.getColumnName());
normalizedColumn.put("columnCode", config.getColumnCode());
normalizedColumn.put("columnValue", columnValue);
normalizedColumn.put("columnFormType", config.getColumnFormType());
normalizedFamily.add(normalizedColumn);
}
normalizedFamilies.add(normalizedFamily);
NutMap onlyKeyColumn = normalizedFamily.stream()
.filter(item -> Objects.equals(activity.getOnlyKey(), item.getString("columnCode")))
.findFirst().orElse(null);
if (onlyKeyColumn == null || StrUtil.isBlank(onlyKeyColumn.getString("columnValue"))) {
throw new BaseException(activity.getKeyWord() + (familyIndex + 1) + "缺少唯一标识信息");
}
if (!currentOnlyKeyValues.add(onlyKeyColumn.getString("columnValue"))) {
throw new BaseException(activity.getKeyWord() + "不能重复选择同一人");
}
}
return normalizedFamilies;
}
/**
* 校验一个动态字段。参数为字段配置、用户值、子女序号、课程和活动;校验失败抛出业务异常,无返回值。
*/
private void validateDynamicColumnValue(FamilyMobileSignColumn config, Object columnValue, int familyIndex,
FamilyCourse course, FamilyActivity activity) {
String displayName = activity.getKeyWord() + (familyIndex + 1) + "" + config.getColumnName();
boolean empty = columnValue == null || (columnValue instanceof String && StrUtil.isBlank((String) columnValue))
|| (columnValue instanceof Collection<?> && ((Collection<?>) columnValue).isEmpty());
if (Boolean.TRUE.equals(config.getIsRequired()) && empty) {
throw new BaseException(displayName + "不能为空");
}
if (empty || StrUtil.isBlank(config.getValidRule())) {
return;
}
String stringValue = String.valueOf(columnValue).trim();
if ("mobile".equals(config.getValidRule()) && !Validator.isMobile(stringValue)) {
throw new BaseException(displayName + "格式不正确");
}
if ("email".equals(config.getValidRule()) && !Validator.isEmail(stringValue)) {
throw new BaseException(displayName + "格式不正确");
}
if (!"idCard".equals(config.getValidRule())) {
return;
}
if (!IdcardUtil.isValidCard(stringValue)) {
throw new BaseException(displayName + "格式不正确");
}
if (course.isFamilyAgeLimit()) {
int age = IdcardUtil.getAgeByIdCard(stringValue);
if (course.getMinAge() != null && age < course.getMinAge()) {
throw new BaseException("限制报名最小年龄为" + course.getMinAge());
}
if (course.getMaxAge() != null && age > course.getMaxAge()) {
throw new BaseException("限制报名最大年龄为" + course.getMaxAge());
}
}
if (course.isFamilySexLimit() && StrUtil.isNotBlank(course.getFamilySex())) {
String sex = IdcardUtil.getGenderByIdCard(stringValue) == 1 ? "" : "";
if (!Objects.equals(course.getFamilySex(), sex)) {
throw new BaseException("限制报名性别为" + course.getFamilySex());
}
}
}
/**
* 新增和修改共用活动时间及面向对象校验;update 仅用于区分业务提示文案。
*/
private void validateSignUpTimeAndScope(FamilyActivity activity, String userId, boolean update) {
if (activity == null) {
throw new BaseException("报名活动不存在或已删除");
}
Date now = new Date();
if (activity.getActivitySignUpStartTime() == null || DateUtil.compare(now, activity.getActivitySignUpStartTime()) < 0) {
throw new BaseException(update ? "报名未开始,不能修改报名信息" : "报名未开始");
}
if (activity.getActivitySignUpEndTime() == null || DateUtil.compare(now, activity.getActivitySignUpEndTime()) >= 0) {
throw new BaseException(update ? "报名已结束,不能修改报名信息" : "报名已结束");
}
int scopeCount = dao().count(ActivityUserScope.class, Cnd.where(ActivityUserScope::getGroupId, "=", activity.getActivityGroupId())
.and(ActivityUserScope::getUserId, "=", userId));
if (scopeCount == 0) {
throw new BaseException("抱歉,您没有此次活动的权限");
}
}
/**
* 按新增报名的名额规则校验修改数据,并从正常或候补人数中扣除本人原报名贡献。
*/
private boolean isSignFullForUpdate(FamilyCourse course, FamilyType type, int currentFamilyNumber,
FamilyUser currentSignUp, String unionId) {
if (course.getCoursePeopleNumber() == 0) {
return true;
}
int limitCount = course.getCoursePeopleNumber();
if (StrUtil.isNotBlank(unionId)) {
List<NutMap> unionLimitList = course.getUnionLimit();
if (Lang.isEmpty(unionLimitList)) {
return false;
}
NutMap unionLimit = unionLimitList.stream()
.filter(item -> Objects.equals(item.getString("id"), unionId)).findFirst().orElse(null);
if (unionLimit == null) {
return true;
}
limitCount = unionLimit.getInt("limitCount");
}
int normalCount = StrUtil.isBlank(unionId)
? statisticsService.queryCourseCount(course.getId(), course.getCourseType())
: statisticsService.queryCourseCount(course.getId(), course.getCourseType(), unionId);
int waitCount = StrUtil.isBlank(unionId)
? statisticsService.queryCourseWaitCount(course.getId(), course.getCourseType())
: statisticsService.queryCourseWaitCount(course.getId(), course.getCourseType(), unionId);
int oldFamilyNumber = Lang.isNotEmpty(currentSignUp.getMobileColumnsValue())
? currentSignUp.getMobileColumnsValue().size() : 0;
int oldContribution = getFamilyContribution(type, oldFamilyNumber);
if (Objects.equals(currentSignUp.getState(), 1) || Objects.equals(currentSignUp.getState(), 3)) {
normalCount = Math.max(normalCount - oldContribution, 0);
} else if (Objects.equals(currentSignUp.getState(), 2)) {
waitCount = Math.max(waitCount - oldContribution, 0);
}
int newContribution = getFamilyContribution(type, currentFamilyNumber);
if (limitCount - normalCount > 0) {
int reservedNumber = StrUtil.isBlank(unionId) ? course.getCourseReservedNumber() : 0;
return normalCount + newContribution + reservedNumber > limitCount;
}
int waitingNum = course.getWaitingNum() == null ? 0 : course.getWaitingNum();
return waitCount + newContribution > waitingNum;
}
/**
* 计算一条报名记录占用的总名额,规则与现有报名统计保持一致。
*/
private int getFamilyContribution(FamilyType type, int familyNumber) {
int contribution = Boolean.TRUE.equals(type.getSelfAddFamily()) ? 1 : 0;
if (Boolean.TRUE.equals(type.getIsBringFamily()) && Boolean.TRUE.equals(type.getIsAddFamily())) {
contribution += familyNumber;
}
return contribution;
}
/**
* 修改报名数量限制校验时排除当前报名记录,再把本次修改作为一条报名重新计算。
*/
private boolean isSignCourseForUpdate(FamilyCourse course, FamilyActivity activity, String currentSignUpId) {
if (activity.getRestrictLimit() == null || activity.getRestrictLimit() == 1) {
return true;
}
if (activity.getRestrictLimit() == 2) {
FamilyTypeLimit typeLimit = dao().fetch(FamilyTypeLimit.class, Cnd.where(FamilyTypeLimit::getTypeId, "=", course.getCourseType())
.and(FamilyTypeLimit::getActivityId, "=", activity.getId()));
if (typeLimit == null || typeLimit.getLimitNum() == 0) {
return true;
}
Sql sql = Sqls.create("""
SELECT count(tsus.id)
FROM family_user tsus
LEFT JOIN family_course tsuc ON tsuc.id = tsus.courseId
WHERE tsuc.courseType = @courseType
AND tsus.userId = @userId
AND tsus.activityId = @activityId
AND tsus.id != @currentSignUpId
""");
sql.setParam("courseType", course.getCourseType());
sql.setParam("userId", SecurityUtil.getUserId());
sql.setParam("activityId", activity.getId());
sql.setParam("currentSignUpId", currentSignUpId);
return count(sql) < typeLimit.getLimitNum();
}
int activitySignCount = dao().count(FamilyUser.class, Cnd.where(FamilyUser::getActivityId, "=", activity.getId())
.and(FamilyUser::getUserId, "=", SecurityUtil.getUserId())
.and(FamilyUser::getId, "!=", currentSignUpId));
return activity.getLimitNum() == null || activitySignCount < activity.getLimitNum();
}
/**
* 校验独立报名时段名额;修改时排除本人原记录,新增时直接按当前占用人数计算。
*/
private void validateCourseTime(FamilyCourse course, String activityCourseId, FamilyUser currentSignUp) {
if (!Boolean.TRUE.equals(course.getCourseIsLimitApply())) {
return;
}
if (StrUtil.isBlank(activityCourseId)) {
throw new BaseException("请选择报名时段");
}
FamilyActivityCourse activityCourse = dao().fetch(FamilyActivityCourse.class, activityCourseId);
if (activityCourse == null || !Objects.equals(activityCourse.getCourseId(), course.getId())) {
throw new BaseException("报名时段不存在或已变更");
}
Cnd normalUserCnd = Cnd.where(FamilyUser::getCourseId, "=", course.getId())
.and(FamilyUser::getState, "in", List.of(1, 3));
if (currentSignUp != null) {
normalUserCnd.and(FamilyUser::getId, "!=", currentSignUp.getId());
}
List<FamilyUser> normalUserList = dao().query(FamilyUser.class, normalUserCnd);
List<String> normalUserIds = normalUserList.stream().map(FamilyUser::getUserId).toList();
int usedCount = Lang.isEmpty(normalUserIds) ? 0 : dao().count(FamilyUserCourse.class,
Cnd.where(FamilyUserCourse::getActivityCourseId, "=", activityCourseId)
.and(FamilyUserCourse::getCourseId, "=", course.getId())
.and(FamilyUserCourse::getUserId, "in", normalUserIds));
if (activityCourse.getCourseLimitNum() == null || usedCount + 1 > activityCourse.getCourseLimitNum()) {
throw new BaseException("该时间段名额已报满,请选择其他时段报名");
}
}
/**
* 仅在报名时段发生变化时重建本人课程时段记录,未变化时保留原签到和领取信息。
*/
private void syncUpdatedUserCourse(FamilyUser currentSignUp, String oldActivityCourseId) {
if (Objects.equals(oldActivityCourseId, currentSignUp.getActivityCourseId())) {
return;
}
dao().clear(FamilyUserCourse.class, Cnd.where(FamilyUserCourse::getActivityId, "=", currentSignUp.getActivityId())
.and(FamilyUserCourse::getCourseId, "=", currentSignUp.getCourseId())
.and(FamilyUserCourse::getUserId, "=", currentSignUp.getUserId()));
List<FamilyActivityCourse> selectedCourseTimes;
if (StrUtil.isNotBlank(currentSignUp.getActivityCourseId())) {
FamilyActivityCourse selected = dao().fetch(FamilyActivityCourse.class, currentSignUp.getActivityCourseId());
selectedCourseTimes = selected == null ? new ArrayList<>() : List.of(selected);
} else {
selectedCourseTimes = dao().query(FamilyActivityCourse.class,
Cnd.where(FamilyActivityCourse::getCourseId, "=", currentSignUp.getCourseId()));
}
List<FamilyUserCourse> userCourseList = selectedCourseTimes.stream().map(courseTime -> {
FamilyUserCourse userCourse = new FamilyUserCourse();
userCourse.setActivityId(currentSignUp.getActivityId());
userCourse.setCourseId(currentSignUp.getCourseId());
userCourse.setUserId(currentSignUp.getUserId());
userCourse.setCourseStartTime(courseTime.getCourseStartTime());
userCourse.setCourseEndTime(courseTime.getCourseEndTime());
userCourse.setAttend(false);
userCourse.setActivityCourseId(courseTime.getId());
return userCourse;
}).toList();
if (Lang.isNotEmpty(userCourseList)) {
dao().insert(userCourseList);
}
}
@Async
@Override
public void asyncInsertUserCourse(String activityId, String courseId, String userId) {
log.info("异步插入{}的上课信息,课程ID为{},活动ID为{}", userId, courseId, activityId);
insertUserCourse(activityId, courseId, userId);
}
/**
* 同步写入报名人员的全部课程时段,用于保证正式报名主记录与时段记录处于同一事务。
*/
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<>();
courseList.forEach(v -> {
@@ -308,7 +1059,9 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
userCourse.setActivityCourseId(v.getId());
list.add(userCourse);
});
dao().insert(list);
if (Lang.isNotEmpty(list)) {
dao().insert(list);
}
}
/**
@@ -374,9 +1127,26 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
public Map<Boolean, String> validFamilyCount(FamilyUser user) {
String activityId = user.getActivityId();
// 查询报名记录
List<FamilyUser> list = dao().query(FamilyUser.class, Cnd.where(FamilyUser::getActivityId, "=", activityId).and(FamilyUser::getUserId, "=", SecurityUtil.getUserId()));
Cnd signUpCnd = Cnd.where(FamilyUser::getActivityId, "=", activityId)
.and(FamilyUser::getUserId, "=", SecurityUtil.getUserId());
if (StrUtil.isNotBlank(user.getId())) {
signUpCnd.and(FamilyUser::getId, "!=", user.getId());
}
List<FamilyUser> list = dao().query(FamilyUser.class, signUpCnd);
FamilyActivity activity = dao().fetch(FamilyActivity.class, activityId);
if (activity == null) {
return Map.of(false, "报名活动不存在或已删除");
}
if (StrUtil.isBlank(activity.getOnlyKey())) {
return Map.of(false, "活动未配置家属唯一标识,请联系管理员");
}
if (activity.getFamilyMaxCount() == null) {
return Map.of(false, "活动未配置家属最多人数,请联系管理员");
}
if (Lang.isEmpty(user.getMobileColumnsValue())) {
return Map.of(false, "请填写" + activity.getKeyWord() + "信息");
}
List<List<NutMap>> allMobileColumnsValue = new ArrayList<>(list.stream()
.map(FamilyUser::getMobileColumnsValue)
@@ -390,8 +1160,10 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
//return allMobileColumnsValue.size() > activity.getFamilyMaxCount();
long count = allMobileColumnsValue.stream()
.filter(Objects::nonNull)
.flatMap(List::stream)
.filter(nutMap -> activity.getOnlyKey().equals(nutMap.getString("columnCode")))
.filter(Objects::nonNull)
.filter(nutMap -> Objects.equals(activity.getOnlyKey(), nutMap.getString("columnCode")))
.map(nutMap -> nutMap.getString("columnValue"))
.filter(Objects::nonNull)
.distinct()
@@ -415,7 +1187,11 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
String tempValue = "";
// 循环家属
for (List<NutMap> listMap : currentValue) {
NutMap nutMap = listMap.stream().filter(o -> o.getString("columnCode").equals(activity.getOnlyKey())).findFirst().orElse(null);
if (Lang.isEmpty(listMap)) {
continue;
}
NutMap nutMap = listMap.stream().filter(Objects::nonNull)
.filter(o -> Objects.equals(o.getString("columnCode"), activity.getOnlyKey())).findFirst().orElse(null);
if(nutMap == null) {
continue;
}
@@ -430,8 +1206,15 @@ public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> i
for (FamilyUser hisRecord : list) {
List<List<NutMap>> hisValueList = hisRecord.getMobileColumnsValue();
if (Lang.isEmpty(hisValueList)) {
continue;
}
for (List<NutMap> hisValue : hisValueList) {
NutMap hisNutMap = hisValue.stream().filter(o -> o.getString("columnCode").equals(activity.getOnlyKey())).findFirst().orElse(null);
if (Lang.isEmpty(hisValue)) {
continue;
}
NutMap hisNutMap = hisValue.stream().filter(Objects::nonNull)
.filter(o -> Objects.equals(o.getString("columnCode"), activity.getOnlyKey())).findFirst().orElse(null);
if(hisNutMap == null) {
continue;
}
@@ -121,6 +121,8 @@ public class QsvActivity extends BaseModel implements Serializable, SysHomeConve
sysHomeActivity.setEndDate(this.getEndTime());
sysHomeActivity.setAllowUserGroupId(this.getGroupId());
sysHomeActivity.setEnable(true);
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
}
@@ -215,6 +215,8 @@ public class ActivitySchool extends BaseModel implements Serializable, SysHomeCo
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(true);
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
}
@@ -146,6 +146,8 @@ public class TrainSignUpActivity extends BaseModel implements Serializable, SysH
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(!this.isDisabled());
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
@@ -98,6 +98,8 @@ public class EvaluateActivity extends BaseModel implements Serializable, SysHome
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(this.getEnable());
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
}
@@ -34,6 +34,7 @@ import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
@@ -148,6 +149,15 @@ public class SpecialStaffManageController {
}
@At
@Ok("void")
@ApiOperation("特别人员管理,导出特别人员")
@SaCheckPermission("staff.special.manage")
public void doExport(SpecialStaffPageForm pageForm, HttpServletResponse response) {
specialStaffManageService.doExport(pageForm, response);
}
@At
@ApiOperation("特别人员管理,查询用户")
@SaCheckPermission("staff.special.manage")
@@ -6,6 +6,8 @@ import com.budwk.app.zhgh.staffmanage.specialstaff.model.SpecialStaff;
import com.budwk.app.zhgh.staffmanage.specialstaff.param.SpecialStaffPageForm;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
/**
* @version 1.0
* @Author zzr
@@ -35,4 +37,11 @@ public interface SpecialStaffManageService extends BaseService<SpecialStaff> {
* @return
*/
NutMap getSpecialStaffUserInfo(String userId);
/**
* 导出特别人员
* @param pageForm 查询条件
* @param response 响应
*/
void doExport(SpecialStaffPageForm pageForm, HttpServletResponse response);
}
@@ -1,23 +1,38 @@
package com.budwk.app.zhgh.staffmanage.specialstaff.service.impl;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffmanage.specialstaff.model.SpecialStaff;
import com.budwk.app.zhgh.staffmanage.specialstaff.param.SpecialStaffPageForm;
import com.budwk.app.zhgh.staffmanage.specialstaff.service.SpecialStaffManageService;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @version 1.0
* @Author zzr
@@ -27,12 +42,21 @@ import org.nutz.lang.util.NutMap;
*/
@IocBean(args = {"refer:dao"})
public class SpecialStaffManageServiceImpl extends BaseServiceImpl<SpecialStaff> implements SpecialStaffManageService {
@Inject
private SysDictService sysDictService;
public SpecialStaffManageServiceImpl(Dao dao) {
super(dao);
}
@Override
public Pagination pageData(SpecialStaffPageForm pageForm) {
Sql sql = getSql(pageForm);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
private Sql getSql(SpecialStaffPageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
s.*,
@@ -77,7 +101,7 @@ public class SpecialStaffManageServiceImpl extends BaseServiceImpl<SpecialStaff>
cnd.andEX("u.personType", "=", pageForm.getPersonType());
cnd.andEX("s.specialStaffType", "=", pageForm.getSpecialStaffType());
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return sql;
}
@@ -133,4 +157,29 @@ public class SpecialStaffManageServiceImpl extends BaseServiceImpl<SpecialStaff>
dao().execute(sql);
return (NutMap) sql.getResult();
}
@Override
public void doExport(SpecialStaffPageForm pageForm, HttpServletResponse response) {
List<NutMap> list = listMap(getSql(pageForm));
Map<String, String> specialStaffTypeMap = sysDictService.getSubListByCode("SPECIAL_STAFF_TYPE")
.stream()
.collect(Collectors.toMap(Sys_dict::getCode, Sys_dict::getName));
list.forEach(row -> row.put("specialStaffType", specialStaffTypeMap.getOrDefault(row.getString("specialStaffType"), row.getString("specialStaffType"))));
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("工号", "loginname", 20));
exportEntities.add(new ExcelExportEntity("姓名", "username", 20));
exportEntities.add(new ExcelExportEntity("性别", "sex", 20));
exportEntities.add(new ExcelExportEntity("联系电话", "mobile", 20));
exportEntities.add(new ExcelExportEntity("人员类型", "personType", 20));
exportEntities.add(new ExcelExportEntity("在职状态", "userState", 20));
exportEntities.add(new ExcelExportEntity("所属工会", "personnelRelationUnionName", 20));
exportEntities.add(new ExcelExportEntity("所属单位", "personnelRelationUnitName", 30));
exportEntities.add(new ExcelExportEntity("特别人员类型", "specialStaffType", 20));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, list);
CommonDownloadUtil.download("特别人员名单.xlsx", workbook, response);
}
}
@@ -116,6 +116,14 @@ public class WelfareListController {
welfareListService.exportAddress(pageForm, response);
}
@At
@SaCheckPermission("welfare.list.mange")
@ApiOperation("导出重复身份证号人员")
@Ok("void")
public void exportRepeatIdCard(WelfareListPageForm pageForm, HttpServletResponse response) {
welfareListService.exportRepeatIdCard(pageForm, response);
}
@At
@SaCheckPermission("welfare.list.mange")
@ApiOperation("导出")
@@ -15,12 +15,14 @@ import com.budwk.app.zhgh.welfare.model.WelfareUserSelection;
import com.budwk.app.zhgh.welfare.param.WelfareSelectionSituationPageForm;
import com.budwk.app.zhgh.welfare.service.WelfareListService;
import com.budwk.app.zhgh.welfare.service.WelfareSelectionSituationService;
import com.budwk.app.zhgh.welfare.service.WelfareSingleService;
import com.budwk.app.zhgh.welfare.service.WelfareStatisticsService;
import com.jogamp.common.util.SecurityUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
@@ -49,6 +51,8 @@ public class WelfareSelectionSituationController {
private WelfareStatisticsService welfareStatisticsService;
@Inject
private WelfareSelectionSituationService situationService;
@Inject
private WelfareSingleService welfareSingleService;
@At("")
@Ok("beetl:/platform/zhgh/welfare/selectionSituation/index.html")
@@ -74,15 +78,31 @@ public class WelfareSelectionSituationController {
@ApiOperation("获取某个用户选择信息")
public Result getUserSelection(String projectId, String userId) {
List<WelfareUserSelection> list = dao.query(WelfareUserSelection.class, Cnd.where(WelfareUserSelection::getWelfareId, "=", projectId).and(WelfareUserSelection::getSelectUserId, "=", userId));
Sys_user user = dao.fetch(Sys_user.class, userId);
String welfareMobile = user == null ? null : user.getWelfareMobile();
// 管理员代选详情返回被代选用户的当前福利电话
list.forEach(selection -> selection.setWelfareMobile(welfareMobile));
return Result.success(list);
}
/**
* 管理员为指定用户提交福利选择
*
* @param selections 选择项数组每项包含选项 ID选择数量以及按项目要求填写的地址或签名
* @param projectId 福利项目 ID
* @param userId 被代选用户 ID
* @param welfareMobile 被代选用户福利电话须为 11 位手机号码
* @return Result成功时返回选择成功校验失败时返回对应错误信息
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SLog(type = "welfare", tag = "选择福利", msg = "代选福利")
@SaCheckPermission("welfare.selection.situation")
public Result confirmSelect(@Param("selections") WelfareUserSelection[] selections, String projectId, String userId) {
public Result confirmSelect(@Param("selections") WelfareUserSelection[] selections, String projectId, String userId, String welfareMobile) {
if (welfareMobile == null || !welfareMobile.matches("^1[3456789]\\d{9}$")) {
return Result.error("请输入正确的福利电话");
}
WelfareProject project = dao.fetch(WelfareProject.class, projectId);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if(project.getChoiceTimeEnd().getTime()<new Date().getTime()){
@@ -95,21 +115,37 @@ public class WelfareSelectionSituationController {
}
int sum = Arrays.stream(selections).mapToInt(selection -> Objects.requireNonNullElse(selection.getSelectNum(), 0)).sum();
if (sum > project.getMultiSelectNum()) {
return Result.error("选择的数量不能超过" + project.getMultiSelectNum());
// 历史福利项目可能未配置最多选几个按单选项目默认最多选择 1 份处理
Integer multiSelectNum = Objects.requireNonNullElse(project.getMultiSelectNum(), 1);
if (sum > multiSelectNum) {
return Result.error("选择的数量不能超过" + multiSelectNum);
}
// 管理员代选时同步维护被代选用户的福利电话后续福利业务统一读取该字段
dao.update(Sys_user.class, Chain.make("welfareMobile", welfareMobile), Cnd.where("id", "=", userId));
//删除上次选择的
dao.clear(WelfareUserSelection.class, Cnd.where(WelfareUserSelection::getWelfareId, "=", projectId).and(WelfareUserSelection::getSelectUserId, "=", userId));
for (WelfareUserSelection welfareUserSelection : selections) {
welfareUserSelection.setWelfareId(projectId);
welfareUserSelection.setSelectUserId(userId);
welfareUserSelection.setSelectTime(new Date());
welfareUserSelection.setIsSelectByAdmin(true);
}
dao.insert(selections);
return Result.success("选择成功");
}
@At
@SaCheckPermission("welfare.selection.situation")
@Aop(TransAop.READ_COMMITTED)
@SLog(tag = "选择情况", msg = "管理员代选福利")
@ApiOperation("管理员代选福利")
public Result doSelectByAdmin(@Valid String projectId, @Param("welfareOptions") @Valid String welfareOptions) {
welfareSingleService.doSelectByAdmin(projectId, welfareOptions);
return Result.success();
}
@At
@SaCheckPermission("welfare.selection.situation")
@@ -119,11 +155,17 @@ public class WelfareSelectionSituationController {
situationService.exportXlsx(pageForm, response);
}
/**
* 获取被代选用户的福利电话
*
* @param userId 被代选用户 ID
* @return Resultdata 为字符串类型的福利电话用户不存在或尚未维护时为空
*/
@At
@SaCheckLogin
public Result getMobileByUserId(String userId) {
public Result getWelfareMobileByUserId(String userId) {
Sys_user user = dao.fetch(Sys_user.class, userId);
return Result.success().addData(user.getMobile());
return Result.success().addData(user == null ? null : user.getWelfareMobile());
}
}
@@ -71,8 +71,8 @@ public class WelfareStatisticsController {
@At
@SaCheckPermission("welfare.statistics")
public Result pageData(String projectId, String unionId) {
NutMap data = welfareStatisticsService.pageData(projectId, unionId);
public Result pageData(String projectId, String unionId, Boolean isSelectByAdmin) {
NutMap data = welfareStatisticsService.pageData(projectId, unionId, isSelectByAdmin);
return Result.success(data);
}
@@ -80,8 +80,8 @@ public class WelfareStatisticsController {
@At
@SaCheckPermission("welfare.statistics")
@ApiOperation("查询某分工会已选择人员")
public Result selectedUnionUserPageData(@Valid PageForm pageForm, String projectId, String unionId) {
Pagination pagination = welfareStatisticsService.selectedUnionUserPageData(pageForm, projectId, unionId);
public Result selectedUnionUserPageData(@Valid PageForm pageForm, String projectId, String unionId, Boolean isSelectByAdmin) {
Pagination pagination = welfareStatisticsService.selectedUnionUserPageData(pageForm, projectId, unionId, isSelectByAdmin);
return Result.success(pagination);
}
@@ -89,8 +89,8 @@ public class WelfareStatisticsController {
@Ok("void")
@SaCheckPermission("welfare.statistics")
@ApiOperation("导出某分工会已选择人员")
public void exportSelectedUnionUser(String projectId, String unionId, HttpServletResponse response) {
welfareStatisticsService.exportSelectedUnionUser(projectId, unionId, response);
public void exportSelectedUnionUser(String projectId, String unionId, Boolean isSelectByAdmin, HttpServletResponse response) {
welfareStatisticsService.exportSelectedUnionUser(projectId, unionId, isSelectByAdmin, response);
}
@At
@@ -6,6 +6,7 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.welfare.model.WelfareList;
@@ -82,11 +83,22 @@ public class WelfareUserSelectController {
return Result.success(pagination);
}
/**
* 提交当前用户的福利选择
*
* @param selections 选择项数组每项包含选项 ID选择数量以及按项目要求填写的地址或签名
* @param projectId 福利项目 ID
* @param welfareMobile 当前用户福利电话须为 11 位手机号码
* @return Result成功时返回选择成功校验失败时返回对应错误信息
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SLog(type = "welfare", tag = "选择福利", msg = "福利")
@SaCheckPermission("welfare.user.select")
public Result confirmSelect(@Param("selections") WelfareUserSelection[] selections, String projectId) {
public Result confirmSelect(@Param("selections") WelfareUserSelection[] selections, String projectId, String welfareMobile) {
if (welfareMobile == null || !welfareMobile.matches("^1[3456789]\\d{9}$")) {
return Result.error("请输入正确的福利电话");
}
WelfareProject project = dao.fetch(WelfareProject.class, projectId);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if (project.getChoiceTimeEnd().getTime() < new Date().getTime()) {
@@ -105,6 +117,9 @@ public class WelfareUserSelectController {
return Result.error("选择的数量不能超过" + project.getMultiSelectNum());
}
// 福利电话以用户资料为唯一数据源福利选择记录不再重复保存普通 mobile 字段
dao.update(Sys_user.class, Chain.make("welfareMobile", welfareMobile), Cnd.where("id", "=", SecurityUtil.getUserId()));
//删除上次选择的
dao.clear(WelfareUserSelection.class, Cnd.where(WelfareUserSelection::getWelfareId, "=", projectId).and(WelfareUserSelection::getSelectUserId, "=", SecurityUtil.getUserId()));
for (WelfareUserSelection welfareUserSelection : selections) {
@@ -121,7 +136,24 @@ public class WelfareUserSelectController {
@ApiOperation("获取用户选择信息")
public Result getUserSelection(String projectId) {
List<WelfareUserSelection> list = dao.query(WelfareUserSelection.class, Cnd.where(WelfareUserSelection::getWelfareId, "=", projectId).and(WelfareUserSelection::getSelectUserId, "=", SecurityUtil.getUserId()));
Sys_user user = dao.fetch(Sys_user.class, SecurityUtil.getUserId());
String welfareMobile = user == null ? null : user.getWelfareMobile();
// 选择记录返回当前福利电话 PC H5 的选择详情统一回显
list.forEach(selection -> selection.setWelfareMobile(welfareMobile));
return Result.success(list);
}
/**
* 获取当前用户的福利电话
*
* @return 字符串类型的福利电话用户不存在或尚未维护时返回空值
*/
@At
@SaCheckPermission("welfare.user.select")
@ApiOperation("获取当前用户福利电话")
public Result getWelfareMobile() {
Sys_user user = dao.fetch(Sys_user.class, SecurityUtil.getUserId());
return Result.success().addData(user == null ? null : user.getWelfareMobile());
}
}
@@ -222,6 +222,8 @@ public class WelfareProject extends BaseModel implements SysHomeConvert {
sysHomeActivity.setAllowUserSql("select userId from welfare_list where projectId = '" + this.getId() + "' and userId = @userId");
sysHomeActivity.setEnable(true);
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
sysHomeActivity.setCreatedAt(this.getCreatedAt());
sysHomeActivity.setUpdatedAt(this.getUpdatedAt());
return sysHomeActivity;
}
}
@@ -81,10 +81,22 @@ public class WelfareUserSelection extends BaseModel implements Serializable {
private Integer selectNum;
@Column
@Comment("手机号")
@Comment("是否管理员代选")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean isSelectByAdmin;
@Deprecated
@Column
@Comment("历史联系电话(福利选择流程不再使用)")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String mobile;
/**
* 当前用户福利电话仅用于福利选择查询结果回显不映射选择记录表字段
*/
private String welfareMobile;
@Column
@Comment("物流信息")
@ColDefine(type = ColType.MYSQL_JSON)
@@ -32,4 +32,7 @@ public class WelfareSelectionSituationPageForm extends PageForm {
@ApiModelProperty("是否已选择")
private Boolean isSelect;
@ApiModelProperty("是否管理员代选")
private Boolean isSelectByAdmin;
}
@@ -53,6 +53,13 @@ public interface WelfareListService extends BaseService<WelfareList> {
*/
void exportAddress(WelfareListPageForm pageForm, HttpServletResponse response);
/**
* 导出重复身份证号人员
* @param pageForm
* @param response
*/
void exportRepeatIdCard(WelfareListPageForm pageForm, HttpServletResponse response);
/**
* 导出
* @param pageForm
@@ -16,7 +16,7 @@ public interface WelfareStatisticsService extends BaseService<WelfareProject> {
* @param unionId
* @return
*/
NutMap pageData(String projectId, String unionId);
NutMap pageData(String projectId, String unionId, Boolean isSelectByAdmin);
/**
* 查询某分工会已选择人员
@@ -24,7 +24,7 @@ public interface WelfareStatisticsService extends BaseService<WelfareProject> {
* @param projectId
* @param unionId
*/
Pagination selectedUnionUserPageData(PageForm pageForm, String projectId, String unionId);
Pagination selectedUnionUserPageData(PageForm pageForm, String projectId, String unionId, Boolean isSelectByAdmin);
/**
* 导出某分工会已选择人员
@@ -32,7 +32,7 @@ public interface WelfareStatisticsService extends BaseService<WelfareProject> {
* @param unionId
* @param response
*/
void exportSelectedUnionUser(String projectId, String unionId, HttpServletResponse response);
void exportSelectedUnionUser(String projectId, String unionId, Boolean isSelectByAdmin, HttpServletResponse response);
/**
* Pagination
@@ -157,7 +157,7 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
SELECT
u.loginname,
u.username,
u.mobile,
u.welfareMobile,
un.name unionname,
it.name unitname,
IF(LOCATE('undefined',wpus.receiveAddress)>0,NULL,wpus.receiveAddress) as receiveAddress,
@@ -187,7 +187,7 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("工号", "loginname", 20));
exportEntities.add(new ExcelExportEntity("姓名", "username", 20));
exportEntities.add(new ExcelExportEntity("电话号码", "mobile", 20));
exportEntities.add(new ExcelExportEntity("福利电话", "welfareMobile", 20));
exportEntities.add(new ExcelExportEntity("所在分工会", "unionname", 30));
exportEntities.add(new ExcelExportEntity("所在单位", "unitname", 50));
exportEntities.add(new ExcelExportEntity("选择份数", "selectNum", 20));
@@ -629,6 +629,72 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
}
}
@Override
public void exportRepeatIdCard(WelfareListPageForm pageForm, HttpServletResponse response) {
Sql sql = Sqls.create("""
SELECT
t1.*,
t2.loginname,
t2.username,
t2.idCard,
t2.sex,
DATE_FORMAT(t2.birthday, '%Y-%m-%d') AS birthday
FROM
`welfare_list` t1
LEFT JOIN sys_user t2 ON t2.id = t1.userId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("t1.projectId", "=", pageForm.getProjectId());
cnd.and("t2.idCard", "in", Sqls.create("select idCard from sys_user where idCard is not null and idCard != '' group by idCard having count(1) > 1"));
if (StrUtil.isNotBlank(pageForm.getUserName())) {
cnd.where().andLike("t2.username", pageForm.getUserName());
}
if (StrUtil.isNotBlank(pageForm.getLoginName())) {
cnd.where().andLike("t2.loginname", pageForm.getLoginName());
}
cnd.andEX("t2.sex", "=", pageForm.getSex());
cnd.andEX("t2.birthday", "=", pageForm.getBirthday());
if (pageForm.getBirthMonths() != null && pageForm.getBirthMonths().length > 0) {
cnd.andEX("MONTH(t2.birthday)", "in", pageForm.getBirthMonths());
}
cnd.andEX("t1.welfareUnitId", "in", pageForm.getUnitIds());
cnd.andEX("t1.welfareUnionId", "=", pageForm.getUnionId());
cnd.andEX("t1.personType", "in", pageForm.getPersonTypes());
cnd.andEX("t1.preparedBy", "in", pageForm.getPreparedBys());
cnd.andEX("t1.userState", "in", pageForm.getUserStates());
cnd.asc("t2.idCard");
cnd.asc("t1.welfareUnionName");
cnd.asc("t1.welfareUnitName");
sql.setCondition(cnd);
List<NutMap> list = listMap(sql);
try {
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("工号", "loginname", 20));
exportEntities.add(new ExcelExportEntity("姓名", "username", 20));
exportEntities.add(new ExcelExportEntity("身份证号", "idCard", 30));
exportEntities.add(new ExcelExportEntity("性别", "sex", 20));
exportEntities.add(new ExcelExportEntity("出生日期", "birthday", 20));
exportEntities.add(new ExcelExportEntity("人员类型", "personType", 20));
exportEntities.add(new ExcelExportEntity("聘用方式", "preparedBy", 20));
exportEntities.add(new ExcelExportEntity("在职状态", "userState", 20));
exportEntities.add(new ExcelExportEntity("所属工会", "welfareUnionName", 20));
exportEntities.add(new ExcelExportEntity("所属单位", "welfareUnitName", 30));
exportEntities.add(new ExcelExportEntity("备注", "remark", 30));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, list);
CommonDownloadUtil.download("重复身份证号人员.xlsx", workbook, response);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void exportXlsx(WelfareListPageForm pageForm, HttpServletResponse response) {
Sql sql = Sqls.create("""
@@ -637,6 +703,7 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
t2.loginname,
t2.username,
t2.sex,
t2.welfareMobile,
DATE_FORMAT(t2.birthday, '%Y-%m-%d') AS birthday
FROM
`welfare_list` t1
@@ -699,7 +766,7 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
exportEntities.add(new ExcelExportEntity("工号", "loginname", 20));
exportEntities.add(new ExcelExportEntity("姓名", "username", 20));
exportEntities.add(new ExcelExportEntity("性别", "sex", 20));
exportEntities.add(new ExcelExportEntity("联系电话", "mobile", 20));
exportEntities.add(new ExcelExportEntity("福利电话", "welfareMobile", 20));
exportEntities.add(new ExcelExportEntity("身份证号", "idCard", 30));
exportEntities.add(new ExcelExportEntity("在职状态", "userState", 20));
exportEntities.add(new ExcelExportEntity("聘用方式", "preparedBy", 20));
@@ -37,6 +37,15 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
super(dao);
}
private void appendSelectByAdminCnd(Cnd cnd, Boolean isSelectByAdmin) {
if (Boolean.TRUE.equals(isSelectByAdmin)) {
cnd.and("t2.isSelectByAdmin", "=", true);
} else if (Boolean.FALSE.equals(isSelectByAdmin)) {
cnd.and("t2.id", "IS NOT", null);
cnd.and(Cnd.exps("t2.isSelectByAdmin", "=", false).or("t2.isSelectByAdmin", "is", null));
}
}
@Override
public Pagination pageData(WelfareSelectionSituationPageForm pageForm) {
Sql sql = Sqls.create("""
@@ -48,8 +57,11 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
t1.welfareUnitName,
t1.welfareUnitId,
GROUP_CONCAT(DISTINCT t3.optionName) AS selectedOptions,
GROUP_CONCAT(DISTINCT t2.mobile) AS mobile,
GROUP_CONCAT(DISTINCT t2.receiveAddress) AS receiveAddress,
CASE
WHEN MAX(t2.isSelectByAdmin) = 1 THEN '是'
ELSE '否'
END AS selectByAdminName,
t4.username AS userName,
t4.loginname AS loginName,
t4.sex,
@@ -79,6 +91,7 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
}
cnd.andEX("t1.welfareUnitId", "in", pageForm.getUnitIds());
cnd.andEX("t1.welfareUnionId", "=", pageForm.getUnionId());
appendSelectByAdminCnd(cnd, pageForm.getIsSelectByAdmin());
if (pageForm.getIsSelect() != null) {
if (pageForm.getIsSelect()) {
@@ -108,8 +121,11 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
t1.welfareUnionName,
t1.welfareUnitName,
GROUP_CONCAT(DISTINCT t3.optionName) AS selectedOptions,
GROUP_CONCAT(DISTINCT t2.mobile) AS mobile,
GROUP_CONCAT(DISTINCT t2.receiveAddress) AS receiveAddress,
CASE
WHEN MAX(t2.isSelectByAdmin) = 1 THEN '是'
ELSE '否'
END AS selectByAdminName,
t4.username AS userName,
t4.loginname AS loginName,
t4.sex,
@@ -138,6 +154,7 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
}
cnd.andEX("t1.welfareUnitId", "in", pageForm.getUnitIds());
cnd.andEX("t1.welfareUnionId", "=", pageForm.getUnionId());
appendSelectByAdminCnd(cnd, pageForm.getIsSelectByAdmin());
if (pageForm.getIsSelect() != null) {
if (pageForm.getIsSelect()) {
@@ -170,8 +187,8 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
entities.add(new ExcelExportEntity("所属单位", "welfareUnitName", 20));
entities.add(new ExcelExportEntity("所属工会", "welfareUnionName", 20));
entities.add(new ExcelExportEntity("所选福利", "selectedOptions", 20));
entities.add(new ExcelExportEntity("福利号码", "welfareMobile", 20));
entities.add(new ExcelExportEntity("联系电话", "mobile", 20));
entities.add(new ExcelExportEntity("是否管理员代选", "selectByAdminName", 20));
entities.add(new ExcelExportEntity("福利电话", "welfareMobile", 20));
if(project.getProvideMode() == 3){
entities.add(new ExcelExportEntity("收货地址", "receiveAddress", 40));
@@ -158,7 +158,7 @@ public class WelfareSingleServiceImpl extends BaseServiceImpl implements Welfare
u.username AS userName,
u.unitname AS unitName,
u.unionname AS unionName,
u.mobile,
u.welfareMobile,
IF(LOCATE('undefined',wpus.receiveAddress)>0,NULL,wpus.receiveAddress) as receiveAddress,
GROUP_CONCAT(DISTINCT wpso.optionName ,'',wpus.selectNum,'') optionName,
wpus.courierNumber
@@ -407,6 +407,7 @@ public class WelfareSingleServiceImpl extends BaseServiceImpl implements Welfare
selection.setReceiveAddress(null);
selection.setSubjectId(v.getString("subjectId"));
selection.setSelectNum(v.getInt("selectNum"));
selection.setIsSelectByAdmin(true);
return selection;
}).collect(Collectors.toList());
dao().insert(userSelections);
@@ -41,8 +41,25 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
super(dao);
}
private void appendSelectByAdminCnd(Cnd cnd, String field, Boolean isSelectByAdmin) {
if (Boolean.TRUE.equals(isSelectByAdmin)) {
cnd.and(field, "=", true);
} else if (Boolean.FALSE.equals(isSelectByAdmin)) {
cnd.and(Cnd.exps(field, "=", false).or(field, "is", null));
}
}
private String buildSelectByAdminJoinCnd(String field, Boolean isSelectByAdmin) {
if (Boolean.TRUE.equals(isSelectByAdmin)) {
return " AND " + field + " = 1";
} else if (Boolean.FALSE.equals(isSelectByAdmin)) {
return " AND (" + field + " = 0 OR " + field + " IS NULL)";
}
return "";
}
@Override
public NutMap pageData(String projectId, String unionId) {
public NutMap pageData(String projectId, String unionId, Boolean isSelectByAdmin) {
// 分工会数据
List<Sys_union> unions = dao().query(Sys_union.class, Cnd.NEW().andEX(Sys_union::getId, "=", unionId).asc(Sys_union::getUnionCode));
List<NutMap> mapUnions = BeanUtil.copyToList(unions, NutMap.class);
@@ -71,8 +88,10 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
`welfare_list` t1
LEFT JOIN welfare_project_user_selection t2 ON t2.selectUserId = t1.userId
AND t2.welfareId = t1.projectId
$selectByAdminJoinCnd
$condition
""");
sql.setVar("selectByAdminJoinCnd", buildSelectByAdminJoinCnd("t2.isSelectByAdmin", isSelectByAdmin));
Cnd cnd = Cnd.NEW();
cnd.and("t1.projectId", "=", projectId);
cnd.andEX("t1.welfareUnionId", "=", unionId);
@@ -92,6 +111,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
""");
Cnd cnd2 = Cnd.NEW();
cnd2.and("t1.welfareId", "=", projectId);
appendSelectByAdminCnd(cnd2, "t1.isSelectByAdmin", isSelectByAdmin);
sql2.setCondition(cnd2);
List<NutMap> userSelections = listMap(sql2);
@@ -119,7 +139,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
}
@Override
public Pagination selectedUnionUserPageData(PageForm pageForm, String projectId, String unionId) {
public Pagination selectedUnionUserPageData(PageForm pageForm, String projectId, String unionId, Boolean isSelectByAdmin) {
Sql sql = Sqls.create("""
SELECT
u.loginname AS loginName,
@@ -131,7 +151,8 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
wl.welfareUnionName,
u.postDoctoralJoinDate,
DATE_FORMAT(u.birthday, '%Y-%m-%d') AS birthday,
wpus.mobile,
u.welfareMobile,
CASE WHEN MAX(wpus.isSelectByAdmin) = 1 THEN '管理员代选' ELSE '个人选择' END AS selectByAdminName,
GROUP_CONCAT(DISTINCT wpso.optionName, '', wpus.selectNum, '') selectOptionName
FROM
welfare_project_user_selection wpus
@@ -143,6 +164,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
Cnd cnd = Cnd.NEW();
cnd.and("wl.welfareUnionId", "=", unionId);
cnd.and("wpus.welfareId", "=", projectId);
appendSelectByAdminCnd(cnd, "wpus.isSelectByAdmin", isSelectByAdmin);
cnd.groupBy("u.loginname");
cnd.desc("wl.welfareUnionName").desc("wl.welfareUnitName").desc("wpus.selectOptionId");
@@ -150,6 +172,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("u.loginname", pageForm.getSearchKeyword());
seg.orLike("u.username", pageForm.getSearchKeyword());
cnd.and(seg);
}
sql.setCondition(cnd);
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
@@ -157,7 +180,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
}
@Override
public void exportSelectedUnionUser(String projectId, String unionId, HttpServletResponse response) {
public void exportSelectedUnionUser(String projectId, String unionId, Boolean isSelectByAdmin, HttpServletResponse response) {
Sql sql = Sqls.create("""
SELECT
u.loginname AS loginName,
@@ -169,7 +192,8 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
wl.welfareUnionName,
u.postDoctoralJoinDate,
DATE_FORMAT(u.birthday, '%Y-%m-%d') AS birthday,
wpus.mobile,
u.welfareMobile,
CASE WHEN MAX(wpus.isSelectByAdmin) = 1 THEN '管理员代选' ELSE '个人选择' END AS selectByAdminName,
GROUP_CONCAT(DISTINCT wpso.optionName, '', wpus.selectNum, '') selectOptionName
FROM
welfare_project_user_selection wpus
@@ -182,6 +206,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
Cnd cnd = Cnd.NEW();
cnd.and("wl.welfareUnionId", "=", unionId);
cnd.and("wpus.welfareId", "=", projectId);
appendSelectByAdminCnd(cnd, "wpus.isSelectByAdmin", isSelectByAdmin);
cnd.groupBy("u.loginname");
cnd.desc("wl.welfareUnionName").desc("wl.welfareUnitName").desc("wpus.selectOptionId");
sql.setCondition(cnd);
@@ -207,7 +232,8 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
entities.add(new ExcelExportEntity("进站时间", "postDoctoralJoinDate", 20));
entities.add(new ExcelExportEntity("单位", "welfareUnitName", 20));
entities.add(new ExcelExportEntity("工会", "welfareUnionName", 20));
entities.add(new ExcelExportEntity("手机号", "mobile", 20));
entities.add(new ExcelExportEntity("福利电话", "welfareMobile", 20));
entities.add(new ExcelExportEntity("选择方式", "selectByAdminName", 20));
entities.add(new ExcelExportEntity("所选福利", "selectOptionName", 20));
// 设置导出参数
@@ -330,7 +356,7 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
u.username AS userName,
wl.welfareUnitName,
wl.welfareUnionName,
wpus.mobile,
u.welfareMobile,
GROUP_CONCAT(DISTINCT wpso.optionName, '', wpus.selectNum, '') selectOptionName
FROM
welfare_project_user_selection wpus
@@ -358,9 +384,10 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(new ExcelExportEntity("工号", "loginName", 20));
exportEntities.add(new ExcelExportEntity("姓名", "userName", 20));
exportEntities.add(new ExcelExportEntity("电话号码", "mobile", 20));
exportEntities.add(new ExcelExportEntity("福利电话", "welfareMobile", 20));
exportEntities.add(new ExcelExportEntity("所在分工会", "welfareUnionName", 20));
exportEntities.add(new ExcelExportEntity("所在单位", "welfareUnitName", 20));
exportEntities.add(new ExcelExportEntity("选择方式", "selectByAdminName", 20));
exportEntities.add(new ExcelExportEntity("所在福利", "optionName", 50));
Sql sql = Sqls.create("""
@@ -369,9 +396,10 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl<WelfareProject
u.username AS userName,
wl.welfareUnitName,
wl.welfareUnionName,
wpus.mobile,
u.welfareMobile,
wpus.selectOptionId,
wpso.optionName,
CASE WHEN MAX(wpus.isSelectByAdmin) = 1 THEN '管理员代选' ELSE '个人选择' END AS selectByAdminName,
GROUP_CONCAT(DISTINCT wpso.optionName, '', wpus.selectNum, '') selectOptionName
FROM
welfare_project_user_selection wpus
@@ -0,0 +1,76 @@
const h5PopupHistory = (() => {
const popupStack = []
let skipNextPop = false
const handlePopState = (event) => {
if (skipNextPop) {
skipNextPop = false
event.stopImmediatePropagation()
return
}
if (popupStack.length === 0) return
event.stopImmediatePropagation()
const popup = popupStack.pop()
popup.closeFromHistory()
}
window.addEventListener("popstate", handlePopState)
return {
/**
* 注册已打开的 H5 弹框并增加一层历史记录
* @param id 弹框唯一标识同一标识不会重复入栈
* @param owner 页面或组件标识用于提交成功及销毁时统一清理
* @param closeFromHistory 返回键触发时关闭弹框的回调
* @return 无返回值
*/
open(id, owner, closeFromHistory) {
if (popupStack.some((item) => item.id === id)) return
popupStack.push({id: id, owner: owner, closeFromHistory: closeFromHistory})
const state = Object.assign({}, window.history.state || {}, {
h5PopupId: id,
url: window.location.href
})
window.history.pushState(state, "", window.location.href)
},
/**
* 通过取消关闭按钮关闭指定弹框时同步回退对应历史记录
* @param id 弹框唯一标识必须是当前最上层弹框
* @return booleantrue 表示已发起历史回退false 表示弹框不在栈顶
*/
close(id) {
const top = popupStack[popupStack.length - 1]
if (!top || top.id !== id) return false
window.history.back()
return true
},
/**
* 提交成功前清理当前组件的全部弹框及其历史记录
* @param owner 页面或组件标识
* @return 无返回值
*/
clearOwner(owner) {
const ownerPopups = popupStack.filter((item) => item.owner === owner)
if (ownerPopups.length === 0) return
ownerPopups.slice().reverse().forEach((item) => item.closeFromHistory())
for (let i = popupStack.length - 1; i >= 0; i--) {
if (popupStack[i].owner === owner) popupStack.splice(i, 1)
}
skipNextPop = true
window.history.go(-ownerPopups.length)
},
/**
* 组件销毁时注销回调防止旧组件被历史返回事件再次调用
* @param owner 页面或组件标识
* @return 无返回值
*/
unregisterOwner(owner) {
for (let i = popupStack.length - 1; i >= 0; i--) {
if (popupStack[i].owner === owner) popupStack.splice(i, 1)
}
}
}
})()
@@ -57,6 +57,7 @@
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
<script src="${base!}/assets/platform/js/util/h5PopupHistory.js"></script>
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
<!--富文本编辑器-->
@@ -74,10 +74,21 @@ const courseList = {
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<el-table-column label="操作" width="200">
<template v-slot="{row}">
<el-button v-if="!row.isSign" type="primary" size="mini" @click="onSign(row)">我要报名</el-button>
<el-button v-else type="danger" size="mini" @click="onCancel(row)">取消报名</el-button>
<template v-if="!row.isSign">
<el-button v-if="isBeforeSignUpTime" type="primary" size="mini" @click="onPrefill(row)">
{{row.hasPrefill ? '修改预填信息' : '预填报名信息'}}
</el-button>
<el-button v-else-if="isInSignUpTime && row.hasPrefill" type="success" size="mini" @click="onQuickSignUp(row)">
立即报名
</el-button>
<el-button v-else-if="isInSignUpTime" type="primary" size="mini" @click="onSign(row)">我要报名</el-button>
</template>
<template v-else>
<el-button v-if="isInSignUpTime" type="primary" size="mini" @click="onEdit(row)">修改报名</el-button>
<el-button type="danger" size="mini" @click="onCancel(row)">取消报名</el-button>
</template>
</template>
</el-table-column>
</el-table>
@@ -150,10 +161,63 @@ const courseList = {
pdf: false,
infoVisible: false,
introduce: '',
nowTime: Date.now(),
signTimeTimer: null,
}
},
computed: {
// 只有严格早于报名开始时间时才允许维护预填数据。
isBeforeSignUpTime() {
return this.activity.activitySignUpStartTime
&& this.$moment(this.nowTime).isBefore(this.$moment(this.activity.activitySignUpStartTime))
},
// 修改报名与我要报名共用同一时间范围:开始时间已到且尚未达到结束时间。
isInSignUpTime() {
const now = this.$moment(this.nowTime)
return this.activity.activitySignUpStartTime
&& this.activity.activitySignUpEndTime
&& !now.isBefore(this.$moment(this.activity.activitySignUpStartTime))
&& now.isBefore(this.$moment(this.activity.activitySignUpEndTime))
},
},
methods: {
async onPreview(introduce) {
onPrefill(row) {
const courseType = this.courseTypeList.find((item) => item.id === row.courseType)
this.tableLoading = true
this.$axios.post("/platform/family/apply/getPrefill", {courseId: row.id})
.then((resp) => {
if (resp.code !== 0) {
this.$message.warning(resp.msg)
return
}
this.$refs.signFormRef.onOpen(row, courseType, this.activity, resp.data, "prefill")
})
.finally(() => {
this.tableLoading = false
})
},
onQuickSignUp(row) {
this.$confirm("将使用报名开始前保存的预填信息直接报名,是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.tableLoading = true
this.$axios.post("/platform/family/apply/quickSignUp", {courseId: row.id})
.then((resp) => {
if (resp.code === 0) {
this.$alert(resp.msg, "提示", {confirmButtonText: "确定", type: "success"})
this.pageData()
} else {
this.$message.warning(resp.msg)
}
})
.finally(() => {
this.tableLoading = false
})
}).catch(() => {})
},
onPreview(introduce) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(introduce, 'text/html');
@@ -161,10 +225,16 @@ const courseList = {
const href = link.getAttribute('href');
const id = href.substring(href.indexOf("=") + 1)
const res = await this.$axios.post("/platform/sys/file/previewFileData", { ids: JSON.stringify([id]) })
this.pdf = true
window.open("/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent(res.data[0].downloadPath), res.data[0].name)
this.$axios.post("/platform/sys/file/previewFileData", {ids: JSON.stringify([id])})
.then((res) => {
this.pdf = true
window.open("/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent(res.data[0].downloadPath), res.data[0].name)
})
.catch(() => {
this.introduce = introduce
this.pdf = false
this.infoVisible = true
})
} catch (e) {
this.introduce = introduce
this.pdf = false
@@ -180,12 +250,14 @@ const courseList = {
}
this.doSearch()
},
async onOpen(row) {
onOpen(row) {
this.activity = row
this.$set(this.pageForm, 'activityId', row.id)
await this.pageData()
await this.getCourseTypeList()
this.queryCourseAssort()
this.pageData()
.then(() => this.getCourseTypeList())
.then(() => {
this.queryCourseAssort()
})
},
onSign(row) {
const courseType = this.courseTypeList.find((v) => v.id === row.courseType)
@@ -208,34 +280,64 @@ const courseList = {
}
})
},
onEdit(row) {
const courseType = this.courseTypeList.find((item) => item.id === row.courseType)
this.tableLoading = true
this.$axios.post("/platform/family/apply/validateUpdateSignUp", {courseId: row.id})
.then((validateRes) => {
if (validateRes.code !== 0) {
this.$message.warning(validateRes.msg)
return null
}
return this.$axios.post("/platform/family/apply/getMySignUp", {courseId: row.id})
})
.then((signUpRes) => {
if (!signUpRes) return
if (signUpRes.code !== 0) {
this.$message.warning(signUpRes.msg)
return
}
this.$refs.signFormRef.onOpen(row, courseType, this.activity, signUpRes.data)
})
.finally(() => {
this.tableLoading = false
})
},
onCancel(row) {
this.$confirm("您确定要取消吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(async () => {
const resp = await this.$axios.post("/platform/family/apply/cancelSignUp", {
}).then(() => {
this.$axios.post("/platform/family/apply/cancelSignUp", {
activityId: row.activityId,
courseId: row.id
})
if (resp.code === 0) {
await this.pageData()
this.$message.success(resp.msg)
} else {
this.$message.warning(resp.msg)
}
.then((resp) => {
if (resp.code !== 0) {
this.$message.warning(resp.msg)
return
}
this.$message.success(resp.msg)
return this.pageData()
})
})
},
async getCourseTypeList() {
const resp = await this.$axios.post("/platform/family/type/getAllType")
if (resp.code === 0) {
this.courseTypeList = resp.data
}
getCourseTypeList() {
return this.$axios.post("/platform/family/type/getAllType")
.then((resp) => {
if (resp.code === 0) {
this.courseTypeList = resp.data
}
return resp
})
},
async openViewCourseTime(id) {
const resp = await this.$axios.post(loc() + "/getCourseTime", { id: id })
this.courseTimeList = resp.data
this.courseTimeListDialog = true
openViewCourseTime(id) {
this.$axios.post(loc() + "/getCourseTime", {id: id})
.then((resp) => {
this.courseTimeList = resp.data
this.courseTimeListDialog = true
})
},
openViewMap(point) {
if (!Array.isArray(point)) {
@@ -270,16 +372,19 @@ const courseList = {
return "<span style='color: red'>余" + lave +"</span>/" + o.coursePeopleNumber + "人"
}
},
async pageData() {
pageData() {
const pageForm = clone({...this.pageForm})
pageForm.assortTypes = JSON.stringify(pageForm.assortTypes)
const resp = await this.$axios.post(loc() + "/pageData", pageForm)
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
} else {
this.$message.warning(resp.msg)
}
return this.$axios.post(loc() + "/pageData", pageForm)
.then((resp) => {
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
} else {
this.$message.warning(resp.msg)
}
return resp
})
},
queryCourseAssort() {
this.$axios.post(loc() + "/queryCourseAssort", {activityId: this.activity.id})
@@ -289,7 +394,13 @@ const courseList = {
},
},
created() {
// 页面停留到报名开始时间时自动切换为正式报名按钮,无需用户手工刷新。
this.signTimeTimer = window.setInterval(() => {
this.nowTime = Date.now()
}, 1000)
},
beforeDestroy() {
if (this.signTimeTimer) window.clearInterval(this.signTimeTimer)
},
style: /*language=CSS*/ `
.glow-box {
@@ -170,10 +170,7 @@ layout("/layouts/platform.html"){
})
},
onOpen(row) {
if(this.$store.state.user.loginname !== '45066' && this.$moment().isBefore(this.$moment(row.activitySignUpStartTime))) {
this.onView(row)
return
}
// 报名前允许进入课程列表维护预填信息,正式报名仍由后端报名时间校验控制。
this.infoVisible = false
this.$refs.guava.view(() => {
this.$refs.courseListRef.onOpen(row)
@@ -188,7 +185,7 @@ layout("/layouts/platform.html"){
})
},
},
async created() {
created() {
this.pageData()
}
})
@@ -1,7 +1,8 @@
const signForm = {
template: /*language=HTML*/ `
<div>
<el-dialog :close-on-click-modal="false" :visible.sync="signDialog" title="信息填写" width="50%"
<el-dialog :close-on-click-modal="false" :visible.sync="signDialog"
:title="prefillMode ? (formData.id ? '修改预填信息' : '预填报名信息') : (editMode ? '修改报名' : '信息填写')" width="50%"
append-to-body>
<el-form :model="formData" ref="form" label-width="120px">
<div class="left-span-label">个人信息</div>
@@ -36,7 +37,8 @@ const signForm = {
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="联系方式" prop="mobile">
<el-form-item label="联系方式" prop="mobile"
:rules="{ required: true, message: '请填写联系方式', trigger: 'blur'}">
<el-input v-model="formData.mobile"></el-input>
</el-form-item>
</el-col>
@@ -137,7 +139,7 @@ const signForm = {
<el-row class="mt20" justify="end" type="flex">
<el-button @click="signDialog = false"> </el-button>
<el-button @click="onSubmit" type="primary"> </el-button>
<el-button :loading="formLoading" @click="onSubmit" type="primary">{{prefillMode ? '保存预填' : '提 交'}}</el-button>
</el-row>
</el-dialog>
</div>
@@ -147,6 +149,9 @@ const signForm = {
data() {
return {
signDialog: false,
editMode: false,
prefillMode: false,
formLoading: false,
formData: {
mobileColumnsValue: [],
},
@@ -172,58 +177,79 @@ const signForm = {
const ac = (Number(this.active) - 1)
this.active = '' + (ac >= 0 ? ac : 0)
},
async validSignUp() {
validSignUp() {
//获取家属是多少人
const res = await this.$axios.post("/platform/family/apply/validateSignUp", {
return this.$axios.post("/platform/family/apply/validateSignUp", {
courseId: this.formData.courseId,
currentFamilyNumber: this.formData.mobileColumnsValue.length || 0
})
if (res.code !== 0) {
this.$alert(res.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
.then((res) => {
if (res.code !== 0) {
this.$alert(res.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
})
return false
}
return true
})
return false
}
return true
},
async validateSourceSignUp() {
if (this.courseRow.courseIsLimitApply) {
const resp = await $.post('/platform/family/apply/validateSourceSignUp', {
validateSourceSignUp() {
if (!this.courseRow.courseIsLimitApply) {
return Promise.resolve(true)
}
return $.post('/platform/family/apply/validateSourceSignUp', {
activityCourseId: this.formData.activityCourseId,
courseId: this.courseRow.id
})
if (resp.code !== 0) {
this.$alert(resp.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
})
return false
}
}
return true
},
async getCourseTimeSelectList(o) {
const resp = await $.post('/platform/family/apply/getCourseTimeSelectList', {courseId: o.id})
if (resp.code === 0) {
this.courseTimeSelectList = resp.data
} else {
this.$message.warning('获取时段信息失败,请联系管理员')
}
},
async onOpen(row, courseType, activity) {
this.initData(row, courseType, activity)
if (row.courseIsLimitApply) {
await this.getCourseTimeSelectList(row)
}
if (courseType && courseType.familyMobileSignColumnList.length > 0) {
courseType.familyMobileSignColumnList.forEach(item => {
item.columnValue = ''
.then((resp) => {
if (resp.code !== 0) {
this.$alert(resp.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
})
return false
}
return true
})
.always(() => {})
},
getCourseTimeSelectList(o, familyUserId) {
const url = familyUserId
? "/platform/family/apply/getEditCourseTimeSelectList"
: "/platform/family/apply/getCourseTimeSelectList"
this.formLoading = true
return $.post(url, {courseId: o.id, familyUserId: familyUserId})
.then((resp) => {
if (resp.code === 0) {
this.$set(this, "courseTimeSelectList", resp.data)
} else {
this.$message.warning(resp.msg || "获取时段信息失败,请联系管理员")
}
return resp
})
.always(() => {
this.formLoading = false
})
},
onOpen(row, courseType, activity, signUpData, mode) {
this.prefillMode = mode === "prefill"
this.initData(row, courseType, activity, signUpData)
this.$set(this, "courseRow", row)
this.$set(this, "courseTypeRow", courseType)
const openDialog = () => {
this.$set(this, "signDialog", true)
}
this.courseRow = row
this.courseTypeRow = courseType
this.signDialog = true
if (row.courseIsLimitApply) {
this.getCourseTimeSelectList(row, this.editMode && signUpData ? signUpData.id : "")
.then((resp) => {
if (resp.code === 0) {
openDialog()
}
})
return
}
openDialog()
},
validateIdCard(idCard) {
if (!idCard) {
@@ -354,60 +380,100 @@ const signForm = {
return { age, sex, };
},
async onSubmit() {
onSubmit() {
//验证家属表单
if (!this.validFamilyForm()) return
if (!await this.validSignUp()) return
if (!await this.validateSourceSignUp()) return
this.$refs["form"].validate((valid) => {
if (valid) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const formData = clone(this.formData)
let array = []
formData.mobileColumnsValue.forEach(item => {
const o = item.map((v) => {
return {
columnName: v.columnName,
columnValue: v.columnValue,
columnCode: v.columnCode,
columnFormType: v.columnFormType
}
const signUpValidation = (this.editMode || this.prefillMode) ? Promise.resolve(true) : this.validSignUp()
signUpValidation.then((signUpValid) => {
if (!signUpValid) return
const sourceValidation = (this.editMode || this.prefillMode) ? Promise.resolve(true) : this.validateSourceSignUp()
sourceValidation.then((sourceValid) => {
if (!sourceValid) return
this.$refs["form"].validate((valid) => {
if (!valid) return
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const formData = clone(this.formData)
const array = formData.mobileColumnsValue.map((item) => {
return item.map((column) => {
return {
columnName: column.columnName,
columnValue: column.columnValue,
columnCode: column.columnCode,
columnFormType: column.columnFormType
}
})
})
array.push(o)
formData.mobileColumnsValue = JSON.stringify(clone(array))
const url = this.prefillMode
? "/platform/family/apply/savePrefill"
: (this.editMode
? "/platform/family/apply/updateSignUp"
: "/platform/family/apply/doSignUp")
this.formLoading = true
this.$axios.post(url, formData)
.then((resp) => {
if (resp.code === 0) {
this.$alert(resp.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
})
this.signDialog = false
this.$emit("refresh")
} else {
this.$message.warning(resp.msg)
}
})
.finally(() => {
this.formLoading = false
})
})
formData.mobileColumnsValue = JSON.stringify(clone(array))
const resp = await this.$axios.post("/platform/family/apply/doSignUp", formData)
if (resp.code === 0) {
this.$alert(resp.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
})
this.signDialog = false
this.$emit('refresh')
} else {
this.$message.warning(resp.msg)
}
})
}
})
})
},
initData(row, courseType, activity) {
this.activity = activity
this.formData = {
// 修改时按 columnCode 将历史值合并进当前字段配置,保留最新必填和格式校验规则。
mergeMobileColumnsValue(courseType, mobileColumnsValue) {
const columnConfig = courseType && Array.isArray(courseType.familyMobileSignColumnList)
? courseType.familyMobileSignColumnList : []
return (mobileColumnsValue || []).map((family) => {
if (columnConfig.length === 0) {
return clone(family)
}
return clone(columnConfig).map((column) => {
const oldColumn = family.find((item) => item.columnCode === column.columnCode)
this.$set(column, "columnValue", oldColumn ? oldColumn.columnValue : "")
return column
})
})
},
initData(row, courseType, activity, signUpData) {
this.$set(this, "activity", activity)
this.editMode = !this.prefillMode && Boolean(signUpData && signUpData.id)
// 部分历史账号未关联单位或分工会,初始化报名表单时需兼容关联对象为空。
const currentUser = this.$store.state.user || {}
const formData = {
activityId: row.activityId,
courseId: row.id,
username: this.$store.state.user.username,
loginname: this.$store.state.user.loginname,
unionName: this.$store.state.user.union.name,
unitName: this.$store.state.user.unit.name,
sex: this.$store.state.user.sex,
mobile: this.$store.state.user.mobile,
username: currentUser.username,
loginname: currentUser.loginname,
unionName: currentUser.union ? currentUser.union.name : "",
unitName: currentUser.unit ? currentUser.unit.name : "",
sex: currentUser.sex,
mobile: currentUser.mobile,
mobileColumnsValue: [],
}
if ((this.editMode || this.prefillMode) && signUpData) {
this.$set(formData, "id", signUpData.id)
this.$set(formData, "mobile", signUpData.mobile)
this.$set(formData, "activityCourseId", signUpData.activityCourseId)
this.$set(formData, "mobileColumnsValue",
this.mergeMobileColumnsValue(courseType, signUpData.mobileColumnsValue))
}
this.$set(this, "formData", formData)
this.$nextTick(() => {
this.$refs.form.clearValidate()
})
@@ -503,6 +503,23 @@ const basicForm = {
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 = ""
})
}
})
if (this.formData.typeLimits) {
this.formData.typeLimits.forEach((typeLimit) => {
typeLimit.id = ""
typeLimit.activityId = ""
})
}
}
},
typeChange(val) {
@@ -41,6 +41,7 @@ layout("/layouts/platform.html"){
<el-card shadow="never">
<table-tool label="人员列表">
<el-button size="small" type="primary" @click="personnelAdd">新增人员</el-button>
<el-button size="small" type="primary" icon="el-icon-download" @click="doExport">导出</el-button>
</table-tool>
<el-table ref="table" :data="tableData" :size="tableSize" row-key="id"
style="width: 100%" @sort-change="pageOrder">
@@ -157,6 +158,9 @@ layout("/layouts/platform.html"){
this.$refs.addOrModifySpecialStaffRef.getUserInfo()
})
},
doExport() {
this.$downLoad(loc() + "/doExport", this.pageForm)
},
flushUnits() {
this.$set(this.pageForm, "unitId", null)
this.units = []
@@ -36,8 +36,8 @@ const selectView = {
</div>
<div class="project-info-content">
<div class="info-item">
<div class="info-label">联系电话</div>
<div class="info-value">{{ mergedSelections[0]?.mobile || '暂无' }}</div>
<div class="info-label">福利电话</div>
<div class="info-value">{{ mergedSelections[0]?.welfareMobile || '暂无' }}</div>
</div>
</div>
</div>
@@ -135,14 +135,14 @@ const optionSelect = {
append-to-body
custom-class="welfare-confirm-dialog">
<div class="confirm-content">
<!-- 联系电话输入 -->
<!-- 福利电话输入统一读写用户资料中的 welfareMobile -->
<div class="confirm-mobile-section">
<div class="confirm-section-title">联系信息</div>
<el-form :model="contactForm" ref="contactForm" :rules="contactRules" label-width="80px">
<el-form-item prop="mobile" label="联系电话">
<el-form-item prop="welfareMobile" label="福利电话">
<el-input
v-model="contactForm.mobile"
placeholder="请输入手机号码"
v-model="contactForm.welfareMobile"
placeholder="请输入福利电话"
maxlength="11"
clearable>
</el-input>
@@ -230,15 +230,15 @@ const optionSelect = {
showOptionDetailDialog: false, // 选项详情弹窗
selectedOption: null, // 当前选中的选项
contactForm: {
mobile: "", // 联系电话,
welfareMobile: "", // 福利电话
receiveAddress: ""
},
contactRules: {
mobile: [
{ required: true, message: "请输入联系电话", trigger: "blur" },
welfareMobile: [
{ required: true, message: "请输入福利电话", trigger: "blur" },
{
pattern: /^1[3456789]\d{9}$/,
message: "请输入正确的手机号码",
message: "请输入正确的福利电话",
trigger: "blur"
}
]
@@ -325,7 +325,7 @@ const optionSelect = {
this.selectedRadioId = null
this.contactForm = {
mobile: "",
welfareMobile: "",
receiveAddress: ""
}
@@ -376,18 +376,8 @@ const optionSelect = {
this.userSelection = resp.data
this.hasSubmittedBefore = this.userSelection.length > 0
if (this.userSelection && this.userSelection.length > 0) {
this.contactForm.mobile = this.userSelection[0]?.mobile
} else {
if (this.isProxySelect) {
// 否则使用默认手机号
$.post("/platform/welfare/selection/situation/getMobileByUserId", { userId: this.userId }).then((res) => {
if (res.code === 0) {
this.contactForm.mobile = res.data
}
})
}
}
// 福利电话始终从用户资料回显,不再读取选择记录中的 mobile。
this.getWelfareMobile()
// 地址回显
if (this.projectInfo.provideMode === 3) {
@@ -422,9 +412,22 @@ const optionSelect = {
})
},
// 获取本人或被代选用户的福利电话,返回值为字符串或空值。
getWelfareMobile() {
const url = this.isProxySelect
? "/platform/welfare/selection/situation/getWelfareMobileByUserId"
: "/platform/welfare/userSelect/getWelfareMobile"
const formData = this.isProxySelect ? { userId: this.userId } : {}
this.$axios.post(url, formData).then((res) => {
if (res.code === 0) {
this.$set(this.contactForm, "welfareMobile", res.data || "")
}
})
},
// 执行提交
doSubmit() {
// 验证手机号
// 校验福利电话和按配送方式动态要求的收货地址。
this.$refs.contactForm.validate((valid) => {
if (!valid) {
return
@@ -442,7 +445,6 @@ const optionSelect = {
{
selectOptionId: this.selectedRadioId,
selectNum: 1,
mobile: this.contactForm.mobile,
receiveAddress: this.contactForm.receiveAddress
}
]
@@ -453,7 +455,6 @@ const optionSelect = {
selections = this.selectedOptions.map((option) => ({
selectOptionId: option.id,
selectNum: option.selectNum,
mobile: this.contactForm.mobile,
receiveAddress: this.contactForm.receiveAddress
}))
}
@@ -461,6 +462,7 @@ const optionSelect = {
let url = "/platform/welfare/userSelect/confirmSelect"
const formData = {
projectId: this.projectId,
welfareMobile: this.contactForm.welfareMobile,
selections: JSON.stringify(selections)
}
if (this.isProxySelect) {
@@ -471,8 +473,6 @@ const optionSelect = {
this.$axios
.post(url, formData)
.then((res) => {
this.isSubmitting = false
if (res.code === 0) {
this.showConfirmDialog = false
this.$message.success("选择成功")
@@ -482,8 +482,10 @@ const optionSelect = {
}
})
.catch(() => {
// 网络异常由全局请求处理器提示。
})
.finally(() => {
this.isSubmitting = false
// this.$message.error("网络错误,请重试")
})
})
},
@@ -61,6 +61,13 @@ layout("/layouts/platform.html"){
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitOptions"></el-option>
</el-select>
</search-item>
<search-item label="管理员代选">
<el-select clearable placeholder="请选择是否管理员代选" style="width: 100%" v-model="pageForm.isSelectByAdmin">
<el-option :value="true" label="是"></el-option>
<el-option :value="false" label="否"></el-option>
</el-select>
</search-item>
</search>
</el-card>
@@ -75,6 +82,16 @@ layout("/layouts/platform.html"){
<el-button type="primary" size="small" icon="el-icon-download" style="margin-left: 10px" @click="exportXlsx">
导出选择情况表
</el-button>
<el-button
type="primary"
size="small"
icon="el-icon-edit"
style="margin-left: 10px"
@click="selectOptionByAdmin"
v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN','SCHOOL_UNION_WELFARE_ADMIN'])"
>
管理员代选择
</el-button>
</table-tool>
<el-table
@@ -114,6 +131,34 @@ layout("/layouts/platform.html"){
<el-dialog title="代选" :visible.sync="optionSelectVisible" width="60%">
<option-select ref="optionSelectRef" @refresh="optionSelectVisible=false;doSearch();"></option-select>
</el-dialog>
<el-dialog :visible.sync="selectByAdminDialogVisible" title="管理员统一选择福利" width="35%">
<el-form label-position="right" label-width="120px">
<el-form-item label="福利选项">
<el-row :gutter="10" :key="item.id" style="margin: 10px" v-for="(item, oidx) in welfareAdminOptions">
<el-col :span="6">
<el-tag style="height:40px;line-height:40px;width: 100%;">
<span style="display: flex; justify-content: center;">{{item.optionName}}</span>
</el-tag>
</el-col>
<el-col :span="18">
<el-input-number
:max="optionMax(item)"
:min="0"
@change="numChange($event, oidx)"
style="width: 50%"
v-model="item.selectNum"
></el-input-number>
</el-col>
</el-row>
</el-form-item>
</el-form>
<el-alert class="text-primary mb10">给没有选择的福利人员统一代选择</el-alert>
<el-row justify="end" type="flex">
<el-button @click="selectByAdminDialogVisible=false">取消</el-button>
<el-button :loading="doSelectByAdminLoading" @click="doSelectByAdmin" type="primary">提交</el-button>
</el-row>
</el-dialog>
</guava>
</div>
@@ -144,20 +189,30 @@ layout("/layouts/platform.html"){
{ prop: "welfareUnionName", label: "所属工会", sortable: true },
{ prop: "welfareUnitName", label: "所属单位", sortable: true },
{ prop: "selectedOptions", label: "所选福利", sortable: true },
{ prop: "welfareMobile", label: "福利号码", sortable: true }
{ prop: "selectByAdminName", label: "是否管理员代选", sortable: true },
{ prop: "welfareMobile", label: "福利电话", sortable: true }
],
optionSelectVisible: false
optionSelectVisible: false,
selectByAdminDialogVisible: false,
doSelectByAdminLoading: false,
welfareAdminOptions: []
}
},
computed: {
currentProject() {
if (this.pageForm.projectId) {
return this.projectOptions.find((item) => item.id === this.pageForm.projectId) || {}
}
return {}
},
welfareOptions() {
if (this.pageForm.projectId) {
return this.projectOptions.find((item) => item.id === this.pageForm.projectId).options
return this.currentProject.options || []
}
return []
},
provideMode() {
return this.projectOptions.find((item) => item.id === this.pageForm.projectId).provideMode
return this.currentProject.provideMode
}
},
methods: {
@@ -177,8 +232,12 @@ layout("/layouts/platform.html"){
// 获取数据
pageData() {
this.tableLoading = true
const pageForm = Object.assign({}, this.pageForm)
if (pageForm.isSelectByAdmin === "" || pageForm.isSelectByAdmin === null || pageForm.isSelectByAdmin === undefined) {
delete pageForm.isSelectByAdmin
}
this.$axios
.post("/platform/welfare/selection/situation/pageData", { pageForm: JSON.stringify(this.pageForm) })
.post("/platform/welfare/selection/situation/pageData", { pageForm: JSON.stringify(pageForm) })
.then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
@@ -192,10 +251,95 @@ layout("/layouts/platform.html"){
// 导出选择情况表
exportXlsx() {
this.$downLoad("/platform/welfare/selection/situation/exportXlsx", { pageForm: JSON.stringify(this.pageForm) })
const pageForm = Object.assign({}, this.pageForm)
if (pageForm.isSelectByAdmin === "" || pageForm.isSelectByAdmin === null || pageForm.isSelectByAdmin === undefined) {
delete pageForm.isSelectByAdmin
}
this.$downLoad("/platform/welfare/selection/situation/exportXlsx", { pageForm: JSON.stringify(pageForm) })
},
// 管理员待选
optionMax(option) {
if (option.maxNum !== undefined && option.maxNum !== null) {
return option.maxNum
}
if (this.currentProject.isCheckBox === "checkBox" && this.currentProject.singleOptionSupportMultipleSelection) {
return this.currentProject.multiSelectNum || 99
}
return 1
},
numChange(value, optionIndex) {
const currentValue = Number(value) || 0
this.$set(this.welfareAdminOptions[optionIndex], "selectNum", currentValue)
if (this.currentProject.isCheckBox !== "checkBox") {
this.welfareAdminOptions.forEach((option, index) => {
this.$set(option, "selectNum", index === optionIndex && currentValue > 0 ? 1 : 0)
})
return
}
if (!this.currentProject.singleOptionSupportMultipleSelection && currentValue > 1) {
this.$set(this.welfareAdminOptions[optionIndex], "selectNum", 1)
this.$message.warning("此套餐最多只能选择1份")
return
}
const sum = this.welfareAdminOptions.reduce((total, option) => total + (option.selectNum || 0), 0)
const maxSelectNum = this.currentProject.isCheckBox === "checkBox" ? (this.currentProject.multiSelectNum || 1) : 1
if (sum > maxSelectNum) {
this.$set(this.welfareAdminOptions[optionIndex], "selectNum", Math.max(0, currentValue - (sum - maxSelectNum)))
this.$message.warning("此次福利最多只能选择" + maxSelectNum + "份")
}
},
selectOptionByAdmin() {
if (!this.pageForm.projectId) {
this.$message.warning("请先选择福利项目")
return
}
this.welfareAdminOptions = this.welfareOptions.map((item) => Object.assign({}, item, { selectNum: 0 }))
this.selectByAdminDialogVisible = true
},
doSelectByAdmin() {
const sum = this.welfareAdminOptions.reduce((total, option) => total + (option.selectNum || 0), 0)
if (sum === 0) {
this.$message.warning("请选择福利")
return
}
this.$confirm("请确定是否给未选择的教职工一键选择当前福利?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
this.doSelectByAdminLoading = true
try {
const welfareOptions = this.welfareAdminOptions.map((w) => {
return {
id: w.id,
subjectId: w.subjectId,
selectNum: w.selectNum
}
})
const resp = await this.$axios.post("/platform/welfare/selection/situation/doSelectByAdmin", {
welfareOptions: JSON.stringify(welfareOptions),
projectId: this.pageForm.projectId
})
if (resp.code === 0) {
this.pageData()
this.selectByAdminDialogVisible = false
this.$message.success(resp.msg || "操作成功")
} else {
this.$message.warning(resp.msg || "操作失败")
}
} finally {
this.doSelectByAdminLoading = false
}
}).catch(() => {})
},
// 管理员代选
proxySelect(row) {
this.optionSelectVisible = true
this.$nextTick(() => {
@@ -28,6 +28,12 @@ layout("/layouts/platform.html"){
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unionOptions"></el-option>
</el-select>
</search-item>
<search-item label="选择方式">
<el-select clearable placeholder="选择方式" style="width: 100%" v-model="pageForm.isSelectByAdmin">
<el-option :value="true" label="管理员代选"></el-option>
<el-option :value="false" label="个人选择"></el-option>
</el-select>
</search-item>
</search>
</el-card>
@@ -38,6 +44,9 @@ layout("/layouts/platform.html"){
<el-button :disabled="!pageForm.projectId" @click="exportSummary" icon="el-icon-printer" size="small" type="primary">
导出汇总表
</el-button>
<el-button :disabled="!pageForm.projectId" @click="selectOptionByAdmin" icon="el-icon-edit" size="small" type="primary">
管理员代选择
</el-button>
</template>
</table-tool>
<el-table
@@ -76,6 +85,34 @@ layout("/layouts/platform.html"){
</template>
</guava>
<el-dialog :visible.sync="selectByAdminDialogVisible" title="管理员统一选择福利" width="35%">
<el-form label-position="right" label-width="120px">
<el-form-item label="福利选项">
<el-row :gutter="10" :key="item.id" style="margin: 10px" v-for="(item, oidx) in welfareOptions">
<el-col :span="6">
<el-tag style="height:40px;line-height:40px;width: 100%;">
<span style="display: flex; justify-content: center;">{{item.optionName}}</span>
</el-tag>
</el-col>
<el-col :span="18">
<el-input-number
:max="optionMax(item)"
:min="0"
@change="numChange($event, oidx)"
style="width: 50%"
v-model="item.selectNum"
></el-input-number>
</el-col>
</el-row>
</el-form-item>
</el-form>
<el-alert class="text-primary mb10">给没有选择的福利人员统一代选择</el-alert>
<el-row justify="end" type="flex">
<el-button @click="selectByAdminDialogVisible=false">取消</el-button>
<el-button :loading="doSelectByAdminLoading" @click="doSelectByAdmin" type="primary">提交</el-button>
</el-row>
</el-dialog>
<selected-user ref="selectedUserRef"></selected-user>
<unselected-user ref="unSelectedUserRef"></unselected-user>
</div>
@@ -102,7 +139,9 @@ layout("/layouts/platform.html"){
welfareOptions: [],
projectInfo: {},
clearable: false,
tableColumns: []
tableColumns: [],
selectByAdminDialogVisible: false,
doSelectByAdminLoading: false
}
},
methods: {
@@ -130,9 +169,95 @@ layout("/layouts/platform.html"){
})
},
optionMax(option) {
if (option.maxNum !== undefined && option.maxNum !== null) {
return option.maxNum
}
if (this.projectInfo.isCheckBox === "checkBox" && this.projectInfo.singleOptionSupportMultipleSelection) {
return this.projectInfo.multiSelectNum || 99
}
return 1
},
numChange(value, optionIndex) {
const currentValue = Number(value) || 0
this.$set(this.welfareOptions[optionIndex], "selectNum", currentValue)
if (this.projectInfo.isCheckBox !== "checkBox") {
this.welfareOptions.forEach((option, index) => {
this.$set(option, "selectNum", index === optionIndex && currentValue > 0 ? 1 : 0)
})
return
}
if (!this.projectInfo.singleOptionSupportMultipleSelection && currentValue > 1) {
this.$set(this.welfareOptions[optionIndex], "selectNum", 1)
this.$message.warning("此套餐最多只能选择1份")
return
}
const sum = this.welfareOptions.reduce((total, option) => total + (option.selectNum || 0), 0)
const maxSelectNum = this.projectInfo.isCheckBox === "checkBox" ? (this.projectInfo.multiSelectNum || 1) : 1
if (sum > maxSelectNum) {
this.$set(this.welfareOptions[optionIndex], "selectNum", Math.max(0, currentValue - (sum - maxSelectNum)))
this.$message.warning("此次福利最多只能选择" + maxSelectNum + "份")
}
},
selectOptionByAdmin() {
if (!this.pageForm.projectId) {
this.$message.warning("请先选择福利项目")
return
}
this.getWelfareOptions()
this.selectByAdminDialogVisible = true
},
doSelectByAdmin() {
const selectNums = this.welfareOptions.map((v) => v.selectNum || 0)
const sum = selectNums.reduce((total, current) => total + current, 0)
if (sum === 0) {
this.$message.warning("请选择福利")
return
}
this.$confirm("请确定是否给未选择的教职工一键选择当前福利?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
this.doSelectByAdminLoading = true
try {
const welfareOptions = this.welfareOptions.map((w) => {
return {
id: w.id,
subjectId: w.subjectId,
selectNum: w.selectNum
}
})
const resp = await this.$axios.post("/platform/welfare/statistics/doSelectByAdmin", {
welfareOptions: JSON.stringify(welfareOptions),
projectId: this.pageForm.projectId
})
if (resp.code === 0) {
this.pageData()
this.selectByAdminDialogVisible = false
this.$message.success(resp.msg || "操作成功")
} else {
this.$message.warning(resp.msg || "操作失败")
}
} finally {
this.doSelectByAdminLoading = false
}
}).catch(() => {})
},
async pageData() {
this.tableLoading = true
const resp = await this.$axios.post("/platform/welfare/statistics/pageData", this.pageForm)
const pageForm = Object.assign({}, this.pageForm)
if (pageForm.isSelectByAdmin === "" || pageForm.isSelectByAdmin === null || pageForm.isSelectByAdmin === undefined) {
delete pageForm.isSelectByAdmin
}
const resp = await this.$axios.post("/platform/welfare/statistics/pageData", pageForm)
if (resp.code === 0) {
this.tableColumns = resp.data.tableColumn
this.tableData = resp.data.tableList
@@ -151,8 +276,8 @@ layout("/layouts/platform.html"){
},
getWelfareOptions() {
const data = this.welfareProjectList.find((p) => p.id === this.pageForm.projectId)
this.projectInfo = data
this.welfareOptions = data.options
this.projectInfo = data || {}
this.welfareOptions = data && data.options ? data.options.map((item) => Object.assign({}, item, { selectNum: 0 })) : []
},
async init() {
if (
@@ -3,6 +3,17 @@ const selectedUser = {
<el-dialog :title="unionName+'已选择用户'" :visible.sync="visible" width="70%">
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<div>
<el-select
v-model="pageForm.isSelectByAdmin"
placeholder="选择方式"
style="width: 140px;"
size="small"
clearable
@change="doSearch"
>
<el-option :value="true" label="管理员代选"></el-option>
<el-option :value="false" label="个人选择"></el-option>
</el-select>
<el-input
v-model="pageForm.searchKeyword"
placeholder="请输入姓名或工号"
@@ -54,7 +65,8 @@ const selectedUser = {
{ prop: "preparedBy", label: "聘用方式", width: 120, sortable: true },
{ prop: "postDoctoralJoinDate", label: "进站时间", sortable: true },
{ label: "单位", prop: "welfareUnitName" },
{ label: "手机号", prop: "mobile" },
{ label: "福利电话", prop: "welfareMobile" },
{ label: "选择方式", prop: "selectByAdminName" },
{ label: "所选福利", prop: "selectOptionName" }
]
}
@@ -65,16 +77,20 @@ const selectedUser = {
this.projectId = projectId
this.unionId = id
this.unionName = name
this.searchKeyword = ""
this.pageForm.searchKeyword = ""
this.$set(this.pageForm, "isSelectByAdmin", null)
this.pageData()
},
pageData() {
const pageForm = Object.assign({}, this.pageForm)
if (pageForm.isSelectByAdmin === "" || pageForm.isSelectByAdmin === null || pageForm.isSelectByAdmin === undefined) {
delete pageForm.isSelectByAdmin
}
this.$axios
.post("/platform/welfare/statistics/selectedUnionUserPageData", {
...this.pageForm,
...pageForm,
projectId: this.projectId,
unionId: this.unionId,
keyword: this.searchKeyword
unionId: this.unionId
})
.then((res) => {
if (res.code === 0) {
@@ -84,14 +100,18 @@ const selectedUser = {
})
},
handleSearch() {
this.pageForm.pageNum = 1
this.pageForm.pageNumber = 1
this.pageData()
},
handleExport() {
this.$downLoad("/platform/welfare/statistics/exportSelectedUnionUser", {
const params = {
projectId: this.projectId,
unionId: this.unionId
})
}
if (this.pageForm.isSelectByAdmin !== null && this.pageForm.isSelectByAdmin !== undefined && this.pageForm.isSelectByAdmin !== "") {
params.isSelectByAdmin = this.pageForm.isSelectByAdmin
}
this.$downLoad("/platform/welfare/statistics/exportSelectedUnionUser", params)
}
}
}
@@ -136,6 +136,16 @@ layout("/layouts/platform.html"){
<el-button :disabled="!pageForm.projectId" @click="openExport" icon="el-icon-download" size="small" type="primary">
导出名单
</el-button>
<el-button
v-if="$auth.hasRole('SYSADMIN')"
:disabled="!pageForm.projectId"
@click="exportRepeatIdCard"
icon="el-icon-download"
size="small"
type="primary"
>
导出重复身份证号人员
</el-button>
<el-button :disabled="!pageForm.projectId" @click="openImport" icon="el-icon-document-add" size="small" type="primary">
导入名单
</el-button>
@@ -329,6 +339,10 @@ layout("/layouts/platform.html"){
exportAddress() {
this.$downLoad("/platform/welfare/list/mange/exportAddress", this.pageForm)
},
// 导出重复身份证号人员
exportRepeatIdCard() {
this.$downLoad("/platform/welfare/list/mange/exportRepeatIdCard", this.pageForm)
},
// 导出名单
openExport() {
const pageForm = clone(this.pageForm)
@@ -145,10 +145,7 @@ layout("/layouts/platform_h5.html"){
this.infoVisible = true
},
onApply(row) {
if(this.$store.state.user.loginname !== '45066' && this.$moment().isBefore(this.$moment(row.activitySignUpStartTime))) {
this.onView(row)
return
}
// 报名前允许进入课程列表维护预填信息,预填不会提前占用名额。
this.$pjaxReplace('/platform/family/apply/list/h5?id=' + row.id)
},
onReady() {
@@ -3,7 +3,9 @@ const applyForm = {
/*language=HTML*/
`
<div>
<van-action-sheet :close-on-click-overlay="false" title="报名信息" v-model="visible">
<van-action-sheet :close-on-click-overlay="false"
:title="prefillMode ? (formData.id ? '修改预填信息' : '预填报名信息') : (editMode ? '修改报名' : '报名信息')"
v-model="visible" @close="onFormClosed">
<van-form ref="formRef" class="form-container">
<van-cell-group title="活动信息" class="form-section">
<van-field label="活动名称" readonly v-model="row.courseName"></van-field>
@@ -16,22 +18,25 @@ const applyForm = {
<van-field label="所在单位" readonly v-model="formData.unitName"></van-field>
<van-field label="所属工会" readonly v-model="formData.unionName"></van-field>
<van-field label="性别" readonly v-model="formData.sex"></van-field>
<van-field label="联系方式" required name="mobile" v-model="formData.mobile"
:rules="[{ required: true, message: '请填写联系方式' }]"
placeholder="请填写联系方式"></van-field>
<template v-if="row.courseIsLimitApply">
<van-field label="报名时段"
required
:rules="[{ required: true, message: '请选择报名时段' }]"
readonly
@click="showCoursePicker = true"
@click="openCoursePicker"
placeholder="请选择报名时段"
name="courseTimeName"
v-model="formData.courseTimeName">
</van-field>
<van-popup v-model="showCoursePicker" position="bottom">
<van-popup v-model="showCoursePicker" position="bottom" @close="onCoursePickerClosed">
<van-picker
show-toolbar
:columns="courseTimeSelectList"
@confirm="onCourseConfirm"
@cancel="showCoursePicker=false"
@cancel="closeCoursePicker"
></van-picker>
</van-popup>
</template>
@@ -65,7 +70,7 @@ const applyForm = {
</van-cell-group>
<div class="button">
<van-button @click="onSubmit" type="info" block>提交</van-button>
<van-button :loading="formLoading" @click="onSubmit" type="info" block>{{prefillMode ? '保存预填' : '提交'}}</van-button>
</div>
</van-form>
</van-action-sheet>
@@ -77,15 +82,25 @@ const applyForm = {
row: {},
activity: {},
visible: false,
editMode: false,
prefillMode: false,
formLoading: false,
formData: {},
showCoursePicker: false,
courseTimeSelectList: [],
courseType: {},
historyOwner: '',
}
},
components: {
"train-dynamic-form": httpVueLoader("/components/module/trainSignUp/TrainDynamicForm.vue")
},
mounted() {
this.historyOwner = "family-apply-form-" + this._uid
},
beforeDestroy() {
h5PopupHistory.unregisterOwner(this.historyOwner)
},
methods: {
addFamily() {
if(this.formData.mobileColumnsValue.length >= this.activity.familyMaxCount) {
@@ -100,40 +115,110 @@ const applyForm = {
const ac = (Number(this.familyActive) - 1)
this.familyActive = '' + (ac >= 0 ? ac : 0)
},
async onOpen(row, courseType, activity) {
this.row = row
this.courseType = courseType
this.activity = activity
this.init(row, courseType)
if(row.courseIsLimitApply) {
await this.getCourseTimeSelectList(row)
onOpen(row, courseType, activity, signUpData, mode) {
this.prefillMode = mode === "prefill"
this.$set(this, "row", row)
this.$set(this, "courseType", courseType)
this.$set(this, "activity", activity)
this.init(row, courseType, signUpData)
const openForm = () => {
h5PopupHistory.open(this.historyOwner + "-form", this.historyOwner, () => {
this.$set(this, "visible", false)
})
this.$set(this, "visible", true)
}
this.visible = true
if(row.courseIsLimitApply) {
this.getCourseTimeSelectList(row, this.editMode && signUpData ? signUpData.id : "")
.then((resp) => {
if (resp.code !== 0) return
this.setCourseTimeName(signUpData ? signUpData.activityCourseId : "")
openForm()
})
return
}
openForm()
},
init(row, courseType) {
this.$set(this.formData, 'username', this.$store.state.user.username)
this.$set(this.formData, 'loginname', this.$store.state.user.loginname)
this.$set(this.formData, 'unionName', this.$store.state.user.union.name)
this.$set(this.formData, 'unitName', this.$store.state.user.unit.name)
this.$set(this.formData, 'sex', this.$store.state.user.sex)
this.$set(this.formData, 'activityId', row.activityId)
this.$set(this.formData, 'courseId', row.id)
this.$set(this.formData, 'mobileColumnsValue', [])
init(row, courseType, signUpData) {
this.editMode = !this.prefillMode && Boolean(signUpData && signUpData.id)
this.$set(this, "familyActive", "")
this.$set(this, "courseTimeSelectList", [])
this.$set(this, "showCoursePicker", false)
const formData = {
username: this.$store.state.user.username,
loginname: this.$store.state.user.loginname,
unionName: this.$store.state.user.union.name,
unitName: this.$store.state.user.unit.name,
sex: this.$store.state.user.sex,
mobile: this.$store.state.user.mobile,
activityId: row.activityId,
courseId: row.id,
mobileColumnsValue: []
}
if ((this.editMode || this.prefillMode) && signUpData) {
this.$set(formData, "id", signUpData.id)
this.$set(formData, "mobile", signUpData.mobile)
this.$set(formData, "activityCourseId", signUpData.activityCourseId)
this.$set(formData, "mobileColumnsValue",
this.mergeMobileColumnsValue(courseType, signUpData.mobileColumnsValue))
}
this.$set(this, "formData", formData)
},
onCourseConfirm(val){
this.formData.activityCourseId = val.value
this.$set(this.formData, "activityCourseId", val.value)
this.$set(this.formData, "courseTimeName", val.text.substring(0, 12))
this.showCoursePicker = false
this.closeCoursePicker()
},
async getCourseTimeSelectList(o) {
const resp = await this.$axios.post('/platform/family/apply/getCourseTimeSelectList',{courseId: o.id})
if (resp.code === 0) {
this.courseTimeSelectList = resp.data
} else {
this.$toast.fail("获取时段信息失败,请联系管理员")
}
getCourseTimeSelectList(o, familyUserId) {
const url = familyUserId
? "/platform/family/apply/getEditCourseTimeSelectList"
: "/platform/family/apply/getCourseTimeSelectList"
this.formLoading = true
return this.$axios.post(url, {courseId: o.id, familyUserId: familyUserId})
.then((resp) => {
if (resp.code === 0) {
this.$set(this, "courseTimeSelectList", resp.data)
} else {
this.$toast.fail(resp.msg || "获取时段信息失败,请联系管理员")
}
return resp
})
.finally(() => {
this.formLoading = false
})
},
// 打开时段选择器时增加一层历史,返回键只关闭当前选择器。
openCoursePicker() {
h5PopupHistory.open(this.historyOwner + "-course-picker", this.historyOwner, () => {
this.$set(this, "showCoursePicker", false)
})
this.$set(this, "showCoursePicker", true)
},
closeCoursePicker() {
h5PopupHistory.close(this.historyOwner + "-course-picker")
},
onCoursePickerClosed() {
h5PopupHistory.close(this.historyOwner + "-course-picker")
},
onFormClosed() {
h5PopupHistory.close(this.historyOwner + "-form")
},
setCourseTimeName(activityCourseId) {
if (!activityCourseId) return
const selected = this.courseTimeSelectList.find((item) => item.value === activityCourseId)
if (selected) this.$set(this.formData, "courseTimeName", selected.text.substring(0, 12))
},
// 修改时按字段编码合并历史值,继续使用活动当前配置的必填和格式规则。
mergeMobileColumnsValue(courseType, mobileColumnsValue) {
const columnConfig = courseType && Array.isArray(courseType.familyMobileSignColumnList)
? courseType.familyMobileSignColumnList : []
return (mobileColumnsValue || []).map((family) => {
if (columnConfig.length === 0) return clone(family)
return clone(columnConfig).map((column) => {
const oldColumn = family.find((item) => item.columnCode === column.columnCode)
this.$set(column, "columnValue", oldColumn ? oldColumn.columnValue : "")
return column
})
})
},
validateIdCard(idCard) {
if (!idCard) {
@@ -266,62 +351,88 @@ const applyForm = {
return { age, sex, };
},
async validateSignUp() {
validateSignUp() {
// 获取家属人数
const res = await this.$axios.post("/platform/family/apply/validateSignUp", {
return this.$axios.post("/platform/family/apply/validateSignUp", {
courseId: this.formData.courseId,
currentFamilyNumber: this.formData.mobileColumnsValue.length || 0
})
if(res.code !== 0) {
this.$dialog.alert({title: '温馨提示', message: res.msg})
}
return res.code === 0
.then((res) => {
if(res.code !== 0) {
this.$dialog.alert({title: '温馨提示', message: res.msg})
}
return res.code === 0
})
},
async validateCourseTime() {
const res = await this.$axios.post('/platform/family/apply/validateSourceSignUp', {
validateUpdateSignUp() {
return this.$axios.post("/platform/family/apply/validateUpdateSignUp", {
courseId: this.formData.courseId
}).then((res) => {
if(res.code !== 0) {
this.$toast.fail(res.msg)
}
return res.code === 0
})
},
validateCourseTime() {
return this.$axios.post('/platform/family/apply/validateSourceSignUp', {
activityCourseId: this.formData.activityCourseId,
courseId: this.row.id
})
if(res.code !== 0) {
this.$dialog.alert({title: '温馨提示', message: res.msg})
}
return res.code === 0
},
async onSubmit() {
if (!this.validFamilyForm()) return
if (!await this.validateSignUp()) return
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(async () => {
if (this.row.courseIsLimitApply) {
if(!await this.validateCourseTime()) return
}
const formData = clone(this.formData)
let array = []
formData.mobileColumnsValue.forEach(item => {
const o = item.map((v) => {
return {
columnName: v.columnName,
columnValue: v.columnValue,
columnCode: v.columnCode,
columnFormType: v.columnFormType
}
})
array.push(o)
})
formData.mobileColumnsValue = JSON.stringify(clone(array))
this.$axios.post("/platform/family/apply/doSignUp", formData).then(res => {
.then((res) => {
if(res.code !== 0) {
this.$dialog.alert({title: '温馨提示', message: res.msg})
.then(() => {
if (res.code === 0) {
this.visible = false
this.$emit('refresh')
}
}
return res.code === 0
})
},
onSubmit() {
if (!this.validFamilyForm()) return
const eligibility = this.prefillMode
? Promise.resolve(true)
: (this.editMode ? this.validateUpdateSignUp() : this.validateSignUp())
eligibility.then((eligible) => {
if (!eligible) return
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
const courseTimeValidation = !this.editMode && !this.prefillMode && this.row.courseIsLimitApply
? this.validateCourseTime() : Promise.resolve(true)
courseTimeValidation.then((courseTimeValid) => {
if (!courseTimeValid) return
const formData = clone(this.formData)
const array = formData.mobileColumnsValue.map((item) => {
return item.map((column) => {
return {
columnName: column.columnName,
columnValue: column.columnValue,
columnCode: column.columnCode,
columnFormType: column.columnFormType
}
})
})
formData.mobileColumnsValue = JSON.stringify(clone(array))
const url = this.prefillMode
? "/platform/family/apply/savePrefill"
: (this.editMode
? "/platform/family/apply/updateSignUp"
: "/platform/family/apply/doSignUp")
this.formLoading = true
this.$axios.post(url, formData)
.then((res) => {
this.$dialog.alert({title: "温馨提示", message: res.msg})
.then(() => {
if (res.code !== 0) return
h5PopupHistory.clearOwner(this.historyOwner)
this.$emit("refresh")
})
})
.finally(() => {
this.formLoading = false
})
})
})
})
})
@@ -71,10 +71,22 @@ layout("/layouts/platform_h5.html"){
<span>微信群二维码</span>
</div>
<template v-if="$moment().isBefore($moment(activity.activitySignUpEndTime))">
<div v-if="row.isSign === false" class="action-btn" @click="onApply(row)">
<div v-if="row.isSign === false && isBeforeSignUpTime()" class="action-btn" @click="onPrefill(row)">
<i class="fa fa-edit"></i>
<span>{{row.hasPrefill ? '修改预填信息' : '预填报名信息'}}</span>
</div>
<div v-if="row.isSign === false && isInSignUpTime() && row.hasPrefill" class="action-btn" @click="onQuickSignUp(row)">
<i class="fa fa-bolt"></i>
<span>立即报名</span>
</div>
<div v-if="row.isSign === false && isInSignUpTime() && !row.hasPrefill" class="action-btn" @click="onApply(row)">
<i class="fa fa-sign-in"></i>
<span>我要报名</span>
</div>
<div v-if="row.isSign === true && isInSignUpTime()" class="action-btn" @click="onEdit(row)">
<i class="fa fa-edit"></i>
<span>修改报名</span>
</div>
<div v-if="row.isSign === true" class="action-btn delete" @click="onCancel(row)">
<i class="fa fa-trash"></i>
<span>取消报名</span>
@@ -129,6 +141,8 @@ layout("/layouts/platform_h5.html"){
introduceRow: {},
activity: {},
nowTime: Date.now(),
signTimeTimer: null,
weekdayCNMap: {0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六'},
}
},
@@ -136,16 +150,58 @@ layout("/layouts/platform_h5.html"){
refresh() {
this.doSearch()
},
async onTime(row) {
// 预填信息只能在报名开始时间之前维护。
isBeforeSignUpTime() {
return this.activity.activitySignUpStartTime
&& this.$moment(this.nowTime).isBefore(this.$moment(this.activity.activitySignUpStartTime))
},
onTime(row) {
// 如果设置签到,并且也报名的话
if(row.isMobileSign === true && row.isSign === true) {
const res = await this.$axios.post('/platform/family/mine/queryCourseSign', {
this.$axios.post('/platform/family/mine/queryCourseSign', {
courseId: row.id
})
row.courseTimes = res.data
.then((res) => {
this.$set(row, "courseTimes", res.data)
this.$refs.timesRef.onOpen(row)
})
return
}
this.$refs.timesRef.onOpen(row)
},
// 修改报名按钮与报名接口使用同一时间边界:开始时间已到且尚未达到结束时间。
isInSignUpTime() {
const now = this.$moment(this.nowTime)
return this.activity.activitySignUpStartTime
&& this.activity.activitySignUpEndTime
&& !now.isBefore(this.$moment(this.activity.activitySignUpStartTime))
&& now.isBefore(this.$moment(this.activity.activitySignUpEndTime))
},
onPrefill(row) {
const courseType = this.sourceTypeOptions.find((item) => item.id === row.courseType)
this.$axios.post("/platform/family/apply/getPrefill", {courseId: row.id})
.then((resp) => {
if (resp.code !== 0) {
this.$toast.fail(resp.msg)
return
}
this.$refs.formRef.onOpen(row, courseType, this.activity, resp.data, "prefill")
})
},
onQuickSignUp(row) {
this.$dialog.confirm({
title: "提示",
message: "将使用报名开始前保存的预填信息直接报名,是否继续?"
}).then(() => {
this.$axios.post("/platform/family/apply/quickSignUp", {courseId: row.id})
.then((resp) => {
this.$dialog.alert({title: "温馨提示", message: resp.msg})
.then(() => {
if (resp.code === 0) this.doSearch()
})
})
}).catch(() => {})
},
onApply(row) {
const courseType = this.sourceTypeOptions.find((v) => v.id === row.courseType)
this.$axios.post('/platform/family/apply/validateSignUp', {
@@ -171,20 +227,38 @@ layout("/layouts/platform_h5.html"){
}
})
},
onEdit(row) {
const courseType = this.sourceTypeOptions.find((item) => item.id === row.courseType)
this.$axios.post("/platform/family/apply/validateUpdateSignUp", {courseId: row.id})
.then((validateRes) => {
if (validateRes.code !== 0) {
this.$toast.fail(validateRes.msg)
return null
}
return this.$axios.post("/platform/family/apply/getMySignUp", {courseId: row.id})
})
.then((signUpRes) => {
if (!signUpRes || signUpRes.code !== 0) {
if (signUpRes) this.$toast.fail(signUpRes.msg)
return
}
this.$refs.formRef.onOpen(row, courseType, this.activity, signUpRes.data)
})
},
onCancel(row) {
vant.Dialog.confirm({
title: '温馨提示',
message: "您确定要<span style='color: red'>取消【" + row.courseName + "】</span>吗?",
confirmButtonColor: '#1867b0',
}).then(async () => {
const resp = await this.$axios.post('/platform/family/apply/cancelSignUp', {
}).then(() => {
this.$axios.post('/platform/family/apply/cancelSignUp', {
activityId: row.activityId,
courseId: row.id
})
this.$toast(resp.msg)
if (resp.code === 0) {
this.doSearch()
}
.then((resp) => {
this.$toast(resp.msg)
if (resp.code === 0) this.doSearch()
})
}).catch(() => {})
},
calcSignUpCount(row) {
@@ -200,30 +274,31 @@ layout("/layouts/platform_h5.html"){
return "<span style='color: red'>余" + lave +"</span>/" + row.coursePeopleNumber + "人"
}
},
async onReady() {
const typeList = await this.getCourseTypeList()
this.sourceTypeOptions = clone(typeList)
this.typeOptions = [
{
text: "全部类型",
value: null
onReady() {
this.getCourseTypeList().then((typeList) => {
this.$set(this, "sourceTypeOptions", clone(typeList))
this.$set(this, "typeOptions", [
{
text: "全部类型",
value: null
}
].concat(typeList.map((item) => ({text: item.typeName, value: item.id}))))
if (this.typeOptions.length > 0) {
this.$set(this.pageForm, "type", this.typeOptions[0].value)
this.doSearch()
}
].concat(typeList.map((v) => ({ text: v.typeName, value: v.id })))
if (this.typeOptions.length > 0) {
this.pageForm.type = this.typeOptions[0].value
this.doSearch()
}
this.fetchActivity()
this.fetchActivity()
})
},
fetchActivity() {
this.$axios.post('/platform/family/manage/findOne', {id: this.pageForm.activityId})
.then((res) => {
this.activity = res.data
this.$set(this, "activity", res.data)
})
},
async getCourseTypeList() {
const resp = await this.$axios.post("/platform/family/type/getAllType")
return resp.data
getCourseTypeList() {
return this.$axios.post("/platform/family/type/getAllType")
.then((resp) => resp.data)
},
doSearch() {
this.$nextTick(() => {
@@ -233,6 +308,15 @@ layout("/layouts/platform_h5.html"){
})
}
},
created() {
// 页面停留到报名开始时间时自动切换按钮,保证预填信息按时冻结。
this.signTimeTimer = window.setInterval(() => {
this.nowTime = Date.now()
}, 1000)
},
beforeDestroy() {
if (this.signTimeTimer) window.clearInterval(this.signTimeTimer)
},
})
</script>
@@ -53,8 +53,8 @@ const selectView = {
</div>
<div class="welfare-card__content">
<div class="info-row">
<div class="info-row__label">联系电话</div>
<div class="info-row__value">{{ mergedSelections[0]?.mobile || '暂无' }}</div>
<div class="info-row__label">福利电话</div>
<div class="info-row__value">{{ mergedSelections[0]?.welfareMobile || '暂无' }}</div>
</div>
<div class="info-row">
<div class="info-row__label">收货地址</div>
@@ -358,8 +358,8 @@ layout("/layouts/platform_h5.html"){
column-gap: 10px;
}
/* 手机号输入样式 */
.mobile-input-section {
/* 福利电话输入样式 */
.welfare-mobile-input-section {
margin-bottom: 20px;
background: #fff;
border-radius: 8px;
@@ -367,17 +367,17 @@ layout("/layouts/platform_h5.html"){
border: 1px solid #ebeef5;
}
.mobile-input-section .van-field {
.welfare-mobile-input-section .van-field {
padding: 12px 16px;
}
.mobile-input-section .van-field__label {
.welfare-mobile-input-section .van-field__label {
width: 70px;
color: var(--text-secondary);
font-weight: 500;
}
.mobile-input-section .van-field__control {
.welfare-mobile-input-section .van-field__control {
color: var(--text-primary);
}
@@ -678,14 +678,14 @@ layout("/layouts/platform_h5.html"){
<div class="confirm-sheet-title">确认选择</div>
<div class="confirm-content-scroll">
<!-- 手机号输入 -->
<div class="mobile-input-section">
<!-- 福利电话输入:统一读写用户资料中的 welfareMobile -->
<div class="welfare-mobile-input-section">
<van-field
v-model="formData.mobile"
label="联系电话"
placeholder="请输入手机号码"
:error="mobileError"
@focus="mobileError = false"
v-model="formData.welfareMobile"
label="福利电话"
placeholder="请输入福利电话"
:error="welfareMobileError"
@focus="welfareMobileError = false"
maxlength="11"
required
></van-field>
@@ -792,10 +792,10 @@ layout("/layouts/platform_h5.html"){
hasSubmittedBefore: false, // 是否之前提交过
showOptionDetailDialog: false, // 选项详情弹窗
selectedOption: null, // 当前选中的选项
mobileError: false, // 手机号错误标记
welfareMobileError: false, // 福利电话错误标记
formData: {
userSign: "", // 用户签名
mobile: "", // 手机号码
welfareMobile: "", // 福利电话
address: "" // 收货地址
},
@@ -884,18 +884,13 @@ layout("/layouts/platform_h5.html"){
this.userSelection = resp.data
this.hasSubmittedBefore = this.userSelection.length > 0
// 如果有用户选择数据,从中获取手机号
if (this.userSelection && this.userSelection.length > 0 && this.userSelection[0].mobile) {
this.formData.mobile = this.userSelection[0].mobile
// 获取签名信息(如果有)
// 历史选择仅负责回显签名,福利电话始终从用户资料获取。
if (this.userSelection && this.userSelection.length > 0) {
if (this.userSelection[0].userSign) {
this.formData.userSign = this.userSelection[0].userSign
this.$set(this.formData, "userSign", this.userSelection[0].userSign)
}
} else if (this.$store.user && this.$store.user.mobile) {
// 如果没有选择过,使用store中的默认手机号
this.formData.mobile = this.$store.user.mobile
}
this.getWelfareMobile()
// 地址回显
if (this.projectInfo.provideMode === 3) {
@@ -930,6 +925,15 @@ layout("/layouts/platform_h5.html"){
})
},
// 获取当前用户福利电话,返回值为字符串或空值。
getWelfareMobile() {
this.$axios.post("/platform/welfare/userSelect/getWelfareMobile", {}).then((res) => {
if (res.code === 0) {
this.$set(this.formData, "welfareMobile", res.data || "")
}
})
},
// 执行提交
doSubmit() {
// 再次检查截止时间
@@ -941,8 +945,8 @@ layout("/layouts/platform_h5.html"){
return
}
// 验证手机号
if (!this.validateMobile()) {
// 验证福利电话
if (!this.validateWelfareMobile()) {
return
}
@@ -975,8 +979,7 @@ layout("/layouts/platform_h5.html"){
selections = [
{
selectOptionId: this.selectedRadioId,
selectNum: 1,
mobile: this.formData.mobile
selectNum: 1
}
]
}
@@ -985,8 +988,7 @@ layout("/layouts/platform_h5.html"){
else {
selections = this.selectedOptions.map((option) => ({
selectOptionId: option.id,
selectNum: option.selectNum,
mobile: this.formData.mobile
selectNum: option.selectNum
}))
}
@@ -1007,12 +1009,10 @@ layout("/layouts/platform_h5.html"){
this.$axios
.post("/platform/welfare/userSelect/confirmSelect", {
projectId: this.projectId,
welfareMobile: this.formData.welfareMobile,
selections: JSON.stringify(selections)
})
.then((res) => {
loading.clear()
this.isSubmitting = false
if (res.code === 0) {
this.$toast.success("选择成功")
setTimeout(() => {
@@ -1023,24 +1023,26 @@ layout("/layouts/platform_h5.html"){
}
})
.catch(() => {
this.$toast.fail("网络错误,请重试")
})
.finally(() => {
loading.clear()
this.isSubmitting = false
this.$toast.fail("网络错误,请重试")
})
},
// 验证手机号
validateMobile() {
if (!this.formData.mobile) {
this.mobileError = true
this.$toast.fail("请输入手机号码")
// 验证福利电话必填且为有效的 11 位手机号码,返回布尔值。
validateWelfareMobile() {
if (!this.formData.welfareMobile) {
this.welfareMobileError = true
this.$toast.fail("请输入福利电话")
return false
}
const mobileReg = /^1[3456789]\d{9}$/
if (!mobileReg.test(this.formData.mobile)) {
this.mobileError = true
this.$toast.fail("请输入正确的手机号码")
if (!mobileReg.test(this.formData.welfareMobile)) {
this.welfareMobileError = true
this.$toast.fail("请输入正确的福利电话")
return false
}