From 398dddc370942cbc587826b72241e656b867b815 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Fri, 23 Oct 2026 16:13:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=93=E8=82=B2=E6=B4=BB=E5=8A=A8=E6=88=90?= =?UTF-8?q?=E7=BB=A9=E7=BB=9F=E8=AE=A1=EF=BC=8C=E5=8D=8F=E4=BC=9A=E7=BB=8F?= =?UTF-8?q?=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/sys/controller/SysUnitController.java | 29 ++- .../sys/services/impl/SysUnitServiceImpl.java | 34 ++-- .../ActivityBasicUnitController.java | 42 ++++- .../service/ActivityBasicUnitService.java | 19 ++ .../impl/ActivityBasicUnitServiceImpl.java | 93 ++++++++++ ...tivitySportsScoreStatisticsController.java | 75 ++------ ...ySportsStatisticsTypeExportController.java | 65 ++----- .../sports/service/ActivitySportsService.java | 11 ++ .../impl/ActivitySportsServiceImpl.java | 68 +++++++ .../impl/ClubPayRecordServiceImpl.java | 17 +- .../UnionReimburseApplyController.java | 4 +- .../service/UnionReimburseService.java | 5 +- .../impl/UnionReimburseServiceImpl.java | 20 +- .../module/activity/score/scoreTopEight.vue | 18 +- .../views/platform/sys/unit/index.html | 55 ++++-- .../views/platform/sys/unit/unitManage.js | 172 +++++++++++------- .../zhgh/activity/basic/unit/index.html | 71 ++++++-- .../activity/sports/ActivityPrize/index.html | 2 +- .../sports/ActivityResults/index.html | 34 +++- .../sports/scoreStatistics/index.html | 13 +- .../unionReimburse/apply/index.html | 12 +- 21 files changed, 608 insertions(+), 251 deletions(-) create mode 100644 src/main/java/com/budwk/app/zhgh/activity/basic/service/ActivityBasicUnitService.java create mode 100644 src/main/java/com/budwk/app/zhgh/activity/basic/service/impl/ActivityBasicUnitServiceImpl.java diff --git a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java index 1f862dcb..d0972e46 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java @@ -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> 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(); } } diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java index f805b307..2c2d752e 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysUnitServiceImpl.java @@ -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 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)); } /** diff --git a/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicUnitController.java b/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicUnitController.java index 4b2fb483..75e20a85 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicUnitController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicUnitController.java @@ -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 list = baseService.dao().query(ActivityBasicUnit.class, cnd); + Set unitIds = list.stream().map(ActivityBasicUnit::getId).collect(Collectors.toSet()); + String virtualRootId = "_activity_unit_root_"; List> 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 extra = new HashMap<>(); + extra.put("unitTypeCode", unit.getUnitTypeCode()); + nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i).setExtra(extra)); } - List> treeList = TreeUtil.build(nodeList, StrUtil.blankToDefault(pid, "0")); + List> 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 child(String parentId) { List units = dao.query(ActivityBasicUnit.class, Cnd.where("parentId", "=", parentId)); units.forEach(unit -> { diff --git a/src/main/java/com/budwk/app/zhgh/activity/basic/service/ActivityBasicUnitService.java b/src/main/java/com/budwk/app/zhgh/activity/basic/service/ActivityBasicUnitService.java new file mode 100644 index 00000000..cde2556c --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/activity/basic/service/ActivityBasicUnitService.java @@ -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 { + + /** + * 将系统单位同步到活动单位,不删除活动单位,不覆盖已有活动单位的工会归属。 + * + * @return NutMap;insertCount 为新增单位数,updateCount 为更新基础资料的已有单位数, + * pendingUnionCount 为新增单位中未能匹配活动工会、需要配置归属的数量 + */ + NutMap syncUnits(); +} diff --git a/src/main/java/com/budwk/app/zhgh/activity/basic/service/impl/ActivityBasicUnitServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/basic/service/impl/ActivityBasicUnitServiceImpl.java new file mode 100644 index 00000000..8b44385e --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/activity/basic/service/impl/ActivityBasicUnitServiceImpl.java @@ -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 implements ActivityBasicUnitService { + + public ActivityBasicUnitServiceImpl(Dao dao) { + super(dao); + } + + /** + * 无请求参数,按 sys_unit.id 同步全部系统单位;返回新增数、更新数和新增单位待配置活动工会数。 + * 已有记录只更新基础字段,连原有的空工会归属也保留,避免覆盖活动模块人工维护的组成单位关系。 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public NutMap syncUnits() { + List sourceUnits = dao().query(Sys_unit.class, Cnd.NEW()); + Set existingIds = dao().query(ActivityBasicUnit.class, Cnd.NEW()).stream() + .map(ActivityBasicUnit::getId).collect(Collectors.toSet()); + Set 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()); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsScoreStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsScoreStatisticsController.java index bfc31df3..632e17d2 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsScoreStatisticsController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsScoreStatisticsController.java @@ -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 schoolList = baseService.dao().query(ActivitySchool.class, Cnd.where("YEAR(applyEndTime)", "=", year)); + // 活动名称下拉只列出正式活动,与年度统计口径一致,过滤掉活动模板(模板标记 + 工作模板表双重判断)。 + List 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 unionList = sysUnionService.query(); - List activitySchoolList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)); + // 年度统计只取正式活动:既排除模板标记,也排除工作模板表中登记的活动(历史模板未回填标记位)。 + List 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 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 resultMap = new ArrayList<>(); - - // int isMenWomen = sex.equals("男") ? 1 : 2; - - Dao dao = sysUnionService.dao(); - List unionList = sysUnionService.query(); - - List activityList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)); - List activityIdList = activityList.stream().map(ActivitySchool::getId).collect(Collectors.toList()); - List activityNameList = activityList.stream().map(ActivitySchool::getName).collect(Collectors.toList()); - - List 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 list = sql.getList(NutMap.class); - - unionList.forEach(u -> { - NutMap unionMap = Lang.obj2nutmap(u); - AtomicReference 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"); diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsStatisticsTypeExportController.java b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsStatisticsTypeExportController.java index 9651f920..dfd0be4b 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsStatisticsTypeExportController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsStatisticsTypeExportController.java @@ -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 resultMap = new ArrayList<>(); - List unionList = sysUnionService.query(); - - List activityList = baseService.dao().query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)); - List activityIdList = activityList.stream().map(ActivitySchool::getId).collect(Collectors.toList()); - List activityNameList = activityList.stream().map(ActivitySchool::getName).collect(Collectors.toList()); - - List 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 list = baseService.listMap(sql); - unionList.forEach(u -> { - NutMap unionMap = Lang.obj2nutmap(u); - AtomicReference 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 resultMap = annualResults.getList("score", Map.class); + List 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 unionList = sysUnionService.query(); - List activitySchoolList = dao.query(ActivitySchool.class, Cnd.where("YEAR(applyStartTime)", "=", year)); + // 与页面查询保持一致,导出时同样过滤掉活动模板(标记位 + 工作模板表双重判断)。 + List 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 labelList = activitySchoolList.stream().map(v -> v.getName()).collect(Collectors.toList()); labelList.add(0, "分工会"); diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsService.java b/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsService.java index 071f66b0..e1406c73 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsService.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsService.java @@ -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 { */ 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); } diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsServiceImpl.java index dd8ec1af..796a9169 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsServiceImpl.java @@ -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 i Cnd.where("id", "=", id)); } + /** + * 按活动类型恢复老项目的年度积分口径,查询和导出使用同一份计算结果。 + * + * @param isMenWomen 1 为男子年度积分,2 为女子年度积分 + * @param year 按活动报名开始时间筛选的年度 + * @return label 为列名列表,score 为以活动名称、分工会、总分作为字段的积分行列表 + */ + @Override + public NutMap getAnnualGenderResults(Integer isMenWomen, Integer year) { + ArrayList resultMap = new ArrayList<>(); + List unionList = dao().query(Sys_union.class, null); + // 年度统计只取正式活动:既排除模板标记,也排除工作模板表中登记的活动(历史模板未回填标记位)。 + List 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 activityIdList = activityList.stream().map(ActivitySchool::getId).collect(Collectors.toList()); + List activityNameList = activityList.stream().map(ActivitySchool::getName).collect(Collectors.toList()); + List 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 list = listMap(sql); + + unionList.forEach(u -> { + NutMap unionMap = Lang.obj2nutmap(u); + AtomicReference 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 { diff --git a/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubPayRecordServiceImpl.java b/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubPayRecordServiceImpl.java index 0ae6649b..7c1a8d64 100644 --- a/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubPayRecordServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubPayRecordServiceImpl.java @@ -198,15 +198,26 @@ public class ClubPayRecordServiceImpl extends BaseServiceImpl 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 clubIds = listMap(scopeSql).stream() .map(row -> row.getString("clubId")) .filter(StrUtil::isNotBlank) diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java index 6829946c..5e6cb6e1 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java @@ -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) diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java index 2d272ba0..00ee4c7c 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java @@ -63,9 +63,10 @@ public interface UnionReimburseService extends BaseService { Result getBudgetBalance(String reimburseFundSource, String clubId, String unionId); /** - * 查询未解散社团,供社团经费报销选择。 + * 查询当前登录人已加入且未解散的协会,供协会经费报销选择和申请范围校验。 + * 无请求参数,用户身份取自服务端登录会话。 * - * @return 全部未解散社团的ID、名称和编码列表 + * @return 协会列表,每项包含 id、clubName、clubCode;未加入协会时返回空列表,同一协会只返回一次 */ List listReimburseClubs(); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java index 9b95fff6..af13e4a6 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java @@ -894,6 +894,10 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl 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 i } /** - * 查询未解散社团,社团选择和余额查询不按负责人归属过滤。 + * 查询当前登录人已加入且未解散的协会,供所属协会选择、余额查询和申请校验共用。 + * 无请求参数,会员归属使用服务端当前登录用户ID,不接受前端指定其他用户。 * - * @return 全部未解散社团的ID、名称和编码 + * @return 协会列表,每项包含 id、clubName、clubCode;未加入协会时返回空列表,同一协会只返回一次 */ @Override public List listReimburseClubs() { @@ -1313,8 +1318,13 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl 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 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())) { diff --git a/src/main/resources/static/components/module/activity/score/scoreTopEight.vue b/src/main/resources/static/components/module/activity/score/scoreTopEight.vue index aff0847a..d8f0a51a 100644 --- a/src/main/resources/static/components/module/activity/score/scoreTopEight.vue +++ b/src/main/resources/static/components/module/activity/score/scoreTopEight.vue @@ -11,7 +11,7 @@ - @@ -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 { diff --git a/src/main/resources/views/platform/sys/unit/index.html b/src/main/resources/views/platform/sys/unit/index.html index 22d5377d..4bd3a4ce 100644 --- a/src/main/resources/views/platform/sys/unit/index.html +++ b/src/main/resources/views/platform/sys/unit/index.html @@ -77,6 +77,7 @@ layout("/layouts/platform.html"){
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 diff --git a/src/main/resources/views/platform/sys/unit/unitManage.js b/src/main/resources/views/platform/sys/unit/unitManage.js index 956d3988..b09ebb5e 100644 --- a/src/main/resources/views/platform/sys/unit/unitManage.js +++ b/src/main/resources/views/platform/sys/unit/unitManage.js @@ -11,7 +11,7 @@ const UNIT_MANAGE_TEMPLATE = { 新建单位 - + @@ -75,8 +75,8 @@ const UNIT_MANAGE_TEMPLATE = { - 取 消 - 确 定 + 取 消 + 确 定
@@ -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) + }) } } diff --git a/src/main/resources/views/platform/zhgh/activity/basic/unit/index.html b/src/main/resources/views/platform/zhgh/activity/basic/unit/index.html index 40ea9a97..1559b671 100644 --- a/src/main/resources/views/platform/zhgh/activity/basic/unit/index.html +++ b/src/main/resources/views/platform/zhgh/activity/basic/unit/index.html @@ -9,10 +9,10 @@ layout("/layouts/platform.html"){
搜索 + 更新单位
@@ -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("加载单位失败,请稍后重试") + }) } }) diff --git a/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html b/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html index d582b9f7..f06926e7 100644 --- a/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html +++ b/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html @@ -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() } diff --git a/src/main/resources/views/platform/zhgh/activity/sports/ActivityResults/index.html b/src/main/resources/views/platform/zhgh/activity/sports/ActivityResults/index.html index 1255c3eb..fbcc2121 100644 --- a/src/main/resources/views/platform/zhgh/activity/sports/ActivityResults/index.html +++ b/src/main/resources/views/platform/zhgh/activity/sports/ActivityResults/index.html @@ -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() { diff --git a/src/main/resources/views/platform/zhgh/activity/sports/scoreStatistics/index.html b/src/main/resources/views/platform/zhgh/activity/sports/scoreStatistics/index.html index 4b78871a..a7fcad94 100644 --- a/src/main/resources/views/platform/zhgh/activity/sports/scoreStatistics/index.html +++ b/src/main/resources/views/platform/zhgh/activity/sports/scoreStatistics/index.html @@ -73,7 +73,8 @@ layout("/layouts/platform.html"){ - 导出成绩excel + 导出成绩excel @@ -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}) diff --git a/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html b/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html index c7f87065..f610381a 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html @@ -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("请完善必填信息")