Quellcode durchsuchen

feat(gameScore): 新增看板排名数据接口

新增 getRankingBoardData 接口,提供极简接口用于获取看板排名数据,
后端完成排序和排名计算,减少前端处理逻辑。

refactor(RankingBoardPage): 优化排行榜页面数据获取逻辑

将排行榜数据获取逻辑改为调用新的后端接口,移除前端排序和排名计算逻辑,
改为后端直接返回有序且带排名的数据,提升性能和准确性。

fix(rankGroup): 修正导出文件名中的文字描述

将排名分组导出文件名从 "_排名分组.xlsx" 修改为 "_排名分组组别.xlsx"
以更准确反映文件内容。
zhou vor 2 Wochen
Ursprung
Commit
983d1a2d40

+ 17 - 0
src/api/system/gameScore/index.ts

@@ -109,6 +109,23 @@ export const getProjectScoreData = (params: {
   });
 };
 
+/**
+ * 获取看板排名数据(极简接口,后端完成排序和排名)
+ * @param params
+ */
+export const getRankingBoardData = (params: {
+  eventId: string | number;
+  projectId: string | number;
+  classification: string;
+  rgId?: string | number;
+}) => {
+  return request({
+    url: '/system/gameScore/rankingBoard',
+    method: 'get',
+    params
+  });
+};
+
 /**
  * 更新成绩并重新计算排名积分
  * @param data 成绩数据

+ 18 - 130
src/views/system/gameEvent/RankingBoardPage.vue

@@ -233,8 +233,7 @@
 
 <script setup lang="ts">
 import { ref, computed, onMounted, onUnmounted, getCurrentInstance, toRefs } from 'vue';
-import { listScoreRanking, listPersonalRanking, listTeamRanking } from '@/api/system/gameEvent/eventRank';
-import { listGameScore, getBonusData, getProjectScoreData } from '@/api/system/gameScore';
+import { getProjectScoreData, getRankingBoardData, updateBonusData, exportBonusExcel } from '@/api/system/gameScore';
 import { listGameEventProject } from '@/api/system/gameEventProject';
 import { getProjectProgress } from '@/api/system/gameEvent/projectProgress';
 import { ProjectProgressVo, GroupProgressVo } from '@/api/system/gameEvent/types';
@@ -359,51 +358,29 @@ const handleTeamCountChange = (value: number) => {
 // 获取团队项目排行榜数据
 const loadTeamProjectScores = async () => {
   if (!selectedTeamProjectId.value) return;
-  const res = await getProjectScoreData({
+  const res = await getRankingBoardData({
     eventId: defaultEventInfo.value.eventId,
     projectId: selectedTeamProjectId.value,
     classification: '1',
-    pageNum: 1,
-    pageSize: 1000 // 获取全量以便筛选
+    rgId: selectedRankGroupId.value || undefined
   });
   
-  // 创建分组ID到分组名称的映射
-  const groupMap = new Map();
-  rankGroupOptions.value.forEach(group => {
-    groupMap.set(group.rgId, group.rgName);
-  });
-  
-  const teamScoreList = (res.data?.tableData?.rows || []).map(item => {
-    return {
-      ...item,
-      classification: '1',
-      rgName: groupMap.get(item.rgId) || item.rgName || '-'
-    }
-  });
+  // 直接使用后端返回的有序且带排名的列表
+  const teamScoreList = res.data || [];
 
-  // 获取当前项目的排序方式
-  const currentProject = teamProjectOptions.value.find(p => p.projectId == selectedTeamProjectId.value);
-  const orderType = currentProject ? currentProject.orderType : '1'; // 默认降序
-
-  teamScores.value = sortAndRankList(teamScoreList, orderType);
-  // 应用分组筛选
-  handleRankGroupChange(selectedRankGroupId.value);
+  teamScores.value = teamScoreList.map(item => ({
+    ...item,
+    classification: '1'
+  }));
+  
+  // 更新过滤后的显示列表(由于后端已按分组过滤,这里直接同步)
+  filteredTeamScores.value = teamScores.value;
 };
 
 // 处理分组筛选变化
 const handleRankGroupChange = (rgId: string | number | null | undefined) => {
-  if (rgId === null || rgId === undefined || rgId === '' || rgId === ALL_GROUPS_VALUE) {
-    // 显示所有队伍
-    filteredTeamScores.value = teamScores.value;
-  } else {
-    // 筛选所属分组的队伍(现在后端已处理父子关系,但此处是前端过滤已加载的数据)
-    // 注意:如果看板是全量数据在前端筛选,前端依然需要知道哪些子分组属于该父分组
-    // 或者更简单的做法:看板数据在获取时就根据 rgId 传参给后端。
-    
-    // 鉴于看板通常是全量计算好后前端切分,我们这里保留一个简单的打平用于辅助过滤
-    const targetIds = getFlatIds(rgId);
-    filteredTeamScores.value = teamScores.value.filter(team => targetIds.includes(team.rgId));
-  }
+  // 分组筛选直接触发后端查询,不再在前端过滤
+  loadTeamProjectScores();
 };
 
 // 从扁平列表中获取某个节点下的所有子孙ID(用于前端筛选)
@@ -530,89 +507,6 @@ const loadProjectProgress = async () => {
   }
 };
 
-// 获取成绩的数值表示(用于排序)
-const getNumericValue = (val: string | number | null | undefined) => {
-  if (val === null || val === undefined || val === '' || val === '0' || val === '00:00:00.000') return 0;
-  if (typeof val === 'number') return val;
-  
-  const valStr = String(val);
-  // 处理计时格式 00:00:00.000
-  if (valStr.includes(':')) {
-    const parts = valStr.split(':');
-    if (parts.length === 3) {
-      const hours = parseInt(parts[0]) || 0;
-      const minutes = parseInt(parts[1]) || 0;
-      const secondsWithMs = parts[2];
-      let seconds = 0;
-      let ms = 0;
-      if (secondsWithMs.includes('.')) {
-        const secondsParts = secondsWithMs.split('.');
-        seconds = parseInt(secondsParts[0]) || 0;
-        ms = parseInt(secondsParts[1]) || 0;
-      } else {
-        seconds = parseInt(secondsWithMs) || 0;
-      }
-      return hours * 3600000 + minutes * 60000 + seconds * 1000 + ms;
-    }
-  }
-  
-  const parsed = parseFloat(valStr);
-  return isNaN(parsed) ? 0 : parsed;
-};
-
-// 前端排序与排名处理
-const sortAndRankList = (list: any[], orderType: string) => {
-  if (!list || list.length === 0) return [];
-  
-  // 成绩取值助手函数
-  const getScoreValue = (item: any) => {
-    if (item.classification == '0') {
-      return item.individualPerformance ?? item.score ?? item.totalScore;
-    } else {
-      return item.teamPerformance ?? item.score ?? item.totalScore;
-    }
-  };
-
-  // 1. 排序逻辑
-  const sorted = [...list].sort((a, b) => {
-    const valA = getScoreValue(a);
-    const valB = getScoreValue(b);
-    
-    const scoreA = getNumericValue(valA);
-    const scoreB = getNumericValue(valB);
-    
-    // 0 值(未参赛/无效成绩)永远排在最后
-    if (scoreA === 0 && scoreB === 0) return 0;
-    if (scoreA === 0) return 1;
-    if (scoreB === 0) return -1;
-    
-    if (orderType == '0') { // 升序(计时类,用时越短越好)
-      return scoreA - scoreB;
-    } else { // 降序(远度/积分,分值越高越好)
-      return scoreB - scoreA;
-    }
-  });
-  
-  // 2. 计算排名(支持并列)
-  let currentRank = 0;
-  let lastScoreValue = -1;
-  
-  return sorted.map((item, index) => {
-    const val = getScoreValue(item);
-    const scoreValue = getNumericValue(val);
-    
-    if (scoreValue === 0) {
-      item.rank = '-'; // 无效成绩不计排名
-    } else if (scoreValue !== lastScoreValue) {
-      currentRank = index + 1;
-      lastScoreValue = scoreValue;
-      item.rank = currentRank;
-    } else {
-      item.rank = currentRank;
-    }
-    return item;
-  });
-};
 
 // 获取排名显示文本
 const getRankDisplay = (item: any, index: number, list: any[]) => {
@@ -627,21 +521,15 @@ const refreshPersonalRanking = async () => {
   if (!selectedProjectId.value) return;
   personalRefreshing.value = true;
   try {
-    const res = await getProjectScoreData({
+    const res = await getRankingBoardData({
       eventId: defaultEventInfo.value.eventId,
       projectId: selectedProjectId.value,
-      classification: '0',
-      pageNum: 1,
-      pageSize: personalDisplayCount.value
+      classification: '0'
     });
-    // 接口返回的是分页数据,取 rows
-    const rows = (res.data?.tableData?.rows || []).map(item => ({ ...item, classification: '0' }));
     
-    // 获取当前项目的排序方式
-    const currentProject = personalProjectOptions.value.find(p => p.projectId == selectedProjectId.value);
-    const orderType = currentProject ? currentProject.orderType : '1'; // 默认降序
+    // 直接使用后端返回的成品数据
+    athleteScoreList.value = (res.data || []).map(item => ({ ...item, classification: '0' }));
     
-    athleteScoreList.value = sortAndRankList(rows, orderType);
     // 手动刷新时重置倒计时
     startCountdown();
     // 显示成功消息

+ 1 - 1
src/views/system/rankGroup/index.vue

@@ -258,7 +258,7 @@ const handleDelete = async (row: RankGroupVO) => {
 const handleExport = () => {
   proxy?.download('system/rankGroup/export', {
     ...queryParams.value
-  }, `${gameEventStore.defaultEventInfo?.eventCode || ''}_排名分组.xlsx`)
+  }, `${gameEventStore.defaultEventInfo?.eventCode || ''}_排名分组组别.xlsx`)
 };
 
 onMounted(() => {