赛事记录-更换队员筛选

This commit is contained in:
马梓彭 2026-04-20 14:00:45 +08:00
parent bca23b5d65
commit 26b98ef2a4
2 changed files with 230 additions and 5 deletions

View File

@ -272,4 +272,43 @@ export const usereventrecordDetailWithItemsGet = (registrationId) => {
*/
export const usereventrecordUpdateMembersPost = (data) => {
return http.post('/usereventrecord/update-members', data);
};
/**
* 11. 验证队员是否符合报名条件
* @param {Object} data - 验证信息
* @param {number} data.eventId - 赛事ID
* @param {number} data.memberId - 队员ID
* @param {number} data.groupId - 组别ID
* @returns {Promise<{
* isValid: boolean,
* ageValid: boolean,
* genderValid: boolean,
* registrationLimitValid: boolean,
* message: string
* }>}
*/
export const usereventrecordMemberValidatePost = (data) => {
return http.post('/usereventrecord/member/validate', data);
};
/**
* 12. 检查报名限制
* @param {Object} data - 检查信息
* @param {number} data.eventId - 赛事ID
* @param {Array<number>} data.memberIds - 队员ID列表
* @param {Array<number>} data.groupIds - 组别ID列表
* @param {Array<{groupId: number, memberIds: Array<number>}>} data.groupMembers - 组别队员分配
* @returns {Promise<{
* hasLimit: boolean,
* limitDetails: Array<{
* memberId: number,
* memberName: string,
* limitType: string,
* limitMessage: string
* }>
* }>}
*/
export const usereventrecordRegistrationLimitCheckPost = (data) => {
return http.post('/usereventrecord/registration-limit/check', data);
};

View File

@ -274,13 +274,14 @@
@click="activeChangeItemGroupId = item.registrationItemId"
>
<div class="assign-card-header">
<span>{{ formatItemTitle(item) }}</span>
</div>
<span>{{ formatItemTitle(item) }}</span>
</div>
<div class="assign-card-members">
<el-tag
v-for="m in (changeMembersMap[item.registrationItemId] ?? [])"
:key="m.userId ?? m.id"
closable
:type="getMemberValidationResult(m, item).isValid ? '' : 'danger'"
@close="removeMemberFromChangeGroup(item.registrationItemId, m.userId ?? m.id)"
style="margin: 3px;"
>
@ -288,6 +289,18 @@
</el-tag>
<span v-if="!(changeMembersMap[item.registrationItemId]?.length)" class="assign-card-placeholder">点击此卡片后,从右侧选择队员</span>
</div>
<!-- 队员验证错误提示 -->
<div v-if="hasValidationErrorsForItem(item)" class="validation-error-section">
<div class="validation-error-item"
v-for="m in (changeMembersMap[item.registrationItemId] ?? [])"
:key="m.userId ?? m.id">
<el-alert v-if="!getMemberValidationResult(m, item).isValid"
:title="getMemberValidationResult(m, item).message"
type="error" :closable="false"
size="small" style="margin-top: 4px;" />
</div>
</div>
</div>
<el-empty v-if="!changeMembersItems.length" description="暂无项目" />
</div>
@ -328,6 +341,19 @@
</div>
</div>
</div>
<!-- 验证提示区域 -->
<div class="validation-notes-section" style="margin-top: 16px;">
<!-- 年龄性别不符合要求提示 -->
<el-alert v-if="hasAnyValidationErrors()" title="队员信息不符合组别要求" type="error" :closable="false" style="margin-bottom: 12px;" />
<!-- 限报提示 -->
<template v-if="hasAnyRegistrationLimit()">
<el-alert v-for="(detail, index) in (registrationLimitCheckResult?.limitDetails ?? [])"
:key="index" :title="detail?.limitMessage ?? '报名限制'"
type="warning" :closable="false" style="margin-bottom: 12px;" />
</template>
</div>
</div>
<el-empty v-else description="暂无数据" />
</template>
@ -335,7 +361,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="isShowChangeMembersDialog = false">取消</el-button>
<el-button type="primary" @click="previewChangeMembers">预览</el-button>
<el-button type="primary" @click="previewChangeMembers" :disabled="hasAnyValidationErrors() || hasAnyRegistrationLimit()">预览</el-button>
</span>
</template>
</el-dialog>
@ -516,7 +542,7 @@
</template>
<script setup>
import { computed, reactive, ref, watch } from 'vue';
import { computed, reactive, ref, watch, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import { UploadFilled, ArrowRight, Close, CircleCheck } from '@element-plus/icons-vue';
import ProTable from '/@/components/ProTable/index.vue';
@ -532,6 +558,8 @@ import {
usereventrecordCancelExecutePost,
usereventrecordDetailWithItemsGet,
usereventrecordUpdateMembersPost,
usereventrecordMemberValidatePost,
usereventrecordRegistrationLimitCheckPost,
} from '/@/api/match/usereventrecord';
import { teamPageGet, teamDetailGet } from '/@/api/match/unitUser';
@ -1207,6 +1235,37 @@ const selectedChangeTeamId = ref(null);
const changeTeamMembers = ref([]);
const changeTeamMemberLoading = ref(false);
// 队员验证相关
const memberValidationMap = ref({}); // { registrationItemId_memberId: validationResult }
const registrationLimitCheckResult = ref(null); // 报名限制检查结果
const validationLoading = ref(false); // 验证加载状态
let validationDebounceTimer = null; // 防抖定时器
// 获取队员验证结果
const getMemberValidationResult = (member, item) => {
const key = `${item.registrationItemId}_${member.userId ?? member.id}`;
return memberValidationMap.value[key] || { isValid: true, ageValid: true, genderValid: true, registrationLimitValid: true, message: '' };
};
// 检查某个项目是否有验证错误
const hasValidationErrorsForItem = (item) => {
const members = changeMembersMap.value[item.registrationItemId] || [];
return members.some(member => {
const result = getMemberValidationResult(member, item);
return !result.isValid;
});
};
// 检查是否有任何验证错误
const hasAnyValidationErrors = () => {
return changeMembersItems.value.some(item => hasValidationErrorsForItem(item));
};
// 检查是否有任何报名限制
const hasAnyRegistrationLimit = () => {
return registrationLimitCheckResult.value?.hasLimit ?? false;
};
const openViewChangeMembersDialog = async (row) => {
if (!row?.registrationId) return;
isShowViewChangeMembersDialog.value = true;
@ -1248,7 +1307,7 @@ const openChangeMembersDialog = async () => {
// 初始化 changeMembersMap
changeMembersItems.value.forEach(item => {
changeMembersMap.value[item.registrationItemId] = (item.members || []).map(m => ({
userId: m.id,
userId: m.userId, // 使用正确的userId
id: m.id,
name: m.name,
gender: m.gender,
@ -1281,6 +1340,15 @@ const onChangeMembersDialogClosed = () => {
changeTeamList.value = [];
selectedChangeTeamId.value = null;
changeTeamMembers.value = [];
// 重置验证相关状态
memberValidationMap.value = {};
registrationLimitCheckResult.value = null;
validationLoading.value = false;
if (validationDebounceTimer) {
clearTimeout(validationDebounceTimer);
validationDebounceTimer = null;
}
};
const fetchChangeTeamList = async () => {
@ -1330,6 +1398,106 @@ const isChangeMemberAssignedToActive = (memberId) => {
return list.some((m) => (m.userId ?? m.id) === memberId);
};
// ==================== 队员验证相关函数 ====================
// 调用后端API验证队员
const validateMemberForItem = async (member, item) => {
try {
// 添加调试信息
console.log('验证队员 - member:', member);
console.log('验证队员 - member.userId:', member.userId);
console.log('验证队员 - member.id:', member.id);
console.log('验证队员 - item.eventProjectGroupId:', item.eventProjectGroupId);
const payload = {
eventId: changeMembersData.value.eventId,
memberId: member.userId ?? member.id,
groupId: item.eventProjectGroupId
};
console.log('验证队员 - payload:', payload);
const { succeeded, data } = await usereventrecordMemberValidatePost(payload);
console.log('验证队员 - response:', { succeeded, data });
if (succeeded && data) {
const key = `${item.registrationItemId}_${member.userId ?? member.id}`;
memberValidationMap.value[key] = data;
return data;
}
} catch (e) {
console.error('验证队员失败:', e);
}
return { isValid: true, ageValid: true, genderValid: true, registrationLimitValid: true, message: '' };
};
// 实时验证所有已选择的队员
const validateAllMembers = async () => {
if (!changeMembersData.value) {
return;
}
validationLoading.value = true;
try {
// 收集所有需要验证的队员和项目组合
const validationTasks = [];
let hasAnyMembers = false;
for (const item of changeMembersItems.value) {
const members = changeMembersMap.value[item.registrationItemId] || [];
if (members.length > 0) {
hasAnyMembers = true;
}
// 为每个队员创建验证任务
for (const member of members) {
validationTasks.push(validateMemberForItem(member, item));
}
}
// 并行执行所有验证任务
await Promise.all(validationTasks);
// 只有在有队员的情况下才检查报名限制
if (hasAnyMembers) {
// 构建报名限制检查的参数
const groupMembers = changeMembersItems.value.map(item => ({
groupId: item.eventProjectGroupId,
memberIds: (changeMembersMap.value[item.registrationItemId] || []).map(m => m.userId ?? m.id)
}));
// 检查报名限制
const { succeeded, data } = await usereventrecordRegistrationLimitCheckPost({
eventId: changeMembersData.value.eventId,
groupMembers: groupMembers
});
if (succeeded && data) {
registrationLimitCheckResult.value = data;
}
}
} catch (error) {
console.error('批量验证队员失败:', error);
} finally {
validationLoading.value = false;
}
};
// 带防抖的实时验证
const debounceValidateAllMembers = () => {
if (validationDebounceTimer) {
clearTimeout(validationDebounceTimer);
}
validationDebounceTimer = setTimeout(() => {
validateAllMembers();
}, 400); // 400毫秒防抖
};
// ==================== 队员分配函数 ====================
const assignMemberToChangeActiveTeam = (member) => {
const gId = activeChangeItemGroupId.value;
if (!gId) {
@ -1344,11 +1512,17 @@ const assignMemberToChangeActiveTeam = (member) => {
return;
}
changeMembersMap.value[gId].push({ ...member });
// 触发实时验证
debounceValidateAllMembers();
};
const removeMemberFromChangeGroup = (groupId, memberId) => {
if (!changeMembersMap.value[groupId]) return;
changeMembersMap.value[groupId] = changeMembersMap.value[groupId].filter((m) => (m.userId ?? m.id) !== memberId);
// 触发实时验证
debounceValidateAllMembers();
};
const previewChangeMembers = () => {
@ -1412,6 +1586,18 @@ const submitChangeMembers = async () => {
min-height: 400px;
}
.validation-notes-section {
margin-top: 16px;
}
.validation-error-section {
padding: 0 14px 14px;
}
.validation-error-item {
margin-bottom: 4px;
}
.step2-left {
flex: 1;
min-width: 0;