| | |
| | | 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="selectedRating"> |
| | | <div class="rating-detail-header"> |
| | | <h4>评委:{{ selectedRating.judgeName }}</h4> |
| | | <p>总分:<span class="total-score">{{ selectedRating.totalScore.toFixed(1) }}</span></p> |
| | | </div> |
| | | |
| | | <el-table |
| | | :data="ratingItems" |
| | | v-loading="ratingDetailLoading" |
| | | style="width: 100%" |
| | | > |
| | | <el-table-column prop="itemName" label="评分项目" min-width="150" /> |
| | | <el-table-column prop="score" label="得分" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <span class="item-score">{{ scope.row.score }}</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 || 100 }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div v-if="selectedRating.remark" class="rating-comment"> |
| | | <h5>评语:</h5> |
| | | <p>{{ selectedRating.remark }}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleRatingDetailClose">关闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | 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 { userApi } from '@/api/user' |
| | | import { getUserInfo } from '@/utils/auth' |
| | | |
| | | const router = useRouter() |
| | | |
| | |
| | | const searchName = ref('') |
| | | const activitiesLoading = ref(false) |
| | | const projectsLoading = ref(false) |
| | | |
| | | // 评审弹窗相关数据 |
| | | const ratingListVisible = ref(false) |
| | | const ratingDetailVisible = ref(false) |
| | | const selectedProject = ref(null) |
| | | const selectedRating = ref(null) |
| | | const judgeRatings = ref([]) |
| | | const ratingItems = ref([]) |
| | | const ratingsLoading = ref(false) |
| | | const ratingDetailLoading = ref(false) |
| | | |
| | | // 分页数据 |
| | | const currentPage = ref(1) |
| | |
| | | } |
| | | |
| | | // 查看详情 |
| | | const viewDetails = (projectId) => { |
| | | router.push(`/project-review/${projectId}/detail`) |
| | | const viewDetails = async (projectId) => { |
| | | // 传递stageId参数,selectedActivity.value就是当前选中的stageId |
| | | const stageId = selectedActivity.value |
| | | if (!stageId) { |
| | | ElMessage.warning('请先选择比赛阶段') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // 获取当前用户信息 |
| | | const userInfo = getUserInfo() |
| | | if (!userInfo) { |
| | | ElMessage.error('用户未登录,请重新登录') |
| | | return |
| | | } |
| | | |
| | | // 检查用户是否有employee身份 |
| | | const hasEmployeeRole = !!userInfo.employee |
| | | |
| | | if (hasEmployeeRole) { |
| | | // 如果用户有employee身份,直接允许查看(不需要权限检查) |
| | | console.log('用户具有员工身份,允许查看所有评分记录') |
| | | router.push(`/project-review/${projectId}/detail?stageId=${stageId}`) |
| | | return |
| | | } |
| | | |
| | | // 如果用户只有judge身份(没有employee身份),需要检查评委权限 |
| | | const judgeInfo = await userApi.getCurrentJudgeInfo() |
| | | |
| | | if (!judgeInfo) { |
| | | ElMessage.error('您没有评委权限,无法进行评审') |
| | | return |
| | | } |
| | | |
| | | // 检查评委是否有当前比赛阶段的权限 |
| | | const hasPermission = await userApi.checkJudgeInActivity(stageId, judgeInfo.judgeId) |
| | | |
| | | if (!hasPermission) { |
| | | ElMessage.error('您没有当前比赛阶段的评审权限,无法进入评审页面') |
| | | return |
| | | } |
| | | |
| | | // 权限检查通过,跳转到评审页面 |
| | | router.push(`/project-review/${projectId}/detail?stageId=${stageId}`) |
| | | } catch (error) { |
| | | console.error('权限检查失败:', error) |
| | | ElMessage.error('权限验证失败,请重新登录') |
| | | } |
| | | } |
| | | |
| | | // 显示评审列表 |
| | | const showRatingList = async (project) => { |
| | | if (project.ratingCount === 0) { |
| | | ElMessage.warning('该项目暂无评审记录') |
| | | return |
| | | } |
| | | |
| | | selectedProject.value = project |
| | | ratingListVisible.value = true |
| | | |
| | | // 加载评审列表 |
| | | await loadJudgeRatings(project.id) |
| | | } |
| | | |
| | | // 加载评委评分列表 |
| | | const loadJudgeRatings = async (activityPlayerId) => { |
| | | ratingsLoading.value = true |
| | | try { |
| | | const result = await getRatingStats(activityPlayerId) |
| | | console.log('getRatingStats 返回数据:', result) |
| | | |
| | | // 使用正确的字段名 |
| | | judgeRatings.value = result.ratings || [] |
| | | console.log('judgeRatings 设置为:', judgeRatings.value) |
| | | } catch (error) { |
| | | console.error('加载评审列表失败:', error) |
| | | ElMessage.error('加载评审列表失败') |
| | | judgeRatings.value = [] |
| | | } finally { |
| | | ratingsLoading.value = false |
| | | } |
| | | } |
| | | |
| | | // 查看评分详情 |
| | | const viewRatingDetail = async (rating) => { |
| | | if (!rating.hasRated) { |
| | | ElMessage.warning('该评委尚未评分') |
| | | return |
| | | } |
| | | |
| | | selectedRating.value = rating |
| | | ratingDetailVisible.value = true |
| | | |
| | | // 加载评分明细 |
| | | await loadRatingDetail(selectedProject.value.id, rating.judgeId) |
| | | } |
| | | |
| | | // 加载评分明细 |
| | | const loadRatingDetail = async (activityPlayerId, judgeId) => { |
| | | ratingDetailLoading.value = true |
| | | try { |
| | | console.log('加载评分明细,activityPlayerId:', activityPlayerId, 'judgeId:', judgeId) |
| | | |
| | | const result = await getJudgeRatingDetail(activityPlayerId, judgeId) |
| | | console.log('评分明细API返回:', result) |
| | | |
| | | if (result && result.items) { |
| | | ratingItems.value = result.items.map(item => ({ |
| | | itemName: item.ratingItemName, |
| | | score: item.score, |
| | | maxScore: item.maxScore || 100 |
| | | })) |
| | | |
| | | // 更新选中评分的备注信息 |
| | | if (selectedRating.value) { |
| | | selectedRating.value.remark = result.remark |
| | | } |
| | | } else { |
| | | ratingItems.value = [] |
| | | } |
| | | } catch (error) { |
| | | console.error('加载评分明细失败:', error) |
| | | ElMessage.error('加载评分明细失败: ' + (error.message || '未知错误')) |
| | | ratingItems.value = [] |
| | | } finally { |
| | | ratingDetailLoading.value = false |
| | | } |
| | | } |
| | | |
| | | // 关闭评审列表弹窗 |
| | | const handleRatingListClose = () => { |
| | | ratingListVisible.value = false |
| | | selectedProject.value = null |
| | | judgeRatings.value = [] |
| | | } |
| | | |
| | | // 关闭评分详情弹窗 |
| | | const handleRatingDetailClose = () => { |
| | | ratingDetailVisible.value = false |
| | | selectedRating.value = null |
| | | ratingItems.value = [] |
| | | } |
| | | |
| | | // 格式化日期 |
| | |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | /* 评审次数按钮样式 */ |
| | | .rating-count-btn { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* 弹窗样式 */ |
| | | .dialog-header { |
| | | margin-bottom: 20px; |
| | | |
| | | h4 { |
| | | margin: 0 0 8px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .project-info { |
| | | margin: 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | |
| | | .rating-detail-header { |
| | | margin-bottom: 20px; |
| | | |
| | | h4 { |
| | | margin: 0 0 8px 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .total-score { |
| | | color: #409eff; |
| | | font-weight: 600; |
| | | font-size: 18px; |
| | | } |
| | | } |
| | | |
| | | .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; |