体育报名外援优化;体育服装管理整改,关联选择活动;比赛名称录入可选工会补全;沿用保存活动bug修复

This commit is contained in:
2026-10-09 10:59:09 +08:00
parent d6961ebf19
commit d551f8c94e
29 changed files with 1117 additions and 433 deletions
@@ -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<ActivitySchoolApply> list = activitySchoolApplyService.listEntity(applySql);
//团体人数
List<ActivitySchoolApply> 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<NutMap>,每项包含 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<Sys_user> list = sysUserService.execute(sql).getList(Sys_user.class);
return list;
return activitySchoolApplyService.searchSystemUsers(queryString, userId);
}
@@ -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
@@ -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
@@ -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()));
@@ -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<NutMap> 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<NutMap> applies = activitySchoolApplyService.listMap(sql);
List<NutMap> 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<NutMap> 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, '男', '女 ' ),
@@ -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<Record> unions = sysUnionService.list(sql);
Map<String, List<NutMap>> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId);
unions.forEach(v -> {
v.set("users", activitySchoolApplyService.query(Cnd.where("unionId", "=", v.getString("id")).and("activityId", "=", activityId).desc("identity")));
List<NutMap> 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<Record> unions = sysUnionService.list(sql);
Map<String, List<NutMap>> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId);
List<Record> 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 -> {
@@ -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<ActivitySchoolApply> 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")
@@ -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;
@@ -13,19 +13,49 @@ import java.util.Map;
*/
public interface ActivitySchoolApplyService extends ViService<ActivitySchoolApply> {
/**
* 获取团体成绩录入可选的全部系统工会,不受活动项目报名情况或启用状态限制。
* @return 工会列表,每项包含 id(成绩记录使用的工会ID)和 unionname(下拉显示名称)
*/
List<NutMap> getResultUnionOptions();
/** @return 当前年份预计分配的外援工号;实际工号以创建接口返回值为准。 */
String previewExternalLoginname();
/**
* 创建仅用于报名的外援用户,按年份生成 wy+年份+流水号的工号。
* 按姓名或工号查找可复用的系统人员。
* @param queryString 搜索内容,空值返回空列表
* @param excludedUserIds 当前页面需要排除的用户 ID
* @return 人员列表,每项包含 id、姓名、工号、性别、身份证号、出生日期、电话和系统单位
*/
List<NutMap> searchSystemUsers(String queryString, String[] excludedUserIds);
/**
* 查询活动报名及其代表出战的工会;历史报名仅在所存单位 ID 与单位名称一致时按该单位归属。
* @param activityId 活动 ID
* @return 报名列表,representedUnionId 是系统工会 ID,representedActivityUnionId 是活动工会 ID,
* 其余字段为报名、活动、项目和队伍的展示字段
*/
List<NutMap> listRepresentedApplies(String activityId);
/**
* 按代表出战的系统工会归集活动报名,供工会提醒名单和报名人数共用。
* @param activityId 活动 ID
* @return 以系统工会 ID 为键、该工会报名记录列表为值的 Map
*/
Map<String, List<NutMap>> 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<ActivitySchoolApply> applies);
@@ -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<ActivitySchool
super(dao);
}
@Override
public List<NutMap> 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<NutMap> 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<NutMap> 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<String, List<NutMap>> 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<ActivitySchool
public void doApplies(String activity, String union_id, String eventId,List<ActivitySchoolApply> 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);
@@ -97,7 +97,11 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> 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<ActivitySchool> 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<ActivitySchool> 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<ActivitySchool> 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<ActivitySchool> imp
return !activitySchool.getEventsIds().contains(v.getEventId());
}).collect(Collectors.toList());
List<ActivitySchoolEvent> collect1 = Lang.array2list(events).stream().filter(v -> {
List<ActivitySchoolEvent> 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<ActivitySchool> imp
if (collect1.size() > 0) {
addTeam(activitySchool.getId(), collect1.toArray(new ActivitySchoolEvent[collect1.size()]));
}
for (ActivitySchoolEvent event : events) {
for (ActivitySchoolEvent event : safeEvents) {
update(event);
}
@@ -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<Record> buyUserList = clothingRegisterService.list(sql);
for (int i = 0; i < buyUserList.size(); i++) {
@@ -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
@@ -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<ClothingRegister> {
/**
* 列表查询
*
* @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<io.v.nutz.activity.models.ActivityBasicSettings>
* 获取已提交的校级活动,供服装登记、记录和台账选择项目。
* year 为空时返回全部年度,否则按活动报名开始时间所属年度筛选。
* 返回的每项包含活动 id(作为 projectId 保存和筛选)及 name(下拉显示名称)。
*/
List<ActivityBasicSettings> getBuyClothesProject();
List<NutMap> getBuyClothesProject(Integer year);
/**
* 获取项目人员名单
* @author JyuHsin
* @date 2022/12/5
* @param projectId: 项目id
* @return java.util.List<org.nutz.lang.util.NutMap>
* @param projectId: 活动ID
* @param userId: 登记人ID
* @param year: 活动报名年度,可为空;旧项目记录按登记年度筛选
* @return 人员列表,每项包含购买人资料及登记记录
*/
List<NutMap> findBuyClothesUser(String projectId, String userId, String year);
@@ -50,28 +51,25 @@ public interface ClothingRegisterService extends ViService<ClothingRegister> {
* 删除
* @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);
}
@@ -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<ClothingRegister>
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<ClothingRegister>
}
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<ClothingRegister>
}
@Override
public List<ActivityBasicSettings> getBuyClothesProject() {
return dao().query(ActivityBasicSettings.class, Cnd.where("clothing", "is not", null));
public List<NutMap> 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<ClothingRegister>
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<ClothingRegister>
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<String> 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<String> 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<NutMap> users = result.getList();
users.forEach(user -> user.setv("canRemove", currentIds.contains(user.getString("id"))));
}
return result;
}
/**
* projectId 为当前活动ID;返回该活动选择的运动项目标识。项目未配置 sports 时以项目ID兜底,
* 避免把不同项目误判为同类;已选项目为空时拒绝登记,防止绕过重复购买限制。
*/
private List<String> getSelectedSportKeys(String projectId) {
List<NutMap> 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<String> 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<String> 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<String> 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<String> 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<String> purchasedIds = findPurchasedUserIds(projectId);
if (userIds.stream().anyMatch(purchasedIds::contains)) {
throw new IllegalArgumentException("所选人员在近两年内已购买同类项目服装,不能重复提交,请刷新名单");
}
List<ClothingRegister> 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<ClothingRegister>
}
@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);
}
});
@@ -14,4 +14,9 @@ public interface SysConfigService extends BaseService<Sys_config> {
* @return
*/
List<Sys_config> getAllList();
/**
* 确保系统配置中存在学校待办开关;首次创建时默认关闭。
*/
void ensureTodoTaskConfig();
}
@@ -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<Sys_config> implements
public List<Sys_config> getAllList() {
return this.query(Cnd.where("delFlag", "=", false));
}
}
@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);
}
}
}
@@ -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);
@@ -34,7 +34,7 @@ public class SysConfController {
@Ok("beetl:/platform/sys/conf/index.html")
@RequiresPermissions("sys.manager.conf")
public void index() {
sysConfigService.ensureTodoTaskConfig();
}
@At
@@ -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<Sys_file> files = condolence.getFiles();
if (Lang.isEmpty(files)) {
condolence.setFiles_num("0");
@@ -19,6 +19,14 @@ public interface CondolenceService extends ViService<Condolence> {
*/
NutMap findOne(String id);
/**
* 判断被慰问人在当前自然年是否已有住院慰问申请。
*
* @param application 本次申请,包含慰问类型、被慰问人和编辑时的申请 ID
* @return 已有其他同年住院慰问申请时返回 true,否则返回 false
*/
boolean hasHospitalCondolenceInCurrentYear(Condolence application);
/**
* 添加
*
@@ -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<Condolence> 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
@@ -38,10 +38,14 @@ window.activity = {
2: '部门工会报名',
3: '小程序报名'
},
getStatus(close, applyStartTime, applyEndTime, startTime, endTime) {
getStatus(close, applyStartTime, applyEndTime, startTime, endTime, isSave) {
if (close) {
return `<span class="text-danger">活动已关闭</span>`
}
// 管理列表传入 isSave;已保存但未提交的活动尚未开放报名。
if (isSave === true || isSave === 1 || isSave === '1') {
return `<span class="text-info">未提交</span>`
}
const now = moment().valueOf()
const dateNow = moment(moment(moment().format('YYYY-MM-DD')).valueOf()).valueOf()
@@ -67,4 +71,4 @@ window.activity = {
return false
},
},
}
}
@@ -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) {
@@ -906,39 +906,58 @@ layout("/layouts/platform.html"){
</el-select>
</el-form-item>
<el-form-item prop="username" label="姓&emsp;&emsp;名">
<el-input maxlength="50" placeholder="请填写外援姓名" v-model="formData.username"></el-input>
<el-autocomplete style="width: 100%" maxlength="50" :trigger-on-focus="false"
placeholder="输入姓名或工号搜索,未匹配时直接填写姓名"
:fetch-suggestions="searchExternalUser" v-model="formData.username"
@input="changeExternalName" @select="selectExternalUser">
<template slot-scope="{ item }">{{item.username}}({{item.loginname}})</template>
</el-autocomplete>
</el-form-item>
<el-form-item prop="loginname" label="工&emsp;&emsp;号">
<el-input disabled maxlength="50" placeholder="正在生成工号" v-model="formData.loginname"
<el-input :disabled="!!formData.matchedUserId" maxlength="50" placeholder="留空自动生成,也可手动填写" v-model="formData.loginname"
type="text"></el-input>
</el-form-item>
<el-form-item label="性&emsp;&emsp;别" prop="sex">
<el-radio-group v-model="formData.sex">
<el-radio-group v-model="formData.sex" :disabled="!!formData.matchedUserId && !!selectedExternalUser && !!selectedExternalUser.sex">
<el-radio :label="'男'" border>男</el-radio>
<el-radio :label="'女'" border>女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input maxlength="18" placeholder="请填写身份证号"
v-model="formData.idcard" @input="updateExternalBirthday"></el-input>
</el-form-item>
<el-form-item label="出生年月" prop="birthday">
<el-date-picker
placeholder="选择出生年月"
style="width: 100%"
type="date"
v-model="formData.birthday"
value-format="yyyy-MM-dd">
</el-date-picker>
<el-date-picker style="width: 100%" type="date" value-format="yyyy-MM-dd"
placeholder="从身份证号读取,也可手动选择" v-model="formData.birthday"></el-date-picker>
</el-form-item>
<el-form-item label="电&emsp;&emsp;话" prop="mobile">
<el-input maxlength="11" placeholder="请填写电话" type="text"
v-model="formData.mobile"></el-input>
</el-form-item>
<el-form-item prop="unitId" label="所在单位">
<el-input disabled v-model="formData.unitname"></el-input>
<el-form-item prop="unitname" label="所在单位">
<el-select v-if="formData.matchedUserId" filterable style="width: 100%"
placeholder="请选择报名使用的单位" v-model="formData.unitId" @change="changeExternalUnit">
<el-option v-for="item in unitOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
<el-autocomplete v-else style="width: 100%" maxlength="100"
placeholder="搜索选择单位,或直接输入其他单位"
:fetch-suggestions="searchExternalUnit" v-model="formData.unitname"
@input="changeExternalUnitInput" @select="selectExternalUnit"></el-autocomplete>
</el-form-item>
<el-form-item label="身体条件" prop="fitForCompetition">
<el-radio-group v-model="formData.fitForCompetition">
<el-radio :label="true" border>适合参赛</el-radio>
<el-radio :label="false" border>不适合参赛</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" :loading="formLoading" @click="doAddUser(false)">确 定</el-button>
<el-button type="primary" :loading="formLoading" :disabled="formData.fitForCompetition !== true"
@click="doAddUser(false)">确 定</el-button>
</span>
</el-dialog>
@@ -949,35 +968,58 @@ layout("/layouts/platform.html"){
>
<el-form :model="formData" ref="singleExternalForm" :rules="lsryRules" label-width="100px">
<el-form-item prop="username" label="姓&emsp;&emsp;名">
<el-input maxlength="50" placeholder="请填写姓名" v-model="formData.username"
type="text"></el-input>
<el-autocomplete style="width: 100%" maxlength="50" :trigger-on-focus="false"
placeholder="输入姓名或工号搜索,未匹配时直接填写姓名"
:fetch-suggestions="searchExternalUser" v-model="formData.username"
@input="changeExternalName" @select="selectExternalUser">
<template slot-scope="{ item }">{{item.username}}({{item.loginname}})</template>
</el-autocomplete>
</el-form-item>
<el-form-item prop="loginname" label="工&emsp;&emsp;号">
<el-input disabled maxlength="50" placeholder="正在生成工号" v-model="formData.loginname"
<el-input :disabled="!!formData.matchedUserId" maxlength="50" placeholder="留空自动生成,也可手动填写" v-model="formData.loginname"
type="text"></el-input>
</el-form-item>
<el-form-item label="性&emsp;&emsp;别" prop="sex">
<el-radio-group v-model="formData.sex">
<el-radio-group v-model="formData.sex" :disabled="!!formData.matchedUserId && !!selectedExternalUser && !!selectedExternalUser.sex">
<el-radio :label="'男'" border>男</el-radio>
<el-radio :label="'女'" border>女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input maxlength="18" placeholder="请填写身份证号"
v-model="formData.idcard" @input="updateExternalBirthday"></el-input>
</el-form-item>
<el-form-item label="出生年月" prop="birthday">
<el-date-picker placeholder="选择出生年月" style="width: 100%" type="date"
v-model="formData.birthday" value-format="yyyy-MM-dd"></el-date-picker>
<el-date-picker style="width: 100%" type="date" value-format="yyyy-MM-dd"
placeholder="从身份证号读取,也可手动选择" v-model="formData.birthday"></el-date-picker>
</el-form-item>
<el-form-item prop="mobile" label="电&emsp;&emsp;话">
<el-input maxlength="11" placeholder="请填写电话" v-model="formData.mobile"
type="text"></el-input>
</el-form-item>
<el-form-item prop="unitId" label="所在单位">
<el-input disabled v-model="formData.unitname"></el-input>
<el-form-item prop="unitname" label="所在单位">
<el-select v-if="formData.matchedUserId" filterable style="width: 100%"
placeholder="请选择报名使用的单位" v-model="formData.unitId" @change="changeExternalUnit">
<el-option v-for="item in unitOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
<el-autocomplete v-else style="width: 100%" maxlength="100"
placeholder="搜索选择单位,或直接输入其他单位"
:fetch-suggestions="searchExternalUnit" v-model="formData.unitname"
@input="changeExternalUnitInput" @select="selectExternalUnit"></el-autocomplete>
</el-form-item>
<el-form-item label="身体条件" prop="fitForCompetition">
<el-radio-group v-model="formData.fitForCompetition">
<el-radio :label="true" border>适合参赛</el-radio>
<el-radio :label="false" border>不适合参赛</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible2 = false">取 消</el-button>
<el-button type="primary" :loading="formLoading" @click="doAddUser(true)">确 定</el-button>
<el-button type="primary" :loading="formLoading" :disabled="formData.fitForCompetition !== true"
@click="doAddUser(true)">确 定</el-button>
</span>
</el-dialog>
@@ -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) {
@@ -121,8 +121,11 @@ layout("/layouts/platform.html"){
</template>
<template v-else-if="column.prop=='startTime'" scope="{row}">
{{moment(row.startTime).format('YYYY-MM-DD')}} -
{{moment(row.endTime).format('YYYY-MM-DD')}}
<span v-if="row.startTime && row.endTime">
{{moment(row.startTime).format('YYYY-MM-DD')}} -
{{moment(row.endTime).format('YYYY-MM-DD')}}
</span>
<span v-else>未设置</span>
</template>
<template v-else-if="column.prop=='state'"
@@ -17,8 +17,8 @@ layout("/layouts/platform.html"){
<div class="search-item-option">
<el-date-picker
style="width: 100%"
placeholder="选择年度"
type="year"
placeholder="全部年度"
type="year" clearable @change="handleYearChange"
v-model="pageForm.year"
value-format="yyyy">
</el-date-picker>
@@ -27,13 +27,13 @@ layout("/layouts/platform.html"){
<div class="search-item">
<div class="search-item-label">项目名称:</div>
<div class="search-item-option">
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable :loading="projectLoading"
style="width: 100%" @change="doSearch">
<el-option
v-for="item in projectList"
:key="item.code"
:key="item.id"
:label="item.name"
:value="item.code">
:value="item.id">
</el-option>
</el-select>
</div>
@@ -113,6 +113,7 @@ layout("/layouts/platform.html"){
],
buyClothesUserData: [],
projectList: [],
projectLoading: false,
unionList: [],
}
},
@@ -128,15 +129,43 @@ layout("/layouts/platform.html"){
this.$refs['guava'].view()
}
},
//获取所有买服装的项目
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)
})
},
// 年度变化后清空不属于新年度的项目选择,再按当前条件查询台账。
handleYearChange() {
const year = this.pageForm.year
this.$set(this.pageForm, 'projectId', null)
this.$set(this, 'projectList', [])
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
this.doSearch()
})
},
},
async created() {
this.projectList = await this.getBuyClothesProject()
this.unionList = await getUnion()
created() {
const year = this.pageForm.year
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
})
getUnion().then((unions) => {
this.$set(this, 'unionList', unions)
})
this.pageData()
}
})
@@ -144,4 +173,4 @@ layout("/layouts/platform.html"){
<!--#
}
#-->
#-->
@@ -17,8 +17,8 @@ layout("/layouts/platform.html"){
<div class="search-item-option">
<el-date-picker
style="width: 100%"
placeholder="选择年度"
type="year"
placeholder="全部年度"
type="year" clearable @change="handleYearChange"
v-model="pageForm.year"
value-format="yyyy">
</el-date-picker>
@@ -27,13 +27,13 @@ layout("/layouts/platform.html"){
<div class="search-item">
<div class="search-item-label">项目名称:</div>
<div class="search-item-option">
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable :loading="projectLoading"
style="width: 100%" @change="doSearch">
<el-option
v-for="item in projectList"
:key="item.code"
:key="item.id"
:label="item.name"
:value="item.code">
:value="item.id">
</el-option>
</el-select>
</div>
@@ -111,6 +111,7 @@ layout("/layouts/platform.html"){
],
buyClothesUserData: [],
projectList: [],
projectLoading: false,
note: '',
clickRow: {},
}
@@ -177,10 +178,32 @@ layout("/layouts/platform.html"){
}
}
},
//获取所有买服装的项目
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)
})
},
// 年度变化后清空不属于新年度的项目选择,再按当前条件查询登记记录。
handleYearChange() {
const year = this.pageForm.year
this.$set(this.pageForm, 'projectId', null)
this.$set(this, 'projectList', [])
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
this.doSearch()
})
},
doExport(row) {
const loading = this.$loading({
@@ -221,8 +244,12 @@ layout("/layouts/platform.html"){
}).catch((err) => {})
},
},
async created() {
this.projectList = await this.getBuyClothesProject()
created() {
const year = this.pageForm.year
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
})
this.pageData()
}
})
@@ -230,4 +257,4 @@ layout("/layouts/platform.html"){
<!--#
}
#-->
#-->
@@ -27,8 +27,8 @@ layout("/layouts/platform.html"){
<div class="search-item-option">
<el-date-picker
style="width: 100%"
placeholder="选择年度"
type="year" disabled
placeholder="全部年度"
type="year" clearable @change="handleYearChange"
v-model="pageForm.year"
value-format="yyyy">
</el-date-picker>
@@ -37,19 +37,24 @@ layout("/layouts/platform.html"){
<div class="search-item">
<div class="search-item-label">项目名称:</div>
<div class="search-item-option">
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable :loading="projectLoading"
@change="doSearch" style="width: 100%">
<el-option
v-for="item in projectList"
:key="item.code"
:key="item.id"
:label="item.name"
:value="item.code">
:value="item.id">
</el-option>
</el-select>
</div>
</div>
<div class="search-item">
<div class="search-item-label">近两年内:</div>
<div class="search-item-label" style="white-space: nowrap;">
近两年内:
<el-tooltip content="按所选活动的报名年度,检查相邻年度活动中相同运动项目的购衣记录;补录往年活动也会查重" placement="top">
<i class="el-icon-question" style="cursor: help;"></i>
</el-tooltip>
</div>
<div class="search-item-option">
<el-radio-group @change="(val) => doSearch()" v-model="pageForm.isBuy">
<el-radio-button :label="true">已购买</el-radio-button>
@@ -112,22 +117,25 @@ layout("/layouts/platform.html"){
</template>
</vi-title2>
</div>
<el-table ref="multipleTable" @selection-change="handleSelectionChange" :data="userData" max-height="600"
<el-table ref="multipleTable" v-loading="tableLoading" @selection-change="handleSelectionChange" :data="userData" max-height="600"
:row-key="(val) => val.id">
<el-table-column :reserve-selection="true" type="selection" width="55"></el-table-column>
<el-table-column :reserve-selection="true" :selectable="canSelectUser" type="selection" width="55"></el-table-column>
<el-table-column label="姓名" prop="username" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginname" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="sex" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="单位" prop="unitname" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionname" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系方式" prop="mobile" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="pageForm.isBuy" label="购买来源" align="center" header-align="center">
<template slot-scope="scope">{{scope.row.canRemove ? '当前活动' : '两年内同类活动'}}</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: space-between; margin-top: 6px">
<div> <!--#include("/layouts/pagination.html"){}#--></div>
<div>
<el-row justify="end" type="flex">
<el-button v-if="!pageForm.isBuy" @click="doHandler" type="primary">提交</el-button>
<el-button v-else @click="doHandler" type="danger">移除</el-button>
<el-button v-if="!pageForm.isBuy" @click="doHandler" :loading="formLoading" type="primary">提交</el-button>
<el-button v-else @click="doHandler" :loading="formLoading" type="danger">移除</el-button>
</el-row>
</div>
</div>
@@ -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()
})
}
})
</script>
<!--#
}
#-->
#-->