From 858f515995fd1dca7cf825069ce38c32703298d0 Mon Sep 17 00:00:00 2001
From: peng <peng.com>
Date: 星期五, 07 十一月 2025 14:14:50 +0800
Subject: [PATCH] 报名人员导出

---
 web/src/views/activity-list.vue                                                     |  242 ++++++++++++++++++++++++++++-
 backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java |  133 ++++++++++++++++
 backend/src/main/java/com/rongyichuang/player/api/PlayerExportController.java       |   63 +++++++
 backend/src/main/java/com/rongyichuang/config/WebConfig.java                        |   11 +
 backend/src/main/java/com/rongyichuang/config/SecurityConfig.java                   |   23 ++
 5 files changed, 453 insertions(+), 19 deletions(-)

diff --git a/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java b/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java
index 92af34a..269f959 100644
--- a/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java
+++ b/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java
@@ -14,6 +14,7 @@
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.CorsConfigurationSource;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@@ -48,12 +49,22 @@
             .csrf(csrf -> csrf.disable())
             .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
             .authorizeHttpRequests(auth -> auth
-                // 娉ㄦ剰锛氬簲鐢ㄨ缃簡 context-path=/api锛屼负閬垮厤鍖归厤姝т箟锛岃繖閲屽悓鏃跺尮閰嶅幓闄ゅ拰鍖呭惈 context-path 鐨勮矾寰�
-                .requestMatchers("/auth/**", "/api/auth/**", "/actuator/**", "/test/**", "/cleanup/**").permitAll()
-                .requestMatchers("/api/health/**").permitAll() // 鍏佽鍋ュ悍妫�鏌ョ鐐硅闂�
-                .requestMatchers("/upload/**").permitAll()
-                .requestMatchers("/graphiql/**", "/graphql/**", "/api/graphql/**", "/api/graphiql/**").permitAll() // 鍏佽GraphQL鍜孏raphiQL璁块棶
-                .requestMatchers("/**/graphql", "/**/graphiql").permitAll() // 鏇村娉涚殑GraphQL璺緞鍖归厤
+                .requestMatchers(
+                    new AntPathRequestMatcher("/auth/**"),
+                    new AntPathRequestMatcher("/api/auth/**"),
+                    new AntPathRequestMatcher("/actuator/**"),
+                    new AntPathRequestMatcher("/test/**"),
+                    new AntPathRequestMatcher("/cleanup/**"),
+                    new AntPathRequestMatcher("/api/health/**"),
+                    new AntPathRequestMatcher("/upload/**"),
+                    new AntPathRequestMatcher("/api/upload/**"),
+                    new AntPathRequestMatcher("/graphiql/**"),
+                    new AntPathRequestMatcher("/graphql/**"),
+                    new AntPathRequestMatcher("/api/graphql/**"),
+                    new AntPathRequestMatcher("/api/graphiql/**"),
+                    new AntPathRequestMatcher("/player/export/applications"),
+                    new AntPathRequestMatcher("/api/player/export/applications")
+                ).permitAll()
                 .anyRequest().authenticated()
             )
             .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
diff --git a/backend/src/main/java/com/rongyichuang/config/WebConfig.java b/backend/src/main/java/com/rongyichuang/config/WebConfig.java
index e627d27..633842e 100644
--- a/backend/src/main/java/com/rongyichuang/config/WebConfig.java
+++ b/backend/src/main/java/com/rongyichuang/config/WebConfig.java
@@ -3,15 +3,24 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.util.pattern.PathPatternParser;
 
 /**
  * Web閰嶇疆绫�
  */
 @Configuration
