|
@@ -132,12 +132,34 @@
|
|
|
<el-form-item label="参与项目" prop="projectList">
|
|
|
<el-transfer
|
|
|
v-model="projectListStr"
|
|
|
- :data="gameEventProjectList"
|
|
|
+ :data="filteredGameEventProjectList"
|
|
|
:titles="['可选项目', '已选项目']"
|
|
|
:button-texts="['移除', '添加']"
|
|
|
filterable
|
|
|
style="width: 100%"
|
|
|
+ @change="handleProjectChange"
|
|
|
/>
|
|
|
+ <!-- 添加校验提示 -->
|
|
|
+ <div v-if="projectValidation.errors.length > 0" class="validation-errors">
|
|
|
+ <el-alert
|
|
|
+ v-for="error in projectValidation.errors"
|
|
|
+ :key="error"
|
|
|
+ :title="error"
|
|
|
+ type="error"
|
|
|
+ :closable="false"
|
|
|
+ show-icon
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div v-if="projectValidation.warnings.length > 0" class="validation-warnings">
|
|
|
+ <el-alert
|
|
|
+ v-for="warning in projectValidation.warnings"
|
|
|
+ :key="warning"
|
|
|
+ :title="warning"
|
|
|
+ type="warning"
|
|
|
+ :closable="false"
|
|
|
+ show-icon
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="备注" prop="remark">
|
|
|
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
|
@@ -191,9 +213,9 @@
|
|
|
|
|
|
<script setup name="GameAthlete" lang="ts">
|
|
|
import { nextTick, ref, onMounted } from 'vue';
|
|
|
-import { listGameAthlete, getGameAthlete, delGameAthlete, addGameAthlete, updateGameAthlete } from '@/api/system/gameAthlete';
|
|
|
+import { listGameAthlete, getGameAthlete, delGameAthlete, addGameAthlete, updateGameAthlete, validateProjectSelection } from '@/api/system/gameAthlete';
|
|
|
import { listGameTeam, updateTeamAthletes } from '@/api/system/gameTeam';
|
|
|
-import { listGameEventProject } from '@/api/system/gameEventProject';
|
|
|
+import { listGameEventProject, getGameEventProject } from '@/api/system/gameEventProject';
|
|
|
// import { getDefaultEvent } from '@/api/system/gameEvent';
|
|
|
import { GameAthleteVO, GameAthleteQuery, GameAthleteForm } from '@/api/system/gameAthlete/types';
|
|
|
import { GameTeamVO } from '@/api/system/gameTeam/types';
|
|
@@ -206,7 +228,7 @@ const defaultEvent = ref<GameEventVO | null>(null); // 默认赛事信息
|
|
|
|
|
|
const gameTeamList = ref<GameTeamVO[]>([]); // 队伍列表
|
|
|
const gameAthleteList = ref<GameAthleteVO[]>([]);
|
|
|
-const gameEventProjectList = ref<Array<{ key: string; label: string }>>([]); // 赛事项目列表(用于穿梭框)
|
|
|
+const gameEventProjectList = ref<Array<{ key: string; label: string; disabled: boolean }>>([]); // 赛事项目列表(用于穿梭框)
|
|
|
const buttonLoading = ref(false);
|
|
|
const loading = ref(true);
|
|
|
const showSearch = ref(true);
|
|
@@ -311,6 +333,8 @@ const data = reactive<PageData<GameAthleteForm, GameAthleteQuery>>({
|
|
|
|
|
|
// 添加额外的ref用于处理默认事件ID
|
|
|
const defaultEventId = computed(() => defaultEvent.value?.eventId);
|
|
|
+// 添加赛事限制信息
|
|
|
+const eventLimitInfo = defaultEvent.value?.limitApplication || 0;
|
|
|
|
|
|
// 监听默认事件变化
|
|
|
watchEffect(() => {
|
|
@@ -322,6 +346,129 @@ watchEffect(() => {
|
|
|
|
|
|
const { queryParams, form, rules } = toRefs(data);
|
|
|
|
|
|
+// 使用计算属性实时计算校验状态
|
|
|
+const validationStatus = computed(() => {
|
|
|
+ const selectedCount = form.value.selectedProjects.length;
|
|
|
+
|
|
|
+ // 如果有限制且超额
|
|
|
+ if (eventLimitInfo > 0 && selectedCount > eventLimitInfo) {
|
|
|
+ return {
|
|
|
+ errors: [`每人限报项目数为${eventLimitInfo}个,您已选择${selectedCount}个项目`],
|
|
|
+ warnings: [],
|
|
|
+ isValid: false,
|
|
|
+ isExceeded: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 正常状态
|
|
|
+ return {
|
|
|
+ errors: [],
|
|
|
+ warnings: [],
|
|
|
+ isValid: true,
|
|
|
+ isExceeded: false
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+const projectValidation = reactive({
|
|
|
+ errors: [] as string[],
|
|
|
+ warnings: [] as string[],
|
|
|
+ isValid: true,
|
|
|
+ isExceeded: false
|
|
|
+});
|
|
|
+
|
|
|
+// 添加过滤后的项目列表(排除已满的项目)
|
|
|
+const filteredGameEventProjectList = computed(() => {
|
|
|
+ return gameEventProjectList.value.map(project => ({
|
|
|
+ ...project,
|
|
|
+ disabled: false // 先设为false,在组件加载时异步更新
|
|
|
+ }));
|
|
|
+});
|
|
|
+
|
|
|
+const projectDetailsCache = ref<Map<number, any>>(new Map());
|
|
|
+
|
|
|
+// 获取项目详情
|
|
|
+const getProjectDetails = async (projectId: number) => {
|
|
|
+ if (projectDetailsCache.value.has(projectId)) {
|
|
|
+ return projectDetailsCache.value.get(projectId);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await getGameEventProject(projectId);
|
|
|
+ projectDetailsCache.value.set(projectId, response.data);
|
|
|
+ return response.data;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取项目详情失败:', error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 判断项目是否已满
|
|
|
+const isProjectFull = async (projectId: number): Promise<boolean> => {
|
|
|
+ const projectDetails = await getProjectDetails(projectId);
|
|
|
+ if (!projectDetails || !projectDetails.limitPerson || projectDetails.limitPerson === 0) {
|
|
|
+ return false; // 无限制或未设置限制
|
|
|
+ }
|
|
|
+
|
|
|
+ // 这里需要调用后端接口获取当前报名人数
|
|
|
+ // 可以通过批量查询优化性能
|
|
|
+ try {
|
|
|
+ const response = await validateProjectSelection({
|
|
|
+ eventId: form.value.eventId,
|
|
|
+ athleteId: form.value.athleteId || null,
|
|
|
+ selectedProjectIds: [projectId]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果该项目报名人数已满,返回true
|
|
|
+ return response.data.errors.some((error: string) =>
|
|
|
+ error.includes('报名人数已满')
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ console.error('检查项目是否已满失败:', error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 项目选择变化处理
|
|
|
+const handleProjectChange = async (value: string[], direction: string, movedKeys: string[]) => {
|
|
|
+ // 立即更新已选项目
|
|
|
+ form.value.selectedProjects = value.map(id => Number(id));
|
|
|
+
|
|
|
+ // 如果从超额状态恢复正常,进行后端校验
|
|
|
+ if (validationStatus.value.isExceeded === false && form.value.selectedProjects.length > 0) {
|
|
|
+ await validateProjectSelection2();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 校验项目选择
|
|
|
+const validateProjectSelection2 = async () => {
|
|
|
+ if (!form.value.eventId || !form.value.selectedProjects.length) {
|
|
|
+ projectValidation.errors = [];
|
|
|
+ projectValidation.warnings = [];
|
|
|
+ projectValidation.isValid = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await validateProjectSelection({
|
|
|
+ eventId: form.value.eventId,
|
|
|
+ athleteId: form.value.athleteId || null,
|
|
|
+ selectedProjectIds: form.value.selectedProjects
|
|
|
+ });
|
|
|
+
|
|
|
+ // 合并前端和后端的校验结果
|
|
|
+ projectValidation.errors = [...validationStatus.value.errors, ...(response.data.errors || [])];
|
|
|
+ projectValidation.warnings = response.data.warnings || [];
|
|
|
+ projectValidation.isValid = response.data.valid && validationStatus.value.isValid;
|
|
|
+ projectValidation.isExceeded = validationStatus.value.isExceeded;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('校验项目选择失败:', error);
|
|
|
+ projectValidation.errors = [...validationStatus.value.errors, '校验失败,请重试'];
|
|
|
+ projectValidation.warnings = [];
|
|
|
+ projectValidation.isValid = false;
|
|
|
+ projectValidation.isExceeded = validationStatus.value.isExceeded;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
/** 获取默认赛事 */
|
|
|
// const getDefaultEventInfo = async () => {
|
|
|
// try {
|
|
@@ -340,29 +487,25 @@ const getTeamNameById = (teamId: string | number | null | undefined) => {
|
|
|
};
|
|
|
|
|
|
// 获取赛事项目列表
|
|
|
-const getProjectList = async (eventId?: string) => {
|
|
|
- const res = await listGameEventProject({
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 1000,
|
|
|
- orderByColumn: '',
|
|
|
- isAsc: ''
|
|
|
- });
|
|
|
- console.log(res);
|
|
|
- gameEventProjectList.value = res.rows.map((item) => ({
|
|
|
- key: String(item.projectId),
|
|
|
- label: `${item.projectName}`
|
|
|
- }));
|
|
|
+const getProjectList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listGameEventProject();
|
|
|
+ gameEventProjectList.value = res.rows.map((item) => ({
|
|
|
+ key: String(item.projectId),
|
|
|
+ label: item.projectName,
|
|
|
+ disabled: false
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 更新项目状态
|
|
|
+ await updateProjectStatus();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取项目列表失败:', error);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// 格式化项目列表显示
|
|
|
const formatProjectList = (projectList: number[]) => {
|
|
|
if (!projectList) return '';
|
|
|
- // 将逗号分隔的ID列表转换为项目名称列表
|
|
|
- // const projectIds = projectValue.split(',');
|
|
|
- // const projectNames = projectIds.map((id) => {
|
|
|
- // const project = gameEventProjectList.value.find((p) => p.key === id);
|
|
|
- // return project ? project.label : id;
|
|
|
- // });
|
|
|
const projectNames = gameEventProjectList.value.filter((p) => projectList.includes(Number(p.key))).map((p) => p.label);
|
|
|
return projectNames.join(',');
|
|
|
};
|
|
@@ -471,6 +614,13 @@ const projectListStr = computed({
|
|
|
const submitForm = () => {
|
|
|
gameAthleteFormRef.value?.validate(async (valid: boolean) => {
|
|
|
if (valid) {
|
|
|
+ // 提交前再次校验
|
|
|
+ await validateProjectSelection2();
|
|
|
+
|
|
|
+ if (!projectValidation.isValid) {
|
|
|
+ proxy?.$modal.msgError('项目选择不符合要求,请检查后重试');
|
|
|
+ return;
|
|
|
+ }
|
|
|
buttonLoading.value = true;
|
|
|
try {
|
|
|
// 处理项目列表数据,将数组转换为逗号分隔的字符串
|
|
@@ -554,6 +704,17 @@ const submitForm = () => {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+// 在组件加载时更新项目状态
|
|
|
+const updateProjectStatus = async () => {
|
|
|
+ if (gameEventProjectList.value.length > 0) {
|
|
|
+ for (const project of gameEventProjectList.value) {
|
|
|
+ const projectId = Number(project.key);
|
|
|
+ const isFull = await isProjectFull(projectId);
|
|
|
+ project.disabled = isFull;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
/** 删除按钮操作 */
|
|
|
const handleDelete = async (row?: GameAthleteVO) => {
|
|
|
const _athleteIds = row?.athleteId || ids.value;
|
|
@@ -645,3 +806,15 @@ onMounted(() => {
|
|
|
getProjectList();
|
|
|
});
|
|
|
</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.validation-errors,
|
|
|
+.validation-warnings {
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.validation-errors .el-alert,
|
|
|
+.validation-warnings .el-alert {
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+</style>
|