This commit is contained in:
@jyuhsin
2025-09-15 16:14:07 +08:00
parent 204deed9d6
commit 7a062e63ea
63 changed files with 25503 additions and 44428 deletions
@@ -0,0 +1,24 @@
package com.budwk.app.zhgh.activity.family.constant;
import com.budwk.app.base.annotation.DictEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @Author JyuHsin
* @Date 2022/11/18
* @Description
*/
@Getter
@DictEnum(key = "ColumnFormTypeEnum", name = "控件类型")
@AllArgsConstructor
public enum ColumnFormTypeEnum {
INPUT("INPUT", "输入框"),
SELECT("SELECT", "选择框"),
//RADIO("RADIO", "单选框"),//选项数组
FILE("FILE", "文件");
private String code;
private String description;
}
@@ -0,0 +1,429 @@
package com.budwk.app.zhgh.activity.family.controller.manage;
import cn.dev33.satoken.annotation.SaCheckPermission;
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.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;
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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
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 java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "活动报名")
@At("/platform/family/apply")
public class FamilyActivityApplyController {
private final ReentrantLock lock = new ReentrantLock(true);
@Inject
private FamilyActivityService familyActivityService;
@Inject
private SysDictService dictService;
@Inject
private FamilyActivityStatisticsService statisticsService;
@Inject
private Dao dao;
@At("/")
@SaCheckPermission("family.apply")
@Ok("beetl:/platform/zhgh/activity/family/apply/index.html")
public void index() {
}
@At("/h5")
@SaCheckPermission("h5.family.apply")
@Ok("beetl:/platform/zhghh5/activity/family/apply/index.html")
public void h5Index() {
}
@At("/list/h5")
@SaCheckPermission("h5.family.apply")
@Ok("beetl:/platform/zhghh5/activity/family/list/index.html")
public void listIndex() {
}
@At
@ApiOperation("活动查询")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result activityPageData(PageForm pageForm,
@Param(value = "year") Integer year,
@Param(value = "activityType") Integer activityType,
@Param(value = "id") String id,
@Param(value = "dataType") String dataType) {
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "=", id);
cnd.andEX("`year`", "=", year);
//查询报名中
if (activityType == 2) {
cnd.and(new Static("now() > activitySignUpStartTime and now() < activitySignUpEndTime"));
}//查询已结束的
else if (activityType == 3) {
cnd.and(new Static("now() > activityEndTime"));
} else if (activityType == 4) {
cnd.and(new Static("now() < activitySignUpStartTime"));
}
if (AuthUtil.hasRole("H04") && !AuthUtil.hasRoleOr("sysadmin, A06")) {
cnd.and("activityMode", "=", 2).and("createdBy", "=", SecurityUtil.getUserId());
}
if("mine".equals(dataType)) {
cnd.and(new Static("id in (select activityId from train_sign_up_user where userId = '%s')".formatted(SecurityUtil.getUserId())));
}
Pagination pagination = familyActivityService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
List<FamilyActivity> familyActivities = pagination.getList();
Map<String, String> familyTypeMap = dictService.getSubListByCode("FAMILY_SIGNUP_TYPE").stream().collect(Collectors.toMap(Sys_dict::getCode, Sys_dict::getName));
familyActivities.forEach(v -> v.setTrainType(familyTypeMap.get(v.getTrainType())));
return Result.success(pagination);
}
@At
@ApiOperation("分活动查询")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm,
@Param(value = "courseTypeId") String courseTypeId,
@Param(value = "activityId") String activityId,
@Param(value = "assortTypes") String[] assortTypes,
@Param(value = "dataType") String dataType) {
Sql sql = Sqls.create("""
SELECT
tsuc.id,
tsuc.activityId,
tsuc.courseName,
tsuc.coursePeopleNumber,
tsuc.courseLocation,
tsuc.courseType,
tsuc.courseLocationCoordinates,
tsuc.courseReservedNumber,
tsuc.courseInstructor,
tsuc.campus,
tsuc.unionLimit,
tsuc.isMobileSign,
tsuc.signType,
tsuc.isReceiveGift,
tsuc.giftType,
tsuc.reserveMode,
tsuc.waitingNum,
tsuc.assort,
tsuc.introduce,
type.typeName,
tsuc.courseIsLimitApply
FROM
`family_course` tsuc
LEFT JOIN family_type type ON type.id = tsuc.courseType
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("type.id", "=", courseTypeId);
cnd.and("tsuc.activityId", "=", activityId);
if(Lang.isNotEmpty(assortTypes)) {
cnd.and("tsuc.assort", "in", assortTypes);
}
if("mine".equals(dataType)) {
cnd.and(new Static("tsuc.id in (select courseId from train_sign_up_user where userId = '%s')".formatted(SecurityUtil.getUserId())));
}
List<FamilyCourse> courseArray = familyActivityService.dao().query(FamilyCourse.class, Cnd.where("activityId", "=", activityId));
courseArray = familyActivityService.filterCourseByHostUnion(courseArray);
cnd.and("tsuc.id", "in", courseArray.stream().map(FamilyCourse::getId).toList());
cnd.asc("tsuc.orderNum");
cnd.asc("type.code");
sql.setCondition(cnd);
Pagination pagination = familyActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> courseList = pagination.getList();
courseList.forEach(c -> {
List<FamilyActivityCourse> courseTimes = dao.query(FamilyActivityCourse.class, Cnd.where("courseId", "=", c.getString("id")).asc("courseDate"));
c.put("courseTimes", courseTimes);
c.put("hasRegisterNum", statisticsService.queryCourseCount(c.getString("id"), c.getString("courseType")));
c.put("hasWaitingNum", statisticsService.queryCourseWaitCount(c.getString("id"), c.getString("courseType")));
//当前用户是否报过
c.put("isSign", familyActivityService.isSignCourseByUser(c.getString("id"), SecurityUtil.getUserId()));
if(StrUtil.isNotBlank(c.getString("unionLimit"))) {
List<NutMap> unionLimit = Json.fromJsonAsList(NutMap.class, c.getString("unionLimit"));
if(Lang.isNotEmpty(unionLimit)) {
NutMap nutMap = unionLimit.stream().filter(o -> o.getString("id").equals(SecurityUtil.getUnionId())).findFirst().orElse(null);
if(nutMap != null) {
c.put("coursePeopleNumber", nutMap.getInt("limitCount"));
}
}
}
});
return Result.success(pagination);
}
@At
@ApiOperation("获取分活动时间")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result getCourseTime(String id) {
List<FamilyActivityCourse> list = familyActivityService.dao().query(FamilyActivityCourse.class, Cnd.where("courseId", "=", id));
return Result.success(list);
}
@At
@ApiOperation("查询分类标识集合")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result queryCourseAssort(String activityId) {
List<FamilyCourse> courseList = familyActivityService.dao().query(
FamilyCourse.class,
Cnd.where(FamilyCourse::getActivityId, "=", activityId).asc(FamilyCourse::getOrderNum)
);
if(Lang.isEmpty(courseList)) {
return Result.success(new ArrayList<>());
}
List<String> assortList = courseList.stream().map(FamilyCourse::getAssort).filter(StrUtil::isNotBlank).toList();
return Result.success(assortList);
}
@At
@ApiOperation("验证是否能报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Result validateSignUp(String courseId,
@Param(value = "currentFamilyNumber") Integer currentFamilyNumber) {
try {
lock.lock();
if(StrUtil.isBlank(courseId)) {
return Result.error("报名信息为空");
}
currentFamilyNumber = currentFamilyNumber != null ? currentFamilyNumber : 0;
FamilyCourse course = dao.fetch(FamilyCourse.class, courseId);
FamilyActivity activity = dao.fetch(FamilyActivity.class, course.getActivityId());
//判断时间
if(DateUtil.compare(new Date(), activity.getActivitySignUpStartTime(), "yyyy-MM-dd HH:mm:ss") < 0) {
return Result.error("报名未开始");
}
if(DateUtil.compare(new Date(), activity.getActivitySignUpEndTime(), "yyyy-MM-dd HH:mm:ss") > 0) {
return Result.error("报名已结束");
}
//判断活动组别
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getActivityGroupId()).and("userId", "=", SecurityUtil.getUserId()));
if(count == 0) {
return Result.error("抱歉,您没有此次活动的权限");
}
//判断是否报名
boolean courseByUser = familyActivityService.isSignCourseByUser(courseId, SecurityUtil.getUserId());
if(courseByUser) {
return Result.error("抱歉,您已经报名");
}
//判断活动人数
boolean signFull = familyActivityService.isSignFull(course, currentFamilyNumber);
if(signFull) {
return Result.error("名额剩余数量不足");
}
//判断活动限制
boolean signCourse = familyActivityService.isSignCourse(course, activity);
if(!signCourse) {
if(activity.getRestrictLimit() != 3) {
return Result.error("您选择的类型已达上限,不能再报该类型的了");
} else {
return Result.error(activity.getActivityName() + "限制报" + activity.getLimitNum() + "个活动,已达上限");
}
}
//判断分工会人数限制
boolean signFullByUnionId = familyActivityService.isSignFullByUnionId(course, currentFamilyNumber);
if(signFullByUnionId) {
return Result.error("您所在的分工会名额不足");
}
return Result.success();
}catch (Exception e) {
e.printStackTrace();
return Result.error("报名失败");
} finally {
lock.unlock();
}
}
@At
@ApiOperation("查询子活动时间段")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Object getCourseTimeSelectList(String courseId) {
// 查课程的时间段
List<FamilyActivityCourse> courseList = dao.query(FamilyActivityCourse.class, Cnd.where("courseId", "=", courseId).asc("courseStartTime"));
// 查课程的报名人数
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<String> idList = userList.stream().map(FamilyUser::getUserId).toList();
applyUserList = applyUserList.stream().filter(o -> idList.contains(o.getUserId())).toList();
// 按照课程下面时间段去分组
Map<String, List<FamilyUserCourse>> collectMap = applyUserList.stream().collect(Collectors.groupingBy(FamilyUserCourse::getActivityCourseId));
List<NutMap> list = courseList.stream().map(v -> {
String id = v.getId();
NutMap nutMap = NutMap.NEW();
List<FamilyUserCourse> familyUserCourses = collectMap.get(id);
int remainingNum = v.getCourseLimitNum() != null ? v.getCourseLimitNum() : 0;
if (Lang.isNotEmpty(familyUserCourses)) {
remainingNum = v.getCourseLimitNum() - familyUserCourses.size();
}
nutMap.put("remainingNum", remainingNum);
nutMap.put("text", DateUtil.format(v.getCourseStartTime(), "HH:mm") + "" + DateUtil.format(v.getCourseEndTime(), "HH:mm") + "段(剩" + remainingNum + ")");
nutMap.put("value", id);
nutMap.put("disabled", remainingNum == 0);
return nutMap;
}).filter(v -> v.getInt("remainingNum") != 0).toList();
return Result.success(list);
}
@At
@ApiOperation("验证子活动是否能报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
public Object validateSourceSignUp(String activityCourseId, String courseId) {
try {
lock.lock();
List<FamilyUser> userList = dao.query(FamilyUser.class, Cnd.where("courseId", "=", courseId).and("state", "=", 1));
List<String> isList = userList.stream().map(FamilyUser::getUserId).toList();
// 该时间段下已报名的人数
int count = dao.count(FamilyUserCourse.class, Cnd.where("activityCourseId", "=", activityCourseId)
.and("courseId", "=", courseId).and("userId", "in", isList));
// 获取改时间段下的活动课程限制报名人数
FamilyActivityCourse course = dao.fetch(FamilyActivityCourse.class, activityCourseId);
Integer courseLimitNum = course.getCourseLimitNum();
// 报名加上自己,如果大于了限制人数,那就无法报名
if (count + 1 > courseLimitNum) {
return Result.error("该时间段名额已报满,请选择其他时段报名");
} else {
return Result.success();
}
} catch (Exception e) {
e.printStackTrace();
return Result.error("报名失败");
} finally {
lock.unlock();
}
}
@At
@ApiOperation("活动报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "活动报名")
public Result doSignUp(FamilyUser familyUser) {
try {
lock.lock();
boolean courseByUser = familyActivityService.isSignCourseByUser(familyUser.getCourseId(), SecurityUtil.getUserId());
if(courseByUser) {
return Result.error("您已报过该活动");
}
//判断人数
int number = 0;
FamilyCourse course = familyActivityService.dao().fetch(FamilyCourse.class, familyUser.getCourseId());
FamilyType type = familyActivityService.dao().fetch(FamilyType.class, course.getCourseType());
if(type != null) {
if(type.getIsBringFamily() && type.getIsAddFamily()) {
List<List<NutMap>> mobileColumnsValue = familyUser.getMobileColumnsValue();
number = Lang.isNotEmpty(mobileColumnsValue) ? mobileColumnsValue.size() : 0;
}
}
boolean signFull = familyActivityService.isSignFull(course, number);
if(signFull) {
return Result.error("当前报名人数已满");
}
boolean signFullByUnionId = familyActivityService.isSignFullByUnionId(course, number);
if(signFullByUnionId) {
return Result.error("该活动您所在的分工会名额不足");
}
familyActivityService.doSignUp(familyUser);
return Result.success();
} catch (Exception e) {
e.printStackTrace();
return Result.success("报名失败");
} finally {
lock.unlock();
}
}
@At
@ApiOperation("取消报名")
@SaCheckPermission(value = {"family.apply", "h5.family.apply"}, mode = SaMode.OR)
@SLog(tag = "亲子活动-活动报名", msg = "取消报名")
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())));
}
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());
//msgApi.sendTextMsg("【" + activity.getActivityName() + "】已候补成功,请按时参加活动!", user.getLoginname());
}
}
//删除报名记录
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();
}
}
@@ -0,0 +1,201 @@
package com.budwk.app.zhgh.activity.family.controller.manage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.zhgh.activity.family.models.*;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
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.trans.Trans;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author zxy
* @Description 培训报名 活动管理
* @createTime 2022年02月23日 09:57:00
*/
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "活动管理")
@At("/platform/family/manage")
public class FamilyActivityController {
@Inject
private FamilyActivityService familyActivityManageService;
@Inject
private Dao dao;
@At("")
@SaCheckPermission("family.manage")
@Ok("beetl:/platform/zhgh/activity/family/manage/index.html")
public void index() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission("family.manage")
public Result pageData(PageForm pageForm,
@Param(value = "year") Integer year,
@Param(value = "activityName") String activityName) {
Cnd cnd = Cnd.NEW();
cnd.andEX("year", "=", year);
cnd.and(Cnd.likeEX("activityName", activityName));
cnd.orderBy("createdAt", "desc");
return Result.success().addData(familyActivityManageService.pageData(pageForm, cnd));
}
@At
@ApiOperation("活动删除")
@SaCheckPermission("family.manage")
@SLog(tag = "亲子活动-活动管理", msg = "删除活动")
public Result onDelete(String id) {
Trans.exec(() -> {
familyActivityManageService.delete(id);
dao.clear(FamilyCourse.class, Cnd.where("activityId", "=", id));
dao.clear(FamilyActivityCourse.class, Cnd.where("activityId", "=", id));
dao.clear(FamilyUser.class, Cnd.where("activityId", "=", id));
dao.clear(FamilyUserCourse.class, Cnd.where("activityId", "=", id));
dao.clear(FamilyActivity.class, Cnd.where("id", "=", id));
dao.clear(FamilyTypeLimit.class, Cnd.where("activityId", "=", id));
dao.delete(Sys_home_activity.class, id);
});
return Result.success();
}
@At
@ApiOperation("活动状态变更")
@SaCheckPermission("family.manage")
public Result activityStatusChange(FamilyActivity activity) {
familyActivityManageService.updateActivityStatus(activity);
dao.update(Sys_home_activity.class,
Chain.make("enable", !activity.isDisabled()),
Cnd.where("id", "=", activity.getId()));
return Result.success();
}
@At
@ApiOperation("查询单个活动")
@SaCheckPermission("family.manage")
public Result findOne(@Param("id") @NotNull String id) {
NutMap dataMap = familyActivityManageService.findOne(id, null, "");
String activityStartTime = dataMap.getString("activityStartTime");
String activityEndTime = dataMap.getString("activityEndTime");
if(StrUtil.isNotBlank(activityStartTime) && StrUtil.isNotBlank(activityEndTime)) {
dataMap.put("activityTime", List.of(activityStartTime, activityEndTime));
} else {
dataMap.put("activityTime", new ArrayList<>());
}
String activitySignUpStartTime = dataMap.getString("activitySignUpStartTime");
String activitySignUpEndTime = dataMap.getString("activitySignUpEndTime");
if(StrUtil.isNotBlank(activitySignUpStartTime) && StrUtil.isNotBlank(activitySignUpEndTime)) {
dataMap.put("activitySignTime", List.of(activitySignUpStartTime, activitySignUpEndTime));
} else {
dataMap.put("activitySignTime", new ArrayList<>());
}
List<NutMap> courseList = dataMap.getList("courseList", NutMap.class);
//查询所有的课程类型
List<FamilyType> familyTypeList = dao.query(FamilyType.class, Cnd.NEW());
Map<String, String> typeMap = familyTypeList.stream().collect(Collectors.toMap(FamilyType::getId, FamilyType::getTypeName));
courseList.forEach(v -> {
List<NutMap> courseTimeList = v.getList("courseTimeList", NutMap.class);
//选择课程日期 下拉框
List<String> setUpCourseData = courseTimeList.stream().map(cd -> cd.getString("courseDate")).distinct().collect(Collectors.toList());
v.put("setUpCourseData", setUpCourseData);
courseTimeList.forEach(ct -> {
String courseStartTime = DateUtil.format(ct.getTime("courseStartTime"), "HH:mm");
String courseEndTime = DateUtil.format(ct.getTime("courseEndTime"), "HH:mm");
ct.put("courseStartTime", courseStartTime);
ct.put("courseEndTime", courseEndTime);
});
v.put("courseTypeName", typeMap.get(v.getString("courseType")));
});
return Result.success().addData(dataMap);
}
@At
@Ok("json:full")
@ApiOperation("亲子活动新增/修改")
@SaCheckPermission("family.manage")
@SLog(tag = "亲子活动-活动管理", msg = "新增/修改活动")
public Result doHandle(FamilyActivity activity) {
if (StrUtil.isBlank(activity.getId())) {
familyActivityManageService.add(activity, null);
} else {
familyActivityManageService.edit(activity);
}
return Result.success();
}
@At
@Ok("json:full")
@ApiOperation("获取分工会人数限制")
@SaCheckPermission("family.manage")
public Result getUnionLimit(@Param(value = "activityScopeId") String activityScopeId) {
Sql sql = Sqls.create("""
SELECT
gh.id,
gh.name,
gh.unioncode,
(select count(1) from `vw_user` where unionid = gh.id $cnd) as teacherCount,
NULL as ratio,
NULL as limitCount
FROM
sys_union gh
order by gh.unioncode
""");
if (StrUtil.isNotBlank(activityScopeId)) {
sql.setVar("cnd", "AND id in (select userId from activity_user_scope where groupId = '" + activityScopeId + "')");
}
List<NutMap> list = familyActivityManageService.listMap(sql);
return Result.success().addData(list);
}
@At
@Ok("json:full")
@ApiOperation("获取报名人员数量")
@SaCheckPermission("family.manage")
public Result getRegisterUserCount(@Param(value = "courseId") String courseId) {
return Result.success().addData(dao.count(FamilyUser.class, Cnd.where("courseId", "=", courseId)));
}
@At
@Ok("json:full")
@ApiOperation("获取历史活动列表")
@SaCheckPermission("family.manage")
public Result getHistoricalActList() {
List<FamilyActivity> query = dao.query(FamilyActivity.class, Cnd.NEW().desc("activityStartTime"));
return Result.success().addData(query);
}
}
@@ -0,0 +1,176 @@
package com.budwk.app.zhgh.activity.family.controller.manage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
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 java.util.ArrayList;
import java.util.List;
/**
* @Author JyuHsin
* @Date 2022/9/21
* @Description 人员调整
*/
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "亲子活动人员调整")
@At("/platform/family/userAdjust")
public class FamilyAdjustController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@Inject
private FamilyActivityService familyActivityManageService;
@Inject
private FamilyActivityStatisticsService familyActivityStatisticsService;
@At("")
@SaCheckPermission("family.adjust")
@Ok("beetl:/platform/zhgh/activity/family/userAdjust/index.html")
public void index() {
}
/**
* 活动list
* @param year 年度
* @return
*/
@At
@ApiOperation("活动列表")
@SaCheckPermission("family.adjust")
public Result activityList(@Param(value = "year") Integer year) {
List<FamilyActivity> activityList = dao.query(FamilyActivity.class, Cnd.NEW().andEX("year", "=", year).andEX("isDisabled", "=", false).desc("activityStartTime"));
return Result.success(activityList);
}
@At
@ApiOperation("分页查询")
@SaCheckPermission("family.adjust")
public Result pageData(PageForm pageForm,
@Param(value = "activityId") String activityId) {
Pagination pagination = familyActivityStatisticsService.pageData(pageForm, activityId);
return Result.success(pagination);
}
@At
@ApiOperation("子活动查询")
@SaCheckPermission("family.adjust")
public Result getCourse(String activityId) {
Sql sql = Sqls.create("""
SELECT
tsuc.id,
tsuc.courseName,
tsuc.coursePeopleNumber,
tsuc.courseType,
tsuc.courseLocation,
tsuc.courseInstructor,
tsuc.courseReservedNumber,
tsuc.waitingNum,
tsuc.reserveMode
FROM
`family_course` tsuc
WHERE
tsuc.activityId = @activityId
ORDER BY courseName asc
""");
sql.setParam("activityId", activityId);
List<NutMap> courseList = baseService.listMap(sql);
courseList.forEach(c -> {
c.put("registerNum", familyActivityStatisticsService.queryCourseCount(c.getString("id"), c.getString("courseType")));
c.put("hasWaitingNum", familyActivityStatisticsService.queryCourseWaitCount(c.getString("id"), c.getString("courseType")));
});
return Result.success(courseList);
}
@At
@ApiOperation("报名用户列表")
@SaCheckPermission("family.adjust")
public Result registerUserList(@Param("courseId") String courseId,
@Param(value = "unionId") String unionId,
@Param(value = "unitId") String unitId,
@Param(value = "searchName") String searchName,
@Param(value = "searchKeyword") String searchKeyword) {
List<NutMap> list = familyActivityStatisticsService.registerUserList(courseId, unionId, unitId, searchName, searchKeyword);
return Result.success(list);
}
@At
@ApiOperation("人员调整")
@SaCheckPermission("family.adjust")
@SLog(tag = "亲子活动-人员调整", msg = "人员调整")
public Result adjust(String activityId, String oldCourseId, String newCourseId, String userId) {
Cnd oldCnd = Cnd.where("activityId", "=", activityId)
.and("courseId", "=", oldCourseId).and("userId", "=", userId);
Cnd newCnd = Cnd.where("activityId", "=", activityId)
.and("courseId", "=", newCourseId).and("userId", "=", userId);
//旧的报名信息
FamilyUser oldfamilyUser = dao.fetch(FamilyUser.class, oldCnd);
oldfamilyUser.setCourseId(newCourseId);
oldfamilyUser.setSignUpTime(DateUtil.date());
dao.update(oldfamilyUser);
//新的课程的信息,上课时间
List<FamilyActivityCourse> activityCourseList = dao.query(FamilyActivityCourse.class, Cnd.where("activityId", "=", activityId)
.and("courseId", "=", newCourseId));
//先清楚旧的信息
dao.clear(FamilyUserCourse.class, oldCnd);
//添加新的信息
List<FamilyUserCourse> familyUserCourseList = new ArrayList<>();
activityCourseList.forEach(item -> {
FamilyUserCourse course = new FamilyUserCourse();
course.setActivityCourseId(activityId);
course.setCourseId(newCourseId);
course.setUserId(userId);
course.setCourseStartTime(item.getCourseStartTime());
course.setCourseEndTime(item.getCourseEndTime());
course.setActivityCourseId(item.getId());
familyUserCourseList.add(course);
});
dao.insert(familyUserCourseList);
return Result.success();
}
@At
@ApiOperation("删除报名人员")
@SaCheckPermission("family.adjust")
@SLog(tag = "亲子活动-人员调整", msg = "删除报名人员")
public Result deleteSignUser(String activityId, String courseId, String userId) {
//删除
Cnd cnd = Cnd.NEW();
cnd.and("activityId", "=", activityId);
cnd.and("courseId", "=", courseId);
cnd.and("userId", "=", userId);
dao.clear(FamilyUser.class, cnd);
dao.clear(FamilyUserCourse.class, cnd);
return Result.success();
}
}
@@ -0,0 +1,141 @@
package com.budwk.app.zhgh.activity.family.controller.manage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.family.models.FamilyUserCourse;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityService;
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.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.Date;
import java.util.List;
/**
* @ClassName FamilyMineController
* @Author JyuHsin
* @Date 2025/9/13 16:56
* @Version 1.0
* @Description TODO
*/
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "活动报名")
@At("/platform/family/mine")
public class FamilyMineController {
@Inject
private Dao dao;
@Inject
private FamilyActivityService activityService;
@At("/")
@SaCheckPermission("family.mine")
@Ok("beetl:/platform/zhgh/activity/family/mine/index.html")
public void index() {
}
@At("/h5")
@SaCheckPermission("h5.family.mine")
@Ok("beetl:/platform/zhghh5/activity/family/mine/index.html")
public void h5Index() {
}
@At
@ApiOperation("主动扫码签到")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"family.mine", "h5.family.mine"}, mode = SaMode.OR)
@SLog(tag = "品牌活动-活动签到", msg = "主动扫码签到")
public Result drivingScan(String courseId) {
// 主动扫码签到是用户自己打开扫一扫,扫二维码签到
int count = dao.count(FamilyUserCourse.class, Cnd.where("courseId", "=", courseId).and("userId", "=", SecurityUtil.getUserId()));
if (count == 0) {
return Result.error(99, "未查询到您的报名记录");
}
// 获取现在的日期,并往后推1个小时
String oneHourLater = DateUtil.offsetHour(new Date(), 1).toString("yyyy-MM-dd HH:mm:ss");
FamilyUserCourse userCourse = dao.fetch(
FamilyUserCourse.class,
Cnd.where("courseId", "=", courseId)
.and("userId", "=", SecurityUtil.getUserId())
.and("courseStartTime", "<=", oneHourLater)
.and("courseEndTime", ">=", oneHourLater)
);
if (userCourse == null) {
return Result.error(99, "未到签到时间");
}
userCourse.setAttend(true);
userCourse.setAttendTime(DateUtil.date());
dao.update(userCourse);
return Result.success();
}
@At
@ApiOperation("被动扫码签到")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"family.mine", "h5.family.mine"}, mode = SaMode.OR)
@SLog(tag = "品牌活动-活动签到", msg = "被动扫码签到")
public Result passiveScan(String id) {
// id表示课程的某个时间段,userId表示是谁出示的二维码
FamilyUserCourse userCourse = dao.fetch(FamilyUserCourse.class, id);
if (userCourse == null) {
return Result.error("未查询到报名记录");
}
int isAfter = DateUtil.compare(new Date(), userCourse.getCourseStartTime());
if (isAfter > 0) {
return Result.error("抱歉,已经开始,无法签到");
}
long diffMillis = Math.abs(DateUtil.between(new Date(), userCourse.getCourseStartTime(), DateUnit.MS));
long oneHourInMs = 3600 * 1000;
if (diffMillis > oneHourInMs) {
return Result.error("签到时间为开始前1个小时");
}
userCourse.setAttend(true);
userCourse.setAttendTime(DateUtil.date());
dao.update(userCourse);
return Result.success();
}
@At
@ApiOperation("分活动查询")
@SaCheckPermission(value = {"family.mine", "h5.family.mine"}, mode = SaMode.OR)
public Result queryCourseSign(String courseId) {
Sql sql = Sqls.create("""
SELECT
uc.*,
date(uc.courseStartTime) as courseDate
FROM
`family_user_course` uc
WHERE
courseId = @courseId and userId = @userId
""");
sql.setParam("userId", SecurityUtil.getUserId());
sql.setParam("courseId", courseId);
List<NutMap> listMap = activityService.listMap(sql);
return Result.success(listMap);
}
}
@@ -0,0 +1,167 @@
package com.budwk.app.zhgh.activity.family.controller.manage;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.EnumUtil;
import com.budwk.app.base.annotation.SLog;
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.base.service.BaseService;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.zhgh.activity.family.models.FamilyMobileSignColumn;
import com.budwk.app.zhgh.activity.family.models.FamilyType;
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.dao.Sqls;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
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 java.util.List;
/**
* @Author JyuHsin
* @Date 2022/9/22
* @Description
*/
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "活动类型管理")
@At("/platform/family/type")
public class FamilyTypeController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("")
@SaCheckPermission("family.type")
@Ok("beetl:/platform/zhgh/activity/family/type/index.html")
public void index() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission("family.type")
public Result pageData(PageForm pageForm,
@Param(value = "typeName") String typeName) {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
select * from family_type $condition
""");
if (Strings.isNotBlank(typeName)) {
cnd.and("typeName", "like", "%" + typeName + "%");
}
if(Strings.isNotBlank(pageForm.getPageOrderName()) && Strings.isNotBlank(pageForm.getPageOrderBy())){
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.asc("xh");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> list = pagination.getList();
list.forEach(item -> {
Cnd c = Cnd.NEW();
c.and("typeId", "=", item.getString("id"));
c.asc("columnIndex");
List<FamilyMobileSignColumn> signColumns = dao.query(FamilyMobileSignColumn.class, c);
item.put("familyMobileSignColumnList", signColumns);
});
return Result.success().addData(pagination);
}
@At
@ApiOperation("活动类型新增")
@SaCheckPermission("family.type")
@SLog(tag = "亲子活动-活动类型管理", msg = "活动类型新增")
public Result doAdd(@Param("data") String data) throws Exception {
FamilyType type = Json.fromJson(FamilyType.class, data);
int count = dao.count(FamilyType.class, Cnd.where("code", "=", type.getCode()));
if (count > 0) {
return Result.error("编码重复!");
}
int totalCount = dao.count(FamilyType.class);
type.setXh(totalCount + 1);
dao.insertWith(type, "familyMobileSignColumnList");
return Result.success();
}
@At
@ApiOperation("活动类型修改")
@SaCheckPermission("family.type")
@SLog(tag = "亲子活动-活动类型管理", msg = "活动类型修改")
public Result doEdit(FamilyType type) {
int count = dao.count(FamilyType.class, Cnd.where("code", "=", type.getCode()).and("id", "!=", type.getId()));
if (count > 0) {
return Result.error("编码重复!");
}
dao.update(type);
dao.clear(FamilyMobileSignColumn.class, Cnd.where("typeId", "=", type.getId()));
dao.insertLinks(type, "familyMobileSignColumnList");
return Result.success();
}
@At
@ApiOperation("活动类型删除")
@SaCheckPermission("family.type")
@SLog(tag = "亲子活动-活动类型管理", msg = "活动类型删除")
public Object doDelete(@Param(value = "id") String id) {
dao.clear(FamilyType.class, Cnd.where("id", "=", id));
dao.clear(FamilyMobileSignColumn.class, Cnd.where("typeId", "=", id));
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("排序号变更")
@SaCheckPermission("family.type")
public Object xhChange(String id, Integer xh, boolean toDown) {
if (toDown) {
FamilyType next = dao.fetch(FamilyType.class, Cnd.where("xh", "=", xh + 1));
next.setXh(next.getXh() - 1);
dao.update(next);
dao.update(FamilyType.class, Chain.make("xh", xh + 1), Cnd.where("id", "=", id));
} else {
FamilyType pre = dao.fetch(FamilyType.class, Cnd.where("xh", "=", xh - 1));
pre.setXh(pre.getXh() + 1);
dao.update(pre);
dao.update(FamilyType.class, Chain.make("xh", xh - 1), Cnd.where("id", "=", id));
}
return Result.success();
}
@At
@ApiOperation("获取所有类型")
@SaCheckLogin
public Result getAllType(@Param(value = "id") String id) {
List<FamilyType> familyTypeList = dao.query(FamilyType.class, Cnd.NEW().andEX("id", "=", id).asc("xh"));
dao.fetchLinks(familyTypeList, "familyMobileSignColumnList", Cnd.NEW().asc("columnIndex"));
return Result.success().addData(familyTypeList);
}
@At
@ApiOperation("自定义表单字段类型")
@SaCheckPermission("family.type")
public Result getColumnType() {
List<String> names = EnumUtil.getNames(ColType.class);
names.add("JSON");
return Result.success(names);
}
}
@@ -0,0 +1,349 @@
package com.budwk.app.zhgh.activity.family.controller.manage;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.afterturn.easypoi.excel.export.ExcelExportService;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
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.base.utils.PageUtil;
import com.budwk.app.zhgh.activity.family.models.FamilyActivity;
import com.budwk.app.zhgh.activity.family.models.FamilyCourse;
import com.budwk.app.zhgh.activity.family.models.FamilyUser;
import com.budwk.app.zhgh.activity.family.service.FamilyBlackListService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.dao.Chain;
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.Strings;
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 javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author zxy
* @Description 人员管理
* @createTime 2022年03月07日 14:27:00
*/
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "亲子活动人员黑名单")
@At("/platform/family/userManage")
public class FamilyUserManageController {
@Inject
private Dao dao;
@Inject
private FamilyBlackListService familyBlackListService;
@At("")
@SaCheckPermission("family.userManage")
@Ok("beetl:/platform/zhgh/activity/family/userManage/index.html")
public void index() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission("family.userManage")
public Result pageData(PageForm pageForm,
@Param(value = "activityId") String activityId,
@Param(value = "courseId") String courseId,
@Param(value = "year") Integer year,
@Param(value = "userKeyWord") String userKeyWord) {
Cnd cnd = Cnd.NEW();
cnd.andEX("tsuu.activityId", "=", activityId);
cnd.andEX("tsuu.courseId", "=", courseId);
cnd.andEX("act.year", "=", year);
if (StrUtil.isNotBlank(userKeyWord)) {
SqlExpressionGroup seg = new SqlExpressionGroup();
cnd.and(seg.andLike("u.username", userKeyWord).orLike("u.loginname", userKeyWord));
}
if(Strings.isNotBlank(pageForm.getPageOrderName()) && Strings.isNotBlank(pageForm.getPageOrderBy())){
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
Pagination pagination = familyBlackListService.pageData(pageForm, cnd, activityId, courseId);
return Result.success(pagination);
}
@At
@ApiOperation("报名人员处理")
@SaCheckPermission("family.userManage")
@SLog(tag = "亲子活动-人员调整", msg = "报名人员处理")
public Result doHandleUser(@Param("userId") String userId) {
familyBlackListService.doHandleUser(userId);
return Result.success();
}
@At
@ApiOperation("根据活动Id获取子活动")
@SaCheckPermission("family.userManage")
public Result getCourseByActivityId(@Param("activityId") String activityId) {
List<FamilyCourse> list = dao.query(FamilyCourse.class, Cnd.where("activityId", "=", activityId));
return Result.success(list);
}
@At
@ApiOperation("获取子活动具体时间")
@SaCheckPermission("family.userManage")
public Result attendClassRecord(String userId) {
familyBlackListService.attendClassRecord(userId);
return Result.success();
}
@At
@ApiOperation("获取候补人员")
@SaCheckPermission("family.userManage")
public Result getReserveUser(String courseId) {
Sql sql = Sqls.create("""
SELECT
uc.* ,
(select signUpTime from family_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as signUpTime,
(select state from family_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as state,
u.username,
u.loginname,
u.mobile,
u.unitName,
u.unionName
FROM
family_user_course uc LEFT JOIN `vw_user` u on uc.userId = u.id
WHERE uc.courseId = @courseId and uc.isAttend = false HAVING state = 2 order by signUpTime desc
""").setParam("courseId", courseId);
return Result.success(familyBlackListService.listMap(sql));
}
@At
@ApiOperation("补充人员")
@SaCheckPermission("family.userManage")
@SLog(tag = "亲子活动-人员管理", msg = "补充人员")
public Result reserveSingUp(String[] ids, String courseId) {
//先查询这个课程有多少个未签到的人员
Sql sql = Sqls.create("""
SELECT
uc.* ,
(select signUpTime from family_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as signUpTime,
(select state from family_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as state
FROM
family_user_course uc
WHERE uc.courseId = @courseId and uc.isAttend = false HAVING state = 1 order by signUpTime desc
""").setParam("courseId", courseId);
List<NutMap> list = familyBlackListService.listMap(sql);
if(ids.length > list.size()) {
return Result.error("您选择了" + ids.length + "位,未签到人员只有" + list.size() + "");
}
//ids的长度为几,就搞几个
List<NutMap> mapList = list.subList(0, ids.length);
List<String> idList = mapList.stream().map(o -> o.getString("userId")).collect(Collectors.toList());
//将这几个没签到的设置为4
dao.update(FamilyUser.class, Chain.make("state", 4), Cnd.where("courseId", "=", courseId)
.and("userId", "in", idList));
//将补充的设置为1
dao.update(FamilyUser.class, Chain.make("state", 1), Cnd.where("courseId", "=", courseId)
.and("userId", "in", ids));
return Result.success();
}
@At
@Ok("void")
@ApiOperation("导出签到人员")
public void exportSignPerson(@Param(value = "activityId") String activityId,
HttpServletResponse response) {
FamilyActivity activity = dao.fetch(FamilyActivity.class, activityId);
Sql sql = Sqls.create("""
SELECT
uc.*,
DATE_FORMAT(uc.courseStartTime, '%Y-%m-%d %H:%i:%s') as courseStartTimeExcel,
DATE_FORMAT(uc.courseEndTime, '%Y-%m-%d %H:%i:%s') as courseEndTimeExcel,
DATE_FORMAT(uc.attendTime, '%Y-%m-%d %H:%i:%s') as attendTimeExcel,
u.username,
u.loginname,
u.unitName,
u.unionName,
u.sex
FROM
family_user_course uc
left join family_course course on uc.courseId = course.id
left join `vw_user` u on u.id = uc.userId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("uc.activityId", "=", activityId);
cnd.and("course.isMobileSign", "=", true);
cnd.desc("isAttend").desc("attendTime");
sql.setCondition(cnd);
List<NutMap> userList = familyBlackListService.listMap(sql);
List<FamilyCourse> courseList = dao.query(FamilyCourse.class, Cnd.where("activityId", "=", activityId));
List<Map<String, Object>> sheetsList = new ArrayList<>();
List<ExcelExportEntity> excelCommonExportEntity = new ArrayList<>();
excelCommonExportEntity.add(new ExcelExportEntity("姓名", "username", 20));
excelCommonExportEntity.add(new ExcelExportEntity("工号", "loginname", 20));
excelCommonExportEntity.add(new ExcelExportEntity("单位", "unitName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("分工会", "unionName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("性别", "sex", 20));
excelCommonExportEntity.add(new ExcelExportEntity("开始时间", "courseStartTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("结束时间", "courseEndTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("是否签到", "isAttend", 20));
excelCommonExportEntity.add(new ExcelExportEntity("签到时间", "attendTimeExcel", 22));
for (FamilyCourse c : courseList) {
String courseId = c.getId();
String courseName = c.getCourseName();
List<NutMap> v = userList.stream().filter(x -> x.getString("courseId").equals(courseId)).collect(Collectors.toList());
ExportParams userExportParams = new ExportParams();
userExportParams.setSheetName(courseName);
List<ExcelExportEntity> currentEntities = new ArrayList<>(excelCommonExportEntity);
for (NutMap userSignData : v) {
if(!userSignData.getBoolean("isAttend")) {
userSignData.put("isAttend", "未签到");
userSignData.put("attendTimeExcel", "未签到");
}else {
userSignData.put("isAttend", "已签到");
}
}
Map<String, Object> userExportMap = new HashMap<>();
userExportMap.put("name", courseName);
userExportMap.put("title", userExportParams);
userExportMap.put("entity", currentEntities);
userExportMap.put("data", v);
sheetsList.add(userExportMap);
}
try {
String fileName = activity.getActivityName() + "签到人员名单.xls";
String disposition = "attachment;filename=" + URLEncoder.encode(fileName, "utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", disposition);
Workbook workbook = new HSSFWorkbook();
for (Map<String, Object> map : sheetsList) {
ExcelExportService service = new ExcelExportService();
service.createSheetForMap(workbook,(ExportParams) map.get("title"),(List<ExcelExportEntity>) map.get("entity"),(Collection<?>) map.get("data"));
}
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@At
@Ok("void")
@ApiOperation("导出领取人员")
public void exportGiftPerson(@Param(value = "activityId") String activityId,
HttpServletResponse response) {
FamilyActivity activity = dao.fetch(FamilyActivity.class, activityId);
Sql sql = Sqls.create("""
SELECT
uc.*,
DATE_FORMAT(uc.courseStartTime, '%Y-%m-%d %H:%i:%s') as courseStartTimeExcel,
DATE_FORMAT(uc.courseEndTime, '%Y-%m-%d %H:%i:%s') as courseEndTimeExcel,
DATE_FORMAT(uc.receiveTime, '%Y-%m-%d %H:%i:%s') as receiveTimeExcel,
u.username,
u.loginname,
u.unitName,
u.unionName,
u.sex
FROM
family_user_course uc
left join family_course course on uc.courseId = course.id
left join `vw_user` u on u.id = uc.userId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("uc.activityId", "=", activityId);
cnd.and("course.isReceiveGift", "=", true).and("course.giftType" ,"=", 1);
cnd.desc("isReceive").desc("receiveTime");
sql.setCondition(cnd);
List<NutMap> userList = familyBlackListService.listMap(sql);
List<FamilyCourse> courseList = dao.query(FamilyCourse.class, Cnd.where("activityId", "=", activityId));
List<Map<String, Object>> sheetsList = new ArrayList<>();
List<ExcelExportEntity> excelCommonExportEntity = new ArrayList<>();
excelCommonExportEntity.add(new ExcelExportEntity("姓名", "username", 20));
excelCommonExportEntity.add(new ExcelExportEntity("工号", "loginname", 20));
excelCommonExportEntity.add(new ExcelExportEntity("单位", "unitName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("分工会", "unionName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("性别", "sex", 20));
excelCommonExportEntity.add(new ExcelExportEntity("开始时间", "courseStartTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("结束时间", "courseEndTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("是否领取", "isReceive", 20));
excelCommonExportEntity.add(new ExcelExportEntity("领取时间", "receiveTimeExcel", 22));
for (FamilyCourse c : courseList) {
String courseId = c.getId();
String courseName = c.getCourseName();
List<NutMap> v = userList.stream().filter(x -> x.getString("courseId").equals(courseId)).collect(Collectors.toList());
ExportParams userExportParams = new ExportParams();
userExportParams.setSheetName(courseName);
List<ExcelExportEntity> currentEntities = new ArrayList<>(excelCommonExportEntity);
for (NutMap userSignData : v) {
if(!userSignData.getBoolean("isReceive")) {
userSignData.put("isReceive", "未领取");
userSignData.put("receiveTimeExcel", "未领取");
}else {
userSignData.put("isReceive", "已领取");
}
}
Map<String, Object> userExportMap = new HashMap<>();
userExportMap.put("name", courseName);
userExportMap.put("title", userExportParams);
userExportMap.put("entity", currentEntities);
userExportMap.put("data", v);
sheetsList.add(userExportMap);
}
try {
String fileName = activity.getActivityName() + "礼品领取人员名单.xls";
String disposition = "attachment;filename=" + URLEncoder.encode(fileName, "utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", disposition);
Workbook workbook = new HSSFWorkbook();
for (Map<String, Object> map : sheetsList) {
ExcelExportService service = new ExcelExportService();
service.createSheetForMap(workbook,(ExportParams) map.get("title"),(List<ExcelExportEntity>) map.get("entity"),(Collection<?>) map.get("data"));
}
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,275 @@
package com.budwk.app.zhgh.activity.family.controller.statistics;
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.afterturn.easypoi.excel.export.ExcelExportService;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
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.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_file;
import com.budwk.app.sys.utils.SysFileMinIoUtil;
import com.budwk.app.zhgh.activity.family.models.FamilyActivity;
import com.budwk.app.zhgh.activity.family.models.FamilyCourse;
import com.budwk.app.zhgh.activity.family.models.FamilyType;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityService;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityStatisticsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author zxy
* @Description 培训报名 统计
* @createTime 2022年02月23日 09:57:00
*/
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "亲子活动统计")
@At("/platform/family/statistics")
public class FamilyActivityStatisticsController {
@Inject
private FamilyActivityService familyActivityManageService;
@Inject
private FamilyActivityStatisticsService familyActivityStatisticsService;
@Inject
private Dao dao;
@At("")
@SaCheckPermission("family.statistics")
@Ok("beetl:/platform/zhgh/activity/family/statistics/index.html")
public void index() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission("family.statistics")
public Result pageData(PageForm pageForm,
@Param(value = "activityId") String activityId) {
Pagination pagination = familyActivityStatisticsService.pageData(pageForm, activityId);
return Result.success(pagination);
}
/**
* 活动list
*
* @param year 年度
* @return
*/
@At
@ApiOperation("活动列表")
@SaCheckPermission("family.statistics")
public Result activityList(@Param(value = "year") Integer year) {
List<FamilyActivity> list = dao.query(FamilyActivity.class, Cnd.NEW().andEX("year", "=", year).desc("activityStartTime"));
return Result.success(list);
}
/**
* 培训班报名人员list
*
* @param courseId
* @return
*/
@At
@ApiOperation("报名人员列表")
@SaCheckPermission("family.statistics")
public Result registerUserList(@Param(value = "courseId") String courseId) {
return Result.success(familyActivityStatisticsService.registerUserList(courseId));
}
@At
@ApiOperation("报名动态列")
@SaCheckPermission("family.statistics")
public Object getTaleColumnInfo(@Param(value = "courseId") String courseId) {
return Result.success(familyActivityStatisticsService.getTaleColumnInfo(courseId));
}
/**
* 培训班上课签到信息
*
* @param courseId
* @return
*/
@At
@ApiOperation("获取签到信息")
@SaCheckPermission("family.statistics")
public Result getSignInfo(@Param("courseId") String courseId) {
return Result.success(familyActivityStatisticsService.getSignInfo(courseId));
}
@At
@ApiOperation("开放报名")
@SaCheckPermission("family.statistics")
public Result signChange(@Param("courseId") String courseId, @Param("openOtherUnion") Boolean openOtherUnion) {
dao.update(FamilyCourse.class, Chain.make("openOtherUnion", openOtherUnion)
, Cnd.where("id", "=", courseId));
return Result.success();
}
@At
@Ok("void")
@ApiOperation("导出签到名单")
@SaCheckPermission("family.statistics")
public void exportSignUser(@Param(value = "activityId") String activityId,
HttpServletResponse response) throws IOException {
try {
FamilyActivity activity = dao.fetch(FamilyActivity.class, activityId);
Sql sql = Sqls.create("""
SELECT
ts.*,
CONCAT(DATE_FORMAT(ac.courseStartTime, '%H:%i:%s'),'至',DATE_FORMAT(ac.courseEndTime, '%H:%i:%s')) AS courseTime,
u.username,
u.loginname,
u.sex,
ifnull(u.mobile, ts.mobile) as newMobile,
u.birthday,
tsc.courseName
FROM
family_user ts
left join family_activity_course ac on ts.activityCourseId = ac.id
left join `vw_user` u on u.id = ts. userId
left join family_course tsc on tsc.id = ts.courseId
WHERE
ts.activityId = @activityId
""").setParam("activityId", activityId);
List<NutMap> userList = familyActivityManageService.listMap(sql);
List<FamilyCourse> courseList = dao.query(FamilyCourse.class, Cnd.where("activityId", "=", activityId));
List<FamilyType> familyTypeList = dao.query(FamilyType.class, Cnd.NEW());
dao.fetchLinks(familyTypeList, "familyMobileSignColumnList", Cnd.NEW().asc("columnIndex"));
Map<String, FamilyType> typeMap = familyTypeList.stream().collect(Collectors.toMap(FamilyType::getId, o -> o));
List<Map<String, Object>> sheetsList = new ArrayList<>();
List<Map<String, String>> basicEntity = List.of(
Map.of("name", "姓名", "key", "username"),
Map.of("name", "工号", "key", "loginname"),
Map.of("name", "单位", "key", "unitName"),
Map.of("name", "分工会", "key", "unionName"),
Map.of("name", "性别", "key", "sex"),
Map.of("name", "手机号", "key", "newMobile"),
Map.of("name", "报名时段", "key", "courseTime")
);
List<ExcelExportEntity> excelCommonExportEntity = basicEntity.stream().map(entity -> {
ExcelExportEntity excelExportEntity = new ExcelExportEntity();
excelExportEntity.setKey(entity.get("key"));
excelExportEntity.setName(entity.get("name"));
excelExportEntity.setWidth(20);
excelExportEntity.setNeedMerge(true);
return excelExportEntity;
}).collect(Collectors.toCollection(ArrayList::new));
for (FamilyCourse c : courseList) {
String k = c.getCourseName();
List<NutMap> courseSignUsers = userList.stream().filter(x -> x.getString("courseName").equals(k)).collect(Collectors.toCollection(ArrayList::new));
ExportParams userExportParams = new ExportParams();
userExportParams.setSheetName(k);
userExportParams.setType(ExcelType.HSSF);
List<ExcelExportEntity> currentEntities = new ArrayList<>(excelCommonExportEntity);
FamilyType signUpType = typeMap.get(c.getCourseType());
if (Lang.isNotEmpty(signUpType.getFamilyMobileSignColumnList())) {
ExcelExportEntity familyEntity = new ExcelExportEntity("家属信息", "familyInfos", 20);
List<ExcelExportEntity> signColumn = signUpType.getFamilyMobileSignColumnList().stream().map(column -> {
ExcelExportEntity entity = new ExcelExportEntity();
entity.setName(column.getColumnName());
entity.setKey(column.getColumnCode());
entity.setWidth(20);
if ("FILE".equals(column.getColumnFormType())) {
entity.setType(2);
entity.setExportImageType(2);
}
return entity;
}).collect(Collectors.toCollection(ArrayList::new));
familyEntity.setList(signColumn);
currentEntities.add(familyEntity);
}
for (NutMap userSignData : courseSignUsers) {
String mobileColumnsValueStr = userSignData.getString("mobileColumnsValue");
if (StrUtil.isNotBlank(mobileColumnsValueStr)) {
JSONArray outerArray = JSONUtil.parseArray(mobileColumnsValueStr);
List<List<JSONObject>> result = outerArray.stream()
.map(item -> {
// 每个 item 又是一个数组
JSONArray innerArray = (JSONArray) item;
return innerArray.toList(JSONObject.class);
})
.toList();
List<NutMap> familyInfos = new ArrayList<>();
for (List<JSONObject> list : result) {
NutMap familyMap = new NutMap();
for (JSONObject column : list) {
if (!"FILE".equals(column.getStr("columnFormType"))) {
familyMap.put(column.getStr("columnCode"), column.getStr("columnValue"));
} else {
if (StrUtil.isNotBlank(column.getStr("columnValue"))) {
List<JSONObject> columnValue = Json.fromJsonAsList(JSONObject.class, column.getStr("columnValue"));
if (columnValue.size() == 1) {
JSONObject sysFile = columnValue.get(0);
Sys_file file = dao.fetch(Sys_file.class, Cnd.where(Sys_file::getDownloadPath, "=", sysFile.get("url")));
byte[] imageBytes = SysFileMinIoUtil.getFileBytes(file.getBucket(), file.getStoragePath());
if (imageBytes.length > 0) {
familyMap.put(column.getStr("columnCode"), imageBytes);
}
}
}
}
}
familyInfos.add(familyMap);
}
userSignData.put("familyInfos", familyInfos);
}
}
Map<String, Object> userExportMap = new HashMap<>();
userExportMap.put("name", k);
userExportMap.put("title", userExportParams);
userExportMap.put("entity", currentEntities);
userExportMap.put("data", courseSignUsers);
sheetsList.add(userExportMap);
}
Workbook workbook = new HSSFWorkbook();
for (Map<String, Object> map : sheetsList) {
ExcelExportService service = new ExcelExportService();
service.createSheetForMap(workbook, (ExportParams) map.get("title"), (List<ExcelExportEntity>) map.get("entity"), (Collection<?>) map.get("data"));
}
CommonDownloadUtil.download(activity.getActivityName() + "报名人员名单" + ".xlsx", workbook, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,142 @@
package com.budwk.app.zhgh.activity.family.models;
import com.budwk.app.base.model.BaseModel;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.services.SysHomeConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.lang.Lang;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动")
@Table("family_activity")
@EqualsAndHashCode(callSuper = false)
public class FamilyActivity extends BaseModel implements Serializable, SysHomeConvert {
@Name
@PrevInsert(uu32 = true)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("活动名称")
private String activityName;
@Column
@ColDefine(type = ColType.INT)
@Comment("年度")
private Integer year;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("活动报名开始时间")
private Date activitySignUpStartTime;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("活动报名开始时间")
private Date activitySignUpEndTime;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("活动开始时间")
private Date activityStartTime;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("活动结束时间")
private Date activityEndTime;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("是否禁用")
@Default("0")
private boolean isDisabled;
@Column
@ColDefine(customType = "longtext")
@Comment("活动介绍")
private String introduce;
@Column
@ColDefine(type = ColType.INT, width = 10)
@Comment("活动限制标识")
private Integer restrictLimit;
@Column
@ColDefine(type = ColType.INT, width = 10)
@Comment("活动限制报名个数")
private Integer limitNum;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("活动封面")
private String cover;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("微信群二维码")
private String wechat;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("上课前是否通知")
@Default("0")
private boolean notice;
@Column
@Comment("活动范围Id")
@ColDefine(type = ColType.INT, width = 32)
private Integer activityGroupId;
@Column
@Comment("活动范围名称")
@ColDefine(type = ColType.VARCHAR, width = 40)
private String activityGroupName;
@Many(field = "activityId")
private List<FamilyCourse> courseList;
@Many(field = "activityId")
private List<FamilyTypeLimit> typeLimits;
@Column
@Comment("活动类型")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String trainType;
@Column
@Comment("关键词")
@Default("家属")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String keyWord;
@Override
public Sys_home_activity covertToSysHomeActivity() {
Sys_home_activity sysHomeActivity = new Sys_home_activity();
sysHomeActivity.setId(this.getId());
sysHomeActivity.setName(this.getActivityName());
sysHomeActivity.setCover(this.getCover());
sysHomeActivity.setUrl("/platform/family/manage/apply");
sysHomeActivity.setH5Url("/platform/mobile/familyActivity/familyList");
if (Lang.isNotEmpty(this.getActivitySignUpStartTime())) {
sysHomeActivity.setStartDate(this.getActivitySignUpStartTime());
sysHomeActivity.setEndDate(this.getActivitySignUpEndTime());
}
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(!this.isDisabled());
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;
}
}
@@ -0,0 +1,57 @@
package com.budwk.app.zhgh.activity.family.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动下的子活动")
@Table("family_activity_course")
@EqualsAndHashCode(callSuper = false)
public class FamilyActivityCourse {
@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.DATETIME)
@Comment("课程开始时间")
private Date courseStartTime;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("课程结束时间")
private Date courseEndTime;
@Column
@ColDefine(type = ColType.DATE)
@Comment("课程时间")
private Date courseDate;
@Column
@ColDefine(type = ColType.INT, width = 4)
@Comment("限制人数")
private Integer courseLimitNum;
private Integer hasRegisterNum;
}
@@ -0,0 +1,34 @@
package com.budwk.app.zhgh.activity.family.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动黑名单")
@Table("family_black_list")
@EqualsAndHashCode(callSuper = false)
public class FamilyBlackList {
@Name
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("用户id")
private String userId;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("是否禁用")
private Boolean isDisabled;
}
@@ -0,0 +1,153 @@
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.*;
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_course")
@EqualsAndHashCode(callSuper = false)
public class FamilyCourse extends BaseModel implements Serializable {
@Name
@PrevInsert(uu32 = true)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("活动ID")
private String activityId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("课程名称")
private String courseName;
@Column
@ColDefine(type = ColType.INT)
@Comment("课程人数")
private int coursePeopleNumber;
@Column
@ColDefine(type = ColType.INT)
@Default(value = "0")
@Comment("预留名额")
private int courseReservedNumber;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("课程地点")
private String courseLocation;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("课程类型")
private String courseType;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
@Comment("课程地点坐标")
private List<Double> courseLocationCoordinates;
@Column
@ColDefine(type = ColType.VARCHAR, width = 20)
@Comment("课程讲师")
private String courseInstructor;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("校区")
private String campus;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("报名人数是否限制")
private Boolean courseIsLimitApply;
@Column
@ColDefine(type = ColType.INT)
@Comment("序号")
private int orderNum;
@Column
@ColDefine(customType = "longtext")
@Comment("详细信息")
private String introduce;
@Many(field = "courseId")
private List<FamilyActivityCourse> courseTimeList;
@Column
@Comment("分工会人数限制")
@ColDefine(type = ColType.MYSQL_JSON)
private List<NutMap> unionLimit;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("移动端是否签到")
private boolean isMobileSign;
@Column
@ColDefine(type = ColType.INT)
@Comment("签到方式 1.扫描二维码签到 2.被扫 3.gps签到")
private Integer signType;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("移动端是否签收礼品")
private boolean isReceiveGift;
@Column
@ColDefine(type = ColType.INT)
@Comment("领取礼品方式 1.扫描二维码 2.线下")
private Integer giftType;
@Column
@ColDefine(type = ColType.INT)
@Comment("预留名额方式 1.报名人员减少模式 2.报名人数不变模式")
private Integer reserveMode;
@Column
@Comment("承办工会")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String hostUnionId;
@Column
@Comment("对内报名时间")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String interTime;
@Column
@Comment("是否开放给其他工会")
@ColDefine(type = ColType.BOOLEAN, width = 4)
private Boolean openOtherUnion;
@Column
@Comment("分类标识")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String assort;
@Column
@ColDefine(type = ColType.INT)
@Default(value = "0")
@Comment("候补名额数")
private Integer waitingNum;
private Integer hasWaitingNum;
private String courseTimeName;
private Integer hasRegisterNum;
private Boolean isBringFamily;
private Boolean isAddFamily;
private Boolean isSign;
private Boolean canSignThisCourseType;
}
@@ -0,0 +1,92 @@
package com.budwk.app.zhgh.activity.family.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动移动端动态表单")
@Table("family_mobile_sign_column")
@EqualsAndHashCode(callSuper = false)
public class FamilyMobileSignColumn implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Name
@Comment("id")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("类型id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String typeId;
@Column
@Comment("字段名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String columnName;
@Column
@Comment("字段编码")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String columnCode;
@Column
@Comment("字段值")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String columnValue;
@Column
@Comment("字段类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String columnType;
@Column
@Comment("下拉框的值")
@ColDefine(type = ColType.MYSQL_JSON)
private List<String> selectValues;
@Column
@Comment("是否必填")
@ColDefine(type = ColType.BOOLEAN)
private Boolean isRequired;
@Column
@Comment("控件类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String columnFormType;
@Column
@Comment("文件个数")
@ColDefine(type = ColType.INT)
private Integer fileNumber;
@Column
@Comment("文件类型")
@ColDefine(type = ColType.MYSQL_JSON)
private List<String> fileType;
@Column
@Comment("序号")
@ColDefine(type = ColType.INT, width = 1)
private Integer columnIndex;
@Column
@Comment("验证规则")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String validRule;
}
@@ -0,0 +1,73 @@
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.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动的子活动类型")
@Table("family_type")
@EqualsAndHashCode(callSuper = false)
public class FamilyType extends BaseModel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Name
@Comment("id")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("类型编码")
@ColDefine(type = ColType.VARCHAR, width = 80)
private String code;
@Column
@Comment("类型名称")
@ColDefine(type = ColType.VARCHAR, width = 80)
private String typeName;
@Column
@Comment("序号")
@ColDefine(type = ColType.INT, width = 1)
private Integer xh;
@Column
@Comment("是否携带家属")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean isBringFamily;
@Column
@Comment("家属纳入总人数")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean isAddFamily;
@Column
@Comment("本人纳入总人数")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean selfAddFamily;
@Many(field = "typeId")
private List<FamilyMobileSignColumn> familyMobileSignColumnList;
@Column
@Comment("家属最多数")
@ColDefine(type = ColType.INT)
private Integer familyMaxCount;
}
@@ -0,0 +1,45 @@
package com.budwk.app.zhgh.activity.family.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serial;
import java.io.Serializable;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("子活动类型限制条件")
@Table("family_type_limit")
@EqualsAndHashCode(callSuper = false)
public class FamilyTypeLimit implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Name
@Comment("id")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("活动id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String activityId;
@Column
@Comment("活动类型id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String typeId;
@Column
@Comment("限制个数")
@ColDefine(type = ColType.INT, width = 10)
private int limitNum;
}
@@ -0,0 +1,89 @@
package com.budwk.app.zhgh.activity.family.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.lang.util.NutMap;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动报名人员")
@Table("family_user")
@EqualsAndHashCode(callSuper = false)
@TableIndexes({@Index(name = "INDEX_FAMILY_USER_COURSEID", fields = {"courseId"}, unique = false)})
public class FamilyUser 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("工会ID")
private String unionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("单位ID")
private String unitId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("工会")
private String unionName;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("单位")
private String unitName;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("联系方式")
private String mobile;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("报名时间")
private Date signUpTime;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("活动课程时段id")
private String activityCourseId;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
@Comment("手机端报名字段和值")
private List<List<NutMap>> mobileColumnsValue;
@Column
@ColDefine(type = ColType.INT)
@Comment("用户报名状态(1.正常 2.待报名成功 3.也是正常,但是是从2变为1的 4.废弃[就是没签到的意思])")
private Integer state;
}
@@ -0,0 +1,90 @@
package com.budwk.app.zhgh.activity.family.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
import java.util.Date;
/**
* @author NINGMEI
*/
@Data
@Accessors(chain = true)
@Comment("亲子活动报名人员子活动表")
@Table("family_user_course")
@EqualsAndHashCode(callSuper = false)
@TableIndexes({
@Index(name = "INDEX_FAMILY_USER_COURSE_USERID", fields = {"userId"}, unique = false),
@Index(name = "INDEX_FAMILY_USER_COURSE_COURSEID", fields = {"courseId"}, unique = false)
})
public class FamilyUserCourse 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.DATETIME)
@Comment("课程开始时间")
private Date courseStartTime;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("课程结束时间")
private Date courseEndTime;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("是否上课")
private boolean isAttend;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("上课打卡时间")
private Date attendTime;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("是否领取礼品")
private Boolean isReceive;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("领取礼品时间")
private Date receiveTime;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("签到扫码人员id(二维码模式)")
private String signScannerCodeUserId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("礼品扫码人员id(二维码模式)")
private String giftScannerCodeUserId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("关联family_activity_course表的id")
private String activityCourseId;
}
@@ -0,0 +1,116 @@
package com.budwk.app.zhgh.activity.family.service;
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.FamilyCourse;
import com.budwk.app.zhgh.activity.family.models.FamilyUser;
import org.nutz.dao.Cnd;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* @author NINGMEI
*/
public interface FamilyActivityService extends BaseService<FamilyActivity> {
/**
* 添加活动
* @param activity 活动信息
* @param course 培训班信息
*/
void add(FamilyActivity activity, FamilyCourse course);
/**
* 编辑活动
* @param activity 活动信息
*/
void edit(FamilyActivity activity);
/**
* 更新活动状态
* @param activity 活动信息
*/
void updateActivityStatus(FamilyActivity activity);
/**
* 查询单条活动信息
* @param id 活动ID
* @return 返回的数据与前端符合
*/
NutMap findOne(String id, Cnd cnd, String fromMode);
/**
* pc分页查询
* @param pageForm
* @param cnd
* @return
*/
Pagination pageData(PageForm pageForm, Cnd cnd);
/**
* 手机端分页查询
* @param pageForm 分页
* @param year 年度
* @param activityStatus 报名状态 0全部 1进行中 2结束
* @return
*/
Pagination mPageData(PageForm pageForm, Integer year, int activityStatus, Integer activityType);
/**
* 手机端报名
* @param familyUser 活动ID
*/
void doSignUp(FamilyUser familyUser) throws Exception;
/**
* 异步插入每个报名成功人员的课程数据
* @param activityId
* @param courseId
* @param userId
*/
void asyncInsertUserCourse(String activityId, String courseId, String userId);
/**
* 该培训班每个分工会名额是否报满
* @return
*/
boolean isSignFullByUnionId(FamilyCourse course, Integer currentFamilyNumber);
/**
* 该培训班是否报满
*/
boolean isSignFull(FamilyCourse course, Integer currentFamilyNumber);
/**
* 还能报该类型的培训班吗 比如书画班最多报一项 健身班两项
* @return
*/
boolean isSignCourse(FamilyCourse course, FamilyActivity activity);
/**
* 当前用户是否已报过该培训班
* @param courseId
* @param userId
* @return
*/
boolean isSignCourseByUser(String courseId, String userId);
/**
* 手机端签到
* @param id 每个培训班每节课每个用户的记录ID
*/
void doQd(String id);
/**
* 某个用户的签到信息
* @param userId 用户id
* @param activityId 活动id
*/
List<NutMap> qdInfoByUserId(String userId, String activityId);
List<FamilyCourse> filterCourseByHostUnion(List<FamilyCourse> courseList);
}
@@ -0,0 +1,62 @@
package com.budwk.app.zhgh.activity.family.service;
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.FamilyUser;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Map;
/**
* @author NINGMEI
*/
public interface FamilyActivityStatisticsService extends BaseService<FamilyUser> {
/**
* 统计分页
* @param pageForm
* @param activityId
* @return
*/
Pagination pageData(PageForm pageForm, String activityId);
/**
* 该课程下的报名人员信息
* @param courseId
* @return
*/
List<NutMap> registerUserList(String courseId);
List<NutMap> getTaleColumnInfo(String courseId);
/**
* 该课程下的报名人员信息
* @param courseId
* @return
*/
List<NutMap> registerUserList(String courseId, String unionId, String unitId, String searchName, String searchKeyword);
/**
* 获取每个课程的签到情况
* @param courseId
* @return k->每个培训班每节课的上课时间 v->上课记录list
*/
Map<String, List<NutMap>> getSignInfo(String courseId);
/**
* 报名人员list 导出
* @param activityId
* @return
*/
List<NutMap> baoMingUserList(String activityId);
int queryCourseCount(String courseId, String courseType);
int queryCourseWaitCount(String courseId, String courseType);
int queryCourseCount(String courseId, String courseType, String unionId);
int queryCourseWaitCount(String courseId, String courseType, String unionId);
}
@@ -0,0 +1,37 @@
package com.budwk.app.zhgh.activity.family.service;
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.FamilyBlackList;
import org.nutz.dao.Cnd;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* @author NINGMEI
*/
public interface FamilyBlackListService extends BaseService<FamilyBlackList> {
/**
* 分页
* @param pageForm 分页
* @return Pagination
*/
Pagination pageData(PageForm pageForm, Cnd cnd, String activityId, String courseId);
/**
* 拉黑、解封用户
* @param userId
*/
void doHandleUser(String userId);
/**
* 上课记录
* @param userId
* @return
*/
List<NutMap> attendClassRecord(String userId);
}
@@ -0,0 +1,470 @@
package com.budwk.app.zhgh.activity.family.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
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.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;
import org.nutz.dao.Chain;
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.Static;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author NINGMEI
*/
@Slf4j
@IocBean(args = {"refer:dao"})
public class FamilyActivityServiceImpl extends BaseServiceImpl<FamilyActivity> implements FamilyActivityService {
@Inject
private FamilyActivityStatisticsService statisticsService;
public FamilyActivityServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void add(FamilyActivity activity, FamilyCourse course) {
dao().insert(activity);
//插入类型限制
List<FamilyTypeLimit> typeLimits = activity.getTypeLimits();
typeLimits.forEach(v -> v.setActivityId(activity.getId()));
dao().insert(typeLimits);
List<FamilyCourse> courseList = activity.getCourseList();
for (FamilyCourse v : courseList) {
v.setActivityId(activity.getId());
v.setOpenOtherUnion(false);
dao().insert(v);
this.setCourseTimeAndInsert(v);
}
if (!activity.isDisabled()) {
Sys_home_activity sysHomeActivity = activity.covertToSysHomeActivity();
dao().insertOrUpdate(sysHomeActivity);
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void edit(FamilyActivity activity) {
//修改活动
update(activity);
//修改类型限制
List<FamilyTypeLimit> typeLimits = activity.getTypeLimits();
typeLimits.forEach(v -> v.setActivityId(activity.getId()));
if(Lang.isNotEmpty(typeLimits)) {
insertOrUpdate(typeLimits);
}
List<FamilyCourse> courseList = activity.getCourseList();
courseList.forEach(v -> {
v.setActivityId(activity.getId());
dao().insertOrUpdate(v);
if (Lang.isNotEmpty(v.getCourseTimeList())) {
this.setCourseTimeAndInsert(v);
}
});
//查询原来的活动
List<FamilyCourse> oldCourseList = dao().query(FamilyCourse.class, Cnd.where("activityId", "=", activity.getId()));
//原来的培训班id
List<String> oldCourseIdList = oldCourseList.stream().map(FamilyCourse::getId).toList();
//原来的上课时间
List<FamilyActivityCourse> oldActCourseTimeList = dao().query(FamilyActivityCourse.class, Cnd.where("activityId", "=", activity.getId()));
//现在的上课时间
List<String> nowCourseTimeListId = new ArrayList<>();
activity.getCourseList().forEach(v -> {
if (v.getCourseTimeList() != null) {
nowCourseTimeListId.addAll(v.getCourseTimeList().stream().map(FamilyActivityCourse::getId).toList());
}
});
List<String> deleteCourseTimeListId = oldActCourseTimeList.stream().map(FamilyActivityCourse::getId).filter(id -> !nowCourseTimeListId.contains(id)).collect(Collectors.toList());
List<String> courseIdList = courseList.stream().map(FamilyCourse::getId).collect(Collectors.toList());
//删除关联的培训班
List<String> deleteIdList = oldCourseIdList.stream().filter(v -> !courseIdList.contains(v)).collect(Collectors.toList());
dao().clear(FamilyCourse.class, Cnd.where("id", "in", deleteIdList));
dao().clear(FamilyActivityCourse.class, Cnd.where("id", "in", deleteCourseTimeListId));
dao().clear(FamilyUser.class, Cnd.where("courseId", "not in", courseIdList).and("activityId", "=", activity.getId()));
dao().clear(FamilyUserCourse.class, Cnd.where("courseId", "not in", courseIdList).and("activityId", "=", activity.getId()));
//查询修改过培训时间的记录
Sql tsuucSql = Sqls.create("""
SELECT
tsuuc.id,
tsuac.courseStartTime,
tsuac.courseEndTime
FROM
`family_user_course` tsuuc
LEFT JOIN family_activity_course tsuac ON tsuac.id = tsuuc.activityCourseId
where tsuuc.courseStartTime != tsuac.courseStartTime or tsuuc.courseEndTime != tsuac.courseEndTime
""");
List<NutMap> tsuucList = listMap(tsuucSql);
tsuucList.forEach(v -> {
Chain chain = Chain.make("courseStartTime", v.getTime("courseStartTime"));
chain.add("courseEndTime", v.getTime("courseEndTime"));
Cnd cnd = Cnd.where("id", "=", v.getString("id"));
dao().update("family_user_course", chain, cnd);
});
if (!activity.isDisabled()) {
Sys_home_activity sysHomeActivity = activity.covertToSysHomeActivity();
dao().insertOrUpdate(sysHomeActivity);
} else {
dao().delete(Sys_home_activity.class, activity.getId());
}
}
private void setCourseTimeAndInsert(FamilyCourse course) {
course.getCourseTimeList().forEach(courseTime -> {
Calendar calendar = Calendar.getInstance();
calendar.setTime(courseTime.getCourseDate());
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DATE);
Calendar startCalendar = Calendar.getInstance();
startCalendar.setTime(courseTime.getCourseStartTime());
startCalendar.set(year, month, day);
courseTime.setCourseStartTime(startCalendar.getTime());
Calendar endCalendar = Calendar.getInstance();
endCalendar.setTime(courseTime.getCourseEndTime());
endCalendar.set(year, month, day);
courseTime.setCourseEndTime(endCalendar.getTime());
courseTime.setActivityId(course.getActivityId());
courseTime.setCourseId(course.getId());
dao().insertOrUpdate(courseTime);
});
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateActivityStatus(FamilyActivity activity) {
updateIgnoreNull(activity);
}
@Override
public NutMap findOne(String id, Cnd cnd, String fromMode) {
if (Lang.isEmpty(cnd)) {
cnd = Cnd.NEW();
}
cnd.asc("orderNum").asc("campus").desc("courseLocation").asc("courseType").asc("courseName");
List<FamilyCourse> courseArray = dao().query(FamilyCourse.class, cnd.and("activityId", "=", id));
if (StrUtil.isNotBlank(fromMode) && "mobile".equals(fromMode)) {
courseArray = this.filterCourseByHostUnion(courseArray);
}
FamilyActivity activity = fetchLinks(dao().fetch(FamilyActivity.class, id), "^(conditionStructure|typeLimits)$");
activity.setCourseList(courseArray);
List<FamilyCourse> courseList = activity.getCourseList();
courseList.forEach(c -> {
if (StrUtil.isNotBlank(c.getCourseType())) {
dao().fetchLinks(c, "^(courseTimeList)$", Cnd.NEW().asc("courseStartTime"));
int courseCount = statisticsService.queryCourseCount(c.getId(), c.getCourseType());
c.setHasRegisterNum(courseCount);
int courseWaitCount = statisticsService.queryCourseWaitCount(c.getId(), c.getCourseType());
c.setHasWaitingNum(courseWaitCount);
//当前用户是否报过
c.setIsSign(isSignCourseByUser(c.getId(), SecurityUtil.getUserId()));
}
});
return Lang.obj2nutmap(activity);
}
@Override
public Pagination pageData(PageForm pageForm, Cnd cnd) {
Pagination pagination = listPageLinks(pageForm.getPageNumber(), pageForm.getPageSize(), cnd, "^(courseList)$");
return pagination;
}
@Override
public Pagination mPageData(PageForm pageForm, Integer year, int activityStatus, Integer activityType) {
Cnd cnd = Cnd.NEW();
cnd.andEX("year", "=", year);
switch (activityStatus) {
case 2 -> {
cnd.and(new Static("activitySignUpStartTime < now()"));
cnd.and(new Static("activitySignUpEndTime > now()"));
}
case 3 -> {
cnd.and(new Static("activityStartTime < now()"));
cnd.and(new Static("activityEndTime > now()"));
}
case 4 -> cnd.and(new Static("activityEndTime < now()"));
case 5 -> cnd.and(new Static("activityStartTime < now()"));
case 6 -> cnd.and(new Static("activityStartTime > now()"));
}
if (activityType != null && activityType == 1) {
cnd.and(new Static("id in (select activityId from family_user where userId = '%s')".formatted(SecurityUtil.getUserId())));
}
cnd.and("isDisabled", "=", 0);
cnd.orderBy("activityEndTime", "desc");
cnd.orderBy("isDisabled", "desc");
cnd.orderBy("createdAt", "desc");
return listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void doSignUp(FamilyUser familyUser) throws Exception {
String userId = SecurityUtil.getUserId();
//查询课程
FamilyCourse course = dao().fetch(FamilyCourse.class, familyUser.getCourseId());
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
//如果这个课程的预留名额方式为报名人数不变
if (course.getReserveMode() == 2) {
//如果当前报名+已报小于这个课程限制人数
//课程已报人数
int normalCount = statisticsService.queryCourseCount(course.getId(), course.getCourseType());
//+1是算自己
int hasRegisterNum = type.getSelfAddFamily() ? normalCount + 1 : 0;
familyUser.setState((hasRegisterNum + course.getCourseReservedNumber()) > course.getCoursePeopleNumber() ? 2 : 1);
} else {
familyUser.setState(1);
}
View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
familyUser.setUnionId(SecurityUtil.getUnionId());
familyUser.setUnionName(user.getUnionName());
familyUser.setUnitId(SecurityUtil.getUnitId());
familyUser.setUnitName(user.getUnitName());
familyUser.setUserId(userId);
familyUser.setSignUpTime(new Date());
dao().insert(familyUser);
if (StrUtil.isNotBlank(familyUser.getActivityCourseId())) {
TrainSignUpActivityCourse fetch = dao().fetch(TrainSignUpActivityCourse.class, familyUser.getActivityCourseId());
TrainSignUpUserCourse userCourse = new TrainSignUpUserCourse();
userCourse.setActivityId(familyUser.getActivityId());
userCourse.setCourseId(familyUser.getCourseId());
userCourse.setUserId(familyUser.getUserId());
userCourse.setCourseStartTime(fetch.getCourseStartTime());
userCourse.setCourseEndTime(fetch.getCourseEndTime());
userCourse.setAttend(false);
userCourse.setAttendTime(null);
userCourse.setActivityCourseId(fetch.getId());
dao().insert(userCourse);
} else {
asyncInsertUserCourse(familyUser.getActivityId(), familyUser.getCourseId(), userId);
}
}
@Async
@Override
public void asyncInsertUserCourse(String activityId, String courseId, String userId) {
log.info("异步插入{}的上课信息,课程ID为{},活动ID为{}", userId, courseId, activityId);
List<FamilyActivityCourse> courseList = dao().query(FamilyActivityCourse.class, Cnd.where("courseId", "=", courseId));
List<FamilyUserCourse> list = new ArrayList<>();
courseList.forEach(v -> {
FamilyUserCourse userCourse = new FamilyUserCourse();
userCourse.setActivityId(activityId);
userCourse.setCourseId(courseId);
userCourse.setUserId(userId);
userCourse.setCourseStartTime(v.getCourseStartTime());
userCourse.setCourseEndTime(v.getCourseEndTime());
userCourse.setAttend(false);
userCourse.setAttendTime(null);
userCourse.setActivityCourseId(v.getId());
list.add(userCourse);
});
dao().insert(list);
}
/**
* 该培训班每个分工会名额是否报满
* @return
*/
@Override
public boolean isSignFullByUnionId(FamilyCourse course, Integer currentFamilyNumber) {
List<NutMap> unionLimit = course.getUnionLimit();
if (Lang.isEmpty(unionLimit)) {
return false;
}
String unionId = SecurityUtil.getUnionId();
NutMap unionLimitMap = unionLimit.stream().filter(v -> v.getString("id").equals(unionId)).findAny().orElse(null);
if (Lang.isEmpty(unionLimitMap)) {
return true;
}
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
//分工会限制人数
int limitCount = unionLimitMap.getInt("limitCount");
//该课程已经报名的总人数
int hasSignCount = statisticsService.queryCourseCount(course.getId(), course.getCourseType(), unionId);
int hasWaitCount = statisticsService.queryCourseWaitCount(course.getId(), course.getCourseType());
//+1是算自己
int current = type.getSelfAddFamily() ? 1 : 0;
currentFamilyNumber = type.getIsBringFamily() && type.getIsAddFamily() ? currentFamilyNumber : 0;
//如果还有正常名额
if(limitCount - hasSignCount > 0) {
return (hasSignCount + current + currentFamilyNumber) > limitCount;
} else {
return (hasWaitCount + current + currentFamilyNumber) > course.getWaitingNum();
}
}
@Override
public boolean isSignFull(FamilyCourse course, Integer currentFamilyNumber) {
//课程限制人数
int coursePeopleNumber = course.getCoursePeopleNumber();
if (coursePeopleNumber == 0) {
return true;
}
//查询课程对应的类型
FamilyType type = dao().fetch(FamilyType.class, course.getCourseType());
//课程已报人数
int hasSignCount = statisticsService.queryCourseCount(course.getId(), course.getCourseType());
int hasWaitCount = statisticsService.queryCourseWaitCount(course.getId(), course.getCourseType());
//+1是算自己
int current = type.getSelfAddFamily() ? 1 : 0;
currentFamilyNumber = type.getIsBringFamily() && type.getIsAddFamily() ? currentFamilyNumber : 0;
//如果还有正常名额
if(coursePeopleNumber - hasSignCount > 0) {
return (hasSignCount + current + currentFamilyNumber + course.getCourseReservedNumber()) > coursePeopleNumber;
} else {
return (hasWaitCount + current + currentFamilyNumber) > course.getWaitingNum();
}
}
@Override
public boolean isSignCourse(FamilyCourse course, FamilyActivity activity) {
//培训班类型
String courseType = course.getCourseType();
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
""");
sql.setParam("courseType", courseType);
sql.setParam("activityId", activity.getId());
sql.setParam("userId", SecurityUtil.getUserId());
int hasRegisterNum = count(sql);
//第一种无限制报名
if (activity.getRestrictLimit() == null || activity.getRestrictLimit() == 1) {
return true;
} else if (activity.getRestrictLimit() == 2) {
FamilyTypeLimit familyTypeLimit = dao().fetch(FamilyTypeLimit.class, Cnd.where("typeId", "=", courseType).and("activityId", "=", activity.getId()));
if (familyTypeLimit == null) {
return true;
}
//此类型的班最多可报几项
int personMaxRegisterNum = familyTypeLimit.getLimitNum();
if (personMaxRegisterNum == 0) {
return true;
}
return hasRegisterNum < personMaxRegisterNum;
} else if (activity.getRestrictLimit() == 3) {
//第三种,限制报几个,不跟类型挂钩
int aCount = dao().count(FamilyUser.class, Cnd.where("activityId", "=", activity.getId()).and("userId", "=", SecurityUtil.getUserId()));
return aCount < activity.getLimitNum();
}
return false;
}
@Override
public boolean isSignCourseByUser(String courseId, String userId) {
return dao().count(FamilyUser.class, Cnd.where("courseId", "=", courseId).and("userId", "=", userId)) > 0;
}
@Override
public void doQd(String id) {
NutMap updateMap = NutMap.NEW();
updateMap.put("isAttend", true);
updateMap.put("attendTime", new Date());
dao().update(FamilyUserCourse.class, Chain.from(updateMap), Cnd.where("id", "=", id));
}
@Override
public List<NutMap> qdInfoByUserId(String userId, String activityId) {
Sql sql = Sqls.create("""
SELECT
c.*,
t.courseLocationCoordinates,
t.isMobileSign,
t.signType,
t.isReceiveGift,
t.giftType,
(select state from family_user su where su.activityId = c.activityId and su.courseId = c.courseId and su.userId = c.userId) as state
FROM
`family_user_course` c
LEFT JOIN family_course t ON t.id = c.courseId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("c.activityId", "=", activityId);
cnd.and("c.userId", "=", userId);
cnd.asc("c.courseStartTime");
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public List<FamilyCourse> filterCourseByHostUnion(List<FamilyCourse> courseList) {
if (Lang.isEmpty(courseList)) {
return new ArrayList<>();
}
return courseList.stream().filter(o -> {
if (StrUtil.isBlank(o.getInterTime()) || o.getOpenOtherUnion() == null || o.getOpenOtherUnion()) {
return true;
} else {
if (SecurityUtil.getUnionId().equals(o.getHostUnionId())) {
return true;
} else {
int compare = cn.hutool.core.date.DateUtil.compare(cn.hutool.core.date.DateUtil.date(), cn.hutool.core.date.DateUtil.parse(o.getInterTime()), "yyyy-MM-dd HH:mm");
return compare >= 0;
}
}
}).toList();
}
}
@@ -0,0 +1,268 @@
package com.budwk.app.zhgh.activity.family.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.activity.family.models.FamilyMobileSignColumn;
import com.budwk.app.zhgh.activity.family.models.FamilyCourse;
import com.budwk.app.zhgh.activity.family.models.FamilyType;
import com.budwk.app.zhgh.activity.family.models.FamilyUser;
import com.budwk.app.zhgh.activity.family.service.FamilyActivityStatisticsService;
import lombok.extern.slf4j.Slf4j;
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.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author zxy
* @Description TODO
* @createTime 2022年03月03日 10:02:00
*/
@IocBean(args = {"refer:dao"})
@Slf4j
public class FamilyActivityStatisticsServiceImpl extends BaseServiceImpl<FamilyUser> implements FamilyActivityStatisticsService {
public FamilyActivityStatisticsServiceImpl(Dao dao) {
super(dao);
}
@Override
public Pagination pageData(PageForm pageForm, String activityId) {
Sql sql = Sqls.create("""
SELECT
tsuc.id,
tsuc.courseName,
tsuc.coursePeopleNumber,
tsuc.courseReservedNumber,
type.typeName as courseType,
tsuc.courseLocation,
tsuc.courseInstructor,
tsuc.reserveMode,
tsuc.isMobileSign,
tsuc.hostUnionId,
tsuc.interTime,
tsuc.waitingNum,
tsuc.openOtherUnion,
tsuc.courseType as cType
FROM
`family_course` tsuc
LEFT JOIN
family_type type on tsuc.courseType = type.id
WHERE
tsuc.activityId = @activityId
ORDER BY tsuc.orderNum
""");
sql.setParam("activityId", activityId);
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> courseList = pagination.getList();
courseList.forEach(c -> {
c.put("registerNum", queryCourseCount(c.getString("id"), c.getString("cType")));
c.put("hasWaitingNum", queryCourseWaitCount(c.getString("id"), c.getString("cType")));
});
return pagination;
}
@Override
public List<NutMap> registerUserList(String courseId) {
Sql sql = Sqls.create("""
SELECT
u.loginname,
u.username,
u.sex,
tsuu.unionId,
tsuu.unionName,
tsuu.unitId,
tsuu.unitName,
ifnull(tsuu.mobile, u.mobile) as mobile,
tsuu.signUpTime,
tsuu.state,
tsuu.mobileColumnsValue
FROM
family_user tsuu
LEFT JOIN `vw_user` u ON u.id = tsuu.userId
$condition
ORDER BY FIELD( tsuu.state, 1, 3, 2, 4 ),u.unionid desc, u.unitid desc
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("tsuu.courseId", "=", courseId);
sql.setCondition(cnd);
List<NutMap> listMap = listMap(sql);
for (NutMap nutMap : listMap) {
// 第一步:解析为 JSONArray(外层数组)
JSONArray outerArray = JSONUtil.parseArray(nutMap.getString("mobileColumnsValue"));
// 第二步:转换为 List<List<JSONObject>>
List<List<JSONObject>> result = outerArray.stream()
.map(item -> {
// 每个 item 又是一个数组
JSONArray innerArray = (JSONArray) item;
return innerArray.toList(JSONObject.class);
})
.toList();
nutMap.put("mobileColumnsValue", result);
nutMap.put("familyCount", result.size());
}
return listMap;
}
@Override
public List<NutMap> getTaleColumnInfo(String courseId) {
FamilyCourse course = dao().fetch(FamilyCourse.class, courseId);
FamilyType upType = dao().fetch(FamilyType.class, course.getCourseType());
dao().fetchLinks(upType, "familyMobileSignColumnList", Cnd.NEW().asc("columnIndex"));
List<FamilyMobileSignColumn> columnList = upType.getFamilyMobileSignColumnList();
List<NutMap> columnTableList = columnList.stream().map(o -> NutMap.NEW().setv("label", o.getColumnName()).setv("prop", o.getColumnCode())).collect(Collectors.toList());
return columnTableList;
}
@Override
public List<NutMap> registerUserList(String courseId, String unionId, String unitId, String searchName, String searchKeyword) {
Sql sql = Sqls.create("""
SELECT
tsuu.id,
u.id as userId,
u.loginname,
u.username,
u.unitname,
u.unionname,
u.sex,
ifnull(tsuu.mobile, u.mobile) as mobile,
tsuu.signUpTime,
tsuu.state
FROM
family_user tsuu
LEFT JOIN `vw_user` u ON u.id = tsuu.userId
$condition
ORDER BY FIELD( tsuu.state, 1, 3, 2, 4 ),u.unionid desc, u.unitid desc
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("tsuu.courseId", "=", courseId);
cnd.andEX("u.unionid", "=", unionId);
cnd.andEX("u.unitid", "=", unitId);
if (StrUtil.isNotBlank(searchKeyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.andLike("username", searchKeyword).orLike("loginname", searchKeyword);
cnd.and(group);
}
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public Map<String, List<NutMap>> getSignInfo(String courseId) {
Sql sql = Sqls.create("""
SELECT
tsuuc.courseStartTime,
tsuuc.courseEndTime,
tsuuc.isAttend,
tsuuc.attendTime,
u.username,
u.loginname,
u.unitname,
u.unionname
FROM
`family_user_course` tsuuc
LEFT JOIN `vw_user` u ON u.id = tsuuc.userId
WHERE
tsuuc.courseId = @courseId
""");
sql.setParam("courseId", courseId);
List<NutMap> list = listMap(sql);
Map<String, List<NutMap>> courseTimeMap = list.stream().map(v -> {
String courseTime = v.getString("courseStartTime") + "" + v.getString("courseEndTime");
v.put("courseTime", courseTime);
return v;
}).collect(Collectors.groupingBy(v -> v.getString("courseTime")));
// 使用Stream API进行降序排序
Map<String, List<NutMap>> sortedDataMap = courseTimeMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
return sortedDataMap;
}
@Override
public List<NutMap> baoMingUserList(String activityId) {
Sql sql = Sqls.create("""
SELECT
u.loginname,
u.username,
u.unitname,
u.unionname,
u.sex,
u.mobile,
uc.courseName,
uc.campus
FROM
`family_user` uu
RIGHT JOIN family_course uc ON uc.id = uu.courseId
LEFT JOIN `vw_user` u ON u.id = uu.userId
WHERE
uc.activityId = @activityId
ORDER BY u.unitCode,u.unioncode,u.sex
""");
sql.setParam("activityId", activityId);
return listMap(sql);
}
@Override
public int queryCourseCount(String courseId, String courseType) {
return this.queryCourseCount(courseId, courseType, null);
}
@Override
public int queryCourseWaitCount(String courseId, String courseType) {
return this.queryCourseWaitCount(courseId, courseType, null);
}
@Override
public int queryCourseCount(String courseId, String courseType, String unionId) {
return this.calcSignCount(courseId, courseType, unionId, List.of(1, 3));
}
@Override
public int queryCourseWaitCount(String courseId, String courseType, String unionId) {
return this.calcSignCount(courseId, courseType, unionId, List.of(2));
}
private int calcSignCount(String courseId, String courseType, String unionId, List<Integer> stateList) {
if(StrUtil.isBlank(courseId) || StrUtil.isBlank(courseType)) {
return 0;
}
FamilyType type = dao().fetch(FamilyType.class, courseType);
AtomicInteger hasRegisterNum = new AtomicInteger();
List<FamilyUser> signUpUsers = dao().query(FamilyUser.class, Cnd.where("courseId", "=", courseId)
.and("state", "in", stateList)
.andEX("unionId", "=", unionId));
signUpUsers.forEach(item -> {
if (type.getSelfAddFamily()) {
hasRegisterNum.getAndIncrement();
}
if (type.getIsBringFamily() && type.getIsAddFamily()) {
List<List<NutMap>> mapList = item.getMobileColumnsValue();
if (Lang.isNotEmpty(mapList)) {
hasRegisterNum.addAndGet(mapList.size());
}
}
});
return hasRegisterNum.get();
}
}
@@ -0,0 +1,103 @@
package com.budwk.app.zhgh.activity.family.service.impl;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.activity.family.models.FamilyBlackList;
import com.budwk.app.zhgh.activity.family.service.FamilyBlackListService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Criteria;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* @author zxy
* @Description TODO
* @createTime 2022年03月07日 14:29:00
*/
@IocBean(args = {"refer:dao"})
public class FamilyUserServiceImpl extends BaseServiceImpl<FamilyBlackList> implements FamilyBlackListService {
public FamilyUserServiceImpl(Dao dao) {
super(dao);
}
@Override
public Pagination pageData(PageForm pageForm, Cnd cnd, String activityId, String courseId) {
Sql sql = Sqls.create("""
SELECT
u.id AS userId,
u.username,
u.loginname,
ifnull(tsuu.mobile, u.mobile) as mobile,
u.unitname,
u.unionname,
tsuc.courseName,
tsuu.state,
( SELECT count( 1 ) FROM family_user_course WHERE userId = tsuu.userId $var) courseTotal,
( SELECT count( 1 ) FROM family_user_course WHERE userId = tsuu.userId AND isAttend = 0 and tsuu.state!=2 AND now()> courseEndTime $var) AS absentCount,
if(tsubl.isDisabled=1,true,false) isDisabled,
group_CONCAT( tsuc.courseName ) AS courseNames
FROM
`family_user` tsuu
LEFT JOIN `vw_user` u ON u.id = tsuu.userId
LEFT JOIN family_activity act on act.id = tsuu.activityId
LEFT JOIN family_course tsuc ON tsuc.id = tsuu.courseId
LEFT JOIN family_black_list tsubl on tsubl.userId = tsuu.userId
$condition
ORDER BY FIELD( tsuu.state, 1, 3, 2, 4 )
""");
cnd.groupBy("tsuu.userId");
Criteria varCnd = Cnd.cri();
varCnd.where().setTop(false);
varCnd.where().andEX("activityId", "=", activityId);
varCnd.where().andEX("courseId", "=", courseId);
if (!varCnd.where().isEmpty()) {
sql.vars().set("var", "and " + varCnd.toSql(null));
}
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
public void doHandleUser(String userId) {
FamilyBlackList blackRecord = dao().fetch(FamilyBlackList.class, Cnd.where("userId", "=", userId));
if (Lang.isEmpty(blackRecord)) {
FamilyBlackList blackList = new FamilyBlackList();
blackList.setUserId(userId);
blackList.setIsDisabled(true);
dao().insert(blackList);
} else {
// blackRecord.setIsDisabled(!blackRecord.getIsDisabled());
// dao().update(blackRecord);
dao().delete(blackRecord);
}
}
@Override
public List<NutMap> attendClassRecord(String userId) {
Sql sql = Sqls.create("""
SELECT
c.courseName,
uc.courseStartTime,
courseEndTime,
uc.isAttend,
uc.attendTime
FROM
`family_user_course` uc
LEFT JOIN family_course c ON c.id = uc.courseId
WHERE
uc.userId = @userId
""");
sql.setParam("userId", userId);
return listMap(sql);
}
}
@@ -170,3 +170,128 @@ body {
margin-top: 10px;
flex-wrap: wrap;
}
/*************************覆盖vant默认CSS********************************/
.van-dropdown-menu__bar{
box-shadow: unset;
}
/*****************************申请表单CSS********************************/
.form-container {
padding-bottom: 80px;
}
.form-container .form-group {
margin-bottom: 12px;
}
.form-container .form-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
padding: 12px 16px;
background-color: #ffffff;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
z-index: 100;
}
.form-container .form-actions .van-button {
flex: 1;
margin: 0 8px;
border-radius: 4px;
height: 40px;
font-size: 15px;
}
.form-container .van-cell-group {
margin-bottom: 12px;
overflow: hidden;
}
.form-container .van-cell-group__title {
font-size: 15px;
color: var(--color-black);
font-weight: 600;
padding: 10px;
position: relative;
}
.form-container .van-cell-group__title::before{
content: "";
width: 5px;
background: var(--color-primary);
display: inline-block;
position: absolute;
left: 0;
top: 0;
bottom: 0;
}
.form-container .van-field__label {
width: 90px;
color: #323233;
font-size: 14px;
}
.form-container .van-field__value {
color: #323233;
}
.form-container .van-cell {
padding: 12px 16px;
}
.form-container .van-cell:not(:last-child)::after {
left: 16px;
right: 16px;
}
.form-container .direction-column-field .van-field__label {
width: auto;
}
.form-container .direction-column-field .van-field__body {
display: block;
}
.form-container .direction-column-field .van-field__control {
margin-top: 8px;
}
/***************************查看详情CSS********************************/
.detail-container {
background: #f9f9f9
}
.detail-container .van-cell-group__title{
font-size: 15px;
color: var(--color-black);
font-weight: 600;
padding: 10px;
position: relative;
}
.detail-container .van-cell-group__title::before{
content: "";
width: 5px;
background: var(--color-primary);
display: inline-block;
position: absolute;
left: 0;
top: 0;
bottom: 0;
}
.detail-container .direction-column-cell{
display: flex;
flex-direction: column;
}
.detail-container .direction-column-cell .van-cell__value{
text-align: left;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,112 @@
@charset "UTF-8";
/* 数据量大与遮罩交互冲突优化 */
.v-modal.v-modal-leave {
display: none;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-corner {
background: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 6px;
border: 2px solid transparent;
background-color: var(--scrollbar-background-color);
background-clip: padding-box;
}
::-webkit-scrollbar-track {
background: transparent;
border-radius: 5px;
}
.el-dialog__header {
padding: 15px 20px 15px 20px;
display: flex;
align-items: center;
border-bottom: 1px solid var(--border-color-lighter);
}
.el-dialog__title {
flex: 1;
font-size: 16px;
margin: 0;
color: rgba(0, 0, 0, 0.88);
font-weight: 600;
line-height: 1.5;
word-break: break-word;
}
.el-dialog__headerbtn{
top: auto;
right: auto;
line-height: 1;
font-size: 18px;
position: static;
}
.el-dialog__footer {
padding-bottom: 12px;
border-top: 1px solid var(--border-color-lighter);
}
.el-table thead {
color: var(--color-text-primary);
font-weight: 500;
}
.el-table td.el-table__cell div {
color: var(--color-text-primary);
}
.el-card {
border: none;
}
.el-card + .el-card {
margin-top: 15px;
}
.el-message:not(.ele-message-border){
background: var(--popover-background-color);
-webkit-box-shadow: var(--box-shadow-light);
box-shadow: var(--box-shadow-light);
border-radius: 2px;
border: none;
}
.el-message {
min-width: auto;
position: fixed;
}
.el-message .el-message__icon {
font-size: 18px;
}
.el-message .el-icon-success {
color: var(--color-success);
}
.el-message:not(.ele-message-border) .el-message__content {
color: inherit;
}
.el-message .el-message__content {
line-height: 18px;
}
.el-button{
border-radius: 2px;
}
.el-descriptions .is-bordered{
table-layout: fixed;!important;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,927 @@
/*
PinchZoom.js
Copyright (c) Manuel Stofer 2013 - today
Author: Manuel Stofer (mst@rtp.ch)
Version: 2.3.5
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// polyfills
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
if (typeof Array.from != 'function') {
Array.from = function (object) {
return [].slice.call(object);
};
}
// utils
var buildElement = function(str) {
// empty string as title argument required by IE and Edge
var tmp = document.implementation.createHTMLDocument('');
tmp.body.innerHTML = str;
return Array.from(tmp.body.children)[0];
};
var triggerEvent = function(el, name) {
var event = document.createEvent('HTMLEvents');
event.initEvent(name, true, false);
el.dispatchEvent(event);
};
var definePinchZoom = function () {
/**
* Pinch zoom
* @param el
* @param options
* @constructor
*/
var PinchZoom = function (el, options) {
this.el = el;
this.zoomFactor = 1;
this.lastScale = 1;
this.offset = {
x: 0,
y: 0
};
this.initialOffset = {
x: 0,
y: 0,
};
this.options = Object.assign({}, this.defaults, options);
this.setupMarkup();
this.bindEvents();
this.update();
// The image may already be loaded when PinchZoom is initialized,
// and then the load event (which trigger update) will never fire.
if (this.isImageLoaded(this.el)) {
this.updateAspectRatio();
this.setupOffsets();
}
this.enable();
},
sum = function (a, b) {
return a + b;
},
isCloseTo = function (value, expected) {
return value > expected - 0.01 && value < expected + 0.01;
};
PinchZoom.prototype = {
defaults: {
tapZoomFactor: 2,
zoomOutFactor: 1.3,
animationDuration: 300,
maxZoom: 4,
minZoom: 0.5,
draggableUnzoomed: true,
lockDragAxis: false,
setOffsetsOnce: false,
use2d: true,
zoomStartEventName: 'pz_zoomstart',
zoomUpdateEventName: 'pz_zoomupdate',
zoomEndEventName: 'pz_zoomend',
dragStartEventName: 'pz_dragstart',
dragUpdateEventName: 'pz_dragupdate',
dragEndEventName: 'pz_dragend',
doubleTapEventName: 'pz_doubletap',
verticalPadding: 0,
horizontalPadding: 0,
onZoomStart: null,
onZoomEnd: null,
onZoomUpdate: null,
onDragStart: null,
onDragEnd: null,
onDragUpdate: null,
onDoubleTap: null
},
/**
* Event handler for 'dragstart'
* @param event
*/
handleDragStart: function (event) {
triggerEvent(this.el, this.options.dragStartEventName);
if(typeof this.options.onDragStart == "function"){
this.options.onDragStart(this, event)
}
this.stopAnimation();
this.lastDragPosition = false;
this.hasInteraction = true;
this.handleDrag(event);
},
/**
* Event handler for 'drag'
* @param event
*/
handleDrag: function (event) {
var touch = this.getTouches(event)[0];
this.drag(touch, this.lastDragPosition);
this.offset = this.sanitizeOffset(this.offset);
this.lastDragPosition = touch;
},
handleDragEnd: function () {
triggerEvent(this.el, this.options.dragEndEventName);
if(typeof this.options.onDragEnd == "function"){
this.options.onDragEnd(this, event)
}
this.end();
},
/**
* Event handler for 'zoomstart'
* @param event
*/
handleZoomStart: function (event) {
triggerEvent(this.el, this.options.zoomStartEventName);
if(typeof this.options.onZoomStart == "function"){
this.options.onZoomStart(this, event)
}
this.stopAnimation();
this.lastScale = 1;
this.nthZoom = 0;
this.lastZoomCenter = false;
this.hasInteraction = true;
},
/**
* Event handler for 'zoom'
* @param event
*/
handleZoom: function (event, newScale) {
// a relative scale factor is used
var touchCenter = this.getTouchCenter(this.getTouches(event)),
scale = newScale / this.lastScale;
this.lastScale = newScale;
// the first touch events are thrown away since they are not precise
this.nthZoom += 1;
if (this.nthZoom > 3) {
this.scale(scale, touchCenter);
this.drag(touchCenter, this.lastZoomCenter);
}
this.lastZoomCenter = touchCenter;
},
handleZoomEnd: function () {
triggerEvent(this.el, this.options.zoomEndEventName);
if(typeof this.options.onZoomEnd == "function"){
this.options.onZoomEnd(this, event)
}
this.end();
},
/**
* Event handler for 'doubletap'
* @param event
*/
handleDoubleTap: function (event) {
var center = this.getTouches(event)[0],
zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor,
startZoomFactor = this.zoomFactor,
updateProgress = (function (progress) {
this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
}).bind(this);
if (this.hasInteraction) {
return;
}
this.isDoubleTap = true;
if (startZoomFactor > zoomFactor) {
center = this.getCurrentZoomCenter();
}
this.animate(this.options.animationDuration, updateProgress, this.swing);
triggerEvent(this.el, this.options.doubleTapEventName);
if(typeof this.options.onDoubleTap == "function"){
this.options.onDoubleTap(this, event)
}
},
/**
* Compute the initial offset
*
* the element should be centered in the container upon initialization
*/
computeInitialOffset: function () {
this.initialOffset = {
x: -Math.abs(this.el.offsetWidth * this.getInitialZoomFactor() - this.container.offsetWidth) / 2,
y: -Math.abs(this.el.offsetHeight * this.getInitialZoomFactor() - this.container.offsetHeight) / 2,
};
},
/**
* Reset current image offset to that of the initial offset
*/
resetOffset: function() {
this.offset.x = this.initialOffset.x;
this.offset.y = this.initialOffset.y;
},
/**
* Determine if image is loaded
*/
isImageLoaded: function (el) {
if (el.nodeName === 'IMG') {
return el.complete && el.naturalHeight !== 0;
} else {
return Array.from(el.querySelectorAll('img')).every(this.isImageLoaded);
}
},
setupOffsets: function() {
if (this.options.setOffsetsOnce && this._isOffsetsSet) {
return;
}
this._isOffsetsSet = true;
this.computeInitialOffset();
this.resetOffset();
},
/**
* Max / min values for the offset
* @param offset
* @return {Object} the sanitized offset
*/
sanitizeOffset: function (offset) {
var elWidth = this.el.offsetWidth * this.getInitialZoomFactor() * this.zoomFactor;
var elHeight = this.el.offsetHeight * this.getInitialZoomFactor() * this.zoomFactor;
var maxX = elWidth - this.getContainerX() + this.options.horizontalPadding,
maxY = elHeight - this.getContainerY() + this.options.verticalPadding,
maxOffsetX = Math.max(maxX, 0),
maxOffsetY = Math.max(maxY, 0),
minOffsetX = Math.min(maxX, 0) - this.options.horizontalPadding,
minOffsetY = Math.min(maxY, 0) - this.options.verticalPadding;
return {
x: Math.min(Math.max(offset.x, minOffsetX), maxOffsetX),
y: Math.min(Math.max(offset.y, minOffsetY), maxOffsetY)
};
},
/**
* Scale to a specific zoom factor (not relative)
* @param zoomFactor
* @param center
*/
scaleTo: function (zoomFactor, center) {
this.scale(zoomFactor / this.zoomFactor, center);
},
/**
* Scales the element from specified center
* @param scale
* @param center
*/
scale: function (scale, center) {
scale = this.scaleZoomFactor(scale);
this.addOffset({
x: (scale - 1) * (center.x + this.offset.x),
y: (scale - 1) * (center.y + this.offset.y)
});
triggerEvent(this.el, this.options.zoomUpdateEventName);
if(typeof this.options.onZoomUpdate == "function"){
this.options.onZoomUpdate(this, event)
}
},
/**
* Scales the zoom factor relative to current state
* @param scale
* @return the actual scale (can differ because of max min zoom factor)
*/
scaleZoomFactor: function (scale) {
var originalZoomFactor = this.zoomFactor;
this.zoomFactor *= scale;
this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom));
return this.zoomFactor / originalZoomFactor;
},
/**
* Determine if the image is in a draggable state
*
* When the image can be dragged, the drag event is acted upon and cancelled.
* When not draggable, the drag event bubbles through this component.
*
* @return {Boolean}
*/
canDrag: function () {
return this.options.draggableUnzoomed || !isCloseTo(this.zoomFactor, 1);
},
/**
* Drags the element
* @param center
* @param lastCenter
*/
drag: function (center, lastCenter) {
if (lastCenter) {
if(this.options.lockDragAxis) {
// lock scroll to position that was changed the most
if(Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) {
this.addOffset({
x: -(center.x - lastCenter.x),
y: 0
});
}
else {
this.addOffset({
y: -(center.y - lastCenter.y),
x: 0
});
}
}
else {
this.addOffset({
y: -(center.y - lastCenter.y),
x: -(center.x - lastCenter.x)
});
}
triggerEvent(this.el, this.options.dragUpdateEventName);
if(typeof this.options.onDragUpdate == "function"){
this.options.onDragUpdate(this, event)
}
}
},
/**
* Calculates the touch center of multiple touches
* @param touches
* @return {Object}
*/
getTouchCenter: function (touches) {
return this.getVectorAvg(touches);
},
/**
* Calculates the average of multiple vectors (x, y values)
*/
getVectorAvg: function (vectors) {
return {
x: vectors.map(function (v) { return v.x; }).reduce(sum) / vectors.length,
y: vectors.map(function (v) { return v.y; }).reduce(sum) / vectors.length
};
},
/**
* Adds an offset
* @param offset the offset to add
* @return return true when the offset change was accepted
*/
addOffset: function (offset) {
this.offset = {
x: this.offset.x + offset.x,
y: this.offset.y + offset.y
};
},
sanitize: function () {
if (this.zoomFactor < this.options.zoomOutFactor) {
this.zoomOutAnimation();
} else if (this.isInsaneOffset(this.offset)) {
this.sanitizeOffsetAnimation();
}
},
/**
* Checks if the offset is ok with the current zoom factor
* @param offset
* @return {Boolean}
*/
isInsaneOffset: function (offset) {
var sanitizedOffset = this.sanitizeOffset(offset);
return sanitizedOffset.x !== offset.x ||
sanitizedOffset.y !== offset.y;
},
/**
* Creates an animation moving to a sane offset
*/
sanitizeOffsetAnimation: function () {
var targetOffset = this.sanitizeOffset(this.offset),
startOffset = {
x: this.offset.x,
y: this.offset.y
},
updateProgress = (function (progress) {
this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x);
this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y);
this.update();
}).bind(this);
this.animate(
this.options.animationDuration,
updateProgress,
this.swing
);
},
/**
* Zooms back to the original position,
* (no offset and zoom factor 1)
*/
zoomOutAnimation: function () {
if (this.zoomFactor === 1) {
return;
}
var startZoomFactor = this.zoomFactor,
zoomFactor = 1,
center = this.getCurrentZoomCenter(),
updateProgress = (function (progress) {
this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
}).bind(this);
this.animate(
this.options.animationDuration,
updateProgress,
this.swing
);
},
/**
* Updates the container aspect ratio
*
* Any previous container height must be cleared before re-measuring the
* parent height, since it depends implicitly on the height of any of its children
*/
updateAspectRatio: function () {
this.unsetContainerY();
this.setContainerY(this.container.parentElement.offsetHeight);
},
/**
* Calculates the initial zoom factor (for the element to fit into the container)
* @return {number} the initial zoom factor
*/
getInitialZoomFactor: function () {
var xZoomFactor = this.container.offsetWidth / this.el.offsetWidth;
var yZoomFactor = this.container.offsetHeight / this.el.offsetHeight;
return Math.min(xZoomFactor, yZoomFactor);
},
/**
* Calculates the aspect ratio of the element
* @return the aspect ratio
*/
getAspectRatio: function () {
return this.el.offsetWidth / this.el.offsetHeight;
},
/**
* Calculates the virtual zoom center for the current offset and zoom factor
* (used for reverse zoom)
* @return {Object} the current zoom center
*/
getCurrentZoomCenter: function () {
var offsetLeft = this.offset.x - this.initialOffset.x;
var centerX = -1 * this.offset.x - offsetLeft / (1 / this.zoomFactor - 1);
var offsetTop = this.offset.y - this.initialOffset.y;
var centerY = -1 * this.offset.y - offsetTop / (1 / this.zoomFactor - 1);
return {
x: centerX,
y: centerY
};
},
/**
* Returns the touches of an event relative to the container offset
* @param event
* @return array touches
*/
getTouches: function (event) {
var rect = this.container.getBoundingClientRect();
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
var posTop = rect.top + scrollTop;
var posLeft = rect.left + scrollLeft;
return Array.prototype.slice.call(event.touches).map(function (touch) {
return {
x: touch.pageX - posLeft,
y: touch.pageY - posTop,
};
});
},
/**
* Animation loop
* does not support simultaneous animations
* @param duration
* @param framefn
* @param timefn
* @param callback
*/
animate: function (duration, framefn, timefn, callback) {
var startTime = new Date().getTime(),
renderFrame = (function () {
if (!this.inAnimation) { return; }
var frameTime = new Date().getTime() - startTime,
progress = frameTime / duration;
if (frameTime >= duration) {
framefn(1);
if (callback) {
callback();
}
this.update();
this.stopAnimation();
this.update();
} else {
if (timefn) {
progress = timefn(progress);
}
framefn(progress);
this.update();
requestAnimationFrame(renderFrame);
}
}).bind(this);
this.inAnimation = true;
requestAnimationFrame(renderFrame);
},
/**
* Stops the animation
*/
stopAnimation: function () {
this.inAnimation = false;
},
/**
* Swing timing function for animations
* @param p
* @return {Number}
*/
swing: function (p) {
return -Math.cos(p * Math.PI) / 2 + 0.5;
},
getContainerX: function () {
return this.container.offsetWidth;
},
getContainerY: function () {
return this.container.offsetHeight;
},
setContainerY: function (y) {
return this.container.style.height = y + 'px';
},
unsetContainerY: function () {
this.container.style.height = null;
},
/**
* Creates the expected html structure
*/
setupMarkup: function () {
this.container = buildElement('<div class="pinch-zoom-container"></div>');
this.el.parentNode.insertBefore(this.container, this.el);
this.container.appendChild(this.el);
this.container.style.overflow = 'hidden';
this.container.style.position = 'relative';
this.el.style.webkitTransformOrigin = '0% 0%';
this.el.style.mozTransformOrigin = '0% 0%';
this.el.style.msTransformOrigin = '0% 0%';
this.el.style.oTransformOrigin = '0% 0%';
this.el.style.transformOrigin = '0% 0%';
this.el.style.position = 'absolute';
},
end: function () {
this.hasInteraction = false;
this.sanitize();
this.update();
},
/**
* Binds all required event listeners
*/
bindEvents: function () {
var self = this;
detectGestures(this.container, this);
this.resizeHandler = this.update.bind(this)
window.addEventListener('resize', this.resizeHandler);
Array.from(this.el.querySelectorAll('img')).forEach(function(imgEl) {
imgEl.addEventListener('load', self.update.bind(self));
});
if (this.el.nodeName === 'IMG') {
this.el.addEventListener('load', this.update.bind(this));
}
},
/**
* Updates the css values according to the current zoom factor and offset
*/
update: function (event) {
if (event && event.type === 'resize') {
this.updateAspectRatio();
this.setupOffsets();
}
if (event && event.type === 'load') {
this.updateAspectRatio();
this.setupOffsets();
}
if (this.updatePlanned) {
return;
}
this.updatePlanned = true;
window.setTimeout((function () {
this.updatePlanned = false;
var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor,
offsetX = -this.offset.x / zoomFactor,
offsetY = -this.offset.y / zoomFactor,
transform3d = 'scale3d(' + zoomFactor + ', ' + zoomFactor + ',1) ' +
'translate3d(' + offsetX + 'px,' + offsetY + 'px,0px)',
transform2d = 'scale(' + zoomFactor + ', ' + zoomFactor + ') ' +
'translate(' + offsetX + 'px,' + offsetY + 'px)',
removeClone = (function () {
if (this.clone) {
this.clone.parentNode.removeChild(this.clone);
delete this.clone;
}
}).bind(this);
// Scale 3d and translate3d are faster (at least on ios)
// but they also reduce the quality.
// PinchZoom uses the 3d transformations during interactions
// after interactions it falls back to 2d transformations
if (!this.options.use2d || this.hasInteraction || this.inAnimation) {
this.is3d = true;
removeClone();
this.el.style.webkitTransform = transform3d;
this.el.style.mozTransform = transform2d;
this.el.style.msTransform = transform2d;
this.el.style.oTransform = transform2d;
this.el.style.transform = transform3d;
} else {
// When changing from 3d to 2d transform webkit has some glitches.
// To avoid this, a copy of the 3d transformed element is displayed in the
// foreground while the element is converted from 3d to 2d transform
if (this.is3d) {
this.clone = this.el.cloneNode(true);
this.clone.style.pointerEvents = 'none';
this.container.appendChild(this.clone);
window.setTimeout(removeClone, 200);
}
this.el.style.webkitTransform = transform2d;
this.el.style.mozTransform = transform2d;
this.el.style.msTransform = transform2d;
this.el.style.oTransform = transform2d;
this.el.style.transform = transform2d;
this.is3d = false;
}
}).bind(this), 0);
},
/**
* Enables event handling for gestures
*/
enable: function() {
this.enabled = true;
},
/**
* Disables event handling for gestures
*/
disable: function() {
this.enabled = false;
},
/**
* Unmounts the zooming container and global event listeners
*/
destroy: function () {
window.removeEventListener('resize', this.resizeHandler);
if (this.container) {
this.container.remove();
this.container = null;
}
}
};
var detectGestures = function (el, target) {
var interaction = null,
fingers = 0,
lastTouchStart = null,
startTouches = null,
setInteraction = function (newInteraction, event) {
if (interaction !== newInteraction) {
if (interaction && !newInteraction) {
switch (interaction) {
case "zoom":
target.handleZoomEnd(event);
break;
case 'drag':
target.handleDragEnd(event);
break;
}
}
switch (newInteraction) {
case 'zoom':
target.handleZoomStart(event);
break;
case 'drag':
target.handleDragStart(event);
break;
}
}
interaction = newInteraction;
},
updateInteraction = function (event) {
if (fingers === 2) {
setInteraction('zoom');
} else if (fingers === 1 && target.canDrag()) {
setInteraction('drag', event);
} else {
setInteraction(null, event);
}
},
targetTouches = function (touches) {
return Array.from(touches).map(function (touch) {
return {
x: touch.pageX,
y: touch.pageY
};
});
},
getDistance = function (a, b) {
var x, y;
x = a.x - b.x;
y = a.y - b.y;
return Math.sqrt(x * x + y * y);
},
calculateScale = function (startTouches, endTouches) {
var startDistance = getDistance(startTouches[0], startTouches[1]),
endDistance = getDistance(endTouches[0], endTouches[1]);
return endDistance / startDistance;
},
cancelEvent = function (event) {
event.stopPropagation();
event.preventDefault();
},
detectDoubleTap = function (event) {
var time = (new Date()).getTime();
if (fingers > 1) {
lastTouchStart = null;
}
if (time - lastTouchStart < 300) {
cancelEvent(event);
target.handleDoubleTap(event);
switch (interaction) {
case "zoom":
target.handleZoomEnd(event);
break;
case 'drag':
target.handleDragEnd(event);
break;
}
} else {
target.isDoubleTap = false;
}
if (fingers === 1) {
lastTouchStart = time;
}
},
firstMove = true;
el.addEventListener('touchstart', function (event) {
if(target.enabled) {
firstMove = true;
fingers = event.touches.length;
detectDoubleTap(event);
}
}, { passive: false });
el.addEventListener('touchmove', function (event) {
if(target.enabled && !target.isDoubleTap) {
if (firstMove) {
updateInteraction(event);
if (interaction) {
cancelEvent(event);
}
startTouches = targetTouches(event.touches);
} else {
switch (interaction) {
case 'zoom':
if (startTouches.length == 2 && event.touches.length == 2) {
target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches)));
}
break;
case 'drag':
target.handleDrag(event);
break;
}
if (interaction) {
cancelEvent(event);
target.update();
}
}
firstMove = false;
}
}, { passive: false });
el.addEventListener('touchend', function (event) {
if(target.enabled) {
fingers = event.touches.length;
updateInteraction(event);
}
});
};
return PinchZoom;
};
var PinchZoom = definePinchZoom();
export default PinchZoom;
@@ -0,0 +1,62 @@
<script>
module.exports = {
name: "TableColumn",
props: {
label: {
type: String,
default: ""
},
value: {
type: [String, Number],
required: false
},
label_suffix:{
type: String,
default: ""
}
},
}
</script>
<template>
<div class="table-column">
<!-- 左侧 Label -->
<div class="label">
<slot name="label">
{{ label }}
{{label_suffix}}
</slot>
</div>
<!-- 右侧 Value -->
<div class="value">
<slot>{{ value }}</slot>
</div>
</div>
</template>
<style scoped>
.table-column {
display: flex;
padding: 6px 0;
align-items: center;
}
.label {
color: #333;
font-size: 14px;
flex-shrink: 0;
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.value {
color: #555;
font-size: 14px;
flex-grow: 1; /* 动态占据剩余部分 */
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出的部分 */
text-overflow: ellipsis; /* 省略号 */
}
</style>
@@ -0,0 +1,278 @@
<template>
<van-pull-refresh v-model="tableRefreshing" @refresh="onRefresh">
<div v-if="tableData.length === 0" class="empty-state">
<van-empty description="暂无数据"></van-empty>
</div>
<van-list v-if="tableData && tableData.length>0"
v-model="tableLoading"
:finished="tableFinished"
finished-text="没有更多了"
@load="onLoad">
<div class="table-list-container">
<div v-for="(row, index) in tableData" :key="index" class="table-list-item">
<slot name="header" :index="index" :row="row">
<div class="item-header">
<div class="item-title">{{ row[title] }}</div>
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>
</div>
</slot>
<div style="display: flex;column-gap: 10px"
:style="{'max-height':img ? '100px':'unset', 'overflow': img ? 'hidden':'unset' }">
<div class="img-container" v-if="img">
<img :src="row[img]" alt="" style="object-fit: cover">
</div>
<div class="">
<slot :index="index" :row="row"></slot>
</div>
</div>
<div class="item-actions">
<slot name="actions" :index="index" :row="row"></slot>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</template>
<script>
module.exports = {
name: "TableList",
props: {
api: {
type: String,
required: true
},
page_form: {
type: Object,
required: true,
default: () => {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: ""
}
}
},
json: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
},
img: {
type: String,
default: ""
}
},
watch: {
page_form: {
handler(newVal, oldVal) {
this.localPageForm = { ...newVal }
},
deep: true,
immediate: true
}
},
data() {
return {
tableData: [],
tableLoading: false,
tableFinished: false,
tableRefreshing: false,
localPageForm: { ...this.page_form }
}
},
methods: {
onLoad() {
if (this.tableFinished) return
this.localPageForm.pageNumber++
this.$emit("update:page_form", { ...this.localPageForm })
this.pageData()
},
pageData() {
this.tableLoading = true
const loading = createListLoading()
this.$axios.post(this.api, this.json ? ({ pageForm: JSON.stringify(this.localPageForm) }) : this.localPageForm).then((res) => {
if (res.code === 0) {
this.tableData = this.tableData.concat(res.data.list)
this.localPageForm.totalCount = res.data.totalCount
if (this.tableData.length >= this.localPageForm.totalCount) {
this.tableFinished = true
}
this.$emit("update:page_form", { ...this.localPageForm })
}
}).finally(() => {
loading.close()
console.log(this.tableLoading)
this.tableLoading = false
console.log(this.tableLoading)
this.tableRefreshing = false
})
},
doSearch() {
this.tableFinished = false
this.tableData = []
this.localPageForm.pageNumber = 1
this.$emit("update:page_form", { ...this.localPageForm })
this.pageData()
},
onRefresh() {
this.localPageForm.pageNumber = 1
this.$emit("update:page_form", { ...this.localPageForm })
this.doSearch()
}
},
mounted() {
this.$emit("ready")
}
}
</script>
<style scoped>
.table-list-container {
padding: 0;
margin-top: 10px;
}
.table-list-container .table-list-item {
background-color: #fff;
border-bottom: 1px solid #eaecef;
padding: 16px;
position: relative;
margin-bottom: 10px;
}
.table-list-container .table-list-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
.table-list-container .table-list-item .item-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.table-list-container .table-list-item .item-title {
font-size: 16px;
font-weight: 500;
color: #1f2f3d;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
padding-right: 10px;
}
.table-list-container .table-list-item .img-container {
width: 100px;
height: 100px;
flex-shrink: 0;
border-radius: 6px;
}
.table-list-container .table-list-item .img-container img {
width: 100%;
height: 100%;
border-radius: 6px;
}
.table-list-container .table-list-item .item-meta {
display: flex;
color: #606266;
font-size: 14px;
margin-bottom: 5px;
padding: 4px 0;
}
.table-list-container .table-list-item .meta-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.table-list-container .table-list-item .meta-item i {
font-size: 14px;
color: #909399;
width: 16px;
text-align: center;
}
.table-list-container .table-list-item .item-content {
color: #606266;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.5;
}
.table-list-container .table-list-item .item-footer {
display: flex;
justify-content: space-between;
margin-top: 12px;
padding-top: 8px;
border-top: 1px dashed #eaecef;
color: #909399;
font-size: 13px;
}
.table-list-container .table-list-item .item-actions {
display: flex;
column-gap: 20px;
justify-content: end;
margin-top: 15px;
padding-top: 12px;
border-top: 1px solid #eaecef;
}
.table-list-container .table-list-item .action-btn {
display: flex;
align-items: center;
justify-content: end;
font-size: 14px;
color: var(--color-primary);
padding: 4px 0;
}
.table-list-container .table-list-item .action-btn i {
margin-right: 6px;
font-size: 16px;
}
.table-list-container .table-list-item .action-btn.delete {
color: #ff0000;
}
.table-list-container .table-list-item .action-btn.review {
color: #67c23a;
}
.table-list-container .empty-state {
text-align: center;
padding: 60px 20px;
color: #909399;
}
.table-list-container .empty-state i {
font-size: 60px;
margin-bottom: 16px;
color: #dcdee0;
}
.table-list-container .empty-state p {
margin-top: 10px;
font-size: 14px;
}
</style>
@@ -0,0 +1,118 @@
<template>
<div>
<template v-for="item in options">
<component :key="item[value_key]" v-if="item[value_key] === value" v-bind="$attrs"
:is="isMobile ? 'van-tag' : 'el-tag'" :type="item.type"
>{{ item[label_key] }}
</component>
</template>
</div>
</template>
<script>
// 全局缓存和请求Promise缓存
const enumCache = {}
const requestPromises = {}
module.exports = {
name: "DictTag",
props: {
value: { type: String | Number },
name: { type: String },
value_key: {
type: String,
default: "code"
},
label_key: {
type: String,
default: "name"
},
},
data() {
return {
options: [],
isMobile: true,
}
},
watch: {
code: {
handler(val) {
this.getEnumOptions()
},
immediate: true
}
},
methods: {
isMobileDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
},
getEnumOptions() {
// // 检查数据缓存
// if (enumCache[this.name]) {
// this.options = enumCache[this.name]
// return
// }
//
// // 检查是否已有相同请求在进行中
// if (requestPromises[this.name]) {
// requestPromises[this.name].then(data => {
// this.options = data
// })
// return
// }
// 创建请求Promise并缓存
requestPromises[this.name] = $.get("/open/common/dictEnumOptions", { name: this.name })
.then(res => {
if (res.code === 0) {
// 存入数据缓存
enumCache[this.name] = res.data
// 清除请求Promise缓存
delete requestPromises[this.name]
return res.data
}
})
requestPromises[this.name].then(data => {
this.options = data
})
}
},
created() {
this.isMobile = this.isMobileDevice()
}
}
</script>
<style scoped>
.el-tag + .el-tag {
margin-left: 10px;
}
.van-tag {
height: 24px;
padding: 0 8px;
line-height: 22px;
}
.van-tag--success {
background-color: #f0f9eb;
border-color: #e1f3d8;
color: #67c23a;
}
.van-tag--primary {
color: var(--color-primary);
background-color: #ecf5ff;
border-color: #d9ecff;
}
.van-tag--danger{
background-color: #fef0f0;
border-color: #fde2e2;
color: #f56c6c;
}
.van-tag--warning{
background-color: #fdf6ec;
border-color: #faecd8;
color: #e6a23c;
}
</style>
@@ -0,0 +1,75 @@
<template>
<div>
<div v-if="pdf" id="pdf-container"></div>
<div v-else v-html="content" class="content"></div>
</div>
</template>
<script>
module.exports = {
name: "PdfIndex",
props: {
content: {
type: String,
default: '',
required: true
},
height: {
type: Number,
default: 300,
required: false
},
},
watch: {
},
data() {
return {
pdf: true,
pdfObj: null,
}
},
methods: {
init() {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(this.content, 'text/html');
const link = doc.querySelector('a');
const href = link.getAttribute('href');
this.$nextTick(() => {
this.pdfObj = new Pdfh5('#pdf-container', {
pdfurl: href,
});
this.pdfObj.on("complete", function () {
const elements = document.querySelectorAll('[class*="canvasImg"]')
let classArray = []
elements.forEach(element => {
classArray.push(element.getAttribute('src'))
})
elements.forEach((element,index) => {
element.addEventListener('click', function() {
vant.ImagePreview({
images: classArray,
startPosition: index,
closeable: true,
})
})
})
})
})
}catch (e) {
this.pdf = false
}
},
},
created() {
this.init()
},
}
</script>
<style scoped>
.content {
padding: 10px;
}
</style>
@@ -0,0 +1,125 @@
<template>
<div class="sign-container">
<template v-if="Object.keys(res).length === 0">
<div class="reader-container">
<div id="reader"></div>
</div>
</template>
<template v-else>
<div class="weui-msg">
<div class="weui-msg__icon-area">
<i v-if="res?.code !== 0" class="weui-icon-warn weui-icon_msg"></i>
<i v-if="res?.code === 0" class="weui-icon-success weui-icon_msg"></i>
</div>
<div class="weui-msg__text-area">
<div class="weui-msg__title">温馨提醒</div>
<div v-html="res?.msg"></div>
</div>
<div class="weui-msg__opr-area">
<p class="weui-btn-area">
<a @click="res = {}; getCameras()"
class="weui-btn weui-btn_primary">重新扫描</a>
</p>
</div>
</div>
</template>
</div>
</template>
<script>
module.exports = {
name: "scanCode",
props: {
},
watch: {
},
data() {
return {
html5QrCode: null,
scanStatus: true,
cameraId: '',
res: {},
}
},
methods: {
getCameras() {
Html5Qrcode.getCameras()
.then((devices) => {
if (devices && devices.length) {
// 如果有2个摄像头,1为前置的
if (devices.length > 1) {
this.cameraId = devices[1].id;
} else {
this.cameraId = devices[0].id;
}
let isHuawei = navigator.userAgent.toLowerCase().match(/huawei/i) === 'huawei';
if(isHuawei) {
const backCamera = devices.filter(o => o.label.includes('back'))
this.cameraId = backCamera[0].id;
}
this.start();
}
})
.catch((err) => {
console.log(err)
})
},
start() {
this.html5QrCode = new Html5Qrcode("reader");
this.html5QrCode.start(
this.cameraId, // retreived in the previous step.
{
fps: 100, // sets the framerate to 10 frame per second,
qrbox: {width: 1000, height: 1000}, // sets only 250 X 250 region of viewfinder to
},
async (decodedText, decodedResult) => {
this.res = await this.$axios.post(decodedText)
this.closeScan()
},
(errorMessage) => {
console.log(errorMessage);
}
)
.catch((err) => {
alert(err)
console.log(`Unable to start scanning, error: ` + err);
});
},
closeScan() {
this.html5QrCode.stop()
.then((ignore) => {
console.log("QR Code scanning stopped.");
})
.catch((err) => {
console.log("Unable to stop scanning.");
});
},
init() {
this.getCameras()
}
},
created() {
},
}
</script>
<style scoped>
.sign-container{
width: 100%;
height: 100%;
}
.reader-container {
padding-top: 50px;
}
#reader {
width: 90%;
margin: 0 auto;
text-align: center;
}
.weui-msg__icon-area {
margin-top: 30px;
}
</style>
@@ -12,6 +12,8 @@
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css" />
<link rel="stylesheet" href="https://cdn.staticfile.net/animate.css/4.1.1/animate.css" />
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css" />
<link rel="stylesheet" href="${base!}/assets/platform/css/pdf/pdfh5.css">
<link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/2.2.0/weui.min.css">
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
<script src="${base!}/assets/platform/plugins/vuex/vuex.js"></script>
@@ -40,6 +42,18 @@
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js?v=0.0.1"></script>
<script src="${base!}/assets/platform/plugins/form-create/designer/index.umd.js"></script>
<!--图片预览-->
<link rel="stylesheet" href="${base!}/assets/platform/plugins/viewerjs/viewer.css" />
<script src="${base!}/assets/platform/plugins/viewerjs/viewer.js"></script>
<!--pdf平铺展示-->
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.js" type="text/javascript" charset="utf-8"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.worker.js" type="text/javascript" charset="utf-8"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdfh5.js" type="text/javascript" charset="utf-8"></script>
<!--h5扫码-->
<script src="${base!}/assets/platform/plugins/html5-qrcode/html5-qrcode.min.js"></script>
<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>
@@ -0,0 +1,322 @@
<!--#include('signForm.js'){}#-->
const courseList = {
template: /*language=HTML*/ `
<div>
<el-row :gutter="20">
<!--<el-col :span="5">
<div class="glow-box">
<img :src="activity.cover"/>
<div class="title">{{ activity.activityName }}</div>
<div v-html="activity.introduce"></div>
</div>
</el-col>-->
<el-col :span="24">
<el-row class="query-row">
<el-col class="query-title hidden-xs-only">类&emsp;&emsp;型:</el-col>
<el-col class="query-content">
<el-select
v-model="pageForm.courseTypeId"
placeholder="请选择类型"
filterable
clearable
style="width: 200px"
@change="doSearch"
>
<el-option v-for="item in courseTypeList" :key="item.id" :label="item.typeName" :value="item.id"></el-option>
</el-select>
</el-col>
</el-row>
<el-row class="query-row" v-if="assortList && assortList.length > 0">
<el-col class="query-title hidden-xs-only">分类标识:</el-col>
<el-col class="query-content">
<el-tag
:effect="pageForm.assortTypes.includes(item) ? 'dark' : 'plain'"
:key="item"
:type="item"
@click="tagClick('assortTypes', item)"
style="margin-right: 10px; cursor: pointer"
v-for="item in assortList"
>
{{ item }}
</el-tag>
</el-col>
</el-row>
<el-table :data="tableData" class="mt10">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{row}" v-if="column.prop=='courseLocationCoordinates'">
<el-button style="padding: 0" @click="openViewMap(row.courseLocationCoordinates)" type="text">
{{row.courseLocation}}
</el-button>
</template>
<template v-slot="{row}" v-else-if="column.prop=='courseTime'">
<el-button style="padding: 0" @click="openViewCourseTime(row.id)" type="text">点击查看时间</el-button>
</template>
<template v-slot="{row}" v-else-if="column.prop=='applyNum'">
<span v-html="calcSignUpCount(row)"></span>
</template>
<template v-slot="{row}" v-else-if="column.prop=='introduce'">
<span v-if="!row.introduce">暂无</span>
<el-button v-else style="padding: 0" @click="onPreview(row.introduce)" type="text">点击查看</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<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>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-col>
</el-row>
<el-dialog :visible.sync="courseTimeListDialog" title="时间信息" width="60%" append-to-body>
<el-table :data="courseTimeList">
<el-table-column label="日期" prop="courseDate">
<template v-slot="{row}">{{$moment(row.courseDate).format('YYYY-MM-DD')}}</template>
</el-table-column>
<el-table-column label="开始时间" prop="courseStartTime">
<template v-slot="{row}">{{$moment(row.courseStartTime).format('HH:mm')}}</template>
</el-table-column>
<el-table-column label="结束时间" prop="courseEndTime">
<template v-slot="{row}">{{$moment(row.courseEndTime).format('HH:mm')}}</template>
</el-table-column>
</el-table>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="courseTimeListDialog = false">取 消</el-button>
</el-row>
</el-dialog>
<el-dialog :visible.sync="viewMapDialog" title="地点" width="60%" append-to-body>
<div id="viewMap" style="width: 100%; height: 500px"></div>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="viewMapDialog = false">取 消</el-button>
</el-row>
</el-dialog>
<sign-form ref="signFormRef" @refresh="doSearch"></sign-form>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
mixins: [initTableMixins],
components: {
"sign-form": signForm,
},
data() {
return {
pageForm: {
assortTypes: [],
},
assortList: [],
courseTimeListDialog: false,
viewMapDialog: false,
courseTimeList: [],
tableColumns: [
{ prop: "courseName", label: "名称" },
{ prop: "typeName", label: "类型", width: 130},
{ prop: "courseLocationCoordinates", label: "地点", width: 200 },
{ prop: "courseInstructor", label: "联系人", width: 100 },
{ prop: "courseTime", label: "时间", width: 200 },
{ prop: "applyNum", label: "已报名人数", width: 200 }
],
activity: {},
courseTypeList: [],
activityType: '',
}
},
methods: {
async onPreview(introduce) {
const parser = new DOMParser();
const doc = parser.parseFromString(introduce, 'text/html');
const link = doc.querySelector('a');
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]) })
window.open("/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent(res.data[0].downloadPath), res.data[0].name)
},
tagClick(key, val) {
let idx = this.pageForm[key].indexOf(val)
if (idx !== -1) {
this.pageForm[key].splice(idx, 1)
} else {
this.pageForm[key].push(val)
}
this.doSearch()
},
async onOpen(row) {
this.activity = row
this.$set(this.pageForm, 'activityId', row.id)
await this.pageData()
await this.getCourseTypeList()
this.queryCourseAssort()
},
onSign(row) {
const courseType = this.courseTypeList.find((v) => v.id === row.courseType)
this.$axios.post("/platform/family/apply/validateSignUp", {courseId: row.id})
.then((res) => {
if (res.code !== 0) {
this.$alert(res.msg, "提示", {
confirmButtonText: "确定",
type: "warning"
})
} else {
let lave = row.coursePeopleNumber - (row.hasRegisterNum + row.courseReservedNumber)
if(row.reserveMode === 2 && lave <= 0) {
this.$alert('您当前的报名为候补报名状态', "提示", {
confirmButtonText: "确定",
type: "warning"
})
}
this.$refs.signFormRef.onOpen(row, courseType)
}
})
},
onCancel(row) {
this.$confirm("您确定要取消吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(async () => {
const resp = await 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)
}
})
},
async getCourseTypeList() {
const resp = await this.$axios.post("/platform/family/type/getAllType")
if (resp.code === 0) {
this.courseTypeList = resp.data
}
},
async openViewCourseTime(id) {
const resp = await this.$axios.post(loc() + "/getCourseTime", { id: id })
this.courseTimeList = resp.data
this.courseTimeListDialog = true
},
openViewMap(point) {
if (!Array.isArray(point)) {
this.$message.warning("没有设置点位,无法通过地图查看")
return
}
this.viewMapDialog = true
this.$nextTick(() => {
const viewMap = new AMap.Map("viewMap", {
resizeEnable: true,
center: point,
zoom: 16
})
if (viewMarker) {
viewMap.remove(viewMarker)
}
viewMarker = new AMap.Marker({
position: point,
offset: new AMap.Pixel(-13, -30)
})
viewMap.add(viewMarker)
viewMap.setFitView(null, false, [150, 60, 100, 60])
})
},
calcSignUpCount(o) {
let lave = o.coursePeopleNumber - (o.hasRegisterNum + o.courseReservedNumber)
if(o.reserveMode === 2) {
let lave2 = o.waitingNum - o.hasWaitingNum
return "<span style='color: red'>余" + lave +"</span>/" + o.coursePeopleNumber + "人"
+ "<span style='color: red'>候补余" + lave2 + "</span>/" + o.waitingNum + "人"
} else {
return "<span style='color: red'>余" + lave +"</span>/" + o.coursePeopleNumber + "人"
}
},
async 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)
}
},
queryCourseAssort() {
this.$axios.post(loc() + "/queryCourseAssort", {activityId: this.activity.id})
.then((resp) => {
this.assortList = resp.data
})
},
},
created() {
},
style: /*language=CSS*/ `
.glow-box {
min-height: calc(100vh - 56px - 40px - 50px - 80px);
max-height: calc(100vh - 56px - 40px - 50px - 80px);
overflow-y: auto;
background: #F0F2F5;
}
img {
width: 100%;
height: 180px;
}
.title {
background: white;
height: 20px;
}
.query-row {
display: flex;
align-items: center;
padding: 6px 0;
}
.query-row:not(:last-child) {
border-bottom: 1px dashed rgb(230, 230, 230);
}
.query-row > .query-title {
width: 100px;
max-width: 100px;
min-width: 100px;
overflow: hidden;
}
.query-row > .query-content {
min-width: 200px;
overflow: hidden;
}
.query-row > .query-content > .el-tag {
margin-bottom: 5px;
margin-top: 5px;
}
@media screen and (max-width: 992px) {
.query-row:nth-child(4) .query-content .el-col:not(:last-child) {
margin-bottom: 5px;
}
.query-title {
display: none;
}
}
`
}
@@ -0,0 +1,166 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
</style>
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度:">
<el-date-picker
placeholder="选择年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
value-format="yyyy"
@change="doSearch"
></el-date-picker>
</search-item>
<search-item label="活动状态:">
<el-select v-model="pageForm.activityType" @change="doSearch" style="width: 100%"
placeholder="请选择活动状态" filterable>
<el-option :value="1" label="全部"></el-option>
<el-option :value="4" label="即将开始"></el-option>
<el-option :value="2" label="报名中"></el-option>
<el-option :value="3" label="已结束"></el-option>
</el-select>
</search-item>
</search>
</el-card>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="活动列表"></table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{row}" v-if="column.prop=='activitySignUpStartTime'">
<span>{{$moment(row.activitySignUpStartTime).format('MM/DD HH:mm')}}</span>
<span></span>
<span>{{$moment(row.activitySignUpEndTime).format('MM/DD HH:mm')}}</span>
</template>
<template v-slot="{row}" v-else-if="column.prop=='activityStartTime'">
<span>{{$moment(row.activityStartTime).format('MM/DD HH:mm')}}</span>
<span></span>
<span>{{$moment(row.activityEndTime).format('MM/DD HH:mm')}}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template v-slot="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看介绍</el-button>
<el-button @click="onOpen(row)" size="mini" type="primary">去报名</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #view>
<course-list ref="courseListRef"></course-list>
</template>
</guava>
<el-dialog title="详细信息" :visible.sync="infoVisible" width="60%">
<activity-info ref="infoRef"></activity-info>
<el-statistic
v-if="time < 0"
format="DD 天 HH 时 mm 分钟 ss 秒"
:value="new Date(infoRow.activitySignUpStartTime)"
time-indices
title="距离开始:"
@finish="time = 0"
style="margin-top: 30px"
>
</el-statistic>
<span slot="footer" class="dialog-footer">
<el-button @click="infoVisible = false">取消</el-button>
<el-button type="primary" @click="onOpen(infoRow)" :disabled="time < 0">
去报名
</el-button>
</span>
</el-dialog>
</div>
<script>
<!--#include('courseList.js'){}#-->
<!--#include('../manage/info.js'){}#-->
new Vue({
el: "#app",
mixins: [initTableMixins],
components: {
"course-list": courseList,
"activity-info": info,
},
data() {
return {
pageForm: {
year: this.$moment().format("YYYY"),
activityType: "2"
},
tableColumns: [
{ label: "活动名称", prop: "activityName", width: 600},
{ label: "活动性质", prop: "trainType"},
{ label: "报名时间", prop: "activitySignUpStartTime"},
{ label: "活动时间", prop: "activityStartTime"},
],
time: 0,
infoRow: {},
infoVisible: false,
}
},
methods: {
onView(row) {
this.infoRow = row
this.time = this.$moment().diff(this.$moment(row.activitySignUpStartTime), 'milliseconds')
this.infoVisible = true
this.$nextTick(() => {
this.$refs.infoRef.initData(row.id)
})
},
onOpen(row) {
if(this.$moment().isBefore(this.$moment(row.activitySignUpStartTime))) {
this.onView(row)
return
}
this.infoVisible = false
this.$refs.guava.view(() => {
this.$refs.courseListRef.onOpen(row)
})
},
pageData() {
this.$axios.post("/platform/family/apply/activityData", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
},
async created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,291 @@
const signForm = {
template: /*language=HTML*/ `
<div>
<el-dialog :close-on-click-modal="false" :visible.sync="signDialog" title="信息填写" width="50%"
append-to-body>
<el-form :model="formData" ref="form" label-width="120px">
<div class="left-span-label">个人信息</div>
<el-row>
<el-col :span="24">
<el-form-item label="姓名" prop="username">
<el-input readonly v-model="formData.username"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="工号" prop="loginname">
<el-input readonly v-model="formData.loginname"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="所在单位" prop="unitName">
<el-input readonly v-model="formData.unitName"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="所属工会" prop="unionName">
<el-input readonly v-model="formData.unionName"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="性别" prop="sex">
<el-input readonly v-model="formData.sex"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="联系方式" prop="mobile">
<el-input v-model="formData.mobile"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="courseRow.courseIsLimitApply">
<el-col :span="24">
<el-form-item label="报名时段" prop="activityCourseId"
:rules="{ required: true, message: '请选择报名时段', trigger: 'blur'}">
<el-select style="width: 100%" v-model="formData.activityCourseId"
placeholder="请选择报名时段">
<el-option v-for="item in courseTimeSelectList"
:label="item.text.substring(0, 12)"
:value="item.value"
:key="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="left-span-label"
style="display: flex; justify-content: space-between; align-items: center">
<div>家属信息</div>
<div>
<el-button type="primary" size="mini" @click="delFamily">
删除家属
</el-button>
<el-button type="primary" size="mini" @click="addFamily">
添加家属
</el-button>
</div>
</div>
<div v-if="formData.mobileColumnsValue && formData.mobileColumnsValue.length > 0">
<el-tabs v-model="active" type="card">
<el-tab-pane v-for="(item, index) in formData.mobileColumnsValue"
:name="index + ''"
:label="'家属' + (index + 1)"
:key="index">
<el-row>
<el-col :span="24">
<el-form-item
v-for="(column, index) in item"
:label="column.columnName"
:key="column.columnCode"
:rules="{ required: column.isRequired,
message: (['SELECT', 'FILE'].includes(column.columnFormType) ? '请选择' : '请填写') + column.columnName,
trigger: 'blur'}"
>
<!--文本框-->
<template
v-if="!['SELECT'].includes(column.columnFormType) && ['VARCHAR','TEXT','INT'].includes(column.columnType)">
<el-input
v-model="column.columnValue"
:placeholder="'请填写' + column.columnName"
:type="['INT'].includes(column.columnType) ? 'number' : ''"
></el-input>
</template>
<!--选择框-->
<template
v-else-if="['SELECT'].includes(column.columnFormType) && ['VARCHAR','TEXT','INT'].includes(column.columnType)">
<el-select v-model="column.columnValue"
:placeholder="'请选择' + column.columnName"
style="width: 100%">
<el-option v-for="item in column.selectValues" :key="item"
:label="item" :value="item"></el-option>
</el-select>
</template>
<!--时间框-->
<template v-else-if="['DATE', 'DATETIME'].includes(column.columnType)">
<el-date-picker
style="width: 100%"
v-model="column.columnValue"
:type="column.columnType === 'DATE' ? 'date' : 'datetime'"
:placeholder="'请选择' + column.columnName"
:value-format="column.columnType === 'DATE' ? 'yyyy-MM-dd' : 'yyyy-MM-dd HH:mm:ss'"
></el-date-picker>
</template>
<!--文件-->
<template v-else-if="['JSON'].includes(column.columnType)">
<file-upload :upload_number="column.fileNumber" :value.sync="column.columnValue"
:accept="column.fileType ? column.fileType.join(',') : ''"
upload_result_type="url"
complete_result upload_mode="drag"
upload_result_category="array"></file-upload>
</template>
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</div>
<div v-if="formData.mobileColumnsValue.length === 0" style="text-align: center">
<span style="font-size: 15px;color: grey">如有家属,请添加家属</span>
</div>
</el-form>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="signDialog = false">取 消</el-button>
<el-button @click="onSubmit" type="primary">提 交</el-button>
</el-row>
</el-dialog>
</div>
`,
store,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
signDialog: false,
formData: {
mobileColumnsValue: [],
},
courseRow: {},
courseTypeRow: {},
courseTimeSelectList: [],
active: '',
}
},
methods: {
addFamily() {
if(this.formData.mobileColumnsValue.length >= this.courseTypeRow.familyMaxCount) {
this.$message.warning('家属最多人数为' + this.courseTypeRow.familyMaxCount)
return
}
const list = clone(this.courseTypeRow.familyMobileSignColumnList)
this.formData.mobileColumnsValue.push(list)
},
delFamily() {
this.formData.mobileColumnsValue.splice(this.active, 1)
const ac = (Number(this.active) - 1)
this.active = '' + (ac >= 0 ? ac : 0)
},
validFamilyForm() {
for (let i = 0; i < this.formData.mobileColumnsValue.length; i++) {
const family = this.formData.mobileColumnsValue[i]
for (const familyColumn of family) {
if(familyColumn.isRequired && !familyColumn.columnValue) {
this.$message.error('家属' + (i + 1) + '的' + familyColumn.columnName + '不能为空')
return false
}
}
}
return true
},
async validSignUp() {
//获取家属是多少人
const res = await this.$axios.post("/platform/family/apply/validateSignUp", {
courseId: this.formData.courseId,
currentFamilyNumber: this.formData.mobileColumnsValue.length || 0
})
if (res.code !== 0) {
this.$message.warning(res.msg)
return false
}
return true
},
async validateSourceSignUp() {
if (this.courseRow.courseIsLimitApply) {
const resp = await $.post('/platform/family/apply/validateSourceSignUp', {
activityCourseId: this.formData.activityCourseId,
courseId: this.courseRow.id
})
if (resp.code !== 0) {
this.$message.warning(resp.msg)
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) {
this.initData(row, courseType)
if (row.courseIsLimitApply) {
await this.getCourseTimeSelectList(row)
}
if (courseType && courseType.familyMobileSignColumnList.length > 0) {
courseType.familyMobileSignColumnList.forEach(item => {
item.columnValue = ''
})
}
this.courseRow = row
this.courseTypeRow = courseType
this.signDialog = true
},
async 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 () => {
let array = []
this.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)
})
this.formData.mobileColumnsValue = JSON.stringify(clone(array))
const resp = await this.$axios.post("/platform/family/apply/doSignUp", this.formData)
if (resp.code === 0) {
this.signDialog = false
this.$message.success(resp.msg)
this.$emit('refresh')
} else {
this.$message.warning(resp.msg)
}
})
}
})
},
initData(row, courseType) {
this.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,
mobileColumnsValue: [],
}
this.$nextTick(() => {
this.$refs.form.clearValidate()
})
},
},
style: /*language=CSS*/ `
.el-tabs__header {
margin: 0 0 15px !important;
}
`
}
@@ -0,0 +1,601 @@
<!--#include('courseTime.js'){}#-->
<!--#include('customForm.js'){}#-->
const basicForm = {
template: /*language=HTML*/ `
<div>
<el-form :model="formData" label-width="110px" ref="form">
<div v-show="step === 1">
<el-row :gutter="20" type="flex" v-if="!formData.id">
<el-col :span="12">
<el-form-item label="沿用活动" prop="useHistory">
<el-radio-group v-model="formData.useHistory" size="small">
<el-radio :label="true" border>沿用之前活动</el-radio>
<el-radio :label="false" border>不沿用之前活动</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="formData.useHistory">
<el-form-item label="往期活动" prop="historicalAct">
<el-select v-model="formData.historicalAct" placeholder="请选择往期活动" style="width: 100%" @change="historicalActChange">
<el-option v-for="item in historicalActList" :key="item.id" :label="item.activityName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" type="flex">
<el-col :span="12">
<el-form-item :rules="{required:true,message: '请输入活动名称', trigger: 'blur'}" label="活动名称" prop="activityName">
<el-input maxlength="50" v-model="formData.activityName" placeholder="请输入活动名称"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="activityType + '通知'" prop="notice">
<el-radio-group v-model="formData.notice" size="small">
<el-radio :label="true" border>通知</el-radio>
<el-radio :label="false" border>不通知</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" type="flex">
<el-col :span="12">
<el-form-item :rules="{required:true,message: '请选择活动时间', trigger: 'blur'}" label="活动时间" prop="activityTime">
<el-date-picker
:picker-options="pickerOptions"
end-placeholder="请选择活动结束日期"
range-separator="至"
start-placeholder="请选择活动开始日期"
style="width: 100%"
type="datetimerange"
v-model="formData.activityTime"
value-format="yyyy-MM-dd HH:mm"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :rules="{required:true,message: '请选择报名时间', trigger: 'blur'}" label="报名时间" prop="activitySignTime">
<el-date-picker
:disabled="false"
end-placeholder="请选择报名结束日期"
range-separator="至"
start-placeholder="请选择报名开始日期"
style="width: 100%"
type="datetimerange"
v-model="formData.activitySignTime"
value-format="yyyy-MM-dd HH:mm"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" type="flex">
<el-col :span="12">
<el-form-item label="面向对象" prop="joinCnd">
<div style="display: flex; justify-content: space-between">
<div style="width: 99%">
<el-select
@change="changeActivity"
clearable
filterable
placeholder="请选择可报名人员范围"
style="width: 99%"
v-model="formData.activityGroupId"
>
<el-option
:key="item.groupId"
:label="item.groupName"
:value="item.groupId"
v-for="item in activityGroupList"
></el-option>
</el-select>
</div>
<div>
<el-button @click="$refs.drawerUserScope.userScopeDialog = true" type="primary">设置</el-button>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="活动类型" prop="trainType" :rules="{required:true,message: '请选择活动类型', trigger: 'blur'}">
<el-select @change="typeChange" filterable placeholder="请选择活动类型" style="width: 100%" v-model="formData.trainType">
<el-option :label="item.name" :value="item.code" :key="item.code" v-for="item in dict.type.FAMILY_SIGNUP_TYPE"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" type="flex">
<el-col :span="12">
<el-form-item :rules="{required:true,message: '请输入关键词', trigger: 'blur'}" label="关键词" prop="keyWord">
<el-input maxlength="50" v-model="formData.keyWord" placeholder="请输入关键词"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item :rules="{required:true,message: '请输入活动介绍', trigger: 'blur'}" label="活动介绍" prop="introduce">
<text-editor v-model="formData.introduce"></text-editor>
</el-form-item>
<el-form-item label="封面图片" prop="cover" :rules="{required:true,message: '请上传活动移动端封面图片', trigger: 'blur'}">
<el-col :span="12">
<file-upload
:upload_number="1"
:value.sync="formData.cover"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval"
></file-upload>
</el-col>
<el-col :span="12">
<el-form-item label="微信群二维码" prop="wechat">
<file-upload
:upload_number="1"
:value.sync="formData.wechat"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval"
></file-upload>
</el-form-item>
</el-col>
</el-form-item>
</div>
<div v-show="step === 2">
<div class="left-span-label" style="display: flex; justify-content: space-between; align-items: center">
<div>{{activityType + '信息(温馨提示:如不需要人数限制,下方人数框填写0或者不填)'}}</div>
<div>
<el-button @click="addCourse" size="mini" type="primary">添加{{ activityType }}</el-button>
</div>
</div>
<el-table :data="formData.courseList">
<el-table-column label="序号" prop="orderNum" width="100px">
<template v-slot="{row}">
<el-input-number
v-model="row.orderNum"
@change="courseOrderNumChange"
:min="1"
:max="formData.courseList.length"
:step="1"
step-strictly
:precision="0"
controls-position="right"
style="width: 76px"
size="small"
label="序号"
></el-input-number>
</template>
</el-table-column>
<el-table-column :label="activityType + '名称'" prop="courseName">
<template v-slot="{row}">
<el-input size="small" v-model="row.courseName" :placeholder="'请输入' + activityType + '名称'"></el-input>
</template>
</el-table-column>
<el-table-column label="类型" prop="courseType" sortable>
<template v-slot="{row}">
<el-select size="small" v-model="row.courseType" @change="(val) => {courseTypeChange(val, row)}">
<el-option :label="c.typeName" :value="c.id" :key="c.id" v-for="c in courseTypeList"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="activityType + '人数'" prop="coursePeopleNumber" width="110px">
<template v-slot="{row}">
<el-input-number
:controls="false"
:max="9999"
size="small"
:min="0"
:precision="0"
step-strictly
style="max-width: 80px"
@change="(c, o) => validNumber(row, null)"
v-model="row.coursePeopleNumber"
placeholder="请输入人数"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="预留名额" prop="courseReservedNumber" width="110px">
<template v-slot="{row}">
<el-input-number
:controls="false"
:max="1000"
:min="0"
size="small"
:precision="0"
step-strictly
style="max-width: 80px"
@change="(c, o) => validNumber(row, o)"
v-model="row.courseReservedNumber"
placeholder="请输入预留名额"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="校区" prop="campus">
<template v-slot="{row}">
<el-select size="small" v-model="row.campus" style="width: 100%">
<el-option :label="item.name" :value="item.name" :key="item.code" v-for="item in dict.type.CAMPUS"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="activityType + '地点'" prop="courseLocation">
<template v-slot="{row}">
<el-input size="small" v-model="row.courseLocation" :placeholder="'请输入' + activityType + '地点'"></el-input>
</template>
</el-table-column>
<el-table-column :label="activityType + '负责人'" prop="courseInstructor">
<template v-slot="{row}">
<el-input size="small" v-model="row.courseInstructor" :placeholder="'请输入' + activityType + '负责人'"></el-input>
</template>
</el-table-column>
<el-table-column label="分类标识" prop="assort">
<template v-slot="{row}">
<el-input size="small" v-model="row.assort" placeholder="请输入分类标识"></el-input>
</template>
</el-table-column>
<el-table-column :label="activityType + '时间'">
<template v-slot="scope">
<el-button @click="openSetUpCourseTime(scope.$index)" type="text">设置{{ activityType }}时间</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template v-slot="scope">
<el-button @click="openMoreInfo(scope, scope.$index)" size="mini" type="primary">自定义设置</el-button>
<el-button @click="removeTableCourse(scope.$index)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="left-span-label" style="margin-top: 20px">个人报名数量限制</div>
<el-form-item label="报名数量限制" prop="cover">
<el-radio-group v-model="formData.restrictLimit" size="small">
<el-radio border :label="1">无限制</el-radio>
<el-radio border :label="2">按类型限制</el-radio>
<el-radio border :label="3">按活动限制</el-radio>
</el-radio-group>
<div calss="limit_div">
<div v-if="formData.restrictLimit === 1" style="color: #c64120; font-size: 12px">注:无限制指对报名的个数不做任何限制</div>
<div v-if="formData.restrictLimit === 2" style="color: #c64120; font-size: 12px">
<div>
<el-button @click="setSignUpCondition" size="mini" type="primary">设置报名限制</el-button>
<span style="color: #c64120; font-size: 12px">注:按类型限制指对每个类型下的个数进行限制</span>
</div>
</div>
<div v-if="formData.restrictLimit === 3">
<div>
<el-input-number v-model="formData.limitNum" :min="1" :max="100" size="mini" label="限制报名个数"></el-input-number>
<span style="color: #c64120; font-size: 12px">注:按活动限制指只能报{{formData.limitNum}}个,不与类型关联</span>
</div>
</div>
</div>
</el-form-item>
</div>
</el-form>
<div style="float: right; margin: 20px 0">
<el-button @click="$emit('back')">取消</el-button>
<el-button v-if="step === 2" type="primary" @click="step = 1">上一步</el-button>
<el-button v-if="step === 1" type="primary" @click="step = 2">下一步</el-button>
<el-button type="primary" @click="onSave">保存</el-button>
<el-button type="primary" @click="onSubmit">提交</el-button>
</div>
<el-dialog :close-on-click-modal="false" :visible.sync="signUpDialog" title="设置报名限制" append-to-body>
<el-table :data="formData.typeLimits">
<el-table-column prop="code" label="类型编码"></el-table-column>
<el-table-column prop="typeName" label="类型名称"></el-table-column>
<el-table-column label="限制个数" width="500">
<template v-slot="{row}">
<el-input-number size="small" v-model="row.limitNum" :min="0"></el-input-number>
<span style="color: #c64120; font-size: 12px">注:值为空或者为0则表示此类型不限制报名个数</span>
</template>
</el-table-column>
</el-table>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="signUpDialog = false">取消</el-button>
<el-button @click="doLimitNum" type="primary">确定</el-button>
</el-row>
</el-dialog>
<drawer-user-scope
@group_change="getActivityGroup"
ref="drawerUserScope"
:group_id.sync="formData.activityGroupId">
</drawer-user-scope>
<course-time ref="courseTimeRef"></course-time>
<custom-form ref="customFormRef"></custom-form>
</div>
`,
dicts: ["CAMPUS", "FAMILY_SIGNUP_TYPE"],
data() {
return {
signUpDialog: false,
step: 1,
formData: {
notice: false,
courseList: [
{ orderNum: 1, courseName: "", courseTimeList: [], isMobileSign: false, isReceiveGift: false, reserveMode: 1, courseIsLimitApply: false },
],
conditionStructure: {
method: "AND",
conditions: [{}]
},
restrictLimit: 1,
limitNum: 1,
typeLimits: [],
keyWord: '家属'
},
historicalActList: [],
trainTypeList: [],
activityType: "子活动",
activityGroupList: [],
pickerOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - 24 * 60 * 60 * 1000
}
},
campusList: [],
courseTypeList: [],
}
},
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue"),
"course-time": courseTime,
"custom-form": customForm,
},
methods: {
async validNumber(row, old) {
if (GetQueryString("id") === "") {
if (row.courseReservedNumber > row.coursePeopleNumber && row.reserveMode === 1) {
this.$alert("预留人数不能大于" + this.activityType + "人数!", "提示", {
confirmButtonText: "确定"
})
row.courseReservedNumber = 0
}
} else {
const registerUserCount = await this.getRegisterUserCount(row.id)
if (row.courseReservedNumber + registerUserCount > row.coursePeopleNumber) {
if (row.reserveMode === 1) {
let num = row.coursePeopleNumber - registerUserCount
let str =
"您设置的" +
this.activityType +
"人数为" +
row.coursePeopleNumber +
"人,当前报名人数为" +
registerUserCount +
"人,预留名额上限为" +
num +
"人!"
this.$alert(str, "提示", {
confirmButtonText: "确定"
})
row.courseReservedNumber = 0
}
}
}
},
async getRegisterUserCount(courseId) {
const resp = await this.$axios.post("/platform/family/manage/getRegisterUserCount", { courseId })
return resp.code === 0 ? resp.data : 0
},
courseTypeChange(val, row) {
row.reserveMode = 1
},
setSignUpCondition() {
if (this.formData.courseList !== undefined && this.formData.courseList.length > 0) {
const courseValid = this.formData.courseList.some((item, index) => {
if (item.courseType === undefined) {
this.$message.warning("请在" + (index + 1) + "行" + this.activityType + "信息中选择类型!")
return true
}
return false
})
if (courseValid) {
return
}
const array = [...new Set(this.formData.courseList.map((o) => o.courseType))]
const typeArray = clone([...this.courseTypeList].filter((x) => array.some((y) => x.id === y)))
const arr = []
typeArray.forEach((item) => {
const t = this.formData.typeLimits.find(o => o.typeId === item.id)
if (t) {
const type = this.courseTypeList.find(o => o.id === t.typeId)
t.code = type.code
t.typeName = type.typeName
arr.push(t)
} else {
arr.push({
code: item.code,
typeName: item.typeName,
typeId: item.id,
limitNum: 0
})
}
})
this.formData.typeLimits = arr
this.signUpDialog = true
} else {
this.$message.warning("请在" + this.activityType + "信息中选择类型!")
}
},
doLimitNum() {
this.signUpDialog = false
},
openMoreInfo(row, index) {
this.$refs.customFormRef.onOpen(this.formData, row, index)
},
async removeTableCourse(index) {
const confirm = await this.$confirm(
"请确认是否删除此" + this.activityType + "?如果该" + this.activityType + "已有报名人员,会随之一起删除!确定吗?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}
)
if (confirm) {
this.formData.courseList.splice(index, 1)
}
},
openSetUpCourseTime(index) {
this.$refs.courseTimeRef.onOpen(this.formData, index)
},
courseOrderNumChange() {
this.formData.courseList = this.formData.courseList.sort((a, b) => a.orderNum - b.orderNum)
},
addCourse() {
this.formData.courseList.push({ courseTimeList: [], isMobileSign: false, isReceiveGift: false, reserveMode: 1, courseIsLimitApply: false })
},
async historicalActChange(val) {
const resp = await this.$axios.post("/platform/family/manage/findOne", {id: val})
if (resp.code === 0) {
this.formData = resp.data
this.typeChange(this.formData.trainType)
this.formData.id = ""
}
},
typeChange(val) {
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find(o => o.code === val)
this.activityType = type?.name || "子活动"
},
changeActivity(val) {
if (val) {
const group = this.activityGroupList.find(v => v.groupId === val)
this.$set(this.formData, "activityGroupName", group.groupName)
} else {
this.$set(this.formData, "activityGroupName", "全部教职工")
}
},
async getActivityGroup() {
const { data } = await this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
return data
},
async getHistoricalActList() {
const resp = await this.$axios.post("/platform/family/manage/getHistoricalActList", {})
return resp.data
},
async onSave() {
let valid = false
this.$refs.form.validateField("activityName", (errMsg) => {
valid = errMsg === ""
})
if (!valid) {
this.$message.warning("请输入活动名称")
return
}
await this.doHandle('保存')
},
onSubmit() {
this.$refs["form"].validate(async (valid, errMsg) => {
if (valid) {
const courseValid = this.formData.courseList.some((v, i) => {
const basicValid = v.courseName && v.coursePeopleNumber && v.courseLocation && v.courseInstructor && v.courseType
const timeValid =
v.courseTimeList.length > 0 &&
v.courseTimeList.every((x) => {
return x.courseStartTime && x.courseEndTime && x.courseStartTime < x.courseEndTime
})
if (!timeValid) {
this.$message.warning("第" + (i + 1) + "行时间填写有误,请核查")
return true
}
if (!basicValid) {
this.$message.warning("第" + (i + 1) + "行信息填写有误,请核查")
return true
}
if (v.isMobileSign === true && v.signType === 3 && (v.courseLocationCoordinates === undefined || v.courseLocationCoordinates.length < 2)) {
this.$message.warning("第" + (i + 1) + "行地点坐标填写有误,请核查")
return true
}
if (v.isMobileSign === true && v.signType === 3 && (v.signType === "" || v.signType === undefined)) {
this.$message.warning("第" + (i + 1) + "行签到方式填写有误,请核查")
return true
}
return false
})
if (courseValid) return
if (this.formData.restrictLimit === 2 && this.formData.typeLimits.length === 0) {
this.$message.warning("请设置报名限制")
return
}
this.formData.isDisabled = false
await this.doHandle('提交')
} else {
if(Object.keys(errMsg).length > 0) {
this.$message.warning(errMsg[Object.keys(errMsg)[0]][0].message)
return
}
this.$message.warning("存在必填项未填写")
}
})
},
async doHandle(type) {
const cloneData = clone(this.formData)
cloneData.activitySignUpStartTime = cloneData.activitySignTime !== undefined ? cloneData.activitySignTime[0] : null
cloneData.activitySignUpEndTime = cloneData.activitySignTime !== undefined ? cloneData.activitySignTime[1] : null
cloneData.activityStartTime = cloneData.activityTime !== undefined ? cloneData.activityTime[0] : null
cloneData.activityEndTime = cloneData.activityTime !== undefined ? cloneData.activityTime[1] : null
if (cloneData.activityStartTime !== undefined && cloneData.activityStartTime !== null) {
cloneData.year = new Date(cloneData.activityStartTime).getFullYear()
}
cloneData.typeLimits = JSON.stringify(this.formData.typeLimits)
cloneData.courseList = JSON.stringify(cloneData.courseList)
const confirm = await this.$confirm("您确定要" + type + "吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if (confirm !== "confirm") return
const resp = await this.$axios.post("/platform/family/manage/doHandle", cloneData)
if (resp.code === 0) {
this.$message.success(resp.msg)
this.step = 1
this.$emit('refresh')
this.$emit('back')
} else {
this.$message.warning(resp.msg)
}
},
async getAllType() {
const resp = await this.$axios.post("/platform/family/type/getAllType", {})
return resp.data
},
async init(row) {
if (row && row.id) {
const resp = await this.$axios.post("/platform/family/manage/findOne", {id: row.id})
if (resp.code === 0) {
this.formData = resp.data
this.typeChange(this.formData.trainType)
}
}
},
async initData(row) {
this.activityGroupList = await this.getActivityGroup()
this.historicalActList = await this.getHistoricalActList()
this.courseTypeList = await this.getAllType()
await this.init(row)
},
},
watch: {
'formData.restrictLimit'(val) {
if (!this.formData.id) {
this.formData.limitNum = val === 3 ? 1 : ''
}
},
'formData.activityGroupId': {
async handler(newVal, oldVal) {
this.activityGroupList = await this.getActivityGroup()
this.userScopeDialog = false
},
deep: true
},
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,215 @@
const courseTime = {
template: /*language=HTML*/ `
<div>
<el-dialog :close-on-click-modal="false" :visible.sync="setUpCourseDialog" title="设置时间" width="60%" append-to-body>
<div style="color: red; margin-bottom: 10px">温馨提示:有人员报名后,禁止修改下列时间,如有修改,请联系管理员</div>
<div class="left-span-label">选择日期</div>
<el-date-picker
v-if="formData.courseList && formData.courseList[courseIndex]"
:picker-options="setUpCoursePickerOptions"
@change="setUpCourseDataChange"
placeholder="选择一个或多个日期"
style="width: 100%"
type="dates"
v-model="formData.courseList[courseIndex].setUpCourseData"
value-format="yyyy-MM-dd"
></el-date-picker>
<div class="left-span-label">设置时间</div>
<el-row style="margin-bottom: 10px">
<el-time-select
:picker-options="{
start: '08:30',
step: '00:05',
end: '23:30'
}"
placeholder="开始时间"
size="mini"
v-model="courseTimeOneKeySet.startTime"
></el-time-select>
<el-time-select
:picker-options="{
start: '08:30',
step: '00:05',
end: '23:30'
}"
placeholder="结束时间"
size="mini"
v-model="courseTimeOneKeySet.endTime"
></el-time-select>
<el-button @click="oneKeySetStartEndTime" size="mini" type="primary">一键设置开始/结束时间</el-button>
<span style="color: #ac3111">报名人数是否限制:</span>
<el-radio-group size="mini" v-model="formData.courseList[courseIndex].courseIsLimitApply">
<el-radio-button :label="true">是</el-radio-button>
<el-radio-button :label="false">否</el-radio-button>
</el-radio-group>
</el-row>
<el-table v-if="formData.courseList && formData.courseList[courseIndex]"
:data="formData.courseList[courseIndex].courseTimeList">
<el-table-column label="日期">
<template v-slot="{row}">
<i class="el-icon-time"></i>
{{$moment(row.courseDate).format('YYYY-MM-DD')}}
</template>
</el-table-column>
<el-table-column label="开始时间">
<template v-slot="{row}">
<el-time-select
:picker-options="{
start: '08:30',
step: '00:05',
end: '23:30'
}"
placeholder="开始时间"
style="width: 100%"
v-model="row.courseStartTime"
></el-time-select>
</template>
</el-table-column>
<el-table-column label="结束时间">
<template v-slot="{row}">
<el-time-select
:picker-options="{
start: '08:30',
step: '00:05',
end: '23:30'
}"
placeholder="结束时间"
style="width: 100%"
v-model="row.courseEndTime"
></el-time-select>
</template>
</el-table-column>
<template v-if="formData.courseList[courseIndex].courseIsLimitApply">
<el-table-column label="人数限制">
<template v-slot="{row}">
<el-input-number v-model="row.courseLimitNum" :min="1" :max="1000"
style="width: 100%"
placeholder="限制报名个数"></el-input-number>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="200">
<template v-slot="{row,$index}">
<el-button
@click="formData.courseList[courseIndex].courseTimeList.splice($index,0,{courseDate:row.courseDate,courseStartTime:'',courseEndTime:''})"
size="small"
>
新增同天时段
</el-button>
<el-button @click="removeSetUpTableCourseRow(row,$index)" size="small" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-row class="mt20" justify="end" type="flex">
<el-button @click="doConfirmSetUpCourse" type="primary">确定</el-button>
</el-row>
</el-dialog>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
const that = this
return {
setUpCourseDialog: false,
courseIndex: 0,
formData: {},
courseTimeOneKeySet: {
startTime: "",
endTime: ""
},
setUpCoursePickerOptions: {
disabledDate(time) {
return (
time.getTime() < Date.parse(that.$moment(that.formData.activityTime[0]).format("YYYY-MM-DD") + " 00:00:00") ||
time.getTime() > Date.parse(that.$moment(that.formData.activityTime[1]).format("YYYY-MM-DD") + " 00:00:00")
)
}
},
}
},
methods: {
removeSetUpTableCourseRow(row, index) {
this.formData.courseList[this.courseIndex].courseTimeList.splice(index, 1)
//培训时间列表日期数组
const courseDateArray = this.formData.courseList[this.courseIndex].courseTimeList.map((v) =>
this.$moment(v.courseDate).format("YYYY-MM-DD")
)
//选择课程日期数组
const scdList = this.formData.courseList[this.courseIndex].setUpCourseData
this.formData.courseList[this.courseIndex].setUpCourseData = scdList.filter((v) => {
return courseDateArray.includes(this.$moment(v).format("YYYY-MM-DD"))
})
},
oneKeySetStartEndTime() {
const { startTime, endTime } = this.courseTimeOneKeySet
this.formData.courseList[this.courseIndex].courseTimeList.forEach((v) => {
this.$set(v, "courseStartTime", startTime)
this.$set(v, "courseEndTime", endTime)
})
this.$forceUpdate()
},
setUpCourseDataChange(val) {
if (!val) {
this.formData.courseList[this.courseIndex].courseTimeList = []
return
}
//培训时间日期Set
if (this.formData.courseList[this.courseIndex].courseTimeList === undefined) {
this.$set(this.formData.courseList[this.courseIndex], "courseTimeList", [])
}
const ctList = new Set(
this.formData.courseList[this.courseIndex].courseTimeList.map((v) => this.$moment(v.courseDate).format("YYYY-MM-DD"))
)
val.forEach((v) => {
if (!ctList.has(v)) {
this.formData.courseList[this.courseIndex].courseTimeList.push({
courseDate: v,
courseTime: null
})
}
})
this.formData.courseList[this.courseIndex].courseTimeList = this.formData.courseList[this.courseIndex].courseTimeList.filter((v) => {
return val.includes(this.$moment(v.courseDate).format("YYYY-MM-DD"))
})
this.formData.courseList[this.courseIndex].courseTimeList.sort((a, b) => {
return Date.parse(a["courseDate"]) - Date.parse(b["courseDate"])
})
},
//设置课程时间确定
doConfirmSetUpCourse() {
const courseTableData = this.formData.courseList[this.courseIndex].courseTimeList
if (courseTableData.length === 0) {
this.$message.warning("请填写时间!")
return
}
if (courseTableData && courseTableData.length > 0) {
const valid = courseTableData.every((v) => v.courseStartTime && v.courseEndTime && v.courseStartTime < v.courseEndTime)
if (!valid) {
this.$message.warning("时间填写不完整或者有误!")
return
}
this.setUpCourseDialog = false
}
},
onOpen(formData, index) {
this.formData = formData
this.courseIndex = index
if (!this.formData.activityTime || this.formData.activityTime.length === 0) {
this.$message.warning("请先设置活动起止时间")
return
}
this.courseIndex = index
this.setUpCourseDialog = true
},
},
created() {
if(!this.formData.courseList) {
this.formData = {courseList: [{unionLimit: []}]}
}
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,221 @@
<!--#include('unionForm.js'){}#-->
const customForm = {
template: /*language=HTML*/ `
<div>
<el-drawer title="自定义设置" size="50%" class="my-drawer" :visible.sync="moreInfoDrawer" append-to-body>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>分工会人数限制</span>
</el-col>
<el-col :span="20">
<el-button @click="openSetUpUnionLimit(moreInfoIndex)" type="primary" size="small">
设置分工会人数限制
</el-button>
<span
v-if="formData.courseList[moreInfoIndex].unionLimit && formData.courseList[moreInfoIndex].unionLimit.length > 0"
class="text-success"
>
已设置
</span>
<span v-else class="text-danger">未设置</span>
</el-col>
</el-row>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>承办工会</span>
</el-col>
<el-col :span="20">
<el-select v-model="formData.courseList[moreInfoIndex].hostUnionId" placeholder="请选择承办工会"
style="width: 100%" clearable>
<el-option :label="item.name" :value="item.id" :key="item.id"
v-for="item in unionList"></el-option>
</el-select>
</el-col>
</el-row>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>对内报名时间</span>
</el-col>
<el-col :span="20">
<el-date-picker
placeholder="请选择对内报名时间"
style="width: 100%"
type="datetime"
v-model="formData.courseList[moreInfoIndex].interTime"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm"
></el-date-picker>
</el-col>
</el-row>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>是否签到</span>
</el-col>
<el-col :span="20">
<el-radio-group v-model="formData.courseList[moreInfoIndex].isMobileSign" size="small">
<el-radio border :label="true">签到</el-radio>
<el-radio border :label="false">不签到</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row v-if="formData.courseList[moreInfoIndex].isMobileSign === true" :gutter="50" type="flex">
<el-col :span="4">
<span>签到方式</span>
</el-col>
<el-col :span="20">
<el-radio-group v-model="formData.courseList[moreInfoIndex].signType" size="small">
<el-radio border :label="1">扫描二维码</el-radio>
<el-radio border :label="2">被扫</el-radio>
<el-radio border :label="3">GPS定位签到</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row
v-if="formData.courseList[moreInfoIndex].isMobileSign === true
&& formData.courseList[moreInfoIndex].signType == 3"
:gutter="50"
type="flex"
>
<el-col :span="4">
<span>地点坐标</span>
</el-col>
<el-col :span="20">
<el-button @click="openMap(moreInfoIndex)" type="primary" size="small">设置签到点</el-button>
</el-col>
</el-row>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>是否领取礼品</span>
</el-col>
<el-col :span="20">
<el-radio-group v-model="formData.courseList[moreInfoIndex].isReceiveGift" size="small">
<el-radio border :label="true">领取</el-radio>
<el-radio border :label="false">不领取</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row v-if="formData.courseList[moreInfoIndex].isReceiveGift === true" :gutter="50" type="flex">
<el-col :span="4">
<span>领取方式</span>
</el-col>
<el-col :span="20">
<el-radio-group v-model="formData.courseList[moreInfoIndex].giftType" size="small">
<el-radio border :label="1">扫描二维码</el-radio>
<el-radio border :label="2">面对面确认</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>报名方式</span>
</el-col>
<el-col :span="20">
<el-radio-group
v-model="formData.courseList[moreInfoIndex].reserveMode"
size="small"
>
<el-radio border :label="1">报名人员减少模式</el-radio>
<el-radio border :label="2">候补模式</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row :gutter="50" type="flex" v-if="formData.courseList[moreInfoIndex].reserveMode === 2">
<el-col :span="4">
<span>候补数量</span>
</el-col>
<el-col :span="20">
<el-input-number :controls="false" size="small" style="width: 100%"
:max="100"
:min="0"
:precision="0"
step-strictly
v-model="formData.courseList[moreInfoIndex].waitingNum"></el-input-number>
</el-col>
</el-row>
<el-row :gutter="50" type="flex">
<el-col :span="4">
<span>详细信息</span>
</el-col>
<el-col :span="20">
<text-editor v-model="formData.courseList[moreInfoIndex].introduce"></text-editor>
</el-col>
</el-row>
<div style="text-align: right">
<el-button @click="moreInfoDrawer = false">取 消</el-button>
<el-button type="primary" @click="moreInfoDrawer = false">保 存</el-button>
</div>
</el-drawer>
<el-dialog :close-on-click-modal="false" :visible.sync="mapDialog" title="位置信息" :append-to-body="true">
<map-container v-if="mapDialog" :position.sync="formData.courseList[mapIndex].courseLocationCoordinates"></map-container>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="mapDialog = false">确 定</el-button>
</span>
</el-dialog>
<union-form ref="unionFormRef"></union-form>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
moreInfoDrawer: false,
unionList: [],
formData: {},
moreInfoRow: {},
moreInfoIndex: 0,
mapDialog: false,
mapIndex: 0,
}
},
components: {
"union-form": unionForm,
"map-container": httpVueLoader("/components/plugins/mapContainer/MapContainer.vue?v=1.0.1")
},
methods: {
openMap(index) {
this.mapIndex = index
this.mapDialog = true
},
openSetUpUnionLimit(index) {
this.$refs.unionFormRef.onOpen(this.formData, index)
},
async onOpen(formData, chooseRow, index) {
this.formData = formData
this.moreInfoRow = chooseRow
this.moreInfoIndex = index
this.unionList = await this.$businessTool.listUnion()
this.moreInfoDrawer = true
},
},
created() {
if(!this.formData.courseList) {
this.formData = {courseList: [{unionLimit: []}]}
}
},
style: /*language=CSS*/ `
.my-drawer .el-col-4 {
text-align: right;
}
.my-drawer .el-row {
margin-bottom: 20px;
}
.el-drawer__body {
padding: 20px;
}
.el-row--flex {
align-items: center;
}
`
}
@@ -0,0 +1,200 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
</style>
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度:">
<el-date-picker
@change="doSearch"
placeholder="请选择年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="活动名称:">
<el-input
@keyup.enter.native="doSearch"
clearable
placeholder="请输入活动名称"
clearable
style="width: 100%"
v-model="pageForm.activityName"
></el-input>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="活动列表">
<el-button @click="openAdd" size="small" type="primary">
<i class="ti-plus"></i>新建活动
</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:width="column.width"
:sortable="column.sortable"
show-overflow-tooltip
v-for="(column, index) in tableColumns"
:key="index"
>
<template v-slot="{ row }" v-if="column.prop === 'isDisabled'">
<el-switch
:active-value="false"
:inactive-value="true"
@change="(val) => { activityStatusChange(row.notice, val,row.id) }"
active-color="#13ce66"
inactive-color="#ff4949"
v-model="row.isDisabled"
></el-switch>
</template>
<template v-slot="{ row: { createdAt } }" v-else-if="column.prop === 'createdAt'">
{{ $moment(createdAt).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template v-slot="{ row }" v-else-if="column.prop === 'activityTime'">
<span>{{ $moment(row.activityStartTime).format('YYYY/MM/DD') }}</span>
<span></span>
<span>{{ $moment(row.activityEndTime).format('YYYY/MM/DD') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150px">
<template v-slot="{ row }">
<el-dropdown>
<el-button plain size="mini">
<i class="ti-settings"></i>
<span class="ti-angle-down"></span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="openActivityCode(row.id)">活动二维码</el-dropdown-item>
<el-dropdown-item @click.native="makeCode(row)">签到二维码</el-dropdown-item>
<el-dropdown-item @click.native="onView(row)">查看</el-dropdown-item>
<el-dropdown-item @click.native="openEdit(row)">编辑</el-dropdown-item>
<el-dropdown-item @click.native="onDelete(row)">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<basic-form ref="formRef" @back="back" @refresh="doSearch"></basic-form>
</template>
<template #view>
<info ref="infoRef"></info>
</template>
<make-qrcode ref="codeRef"></make-qrcode>
<el-dialog
title="活动二维码"
:visible.sync="codeDialogVisible"
:close-on-click-modal="false"
width="30%">
<div style=" display: flex;justify-content: center;">
<qrcode :options="{ width: 400 }" :value="activityUrl" ></qrcode>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="codeDialogVisible = false" type="primary">关 闭</el-button>
</span>
</el-dialog>
</guava>
</div>
<script>
<!--#include('info.js'){}#-->
<!--#include('basicForm.js'){}#-->
<!--#include('makeQrcode.js'){}#-->
new Vue({
el: "#app",
store,
dicts: ["CAMPUS", "FAMILY_SIGNUP_TYPE"],
mixins: [initTableMixins],
components: {
"info": info,
"basic-form": basicForm,
"make-qrcode": makeQrcode,
},
data() {
return {
pageForm: {
year: new Date().getFullYear() + ''
},
tableColumns: [
{ label: "活动名称", prop: "activityName", width: 600 },
{ label: "活动时间", prop: "activityTime" },
{ label: "是否开启", prop: "isDisabled", width: 100 },
{ label: "创建时间", prop: "createdAt" }
],
codeDialogVisible: false,
activityUrl: '',
}
},
methods: {
back() {
this.$refs.guava.index()
},
openAdd() {
this.$refs.guava.edit(() => {
this.$refs.formRef.initData()
})
},
openEdit(row) {
this.$refs.guava.edit(() => {
this.$refs.formRef.initData(row)
})
},
onView(row) {
this.$refs.guava.view(() => {
this.$refs.infoRef.initData(row.id)
})
},
openActivityCode(id) {
this.activityUrl = location.origin + "/platform/family/apply/h5?id=" + id
this.codeDialogVisible = true
},
makeCode(row) {
this.$refs.codeRef.initData(row)
},
async activityStatusChange(notice, val, id) {
const resp = await this.$axios.post(loc() + "/activityStatusChange", { notice: notice, isDisabled: val, id })
if (resp.code === 0) {
this.$message.success(resp.msg)
} else {
this.$message.warning(resp.msg)
}
},
async onDelete(row) {
this.$confirm("此操作将永久删除, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post(loc() + "/onDelete", { id: row.id })
this.$message.success(resp.msg)
this.doSearch()
}).catch(() => {})
},
},
async created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,111 @@
const info = {
template: /*language=HTML*/ `
<div>
<el-tabs>
<el-tab-pane label="基础信息">
<el-descriptions :column="2" border class="table_fixed mt20">
<el-descriptions-item :span="2" label="活动名称">{{viewData.activityName}}</el-descriptions-item>
<el-descriptions-item label="年度">{{viewData.year}}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{$moment(viewData.createdAt).format('YYYY-MM-DD HH:mm:ss')}}</el-descriptions-item>
<el-descriptions-item label="活动开始时间">{{viewData.activityStartTime}}</el-descriptions-item>
<el-descriptions-item label="活动结束时间">{{viewData.activityEndTime}}</el-descriptions-item>
<el-descriptions-item label="报名开始时间">{{viewData.activitySignUpStartTime}}</el-descriptions-item>
<el-descriptions-item label="报名结束时间">{{viewData.activitySignUpEndTime}}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane :label="activityType + '信息'">
<el-table :data="viewData.courseList" class="mt20">
<el-table-column :label="activityType + '名称'" prop="courseName"></el-table-column>
<el-table-column label="类型" prop="courseTypeName"></el-table-column>
<el-table-column label="人数" prop="coursePeopleNumber"></el-table-column>
<el-table-column label="预留名额" prop="courseReservedNumber"></el-table-column>
<el-table-column label="地点" prop="courseLocation">
<template v-slot="{row}">
<el-button @click="openViewMap(row.courseLocationCoordinates)" type="text">{{row.courseLocation}}</el-button>
</template>
</el-table-column>
<el-table-column label="校区" prop="campus"></el-table-column>
<el-table-column label="负责人" prop="courseInstructor"></el-table-column>
<el-table-column label="上课时间" prop="courseTimeList" width="200px">
<template v-slot="{ row }">
<el-button @click="openViewCourseTime(row.courseTimeList)" type="text">点击查看课程时间</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<el-dialog :visible.sync="courseTimeListDialog" :title="activityType + '时间'" width="60%" append-to-body>
<el-table :data="courseTimeList">
<el-table-column label="日期" prop="courseDate">
<template v-slot="{row}">{{$moment(row.courseDate).format('YYYY-MM-DD')}}</template>
</el-table-column>
<el-table-column label="开始时间" prop="courseStartTime"></el-table-column>
<el-table-column label="结束时间" prop="courseEndTime"></el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="courseTimeListDialog = false" type="primary">关 闭</el-button>
</span>
</el-dialog>
<el-dialog :visible.sync="viewMapDialog" title="地点" width="60%">
<div id="viewMap" style="width: 100%; height: 500px"></div>
</el-dialog>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
activityType: '',
viewData: {},
courseTimeListDialog: false,
courseTimeList: [],
viewMapDialog: false,
}
},
methods: {
initData(id) {
this.$axios.post("/platform/family/manage/findOne", { id: id })
.then((resp) => {
if (resp.code === 0) {
this.viewData = resp.data
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find((o) => o.code === resp.data.trainType)
this.activityType = type?.name || "子活动"
}
})
},
openViewCourseTime(courseTimeList) {
this.courseTimeList = courseTimeList
this.courseTimeListDialog = true
},
openViewMap(point) {
if (!Array.isArray(point)) {
this.$message.warning("没有设置点位,无法通过地图查看")
return
}
this.viewMapDialog = true
this.$nextTick(() => {
viewMap = new AMap.Map("viewMap", {
resizeEnable: true,
center: point,
zoom: 16
})
if (viewMarker) {
viewMap.remove(viewMarker)
}
viewMarker = new AMap.Marker({
position: point,
offset: new AMap.Pixel(-13, -30)
})
viewMap.add(viewMarker)
viewMap.setFitView(null, false, [150, 60, 100, 60])
})
},
},
style: /*language=CSS*/ `
.el-button--text {
padding: 0;
}
`
}
@@ -0,0 +1,84 @@
const makeQrcode = {
template: /*language=HTML*/ `
<div>
<el-dialog :close-on-click-modal="false" :visible.sync="courseDialog"
append-to-body :title="activityType + '列表'" width="60%">
<el-table :data="courseList">
<el-table-column :label="activityType + '名称'" prop="courseName"></el-table-column>
<el-table-column label="校区" prop="campus"></el-table-column>
<el-table-column label="是否签到" prop="isMobileSign">
<template v-slot="{row}">
<span v-if="row.isMobileSign === true">签到</span>
<span v-else>不签到</span>
</template>
</el-table-column>
<el-table-column label="签到方式" prop="signType">
<template v-slot="{row}">
<span v-if="row.signType === 1">扫描二维码</span>
<span v-else-if="row.signType === 2">被扫</span>
<span v-else-if="row.signType === 3">GPS定位签到</span>
<span v-else>暂无签到方式</span>
</template>
</el-table-column>
<el-table-column label="是否领取礼品" prop="isReceiveGift">
<template v-slot="{row}">
<span v-if="row.isReceiveGift === true">领取</span>
<span v-else>不领取</span>
</template>
</el-table-column>
<el-table-column label="领取方式" prop="giftType">
<template v-slot="{row}">
<span v-if="row.giftType === 1">扫描二维码</span>
<span v-else-if="row.giftType === 2">面对面确认</span>
<span v-else>暂无领取方式</span>
</template>
</el-table-column>
<el-table-column label="操作" width="260">
<template v-slot="{row}">
<el-button v-if="row.signType === 1"
@click="makeCourseCode(row)"
size="mini" type="primary">
生成二维码
</el-button>
<el-button v-else size="mini" type="text">该{{activityType}}的签到方式不支持生成二维码</el-button>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="courseDialog = false" type="primary">关 闭</el-button>
</span>
</el-dialog>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
courseDialog: false,
activityType: "",
courseList: [],
}
},
methods: {
initData(row) {
this.courseList = row.courseList
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find((o) => o.code === row.trainType)
this.activityType = type?.name || "子活动"
this.courseDialog = true
},
makeCourseCode(row) {
const url = '/platform/family/mine/drivingScan'
const data = url + '?courseId=' + row.id
console.log(data)
const content = jrQrcode.getQrBase64(data)
let image = new Image()
image.src = content
let viewer = new Viewer(image, {
zIndex: 99999999,
})
viewer.show()
},
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,221 @@
const unionForm = {
template: /*language=HTML*/ `
<div>
<el-dialog :close-on-click-modal="false" :visible.sync="setUpUnionLimitDialog" title="分工会人数限制" top="50px" append-to-body>
<div class="left-span-label" style="display: flex; justify-content: space-between; align-items: center">
<div>分段设置</div>
<div>
<el-button size="small" type="primary" @click="unionLimitQuickSetting">应用分段设置</el-button>
</div>
</div>
<el-row v-for="(item,index) in unionUserNumCalc" :key="index" :gutter="20" class="mb5">
<el-col :span="7">
<el-input-number
v-model="item.startNum"
:precision="0"
:step="1"
:min="1"
placeholder="请输入最小人数"
style="width: 100%"
></el-input-number>
</el-col>
<el-col :span="7">
<el-input-number
v-model="item.endNum"
:precision="0"
:step="1"
:min="1"
placeholder="请输入最大人数"
style="width: 100%"
></el-input-number>
</el-col>
<el-col :span="6">
<el-input-number
v-model="item.resultNum"
:precision="0"
:step="1"
:min="1"
placeholder="请输入限制人数"
style="width: 100%"
></el-input-number>
</el-col>
<el-col :span="4" class="text-right">
<el-button icon="el-icon-plus" @click="unionUserNumCalc.push({})"></el-button>
<el-button icon="el-icon-minus" @click="unionUserNumCalc.splice(index,1)" :disabled="unionUserNumCalc.length===1"></el-button>
</el-col>
</el-row>
<el-row class="mt10">
<div v-for="(item,index) in unionUserNumCalcTips" class="text-danger">
<span class="mr10">({{index+1}}).</span>
{{item}}
</div>
</el-row>
<div class="left-span-label" style="display: flex; justify-content: space-between; align-items: center">
<div>比例设置</div>
<div>
一键比例:
<el-input-number
v-model="unionUserNumOneKeyRatio"
:precision="0"
:step="1"
:min="0"
placeholder="请输入一键比例"
size="small"
:max="100"
></el-input-number>
<el-button size="small" type="primary" @click="applyScaleSettings" class="ml5">应用比例设置</el-button>
</div>
</div>
<el-table :data="formData.courseList[unionLimitIndex].unionLimit" max-height="500px">
<el-table-column prop="name" label="分工会"></el-table-column>
<el-table-column prop="teacherCount" label="人数"></el-table-column>
<el-table-column prop="ratio" label="比例(%)">
<template v-slot="{row}">
<el-input-number v-model="row.ratio" :precision="0" :step="1" :min="0" :max="100"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="limitCount" label="限制人数">
<template v-slot="{row}">
<el-input-number
v-model="row.limitCount"
:precision="0"
:step="1"
:min="0"
:max="row.teacherCount"
@change="calSummaryCount"
></el-input-number>
</template>
</el-table-column>
</el-table>
<el-row class="mt20" justify="end" type="flex">
<div class="text-danger" style="width: 100%; font-size: 18px; display: flex; align-items: center; font-weight: bold">
限制总人数:{{summaryCount}}人
</div>
<el-button @click="clearUnionLimit" type="danger">清空分工会人数限制</el-button>
<el-button @click="setUpUnionLimitDialog=false">取消</el-button>
<el-button @click="doConfirmSetUpUnionLimit" type="primary">确定</el-button>
</el-row>
</el-dialog>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
formData: {},
unionLimitIndex: 0,
setUpUnionLimitDialog: false,
unionList: [],
unionUserNumCalc: [{}],
unionUserNumOneKeyRatio: null,
summaryCount: 0,
}
},
computed: {
unionUserNumCalcTips() {
return this.unionUserNumCalc.map((v) => {
return (
(v.startNum ? v.startNum : "?") +
"-" +
(v.endNum ? v.endNum : "?") +
"人的分工会,限报" +
(v.resultNum ? v.resultNum : "?") +
"人"
)
})
}
},
methods: {
calSummaryCount() {
const array = this.formData.courseList[this.unionLimitIndex].unionLimit
if (array) {
this.summaryCount = array.reduce((prev, curr) => {
const value = Number(curr.limitCount)
if (!isNaN(value)) {
return prev + curr.limitCount
} else {
return prev
}
}, 0)
}
},
async onOpen(formData, index) {
this.unionList = await this.$businessTool.listUnion()
this.formData = formData
this.unionLimitIndex = index
this.setUpUnionLimitDialog = true
if (
!this.formData.courseList[this.unionLimitIndex].unionLimit ||
this.formData.courseList[this.unionLimitIndex].unionLimit.length === 0
) {
const resp = await this.$axios.get("/platform/family/manage/getUnionLimit", {
activityScopeId: this.formData.activityGroupId
})
if (resp.code === 0) {
this.$set(this.formData.courseList[this.unionLimitIndex], "unionLimit", resp.data)
}
}
this.summaryCount = this.formData.courseList[this.unionLimitIndex].unionLimit.reduce((prev, curr) => {
const value = Number(curr.limitCount)
if (!isNaN(value)) {
return prev + curr.limitCount
} else {
return prev
}
}, 0)
},
unionLimitQuickSetting() {
this.unionUserNumCalc.forEach((v, i) => {
const startNum = v.startNum
const endNum = v.endNum
const resultNum = v.resultNum
if (startNum > endNum) {
this.$message.warning("第" + (i + 1) + "行设置错误")
} else {
this.formData.courseList[this.unionLimitIndex].unionLimit.forEach((x) => {
if (x.teacherCount >= startNum && x.teacherCount <= endNum) {
x.limitCount = resultNum
}
})
this.calSummaryCount()
}
})
},
doConfirmSetUpUnionLimit() {
this.setUpUnionLimitDialog = false
},
//应用比例设置
applyScaleSettings() {
this.formData.courseList[this.unionLimitIndex].unionLimit.forEach((v) => {
v.limitCount = parseFloat(((v.teacherCount * this.unionUserNumOneKeyRatio) / 100).toFixed(0))
if (v.ratio) {
v.limitCount = parseFloat(((v.teacherCount * v.ratio) / 100).toFixed(0))
}
})
this.calSummaryCount()
},
async clearUnionLimit() {
const confirm = await this.$confirm("确定要清空分工会人数限制吗, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if ("confirm" === confirm) {
this.formData.courseList[this.unionLimitIndex].unionLimit = []
this.unionUserNumOneKeyRatio = null
this.unionUserNumCalc = [{}]
this.setUpUnionLimitDialog = false
}
},
},
created() {
if(!this.formData.courseList) {
this.formData = {courseList: [{unionLimit: []}]}
}
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,156 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.el-icon-arrow-left,
.el-icon-arrow-right {
font-size: 28px;
font-weight: bolder;
}
</style>
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度:">
<el-date-picker
placeholder="请选择年度"
type="year"
style="width: 100%"
@change="yearChange"
v-model="pageForm.year"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="活动名称:">
<el-select @change="activityChange" style="width: 100%" v-model="pageForm.activityId">
<el-option :label="item.activityName" :value="item.id" v-for="item in activityList" :key="item.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :label="activityType">
<el-button type="primary" size="small" @click="exportSignUser" :disabled="!pageForm.activityId">导出报名人员</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column :label="activityType" prop="courseName" show-overflow-tooltip></el-table-column>
<el-table-column label="类型" prop="courseType" sortable show-overflow-tooltip></el-table-column>
<el-table-column :label="activityType + '地点'" prop="courseLocation" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="负责人" prop="courseInstructor" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="最多报名人数" prop="coursePeopleNumber" sortable align="center" header-align="center" show-overflow-tooltip>
<template v-slot="{row}">
<span v-if="row.reserveMode === 1">
{{row.coursePeopleNumber}}
</span>
<span v-if="row.reserveMode === 2">
{{(row.coursePeopleNumber + row.waitingNum) + '(候补占' + row.waitingNum + ''}}
</span>
</template>
</el-table-column>
<el-table-column label="预留人数" prop="courseReservedNumber" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="已报名人数" prop="registerNum" sortable show-overflow-tooltip>
<template v-slot="{row}">
<el-link @click="openView(row)" type="primary">
{{row.registerNum}}
</el-link>
</template>
</el-table-column>
<el-table-column label="操作" width="200px">
<template v-slot="{row}">
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button @click="openSign(row)" size="mini" v-if="row.interTime" :type="row.openOtherUnion === true ? 'danger' : 'primary'">
{{row.openOtherUnion === true ? '关闭报名' : '开放报名'}}
</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #view>
<info ref="infoRef"></info>
</template>
</guava>
</div>
<script>
<!--#include('info.js'){}#-->
new Vue({
el: "#app",
dicts: ["FAMILY_SIGNUP_TYPE"],
mixins: [initTableMixins],
components: {
"info": info,
},
data() {
return {
activityList: [],
pageForm: {
year: new Date().getFullYear().toString(),
activityId: null
},
tableColumns: [
{ label: "名称", prop: "courseName" },
{ label: "类型", prop: "courseType", sortable: true },
{ label: "地点", prop: "courseLocation" },
{ label: "教师", prop: "courseInstructor", sortable: true },
{ label: "最多报名人数", prop: "coursePeopleNumber", sortable: true },
{ label: "已报名人数", prop: "registerNum", sortable: true }
],
activityType: "子活动",
}
},
methods: {
async openSign(row) {
const resp = await this.$axios.post(loc() + "/signChange", { courseId: row.id, openOtherUnion: !row.openOtherUnion })
if (resp.code === 0) {
this.$message.success(resp.msg)
this.doSearch()
} else {
this.$message.warning(resp.msg)
}
},
activityChange(val) {
const activity = this.activityList.find((o) => o.id === val)
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find(o => o.code === activity.activityType)
this.activityType = type?.name || "子活动"
},
exportSignUser() {
this.$downLoad(loc() + "/exportSignUser", { activityId: this.pageForm.activityId })
},
async yearChange() {
this.pageForm.activityId = null
await this.getActivityList()
await this.doSearch()
},
async getActivityList() {
const resp = await this.$axios.post(loc() + "/activityList", { year: this.pageForm.year })
this.activityList = resp.data
if (this.activityList && this.activityList.length > 0) {
this.pageForm.activityId = this.activityList[0].id
this.activityChange(this.activityList[0].id)
}
},
async initData() {
await this.getActivityList()
},
async openView(row) {
this.$refs.guava.view(() => {
this.$refs.infoRef.onOpen(row)
})
}
},
async created() {
await this.initData()
await this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,123 @@
const info = {
template: /*language=HTML*/ `
<div>
<el-tabs>
<el-tab-pane label="报名人员">
<el-table :data="registerUserTableData" class="mt20">
<el-table-column
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="(column, index) in registerUserTableColumns"
:key="index"
>
<template v-slot="{ row }" v-if="column.prop === 'state'">
<span class="text-success" v-if="row.state === 1">正常报名</span>
<span class="text-warning" v-else-if="row.state === 2">候补报名</span>
<span class="text-success" v-else-if="row.state === 3">正常报名(候补)</span>
<span class="text-info" v-else-if="row.state === 4">无效报名(缺席)</span>
</template>
<template v-slot="{ row }" v-else-if="column.prop === 'familyCount'">
<el-link @click="viewFamily(row)" type="primary">
{{row.familyCount}}
</el-link>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template v-slot="{ row }">
<el-button size="mini" type="primary" @click="viewFamily(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="签到情况" v-if="clickRow.isMobileSign === true && Object.keys(userSignInfo).length > 0">
<el-tabs style="height: 600px" tab-position="left" class="mt20">
<el-tab-pane v-for="(item, key) in userSignInfo" :key="key">
<span slot="label">
<i class="el-icon-date"></i>
{{key}}
</span>
<el-table :data="item" style="max-height: 600px; overflow-y: auto">
<el-table-column label="姓名" prop="username"></el-table-column>
<el-table-column label="工号" prop="loginname"></el-table-column>
<el-table-column label="是否签到" prop="isAttend">
<template v-slot="{row}">
<template v-if="Date.now() < Date.parse(row.courseStartTime)">
<span class="text-default">未开始</span>
</template>
<template v-else>
<span class="text-success" v-if="row.isAttend">已签到</span>
<span class="text-warning" v-else>未签到</span>
</template>
</template>
</el-table-column>
<el-table-column label="签到时间" prop="attendTime"></el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
</el-tabs>
<el-dialog title="家属信息" append-to-body :close-on-click-modal="false" :visible.sync="familyVisible" width="50%">
<template v-for="(item,index) in familyRow.mobileColumnsValue" :key="index">
<div class="left-span-label">家属{{ index + 1 }}</div>
<el-descriptions border class="descriptions-form">
<el-descriptions-item v-for="(column, i) in item" :label="column.columnName" :key="i">
<div v-if="column.columnFormType !== 'FILE'">{{ column.columnValue }}</div>
<div v-else>
<file-preview complete_result :files="column.columnValue"></file-preview>
</div>
</el-descriptions-item>
</el-descriptions>
</template>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="familyVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
registerUserTableData: [],
registerUserTableColumns: [
{ label: "姓名", prop: "username" },
{ label: "工号", prop: "loginname" },
{ label: "单位", prop: "unitName" },
{ label: "分工会", prop: "unionName" },
{ label: "联系方式", prop: "mobile" },
{ label: "报名时间", prop: "signUpTime" },
{ label: "报名状态", prop: "state" },
{ label: "家属人数", prop: "familyCount" },
],
userSignInfo: {},
clickRow: {},
familyRow: {},
familyVisible: false,
}
},
methods: {
async onOpen(row) {
this.clickRow = row
const resp_register = await this.$axios.post(loc() + "/registerUserList", {courseId: row.id})
this.registerUserTableData = resp_register.data
const resp_signInfo = await this.$axios.post(loc() + "/getSignInfo", {courseId: row.id})
this.userSignInfo = resp_signInfo.data
/*const resp_columnInfo = await $.get(loc() + '/getTaleColumnInfo', {courseId: row.id})
if (resp_columnInfo.data) {
this.registerUserTableColumns = []
this.registerUserTableColumns = this.cloneTableColumns.concat(resp_columnInfo.data)
}*/
},
viewFamily(row) {
this.familyRow = row
this.familyVisible = true
},
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,364 @@
const basicForm = {
template: /*language=HTML*/ `
<div>
<el-form label-width="120px" :model="formData" ref="addForm" :rules="rules">
<el-form-item prop="code" label="类型编码">
<el-input v-model="formData.code" placeholder="请输入类型编码"></el-input>
</el-form-item>
<el-form-item prop="typeName" label="类型名称">
<el-input v-model="formData.typeName" placeholder="请输入类型名称"></el-input>
</el-form-item>
<!--<el-form-item prop="isBringFamily" label="是否携带家属">
<el-radio-group v-model="formData.isBringFamily" @input="isBringFamilyInput">
<el-radio :label="true" border>携带</el-radio>
<el-radio :label="false" border>不携带</el-radio>
</el-radio-group>
</el-form-item>-->
<el-form-item v-if="formData.isBringFamily === true" prop="familyMaxCount" label="家属最多数">
<el-input v-model="formData.familyMaxCount" placeholder="请输入家属最多数" type="number"></el-input>
</el-form-item>
<el-form-item v-if="formData.isBringFamily === true" prop="isAddFamily" label="家属纳入总人数">
<el-radio-group v-model="formData.isAddFamily">
<el-radio :label="true" border>纳入</el-radio>
<el-radio :label="false" border>不纳入</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="formData.isBringFamily === true" prop="selfAddFamily" label="本人纳入总人数">
<el-radio-group v-model="formData.selfAddFamily">
<el-radio :label="true" border>纳入</el-radio>
<el-radio :label="false" border>不纳入</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div class="left-span-label" style="display: flex; justify-content: space-between; align-items: center">
<div>
报名填写字段
<span style="color: #c64120; margin-left: 10px">
注:移动端报名时字段的显示顺序会按照下面表格中的序号进行排列、字段编码不能重复
</span>
</div>
<div>
<el-button
@click="formData.familyMobileSignColumnList.push({isRequired: false})"
size="small"
type="primary"
style="margin-left: 10px"
>
<i class="ti-plus"></i>
添加
</el-button>
</div>
</div>
<el-table :data="formData.familyMobileSignColumnList">
<el-table-column label="序号" prop="columnIndex">
<template v-slot="{row}">
<el-input-number v-model="row.columnIndex" placeholder="请输入序号"></el-input-number>
</template>
</el-table-column>
<el-table-column label="字段名称" prop="columnName">
<template v-slot="{row}">
<el-input v-model="row.columnName" placeholder="请输入字段名称"></el-input>
</template>
</el-table-column>
<el-table-column label="字段编码" prop="columnCode">
<template v-slot="{row}">
<el-input
v-model="row.columnCode"
placeholder="请输入字段编码"
:disabled="formData.isBringFamily === true && row.columnCode === 'xdqsrs'"
></el-input>
</template>
</el-table-column>
<el-table-column label="验证规则" prop="validRule">
<template v-slot="{row}">
<el-select v-model="row.validRule" style="width: 100%" filterable placeholder="请选择验证规则" clearable>
<el-option label="身份证" value="idCard"></el-option>
<el-option label="手机号" value="mobile"></el-option>
<el-option label="邮箱" value="email"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="控件类型" prop="columnFormType">
<template v-slot="{row, $index}">
<el-select
filterable
v-model="row.columnFormType"
@change="(val) => {columnFormTypeChange(val, $index)}"
placeholder="请选择控件类型"
>
<el-option v-for="item in columnFormTypeList" :label="item.description" :value="item.code" :key="item.code"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="字段类型" prop="columnType">
<template v-slot="{row}">
<el-select
filterable
placeholder="请选择字段类型"
v-model="row.columnType"
>
<el-option v-for="item in columnTypeOptions" :label="item" :value="item" :key="item"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="是否必填" prop="isRequired" sortable>
<template v-slot="{row}">
<el-switch
v-model="row.isRequired"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
active-text="是"
inactive-text="否"
></el-switch>
</template>
</el-table-column>
<el-table-column label="操作">
<template v-slot="{row, $index}">
<el-button
@click="openDetailedParams(row, $index)"
:disabled="!['SELECT', 'RADIO', 'FILE'].includes(row.columnFormType)"
size="mini"
type="primary"
>
设置详细参数
</el-button>
<el-button
@click="formData.familyMobileSignColumnList.splice($index, 1)"
:disabled="formData.isBringFamily === true && row.columnCode === 'xdqsrs'"
size="mini"
type="danger"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div style="float: right; margin: 20px 0">
<el-button @click="$emit('refresh')">取 消</el-button>
<el-button type="primary" @click="doHandle">确 定</el-button>
</div>
<el-drawer append-to-body :visible.sync="detailedParamsVisible" size="40%">
<template #title>
<div class="left-span-label">设置详细参数</div>
</template>
<el-form class="mt20" ref="paramForm" :rules="paramFormRules" :model="clickRow" label-width="80px">
<template v-if="['SELECT'].includes(clickRow.columnFormType)">
<el-form-item label="选项列表" prop="selectValues">
<el-select
v-model="clickRow.selectValues"
multiple
style="width: 100%"
filterable
allow-create
default-first-option
placeholder="请选择选项列表(如无数据时,需手动输入选项进行添加)"
>
<el-option v-for="item in clickRow.columnOptions" :label="item" :value="item" :key="item"></el-option>
</el-select>
</el-form-item>
</template>
<template v-if="['RADIO'].includes(clickRow.columnFormType)">
<el-form-item label="选项列表" prop="selectValues">
<el-select
v-model="clickRow.selectValues"
multiple
style="width: 100%"
filterable
allow-create
default-first-option
placeholder="请选择选项列表(如无数据时,需手动输入选项进行添加)"
>
<el-option v-for="item in clickRow.columnOptions" :label="item" :value="item" :key="item"></el-option>
</el-select>
</el-form-item>
</template>
<template v-if="['FILE'].includes(clickRow.columnFormType)">
<el-form-item label="文件类型" prop="fileType">
<el-select v-model="clickRow.fileType" multiple style="width: 100%" filterable placeholder="请选择文件类型">
<el-option label="jpg" value=".jpg"></el-option>
<el-option label="png" value=".png"></el-option>
<el-option label="jpeg" value=".jpeg"></el-option>
<el-option label="doc" value=".doc"></el-option>
<el-option label="docx" value=".docx"></el-option>
<el-option label="xlsx" value=".xlsx"></el-option>
<el-option label="pdf" value=".pdf"></el-option>
<el-option label="mp3" value=".mp3"></el-option>
<el-option label="mp4" value=".mp4"></el-option>
</el-select>
</el-form-item>
<el-form-item label="文件个数" prop="fileNumber">
<el-input-number v-model="clickRow.fileNumber" :min="1" :max="10" placeholder="请输入文件个数" style="width: 100%"></el-input-number>
</el-form-item>
</template>
</el-form>
<div style="float: right; margin: 20px 0">
<el-button @click="detailedParamsVisible = false">取 消</el-button>
<el-button type="primary" @click="doDetailParams">确 定</el-button>
</div>
</el-drawer>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
formData: {
familyMobileSignColumnList: [{ isRequired: false }],
isBringFamily: true,
isAddFamily: true,
selfAddFamily: true,
},
rules: {
typeName: [{ required: true, message: "请输入类型名称", trigger: ["change", "blur"] }],
code: [{ required: true, message: "请输入类型编码", trigger: ["change", "blur"] }],
isBringFamily: [{ required: true, message: "请选择是否携带家属", trigger: ["blur", "change"] }],
isAddFamily: [{ required: true, message: "请选择家属是否纳入总人数", trigger: ["blur", "change"] }],
familyMaxCount: [{ required: true, message: "请输入家属最多数", trigger: ["blur", "change"] }],
},
columnTypeOptions: [],
columnFormTypeList: [],
clickRow: {},
paramFormRules: {
selectValues: [{ required: true, message: "请选择选项列表", trigger: ["blur"] }],
fileType: [{ required: true, message: "请选择文件类型", trigger: ["blur"] }],
fileNumber: [{ required: true, message: "请输入文件个数", trigger: ["blur", "change"] }]
},
detailedParamsVisible: false,
}
},
methods: {
isBringFamilyInput(o) {
this.$set(this.formData, "isAddFamily", o === true ? false : null)
if (o === true) {
this.formData.familyMobileSignColumnList.unshift({
columnName: "携带亲属人数",
columnCode: "xdqsrs",
columnFormType: "INPUT",
columnType: "INT",
isRequired: true
})
} else {
this.formData.familyMobileSignColumnList.forEach((item, index) => {
if (item.columnCode === "xdqsrs" && this.formData.isBringFamily === false) {
this.formData.familyMobileSignColumnList.splice(index, 1)
}
})
}
},
columnFormTypeChange(val, index) {
if (val === "FILE") {
this.formData.familyMobileSignColumnList[index].columnType = "JSON"
this.formData.familyMobileSignColumnList[index].isDisabled = true
} else {
this.formData.familyMobileSignColumnList[index].columnType = ""
this.formData.familyMobileSignColumnList[index].isDisabled = false
}
},
doHandle() {
if (this.formData.id) {
this.doEdit()
} else {
this.doAdd()
}
},
async doDetailParams() {
const isValid = await this.$refs["paramForm"].validate()
if (isValid) {
this.detailedParamsVisible = false
}
},
openDetailedParams(row, index) {
this.clickRow = row
this.detailedParamsVisible = true
if (this.$refs["paramForm"]) this.$refs["paramForm"].clearValidate()
},
doAdd() {
this.$refs["addForm"].validate((valid) => {
if (valid) {
const newListLength = new Set(this.formData.familyMobileSignColumnList.map((item) => item.columnCode)).size
const listLength = this.formData.familyMobileSignColumnList.length
if (listLength > newListLength) {
this.$message.warning('字段编码不能重复')
return
}
const cloneData = clone(this.formData)
this.$axios.post(loc() + "/doAdd", { data: JSON.stringify(cloneData) }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.$emit('refresh')
} else {
this.$message.error(res.msg)
}
})
}
})
},
doEdit() {
this.$refs["addForm"].validate((valid) => {
if (valid) {
const newListLength = new Set(this.formData.familyMobileSignColumnList.map((item) => item.columnCode)).size
const listLength = this.formData.familyMobileSignColumnList.length
if (listLength > newListLength) {
this.$message.warning('字段编码不能重复')
return
}
const cloneData = clone(this.formData)
cloneData.familyMobileSignColumnList = JSON.stringify(cloneData.familyMobileSignColumnList)
this.$axios.post(loc() + "/doEdit", cloneData).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.$emit('refresh')
} else {
this.$message.error(res.msg)
}
})
}
})
},
async getColTypeOptions() {
const resp = await this.$axios.get(loc() + "/getColumnType")
this.columnTypeOptions = resp.data
},
async getEnumOptions() {
const resp = await this.$axios.post("/open/common/dictEnumOptions", { name: "ColumnFormTypeEnum" })
return resp.data
},
initData(row) {
if(row && row.id) {
this.formData = JSON.parse(JSON.stringify(row))
this.formData.familyMobileSignColumnList.forEach((item) => {
item.isDisabled = item.columnFormType === "FILE"
})
} else {
this.formData = {
familyMobileSignColumnList: [{ isRequired: false }],
isBringFamily: true,
isAddFamily: true,
selfAddFamily: true,
}
if (this.$refs["addForm"]) this.$refs["addForm"].resetFields()
}
},
},
async created() {
await this.getColTypeOptions()
this.columnFormTypeList = await this.getEnumOptions()
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,120 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.el-row--flex.is-justify-space-between {
justify-content: left;
}
.el-drawer__body {
padding: 0 30px 0 10px;
}
</style>
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="类型名称:">
<el-input clearable placeholder="请输入类型名称" v-model="pageForm.typeName"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="申请列表">
<el-button type="primary" size="small" @click="openAdd">
<i class="ti-plus"></i>
新增类型
</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
<el-table-column label="序号" width="70" type="index">
<template v-slot="scope">
<span>{{scope.$index+(pageForm.pageNumber - 1) * pageForm.pageSize + 1}}</span>
</template>
</el-table-column>
<el-table-column label="类型编码" prop="code" sortable></el-table-column>
<el-table-column label="类型名称" prop="typeName"></el-table-column>
<el-table-column label="操作" width="300px">
<template v-slot="{ row }">
<el-button v-if="row.xh!==1" size="mini" type="primary" @click="rowChange(row, false)">上移</el-button>
<el-button v-if="row.xh!==pageForm.totalCount" size="mini" type="primary" @click="rowChange(row, true)">下移</el-button>
<el-button type="primary" size="mini" @click="openEdit(row)">编辑</el-button>
<el-button type="danger" size="mini" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</template>
<template #edit>
<basic-form ref="basicFormRef" @refresh="doSearch();$refs.guava.index()"></basic-form>
</template>
</guava>
</div>
<script>
<!--#include('basicForm.js'){}#-->
const vue = new Vue({
el: "#app",
mixins: [initTableMixins],
components: {
"basic-form": basicForm,
},
data() {
return {}
},
methods: {
async rowChange(row, toDown) {
await this.$axios.post(loc() + "/xhChange", { id: row.id, xh: row.xh, toDown }).then((res) => {
if (res.code === 0) {
this.doSearch()
}
})
},
openAdd() {
this.$refs.guava.edit(() => {
this.$refs.basicFormRef.initData()
})
},
pageData() {
this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
openEdit(obj) {
this.$refs.guava.edit(() => {
this.$refs.basicFormRef.initData(obj)
})
},
doDelete(id) {
this.$confirm("您确定要删除吗, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$axios.post(loc() + "/doDelete", { id: id }).then((res) => {
if (res.code === 0) {
this.doSearch()
this.$message.success(res.msg)
}
})
})
.catch(() => {})
},
},
async created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,144 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
</style>
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度:">
<el-date-picker
placeholder="请选择年度"
type="year"
style="width: 100%"
@change="yearChange"
v-model="pageForm.year"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="活动名称:">
<el-select @change="activityChange" style="width: 100%" v-model="pageForm.activityId">
<el-option :label="item.activityName" :value="item.id" v-for="item in activityList" :key="item.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool label="人员调整"></table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column :label="activityType" prop="courseName" show-overflow-tooltip></el-table-column>
<el-table-column label="类型" prop="courseType" sortable show-overflow-tooltip></el-table-column>
<el-table-column :label="activityType + '地点'" prop="courseLocation" sortable show-overflow-tooltip></el-table-column>
<el-table-column
label="负责人"
prop="courseInstructor"
sortable
show-overflow-tooltip
></el-table-column>
<el-table-column label="最多报名人数" prop="coursePeopleNumber" sortable show-overflow-tooltip>
<template v-slot="{row}">
<span v-if="row.reserveMode === 1">
{{row.coursePeopleNumber}}
</span>
<span v-if="row.reserveMode === 2">
{{(row.coursePeopleNumber + row.waitingNum) + '(候补占' + row.waitingNum + ''}}
</span>
</template>
</el-table-column>
<el-table-column label="预留人数" prop="courseReservedNumber" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="已报名人数" prop="registerNum" sortable show-overflow-tooltip>
<template v-slot="{row}">
<el-link @click="openView(row)" type="primary">
{{row.registerNum}}
</el-link>
</template>
</el-table-column>
<el-table-column label="操作" width="150px">
<template v-slot="{row}">
<el-button @click="openView(row)" size="mini" type="primary">调整</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #view>
<user-info ref="userInfoRef" @refresh="doSearch"></user-info>
</template>
</guava>
</div>
<script>
<!--#include('userInfo.js'){}#-->
new Vue({
el: "#app",
dicts: ["FAMILY_SIGNUP_TYPE"],
mixins: [initTableMixins],
components: {
"user-info": userInfo,
},
data() {
return {
activityType: "子活动",
activityList: [],
pageForm: {
year: new Date().getFullYear().toString(),
activityId: null
},
tableColumns: [
{ label: "名称", prop: "courseName" },
{ label: "类型", prop: "courseType", sortable: true },
{ label: "地点", prop: "courseLocation" },
{ label: "教师", prop: "courseInstructor", sortable: true },
{ label: "最多报名人数", prop: "coursePeopleNumber", sortable: true },
{ label: "已报名人数", prop: "registerNum", sortable: true }
],
}
},
methods: {
activityChange(val) {
const activity = this.activityList.find((o) => o.id === val)
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find(o => o.code === activity.activityType)
this.activityType = type?.name || "子活动"
},
async yearChange() {
this.pageForm.activityId = null
await this.getActivityList()
await this.doSearch()
},
async getActivityList() {
const resp = await this.$axios.post(loc() + "/activityList", { year: this.pageForm.year })
this.activityList = resp.data
if (this.activityList && this.activityList.length > 0) {
this.pageForm.activityId = this.activityList[0].id
this.activityChange(this.activityList[0].id)
}
},
async initData() {
await this.getActivityList()
},
async openView(o) {
this.$refs.guava.view(() => {
const activity = this.activityList.find((o) => o.id === this.pageForm.activityId)
this.$refs.userInfoRef.onOpen(o, activity)
})
},
},
async created() {
await this.initData()
await this.pageData()
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,224 @@
const userInfo = {
template: /*language=HTML*/ `
<div>
<div class="left-span-label">{{ registerCourse.courseName + '报名人员' }}</div>
<div>
<search @search="registerSearch">
<search-item label="姓名/工号:">
<el-input v-model="registerForm.searchKeyword" placeholder="请输入内容"></el-input>
</search-item>
<search-item label="所属工会:">
<el-select v-model="registerForm.unionId" clearable filterable placeholder="请选择院级工会" @change="getUnitList()">
<el-option v-for="item in unionList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="所属单位:">
<el-select v-model="registerForm.unitId" clearable filterable placeholder="请选择单位">
<el-option v-for="item in unitList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
</search>
</div>
<el-table :data="registerUserTableData" class="mt15">
<el-table-column
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="(column, index) in registerUserTableColumns"
:key="index"
>
<template v-slot="{row}" v-if="column.prop === 'state'">
<span class="text-success" v-if="row.state === 1">正常报名</span>
<span class="text-warning" v-else-if="row.state === 2">候补报名</span>
<span class="text-success" v-else-if="row.state === 3">正常报名(候补)</span>
<span class="text-info" v-else-if="row.state === 4">无效报名(缺席)</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150px">
<template v-slot="{row}">
<el-button v-if="row.state === 1 || row.state === 3" @click="adjust(row)" size="mini" type="primary">修改</el-button>
<el-button @click="deleteSignUser(row)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="人员调整" append-to-body class="adjustDialog" :close-on-click-modal="false" :visible.sync="adjustDialogVisible" width="50%">
<span style="color: #c33714">注:如单选为灰色选择不了,则表示对应的{{ activityType }}人数已满!候补报名不做调整。</span>
<el-table :data="courseList" class="mt20">
<el-table-column label="单选" width="160">
<template v-slot="{row}">
<el-radio
:label="row.id"
@change.native="getCurrentRow(row)"
:disabled="(row.courseReservedNumber + row.registerNum) >= row.coursePeopleNumber"
v-model="afterAdjustCourse"
></el-radio>
</template>
</el-table-column>
<el-table-column
:label="activityType + '列表'"
prop="courseName"
align="center"
header-align="center"
show-overflow-tooltip
></el-table-column>
<el-table-column label="最多报名人数" prop="coursePeopleNumber" align="center" header-align="center">
</el-table-column>
<el-table-column label="预留名额" prop="courseReservedNumber" align="center" header-align="center"></el-table-column>
<el-table-column label="已报名人数" prop="registerNum" align="center" header-align="center"></el-table-column>
<el-table-column label="候补人数" prop="hasWaitingNum" align="center" header-align="center"></el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="adjustDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="doAdjust">确 定</el-button>
</span>
</el-dialog>
</div>
`,
dicts: ["FAMILY_SIGNUP_TYPE"],
data() {
return {
afterAdjustCourse: "",
adjustDialogVisible: false,
unionList: [],
unitList: [],
registerForm: {
unionId: "",
unitId: ""
},
registerCourse: {},
courseList: [],
userInfo: {},
chooseRow: {},
registerUserTableData: [],
registerUserTableColumns: [
{ label: "姓名", prop: "username" },
{ label: "工号", prop: "loginname" },
{ label: "单位", prop: "unitName" },
{ label: "分工会", prop: "unionName" },
{ label: "联系方式", prop: "mobile" },
{ label: "报名时间", prop: "signUpTime" },
{ label: "报名状态", prop: "state" }
],
activityType: '',
activity: {},
}
},
methods: {
async onOpen(row, activity) {
this.registerCourse = row
this.registerForm.courseId = row.id
this.activity = activity
const resp_register = await this.$axios.post(loc() + "/registerUserList", this.registerForm)
this.registerUserTableData = resp_register.data
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find(o => o.code === activity.activityType)
this.activityType = type?.name || "子活动"
},
getCurrentRow(row) {
this.chooseRow = row
},
async doAdjust() {
if (!this.afterAdjustCourse) {
this.$message.warning("请选择要调整的" + this.activityType + "")
return
}
this.$confirm(
"您确定要将" +
"<span style='color: red'>" +
this.userInfo.username +
"</span>" +
"的报名信息调整到" +
"<span style='color: red'>" +
this.chooseRow.courseName +
"</span>" +
"中吗?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true
}
)
.then(async () => {
const loading = this.$loading({
lock: true,
text: "努力调整中,感谢您的耐心等待。。。",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)"
})
const resp = await this.$axios.post(loc() + "/adjust", {
activityId: this.activity.id,
oldCourseId: this.registerCourse.id,
newCourseId: this.afterAdjustCourse,
userId: this.userInfo.userId
})
if (resp.code === 0) {
this.$message.success(resp.msg)
this.adjustDialogVisible = false
await this.registerSearch()
this.$emit("refresh", null)
} else {
this.$message.warning(resp.msg)
}
loading.close()
})
.catch(() => {})
},
async adjust(o) {
this.userInfo = o
this.afterAdjustCourse = ""
await this.getCourse(this.activity.id)
this.adjustDialogVisible = true
},
deleteSignUser(o) {
this.$confirm("您确定要删除" + "<span style='color: red'>" + o.username + "</span>" + "的报名信息吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true
})
.then(async () => {
const resp = await this.$axios.post(loc() + "/deleteSignUser", {
activityId: this.activity.id,
courseId: this.registerCourse.id,
userId: o.userId
})
if (resp.code === 0) {
this.$message.success(resp.msg)
await this.registerSearch()
this.doSearch()
} else {
this.$message.warning(resp.msg)
}
})
.catch(() => {})
},
async registerSearch() {
this.registerForm.courseId = this.registerCourse.id
const resp_register = await $.get(loc() + "/registerUserList", this.registerForm)
this.registerUserTableData = resp_register.data
},
async getCourse(val) {
const {data} = await this.$axios.post(loc() + "/getCourse", {activityId: val})
this.courseList = data
}
},
async created() {
this.unionList = await this.$businessTool.listUnion()
this.unitList = await this.$businessTool.listUnit(this.registerForm.unionId)
},
style: /*language=CSS*/ `
.adjustDialog .el-radio__label {
display: none;
}
.adjustDialog .el-dialog__body {
max-height: 600px;
overflow-y: auto;
}
`
}
@@ -0,0 +1,235 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度:">
<el-date-picker
placeholder="选择年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="活动名称:">
<el-select @change="activityChange" style="width: 100%" v-model="pageForm.activityId">
<el-option :label="item.activityName" :value="item.id" v-for="item in activityList" :key="item.id"></el-option>
</el-select>
</search-item>
<search-item :label="activityType + ''">
<el-select @change="courseChange" :placeholder="'请选择' + activityType" style="width: 100%" v-model="pageForm.courseId">
<el-option :label="item.courseName" :value="item.id" v-for="item in courseList" :key="item.id"></el-option>
</el-select>
</search-item>
<search-item label="人员信息:">
<el-input clearable placeholder="输入姓名或者工号搜索" v-model="pageForm.userKeyWord"></el-input>
</search-item>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool ref="tool" :label="activityType + '(温馨提示:如需补充人员,请在上面选择具体的' + activityType + ''">
<el-button @click="openReserve" type="primary" v-if="reserveMode === 2" size="small">补充人员</el-button>
<el-button @click="exportSignPerson" type="primary" size="small">导出签到名单</el-button>
<el-button @click="exportGiftPerson" type="primary" size="small">导出领取名单</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column
:label="column.prop === 'courseNames' ? activityType : column.label"
:prop="column.prop"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-if="(column.prop !== 'state' && column.prop !== 'absentCount') || (reserveMode === 2 && column.prop === 'state' && column.prop !== 'absentCount')
|| (column.prop === 'absentCount' && course.isMobileSign === true)"
v-for="(column,index) in tableColumns"
:key="index"
>
<template v-slot="{row}" v-if="column.prop==='isDisabled'">
<span class="text-danger" v-if="row.isDisabled"></span>
<span class="text-success" v-else></span>
</template>
<template v-slot="{row}" v-else-if="column.prop==='state'">
<span class="text-success" v-if="row.state === 1">正常报名</span>
<span class="text-warning" v-else-if="row.state === 2">候补报名</span>
<span class="text-success" v-else-if="row.state === 3">正常报名(候补)</span>
<span class="text-info" v-else-if="row.state === 4">无效报名(缺席)</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150px">
<template v-slot="{row}">
<el-button @click="handleUser(row.userId)" size="mini" type="danger" v-if="!row.isDisabled">拉黑</el-button>
<el-button @click="handleUser(row.userId)" size="mini" type="success" v-if="row.isDisabled">解封</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog title="补充人员" :visible.sync="reserveDialogVisible" width="55%" top="3%">
<vi-title title="以下为候补人员,已为您按照报名时间降序排列"></vi-title>
<el-table :data="reserveTableData" ref="multipleTable" @selection-change="handleSelectionChange">
<el-table-column :selectable="(row, index) => {return row.state === 2}" type="selection" width="55"></el-table-column>
<el-table-column prop="username" label="姓名"></el-table-column>
<el-table-column prop="loginname" label="工号"></el-table-column>
<el-table-column prop="mobile" label="联系方式"></el-table-column>
<el-table-column prop="unitName" label="单位"></el-table-column>
<el-table-column prop="unionName" label="工会"></el-table-column>
<el-table-column prop="signUpTime" label="报名时间"></el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="reserveDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="reserveDo">确 定</el-button>
</span>
</el-dialog>
</div>
<script>
new Vue({
el: "#app",
dicts: ["FAMILY_SIGNUP_TYPE"],
mixins: [initTableMixins],
data() {
return {
reserveDialogVisible: false,
reserveTableData: [],
activityList: [],
pageForm: {
year: new Date().getFullYear().toString(),
activityId: null
},
tableColumns: [
{ label: "姓名", prop: "username" },
{ label: "工号", prop: "loginname" },
{ label: "联系方式", prop: "mobile" },
{ label: "单位", prop: "unitName", sortable: true },
{ label: "分工会", prop: "unionName", sortable: true },
{ label: "课程", prop: "courseNames", sortable: true },
{ label: "缺席次数", prop: "absentCount" },
{ label: "是否黑名单", prop: "isDisabled", sortable: true }
],
activityType: "子活动",
courseList: [],
course: {},
reserveMode: 1,
multipleSelection: [],
}
},
methods: {
handleSelectionChange(val) {
this.multipleSelection = val
},
async openReserve() {
const activity = this.activityList.find((o) => o.id === this.pageForm.activityId)
if (this.$moment().unix() < this.$moment(activity.activityEndTime).unix()) {
this.$alert("此活动还未结束,无法补充", "提示", {
confirmButtonText: "确定"
})
return
}
const resp = await this.$axios.post("/platform/family/userManage/getReserveUser", { courseId: this.course.id })
this.reserveTableData = resp.data
this.reserveDialogVisible = true
},
reserveDo() {
if (this.multipleSelection.length === 0) {
this.$message.warning("请先在左侧多选框中选择要补充的人员")
return
}
this.$confirm("您选择了" + this.multipleSelection.length + "位教职工,确定要进行补充操作吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(async () => {
const ids = this.multipleSelection.map((o) => o.userId)
const resp = await this.$axios.post("/platform/family/userManage/reserveSingUp", {
ids: JSON.stringify(ids),
courseId: this.course.id
})
if (resp.code === 0) {
this.$message.success(resp.msg)
this.doSearch()
this.multipleSelection = []
this.$refs.multipleTable.clearSelection()
this.reserveDialogVisible = false
}
})
.catch(() => {})
},
courseChange(val) {
const course = this.courseList.find((o) => o.id === val)
this.course = course
this.reserveMode = course.reserveMode
if (this.reserveMode === 2) {
this.tableColumns.push({ label: "报名状态", prop: "state", sortable: true })
} else {
const o = this.tableColumns.find((o) => o.label === "报名状态")
if (o !== null && o !== undefined) {
this.tableColumns.splice(this.tableColumns.length - 1, 1)
}
}
this.$refs.tool.app = this
this.doSearch()
},
exportSignPerson() {
this.exportPro(loc() + "/exportSignPerson", { activityId: this.pageForm.activityId })
},
exportGiftPerson() {
this.exportPro(loc() + "/exportGiftPerson", { activityId: this.pageForm.activityId })
},
exportPro(url, param) {
if (param.activityId === null) {
this.$message.warning("请选择活动")
return
}
this.$downLoad(url, param)
},
async activityChange(val) {
const activity = this.activityList.find((o) => o.id === val)
const type = this.dict.type.FAMILY_SIGNUP_TYPE.find(o => o.code === activity.activityType)
this.activityType = type?.name || "子活动"
const resp = await this.$axios.post(loc() + "/getCourseByActivityId", { activityId: val })
this.courseList = resp.data
this.pageForm.courseId = ""
},
async handleUser(userId) {
const resp = await $.post(loc() + "/doHandleUser", { userId })
if (resp.code === 0) {
this.$message.success(resp.msg)
this.doSearch()
}
},
async yearChange() {
this.pageForm.activityId = null
await this.getActivityList()
await this.doSearch()
},
async getActivityList() {
const resp = await this.$axios.post("/platform/family/statistics/activityList", { year: this.pageForm.year })
this.activityList = resp.data
if (this.activityList && this.activityList.length > 0) {
this.pageForm.activityId = this.activityList[0].id
await this.activityChange(this.activityList[0].id)
}
},
async initData() {
await this.getActivityList()
},
},
async created() {
await this.initData()
await this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,144 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.info-title {
font-weight: bold;
background-color: white;
padding: 13px;
box-shadow: 0 8px 12px #ebedf0;
text-align: center !important;
}
.info-container {
height: calc(100vh - 46px - 44px);
min-height: calc(100vh - 46px - 44px);
overflow-y: auto;
}
.info-img {
display: block;
}
.van-count-down {
color: #fff;
}
</style>
<div id="app">
<van-nav-bar title="亲子活动" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.activityType" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/family/apply/activityPageData"
:page_form.sync="pageForm"
ref="tableListRef"
title="activityName"
img="cover"
@ready="onReady"
>
<template v-slot="{index,row}">
<table-column label="面向对象">{{row.activityGroupName}}</table-column>
<table-column label="报名时间">
{{$moment(row.activitySignUpStartTime).format('MM/DD HH:mm') + '~' + $moment(row.activitySignUpEndTime).format('MM/DD HH:mm')}}
</table-column>
<table-column label="活动时间">
{{$moment(row.activityStartTime).format('MM/DD HH:mm') + '~' + $moment(row.activityEndTime).format('MM/DD HH:mm')}}
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看介绍</span>
</div>
<div class="action-btn" @click="onApply(row)">
<i class="fa fa-edit"></i>
<span>去报名</span>
</div>
</template>
</table-list>
<van-popup v-model="infoVisible" position="right" :style="{ width: '100%', height: '100%' }">
<van-nav-bar title="详细信息" left-text="返回" left-arrow @click-left="infoVisible = false" fixed placeholder></van-nav-bar>
<div class="info-container">
<van-image class="info-img" height="186" width="100%" :src="infoRow.cover"></van-image>
<div class="info-title">{{infoRow.activityName}}</div>
<pdf-preview style="height: calc(100vh - 46px - 44px - 186px - 57px)" :content="infoRow.introduce"></pdf-preview>
</div>
<van-button @click="onApply(infoRow)" type="primary" block>
<span v-if="time >= 0">
去报名
</span>
<template v-else>
距离开始
<van-count-down :time="Math.abs(time)" class="count-down" format="DD 天 HH 时 mm 分 ss 秒" @finish="time = 0"></van-count-down>
</template>
</van-button>
</van-popup>
</div>
<script>
const vue = new Vue({
el: "#app",
store,
components: {
"pdf-preview": httpVueLoader("/components/plugins/sysFilePreview/PdfIndex.vue?v=" + new Date().getTime())
},
data() {
return {
time: 0,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
year: new Date().getFullYear(),
activityType: 2,
},
typeOptions: [
{text: '全部', value: 1},
{text: '即将开始', value: 4},
{text: '报名中', value: 2},
{text: '已结束', value: 3},
],
infoVisible: false,
infoRow: {},
}
},
methods: {
onView(row) {
this.infoRow = row
this.time = this.$moment().diff(this.$moment(row.activitySignUpStartTime), 'milliseconds')
this.infoVisible = true
},
onApply(row) {
if(this.$moment().isBefore(this.$moment(row.activitySignUpStartTime))) {
this.onView(row)
return
}
this.$pjaxReplace('/platform/family/apply/list/h5?id=' + row.id)
},
onReady() {
this.doSearch()
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,89 @@
const times = {
template:
/*language=HTML*/
`
<div>
<van-action-sheet v-model="visible" title="时间段" cancel-text="取消">
<button v-for="(item,index) in row?.courseTimes" type="button" class="van-action-sheet__item">
<div style="display: flex; justify-content: space-between; align-items: center">
<div>
<div class="van-action-sheet__name">
<label>{{ weekdayCNMap[$moment(item.courseDate).day()] }}</label>
<label>{{ $moment(item.courseDate).format('YYYY-MM-DD') }}</label>
</div>
<div class="van-action-sheet__subname">
{{$moment(item.courseStartTime).format('MM-DD HH:mm') + ' 至 ' + $moment(item.courseEndTime).format('MM-DD HH:mm')}}
</div>
</div>
<div v-if="row.isSign === true && row.isMobileSign === true" class="sign_button">
<van-button v-if="item.isAttend !== true" @click.stop="onSign(item)" size="mini" type="info">签到</van-button>
<van-button v-if="item.isAttend === true" size="mini" type="info" disabled>已签到</van-button>
</div>
</div>
</button>
</van-action-sheet>
<van-popup round :safe-area-inset-bottom="true"
:close-on-click-overlay="false"
v-model="signVisible"
:style="{ width: '80%', height: '66%' }"
get-container="#app"
@close="onSignClose"
closeable
>
<scan-code ref="scanCodeRef"></scan-code>
</van-popup>
</div>
`,
store,
data() {
return {
visible:false,
row: null,
weekdayCNMap: {0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六'},
selectCourseTime: {},
signVisible: false,
}
},
components: {
"scan-code": httpVueLoader("/components/plugins/sysScanCode/index.vue?v=" + new Date().getTime())
},
methods: {
onSignClose() {
this.$refs.scanCodeRef.closeScan()
},
onOpen(row) {
this.row = row
this.visible = true
},
onSign(courseTime) {
this.selectCourseTime = courseTime
if(this.row.signType === 1) {
this.signVisible = true
this.$nextTick(() => {
this.$refs.scanCodeRef.init()
})
}
if(this.row.signType === 2) {
this.makeCode()
}
if(this.row.signType === 3) {
this.$toast('此签到模式正在升级中')
}
},
makeCode() {
const url = '/platform/family/mine/passiveScan'
const data = url + '?id=' + this.selectCourseTime.id
console.log(data)
const content = jrQrcode.getQrBase64(data)
vant.ImagePreview([content])
},
onClose(){
this.visible = false
},
},
style: /*language=CSS*/ `
`
}
@@ -0,0 +1,277 @@
const applyForm = {
template:
/*language=HTML*/
`
<div>
<van-popup v-model="visible" position="right" :style="{ width: '100%', height: '100%', 'background-color': '#F7F8FA' }">
<van-nav-bar title="报名信息" left-text="返回" left-arrow @click-left="visible = false" fixed placeholder></van-nav-bar>
<van-form ref="formRef" class="form-container">
<van-cell-group title="活动信息" class="form-section">
<van-field label="活动名称" readonly v-model="row.courseName"></van-field>
<van-field label="校区" readonly v-model="row.campus"></van-field>
<van-field label="活动地点" readonly v-model="row.courseLocation"></van-field>
</van-cell-group>
<van-cell-group title="基础信息" class="form-section">
<van-field label="姓名" readonly v-model="formData.username"></van-field>
<van-field label="工号" readonly v-model="formData.loginname"></van-field>
<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>
<template v-if="row.courseIsLimitApply">
<van-field label="报名时段"
required
:rules="[{ required: true, message: '请选择报名时段' }]"
readonly
@click="showCoursePicker = true"
placeholder="请选择报名时段"
name="courseTimeName"
v-model="formData.courseTimeName">
</van-field>
<van-popup v-model="showCoursePicker" position="bottom">
<van-picker
show-toolbar
:columns="courseTimeSelectList"
@confirm="onCourseConfirm"
@cancel="showCoursePicker=false"
></van-picker>
</van-popup>
</template>
</van-cell-group>
<van-cell-group class="form-section">
<template #title>
<div style="display: flex; align-items: center; justify-content: space-between">
<div>{{activity.keyWord}}信息</div>
<div>
<van-tag @click="delFamily" size="large" type="primary" color="lightgrey">删除{{activity.keyWord}}</van-tag>
<van-tag @click="addFamily" size="large" type="primary" color="#1867b0">添加{{activity.keyWord}}</van-tag>
</div>
</div>
</template>
<div v-if="formData.mobileColumnsValue && formData.mobileColumnsValue.length > 0" class="mt10">
<van-tabs v-model="familyActive" type="card" color="#0e78c5" animated>
<van-tab v-for="item, index in formData.mobileColumnsValue"
:name="index + ''"
:title="activity.keyWord + (index + 1)"
:key="index">
<train-dynamic-form v-model="formData.mobileColumnsValue[index]"></train-dynamic-form>
</van-tab>
</van-tabs>
</div>
<div v-if="formData.mobileColumnsValue?.length === 0" class="companionList_empty_text">
<span>
暂无数据,请添加{{activity.keyWord}}
</span>
</div>
</van-cell-group>
<div class="form-actions">
<van-button @click="onSubmit" round type="info">提交</van-button>
</div>
</van-form>
</van-popup>
</div>
`,
data() {
return {
familyActive: '',
row: {},
activity: {},
visible: false,
formData: {},
showCoursePicker: false,
courseTimeSelectList: [],
courseType: {},
}
},
components: {
"train-dynamic-form": httpVueLoader("/components/module/trainSignUp/TrainDynamicForm.vue")
},
methods: {
addFamily() {
if(this.formData.mobileColumnsValue.length >= this.formData.count) {
this.$toast('您选择的名额数为' + this.formData.count + '' + this.activity.keyWord + '最多人数为' + this.formData.count)
return
}
const list = clone(this.courseType.familyMobileSignColumnList)
this.formData.mobileColumnsValue.push(list)
},
delFamily() {
this.formData.mobileColumnsValue.splice(this.familyActive, 1)
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)
}
this.visible = true
},
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', [])
},
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
},
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("获取时段信息失败,请联系管理员")
}
},
validateIdCard(idCard) {
if (!idCard) {
return false
}
// 18位身份证号码校验
if (idCard.length === 18) {
const idCardRegex = /^[1-9]\d{5}(18|19|20)?\d{2}((0[1-9])|(10|11|12))(([0|1|2][0-9])|10|20|30|31)\d{3}(\d|[Xx])$/
if (!idCardRegex.test(idCard)) {
return false
}
// 校验码计算
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
const checksums = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]
let sum = 0
for (let i = 0; i < 17; i++) {
sum += idCard[i] * weights[i]
}
const checksum = checksums[sum % 11]
return checksum === idCard[17].toUpperCase()
}
// 15位身份证号码校验
else if (idCard.length === 15) {
const idCardRegex = /^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0|1|2][0-9])|10|20|30|31)\d{3}$/
return idCardRegex.test(idCard)
}
// 外国人或其他情况
else {
return false
}
},
validFamilyForm() {
// 校验规则映射
const validators = {
idCard: (value) => this.validateIdCard(value),
mobile: (value) => /^1[3-9]\d{9}$/.test(value),
email: (value) => /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value)
};
// 显示提示弹窗(直接传完整的 message)
const showAlert = (message) => {
vant.Dialog.alert({
title: '提示',
message: message, // 直接使用传入的完整消息
confirmButtonColor: '#1867b0'
});
};
const { mobileColumnsValue } = this.formData;
for (let i = 0; i < mobileColumnsValue.length; i++) {
const family = mobileColumnsValue[i];
for (const col of family) {
const value = col.columnValue?.trim(); // 安全处理空值和空格
// 必填校验
if (col.isRequired && !value) {
const message = this.activity.keyWord + (i + 1) + '的' + col.columnName + '不能为空';
showAlert(message);
return false;
}
// 格式校验
if (col.validRule && value) {
const validator = validators[col.validRule];
if (validator && !validator(value)) {
const message = this.activity.keyWord + (i + 1) + '的' + col.columnName + '格式不正确';
showAlert(message);
return false;
}
}
}
}
return true;
},
async validateSignUp() {
// 获取家属人数
const res = await this.$axios.post("/platform/family/apply/validateSignUp", {
courseId: this.formData.courseId,
currentFamilyNumber: this.formData.mobileColumnsValue.length || 0
})
return res.code === 0
},
async validateCourseTime() {
const res = await this.$axios.post('/platform/family/apply/validateSourceSignUp', {
activityCourseId: this.formData.activityCourseId,
courseId: this.row.id
})
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 => {
if (res.code === 0) {
this.$toast(res.msg)
this.visible = false
this.$emit('refresh')
}
})
})
})
},
},
style: /*language=CSS*/ `
.form-container {
height: calc(100vh - 46px - 64px - 20px);
overflow-y: auto;
}
.companionList_empty_text {
text-align: center;
padding: 10px 0;
font-size: 14px;
color: grey;
}
`
}
@@ -0,0 +1,225 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.primary-color {
color: var(--color-primary);
}
.sign_button .van-button{
width: 66px;
height: 30px;
font-size: 14px;
border-radius: 6px;
}
</style>
<div id="app">
<van-nav-bar title="活动报名" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item v-model="pageForm.courseTypeId" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/family/apply/pageData"
:page_form.sync="pageForm"
ref="tableListRef"
@ready="onReady"
>
<template #header="{index,row}">
<div class="item-header">
<div class="item-title">{{ row.courseName }}</div>
<div v-html="calcSignUpCount(row)"></div>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="类型">{{row.typeName}}</table-column>
<table-column label="地点">{{row.courseLocation}}</table-column>
<table-column label="联系人">{{row.courseInstructor}}</table-column>
<table-column label="时间">
<span v-if="row.courseTimes && row.courseTimes.length === 1">
{{ $moment(row.courseTimes[0].courseStartTime).format('HH:mm') + '~' + $moment(row.courseTimes[0].courseEndTime).format('HH:mm') }}
</span>
<span v-else @click="onTime(row)" class="primary-color">点我查看</span>
</table-column>
<table-column v-if="row.introduce" label="详细信息">
<span @click="introduceRow = row; introduceVisible = true" class="primary-color">点我查看</span>
</table-column>
</template>
<template #actions="{index,row}">
<div v-if="activity.wechat && row.isSign" class="action-btn" @click="this.vant.ImagePreview([activity.wechat])">
<i class="fa fa-wechat"></i>
<span>微信群二维码</span>
</div>
<template v-if="$moment().isBefore($moment(activity.activitySignUpEndTime))">
<div v-if="row.isSign === false" class="action-btn" @click="onApply(row)">
<i class="fa fa-sign-in"></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>
</div>
</template>
<div v-if="row.isSign === true && row.isMobileSign === true && $moment().isAfter($moment(activity.activitySignUpEndTime))"
class="action-btn"
@click="onTime(row)"
>
<i class="fa fa-sign-in"></i>
<span>签到</span>
</div>
</template>
</table-list>
<van-popup v-model="introduceVisible" position="right" :style="{ width: '100%', height: '100%', 'background-color': '#F7F8FA' }">
<van-nav-bar title="详细信息" left-text="返回" left-arrow @click-left="introduceVisible = false" fixed placeholder></van-nav-bar>
<pdf-preview style="height: calc(100vh - 46px)" :content="introduceRow.introduce"></pdf-preview>
</van-popup>
<times ref="timesRef"></times>
<apply-form ref="formRef" @refresh="refresh"></apply-form>
</div>
<script src="${base!}/assets/platform/plugins/jr-qrcode/jr-qrcode.js"></script>
<script>
<!--#include('../common/times.js'){}#-->
<!--#include('applyForm.js'){}#-->
const vue = new Vue({
el: "#app",
store,
components: {
'times': times,
'apply-form': applyForm,
"pdf-preview": httpVueLoader("/components/plugins/sysFilePreview/PdfIndex.vue?v=" + new Date().getTime())
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
year: new Date().getFullYear(),
courseTypeId: null,
activityId: GetQueryString('id'),
dataType: GetQueryString('dataType'),
},
typeOptions: [],
assortOptions: [],
sourceTypeOptions: [],
introduceVisible: false,
introduceRow: {},
activity: {},
}
},
methods: {
refresh() {
this.doSearch()
},
async onTime(row) {
// 如果设置签到,并且也报名的话
if(row.isMobileSign === true && row.isSign === true) {
const res = await this.$axios.post('/platform/family/mine/queryCourseSign', {
courseId: row.id
})
row.courseTimes = res.data
}
this.$refs.timesRef.onOpen(row)
},
onApply(row) {
const courseType = this.sourceTypeOptions.find((v) => v.id === row.courseType)
this.$axios.post('/platform/family/apply/validateSignUp', {
courseId: row.id
})
.then((res) => {
if (res.code !== 0) {
vant.Dialog.alert({
title: '提示',
message: res.msg,
confirmButtonColor: '#1867b0'
})
} else {
let lave = row.coursePeopleNumber - (row.hasRegisterNum + row.courseReservedNumber)
if(row.reserveMode === 2 && lave <= 0) {
vant.Dialog.alert({
title: '提示',
message: '您当前的报名为候补报名状态',
confirmButtonColor: '#1867b0'
})
}
this.$refs.formRef.onOpen(row, courseType, this.activity)
}
})
},
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', {
activityId: row.activityId,
courseId: row.id
})
this.$toast(resp.msg)
if (resp.code === 0) {
this.doSearch()
}
}).catch(() => {})
},
calcSignUpCount(row) {
if(!row.coursePeopleNumber || row.coursePeopleNumber === 0) {
return "名额数不限制"
}
let lave = row.coursePeopleNumber - (row.hasRegisterNum + row.courseReservedNumber)
if(row.reserveMode === 2) {
let lave2 = row.waitingNum - row.hasWaitingNum
return "<span style='color: red'>余" + lave +"</span>/" + row.coursePeopleNumber + "人"
+ "<span style='color: red'>候补余" + lave2 + "</span>/" + row.waitingNum + "人"
} else {
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
}
].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()
},
fetchActivity() {
this.$axios.post('/platform/family/manage/findOne', {id: this.pageForm.activityId})
.then((res) => {
this.activity = res.data
})
},
async getCourseTypeList() {
const resp = await this.$axios.post("/platform/family/type/getAllType")
return resp.data
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,130 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.info-title {
font-weight: bold;
background-color: white;
padding: 13px;
box-shadow: 0 8px 12px #ebedf0;
text-align: center !important;
}
.info-container {
height: calc(100vh - 46px - 44px);
min-height: calc(100vh - 46px - 44px);
overflow-y: auto;
}
.info-img {
display: block;
}
.van-count-down {
color: #fff;
}
</style>
<div id="app">
<van-nav-bar title="亲子活动-我的报名" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.activityType" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/family/apply/activityPageData"
:page_form.sync="pageForm"
ref="tableListRef"
title="activityName"
img="cover"
@ready="onReady"
>
<template v-slot="{index,row}">
<table-column label="面向对象">{{row.activityGroupName}}</table-column>
<table-column label="报名时间">
{{$moment(row.activitySignUpStartTime).format('MM/DD HH:mm') + '~' + $moment(row.activitySignUpEndTime).format('MM/DD HH:mm')}}
</table-column>
<table-column label="活动时间">
{{$moment(row.activityStartTime).format('MM/DD HH:mm') + '~' + $moment(row.activityEndTime).format('MM/DD HH:mm')}}
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看介绍</span>
</div>
<div class="action-btn" @click="onApply(row)">
<i class="fa fa-edit"></i>
<span>查看</span>
</div>
</template>
</table-list>
<van-popup v-model="infoVisible" position="right" :style="{ width: '100%', height: '100%' }">
<van-nav-bar title="详细信息" left-text="返回" left-arrow @click-left="infoVisible = false" fixed placeholder></van-nav-bar>
<div class="info-container">
<van-image class="info-img" height="186" width="100%" :src="infoRow.cover"></van-image>
<div class="info-title">{{infoRow.activityName}}</div>
<pdf-preview style="height: calc(100vh - 46px - 44px - 186px - 57px)" :content="infoRow.introduce"></pdf-preview>
</div>
<van-button @click="onApply(infoRow)" type="primary" block>下一步</van-button>
</van-popup>
</div>
<script>
const vue = new Vue({
el: "#app",
store,
components: {
"pdf-preview": httpVueLoader("/components/plugins/sysFilePreview/PdfIndex.vue?v=" + new Date().getTime())
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
year: new Date().getFullYear(),
activityType: 1,
dataType: 'mine'
},
typeOptions: [
{text: '全部', value: 1},
{text: '报名中', value: 2},
{text: '已结束', value: 3},
],
infoVisible: false,
infoRow: {},
}
},
methods: {
onView(row) {
this.infoRow = row
this.infoVisible = true
},
onApply(row) {
this.$pjaxReplace('/platform/family/apply/list/h5?id=' + row.id + '&dataType=mine')
},
onReady() {
this.doSearch()
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
})
</script>
<!--#
}
#-->