This commit is contained in:
那些花儿
2025-04-30 08:41:52 +08:00
15 changed files with 524 additions and 62 deletions
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.result.Result;
import com.budwk.app.client.auth.models.Client_user;
import com.budwk.app.client.auth.service.ClientAuthService;
import com.budwk.app.client.auth.vo.ClientLoginBody;
import com.budwk.app.sys.services.SysMsgService;
@@ -88,6 +89,15 @@ public class ClientAuthController {
}
}
@At("/info")
@Ok("json")
@SaCheckLogin(type = StpClientUtil.TYPE)
public Result info() {
Client_user user = dao.fetch(Client_user.class, StpClientUtil.STP.getLoginIdAsLong());
return Result.success(user);
}
/**
* 退出登录
*/
@@ -69,10 +69,20 @@ public class ClientGroupController {
return Result.success(list);
}
/**
* 查询自己在那个团队
* @param activityId
* @return
*/
@At("/info/?")
@SaCheckLogin(type = StpClientUtil.TYPE)
public Result info(Long activityId) {
Sql sql = Sqls.create("select * from act_group where activityId=@activityId").setParam("activityId", activityId);
ActGroupUser groupUser = dao.fetch(ActGroupUser.class, Cnd.where(ActGroupUser::getUserId, "=", StpClientUtil.STP.getLoginIdAsLong())
.and(ActGroupUser::getActivityId, "=", activityId));
if(Lang.isEmpty(groupUser)){
return Result.error("您还没加入任何团队");
}
Sql sql = Sqls.create("select * from act_group where id=@id").setParam("id", groupUser.getGroupId());
sql.setCallback(Sqls.callback.map());
NutMap map = (NutMap) dao.execute(sql).getResult();
Sql sqlGroupUser = Sqls.create("""
@@ -85,6 +95,8 @@ public class ClientGroupController {
`act_group_user` t1
LEFT JOIN act_user t2 ON t1.userId = t2.userId
WHERE t1.groupId=@groupId
ORDER BY
t1.isLeader DESC
""").setParam("groupId", map.getString("id"));
List<NutMap> groupUsers = clientActivityService.listMap(sqlGroupUser);
map.put("groupUsers", groupUsers);
@@ -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();
}
}
@@ -36,6 +36,11 @@ public class ActGroup extends BaseModel {
@ColDefine(type = ColType.VARCHAR, width = 255)
private String groupName;
@Column
@Comment("队伍排序")
@ColDefine(type = ColType.INT)
private Integer sort;
@Column
@Comment("队伍总得分")
@ColDefine(customType = "decimal", width = 10, precision = 2)
@@ -28,6 +28,11 @@ public class ActGroupUser extends BaseModel {
@ColDefine(customType = "bigint")
private Long groupId;
@Column
@Comment("活动ID")
@ColDefine(customType = "bigint")
private Long activityId;
@Column
@Comment("用户id")
@ColDefine(customType = "bigint")
@@ -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()
}
+6 -3
View File
@@ -1,10 +1,13 @@
<script>
import { setupRouterInterceptor, getDefaultWhiteList } from './utils/routerInterceptor'
import {setupRouterInterceptor, getDefaultWhiteList} from './utils/routerInterceptor'
export default {
globalData: {
user: null
},
onLaunch: function () {
console.log('App Launch')
// 初始化路由拦截器
setupRouterInterceptor(getDefaultWhiteList())
},
@@ -46,7 +49,7 @@ html, body {
overflow-x: hidden;
}
uni-page-body{
uni-page-body {
height: 100%;
}
+21 -24
View File
@@ -1,6 +1,6 @@
<template>
<view class="page-container">
<view v-if="actList&&actList?.length>0">
<view v-if="team">
<t-sticky>
<t-dropdown-menu class="top-dropdown-menu">
<t-dropdown-item :options="actOptions" :value="pageForm.actId" @change="actChange"/>
@@ -17,7 +17,7 @@
<div class="team-basic-info">
<div class="team-name">{{ team.groupName }}</div>
<div class="team-meta">创建于 {{moment(team.createdAt).format("YYYY-MM-DD HH:mm")}}</div>
<div class="team-meta">创建于 {{ moment(team.createdAt).format("YYYY-MM-DD HH:mm") }}</div>
</div>
</div>
@@ -41,9 +41,9 @@
</div>
<!-- 项目报名按钮 -->
<div class="action-section" v-if="team.isSelfLeader">
<div class="action-section" >
<t-button block theme="danger" size="large" class="register-btn" @click="goToEventRegistration">
参与项目报名
{{ team.isSelfLeader ? '参与项目报名' : '查看项目报名' }}
</t-button>
</div>
@@ -103,9 +103,20 @@
</div>
</div>
</view>
<view v-else>暂无团队</view>
</view>
<view v-else style="background-color: #FFFFFF">
<view >
<t-result>
<template #image>
<t-image src="https://tdesign.gtimg.com/mobile/demos/result1.png" class="external-class-image" />
</template>
<template #title>
<div>暂无团队</div>
</template>
</t-result>
</view>
</view>
</view>
<t-dialog
v-model:visible="leaderConfirmVisible"
title="申请成为队长"
@@ -126,8 +137,6 @@
</div>
</template>
</t-dialog>
</template>
<script setup>
@@ -155,21 +164,7 @@ const pageForm = reactive({
const teamId = ''
let team = ref({
id: teamId,
name: 'xx队',
logoUrl: null,
createdDate: '2023-10-15',
eventId: '',
eventName: '',
memberCount: 12,
rank: 5,
score: 256,
description: 'xxxxxxxxxxxxxxxxxxx',
// 团队成员
groupUsers: []
});
let team = ref(null);
// 申请成为队长弹框提示
const leaderConfirmVisible = ref(false)
@@ -190,6 +185,7 @@ const onLeaderConfirm = async () => {
pageForm.groupId = team.value.id
const {code, msg} = await useRequest().post('/client/group/apply-leader/' + team.value.id);
if (code === 0) {
await getTeamInfo();
Toast({message: '申请成功', theme: 'success'});
leaderConfirmVisible.value = false
} else {
@@ -205,13 +201,14 @@ const goToEventRegistration = () => {
uni.navigateTo({url: '/pages/activity/team-form/index?id=' + pageForm.actId})
};
// 根据活动查询队伍信息
const getTeamInfo = async () => {
try {
const {code, msg, data} = await useRequest().post('/client/group/info/' + pageForm.actId);
if (code === 0) {
team.value = data
console.log(team.value.groupUsers.find(t=>t.userId===uni.getStorageSync('userId')))
console.log()
} else {
Toast({message: msg, theme: 'error'});
}
+49 -33
View File
@@ -1,4 +1,5 @@
import {Dialog} from "tdesign-mobile-vue";
import {useRequest} from "@/utils/request";
/**
* 路由拦截器配置
@@ -41,44 +42,61 @@ function addNavigationInterceptors(whiteList) {
* @param {Array} whiteList - 白名单页面列表
* @returns {Object|boolean} - 继续导航或阻止导航
*/
function routeInterceptor(args, whiteList) {
async function routeInterceptor(args, whiteList) {
// 获取目标路径
const token = uni.getStorageSync('token')
const targetUrl = args.url
// 检查目标路径是否在白名单中
const isInWhiteList = whiteList.some(path => {
return targetUrl.startsWith(path) || targetUrl === path
})
let isInWhiteList = targetUrl === '/'
if (!isInWhiteList) {
// 检查目标路径是否在白名单中
isInWhiteList = whiteList.some(path => {
return targetUrl.startsWith(path) || targetUrl === path
})
}
console.log('路由拦截检查:', {url: targetUrl, isInWhiteList, hasToken: !!token})
// 如果不在白名单且未登录,则跳转到登录页
if (!isInWhiteList && !token) {
console.log('拦截导航,跳转到登录页')
// 保存用户尝试访问的页面,以便登录后跳回
try {
uni.setStorageSync('REDIRECT_URL', targetUrl)
} catch (e) {
}
Dialog.confirm({
title: '提示',
content: '您尚未登录,是否前往登录页面?',
cancelBtn: '取消',
confirmBtn: '确认',
onConfirm: () => {
uni.navigateTo({
url: '/pages/login/index'
})
},
onCancel: () => {
console.log('取消登录')
if (!isInWhiteList) {
if (token) {
// 获取用户信息
const {user} = getApp().globalData
if (user == null) {
const {code, data, msg} = await useRequest().get('/client/auth/info')
if (code === 0) {
getApp().globalData.user = data
return args
}
}
})
// 阻止原导航
return false
return args
} else {
console.log('拦截导航,跳转到登录页')
// 保存用户尝试访问的页面,以便登录后跳回
try {
uni.setStorageSync('REDIRECT_URL', targetUrl)
} catch (e) {
}
Dialog.confirm({
title: '提示',
content: '您尚未登录,是否前往登录页面?',
cancelBtn: '取消',
confirmBtn: '确认',
onConfirm: () => {
uni.navigateTo({
url: '/pages/login/index'
})
},
onCancel: () => {
console.log('取消登录')
}
})
// 阻止原导航
return false
}
}
// 继续原导航
return args
@@ -90,9 +108,7 @@ function routeInterceptor(args, whiteList) {
*/
export const getDefaultWhiteList = () => {
return [
'/',
'/pages/index/index',
'/pages/login/index',
'/pages/profile/index'
'/pages/login/index'
]
}