web/src/views/review-detail.vue
@@ -14,12 +14,15 @@
          <div class="project-section">
            <!-- 项目基本信息 -->
            <h4>项目信息</h4>
            <el-descriptions :column="2" border>
              <el-descriptions-item label="项目名称">
                {{ projectDetail.projectName || '未填写' }}
            <el-descriptions :column="2" border class="project-info">
              <el-descriptions-item label="比赛名称" :span="2">
                {{ competitionName || '未填写' }}
              </el-descriptions-item>
              <el-descriptions-item label="比赛名称">
                {{ projectDetail.activityName }}
              <el-descriptions-item label="比赛阶段" :span="2">
                {{ stageName || projectDetail.activityName || '未填写' }}
              </el-descriptions-item>
              <el-descriptions-item label="参赛项目名称" :span="2">
                {{ projectDetail.projectName || '未填写' }}
              </el-descriptions-item>
              <el-descriptions-item label="项目描述" :span="2">
                <div class="description-content">
@@ -183,7 +186,7 @@
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Document, UserFilled } from '@element-plus/icons-vue'
import { getProjectDetail, getRatingStats, submitRating, getCurrentJudgeRating } from '@/api/projectReview'
import { getProjectDetail, getRatingStats, submitRating, getCurrentJudgeRating, getActiveActivities } from '@/api/projectReview'
import { userApi } from '@/api/user'
import { getUserInfo } from '@/utils/auth'
@@ -195,6 +198,8 @@
const submitting = ref(false)
const projectDetail = ref(null)
const ratingStats = ref({ ratingCount: 0, averageScore: 0 })
const competitionName = ref('')
const stageName = ref('')
const ratingItems = ref([])
const ratingComment = ref('')
const previewVisible = ref(false)
@@ -211,7 +216,25 @@
// 计算属性
const projectId = computed(() => route.params.id)
const stageId = computed(() => route.query.stageId)
const stageId = computed(() => route.query.stageId || (projectDetail.value ? projectDetail.value.stageId : null))
const loadStageMeta = async () => {
  try {
    if (!projectDetail.value || !projectDetail.value.stageId) return
    const stages = await getActiveActivities()
    const stage = (stages || []).find(s => String(s.id) === String(projectDetail.value.stageId))
    if (stage) {
      stageName.value = stage.name || ''
      competitionName.value = stage.parent?.name || ''
    } else {
      stageName.value = projectDetail.value.activityName || ''
      competitionName.value = ''
    }
  } catch (e) {
    stageName.value = projectDetail.value?.activityName || ''
    competitionName.value = ''
  }
}
// 权限验证方法
const checkPermissions = async () => {
@@ -225,48 +248,64 @@
      return false
    }
    
    // 检查是否有employee身份
    if (userInfo.employee) {
      isEmployee.value = true
    // 设置员工身份标识
    isEmployee.value = !!userInfo.employee
    // 优先检查评委身份和权限(即使用户同时有员工身份)
    const judgeInfo = await userApi.getCurrentJudgeInfo()
    if (judgeInfo) {
      currentJudge.value = judgeInfo
      hasJudgePermission.value = true
      // 检查是否在当前比赛阶段的评委列表中
      if (projectDetail.value && projectDetail.value.stageId) {
        const isInActivity = await userApi.checkJudgeInActivity(
          projectDetail.value.stageId,
          judgeInfo.judgeId
        )
        if (isInActivity) {
          isJudgeInActivity.value = true
          canModifyRating.value = true // 有评委权限,可以修改评分
          permissionChecked.value = true
          if (isEmployee.value) {
            ElMessage.success('您同时拥有员工和评委身份,当前以评委身份进行评审')
          }
          return true
        } else {
          isJudgeInActivity.value = false
          // 如果没有当前比赛的评委权限,但有员工身份,则以员工身份查看
          if (isEmployee.value) {
            canModifyRating.value = false
            permissionChecked.value = true
            ElMessage.info('您没有当前比赛的评委权限,以员工身份查看评审详情')
            return true
          } else {
            ElMessage.error('您不是当前比赛的评委,无法进行评审')
            router.push('/project-review')
            return false
          }
        }
      }
    }
    // 如果没有评委身份,但有员工身份,则以员工身份查看
    if (isEmployee.value) {
      hasJudgePermission.value = false
      canModifyRating.value = false // employee只能查看,不能修改
      permissionChecked.value = true
      ElMessage.info('您以员工身份查看评审详情,只能查看不能修改评分')
      return true
    }
    
    // 如果没有employee身份,检查judge身份和权限
    const judgeInfo = await userApi.getCurrentJudgeInfo()
    // 既没有评委身份也没有员工身份
    hasJudgePermission.value = false
    ElMessage.error('您没有评委权限,无法进行评审')
    router.push('/project-review')
    return false
    
    if (!judgeInfo) {
      hasJudgePermission.value = false
      ElMessage.error('您没有评委权限,无法进行评审')
      router.push('/project-review')
      return false
    }
    currentJudge.value = judgeInfo
    hasJudgePermission.value = true
    // 检查是否在当前比赛阶段的评委列表中
    if (projectDetail.value && projectDetail.value.stageId) {
      const isInActivity = await userApi.checkJudgeInActivity(
        projectDetail.value.stageId,
        judgeInfo.judgeId
      )
      if (!isInActivity) {
        isJudgeInActivity.value = false
        ElMessage.error('您不是当前比赛的评委,无法进行评审')
        router.push('/project-review')
        return false
      }
      isJudgeInActivity.value = true
      canModifyRating.value = true // judge有权限修改评分
    }
    permissionChecked.value = true
    return true
  } catch (error) {
    console.error('权限验证失败:', error)
    hasJudgePermission.value = false
@@ -278,8 +317,8 @@
// 加载当前评委已有的评审数据
const loadExistingRating = async () => {
  // employee用户不需要加载评委的评分数据
  if (isEmployee.value || !hasJudgePermission.value) return
  // 只有具有评委权限且可以修改评分的用户才需要加载评委的评分数据
  if (!hasJudgePermission.value || !canModifyRating.value) return
  
  try {
    const rating = await getCurrentJudgeRating(parseInt(projectId.value))
@@ -315,6 +354,7 @@
  try {
    const data = await getProjectDetail(projectId.value)
    projectDetail.value = data
    await loadStageMeta()
    
    // 初始化评分项
    if (data.ratingForm && data.ratingForm.items) {
@@ -357,12 +397,13 @@
    return
  }
  
  // 验证stageId
  if (!stageId.value) {
  // 统一获取stageId(优先路由参数,其次详情里的stageId)
  const sid = stageId.value ? parseInt(stageId.value) : (projectDetail.value?.stageId ? parseInt(projectDetail.value.stageId) : null)
  if (!sid) {
    ElMessage.error('缺少比赛阶段信息,请重新进入页面')
    return
  }
  // 验证评分
  const hasEmptyScore = ratingItems.value.some(item => item.score === 0 || item.score === null)
  if (hasEmptyScore) {
@@ -381,7 +422,7 @@
    
    const ratingData = {
      activityPlayerId: parseInt(projectId.value),
      stageId: parseInt(stageId.value),
      stageId: sid,
      ratings: ratingItems.value.map(item => ({
        itemId: item.id,
        score: item.score
@@ -502,6 +543,7 @@
.description-content {
  line-height: 1.6;
  color: #606266;
  white-space: pre-wrap;
}
.attachments {
@@ -669,4 +711,25 @@
:deep(.el-card__body) {
  padding: 16px;
}
/* 仅针对项目信息这组描述设置标签/内容宽度比例 */
.project-info :deep(.el-descriptions__label) {
  width: 40% !important;
  min-width: 40%;
  box-sizing: border-box;
}
.project-info :deep(.el-descriptions__content) {
  width: 60% !important;
  min-width: 60%;
  box-sizing: border-box;
}
/* 窄屏自适应:小屏时回退为上下结构 */
@media (max-width: 768px) {
  .project-info :deep(.el-descriptions__label),
  .project-info :deep(.el-descriptions__content) {
    width: 100% !important;
    min-width: 100%;
  }
}
</style>