前端优化
加载匹配功能与加载骨架特效
骨架屏 : vant-skeleton
index.vue中
/**
* 加载数据
*/
const loadData = async () => {
let userListData;
loading.value = true;
//心动模式
if (isMatchMode.value){
const num = 10;//推荐人数
userListData = await myAxios.get('user/match',{
params: {
num,
},
})
.then(function (response) {
console.log('/user/match succeed',response);
return response?.data;
})
.catch(function (error) {
console.log('/user/match error',error);
showFailToast('请求失败');
});
}
else {
//不开启推荐模式,默认全部查询
//普通用户使用分页查询todo 并没有实现
userListData = await myAxios.get('/user/recommend',{
params: {
pageSize: 8,
pageNum: 1,
},
})
.then(function (response) {
console.log('/user/recommend succeed', response);
return response?.data?.records;
})
.catch(function (error) {
console.log('/user/recommends error',error);
showFailToast('请求失败');
});
}
if (userListData){
userListData.forEach((user: userType) =>{
if (user.tags){
user.tags = JSON.parse(user.tags);
}
})
userList.value = userListData;
}
loading.value = false;
}
//`watchEffect`函数用于在响应式数据发生变化时执行副作用(side effect)操作。在这个例子中,当数据发生变化时,会调用`loadData()`函数来加载数据。
watchEffect(() =>{
loadData();
})
前端导航标题
router :控制路由跳转
route: 获取路由信息
解决:使用 router.beforeEach,根据要跳转页面的 url 路径 匹配 config/routes 配置的 title 字段
//标题
const DEFAULT_TITLE='柚见'
const title=ref(DEFAULT_TITLE);
/**
* 根据路由切换标题
*/
router.beforeEach((to,from)=>{
const toPath=to.path;
const route=routes.find((r)=>{
return toPath==r.path;
})
title.value=route.title ?? DEFAULT_TITLE;
})
重定向到登录页
根据后端返回的未登录的错误码,重定向
全局响应拦截器中修改
发现页面一直不能正常跳转
切换hash模式
不同的历史模式 | Vue Router (vuejs.org)
登录成功后自动跳转回之前页面
添加创建队伍按钮
添加样式
引入样式
换成vant库中的+
<!-- 创建队伍按钮-->
<van-button type="primary" @click="doJoinTeam" class="add-button" icon="plus">
</van-button>
队伍操作按钮权限控制
更新队伍:仅创建人可见
v-if="team.userId === currentUser?.id"
解散队伍:仅创建人可见
v-if="team.userId === currentUser?.id"
加入队伍: 仅非队伍创建人、且未加入队伍的人可见
退出队伍:创建人不可见,仅已加入队伍的人可见
后端修改
仅加入队伍和创建队伍的人能看到队伍操作按钮(listTeam 接口要能获取我加入的队伍状态) ✔
方案 1:前端查询我加入了哪些队伍列表,然后判断每个队伍 id 是否在列表中(前端要多发一次请求)
方案 2:在后端去做上述事情(推荐)
加密队伍与公开队伍的展示
前端修改
Tab 标签页 - Vant 4 (gitee.io)
<van-tabs v-model:active="activeName">
<van-tab title="公开队伍" name="public"></van-tab>
<van-tab title="加密队伍" name="secret"></van-tab>
</van-tabs>
const onTabChange=(name)=>{
if(name==='public')
{
//查询公开队伍
listTeam()
}else if(name === 'secret'){
//查询加密队伍
listTeam(' ',2);
}
console.log(name)
}
//只有管理员和本人才能查看非私有的房间
if (!isManager && !statusEnum.equals(TeamStatusEnum.PUBLIC) && !statusEnum.equals(TeamStatusEnum.SECRET)) {
throw new BusinessException(ErrorCode.NO_AUTH);
}
点击加入加密队伍后,弹窗密码跳出
<script setup lang="ts">
import {TeamType} from "../models/team";
import {teamStatusEnum} from "../constants/team.ts";
import myAxios from "../plungins/myAxios.js";
import {showFailToast, showSuccessToast} from "vant";
import {onMounted, ref} from "vue";
import {getCurrentUser} from "../services/user.ts";
import {useRouter} from "vue-router";
const router=useRouter();
const password=ref('');
//加入该队伍的id
const joinId=ref(0)
const showPwd=ref(false)
interface TeamCardListProps{
teamList: TeamType[];
}
const props= withDefaults(defineProps<TeamCardListProps>(),{
//@ts-ignore
teamList: [] as TeamType[]
});
//获得当前用户
const currentUser=ref()
onMounted(async ()=>{
const res=await getCurrentUser()
currentUser.value=res
})
//加入队伍之前---------------------------------------------------------------------
const preJoinTeam=(team : TeamType)=>{
joinId.value=team.id;
if(team.status === 2)
{
//打开加密弹窗
showPwd.value=true;
}
else if(team.status === 0)
{
doJoinTeam();
}
}
const doJoinCancel = () => {
joinId.value = 0;
password.value = '';
}
//点击公开队伍,加入队伍------------------------------------------------------------
const doJoinTeam=async()=>{
if(!joinId.value)
{
return ;
}
const res=await myAxios.post('/team/join',{
teamId:joinId.value,
password:password.value
})
if(res?.code===0){
showSuccessToast("加入成功")
doJoinCancel();
}else{
showFailToast("加入失败\n"+(res.description ? `${res.description}`: ''))
}
}
//跳转到更新页---------------------------------------------------------------------
const doUpdateTeam=({id}: { id: any })=>{
router.push({
path:'/team/update',
query:{
id
}
})
}
//点击退出队伍-------------------------------------------------------------------
const doQuitTeam=async (id)=>{
const res=await myAxios.post('/team/quit',{
teamId:id
})
if(res?.code===0){
showSuccessToast("操作成功")
}else{
showFailToast("操作失败\n"+(res.description ? `${res.description}`: ''))
}
}
//点击解散队伍-------------------------------------------------------------------------
const doDeleteTeam=async ({id}: { id: any })=>{
const res=await myAxios.post('/team/delete',{
id
})
if(res?.code===0){
showSuccessToast("操作成功")
}else{
showFailToast("操作失败\n"+(res.description ? `${res.description}`: ''))
}
}
</script>
已加入队伍人数
<div>
{{ `已加入人数 : ${team.hasJoinNum} / ` + team.maxNum }}
</div>
后端优化
队伍按钮权限控制
@GetMapping("/list")
public BaseResponse<List<TeamUserVO>> listTeams(TeamQuery teamQuery,HttpServletRequest request) {
if (teamQuery == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
boolean isManager = userService.isManager(request);
User loginUser = userService.getLoginUser(request);
// 1、查询队伍列表
List<TeamUserVO> teamList = teamService.listTeams(teamQuery, isManager);
final List<Integer> teamIdList = teamList.stream().map(TeamUserVO::getId).collect(Collectors.toList());
// 2、判断当前用户是否已加入队伍
QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
try {
userTeamQueryWrapper.eq("userId", loginUser.getId());
userTeamQueryWrapper.in("teamId", teamIdList);
List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
// 已加入的队伍 id 集合
Set<Integer> hasJoinTeamIdSet = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toSet());
teamList.forEach(team -> {
boolean hasJoin = hasJoinTeamIdSet.contains(team.getId());
team.setHasJoin(hasJoin);
});
} catch (Exception e) {}
return ResultUtils.success(teamList);
}
查询已加入队伍的人数
//3.查询已加入队伍数
QueryWrapper<UserTeam> userTeamQueryWrapper2 = new QueryWrapper<>();
userTeamQueryWrapper.in("teamId", teamIdList);
List<UserTeam> userTeamList2 = userTeamService.list(userTeamQueryWrapper2);
//队伍id => userId集合
Map<Integer, List<UserTeam>> teamIdUserTeamList = userTeamList2.stream().collect(Collectors.groupingBy(UserTeam::getTeamId));
teamList.forEach(team ->
team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(), new ArrayList<>()).size())
);
多次快速点击加入队伍,重复加入队伍
只要我们点的足够快,就可以在同一时间内往数据库插入多条同样的数据,所以这里我们使用分布式锁(推荐)使用两把锁,一把锁锁队伍,一把锁锁用户(实现较难,不推荐)
之前的代码
//该用户已加入的队伍数量不能超过5个
int userId = loginUser.getId();
// 只有一个线程能获取到锁
RLock lock = redissonClient.getLock("youjian:join_team");
QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
userTeamQueryWrapper.eq("userId",userId);
int hasJoinNum = (int) userTeamService.count(userTeamQueryWrapper);
if (hasJoinNum >= 5){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"最多创建和加入5个队伍");
}
//不能重复加入已加入的队伍
userTeamQueryWrapper = new QueryWrapper<>();
userTeamQueryWrapper.eq("userId",userId);
userTeamQueryWrapper.eq("teamId",teamId);
int hasUserJoinTeam = (int) userTeamService.count(userTeamQueryWrapper);
if (hasUserJoinTeam > 0){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户已加入该队伍");
}
//不能加入已满队伍
userTeamQueryWrapper = new QueryWrapper<>();
userTeamQueryWrapper.eq("teamId",teamId);
int teamHasJoinNum = (int) userTeamService.count(userTeamQueryWrapper);
if (teamHasJoinNum >= team.getMaxNum()){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"队伍已满");
}
//3.加入,修改队伍信息
UserTeam userTeam = new UserTeam();
userTeam.setUserId(userId);
userTeam.setTeamId(teamId);
userTeam.setJoinTime(new Date());
return userTeamService.save(userTeam);
修改后
@Resource
private RedissonClient redissonClient;