<template>
|
<div class="activity-page">
|
<div class="page-card">
|
<!-- 页面头部 -->
|
<div class="page-header">
|
<div class="title-section">
|
<h1 class="page-title">比赛信息</h1>
|
<p class="page-subtitle">管理您的精彩比赛</p>
|
</div>
|
</div>
|
|
<!-- 搜索工具栏 -->
|
<div class="search-toolbar">
|
<el-input
|
v-model="searchForm.name"
|
placeholder="请输入比赛名称"
|
style="width: 200px"
|
clearable
|
@keyup.enter="handleSearch"
|
@clear="handleClear"
|
/>
|
<el-select
|
v-model="searchForm.state"
|
placeholder="比赛状态"
|
style="width: 160px"
|
clearable
|
@change="handleStateChange"
|
>
|
<el-option
|
v-for="option in stateOptions"
|
:key="option.value"
|
:label="option.label"
|
:value="option.value"
|
/>
|
</el-select>
|
<el-button type="primary" @click="handleSearch">
|
<el-icon><Search /></el-icon>
|
查询
|
</el-button>
|
<el-button type="primary" @click="handleAdd">
|
<el-icon><Plus /></el-icon>
|
新增比赛
|
</el-button>
|
</div>
|
|
<!-- 比赛列表 -->
|
<el-table :data="tableData" style="width: 100%" v-loading="loading">
|
<el-table-column prop="name" label="比赛名称" min-width="200" />
|
<el-table-column prop="playerCount" label="报名人数" width="120" align="center" />
|
<el-table-column prop="matchTime" label="比赛时间" width="180" />
|
<el-table-column prop="signupDeadline" label="报名截止时间" width="180" />
|
<el-table-column prop="stateName" label="状态" width="100" align="center">
|
<template #default="{ row }">
|
<el-tag :type="getStatusType(row.stateName)">{{ row.stateName }}</el-tag>
|
</template>
|
</el-table-column>
|
<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"
|
size="small"
|
@click="handleEdit(row)"
|
class="action-btn edit-btn"
|
title="编辑"
|
/>
|
<el-button
|
text
|
:icon="Delete"
|
size="small"
|
@click="handleDelete(row)"
|
class="action-btn delete-btn"
|
title="删除"
|
/>
|
</div>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<div class="pagination">
|
<el-pagination
|
v-model:current-page="pagination.page"
|
v-model:page-size="pagination.size"
|
:page-sizes="[10, 20, 50, 100]"
|
:total="pagination.total"
|
layout="total, sizes, prev, pager, next, jumper"
|
@size-change="handleSizeChange"
|
@current-change="handleCurrentChange"
|
/>
|
</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>
|
|
<script setup lang="ts">
|
import { reactive, ref, onMounted, onActivated, watch } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { useRouter } from 'vue-router'
|
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 组件开始加载 ===')
|
|
const loading = ref(false)
|
const router = useRouter()
|
|
// 搜索表单
|
const searchForm = reactive({
|
name: '',
|
state: ''
|
})
|
|
const stateOptions = [
|
{ label: '未发布', value: 0 },
|
{ label: '发布', value: 1 },
|
{ label: '关闭', value: 2 }
|
]
|
|
// 分页信息
|
const pagination = reactive({
|
page: 1,
|
size: 10,
|
total: 0
|
})
|
|
// 表格数据
|
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,
|
newVal => {
|
console.log('=== tableData 发生变化 ===', newVal)
|
console.log('tableData.value.length:', newVal.length)
|
},
|
{ deep: true }
|
)
|
|
// 获取状态标签类型
|
const getStatusType = (status: string) => {
|
const typeMap: Record<string, string> = {
|
已发布: 'success',
|
发布: 'success',
|
进行中: 'success',
|
报名中: 'warning',
|
待开始: 'info',
|
已结束: 'info',
|
关闭: '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] || '未知'
|
}
|
|
// 搜索
|
const handleSearch = () => {
|
pagination.page = 1
|
loadData()
|
}
|
|
const handleStateChange = () => {
|
pagination.page = 1
|
loadData()
|
}
|
|
// 清空搜索
|
const handleClear = () => {
|
searchForm.name = ''
|
loadData()
|
}
|
|
// 新增比赛
|
const handleAdd = () => {
|
router.push('/activity/new')
|
}
|
|
// 编辑比赛
|
const handleEdit = (row: any) => {
|
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 {
|
await ElMessageBox.confirm(
|
`确定要删除比赛 “${row.name}” 吗?`,
|
'提示',
|
{
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}
|
)
|
|
await deleteActivity(row.id)
|
ElMessage.success('删除成功')
|
loadData()
|
} catch {
|
// 用户取消操作
|
}
|
}
|
|
// 分页大小改变
|
const handleSizeChange = (size: number) => {
|
pagination.size = size
|
loadData()
|
}
|
|
// 当前页改变
|
const handleCurrentChange = (page: number) => {
|
pagination.page = page
|
loadData()
|
}
|
|
// 加载数据
|
const loadData = async () => {
|
loading.value = true
|
try {
|
const keyword = (searchForm.name || '').trim()
|
// 使用条件调用避免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: any) => ({
|
...item,
|
playerCount: item.playerCount || 0,
|
stateName: item.stateName || ''
|
}))
|
|
tableData.value = mappedData
|
pagination.total = data?.totalElements || 0
|
} catch (e: any) {
|
console.error('加载数据失败:', e)
|
ElMessage.error(e?.message || '加载比赛列表失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
onMounted(() => {
|
loadData()
|
})
|
|
// 页面激活时重新加载数据(解决从新增/编辑返回后列表不同步的问题)
|
onActivated(() => {
|
loadData()
|
})
|
</script>
|
|
<style scoped>
|
.activity-page {
|
padding: 20px;
|
}
|
|
.page-card {
|
background: white;
|
border-radius: 8px;
|
padding: 24px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
/* 页面头部样式 */
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 24px;
|
gap: 20px;
|
}
|
|
.title-section {
|
flex: 1;
|
}
|
|
.page-title {
|
margin: 0 0 8px 0;
|
font-size: 24px;
|
font-weight: 600;
|
color: #1a1a1a;
|
line-height: 1.2;
|
}
|
|
.page-subtitle {
|
margin: 0;
|
font-size: 14px;
|
color: #666;
|
line-height: 1.4;
|
}
|
|
/* 工具栏样式 */
|
.toolbar {
|
display: flex;
|
gap: 12px;
|
align-items: center;
|
flex-shrink: 0;
|
}
|
|
/* 搜索工具栏样式 */
|
.search-toolbar {
|
display: flex;
|
gap: 12px;
|
align-items: center;
|
justify-content: flex-end;
|
margin-bottom: 20px;
|
}
|
|
/* 搜索按钮 */
|
.search-icon {
|
color: #999;
|
}
|
|
.search-button {
|
margin-right: 4px;
|
}
|
|
/* 表格操作按钮样式 */
|
.table-actions {
|
display: flex;
|
gap: 12px;
|
justify-content: center;
|
}
|
|
.action-btn {
|
padding: 4px;
|
border: none;
|
background: transparent !important;
|
transition: all 0.2s ease;
|
}
|
|
.edit-btn {
|
color: #409eff;
|
}
|
|
.edit-btn:hover {
|
color: #337ecc;
|
transform: scale(1.2);
|
background: rgba(64, 158, 255, 0.1) !important;
|
}
|
|
.delete-btn {
|
color: #f56c6c;
|
}
|
|
.delete-btn:hover {
|
color: #dd6161;
|
transform: scale(1.2);
|
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;
|
justify-content: flex-end;
|
}
|
|
/* 响应式适配 */
|
@media (max-width: 768px) {
|
.page-header {
|
flex-direction: column;
|
align-items: stretch;
|
gap: 16px;
|
}
|
|
.toolbar {
|
flex-wrap: wrap;
|
gap: 8px;
|
}
|
|
.toolbar .el-input {
|
width: 100% !important;
|
max-width: 280px;
|
}
|
}
|
</style>
|