Browse Source

feat(system): 添加成绩汇总表导出功能

- 新增 exportScoresSummary API 用于导出成绩汇总表
- 在 GameScore 组件中实现导出成绩汇总表的逻辑
- 优化了请求拦截器,支持处理 blob 类型的响应
- 移除了默认赛事信息的获取和使用,改为使用 store 中的默认赛事信息
zhou 3 days ago
parent
commit
98029c5e06
3 changed files with 134 additions and 20 deletions
  1. 13 0
      src/api/system/gameScore/index.ts
  2. 2 2
      src/utils/request.ts
  3. 119 18
      src/views/system/gameScore/index.vue

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

@@ -133,3 +133,16 @@ export const recalculateRankingsAndPoints = (eventId: string | number, projectId
     params: { eventId, projectId }
   });
 };
+
+/**
+ * 导出成绩汇总表
+ * @param eventId 赛事ID
+ */
+export const exportScoresSummary = (eventId: string | number) => {
+  return request({
+    url: '/system/gameScore/exportScoresSummary',
+    method: 'post',
+    params: { eventId },
+    responseType: 'blob' // 确保以二进制流形式接收响应
+  });
+};

+ 2 - 2
src/utils/request.ts

@@ -123,8 +123,8 @@ service.interceptors.response.use(
     // 获取错误信息
     const msg = errorCode[code] || res.data.msg || errorCode['default'];
     // 二进制数据则直接返回
-    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
-      return res.data;
+    if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer' || res.config.responseType === 'blob' || res.config.responseType === 'arraybuffer') {
+      return res;
     }
     if (code === 401) {
       // prettier-ignore

+ 119 - 18
src/views/system/gameScore/index.vue

@@ -96,7 +96,7 @@
 </template>
 
 <script setup name="GameScore" lang="ts">
-import { listGameScore, getGameScore, delGameScore, addGameScore, updateGameScore, getProjectScoreData } from '@/api/system/gameScore';
+import { listGameScore, getGameScore, delGameScore, addGameScore, updateGameScore, getProjectScoreData, exportScoresSummary } from '@/api/system/gameScore';
 import { getDefaultEvent } from '@/api/system/gameEvent'
 import { listGameEventProject } from '@/api/system/gameEventProject';
 import { getGameTeam } from '@/api/system/gameTeam';
@@ -106,13 +106,16 @@ import { GameEventVO, GameEventQuery } from '@/api/system/gameEvent/types';
 import { GameEventProjectVO, GameEventProjectQuery } from '@/api/system/gameEventProject/types';
 import { GameTeamVO } from '@/api/system/gameTeam/types';
 import { GameAthleteVO } from '@/api/system/gameAthlete/types';
+import { ElLoading, ElMessage } from 'element-plus';
+import { useGameEventStore } from '@/store/modules/gameEvent';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { game_project_type } = toRefs<any>(proxy?.useDict('game_project_type'));
 const router = useRouter();
 
 // 默认赛事信息
-const defaultEvent = ref<GameEventVO>({} as GameEventVO)
+// const defaultEvent = ref<GameEventVO>({} as GameEventVO)
+const gameEventStore = useGameEventStore();
 const gameScoreList = ref<GameScoreVO[]>([]);
 
 const buttonLoading = ref(false);
@@ -171,25 +174,25 @@ const data = reactive<PageData<GameScoreForm, GameScoreQuery>>({
 
 const { queryParams, form, rules } = toRefs(data);
 // 添加额外的ref用于处理默认事件ID
-const defaultEventId = computed(() => defaultEvent.value?.eventId);
+// const defaultEventId = computed(() => defaultEvent.value?.eventId);
 
 // 监听默认事件变化
-watchEffect(() => {
-  if (defaultEventId.value) {
-    form.value.eventId = defaultEventId.value;
-    queryParams.value.eventId = defaultEventId.value;
-  }
-});
+// watchEffect(() => {
+//   if (defaultEventId.value) {
+//     form.value.eventId = defaultEventId.value;
+//     queryParams.value.eventId = defaultEventId.value;
+//   }
+// });
 
 // 获取默认赛事
-const getDefaultEventInfo = async () => {
-  try {
-    const res = await getDefaultEvent()
-    defaultEvent.value = res.data
-  } catch (error) {
-    ElMessage.error('获取默认赛事失败')
-  }
-}
+// const getDefaultEventInfo = async () => {
+//   try {
+//     const res = await getDefaultEvent()
+//     defaultEvent.value = res.data
+//   } catch (error) {
+//     ElMessage.error('获取默认赛事失败')
+//   }
+// }
 
 /** 查询成绩列表 */
 const getList = async () => {
@@ -211,6 +214,7 @@ const getList = async () => {
 const refreshData = async () => {
   await loadProjects();
 };
+// 打印成绩
 const printScores = async () => {
   try {
     // 显示加载状态
@@ -506,8 +510,105 @@ const getGroupTypeName = (groupNum: number) => {
   };
   return groupMap[groupNum] || `${groupNum}组`;
 };
+
 const exportScoresNames = async () => { 
-  console.log('导出成绩逻辑待实现');
+  try {
+    // 显示加载状态
+    const loadingInstance = ElLoading.service({
+      lock: true,
+      text: '正在导出成绩汇总表...',
+      background: 'rgba(0, 0, 0, 0.7)'
+    });
+
+    // 获取默认赛事ID
+    const event = gameEventStore.defaultEventInfo;
+    const eventId = event?.eventId;
+    
+    if (!eventId) {
+      proxy?.$modal.msgWarning('未指定赛事,无法导出');
+      loadingInstance.close();
+      return;
+    }
+
+    // 调用导出接口
+    const response = await exportScoresSummary(eventId);
+    
+    // 校验响应是否为有效的二进制数据
+    if (!response || !response.data || !(response.data instanceof Blob)) {
+      proxy?.$modal.msgError('导出失败:服务器返回数据异常');
+      loadingInstance.close();
+      return;
+    }
+
+    // 创建Blob时,明确指定类型
+    const blob = new Blob([response.data], { 
+      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
+    });
+    
+    // 验证Blob大小是否合理(防止空文件)
+    if (blob.size === 0) {
+      proxy?.$modal.msgError('导出失败:生成的文件为空');
+      loadingInstance.close();
+      return;
+    }
+
+    const url = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `成绩汇总表_${new Date().toLocaleDateString()}.xlsx`;
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(url);
+    
+    loadingInstance.close();
+    proxy?.$modal.msgSuccess('导出成功');
+    
+  } catch (error) {
+    console.error('导出失败:', error);
+    let errorMessage = '未知错误';
+    
+    if (error instanceof Error) {
+      errorMessage = error.message;
+    } else if (typeof error === 'string') {
+      errorMessage = error;
+    } else if (error && typeof error === 'object' && 'message' in error) {
+      errorMessage = String(error.message);
+    }
+    
+    // 尝试获取更详细的错误信息
+    if (error && typeof error === 'object' && 'response' in error) {
+      const response = (error as any).response;
+      if (response && response.data) {
+        try {
+          if (response.data instanceof Blob) {
+            // 如果是blob,尝试读取错误信息
+            const reader = new FileReader();
+            reader.onload = function(e) {
+              try {
+                const text = e.target?.result as string;
+                const errorObj = JSON.parse(text);
+                if (errorObj.msg) {
+                  errorMessage = errorObj.msg;
+                }
+              } catch (parseError) {
+                console.warn('无法解析错误响应:', parseError);
+              }
+            };
+            reader.readAsText(response.data);
+          } else if (typeof response.data === 'string') {
+            errorMessage = response.data;
+          } else if (response.data.msg) {
+            errorMessage = response.data.msg;
+          }
+        } catch (parseError) {
+          console.warn('解析错误响应失败:', parseError);
+        }
+      }
+    }
+    
+    proxy?.$modal.msgError('导出失败:' + errorMessage);
+  }
 };
 
 /**