detail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <template>
  2. <div class="p-4">
  3. <!-- 顶部信息区域 -->
  4. <el-card shadow="hover" class="mb-4">
  5. <div class="flex justify-between items-center">
  6. <div class="text-left">
  7. <h2 class="text-xl font-bold text-gray-800 mb-2">{{ projectName }} - {{ groupInfo.groupName }}</h2>
  8. <div class="grid grid-cols-3 gap-4 text-sm text-gray-600">
  9. <div><span class="font-medium"></span>{{ groupInfo.personNum }}人</div>
  10. <div><span class="font-medium">组数:</span>{{ groupInfo.includeGroupNum }}组</div>
  11. <div><span class="font-medium">道数:</span>{{ groupInfo.trackNum }}道</div>
  12. <div><span class="font-medium">开始时间:</span>{{ groupInfo.beginTime }}</div>
  13. <div><span class="font-medium">预计结束时间:</span>{{ groupInfo.endTime }}</div>
  14. <div><span class="font-medium">场地数量:</span>{{ groupInfo.fieldNum }}个</div>
  15. </div>
  16. </div>
  17. <div class="text-right">
  18. <el-button type="primary" @click="regenerateGroups" :loading="generating">重新生成分组</el-button>
  19. <el-button @click="goBack">返回</el-button>
  20. </div>
  21. </div>
  22. </el-card>
  23. <!-- 分组详情表格 -->
  24. <el-card shadow="hover">
  25. <template #header>
  26. <div class="flex justify-between items-center">
  27. <span class="font-medium">分组详情</span>
  28. <div class="text-sm text-gray-500">
  29. 共 {{ groupInfo.includeGroupNum }} 组,{{ groupInfo.trackNum }} 条道
  30. </div>
  31. </div>
  32. </template>
  33. <div class="overflow-x-auto">
  34. <table class="w-full border-collapse border border-gray-300">
  35. <!-- 表头 -->
  36. <thead>
  37. <tr class="bg-gray-100">
  38. <th class="border border-gray-300 px-4 py-2 text-center font-medium">组别</th>
  39. <th
  40. v-for="track in groupInfo.trackNum"
  41. :key="track"
  42. class="border border-gray-300 px-4 py-2 text-center font-medium"
  43. >
  44. 第{{ track }}道
  45. </th>
  46. </tr>
  47. </thead>
  48. <!-- 分组内容 -->
  49. <tbody>
  50. <tr
  51. v-for="groupIndex in groupInfo.includeGroupNum"
  52. :key="groupIndex"
  53. class="hover:bg-gray-50"
  54. >
  55. <td class="border border-gray-300 px-4 py-3 text-center font-medium bg-blue-50">
  56. 第{{ groupIndex }}组
  57. </td>
  58. <td
  59. v-for="track in groupInfo.trackNum"
  60. :key="track"
  61. class="border border-gray-300 px-4 py-3 text-center"
  62. >
  63. <div v-if="getAthleteByGroupAndTrack(groupIndex, track)" class="space-y-1">
  64. <div class="font-medium text-blue-600">{{ getAthleteByGroupAndTrack(groupIndex, track)?.athleteCode }}</div>
  65. <div class="text-sm">{{ getAthleteByGroupAndTrack(groupIndex, track)?.name }}</div>
  66. <div class="text-xs text-gray-500">{{ getAthleteByGroupAndTrack(groupIndex, track)?.teamName || getTeamName(getAthleteByGroupAndTrack(groupIndex, track)?.teamId) }}</div>
  67. </div>
  68. <div v-else class="text-gray-400 text-sm">-</div>
  69. </td>
  70. </tr>
  71. </tbody>
  72. </table>
  73. </div>
  74. </el-card>
  75. <!-- 统计信息 -->
  76. <el-card shadow="hover" class="mt-4">
  77. <template #header>
  78. <span class="font-medium">分组统计</span>
  79. </template>
  80. <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
  81. <div class="text-center p-3 bg-blue-50 rounded-lg">
  82. <div class="text-2xl font-bold text-blue-600">{{ totalAthletes }}</div>
  83. <div class="text-sm text-gray-600">符合条件的运动员数量</div>
  84. </div>
  85. <div class="text-center p-3 bg-green-50 rounded-lg">
  86. <div class="text-2xl font-bold text-green-600">{{ groupInfo.includeGroupNum }}</div>
  87. <div class="text-sm text-gray-600">分组数</div>
  88. </div>
  89. <div class="text-center p-3 bg-purple-50 rounded-lg">
  90. <div class="text-2xl font-bold text-purple-600">{{ groupInfo.trackNum }}</div>
  91. <div class="text-sm text-gray-600">道数</div>
  92. </div>
  93. <!-- <div class="text-center p-3 bg-orange-50 rounded-lg">
  94. <div class="text-2xl font-bold text-orange-600">{{ groupInfo.personNum }}</div>
  95. <div class="text-sm text-gray-600">{{ groupInfo.groupName }}人数</div>
  96. </div> -->
  97. <div class="text-center p-3 bg-orange-50 rounded-lg">
  98. <div class="text-2xl font-bold text-orange-600">{{ roundType }}</div>
  99. <div class="text-sm text-gray-600">录取人数</div>
  100. </div>
  101. </div>
  102. </el-card>
  103. </div>
  104. </template>
  105. <script setup name="GameEventGroupDetail" lang="ts">
  106. import { ref, onMounted, getCurrentInstance, onUpdated } from 'vue';
  107. import { useRoute, useRouter } from 'vue-router';
  108. import { getGameEventGroup, generateGroups, getGroupResultFromDB } from '@/api/system/gameEventGroup';
  109. import { getGameEventProject } from '@/api/system/gameEventProject';
  110. import { GameEventGroupVO } from '@/api/system/gameEventGroup/types';
  111. import type { ComponentInternalInstance } from 'vue';
  112. const route = useRoute();
  113. const router = useRouter();
  114. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  115. // 分组信息
  116. const groupInfo = ref<GameEventGroupVO>({} as GameEventGroupVO);
  117. // 分组结果
  118. const groupResult = ref<Map<string, any>>(new Map());
  119. // 加载状态
  120. const loading = ref(false);
  121. // 生成分组状态
  122. const generating = ref(false);
  123. // 项目名称和录取人数
  124. const projectName = ref('');
  125. const roundType = ref(0);
  126. // 总运动员数
  127. const totalAthletes = ref(0);
  128. // 获取道次名称
  129. const getTrackName = (track: number) => {
  130. const trackNames = ['一', '二', '三', '四', '五', '六', '七', '八','九','十'];
  131. return trackNames[track - 1] || track;
  132. };
  133. // 根据组别和道次获取运动员
  134. const getAthleteByGroupAndTrack = (groupIndex: number, track: number) => {
  135. const key = `${groupIndex}-${track}`;
  136. return groupResult.value.get(key);
  137. };
  138. // 根据队伍ID获取队伍名称
  139. const getTeamName = (teamId: string | number | undefined) => {
  140. if (!groupResult.value.has('teams')) return '';
  141. const teams = groupResult.value.get('teams') || [];
  142. const team = teams.find((t: any) => t.teamId === teamId);
  143. return team?.teamName || '';
  144. };
  145. // 获取分组信息
  146. const getGroupInfo = async () => {
  147. try {
  148. loading.value = true;
  149. const groupId = route.params.groupId;
  150. if (!groupId || Array.isArray(groupId)) {
  151. proxy?.$modal.msgError('分组ID不能为空');
  152. return;
  153. }
  154. const res = await getGameEventGroup(groupId);
  155. groupInfo.value = res.data;
  156. // 优先从数据库读取分组数据
  157. await loadGroupResultFromDB();
  158. } catch (error) {
  159. console.error('获取分组信息失败:', error);
  160. proxy?.$modal.msgError('获取分组信息失败');
  161. } finally {
  162. loading.value = false;
  163. }
  164. };
  165. // 从数据库加载分组结果
  166. const loadGroupResultFromDB = async () => {
  167. try {
  168. const groupId = route.params.groupId;
  169. if (!groupId || Array.isArray(groupId)) {
  170. proxy?.$modal.msgError('分组ID不能为空');
  171. return;
  172. }
  173. const res = await getGroupResultFromDB(groupId);
  174. const data = res.data;
  175. if (data.success) {
  176. // 更新分组结果
  177. groupResult.value.clear();
  178. // 设置分组结果
  179. if (data.groupResult) {
  180. Object.entries(data.groupResult).forEach(([key, athlete]) => {
  181. groupResult.value.set(key, athlete);
  182. });
  183. }
  184. if (data.totalAthletes !== undefined) {
  185. totalAthletes.value = data.totalAthletes;
  186. }
  187. // 设置项目信息(从分组信息中获取)
  188. if (groupInfo.value.projectId) {
  189. // 获取项目详细信息
  190. try {
  191. const projectRes = await getGameEventProject(groupInfo.value.projectId);
  192. const projectInfo = projectRes.data;
  193. projectName.value = projectInfo.projectName;
  194. roundType.value = projectInfo.roundType ? parseInt(projectInfo.roundType) : 0;
  195. } catch (error) {
  196. console.error('获取项目信息失败:', error);
  197. projectName.value = '项目名称'; // 默认值
  198. roundType.value = 0; // 默认值
  199. }
  200. }
  201. console.log('从数据库加载分组结果成功');
  202. } else {
  203. // 数据库中没有数据,自动生成分组
  204. console.log('数据库中没有分组数据,自动生成分组');
  205. await generateGroupsData();
  206. }
  207. } catch (error) {
  208. console.error('从数据库加载分组结果失败:', error);
  209. // 加载失败时,自动生成分组
  210. await generateGroupsData();
  211. }
  212. };
  213. // 生成分组
  214. const generateGroupsData = async () => {
  215. try {
  216. generating.value = true;
  217. const groupId = route.params.groupId;
  218. if (!groupId || Array.isArray(groupId)) {
  219. proxy?.$modal.msgError('分组ID不能为空');
  220. return;
  221. }
  222. const res = await generateGroups(groupId);
  223. const data = res.data;
  224. if (data.success) {
  225. // 更新分组结果
  226. groupResult.value.clear();
  227. // 设置分组结果
  228. if (data.groupResult) {
  229. Object.entries(data.groupResult).forEach(([key, athlete]) => {
  230. groupResult.value.set(key, athlete);
  231. });
  232. }
  233. // 设置其他数据
  234. if (data.project) {
  235. projectName.value = data.project.projectName || '';
  236. roundType.value = data.project.roundType || 0;
  237. }
  238. if (data.totalAthletes !== undefined) {
  239. totalAthletes.value = data.totalAthletes;
  240. }
  241. // 设置队伍信息
  242. if (data.teams) {
  243. groupResult.value.set('teams', data.teams);
  244. }
  245. proxy?.$modal.msgSuccess('分组生成成功');
  246. } else {
  247. proxy?.$modal.msgWarning(data.message || '生成分组失败');
  248. }
  249. } catch (error) {
  250. console.error('生成分组失败:', error);
  251. proxy?.$modal.msgError('生成分组失败');
  252. } finally {
  253. generating.value = false;
  254. }
  255. };
  256. // 重新生成分组
  257. const regenerateGroups = async () => {
  258. await generateGroupsData();
  259. };
  260. // 返回上一页
  261. const goBack = () => {
  262. router.go(-1);
  263. };
  264. onUpdated(() => {
  265. getGroupInfo();
  266. });
  267. onMounted(() => {
  268. getGroupInfo();
  269. });
  270. </script>
  271. <style scoped>
  272. .overflow-x-auto {
  273. overflow-x: auto;
  274. }
  275. /* 响应式表格 */
  276. @media (max-width: 768px) {
  277. .grid {
  278. grid-template-columns: repeat(1, 1fr);
  279. }
  280. .overflow-x-auto {
  281. font-size: 12px;
  282. }
  283. .px-4 {
  284. padding-left: 8px;
  285. padding-right: 8px;
  286. }
  287. .py-3 {
  288. padding-top: 6px;
  289. padding-bottom: 6px;
  290. }
  291. }
  292. </style>