123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- <template>
- <div class="p-4">
- <!-- 顶部信息区域 -->
- <el-card shadow="hover" class="mb-4">
- <div class="flex justify-between items-center">
- <div class="text-left">
- <h2 class="text-xl font-bold text-gray-800 mb-2">{{ projectName }} - {{ groupInfo.groupName }}</h2>
- <div class="grid grid-cols-3 gap-4 text-sm text-gray-600">
- <div><span class="font-medium"></span>{{ groupInfo.personNum }}人</div>
- <div><span class="font-medium">组数:</span>{{ groupInfo.includeGroupNum }}组</div>
- <div><span class="font-medium">道数:</span>{{ groupInfo.trackNum }}道</div>
- <div><span class="font-medium">开始时间:</span>{{ groupInfo.beginTime }}</div>
- <div><span class="font-medium">预计结束时间:</span>{{ groupInfo.endTime }}</div>
- <div><span class="font-medium">场地数量:</span>{{ groupInfo.fieldNum }}个</div>
- </div>
- </div>
- <div class="text-right">
- <el-button type="primary" @click="generateGroups" :loading="generating">重新生成分组</el-button>
- <el-button @click="goBack">返回</el-button>
- </div>
- </div>
- </el-card>
- <!-- 分组详情表格 -->
- <el-card shadow="hover">
- <template #header>
- <div class="flex justify-between items-center">
- <span class="font-medium">分组详情</span>
- <div class="text-sm text-gray-500">
- 共 {{ groupInfo.includeGroupNum }} 组,{{ groupInfo.trackNum }} 条道
- </div>
- </div>
- </template>
- <div class="overflow-x-auto">
- <table class="w-full border-collapse border border-gray-300">
- <!-- 表头 -->
- <thead>
- <tr class="bg-gray-100">
- <th class="border border-gray-300 px-4 py-2 text-center font-medium">组别</th>
- <th
- v-for="track in groupInfo.trackNum"
- :key="track"
- class="border border-gray-300 px-4 py-2 text-center font-medium"
- >
- 第{{ getTrackName(track) }}道
- </th>
- </tr>
- </thead>
-
- <!-- 分组内容 -->
- <tbody>
- <tr
- v-for="groupIndex in groupInfo.includeGroupNum"
- :key="groupIndex"
- class="hover:bg-gray-50"
- >
- <td class="border border-gray-300 px-4 py-3 text-center font-medium bg-blue-50">
- 第{{ groupIndex }}组
- </td>
- <td
- v-for="track in groupInfo.trackNum"
- :key="track"
- class="border border-gray-300 px-4 py-3 text-center"
- >
- <div v-if="getAthleteByGroupAndTrack(groupIndex, track)" class="space-y-1">
- <div class="font-medium text-blue-600">{{ getAthleteByGroupAndTrack(groupIndex, track)?.athleteCode }}</div>
- <div class="text-sm">{{ getAthleteByGroupAndTrack(groupIndex, track)?.name }}</div>
- <div class="text-xs text-gray-500">{{ getTeamName(getAthleteByGroupAndTrack(groupIndex, track)?.teamId) }}</div>
- </div>
- <div v-else class="text-gray-400 text-sm">-</div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- </el-card>
- <!-- 统计信息 -->
- <el-card shadow="hover" class="mt-4">
- <template #header>
- <span class="font-medium">分组统计</span>
- </template>
-
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
- <div class="text-center p-3 bg-blue-50 rounded-lg">
- <div class="text-2xl font-bold text-blue-600">{{ totalAthletes }}</div>
- <div class="text-sm text-gray-600">符合条件的运动员数量</div>
- </div>
- <div class="text-center p-3 bg-green-50 rounded-lg">
- <div class="text-2xl font-bold text-green-600">{{ groupInfo.includeGroupNum }}</div>
- <div class="text-sm text-gray-600">分组数</div>
- </div>
- <div class="text-center p-3 bg-purple-50 rounded-lg">
- <div class="text-2xl font-bold text-purple-600">{{ groupInfo.trackNum }}</div>
- <div class="text-sm text-gray-600">道数</div>
- </div>
- <!-- <div class="text-center p-3 bg-orange-50 rounded-lg">
- <div class="text-2xl font-bold text-orange-600">{{ groupInfo.personNum }}</div>
- <div class="text-sm text-gray-600">{{ groupInfo.groupName }}人数</div>
- </div> -->
- <div class="text-center p-3 bg-orange-50 rounded-lg">
- <div class="text-2xl font-bold text-orange-600">{{ roundType }}</div>
- <div class="text-sm text-gray-600">录取人数</div>
- </div>
- </div>
- </el-card>
- <!-- 调试信息 -->
- <!-- <el-card shadow="hover" class="mt-4">
- <template #header>
- <span class="font-medium">调试信息</span>
- </template>
-
- <div class="space-y-2 text-sm">
- <div><strong>项目ID:</strong> {{ groupInfo.projectId }}</div>
- <div><strong>性别要求:</strong> {{ groupInfo.memberGender === '0' ? '不分男女' : groupInfo.memberGender === '1' ? '男' : '女' }}</div>
- <div><strong>运动员总数:</strong> {{ athletes.length }}</div>
- <div><strong>符合条件的运动员数:</strong> {{ totalAthletes }}</div>
- <div><strong>分组结果大小:</strong> {{ groupResult.size }}</div>
- <div><strong>分组结果:</strong></div>
- <pre class="bg-gray-100 p-2 rounded text-xs overflow-auto max-h-40">{{ JSON.stringify(Array.from(groupResult.entries()), null, 2) }}</pre>
- </div>
- </el-card> -->
- </div>
- </template>
- <script setup name="GameEventGroupDetail" lang="ts">
- import { ref, onMounted, computed, getCurrentInstance } from 'vue';
- import { useRoute, useRouter } from 'vue-router';
- import { getGameEventGroup } from '@/api/system/gameEventGroup';
- import { listGameAthlete } from '@/api/system/gameAthlete';
- import { listGameTeam } from '@/api/system/gameTeam';
- import { listGameEventProject } from '@/api/system/gameEventProject';
- import { GameEventGroupVO } from '@/api/system/gameEventGroup/types';
- import { GameAthleteVO } from '@/api/system/gameAthlete/types';
- import { GameTeamVO } from '@/api/system/gameTeam/types';
- import { GameEventProjectVO } from '@/api/system/gameEventProject/types';
- import type { ComponentInternalInstance } from 'vue';
- const route = useRoute();
- const router = useRouter();
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- // 分组信息
- const groupInfo = ref<GameEventGroupVO>({} as GameEventGroupVO);
- // 运动员列表
- const athletes = ref<GameAthleteVO[]>([]);
- // 队伍列表
- const teams = ref<GameTeamVO[]>([]);
- // 项目列表
- const projects = ref<GameEventProjectVO[]>([]);
- // 分组结果
- const groupResult = ref<Map<string, GameAthleteVO>>(new Map());
- // 加载状态
- const loading = ref(false);
- // 生成分组状态
- const generating = ref(false);
- const roundType = ref();
- // 项目名称(需要从项目信息中获取)
- const projectName = computed(() => {
- if (!groupInfo.value.projectId) return '';
- const project = projects.value.find(p => p.projectId === groupInfo.value.projectId);
- roundType.value = project?.roundType || 0;
- return project?.projectName || '';
- });
- // 计算总运动员数
- const totalAthletes = computed(() => {
- return athletes.value.filter(athlete => {
- // 检查运动员是否参与该项目
- if (!athlete.projectList) return false;
-
- // 处理项目列表
- let projectIds: string[] = [];
- if (Array.isArray(athlete.projectList)) {
- projectIds = athlete.projectList.map(p => p.toString());
- } else if (typeof athlete.projectList === 'string') {
- try {
- projectIds = JSON.parse(athlete.projectList);
- } catch (e) {
- // 如果不是JSON格式,可能是逗号分隔的字符串
- projectIds = (athlete.projectList as string).split(',').map(p => p.trim());
- }
- }
-
- const targetProjectId = groupInfo.value.projectId?.toString();
- const hasProject = projectIds.includes(targetProjectId);
-
- if (!hasProject) {
- return false;
- }
-
- // 检查性别是否匹配
- if (groupInfo.value.memberGender && groupInfo.value.memberGender !== '0') {
- // 使用字典来匹配性别,而不是硬编码的字符串
- if (athlete.gender?.toString() !== groupInfo.value.memberGender?.toString()) {
- return false;
- }
- }
-
- return true;
- }).length;
- });
- // 调试:打印运动员数据
- const debugAthletes = computed(() => {
- return athletes.value.map(athlete => ({
- id: athlete.athleteId,
- name: athlete.name,
- teamId: athlete.teamId,
- projectList: athlete.projectList,
- gender: athlete.gender
- }));
- });
- // 获取道次名称
- const getTrackName = (track: number) => {
- const trackNames = ['一', '二', '三', '四', '五', '六', '七', '八','九','十'];
- return trackNames[track - 1] || track;
- };
- // 根据组别和道次获取运动员
- const getAthleteByGroupAndTrack = (groupIndex: number, track: number) => {
- const key = `${groupIndex}-${track}`;
- return groupResult.value.get(key);
- };
- // 根据队伍ID获取队伍名称
- const getTeamName = (teamId: string | number | undefined) => {
- if (!teamId) return '';
- const team = teams.value.find(t => t.teamId === teamId);
- return team?.teamName || '';
- };
- // 获取分组信息
- const getGroupInfo = async () => {
- try {
- loading.value = true;
- const groupId = route.query.id;
- if (!groupId || Array.isArray(groupId)) {
- proxy?.$modal.msgError('分组ID不能为空');
- return;
- }
-
- const res = await getGameEventGroup(groupId);
- groupInfo.value = res.data;
-
- // 获取运动员、队伍和项目信息
- await Promise.all([
- getAthletes(),
- getTeams(),
- getProjects()
- ]);
-
- // 生成分组
- generateGroups();
- } catch (error) {
- console.error('获取分组信息失败:', error);
- proxy?.$modal.msgError('获取分组信息失败');
- } finally {
- loading.value = false;
- }
- };
- // 获取运动员列表
- const getAthletes = async () => {
- try {
- const res = await listGameAthlete({
- pageNum: 1,
- pageSize: 1000,
- eventId: groupInfo.value.eventId,
- orderByColumn: '',
- isAsc: ''
- });
- athletes.value = res.rows;
- } catch (error) {
- console.error('获取运动员列表失败:', error);
- }
- };
- // 获取队伍列表
- const getTeams = async () => {
- try {
- const res = await listGameTeam({
- pageNum: 1,
- pageSize: 1000,
- eventId: groupInfo.value.eventId,
- orderByColumn: '',
- isAsc: ''
- });
- teams.value = res.rows;
- } catch (error) {
- console.error('获取队伍列表失败:', error);
- }
- };
- // 获取项目列表
- const getProjects = async () => {
- try {
- const res = await listGameEventProject({
- pageNum: 1,
- pageSize: 1000,
- orderByColumn: '',
- isAsc: ''
- });
- projects.value = res.rows;
- } catch (error) {
- console.error('获取项目列表失败:', error);
- }
- };
- // 生成分组
- const generateGroups = async () => {
- try {
- generating.value = true;
-
- // 清空之前的分组结果
- groupResult.value.clear();
-
- // 筛选符合条件的运动员
- const eligibleAthletes = athletes.value.filter(athlete => {
-
- // 检查是否参与该项目
- if (!athlete.projectList) {
- return false;
- }
-
- // 处理项目列表
- let projectIds: string[] = [];
- if (Array.isArray(athlete.projectList)) {
- projectIds = athlete.projectList.map(p => p.toString());
- } else if (typeof athlete.projectList === 'string') {
- try {
- projectIds = JSON.parse(athlete.projectList);
- } catch (e) {
- // 如果不是JSON格式,可能是逗号分隔的字符串
- projectIds = (athlete.projectList as string).split(',').map(p => p.trim());
- }
- }
-
- const targetProjectId = groupInfo.value.projectId?.toString();
- const hasProject = projectIds.includes(targetProjectId);
-
- if (!hasProject) {
- return false;
- }
-
- // 检查性别是否匹配
- if (groupInfo.value.memberGender && groupInfo.value.memberGender !== '0') {
- // 使用字典来匹配性别,而不是硬编码的字符串
- if (athlete.gender?.toString() !== groupInfo.value.memberGender?.toString()) {
- return false;
- }
- }
-
- return true;
- });
-
- if (eligibleAthletes.length === 0) {
- proxy?.$modal.msgWarning('没有找到符合条件的运动员');
- return;
- }
-
- // 随机打乱运动员顺序
- const shuffledAthletes = [...eligibleAthletes].sort(() => Math.random() - 0.5);
-
- // 记录已分配的运动员ID,避免重复分配
- const assignedAthleteIds = new Set();
-
- // 按组别和道次分配运动员
- for (let groupIndex = 1; groupIndex <= groupInfo.value.includeGroupNum; groupIndex++) {
- for (let track = 1; track <= groupInfo.value.trackNum; track++) {
- // 寻找可用的运动员
- let selectedAthlete = null;
- let athleteIndex = 0;
-
- while (athleteIndex < shuffledAthletes.length && !selectedAthlete) {
- const candidateAthlete = shuffledAthletes[athleteIndex];
-
- // 检查运动员是否已经被分配
- if (assignedAthleteIds.has(candidateAthlete.athleteId)) {
- athleteIndex++;
- continue;
- }
-
- // 检查同一组中是否已有同一队伍的运动员
- const hasSameTeamInGroup = Array.from(groupResult.value.entries())
- .some(([key, existingAthlete]) => {
- const [existingGroup] = key.split('-');
- return existingGroup === groupIndex.toString() &&
- existingAthlete.teamId === candidateAthlete.teamId;
- });
-
- if (!hasSameTeamInGroup) {
- selectedAthlete = candidateAthlete;
- // 标记运动员为已分配
- assignedAthleteIds.add(candidateAthlete.athleteId);
- }
-
- athleteIndex++;
- }
-
- // 如果找到了合适的运动员,分配到当前组和道次
- if (selectedAthlete) {
- const key = `${groupIndex}-${track}`;
- groupResult.value.set(key, selectedAthlete);
- }
- }
- }
-
- proxy?.$modal.msgSuccess('分组生成成功');
- } catch (error) {
- console.error('生成分组失败:', error);
- proxy?.$modal.msgError('生成分组失败');
- } finally {
- generating.value = false;
- }
- };
- // 返回上一页
- const goBack = () => {
- router.go(-1);
- };
- onMounted(() => {
- getGroupInfo();
- });
- </script>
- <style scoped>
- .overflow-x-auto {
- overflow-x: auto;
- }
- /* 响应式表格 */
- @media (max-width: 768px) {
- .grid {
- grid-template-columns: repeat(1, 1fr);
- }
-
- .overflow-x-auto {
- font-size: 12px;
- }
-
- .px-4 {
- padding-left: 8px;
- padding-right: 8px;
- }
-
- .py-3 {
- padding-top: 6px;
- padding-bottom: 6px;
- }
- }
- </style>
|