From c4a9cad1c50e89365e2a58b50e259af642ed3b8c Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期二, 07 十月 2025 16:12:20 +0800
Subject: [PATCH] feat(review): 调整评审详情展示顺序与样式,描述支持多行,项目信息列宽40/60 fix(auth): 登录页与首页循环跳转保护;api.ts 在登录页不再重定向;401分支在登录页不跳转 fix(router): /login 放行策略优化,避免死循环;评审列表跳转到 /project-review/:id/detail fix(frontend): 补齐 utils/appConfig.ts,避免启动白屏 fix(review): 详情页提交评分缺少stageId时回退使用项目详情的stageId feat(backend): ActivityPlayerDetailResponse.playerInfo 补充 avatarUrl/avatar,服务组装时填充用户头像 chore(dev): 启动脚本注入本地JWT密钥,重启前后端

---
 web/src/views/review-detail.vue |  159 +++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 111 insertions(+), 48 deletions(-)

diff --git a/web/src/views/review-detail.vue b/web/src/views/review-detail.vue
index 2dcace8..848948b 100644
--- a/web/src/views/review-detail.vue
+++ b/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韬唤锛屾鏌udge韬唤鍜屾潈闄�
-    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>
\ No newline at end of file

--
Gitblit v1.8.0