Compare commits

11 Commits
62 changed files with 2468 additions and 827 deletions
@@ -1,34 +1,29 @@
package com.budwk.app.sys.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.constant.RoleConstant;
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_menu;
import com.budwk.app.sys.services.SysHomeActivityService;
import com.budwk.app.sys.services.SysHomeNewsService;
import com.budwk.app.sys.services.SysMenuService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
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.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.annotation.At;
@@ -52,7 +47,7 @@ public class SysHomeController {
@Inject
private SysUserService sysUserService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
private SysHomeActivityService sysHomeActivityService;
@Inject
private SysHomeNewsService sysHomeNewsService;
@@ -201,71 +196,16 @@ public class SysHomeController {
}
/**
* includeEnded可选:true供PC首页展示已结束活动,缺省false保持既有调用行为。
* 返回Result,data为当前登录用户可见的活动实体列表,含名称、封面、链接及起止时间,不含内部SQL和类路径。
*/
@At
@SaCheckLogin
@ApiOperation("首页推送活动")
@Ok("json")
public Result listHomeActivity() {
FieldFilter fieldFilter = FieldFilter.locked(Sys_home_activity.class, "classPath");
List<Sys_home_activity> list = Daos.ext(dao, fieldFilter).query(Sys_home_activity.class, Cnd.where("enable", "=", 1).desc("top"));
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);
public Result listHomeActivity(@Param(value = "includeEnded", df = "false") boolean includeEnded) {
return Result.success(sysHomeActivityService.listHomeActivity(SecurityUtil.getUserId(), includeEnded));
}
@At
@@ -8,7 +8,10 @@ import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.*;
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.web.commons.auth.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
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.IocBean;
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.At;
import org.nutz.mvc.annotation.Ok;
@@ -26,9 +32,15 @@ import org.nutz.mvc.upload.UploadAdaptor;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@@ -45,6 +57,10 @@ public class SysUnionCadreImportController {
private SysDictService sysDictService;
@Inject
private SysUnionController sysUnionController;
@Inject
private SysRoleService sysRoleService;
@Inject
private SysUserService sysUserService;
@SuppressWarnings("unchecked")
private List<NutMap> roleOptions() {
@@ -72,7 +88,7 @@ public class SysUnionCadreImportController {
append(notes, "届次编码(选填)", "可留空;填写时使用届次对照表中的编码,与单个添加一致。");
append(notes, "所属单位编码(书记必填)", "仅二级党委书记填写,多个单位用英文逗号分隔,必须为当前分工会的二级单位;其他角色留空。");
append(notes, "重复记录", "普通干部按人员和角色跳过;二级党委书记按人员、角色和工会跳过。禁用的已有关系也跳过,不覆盖。");
append(notes, "保存方式", "系统管理员直接授权,写入干部及角色记录并清理权限缓存,不发起审批。逐行保存,失败行不影响其他行。");
append(notes, "保存方式", "系统管理员直接授权,批量校验并保存干部及角色记录,统一清理权限缓存,不发起审批。校验失败行不写入;保存异常时拆分定位失败行,其他有效行继续导入。");
append(notes, "已有干部记录", "已有在任或待审核干部记录但未分配角色时跳过,避免重复创建或绕过已有审批。");
Sheet unions = createSheet(workbook, "工会对照", "工会编码", "工会名称");
List<Sys_union> unionList = dao.query(Sys_union.class, Cnd.NEW().asc("unionCode"));
@@ -145,101 +161,271 @@ public class SysUnionCadreImportController {
if (rows.isEmpty()) {
return Result.error("文件中没有需要导入的数据");
}
List<String> roleCodes = roleOptions().stream().map(item -> item.getString("code")).toList();
List<String> terms = sysDictService.getSubListByCode("TEACHER_CONGRESS_J").stream()
.map(Sys_dict::getCode).toList();
int success = 0;
int skipped = 0;
Set<String> roleCodes = sysDictService.getSubListByCode("BRANCH_UNION_ROLES").stream()
.map(Sys_dict::getCode).collect(Collectors.toSet());
roleCodes.add(RoleConstant.UNIT_PARTY_SECRETARY.name());
Set<String> terms = sysDictService.getSubListByCode("TEACHER_CONGRESS_J").stream()
.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) {
try {
importRow(row, roleCodes, terms);
} catch (Exception e) {
log.error("导入分工会干部失败,行号:{}", row.getInt("rowNumber"), e);
outcome(row, "失败", "保存失败,请检查该行数据或联系管理员");
}
if ("成功".equals(row.getString("status"))) {
success++;
} else if ("跳过".equals(row.getString("status"))) {
skipped++;
ImportEntry entry = prepareRow(row, roleCodes, terms, unions, users, roles, units,
existingRoles, existingCadres, fileRoles, operator);
if (entry != null) {
entries.add(entry);
}
}
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)
.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 loginname = row.getString("loginname");
String roleCode = row.getString("roleCode");
String j = row.getString("j");
if (StrUtil.isBlank(unionCode) || StrUtil.isBlank(loginname) || StrUtil.isBlank(roleCode)) {
outcome(row, "失败", "工会编码、工号、角色编码不能为空");
return;
List<String> errors = new ArrayList<>();
Sys_union union = unique(unions, unionCode, "工会编码", errors);
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));
List<View_user> users = dao.query(View_user.class, Cnd.where("loginname", "=", loginname));
List<Sys_role> roles = dao.query(Sys_role.class, Cnd.where("code", "=", roleCode));
if (unions.size() != 1 || users.size() != 1 || roles.size() != 1 || !roleCodes.contains(roleCode)) {
outcome(row, "失败", "工会、工号或分工会角色编码不存在或不唯一,请按模板对照表填写");
return;
if (!errors.isEmpty()) {
outcome(row, "失败", String.join(";", errors));
return null;
}
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);
Cnd existing = Cnd.where("userId", "=", user.getId()).and("roleId", "=", role.getId());
if (secretary) {
existing.and("unionId", "=", union.getId());
}
if (dao.count(Sys_user_role.class, existing) > 0) {
outcome(row, "跳过", "已分配该角色,保留原有记录(含禁用关系)");
return;
List<String> key = roleKey(user.getId(), role.getId(), union.getId(), secretary);
if (existingRoles.contains(key)) {
outcome(row, "跳过", "工号【" + loginname + "】已分配角色【" + roleCode + "】"
+ (secretary ? "(工会编码【" + unionCode + "】)" : "") + ",保留原有记录(含禁用关系)");
return null;
}
if (!union.getId().equals(user.getUnionId())) {
outcome(row, "失败", "所选人员不属于该分工会");
return;
errors.add("工号【" + loginname + "】所属工会为【" + StrUtil.blankToDefault(user.getUnionName(), "未分配工会")
+ "】,与填写的工会【" + union.getName() + "(" + unionCode + ")】不一致");
}
if (StrUtil.isNotBlank(j) && !terms.contains(j)) {
outcome(row, "失败", "届次编码不存在,请参照届次对照表或留空");
return;
errors.add("届次编码【" + j + "】不存在,请参照届次对照表或留空");
}
if (dao.count(Sys_union_cadre.class, Cnd.where("userId", "=", user.getId())
.and("roleCode", "=", roleCode).and("unionId", "=", union.getId()).and("isServing", "=", true)) > 0) {
outcome(row, "跳过", "已有在任或待审核干部记录,请通过原记录处理");
return;
if (existingCadres.contains(Arrays.asList(user.getId(), roleCode, union.getId()))) {
outcome(row, "跳过", "工号【" + loginname + "】在工会【" + unionCode + "】已有角色【" + roleCode
+ "】的在任或待审核干部记录,请通过原记录处理");
return null;
}
List<String> unitIds = new ArrayList<>();
String unitCodes = row.getString("unitCodes");
if (secretary) {
List<String> codes = Arrays.stream(unitCodes.split("[,,]"))
.map(String::trim).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());
List<String> codes = splitUnitCodes(unitCodes);
if (codes.isEmpty()) {
outcome(row, "失败", "二级党委书记必须填写所属单位编码");
return;
errors.add("角色【二级党委书记】必须填写所属单位编码,请参照所属单位对照表");
}
for (String code : codes) {
List<Sys_unit> units = dao.query(Sys_unit.class, Cnd.where("unitcode", "=", code)
.and("unionId", "=", union.getId()).and("unitLevel", "=", 2));
if (units.size() != 1) {
outcome(row, "失败", "所属单位编码不存在、不唯一或不是该分工会的二级单位:" + code);
return;
List<Sys_unit> matches = units.getOrDefault(code, List.of());
List<Sys_unit> valid = matches.stream().filter(unit -> union.getId().equals(unit.getUnionId())
&& Integer.valueOf(2).equals(unit.getUnitLevel())).toList();
if (matches.isEmpty()) {
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)) {
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;
}
// 经由注入的原控制器代理调用,保留单行事务、直接授权及缓存清理逻辑。
Result result = secretary
? sysUnionController.insertBranchUnionPartySecretaryRole(user.getId(), union.getId(), unitIds.toArray(new String[0]), j)
: sysUnionController.insertBranchUnionUserRole(user.getId(), roleCode, union.getId(), j);
if (result.isSuccess()) {
outcome(row, "成功", "添加成功");
} else if ("请勿重复添加".equals(result.getMsg()) || "该人员已设置二级党委书记,请先删除后再重新设置".equals(result.getMsg())) {
outcome(row, "跳过", result.getMsg());
} else {
outcome(row, "失败", result.getMsg());
try {
// 两张表各批量写入一次,同一事务保证干部记录和角色授权同时生效。
Trans.exec((Atom) () -> {
dao.fastInsert(entries.stream().map(entry -> entry.cadre).toList());
dao.fastInsert(entries.stream().flatMap(entry -> entry.relations.stream()).toList());
});
entries.forEach(entry -> outcome(entry.row, "成功", "添加成功"));
} catch (Exception e) {
if (entries.size() > 1) {
// 仅保存异常时拆分批次定位坏行,正常导入不产生逐行写入。
int middle = entries.size() / 2;
saveBatch(entries.subList(0, middle));
saveBatch(entries.subList(middle, entries.size()));
} else {
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.SysUnitService;
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.web.commons.auth.utils.AuthUtil;
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())) {
Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId());
cnd.and("parentId", "=", "").or("parentId", "is", null);
} else {
cnd.and("parentId", "=", pid).and("id", "<>", pid);
cnd.and("parentId", "=", pid);
}
cnd.asc("location").asc("path");
list = sysUnitService.query(cnd);
@@ -228,9 +227,9 @@ public class SysUnitController {
} else {
Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId());
cnd.and("parentId", "=", "").or("parentId", "is", null);
} else {
cnd.and("parentId", "=", pid).and("id", "<>", pid);
cnd.and("parentId", "=", pid);
}
cnd.asc("location").asc("path");
list = sysUnitService.query(cnd);
@@ -259,9 +258,9 @@ public class SysUnitController {
if (StpUtil.hasRole("SYSADMIN")) {
Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId());
cnd.and("parentId", "=", "").or("parentId", "is", null);
} else {
cnd.and("parentId", "=", pid).and("id", "<>", pid);
cnd.and("parentId", "=", pid);
}
cnd.asc("location").asc("path");
list = sysUnitService.query(cnd);
@@ -272,9 +271,9 @@ public class SysUnitController {
} else {
Cnd cnd = Cnd.NEW();
if (Strings.isBlank(pid)) {
cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId());
cnd.and("parentId", "=", "").or("parentId", "is", null);
} else {
cnd.and("parentId", "=", pid).and("id", "<>", pid);
cnd.and("parentId", "=", pid);
}
cnd.asc("location").asc("path");
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")
@Ok("json")
@SaCheckLogin
public Object tree(@Param("pid") String pid, HttpServletRequest req) {
try {
String rootId = StrUtil.blankToDefault(pid, "0");
String virtualRootId = "root";
Cnd cnd = Cnd.NEW();
cnd.asc("unitcode");
List<Sys_unit> list = sysUnitService.query(cnd);
String rootId = StrUtil.isBlank(pid) ? DataCenterUtil.findRootUnit(list).getId() : pid;
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
for (int i = 0; i < list.size(); i++) {
Sys_unit unit = list.get(i);
/*
* 动态查询根挂到虚拟根下,兼容根节点父级为空或指向自身的数据。
* 单位根节点存在 id 与 parentId 都为 0 的自引用数据。
* 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。
*/
String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId();
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i)
.setExtra(
Map.of(
// 历史手工新建单位可能未设置类型,展示时按其他类处理,避免空值使整棵树加载失败。
"unitTypeCode", unit.getUnitTypeCode() == null ? 0 : unit.getUnitTypeCode()
)
));
@@ -324,23 +333,37 @@ public class SysUnitController {
List<Tree<String>> treeList = TreeUtil.build(nodeList, virtualRootId);
return Result.success(treeList);
} catch (Exception e) {
log.error("加载单位树失败", e);
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
@Ok("json")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("sys.manager.unit.add")
@SLog(tag = "新建单位", msg = "单位名称:${args[0].name}")
public Object addDo(@Param("..") Sys_unit unit, @Param("parentId") String parentId, HttpServletRequest req) {
try {
if ("root".equals(parentId)) {
parentId = "";
if (Strings.isBlank(unit.getName()) || Strings.isBlank(unit.getUnitcode()) || Strings.isBlank(parentId) || "root".equals(parentId)) {
return Result.error("单位名称、单位编码和上级单位不能为空");
}
unit.setCreatedBy(SecurityUtil.getUserId());
sysUnitService.save(unit, parentId);
return Result.success();
} catch (BaseException e) {
return Result.error(e.getMessage());
} catch (Exception e) {
log.error("新建单位失败", e);
return Result.error();
}
}
@@ -3,5 +3,17 @@ package com.budwk.app.sys.services;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_home_activity;
import java.util.List;
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;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
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.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.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"})
public class SysHomeActivityServiceImpl extends BaseServiceImpl<Sys_home_activity> implements SysHomeActivityService {
@Inject
private ActivityBasicScopeService activityBasicScopeService;
public SysHomeActivityServiceImpl(Dao 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) {
parentId.setChild(child(parentId.getId()));
list.add(parentId);
return Result.success().addData(list);
}
return Result.success();
return Result.success(list);
}
private List<ActivityBasicSettings> child(String parentId) {
@@ -73,6 +72,9 @@ public class ActivityBasicSettingsController {
Cnd cnd = Cnd.NEW();
if (StrUtil.isBlank(parentId)) {
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());
} else {
cnd.and("parentId", "=", parentId);
@@ -116,7 +118,13 @@ public class ActivityBasicSettingsController {
@At
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));
if (fetch == null) {
return Result.success(new ArrayList<ActivityBasicSettings>());
}
List<ActivityBasicSettings> query = dao.query(ActivityBasicSettings.class, Cnd.where("parentId", "=", fetch.getId()).asc("code"));
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);
courseList.forEach(c -> {
dao().fetchLinks(c, "^(courseTimeList)$", Cnd.NEW().asc("courseStartTime"));
if (c.getCourseTimeList() == null) {
c.setCourseTimeList(new ArrayList<>());
}
if (StrUtil.isNotBlank(c.getCourseType())) {
dao().fetchLinks(c, "^(courseTimeList)$", Cnd.NEW().asc("courseStartTime"));
int courseCount = statisticsService.queryCourseCount(c.getId(), c.getCourseType());
c.setHasRegisterNum(courseCount);
int courseWaitCount = statisticsService.queryCourseWaitCount(c.getId(), c.getCourseType());
@@ -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.unitname as unitName,
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
club_user c
LEFT JOIN `vw_user` u ON u.id = c.userId
@@ -98,14 +98,14 @@ public class TherapyRecuperationModule {
Sys_config setting = new Sys_config();
setting.setConfigKey("staffTherapyRecuperationVersion");
setting.setConfigValue("zjnu");
setting.setNote("普惠疗休养V3独立子模块报名规则版本");
setting.setNote("普惠疗休养独立子模块报名规则版本");
dao.insert(setting);
}
private void seedConfig() {
if (dao.count(TheRapyRecuperationConfig.class) != 0) return;
TheRapyRecuperationConfig config = new TheRapyRecuperationConfig();
config.setConfigName("普惠疗休养V3配置");
config.setConfigName("普惠疗休养配置");
config.setIsSnLine(false);
config.setIsSwLine(false);
config.setTravelAudit(false);
@@ -128,7 +128,7 @@ public class TherapyRecuperationModule {
module.setFaIcon("fa fa-heart");
dao.insert(module);
}
Sys_menu root = addMenu(module.getId(), "", "普惠疗休养V3", ROOT, "inclusiveTherapyV3", "pc");
Sys_menu root = addMenu(module.getId(), "", "普惠疗休养", ROOT, "inclusiveTherapyV3", "pc");
List<Sys_role> roles = dao.query(Sys_role.class, Cnd.where("disabled", "=", false));
for (String[] screen : SCREENS) {
Sys_menu menu = addMenu(module.getId(), root.getId(), screen[1], ROOT + screen[2],
@@ -158,7 +158,7 @@ public class TherapyRecuperationModule {
h5Module.setFaIcon("fa fa-heart");
dao.insert(h5Module);
}
Sys_menu mobile = addMenu(h5Module.getId(), "", "普惠疗休养V3", ROOT + "/mobile/index", "inclusiveTherapyV3.mobile", "h5");
Sys_menu mobile = addMenu(h5Module.getId(), "", "普惠疗休养", ROOT + "/mobile/index", "inclusiveTherapyV3.mobile", "h5");
for (Sys_role role : roles) {
if (List.of("public", "PUBLIC", "MEMBER", "sysadmin", "superadmin", "SYSADMIN").contains(role.getCode())) {
grant(role.getId(), root.getId());
@@ -37,7 +37,7 @@ public class TherapyWorkspaceController {
break;
}
}
if (!found) throw new IllegalArgumentException("未知的普惠疗休养V3页面");
if (!found) throw new IllegalArgumentException("未知的普惠疗休养页面");
}
response.sendRedirect(url);
}
@@ -29,7 +29,7 @@ public class TherapyEmail {
String user = setting("user");
String password = setting("password");
if (host == null || from == null || user == null || password == null) {
throw new IllegalStateException("普惠疗休养V3邮件发送配置不完整");
throw new IllegalStateException("普惠疗休养邮件发送配置不完整");
}
String portSetting = setting("port");
int port = portSetting == null ? 465 : Integer.parseInt(portSetting);
+1
View File
@@ -13,6 +13,7 @@
port: 6379
database: 4
mode: normal
password: dd3s@Redis#2026
jdbc:
url: jdbc:mysql://127.0.0.1:3306/zhgh_zjnu_v4?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
@@ -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');
@@ -155,7 +155,7 @@ def main():
output = args.output.resolve()
if output.exists():
raise ValueError("导出文件已存在,不覆盖:" + str(output))
log("开始导出普惠疗休养V3;源数据库:" + args.database)
log("开始导出普惠疗休养;源数据库:" + args.database)
log("[1/4] 连接数据库并检查源表结构")
client = MySQL(args)
tables = {suffix: "the_rapy_recuperation_" + suffix for suffix in SUFFIXES}
@@ -145,7 +145,7 @@ class IdentityMap:
def default_config(row):
permitted = {"id": None, "configName": {"智慧工会疗休养配置", "普惠疗休养V3配置"},
permitted = {"id": None, "configName": {"智慧工会疗休养配置", "普惠疗休养配置"},
"isSnLine": {"0"}, "isSwLine": {"0"}, "travelAudit": {"0"}, "bedInfo": {"1"},
"familyInfo": {"1"}, "outsideQuotaProportion": {"0", "0.00", "0.0"},
"unionLineNumberConfigs": {"[]"}, "lots": {"[]"}}
@@ -271,7 +271,7 @@ def main():
parser.add_argument("--minio-region", default=os.environ.get("MINIO_REGION", "us-east-1"))
parser.add_argument("--mapping", type=Path)
args = parser.parse_args()
log("开始导入普惠疗休养V3;目标数据库:" + args.database)
log("开始导入普惠疗休养;目标数据库:" + args.database)
log("[1/5] 读取导出包并校验数据:" + str(args.input))
overrides = json.loads(args.mapping.read_text(encoding="utf-8-sig")) if args.mapping else {}
uploaded_location = None
@@ -415,7 +415,7 @@ def main():
for row in source:
user = get(row, "userId")
prepared["activity_user_scope"].append({
"groupId": new_group, "groupName": "普惠疗休养V3 / " + (get(row, "groupName") or "参加人员"),
"groupId": new_group, "groupName": "普惠疗休养 / " + (get(row, "groupName") or "参加人员"),
"userId": identities["user"].resolve(user, "user") if user else "",
"groupType": "1", "groupSql": None,
"creator": identities["user"].resolve(get(row, "creator"), "user", required=False), "delFlag": "0"})
@@ -478,7 +478,7 @@ def main():
",".join(literal(value) for value in row.values()) + ");")
sql.append("UPDATE sys_config SET configValue=" + literal(version) + " WHERE configKey='staffTherapyRecuperationVersion';")
sql.append("INSERT INTO sys_config(configKey,configValue,note,delFlag) SELECT 'staffTherapyRecuperationVersion'," +
literal(version) + "," + literal("普惠疗休养V3报名规则版本") + ",0 WHERE NOT EXISTS "
literal(version) + "," + literal("普惠疗休养报名规则版本") + ",0 WHERE NOT EXISTS "
"(SELECT 1 FROM sys_config WHERE configKey='staffTherapyRecuperationVersion');")
sql.append("COMMIT;")
log("[5/5] 开始数据库事务,写入 %s 条记录;数据量较大时请等待事务提交" %
@@ -492,7 +492,7 @@ def main():
print("已上传附件保留在 MinIO 前缀:" + uploaded_location, file=sys.stderr)
raise
log("数据库事务提交成功")
log("导入完成:普惠疗休养V3,表前缀 " + PREFIX)
log("导入完成:普惠疗休养,表前缀 " + PREFIX)
log("业务记录:%s;审核记录:%s;附件:%s" %
(sum(len(records[name]) for name in SUFFIXES), len(records["_audit"]), len(file_info)))
if uploaded_location:
@@ -1,4 +1,4 @@
-- 仅清理普惠疗休养V3菜单及角色菜单关联,保留职工权益模块和业务数据。
-- 仅清理普惠疗休养菜单及角色菜单关联,保留职工权益模块和业务数据。
START TRANSACTION;
DELETE rm FROM sys_role_menu rm
@@ -1,14 +1,14 @@
-- 普惠疗休养V3:父菜单及12个子菜单,归属已有的职工权益模块。
-- 普惠疗休养:PC父菜单及12个子菜单、移动端入口,归属职工权益模块。
-- 角色菜单关系暂时只授予本项目超级管理员角色 SYSADMIN。
SET NAMES utf8mb4;
START TRANSACTION;
-- 普惠疗休养V3
-- 普惠疗休养
INSERT INTO sys_menu (id, parentId, path, name, type, href, target, icon, showit, disabled, permission, location, hasChildren, platform, moduleId, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT '7ea90000000000000000000000000000', '',
(SELECT LPAD(COALESCE(MAX(CAST(path AS UNSIGNED)),0)+1,4,'0') FROM sys_menu WHERE CHAR_LENGTH(path)=4),
'普惠疗休养V3', 'menu', '/platform/inclusiveTherapyV3', '_self', 'fa fa-heart', 1, 0,
'普惠疗休养', 'menu', '/platform/inclusiveTherapyV3', '_self', 'fa fa-heart', 1, 0,
'inclusiveTherapyV3', 50, 1, 'pc', id, '', UNIX_TIMESTAMP(NOW())*1000, '', UNIX_TIMESTAMP(NOW())*1000, 0
FROM sys_module WHERE name='职工权益' AND platform='pc' LIMIT 1;
@@ -64,8 +64,8 @@ FROM sys_menu WHERE permission='inclusiveTherapyV3';
-- 线路选择查询
INSERT INTO sys_menu (id, parentId, path, name, type, href, target, icon, showit, disabled, permission, location, hasChildren, platform, moduleId, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT '7ea90000000000000000000000000008', id, CONCAT(path, '0008'),
'线路选择查询', 'menu', '/platform/inclusiveTherapyV3/lineSelectionOverview', '_self', 'fa fa-th-large', 1, 0,
'inclusiveTherapyV3.lineSelectionOverview', 8, 0, 'pc', moduleId, '', UNIX_TIMESTAMP(NOW())*1000, '', UNIX_TIMESTAMP(NOW())*1000, 0
'线路选择查询', 'menu', '/platform/inclusiveTherapyV3/statistics/unionLineNumberStatistics', '_self', 'fa fa-th-large', 1, 0,
'inclusiveTherapyV3.unionLineNumberStatistics', 8, 0, 'pc', moduleId, '', UNIX_TIMESTAMP(NOW())*1000, '', UNIX_TIMESTAMP(NOW())*1000, 0
FROM sys_menu WHERE permission='inclusiveTherapyV3';
-- 成团结果通知
@@ -85,8 +85,8 @@ FROM sys_menu WHERE permission='inclusiveTherapyV3';
-- 分工会线路选择统计
INSERT INTO sys_menu (id, parentId, path, name, type, href, target, icon, showit, disabled, permission, location, hasChildren, platform, moduleId, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT '7ea9000000000000000000000000000b', id, CONCAT(path, '0011'),
'分工会线路选择统计', 'menu', '/platform/inclusiveTherapyV3/statistics/unionLineNumberStatistics', '_self', 'fa fa-th-large', 1, 0,
'inclusiveTherapyV3.unionLineNumberStatistics', 11, 0, 'pc', moduleId, '', UNIX_TIMESTAMP(NOW())*1000, '', UNIX_TIMESTAMP(NOW())*1000, 0
'分工会线路选择统计', 'menu', '/platform/inclusiveTherapyV3/lineSelectionOverview', '_self', 'fa fa-th-large', 1, 0,
'inclusiveTherapyV3.lineSelectionOverview', 11, 0, 'pc', moduleId, '', UNIX_TIMESTAMP(NOW())*1000, '', UNIX_TIMESTAMP(NOW())*1000, 0
FROM sys_menu WHERE permission='inclusiveTherapyV3';
-- 人数统计
@@ -115,4 +115,41 @@ WHERE r.code='SYSADMIN' AND r.disabled=0 AND COALESCE(r.delFlag,0)=0
'inclusiveTherapyV3.unionLineNumberStatistics',
'inclusiveTherapyV3.travelAgencyMonthlyStatistics');
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note,
location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, processKey, platform,
moduleId, isQuickEntry, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
VALUES ('e08d3515ed2f4ce28591f30703b68f1a', '', '0179', '普惠疗休养', null, 'menu', null, null, null, true, false,
'inclusiveTherapyV3.mobile', null, 1025, true, '17a7f8ad3ee947b4a26175049a9c253d', 1793064184942,
'17a7f8ad3ee947b4a26175049a9c253d', 1793064184942, false, null, 'H5', '586fffaeddc94a7493c81b6d69e546ab', false,
'/platform/sys/file/download?id=5j67fh5qaij5mr4p3q91peu46r', 'p', false, false);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note,
location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, processKey, platform,
moduleId, isQuickEntry, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
VALUES ('a57fbc8998d04acdb9d694841e1d572c', 'e08d3515ed2f4ce28591f30703b68f1a', '01790001', '疗休养报名', null, 'menu',
'/platform/inclusiveTherapyV3/mobile/index', null, null, true, false, 'inclusiveTherapyV3.mobile.signup', null,
1026, false, '17a7f8ad3ee947b4a26175049a9c253d', 1793064222219, '17a7f8ad3ee947b4a26175049a9c253d',
1793064310249, false, null, 'H5', '586fffaeddc94a7493c81b6d69e546ab', false,
'/platform/sys/file/download?id=6llemukl0ugaeqdgh7ud7g31ll', 'l', false, false);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note,
location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, processKey, platform,
moduleId, isQuickEntry, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
VALUES ('30f0cfd45c60406aaea5b44da88f828a', 'e08d3515ed2f4ce28591f30703b68f1a', '01790002', '我的报名', null, 'menu',
'/platform/inclusiveTherapyV3/mobile/myRecuperation', null, null, true, false,
'inclusiveTherapyV3.mobile.mySignup', null, 1027, false, '17a7f8ad3ee947b4a26175049a9c253d', 1793064293963,
'17a7f8ad3ee947b4a26175049a9c253d', 1793064317183, false, null, 'H5', '586fffaeddc94a7493c81b6d69e546ab', false,
'/platform/sys/file/download?id=v9phasf6roh33ocmnej2h2qbj1', 'w', false, false);
-- 移动端三个菜单授予超级管理员角色。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id FROM sys_role r CROSS JOIN sys_menu m
WHERE r.code IN ('SYSADMIN', 'superadmin') AND r.disabled=0 AND COALESCE(r.delFlag,0)=0
AND m.platform='H5'
AND m.id IN (
'e08d3515ed2f4ce28591f30703b68f1a',
'a57fbc8998d04acdb9d694841e1d572c',
'30f0cfd45c60406aaea5b44da88f828a')
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm WHERE rm.roleId=r.id AND rm.menuId=m.id);
COMMIT;
@@ -1,4 +1,4 @@
-- v4 职工福利 / 普惠疗休养V3独立子模块初始化脚本(MySQL 8)。
-- v4 职工福利 / 普惠疗休养独立子模块初始化脚本(MySQL 8)。
-- 在 v4 目标数据库中执行;表结构依据 therapyRecuperation 模块实体定义。
-- v3 the_rapy_recuperation_* 对应本模块 inclusive_therapy_v3_*,不覆盖 v4 其他疗休养表。
-- 仅新增表、视图和默认配置,不迁移 v3 历史业务数据。
@@ -353,12 +353,12 @@ SELECT 2750,'审核通过','#67c23a'
WHERE NOT EXISTS (SELECT 1 FROM `inclusive_therapy_v3_audit_state` WHERE `stateId`=2750);
INSERT INTO `sys_config` (`configKey`,`configValue`,`note`,`createdBy`,`createdAt`,`updatedBy`,`updatedAt`,`delFlag`)
SELECT 'staffTherapyRecuperationVersion','zjnu','普惠疗休养V3独立子模块报名规则版本','',@inclusive_therapy_v3_now,'',@inclusive_therapy_v3_now,0
SELECT 'staffTherapyRecuperationVersion','zjnu','普惠疗休养独立子模块报名规则版本','',@inclusive_therapy_v3_now,'',@inclusive_therapy_v3_now,0
WHERE NOT EXISTS (SELECT 1 FROM `sys_config` WHERE `configKey`='staffTherapyRecuperationVersion');
INSERT INTO `inclusive_therapy_v3_config`
(`id`,`configName`,`isSnLine`,`isSwLine`,`travelAudit`,`bedInfo`,`familyInfo`,`outsideQuotaProportion`,`unionLineNumberConfigs`)
SELECT REPLACE(UUID(),'-',''),'普惠疗休养V3配置',0,0,0,1,1,0,JSON_ARRAY()
SELECT REPLACE(UUID(),'-',''),'普惠疗休养配置',0,0,0,1,1,0,JSON_ARRAY()
WHERE NOT EXISTS (SELECT 1 FROM `inclusive_therapy_v3_config`);
COMMIT;
@@ -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
})
}
@@ -1,8 +1,8 @@
Vue.component('vi-title', {
props: ['title'],
template: `
<div style="display: flex;flex-direction:row;color: #1867b0;font-weight: bolder;margin-bottom: 20px">
<div style="width: 5px;height: 24px;background-color: #1867b0;border-radius: 2px"></div>
<div style="display: flex;flex-direction:row;color: var(--color-primary);font-weight: bolder;margin-bottom: 20px">
<div style="width: 5px;height: 24px;background-color: var(--color-primary);border-radius: 2px"></div>
<div style="margin-left: 20px;line-height: 24px;font-size: 15px;letter-spacing: 1px">
{{ title }}
</div>
@@ -61,7 +61,7 @@
}
.search .search-item-label {
color: rgb(100, 100, 100);
color: var(--color-text-regular);
margin-right: 10px;
width: 80px;
min-width: 80px;
@@ -97,18 +97,18 @@
.table-tool-group {
margin-left: 15px;
overflow: hidden;
border-radius: 20px;
border-radius: 2px;
display: flex;
}
.table-tool-group .table-tool-group-item {
width: 40px;
height: 40px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background-color: rgba(240, 240, 240, .7);
background-color: var(--background-color-base);
font-weight: bolder;
transition: all 250ms;
}
@@ -119,7 +119,7 @@
.table-tool-group .table-tool-group-item:hover {
color: white;
background-color: rgb(64, 158, 255);
background-color: var(--color-primary);
}
.width-100 {
@@ -176,11 +176,11 @@
.w-e-toolbar {
background-color: white !important;
border-color: #DCDFE6 !important;
border-color: var(--border-color-base) !important;
}
.w-e-text-container {
border-color: #DCDFE6 !important;
border-color: var(--border-color-base) !important;
}
.el-radio-group label {
@@ -16,15 +16,16 @@
}
.el-pagination {
height: 50px;
overflow: hidden;
min-height: 32px;
flex-wrap: wrap;
row-gap: 8px;
display: flex;
align-items: center;
}
.el-pagination button, .el-pagination span:not([class*=suffix]) {
line-height: 50px;
height: 50px;
line-height: 28px;
height: 28px;
}
a .collapsed {
@@ -128,7 +129,7 @@ a .expanded {
.el-upload__tip {
margin-top: 0px;
color: #c33714;
color: var(--color-danger);
}
.el-dialog {
@@ -137,16 +138,12 @@ a .expanded {
.el-table__empty-block {
width: 100% !important;
min-height: 300px;
background: url("/none.png") no-repeat center center;
background-size: 360px 260px;
min-height: 120px;
}
.el-table__empty-text {
display: block;
position: absolute;
bottom: 80px;
height: 40px;
line-height: 40px;
position: static;
line-height: 24px;
/*display: none !important;*/
}
@@ -0,0 +1,101 @@
#app.therapy-mobile {
--color-primary: #1867b0;
--therapy-primary-light: #eef5fc;
box-sizing: border-box;
min-height: 100vh;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
background: #f7f8fa !important;
color: #323233;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif !important;
font-size: 14px;
}
#app.therapy-mobile .van-nav-bar--fixed {
left: 50%;
width: 100%;
max-width: 640px;
transform: translateX(-50%);
}
#app.therapy-mobile .van-nav-bar__title {
color: #323233;
font-size: 16px;
font-weight: 600;
}
#app.therapy-mobile .van-nav-bar .van-icon,
#app.therapy-mobile .van-dropdown-menu__title--active,
#app.therapy-mobile .van-dropdown-item__option--active,
#app.therapy-mobile .van-dropdown-item__option--active .van-icon,
#app.therapy-mobile .van-search__action,
#app.therapy-mobile .union-popup-action {
color: var(--color-primary);
}
#app.therapy-mobile .van-doc-card,
#app.therapy-mobile .list-card {
margin: 12px;
border: 1px solid #ebedf0;
border-radius: 10px;
background: #fff;
box-shadow: none;
}
#app.therapy-mobile .van-doc-card { padding: 16px; }
#app.therapy-mobile .van-grid-item__content { padding: 12px 6px; border-radius: 8px; }
#app.therapy-mobile .van-grid-item__content:active,
#app.therapy-mobile .type-icon.active-icon,
#app.therapy-mobile .union-item.active-union { background: var(--therapy-primary-light); }
#app.therapy-mobile .type-icon.active-icon { border-color: var(--color-primary); }
#app.therapy-mobile .active,
#app.therapy-mobile .cus_button,
#app.therapy-mobile .sign_button .van-button {
background: var(--color-primary);
border-color: var(--color-primary);
color: #fff;
}
#app.therapy-mobile .choose_button .button_text { margin-top: 8px; font-size: 14px; line-height: 22px; }
#app.therapy-mobile .van-dropdown-menu__item { flex: 1; min-width: 0; }
#app.therapy-mobile .van-dropdown-menu__title { max-width: 100%; font-size: 14px; }
#app.therapy-mobile .list-card { overflow: hidden; }
#app.therapy-mobile .list-card > .content { padding: 14px; max-height: none; align-items: flex-start; }
#app.therapy-mobile .list-card > .content > .van-image {
flex: 0 0 96px;
width: 96px !important;
height: 80px !important;
}
#app.therapy-mobile .list-card > .content > .van-image img { object-fit: cover; }
#app.therapy-mobile .content-right { flex: 1; width: auto; max-width: none; min-width: 0; padding-left: 12px; }
#app.therapy-mobile .cr-title { margin-bottom: 6px; font-size: 15px; line-height: 22px; color: #323233; }
#app.therapy-mobile .concat,
#app.therapy-mobile .mobile,
#app.therapy-mobile .union { font-size: 12px; line-height: 21px; color: #646566; }
#app.therapy-mobile .operateDiv {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
padding: 12px 14px;
border-top: 1px solid #f2f3f5;
}
#app.therapy-mobile .operateDiv .van-button {
min-width: 64px;
height: 32px;
margin: 0;
border-radius: 6px;
font-size: 13px;
}
#app.therapy-mobile .pop_footer {
box-sizing: border-box;
height: auto;
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
border-top: 1px solid #ebedf0;
}
#app.therapy-mobile .pop_footer .van-button { width: 100%; height: 40px; }
#app.therapy-mobile .cus_content {
box-sizing: border-box;
height: calc(100% - 46px);
padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
#app.therapy-mobile .van-list__finished-text { color: #969799; font-size: 13px; }
@@ -11,7 +11,7 @@
<el-table-column label="总分" prop="totalScore" header-align="center"
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>
</el-table>
@@ -47,7 +47,21 @@ module.exports = {
this.tableData = []
const {data} = await this.$axios.post(loc() + "/isScoreTopEight", this.Form)
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) {
this.$message.error(e.msg)
} finally {
@@ -283,6 +283,11 @@
})()
function pjaxReplace(url, data = null) {
// 普惠疗休养移动端使用独立布局,没有公共 PJAX 容器,需整页跳转以保留应用中心样式。
if (url && url.indexOf("/platform/inclusiveTherapyV3/mobile/") === 0) {
window.location.href = url
return
}
showH5PageSkeleton(url)
// 疗休养报名页面使用独立 H5 布局,其中包含图片懒加载等专用初始化逻辑。
// 禁止通过公共 PJAX 只替换页面片段,否则专用布局脚本不会执行,首次进入会出现图片懒加载指令未注册。
@@ -15,7 +15,7 @@ const act = {
<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>
</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)}}
</span>
</div>
@@ -76,9 +76,9 @@ const act = {
if (!this.swiper) return
this.swiper.isEnd ? this.swiper.slideTo(0) : this.swiper.slideNext()
},
/*查询活动*/
// PC首页包含已结束活动;接口data为按未结束在前、已结束在后排列的活动实体数组。
listAct() {
this.$axios.post("/platform/home/listHomeActivity").then((res) => {
this.$axios.post("/platform/home/listHomeActivity", {includeEnded: true}).then((res) => {
if (res.code === 0) {
// for (let i = 0; i < 10; i++) {
// this.actList = this.actList.concat(res.data)
@@ -149,7 +149,7 @@ const act = {
// 活动状态
getStatusText(item) {
if (this.$moment().unix() > this.$moment(item.endDate).unix()) {
return "已结束"
return "报名已结束"
} else if (this.$moment().unix() < this.$moment(item.startDate).unix()) {
return "未开始"
} else {
@@ -40,7 +40,7 @@ const branchUnionGroupManage = {
</el-card>
<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-item label="小组编码" prop="code">
<el-input v-model="formData.code" placeholder="小组编码" maxlength="10"
@@ -103,6 +103,7 @@ const branchUnionGroupManage = {
unitIds: [{ required: true, message: "请选择组成单位", trigger: "change" }],
},
userOptions: [],
userOptionsRequestId: 0,
units: [],
}
},
@@ -173,13 +174,21 @@ const branchUnionGroupManage = {
})
},
clearUserOptions() {
this.userOptions = []
// 关闭后使未完成的搜索失效,防止旧结果回填到下一次打开的弹窗。
this.userOptionsRequestId++
},
getUserOptions(keyword, userIds = null) {
if (!this.dialogFormVisible && !userIds) return
const requestId = ++this.userOptionsRequestId
$.get("/platform/sys/unionGroup/listUser", {
keyword,
unionId: this.union_id,
userIds: JSON.stringify(userIds)
}).then((res) => {
if (res.code === 0) {
if (res.code === 0 && this.dialogFormVisible && requestId === this.userOptionsRequestId) {
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-else>将文件拖到此处,或<em>点击选择文件</em></div>
</el-upload>
<div class="union-cadre-import-note">选择文件后点击“开始导入”。逐行保存,一行失败不影响其他行。</div>
<div class="union-cadre-import-note">选择文件后点击“开始导入”。批量校验并保存,失败行显示具体原因,其他有效行继续导入。</div>
</section>
</div>
<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>
<div style="max-height: calc(100vh - 200px); overflow-y: auto">
<el-tree
v-loading="treeLoading"
:data="treeData"
ref="treeRef"
:expand-on-click-node="false"
@@ -151,13 +152,14 @@ layout("/layouts/platform.html"){
<!--#include("unitLeader.js"){}#-->
new Vue({
el: "#app",
data: function () {
data() {
return {
unitTabActive: "threeUnitManage",
filterText: null,
currentTreeNode: null,
currentTreeData: null,
defaultExpandedKeys: [],
treeLoading: false,
treeData: []
}
},
@@ -173,26 +175,45 @@ layout("/layouts/platform.html"){
},
methods: {
getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data
if (this.treeData) {
this.currentTreeData = this.treeData[0]
this.defaultExpandedKeys = this.treeData.length > 0 ? [this.treeData[0].id] : []
this.$set(this, "treeLoading", true)
// tree 返回单位树数组;失败时保留现有内容,成功后按 ID 恢复当前单位。
return this.$axios.post(loc() + "/tree").then((res) => {
if (res.code !== 0 || !Array.isArray(res.data)) return
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) {
this.currentTreeData = data
this.currentTreeNode = node
this.$nextTick(()=>{
if (data.unitTypeCode==1) {
this.$refs.bmlRef.pageData()
this.$refs.userRef.pageData()
this.$refs.leaderRef.pageData()
} else {
this.$refs.bzlRef.pageData()
}
})
this.$set(this, "currentTreeData", data)
this.$set(this, "currentTreeNode", node)
// 单位列表由 currentData 监听刷新,部门类另外更新人员和干部页签。
this.$nextTick(() => {
if (data.unitTypeCode == 1) {
this.$refs.userRef.pageData()
this.$refs.leaderRef.pageData()
}
})
},
filterNode(value, data, node) {
if (!value) return true
@@ -11,7 +11,7 @@ const UNIT_MANAGE_TEMPLATE = {
<table-tool label="单位列表">
<el-button @click="openAdd" type="primary" size="small">新建单位</el-button>
</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">
<template v-slot="scope">{{scope.$index + (pageForm.pageNumber - 1) * pageForm.pageSize + 1}}</template>
</el-table-column>
@@ -75,8 +75,8 @@ const UNIT_MANAGE_TEMPLATE = {
<!-- </el-form-item>-->
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="doHandle">确 定</el-button>
<el-button :disabled="formLoading" @click="closeDialog">取 消</el-button>
<el-button type="primary" :loading="formLoading" @click="doHandle">确 定</el-button>
</span>
</el-dialog>
</div>
@@ -110,6 +110,8 @@ const UNIT_MANAGE_TEMPLATE = {
audit: false
},
tableData: [],
tableLoading: false,
formLoading: false,
dialogVisible: false,
formData: {},
formRules: {
@@ -128,96 +130,130 @@ const UNIT_MANAGE_TEMPLATE = {
}
},
computed: {},
watch: {
// 首次进入、切换节点以及刷新树后,均按最新上级单位从第一页重新查询。
currentData: {
immediate: true,
handler() {
this.doSearch()
}
}
},
methods: {
doSearch() {
this.pageForm.pageNumber = 1
this.pageData()
this.$set(this.pageForm, "pageNumber", 1)
return this.pageData()
},
openAdd() {
this.dialogVisible = true
this.formData = {} //打开新增窗口,表单先清空
this.parentUnit = []
this.options = []
// 上级关系取当前单位 ID,与用户是否点击过树节点无关;手工新增默认其他类。
this.$set(this, "formData", {
parentId: this.currentData.id,
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() {
this.$confirm("确定要提交吗?", "提示", {
if (this.formLoading) return
this.$refs.form.validate((valid) => {
if (!valid) return
return this.$confirm("确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
if (this.formLoading) return
const url = this.formData.id ? "/platform/sys/unit/editDo" : "/platform/sys/unit/addDo"
// 新建始终携带当前上级;编辑沿用回显的原上级,不在保存时意外变更层级。
if (!this.formData.id) {
this.$set(this.formData, "parentId", this.currentData.id)
}
this.$set(this, "formLoading", true)
return this.$axios.post(url, this.formData).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$set(this, "dialogVisible", false)
// 父页面刷新并恢复当前树节点,currentData 监听统一刷新列表,避免重复查询。
this.$emit("refresh", null)
} else {
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(() => {
const self = this
const url = this.formData.id ? "/platform/sys/unit/editDo" : "/platform/sys/unit/addDo"
this.$refs["form"].validate(async function (valid) {
if (valid) {
if (self.currentNode) {
self.formData.parentId = self.currentData.id
}
const resp = await self.$axios.post(url, self.formData)
if (resp.code === 0) {
self.$message.success(resp.msg)
self.doSearch()
self.dialogVisible = false
self.$emit("refresh", null)
} else {
self.$message.warning(resp.msg)
}
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)
}
})
})
},
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) {
this.$message.success(resp.msg)
this.doSearch()
this.$emit("refresh", null)
} else {
this.$message.warning(resp.msg)
}
}
},
pageNumberChange(val) {
this.pageForm.pageNumber = val
this.$set(this.pageForm, "pageNumber", val)
this.pageData()
},
pageSizeChange(val) {
this.pageForm.pageSize = val
this.$set(this.pageForm, "pageSize", val)
this.pageData()
},
pageData() {
this.pageForm.unitId = this.currentData.id
// this.pageForm.unitLevel = this.unitLevel
this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => {
if (!this.currentData || !this.currentData.id) return
// unitId 限定当前上级,返回 data.list 为下设单位,data.totalCount 为分页总数。
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) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
this.$set(this, "tableData", res.data.list)
this.$set(this.pageForm, "totalCount", res.data.totalCount)
}
}).finally(() => {
this.$set(this, "tableLoading", false)
})
}
},
async created() {
this.pageData()
if (this.$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN')) {
this.unions = await this.$businessTool.listUnion()
} else {
this.unions = await this.$businessTool.listUnion(this.store.state.user.union.id)
}
created() {
const unionId = this.$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN') ? null : this.store.state.user.union.id
this.$businessTool.listUnion(unionId).then((unions) => {
this.$set(this, "unions", unions)
})
}
}
@@ -119,7 +119,7 @@ layout("/layouts/platform.html"){
currentTreeNode: null,
currentTreeData: null,
checkData: {},
checkData: { id: "0", name: "无(新建根节点)" },
tableData: [],
basicTypeDialog: false,
@@ -146,10 +146,8 @@ layout("/layouts/platform.html"){
getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data
if (this.treeData) {
this.checkData = this.treeData[0]
}
this.treeData = res.data || []
this.checkData = this.treeData.length ? this.treeData[0] : { id: "0", name: "无(新建根节点)" }
})
},
@@ -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">
<table-tool>
<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>
<el-table :data="tableData" border size="small" height="calc(100vh - 375px)">
<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],
data() {
return {
pageDataUrl: "/platform/activity/basic/union/pageData",
dialogFormVisible: false,
dialogGlyFormVisible: false,
formData: {},
@@ -88,10 +90,25 @@ const ACTIVITY_BASIC_BRANCH_UNION_MANAGE = {
}
},
methods: {
pageData() {
this.$axios.post("/platform/activity/basic/union/pageData", this.pageForm).then((res) => {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
// 同步新增工会和已有工会名称;复用表格查询及父页面刷新事件,保留现有查询和分页逻辑。
updateUnions() {
if (this.formLoading) {
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() {
@@ -9,10 +9,10 @@ layout("/layouts/platform.html"){
<div style="max-height: calc(100vh - 160px); overflow-y: auto">
<el-tree
:data="treeData"
:default-expanded-keys="['1']"
:default-expanded-keys="pageForm.parentId ? [pageForm.parentId] : []"
:expand-on-click-node="false"
:props="{
children: 'child',
children: 'children',
label: 'name'
}"
@node-click="treeNodeClick"
@@ -52,6 +52,7 @@ layout("/layouts/platform.html"){
</div>
<div class="search-item">
<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>
</el-card>
@@ -104,6 +105,36 @@ layout("/layouts/platform.html"){
},
components: {},
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) {
const confirm = await this.$confirm("确定要删除该单位吗?", "提示", {
confirmButtonText: "确定",
@@ -122,24 +153,42 @@ layout("/layouts/platform.html"){
}
},
treeNodeClick(data, node) {
this.currentTreeData = data
this.currentTreeNode = node
this.pageForm.parentId = data.id
this.$set(this, "currentTreeData", data)
this.$set(this, "currentTreeNode", node)
this.$set(this.pageForm, "parentId", data.id)
this.doSearch()
},
getTreeData() {
this.$axios.post(loc() + "/tree").then((res) => {
this.treeData = res.data
if (this.treeData) {
this.currentTreeData = this.treeData[0]
return this.$axios.post(loc() + "/tree").then((res) => {
if (res.code !== 0) {
this.notifyWarning(res.msg)
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() {}
},
created() {
this.pageData()
this.getTreeData()
this.getTreeData().then((loaded) => {
if (loaded) {
this.doSearch()
}
}).catch(() => {
this.notifyError("加载单位失败,请稍后重试")
})
}
})
</script>
@@ -370,12 +370,14 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
</el-form-item>
</el-col>
<!--
<el-col :span="24">
<el-form-item label="报名表单" prop="formConfig"
v-if="formData.isEnrollSystem&&formData.signUpMethod===1">
<custom-form-field v-model="formData.formConfig"></custom-form-field>
</el-form-item>
</el-col>
-->
<el-col :span="24" v-if="formData.signUpMethod!=null&&formData.isEnrollSystem">
<el-form-item label="封面图" prop="cover">
@@ -592,6 +594,7 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
name: [{required: true, message: "必填", trigger: ["blur", "change"]}],
type: [{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"]}],
plannedDate: [{required: true, message: "必填", trigger: ["blur", "change"]}],
time: [{required: true, message: "必填", trigger: ["blur", "change"]}],
@@ -725,7 +728,15 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
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) {
this.$message.error("请选择活动项目类型")
return
@@ -6,18 +6,19 @@ layout("/layouts/platform.html"){
<template>
<el-card shadow="never">
<search @search="doSearch" :is-search-button="false">
<search @search="refreshPrizes" :is-search-button="false">
<search-item label="年度">
<el-date-picker
:clearable="false"
:disabled="tableLoading"
@change="getActivitys"
placeholder="选择年"
style="width: 100%" type="year"
v-model="pageForm.year" value-format="yyyy">
</el-date-picker>
</search-item>
<search-item label="活动名称" v-if="!pageForm.status">
<el-select :clearable="false" @change="actChange(pageForm.activityId)"
<search-item label="活动名称" v-if="pageForm.status!=='1'">
<el-select :clearable="false" :disabled="tableLoading" @change="actChange(pageForm.activityId)"
filterable
placeholder="请选择活动名称"
style="width: 100%" v-model="pageForm.activityId">
@@ -30,7 +31,7 @@ layout("/layouts/platform.html"){
</el-select>
</search-item>
<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
placeholder="请选择工会"
style="width: 100%" v-model="pageForm.unionId">
@@ -43,9 +44,9 @@ layout("/layouts/platform.html"){
</el-select>
</search-item>
<search-item label="性别" v-if="pageForm.status==='1'||!pageForm.status">
<el-select filterable
<el-select filterable :disabled="tableLoading"
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-select>
@@ -56,7 +57,7 @@ layout("/layouts/platform.html"){
<el-card class="mt10" shadow="never">
<table-tool
:label="pageForm.status==='2'?('浙江师范大学'+pageForm.year+'年教职工运动会获奖个数汇总表'):'奖品名单'">
:label="pageForm.status==='2'?('中国地质大学(武汉)'+pageForm.year+'年教职工运动会获奖个数汇总表'):'奖品名单'">
<el-tag
:effect="pageForm.status===item.code?'dark':'plain'"
:key="item.code"
@@ -66,17 +67,32 @@ layout("/layouts/platform.html"){
v-for="item in statusOptions">
{{ item.name }}
</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>
<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">
<tr>
<td class="text-center" colspan="11">
<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}})获奖名单
</span>
</td>
@@ -97,10 +113,10 @@ layout("/layouts/platform.html"){
<td>{{k}}</td>
<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">
{{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>
</td>
<td></td>
<td>{{ prizeSourceText(o) }}</td>
</tr>
<tr v-for="(o,k,index) in tableDataUnionPrize['男子项目']">
<td :rowspan="Object.keys(tableDataUnionPrize['男子项目']).length"
@@ -111,7 +127,7 @@ layout("/layouts/platform.html"){
<td v-for="i in 8">
{{o.filter(f=>f.ranking==i).map(g=>g.username).join('、')}}
</td>
<td></td>
<td>{{ prizeSourceText(o) }}</td>
</tr>
<tr v-for="(o,k,index) in tableDataUnionPrize['女子项目']">
<td :rowspan="Object.keys(tableDataUnionPrize['女子项目']).length" dd
@@ -122,7 +138,13 @@ layout("/layouts/platform.html"){
<td v-for="i in 8">
{{o.filter(f=>f.ranking==i).map(g=>g.username).join('、')}}
</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>
<td>合计</td>
@@ -283,200 +305,161 @@ layout("/layouts/platform.html"){
],
tableDataUnionPrize: {},
lastSum: {},
tableDataPrize: [],
tableDataPrize: {},
confirmedTotal: 0,
pendingResults: [],
queryFailed: false,
statusOptions: [{code: "1", name: "分组汇总年度前八"}, {code: "2", name: "奖品个数年度汇总表"}]
}
},
components: {},
methods: {
async statusType(state) {
this.tableLoading = true
if (state === "1") {
const data = await this.summaryYearPrizes()
if (!data) {
this.tableLoading = false
return
}
} else if (state === "2") {
this.pageForm.sex = null
await this.summary()
statusType(state) {
if (this.tableLoading) return
const next = this.pageForm.status === state ? null : state
if (next === '1' && !this.pageForm.sex) {
this.$message.warning('请选择性别')
return
}
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
this.$set(this.pageForm, 'status', next)
this.$set(this, 'isSummary', next === '1' ? 3 : (next === '2' ? 2 : 1))
return this.refreshPrizes()
},
getSummaryByYear(o, i, type) {
if (type === "集体项目") {
const datas = o.filter(o => o.ranking === i).map(o => o.unionname)
if (datas) {
return datas.join(",")
} else {
return ""
}
} else {
//甲组里面有可能有团体项目,所以说如果这个没有username就代表是团体
return o.filter(f => f.ranking === i).map(g => g.username ? g.username : g.unionname).join('、')
}
// 三种展示分别刷新,沿用mixins的分页能力,不另写公共表格分页方法。
refreshPrizes() {
if (this.isSummary === 3) return this.summaryYearPrizes()
if (this.isSummary === 2) return this.summary()
return this.getPrizes()
},
async summaryYearPrizes() {
try {
if (!this.pageForm.sex) {
this.$message.warning("请选择性别")
return false
}
this.$set(this.pageForm, "unionId", null)
this.$set(this.pageForm, "activityId", null)
this.isSummary = 3
const {data} = await this.$axios.post(loc() + "/getSummaryYearPrizes", this.pageForm)
this.tableDataPrize = data
return true
} catch (e) {
this.$message.error(e.msg)
return false
}
sexChange() {
if (this.isSummary === 3) return this.summaryYearPrizes()
},
getTableTdContent(d, i) {
return d.filter(v => {
if (v.isteampersonal === 1) {
if (v.ranking == i) {
console.log(v, i)
return v.username
}
} else {
if (v.ranking == i) {
return v.numberofpeople
}
}
}).map(v => {
return v.isteampersonal === 1 ? v.username : v.numberofpeople
}).toString()
getSummaryByYear(rows, ranking, type) {
return rows.filter(item => item.ranking === ranking).map(item => type === '集体项目' ? item.unionname : (item.username || item.unionname)).join('、')
},
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)
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) {
this.$message.warning('请选择性别')
return Promise.resolve()
}
this.$set(this, 'tableLoading', true)
this.$set(this, 'queryFailed', false)
this.$set(this, 'tableDataPrize', {})
return this.$axios.post(loc() + '/getSummaryYearPrizes', this.pageForm).then((resp) => {
if (resp.code !== 0) throw new Error(resp.msg || '年度名次查询失败')
this.$set(this, 'tableDataPrize', resp.data)
}).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '年度名次查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
},
summary() {
this.clearPrizeStatistics()
this.$set(this, 'tableLoading', true)
this.$set(this, 'queryFailed', false)
return this.$axios.post(loc() + '/summary', {activityId: this.pageForm.activityId}).then((resp) => {
if (resp.code !== 0) throw new Error(resp.msg || '奖品汇总查询失败')
this.$set(this, 'tableData', resp.data.score || [])
this.acceptPrizeStatistics(resp.data)
}).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '奖品汇总查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
},
print() {
let subOutputRankPrint = document.getElementById('print');
let newContent = subOutputRankPrint.innerHTML;
let oldContent = document.body.innerHTML;
document.body.innerHTML = newContent;
window.print();
window.location.reload();
document.body.innerHTML = oldContent;
return false;
if (this.tableLoading || this.queryFailed) return
let newContent = document.getElementById('print').innerHTML
let oldContent = document.body.innerHTML
document.body.innerHTML = newContent
window.print()
window.location.reload()
document.body.innerHTML = oldContent
return false
},
actChange(activityId) {
this.tableColumns = []
this.tableData = []
this.pageForm.activityId = ""
const activityData = this.activityList.find(v => v.id == activityId)
this.pageForm.activityId = activityData.id
this.getPrizes()
this.$set(this.pageForm, 'activityId', activityId)
return this.refreshPrizes()
},
unionChange(unionId) {
this.tableColumns = []
this.tableData = []
this.pageForm.unionId = ""
this.isSummary = 1
const unionData = this.unionList.find(v => v.id == unionId)
this.pageForm.unionId = unionData.id
this.unionName = unionData.name
this.getPrizes()
this.$set(this.pageForm, 'unionId', unionId)
const union = this.unionList.find(item => item.id === unionId)
this.$set(this, 'unionName', union ? union.name : '')
return this.getPrizes()
},
async getActivitys() {
this.tableData = []
const {data} = await this.$axios.post("/platform/activity/score/statistics/getActivitys", {year: this.pageForm.year})
this.pageForm.activityId = ""
this.activityList = data
if (data && data.length > 0) {
this.pageForm.activityId = data[0].id
}
await this.getPrizes()
},
async getPrizes() {
try {
this.tableLoading = true
const {data} = await this.$axios.post(loc() + "/getPrizes", this.pageForm)
this.tableDataUnionPrize = data
const lastSum = {}
for (let i = 1; i <= 8; i++) {
let sum = 0
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])
}
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
getActivitys() {
this.clearPrizeStatistics()
this.$set(this, 'tableDataPrize', {})
this.$set(this, 'tableLoading', true)
this.$set(this, 'queryFailed', false)
return this.$axios.post('/platform/activity/score/statistics/getActivitys', {year: this.pageForm.year}).then((resp) => {
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 : '')
}
lastSum[9] = Object.values(lastSum).reduce((a, b) => a + b)
this.lastSum = lastSum
} catch (e) {
this.$message.error(e.msg)
} finally {
this.tableLoading = false
}
return this.refreshPrizes()
}).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '活动查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
},
getPrizes() {
this.clearPrizeStatistics()
this.$set(this, 'tableLoading', true)
this.$set(this, 'queryFailed', false)
return this.$axios.post(loc() + '/getPrizes', this.pageForm).then((resp) => {
if (resp.code !== 0) throw new Error(resp.msg || '奖品名单查询失败')
this.$set(this, 'tableDataUnionPrize', resp.data)
// 名单与汇总共用后端人数统计,前端不再用积分或整队人数另算一套合计。
this.$set(this, 'lastSum', resp.data.lastSum || {})
this.acceptPrizeStatistics(resp.data)
}).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '奖品名单查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
},
},
async created() {
await this.getActivitys()
this.unionList = await this.$businessTool.listUnion()
if (this.unionList.length > 0) {
this.pageForm.unionId = this.unionList[0].id
this.unionName = this.unionList[0].unionname
}
await this.getPrizes()
created() {
this.$set(this, 'tableLoading', true)
this.$businessTool.listUnion().then((unions) => {
this.$set(this, 'unionList', unions || [])
this.$set(this.pageForm, 'unionId', unions.length ? unions[0].id : '')
this.$set(this, 'unionName', unions.length ? unions[0].name : '')
return this.getActivitys()
}).catch((error) => {
this.$set(this, 'queryFailed', true)
this.$message.error(error.message || error.msg || '工会查询失败')
}).finally(() => {
this.$set(this, 'tableLoading', false)
})
}
})
</script>
File diff suppressed because it is too large Load Diff
@@ -73,7 +73,8 @@ layout("/layouts/platform.html"){
</el-select>
</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>
</search-item>
</search>
@@ -301,10 +302,18 @@ layout("/layouts/platform.html"){
},
doExcelCj() {
const {activityId, unionname} = this.pageForm
// 年度统计视图下 activityId 已被清空,此处导出会查不到数据
if (!activityId) {
this.$message.warning("当前为年度统计视图,无法导出单个活动的成绩明细,请先选择「运动会成绩统计」中的任一维度")
return
}
let unionId = ''
if (unionname) {
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})
@@ -585,11 +585,13 @@ const basicForm = {
return
}
const courseValid = this.formData.courseList.some((v, i) => {
// 新活动通过多选关系设置负责人;未重新设置的旧活动仍允许保留历史联系人。
const hasResponsible = Array.isArray(v.responsibleList)
? v.responsibleList.length > 0 : !!v.courseInstructor
const basicValid = v.courseName && v.coursePeopleNumber && v.courseLocation && hasResponsible && v.courseType
const missingFields = []
if (!v.courseName) missingFields.push(this.trainType + "名称")
if (!v.coursePeopleNumber) missingFields.push(this.trainType + "人数(不能为空或为0)")
if (!v.courseLocation) missingFields.push(this.trainType + "地点")
if (!v.courseType) missingFields.push("类型")
const timeValid =
Array.isArray(v.courseTimeList) &&
v.courseTimeList.length > 0 &&
v.courseTimeList.every((x) => {
return x.courseStartTime && x.courseEndTime && x.courseStartTime < x.courseEndTime
@@ -598,8 +600,8 @@ const basicForm = {
this.$message.warning("第" + (i + 1) + "行时间填写有误,请核查")
return true
}
if (!basicValid) {
this.$message.warning("第" + (i + 1) + "行信息填写有误,请核查")
if (missingFields.length > 0) {
this.$message.warning("第" + (i + 1) + "行请补充:" + missingFields.join("、"))
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"]}],
reimburseProject: [{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: [{
required: true,
whitespace: true,
@@ -1890,10 +1894,14 @@ layout("/layouts/platform.html"){
this.normalizeFundBalance()
resolve(true)
} else {
// 支付信息已移至发票信息页,支付明细校验失败时跳转到对应页签。
// 新增必填项属于基本信息;同时存在支付错误时优先展示这两个字段的校验提示。
const basicInfoInvalid = Object.keys(invalidFields || {})
.some((field) => ["paymentNotes", "clubId"].includes(field))
const paymentDetailInvalid = Object.keys(invalidFields || {})
.some((field) => field.indexOf("paymentDetails.") === 0)
if (paymentDetailInvalid) {
if (basicInfoInvalid) {
this.$set(this, "activeTabName", "basic")
} else if (paymentDetailInvalid) {
this.$set(this, "activeTabName", "invoice")
}
this.$message.warning("请完善必填信息")
@@ -201,8 +201,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
(pageForm.regionalNature==='省内'&&modifyConfig.isSnLine)||
(pageForm.regionalNature==='省外'&&modifyConfig.isSwLine)">
<el-button size="small"
style="background-color: #fff;border-color: #b3d8ff;color: #1867b0;font-size: 15px"
<el-button size="small" type="primary" plain
>{{selectLine.lineName?selectLine.lineName:'全部'}}线路的人数:{{lineSignNumber}}人
</el-button>
@@ -243,6 +243,8 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :md="12" :sm="24" :xs="24">
<el-form-item label="旅行社名称" prop="travelAgencyId">
<el-select clearable filterable style="width: 100%" v-model="formData.travelAgencyId">
@@ -272,7 +274,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<el-col :md="12" :sm="24" :xs="24">
<el-row :gutter="20">
<el-col :span="12">
<el-col :span="24">
<el-form-item label="线路类型" prop="regionalNature">
<el-radio-group class="mr0-radio" size="small" v-model="formData.regionalNature">
<el-radio :label="item.value"
@@ -284,6 +286,8 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
</el-col>
</el-row>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :md="12" :sm="24" :xs="24">
<el-form-item label="交通工具" prop="trafficTools">
<el-input clearable maxlength="50" v-model="formData.trafficTools"></el-input>
@@ -312,6 +316,8 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
</el-col>
</el-row>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :md="12" :sm="24" :xs="24">
<el-form-item label="预计费用(元/人次)" prop="estimatedCost">
<el-input clearable maxlength="50" v-model="formData.estimatedCost"></el-input>
@@ -1,5 +1,5 @@
<!--#
layout("/layouts/platform.html"){
#-->
<iframe src="${therapyPageUrl,escape}" title="普惠疗休养V3" style="width:100%;height:calc(100vh - 100px);border:0;background:#fff"></iframe>
<iframe src="${therapyPageUrl,escape}" title="普惠疗休养" style="width:100%;height:calc(100vh - 100px);border:0;background:#fff"></iframe>
<!--# } #-->
@@ -1,23 +1,36 @@
<!doctype html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>普惠疗休养V3</title>
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
<script>const APP_DOMAIN = location.origin; const FILE_DOMAIN = location.origin;</script>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/index.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/mobile/theme.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/mobile/main.css">
<script src="${base!}/assets/platform/plugins/vant/index.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/basic.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/v.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/mobileMixins.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/compat.js"></script>
<link rel="stylesheet" href="${base!}/assets/platform/css/pdf/pdfh5.css">
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.js"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.worker.js"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdfh5.js"></script>
<style>[v-cloak]{display:none}body{margin:0}</style>
</head><body>${layoutContent}</body></html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>普惠疗休养</title>
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
<script>const APP_DOMAIN = location.origin;
const FILE_DOMAIN = location.origin;</script>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/index.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/mobile/theme.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/mobile/main.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/mobile/pages.css">
<script src="${base!}/assets/platform/plugins/vant/index.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/basic.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/v.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/mobileMixins.js"></script>
<script src="${base!}/assets/staffbenefit/therapyRecuperation/compat.js"></script>
<link rel="stylesheet" href="${base!}/assets/platform/css/pdf/pdfh5.css">
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.js"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdf.worker.js"></script>
<script src="${base!}/assets/platform/plugins/pdfJs/h5/pdfh5.js"></script>
<style>[v-cloak] {
display: none !important;
}
body {
margin: 0
}</style>
</head>
<body>${layoutContent}</body>
</html>
@@ -1,16 +1,17 @@
<!--# if(!@com.budwk.app.zhgh.staffbenefit.therapyRecuperation.support.TherapyContext.isEmbeddedPage()){ #-->
<!--# layout("/layouts/platform.html"){ #-->
<iframe src="${@com.budwk.app.zhgh.staffbenefit.therapyRecuperation.support.TherapyContext.getEmbeddedPageUrl(),escape}" title="普惠疗休养V3" style="width:100%;height:calc(100vh - 100px);border:0;background:#fff"></iframe>
<iframe src="${@com.budwk.app.zhgh.staffbenefit.therapyRecuperation.support.TherapyContext.getEmbeddedPageUrl(),escape}" title="普惠疗休养" style="width:100%;height:calc(100vh - 100px);border:0;background:#fff"></iframe>
<!--# } } else { #-->
<!doctype html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>普惠疗休养V3</title>
<title>普惠疗休养</title>
<script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
<script src="${base!}/assets/platform/plugins/vue-http-loader/httpVueLoader.js"></script>
<script src="${base!}/assets/platform/plugins/moment/moment.min.js"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js"></script>
<script>const APP_DOMAIN = location.origin; const FILE_DOMAIN = location.origin;</script>
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css">
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" href="${base!}/assets/platform/fonts/font-awesome.min.css">
<link rel="stylesheet" href="${base!}/assets/platform/fonts/themify-icons.css">
@@ -20,7 +21,18 @@
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/css/main.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/css/elmain.css">
<link rel="stylesheet" href="${base!}/assets/staffbenefit/therapyRecuperation/css/panel.css">
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/index_custom.css">
<link rel="stylesheet" href="${base!}/assets/platform/plugins/element-ui/lib/theme-chalk/override.css">
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css">
<script src="${base!}/assets/platform/plugins/element-ui/lib/index.js"></script>
<script>
ELEMENT.Dialog.props.closeOnClickModal.default = false;
ELEMENT.Dialog.props.top.default = '50px';
ELEMENT.Table.props.border.default = true;
ELEMENT.TableColumn.props.headerAlign.default = 'center';
ELEMENT.TableColumn.props.align.default = 'center';
Vue.use(ELEMENT, {zIndex: 20000});
</script>
<script src="${base!}/assets/platform/plugins/wangEditor4/wangEditor.js"></script>
<script src="${base!}/assets/platform/plugins/sortablejs/sortable.min.js"></script>
<script src="${base!}/assets/platform/plugins/vuedraggable/vuedraggable.umd.min.js"></script>
@@ -280,7 +280,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<!-- <el-button icon="el-icon-s-promotion" @click="openImport" size="small"
type="primary">参加人员导入
</el-button>-->
<!--# if(@com.budwk.app.zhgh.staffbenefit.therapyRecuperation.support.TherapyContext.hasRole('H04')){ #-->
<!--# if(@com.budwk.app.zhgh.staffbenefit.therapyRecuperation.support.TherapyContext.hasAnyRoles('H04,superadmin')){ #-->
<template>
<el-button icon="el-icon-s-promotion"
@click="$downloadFile('/platform/inclusiveTherapyV3/query/exportDeclareForm')"
@@ -424,7 +424,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
v-if="pageForm.lb==='ry'">
<template scope="{row}">
<el-card shadow="never">
<h4 slot="header" style="color: #1867b0">
<h4 slot="header" style="color: var(--color-primary)">
家属信息
</h4>
<el-table :data="row.companionList">
@@ -528,7 +528,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<el-table-column label="家属信息" type="expand" width="150px">
<template slot-scope="{row}">
<el-card shadow="never">
<h4 slot="header" style="color: #1867b0">
<h4 slot="header" style="color: var(--color-primary)">
家属信息
</h4>
<el-table :data="row.companionList">
@@ -752,7 +752,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
title="家属信息"
:visible.sync="familyDrawer"
direction="rtl">
<h4 slot="header" style="color: #1867b0">
<h4 slot="header" style="color: var(--color-primary)">
家属信息
</h4>
<el-table :data="companionList">
@@ -35,7 +35,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<div id="app">
<el-card class="m10" shadow="never" style="height: calc(100% - 20px)">
<template #header>
<h3 style="color: #1867b0">导入参加人员</h3>
<h3 style="color: var(--color-primary)">导入参加人员</h3>
</template>
<el-steps :active="stepActive">
@@ -4,7 +4,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<style>
.companion_card {
border: 1px #1867b0 solid;
border: 1px var(--color-primary) solid;
}
.row-expand-cover .el-table__expand-icon {
@@ -189,7 +189,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<el-table-column label="序号" type="expand" width="150px">
<template slot-scope="{row}">
<el-card class="companion_card" shadow="never">
<h4 slot="header" style="color: #1867b0">
<h4 slot="header" style="color: var(--color-primary)">
家属信息
</h4>
<el-table :data="row.companionList">
@@ -24,7 +24,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
<guava>
<el-card shadow="never">
<div slot="header" class="clearfix">
<h3 style="color: rgb(24, 103, 176);font-family: Microsoft YaHei;">疗休养配置</h3>
<h3 style="color: var(--color-primary);font-family: Microsoft YaHei;">疗休养配置</h3>
</div>
<el-form ref="addForm" :model="formData" :rules="formRules" label-width="170px">
@@ -340,7 +340,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/platform.html"){
travelAudit: 0,
lots: [],
unionLineNumberConfigs: [],
configName: '普惠疗休养V3配置',
configName: '普惠疗休养配置',
// 报名时间范围对应后台 signUpStartTime/signUpEndTime 两个字段。
signUpStartTime: null,
signUpEndTime: null,
@@ -170,6 +170,7 @@ layout("/layouts/platform.html"){
<el-table-column align="center" header-align="center" label="更新时间"
min-width="145px" prop="changeTime"
show-overflow-tooltip sortable>
<template scope="{row}">{{row.changeTime || '--'}}</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="异动类型"
prop="changeType" show-overflow-tooltip sortable>
@@ -649,17 +650,16 @@ layout("/layouts/platform.html"){
}
},
getChangeTypes(changeTypes){
if (!changeTypes) return null
const changeTypesList = JSON.parse(changeTypes)
if (changeTypesList) {
if (!changeTypesList || changeTypesList.length === 0 || !this.changeTypeData || this.changeTypeData.length === 0) return null
return changeTypesList
.map((v) => {
return this.changeTypeData.find((item) => item.code === v).changeTypeName
})
.join(",")
}
return null
if (!changeTypes) return '--'
const changeTypesList = Array.isArray(changeTypes) ? changeTypes : JSON.parse(changeTypes)
if (!Array.isArray(changeTypesList) || changeTypesList.length === 0) return '--'
// 历史编码可能已不在当前枚举中,保留原编码,避免整张表格渲染中断。
return changeTypesList
.map((v) => {
const changeType = (this.changeTypeData || []).find((item) => item.code === v)
return changeType ? changeType.changeTypeName : (v || '未知异动类型')
})
.join(",")
},
changeDateRangeChange(val) {
if (val && val.length > 0) {
@@ -200,7 +200,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
<div id="app" v-cloak>
<van-sticky>
<van-nav-bar @click-left="history.back()" fixed left-arrow placeholder title="普惠疗休养V3-酒店介绍"></van-nav-bar>
<van-nav-bar @click-left="history.back()" fixed left-arrow placeholder title="普惠疗休养-酒店介绍"></van-nav-bar>
</van-sticky>
<!--线路详情-->
@@ -3,60 +3,94 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
#-->
<style>
.van-doc-card {
margin: 10px;
padding: 12px 12px 12px 12px;
.therapy-home {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
color: #323233;
}
.therapy-home .van-nav-bar--fixed {
left: 50%;
width: 100%;
max-width: 640px;
transform: translateX(-50%);
}
.therapy-home .van-doc-card {
margin: 12px;
padding: 16px;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 8px 12px #ebedf0;
line-height: 20px;
border-radius: 12px;
line-height: 24px;
position: relative;
}
.van-grid-item__content {
padding: 0;
.therapy-home .therapy-banner {
display: block;
width: calc(100% - 24px);
margin: 12px;
height: clamp(140px, 42vw, 220px);
overflow: hidden;
border-radius: 12px;
}
.choose_button .button_text {
.therapy-home .van-grid-item__content {
padding: 12px 8px;
border-radius: 8px;
}
.therapy-home .van-grid-item__content:active {
background-color: #f2f7fc;
}
.therapy-home .choose_button .button_text {
font-size: 14px;
margin-top: 6px;
margin-top: 10px;
font-weight: 500;
}
.list-card {
width: auto;
.therapy-home .notice-title {
margin: 0 0 12px;
padding-left: 10px;
border-left: 3px solid #1867b0;
font-size: 16px;
font-weight: 600;
}
.pre_text {
.therapy-home .pre_text {
white-space: break-spaces;
padding-left: 7px;
overflow-wrap: anywhere;
font-size: 14px;
line-height: 1.8;
overflow-x: auto;
}
.pdfjs {
height: 90%;
.therapy-home .pre_text img,
.therapy-home .pre_text video {
max-width: 100% !important;
height: auto !important;
}
.therapy-home .pdf_div {
height: 70vh;
min-height: 320px;
overflow: auto;
}
</style>
<div id="app" v-cloak>
<div id="app" class="therapy-home therapy-mobile" v-cloak>
<van-sticky>
<van-nav-bar @click-left="history.back()" fixed left-arrow placeholder title="普惠疗休养V3"></van-nav-bar>
<van-nav-bar @click-left="historyBack" fixed left-arrow placeholder title="普惠疗休养"></van-nav-bar>
</van-sticky>
<!--顶部轮播图-->
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item>
<van-image width="100%" height="180" src="/assets/staffbenefit/therapyRecuperation/img/index1.jpeg"></van-image>
</van-swipe-item>
<van-swipe-item>
<van-image height="180" src="/assets/staffbenefit/therapyRecuperation/img/index1.jpeg"></van-image>
</van-swipe-item>
</van-swipe>
<van-image class="therapy-banner" fit="cover" alt="疗休养风景" src="/assets/staffbenefit/therapyRecuperation/img/index1.jpeg"></van-image>
<!--按钮导航-->
<div class="choose_button van-doc-card">
<van-grid :column-num="chooseButton.length" :border="false">
<van-grid-item @click="toLineList(item.value)" v-for="item in chooseButton">
<van-grid :column-num="Math.max(chooseButton.length, 1)" :border="false">
<van-grid-item @click="toLineList(item.value)" v-for="item in chooseButton" :key="item.value">
<van-image width="46" height="46" :src="item.imgUrl"></van-image>
<div class="button_text">{{item.label}}</div>
</van-grid-item>
@@ -64,8 +98,8 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
</div>
<!--疗休养描述-->
<div class="van-doc-card" style="margin-top: 20px; height: calc(100vh - 200px)">
<van-image width="190" height="43" src="/assets/staffbenefit/therapyRecuperation/img/title.png"></van-image>
<div class="van-doc-card">
<h2 class="notice-title">疗休养须知</h2>
<div v-if="isPdf" class="pre_text" v-html="configData.notice"></div>
<div v-else id="demo" class="pdf_div"></div>
</div>
@@ -84,6 +118,9 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
}
},
methods: {
historyBack() {
window.history.back()
},
async toLineList(index) {
/*const isExist = await getScopeUser(this.configData.activityGroupId)
if (!isExist) {
@@ -222,7 +222,7 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
<van-sticky>
<van-nav-bar @click-left="history.go(-1)" fixed left-arrow placeholder
title="普惠疗休养V3-线路介绍"></van-nav-bar>
title="普惠疗休养-线路介绍"></van-nav-bar>
</van-sticky>
<template v-if="isLoad">
@@ -303,9 +303,9 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
</style>
<div id="app" v-cloak v-if="isLoad">
<div id="app" class="therapy-mobile" v-cloak v-if="isLoad">
<van-sticky>
<van-nav-bar @click-left="history.back()" fixed left-arrow placeholder title="疗休养线路"></van-nav-bar>
<van-nav-bar @click-left="historyBack" fixed left-arrow placeholder title="疗休养线路"></van-nav-bar>
<!--<van-dropdown-menu>
@@ -631,6 +631,9 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
}
},
methods: {
historyBack() {
window.history.back()
},
async openSignUser(row, type) {
const id = type === 'line' ? row.usId : row.id
location.href = '/platform/inclusiveTherapyV3/mobile/userSignInfo?type=' + type + '&id=' + id
@@ -179,9 +179,9 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
</style>
<div id="app" v-cloak>
<div id="app" class="therapy-mobile" v-cloak>
<van-sticky>
<van-nav-bar @click-left="history.back()" fixed left-arrow placeholder
<van-nav-bar @click-left="historyBack" fixed left-arrow placeholder
title="我的疗休养"></van-nav-bar>
</van-sticky>
@@ -413,6 +413,9 @@ layout("/platform/zhgh/staffbenefit/therapyRecuperation/layouts/mobile.html"){
}
},
methods: {
historyBack() {
window.history.back()
},
isValidDate(value) {
return value && moment(value).isValid()
},