From 33943a1d6219081fc9794657e57ae5f3cc054169 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Thu, 8 Oct 2026 18:00:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=88=86=E5=B7=A5=E4=BC=9A?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E8=BE=BE=E6=A0=87=E7=BB=9F=E8=AE=A1=E8=8F=9C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FitnessWalkStepRankingController.java | 2 + .../FitnessWalkStepStatisticsController.java | 6 +- ...nessWalkStepUnionStatisticsController.java | 70 +++++ ...FitnessWalkStepUnionStatisticsService.java | 33 ++ .../impl/FitnessWalkCommonServiceImpl.java | 28 +- ...essWalkStepUnionStatisticsServiceImpl.java | 286 ++++++++++++++++++ .../activity/fitnesswalk/stepRanking.html | 2 + .../fitnesswalk/stepUnionStatistics.html | 286 ++++++++++++++++++ 8 files changed, 706 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepUnionStatisticsController.java create mode 100644 src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepUnionStatisticsService.java create mode 100644 src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepUnionStatisticsServiceImpl.java create mode 100644 src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepUnionStatistics.html diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java index 3cbf1f5..4218893 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java @@ -302,6 +302,8 @@ public class FitnessWalkStepRankingController { entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); entityList.add(new ExcelExportEntity("单位", "unitname", 20)); entityList.add(new ExcelExportEntity("today".equals(mode) ? "今日步数" : "活动期间总步数", "total_steps", 20)); + // 与页面共用排行榜查询结果,今日榜导出也展示活动期间累计的达标天数。 + entityList.add(new ExcelExportEntity("达标天数", "standardsDays", 20)); try { String fileName = ("today".equals(mode) ? DateUtil.format(new Date(), "yyyy年MM月dd日") : "活动期间总") + "步数榜.xlsx"; ExportParams exportParams = new ExportParams(); diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java index 2382d72..0a8abee 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java @@ -88,9 +88,9 @@ public class FitnessWalkStepStatisticsController { @Param("year") Integer year, @Param("activityId") String activityId, @Param("activityDate") String[] activityDate, - @Param("unionId") String unionId, - @Param("unitId") String unitId) { - CndPlus cnd = CndPlus.create(); + String unionId, + String unitId) { + Cnd cnd = Cnd.NEW(); Sql sql = Sqls.create(getSql()); if (Lang.isNotEmpty(activityDate)) { cnd.and("date_format(al.applyDate,'%Y-%m-%d')", ">=", activityDate[0]); diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepUnionStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepUnionStatisticsController.java new file mode 100644 index 0000000..572de1e --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepUnionStatisticsController.java @@ -0,0 +1,70 @@ +package com.budwk.app.zhgh.activity.fitnesswalk.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.date.DateUtil; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepUnionStatisticsService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +/** + * 分工会活动达标统计,活动资格判断及人数汇总统一交由 service 处理。 + */ +@IocBean +@Ok("json") +@At("/platform/fitnessWalk/stepUnionStatistics") +public class FitnessWalkStepUnionStatisticsController { + + @Inject + private FitnessWalkStepUnionStatisticsService fitnessWalkStepUnionStatisticsService; + + /** + * 打开分工会活动达标统计页面,菜单链接与当前路由保持一致。 + */ + @At("") + @Ok("beetl:/platform/zhgh/activity/fitnesswalk/stepUnionStatistics.html") + @SaCheckPermission("fitnessWalk.stepUnionStatistics") + public void index() { + } + + /** + * 获取年份对应的活动选项,供页面多选;默认查询本年度。 + * + * @param year 活动开始年份,不传时使用当前年份 + * @return Result;data 为活动选项数组,选项的 value 是活动 ID,label 和 ruleLabel 用于展示名称及达标标准 + */ + @At + @SaCheckPermission("fitnessWalk.stepUnionStatistics") + public Result activityOptions(@Param("year") Integer year) { + try { + return Result.success(fitnessWalkStepUnionStatisticsService.getActivityOptions(year == null ? DateUtil.thisYear() : year)); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } + + /** + * 接收活动多选及分工会筛选,返回各活动的达标结果和所选范围的平均达标率。 + * + * @param year 活动开始年份,不传时使用当前年份 + * @param activityIds 活动 ID 数组,页面通过 JSON 数组提交;空数组表示该年份全部活动 + * @param unionId 分工会 ID,空值表示查询当前账号可查看的全部分工会 + * @return Result;data.activities 为实际统计活动,data.list 为分工会行, + * activityStatistics 按活动 ID 保存人数及比例,averageQualifiedRate 为所选活动平均达标率 + */ + @At + @Ok("json:full") + @SaCheckPermission("fitnessWalk.stepUnionStatistics") + public Result pageData(@Param("year") Integer year, + @Param("activityIds") String[] activityIds, + @Param("unionId") String unionId) { + try { + return Result.success(fitnessWalkStepUnionStatisticsService.getUnionStatistics(year == null ? DateUtil.thisYear() : year, activityIds, unionId)); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepUnionStatisticsService.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepUnionStatisticsService.java new file mode 100644 index 0000000..cb0e10d --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepUnionStatisticsService.java @@ -0,0 +1,33 @@ +package com.budwk.app.zhgh.activity.fitnesswalk.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkStep; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +/** + * 按计步活动统计分工会会员的达标人数、达标率及所选活动的平均达标率。 + */ +public interface FitnessWalkStepUnionStatisticsService extends BaseService { + + /** + * 查询指定年份开始、且当前账号可查看的计步活动。 + * + * @param year 活动开始年份,例如 2026 + * @return 活动选项列表;value 为活动 ID,label 为活动名称及日期,ruleLabel 为该活动的达标规则 + */ + List getActivityOptions(int year); + + /** + * 使用各活动自己的达标配置统计分工会,活动内按人员去重,活动之间分别计算。 + * + * @param year 活动开始年份,例如 2026 + * @param activityIds 所选活动 ID 数组;未选择时统计该年份全部可查看活动,已选择时严格限定所选活动 + * @param unionId 分工会 ID;管理员传空值时统计全部分工会,其他账号仅统计自己所属分工会 + * @return 统计结果 Map;activities 为实际统计的活动选项,list 为分工会汇总行。 + * 每行包含 unionId、unionName、memberNum、activityStatistics 和 averageQualifiedRate; + * activityStatistics 按活动 ID 保存 qualifiedNum(达标人数)及 qualifiedRate(百分比文本) + */ + NutMap getUnionStatistics(int year, String[] activityIds, String unionId); +} diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkCommonServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkCommonServiceImpl.java index cb70ef3..4a2c070 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkCommonServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkCommonServiceImpl.java @@ -547,6 +547,17 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl= @step - AND activityId=@activityId - AND $dateSql + activityId=@activityId + AND DATE(applyDate) >= @activityStartDate + AND DATE(applyDate) <= @activityEndDate + AND (@requireDailyQualification = 0 OR step >= @qualificationStep) GROUP BY userId ) AS over3k ON u.id = over3k.userId $condition """).setParam("activityId",activityId).setParam("step",0); + // 达标天数独立使用活动日期,切换到今日榜时也保留整个活动期间的累计结果。 + sql.setParam("activityStartDate", DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd")); + sql.setParam("activityEndDate", DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd")); + // 与计步抽奖页一致:配置资格天数时按每日资格步数计数,否则统计有记录的日期。 + // 历史活动的资格步数为空时按 0 处理;同一天重复记录通过 COUNT(DISTINCT DATE) 只计一次。 + sql.setParam("requireDailyQualification", activity.getLotteryQualificationDays() == null ? 0 : 1); + sql.setParam("qualificationStep", activity.getLotteryQualificationSteps() == null ? 0 : activity.getLotteryQualificationSteps()); + if ("today".equals(mode)) { sql.setVar("dateSql",new Static("DATE(applyDate) = '"+DateUtil.today()+"'")); // cnd.and("DATE( s.applyDate )", "=", DateUtil.today()); diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepUnionStatisticsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepUnionStatisticsServiceImpl.java new file mode 100644 index 0000000..e6e1987 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepUnionStatisticsServiceImpl.java @@ -0,0 +1,286 @@ +package com.budwk.app.zhgh.activity.fitnesswalk.service.impl; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.web.commons.auth.utils.AuthUtil; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; +import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; +import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkStep; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepUnionStatisticsService; +import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 分工会计步活动达标统计;使用当前会员人数作为分母,每个活动单独判断人员资格。 + */ +@IocBean(args = {"refer:dao"}) +public class FitnessWalkStepUnionStatisticsServiceImpl extends BaseServiceImpl implements FitnessWalkStepUnionStatisticsService { + private static final int ACTIVITY_QUERY_BATCH_SIZE = 100; + + @Inject + private WeAppCloudUtil weAppCloudUtil; + + /** + * @param dao 项目注入的 Nutz 数据访问对象,用于查询当前会员及活动步数记录 + */ + public FitnessWalkStepUnionStatisticsServiceImpl(Dao dao) { + super(dao); + } + + /** + * @param year 活动开始年份,允许查询 1970 年至当前年份 + * @return 当前账号可查看的活动选项,包含 ID、名称、日期和达标规则 + */ + @Override + public List getActivityOptions(int year) { + return queryYearActivities(year).stream().map(this::toActivityOption).collect(Collectors.toList()); + } + + /** + * @param year 活动开始年份 + * @param activityIds 所选活动 ID;空数组查询全部活动,非空数组只查询所选活动 + * @param unionId 分工会 ID;非管理员的查询范围由服务层限制为本人分工会 + * @return activities 为实际统计活动,list 为全部可查看分工会的活动达标矩阵;比例以两位小数百分比返回 + */ + @Override + public NutMap getUnionStatistics(int year, String[] activityIds, String unionId) { + List activities = resolveSelectedActivities(queryYearActivities(year), activityIds); + List activityOptions = activities.stream().map(this::toActivityOption).collect(Collectors.toList()); + if (activities.isEmpty()) { + return NutMap.NEW().addv("activities", activityOptions).addv("list", Collections.emptyList()); + } + + String effectiveUnionId = resolveUnionId(unionId); + // 分母和达标人数均限定为当前会员,避免未入会人员计入分子后出现超过 100% 的比例。 + Sql memberSql = Sqls.create(""" + SELECT + un.id AS unionId, + un.name AS unionName, + COALESCE(m.memberNum, 0) AS memberNum + FROM sys_union un + LEFT JOIN ( + SELECT unionId, COUNT(DISTINCT id) AS memberNum + FROM vw_user + WHERE member = 1 + GROUP BY unionId + ) m ON m.unionId = un.id + WHERE (@hasUnionFilter = 0 OR un.id = @unionId) + ORDER BY un.unionCode + """); + memberSql.setParam("hasUnionFilter", Strings.isBlank(effectiveUnionId) ? 0 : 1); + memberSql.setParam("unionId", Strings.sNull(effectiveUnionId)); + List rows = listMap(memberSql); + + Map> activityCounts = new LinkedHashMap<>(); + for (FitnessWalkActivity activity : activities) { + Map unionCounts = new LinkedHashMap<>(); + for (NutMap countRow : listMap(buildQualifiedCountSql(activity, effectiveUnionId))) { + unionCounts.put(countRow.getString("unionId"), countRow.getLong("qualifiedNum", 0)); + } + activityCounts.put(activity.get_id(), unionCounts); + } + + for (NutMap row : rows) { + String rowUnionId = row.getString("unionId"); + long memberNum = row.getLong("memberNum", 0); + long qualifiedTotal = 0; + NutMap activityStatistics = NutMap.NEW(); + for (FitnessWalkActivity activity : activities) { + long qualifiedNum = activityCounts.get(activity.get_id()).getOrDefault(rowUnionId, 0L); + qualifiedTotal += qualifiedNum; + activityStatistics.put(activity.get_id(), NutMap.NEW() + .addv("qualifiedNum", qualifiedNum) + .addv("qualifiedRate", formatRate(qualifiedNum, BigDecimal.valueOf(memberNum)))); + } + row.put("activityStatistics", activityStatistics); + // 每个活动使用同一分工会会员分母,因此先汇总人数再除以会员人数×活动数,等价于活动达标率的算术平均。 + // 直接使用原始人数计算,避免先对单活动比例四舍五入后再平均造成误差;零达标活动仍计入活动数。 + row.put("averageQualifiedRate", formatRate(qualifiedTotal, BigDecimal.valueOf(memberNum).multiply(BigDecimal.valueOf(activities.size())))); + } + return NutMap.NEW().addv("activities", activityOptions).addv("list", rows); + } + + /** + * 分批获取指定年份的活动及资格配置,避免云查询默认条数和公共活动缓存影响新统计页面。 + */ + private List queryYearActivities(int year) { + if (year < 1970 || year > DateUtil.thisYear()) { + throw new IllegalArgumentException("请选择有效的活动年份"); + } + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + long startTs = DateUtil.beginOfYear(calendar.getTime()).getTime(); + long endTs = DateUtil.endOfYear(calendar.getTime()).getTime(); + String effectiveUnionId = resolveUnionId(null); + + StringBuilder query = new StringBuilder("db.collection('activity').where({activityModel:"); + query.append(JSON.toJSONString(FitnessWalkMode.STEP_COUNT.getValue())) + .append(",startTime:_.gte(").append(startTs).append(").and(_.lte(").append(endTs).append("))"); + if (Strings.isNotBlank(effectiveUnionId)) { + query.append(",unionId:").append(JSON.toJSONString(effectiveUnionId)); + } + query.append("}).field({_id:true,masterActivityName:true,branchActivityName:true,activityModel:true,") + .append("startTime:true,endTime:true,lotteryQualificationDays:true,lotteryQualificationSteps:true,unionId:true})") + .append(".orderBy('startTime','desc').orderBy('_id','asc')"); + + Map activityMap = new LinkedHashMap<>(); + int skip = 0; + while (true) { + String sql = query + ".skip(" + skip + ").limit(" + ACTIVITY_QUERY_BATCH_SIZE + ").get()"; + JSONObject response = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql); + JSONArray data = response.getJSONArray("data"); + if (data == null) { + throw new IllegalArgumentException("活动列表获取失败,请稍后重试"); + } + for (Object item : data) { + FitnessWalkActivity activity = JSON.parseObject((String) item, FitnessWalkActivity.class); + // 历史活动缺失日期时不进入统计,防止空日期变成当前日期而误算达标人数。 + if (Strings.isNotBlank(activity.get_id()) && activity.getStartTime() != null && activity.getEndTime() != null + && activity.getStartTime() <= activity.getEndTime()) { + activityMap.put(activity.get_id(), activity); + } + } + if (data.size() < ACTIVITY_QUERY_BATCH_SIZE) { + break; + } + skip += ACTIVITY_QUERY_BATCH_SIZE; + } + return new ArrayList<>(activityMap.values()); + } + + /** + * 已选择活动时逐个校验年份和可见范围,失效选项报错,避免错误地扩大为全部活动。 + */ + private List resolveSelectedActivities(List availableActivities, String[] activityIds) { + if (activityIds == null || activityIds.length == 0) { + return availableActivities; + } + Set selectedIds = new LinkedHashSet<>(); + for (String activityId : activityIds) { + if (Strings.isBlank(activityId)) { + throw new IllegalArgumentException("所选活动无效,请重新选择"); + } + selectedIds.add(activityId); + } + Set availableIds = availableActivities.stream().map(FitnessWalkActivity::get_id).collect(Collectors.toSet()); + if (!availableIds.containsAll(selectedIds)) { + throw new IllegalArgumentException("所选活动不属于当前年份或不可查看,请刷新活动列表后重新选择"); + } + return availableActivities.stream().filter(activity -> selectedIds.contains(activity.get_id())).collect(Collectors.toList()); + } + + /** + * 管理员可筛选全部分工会;其他账号忽略外部传入范围,只统计本人所属分工会。 + */ + private String resolveUnionId(String unionId) { + if (AuthUtil.hasRoleOr("SYSADMIN", "SCHOOL_UNION_ADMIN")) { + return unionId; + } + String currentUnionId = SecurityUtil.getUnionId(); + if (Strings.isBlank(currentUnionId)) { + throw new IllegalArgumentException("当前账号未关联分工会,无法查询统计"); + } + return currentUnionId; + } + + /** + * 按计步抽奖页的三种资格口径统计人数:达标天数、累计步数或有步数记录。 + * 有资格天数时只计达到每日步数门槛的日期;同日重复上传保留最大步数,避免累计步数重复计算。 + */ + private Sql buildQualifiedCountSql(FitnessWalkActivity activity, String unionId) { + Sql sql = Sqls.create(""" + SELECT qualified.unionId, COUNT(DISTINCT qualified.userId) AS qualifiedNum + FROM ( + SELECT u.id AS userId, u.unionId + FROM vw_user u + LEFT JOIN ( + SELECT userId, DATE(applyDate) AS applyDay, MAX(step) AS daySteps + FROM fitness_walk_step + WHERE activityId = @activityId + AND DATE(applyDate) >= @startDate + AND DATE(applyDate) <= @endDate + AND (@hasDayRequirement = 0 OR step >= @qualificationSteps) + GROUP BY userId, DATE(applyDate) + ) daily ON daily.userId = u.id + WHERE u.member = 1 AND (@hasUnionFilter = 0 OR u.unionId = @unionId) + GROUP BY u.id, u.unionId + HAVING ( + (@hasDayRequirement = 1 AND COUNT(DISTINCT daily.applyDay) > 0 + AND COUNT(DISTINCT daily.applyDay) >= @qualificationDays) + OR (@hasDayRequirement = 0 AND @hasStepRequirement = 1 + AND COALESCE(SUM(daily.daySteps), 0) >= @qualificationSteps) + OR (@hasDayRequirement = 0 AND @hasStepRequirement = 0 + AND COALESCE(SUM(daily.daySteps), 0) > 0) + ) + ) qualified + GROUP BY qualified.unionId + """); + sql.setParam("activityId", activity.get_id()); + sql.setParam("startDate", DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd")); + sql.setParam("endDate", DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd")); + sql.setParam("hasDayRequirement", activity.getLotteryQualificationDays() == null ? 0 : 1); + sql.setParam("hasStepRequirement", activity.getLotteryQualificationSteps() == null ? 0 : 1); + sql.setParam("qualificationDays", activity.getLotteryQualificationDays() == null ? 0 : activity.getLotteryQualificationDays()); + sql.setParam("qualificationSteps", activity.getLotteryQualificationSteps() == null ? 0 : activity.getLotteryQualificationSteps()); + sql.setParam("hasUnionFilter", Strings.isBlank(unionId) ? 0 : 1); + sql.setParam("unionId", Strings.sNull(unionId)); + return sql; + } + + /** + * 名称同时包含主活动、分活动及日期,避免同名月度活动在多选和表头中无法区分。 + */ + private NutMap toActivityOption(FitnessWalkActivity activity) { + String name = Strings.sNull(activity.getMasterActivityName()); + if (Strings.isNotBlank(activity.getBranchActivityName()) && !activity.getBranchActivityName().equals(name)) { + name += " / " + activity.getBranchActivityName(); + } + String startDate = DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd"); + String endDate = DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd"); + String ruleLabel; + if (activity.getLotteryQualificationDays() != null) { + int steps = activity.getLotteryQualificationSteps() == null ? 0 : activity.getLotteryQualificationSteps(); + ruleLabel = "每日至少" + steps + "步,活动期间至少达标" + activity.getLotteryQualificationDays() + "天"; + } else if (activity.getLotteryQualificationSteps() != null) { + ruleLabel = "活动期间累计至少" + activity.getLotteryQualificationSteps() + "步"; + } else { + ruleLabel = "活动期间总步数大于0"; + } + return NutMap.NEW().addv("value", activity.get_id()).addv("name", name) + .addv("label", name + "(" + startDate + " 至 " + endDate + ")") + .addv("ruleLabel", ruleLabel); + } + + /** + * 统一输出两位小数百分比;没有会员或没有统计活动时返回 0.00%,避免除零。 + */ + private String formatRate(long qualifiedNum, BigDecimal denominator) { + if (denominator.signum() <= 0) { + return "0.00%"; + } + return BigDecimal.valueOf(qualifiedNum).multiply(BigDecimal.valueOf(100)) + .divide(denominator, 2, RoundingMode.HALF_UP).toPlainString() + "%"; + } +} diff --git a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html index 3b06755..b51b0d4 100644 --- a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html +++ b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html @@ -139,6 +139,8 @@ layout("/layouts/platform.html"){ {label: '分工会', prop: 'unionname', sortable: true}, {label: '单位', prop: 'unitname'}, {label: '步数', prop: 'total_steps', sortable: true}, + // 今日榜和总榜均展示所选活动期间累计的达标天数。 + {label: '达标天数', prop: 'standardsDays', sortable: true}, ], } }, diff --git a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepUnionStatistics.html b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepUnionStatistics.html new file mode 100644 index 0000000..7ae5080 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepUnionStatistics.html @@ -0,0 +1,286 @@ + + +
+ + + +
+ +