| | |
| | | clearable |
| | | /> |
| | | <el-select |
| | | v-model="selectedRegion" |
| | | placeholder="请选择区域" |
| | | @change="handleRegionChange" |
| | | style="width: 200px" |
| | | clearable |
| | | filterable |
| | | > |
| | | <el-option |
| | | v-for="region in regions" |
| | | :key="region.id" |
| | | :label="region.name" |
| | | :value="region.id" |
| | | /> |
| | | </el-select> |
| | | <el-select |
| | | v-model="selectedActivity" |
| | | placeholder="请选择比赛" |
| | | @change="handleActivityChange" |
| | | style="width: 200px" |
| | | style="width: 220px" |
| | | clearable |
| | | filterable |
| | | > |
| | |
| | | </span> |
| | | </el-option> |
| | | </el-select> |
| | | <el-button |
| | | type="primary" |
| | | :icon="Search" |
| | | <el-button |
| | | type="primary" |
| | | :icon="Search" |
| | | @click="loadProjects" |
| | | :loading="projectsLoading" |
| | | > |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table |
| | | :data="projects" |
| | | style="width: 100%" |
| | | <el-table |
| | | :data="projects" |
| | | style="width: 100%" |
| | | v-loading="projectsLoading" |
| | | empty-text="请先选择比赛" |
| | | > |
| | | <el-table-column prop="playerName" label="项目名称" min-width="150"> |
| | | <el-table-column prop="projectName" label="项目名称" min-width="150"> |
| | | <template #default="scope"> |
| | | {{ scope.row.projectName || scope.row.playerName }} |
| | | {{ scope.row.projectName || '未填写项目名称' }} |
| | | </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'"> |
| | | <el-button |
| | | text |
| | | :type="scope.row.ratingCount > 0 ? 'success' : 'info'" |
| | | @click="showRatingList(scope.row)" |
| | | :disabled="scope.row.ratingCount === 0" |
| | | class="rating-count-btn" |
| | | > |
| | | {{ scope.row.ratingCount }} |
| | | </el-tag> |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="averageScore" label="平均分" width="100" align="center"> |
| | |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- 评审列表弹窗 --> |
| | | <el-dialog |
| | | v-model="ratingListVisible" |
| | | title="评审列表" |
| | | width="60%" |
| | | :before-close="handleRatingListClose" |
| | | > |
| | | <div v-if="selectedProject"> |
| | | <div class="dialog-header"> |
| | | <h4>{{ selectedProject.projectName || selectedProject.playerName }} - 评审详情</h4> |
| | | <p class="project-info">参赛人:{{ selectedProject.playerName }} | 联系电话:{{ selectedProject.phone }}</p> |
| | | </div> |
| | | |
| | | <el-table |
| | | :data="judgeRatings" |
| | | v-loading="ratingsLoading" |
| | | style="width: 100%" |
| | | > |
| | | <el-table-column prop="judgeName" label="评委姓名" min-width="120" /> |
| | | <el-table-column prop="totalScore" label="评分" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span v-if="scope.row.hasRated && scope.row.totalScore" class="score"> |
| | | {{ scope.row.totalScore.toFixed(1) }} |
| | | </span> |
| | | <span v-else class="no-score">未评分</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="hasRated" label="状态" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.hasRated ? 'success' : 'info'"> |
| | | {{ scope.row.hasRated ? '已评分' : '未评分' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | text |
| | | type="primary" |
| | | @click="viewRatingDetail(scope.row)" |
| | | :disabled="!scope.row.hasRated" |
| | | size="small" |
| | | > |
| | | 查看详情 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleRatingListClose">关闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 评分详情弹窗 --> |
| | | <el-dialog |
| | | v-model="ratingDetailVisible" |
| | | title="评分详情" |
| | | width="50%" |
| | | :before-close="handleRatingDetailClose" |
| | | > |
| | | <div v-if="ratingDetail"> |
| | | <div class="dialog-header"> |
| | | <h4>{{ ratingDetail.judgeName }} 评审结果</h4> |
| | | <p class="project-info"> |
| | | 总分: |
| | | <span class="total-score"> |
| | | {{ ratingDetail.totalScore?.toFixed(1) ?? '暂无' }} |
| | | </span> |
| | | </p> |
| | | </div> |
| | | |
| | | <el-table |
| | | :data="ratingDetail.items || []" |
| | | border |
| | | size="small" |
| | | > |
| | | <el-table-column prop="itemName" label="评分项" min-width="160" /> |
| | | <el-table-column prop="score" label="得分" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span class="item-score">{{ scope.row.score?.toFixed(1) ?? '-' }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="maxScore" label="满分" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span class="max-score">{{ scope.row.maxScore?.toFixed(1) ?? '-' }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="rating-comment" v-if="ratingDetail.comment"> |
| | | <h5>评语</h5> |
| | | <p>{{ ratingDetail.comment }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleRatingDetailClose">关闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | <script setup lang="ts"> |
| | | import { ref, onMounted } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Search, Trophy, View } from '@element-plus/icons-vue' |
| | | import { getActiveActivities, getRatingStats } from '@/api/projectReview' |
| | | import { getActiveActivities, getRatingStats, getJudgeRatingDetail } from '@/api/projectReview' |
| | | import { getProjectReviewApplications } from '@/api/projectReviewNew' |
| | | import { getLeafRegions } from '@/api/region' |
| | | import { userApi } from '@/api/user' |
| | | import { getUserInfo } from '@/utils/auth' |
| | | |
| | | const router = useRouter() |
| | | |
| | | // 响应式数据 |
| | | const activities = ref([]) |
| | | const selectedActivity = ref(null) |
| | | const projects = ref([]) |
| | | interface ActivityItem { |
| | | id: number |
| | | name: string |
| | | pid: number |
| | | parent?: ActivityItem |
| | | } |
| | | |
| | | interface RegionItem { |
| | | id: number |
| | | name: string |
| | | } |
| | | |
| | | const activities = ref<ActivityItem[]>([]) |
| | | const regions = ref<RegionItem[]>([]) |
| | | const selectedActivity = ref<number | null>(null) |
| | | const selectedRegion = ref<number | null>(null) |
| | | const projects = ref<any[]>([]) |
| | | const searchName = ref('') |
| | | const activitiesLoading = ref(false) |
| | | const projectsLoading = ref(false) |
| | | |
| | | // 分页数据 |
| | | // 弹窗相关状态 |
| | | const ratingListVisible = ref(false) |
| | | const ratingDetailVisible = ref(false) |
| | | const selectedProject = ref<any | null>(null) |
| | | const judgeRatings = ref<any[]>([]) |
| | | const ratingDetail = ref<any | null>(null) |
| | | const ratingsLoading = ref(false) |
| | | const ratingDetailLoading = ref(false) |
| | | |
| | | // 分页 |
| | | const currentPage = ref(1) |
| | | const pageSize = ref(10) |
| | | const total = ref(0) |
| | | |
| | | const loadRegions = async () => { |
| | | try { |
| | | const data = await getLeafRegions() |
| | | regions.value = data || [] |
| | | } catch (error: any) { |
| | | console.error('加载区域列表失败:', error) |
| | | ElMessage.error(error?.message || '加载区域列表失败') |
| | | } |
| | | } |
| | | |
| | | // 加载比赛列表 |
| | | const loadActivities = async () => { |
| | |
| | | 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.log('比赛数据:', data) |
| | | |
| | | activities.value = data || [] |
| | | |
| | | if (activities.value.length === 1) { |
| | | selectedActivity.value = activities.value[0].id |
| | | console.log('自动选中唯一比赛:', selectedActivity.value) |
| | | loadProjects() |
| | | } |
| | | } catch (error: any) { |
| | | console.error('加载比赛列表失败:', error) |
| | | ElMessage.error(error.message) |
| | | ElMessage.error(error?.message || '加载比赛列表失败') |
| | | } finally { |
| | | activitiesLoading.value = false |
| | | console.log('=== 比赛列表加载完成 ===') |
| | | } |
| | | } |
| | | |
| | |
| | | ElMessage.warning('请先选择比赛') |
| | | return |
| | | } |
| | | |
| | | |
| | | console.log('=== 开始加载项目列表 ===') |
| | | console.log('selectedActivity.value:', selectedActivity.value) |
| | | console.log('selectedRegion.value:', selectedRegion.value) |
| | | console.log('currentPage.value:', currentPage.value) |
| | | console.log('pageSize.value:', pageSize.value) |
| | | |
| | | |
| | | projectsLoading.value = true |
| | | try { |
| | | const params = { |
| | | const params: Record<string, unknown> = { |
| | | activityId: selectedActivity.value, |
| | | page: currentPage.value, |
| | | size: pageSize.value |
| | | } |
| | | |
| | | // 如果有搜索名称,添加到参数中 |
| | | |
| | | if (selectedRegion.value) { |
| | | params.regionId = selectedRegion.value |
| | | } |
| | | |
| | | if (searchName.value && searchName.value.trim()) { |
| | | params.name = searchName.value.trim() |
| | | } |
| | | |
| | | console.log('API调用参数:', params) |
| | | |
| | | |
| | | console.log('请求参数:', 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) { |
| | | } catch (error: any) { |
| | | console.error('加载项目列表失败:', error) |
| | | ElMessage.error('加载项目列表失败') |
| | | // 如果API调用失败,显示空数据 |
| | | projects.value = [] |
| | | total.value = 0 |
| | | ElMessage.error(error?.message || '加载项目列表失败') |
| | | } finally { |
| | | projectsLoading.value = false |
| | | } |
| | | } |
| | | |
| | | // 处理区域选择变化 |
| | | const handleRegionChange = () => { |
| | | currentPage.value = 1 |
| | | if (selectedActivity.value) { |
| | | loadProjects() |
| | | } |
| | | } |
| | | |
| | | // 处理比赛选择变化 |
| | | const handleActivityChange = (activityId) => { |
| | | const handleActivityChange = () => { |
| | | currentPage.value = 1 |
| | | loadProjects() |
| | | } |
| | |
| | | // 清空搜索 |
| | | const handleClear = () => { |
| | | searchName.value = '' |
| | | currentPage.value = 1 |
| | | if (selectedActivity.value) { |
| | | loadProjects() |
| | | } |
| | | loadProjects() |
| | | } |
| | | |
| | | // 重置搜索 |
| | | const resetSearch = () => { |
| | | searchName.value = '' |
| | | selectedActivity.value = null |
| | | currentPage.value = 1 |
| | | projects.value = [] |
| | | total.value = 0 |
| | | } |
| | | |
| | | // 分页处理 |
| | | const handleSizeChange = (size) => { |
| | | // 分页大小改变 |
| | | const handleSizeChange = (size: number) => { |
| | | pageSize.value = size |
| | | currentPage.value = 1 |
| | | loadProjects() |
| | | } |
| | | |
| | | const handleCurrentChange = (page) => { |
| | | // 当前页改变 |
| | | const handleCurrentChange = (page: number) => { |
| | | currentPage.value = page |
| | | loadProjects() |
| | | } |
| | | |
| | | // 查看详情 |
| | | const viewDetails = (projectId) => { |
| | | router.push(`/project-review/${projectId}/detail`) |
| | | } |
| | | |
| | | // 格式化日期 |
| | | const formatDate = (dateString) => { |
| | | if (!dateString) return '-' |
| | | return new Date(dateString).toLocaleString('zh-CN') |
| | | } |
| | | |
| | | // 获取状态类型 |
| | | const getStateType = (state) => { |
| | | const stateMap = { |
| | | 0: 'danger', // 已拒绝 |
| | | 1: 'warning', // 待审核 |
| | | 2: 'success', // 已通过 |
| | | 3: 'info' // 已结束 |
| | | // 展示评审列表 |
| | | const showRatingList = async (project: any) => { |
| | | selectedProject.value = project |
| | | ratingsLoading.value = true |
| | | ratingListVisible.value = true |
| | | try { |
| | | const data = await getRatingStats(project.id) |
| | | judgeRatings.value = data || [] |
| | | } catch (error: any) { |
| | | console.error('加载评审列表失败:', error) |
| | | ElMessage.error(error?.message || '加载评审列表失败') |
| | | } finally { |
| | | ratingsLoading.value = false |
| | | } |
| | | return stateMap[state] || 'info' |
| | | } |
| | | |
| | | // 获取状态名称 |
| | | const getStateName = (state) => { |
| | | const stateMap = { |
| | | 0: '已拒绝', |
| | | 1: '待评审', |
| | | 2: '已通过', |
| | | 3: '已结束' |
| | | const handleRatingListClose = () => { |
| | | ratingListVisible.value = false |
| | | judgeRatings.value = [] |
| | | } |
| | | |
| | | // 查看评分详情 |
| | | const viewRatingDetail = async (rating: any) => { |
| | | ratingDetailVisible.value = true |
| | | ratingDetailLoading.value = true |
| | | try { |
| | | const data = await getJudgeRatingDetail(rating.id) |
| | | ratingDetail.value = data |
| | | } catch (error: any) { |
| | | console.error('加载评分详情失败:', error) |
| | | ElMessage.error(error?.message || '加载评分详情失败') |
| | | } finally { |
| | | ratingDetailLoading.value = false |
| | | } |
| | | return stateMap[state] || '未知' |
| | | } |
| | | |
| | | const handleRatingDetailClose = () => { |
| | | ratingDetailVisible.value = false |
| | | ratingDetail.value = null |
| | | } |
| | | |
| | | // 格式化时间 |
| | | const formatDate = (value: string | number | Date) => { |
| | | if (!value) return '-' |
| | | const date = new Date(value) |
| | | return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String( |
| | | date.getDate() |
| | | ).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String( |
| | | date.getMinutes() |
| | | ).padStart(2, '0')}` |
| | | } |
| | | |
| | | // 获取评审状态类型(基于评审次数) |
| | | const getReviewStatusType = (ratingCount) => { |
| | | const getReviewStatusType = (ratingCount: number) => { |
| | | return ratingCount > 0 ? 'success' : 'warning' |
| | | } |
| | | |
| | | // 获取评审状态名称(基于评审次数) |
| | | const getReviewStatusName = (ratingCount) => { |
| | | const getReviewStatusName = (ratingCount: number) => { |
| | | return ratingCount > 0 ? '已评审' : '未评审' |
| | | } |
| | | |
| | | // 获取比赛名称(如果是阶段,返回父比赛名称;如果是比赛,返回自己的名称) |
| | | const getActivityName = (activity) => { |
| | | const getActivityName = (activity: ActivityItem) => { |
| | | if (activity.pid > 0 && activity.parent) { |
| | | return activity.parent.name |
| | | } |
| | |
| | | } |
| | | |
| | | // 获取活动显示名称(用于搜索和选中时显示) |
| | | const getActivityDisplayName = (activity) => { |
| | | const getActivityDisplayName = (activity: ActivityItem) => { |
| | | if (activity.pid > 0 && activity.parent) { |
| | | return `${activity.parent.name} - ${activity.name}` |
| | | } |
| | | return activity.name |
| | | } |
| | | |
| | | // 获取阶段名称(如果是阶段,返回阶段名称;如果是比赛,返回比赛名称) |
| | | const getStageName = (activity) => { |
| | | return activity.name |
| | | // 权限检查:只有管理员可以查看项目评审 |
| | | const checkPermission = async () => { |
| | | const userInfo = getUserInfo() |
| | | if (!userInfo) { |
| | | router.push('/login') |
| | | } |
| | | } |
| | | |
| | | |
| | | // 查看项目详情 |
| | | const viewDetails = (projectId: number) => { |
| | | router.push(`/review/detail/${projectId}`) |
| | | } |
| | | |
| | | // 组件挂载时加载数据 |
| | | onMounted(() => { |
| | | checkPermission() |
| | | loadRegions() |
| | | loadActivities() |
| | | }) |
| | | </script> |
| | |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | /* 评审次数按钮样式 */ |
| | | .rating-count-btn { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* 弹窗样式 */ |
| | | .dialog-header { |
| | | margin-bottom: 20px; |
| | | |
| | | h4 { |
| | | margin: 0 0 8px 0; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #1f2937; |
| | | } |
| | | |
| | | .project-info { |
| | | margin: 0; |
| | | color: #6b7280; |
| | | font-size: 13px; |
| | | } |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | .rating-comment { |
| | | margin-top: 20px; |
| | | padding: 16px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 8px; |
| | | |
| | | h5 { |
| | | margin: 0 0 8px 0; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | p { |
| | | margin: 0; |
| | | color: #606266; |
| | | line-height: 1.6; |
| | | } |
| | | } |
| | | |
| | | .score { |
| | | color: #67c23a; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .no-score { |
| | | color: #909399; |
| | | } |
| | | |
| | | .item-score { |
| | | color: #409eff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .max-score { |
| | | color: #909399; |
| | | } |
| | | |
| | | /* 搜索工具栏 */ |
| | | .search-toolbar { |
| | | display: flex; |
| | |
| | | display: flex; |
| | | gap: 12px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .search-area { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | /* 操作按钮样式 */ |
| | |
| | | .view-btn:hover { |
| | | background-color: rgba(59, 130, 246, 0.1) !important; |
| | | transform: scale(1.2); |
| | | } |
| | | |
| | | .score { |
| | | color: #67c23a; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .no-score { |
| | | color: #909399; |
| | | font-style: italic; |
| | | } |
| | | |
| | | .pagination-container { |
| | |
| | | gap: 12px; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .search-area { |
| | | |
| | | .search-form { |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | } |
| | | </style> |
| | | </style> |