Compare commits

9 Commits
36 changed files with 2136 additions and 709 deletions
@@ -1,34 +1,29 @@
package com.budwk.app.sys.controller; package com.budwk.app.sys.controller;
import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil; import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template; import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.models.Sys_menu; import com.budwk.app.sys.models.Sys_menu;
import com.budwk.app.sys.services.SysHomeActivityService;
import com.budwk.app.sys.services.SysHomeNewsService; import com.budwk.app.sys.services.SysHomeNewsService;
import com.budwk.app.sys.services.SysMenuService; import com.budwk.app.sys.services.SysMenuService;
import com.budwk.app.sys.services.SysUserService; import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter; import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls; import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos; import org.nutz.dao.util.Daos;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log; import org.nutz.log.Log;
import org.nutz.log.Logs; import org.nutz.log.Logs;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
@@ -52,7 +47,7 @@ public class SysHomeController {
@Inject @Inject
private SysUserService sysUserService; private SysUserService sysUserService;
@Inject @Inject
private ActivityBasicScopeService activityBasicScopeService; private SysHomeActivityService sysHomeActivityService;
@Inject @Inject
private SysHomeNewsService sysHomeNewsService; private SysHomeNewsService sysHomeNewsService;
@@ -201,71 +196,16 @@ public class SysHomeController {
} }
/**
* includeEnded可选:true供PC首页展示已结束活动,缺省false保持既有调用行为。
* 返回Result,data为当前登录用户可见的活动实体列表,含名称、封面、链接及起止时间,不含内部SQL和类路径。
*/
@At @At
@SaCheckLogin @SaCheckLogin
@ApiOperation("首页推送活动") @ApiOperation("首页推送活动")
@Ok("json") @Ok("json")
public Result listHomeActivity() { public Result listHomeActivity(@Param(value = "includeEnded", df = "false") boolean includeEnded) {
FieldFilter fieldFilter = FieldFilter.locked(Sys_home_activity.class, "classPath"); return Result.success(sysHomeActivityService.listHomeActivity(SecurityUtil.getUserId(), includeEnded));
List<Sys_home_activity> list = Daos.ext(dao, fieldFilter).query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top"));
String userId = SecurityUtil.getUserId();
List<Sys_home_activity> allowActivityList = new ArrayList<>();
// 今天的时间
Date today = DateUtil.date();
for (Sys_home_activity activity : list) {
// 该活动的时间范围
Date startDate = activity.getStartDate();
Date endDate = activity.getEndDate();
// 判断当前时间是否在活动范围内,不在就过滤掉
// if (!DateUtil.isIn(today, startDate, endDate)) {
// continue;
// }
// 只过滤结束的
if (DateUtil.compare(today, endDate) > 0) {
continue;
}
Integer allowUserGroupId = activity.getAllowUserGroupId();
String allowUserSql = activity.getAllowUserSql();
activity.setAllowUserSql(null);
if (allowUserGroupId == null && StrUtil.isBlank(allowUserSql)) {
allowActivityList.add(activity);
continue;
}
if (allowUserGroupId != null) {
if (activityBasicScopeService.isUserInGroup(allowUserGroupId, userId)) {
allowActivityList.add(activity);
continue;
}
}
if (StrUtil.isNotBlank(allowUserSql)) {
Sql sql = Sqls.create(allowUserSql).setParam("userId", userId);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
NutMap nutMap = (NutMap) sql.getResult();
if (Lang.isNotEmpty(nutMap) && StrUtil.isNotBlank(nutMap.getString("userId"))) {
allowActivityList.add(activity);
}
}
}
return Result.success(allowActivityList);
//
//
//
//
// FieldFilter fieldFilter = FieldFilter.locked(Sys_home_activity.class, "allowUserSql|classPath");
// List<Sys_home_activity> list = Daos.ext(dao, fieldFilter).query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top"));
//
// List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
// Set<String> hrefs = menus.stream().map(Sys_menu::getHref).collect(Collectors.toSet());
//
// List<Sys_home_activity> hasPermisiionList = list.stream().filter(activity -> {
// String url = activity.getUrl();
// String h5Url = activity.getH5Url();
// return hrefs.contains(url) || hrefs.contains(h5Url);
// }).toList().stream().toList();
// return Result.success(hasPermisiionList);
} }
@At @At
@@ -8,7 +8,10 @@ import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.*; import com.budwk.app.sys.models.*;
import com.budwk.app.sys.services.SysDictService; import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.sys.views.View_user; import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@@ -17,6 +20,9 @@ import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.lang.random.R;
import org.nutz.trans.Atom;
import org.nutz.trans.Trans;
import org.nutz.mvc.annotation.AdaptBy; import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
@@ -26,9 +32,15 @@ import org.nutz.mvc.upload.UploadAdaptor;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.InputStream; import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -45,6 +57,10 @@ public class SysUnionCadreImportController {
private SysDictService sysDictService; private SysDictService sysDictService;
@Inject @Inject
private SysUnionController sysUnionController; private SysUnionController sysUnionController;
@Inject
private SysRoleService sysRoleService;
@Inject
private SysUserService sysUserService;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List<NutMap> roleOptions() { private List<NutMap> roleOptions() {
@@ -72,7 +88,7 @@ public class SysUnionCadreImportController {
append(notes, "届次编码(选填)", "可留空;填写时使用届次对照表中的编码,与单个添加一致。"); append(notes, "届次编码(选填)", "可留空;填写时使用届次对照表中的编码,与单个添加一致。");
append(notes, "所属单位编码(书记必填)", "仅二级党委书记填写,多个单位用英文逗号分隔,必须为当前分工会的二级单位;其他角色留空。"); append(notes, "所属单位编码(书记必填)", "仅二级党委书记填写,多个单位用英文逗号分隔,必须为当前分工会的二级单位;其他角色留空。");
append(notes, "重复记录", "普通干部按人员和角色跳过;二级党委书记按人员、角色和工会跳过。禁用的已有关系也跳过,不覆盖。"); append(notes, "重复记录", "普通干部按人员和角色跳过;二级党委书记按人员、角色和工会跳过。禁用的已有关系也跳过,不覆盖。");
append(notes, "保存方式", "系统管理员直接授权,写入干部及角色记录并清理权限缓存,不发起审批。逐行保存,失败行不影响其他行。"); append(notes, "保存方式", "系统管理员直接授权,批量校验并保存干部及角色记录,统一清理权限缓存,不发起审批。校验失败行不写入;保存异常时拆分定位失败行,其他有效行继续导入。");
append(notes, "已有干部记录", "已有在任或待审核干部记录但未分配角色时跳过,避免重复创建或绕过已有审批。"); append(notes, "已有干部记录", "已有在任或待审核干部记录但未分配角色时跳过,避免重复创建或绕过已有审批。");
Sheet unions = createSheet(workbook, "工会对照", "工会编码", "工会名称"); Sheet unions = createSheet(workbook, "工会对照", "工会编码", "工会名称");
List<Sys_union> unionList = dao.query(Sys_union.class, Cnd.NEW().asc("unionCode")); List<Sys_union> unionList = dao.query(Sys_union.class, Cnd.NEW().asc("unionCode"));
@@ -145,101 +161,271 @@ public class SysUnionCadreImportController {
if (rows.isEmpty()) { if (rows.isEmpty()) {
return Result.error("文件中没有需要导入的数据"); return Result.error("文件中没有需要导入的数据");
} }
List<String> roleCodes = roleOptions().stream().map(item -> item.getString("code")).toList(); Set<String> roleCodes = sysDictService.getSubListByCode("BRANCH_UNION_ROLES").stream()
List<String> terms = sysDictService.getSubListByCode("TEACHER_CONGRESS_J").stream() .map(Sys_dict::getCode).collect(Collectors.toSet());
.map(Sys_dict::getCode).toList(); roleCodes.add(RoleConstant.UNIT_PARTY_SECRETARY.name());
int success = 0; Set<String> terms = sysDictService.getSubListByCode("TEACHER_CONGRESS_J").stream()
int skipped = 0; .map(Sys_dict::getCode).collect(Collectors.toSet());
List<String> unionCodes = values(rows, "unionCode");
List<String> loginnames = values(rows, "loginname");
List<String> inputRoleCodes = values(rows, "roleCode");
List<String> unitCodes = rows.stream().flatMap(row -> splitUnitCodes(row.getString("unitCodes")).stream())
.distinct().toList();
// 各类基础数据及已有关系按表一次查询,逐行校验时不再访问数据库。
Map<String, List<Sys_union>> unions = group(unionCodes.isEmpty() ? List.of()
: dao.query(Sys_union.class, Cnd.where("unionCode", "in", unionCodes)), Sys_union::getUnionCode);
Map<String, List<View_user>> users = group(loginnames.isEmpty() ? List.of()
: dao.query(View_user.class, Cnd.where("loginname", "in", loginnames)), View_user::getLoginname);
Map<String, List<Sys_role>> roles = group(inputRoleCodes.isEmpty() ? List.of()
: dao.query(Sys_role.class, Cnd.where("code", "in", inputRoleCodes)), Sys_role::getCode);
Map<String, List<Sys_unit>> units = group(unitCodes.isEmpty() ? List.of()
: dao.query(Sys_unit.class, Cnd.where("unitcode", "in", unitCodes)), Sys_unit::getUnitcode);
List<String> userIds = users.values().stream().flatMap(List::stream).map(View_user::getId).distinct().toList();
List<String> roleIds = roles.values().stream().flatMap(List::stream).map(Sys_role::getId).distinct().toList();
Set<List<String>> existingRoles = new HashSet<>();
if (!userIds.isEmpty() && !roleIds.isEmpty()) {
for (Sys_user_role relation : dao.query(Sys_user_role.class,
Cnd.where("userId", "in", userIds).and("roleId", "in", roleIds))) {
existingRoles.add(roleKey(relation.getUserId(), relation.getRoleId(), relation.getUnionId(),
roles.getOrDefault(RoleConstant.UNIT_PARTY_SECRETARY.name(), List.of()).stream()
.anyMatch(role -> role.getId().equals(relation.getRoleId()))));
}
}
Set<List<String>> existingCadres = new HashSet<>();
if (!userIds.isEmpty() && !inputRoleCodes.isEmpty() && !unionCodes.isEmpty()) {
List<String> unionIds = unions.values().stream().flatMap(List::stream).map(Sys_union::getId).distinct().toList();
if (!unionIds.isEmpty()) {
for (Sys_union_cadre cadre : dao.query(Sys_union_cadre.class, Cnd.where("userId", "in", userIds)
.and("roleCode", "in", inputRoleCodes).and("unionId", "in", unionIds).and("isServing", "=", true))) {
existingCadres.add(Arrays.asList(cadre.getUserId(), cadre.getRoleCode(), cadre.getUnionId()));
}
}
}
List<ImportEntry> entries = new ArrayList<>();
Map<List<String>, Integer> fileRoles = new java.util.HashMap<>();
String operator = SecurityUtil.getUserId();
for (NutMap row : rows) { for (NutMap row : rows) {
try { ImportEntry entry = prepareRow(row, roleCodes, terms, unions, users, roles, units,
importRow(row, roleCodes, terms); existingRoles, existingCadres, fileRoles, operator);
} catch (Exception e) { if (entry != null) {
log.error("导入分工会干部失败,行号:{}", row.getInt("rowNumber"), e); entries.add(entry);
outcome(row, "失败", "保存失败,请检查该行数据或联系管理员");
} }
if ("成功".equals(row.getString("status"))) {
success++;
} else if ("跳过".equals(row.getString("status"))) {
skipped++;
} }
saveBatch(entries);
int success = (int) rows.stream().filter(row -> "成功".equals(row.getString("status"))).count();
int skipped = (int) rows.stream().filter(row -> "跳过".equals(row.getString("status"))).count();
if (success > 0) {
sysRoleService.clearCache();
sysUserService.clearCache();
} }
return Result.success(NutMap.NEW().addv("success", success).addv("skipped", skipped) return Result.success(NutMap.NEW().addv("success", success).addv("skipped", skipped)
.addv("failed", rows.size() - success - skipped).addv("rows", rows)); .addv("failed", rows.size() - success - skipped).addv("rows", rows));
} }
private void importRow(NutMap row, List<String> roleCodes, List<String> terms) { private List<String> values(List<NutMap> rows, String field) {
return rows.stream().map(row -> row.getString(field)).filter(StrUtil::isNotBlank).distinct().toList();
}
private List<String> splitUnitCodes(String codes) {
return Arrays.stream(codes.split("[,,]")).map(String::trim).filter(StrUtil::isNotBlank).distinct().toList();
}
private <T> Map<String, List<T>> group(List<T> items, Function<T, String> key) {
return items.stream().collect(Collectors.groupingBy(key));
}
private List<String> roleKey(String userId, String roleId, String unionId, boolean secretary) {
return Arrays.asList(userId, roleId, secretary ? unionId : "");
}
private <T> T unique(Map<String, List<T>> records, String code, String label, List<String> errors) {
if (StrUtil.isBlank(code)) {
errors.add(label + "不能为空");
return null;
}
List<T> matches = records.getOrDefault(code, List.of());
if (matches.isEmpty()) {
errors.add(label + "【" + code + "】不存在");
return null;
}
if (matches.size() > 1) {
errors.add(label + "【" + code + "】匹配到" + matches.size() + "条记录,需先处理系统中的重复编码");
return null;
}
return matches.get(0);
}
private ImportEntry prepareRow(NutMap row, Set<String> roleCodes, Set<String> terms,
Map<String, List<Sys_union>> unions, Map<String, List<View_user>> users,
Map<String, List<Sys_role>> roles, Map<String, List<Sys_unit>> units,
Set<List<String>> existingRoles, Set<List<String>> existingCadres,
Map<List<String>, Integer> fileRoles, String operator) {
String unionCode = row.getString("unionCode"); String unionCode = row.getString("unionCode");
String loginname = row.getString("loginname"); String loginname = row.getString("loginname");
String roleCode = row.getString("roleCode"); String roleCode = row.getString("roleCode");
String j = row.getString("j"); String j = row.getString("j");
if (StrUtil.isBlank(unionCode) || StrUtil.isBlank(loginname) || StrUtil.isBlank(roleCode)) { List<String> errors = new ArrayList<>();
outcome(row, "失败", "工会编码、工号、角色编码不能为空"); Sys_union union = unique(unions, unionCode, "工会编码", errors);
return; View_user user = unique(users, loginname, "工号", errors);
Sys_role role = unique(roles, roleCode, "角色编码", errors);
if (StrUtil.isNotBlank(roleCode) && !roleCodes.contains(roleCode)) {
errors.add("角色编码【" + roleCode + "】不在允许导入的分工会干部角色范围内,请参照角色对照表");
} }
List<Sys_union> unions = dao.query(Sys_union.class, Cnd.where("unionCode", "=", unionCode)); if (!errors.isEmpty()) {
List<View_user> users = dao.query(View_user.class, Cnd.where("loginname", "=", loginname)); outcome(row, "失败", String.join(";", errors));
List<Sys_role> roles = dao.query(Sys_role.class, Cnd.where("code", "=", roleCode)); return null;
if (unions.size() != 1 || users.size() != 1 || roles.size() != 1 || !roleCodes.contains(roleCode)) {
outcome(row, "失败", "工会、工号或分工会角色编码不存在或不唯一,请按模板对照表填写");
return;
} }
Sys_union union = unions.get(0);
View_user user = users.get(0);
Sys_role role = roles.get(0);
boolean secretary = RoleConstant.UNIT_PARTY_SECRETARY.name().equals(roleCode); boolean secretary = RoleConstant.UNIT_PARTY_SECRETARY.name().equals(roleCode);
Cnd existing = Cnd.where("userId", "=", user.getId()).and("roleId", "=", role.getId()); List<String> key = roleKey(user.getId(), role.getId(), union.getId(), secretary);
if (secretary) { if (existingRoles.contains(key)) {
existing.and("unionId", "=", union.getId()); outcome(row, "跳过", "工号【" + loginname + "】已分配角色【" + roleCode + "】"
} + (secretary ? "(工会编码【" + unionCode + "】)" : "") + ",保留原有记录(含禁用关系)");
if (dao.count(Sys_user_role.class, existing) > 0) { return null;
outcome(row, "跳过", "已分配该角色,保留原有记录(含禁用关系)");
return;
} }
if (!union.getId().equals(user.getUnionId())) { if (!union.getId().equals(user.getUnionId())) {
outcome(row, "失败", "所选人员不属于该分工会"); errors.add("工号【" + loginname + "】所属工会为【" + StrUtil.blankToDefault(user.getUnionName(), "未分配工会")
return; + "】,与填写的工会【" + union.getName() + "(" + unionCode + ")】不一致");
} }
if (StrUtil.isNotBlank(j) && !terms.contains(j)) { if (StrUtil.isNotBlank(j) && !terms.contains(j)) {
outcome(row, "失败", "届次编码不存在,请参照届次对照表或留空"); errors.add("届次编码【" + j + "】不存在,请参照届次对照表或留空");
return;
} }
if (dao.count(Sys_union_cadre.class, Cnd.where("userId", "=", user.getId()) if (existingCadres.contains(Arrays.asList(user.getId(), roleCode, union.getId()))) {
.and("roleCode", "=", roleCode).and("unionId", "=", union.getId()).and("isServing", "=", true)) > 0) { outcome(row, "跳过", "工号【" + loginname + "】在工会【" + unionCode + "】已有角色【" + roleCode
outcome(row, "跳过", "已有在任或待审核干部记录,请通过原记录处理"); + "】的在任或待审核干部记录,请通过原记录处理");
return; return null;
} }
List<String> unitIds = new ArrayList<>(); List<String> unitIds = new ArrayList<>();
String unitCodes = row.getString("unitCodes"); String unitCodes = row.getString("unitCodes");
if (secretary) { if (secretary) {
List<String> codes = Arrays.stream(unitCodes.split("[,,]")) List<String> codes = splitUnitCodes(unitCodes);
.map(String::trim).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());
if (codes.isEmpty()) { if (codes.isEmpty()) {
outcome(row, "失败", "二级党委书记必须填写所属单位编码"); errors.add("角色【二级党委书记】必须填写所属单位编码,请参照所属单位对照表");
return;
} }
for (String code : codes) { for (String code : codes) {
List<Sys_unit> units = dao.query(Sys_unit.class, Cnd.where("unitcode", "=", code) List<Sys_unit> matches = units.getOrDefault(code, List.of());
.and("unionId", "=", union.getId()).and("unitLevel", "=", 2)); List<Sys_unit> valid = matches.stream().filter(unit -> union.getId().equals(unit.getUnionId())
if (units.size() != 1) { && Integer.valueOf(2).equals(unit.getUnitLevel())).toList();
outcome(row, "失败", "所属单位编码不存在、不唯一或不是该分工会的二级单位:" + code); if (matches.isEmpty()) {
return; errors.add("所属单位编码【" + code + "】不存在");
} else if (valid.isEmpty()) {
boolean inUnion = matches.stream().anyMatch(unit -> union.getId().equals(unit.getUnionId()));
errors.add(inUnion ? "所属单位编码【" + code + "】属于工会【" + unionCode + "】,但单位级别不是二级"
: "所属单位编码【" + code + "】不属于填写的工会【" + union.getName() + "(" + unionCode + ")】");
} else if (valid.size() > 1) {
errors.add("所属单位编码【" + code + "】在工会【" + unionCode + "】下匹配到" + valid.size() + "个二级单位,编码不唯一");
} else {
unitIds.add(valid.get(0).getId());
} }
unitIds.add(units.get(0).getId());
} }
} else if (StrUtil.isNotBlank(unitCodes)) { } else if (StrUtil.isNotBlank(unitCodes)) {
outcome(row, "失败", "普通干部角色的所属单位编码应留空"); errors.add("角色【" + roleCode + "】不是二级党委书记,所属单位编码【" + unitCodes + "】应留空");
}
if (!errors.isEmpty()) {
outcome(row, "失败", String.join(";", errors));
return null;
}
Integer previousRow = fileRoles.putIfAbsent(key, row.getInt("rowNumber"));
if (previousRow != null) {
outcome(row, "跳过", "与本文件第" + previousRow + "行的人员角色授权重复");
return null;
}
Sys_union_cadre cadre = new Sys_union_cadre();
cadre.setId(R.UU32());
cadre.setUserId(user.getId());
cadre.setUnionId(union.getId());
cadre.setMobile(user.getMobile());
cadre.setUnionName(user.getUnionName());
cadre.setLoginName(user.getLoginname());
cadre.setUserName(user.getUsername());
cadre.setRoleCode(roleCode);
cadre.setJ(j);
cadre.setApplyDate(new Date());
cadre.setIsServing(true);
cadre.setIsJoin(true);
// fastInsert不执行实体插入拦截器,显式补齐主键及审计字段。
cadre.setCreatedBy(operator);
cadre.setUpdatedBy(operator);
cadre.setCreatedAt(System.currentTimeMillis());
cadre.setUpdatedAt(cadre.getCreatedAt());
cadre.setDelFlag(false);
List<Sys_user_role> relations = new ArrayList<>();
for (String unitId : secretary ? unitIds : java.util.Collections.singletonList((String) null)) {
Sys_user_role relation = new Sys_user_role();
relation.setUserId(user.getId());
relation.setRoleId(role.getId());
relation.setUnionId(union.getId());
relation.setUnitId(unitId);
relations.add(relation);
}
return new ImportEntry(row, cadre, relations);
}
private void saveBatch(List<ImportEntry> entries) {
if (entries.isEmpty()) {
return; return;
} }
// 经由注入的原控制器代理调用,保留单行事务、直接授权及缓存清理逻辑。 try {
Result result = secretary // 两张表各批量写入一次,同一事务保证干部记录和角色授权同时生效。
? sysUnionController.insertBranchUnionPartySecretaryRole(user.getId(), union.getId(), unitIds.toArray(new String[0]), j) Trans.exec((Atom) () -> {
: sysUnionController.insertBranchUnionUserRole(user.getId(), roleCode, union.getId(), j); dao.fastInsert(entries.stream().map(entry -> entry.cadre).toList());
if (result.isSuccess()) { dao.fastInsert(entries.stream().flatMap(entry -> entry.relations.stream()).toList());
outcome(row, "成功", "添加成功"); });
} else if ("请勿重复添加".equals(result.getMsg()) || "该人员已设置二级党委书记,请先删除后再重新设置".equals(result.getMsg())) { entries.forEach(entry -> outcome(entry.row, "成功", "添加成功"));
outcome(row, "跳过", result.getMsg()); } catch (Exception e) {
if (entries.size() > 1) {
// 仅保存异常时拆分批次定位坏行,正常导入不产生逐行写入。
int middle = entries.size() / 2;
saveBatch(entries.subList(0, middle));
saveBatch(entries.subList(middle, entries.size()));
} else { } else {
outcome(row, "失败", result.getMsg()); NutMap row = entries.get(0).row;
log.error("导入分工会干部保存失败,行号:{},工号:{},角色编码:{}",
row.getInt("rowNumber"), row.getString("loginname"), row.getString("roleCode"), e);
outcome(row, "失败", saveFailure(e));
}
}
}
private String saveFailure(Exception error) {
for (Throwable cause = error; cause != null; cause = cause.getCause()) {
if (cause instanceof SQLException) {
SQLException sql = (SQLException) cause;
String state = sql.getSQLState();
if ("23505".equals(state) || sql.getErrorCode() == 1062) {
return "保存时发生唯一键冲突,可能已有相同干部或角色授权记录;本行已回滚";
}
if ("22001".equals(state) || sql.getErrorCode() == 1406) {
return "干部信息字段超出数据库长度限制,请检查工号、姓名、手机号、工会名称及届次长度;本行已回滚";
}
if ("23502".equals(state) || sql.getErrorCode() == 1048 || sql.getErrorCode() == 1364) {
return "干部记录或角色授权缺少数据库要求的必填值,请检查该人员的基础信息及干部表必填字段;本行已回滚";
}
if ("23503".equals(state) || sql.getErrorCode() == 1452) {
return "人员、工会、角色或所属单位的关联记录已不存在,请核对对应系统数据;本行已回滚";
}
if (state != null && state.startsWith("23")) {
return "干部记录或角色授权违反数据库必填、关联或唯一性约束,请核对人员、工会和角色数据;本行已回滚";
}
if (state != null && state.startsWith("08")) {
return "保存干部及角色授权时数据库连接异常,本行未完成导入,可稍后重新导入";
}
if (state != null && state.startsWith("40")) {
return "保存干部及角色授权时发生事务冲突,本行已回滚,可重新导入";
}
}
}
return "干部记录或角色授权写入异常,本行未完成导入;请管理员按Excel行号、工号及角色编码查看导入日志";
}
private static class ImportEntry {
private final NutMap row;
private final Sys_union_cadre cadre;
private final List<Sys_user_role> relations;
private ImportEntry(NutMap row, Sys_union_cadre cadre, List<Sys_user_role> relations) {
this.row = row;
this.cadre = cadre;
this.relations = relations;
} }
} }
@@ -22,7 +22,6 @@ import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.services.SysRoleService; import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUnitService; import com.budwk.app.sys.services.SysUnitService;
import com.budwk.app.sys.services.SysUserService; import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.sys.utils.DataCenterUtil;
import com.budwk.app.sys.views.View_user; import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil;
@@ -215,9 +214,9 @@ public class SysUnitController {
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) { if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); cnd.and("parentId", "=", "").or("parentId", "is", null);
} else { } else {
cnd.and("parentId", "=", pid).and("id", "<>", pid); cnd.and("parentId", "=", pid);
} }
cnd.asc("location").asc("path"); cnd.asc("location").asc("path");
list = sysUnitService.query(cnd); list = sysUnitService.query(cnd);
@@ -228,9 +227,9 @@ public class SysUnitController {
} else { } else {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) { if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); cnd.and("parentId", "=", "").or("parentId", "is", null);
} else { } else {
cnd.and("parentId", "=", pid).and("id", "<>", pid); cnd.and("parentId", "=", pid);
} }
cnd.asc("location").asc("path"); cnd.asc("location").asc("path");
list = sysUnitService.query(cnd); list = sysUnitService.query(cnd);
@@ -259,9 +258,9 @@ public class SysUnitController {
if (StpUtil.hasRole("SYSADMIN")) { if (StpUtil.hasRole("SYSADMIN")) {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) { if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); cnd.and("parentId", "=", "").or("parentId", "is", null);
} else { } else {
cnd.and("parentId", "=", pid).and("id", "<>", pid); cnd.and("parentId", "=", pid);
} }
cnd.asc("location").asc("path"); cnd.asc("location").asc("path");
list = sysUnitService.query(cnd); list = sysUnitService.query(cnd);
@@ -272,9 +271,9 @@ public class SysUnitController {
} else { } else {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) { if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); cnd.and("parentId", "=", "").or("parentId", "is", null);
} else { } else {
cnd.and("parentId", "=", pid).and("id", "<>", pid); cnd.and("parentId", "=", pid);
} }
cnd.asc("location").asc("path"); cnd.asc("location").asc("path");
list = sysUnitService.query(cnd); list = sysUnitService.query(cnd);
@@ -296,27 +295,37 @@ public class SysUnitController {
} }
} }
/**
* 获取以指定单位为根的单位树。
*
* @param pid 根单位 ID;未传时使用系统根单位 0
* @param req 当前 HTTP 请求
* @return Result 对象;code 为 0 时 data 为树数组,节点包含 id、parentId、name、children、unitTypeCode;
* unitTypeCode 为 0 表示其他类、1 表示部门类,历史空值仅在返回时按 0 展示
*/
@At("/tree") @At("/tree")
@Ok("json") @Ok("json")
@SaCheckLogin @SaCheckLogin
public Object tree(@Param("pid") String pid, HttpServletRequest req) { public Object tree(@Param("pid") String pid, HttpServletRequest req) {
try { try {
String rootId = StrUtil.blankToDefault(pid, "0");
String virtualRootId = "root"; String virtualRootId = "root";
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
cnd.asc("unitcode"); cnd.asc("unitcode");
List<Sys_unit> list = sysUnitService.query(cnd); List<Sys_unit> list = sysUnitService.query(cnd);
String rootId = StrUtil.isBlank(pid) ? DataCenterUtil.findRootUnit(list).getId() : pid;
List<TreeNode<String>> nodeList = CollUtil.newArrayList(); List<TreeNode<String>> nodeList = CollUtil.newArrayList();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
Sys_unit unit = list.get(i); Sys_unit unit = list.get(i);
/* /*
* 动态查询根挂到虚拟根下,兼容根节点父级为空或指向自身的数据。 * 单位根节点存在 id 与 parentId 都为 0 的自引用数据。
* 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。
*/ */
String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId(); String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId();
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i) nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i)
.setExtra( .setExtra(
Map.of( Map.of(
// 历史手工新建单位可能未设置类型,展示时按其他类处理,避免空值使整棵树加载失败。
"unitTypeCode", unit.getUnitTypeCode() == null ? 0 : unit.getUnitTypeCode() "unitTypeCode", unit.getUnitTypeCode() == null ? 0 : unit.getUnitTypeCode()
) )
)); ));
@@ -324,23 +333,37 @@ public class SysUnitController {
List<Tree<String>> treeList = TreeUtil.build(nodeList, virtualRootId); List<Tree<String>> treeList = TreeUtil.build(nodeList, virtualRootId);
return Result.success(treeList); return Result.success(treeList);
} catch (Exception e) { } catch (Exception e) {
log.error("加载单位树失败", e);
return Result.error(); return Result.error();
} }
} }
/**
* 新建当前单位的下设单位。
*
* @param unit 表单数据;name 为单位名称,unitcode 为唯一编码,unitLevel 为单位等级,unionId 为所属工会;
* unitTypeCode 为部门类别,0 表示其他类、1 表示部门类,未传时由 service 按其他类保存
* @param parentId 当前选中上级单位的 ID,必须对应已存在的单位
* @param req 当前 HTTP 请求
* @return Result 对象;code 为 0 表示保存成功,非 0 表示失败,msg 为提示信息,data 为空
*/
@At @At
@Ok("json") @Ok("json")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("sys.manager.unit.add") @SaCheckPermission("sys.manager.unit.add")
@SLog(tag = "新建单位", msg = "单位名称:${args[0].name}") @SLog(tag = "新建单位", msg = "单位名称:${args[0].name}")
public Object addDo(@Param("..") Sys_unit unit, @Param("parentId") String parentId, HttpServletRequest req) { public Object addDo(@Param("..") Sys_unit unit, @Param("parentId") String parentId, HttpServletRequest req) {
try { try {
if ("root".equals(parentId)) { if (Strings.isBlank(unit.getName()) || Strings.isBlank(unit.getUnitcode()) || Strings.isBlank(parentId) || "root".equals(parentId)) {
parentId = ""; return Result.error("单位名称、单位编码和上级单位不能为空");
} }
unit.setCreatedBy(SecurityUtil.getUserId()); unit.setCreatedBy(SecurityUtil.getUserId());
sysUnitService.save(unit, parentId); sysUnitService.save(unit, parentId);
return Result.success(); return Result.success();
} catch (BaseException e) {
return Result.error(e.getMessage());
} catch (Exception e) { } catch (Exception e) {
log.error("新建单位失败", e);
return Result.error(); return Result.error();
} }
} }
@@ -3,5 +3,17 @@ package com.budwk.app.sys.services;
import com.budwk.app.base.service.BaseService; import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_home_activity; import com.budwk.app.sys.models.Sys_home_activity;
import java.util.List;
public interface SysHomeActivityService extends BaseService<Sys_home_activity> { public interface SysHomeActivityService extends BaseService<Sys_home_activity> {
/**
* 获取指定用户有权查看的已启用首页活动。
*
* @param userId 当前登录用户ID,用于人员分组及自定义SQL的可见范围校验
* @param includeEnded true包含已结束活动并按状态、置顶和开始时间排序;false保持原有过滤和排序
* @return 活动实体列表,包含id、name、cover、url、h5Url、startDate、endDate等展示字段,
* 不返回classPath和allowUserSql内部配置
*/
List<Sys_home_activity> listHomeActivity(String userId, boolean includeEnded);
} }
@@ -1,15 +1,87 @@
package com.budwk.app.sys.services.impl; package com.budwk.app.sys.services.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity; import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.services.SysHomeActivityService; import com.budwk.app.sys.services.SysHomeActivityService;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
@IocBean(args = {"refer:dao"}) @IocBean(args = {"refer:dao"})
public class SysHomeActivityServiceImpl extends BaseServiceImpl<Sys_home_activity> implements SysHomeActivityService { public class SysHomeActivityServiceImpl extends BaseServiceImpl<Sys_home_activity> implements SysHomeActivityService {
@Inject
private ActivityBasicScopeService activityBasicScopeService;
public SysHomeActivityServiceImpl(Dao dao) { public SysHomeActivityServiceImpl(Dao dao) {
super(dao); super(dao);
} }
/**
* 沿用首页原有的启用及人员可见范围校验,仅在调用方明确要求时展示已结束活动。
* 参数和返回结构见服务接口;查询过程不更新活动配置。
*/
@Override
public List<Sys_home_activity> listHomeActivity(String userId, boolean includeEnded) {
FieldFilter fieldFilter = FieldFilter.locked(Sys_home_activity.class, "classPath");
List<Sys_home_activity> list = Daos.ext(dao(), fieldFilter).query(Sys_home_activity.class,
Cnd.where("enable", "=", 1).desc("top"));
List<Sys_home_activity> allowActivityList = new ArrayList<>();
// 同一次请求使用同一个时间点,保证过滤与排序对活动是否结束的判断一致。
Date today = DateUtil.date();
for (Sys_home_activity activity : list) {
// 未传includeEnded的既有调用继续过滤已结束活动,保留原来的日期比较口径。
if (!includeEnded && DateUtil.compare(today, activity.getEndDate()) > 0) {
continue;
}
Integer allowUserGroupId = activity.getAllowUserGroupId();
String allowUserSql = activity.getAllowUserSql();
// 自定义SQL仅用于服务端判断可见范围,禁止随活动列表返回前端。
activity.setAllowUserSql(null);
if (allowUserGroupId == null && StrUtil.isBlank(allowUserSql)) {
allowActivityList.add(activity);
continue;
}
if (allowUserGroupId != null) {
if (activityBasicScopeService.isUserInGroup(allowUserGroupId, userId)) {
allowActivityList.add(activity);
continue;
}
}
if (StrUtil.isNotBlank(allowUserSql)) {
Sql sql = Sqls.create(allowUserSql).setParam("userId", userId);
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
NutMap nutMap = (NutMap) sql.getResult();
if (Lang.isNotEmpty(nutMap) && StrUtil.isNotBlank(nutMap.getString("userId"))) {
allowActivityList.add(activity);
}
}
}
if (includeEnded) {
// 未结束活动整体在前,已结束置顶活动也不能越组;组内置顶优先,再按开始时间倒序,空开始时间排后。
allowActivityList.sort(Comparator
.comparing((Sys_home_activity activity) -> DateUtil.compare(today, activity.getEndDate()) > 0)
.thenComparing(activity -> Boolean.TRUE.equals(activity.getTop()), Comparator.reverseOrder())
.thenComparing(Sys_home_activity::getStartDate, Comparator.nullsLast(Comparator.reverseOrder())));
}
return allowActivityList;
}
} }
@@ -54,9 +54,8 @@ public class ActivityBasicSettingsController {
if (parentId != null) { if (parentId != null) {
parentId.setChild(child(parentId.getId())); parentId.setChild(child(parentId.getId()));
list.add(parentId); list.add(parentId);
return Result.success().addData(list);
} }
return Result.success(); return Result.success(list);
} }
private List<ActivityBasicSettings> child(String parentId) { private List<ActivityBasicSettings> child(String parentId) {
@@ -73,6 +72,9 @@ public class ActivityBasicSettingsController {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
if (StrUtil.isBlank(parentId)) { if (StrUtil.isBlank(parentId)) {
ActivityBasicSettings rootSetting = dao.fetch(ActivityBasicSettings.class, Cnd.where("parentId", "=", 0)); ActivityBasicSettings rootSetting = dao.fetch(ActivityBasicSettings.class, Cnd.where("parentId", "=", 0));
if (rootSetting == null) {
return Result.success(new Pagination<ActivityBasicSettings>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<ActivityBasicSettings>()));
}
cnd.and("parentId", "=", rootSetting.getId()); cnd.and("parentId", "=", rootSetting.getId());
} else { } else {
cnd.and("parentId", "=", parentId); cnd.and("parentId", "=", parentId);
@@ -116,7 +118,13 @@ public class ActivityBasicSettingsController {
@At @At
public Result getActivityTwoLevelType(String code) { public Result getActivityTwoLevelType(String code) {
if (StrUtil.isBlank(code)) {
return Result.success(new ArrayList<ActivityBasicSettings>());
}
ActivityBasicSettings fetch = dao.fetch(ActivityBasicSettings.class, Cnd.where("code", "=", code)); ActivityBasicSettings fetch = dao.fetch(ActivityBasicSettings.class, Cnd.where("code", "=", code));
if (fetch == null) {
return Result.success(new ArrayList<ActivityBasicSettings>());
}
List<ActivityBasicSettings> query = dao.query(ActivityBasicSettings.class, Cnd.where("parentId", "=", fetch.getId()).asc("code")); List<ActivityBasicSettings> query = dao.query(ActivityBasicSettings.class, Cnd.where("parentId", "=", fetch.getId()).asc("code"));
return Result.success(query); return Result.success(query);
} }
@@ -0,0 +1,35 @@
package com.budwk.app.zhgh.activity.basic.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import org.nutz.lang.util.NutMap;
/**
* 活动工会资料同步及组成单位维护,保留活动模块独立维护的临时分工会。
*/
public interface ActivityBasicUnionService extends BaseService<ActivityBasicUnion> {
/**
* 无请求参数,按 sys_union.id 新增缺少的活动工会,仅更新已有非临时工会的名称,不删除记录。
*
* @return NutMap;insertCount 为新增工会数,updateCount 为名称实际变更的工会数
*/
NutMap syncUnions();
/**
* 获取全部二级活动单位,不按现有分工会归属过滤候选项。
*
* @param unionId 当前活动分工会ID,用于回显已选组成单位
* @return NutMap;allUnits 为二级单位列表,selectUnitIds 为当前分工会已选二级单位ID列表
*/
NutMap getPartUnitTransferData(String unionId);
/**
* 设置当前活动分工会的组成单位,选中单位允许从其他活动分工会转入。
*
* @param unionId 当前活动分工会ID
* @param unitIds 二级活动单位ID数组,传空数组或 null 表示清空当前工会组成单位
* @return 无返回值;包含不存在或非二级单位的ID时抛出异常,不修改归属
*/
void setPartUnits(String unionId, String[] unitIds);
}
@@ -0,0 +1,19 @@
package com.budwk.app.zhgh.activity.basic.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import org.nutz.lang.util.NutMap;
/**
* 活动单位基础资料同步,活动工会归属仍由活动模块独立维护。
*/
public interface ActivityBasicUnitService extends BaseService<ActivityBasicUnit> {
/**
* 将系统单位同步到活动单位,不删除活动单位,不覆盖已有活动单位的工会归属。
*
* @return NutMap;insertCount 为新增单位数,updateCount 为更新基础资料的已有单位数,
* pendingUnionCount 为新增单位中未能匹配活动工会、需要配置归属的数量
*/
NutMap syncUnits();
}
@@ -0,0 +1,115 @@
package com.budwk.app.zhgh.activity.basic.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicUnionService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ActivityBasicUnionServiceImpl extends BaseServiceImpl<ActivityBasicUnion> implements ActivityBasicUnionService {
public ActivityBasicUnionServiceImpl(Dao dao) {
super(dao);
}
/**
* 无请求参数,按来源工会ID同步全部工会组织;返回新增数和名称实际变更数。
* 已有工会只更新名称及修改审计信息,不覆盖编码、联系方式、校区、组成单位和管理员关系。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public NutMap syncUnions() {
List<Sys_union> sourceUnions = dao().query(Sys_union.class, Cnd.NEW());
Map<String, ActivityBasicUnion> existingUnions = dao().query(ActivityBasicUnion.class, Cnd.NEW()).stream()
.collect(Collectors.toMap(ActivityBasicUnion::getId, union -> union));
String operatorId = SecurityUtil.getUserId();
long updateTime = System.currentTimeMillis();
int insertCount = 0;
int updateCount = 0;
for (Sys_union sourceUnion : sourceUnions) {
if (StrUtil.isBlank(sourceUnion.getId())) {
throw new BaseException("工会组织存在空ID,本次同步已取消,请先检查工会组织资料");
}
ActivityBasicUnion existingUnion = existingUnions.get(sourceUnion.getId());
if (existingUnion == null) {
// 按列插入以避开实体UUID生成拦截器,保留来源ID,保证重复同步不会重复新增。
dao().insert(ActivityBasicUnion.class, Chain.make("id", sourceUnion.getId())
.add("name", sourceUnion.getName()).add("unionCode", sourceUnion.getUnionCode())
.add("telephone", sourceUnion.getTelephone()).add("campus", sourceUnion.getCampus())
.add("source", "sys").add("createdBy", operatorId).add("createdAt", updateTime)
.add("updatedBy", operatorId).add("updatedAt", updateTime).add("delFlag", false));
insertCount++;
} else if (!"activity".equals(existingUnion.getSource())
&& !Objects.equals(existingUnion.getName(), sourceUnion.getName())) {
// 临时分工会即使与来源ID意外相同也不覆盖;来源中已不存在的工会自然保留。
dao().update(ActivityBasicUnion.class, Chain.make("name", sourceUnion.getName())
.add("updatedBy", operatorId).add("updatedAt", updateTime),
Cnd.where("id", "=", sourceUnion.getId()));
updateCount++;
}
}
return NutMap.NEW().addv("insertCount", insertCount).addv("updateCount", updateCount);
}
/**
* 按单位层级查询全部二级活动单位,单位类别和现有工会归属不影响候选范围。
*
* @param unionId 当前活动分工会ID,仅用于计算回显的选中项
* @return NutMap;allUnits 为候选单位实体列表,selectUnitIds 为当前工会已选二级单位ID列表
*/
@Override
public NutMap getPartUnitTransferData(String unionId) {
List<ActivityBasicUnit> units = dao().query(ActivityBasicUnit.class,
Cnd.where("unitLevel", "=", 2).asc("unitcode"));
List<String> selectUnitIds = units.stream()
.filter(unit -> Objects.equals(unit.getUnionId(), unionId))
.map(ActivityBasicUnit::getId).toList();
return NutMap.NEW().addv("selectUnitIds", selectUnitIds).addv("allUnits", units);
}
/**
* 校验全部选中单位均为二级单位后,替换当前活动分工会的组成单位关系。
*
* @param unionId 当前活动分工会ID
* @param unitIds 二级活动单位ID数组,空数组或 null 表示清空组成单位
* @return 无返回值;无效单位参数在修改归属之前抛出异常
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void setPartUnits(String unionId, String[] unitIds) {
List<String> selectedIds = unitIds == null ? List.of() : Arrays.stream(unitIds).distinct().toList();
if (!selectedIds.isEmpty()) {
// 必须先完整校验再解除原归属,防止提交三级、空ID或不存在的单位时丢失已有组成单位。
if (selectedIds.stream().anyMatch(StrUtil::isBlank)
|| dao().count(ActivityBasicUnit.class,
Cnd.where("id", "in", selectedIds).and("unitLevel", "=", 2)) != selectedIds.size()) {
throw new BaseException("组成单位只能选择有效的二级单位,请刷新后重新选择");
}
}
// 保存时替换当前组成单位,历史误选的三级单位也随本次重新设置解除关联。
dao().update(ActivityBasicUnit.class, Chain.make("unionId", null), Cnd.where("unionId", "=", unionId));
if (!selectedIds.isEmpty()) {
// 允许将已属于其他活动分工会的二级单位转入,不增加原工会归属限制。
dao().update(ActivityBasicUnit.class, Chain.make("unionId", unionId),
Cnd.where("id", "in", selectedIds).and("unitLevel", "=", 2));
}
}
}
@@ -0,0 +1,93 @@
package com.budwk.app.zhgh.activity.basic.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicUnitService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ActivityBasicUnitServiceImpl extends BaseServiceImpl<ActivityBasicUnit> implements ActivityBasicUnitService {
public ActivityBasicUnitServiceImpl(Dao dao) {
super(dao);
}
/**
* 无请求参数,按 sys_unit.id 同步全部系统单位;返回新增数、更新数和新增单位待配置活动工会数。
* 已有记录只更新基础字段,连原有的空工会归属也保留,避免覆盖活动模块人工维护的组成单位关系。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public NutMap syncUnits() {
List<Sys_unit> sourceUnits = dao().query(Sys_unit.class, Cnd.NEW());
Set<String> existingIds = dao().query(ActivityBasicUnit.class, Cnd.NEW()).stream()
.map(ActivityBasicUnit::getId).collect(Collectors.toSet());
Set<String> activityUnionIds = dao().query(ActivityBasicUnion.class, Cnd.NEW()).stream()
.map(ActivityBasicUnion::getId).collect(Collectors.toSet());
int insertCount = 0;
int updateCount = 0;
int pendingUnionCount = 0;
String operatorId = SecurityUtil.getUserId();
long updateTime = System.currentTimeMillis();
for (Sys_unit sourceUnit : sourceUnits) {
if (StrUtil.isBlank(sourceUnit.getId())) {
throw new BaseException("系统单位存在空ID,本次同步已取消,请先检查系统单位资料");
}
Chain fields = buildBasicFields(sourceUnit)
.add("updatedBy", operatorId).add("updatedAt", updateTime);
if (existingIds.contains(sourceUnit.getId())) {
// 更新列中不包含 unionId、unionGroupId 和 id,保留现有归属及报名记录使用的单位ID。
dao().update(ActivityBasicUnit.class, fields, Cnd.where("id", "=", sourceUnit.getId()));
updateCount++;
} else {
// 新单位仅继承已存在于活动工会表的归属,不能将普通工会ID直接当作有效活动工会。
String unionId = StrUtil.isNotBlank(sourceUnit.getUnionId()) && activityUnionIds.contains(sourceUnit.getUnionId())
? sourceUnit.getUnionId() : null;
if (unionId == null) {
pendingUnionCount++;
}
fields.add("id", sourceUnit.getId()).add("unionId", unionId)
.add("createdBy", operatorId).add("createdAt", updateTime).add("delFlag", false);
// 按列插入避开实体的 UUID 生成拦截器,确保活动单位ID始终等于系统单位ID。
dao().insert(ActivityBasicUnit.class, fields);
existingIds.add(sourceUnit.getId());
insertCount++;
}
}
return NutMap.NEW().addv("insertCount", insertCount).addv("updateCount", updateCount)
.addv("pendingUnionCount", pendingUnionCount);
}
/**
* 只列出允许从系统单位更新的基础资料;工会、小组归属和审计创建信息不属于基础同步字段。
* 父级、路径与层级沿用源值,树展示需要的根节点适配在接口内存中完成,不改写单位关系。
*
* @param unit sys_unit 中的完整单位记录
* @return 可供 DAO 按列新增或更新的 Chain,不含单位ID和工会归属
*/
private Chain buildBasicFields(Sys_unit unit) {
return Chain.make("parentId", unit.getParentId()).add("path", unit.getPath())
.add("name", unit.getName()).add("aliasName", unit.getAliasName()).add("unitcode", unit.getUnitcode())
.add("note", unit.getNote()).add("address", unit.getAddress()).add("telephone", unit.getTelephone())
.add("email", unit.getEmail()).add("website", unit.getWebsite()).add("location", unit.getLocation())
.add("hasChildren", unit.isHasChildren()).add("logo", unit.getLogo()).add("unitLevel", unit.getUnitLevel())
.add("divisionCollegeCode", unit.getDivisionCollegeCode()).add("institutionType", unit.getInstitutionType())
.add("unitType", unit.getUnitType()).add("unitTypeCode", unit.getUnitTypeCode());
}
}
@@ -0,0 +1,40 @@
package com.budwk.app.zhgh.activity.sports.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import java.math.BigDecimal;
/** 一条获奖成绩的奖品人数归属,与积分分配独立;一人一次获奖对应一份奖品。 */
@Data
@EqualsAndHashCode(callSuper = true)
@Table("activity_results_prize")
@Comment("活动成绩奖品人数分配")
@TableIndexes({@Index(name = "ARP_RESULT_UNION", fields = {"resultId", "prizeUnionId"}, unique = true)})
public class ActivityResultsPrize extends BaseModel {
@Name
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@Comment("成绩主记录ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String resultId;
@Column
@Comment("领取奖品的正式分工会ID,对应sys_union")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String prizeUnionId;
@Column
@Comment("奖品领取工会名称快照")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String prizeUnionName;
@Column
@Comment("实际获奖人数,个人成绩合计为1,团体成绩合计等于获奖总人数")
@ColDefine(type = ColType.INT)
// 接收时保留原始数值精度,避免JSON直接绑定Integer时把3.5截断为3;保存前严格转为int范围整数。
private BigDecimal numberOfPeople;
}
@@ -0,0 +1,40 @@
package com.budwk.app.zhgh.activity.sports.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import java.math.BigDecimal;
/** 一次团体获奖在正式分工会之间的积分分配,不代表新增获奖或人数分配。 */
@Data
@EqualsAndHashCode(callSuper = true)
@Table("activity_results_score")
@Comment("活动成绩积分分配")
@TableIndexes({@Index(name = "ARS_RESULT_UNION", fields = {"resultId", "scoreUnionId"}, unique = true)})
public class ActivityResultsScore extends BaseModel {
@Name
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@Comment("成绩主记录ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String resultId;
@Column
@Comment("正式计分工会ID,对应sys_union")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String scoreUnionId;
@Column
@Comment("计分工会名称快照")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String scoreUnionName;
@Column
@Comment("分配积分,合计必须等于主成绩总积分")
@ColDefine(customType = "decimal(10,2)")
private BigDecimal integral;
}
@@ -649,8 +649,11 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
fillCourseResponsibles(courseList); fillCourseResponsibles(courseList);
courseList.forEach(c -> { courseList.forEach(c -> {
if (StrUtil.isNotBlank(c.getCourseType())) {
dao().fetchLinks(c, "^(courseTimeList)$", Cnd.NEW().asc("courseStartTime")); dao().fetchLinks(c, "^(courseTimeList)$", Cnd.NEW().asc("courseStartTime"));
if (c.getCourseTimeList() == null) {
c.setCourseTimeList(new ArrayList<>());
}
if (StrUtil.isNotBlank(c.getCourseType())) {
int courseCount = statisticsService.queryCourseCount(c.getId(), c.getCourseType()); int courseCount = statisticsService.queryCourseCount(c.getId(), c.getCourseType());
c.setHasRegisterNum(courseCount); c.setHasRegisterNum(courseCount);
int courseWaitCount = statisticsService.queryCourseWaitCount(c.getId(), c.getCourseType()); int courseWaitCount = statisticsService.queryCourseWaitCount(c.getId(), c.getCourseType());
@@ -0,0 +1,104 @@
package com.budwk.app.zhgh.club.controller.statistics;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.zhgh.club.service.SysClubService;
import com.budwk.app.zhgh.club.service.impl.SysClubUserServiceImpl;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.validation.Valid;
import java.util.List;
@IocBean
@Ok("json")
@At("/platform/club/statistics/member")
public class ClubMemberStatisticsController {
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/statistics/member.html")
@SaCheckPermission("club.statistics.member")
public void index() {
}
@At
@SaCheckPermission("club.statistics.member")
public Result pageData(@Valid PageForm pageForm, String clubId, String userId, String keyWord, String unitName, String userState, String sex) {
Sql sql = generateSql(false, clubId, userId, keyWord, unitName, userState, sex);
Pagination pagination = sysClubService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> members = pagination.getList(NutMap.class);
for (NutMap member : members) {
List<String> roles = StrUtil.isBlank(member.getString("roleCode"))
? List.of() : Json.fromJsonAsList(String.class, member.getString("roleCode"));
member.put("roleName", SysClubUserServiceImpl.convertRoleName(roles));
member.put("roleCode", roles);
}
pagination.setList(members);
return Result.success(pagination);
}
@At
@SaCheckPermission("club.statistics.member")
public Result clubOptions() {
return Result.success(sysClubService.listMap(generateSql(true, null, null, null, null, null, null)));
}
private Sql generateSql(boolean options, String clubId, String userId, String keyWord, String unitName, String userState, String sex) {
String fields = options ? "DISTINCT club.id, club.clubCode, club.clubName" : """
c.id, c.userId, club.clubCode, club.clubName,
COALESCE(NULLIF(u.username, ''), c.userName) AS userName,
COALESCE(NULLIF(u.loginname, ''), c.loginName) AS loginName,
u.sex, COALESCE(NULLIF(u.mobile, ''), c.mobile) AS mobile,
u.unitname AS unitName, u.userState, c.roleCode, c.joinTime
""";
Sql sql = Sqls.create("SELECT " + fields + "\n" + """
FROM sys_club club
INNER JOIN club_user c ON c.clubId = club.id
LEFT JOIN vw_user u ON u.id = c.userId
$condition
AND EXISTS (
SELECT 1 FROM wf_process_instance registerInstance
INNER JOIN wf_process_define registerDefine
ON registerDefine.id = registerInstance.processDefineId
WHERE registerInstance.businessNo = club.id
AND registerInstance.state = @finishedState
AND registerInstance.delFlag = 0
AND registerDefine.name = @registerProcessKey
AND registerDefine.delFlag = 0
)
ORDER BY club.clubCode, club.id
""" + (options ? "" : ", c.id"));
Cnd cnd = Cnd.where("club.dismiss", "=", false);
cnd.and("club.delFlag", "=", false);
cnd.and("c.delFlag", "=", false);
cnd.andEX("club.id", "=", clubId);
cnd.andEX("c.userId", "=", userId);
cnd.andEX("u.userState", "=", userState);
cnd.andEX("u.sex", "=", sex);
if (StrUtil.isNotBlank(unitName)) {
cnd.and("u.unitname", "=", unitName);
}
if (StrUtil.isNotBlank(keyWord)) {
cnd.and(Cnd.exps("COALESCE(NULLIF(u.username, ''), c.userName)", "like", "%" + keyWord + "%")
.or("COALESCE(NULLIF(u.loginname, ''), c.loginName)", "like", "%" + keyWord + "%"));
}
sql.setCondition(cnd);
sql.setParam("finishedState", ProcessInstanceStateEnum.FINISHED.getCode());
sql.setParam("registerProcessKey", "XHZC");
return sql;
}
}
@@ -129,7 +129,21 @@ public class ClubStatisticsController {
u.mobile, u.mobile,
u.unitname as unitName, u.unitname as unitName,
u.userState, u.userState,
c.* c.id,
c.clubId,
c.userId,
c.roleCode,
c.clubPosition,
c.email,
c.avatar,
c.sameTimeJoinOtherClubSituation,
c.awardsExperience,
c.joinTime,
c.createdBy,
c.createdAt,
c.updatedBy,
c.updatedAt,
c.delFlag
FROM FROM
club_user c club_user c
LEFT JOIN `vw_user` u ON u.id = c.userId LEFT JOIN `vw_user` u ON u.id = c.userId
@@ -0,0 +1,58 @@
-- 1. 将现有“社团统计”移到新父级下面,保留原权限和角色关联。
UPDATE sys_menu
SET parentId = 'f2b61f72eab34e139bc150a2d907c385',
path = '001100060001',
updatedAt = UNIX_TIMESTAMP(NOW()) * 1000,
permission = 'club.statistics.group'
WHERE id = 'b8817cce3ae34d49844a7ea8d8b15f3f'
AND permission = 'club.statistics';
-- 2. 新增“社团统计”父级菜单。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target,
icon, showit, disabled, permission, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag,
platform, moduleId, initialPinyinName, isRecommendApp, isRecommendService
) VALUES (
'f2b61f72eab34e139bc150a2d907c385',
'',
'00110006',
'社团统计', '', 'menu',
'', '',
'', 1, 0, 'club.statistics', 411, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000, 0,
'PC', '4cfc8977507c40b3acee8443c7c5a8a3', 's', 0, 0
);
-- 3. 新增“成员统计”子菜单。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target,
icon, showit, disabled, permission, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag,
platform, moduleId, initialPinyinName, isRecommendApp, isRecommendService
) VALUES (
'a914ce82f0764e9c8f1b35a6d2270b41',
'f2b61f72eab34e139bc150a2d907c385',
'001100060002',
'成员统计', '', 'menu',
'/platform/club/statistics/member', 'data-pjax',
'', 1, 0, 'club.statistics.member', 412, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000, 0,
'PC', '4cfc8977507c40b3acee8443c7c5a8a3', 'c', 0, 0
);
-- 成员统计可查询所有社团,仅给系统管理员和校工会管理员授权。
-- 社团统计父级
INSERT INTO sys_role_menu (roleId, menuId) VALUES
('419844ad90f34ca58807e84224905f27', 'f2b61f72eab34e139bc150a2d907c385'),
('6aece46b946e4fb9ac4cb15c0cd8c3e6', 'f2b61f72eab34e139bc150a2d907c385'),
('722d454171ac49f1bba60b7672e500ff', 'f2b61f72eab34e139bc150a2d907c385'),
('72e17abb91cb48b4820474904e189182', 'f2b61f72eab34e139bc150a2d907c385'),
('7f4a820d5a394bb8a0a92d6699afe7c8', 'f2b61f72eab34e139bc150a2d907c385');
-- 成员统计
INSERT INTO sys_role_menu (roleId, menuId) VALUES
('722d454171ac49f1bba60b7672e500ff', 'a914ce82f0764e9c8f1b35a6d2270b41'),
('7f4a820d5a394bb8a0a92d6699afe7c8', 'a914ce82f0764e9c8f1b35a6d2270b41');
@@ -1,58 +0,0 @@
;(function (a) {
;(jQuery.browser = jQuery.browser || {}).mobile =
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
a
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
a.substr(0, 4)
)
})(navigator.userAgent || navigator.vendor || window.opera)
$.ajaxSetup({
contentType: "application/x-www-form-urlencoded;charset=utf-8",
success: function (response, textStatus, jqXHR) {
if (response.code !== 0) {
// AJAX全局回调没有Vue实例上下文,使用Element全局消息组件展示业务错误。
if (window.ELEMENT && ELEMENT.Message) {
ELEMENT.Message.error(response.msg)
}
} else {
return response
}
},
error: function (v1) {
this.$message.error(v1.status + " " + v1.statusText)
},
complete: function (XMLHttpRequest, textStatus) {
var sessionstatus = XMLHttpRequest.getResponseHeader("loginStatus") //通过XMLHttpRequest取得响应头,sessionstatus,
if (sessionstatus == "accessDenied") {
this.$message({
message: "\u767b\u5f55\u5931\u6548\uff0c\u8bf7\u5237\u65b0\u9875\u9762\u91cd\u65b0\u767b\u5f55",
type: "error"
})
}
if (sessionstatus == "unauthorized") {
this.$message({
message: "\u6ca1\u6709\u6743\u9650",
type: "error"
})
}
}
})
function pjaxReplace(url, data = {}) {
const { pathname, search } = location
if (url === pathname + search) {
return
}
$.pjax({
url: url,
container: "#sub-app-container-main-content-body",
maxCacheLength: 0,
push: false,
replace: true,
fragment: "#sub-app-container-main-content-body",
timeout: 8000,
data
})
}
@@ -11,7 +11,7 @@
<el-table-column label="总分" prop="totalScore" header-align="center" <el-table-column label="总分" prop="totalScore" header-align="center"
align="center" show-overflow-tooltip></el-table-column> align="center" show-overflow-tooltip></el-table-column>
<el-table-column align="center" header-align="center" type="index" :index="indexMethod" label="名次" <el-table-column align="center" header-align="center" prop="rank" label="名次"
width="80px"></el-table-column> width="80px"></el-table-column>
</el-table> </el-table>
@@ -47,7 +47,21 @@ module.exports = {
this.tableData = [] this.tableData = []
const {data} = await this.$axios.post(loc() + "/isScoreTopEight", this.Form) const {data} = await this.$axios.post(loc() + "/isScoreTopEight", this.Form)
const table = data.score.sort(this.compare("totalScore")) const table = data.score.sort(this.compare("totalScore"))
this.tableData = table.slice(0, 8) // 按竞赛名次规则取前八:同分并列同名次,并列后名次不跳号(dense rank);
// 名次大于 8 的舍弃。不能直接 slice(0, 8),否则同分并列的工会会被切掉。
const ranked = []
let rank = 0
let prevScore = null
table.forEach(v => {
// 总分带 0.5 折半小数,比较用容差,避免浮点误差误判并列
if (prevScore === null || Math.abs(v.totalScore - prevScore) > 0.000001) {
// 每遇到一个新的分值,名次 +1;同分者沿用同一名次
rank += 1
prevScore = v.totalScore
}
ranked.push(Object.assign({}, v, {rank: rank}))
})
this.tableData = ranked.filter(v => v.rank <= 8)
} catch (e) { } catch (e) {
this.$message.error(e.msg) this.$message.error(e.msg)
} finally { } finally {
@@ -15,7 +15,7 @@ const act = {
<el-image :src="activity.cover" fit="cover" :alt="activity.name"> <el-image :src="activity.cover" fit="cover" :alt="activity.name">
<div slot="error" class="portal-cover-empty"><i class="fa fa-picture-o" aria-hidden="true"></i><span>暂无图片</span></div> <div slot="error" class="portal-cover-empty"><i class="fa fa-picture-o" aria-hidden="true"></i><span>暂无图片</span></div>
</el-image> </el-image>
<span class="status" :class="{'status-ended': getStatusText(activity) === '已结束', 'status-upcoming': getStatusText(activity) === '未开始'}"> <span class="status" :class="{'status-ended': getStatusText(activity) === '报名已结束', 'status-upcoming': getStatusText(activity) === '未开始'}">
{{getStatusText(activity)}} {{getStatusText(activity)}}
</span> </span>
</div> </div>
@@ -76,9 +76,9 @@ const act = {
if (!this.swiper) return if (!this.swiper) return
this.swiper.isEnd ? this.swiper.slideTo(0) : this.swiper.slideNext() this.swiper.isEnd ? this.swiper.slideTo(0) : this.swiper.slideNext()
}, },
/*查询活动*/ // PC首页包含已结束活动;接口data为按未结束在前、已结束在后排列的活动实体数组。
listAct() { listAct() {
this.$axios.post("/platform/home/listHomeActivity").then((res) => { this.$axios.post("/platform/home/listHomeActivity", {includeEnded: true}).then((res) => {
if (res.code === 0) { if (res.code === 0) {
// for (let i = 0; i < 10; i++) { // for (let i = 0; i < 10; i++) {
// this.actList = this.actList.concat(res.data) // this.actList = this.actList.concat(res.data)
@@ -149,7 +149,7 @@ const act = {
// 活动状态 // 活动状态
getStatusText(item) { getStatusText(item) {
if (this.$moment().unix() > this.$moment(item.endDate).unix()) { if (this.$moment().unix() > this.$moment(item.endDate).unix()) {
return "已结束" return "报名已结束"
} else if (this.$moment().unix() < this.$moment(item.startDate).unix()) { } else if (this.$moment().unix() < this.$moment(item.startDate).unix()) {
return "未开始" return "未开始"
} else { } else {
@@ -40,7 +40,7 @@ const branchUnionGroupManage = {
</el-card> </el-card>
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogFormVisible" width="600px" <el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogFormVisible" width="600px"
:close-on-click-modal="false"> :close-on-click-modal="false" @close="clearUserOptions">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px"> <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="小组编码" prop="code"> <el-form-item label="小组编码" prop="code">
<el-input v-model="formData.code" placeholder="小组编码" maxlength="10" <el-input v-model="formData.code" placeholder="小组编码" maxlength="10"
@@ -103,6 +103,7 @@ const branchUnionGroupManage = {
unitIds: [{ required: true, message: "请选择组成单位", trigger: "change" }], unitIds: [{ required: true, message: "请选择组成单位", trigger: "change" }],
}, },
userOptions: [], userOptions: [],
userOptionsRequestId: 0,
units: [], units: [],
} }
}, },
@@ -173,13 +174,21 @@ const branchUnionGroupManage = {
}) })
}, },
clearUserOptions() {
this.userOptions = []
// 关闭后使未完成的搜索失效,防止旧结果回填到下一次打开的弹窗。
this.userOptionsRequestId++
},
getUserOptions(keyword, userIds = null) { getUserOptions(keyword, userIds = null) {
if (!this.dialogFormVisible && !userIds) return
const requestId = ++this.userOptionsRequestId
$.get("/platform/sys/unionGroup/listUser", { $.get("/platform/sys/unionGroup/listUser", {
keyword, keyword,
unionId: this.union_id, unionId: this.union_id,
userIds: JSON.stringify(userIds) userIds: JSON.stringify(userIds)
}).then((res) => { }).then((res) => {
if (res.code === 0) { if (res.code === 0 && this.dialogFormVisible && requestId === this.userOptionsRequestId) {
this.userOptions = res.data this.userOptions = res.data
} }
}) })
@@ -71,7 +71,7 @@ const branchUnionManage = {
<div class="el-upload__text" v-if="cadreImportLoading">正在导入,请稍候…</div> <div class="el-upload__text" v-if="cadreImportLoading">正在导入,请稍候…</div>
<div class="el-upload__text" v-else>将文件拖到此处,或<em>点击选择文件</em></div> <div class="el-upload__text" v-else>将文件拖到此处,或<em>点击选择文件</em></div>
</el-upload> </el-upload>
<div class="union-cadre-import-note">选择文件后点击“开始导入”。逐行保存,一行失败不影响其他行。</div> <div class="union-cadre-import-note">选择文件后点击“开始导入”。批量校验并保存,失败行显示具体原因,其他有效行继续导入。</div>
</section> </section>
</div> </div>
<section v-if="cadreImportResult" class="union-cadre-import-results"> <section v-if="cadreImportResult" class="union-cadre-import-results">
@@ -77,6 +77,7 @@ layout("/layouts/platform.html"){
<el-input placeholder="输入关键字进行查找" v-model="filterText" clearable></el-input> <el-input placeholder="输入关键字进行查找" v-model="filterText" clearable></el-input>
<div style="max-height: calc(100vh - 200px); overflow-y: auto"> <div style="max-height: calc(100vh - 200px); overflow-y: auto">
<el-tree <el-tree
v-loading="treeLoading"
:data="treeData" :data="treeData"
ref="treeRef" ref="treeRef"
:expand-on-click-node="false" :expand-on-click-node="false"
@@ -151,13 +152,14 @@ layout("/layouts/platform.html"){
<!--#include("unitLeader.js"){}#--> <!--#include("unitLeader.js"){}#-->
new Vue({ new Vue({
el: "#app", el: "#app",
data: function () { data() {
return { return {
unitTabActive: "threeUnitManage", unitTabActive: "threeUnitManage",
filterText: null, filterText: null,
currentTreeNode: null, currentTreeNode: null,
currentTreeData: null, currentTreeData: null,
defaultExpandedKeys: [], defaultExpandedKeys: [],
treeLoading: false,
treeData: [] treeData: []
} }
}, },
@@ -173,24 +175,43 @@ layout("/layouts/platform.html"){
}, },
methods: { methods: {
getTreeData() { getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => { this.$set(this, "treeLoading", true)
this.treeData = res.data // tree 返回单位树数组;失败时保留现有内容,成功后按 ID 恢复当前单位。
if (this.treeData) { return this.$axios.post(loc() + "/tree").then((res) => {
this.currentTreeData = this.treeData[0] if (res.code !== 0 || !Array.isArray(res.data)) return
this.defaultExpandedKeys = this.treeData.length > 0 ? [this.treeData[0].id] : [] const selectedId = this.currentTreeData?.id
this.$set(this, "treeData", res.data)
this.$set(this, "defaultExpandedKeys", res.data.length > 0 ? [res.data[0].id] : [])
if (res.data.length === 0) {
this.$set(this, "currentTreeData", null)
this.$set(this, "currentTreeNode", null)
return
} }
this.$nextTick(() => {
const tree = this.$refs.treeRef
const node = (selectedId && tree.getNode(selectedId)) || tree.getNode(res.data[0].id)
// 将选中节点的祖先展开,避免刷新后当前单位隐藏在折叠的分支中。
let parent = node.parent
while (parent && parent.level > 0) {
parent.expanded = true
parent = parent.parent
}
tree.setCurrentKey(node.data.id)
tree.filter(this.filterText)
this.treeNodeClick(node.data, node)
})
}).finally(() => {
this.$set(this, "treeLoading", false)
}) })
}, },
treeNodeClick(data, node) { treeNodeClick(data, node) {
this.currentTreeData = data this.$set(this, "currentTreeData", data)
this.currentTreeNode = node this.$set(this, "currentTreeNode", node)
// 单位列表由 currentData 监听刷新,部门类另外更新人员和干部页签。
this.$nextTick(() => { this.$nextTick(() => {
if (data.unitTypeCode == 1) { if (data.unitTypeCode == 1) {
this.$refs.bmlRef.pageData()
this.$refs.userRef.pageData() this.$refs.userRef.pageData()
this.$refs.leaderRef.pageData() this.$refs.leaderRef.pageData()
} else {
this.$refs.bzlRef.pageData()
} }
}) })
}, },
@@ -11,7 +11,7 @@ const UNIT_MANAGE_TEMPLATE = {
<table-tool label="单位列表"> <table-tool label="单位列表">
<el-button @click="openAdd" type="primary" size="small">新建单位</el-button> <el-button @click="openAdd" type="primary" size="small">新建单位</el-button>
</table-tool> </table-tool>
<el-table :data="tableData" size="mini"> <el-table :data="tableData" v-loading="tableLoading" size="mini">
<el-table-column label="序号" type="index" width="60"> <el-table-column label="序号" type="index" width="60">
<template v-slot="scope">{{scope.$index + (pageForm.pageNumber - 1) * pageForm.pageSize + 1}}</template> <template v-slot="scope">{{scope.$index + (pageForm.pageNumber - 1) * pageForm.pageSize + 1}}</template>
</el-table-column> </el-table-column>
@@ -75,8 +75,8 @@ const UNIT_MANAGE_TEMPLATE = {
<!-- </el-form-item>--> <!-- </el-form-item>-->
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button> <el-button :disabled="formLoading" @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="doHandle">确 定</el-button> <el-button type="primary" :loading="formLoading" @click="doHandle">确 定</el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
@@ -110,6 +110,8 @@ const UNIT_MANAGE_TEMPLATE = {
audit: false audit: false
}, },
tableData: [], tableData: [],
tableLoading: false,
formLoading: false,
dialogVisible: false, dialogVisible: false,
formData: {}, formData: {},
formRules: { formRules: {
@@ -128,96 +130,130 @@ const UNIT_MANAGE_TEMPLATE = {
} }
}, },
computed: {}, computed: {},
watch: {
// 首次进入、切换节点以及刷新树后,均按最新上级单位从第一页重新查询。
currentData: {
immediate: true,
handler() {
this.doSearch()
}
}
},
methods: { methods: {
doSearch() { doSearch() {
this.pageForm.pageNumber = 1 this.$set(this.pageForm, "pageNumber", 1)
this.pageData() return this.pageData()
}, },
openAdd() { openAdd() {
this.dialogVisible = true // 上级关系取当前单位 ID,与用户是否点击过树节点无关;手工新增默认其他类。
this.formData = {} //打开新增窗口,表单先清空 this.$set(this, "formData", {
this.parentUnit = [] parentId: this.currentData.id,
this.options = [] unitLevel: this.unitLevel,
unitTypeCode: 0
})
this.$set(this, "parentUnit", [])
this.$set(this, "options", [])
this.$set(this, "dialogVisible", true)
this.$nextTick(() => {
this.$refs.form.clearValidate()
})
},
closeDialog() {
// 提交过程中保留表单,等待请求完成后再允许取消。
if (!this.formLoading) {
this.$set(this, "dialogVisible", false)
}
}, },
doHandle() { doHandle() {
this.$confirm("确定要提交吗?", "提示", { if (this.formLoading) return
this.$refs.form.validate((valid) => {
if (!valid) return
return this.$confirm("确定要提交吗?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning" type: "warning"
}).then(() => { }).then(() => {
const self = this if (this.formLoading) return
const url = this.formData.id ? "/platform/sys/unit/editDo" : "/platform/sys/unit/addDo" const url = this.formData.id ? "/platform/sys/unit/editDo" : "/platform/sys/unit/addDo"
this.$refs["form"].validate(async function (valid) { // 新建始终携带当前上级;编辑沿用回显的原上级,不在保存时意外变更层级。
if (valid) { if (!this.formData.id) {
if (self.currentNode) { this.$set(this.formData, "parentId", this.currentData.id)
self.formData.parentId = self.currentData.id
} }
const resp = await self.$axios.post(url, self.formData) this.$set(this, "formLoading", true)
if (resp.code === 0) { return this.$axios.post(url, this.formData).then((resp) => {
self.$message.success(resp.msg)
self.doSearch()
self.dialogVisible = false
self.$emit("refresh", null)
} else {
self.$message.warning(resp.msg)
}
}
})
})
},
async openEdit(id) {
console.log(this.currentData)
const resp = await this.$axios.post("/platform/sys/unit/edit/" + id)
if (resp.code === 0) {
this.formData = resp.data
this.dialogVisible = true
} else {
this.$message.warning(resp.msg)
}
},
async doDelete(id) {
const confirm = await this.$confirm("此操作将永久删除, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if (confirm === "confirm") {
const resp = await this.$axios.post("/platform/sys/unit/delete/" + id)
if (resp.code === 0) { if (resp.code === 0) {
this.$message.success(resp.msg) this.$message.success(resp.msg)
this.doSearch() this.$set(this, "dialogVisible", false)
// 父页面刷新并恢复当前树节点,currentData 监听统一刷新列表,避免重复查询。
this.$emit("refresh", null) this.$emit("refresh", null)
} else { } else {
this.$message.warning(resp.msg) this.$message.warning(resp.msg)
} }
}).finally(() => {
this.$set(this, "formLoading", false)
})
}).catch(() => {
// 取消确认直接结束;请求错误由全局 axios 拦截器提示,保留表单供重试。
})
})
},
// id 为待编辑单位 ID,接口 data 为单位实体;保留已有部门类别及原上级关系。
openEdit(id) {
return this.$axios.post("/platform/sys/unit/edit/" + id).then((resp) => {
if (resp.code === 0) {
this.$set(this, "formData", resp.data)
this.$set(this, "dialogVisible", true)
this.$nextTick(() => {
this.$refs.form.clearValidate()
})
} else {
this.$message.warning(resp.msg)
} }
})
},
doDelete(id) {
return this.$confirm("此操作将永久删除, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
return this.$axios.post("/platform/sys/unit/delete/" + id).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$emit("refresh", null)
} else {
this.$message.warning(resp.msg)
}
})
})
}, },
pageNumberChange(val) { pageNumberChange(val) {
this.pageForm.pageNumber = val this.$set(this.pageForm, "pageNumber", val)
this.pageData() this.pageData()
}, },
pageSizeChange(val) { pageSizeChange(val) {
this.pageForm.pageSize = val this.$set(this.pageForm, "pageSize", val)
this.pageData() this.pageData()
}, },
pageData() { pageData() {
this.pageForm.unitId = this.currentData.id if (!this.currentData || !this.currentData.id) return
// this.pageForm.unitLevel = this.unitLevel // unitId 限定当前上级,返回 data.list 为下设单位,data.totalCount 为分页总数。
this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => { this.$set(this.pageForm, "unitId", this.currentData.id)
this.$set(this, "tableLoading", true)
return this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => {
if (res.code === 0) { if (res.code === 0) {
this.tableData = res.data.list this.$set(this, "tableData", res.data.list)
this.pageForm.totalCount = res.data.totalCount this.$set(this.pageForm, "totalCount", res.data.totalCount)
} }
}).finally(() => {
this.$set(this, "tableLoading", false)
}) })
} }
}, },
async created() { created() {
this.pageData() const unionId = this.$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN') ? null : this.store.state.user.union.id
this.$businessTool.listUnion(unionId).then((unions) => {
if (this.$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN')) { this.$set(this, "unions", unions)
this.unions = await this.$businessTool.listUnion() })
} else {
this.unions = await this.$businessTool.listUnion(this.store.state.user.union.id)
}
} }
} }
@@ -119,7 +119,7 @@ layout("/layouts/platform.html"){
currentTreeNode: null, currentTreeNode: null,
currentTreeData: null, currentTreeData: null,
checkData: {}, checkData: { id: "0", name: "无(新建根节点)" },
tableData: [], tableData: [],
basicTypeDialog: false, basicTypeDialog: false,
@@ -146,10 +146,8 @@ layout("/layouts/platform.html"){
getTreeData() { getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => { this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data this.treeData = res.data || []
if (this.treeData) { this.checkData = this.treeData.length ? this.treeData[0] : { id: "0", name: "无(新建根节点)" }
this.checkData = this.treeData[0]
}
}) })
}, },
@@ -14,6 +14,7 @@ const ACTIVITY_BASIC_BRANCH_UNION_MANAGE = {
<el-card shadow="never" style="border: 1px solid var(--border-color-lighter); margin-top: 10px"> <el-card shadow="never" style="border: 1px solid var(--border-color-lighter); margin-top: 10px">
<table-tool> <table-tool>
<el-button @click="openAdd" icon="ti-plus" type="primary" style="margin-left: auto" size="small">新建临时分工会</el-button> <el-button @click="openAdd" icon="ti-plus" type="primary" style="margin-left: auto" size="small">新建临时分工会</el-button>
<el-button @click="updateUnions" :loading="formLoading" :disabled="formLoading" icon="el-icon-refresh" type="primary" size="small">更新工会</el-button>
</table-tool> </table-tool>
<el-table :data="tableData" border size="small" height="calc(100vh - 375px)"> <el-table :data="tableData" border size="small" height="calc(100vh - 375px)">
<el-table-column label="序号" type="index" :index="indexMethod" width="100px"></el-table-column> <el-table-column label="序号" type="index" :index="indexMethod" width="100px"></el-table-column>
@@ -79,6 +80,7 @@ const ACTIVITY_BASIC_BRANCH_UNION_MANAGE = {
mixins: [initTableMixins], mixins: [initTableMixins],
data() { data() {
return { return {
pageDataUrl: "/platform/activity/basic/union/pageData",
dialogFormVisible: false, dialogFormVisible: false,
dialogGlyFormVisible: false, dialogGlyFormVisible: false,
formData: {}, formData: {},
@@ -88,10 +90,25 @@ const ACTIVITY_BASIC_BRANCH_UNION_MANAGE = {
} }
}, },
methods: { methods: {
pageData() { // 同步新增工会和已有工会名称;复用表格查询及父页面刷新事件,保留现有查询和分页逻辑。
this.$axios.post("/platform/activity/basic/union/pageData", this.pageForm).then((res) => { updateUnions() {
this.tableData = res.data.list if (this.formLoading) {
this.pageForm.totalCount = res.data.totalCount return
}
this.$set(this, "formLoading", true)
return this.$axios.post("/platform/activity/basic/union/updateUnions").then((res) => {
if (res.code !== 0) {
this.notifyWarning(res.msg)
return
}
const result = res.data
this.notifySuccess("工会更新完成:新增 " + result.insertCount + " 个,更新名称 " + result.updateCount + " 个。")
this.$emit("refresh", null)
return this.pageData()
}).catch(() => {
this.notifyError("工会更新或刷新失败,请刷新页面后核对结果")
}).finally(() => {
this.$set(this, "formLoading", false)
}) })
}, },
openAdd() { openAdd() {
@@ -9,10 +9,10 @@ layout("/layouts/platform.html"){
<div style="max-height: calc(100vh - 160px); overflow-y: auto"> <div style="max-height: calc(100vh - 160px); overflow-y: auto">
<el-tree <el-tree
:data="treeData" :data="treeData"
:default-expanded-keys="['1']" :default-expanded-keys="pageForm.parentId ? [pageForm.parentId] : []"
:expand-on-click-node="false" :expand-on-click-node="false"
:props="{ :props="{
children: 'child', children: 'children',
label: 'name' label: 'name'
}" }"
@node-click="treeNodeClick" @node-click="treeNodeClick"
@@ -52,6 +52,7 @@ layout("/layouts/platform.html"){
</div> </div>
<div class="search-item"> <div class="search-item">
<el-button @click="doSearch" icon="el-icon-search" type="primary">搜索</el-button> <el-button @click="doSearch" icon="el-icon-search" type="primary">搜索</el-button>
<el-button @click="updateUnits" :loading="formLoading" :disabled="formLoading" icon="el-icon-refresh" type="primary">更新单位</el-button>
</div> </div>
</div> </div>
</el-card> </el-card>
@@ -104,6 +105,36 @@ layout("/layouts/platform.html"){
}, },
components: {}, components: {},
methods: { methods: {
// 点击后同步一次系统单位;已有活动工会归属由后端保留,完成后刷新树和当前单位列表。
updateUnits() {
if (this.formLoading) {
return
}
this.$set(this, "formLoading", true)
this.$axios.post(loc() + "/updateUnits").then((res) => {
if (res.code !== 0) {
this.notifyWarning(res.msg)
return
}
const result = res.data
let message = "单位更新完成:新增 " + result.insertCount + " 个,更新 " + result.updateCount + " 个。"
if (result.pendingUnionCount > 0) {
message += "新增单位中有 " + result.pendingUnionCount + " 个待配置活动工会,请到活动工会的组成单位中维护。"
this.notifyWarning(message)
} else {
this.notifySuccess(message)
}
return this.getTreeData().then((loaded) => {
if (loaded) {
this.doSearch()
}
})
}).catch(() => {
this.notifyError("单位更新或刷新失败,请刷新页面后核对结果")
}).finally(() => {
this.$set(this, "formLoading", false)
})
},
async doDelete(id) { async doDelete(id) {
const confirm = await this.$confirm("确定要删除该单位吗?", "提示", { const confirm = await this.$confirm("确定要删除该单位吗?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
@@ -122,24 +153,42 @@ layout("/layouts/platform.html"){
} }
}, },
treeNodeClick(data, node) { treeNodeClick(data, node) {
this.currentTreeData = data this.$set(this, "currentTreeData", data)
this.currentTreeNode = node this.$set(this, "currentTreeNode", node)
this.pageForm.parentId = data.id this.$set(this.pageForm, "parentId", data.id)
this.doSearch() this.doSearch()
}, },
getTreeData() { getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => { return this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data if (res.code !== 0) {
if (this.treeData) { this.notifyWarning(res.msg)
this.currentTreeData = this.treeData[0] return false
} }
this.$set(this, "treeData", res.data || [])
// 同步后沿用当前选择,首次进入按真实树根查询,不再依赖旧的根单位ID常量。
if (!this.pageForm.parentId && this.treeData.length > 0) {
this.$set(this.pageForm, "parentId", this.treeData[0].id)
}
this.$nextTick(() => {
const tree = this.$refs.treeRef
const node = tree.getNode(this.pageForm.parentId)
this.$set(this, "currentTreeNode", node || null)
this.$set(this, "currentTreeData", node ? node.data : null)
tree.setCurrentKey(this.pageForm.parentId)
})
return true
}) })
}, },
openUnit() {} openUnit() {}
}, },
created() { created() {
this.pageData() this.getTreeData().then((loaded) => {
this.getTreeData() if (loaded) {
this.doSearch()
}
}).catch(() => {
this.notifyError("加载单位失败,请稍后重试")
})
} }
}) })
</script> </script>
@@ -370,12 +370,14 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
</el-form-item> </el-form-item>
</el-col> </el-col>
<!--
<el-col :span="24"> <el-col :span="24">
<el-form-item label="报名表单" prop="formConfig" <el-form-item label="报名表单" prop="formConfig"
v-if="formData.isEnrollSystem&&formData.signUpMethod===1"> v-if="formData.isEnrollSystem&&formData.signUpMethod===1">
<custom-form-field v-model="formData.formConfig"></custom-form-field> <custom-form-field v-model="formData.formConfig"></custom-form-field>
</el-form-item> </el-form-item>
</el-col> </el-col>
-->
<el-col :span="24" v-if="formData.signUpMethod!=null&&formData.isEnrollSystem"> <el-col :span="24" v-if="formData.signUpMethod!=null&&formData.isEnrollSystem">
<el-form-item label="封面图" prop="cover"> <el-form-item label="封面图" prop="cover">
@@ -592,6 +594,7 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
name: [{required: true, message: "必填", trigger: ["blur", "change"]}], name: [{required: true, message: "必填", trigger: ["blur", "change"]}],
type: [{required: true, message: "必填", trigger: ["blur", "change"]}], type: [{required: true, message: "必填", trigger: ["blur", "change"]}],
projectTypeCode: [{required: true, message: "必填", trigger: ["blur", "change"]}], projectTypeCode: [{required: true, message: "必填", trigger: ["blur", "change"]}],
mobile: this.activity_type === 40001 ? [{pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: ["blur", "change"]}] : [],
address: [{required: true, message: "必填", trigger: ["blur", "change"]}], address: [{required: true, message: "必填", trigger: ["blur", "change"]}],
plannedDate: [{required: true, message: "必填", trigger: ["blur", "change"]}], plannedDate: [{required: true, message: "必填", trigger: ["blur", "change"]}],
time: [{required: true, message: "必填", trigger: ["blur", "change"]}], time: [{required: true, message: "必填", trigger: ["blur", "change"]}],
@@ -725,7 +728,15 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
return true return true
}, },
// 保存 // 保存
onSave() { async onSave() {
if (this.activity_type === 40001) {
const mobileValid = await new Promise(resolve => {
this.$refs["addForm"].validateField("mobile", error => resolve(!error))
})
if (!mobileValid) {
return
}
}
if (!this.formData.projectTypeCode) { if (!this.formData.projectTypeCode) {
this.$message.error("请选择活动项目类型") this.$message.error("请选择活动项目类型")
return return
@@ -6,18 +6,19 @@ layout("/layouts/platform.html"){
<template> <template>
<el-card shadow="never"> <el-card shadow="never">
<search @search="doSearch" :is-search-button="false"> <search @search="refreshPrizes" :is-search-button="false">
<search-item label="年度"> <search-item label="年度">
<el-date-picker <el-date-picker
:clearable="false" :clearable="false"
:disabled="tableLoading"
@change="getActivitys" @change="getActivitys"
placeholder="选择年" placeholder="选择年"
style="width: 100%" type="year" style="width: 100%" type="year"
v-model="pageForm.year" value-format="yyyy"> v-model="pageForm.year" value-format="yyyy">
</el-date-picker> </el-date-picker>
</search-item> </search-item>
<search-item label="活动名称" v-if="!pageForm.status"> <search-item label="活动名称" v-if="pageForm.status!=='1'">
<el-select :clearable="false" @change="actChange(pageForm.activityId)" <el-select :clearable="false" :disabled="tableLoading" @change="actChange(pageForm.activityId)"
filterable filterable
placeholder="请选择活动名称" placeholder="请选择活动名称"
style="width: 100%" v-model="pageForm.activityId"> style="width: 100%" v-model="pageForm.activityId">
@@ -30,7 +31,7 @@ layout("/layouts/platform.html"){
</el-select> </el-select>
</search-item> </search-item>
<search-item label="工会名称" v-if="!pageForm.status"> <search-item label="工会名称" v-if="!pageForm.status">
<el-select :clearable="false" @change="unionChange(pageForm.unionId)" <el-select :clearable="false" :disabled="tableLoading" @change="unionChange(pageForm.unionId)"
filterable filterable
placeholder="请选择工会" placeholder="请选择工会"
style="width: 100%" v-model="pageForm.unionId"> style="width: 100%" v-model="pageForm.unionId">
@@ -43,9 +44,9 @@ layout("/layouts/platform.html"){
</el-select> </el-select>
</search-item> </search-item>
<search-item label="性别" v-if="pageForm.status==='1'||!pageForm.status"> <search-item label="性别" v-if="pageForm.status==='1'||!pageForm.status">
<el-select filterable <el-select filterable :disabled="tableLoading"
placeholder="请选择性别" placeholder="请选择性别"
style="width: 100%" v-model="pageForm.sex"> style="width: 100%" v-model="pageForm.sex" @change="sexChange">
<el-option label="男" value="男"></el-option> <el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option> <el-option label="女" value="女"></el-option>
</el-select> </el-select>
@@ -56,7 +57,7 @@ layout("/layouts/platform.html"){
<el-card class="mt10" shadow="never"> <el-card class="mt10" shadow="never">
<table-tool <table-tool
:label="pageForm.status==='2'?('浙江师范大学'+pageForm.year+'年教职工运动会获奖个数汇总表'):'奖品名单'"> :label="pageForm.status==='2'?('中国地质大学(武汉)'+pageForm.year+'年教职工运动会获奖个数汇总表'):'奖品名单'">
<el-tag <el-tag
:effect="pageForm.status===item.code?'dark':'plain'" :effect="pageForm.status===item.code?'dark':'plain'"
:key="item.code" :key="item.code"
@@ -66,17 +67,32 @@ layout("/layouts/platform.html"){
v-for="item in statusOptions"> v-for="item in statusOptions">
{{ item.name }} {{ item.name }}
</el-tag> </el-tag>
<el-button @click="print" icon="el-icon-printer" type="primary" size="small">打 印</el-button> <el-button @click="print" :disabled="tableLoading || queryFailed" icon="el-icon-printer" type="primary" size="small">打 印</el-button>
</table-tool> </table-tool>
<div id=print> <div id=print>
<template v-if="pageForm.activityId!=null"> <!-- 未分配记录同时进入打印内容,避免将部分已确认数量当作最终发放总数。 -->
<div v-if="isSummary!==3" class="mb10">
<p>{{ isSummary===2 ? '当前活动' : '当前工会' }}已确认奖品:{{ confirmedTotal }}份;当前活动待确认成绩:{{ pendingResults.length }}条。</p>
<el-alert v-if="pendingResults.length" type="warning" :closable="false" title="当前数量尚未包含以下待确认成绩,请补充分配后再作为最终发放依据。"></el-alert>
<table v-if="pendingResults.length" class="table table-bordered">
<tr><th>项目</th><th>参赛工会/人员</th><th>名次</th><th>获奖总人数</th><th>操作</th></tr>
<tr v-for="item in pendingResults" :key="item.id">
<td>{{ item.allName }}</td>
<td>{{ item.username || item.participantUnionName }}</td>
<td>第{{ item.ranking }}名</td>
<td>{{ item.totalNumberOfPeople == null ? '待确认' : item.totalNumberOfPeople }}</td>
<td><a :href="resultsInputUrl(item)">补充分配</a></td>
</tr>
</table>
</div>
<template v-if="isSummary!==3">
<table class="table table-bordered" v-if="isSummary==1" v-loading="tableLoading"> <table class="table table-bordered" v-if="isSummary==1" v-loading="tableLoading">
<tr> <tr>
<td class="text-center" colspan="11"> <td class="text-center" colspan="11">
<span v-if="activityList.find(v=>v.id === pageForm.activityId)"> <span v-if="activityList.find(v=>v.id === pageForm.activityId)">
浙江师范大学{{ activityList.find(v=>v.id === pageForm.activityId).name }} 中国地质大学(武汉){{ activityList.find(v=>v.id === pageForm.activityId).name }}
({{unionName}})获奖名单 ({{unionName}})获奖名单
</span> </span>
</td> </td>
@@ -97,10 +113,10 @@ layout("/layouts/platform.html"){
<td>{{k}}</td> <td>{{k}}</td>
<td v-for="i in 8"> <td v-for="i in 8">
<span v-if="o.filter(f=>f.ranking==i && f.numberOfPeople!=null) && o.filter(f=>f.ranking==i && f.numberOfPeople!=null).length>0"> <span v-if="o.filter(f=>f.ranking==i && f.numberOfPeople!=null) && o.filter(f=>f.ranking==i && f.numberOfPeople!=null).length>0">
{{o.filter(f=>f.ranking==i && f.numberOfPeople!=null).map(g=>g.numberOfPeople).reduce((a,b)=>a+b)}} {{o.filter(f=>f.ranking==i && f.numberOfPeople!=null).map(g=>Number(g.numberOfPeople)).reduce((a,b)=>a+b,0)}}
</span> </span>
</td> </td>
<td></td> <td>{{ prizeSourceText(o) }}</td>
</tr> </tr>
<tr v-for="(o,k,index) in tableDataUnionPrize['男子项目']"> <tr v-for="(o,k,index) in tableDataUnionPrize['男子项目']">
<td :rowspan="Object.keys(tableDataUnionPrize['男子项目']).length" <td :rowspan="Object.keys(tableDataUnionPrize['男子项目']).length"
@@ -111,7 +127,7 @@ layout("/layouts/platform.html"){
<td v-for="i in 8"> <td v-for="i in 8">
{{o.filter(f=>f.ranking==i).map(g=>g.username).join('、')}} {{o.filter(f=>f.ranking==i).map(g=>g.username).join('、')}}
</td> </td>
<td></td> <td>{{ prizeSourceText(o) }}</td>
</tr> </tr>
<tr v-for="(o,k,index) in tableDataUnionPrize['女子项目']"> <tr v-for="(o,k,index) in tableDataUnionPrize['女子项目']">
<td :rowspan="Object.keys(tableDataUnionPrize['女子项目']).length" dd <td :rowspan="Object.keys(tableDataUnionPrize['女子项目']).length" dd
@@ -122,7 +138,13 @@ layout("/layouts/platform.html"){
<td v-for="i in 8"> <td v-for="i in 8">
{{o.filter(f=>f.ranking==i).map(g=>g.username).join('、')}} {{o.filter(f=>f.ranking==i).map(g=>g.username).join('、')}}
</td> </td>
<td></td> <td>{{ prizeSourceText(o) }}</td>
</tr>
<tr v-for="(o,k,index) in tableDataUnionPrize['其他']">
<td :rowspan="Object.keys(tableDataUnionPrize['其他']).length" v-if="index===0">其他项目</td>
<td>{{ k }}</td>
<td v-for="i in 8">{{ o.filter(item=>item.ranking===i).map(item=>item.username).join('、') }}</td>
<td>{{ prizeSourceText(o) }}</td>
</tr> </tr>
<tr> <tr>
<td>合计</td> <td>合计</td>
@@ -283,200 +305,161 @@ layout("/layouts/platform.html"){
], ],
tableDataUnionPrize: {}, tableDataUnionPrize: {},
lastSum: {}, lastSum: {},
tableDataPrize: [], tableDataPrize: {},
confirmedTotal: 0,
pendingResults: [],
queryFailed: false,
statusOptions: [{code: "1", name: "分组汇总年度前八"}, {code: "2", name: "奖品个数年度汇总表"}] statusOptions: [{code: "1", name: "分组汇总年度前八"}, {code: "2", name: "奖品个数年度汇总表"}]
} }
}, },
components: {}, components: {},
methods: { methods: {
async statusType(state) { statusType(state) {
this.tableLoading = true if (this.tableLoading) return
if (state === "1") { const next = this.pageForm.status === state ? null : state
const data = await this.summaryYearPrizes() if (next === '1' && !this.pageForm.sex) {
if (!data) { this.$message.warning('请选择性别')
this.tableLoading = false
return return
} }
} else if (state === "2") { this.$set(this.pageForm, 'status', next)
this.pageForm.sex = null this.$set(this, 'isSummary', next === '1' ? 3 : (next === '2' ? 2 : 1))
await this.summary() return this.refreshPrizes()
}
if (this.pageForm.status === state) {
this.pageForm.status = null
this.pageForm.sex = null
this.pageForm.activityId = this.activityList[0].id
this.pageForm.unionId = this.unionList[0].id
this.unionChange(this.unionList[0].id)
} else {
this.pageForm.status = state
}
this.tableLoading = false
}, },
getSummaryByYear(o, i, type) { // 三种展示分别刷新,沿用mixins的分页能力,不另写公共表格分页方法。
if (type === "集体项目") { refreshPrizes() {
const datas = o.filter(o => o.ranking === i).map(o => o.unionname) if (this.isSummary === 3) return this.summaryYearPrizes()
if (datas) { if (this.isSummary === 2) return this.summary()
return datas.join(",") return this.getPrizes()
} else {
return ""
}
} else {
//甲组里面有可能有团体项目,所以说如果这个没有username就代表是团体
return o.filter(f => f.ranking === i).map(g => g.username ? g.username : g.unionname).join('、')
}
}, },
async summaryYearPrizes() { sexChange() {
try { if (this.isSummary === 3) return this.summaryYearPrizes()
},
getSummaryByYear(rows, ranking, type) {
return rows.filter(item => item.ranking === ranking).map(item => type === '集体项目' ? item.unionname : (item.username || item.unionname)).join('、')
},
prizeSourceText(rows) {
return [...new Set(rows.map(item => item.participantUnionName).filter(Boolean))].join('、')
},
resultsInputUrl(item) {
return '/platform/activity/results/input?activityId=' + encodeURIComponent(item.activityId)
+ '&eventId=' + encodeURIComponent(item.eventId) + '&year=' + encodeURIComponent(this.pageForm.year)
},
acceptPrizeStatistics(data) {
this.$set(this, 'confirmedTotal', Number(data.confirmedTotal) || 0)
this.$set(this, 'pendingResults', data.pendingResults || [])
},
clearPrizeStatistics() {
this.$set(this, 'confirmedTotal', 0)
this.$set(this, 'pendingResults', [])
this.$set(this, 'lastSum', {})
this.$set(this, 'tableDataUnionPrize', {})
this.$set(this, 'tableData', [])
},
summaryYearPrizes() {
if (!this.pageForm.sex) { if (!this.pageForm.sex) {
this.$message.warning("请选择性别") this.$message.warning('请选择性别')
return false return Promise.resolve()
} }
this.$set(this.pageForm, "unionId", null) this.$set(this, 'tableLoading', true)
this.$set(this.pageForm, "activityId", null) this.$set(this, 'queryFailed', false)
this.isSummary = 3 this.$set(this, 'tableDataPrize', {})
const {data} = await this.$axios.post(loc() + "/getSummaryYearPrizes", this.pageForm) return this.$axios.post(loc() + '/getSummaryYearPrizes', this.pageForm).then((resp) => {
this.tableDataPrize = data if (resp.code !== 0) throw new Error(resp.msg || '年度名次查询失败')
return true this.$set(this, 'tableDataPrize', resp.data)
} catch (e) { }).catch((error) => {
this.$message.error(e.msg) this.$set(this, 'queryFailed', true)
return false this.$message.error(error.message || error.msg || '年度名次查询失败')
} }).finally(() => {
this.$set(this, 'tableLoading', false)
})
}, },
getTableTdContent(d, i) { summary() {
return d.filter(v => { this.clearPrizeStatistics()
if (v.isteampersonal === 1) { this.$set(this, 'tableLoading', true)
if (v.ranking == i) { this.$set(this, 'queryFailed', false)
console.log(v, i) return this.$axios.post(loc() + '/summary', {activityId: this.pageForm.activityId}).then((resp) => {
return v.username if (resp.code !== 0) throw new Error(resp.msg || '奖品汇总查询失败')
} this.$set(this, 'tableData', resp.data.score || [])
} else { this.acceptPrizeStatistics(resp.data)
if (v.ranking == i) { }).catch((error) => {
return v.numberofpeople this.$set(this, 'queryFailed', true)
} this.$message.error(error.message || error.msg || '奖品汇总查询失败')
} }).finally(() => {
}).map(v => { this.$set(this, 'tableLoading', false)
return v.isteampersonal === 1 ? v.username : v.numberofpeople })
}).toString()
},
async summary() {
try {
if (this.activityList.length === 0) {
return
}
if (!this.pageForm.activityId) {
this.pageForm.activityId = this.activityList[0].id
}
this.$set(this.pageForm, "unionId", null)
this.isSummary = 2
this.tableData = []
const {data} = await this.$axios.post(loc() + "/summary", this.pageForm)
this.$set(this, "tableData", data.score)
this.unionName = "汇总"
} catch (e) {
this.$message.error(e.msg)
}
}, },
print() { print() {
let subOutputRankPrint = document.getElementById('print'); if (this.tableLoading || this.queryFailed) return
let newContent = subOutputRankPrint.innerHTML; let newContent = document.getElementById('print').innerHTML
let oldContent = document.body.innerHTML; let oldContent = document.body.innerHTML
document.body.innerHTML = newContent; document.body.innerHTML = newContent
window.print(); window.print()
window.location.reload(); window.location.reload()
document.body.innerHTML = oldContent; document.body.innerHTML = oldContent
return false; return false
}, },
actChange(activityId) { actChange(activityId) {
this.tableColumns = [] this.$set(this.pageForm, 'activityId', activityId)
this.tableData = [] return this.refreshPrizes()
this.pageForm.activityId = ""
const activityData = this.activityList.find(v => v.id == activityId)
this.pageForm.activityId = activityData.id
this.getPrizes()
}, },
unionChange(unionId) { unionChange(unionId) {
this.tableColumns = [] this.$set(this.pageForm, 'unionId', unionId)
this.tableData = [] const union = this.unionList.find(item => item.id === unionId)
this.pageForm.unionId = "" this.$set(this, 'unionName', union ? union.name : '')
this.isSummary = 1 return this.getPrizes()
const unionData = this.unionList.find(v => v.id == unionId)
this.pageForm.unionId = unionData.id
this.unionName = unionData.name
this.getPrizes()
}, },
async getActivitys() { getActivitys() {
this.tableData = [] this.clearPrizeStatistics()
const {data} = await this.$axios.post("/platform/activity/score/statistics/getActivitys", {year: this.pageForm.year}) this.$set(this, 'tableDataPrize', {})
this.pageForm.activityId = "" this.$set(this, 'tableLoading', true)
this.activityList = data this.$set(this, 'queryFailed', false)
if (data && data.length > 0) { return this.$axios.post('/platform/activity/score/statistics/getActivitys', {year: this.pageForm.year}).then((resp) => {
this.pageForm.activityId = data[0].id if (resp.code !== 0) throw new Error(resp.msg || '活动查询失败')
const activities = resp.data || []
this.$set(this, 'activityList', activities)
if (!activities.some(item => item.id === this.pageForm.activityId)) {
this.$set(this.pageForm, 'activityId', activities.length ? activities[0].id : '')
} }
await this.getPrizes() return this.refreshPrizes()
}).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '活动查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
}, },
async getPrizes() { getPrizes() {
try { this.clearPrizeStatistics()
this.tableLoading = true this.$set(this, 'tableLoading', true)
const {data} = await this.$axios.post(loc() + "/getPrizes", this.pageForm) this.$set(this, 'queryFailed', false)
this.tableDataUnionPrize = data return this.$axios.post(loc() + '/getPrizes', this.pageForm).then((resp) => {
const lastSum = {} if (resp.code !== 0) throw new Error(resp.msg || '奖品名单查询失败')
for (let i = 1; i <= 8; i++) { this.$set(this, 'tableDataUnionPrize', resp.data)
let sum = 0 // 名单与汇总共用后端人数统计,前端不再用积分或整队人数另算一套合计。
if (!$.isEmptyObject(data['男子项目'])) { this.$set(this, 'lastSum', resp.data.lastSum || {})
let xm = [] this.acceptPrizeStatistics(resp.data)
for (let key in data['男子项目']) { }).catch((error) => {
xm = xm.concat(data['男子项目'][key]) this.$set(this, 'queryFailed', true)
} this.$message.error(error.message || error.msg || '奖品名单查询失败')
sum += xm.filter(v => v.ranking === i).length }).finally(() => {
} this.$set(this, 'tableLoading', false)
if (!$.isEmptyObject(data['女子项目'])) { })
let xm = []
for (let key in data['女子项目']) {
xm = xm.concat(data['女子项目'][key])
}
sum += xm.filter(v => v.ranking === i).length
}
if (!$.isEmptyObject(data['团体项目'])) {
let xm = []
for (let key in data['团体项目']) {
xm = xm.concat(data['团体项目'][key])
}
const teams = xm.filter(v => v.ranking === i).map(v => Number(v.numberOfPeople))
if (teams && teams.length > 0) {
sum += teams.reduce((a, b) => a + b)
}
}
lastSum[i] = sum
}
lastSum[9] = Object.values(lastSum).reduce((a, b) => a + b)
this.lastSum = lastSum
} catch (e) {
this.$message.error(e.msg)
} finally {
this.tableLoading = false
}
}, },
}, },
async created() { created() {
await this.getActivitys() this.$set(this, 'tableLoading', true)
this.unionList = await this.$businessTool.listUnion() this.$businessTool.listUnion().then((unions) => {
if (this.unionList.length > 0) { this.$set(this, 'unionList', unions || [])
this.pageForm.unionId = this.unionList[0].id this.$set(this.pageForm, 'unionId', unions.length ? unions[0].id : '')
this.unionName = this.unionList[0].unionname this.$set(this, 'unionName', unions.length ? unions[0].name : '')
} return this.getActivitys()
await this.getPrizes() }).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '工会查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
} }
}) })
</script> </script>
File diff suppressed because it is too large Load Diff
@@ -73,7 +73,8 @@ layout("/layouts/platform.html"){
</el-select> </el-select>
</search-item> </search-item>
<search-item> <search-item>
<el-button @click="doExcelCj" icon="el-icon-printer" type="primary">导出成绩excel <el-button @click="doExcelCj" icon="el-icon-printer" type="primary"
:disabled="!pageForm.activityId">导出成绩excel
</el-button> </el-button>
</search-item> </search-item>
</search> </search>
@@ -301,10 +302,18 @@ layout("/layouts/platform.html"){
}, },
doExcelCj() { doExcelCj() {
const {activityId, unionname} = this.pageForm const {activityId, unionname} = this.pageForm
// 年度统计视图下 activityId 已被清空,此处导出会查不到数据
if (!activityId) {
this.$message.warning("当前为年度统计视图,无法导出单个活动的成绩明细,请先选择「运动会成绩统计」中的任一维度")
return
}
let unionId = '' let unionId = ''
if (unionname) { if (unionname) {
const unionlist = clone(this.unionList) const unionlist = clone(this.unionList)
unionId = unionlist.find(v => v.unionname === unionname).id const target = unionlist.find(v => v.name === unionname)
if (target) {
unionId = target.id
}
} }
this.$downLoad(loc() + "/doExcelCj", {activityId: activityId, unionId: unionId}) this.$downLoad(loc() + "/doExcelCj", {activityId: activityId, unionId: unionId})
@@ -585,11 +585,13 @@ const basicForm = {
return return
} }
const courseValid = this.formData.courseList.some((v, i) => { const courseValid = this.formData.courseList.some((v, i) => {
// 新活动通过多选关系设置负责人;未重新设置的旧活动仍允许保留历史联系人。 const missingFields = []
const hasResponsible = Array.isArray(v.responsibleList) if (!v.courseName) missingFields.push(this.trainType + "名称")
? v.responsibleList.length > 0 : !!v.courseInstructor if (!v.coursePeopleNumber) missingFields.push(this.trainType + "人数(不能为空或为0)")
const basicValid = v.courseName && v.coursePeopleNumber && v.courseLocation && hasResponsible && v.courseType if (!v.courseLocation) missingFields.push(this.trainType + "地点")
if (!v.courseType) missingFields.push("类型")
const timeValid = const timeValid =
Array.isArray(v.courseTimeList) &&
v.courseTimeList.length > 0 && v.courseTimeList.length > 0 &&
v.courseTimeList.every((x) => { v.courseTimeList.every((x) => {
return x.courseStartTime && x.courseEndTime && x.courseStartTime < x.courseEndTime return x.courseStartTime && x.courseEndTime && x.courseStartTime < x.courseEndTime
@@ -598,8 +600,8 @@ const basicForm = {
this.$message.warning("第" + (i + 1) + "行时间填写有误,请核查") this.$message.warning("第" + (i + 1) + "行时间填写有误,请核查")
return true return true
} }
if (!basicValid) { if (missingFields.length > 0) {
this.$message.warning("第" + (i + 1) + "行信息填写有误,请核查") this.$message.warning("第" + (i + 1) + "行请补充:" + missingFields.join("、"))
return true return true
} }
// 联系方式允许手机号或办公电话,逐人校验,不能只检查兼容展示的第一位。 // 联系方式允许手机号或办公电话,逐人校验,不能只检查兼容展示的第一位。
@@ -0,0 +1,126 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search :is-search-button="false" @search="doSearch">
<search-item label="社团名称:">
<el-select v-model="pageForm.clubId" placeholder="请选择社团" clearable filterable>
<el-option v-for="item in clubList" :key="item.id" :label="item.clubName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号:">
<user-select
v-model="pageForm.userId"
placeholder="请输入姓名或工号选择人员"
api_input_key_name="query"
:option_label_func="(item) => item.username + '(' + item.loginname + ')'"
></user-select>
</search-item>
<search-item label="所属单位:">
<el-select v-model="pageForm.unitName" placeholder="请选择所属单位" clearable filterable>
<el-option v-for="item in unitList" :key="item.id" :label="item.name" :value="item.name"></el-option>
</el-select>
</search-item>
<search-item label="人员状态:">
<el-select v-model="pageForm.userState" placeholder="请选择人员状态" clearable>
<el-option label="在职" value="在职"></el-option>
<el-option label="在岗" value="在岗"></el-option>
<el-option label="退休" value="退休"></el-option>
<el-option label="不在岗" value="不在岗"></el-option>
</el-select>
</search-item>
<search-item label="性别:">
<el-select v-model="pageForm.sex" placeholder="请选择性别" clearable>
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="成员统计">
<span>成员记录数:{{ pageForm.totalCount }}(同一人员加入多个社团分别统计)</span>
</table-tool>
<el-table :data="tableData">
<el-table-column label="社团名称" prop="clubName" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="sex" show-overflow-tooltip></el-table-column>
<el-table-column label="联系方式" prop="mobile" show-overflow-tooltip></el-table-column>
<el-table-column label="所属单位" prop="unitName" show-overflow-tooltip></el-table-column>
<el-table-column label="在职状态" prop="userState" show-overflow-tooltip></el-table-column>
<el-table-column label="职务" prop="roleName" show-overflow-tooltip></el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
clubList: [],
unitList: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
clubId: "",
userId: "",
unitName: "",
userState: "",
sex: ""
}
}
},
methods: {
resetSearch() {
this.pageForm.clubId = ""
this.pageForm.userId = ""
this.pageForm.unitName = ""
this.pageForm.userState = ""
this.pageForm.sex = ""
this.doSearch()
},
async getUnit() {
const resp = await this.$axios.post("/open/common/unitOptions")
if (resp.code === 0) {
this.unitList = resp.data
} else {
this.$message.warning(resp.msg)
}
},
async pageData() {
const resp = await this.$axios.post("/platform/club/statistics/member/pageData", this.pageForm)
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
} else {
this.$message.warning(resp.msg)
}
},
async getClub() {
const resp = await this.$axios.post("/platform/club/statistics/member/clubOptions")
if (resp.code === 0) {
this.clubList = resp.data
} else {
this.$message.warning(resp.msg)
}
}
},
async created() {
await this.getClub()
await this.getUnit()
await this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -818,6 +818,10 @@ layout("/layouts/platform.html"){
paymentWay: [{required: true, message: "请选择支付方式", trigger: ["change", "blur"]}], paymentWay: [{required: true, message: "请选择支付方式", trigger: ["change", "blur"]}],
reimburseProject: [{required: true, message: "请选择报销项目", trigger: ["change", "blur"]}], reimburseProject: [{required: true, message: "请选择报销项目", trigger: ["change", "blur"]}],
reimburseFundSource: [{required: true, message: "请选择经费来源", trigger: ["change", "blur"]}], reimburseFundSource: [{required: true, message: "请选择经费来源", trigger: ["change", "blur"]}],
// 报销事由仅在慰问项目显示,提交和重新提交时拒绝空值及纯空白。
paymentNotes: [{required: true, whitespace: true, message: "请填写报销事由", trigger: ["change", "blur"]}],
// 所属协会仅在协会经费显示,候选范围由后端当前会员关系确定。
clubId: [{required: true, message: "请选择所属协会", trigger: ["change", "blur"]}],
bankUserName: [{ bankUserName: [{
required: true, required: true,
whitespace: true, whitespace: true,
@@ -1890,10 +1894,14 @@ layout("/layouts/platform.html"){
this.normalizeFundBalance() this.normalizeFundBalance()
resolve(true) resolve(true)
} else { } else {
// 支付信息已移至发票信息页,支付明细校验失败时跳转到对应页签。 // 新增必填项属于基本信息;同时存在支付错误时优先展示这两个字段的校验提示。
const basicInfoInvalid = Object.keys(invalidFields || {})
.some((field) => ["paymentNotes", "clubId"].includes(field))
const paymentDetailInvalid = Object.keys(invalidFields || {}) const paymentDetailInvalid = Object.keys(invalidFields || {})
.some((field) => field.indexOf("paymentDetails.") === 0) .some((field) => field.indexOf("paymentDetails.") === 0)
if (paymentDetailInvalid) { if (basicInfoInvalid) {
this.$set(this, "activeTabName", "basic")
} else if (paymentDetailInvalid) {
this.$set(this, "activeTabName", "invoice") this.$set(this, "activeTabName", "invoice")
} }
this.$message.warning("请完善必填信息") this.$message.warning("请完善必填信息")
@@ -170,6 +170,7 @@ layout("/layouts/platform.html"){
<el-table-column align="center" header-align="center" label="更新时间" <el-table-column align="center" header-align="center" label="更新时间"
min-width="145px" prop="changeTime" min-width="145px" prop="changeTime"
show-overflow-tooltip sortable> show-overflow-tooltip sortable>
<template scope="{row}">{{row.changeTime || '--'}}</template>
</el-table-column> </el-table-column>
<el-table-column align="center" header-align="center" label="异动类型" <el-table-column align="center" header-align="center" label="异动类型"
prop="changeType" show-overflow-tooltip sortable> prop="changeType" show-overflow-tooltip sortable>
@@ -649,17 +650,16 @@ layout("/layouts/platform.html"){
} }
}, },
getChangeTypes(changeTypes){ getChangeTypes(changeTypes){
if (!changeTypes) return null if (!changeTypes) return '--'
const changeTypesList = JSON.parse(changeTypes) const changeTypesList = Array.isArray(changeTypes) ? changeTypes : JSON.parse(changeTypes)
if (changeTypesList) { if (!Array.isArray(changeTypesList) || changeTypesList.length === 0) return '--'
if (!changeTypesList || changeTypesList.length === 0 || !this.changeTypeData || this.changeTypeData.length === 0) return null // 历史编码可能已不在当前枚举中,保留原编码,避免整张表格渲染中断。
return changeTypesList return changeTypesList
.map((v) => { .map((v) => {
return this.changeTypeData.find((item) => item.code === v).changeTypeName const changeType = (this.changeTypeData || []).find((item) => item.code === v)
return changeType ? changeType.changeTypeName : (v || '未知异动类型')
}) })
.join(",") .join(",")
}
return null
}, },
changeDateRangeChange(val) { changeDateRangeChange(val) {
if (val && val.length > 0) { if (val && val.length > 0) {