web/src/views/competition-promotion/index.vue
@@ -1,117 +1,122 @@
<template>
  <div class="promotion-container">
    <el-card>
      <template #header>
        <div class="card-header">
          <h3 class="card-title">比赛晋级管理</h3>
        </div>
      </template>
      <!-- 搜索栏 -->
      <el-form :inline="true" class="search-form">
        <el-form-item label="比赛名称">
          <el-input
            v-model="searchForm.name"
            placeholder="请输入比赛名称"
            @keyup.enter="loadData"
            style="width: 200px"
            clearable
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="loadData" :loading="loading">
            搜索
          </el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
      <!-- 数据表格 -->
      <el-table
        :data="competitions"
        style="width: 100%"
        v-loading="loading"
        empty-text="暂无比赛数据"
      >
        <el-table-column prop="competitionName" label="比赛名称" min-width="150">
          <template #default="scope">
            <div class="competition-info">
              <div class="main-name">{{ scope.row.competitionName }}</div>
              <div class="stage-name">{{ scope.row.stageName }}</div>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="maxParticipants" label="最大人数" width="100" align="center">
          <template #default="scope">
            <el-tag type="info">{{ scope.row.maxParticipants || '不限' }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="currentCount" label="当前数量" width="100" align="center">
          <template #default="scope">
            <el-button
              type="text"
              @click="viewParticipants(scope.row)"
              class="count-link"
            >
              {{ scope.row.currentCount }}
            </el-button>
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态" width="100" align="center">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="startTime" label="开始时间" width="180">
          <template #default="scope">
            {{ formatDate(scope.row.startTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="endTime" label="结束时间" width="180">
          <template #default="scope">
            {{ formatDate(scope.row.endTime) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="150" fixed="right">
          <template #default="scope">
            <!-- 只有非第一阶段才显示晋级按钮 -->
            <el-button
              v-if="scope.row.sortOrder > 1"
              type="primary"
              size="small"
              @click="selectPromotionCandidates(scope.row)"
              :disabled="scope.row.state !== 1"
            >
              选择晋级人员
            </el-button>
            <!-- 第一阶段显示提示文本 -->
            <span v-else class="no-promotion-text">首轮比赛</span>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <div class="pagination-container">
        <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 class="page-header">
      <div class="title-section">
        <h1 class="page-title">比赛晋级</h1>
        <p class="page-subtitle">管理比赛各阶段的晋级流程,选择优秀参赛者进入下一轮</p>
      </div>
    </el-card>
    </div>
    <!-- 搜索工具栏 -->
    <div class="search-toolbar">
      <div class="search-area">
        <el-input
          v-model="searchForm.name"
          placeholder="请输入比赛名称"
          @keyup.enter="loadData"
          @clear="handleClear"
          style="width: 200px"
          clearable
        >
          <template #prefix>
            <el-icon><Search /></el-icon>
          </template>
        </el-input>
        <el-button type="primary" @click="loadData" :loading="loading">
          <el-icon><Search /></el-icon>
          搜索
        </el-button>
        <el-button @click="resetSearch">重置</el-button>
      </div>
    </div>
    <!-- 数据表格 -->
    <el-table
      :data="competitions"
      style="width: 100%"
      v-loading="loading"
      empty-text="暂无比赛数据"
    >
      <el-table-column prop="competitionName" label="比赛名称" min-width="150">
        <template #default="scope">
          <div class="competition-info">
            <div class="main-name">{{ scope.row.competitionName }}</div>
            <div class="stage-name">{{ scope.row.stageName }}</div>
          </div>
        </template>
      </el-table-column>
      <el-table-column prop="maxParticipants" label="最大人数" width="100" align="center">
        <template #default="scope">
          <el-tag type="info">{{ scope.row.maxParticipants || '不限' }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="currentCount" label="当前数量" width="100" align="center">
        <template #default="scope">
          <el-button
            type="text"
            @click="viewParticipants(scope.row)"
            class="count-link"
          >
            {{ scope.row.currentCount }}
          </el-button>
        </template>
      </el-table-column>
      <el-table-column prop="status" label="状态" width="100" align="center">
        <template #default="scope">
          <el-tag :type="getStatusType(scope.row.status)">
            {{ getStatusText(scope.row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="startTime" label="开始时间" width="180">
        <template #default="scope">
          {{ formatDate(scope.row.startTime) }}
        </template>
      </el-table-column>
      <el-table-column prop="endTime" label="结束时间" width="180">
        <template #default="scope">
          {{ formatDate(scope.row.endTime) }}
        </template>
      </el-table-column>
      <el-table-column label="操作" width="80" align="center">
        <template #default="scope">
          <!-- 只有非第一阶段才显示晋级按钮 -->
          <el-button
            v-if="scope.row.sortOrder > 1"
            text
            :icon="TrophyBase"
            @click="selectPromotionCandidates(scope.row)"
            :disabled="scope.row.state !== 1"
            class="action-btn promotion-btn"
            title="选择晋级人员"
          />
          <!-- 第一阶段显示提示图标 -->
          <el-icon v-else class="no-promotion-icon" title="首轮比赛">
            <InfoFilled />
          </el-icon>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <div class="pagination-container">
      <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>
    <!-- 晋级人员选择对话框 -->
    <el-dialog
@@ -194,15 +199,17 @@
      <template #footer>
        <div class="dialog-footer">
          <span class="selected-info">已选择 {{ selectedParticipants.length }} 人</span>
          <el-button @click="handlePromotionDialogClose">取消</el-button>
          <el-button
            type="primary"
            @click="confirmPromotion"
            :disabled="selectedParticipants.length === 0"
            :loading="promotionLoading"
          >
            确认晋级
          </el-button>
          <div class="footer-buttons">
            <el-button @click="handlePromotionDialogClose">取消</el-button>
            <el-button
              type="primary"
              @click="confirmPromotion"
              :disabled="selectedParticipants.length === 0"
              :loading="promotionLoading"
            >
              确认晋级
            </el-button>
          </div>
        </div>
      </template>
    </el-dialog>
@@ -213,7 +220,7 @@
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { Search, TrophyBase, InfoFilled } from '@element-plus/icons-vue'
import PromotionApi from '@/api/promotion'
const router = useRouter()
@@ -280,7 +287,7 @@
      {
        id: 1,
        competitionName: '2027创新创业大赛',
        stageName: '海选',
        stageName: '第一阶段',
        maxParticipants: null,
        currentCount: 15,
        status: 1,
@@ -290,7 +297,7 @@
      {
        id: 2,
        competitionName: '2027创新创业大赛',
        stageName: '初赛',
        stageName: '第二阶段',
        maxParticipants: 50,
        currentCount: 8,
        status: 1,
@@ -300,7 +307,7 @@
      {
        id: 3,
        competitionName: '2027创新创业大赛',
        stageName: '决赛',
        stageName: '第三阶段',
        maxParticipants: 10,
        currentCount: 0,
        status: 0,
@@ -322,6 +329,13 @@
  } finally {
    loading.value = false
  }
}
// 清空搜索
const handleClear = () => {
  searchForm.name = ''
  pagination.page = 1
  loadData()
}
// 重置搜索
@@ -412,8 +426,8 @@
      ],
      selectableCount: 10,
      totalCount: 15,
      previousStageName: '海选',
      currentStageName: '初赛'
      previousStageName: '第一阶段',
      currentStageName: '第二阶段'
    }
    
    promotableData.value = mockData
@@ -611,24 +625,45 @@
  padding: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
/* 页面标题区域 */
.page-header {
  margin-bottom: 24px;
}
.card-title {
  margin: 0;
  font-size: 18px;
.title-section {
  text-align: left;
}
.page-title {
  font-size: 24px;
  font-weight: 600;
  color: #303133;
  color: #1f2937;
  margin: 0 0 8px 0;
}
.search-form {
.page-subtitle {
  font-size: 14px;
  color: #6b7280;
  margin: 0;
  line-height: 1.5;
}
/* 搜索工具栏 */
.search-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
  padding: 20px;
  background: #f8f9fa;
  padding: 16px;
  background-color: #f9fafb;
  border-radius: 8px;
  border: 1px solid #e5e7eb;
}
.search-area {
  display: flex;
  align-items: center;
  gap: 12px;
}
.competition-info {
@@ -657,6 +692,28 @@
  }
}
/* 操作按钮样式 */
.action-btn {
  padding: 8px !important;
  margin: 0 6px;
  border-radius: 6px;
  transition: all 0.2s ease;
}
.promotion-btn {
  color: #f59e0b !important;
}
.promotion-btn:hover {
  background-color: rgba(245, 158, 11, 0.1) !important;
  transform: scale(1.2);
}
.no-promotion-icon {
  color: #9ca3af;
  font-size: 16px;
}
.score {
  font-weight: 600;
  color: #67c23a;
@@ -665,12 +722,6 @@
.no-score {
  color: #909399;
  font-size: 12px;
}
.no-promotion-text {
  color: #909399;
  font-size: 12px;
  font-style: italic;
}
.pagination-container {
@@ -723,5 +774,24 @@
    color: #606266;
    font-size: 14px;
  }
  .footer-buttons {
    display: flex;
    gap: 12px;
  }
}
/* 响应式适配 */
@media (max-width: 768px) {
  .search-toolbar {
    flex-direction: column;
    gap: 12px;
    align-items: stretch;
  }
  .search-area {
    justify-content: center;
    flex-wrap: wrap;
  }
}
</style>