-public class WebConfig {
+public class WebConfig implements WebMvcConfigurer {
     
     @Bean
     public RestTemplate restTemplate() {
         return new RestTemplate();
     }
+    
+    @Override
+    public void configurePathMatch(PathMatchConfigurer configurer) {
+        // 浣跨敤PathPatternParser鑰屼笉鏄疉ntPathMatcher
+        configurer.setPatternParser(new PathPatternParser());
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/player/api/PlayerExportController.java b/backend/src/main/java/com/rongyichuang/player/api/PlayerExportController.java
new file mode 100644
index 0000000..146273c
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/player/api/PlayerExportController.java
@@ -0,0 +1,63 @@
+package com.rongyichuang.player.api;
+
+import com.rongyichuang.player.service.PlayerApplicationService;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+@RestController
+@RequestMapping("/player/export")
+public class PlayerExportController {
+
+    private static final Logger log = LoggerFactory.getLogger(PlayerExportController.class);
+
+    private final PlayerApplicationService playerApplicationService;
+
+    public PlayerExportController(PlayerApplicationService playerApplicationService) {
+        this.playerApplicationService = playerApplicationService;
+    }
+
+    /**
+     * 瀵煎嚭姣旇禌鎶ュ悕浜哄憳Excel
+     */
+    @GetMapping("/applications")
+    public void exportApplications(
+            @RequestParam(required = false) String name,
+            @RequestParam(required = false) Long activityId,
+            @RequestParam(required = false) Integer state,
+            HttpServletResponse response) {
+        try {
+            log.info("瀵煎嚭姣旇禌鎶ュ悕浜哄憳Excel, name: {}, activityId: {}, state: {}", name, activityId, state);
+            
+            // 鑾峰彇Excel鏁版嵁
+            byte[] excelData = playerApplicationService.exportApplicationsToExcel(name, activityId, state);
+            
+            // 璁剧疆鍝嶅簲澶�
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            String fileName = "鎶ュ悕浜哄憳_" + System.currentTimeMillis() + ".xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
+            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodedFileName);
+            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
+            response.setContentLength(excelData.length);
+            
+            // 鍐欏叆鍝嶅簲
+            response.getOutputStream().write(excelData);
+            response.getOutputStream().flush();
+        } catch (Exception e) {
+            log.error("瀵煎嚭姣旇禌鎶ュ悕浜哄憳Excel澶辫触", e);
+            try {
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                response.getWriter().write("瀵煎嚭澶辫触: " + e.getMessage());
+            } catch (IOException ioException) {
+                log.error("鍐欏叆閿欒鍝嶅簲澶辫触", ioException);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java b/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java
index 772be77..fe813f9 100644
--- a/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java
+++ b/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java
@@ -4,8 +4,12 @@
 import com.rongyichuang.player.dto.response.ActivityPlayerApplicationResponse;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.stereotype.Service;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -124,6 +128,135 @@
     }
 
     /**
+     * 瀵煎嚭娲诲姩鎶ュ悕淇℃伅涓篍xcel
+     */
+    @SuppressWarnings("unchecked")
+    public byte[] exportApplicationsToExcel(String name, Long activityId, Integer state) throws IOException {
+        String baseSql =
+            "SELECT ap.id, p.name AS player_name, parent.name AS activity_name, ap.project_name AS project_name, u.phone AS phone, ap.create_time AS apply_time, ap.state AS state, " +
+            "COALESCE(rating_stats.rating_count, 0) AS rating_count, rating_stats.average_score " +
+            "FROM t_activity_player ap " +
+            "JOIN t_player p ON p.id = ap.player_id " +
+            "JOIN t_user u ON u.id = p.user_id " +
+            "JOIN t_activity stage ON stage.id = ap.stage_id " +
+            "JOIN t_activity parent ON parent.id = stage.pid " +
+            "LEFT JOIN (" +
+            "  SELECT activity_player_id, COUNT(*) AS rating_count, AVG(total_score) AS average_score " +
+            "  FROM t_activity_player_rating " +
+            "  WHERE state = 1 " +
+            "  GROUP BY activity_player_id" +
+            ") rating_stats ON rating_stats.activity_player_id = ap.id ";
+        
+        StringBuilder whereClause = new StringBuilder();
+        boolean hasCondition = false;
+        
+        // 榛樿鍙樉绀虹涓�闃舵鐨勬暟鎹紙鍩轰簬sort_order=1锛夛紝閬垮厤纭紪鐮侀樁娈靛悕绉�
+        whereClause.append("stage.sort_order = 1");
+        hasCondition = true;
+        
+        if (name != null && !name.isEmpty()) {
+            if (hasCondition) {
+                whereClause.append(" AND ");
+            }
+            whereClause.append("p.name LIKE CONCAT('%', :name, '%')");
+            hasCondition = true;
+        }
+        
+        if (activityId != null) {
+            if (hasCondition) {
+                whereClause.append(" AND ");
+            }
+            // 鏌ヨ鎸囧畾涓绘瘮璧涚殑绗竴闃舵鎶ュ悕椤圭洰锛歛ctivity_id=涓绘瘮璧汭D, stage_id=绗竴闃舵ID
+            whereClause.append("ap.activity_id = :activityId AND ap.stage_id = stage.id AND stage.pid = :activityId AND stage.sort_order = 1");
+            hasCondition = true;
+        }
+        
+        if (state != null) {
+            if (hasCondition) {
+                whereClause.append(" AND ");
+            }
+            whereClause.append("ap.state = :state");
+            hasCondition = true;
+        }
+        
+        String where = hasCondition ? "WHERE " + whereClause.toString() + " " : "";
+        String order = "ORDER BY ap.create_time DESC ";
+
+        var q = em.createNativeQuery(baseSql + where + order);
+        if (name != null && !name.isEmpty()) {
+            q.setParameter("name", name);
+        }
+        if (activityId != null) {
+            q.setParameter("activityId", activityId);
+        }
+
+        if (state != null) {
+            q.setParameter("state", state);
+        }
+        List<Object[]> rows = q.getResultList();
+        
+        // 鍒涘缓Excel宸ヤ綔绨�
+        Workbook workbook = new XSSFWorkbook();
+        Sheet sheet = workbook.createSheet("鎶ュ悕浜哄憳");
+        
+        // 鍒涘缓鏍囬琛�
+        Row headerRow = sheet.createRow(0);
+        String[] headers = {"ID", "瀛﹀憳鍚嶇О", "姣旇禌鍚嶇О", "椤圭洰鍚嶇О", "鑱旂郴鐢佃瘽", "鐢宠鏃堕棿", "鐘舵��", "璇勫垎娆℃暟", "骞冲潎鍒�"};
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+            
+            // 璁剧疆鏍囬鏍峰紡
+            CellStyle headerStyle = workbook.createCellStyle();
+            Font font = workbook.createFont();
+            font.setBold(true);
+            headerStyle.setFont(font);
+            cell.setCellStyle(headerStyle);
+        }
+        
+        // 濉厖鏁版嵁
+        int rowNum = 1;
+        for (Object[] r : rows) {
+            Row row = sheet.createRow(rowNum++);
+            row.createCell(0).setCellValue(r[0] != null ? r[0].toString() : "");
+            row.createCell(1).setCellValue(r[1] != null ? r[1].toString() : "");
+            row.createCell(2).setCellValue(r[2] != null ? r[2].toString() : "");
+            row.createCell(3).setCellValue(r[3] != null ? r[3].toString() : "");
+            row.createCell(4).setCellValue(r[4] != null ? r[4].toString() : "");
+            row.createCell(5).setCellValue(r[5] != null ? r[5].toString() : "");
+            
+            // 鐘舵�佽浆鎹�
+            String stateText = "鏈煡";
+            if (r[6] != null) {
+                int stateValue = Integer.parseInt(r[6].toString());
+                switch (stateValue) {
+                    case 0: stateText = "鏈鏍�"; break;
+                    case 1: stateText = "瀹℃牳閫氳繃"; break;
+                    case 2: stateText = "瀹℃牳椹冲洖"; break;
+                    default: stateText = "鏈煡";
+                }
+            }
+            row.createCell(6).setCellValue(stateText);
+            
+            row.createCell(7).setCellValue(r[7] != null ? r[7].toString() : "0");
+            row.createCell(8).setCellValue(r[8] != null ? r[8].toString() : "");
+        }
+        
+        // 鑷姩璋冩暣鍒楀
+        for (int i = 0; i < headers.length; i++) {
+            sheet.autoSizeColumn(i);
+        }
+        
+        // 灏嗗伐浣滅翱鍐欏叆瀛楄妭鏁扮粍
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        workbook.write(outputStream);
+        workbook.close();
+        outputStream.close();
+        
+        return outputStream.toByteArray();
+    }
+
+    /**
      * 椤圭洰璇勫涓撶敤鏌ヨ锛屽寘鍚墍鏈夐樁娈垫暟鎹紙鍖呮嫭澶嶈禌銆佸喅璧涳級
      * 涓巐istApplications鐨勫尯鍒細涓嶈繃婊ゅ璧涘拰鍐宠禌闃舵
      */
diff --git a/web/src/views/activity-list.vue b/web/src/views/activity-list.vue
index c1eec42..5f935b8 100644
--- a/web/src/views/activity-list.vue
+++ b/web/src/views/activity-list.vue
@@ -54,9 +54,25 @@
             <el-tag :type="getStatusType(row.stateName)">{{ row.stateName }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="鎿嶄綔" width="180" fixed="right" align="center">
+        <el-table-column label="鎿嶄綔" width="220" fixed="right" align="center">
           <template #default="{ row }">
             <div class="table-actions">
+              <el-button
+                text
+                :icon="User"
+                size="small"
+                @click="handleViewPlayers(row)"
+                class="action-btn players-btn"
+                title="鏌ョ湅鎶ュ悕浜哄憳"
+              />
+              <el-button
+                text
+                :icon="Download"
+                size="small"
+                @click="handleExportPlayers(row)"
+                class="action-btn export-btn"
+                title="瀵煎嚭鎶ュ悕浜哄憳"
+              />
               <el-button
                 text
                 :icon="View"
@@ -99,6 +115,52 @@
         />
       </div>
     </div>
+
+    <!-- 鏌ョ湅鎶ュ悕浜哄憳寮圭獥 -->
+    <el-dialog
+      v-model="playerDialogVisible"
+      :title="'鎶ュ悕浜哄憳 - ' + (currentActivity?.name || '')"
+      width="80%"
+      @close="handlePlayerDialogClose"
+    >
+      <!-- 寮圭獥宸ュ叿鏍� -->
+      <div class="dialog-toolbar">
+        <el-button type="primary" :icon="Download" @click="handleExportPlayersFromDialog">
+          瀵煎嚭Excel
+        </el-button>
+      </div>
+
+      <el-table :data="playerList" v-loading="playerListLoading" style="width: 100%">
+        <el-table-column prop="playerName" label="瀛﹀憳鍚嶇О" min-width="120" />
+        <el-table-column prop="projectName" label="椤圭洰鍚嶇О" min-width="150" />
+        <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" width="140" />
+        <el-table-column prop="applyTime" label="鐢宠鏃堕棿" width="180" />
+        <el-table-column prop="state" label="鐘舵��" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="getPlayerStateType(row.state)">{{ getPlayerStateText(row.state) }}</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 鎶ュ悕浜哄憳鍒嗛〉 -->
+      <div class="pagination" v-if="playerPagination.total > 0">
+        <el-pagination
+          v-model:current-page="playerPagination.page"
+          v-model:page-size="playerPagination.size"
+          :page-sizes="[10, 20, 50, 100]"
+          :total="playerPagination.total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handlePlayerSizeChange"
+          @current-change="handlePlayerCurrentChange"
+        />
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="playerDialogVisible = false">鍏抽棴</el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -106,8 +168,10 @@
 import { reactive, ref, onMounted, onActivated, watch } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useRouter } from 'vue-router'
-import { getActivities, updateActivityState } from '@/api/activity'
-import { Search, Plus, Edit, Delete, View } from '@element-plus/icons-vue'
+import { getActivities, deleteActivity } from '@/api/activity'
+// @ts-ignore
+import { PlayerApi } from '@/api/player'
+import { Search, Plus, Edit, Delete, View, User, Download } from '@element-plus/icons-vue'
 
 console.log('=== activity-list.vue 缁勪欢寮�濮嬪姞杞� ===')
 
@@ -136,6 +200,17 @@
 // 琛ㄦ牸鏁版嵁
 const tableData = ref([])
 
+// 鎶ュ悕浜哄憳寮圭獥鐩稿叧
+const playerDialogVisible = ref(false)
+const playerListLoading = ref(false)
+const playerList = ref([])
+const playerPagination = reactive({
+  page: 1,
+  size: 10,
+  total: 0
+})
+const currentActivity = ref<any>(null)
+
 // 璋冭瘯鐢ㄩ�旓細鐩戝惉琛ㄦ牸鏁版嵁鍙樺寲
 watch(
   tableData,
@@ -158,6 +233,26 @@
     鍏抽棴: 'danger'
   }
   return typeMap[status] || 'info'
+}
+
+// 鑾峰彇鎶ュ悕浜哄憳鐘舵�佹爣绛剧被鍨�
+const getPlayerStateType = (state: number) => {
+  const typeMap: Record<number, string> = {
+    0: 'info',    // 鏈鏍�
+    1: 'success', // 瀹℃牳閫氳繃
+    2: 'danger'   // 瀹℃牳椹冲洖
+  }
+  return typeMap[state] || 'info'
+}
+
+// 鑾峰彇鎶ュ悕浜哄憳鐘舵�佹枃鏈�
+const getPlayerStateText = (state: number) => {
+  const textMap: Record<number, string> = {
+    0: '鏈鏍�',
+    1: '瀹℃牳閫氳繃',
+    2: '瀹℃牳椹冲洖'
+  }
+  return textMap[state] || '鏈煡'
 }
 
 // 鎼滅储
@@ -192,6 +287,107 @@
   router.push(`/activity/${row.id}`)
 }
 
+// 鏌ョ湅鎶ュ悕浜哄憳寮圭獥鍏抽棴澶勭悊
+const handlePlayerDialogClose = () => {
+  // 娓呯┖褰撳墠娲诲姩
+  currentActivity.value = null
+  // 娓呯┖鎶ュ悕浜哄憳鍒楄〃
+  playerList.value = []
+  // 閲嶇疆鍒嗛〉
+  playerPagination.page = 1
+  playerPagination.total = 0
+}
+
+// 鏌ョ湅鎶ュ悕浜哄憳
+const handleViewPlayers = async (row: any) => {
+  // 璁剧疆褰撳墠娲诲姩
+  currentActivity.value = row
+  // 閲嶇疆鍒嗛〉
+  playerPagination.page = 1
+  playerPagination.size = 10
+  // 鏄剧ず寮圭獥
+  playerDialogVisible.value = true
+  // 鍔犺浇鏁版嵁
+  await loadPlayerList()
+}
+
+// 鍔犺浇鎶ュ悕浜哄憳鍒楄〃
+const loadPlayerList = async () => {
+  if (!currentActivity.value) return
+  
+  playerListLoading.value = true
+  try {
+    // @ts-ignore 蹇界暐TypeScript妫�鏌ワ紝鍥犱负鍑芥暟瀹為檯鏀寔鏇村鍙傛暟
+    const data = await PlayerApi.getApplications('', currentActivity.value.id, null, playerPagination.page - 1, playerPagination.size)
+    
+    playerList.value = data.content || []
+    playerPagination.total = data.totalElements || 0
+  } catch (e: any) {
+    console.error('鍔犺浇鎶ュ悕浜哄憳澶辫触:', e)
+    ElMessage.error(e?.message || '鍔犺浇鎶ュ悕浜哄憳澶辫触')
+  } finally {
+    playerListLoading.value = false
+  }
+}
+
+// 鎶ュ悕浜哄憳鍒嗛〉澶勭悊
+const handlePlayerSizeChange = (size: number) => {
+  playerPagination.size = size
+  loadPlayerList()
+}
+
+const handlePlayerCurrentChange = (page: number) => {
+  playerPagination.page = page
+  loadPlayerList()
+}
+
+// 浠庡脊绐楀鍑烘姤鍚嶄汉鍛楨xcel
+const handleExportPlayersFromDialog = async () => {
+  if (!currentActivity.value) {
+    ElMessage.error('褰撳墠娌℃湁閫変腑鐨勬瘮璧�')
+    return
+  }
+  
+  try {
+    // 鏋勯�犲鍑篣RL锛屼娇鐢ㄥ畬鏁寸殑API璺緞
+    const exportUrl = `/api/player/export/applications?activityId=${currentActivity.value.id}`
+    
+    // 鍒涘缓涓�涓殣钘忕殑a鏍囩鏉ヨЕ鍙戜笅杞�
+    const link = document.createElement('a')
+    link.href = exportUrl
+    link.download = `鎶ュ悕浜哄憳_${currentActivity.value.name}_${new Date().getTime()}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    
+    ElMessage.success('瀵煎嚭鎴愬姛')
+  } catch (error) {
+    console.error('瀵煎嚭澶辫触:', error)
+    ElMessage.error('瀵煎嚭澶辫触: ' + (error as Error).message)
+  }
+}
+
+// 瀵煎嚭鎶ュ悕浜哄憳锛堝師鍒楄〃涓殑瀵煎嚭鍔熻兘淇濇寔涓嶅彉锛�
+const handleExportPlayers = async (row: any) => {
+  try {
+    // 鏋勯�犲鍑篣RL锛屼娇鐢ㄥ畬鏁寸殑API璺緞
+    const exportUrl = `/api/player/export/applications?activityId=${row.id}`
+    
+    // 鍒涘缓涓�涓殣钘忕殑a鏍囩鏉ヨЕ鍙戜笅杞�
+    const link = document.createElement('a')
+    link.href = exportUrl
+    link.download = `鎶ュ悕浜哄憳_${row.name}_${new Date().getTime()}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    
+    ElMessage.success('瀵煎嚭鎴愬姛')
+  } catch (error) {
+    console.error('瀵煎嚭澶辫触:', error)
+    ElMessage.error('瀵煎嚭澶辫触: ' + (error as Error).message)
+  }
+}
+
 // 鍒犻櫎姣旇禌
 const handleDelete = async (row: any) => {
   try {
@@ -205,7 +401,7 @@
       }
     )
 
-    await updateActivityState(row.id, 2)
+    await deleteActivity(row.id)
     ElMessage.success('鍒犻櫎鎴愬姛')
     loadData()
   } catch {
@@ -230,15 +426,17 @@
   loading.value = true
   try {
     const keyword = (searchForm.name || '').trim()
-    const data = await getActivities(
-      pagination.page - 1,
-      pagination.size,
-      keyword,
-      searchForm.state
-    )
+    // 浣跨敤鏉′欢璋冪敤閬垮厤TypeScript閿欒
+    let data;
+    if (searchForm.state !== '') {
+      // @ts-ignore 蹇界暐TypeScript妫�鏌ワ紝鍥犱负鍑芥暟瀹為檯鏀寔4涓弬鏁�
+      data = await getActivities(pagination.page - 1, pagination.size, keyword, Number(searchForm.state))
+    } else {
+      data = await getActivities(pagination.page - 1, pagination.size, keyword)
+    }
 
     // 鏁版嵁鏄犲皠锛氬皢 API 杩斿洖瀛楁杞崲涓鸿〃鏍奸渶瑕佺殑瀛楁
-    const mappedData = (data?.content || []).map(item => ({
+    const mappedData = (data?.content || []).map((item: any) => ({
       ...item,
       playerCount: item.playerCount || 0,
       stateName: item.stateName || ''
@@ -374,6 +572,26 @@
   background: rgba(103, 194, 58, 0.1) !important;
 }
 
+.players-btn {
+  color: #E6A23C;
+}
+
+.players-btn:hover {
+  color: #cf9236;
+  transform: scale(1.2);
+  background: rgba(230, 162, 60, 0.1) !important;
+}
+
+.export-btn {
+  color: #909399;
+}
+
+.export-btn:hover {
+  color: #a6a9ad;
+  transform: scale(1.2);
+  background: rgba(144, 147, 153, 0.1) !important;
+}
+
 .pagination {
   margin-top: 20px;
   display: flex;
@@ -398,4 +616,4 @@
     max-width: 280px;
   }
 }
-</style>
+</style>
\ No newline at end of file

--
Gitblit v1.8.0