From d551f8c94e570aed99c5a71efe9a11bf2d98fc65 Mon Sep 17 00:00:00 2001 From: hongqiwei <1143662660@qq.com> Date: Fri, 9 Oct 2026 10:49:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=93=E8=82=B2=E6=8A=A5=E5=90=8D=E5=A4=96?= =?UTF-8?q?=E6=8F=B4=E4=BC=98=E5=8C=96=EF=BC=9B=E4=BD=93=E8=82=B2=E6=9C=8D?= =?UTF-8?q?=E8=A3=85=E7=AE=A1=E7=90=86=E6=95=B4=E6=94=B9=EF=BC=8C=E5=85=B3?= =?UTF-8?q?=E8=81=94=E9=80=89=E6=8B=A9=E6=B4=BB=E5=8A=A8=EF=BC=9B=E6=AF=94?= =?UTF-8?q?=E8=B5=9B=E5=90=8D=E7=A7=B0=E5=BD=95=E5=85=A5=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E5=B7=A5=E4=BC=9A=E8=A1=A5=E5=85=A8=EF=BC=9B=E6=B2=BF=E7=94=A8?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=B4=BB=E5=8A=A8bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../school/ActivityApplyController.java | 48 +-- .../school/ActivityCommonController.java | 2 +- .../school/ActivityGradesController.java | 4 +- .../school/ActivityInfoManageController.java | 4 + .../school/ActivityReadingController.java | 63 ++-- .../school/ActivityRemindController.java | 23 +- .../school/ActivityResultsController.java | 28 +- .../activity/models/ActivitySchoolApply.java | 3 + .../services/ActivitySchoolApplyService.java | 38 +- .../impl/ActivitySchoolApplyServiceImpl.java | 151 +++++++- .../impl/ActivitySchoolServiceImpl.java | 38 +- .../controller/ClothingRecordController.java | 21 +- .../ClothingRegisterController.java | 33 +- .../services/ClothingRegisterService.java | 50 ++- .../impl/ClothingRegisterServiceImpl.java | 158 +++++++- .../v/nutz/sys/services/SysConfigService.java | 5 + .../services/impl/SysConfigServiceImpl.java | 16 +- .../nutz/todoTask/TodoTaskInterfaceImpl.java | 22 ++ .../platform/sys/SysConfController.java | 2 +- .../condolence/CondolenceApplyCon.java | 7 + .../nutz/zgfw/service/CondolenceService.java | 8 + .../service/impl/CondolenceServiceImpl.java | 23 ++ .../platform/module/activity/activity.js | 8 +- .../activity/school/AddActivity.vue | 159 ++++---- .../activity/school/ActivityApply.html | 353 +++++++++++++----- .../activity/school/ActivityManage.html | 7 +- .../views/platform/clothing/account.html | 55 ++- .../views/platform/clothing/record.html | 51 ++- .../views/platform/clothing/register.html | 170 ++++++--- 29 files changed, 1117 insertions(+), 433 deletions(-) diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityApplyController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityApplyController.java index 90bdea6..52b389b 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityApplyController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityApplyController.java @@ -89,7 +89,11 @@ public class ActivityApplyController { LEFT JOIN activity_school acts ON acts.id=apply.activityId LEFT JOIN activity_event ev ON ev.id = apply.eventId $condition """); - cnd.and("apply.activityUnionId", "=", getUnionId()); + // 其他工会出战的外援仍由原报名人维护,重新打开项目时必须回显。 + SqlExpressionGroup ownerOrUnion = new SqlExpressionGroup(); + ownerOrUnion.or("apply.activityUnionId", "=", getUnionId()); + ownerOrUnion.or("apply.applyUser", "=", ShiroUtil.getUserId()); + cnd.and(ownerOrUnion); /* if (!ShiroUtil.hasAnyRoles("A06,sysadmin")) { SqlExpressionGroup group = new SqlExpressionGroup(); if (ShiroUtil.hasAnyRoles("H01")) { @@ -239,12 +243,13 @@ public class ActivityApplyController { WHERE asa.eventId = @eventId AND asa.activityId=@activityId - AND asa.activityUnionId = @unionid + AND (asa.activityUnionId = @unionid OR asa.applyUser = @applyUser) AND asa.`status` != 3 ORDER BY asa.identity DESC, asa.sex - """).setParam("eventId", eventId).setParam("unionid", union_id).setParam("activityId", activityId); + """).setParam("eventId", eventId).setParam("unionid", union_id) + .setParam("applyUser", ShiroUtil.getUserId()).setParam("activityId", activityId); List list = activitySchoolApplyService.listEntity(applySql); //团体人数 List awardsModeT = list.stream().filter(v -> v.getAwardsMode().equals("2")).collect(Collectors.toList()); @@ -387,20 +392,26 @@ public class ActivityApplyController { } /** - * 创建报名专用外援,工号由服务端生成,页面不能指定。 + * 创建报名专用人员;工号留空时由服务端生成,手动填写时服务端负责查重。 * @param username 外援姓名 * @param sex 性别,男或女 - * @param birthday 出生日期,yyyy-MM-dd + * @param loginname 可为空;留空时自动生成,手动填写时不能与现有账号重复 + * @param idcard 18位身份证号,出生日期由服务端从身份证号读取 * @param mobile 手机号码 - * @return Sys_user,id 为报名人员 ID,loginname 为 wy 年份流水工号,其他字段用于页面回显 + * @param fitForCompetition 身体条件是否适合参赛,只有 true 可创建 + * @return Sys_user,id 为报名人员 ID,loginname 为最终工号,其他字段用于页面回显 */ @At @ViReturn @RequiresPermissions("activity.apply") @Aop(TransAop.READ_COMMITTED) - public Object addExternalUser(String username, String sex, String birthday, String mobile) { + public Object addExternalUser(String username, String loginname, String sex, String idcard, String mobile, + Boolean fitForCompetition) { + if (!Boolean.TRUE.equals(fitForCompetition)) { + throw new IllegalArgumentException("身体条件不适合参赛,不能添加外援"); + } Sys_user operator = (Sys_user) ShiroUtil.getPrincipal(); - return activitySchoolApplyService.createExternalUser(username, sex, birthday, mobile, operator.getUnitid()); + return activitySchoolApplyService.createExternalUser(username, loginname, sex, idcard, mobile, operator.getUnitid()); } /** @return String,当前年份的下一个外援工号预览,实际编号以创建接口返回值为准。 */ @@ -765,25 +776,18 @@ public class ActivityApplyController { return insert.getId(); } + /** + * 查询添加外援时可复用的系统人员。 + * @param queryString 姓名或工号片段;空值不查询 + * @param userId 不参与返回的用户 ID 列表,可为空 + * @return List,每项包含 id、username、loginname、sex、idcard、birthday、mobile、unitid、unitname + */ @At @ViReturn @RequiresPermissions("activity.apply") @Ok("json:{ignoreNull:true}") public Object getSysUser(String queryString, String[] userId) { - Cnd cnd = Cnd.NEW(); - cnd.andEX("id", "not in", userId); - if (StrUtil.isNotBlank(queryString)) { - SqlExpressionGroup group = new SqlExpressionGroup(); - group.andLike("username", queryString).orLike("loginname", queryString); - cnd.and(group); - } -// cnd.and("unionid", "=", Vi.getUnionId()); - Sql sql = Sqls.create("select id,loginname,username,sex,mobile,unitid,birthday from `user` $condition LIMIT 30"); - sql.setCondition(cnd); - sql.setCallback(Sqls.callback.entities()); - sql.setEntity(sysUserService.getEntity()); - List list = sysUserService.execute(sql).getList(Sys_user.class); - return list; + return activitySchoolApplyService.searchSystemUsers(queryString, userId); } diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityCommonController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityCommonController.java index 624b95e..8c7e8ab 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityCommonController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityCommonController.java @@ -280,7 +280,7 @@ public class ActivityCommonController { app.activityUnionName, app.sex, COALESCE(NULLIF(app.mobile, ''), u.mobile) mobile, - u.idCard idcard, + COALESCE(NULLIF(app.idCard, ''), u.idCard) idcard, COALESCE(unit.unitcode, u.unitcode) unitcode, CASE WHEN u.member = 1 THEN '是' ELSE '否' END isMember, CASE diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityGradesController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityGradesController.java index c67fac3..846ef2f 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityGradesController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityGradesController.java @@ -179,8 +179,8 @@ public class ActivityGradesController { Cnd cnd = Cnd.NEW(); Sql sql = Sqls.create(""" SELECT - us.unitname, - apply.* + apply.*, + COALESCE(NULLIF(apply.unitname, ''), us.unitname) unitname FROM activity_school_apply apply LEFT JOIN `user` us ON apply.userId = us.id $condition diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityInfoManageController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityInfoManageController.java index 9b129c2..6381978 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityInfoManageController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityInfoManageController.java @@ -138,6 +138,10 @@ public class ActivityInfoManageController { } else if ("four".equals(query)) { cnd.and(new Static("now() > school.endTime")); } + // 时间状态快捷查询只展示已提交活动;历史记录的 isSave 可能为空。 + if ("one".equals(query) || "two".equals(query) || "three".equals(query) || "four".equals(query)) { + cnd.and(new Static("(school.isSave IS NULL OR school.isSave <> 1)")); + } if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) { if (ActivityRoleUtil.isUnionManager()) { cnd.and("school.belongUnionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId())); diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityReadingController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityReadingController.java index ee9b804..47f01f5 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityReadingController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityReadingController.java @@ -112,42 +112,21 @@ public class ActivityReadingController { ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", unitId)); ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid())); ActivitySchool activitySchool = activitySchoolService.fetch(id); - Cnd cnd = Cnd.NEW(); - Sql sql = Sqls.create(""" - SELECT - app.*, - school.`name` activityName, - school.applyType applyType, - school.belongUnionId belongUnionId, - school.belongClubId belongClubId, - team.`name` team, - COALESCE(NULLIF(app.unitname, ''), unit.`name`) unitname, - ev.allName, - ev.id evid, - ev.projectType - FROM - activity_school_apply app - LEFT JOIN activity_school school ON school.id = app.activityId - LEFT JOIN activity_school_team team ON app.teamId = team.id - LEFT JOIN activity_event ev ON ev.id = app.eventId - LEFT JOIN sys_unit unit ON app.unitId = unit.id - $condition - """); - - cnd.and("app.activityId", "=", id); -// cnd.and("YEAR(app.applyDate)", "=", year); - sql.setCondition(cnd); + List applies = activitySchoolApplyService.listRepresentedApplies(id); if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) { if ((ActivityRoleUtil.isUnionManager() || ShiroUtil.hasRole("gh08")) && activityLevel.equals("40002")) { - cnd.and("app.activityUnionId", "=", basicUnit.getUnionid()); - cnd.and("school.belongUnionId", "=", basicUnit.getUnionid()); + // 权限仍按活动所属工会限制;报名分组按外援选中的单位工会计算。 + applies = basicUnit.getUnionid().equals(activitySchool.getBelongUnionId()) + ? applies.stream().filter(v -> basicUnit.getUnionid().equals(v.getString("representedActivityUnionId"))) + .collect(Collectors.toList()) : List.of(); } if (ShiroUtil.hasRole("H02") && activityLevel.equals("40003")) { - cnd.and("school.belongClubId", "in", vi.getMangeClubStr()); + if (activitySchoolService.count(Cnd.where("id", "=", id) + .and("belongClubId", "in", vi.getMangeClubStr())) == 0) { + applies = List.of(); + } } } - cnd.desc("ev.allName").asc("ev.projectType").asc("team.`name`"); - List applies = activitySchoolApplyService.listMap(sql); List apply = new ArrayList(); @@ -157,10 +136,6 @@ public class ActivityReadingController { unionCnd.and("id", "=", basicUnit.getUnionid()); } } - if (activityLevel.equals("40002")) { - unionCnd.and("id", "=", activitySchool.getBelongUnionId()); - } - unionCnd.and("isEnable", "=", true); unionCnd.asc("unioncode"); if (!activityLevel.equals("40003")) { @@ -168,10 +143,20 @@ public class ActivityReadingController { for (ActivityBasicUnion union : unions) { String unionId = union.getId(); + boolean hasApplies = applies.stream() + .anyMatch(v -> unionId.equals(v.getString("representedActivityUnionId"))); + if (!union.isEnable() && !hasApplies) { + continue; + } + // 院级活动保留主办工会,同时展示由所选单位代表其他工会出战的报名。 + if (activityLevel.equals("40002") && !unionId.equals(activitySchool.getBelongUnionId()) + && !hasApplies) { + continue; + } NutMap map = new NutMap(); List applyList = applies.stream().filter(v -> { - return v.getString("activityUnionId").equals(unionId); + return unionId.equals(v.getString("representedActivityUnionId")); }).collect(Collectors.toList()); map.setv("id", unionId); map.setv("unionname", union.getUnionname()); @@ -183,7 +168,7 @@ public class ActivityReadingController { } } else { NutMap map = new NutMap(); - Sys_club sysClub = sysClubService.fetch(cnd.where("id", "=", activitySchool.getBelongClubId())); + Sys_club sysClub = sysClubService.fetch(Cnd.where("id", "=", activitySchool.getBelongClubId())); map.setv("id", sysClub.getId()); map.setv("unionname", sysClub.getName()); map.setv("unioncode", sysClub.getCode()); @@ -246,12 +231,16 @@ public class ActivityReadingController { FROM `activity_school_apply` apply LEFT JOIN activity_event `event` ON `event`.id = apply.eventId + LEFT JOIN sys_unit selectedUnit ON selectedUnit.id = apply.unitId + LEFT JOIN activity_basic_unit activityUnit ON activityUnit.id = apply.unitId LEFT JOIN activity_basic_settings groupSetting ON groupSetting.id = `event`.competitionCategory LEFT JOIN activity_basic_settings projectNameSetting ON projectNameSetting.id = `event`.sports LEFT JOIN activity_basic_settings distanceSetting ON distanceSetting.id = `event`.distance WHERE apply.activityId = @id - AND apply.unionId = @unionId + AND (CASE WHEN apply.unitname = selectedUnit.name AND selectedUnit.unionid IS NOT NULL + AND activityUnit.unionid IS NOT NULL THEN selectedUnit.unionid + ELSE apply.unionId END) = @unionId AND JSON_CONTAINS(apply.identity,JSON_Array( '1' )) ORDER BY FIELD( apply.sex, '男', '女 ' ), diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityRemindController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityRemindController.java index 17d1e3a..45695b5 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityRemindController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityRemindController.java @@ -23,10 +23,12 @@ import org.nutz.dao.util.cri.Static; 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 java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -139,20 +141,21 @@ public class ActivityRemindController { WHERE sur.unionid = un.id AND sr.id IN (@h04RoleId, @activityRoleId) - ) unionleader, - ( SELECT COUNT( 1 ) FROM activity_school_apply asp WHERE asp.unionId = un.id AND asp.activityId = @activityId ) num + ) unionleader FROM `sys_union` un ORDER BY un.unioncode """).setParam("h04RoleId", Roles.UNION_MANGER) - .setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN) - .setParam("activityId", activityId); + .setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN); List unions = sysUnionService.list(sql); + Map> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId); unions.forEach(v -> { - v.set("users", activitySchoolApplyService.query(Cnd.where("unionId", "=", v.getString("id")).and("activityId", "=", activityId).desc("identity"))); + List users = appliesByUnion.getOrDefault(v.getString("id"), List.of()); + v.set("users", users); + v.set("num", users.size()); }); return unions; @@ -185,20 +188,20 @@ public class ActivityRemindController { WHERE sur.unionid = un.id AND sr.id IN (@h04RoleId, @activityRoleId) - ) telephone, - ( SELECT COUNT( 1 ) FROM activity_school_apply asp WHERE asp.unionId = un.id AND asp.activityId = @activityId ) num + ) telephone FROM `sys_union` un ORDER BY un.unioncode """).setParam("h04RoleId", Roles.UNION_MANGER) - .setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN) - .setParam("activityId", activityId); + .setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN); List unions = sysUnionService.list(sql); + Map> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId); List collect = unions.stream().filter(v -> { - return v.getInt("num") <= 0 && v.getString("telephone") != null; + return appliesByUnion.getOrDefault(v.getString("id"), List.of()).isEmpty() + && v.getString("telephone") != null; }).collect(Collectors.toList()); collect.forEach(v -> { diff --git a/src/main/java/io/v/nutz/activity/controller/school/ActivityResultsController.java b/src/main/java/io/v/nutz/activity/controller/school/ActivityResultsController.java index 51a6645..8a0a16c 100644 --- a/src/main/java/io/v/nutz/activity/controller/school/ActivityResultsController.java +++ b/src/main/java/io/v/nutz/activity/controller/school/ActivityResultsController.java @@ -5,6 +5,7 @@ import io.v.nutz.activity.models.ActivityBasicUnion; import io.v.nutz.activity.models.ActivityBasicUnit; import io.v.nutz.activity.models.ActivityResults; import io.v.nutz.activity.models.ActivitySchoolApply; +import io.v.nutz.activity.services.ActivitySchoolApplyService; import io.v.nutz.annontation.ViReturn; import io.v.nutz.base.utils.DateUtil; import io.v.nutz.base.utils.Vi; @@ -47,6 +48,9 @@ public class ActivityResultsController { @Inject("ActivitySchoolApply") private ViService activitySchoolApplyViService; + @Inject + private ActivitySchoolApplyService activitySchoolApplyService; + @At("") @Ok("beetl:platform/activity/school/ActivityResults.html") @@ -195,32 +199,14 @@ public class ActivityResultsController { /** - * 查询团体参赛部门工会 - * - * @param activityId - * @param eventId - * @return + * activityId、eventId 为页面当前活动和项目ID,仅保留接口参数兼容原调用。 + * 返回全部系统工会列表,每项包含 id 和 unionname,供团体成绩录入选择。 */ @At @ViReturn public Object getUnionList(String activityId, String eventId) { - Sql sql = Sqls.create(""" - - SELECT - un.id, - un.unionname - FROM - activity_school_apply asa - LEFT JOIN sys_union un ON un.id = asa.unionId - WHERE - asa.activityId = @activityId - AND asa.eventId = @eventId - - GROUP BY - un.unionname - """).setParam("activityId", activityId).setParam("eventId", eventId); - return activitySchoolApplyViService.list(sql); + return activitySchoolApplyService.getResultUnionOptions(); } @Inject("Sys_user") diff --git a/src/main/java/io/v/nutz/activity/models/ActivitySchoolApply.java b/src/main/java/io/v/nutz/activity/models/ActivitySchoolApply.java index 15c3a2a..82108e4 100644 --- a/src/main/java/io/v/nutz/activity/models/ActivitySchoolApply.java +++ b/src/main/java/io/v/nutz/activity/models/ActivitySchoolApply.java @@ -168,6 +168,9 @@ public class ActivitySchoolApply implements Serializable { private String mobile; + @Column + @Comment("报名时使用的身份证号,系统资料为空时保存本次手动补充值") + @ColDefine(type = ColType.VARCHAR, width = 50) @Excel(name = "身份证号", width = 20) private String idCard; diff --git a/src/main/java/io/v/nutz/activity/services/ActivitySchoolApplyService.java b/src/main/java/io/v/nutz/activity/services/ActivitySchoolApplyService.java index d4e97b1..ddc647e 100644 --- a/src/main/java/io/v/nutz/activity/services/ActivitySchoolApplyService.java +++ b/src/main/java/io/v/nutz/activity/services/ActivitySchoolApplyService.java @@ -13,19 +13,49 @@ import java.util.Map; */ public interface ActivitySchoolApplyService extends ViService { + /** + * 获取团体成绩录入可选的全部系统工会,不受活动项目报名情况或启用状态限制。 + * @return 工会列表,每项包含 id(成绩记录使用的工会ID)和 unionname(下拉显示名称) + */ + List getResultUnionOptions(); + /** @return 当前年份预计分配的外援工号;实际工号以创建接口返回值为准。 */ String previewExternalLoginname(); /** - * 创建仅用于报名的外援用户,按年份生成 wy+年份+流水号的工号。 + * 按姓名或工号查找可复用的系统人员。 + * @param queryString 搜索内容,空值返回空列表 + * @param excludedUserIds 当前页面需要排除的用户 ID + * @return 人员列表,每项包含 id、姓名、工号、性别、身份证号、出生日期、电话和系统单位 + */ + List searchSystemUsers(String queryString, String[] excludedUserIds); + + /** + * 查询活动报名及其代表出战的工会;历史报名仅在所存单位 ID 与单位名称一致时按该单位归属。 + * @param activityId 活动 ID + * @return 报名列表,representedUnionId 是系统工会 ID,representedActivityUnionId 是活动工会 ID, + * 其余字段为报名、活动、项目和队伍的展示字段 + */ + List listRepresentedApplies(String activityId); + + /** + * 按代表出战的系统工会归集活动报名,供工会提醒名单和报名人数共用。 + * @param activityId 活动 ID + * @return 以系统工会 ID 为键、该工会报名记录列表为值的 Map + */ + Map> groupRepresentedAppliesByUnion(String activityId); + + /** + * 创建仅用于报名的用户;工号可采用 wy 年份流水号,也可手动填写。 * @param username 外援姓名 * @param sex 性别,男或女 - * @param birthday 出生日期,yyyy-MM-dd + * @param loginname 留空时自动生成的工号;手动填写时不能与系统现有工号重复 + * @param idcard 身份证号,出生日期从中读取 * @param mobile 手机号码 * @param unitId 报名人所在单位 ID,用于归属当前报名工会 - * @return 已创建的系统用户,包含用户 ID 和自动生成的工号 + * @return 已创建的系统用户,包含用户 ID、最终工号和系统资料 */ - Sys_user createExternalUser(String username, String sex, String birthday, String mobile, String unitId); + Sys_user createExternalUser(String username, String loginname, String sex, String idcard, String mobile, String unitId); void doApplies(List applies); diff --git a/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolApplyServiceImpl.java b/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolApplyServiceImpl.java index 11fb145..4034a51 100644 --- a/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolApplyServiceImpl.java +++ b/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolApplyServiceImpl.java @@ -1,6 +1,7 @@ package io.v.nutz.activity.services.impl; +import cn.hutool.core.util.IdcardUtil; import io.v.nutz.activity.models.ActivityBasicUnion; import io.v.nutz.activity.models.ActivityBasicUnit; import io.v.nutz.activity.models.ActivitySchoolApply; @@ -8,11 +9,13 @@ import io.v.nutz.activity.services.ActivitySchoolApplyService; import io.v.nutz.base.utils.DateUtil; import io.v.nutz.service.impl.ViServiceImpl; import io.v.nutz.sys.models.Sys_user; +import io.v.nutz.sys.models.Sys_unit; 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.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.Lang; @@ -30,6 +33,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @IocBean(args = {"refer:dao"}) @@ -41,44 +45,122 @@ public class ActivitySchoolApplyServiceImpl extends ViServiceImpl getResultUnionOptions() { + // 团体成绩可以选择未报名该项目的工会,因此从系统工会表完整读取。 + return listMap(Sqls.create("SELECT id, unionname FROM sys_union ORDER BY unioncode")); + } + @Override public String previewExternalLoginname() { String prefix = "wy" + Year.now().getValue(); - Sql next = Sqls.create("SELECT COALESCE(MAX(CAST(SUBSTRING(loginname, 7) AS UNSIGNED)), 0) " - + "FROM sys_user WHERE loginname REGEXP @pattern"); - next.setParam("pattern", "^" + prefix + "[0-9]+$"); - next.setCallback(Sqls.callback.integer()); - dao().execute(next); - int number = ((Number) next.getResult()).intValue() + 1; - return prefix + String.format("%02d", number); + Sql sql = Sqls.create("SELECT loginname FROM sys_user WHERE loginname LIKE @prefix"); + sql.setParam("prefix", prefix + "%"); + long number = 0; + for (NutMap row : listMap(sql)) { + String loginname = row.getString("loginname"); + if (Strings.isBlank(loginname) || !loginname.matches("^" + prefix + "[0-9]+$")) continue; + try { + number = Math.max(number, Long.parseLong(loginname.substring(prefix.length()))); + } catch (NumberFormatException ignored) { + // 非法超长工号不参与流水号计算,保留其他有效工号的最大值。 + } + } + return prefix + String.format("%02d", number + 1); + } + + @Override + public List searchSystemUsers(String queryString, String[] excludedUserIds) { + String query = Strings.sNull(queryString).trim(); + if (query.isEmpty()) return List.of(); + Sql sql = Sqls.create("SELECT u.id,u.loginname,u.username,u.sex,u.idcard,u.birthday,u.mobile," + + "u.unitid,un.name unitname FROM sys_user u " + + "LEFT JOIN sys_unit un ON un.id=u.unitid $condition LIMIT 30"); + Cnd cnd = Cnd.NEW(); + cnd.andEX("u.id", "not in", excludedUserIds); + SqlExpressionGroup names = new SqlExpressionGroup(); + names.orLike("u.username", query).orLike("u.loginname", query); + cnd.and(names); + sql.setCondition(cnd); + return listMap(sql); + } + + @Override + public List listRepresentedApplies(String activityId) { + // 兼容提交时曾写入操作人工会的外援记录;只有保存的单位 ID 和名称一致才采用所选单位的工会。 + Sql sql = Sqls.create(""" + SELECT app.*, school.name activityName, school.applyType, school.belongUnionId, + school.belongClubId, team.name team, + COALESCE(NULLIF(app.unitname, ''), unit.name) unitname, + ev.allName, ev.id evid, ev.projectType, + CASE WHEN app.unitname = unit.name AND unit.unionid IS NOT NULL + AND basicUnit.unionid IS NOT NULL THEN unit.unionid + ELSE app.unionId END representedUnionId, + CASE WHEN app.unitname = unit.name AND unit.unionid IS NOT NULL + AND basicUnit.unionid IS NOT NULL THEN basicUnit.unionid + ELSE app.activityUnionId END representedActivityUnionId + FROM activity_school_apply app + LEFT JOIN activity_school school ON school.id = app.activityId + LEFT JOIN activity_school_team team ON team.id = app.teamId + LEFT JOIN activity_event ev ON ev.id = app.eventId + LEFT JOIN sys_unit unit ON unit.id = app.unitId + LEFT JOIN activity_basic_unit basicUnit ON basicUnit.id = app.unitId + WHERE app.activityId = @activityId + ORDER BY ev.allName DESC, ev.projectType ASC, team.name ASC + """).setParam("activityId", activityId); + return listMap(sql); + } + + @Override + public Map> groupRepresentedAppliesByUnion(String activityId) { + return listRepresentedApplies(activityId).stream() + .filter(row -> Strings.isNotBlank(row.getString("representedUnionId"))) + .collect(Collectors.groupingBy(row -> row.getString("representedUnionId"))); } @Override @Aop(TransAop.READ_COMMITTED) - public Sys_user createExternalUser(String username, String sex, String birthday, String mobile, String unitId) { + public Sys_user createExternalUser(String username, String loginname, String sex, String idcard, String mobile, String unitId) { String name = Strings.sNull(username).trim(); + String account = Strings.sNull(loginname).trim(); + String card = Strings.sNull(idcard).trim().toUpperCase(); String phone = Strings.sNull(mobile).trim(); if (name.isEmpty() || name.length() > 50 || !("男".equals(sex) || "女".equals(sex)) || !phone.matches("^1[3456789]\\d{9}$")) { throw new IllegalArgumentException("请填写外援姓名、性别及正确的手机号码"); } + if (account.length() > 50) { + throw new IllegalArgumentException("工号不能超过50位"); + } + if (!card.matches("^[0-9]{17}[0-9X]$") || !IdcardUtil.isValidCard(card)) { + throw new IllegalArgumentException("请填写正确的18位身份证号"); + } + String birthday = card.substring(6, 10) + "-" + card.substring(10, 12) + "-" + card.substring(12, 14); try { LocalDate.parse(birthday); } catch (DateTimeParseException | NullPointerException e) { - throw new IllegalArgumentException("请选择正确的出生年月"); + throw new IllegalArgumentException("身份证号中的出生日期不正确"); } ActivityBasicUnit unit = dao().fetch(ActivityBasicUnit.class, unitId); if (unit == null || Strings.isBlank(unit.getUnionid())) { throw new IllegalArgumentException("当前报名单位未配置活动工会"); } - // 工号在服务端分配,避免多个浏览器按相同的前端预览号建人。 + // 自动工号与手填工号都在同一锁内查重,避免并发创建出相同账号。 synchronized (EXTERNAL_NUMBER_LOCK) { + if (account.isEmpty()) account = previewExternalLoginname(); + if (dao().count(Sys_user.class, Cnd.where("loginname", "=", account)) > 0) { + throw new IllegalArgumentException("工号已存在,请更换工号"); + } + if (dao().count(Sys_user.class, Cnd.where("idcard", "=", card)) > 0) { + throw new IllegalArgumentException("身份证号已存在,请搜索并选择系统人员"); + } Sys_user user = new Sys_user(); user.setId(R.UU32()); - user.setLoginname(previewExternalLoginname()); + user.setLoginname(account); user.setUsername(name); user.setSex(sex); user.setBirthday(birthday); + user.setIdcard(card); user.setMobile(phone); user.setUnitid(unitId); user.setUnionid(unit.getUnionid()); @@ -102,17 +184,52 @@ public class ActivitySchoolApplyServiceImpl extends ViServiceImpl applies) { for (ActivitySchoolApply apply : applies) { Sys_user user = Strings.isBlank(apply.getUserId()) ? null : dao().fetch(Sys_user.class, apply.getUserId()); + if (Strings.isNotBlank(apply.getIdCard())) { + String card = apply.getIdCard().trim().toUpperCase(); + if (!IdcardUtil.isValidCard(card)) { + throw new IllegalArgumentException("报名人员身份证号不正确"); + } + apply.setIdCard(card); + } if (user != null && user.isDisabled() && Strings.isNotBlank(user.getLoginname()) && user.getLoginname().matches("wy[0-9]{6,}")) { - // 外援资料以创建时保存的用户记录为准,不接受浏览器修改工号或单位名称。 + // 工号和姓名保持系统身份;身份证、出生日期和电话保留本次报名的纠错值。 apply.setLoginname(user.getLoginname()); apply.setUsername(user.getUsername()); - apply.setSex(user.getSex()); - apply.setBirthday(user.getBirthday()); - apply.setMobile(user.getMobile()); - apply.setUnitId(user.getUnitid()); - apply.setUnitname("外援"); + if (Strings.isNotBlank(user.getSex())) apply.setSex(user.getSex()); + if (Strings.isBlank(apply.getIdCard())) apply.setIdCard(user.getIdcard()); + if (Strings.isBlank(apply.getBirthday())) apply.setBirthday(user.getBirthday()); + if (Strings.isBlank(apply.getMobile())) apply.setMobile(user.getMobile()); + if (Strings.isBlank(apply.getUnitId())) { + apply.setUnitId(user.getUnitid()); + } + if (Strings.isBlank(apply.getUnitname())) { + throw new IllegalArgumentException("请填写外援报名单位"); + } } + if (Strings.isNotBlank(apply.getUnitId()) && Strings.isNotBlank(apply.getUnitname())) { + Sys_unit selectedUnit = dao().fetch(Sys_unit.class, apply.getUnitId()); + // 单位来自系统下拉时,所属工会就是本次代表出战的工会;手填单位保留报名人工会。 + if (selectedUnit != null && apply.getUnitname().trim().equals(selectedUnit.getName())) { + ActivityBasicUnit activityUnit = dao().fetch(ActivityBasicUnit.class, apply.getUnitId()); + if (Strings.isBlank(selectedUnit.getUnionid()) || activityUnit == null + || Strings.isBlank(activityUnit.getUnionid())) { + throw new IllegalArgumentException("所选单位未配置活动工会,不能确定代表出战的工会"); + } + ActivityBasicUnion activityUnion = dao().fetch(ActivityBasicUnion.class, activityUnit.getUnionid()); + if (activityUnion == null) { + throw new IllegalArgumentException("所选单位对应的活动工会不存在"); + } + apply.setUnionId(selectedUnit.getUnionid()); + apply.setActivityUnionId(activityUnion.getId()); + apply.setActivityUnionName(activityUnion.getUnionname()); + } + } + } + // 重存时清理本操作人以前代表其他工会报名的记录,避免换单位后留下重复报名。 + if (!applies.isEmpty()) { + clear(Cnd.where("activityId", "=", activity).and("eventId", "=", eventId) + .and("applyUser", "=", applies.get(0).getApplyUser()).and("activityUnionId", "!=", union_id)); } clear(Cnd.where("activityId", "=", activity).and("activityUnionId", "=", union_id).and("eventId","=",eventId)); doApplies(applies); diff --git a/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolServiceImpl.java b/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolServiceImpl.java index f2293fa..a9345dc 100644 --- a/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolServiceImpl.java +++ b/src/main/java/io/v/nutz/activity/services/impl/ActivitySchoolServiceImpl.java @@ -97,7 +97,11 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl imp @Aop(TransAop.READ_COMMITTED) public String doReuseAdd(String sourceId, ActivitySchool school, ActivitySchoolEvent[] events) { findReuseActivity(sourceId); - if (school.getEventsIds() == null || school.getEventsIds().isEmpty()) { + if (school.getEventsIds() == null) { + school.setEventsIds(new java.util.ArrayList<>()); + } + boolean isDraft = Integer.valueOf(1).equals(school.getIsSave()); + if (!isDraft && school.getEventsIds().isEmpty()) { throw new IllegalArgumentException("请选择活动项目"); } ActivitySchoolEvent[] cleanEvents = Arrays.stream(events == null ? new ActivitySchoolEvent[0] : events) @@ -108,19 +112,22 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl imp event.setId(null); event.setActivityId(null); }).toArray(ActivitySchoolEvent[]::new); - for (String eventId : school.getEventsIds()) { - ActivityEvent event = dao().fetch(ActivityEvent.class, Cnd.where("id", "=", eventId).and("whetherEnable", "=", 1)); - if (event == null) { - throw new IllegalArgumentException("所选项目已停用或不存在,请重新选择"); - } - if (Arrays.stream(cleanEvents).noneMatch(config -> eventId.equals(config.getEventId()))) { - throw new IllegalArgumentException("项目配置不完整,请进入下一步完善项目信息"); + // 草稿允许暂不选择或配置项目;正式提交仍需验证项目可用且配置齐全。 + if (!isDraft) { + for (String eventId : school.getEventsIds()) { + ActivityEvent event = dao().fetch(ActivityEvent.class, Cnd.where("id", "=", eventId).and("whetherEnable", "=", 1)); + if (event == null) { + throw new IllegalArgumentException("所选项目已停用或不存在,请重新选择"); + } + if (Arrays.stream(cleanEvents).noneMatch(config -> eventId.equals(config.getEventId()))) { + throw new IllegalArgumentException("项目配置不完整,请进入下一步完善项目信息"); + } } } school.setId(null); school.setClose(false); school.setFoundDate(DateUtil.getDate()); - if (school.getActivityLevel() == 40002) { + if (Integer.valueOf(40002).equals(school.getActivityLevel())) { school.setBelongUnionId(ActivityRoleUtil.getCurrentUnionId(dao())); } else { school.setBelongUnionId(null); @@ -166,7 +173,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl imp public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) { activitySchool.setClose(false); ActivitySchool school = insert(activitySchool); - addTeam(school.getId(), events); + addTeam(school.getId(), events == null ? new ActivitySchoolEvent[0] : events); return school.getId(); } @@ -174,7 +181,12 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl imp @Override @Aop(TransAop.READ_COMMITTED) public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events, String applyEndTime) { - long time = DateUtil.formatDate(applyEndTime); + // 未填写报名结束时间的草稿也可再次保存;没有项目时按空配置处理。 + long time = Strings.isBlank(applyEndTime) ? 0 : DateUtil.formatDate(applyEndTime); + if (activitySchool.getEventsIds() == null) { + activitySchool.setEventsIds(new java.util.ArrayList<>()); + } + ActivitySchoolEvent[] safeEvents = events == null ? new ActivitySchoolEvent[0] : events; long l = System.currentTimeMillis(); if (l < time) { @@ -191,7 +203,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl imp return !activitySchool.getEventsIds().contains(v.getEventId()); }).collect(Collectors.toList()); - List collect1 = Lang.array2list(events).stream().filter(v -> { + List collect1 = Lang.array2list(safeEvents).stream().filter(v -> { return !query.stream().map(x -> x.getEventId()).collect(Collectors.toList()).contains(v.getEventId()) && (v.getId() == null || !v.getId().equals("allItems")); }).collect(Collectors.toList()); @@ -206,7 +218,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl imp if (collect1.size() > 0) { addTeam(activitySchool.getId(), collect1.toArray(new ActivitySchoolEvent[collect1.size()])); } - for (ActivitySchoolEvent event : events) { + for (ActivitySchoolEvent event : safeEvents) { update(event); } diff --git a/src/main/java/io/v/nutz/clothing/controller/ClothingRecordController.java b/src/main/java/io/v/nutz/clothing/controller/ClothingRecordController.java index d0a4560..929b293 100644 --- a/src/main/java/io/v/nutz/clothing/controller/ClothingRecordController.java +++ b/src/main/java/io/v/nutz/clothing/controller/ClothingRecordController.java @@ -21,6 +21,8 @@ import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.entity.Record; import org.nutz.dao.sql.Sql; +import org.nutz.aop.interceptor.ioc.TransAop; +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; @@ -67,16 +69,17 @@ public class ClothingRecordController { @At @RequiresAuthentication + @Aop(TransAop.READ_COMMITTED) public Object doDelete(String projectId, String year, String userId) { if(StrUtil.isBlank(projectId)) { - Result.error("项目不能为空!"); + return Result.error("项目不能为空!"); } if(StrUtil.isBlank(year)) { - Result.error("年份不能为空!"); + return Result.error("年份不能为空!"); } if(StrUtil.isBlank(userId)) { - Result.error("登记人不能为空!"); + return Result.error("登记人不能为空!"); } boolean b = clothingRegisterService.doDelete(projectId, year, userId); return b ? Result.success(): Result.error(); @@ -104,6 +107,10 @@ public class ClothingRecordController { return b ? Result.success(): Result.error(); } + /** + * year 为活动报名年度,projectId 为活动ID,userId 为登记人ID;旧项目记录按登记年度筛选。 + * request 和 response 为当前 HTTP 请求及下载响应;返回值为 void,内容写入响应流。 + */ @At @Ok("void") public void doExport(String year, String projectId, String userId, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -135,11 +142,13 @@ public class ClothingRecordController { FROM clothing_register c LEFT JOIN `user` u ON u.id = c.buyUserId + LEFT JOIN activity_school s ON s.id = c.projectId $condition """); - cnd.and("year(ctime)", "=", year); - cnd.and("projectId", "=", projectId); - cnd.and("userId", "=", userId); + // 导出与台账采用同一业务年度,旧项目记录回退到登记年度。 + cnd.and("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year); + cnd.and("c.projectId", "=", projectId); + cnd.and("c.userId", "=", userId); sql.setCondition(cnd); List buyUserList = clothingRegisterService.list(sql); for (int i = 0; i < buyUserList.size(); i++) { diff --git a/src/main/java/io/v/nutz/clothing/controller/ClothingRegisterController.java b/src/main/java/io/v/nutz/clothing/controller/ClothingRegisterController.java index b8e2d4d..59cf554 100644 --- a/src/main/java/io/v/nutz/clothing/controller/ClothingRegisterController.java +++ b/src/main/java/io/v/nutz/clothing/controller/ClothingRegisterController.java @@ -13,6 +13,8 @@ import org.apache.shiro.authz.annotation.RequiresPermissions; import org.nutz.dao.Cnd; import org.nutz.dao.Sqls; import org.nutz.dao.entity.Record; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.mvc.annotation.At; @@ -41,30 +43,43 @@ public class ClothingRegisterController { @RequiresPermissions("clothing.register") public void index() {} + /** pageForm 为分页和姓名工号条件;projectId 为活动ID;isBuy 为购买状态;sex、unionId 为可选筛选,返回人员分页数据。 */ @At @ViReturn - public Object pageData(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId) { + public Object pageData(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId) { - return clothingRegisterService.getRegisterUser(pageForm, year, projectId, isBuy, sex, unionId); + return clothingRegisterService.getRegisterUser(pageForm, projectId, isBuy, sex, unionId); } + /** + * ids 为所选人员ID;projectId 为活动ID,购买限制以该活动的报名年度和已选项目为准; + * isBuy 为购买状态,true 时仅移除当前活动记录,false 时登记; + * 成功返回 null 由 ViReturn 包装,业务校验失败返回含错误说明的 Result。 + */ @At @ViReturn - public Object doHandler(String[] ids, String projectId, Integer year, Boolean isBuy) { + @Aop(TransAop.READ_COMMITTED) + public Object doHandler(String[] ids, String projectId, Boolean isBuy) { - if(!isBuy) { - clothingRegisterService.add(ids, projectId); - } else { - clothingRegisterService.removeBuyUser(ids, projectId, year); + try { + if(!Boolean.TRUE.equals(isBuy)) { + clothingRegisterService.add(ids, projectId); + } else { + clothingRegisterService.removeBuyUser(ids, projectId); + } + } catch (IllegalArgumentException exception) { + // 重复购衣等业务校验错误直接返回给页面,供提交按钮显示明确原因。 + return Result.error(exception.getMessage()); } return null; } + /** year 为空时返回全部已提交活动,否则按报名开始时间所属年度筛选;data 数组的每项包含活动 id 和 name。 */ @At @ViReturn - public Object getBuyClothesProject() { + public Object getBuyClothesProject(Integer year) { - return clothingRegisterService.getBuyClothesProject(); + return clothingRegisterService.getBuyClothesProject(year); } @At diff --git a/src/main/java/io/v/nutz/clothing/services/ClothingRegisterService.java b/src/main/java/io/v/nutz/clothing/services/ClothingRegisterService.java index 82be0ac..49386ee 100644 --- a/src/main/java/io/v/nutz/clothing/services/ClothingRegisterService.java +++ b/src/main/java/io/v/nutz/clothing/services/ClothingRegisterService.java @@ -1,7 +1,6 @@ package io.v.nutz.clothing.services; import cn.wizzer.framework.page.Pagination; -import io.v.nutz.activity.models.ActivityBasicSettings; import io.v.nutz.clothing.models.ClothingRegister; import io.v.nutz.modules.models.PageForm; import io.v.nutz.service.ViService; @@ -20,29 +19,31 @@ public interface ClothingRegisterService extends ViService { /** * 列表查询 * - * @param pageForm: 表单 - * @param projectId: 项目id - * @param year: 年份 - * @return io.v.nutz.base.page.Pagination + * @param pageForm: 分页及查询表单 + * @param projectId: 活动ID,可为空 + * @param year: 活动报名年度;旧项目记录按登记年度筛选 + * @param unionId: 工会ID,可为空 + * @return Pagination,list 为按活动、登记人和年度分组的台账,totalCount 为分组总数 * @author JyuHsin * @date 2022/12/5 */ Pagination pageData(PageForm pageForm, String projectId, String year, String unionId); /** - * 获取所有买服装的项目 - * @author JyuHsin - * @date 2022/12/5 - * @return java.util.List + * 获取已提交的校级活动,供服装登记、记录和台账选择项目。 + * year 为空时返回全部年度,否则按活动报名开始时间所属年度筛选。 + * 返回的每项包含活动 id(作为 projectId 保存和筛选)及 name(下拉显示名称)。 */ - List getBuyClothesProject(); + List getBuyClothesProject(Integer year); /** * 获取项目人员名单 * @author JyuHsin * @date 2022/12/5 - * @param projectId: 项目id - * @return java.util.List + * @param projectId: 活动ID + * @param userId: 登记人ID + * @param year: 活动报名年度,可为空;旧项目记录按登记年度筛选 + * @return 人员列表,每项包含购买人资料及登记记录 */ List findBuyClothesUser(String projectId, String userId, String year); @@ -50,28 +51,25 @@ public interface ClothingRegisterService extends ViService { * 删除 * @author JyuHsin * @date 2022/12/5 - * @param projectId: 项目id - * @param year: 年份 - * @param userId: 登记人 - * @return void + * @param projectId: 活动ID + * @param year: 活动报名年度;旧项目记录按登记年度删除 + * @param userId: 登记人ID + * @return 是否完成指定活动、年度和登记人的删除 */ boolean doDelete(String projectId, String year, String userId); /** - * 服装登记菜单查询用户 - * @author JyuHsin - * @date 2022/12/5 - * @param pageForm: - * @param year: - * @param projectId: - * @param isBuy: - * @return cn.wizzer.framework.page.Pagination + * 查询服装登记人员。pageForm 包含页码、页大小和姓名或工号查询条件;projectId 为当前活动ID; + * isBuy 为购买状态,true 返回与活动年度相差不超过一年且同类项目已购买的人员,false 返回未购买人员; + * sex 为可选性别,unionId 为可选工会ID。返回 Pagination,list 为人员及可移除状态,totalCount 为总人数。 */ - Pagination getRegisterUser(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId); + Pagination getRegisterUser(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId); + /** ids 为本次登记的人员ID,projectId 为活动ID;两年内同类项目已有购买记录时拒绝提交。 */ Object add(String[] ids, String projectId); - boolean removeBuyUser(String[] ids, String projectId, Integer year); + /** ids 为人员ID,projectId 为当前活动ID;仅移除当前活动的登记记录,不删除其他同类活动的历史记录。 */ + boolean removeBuyUser(String[] ids, String projectId); boolean deleteBuyUser(String id); } diff --git a/src/main/java/io/v/nutz/clothing/services/impl/ClothingRegisterServiceImpl.java b/src/main/java/io/v/nutz/clothing/services/impl/ClothingRegisterServiceImpl.java index 107de21..6a02e1c 100644 --- a/src/main/java/io/v/nutz/clothing/services/impl/ClothingRegisterServiceImpl.java +++ b/src/main/java/io/v/nutz/clothing/services/impl/ClothingRegisterServiceImpl.java @@ -2,7 +2,7 @@ package io.v.nutz.clothing.services.impl; import cn.hutool.core.util.StrUtil; import cn.wizzer.framework.page.Pagination; -import io.v.nutz.activity.models.ActivityBasicSettings; +import io.v.nutz.activity.models.ActivitySchool; import io.v.nutz.base.utils.DateUtil; import io.v.nutz.base.utils.Vi; import io.v.nutz.clothing.models.ClothingRegister; @@ -20,13 +20,18 @@ 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.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.Strings; import org.nutz.lang.util.NutMap; import org.nutz.trans.Trans; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * @Author JyuHsin @@ -48,14 +53,15 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl Sql sql = Sqls.create(""" SELECT c.*, - YEAR ( c.ctime ) AS `year`, + COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime)) AS `year`, u.username, u.unionname, - a.name AS projectName, + COALESCE(s.name, a.name) AS projectName, count(c.buyUserId) AS buyCount FROM clothing_register c LEFT JOIN `user` u ON u.id = c.userId + LEFT JOIN activity_school s ON s.id = c.projectId LEFT JOIN activity_basic_settings a ON a.CODE = c.projectId $condition """); @@ -65,8 +71,9 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl } cnd.andExcludeEmpty("u.unionId", "=", unionId); cnd.andExcludeEmpty("c.projectId", "=", projectId); - cnd.andExcludeEmpty("year(c.ctime)", "=", year); - cnd.groupBy("c.userId"); + // 活动记录按报名年度归档;无法关联活动的旧记录仍按实际登记年度展示。 + cnd.andExcludeEmpty("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year); + cnd.groupBy("c.projectId", "c.userId", "COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))"); cnd.desc("year").asc("projectId"); sql.setCondition(cnd); @@ -74,8 +81,17 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl } @Override - public List getBuyClothesProject() { - return dao().query(ActivityBasicSettings.class, Cnd.where("clothing", "is not", null)); + public List getBuyClothesProject(Integer year) { + // isSave=1 是草稿;历史已提交活动可能为 NULL,需与活动管理页的判定保持一致。 + CndPlus cnd = CndPlus.create(); + cnd.and(new Static("(isSave IS NULL OR isSave <> 1)")); + cnd.andExcludeEmpty("YEAR(applyStartTime)", "=", year); + cnd.desc("startTime").desc("foundDate"); + return listMap(Sqls.create(""" + SELECT id, name + FROM activity_school + $condition + """).setCondition(cnd)); } @Override @@ -94,26 +110,38 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl FROM clothing_register c LEFT JOIN `user` u ON u.id = c.buyUserId + LEFT JOIN activity_school s ON s.id = c.projectId $condition """); cnd.andExcludeEmpty("c.projectId", "=", projectId); cnd.andExcludeEmpty("c.userId", "=", userId); - cnd.andExcludeEmpty("year(c.ctime)", "=", year); + cnd.andExcludeEmpty("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year); sql.setCondition(cnd); return listMap(sql); } @Override + @Aop(TransAop.READ_COMMITTED) public boolean doDelete(String projectId, String year, String userId) { - int clearCount = dao().clear(ClothingRegister.class, Cnd.where("projectId", "=", projectId) - .and("year(ctime)", "=", year).and("userId", "=", userId)); + Cnd cnd = Cnd.where("projectId", "=", projectId).and("userId", "=", userId); + ActivitySchool school = dao().fetch(ActivitySchool.class, projectId); + String applyStartTime = school == null ? null : school.getApplyStartTime(); + // 新活动以报名年度定位整组登记;旧项目没有活动年度时沿用登记时间筛选。 + if (Strings.isNotBlank(applyStartTime) && applyStartTime.length() >= 4) { + if (!year.equals(applyStartTime.substring(0, 4))) { + return false; + } + } else { + cnd.and("year(ctime)", "=", year); + } + int clearCount = dao().clear(ClothingRegister.class, cnd); return clearCount >= 0; } @Override - public Pagination getRegisterUser(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId) { + public Pagination getRegisterUser(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId) { CndPlus cnd = CndPlus.create(); Sql sql = Sqls.create(""" @@ -129,20 +157,105 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl if (Strings.isNotBlank(pageForm.getSearchName()) && Strings.isNotBlank(pageForm.getSearchKeyword())) { cnd.where().andLike(pageForm.getSearchName(), pageForm.getSearchKeyword()); } - if(year != null) { - int lastYear = year - 1; - cnd.and(new Static("id %s (select buyUserId from clothing_register where projectId = '%s' and (year(ctime) = '%s' or year(ctime) = '%s'))" - .formatted(isBuy ? "in" : "not in", projectId, year, lastYear))); + if (Strings.isBlank(projectId)) { + // 没有活动时不能把全部会员误显示为该活动的“未购买”人员。 + cnd.and(new Static("1 = 0")); + } else { + // 活动名称和ID每年可能变化,购买状态按活动选中项目的运动项目及活动年度判断。 + Set purchasedIds = findPurchasedUserIds(projectId); + if (purchasedIds.isEmpty()) { + if (Boolean.TRUE.equals(isBuy)) cnd.and(new Static("1 = 0")); + } else { + cnd.and("u.id", Boolean.TRUE.equals(isBuy) ? "in" : "not in", purchasedIds.toArray(new String[0])); + } } sql.setCondition(cnd); - return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + Pagination result = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + if (Boolean.TRUE.equals(isBuy) && Strings.isNotBlank(projectId)) { + // 历史活动中已购买的人员可查看,但只能移除当前活动自身的登记记录。 + Set currentIds = listMap(Sqls.create("SELECT DISTINCT buyUserId AS buy_user_id FROM clothing_register WHERE projectId = @projectId") + .setParam("projectId", projectId)).stream() + .map(row -> row.getString("buy_user_id")).collect(Collectors.toSet()); + List users = result.getList(); + users.forEach(user -> user.setv("canRemove", currentIds.contains(user.getString("id")))); + } + return result; + } + + /** + * projectId 为当前活动ID;返回该活动选择的运动项目标识。项目未配置 sports 时以项目ID兜底, + * 避免把不同项目误判为同类;已选项目为空时拒绝登记,防止绕过重复购买限制。 + */ + private List getSelectedSportKeys(String projectId) { + List events = listMap(Sqls.create(""" + SELECT DISTINCT COALESCE(NULLIF(ae.sports, ''), ae.id) AS sport_key + FROM activity_school_event ase + JOIN activity_event ae ON ae.id = ase.eventId + WHERE ase.activityId = @projectId + """).setParam("projectId", projectId)); + List keys = events.stream().map(event -> event.getString("sport_key")) + .filter(Strings::isNotBlank).distinct().collect(Collectors.toList()); + if (keys.isEmpty()) throw new IllegalArgumentException("当前活动未配置可识别的活动项目,无法登记服装"); + return keys; + } + + /** + * projectId 为当前活动ID;返回与活动年度相差不超过一年、购买过任一相同运动项目服装的人员ID。 + * 年度取活动报名开始时间,避免跨年补登记时因 clothing_register.ctime 不同而漏判。 + */ + private Set findPurchasedUserIds(String projectId) { + ActivitySchool school = dao().fetch(ActivitySchool.class, projectId); + if (school == null || Integer.valueOf(1).equals(school.getIsSave())) { + throw new IllegalArgumentException("活动不存在或尚未提交"); + } + String applyStartTime = school.getApplyStartTime(); + if (Strings.isBlank(applyStartTime) || applyStartTime.length() < 4) { + throw new IllegalArgumentException("当前活动未设置报名开始时间,无法判断两年购买范围"); + } + int activityYear; + try { + activityYear = Integer.parseInt(applyStartTime.substring(0, 4)); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException("当前活动报名开始时间格式不正确", exception); + } + List sportKeys = getSelectedSportKeys(projectId); + CndPlus cnd = CndPlus.create(); + // 补录历史活动时也要检查后一年的购买记录,避免登记顺序改变查重结果。 + cnd.and("YEAR(s.applyStartTime)", "in", new Integer[]{activityYear - 1, activityYear, activityYear + 1}); + cnd.and("COALESCE(NULLIF(ae.sports, ''), ae.id)", "in", sportKeys.toArray(new String[0])); + Sql sql = Sqls.create(""" + SELECT DISTINCT c.buyUserId AS buy_user_id + FROM clothing_register c + JOIN activity_school s ON s.id = c.projectId + JOIN activity_school_event ase ON ase.activityId = s.id + JOIN activity_event ae ON ae.id = ase.eventId + $condition + """).setCondition(cnd); + return listMap(sql).stream().map(row -> row.getString("buy_user_id")) + .filter(Strings::isNotBlank).collect(Collectors.toSet()); } @Override + @Aop(TransAop.READ_COMMITTED) public Object add(String[] ids, String projectId) { + if (ids == null || ids.length == 0 || Strings.isBlank(projectId)) { + throw new IllegalArgumentException("请选择活动和登记人员"); + } + List userIds = Arrays.stream(ids).filter(Strings::isNotBlank).distinct().sorted().collect(Collectors.toList()); + if (userIds.isEmpty()) throw new IllegalArgumentException("请选择登记人员"); + // 锁定人员行后再查重,防止并发提交同时通过两年内同项目的校验。 + for (String id : userIds) { + if (listMap(Sqls.create("SELECT id FROM `user` WHERE id = @id FOR UPDATE").setParam("id", id)).isEmpty()) { + throw new IllegalArgumentException("所选人员不存在,请刷新名单"); + } + } + Set purchasedIds = findPurchasedUserIds(projectId); + if (userIds.stream().anyMatch(purchasedIds::contains)) { + throw new IllegalArgumentException("所选人员在近两年内已购买同类项目服装,不能重复提交,请刷新名单"); + } List clothingRegisterList = new ArrayList<>(); - for (String id : ids) { + for (String id : userIds) { ClothingRegister clothingRegister = new ClothingRegister(); clothingRegister.setProjectId(projectId); clothingRegister.setUserId(ShiroUtil.getPrincipalProperty("id").toString()); @@ -154,12 +267,17 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl } @Override - public boolean removeBuyUser(String[] ids, String projectId, Integer year) { + @Aop(TransAop.READ_COMMITTED) + public boolean removeBuyUser(String[] ids, String projectId) { + if (ids == null || ids.length == 0 || Strings.isBlank(projectId)) { + throw new IllegalArgumentException("请选择当前活动中需要移除的人员"); + } Trans.exec(() -> { for (String id : ids) { - dao().clear(ClothingRegister.class, Cnd.where("projectId", "=", projectId) - .and("year(ctime)", "=", year).and("buyUserId", "=", id)); + Cnd cnd = Cnd.where("projectId", "=", projectId).and("buyUserId", "=", id); + // 同类历史活动的购买记录仍保留,仅删除当前活动中选中的登记。 + dao().clear(ClothingRegister.class, cnd); } }); diff --git a/src/main/java/io/v/nutz/sys/services/SysConfigService.java b/src/main/java/io/v/nutz/sys/services/SysConfigService.java index ffe54ba..fce34d0 100644 --- a/src/main/java/io/v/nutz/sys/services/SysConfigService.java +++ b/src/main/java/io/v/nutz/sys/services/SysConfigService.java @@ -14,4 +14,9 @@ public interface SysConfigService extends BaseService { * @return */ List getAllList(); + + /** + * 确保系统配置中存在学校待办开关;首次创建时默认关闭。 + */ + void ensureTodoTaskConfig(); } diff --git a/src/main/java/io/v/nutz/sys/services/impl/SysConfigServiceImpl.java b/src/main/java/io/v/nutz/sys/services/impl/SysConfigServiceImpl.java index e9e1816..99d41e8 100644 --- a/src/main/java/io/v/nutz/sys/services/impl/SysConfigServiceImpl.java +++ b/src/main/java/io/v/nutz/sys/services/impl/SysConfigServiceImpl.java @@ -3,8 +3,10 @@ package io.v.nutz.sys.services.impl; import io.v.nutz.base.service.impl.BaseServiceImpl; import io.v.nutz.sys.models.Sys_config; import io.v.nutz.sys.services.SysConfigService; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.IocBean; import java.util.List; @@ -21,4 +23,16 @@ public class SysConfigServiceImpl extends BaseServiceImpl implements public List getAllList() { return this.query(Cnd.where("delFlag", "=", false)); } -} \ No newline at end of file + + @Override + @Aop(TransAop.READ_COMMITTED) + public void ensureTodoTaskConfig() { + if (fetch("TodoTaskEnabled") == null) { + Sys_config config = new Sys_config(); + config.setConfigKey("TodoTaskEnabled"); + config.setConfigValue("false"); + config.setNote("启用学校统一待办推送、更新和删除;关闭时不调用学校待办网关"); + insert(config); + } + } +} diff --git a/src/main/java/io/v/nutz/todoTask/TodoTaskInterfaceImpl.java b/src/main/java/io/v/nutz/todoTask/TodoTaskInterfaceImpl.java index eab61ff..11c3814 100644 --- a/src/main/java/io/v/nutz/todoTask/TodoTaskInterfaceImpl.java +++ b/src/main/java/io/v/nutz/todoTask/TodoTaskInterfaceImpl.java @@ -5,6 +5,8 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; +import io.v.nutz.sys.models.Sys_config; +import io.v.nutz.sys.services.SysConfigService; import io.v.nutz.web.commons.utils.ShiroUtil; import lombok.extern.slf4j.Slf4j; import org.nutz.dao.Dao; @@ -26,6 +28,17 @@ import java.util.Map; @Slf4j public class TodoTaskInterfaceImpl implements TodoTaskInterface { + /** + * 只有系统配置明确开启时,才调用学校统一待办网关。 + */ + private boolean isTodoTaskEnabled() { + Sys_config config = sysConfigService.fetch("TodoTaskEnabled"); + return config != null && "true".equals(config.getConfigValue()); + } + + @Inject + private SysConfigService sysConfigService; + @Inject private RedisService redisService; @@ -52,6 +65,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface { @Override public void pushTask(TodoTask todoTask) { + if (!isTodoTaskEnabled()) { + return; + } log.info("【推送待办入参】:" + Json.toJson(todoTask)); String accessToken = getAccessToken(); HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.PUSH_TASK_URL); @@ -69,6 +85,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface { @Override public void updateTask(TodoTask todoTask) { + if (!isTodoTaskEnabled()) { + return; + } log.info("【更新待办入参】:" + Json.toJson(todoTask)); String accessToken = getAccessToken(); HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.UPDATE_TASK_URL); @@ -86,6 +105,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface { @Override public void deleteTask(TodoTask todoTask) { + if (!isTodoTaskEnabled()) { + return; + } log.info("【删除待办入参】:" + Json.toJson(todoTask)); String accessToken = getAccessToken(); HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.DELETE_TASK_URL); diff --git a/src/main/java/io/v/nutz/web/controllers/platform/sys/SysConfController.java b/src/main/java/io/v/nutz/web/controllers/platform/sys/SysConfController.java index fd39a0a..1a6f8c6 100644 --- a/src/main/java/io/v/nutz/web/controllers/platform/sys/SysConfController.java +++ b/src/main/java/io/v/nutz/web/controllers/platform/sys/SysConfController.java @@ -34,7 +34,7 @@ public class SysConfController { @Ok("beetl:/platform/sys/conf/index.html") @RequiresPermissions("sys.manager.conf") public void index() { - + sysConfigService.ensureTodoTaskConfig(); } @At diff --git a/src/main/java/io/v/nutz/zgfw/controller/condolence/CondolenceApplyCon.java b/src/main/java/io/v/nutz/zgfw/controller/condolence/CondolenceApplyCon.java index 2b4d664..607ddd8 100644 --- a/src/main/java/io/v/nutz/zgfw/controller/condolence/CondolenceApplyCon.java +++ b/src/main/java/io/v/nutz/zgfw/controller/condolence/CondolenceApplyCon.java @@ -27,6 +27,8 @@ 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.aop.interceptor.ioc.TransAop; +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; @@ -98,7 +100,12 @@ public class CondolenceApplyCon { @At @ViReturn + @Aop(TransAop.READ_COMMITTED) public Object doAdd(@Param("data") Condolence condolence, String sign) { + if (condolenceService.hasHospitalCondolenceInCurrentYear(condolence)) { + return Result.error().addMsg("住院慰问每年只能申请一次,请于下一年度再申请"); + } + List files = condolence.getFiles(); if (Lang.isEmpty(files)) { condolence.setFiles_num("0"); diff --git a/src/main/java/io/v/nutz/zgfw/service/CondolenceService.java b/src/main/java/io/v/nutz/zgfw/service/CondolenceService.java index 6aa7a24..9e62cbb 100644 --- a/src/main/java/io/v/nutz/zgfw/service/CondolenceService.java +++ b/src/main/java/io/v/nutz/zgfw/service/CondolenceService.java @@ -19,6 +19,14 @@ public interface CondolenceService extends ViService { */ NutMap findOne(String id); + /** + * 判断被慰问人在当前自然年是否已有住院慰问申请。 + * + * @param application 本次申请,包含慰问类型、被慰问人和编辑时的申请 ID + * @return 已有其他同年住院慰问申请时返回 true,否则返回 false + */ + boolean hasHospitalCondolenceInCurrentYear(Condolence application); + /** * 添加 * diff --git a/src/main/java/io/v/nutz/zgfw/service/impl/CondolenceServiceImpl.java b/src/main/java/io/v/nutz/zgfw/service/impl/CondolenceServiceImpl.java index 1a0ec8b..9d99442 100644 --- a/src/main/java/io/v/nutz/zgfw/service/impl/CondolenceServiceImpl.java +++ b/src/main/java/io/v/nutz/zgfw/service/impl/CondolenceServiceImpl.java @@ -2,9 +2,11 @@ package io.v.nutz.zgfw.service.impl; import cn.wizzer.framework.base.service.BaseService; +import io.v.nutz.base.utils.DateUtil; import io.v.nutz.modules.services.ReviewService; import io.v.nutz.service.impl.ViServiceImpl; import io.v.nutz.zgfw.model.condolence.Condolence; +import io.v.nutz.zgfw.model.condolence.CondolenceType; import io.v.nutz.zgfw.service.CondolenceService; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; @@ -28,9 +30,30 @@ public class CondolenceServiceImpl extends ViServiceImpl implements private ReviewService reviewService; @Inject private BaseService baseService; + private final Dao condolenceDao; public CondolenceServiceImpl(Dao dao) { super(dao); + this.condolenceDao = dao; + } + + @Override + public boolean hasHospitalCondolenceInCurrentYear(Condolence application) { + CondolenceType type = condolenceDao.fetch(CondolenceType.class, application.getType()); + if (type == null || !"C".equals(type.getCode())) { + return false; + } + + // 按被慰问人和申请年份限制次数;编辑时排除当前申请,已退回的其他申请仍计入次数。 + String year = String.valueOf(DateUtil.getYear()); + Cnd cnd = Cnd.where("be_user", "=", application.getBe_user()) + .and("type", "=", application.getType()) + .and("apply_time", ">=", year + "-01-01") + .and("apply_time", "<", (DateUtil.getYear() + 1) + "-01-01"); + if (Strings.isNotBlank(application.getId())) { + cnd.and("id", "!=", application.getId()); + } + return count(cnd) > 0; } @Override diff --git a/src/main/resources/static/assets/platform/module/activity/activity.js b/src/main/resources/static/assets/platform/module/activity/activity.js index 0940b78..d8b7ff9 100644 --- a/src/main/resources/static/assets/platform/module/activity/activity.js +++ b/src/main/resources/static/assets/platform/module/activity/activity.js @@ -38,10 +38,14 @@ window.activity = { 2: '部门工会报名', 3: '小程序报名' }, - getStatus(close, applyStartTime, applyEndTime, startTime, endTime) { + getStatus(close, applyStartTime, applyEndTime, startTime, endTime, isSave) { if (close) { return `活动已关闭` } + // 管理列表传入 isSave;已保存但未提交的活动尚未开放报名。 + if (isSave === true || isSave === 1 || isSave === '1') { + return `未提交` + } const now = moment().valueOf() const dateNow = moment(moment(moment().format('YYYY-MM-DD')).valueOf()).valueOf() @@ -67,4 +71,4 @@ window.activity = { return false }, }, -} \ No newline at end of file +} diff --git a/src/main/resources/static/components/activity/school/AddActivity.vue b/src/main/resources/static/components/activity/school/AddActivity.vue index 2d1ea86..108a6eb 100644 --- a/src/main/resources/static/components/activity/school/AddActivity.vue +++ b/src/main/resources/static/components/activity/school/AddActivity.vue @@ -649,7 +649,6 @@ module.exports = { applyTime: [{required: true, message: '必填', trigger: ['blur', 'change']}], unitSponsor: [{required: true, message: '必填', trigger: ['blur', 'change']}], // unitJointly: [{required: true, message: '必填', trigger: ['blur', 'change']}], - time: [{required: true, message: '必填', trigger: ['blur', 'change']}], activityGroupId: [{required: true, message: '必填', trigger: ['blur', 'change']}], gameStyle: [{required: true, message: '必填', trigger: ['blur', 'change']}], applyWay: [{required: true, message: '必填', trigger: ['blur', 'change']}], @@ -777,7 +776,8 @@ module.exports = { }, // 合并当前所选项目与已沿用配置;新增选择使用原有默认值,已移除项目不再提交。 buildReuseEvents() { - return this.events.filter((event) => this.form.eventsIds.includes(event.id)).map((event) => { + const selectedIds = this.form.eventsIds || [] + return this.events.filter((event) => selectedIds.includes(event.id)).map((event) => { const existing = this.checkedEventInfo.find((config) => config.eventId === event.id) const config = existing ? clone(existing) : { eventId: event.id, projectType: event.projectType, leanderNum: 0, coachNum: 0, @@ -792,7 +792,7 @@ module.exports = { return config }) }, - // isSave 区分保存和提交;新接口返回新活动ID,保存成功后转入该新活动的编辑状态。 + // isSave 区分保存草稿和提交;草稿跳过字段校验,提交仍校验报名、活动及项目配置。 doReuseAdd(isSave) { if (this.formLoading || this.reuseLoading) return if (!this.reuseReady) { @@ -801,70 +801,66 @@ module.exports = { } const save = () => { const form = clone(this.form) - if (!form.applyTime || form.applyTime.length !== 2 || !form.time || form.time.length !== 2) { - this.$message.warning('请重新填写报名日期和活动时间') - return - } - if (moment(form.time[0]).valueOf() <= moment(form.applyTime[0]).valueOf()) { - this.$message.error('活动时间必须晚于报名时间') - return - } - if (!form.eventsIds.length) { - this.$message.warning('请选择活动项目') - return - } - if (this.getArrDifference(form.unitJointly, form.unitSponsor).length === 0) { - this.$message.error('协办单位里存在主办单位') - return + if (!isSave) { + if (!form.applyTime || form.applyTime.length !== 2 || !form.applyTime[0] || !form.applyTime[1]) { + this.$message.warning('请填写报名日期') + return + } + if (!form.time || form.time.length !== 2 || !form.time[0] || !form.time[1]) { + this.$message.warning('请填写活动时间') + return + } + if (moment(form.time[0]).valueOf() <= moment(form.applyTime[0]).valueOf()) { + this.$message.error('活动时间必须晚于报名时间') + return + } + if (!form.eventsIds || !form.eventsIds.length) { + this.$message.warning('请选择活动项目') + return + } + if (this.getArrDifference(form.unitJointly || [], form.unitSponsor || []).length === 0) { + this.$message.error('协办单位里存在主办单位') + return + } } this.$set(form, 'id', null) - this.$set(form, 'isSave', isSave) - this.$set(form, 'applyStartTime', form.applyTime[0]) - this.$set(form, 'applyEndTime', form.applyTime[1]) - this.$set(form, 'startTime', form.time[0]) - this.$set(form, 'endTime', form.time[1]) + this.$set(form, 'isSave', isSave ? 1 : 0) + // 草稿可没有报名和活动时间,缺失时明确传 null,避免复制旧活动日期。 + this.$set(form, 'applyStartTime', form.applyTime && form.applyTime[0] && form.applyTime[1] ? form.applyTime[0] : null) + this.$set(form, 'applyEndTime', form.applyTime && form.applyTime[0] && form.applyTime[1] ? form.applyTime[1] : null) + this.$set(form, 'startTime', form.time && form.time[0] && form.time[1] ? form.time[0] : null) + this.$set(form, 'endTime', form.time && form.time[0] && form.time[1] ? form.time[1] : null) this.$set(this, 'formLoading', true) - const loading = this.$loading({lock: true, text: '正在提交...', background: COVER_LAYER_COLOR}) - return $.post('/platform/activity/school/info/mange/doReuseAdd', { + const loading = this.$loading({lock: true, text: isSave ? '正在保存...' : '正在提交...', background: COVER_LAYER_COLOR}) + const request = $.post('/platform/activity/school/info/mange/doReuseAdd', { sourceId: this.reuseActivityId, data: JSON.stringify(form), events: JSON.stringify(this.buildReuseEvents()) - }).then((resp) => { + }) + // jQuery 1.x 的 then 回调抛错后可能中断后续回调;直接监听请求结束以关闭遮罩。 + request.always(() => { + this.$set(this, 'formLoading', false) + loading.close() + }) + return request.then((resp) => { if (resp.code !== 0) { this.$message.error(resp.msg) return } + if (isSave) { + this.$message.success('保存成功') + window.location.href = '/platform/activity/school/info/mange' + return + } this.$set(this.form, 'id', resp.data) this.$set(this, 'reuseActivityId', null) - if (isSave) { - this.$emit('flush') - this.$message.success('保存成功') - // 保存后读取新项目主键,后续编辑必须更新新活动的项目,不能继续使用空主键。 - return $.post('/platform/activity/school/info/mange/findOne', {id: resp.data}).then((saved) => { - if (saved.code !== 0) { - this.$message.error('活动已保存,回显失败,请从活动列表重新打开') - return - } - const configs = (saved.data.schoolEvents || []).map((event) => { - this.$set(event, 'unionLimit', this.reuseArray(event.unionLimit)) - this.$set(event, 'ageDate', event.startAgeDate ? [event.startAgeDate, event.endAgeDate] : []) - return event - }) - this.$set(this, 'checkedEventInfo', configs) - this.$set(this, 'checkedEventInfo2', clone(configs)) - this.$set(this, 'eventsIds', clone(this.form.eventsIds)) - const selected = configs.find((event) => event.eventId === this.eventForm.checkedEventId) - if (selected) { - this.$set(this, 'eventForm', Object.assign({}, selected, {checkedEventId: selected.eventId})) - } - }) - } this.$emit('flip') - }).fail(() => { + }, () => { this.$message.error('请求失败,请检查活动列表后重试') - }).always(() => { - this.$set(this, 'formLoading', false) - loading.close() }) } + if (isSave) { + if (this.$refs.activityForm) this.$refs.activityForm.clearValidate() + return save() + } if (this.$refs.activityForm) { this.$refs.activityForm.validate((valid) => { if (valid) save() @@ -1110,7 +1106,8 @@ module.exports = { return } - if (moment(this.form.time[0]).valueOf() <= moment(this.form.applyTime[0]).valueOf()) { + // 活动时间可在保存草稿后补填;已填写时仍需晚于报名开始时间。 + if (this.form.time && this.form.time[0] && this.form.time[1] && moment(this.form.time[0]).valueOf() <= moment(this.form.applyTime[0]).valueOf()) { this.$notify.error({title: '错误', message: '活动时间必须晚于报名时间'}); return } @@ -1247,7 +1244,7 @@ module.exports = { this.clubOptions = await getClubsByRole() this.eventsIds = clone(JSON.parse(v.eventsIds)) v.applyTime = [v.applyStartTime, v.applyEndTime] - v.time = [v.startTime, v.endTime] + this.$set(v, 'time', v.startTime && v.endTime ? [v.startTime, v.endTime] : null) v.eventsIds = JSON.parse(v.eventsIds) v.applyWay = JSON.parse(v.applyWay) v.unitSponsor = JSON.parse(v.unitSponsor) @@ -1323,6 +1320,12 @@ module.exports = { if (this.reuseLoading || this.formLoading) return if (this.reuseActivityId && !this.form.id) return this.doReuseAdd(false) + // 提交会开放活动,必须先补齐活动时间;仅保存草稿允许留空。 + if (!this.form.time || this.form.time.length !== 2 || !this.form.time[0] || !this.form.time[1]) { + this.$message.warning('请填写活动时间后再提交') + return + } + const deleteEventIds = this.eventsIds.filter(v => !this.form.eventsIds.includes(v)) const {data} = await $.get(loc() + "/getEventApply", { activityId: this.form.id, @@ -1340,8 +1343,10 @@ module.exports = { await this.doOperate() } }, - async doSave() { + doSave() { if (this.reuseLoading || this.formLoading) return + // 保存草稿不触发表单必填校验;下一步和提交仍按各自规则处理。 + if (this.$refs.activityForm) this.$refs.activityForm.clearValidate() if (this.reuseActivityId && !this.form.id) return this.doReuseAdd(true) let url = this.form.id ? "/doEdit" : "/doAdd" @@ -1364,25 +1369,32 @@ module.exports = { const form = clone(this.form) form.restrictMaxNum = form.leanderNum + form.coachNum + form.athletesMaxNum + form.substituteNum form.restrictMinNum = form.athletesMaxNum - form.isSave = true + form.isSave = 1 const {time, applyTime} = form - if (applyTime && applyTime.length) { + if (applyTime && applyTime.length === 2 && applyTime[0] && applyTime[1]) { form.applyStartTime = applyTime[0] form.applyEndTime = applyTime[1] form.applyTime = undefined + } else { + form.applyStartTime = null + form.applyEndTime = null } - if (time && time.length) { + if (time && time.length === 2 && time[0] && time[1]) { form.startTime = time[0] form.endTime = time[1] form.time = undefined + } else { + // 清空已有活动时间时显式传 null,避免编辑草稿时沿用旧值。 + form.startTime = null + form.endTime = null } if (this.eventForm.checkedEventId == "allItems") { events = events.filter(v => v.projectType == 1 && v.id != "allItems") - } else { + } else if (this.eventForm.checkedEventId) { this.$set(this.eventForm, "activityId", this.form.id) this.$set(this.eventForm, "eventId", this.eventForm.checkedEventId) if (this.eventForm.ageDate && this.eventForm.ageDate.length > 0) { @@ -1391,26 +1403,29 @@ module.exports = { } events = this.eventForm } + this.$set(this, 'formLoading', true) const loading = this.$loading({ lock: true, - text: '正在提交...', + text: '正在保存...', spinner: 'el-icon-loading', background: COVER_LAYER_COLOR }); - const resp = await $.post("/platform/activity/school/info/mange" + url, { + return $.post("/platform/activity/school/info/mange" + url, { data: JSON.stringify(form), events: JSON.stringify(events) - }) - - closeLoadingFunc(loading, resp, async (data) => { - - this.$emit('flush') - if (!this.form.id) form.id = data - await this.openEdit(form, false) - - }, (data, msg) => { - this.$message.error(msg); + }).then((resp) => { + if (resp.code === 0) { + this.$message.success('保存成功') + window.location.href = '/platform/activity/school/info/mange' + } else { + this.$message.error(resp.msg) + } + }, () => { + this.$message.error('保存失败,请检查活动列表后重试') + }).always(() => { + this.$set(this, 'formLoading', false) + loading.close() }) }, examineTrans(form) { diff --git a/src/main/resources/views/platform/activity/school/ActivityApply.html b/src/main/resources/views/platform/activity/school/ActivityApply.html index a6cadc9..5fc67c3 100644 --- a/src/main/resources/views/platform/activity/school/ActivityApply.html +++ b/src/main/resources/views/platform/activity/school/ActivityApply.html @@ -906,39 +906,58 @@ layout("/layouts/platform.html"){ - + + + - - + 男 女 + + + - - + - - + + + + + + + + + 适合参赛 + 不适合参赛 + 取 消 - 确 定 + 确 定 @@ -949,35 +968,58 @@ layout("/layouts/platform.html"){ > - + + + - - + 男 女 + + + - + - - + + + + + + + + + 适合参赛 + 不适合参赛 + 取 消 - 确 定 + 确 定 @@ -1041,6 +1083,10 @@ layout("/layouts/platform.html"){ }, activeName: '', userOptions: [], + selectedExternalUser: null, + selectedExternalUnit: null, + externalSearchLoading: false, + externalSearchQuery: '', applyUsers: [], applyValue: [], applyData: [], @@ -1093,13 +1139,29 @@ layout("/layouts/platform.html"){ }, lsryRules: { username: [{required: true, message: '请填写姓名', trigger: 'blur'}], - loginname: [{required: true, message: '必填', trigger: ['blur', 'change']}], + loginname: [{validator: (rule, value, callback) => { + if (this.formData.matchedUserId && !value) { + callback(new Error('所选系统人员没有工号')) + return + } + callback() + }, trigger: ['blur', 'change']}], sex: [{required: true, message: '必填', trigger: ['blur', 'change']}], + idcard: [{validator: (rule, value, callback) => { + const card = (value || '').toUpperCase() + const date = card.substring(6, 14) + if (!/^[0-9]{17}[0-9X]$/.test(card) || !moment(date, 'YYYYMMDD', true).isValid()) { + callback(new Error('请填写正确的18位身份证号')) + return + } + callback() + }, trigger: ['blur', 'change']}], birthday: [{required: true, message: '请选择出生年月', trigger: 'change'}], mobile: [ {required: true, message: '手机号码', trigger: ['blur', 'change']}, {pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码'}], - unitId: [{required: true, message: '必填', trigger: ['blur', 'change']}], + unitname: [{required: true, message: '请选择或填写单位', trigger: ['blur', 'change']}], + fitForCompetition: [{required: true, message: '请选择身体条件', trigger: 'change'}], }, submitLoading: false, selection: [], @@ -1686,89 +1748,200 @@ layout("/layouts/platform.html"){ this.leaderOptions = []; } }, - doAddUser(single) { - this.$refs[single ? 'singleExternalForm' : 'form'].validate((valid) => { - if (valid) { - this.formLoading = true - $.post(loc() + "/addExternalUser", { - username: this.formData.username, - sex: this.formData.sex, - birthday: this.formData.birthday, - mobile: this.formData.mobile - }).then((res) => { - if (res.code !== 0) { - this.notifyError(res.msg || '外援创建失败') - return - } - const user = res.data - const data = { - ...this.formData, - userId: user.id, - loginname: user.loginname, - username: user.username, - sex: user.sex, - birthday: user.birthday, - mobile: user.mobile, - unitId: user.unitid, - unitname: '外援', - age: moment().diff(moment(user.birthday), 'years'), - eventId: this.eventData.eventId, - activityId: this.activityData.id, - team: single ? null : this.teams.find(x => x.id === this.formData.teamId).name, - isAddUser: true - } - if (single) { - this.appliedData3.push(data) - } else { - this.appliedData.push(data) - } - this.sortApplyed() - this.flushApplyed(this.appliedData) - this.notifySuccess('已添加外援') - this.dialogVisible = false - this.dialogVisible2 = false - }).fail(() => { - this.notifyError('外援创建失败,请重试') - }).always(() => { - this.formLoading = false - }) - } else { - this.$notify.error({title: '错误', message: '存在未填写的的必填项'}); + searchExternalUser(query, callback) { + // 姓名输入框既可搜索系统人员,也可直接填写新人员姓名。 + this.externalSearchQuery = query + if (!query || !query.trim()) { + this.externalSearchLoading = false + callback([]) + return + } + this.externalSearchLoading = true + $.post(loc() + "/getSysUser", {queryString: query}).then((res) => { + if (this.externalSearchQuery !== query) { + callback([]) + return } + const users = res.code === 0 ? (res.data || []) : [] + callback(users.map(user => ({...user, value: user.username}))) + }).fail(() => { + callback([]) + }).always(() => { + if (this.externalSearchQuery === query) this.externalSearchLoading = false + }) + }, + changeExternalName(value) { + if (!this.selectedExternalUser || value === this.selectedExternalUser.username) return + // 修改已选姓名即退出匹配,清除旧人的资料,避免作为新人员误提交。 + this.selectedExternalUser = null + this.$set(this.formData, 'matchedUserId', '') + const fields = ['loginname', 'sex', 'idcard', 'birthday', 'mobile', 'unitname'] + fields.forEach(key => { + this.$set(this.formData, key, '') + }) + this.$set(this.formData, 'unitId', "${@shiro.getPrincipalProperty('unit').getId()}") + this.selectedExternalUnit = null + }, + selectExternalUser(user) { + // 选中候选人后复用其账号,系统字段为空的部分可在下方补填。 + this.selectedExternalUser = user + this.$set(this.formData, 'matchedUserId', user.id) + const fields = ['username', 'loginname', 'sex', 'idcard', 'birthday', 'mobile'] + fields.forEach(key => { + this.$set(this.formData, key, user[key] || '') + }) + const card = (user.idcard || '').toUpperCase() + const date = card.substring(6, 14) + if (!user.birthday && /^[0-9]{17}[0-9X]$/.test(card) && moment(date, 'YYYYMMDD', true).isValid()) { + this.$set(this.formData, 'birthday', moment(date, 'YYYYMMDD').format('YYYY-MM-DD')) + } + const unit = this.unitOptions.find(item => item.id === user.unitid) + this.$set(this.formData, 'unitId', unit ? unit.id : '') + this.$set(this.formData, 'unitname', unit ? unit.name : '') + }, + changeExternalUnit(id) { + const unit = this.unitOptions.find(item => item.id === id) + this.$set(this.formData, 'unitname', unit ? unit.name : '') + }, + searchExternalUnit(query, callback) { + const units = this.unitOptions.filter(item => !query || item.name.indexOf(query) > -1) + callback(units.slice(0, 30).map(item => ({...item, value: item.name}))) + }, + changeExternalUnitInput(value) { + // 手动改写单位名称后解除已选单位 ID,报名归属仍沿用操作人的单位。 + if (this.selectedExternalUnit && value !== this.selectedExternalUnit.name) { + this.selectedExternalUnit = null + this.$set(this.formData, 'unitId', "${@shiro.getPrincipalProperty('unit').getId()}") + } + }, + selectExternalUnit(unit) { + this.selectedExternalUnit = unit + this.$set(this.formData, 'unitId', unit.id) + this.$set(this.formData, 'unitname', unit.name) + }, + updateExternalBirthday(value) { + // 身份证号变化时重新计算出生年月,避免沿用系统中可能错误的日期。 + const card = (value || '').toUpperCase() + const date = card.substring(6, 14) + const birthday = /^[0-9]{17}[0-9X]$/.test(card) && moment(date, 'YYYYMMDD', true).isValid() + ? moment(date, 'YYYYMMDD').format('YYYY-MM-DD') : '' + this.$set(this.formData, 'birthday', birthday) + }, + addExternalToApply(user, single) { + const data = { + ...this.formData, + userId: user.id, + loginname: user.loginname, + username: user.username, + sex: this.formData.sex, + idcard: this.formData.idcard, + idCard: this.formData.idcard, + birthday: this.formData.birthday, + mobile: this.formData.mobile, + unitId: this.formData.unitId, + unitname: this.formData.unitname.trim(), + age: this.formData.birthday ? moment().diff(moment(this.formData.birthday), 'years') : null, + eventId: this.eventData.eventId, + activityId: this.activityData.id, + team: single ? null : this.teams.find(item => item.id === this.formData.teamId).name, + isAddUser: true + } + if (single) { + this.appliedData3.push(data) + } else { + this.appliedData.push(data) + } + // 系统人员可能仍在左侧可报名单中,移除该选项以免同一项目重复加入。 + this.applyData = this.applyData.filter(item => item.id !== user.id) + this.applyValue = this.applyValue.filter(id => id !== user.id) + this.rightChecked = this.rightChecked.filter(id => id !== user.id) + this.sortApplyed() + this.flushApplyed(this.appliedData) + this.notifySuccess('已添加报名人员') + this.dialogVisible = false + this.dialogVisible2 = false + }, + doAddUser(single) { + // 匹配人员直接加入报名;未匹配人员先创建账号,再加入本次报名。 + if (this.formData.fitForCompetition !== true) return + this.$refs[single ? 'singleExternalForm' : 'form'].validate((valid) => { + if (!valid) { + this.$notify.error({title: '错误', message: '存在未填写或不正确的必填项'}) + return + } + if (!this.formData.unitname || !this.formData.unitname.trim()) { + this.notifyError('请选择或填写单位') + return + } + if (this.appliedData.concat(this.appliedData3).some(item => item.userId === this.formData.matchedUserId)) { + this.notifyError('该人员已在当前项目报名列表中') + return + } + if (this.formData.matchedUserId) { + if (this.selectedExternalUser && this.selectedExternalUser.id === this.formData.matchedUserId) { + this.addExternalToApply(this.selectedExternalUser, single) + } + return + } + this.formLoading = true + $.post(loc() + "/addExternalUser", { + username: this.formData.username, + loginname: this.formData.loginname, + sex: this.formData.sex, + idcard: this.formData.idcard, + mobile: this.formData.mobile, + fitForCompetition: this.formData.fitForCompetition + }).then((res) => { + if (res.code !== 0) { + this.notifyError(res.msg || '外援创建失败') + return + } + this.addExternalToApply(res.data, single) + }).fail(() => { + this.notifyError('外援创建失败,请重试') + }).always(() => { + this.formLoading = false + }) }) }, openAddUser() { this.formData = { teamId: this.team, identity: ['1'], + username: '', + loginname: '', + sex: '', + idcard: '', + birthday: '', + mobile: '', unitId: "${@shiro.getPrincipalProperty('unit').getId()}", - unitname: '外援' + unitname: '', + matchedUserId: '', + fitForCompetition: null } + this.selectedExternalUser = null + this.selectedExternalUnit = null + this.externalSearchQuery = '' this.dialogVisible = true - this.previewExternalNumber() - }, - previewExternalNumber() { - this.formLoading = true - $.post(loc() + "/previewExternalLoginname", {}).then((res) => { - if (res.code === 0) { - this.$set(this.formData, 'loginname', res.data) - } else { - this.notifyError(res.msg || '工号生成失败') - } - }).fail(() => { - this.notifyError('工号生成失败,请重试') - }).always(() => { - this.formLoading = false - }) }, openAddUserD() { this.formData = { identity: ['1'], + username: '', + loginname: '', + sex: '', + idcard: '', + birthday: '', + mobile: '', unitId: "${@shiro.getPrincipalProperty('unit').getId()}", - unitname: '外援' + unitname: '', + matchedUserId: '', + fitForCompetition: null } + this.selectedExternalUser = null + this.selectedExternalUnit = null + this.externalSearchQuery = '' this.dialogVisible2 = true - this.previewExternalNumber() }, async doApplied(flag1) { diff --git a/src/main/resources/views/platform/activity/school/ActivityManage.html b/src/main/resources/views/platform/activity/school/ActivityManage.html index a063c87..ccddbb5 100644 --- a/src/main/resources/views/platform/activity/school/ActivityManage.html +++ b/src/main/resources/views/platform/activity/school/ActivityManage.html @@ -121,8 +121,11 @@ layout("/layouts/platform.html"){ - - + + + +
- 提交 - 移除 + 提交 + 移除
@@ -154,11 +162,12 @@ layout("/layouts/platform.html"){ data() { return { pageForm: { - year: moment().format('YYYY'), + year: null, isBuy: false, searchName: 'u.username' }, projectList: [], + projectLoading: false, userData: [], userValue: [], treeUnionList: [], @@ -172,7 +181,11 @@ layout("/layouts/platform.html"){ }, methods: { handleSelectionChange(val) { - this.userValue = val.length > 0 ? val.map(o => o.id) : [] + this.$set(this, 'userValue', val.length > 0 ? val.map(o => o.id) : []) + }, + // 其他同类活动的历史购买只能查看;移除操作仅处理当前活动的登记。 + canSelectUser(row) { + return !this.pageForm.isBuy || row.canRemove }, filterNode(value, data, node) { if (!value || node.level === 1) return true; @@ -187,7 +200,7 @@ layout("/layouts/platform.html"){ this.pageData() } }, - async doHandler() { + doHandler() { if(!this.pageForm.projectId) { this.notifyWarning('请选择项目!') return @@ -196,50 +209,104 @@ layout("/layouts/platform.html"){ this.notifyWarning('请选择人员!') return } - const confirm = await this.$confirm('您选择了' + this.userValue.length + '人, 是否继续?', '提示', { + this.$confirm('您选择了' + this.userValue.length + '人, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' - }) - if (confirm === "confirm") { - const resp = await $.post('/platform/clothing/register/doHandler', { + }).then(() => { + this.$set(this, 'formLoading', true) + return $.post('/platform/clothing/register/doHandler', { ids: JSON.stringify(this.userValue), projectId: this.pageForm.projectId, - year: this.pageForm.year, isBuy: this.pageForm.isBuy, }) - if(resp.code === 0) { - this.doSearch() - this.notifySuccess(resp.msg) - } else { - this.notifyWarning(resp.msg) - } - } + .then((resp) => { + if (resp.code === 0) { + this.doSearch() + this.notifySuccess(resp.msg) + } else { + this.notifyWarning(resp.msg) + } + }, () => { + this.notifyWarning('提交失败,请刷新名单后重试') + }) + .always(() => { + this.$set(this, 'formLoading', false) + }) + }).catch(() => {}) }, doSearch() { this.$refs.multipleTable.clearSelection() this.pageData() }, - //查询人员名单 - async pageData() { - const resp = await $.post('/platform/clothing/register/pageData', this.pageForm) - if (resp.code === 0) { - this.userData = resp.data.list - this.pageForm.totalCount = resp.data.totalCount - } else { - this.$message.error(resp.msg) - } + // 查询与所选活动报名年度相差不超过一年、同类运动项目的购买状态。 + pageData() { + const projectId = this.pageForm.projectId + const isBuy = this.pageForm.isBuy + this.$set(this, 'tableLoading', true) + return $.post('/platform/clothing/register/pageData', this.pageForm) + .then((resp) => { + if (this.pageForm.projectId !== projectId || this.pageForm.isBuy !== isBuy) return + if (resp.code === 0) { + this.$set(this, 'userData', resp.data.list) + this.$set(this.pageForm, 'totalCount', resp.data.totalCount) + } else { + this.$set(this, 'userData', []) + this.$set(this.pageForm, 'totalCount', 0) + this.$message.error(resp.msg) + } + }, () => { + if (this.pageForm.projectId !== projectId || this.pageForm.isBuy !== isBuy) return + this.$set(this, 'userData', []) + this.$set(this.pageForm, 'totalCount', 0) + this.$message.error('人员名单加载失败') + }) + .always(() => { + this.$set(this, 'tableLoading', false) + }) }, - //获取所有买服装的项目 - async getBuyClothesProject() { - const {data} = await $.post('/platform/clothing/register/getBuyClothesProject') - return data + // 年度为空时获取全部已提交活动;指定年度时按活动报名开始时间筛选。 + getBuyClothesProject(year) { + this.$set(this, 'projectLoading', true) + return $.post('/platform/clothing/register/getBuyClothesProject', {year: year}) + .then((resp) => { + if (resp.code === 0) return resp.data || [] + this.notifyWarning(resp.msg) + return [] + }, () => { + this.notifyWarning('活动列表加载失败') + return [] + }) + .always(() => { + this.$set(this, 'projectLoading', false) + }) }, - async unionTree() { - const resp = await $.post('/platform/clothing/register/getTreeUnionList') - if(resp.code === 0) { - this.sysadmin = resp.data.sysadmin - this.treeUnionList = [resp.data] + // 切换年度时清除原项目,避免用上一年度的活动ID继续查询或登记。 + refreshProjects() { + const year = this.pageForm.year + this.$set(this.pageForm, 'projectId', null) + this.$set(this, 'projectList', []) + this.$set(this, 'userData', []) + this.$set(this, 'userValue', []) + return this.getBuyClothesProject(year).then((projects) => { + if (this.pageForm.year !== year) return + this.$set(this, 'projectList', projects) + if (projects.length > 0) { + this.$set(this.pageForm, 'projectId', projects[0].id) + } + }) + }, + handleYearChange() { + const year = this.pageForm.year + this.refreshProjects().then(() => { + if (this.pageForm.year === year) this.doSearch() + }) + }, + unionTree() { + return $.post('/platform/clothing/register/getTreeUnionList').then((resp) => { + if (resp.code !== 0) return + this.$set(this, 'sysadmin', resp.data.sysadmin) + this.$set(this, 'treeUnionList', [resp.data]) if (this.sysadmin) { this.handleNodeClick({unioncode: ''}, {level: 1}) } else { @@ -257,20 +324,19 @@ layout("/layouts/platform.html"){ level: 2, data: data }) } - } + }, () => { + this.notifyWarning('工会列表加载失败') + }).always(() => {}) }, }, - async created() { - this.projectList = await this.getBuyClothesProject() - if (this.projectList && this.projectList.length > 0) { - this.pageForm.projectId = this.projectList[0].code - } - await this.unionTree() - this.pageData() + created() { + this.refreshProjects().then(() => { + this.unionTree() + }) } }) \ No newline at end of file +#-->