peng
2025-11-07 858f515995fd1dca7cf825069ce38c32703298d0
web/src/views/activity-list.vue
@@ -54,9 +54,33 @@
            <el-tag :type="getStatusType(row.stateName)">{{ row.stateName }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120" 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"
                size="small"
                @click="handleView(row)"
                class="action-btn view-btn"
                title="详情"
              />
              <el-button
                text
                :icon="Edit"
@@ -91,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>
@@ -98,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 } 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 组件开始加载 ===')
@@ -128,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,
@@ -150,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] || '未知'
}
// 搜索
@@ -179,6 +282,112 @@
  router.push(`/activity/edit/${row.id}`)
}
// 查看详情
const handleView = (row: any) => {
  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()
}
// 从弹窗导出报名人员Excel
const handleExportPlayersFromDialog = async () => {
  if (!currentActivity.value) {
    ElMessage.error('当前没有选中的比赛')
    return
  }
  try {
    // 构造导出URL,使用完整的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 {
    // 构造导出URL,使用完整的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 {
@@ -192,7 +401,7 @@
      }
    )
    await updateActivityState(row.id, 2)
    await deleteActivity(row.id)
    ElMessage.success('删除成功')
    loadData()
  } catch {
@@ -217,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 || ''
@@ -351,6 +562,36 @@
  background: rgba(245, 108, 108, 0.1) !important;
}
.view-btn {
  color: #67C23A;
}
.view-btn:hover {
  color: #5daf34;
  transform: scale(1.2);
  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;
@@ -375,4 +616,4 @@
    max-width: 280px;
  }
}
</style>
</style>