This commit is contained in:
Paidax
2025-04-29 16:51:08 +08:00
parent c08670699a
commit 48c533bc09
8 changed files with 415 additions and 1 deletions
@@ -3,14 +3,26 @@ package com.budwk.app.module.activity.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.page.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.module.activity.models.ActGroup;
import com.budwk.app.module.activity.models.ActUser;
import com.budwk.app.module.activity.service.ActGroupService;
import com.budwk.app.module.activity.service.ActUserService;
import com.budwk.app.module.activity.vo.ActGroupVo;
import com.budwk.app.web.commons.slog.annotation.SLog;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
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.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.ArrayList;
import java.util.List;
/**
* @version 1.0
* @Author zzr
@@ -28,6 +40,8 @@ public class ActGroupController {
private Dao dao;
@Inject
private ActUserService actUserService;
@Inject
private ActGroupService actGroupService;
@At("")
@SaCheckPermission("activity.group")
@@ -41,4 +55,42 @@ public class ActGroupController {
public Result pageData(PageForm pageForm, String activityId){
return null;
}
/**
* 查询签到人员
* @param activityId
* @return
*/
@At
@SaCheckPermission("activity.group")
public Result querySingUpData(String activityId){
Sql sql = Sqls.create("""
SELECT
act.id AS activityId,
act.title AS activityName,
COUNT(u.id) AS signUpNum,
SUM(CASE WHEN u.sex = '男' THEN 1 ELSE 0 END) AS manNum,
SUM(CASE WHEN u.sex = '女' THEN 1 ELSE 0 END) AS womanNum
FROM
act_user u
LEFT JOIN activity act ON act.id = u.activityId
WHERE
u.isSignIn = 1 AND u.activityId = @activityId
""").setParam("activityId", activityId);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
NutMap map = (NutMap) sql.getResult();
return Result.success().addData(map);
}
@At
@SaCheckPermission("activity.group")
@SLog(tag = "活动分组", msg = "随机分组")
public Result groupSetting(ActGroupVo actGroupVo){
// 查询所有签到的用户,按照分组参数进行分组
actGroupService.groupSetting(actGroupVo);
return Result.success();
}
}
@@ -0,0 +1,35 @@
package com.budwk.app.module.activity.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
/**
* @version 1.0
* @Author zzr
* @name:ActUnGroupUser
* @Date 2025/4/29 14:12
* @注释 :未分组人员
*/
@Data
@Table("act_un_group_user")
@Comment("活动未分组人员")
@EqualsAndHashCode(callSuper = true)
@TableMeta("{'mysql-charset':'utf8mb4'}")
public class ActUnGroupUser extends BaseModel {
@Id
@Comment("ID")
private Long id;
@Column
@Comment("活动ID")
@ColDefine(customType = "bigint")
private Long activityId;
@Column
@Comment("用户ID")
@ColDefine(customType = "bigint")
private Long userId;
}
@@ -2,6 +2,7 @@ package com.budwk.app.module.activity.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.module.activity.models.ActGroup;
import com.budwk.app.module.activity.vo.ActGroupVo;
/**
* @ClassName ActGroupService
@@ -11,4 +12,7 @@ import com.budwk.app.module.activity.models.ActGroup;
* @Description TODO
*/
public interface ActGroupService extends BaseService<ActGroup> {
void groupSetting(ActGroupVo groupVo);
}
@@ -2,10 +2,24 @@ package com.budwk.app.module.activity.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.module.activity.models.ActGroup;
import com.budwk.app.module.activity.models.ActGroupUser;
import com.budwk.app.module.activity.models.ActUnGroupUser;
import com.budwk.app.module.activity.models.ActUser;
import com.budwk.app.module.activity.service.ActGroupService;
import com.budwk.app.module.activity.vo.ActGroupVo;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @ClassName ActGroupServiceImpl
* @Author JyuHsin
@@ -19,4 +33,98 @@ public class ActGroupServiceImpl extends BaseServiceImpl<ActGroup> implements Ac
public ActGroupServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void groupSetting(ActGroupVo groupVo) {
// 删除已经存在的分组信息
dao().clear(ActGroup.class, Cnd.where(ActGroup::getActivityId, "=", groupVo.getActivityId()));
dao().clear(ActGroupUser.class, Cnd.where(ActGroupUser::getActivityId, "=", groupVo.getActivityId()));
dao().clear(ActUnGroupUser.class, Cnd.where(ActUnGroupUser::getActivityId, "=", groupVo.getActivityId()));
// 查询报名了并且已经完成签到的人员
List<ActUser> signUpUserList = dao().query(ActUser.class, Cnd.where(ActUser::getActivityId, "=", groupVo.getActivityId())
.and(ActUser::getIsSignIn, "=", 1));
int size = signUpUserList.size();
Integer oneGroupLimitUserNum = groupVo.getOneGroupLimitUserNum();
int result = Math.floorDiv(size, oneGroupLimitUserNum);
// 初始化分组
List<List<ActUser>> groups = Collections.nCopies(result, new ArrayList<>());
// 分离男女用户
Map<String, List<ActUser>> genderMap = signUpUserList.stream()
.collect(Collectors.groupingBy(user -> "男".equals(user.getSex()) ? "男" : "女"));
List<ActUser> males = genderMap.getOrDefault("男", Collections.emptyList());
List<ActUser> females = genderMap.getOrDefault("女", Collections.emptyList());
// 随机化列表
Collections.shuffle(males);
Collections.shuffle(females);
// 尝试分配
List<ActUser> unassignedUsers = new ArrayList<>();
int maleIndex = 0, femaleIndex = 0;
while (maleIndex < males.size() || femaleIndex < females.size()) {
boolean assigned = false;
for (List<ActUser> group : groups) {
if (group.size() < groupVo.getOneGroupLimitUserNum()) {
int currentMaleCount = (int) group.stream().filter(u -> "男".equals(u.getSex())).count();
int currentFemaleCount = (int) group.stream().filter(u -> "女".equals(u.getSex())).count();
if (maleIndex < males.size() && (groupVo.getManUserNum() == -1 || currentMaleCount < groupVo.getManUserNum())) {
group.add(males.get(maleIndex++));
assigned = true;
} else if (femaleIndex < females.size() && (groupVo.getWomanUserNum() == -1 || currentFemaleCount < groupVo.getWomanUserNum())) {
group.add(females.get(femaleIndex++));
assigned = true;
}
}
}
if (!assigned) {
break;
}
}
// 收集未分配的用户
unassignedUsers.addAll(males.subList(maleIndex, males.size()));
unassignedUsers.addAll(females.subList(femaleIndex, females.size()));
// 往组别表里面添加记录
List<ActGroup> groupList = IntStream.range(0, groups.size())
.mapToObj(index -> {
ActGroup actGroup = new ActGroup();
actGroup.setActivityId(groupVo.getActivityId());
actGroup.setGroupName("第" + (index + 1) + "组");
List<ActGroupUser> userList = groups.get(index).stream().map(v -> {
ActGroupUser user = new ActGroupUser();
user.setUserId(v.getId());
user.setActivityId(groupVo.getActivityId());
return user;
}).collect(Collectors.toList());
actGroup.setGroupUsers(userList);
return actGroup;
}).toList();
for (ActGroup group : groupList) {
dao().insertWith(group, "groupUsers");
}
// 添加未能正确分组的人员
List<ActUnGroupUser> userList = unassignedUsers.stream().map(v -> {
ActUnGroupUser user = new ActUnGroupUser();
user.setUserId(v.getUserId());
user.setActivityId(groupVo.getActivityId());
return user;
}).toList();
dao().insert(userList);
}
}
@@ -0,0 +1,26 @@
package com.budwk.app.module.activity.vo;
import lombok.Data;
/**
* @version 1.0
* @Author zzr
* @name:ActGroupVo
* @Date 2025/4/29 11:12
* @注释
*/
@Data
public class ActGroupVo {
// 活动ID
private Long activityId;
// 单个小组限制人数
private Integer oneGroupLimitUserNum;
// 限制男用户人数,设-1为不限制
private Integer manUserNum;
// 限制女用户人数,设-1为不限制
private Integer womanUserNum;
}
@@ -0,0 +1,100 @@
const GROUP_SETTING = {
template: /*language=HTML*/ `
<el-dialog title="设定随机分组参数" :visible.sync="groupingDialog" width="50%">
<el-form :model="form" :rules="formRules" label-width="150px" ref="formRef">
<el-row :gutter="10">
<el-col :span="24">
<el-form-item label="活动名称" prop="activityName">
<el-input v-model="form.activityName" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="活动签到总人数" prop="signUpNum">
<el-input v-model="form.signUpNum" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="签到男用户数" prop="manNum">
<el-input v-model="form.manNum" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="签到女用户数" prop="womanNum">
<el-input v-model="form.womanNum" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="单个小组限制总人数" prop="oneGroupLimitUserNum">
<el-input-number style="width: 100%" v-model="form.oneGroupLimitUserNum"
placeholder="请输入单个小组限制总人数"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="男用户限制人数" prop="manUserNum">
<el-input-number style="width: 100%" v-model="form.manUserNum"
placeholder="请输入男用户限制人数"></el-input-number>
<span style="color: #bc2a25">设-1则标识不限制</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="女用户限制人数" prop="womanUserNum">
<el-input-number style="width: 100%" v-model="form.womanUserNum"
placeholder="请输入女用户限制人数"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="groupingDialog = false">取消</el-button>
<el-button type="primary" @click="onGroupingSubmit">分组</el-button>
</span>
</el-dialog>
`,
data(){
return{
groupingDialog: false,
form: {
activityId: '',
activityName: '',
groupNum: 0,
oneGroupLimitUserNum: 0,
manUserNum: 0,
womanUserNum: 0
},
formRules: {
oneGroupLimitUserNum: [{ required: true, message: '请输入单个小组限制总人数', trigger: ['blur', 'change'] }],
manUserNum: [{ required: true, message: '请输入男用户限制人数', trigger: ['blur', 'change'] }],
womanUserNum: [{ required: true, message: '请输入女用户限制人数', trigger: ['blur', 'change'] }]
},
}
},
methods: {
async onOpen(activityId){
const resp = await $.post('/platform/activity/group/querySingUpData', {activityId})
if (resp.code === 0) {
this.form = {...resp.data}
this.groupingDialog = true
}
},
async onGroupingSubmit(){
console.log(this.form)
const loading = this.$loading({
lock: true,
text: '分组确认中,请稍后...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const resp = await $.post('/platform/activity/group/groupSetting', this.form)
loading.close()
if (resp.code === 0) {
this.groupingDialog = false
this.$refs.formRef.resetFields()
this.$message.success(resp.msg)
} else {
this.$message.error('操作失败')
}
}
}
}
@@ -0,0 +1,90 @@
<!--#
layout("/layouts/platform_1.html"){
#-->
<div id="app">
<el-card shadow="never">
<div class="search">
<div class="search-item">
<div class="search-item-label">年度</div>
<div class="search-item-option">
<el-date-picker
:clearable="false"
placeholder="选择年"
style="width: 100%"
type="year"
@change="getActivityList"
v-model="pageForm.year"
value-format="yyyy"
></el-date-picker>
</div>
</div>
<div class="search-item">
<div class="search-item-label">所属活动</div>
<div class="search-item-option">
<el-select @change="doSearch"
placeholder="请选择活动查询"
style="width: 100%;"
v-model="pageForm.activityId">
<el-option :label="item.title" :value="item.id"
v-for="item in activityList"></el-option>
</el-select>
</div>
</div>
</div>
</el-card>
<el-card shadow="never" class="mt-3">
<el-row type="flex" class="mb-3" style="justify-content: flex-end">
<el-button icon="el-icon-plus" type="primary" size="small" @click="openGrouping">开始分组</el-button>
</el-row>
<el-table v-loading="tableLoading" show-overflow-tooltip :data="tableData">
</el-table>
</el-card>
<group-setting ref="groupSettingRef"></group-setting>
</div>
<script>
<!--#include('groupSetting.js'){}#-->
new Vue({
el: "#app",
mixins: [initTableMixins],
components: {
"group-setting": GROUP_SETTING
},
data() {
return {
pageForm: {
year: new Date().getFullYear() + ""
},
activityList: []
}
},
methods: {
openGrouping(){
if (!this.pageForm.activityId) {
this.$message.error("请选择活动")
return
}
this.$refs.groupSettingRef.onOpen(this.pageForm.activityId)
},
async getActivityList(){
const resp = await $.post('/platform/activity/user/getActivityList', {
year: this.pageForm.year
})
if (resp.code === 0 && resp.data && resp.data.length > 0) {
this.activityList = resp.data
this.$set(this.pageForm, 'activityId', this.activityList[0].id)
} else {
this.$set(this.pageForm, 'activityId', '')
}
// this.pageData()
}
},
created() {
this.getActivityList()
}
})
</script>
<!--#
}
#-->
@@ -388,7 +388,6 @@ layout("/layouts/platform_1.html"){
this.$set(this.pageForm, 'activityId', this.activityList[0].id)
} else {
this.$set(this.pageForm, 'activityId', '')
console.log(resp.msg)
}
this.pageData()
}