体育活动成绩统计,协会经费
This commit is contained in:
@@ -295,6 +295,14 @@ 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
|
||||
@@ -317,30 +325,45 @@ public class SysUnitController {
|
||||
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i)
|
||||
.setExtra(
|
||||
Map.of(
|
||||
"unitTypeCode", unit.getUnitTypeCode()
|
||||
// 历史手工新建单位可能未设置类型,展示时按其他类处理,避免空值使整棵树加载失败。
|
||||
"unitTypeCode", unit.getUnitTypeCode() == null ? 0 : unit.getUnitTypeCode()
|
||||
)
|
||||
));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RedisConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
@@ -46,27 +47,36 @@ public class SysUnitServiceImpl extends BaseServiceImpl<Sys_unit> implements Sys
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增单位
|
||||
* 新增下设单位,校验上级关系并补齐影响树展示的字段。
|
||||
*
|
||||
* @param unit
|
||||
* @param pid
|
||||
* @param unit 单位表单;unitcode 同时作为单位 ID,unitTypeCode 未传时保存为其他类(0),不自动认定为部门类(1)
|
||||
* @param pid 已存在的上级单位 ID,不允许为空,避免新增单位脱离当前单位树
|
||||
* @throws BaseException 上级单位为空、不存在或部门类别不是 0/1 时拒绝保存
|
||||
*/
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void save(Sys_unit unit, String pid) {
|
||||
String path = "";
|
||||
if (!Strings.isEmpty(pid)) {
|
||||
Sys_unit pp = this.fetch(pid);
|
||||
path = pp.getPath();
|
||||
if (StrUtil.isBlank(pid)) {
|
||||
throw new BaseException("请选择上级单位");
|
||||
}
|
||||
if(StrUtil.isNotBlank(path)) {
|
||||
unit.setPath(getSubPath("sys_unit", "path", path));
|
||||
Sys_unit parentUnit = this.fetch(pid);
|
||||
if (parentUnit == null) {
|
||||
throw new BaseException("上级单位不存在,请刷新页面后重试");
|
||||
}
|
||||
// 手工创建不具备信息中心的部门分类依据,仅对缺失类型补其他类,保留明确传入的部门类设置。
|
||||
if (unit.getUnitTypeCode() == null) {
|
||||
unit.setUnitTypeCode(0);
|
||||
} else if (!Integer.valueOf(0).equals(unit.getUnitTypeCode()) && !Integer.valueOf(1).equals(unit.getUnitTypeCode())) {
|
||||
throw new BaseException("部门类别只能为其他类或部门类");
|
||||
}
|
||||
if (unit.getUnitLevel() == null && parentUnit.getUnitLevel() != null) {
|
||||
unit.setUnitLevel(parentUnit.getUnitLevel() + 1);
|
||||
}
|
||||
// 与单位同步逻辑一致:上级路径缺失时也生成有效路径,避免新记录继续保留空路径。
|
||||
unit.setPath(getSubPath("sys_unit", "path", parentUnit.getPath()));
|
||||
unit.setId(unit.getUnitcode());
|
||||
unit.setParentId(pid);
|
||||
dao().fastInsert(unit);
|
||||
if (!Strings.isEmpty(pid)) {
|
||||
this.update(Chain.make("hasChildren", true), Cnd.where("id", "=", pid));
|
||||
}
|
||||
this.update(Chain.make("hasChildren", true), Cnd.where("id", "=", pid));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
-10
@@ -10,10 +10,8 @@ import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_unit;
|
||||
import com.budwk.app.sys.services.SysUnitService;
|
||||
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;
|
||||
@@ -28,8 +26,11 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @ClassName ActivityBasicUnitController
|
||||
@@ -46,6 +47,8 @@ public class ActivityBasicUnitController {
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private ActivityBasicUnitService activityBasicUnitService;
|
||||
|
||||
|
||||
@At("")
|
||||
@@ -62,6 +65,8 @@ public class ActivityBasicUnitController {
|
||||
cnd.and("parentId", "=", "1");
|
||||
} else {
|
||||
cnd.and("parentId", "=", parentId);
|
||||
// 系统根可能以自身为父级,列表只显示其下级单位,避免把根本身当作下级操作。
|
||||
cnd.and("id", "!=", parentId);
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchName()) && StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.and(Cnd.likeEX(pageForm.getSearchName(), pageForm.getSearchKeyword()));
|
||||
@@ -82,22 +87,39 @@ public class ActivityBasicUnitController {
|
||||
cnd.asc("unitcode");
|
||||
List<ActivityBasicUnit> list = baseService.dao().query(ActivityBasicUnit.class, cnd);
|
||||
|
||||
Set<String> unitIds = list.stream().map(ActivityBasicUnit::getId).collect(Collectors.toSet());
|
||||
String virtualRootId = "_activity_unit_root_";
|
||||
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
nodeList.add(new TreeNode<>(list.get(i).getId(), list.get(i).getParentId(), list.get(i).getName(), i)
|
||||
.setExtra(
|
||||
Map.of(
|
||||
"unitTypeCode", list.get(i).getUnitTypeCode()
|
||||
)
|
||||
));
|
||||
ActivityBasicUnit unit = list.get(i);
|
||||
String parentId = unit.getParentId();
|
||||
// 系统根可能自引用或使用空父级;只在构树时挂到虚拟根,保留同步后的真实父级关系。
|
||||
if (StrUtil.isBlank(parentId) || unit.getId().equals(parentId) || !unitIds.contains(parentId)) {
|
||||
parentId = virtualRootId;
|
||||
}
|
||||
// 源单位分类允许为空,使用支持空值的Map避免同步后整棵单位树加载失败。
|
||||
Map<String, Object> extra = new HashMap<>();
|
||||
extra.put("unitTypeCode", unit.getUnitTypeCode());
|
||||
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i).setExtra(extra));
|
||||
}
|
||||
List<Tree<String>> treeList = TreeUtil.build(nodeList, StrUtil.blankToDefault(pid, "0"));
|
||||
List<Tree<String>> treeList = TreeUtil.build(nodeList, virtualRootId);
|
||||
return Result.success(treeList);
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 无请求参数,按当前系统单位资料更新活动单位。
|
||||
* 返回 Result,data 中 insertCount/updateCount/pendingUnionCount 分别表示新增、更新及新增待配置工会数量。
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("activity.basic.unit")
|
||||
public Result updateUnits() {
|
||||
return Result.success(activityBasicUnitService.syncUnits());
|
||||
}
|
||||
|
||||
private List<ActivityBasicUnit> child(String parentId) {
|
||||
List<ActivityBasicUnit> units = dao.query(ActivityBasicUnit.class, Cnd.where("parentId", "=", parentId));
|
||||
units.forEach(unit -> {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
+93
@@ -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());
|
||||
}
|
||||
}
|
||||
+18
-57
@@ -12,7 +12,7 @@ import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.sys.services.SysUnionService;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolEvent;
|
||||
import com.budwk.app.zhgh.activity.sports.service.ActivitySportsService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -53,6 +53,9 @@ public class ActivitySportsScoreStatisticsController {
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
|
||||
@Inject
|
||||
private ActivitySportsService activitySportsService;
|
||||
|
||||
|
||||
// @Inject
|
||||
// private OfficeTemplateUtil officeTemplateUtil;
|
||||
@@ -246,7 +249,10 @@ public class ActivitySportsScoreStatisticsController {
|
||||
@At
|
||||
@SaCheckLogin
|
||||
public Result getActivitys(Integer year) {
|
||||
List<ActivitySchool> schoolList = baseService.dao().query(ActivitySchool.class, Cnd.where("YEAR(applyEndTime)", "=", year));
|
||||
// 活动名称下拉只列出正式活动,与年度统计口径一致,过滤掉活动模板(模板标记 + 工作模板表双重判断)。
|
||||
List<ActivitySchool> schoolList = baseService.dao().query(ActivitySchool.class, Cnd.where("YEAR(applyEndTime)", "=", year)
|
||||
.and("templateStatus", "!=", ActivitySchool.TEMPLATE_STATUS_SET)
|
||||
.and(new Static("id NOT IN (SELECT id FROM sys_home_template)")));
|
||||
return Result.success(schoolList);
|
||||
}
|
||||
|
||||
@@ -331,7 +337,10 @@ public class ActivitySportsScoreStatisticsController {
|
||||
|
||||
Dao dao = sysUnionService.dao();
|
||||
List<Sys_union> unionList = sysUnionService.query();
|
||||
List<ActivitySchool> activitySchoolList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year));
|
||||
// 年度统计只取正式活动:既排除模板标记,也排除工作模板表中登记的活动(历史模板未回填标记位)。
|
||||
List<ActivitySchool> activitySchoolList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)
|
||||
.and("templateStatus", "!=", ActivitySchool.TEMPLATE_STATUS_SET)
|
||||
.and(new Static("id NOT IN (SELECT id FROM sys_home_template)")));
|
||||
|
||||
List<String> labelList = activitySchoolList.stream().map(v -> v.getName()).collect(Collectors.toList());
|
||||
labelList.add(0, "分工会");
|
||||
@@ -363,64 +372,16 @@ public class ActivitySportsScoreStatisticsController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isMenWomen 1 为年度男子团体总分,2 为年度女子团体总分
|
||||
* @param year 活动报名开始时间所属年度
|
||||
* @return 成功结果中 label 为列名列表,score 为包含分工会、各活动积分和总分的行列表
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("年度男子团体总分/年度女子团体总分")
|
||||
@SaCheckPermission("activity.score.statistics")
|
||||
public Result getYear8(Integer isMenWomen, Integer year) {
|
||||
ArrayList<Map> resultMap = new ArrayList<>();
|
||||
|
||||
// int isMenWomen = sex.equals("男") ? 1 : 2;
|
||||
|
||||
Dao dao = sysUnionService.dao();
|
||||
List<Sys_union> unionList = sysUnionService.query();
|
||||
|
||||
List<ActivitySchool> activityList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year));
|
||||
List<String> activityIdList = activityList.stream().map(ActivitySchool::getId).collect(Collectors.toList());
|
||||
List<String> activityNameList = activityList.stream().map(ActivitySchool::getName).collect(Collectors.toList());
|
||||
|
||||
List<String> eventIdList = dao.query(ActivitySchoolEvent.class, Cnd.where("activityId", "in", activityIdList)).stream().map(v -> v.getEventId()).collect(Collectors.toList());
|
||||
|
||||
activityNameList.add(0, "分工会");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ae.isMenWomen,
|
||||
ae.isInterest,
|
||||
ar.integral,
|
||||
ar.unionId,
|
||||
ar.activityId
|
||||
FROM
|
||||
activity_event ae
|
||||
LEFT JOIN activity_results ar ON ar.eventId = ae.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ae.id", "in", eventIdList);
|
||||
cnd.and("ar.activityId", "in", activityIdList);
|
||||
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(sql);
|
||||
List<NutMap> list = sql.getList(NutMap.class);
|
||||
|
||||
unionList.forEach(u -> {
|
||||
NutMap unionMap = Lang.obj2nutmap(u);
|
||||
AtomicReference<Double> scoreSum = new AtomicReference<>((double) 0);
|
||||
|
||||
activityList.forEach(a -> {
|
||||
double sexScore = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getInt("isMenWomen") == isMenWomen && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
double qwScore = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getInt("isInterest") == 1 && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
scoreSum.updateAndGet(v -> v + sexScore + qwScore * 0.5);
|
||||
unionMap.put(a.getName(), sexScore + (qwScore * 0.5));
|
||||
});
|
||||
unionMap.put("总分", scoreSum.get());
|
||||
unionMap.put("分工会", unionMap.getString("name"));
|
||||
resultMap.add(unionMap);
|
||||
|
||||
});
|
||||
activityNameList.add("总分");
|
||||
return Result.success(Map.of("label", activityNameList, "score", resultMap));
|
||||
|
||||
return Result.success(activitySportsService.getAnnualGenderResults(isMenWomen, year));
|
||||
}
|
||||
public static Double getDouble(NutMap o) {
|
||||
return o.getDouble("score");
|
||||
|
||||
+18
-47
@@ -12,7 +12,7 @@ import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.sys.services.SysUnionService;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolEvent;
|
||||
import com.budwk.app.zhgh.activity.sports.service.ActivitySportsService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -48,56 +48,24 @@ public class ActivitySportsStatisticsTypeExportController {
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
|
||||
@Inject
|
||||
private ActivitySportsService activitySportsService;
|
||||
|
||||
/**
|
||||
* 导出年度男子或女子团体积分,无返回值,文件直接写入 HTTP 响应。
|
||||
*
|
||||
* @param isMenWomen 1 导出年度男子团体总分,2 导出年度女子团体总分
|
||||
* @param year 活动报名开始时间所属年度
|
||||
* @param response 用于输出包含名次、分工会、各活动积分和总分的 XSSF Excel 文件
|
||||
*/
|
||||
@Ok("void")
|
||||
@At
|
||||
@ApiOperation("年度男子团体总分/年度女子团体总分")
|
||||
@SaCheckPermission("activity.score.statistics")
|
||||
public void getYear8(Integer isMenWomen, Integer year, HttpServletResponse response) {
|
||||
ArrayList<Map> resultMap = new ArrayList<>();
|
||||
List<Sys_union> unionList = sysUnionService.query();
|
||||
|
||||
List<ActivitySchool> activityList = baseService.dao().query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year));
|
||||
List<String> activityIdList = activityList.stream().map(ActivitySchool::getId).collect(Collectors.toList());
|
||||
List<String> activityNameList = activityList.stream().map(ActivitySchool::getName).collect(Collectors.toList());
|
||||
|
||||
List<String> eventIdList = baseService.dao().query(ActivitySchoolEvent.class, Cnd.where("activityId", "in", activityIdList)).stream().map(v -> v.getEventId()).collect(Collectors.toList());
|
||||
|
||||
activityNameList.add(0, "分工会");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ae.isMenWomen,
|
||||
ae.isInterest,
|
||||
ar.integral,
|
||||
ar.unionId,
|
||||
ar.activityId
|
||||
FROM
|
||||
activity_event ae
|
||||
LEFT JOIN activity_results ar ON ar.eventId = ae.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ae.id", "in", eventIdList);
|
||||
cnd.and("ar.activityId", "in", activityIdList);
|
||||
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = baseService.listMap(sql);
|
||||
unionList.forEach(u -> {
|
||||
NutMap unionMap = Lang.obj2nutmap(u);
|
||||
AtomicReference<Double> scoreSum = new AtomicReference<>((double) 0);
|
||||
|
||||
activityList.forEach(a -> {
|
||||
double sexScore = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getInt("isMenWomen") == isMenWomen && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
double qwScore = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getInt("isInterest") == 1 && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
scoreSum.updateAndGet(v -> v + (sexScore + (qwScore * 0.5)));
|
||||
unionMap.put(a.getName(), sexScore + (qwScore * 0.5));
|
||||
});
|
||||
unionMap.put("总分", scoreSum.get());
|
||||
unionMap.put("分工会", unionMap.getString("name"));
|
||||
resultMap.add(unionMap);
|
||||
|
||||
});
|
||||
activityNameList.add("总分");
|
||||
NutMap annualResults = activitySportsService.getAnnualGenderResults(isMenWomen, year);
|
||||
List<Map> resultMap = annualResults.getList("score", Map.class);
|
||||
List<String> activityNameList = annualResults.getList("label", String.class);
|
||||
|
||||
resultMap.sort(Comparator.comparing((Map m) -> (new BigDecimal(m.get("总分").toString()))).reversed());
|
||||
for (int i = 0; i < resultMap.size(); i++) {
|
||||
@@ -131,7 +99,10 @@ public class ActivitySportsStatisticsTypeExportController {
|
||||
|
||||
Dao dao = sysUnionService.dao();
|
||||
List<Sys_union> unionList = sysUnionService.query();
|
||||
List<ActivitySchool> activitySchoolList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year));
|
||||
// 与页面查询保持一致,导出时同样过滤掉活动模板(标记位 + 工作模板表双重判断)。
|
||||
List<ActivitySchool> activitySchoolList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)
|
||||
.and("templateStatus", "!=", ActivitySchool.TEMPLATE_STATUS_SET)
|
||||
.and(new Static("id NOT IN (SELECT id FROM sys_home_template)")));
|
||||
|
||||
List<String> labelList = activitySchoolList.stream().map(v -> v.getName()).collect(Collectors.toList());
|
||||
labelList.add(0, "分工会");
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.budwk.app.zhgh.activity.sports.service;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolEvent;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -33,5 +34,15 @@ public interface ActivitySportsService extends BaseService<ActivitySchool> {
|
||||
*/
|
||||
void cancelTemplate(String id);
|
||||
|
||||
/**
|
||||
* 按老项目规则计算年度男子或女子团体积分,供页面查询和 Excel 导出共同使用。
|
||||
*
|
||||
* @param isMenWomen 统计性别,1 为男子、2 为女子;单一活动不按项目性别筛选
|
||||
* @param year 报名开始时间所属年度,例如 2026
|
||||
* @return NutMap:label 为“分工会、各活动名称、总分”的列名列表;score 为各分工会的积分行列表,
|
||||
* 每行以活动名称存放该活动积分,以“分工会”和“总分”存放分工会名称及年度合计
|
||||
*/
|
||||
NutMap getAnnualGenderResults(Integer isMenWomen, Integer year);
|
||||
|
||||
void exportXlsx(String id, String unionId, HttpServletResponse response);
|
||||
}
|
||||
|
||||
+68
@@ -9,6 +9,7 @@ import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.DateUtil;
|
||||
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_union;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
|
||||
@@ -26,15 +27,19 @@ import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.entity.Record;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@@ -246,6 +251,69 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
Cnd.where("id", "=", id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按活动类型恢复老项目的年度积分口径,查询和导出使用同一份计算结果。
|
||||
*
|
||||
* @param isMenWomen 1 为男子年度积分,2 为女子年度积分
|
||||
* @param year 按活动报名开始时间筛选的年度
|
||||
* @return label 为列名列表,score 为以活动名称、分工会、总分作为字段的积分行列表
|
||||
*/
|
||||
@Override
|
||||
public NutMap getAnnualGenderResults(Integer isMenWomen, Integer year) {
|
||||
ArrayList<Map> resultMap = new ArrayList<>();
|
||||
List<Sys_union> unionList = dao().query(Sys_union.class, null);
|
||||
// 年度统计只取正式活动:既排除模板标记,也排除工作模板表中登记的活动(历史模板未回填标记位)。
|
||||
List<ActivitySchool> activityList = dao().query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)
|
||||
.and("templateStatus", "!=", ActivitySchool.TEMPLATE_STATUS_SET)
|
||||
.and(new Static("id NOT IN (SELECT id FROM sys_home_template)")));
|
||||
List<String> activityIdList = activityList.stream().map(ActivitySchool::getId).collect(Collectors.toList());
|
||||
List<String> activityNameList = activityList.stream().map(ActivitySchool::getName).collect(Collectors.toList());
|
||||
List<String> eventIdList = dao().query(ActivitySchoolEvent.class, Cnd.where("activityId", "in", activityIdList)).stream().map(v -> v.getEventId()).collect(Collectors.toList());
|
||||
|
||||
activityNameList.add(0, "分工会");
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ae.isMenWomen,
|
||||
ae.isInterest,
|
||||
ar.integral,
|
||||
ar.unionId,
|
||||
ar.activityId
|
||||
FROM
|
||||
activity_event ae
|
||||
LEFT JOIN activity_results ar ON ar.eventId = ae.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ae.id", "in", eventIdList);
|
||||
cnd.and("ar.activityId", "in", activityIdList);
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> list = listMap(sql);
|
||||
|
||||
unionList.forEach(u -> {
|
||||
NutMap unionMap = Lang.obj2nutmap(u);
|
||||
AtomicReference<Double> scoreSum = new AtomicReference<>((double) 0);
|
||||
activityList.forEach(a -> {
|
||||
if (Integer.valueOf(1).equals(a.getApplyType())) {
|
||||
// 单一活动按分工会和活动累计全分,男子、女子年度榜均计入,不依赖项目性别或趣味标记。
|
||||
double score = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
scoreSum.updateAndGet(v -> v + score);
|
||||
unionMap.put(a.getName(), score);
|
||||
} else {
|
||||
// 综合类活动沿用原规则:对应性别积分,加上全部趣味项目积分的一半。
|
||||
double sexScore = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getInt("isMenWomen") == isMenWomen && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
double qwScore = list.stream().filter(x -> x.getString("unionId").equals(u.getId()) && x.getInt("isInterest") == 1 && x.getString("activityId").equals(a.getId())).mapToDouble(h -> h.getDouble("integral")).sum();
|
||||
scoreSum.updateAndGet(v -> v + sexScore + qwScore * 0.5);
|
||||
unionMap.put(a.getName(), sexScore + (qwScore * 0.5));
|
||||
}
|
||||
});
|
||||
unionMap.put("总分", scoreSum.get());
|
||||
unionMap.put("分工会", unionMap.getString("name"));
|
||||
resultMap.add(unionMap);
|
||||
});
|
||||
activityNameList.add("总分");
|
||||
return NutMap.NEW().setv("label", activityNameList).setv("score", resultMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportXlsx(String id, String unionId, HttpServletResponse response) {
|
||||
try {
|
||||
|
||||
@@ -198,15 +198,26 @@ public class ClubPayRecordServiceImpl extends BaseServiceImpl<ClubPayRecord> imp
|
||||
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name());
|
||||
}
|
||||
|
||||
/** 普通菜单授权用户仅能查看其有效角色关联的社团;无关联时显式返回空范围。 */
|
||||
/**
|
||||
* 为缴费列表、协会下拉选项及状态设置追加当前用户所管理的协会范围,无返回值。
|
||||
* 管理角色沿用项目现有口径:会长、副会长、秘书长、副秘书长、操作员;普通会员不纳入。
|
||||
* @param cnd 当前业务查询条件,管理员保留原条件,无管理协会时追加恒假条件
|
||||
* @param clubField 当前查询的协会ID字段,可包含表别名,如 cpr.clubId 或 c.id
|
||||
*/
|
||||
private void addPayClubScope(Cnd cnd, String clubField) {
|
||||
if (!canManageAllPayClubs()) {
|
||||
// 用户ID使用 SQL 参数绑定,社团集合交给 Cnd 处理,避免空集合被可选条件忽略。
|
||||
// 用户ID和管理角色均使用 SQL 参数绑定;社团集合交给 Cnd 处理,空集合显式限制为无权限。
|
||||
Sql scopeSql = Sqls.create("""
|
||||
SELECT DISTINCT ur.clubId FROM sys_user_role ur
|
||||
INNER JOIN sys_role r ON r.id = ur.roleId AND r.disabled = false
|
||||
WHERE ur.userId = @userId AND ur.enable = true AND ur.clubId IS NOT NULL
|
||||
""").setParam("userId", SecurityUtil.getUserId());
|
||||
AND r.code IN (@presidentRole, @vicePresidentRole, @secretaryRole, @viceSecretaryRole, @operatorRole)
|
||||
""").setParam("userId", SecurityUtil.getUserId())
|
||||
.setParam("presidentRole", RoleConstant.CLUB_PRESIDENT.name())
|
||||
.setParam("vicePresidentRole", RoleConstant.CLUB_VICE_PRESIDENT.name())
|
||||
.setParam("secretaryRole", RoleConstant.CLUB_SECRETARY.name())
|
||||
.setParam("viceSecretaryRole", RoleConstant.CLUB_VICE_SECRETARY.name())
|
||||
.setParam("operatorRole", RoleConstant.CLUB_OPERATOR.name());
|
||||
List<String> clubIds = listMap(scopeSql).stream()
|
||||
.map(row -> row.getString("clubId"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
|
||||
+2
-2
@@ -315,9 +315,9 @@ public class UnionReimburseApplyController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询可申请社团经费报销的社团。
|
||||
* 查询当前登录人所属的可申请报销协会,无请求参数,用户身份由服务端登录会话确定。
|
||||
*
|
||||
* @return 全部未解散社团列表
|
||||
* @return Result.data 为已加入且未解散的协会列表,每项包含 id、clubName、clubCode;无所属协会时为空列表
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
|
||||
|
||||
+3
-2
@@ -63,9 +63,10 @@ public interface UnionReimburseService extends BaseService<UnionReimburse> {
|
||||
Result getBudgetBalance(String reimburseFundSource, String clubId, String unionId);
|
||||
|
||||
/**
|
||||
* 查询未解散社团,供社团经费报销选择。
|
||||
* 查询当前登录人已加入且未解散的协会,供协会经费报销选择和申请范围校验。
|
||||
* 无请求参数,用户身份取自服务端登录会话。
|
||||
*
|
||||
* @return 全部未解散社团的ID、名称和编码列表
|
||||
* @return 协会列表,每项包含 id、clubName、clubCode;未加入协会时返回空列表,同一协会只返回一次
|
||||
*/
|
||||
List<NutMap> listReimburseClubs();
|
||||
|
||||
|
||||
+17
-3
@@ -894,6 +894,10 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
// return Result.error("请填写开户行");
|
||||
// }
|
||||
if (!needInvoiceDetails(unionReimburse)) {
|
||||
// 慰问报销提交和重新提交时必须填写非空白事由,保存草稿仍沿用现有暂存规则。
|
||||
if (StrUtil.isBlank(unionReimburse.getPaymentNotes())) {
|
||||
return Result.error("请填写报销事由");
|
||||
}
|
||||
return validateInvoiceDetails(unionReimburse);
|
||||
}
|
||||
if (StrUtil.isBlank(unionReimburse.getActivityName())) {
|
||||
@@ -1303,9 +1307,10 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询未解散社团,社团选择和余额查询不按负责人归属过滤。
|
||||
* 查询当前登录人已加入且未解散的协会,供所属协会选择、余额查询和申请校验共用。
|
||||
* 无请求参数,会员归属使用服务端当前登录用户ID,不接受前端指定其他用户。
|
||||
*
|
||||
* @return 全部未解散社团的ID、名称和编码
|
||||
* @return 协会列表,每项包含 id、clubName、clubCode;未加入协会时返回空列表,同一协会只返回一次
|
||||
*/
|
||||
@Override
|
||||
public List<NutMap> listReimburseClubs() {
|
||||
@@ -1313,8 +1318,13 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
SELECT c.id, c.clubName, c.clubCode
|
||||
FROM sys_club c
|
||||
WHERE c.dismiss = 0
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM club_user cu
|
||||
WHERE cu.clubId = c.id AND cu.userId = @userId
|
||||
)
|
||||
ORDER BY c.clubCode, c.id
|
||||
""");
|
||||
sql.setParam("userId", SecurityUtil.getUserId());
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
@@ -1323,11 +1333,15 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
if (!"UNION_REIMBURSE_PROJECT_5".equals(form.getReimburseProject())) {
|
||||
return Result.error("社团经费请选择社团活动");
|
||||
}
|
||||
// 协会经费必须选择本人已加入的有效协会,保存草稿也不能绕过所属协会范围。
|
||||
if (StrUtil.isBlank(form.getClubId())) {
|
||||
return Result.error("请选择所属协会");
|
||||
}
|
||||
NutMap club = listReimburseClubs().stream()
|
||||
.filter(item -> Objects.equals(form.getClubId(), item.getString("id")))
|
||||
.findFirst().orElse(null);
|
||||
if (club == null) {
|
||||
return Result.error("请选择有效的社团");
|
||||
return Result.error("只能选择本人所在且未解散的协会");
|
||||
}
|
||||
form.setClubName(club.getString("clubName"));
|
||||
} else if ("UNION_REIMBURSE_PROJECT_5".equals(form.getReimburseProject())) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -474,7 +474,7 @@ layout("/layouts/platform.html"){
|
||||
this.unionList = await this.$businessTool.listUnion()
|
||||
if (this.unionList.length > 0) {
|
||||
this.pageForm.unionId = this.unionList[0].id
|
||||
this.unionName = this.unionList[0].unionname
|
||||
this.unionName = this.unionList[0].name
|
||||
}
|
||||
await this.getPrizes()
|
||||
}
|
||||
|
||||
@@ -567,16 +567,32 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
async openAdd() {
|
||||
this.unitOptions = await this.$businessTool.listUnit()
|
||||
this.$set(this.formData, "activityId", this.activityId)
|
||||
this.$set(this.formData, "eventId", this.eventId)
|
||||
this.$set(this.formData, "awardsMode", this.awardsMode)
|
||||
this.$set(this.formData, "identity", ['1'])
|
||||
this.$set(this.formData, "status", 2)
|
||||
this.$set(this.formData, "sex", "男")
|
||||
this.dialogVisible = true
|
||||
if (this.$refs['form']) {
|
||||
this.$refs['form'].resetFields()
|
||||
// 每次打开都重建表单数据,避免上一次录入内容残留
|
||||
this.formData = {
|
||||
activityId: this.activityId,
|
||||
eventId: this.eventId,
|
||||
awardsMode: this.awardsMode,
|
||||
identity: ['1'],
|
||||
status: 2,
|
||||
sex: "男",
|
||||
userid: "",
|
||||
username: "",
|
||||
loginname: "",
|
||||
mobile: "",
|
||||
unitId: "",
|
||||
unitname: "",
|
||||
unionId: "",
|
||||
unionname: ""
|
||||
}
|
||||
// 清空远程搜索候选,避免下拉仍显示上次搜索结果
|
||||
this.userOptions = []
|
||||
this.dialogVisible = true
|
||||
// resetFields 会把值还原成首次渲染时的快照,不能用于清空;这里只清校验提示
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs['form']) {
|
||||
this.$refs['form'].clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
async doAddUser() {
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
+10
-2
@@ -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("请完善必填信息")
|
||||
|
||||
Reference in New Issue
Block a user