| | |
| | | <template> |
| | | <div class="review-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-select |
| | | v-model="selectedActivity" |
| | | placeholder="请选择比赛" |
| | | @change="handleActivityChange" |
| | | style="width: 300px" |
| | | clearable |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="activity in activities" |
| | | :key="activity.id" |
| | | :label="getActivityDisplayName(activity)" |
| | | :value="activity.id" |
| | | > |
| | | <span>{{ getActivityName(activity) }}</span> |
| | | <span v-if="activity.pid > 0" style="color: #409eff; margin-left: 8px;"> |
| | | {{ activity.name }} |
| | | </span> |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="项目名称"> |
| | | <el-input |
| | | v-model="searchName" |
| | | placeholder="请输入项目名称" |
| | | @keyup.enter="loadProjects" |
| | | style="width: 200px" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="loadProjects" :loading="projectsLoading"> |
| | | 搜索 |
| | | </el-button> |
| | | <el-button @click="resetSearch">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <el-table |
| | | :data="projects" |
| | | style="width: 100%" |
| | | v-loading="projectsLoading" |
| | | empty-text="请先选择比赛" |
| | | > |
| | | <el-table-column prop="playerName" label="项目名称" min-width="150"> |
| | | <template #default="scope"> |
| | | {{ scope.row.projectName || scope.row.playerName }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="playerName" label="参赛人姓名" min-width="120" /> |
| | | <el-table-column prop="phone" label="联系电话" min-width="120" /> |
| | | <el-table-column prop="ratingCount" label="评审次数" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.ratingCount > 0 ? 'success' : 'info'"> |
| | | {{ scope.row.ratingCount }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="averageScore" label="平均分" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span v-if="scope.row.averageScore > 0" class="score"> |
| | | {{ scope.row.averageScore.toFixed(1) }} |
| | | </span> |
| | | <span v-else class="no-score">未评分</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="applyTime" label="报名时间" width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDate(scope.row.applyTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="state" label="状态" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStateType(scope.row.state)"> |
| | | {{ getStateName(scope.row.state) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="120" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" link @click="viewDetails(scope.row.id)"> |
| | | 详情评审 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 分页 --> |
| | | <div class="pagination-container" v-if="total > 0"> |
| | | <el-pagination |
| | | v-model:current-page="currentPage" |
| | | v-model:page-size="pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="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-form"> |
| | | <el-input |
| | | v-model="searchName" |
| | | placeholder="请输入项目名称" |
| | | @keyup.enter="loadProjects" |
| | | @clear="handleClear" |
| | | style="width: 180px" |
| | | clearable |
| | | /> |
| | | <el-select |
| | | v-model="selectedActivity" |
| | | placeholder="请选择比赛" |
| | | @change="handleActivityChange" |
| | | style="width: 200px" |
| | | clearable |
| | | filterable |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Trophy /></el-icon> |
| | | </template> |
| | | <el-option |
| | | v-for="activity in activities" |
| | | :key="activity.id" |
| | | :label="getActivityDisplayName(activity)" |
| | | :value="activity.id" |
| | | > |
| | | <span>{{ getActivityName(activity) }}</span> |
| | | <span v-if="activity.pid > 0" style="color: #409eff; margin-left: 8px;"> |
| | | {{ activity.name }} |
| | | </span> |
| | | </el-option> |
| | | </el-select> |
| | | <el-button |
| | | type="primary" |
| | | :icon="Search" |
| | | @click="loadProjects" |
| | | :loading="projectsLoading" |
| | | > |
| | | 查询 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table |
| | | :data="projects" |
| | | style="width: 100%" |
| | | v-loading="projectsLoading" |
| | | empty-text="请先选择比赛" |
| | | > |
| | | <el-table-column prop="playerName" label="项目名称" min-width="150"> |
| | | <template #default="scope"> |
| | | {{ scope.row.projectName || scope.row.playerName }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="playerName" label="参赛人姓名" min-width="120" /> |
| | | <el-table-column prop="phone" label="联系电话" min-width="120" /> |
| | | <el-table-column prop="ratingCount" label="评审次数" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.ratingCount > 0 ? 'success' : 'info'"> |
| | | {{ scope.row.ratingCount }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="averageScore" label="平均分" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span v-if="scope.row.averageScore > 0" class="score"> |
| | | {{ scope.row.averageScore.toFixed(1) }} |
| | | </span> |
| | | <span v-else class="no-score">未评分</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="applyTime" label="报名时间" width="180"> |
| | | <template #default="scope"> |
| | | {{ formatDate(scope.row.applyTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="reviewStatus" label="评审状态" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getReviewStatusType(scope.row.ratingCount)"> |
| | | {{ getReviewStatusName(scope.row.ratingCount) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="80" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | text |
| | | :icon="View" |
| | | @click="viewDetails(scope.row.id)" |
| | | class="action-btn view-btn" |
| | | title="详情评审" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 分页 --> |
| | | <div class="pagination-container" v-if="total > 0"> |
| | | <el-pagination |
| | | v-model:current-page="currentPage" |
| | | v-model:page-size="pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ref, onMounted } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getActiveActivities, getCompetitionProjects } from '@/api/projectReview' |
| | | import { Search, Trophy, View } from '@element-plus/icons-vue' |
| | | import { getActiveActivities, getRatingStats } from '@/api/projectReview' |
| | | import { getProjectReviewApplications } from '@/api/projectReviewNew' |
| | | |
| | | const router = useRouter() |
| | | |
| | |
| | | |
| | | // 加载比赛列表 |
| | | const loadActivities = async () => { |
| | | console.log('=== 开始加载比赛列表 ===') |
| | | activitiesLoading.value = true |
| | | try { |
| | | console.log('调用 getActiveActivities...') |
| | | const data = await getActiveActivities() |
| | | console.log('getActiveActivities 返回数据:', data) |
| | | activities.value = data |
| | | console.log('activities.value 设置为:', activities.value) |
| | | console.log('activities.value.length:', activities.value?.length) |
| | | } catch (error) { |
| | | console.error('加载比赛列表失败:', error) |
| | | ElMessage.error(error.message) |
| | | } finally { |
| | | activitiesLoading.value = false |
| | | console.log('=== 比赛列表加载完成 ===') |
| | | } |
| | | } |
| | | |
| | |
| | | return |
| | | } |
| | | |
| | | console.log('=== 开始加载项目列表 ===') |
| | | console.log('selectedActivity.value:', selectedActivity.value) |
| | | console.log('currentPage.value:', currentPage.value) |
| | | console.log('pageSize.value:', pageSize.value) |
| | | |
| | | projectsLoading.value = true |
| | | try { |
| | | const response = await getCompetitionProjects( |
| | | selectedActivity.value, |
| | | currentPage.value - 1, // 后端从0开始 |
| | | pageSize.value, |
| | | searchName.value |
| | | ) |
| | | const params = { |
| | | activityId: selectedActivity.value, |
| | | page: currentPage.value, |
| | | size: pageSize.value |
| | | } |
| | | |
| | | projects.value = response.content || [] |
| | | total.value = response.totalElements || 0 |
| | | // 如果有搜索名称,添加到参数中 |
| | | if (searchName.value && searchName.value.trim()) { |
| | | params.name = searchName.value.trim() |
| | | } |
| | | |
| | | console.log('API调用参数:', params) |
| | | |
| | | const response = await getProjectReviewApplications(params) |
| | | console.log('API响应:', response) |
| | | |
| | | // 处理响应数据 - 新的分页结构 |
| | | const pageData = response.projectReviewApplications |
| | | projects.value = pageData?.content || [] |
| | | total.value = pageData?.totalElements || 0 |
| | | |
| | | console.log('设置 projects.value:', projects.value) |
| | | console.log('projects.value.length:', projects.value.length) |
| | | console.log('设置 total.value:', total.value) |
| | | } catch (error) { |
| | | console.error('加载项目列表失败:', error) |
| | | ElMessage.error('加载项目列表失败') |
| | |
| | | loadProjects() |
| | | } |
| | | |
| | | // 重置搜索 |
| | | const resetSearch = () => { |
| | | // 清空搜索 |
| | | const handleClear = () => { |
| | | searchName.value = '' |
| | | currentPage.value = 1 |
| | | if (selectedActivity.value) { |
| | | loadProjects() |
| | | } |
| | | } |
| | | |
| | | // 重置搜索 |
| | | const resetSearch = () => { |
| | | searchName.value = '' |
| | | selectedActivity.value = null |
| | | currentPage.value = 1 |
| | | projects.value = [] |
| | | total.value = 0 |
| | | } |
| | | |
| | | // 分页处理 |
| | |
| | | return stateMap[state] || '未知' |
| | | } |
| | | |
| | | // 获取评审状态类型(基于评审次数) |
| | | const getReviewStatusType = (ratingCount) => { |
| | | return ratingCount > 0 ? 'success' : 'warning' |
| | | } |
| | | |
| | | // 获取评审状态名称(基于评审次数) |
| | | const getReviewStatusName = (ratingCount) => { |
| | | return ratingCount > 0 ? '已评审' : '未评审' |
| | | } |
| | | |
| | | // 获取比赛名称(如果是阶段,返回父比赛名称;如果是比赛,返回自己的名称) |
| | | const getActivityName = (activity) => { |
| | | if (activity.pid > 0 && activity.parent) { |
| | |
| | | return activity.name |
| | | } |
| | | |
| | | |
| | | |
| | | // 组件挂载时加载数据 |
| | | onMounted(() => { |
| | | loadActivities() |
| | |
| | | padding: 20px; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | /* 页面标题区域 */ |
| | | .page-header { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .card-title { |
| | | .title-section { |
| | | text-align: left; |
| | | } |
| | | |
| | | .page-title { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #1f2937; |
| | | margin: 0 0 8px 0; |
| | | } |
| | | |
| | | .page-subtitle { |
| | | font-size: 14px; |
| | | color: #6b7280; |
| | | margin: 0; |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | /* 搜索工具栏 */ |
| | | .search-toolbar { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .search-form { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | display: flex; |
| | | gap: 12px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .search-area { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | /* 操作按钮样式 */ |
| | | .action-btn { |
| | | padding: 8px !important; |
| | | margin: 0 6px; |
| | | border-radius: 6px; |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .view-btn { |
| | | color: #3b82f6 !important; |
| | | } |
| | | |
| | | .view-btn:hover { |
| | | background-color: rgba(59, 130, 246, 0.1) !important; |
| | | transform: scale(1.2); |
| | | } |
| | | |
| | | .score { |
| | |
| | | :deep(.el-tag) { |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | /* 响应式适配 */ |
| | | @media (max-width: 768px) { |
| | | .search-toolbar { |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .search-area { |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | } |
| | | </style> |