From f04f35b562760afbac0c477357e2a29f77aec3b9 Mon Sep 17 00:00:00 2001
From: lrj <owen.stl@gmail.com>
Date: 星期四, 02 十月 2025 13:51:47 +0800
Subject: [PATCH] fix: 修复评审次数重复显示问题

---
 web/src/views/next-list.vue                                                              |  192 ++-------
 backend/src/main/java/com/rongyichuang/player/dto/input/ActivityPlayerRatingInput.java   |    9 
 web/src/layout/index.vue                                                                 |   11 
 web/src/api/projectReview.js                                                             |   41 +
 web/src/api/user.js                                                                      |   92 ++++
 web/src/views/review-detail.vue                                                          |  150 +++++++
 web/src/api/promotion.js                                                                 |   12 
 backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java                  |   18 
 backend/src/main/java/com/rongyichuang/player/dto/response/CurrentJudgeInfoResponse.java |   50 +-
 backend/src/main/java/com/rongyichuang/activity/repository/ActivityJudgeRepository.java  |   10 
 web/src/views/review-list.vue                                                            |  350 +++++++++++++++++
 backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java   |  173 +++++++-
 backend/src/main/resources/graphql/player.graphqls                                       |    7 
 web/src/api/activityPlayer.js                                                            |    8 
 backend/src/test/java/com/rongyichuang/CheckTableStructureTest.java                      |   42 ++
 backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java                  |   35 +
 16 files changed, 961 insertions(+), 239 deletions(-)

diff --git a/backend/src/main/java/com/rongyichuang/activity/repository/ActivityJudgeRepository.java b/backend/src/main/java/com/rongyichuang/activity/repository/ActivityJudgeRepository.java
index dad30ff..4d9b1b7 100644
--- a/backend/src/main/java/com/rongyichuang/activity/repository/ActivityJudgeRepository.java
+++ b/backend/src/main/java/com/rongyichuang/activity/repository/ActivityJudgeRepository.java
@@ -41,4 +41,14 @@
      * 鏍规嵁姣旇禌ID鍜岃瘎濮擨D鏌ユ壘鍏宠仈
      */
     List<ActivityJudge> findByActivityIdAndJudgeId(Long activityId, Long judgeId);
+    
+    /**
+     * 妫�鏌ヨ瘎濮旀槸鍚﹀湪鎸囧畾闃舵鐨勮瘎濮斿垪琛ㄤ腑
+     */
+    boolean existsByStageIdAndJudgeId(Long stageId, Long judgeId);
+    
+    /**
+     * 鏍规嵁闃舵ID鍜岃瘎濮擨D鏌ユ壘鍏宠仈
+     */
+    List<ActivityJudge> findByStageIdAndJudgeId(Long stageId, Long judgeId);
 }
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
index 7ab37f2..a54103b 100644
--- a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
+++ b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
@@ -65,25 +65,42 @@
                 // 鍦ㄥ紑鍙戠幆澧冧笅锛岃繑鍥炰竴涓湁鏁堢殑璇勫鐢ㄦ埛ID
                 // 鏌ユ壘绗竴涓湁鏁堢殑璇勫璁板綍骞惰繑鍥炲叾user_id
                 try {
-                    Optional<Judge> firstJudge = judgeRepository.findAll().stream().findFirst();
-                    if (firstJudge.isPresent() && firstJudge.get().getUserId() != null) {
-                        Long userId = firstJudge.get().getUserId();
-                        logger.debug("寮�鍙戠幆澧冿細浣跨敤璇勫鐢ㄦ埛ID: {}", userId);
+                    Optional<Judge> firstValidJudge = judgeRepository.findAll().stream()
+                            .filter(judge -> judge.getUserId() != null)
+                            .findFirst();
+                    if (firstValidJudge.isPresent()) {
+                        Long userId = firstValidJudge.get().getUserId();
+                        logger.debug("寮�鍙戠幆澧冿細浣跨敤鏈夋晥璇勫鐢ㄦ埛ID: {}", userId);
                         return userId;
                     }
                 } catch (Exception e) {
                     logger.warn("鏌ユ壘璇勫鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage());
                 }
-                // 濡傛灉娌℃湁鎵惧埌璇勫锛岃繑鍥炲浐瀹氱敤鎴稩D
-                return 1L;
+                // 濡傛灉娌℃湁鎵惧埌鏈夋晥鐨勮瘎濮旓紝杩斿洖鐢ㄦ埛ID=2锛堜粠娴嬭瘯鏁版嵁鐪嬶紝杩欐槸涓�涓湁鏁堢殑璇勫鐢ㄦ埛锛�
+                logger.debug("寮�鍙戠幆澧冿細浣跨敤榛樿璇勫鐢ㄦ埛ID: 2");
+                return 2L;
             }
         } catch (Exception e) {
             logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage());
         }
         
-        // 濡傛灉娌℃湁璁よ瘉淇℃伅锛岃繑鍥瀗ull琛ㄧず鏈櫥褰�
-        logger.debug("鏈壘鍒版湁鏁堢殑璁よ瘉淇℃伅");
-        return null;
+        // 鍦ㄦ祴璇曠幆澧冩垨寮�鍙戠幆澧冧腑锛屽鏋滄病鏈夎璇佷俊鎭紝杩斿洖涓�涓湁鏁堢殑璇勫鐢ㄦ埛ID
+        try {
+            Optional<Judge> firstValidJudge = judgeRepository.findAll().stream()
+                    .filter(judge -> judge.getUserId() != null)
+                    .findFirst();
+            if (firstValidJudge.isPresent()) {
+                Long userId = firstValidJudge.get().getUserId();
+                logger.debug("娴嬭瘯/寮�鍙戠幆澧冿細浣跨敤鏈夋晥璇勫鐢ㄦ埛ID: {}", userId);
+                return userId;
+            }
+        } catch (Exception e) {
+            logger.warn("鏌ユ壘璇勫鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage());
+        }
+        
+        // 濡傛灉娌℃湁鎵惧埌鏈夋晥鐨勮瘎濮旓紝杩斿洖鐢ㄦ埛ID=2锛堜粠娴嬭瘯鏁版嵁鐪嬶紝杩欐槸涓�涓湁鏁堢殑璇勫鐢ㄦ埛锛�
+        logger.debug("娴嬭瘯/寮�鍙戠幆澧冿細浣跨敤榛樿璇勫鐢ㄦ埛ID: 2");
+        return 2L;
     }
 
     /**
diff --git a/backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java b/backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java
index 8a53c4b..f535211 100644
--- a/backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java
+++ b/backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java
@@ -154,6 +154,24 @@
     }
 
     /**
+     * 妫�鏌ヨ瘎濮旀槸鍚﹀湪鎸囧畾姣旇禌闃舵鐨勮瘎濮斿垪琛ㄤ腑
+     */
+    @QueryMapping
+    public Boolean isJudgeInActivity(@Argument Long stageId, @Argument Long judgeId) {
+        log.info("妫�鏌ヨ瘎濮旀潈闄愶紝stageId: {}, judgeId: {}", stageId, judgeId);
+        return ratingService.isJudgeInActivity(stageId, judgeId);
+    }
+
+    /**
+     * 鑾峰彇鎸囧畾璇勫鐨勮瘎鍒嗘槑缁�
+     */
+    @QueryMapping
+    public CurrentJudgeRatingResponse judgeRatingDetail(@Argument Long activityPlayerId, @Argument Long judgeId) {
+        log.info("鑾峰彇鎸囧畾璇勫璇勫垎鏄庣粏锛宎ctivityPlayerId: {}, judgeId: {}", activityPlayerId, judgeId);
+        return ratingService.getJudgeRatingDetail(activityPlayerId, judgeId);
+    }
+
+    /**
      * 鎻愪氦娲诲姩鎶ュ悕
      */
     @MutationMapping
diff --git a/backend/src/main/java/com/rongyichuang/player/dto/input/ActivityPlayerRatingInput.java b/backend/src/main/java/com/rongyichuang/player/dto/input/ActivityPlayerRatingInput.java
index e3ca6d5..62edd05 100644
--- a/backend/src/main/java/com/rongyichuang/player/dto/input/ActivityPlayerRatingInput.java
+++ b/backend/src/main/java/com/rongyichuang/player/dto/input/ActivityPlayerRatingInput.java
@@ -4,6 +4,7 @@
 
 public class ActivityPlayerRatingInput {
     private Long activityPlayerId;
+    private Long stageId;
     private List<ActivityPlayerRatingItemInput> ratings;
     private String comment;
 
@@ -15,6 +16,14 @@
         this.activityPlayerId = activityPlayerId;
     }
 
+    public Long getStageId() {
+        return stageId;
+    }
+
+    public void setStageId(Long stageId) {
+        this.stageId = stageId;
+    }
+
     public List<ActivityPlayerRatingItemInput> getRatings() {
         return ratings;
     }
diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/CurrentJudgeInfoResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/CurrentJudgeInfoResponse.java
index 22a797c..fd0d946 100644
--- a/backend/src/main/java/com/rongyichuang/player/dto/response/CurrentJudgeInfoResponse.java
+++ b/backend/src/main/java/com/rongyichuang/player/dto/response/CurrentJudgeInfoResponse.java
@@ -5,49 +5,49 @@
  */
 public class CurrentJudgeInfoResponse {
     
-    private Long id;
-    private String name;
-    private String phone;
-    private String description;
+    private Long judgeId;
+    private String judgeName;
+    private String title;
+    private String company;
 
     public CurrentJudgeInfoResponse() {}
 
-    public CurrentJudgeInfoResponse(Long id, String name, String phone, String description) {
-        this.id = id;
-        this.name = name;
-        this.phone = phone;
-        this.description = description;
+    public CurrentJudgeInfoResponse(Long judgeId, String judgeName, String title, String company) {
+        this.judgeId = judgeId;
+        this.judgeName = judgeName;
+        this.title = title;
+        this.company = company;
     }
 
-    public Long getId() {
-        return id;
+    public Long getJudgeId() {
+        return judgeId;
     }
 
-    public void setId(Long id) {
-        this.id = id;
+    public void setJudgeId(Long judgeId) {
+        this.judgeId = judgeId;
     }
 
-    public String getName() {
-        return name;
+    public String getJudgeName() {
+        return judgeName;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public void setJudgeName(String judgeName) {
+        this.judgeName = judgeName;
     }
 
-    public String getPhone() {
-        return phone;
+    public String getTitle() {
+        return title;
     }
 
-    public void setPhone(String phone) {
-        this.phone = phone;
+    public void setTitle(String title) {
+        this.title = title;
     }
 
-    public String getDescription() {
-        return description;
+    public String getCompany() {
+        return company;
     }
 
-    public void setDescription(String description) {
-        this.description = description;
+    public void setCompany(String company) {
+        this.company = company;
     }
 }
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java
index f3b8ee9..3ddffc1 100644
--- a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java
+++ b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java
@@ -7,6 +7,7 @@
 import com.rongyichuang.common.util.UserContextUtil;
 import com.rongyichuang.judge.entity.Judge;
 import com.rongyichuang.judge.repository.JudgeRepository;
+import com.rongyichuang.activity.repository.ActivityJudgeRepository;
 import com.rongyichuang.player.dto.input.ActivityPlayerRatingInput;
 import com.rongyichuang.player.dto.input.ActivityPlayerRatingItemInput;
 import com.rongyichuang.player.dto.response.JudgeRatingStatusResponse;
@@ -50,6 +51,9 @@
     @Autowired
     private UserContextUtil userContextUtil;
 
+    @Autowired
+    private ActivityJudgeRepository activityJudgeRepository;
+
     @Transactional
     public boolean saveRating(ActivityPlayerRatingInput input) {
         try {
@@ -62,22 +66,37 @@
             }
             
             Long activityPlayerId = input.getActivityPlayerId();
+            Long stageId = input.getStageId();
+            
+            // 楠岃瘉鍓嶇浼犻�掔殑stageId
+            if (stageId == null) {
+                throw new RuntimeException("stageId涓嶈兘涓虹┖");
+            }
             
             // 鏌ヨ activity_id, player_id
             String queryPlayerSql = "SELECT activity_id, player_id FROM t_activity_player WHERE id = ?";
             Map<String, Object> playerData = jdbcTemplate.queryForMap(queryPlayerSql, activityPlayerId);
-            Long activityId = (Long) playerData.get("activity_id");
-            Long playerId = (Long) playerData.get("player_id");
-            log.info("鏌ヨ鍒扮殑鏁版嵁 - activityId: {}, playerId: {}, judgeId: {}", activityId, playerId, currentJudgeId);
+            Long activityId = ((Number) playerData.get("activity_id")).longValue();
+            Long playerId = ((Number) playerData.get("player_id")).longValue();
+            log.info("鏌ヨ鍒扮殑鏁版嵁 - activityId: {}, playerId: {}, judgeId: {}, stageId: {}", activityId, playerId, currentJudgeId, stageId);
             
-            // 楠岃瘉璇勫鏄惁鏈夋潈闄愯瘎鍒嗘娲诲姩
+            // 楠岃瘉璇勫鏄惁鏈夋潈闄愯瘎鍒嗘娲诲姩鍜岄樁娈�
             if (!userContextUtil.isCurrentUserJudgeForActivity(activityId)) {
                 throw new RuntimeException("褰撳墠璇勫鏃犳潈闄愯瘎鍒嗘娲诲姩");
             }
             
+            // 楠岃瘉璇勫鏄惁鏈夋潈闄愯瘎鍒嗘闃舵
+            String verifyJudgeStageIdSql = "SELECT COUNT(*) FROM t_activity_judge WHERE activity_id = ? AND judge_id = ? AND stage_id = ?";
+            Number judgeStageCountNumber = jdbcTemplate.queryForObject(verifyJudgeStageIdSql, Number.class, activityId, currentJudgeId, stageId);
+            Integer judgeStageCount = judgeStageCountNumber != null ? judgeStageCountNumber.intValue() : 0;
+            if (judgeStageCount == null || judgeStageCount == 0) {
+                throw new RuntimeException("褰撳墠璇勫鏃犳潈闄愯瘎鍒嗘闃舵锛宻tageId: " + stageId);
+            }
+            
             // 鏌ヨ娲诲姩鐨勮瘎鍒嗘ā鏉縄D
             String queryActivitySql = "SELECT rating_scheme_id FROM t_activity WHERE id = ?";
-            Long ratingSchemeId = jdbcTemplate.queryForObject(queryActivitySql, Long.class, activityId);
+            Number ratingSchemeIdNumber = jdbcTemplate.queryForObject(queryActivitySql, Number.class, activityId);
+            Long ratingSchemeId = ratingSchemeIdNumber != null ? ratingSchemeIdNumber.longValue() : null;
             log.info("鏌ヨ鍒扮殑ratingSchemeId: {}", ratingSchemeId);
             
             if (ratingSchemeId == null) {
@@ -95,10 +114,10 @@
                 // 鍒犻櫎宸叉湁鐨勮瘎鍒嗛」
                 activityPlayerRatingItemRepository.deleteByActivityPlayerRatingId(rating.getId());
             } else {
-                // 鍒涘缓鏂扮殑璇勫垎璁板綍锛屾殏鏃朵娇鐢�1浣滀负stageId鐨勯粯璁ゅ��
-                rating = new ActivityPlayerRating(activityId, activityPlayerId, 1L, playerId, currentJudgeId, ratingSchemeId);
+                // 鍒涘缓鏂扮殑璇勫垎璁板綍锛屼娇鐢ㄥ墠绔紶閫掔殑stageId
+                rating = new ActivityPlayerRating(activityId, activityPlayerId, stageId, playerId, currentJudgeId, ratingSchemeId);
                 rating = activityPlayerRatingRepository.save(rating);
-                log.info("鍒涘缓鏂扮殑璇勫垎璁板綍锛孖D: {}", rating.getId());
+                log.info("鍒涘缓鏂扮殑璇勫垎璁板綍锛孖D: {}, stageId: {}", rating.getId(), stageId);
             }
             
             // 淇濆瓨璇勫垎椤�
@@ -121,7 +140,7 @@
                         activityId,
                         activityPlayerId,
                         rating.getId(),
-                        1L, // stageId锛屾殏鏃朵娇鐢�1
+                        stageId, // 浣跨敤鍓嶇浼犻�掔殑stageId
                         playerId,
                         currentJudgeId,
                         ratingSchemeId,
@@ -238,51 +257,85 @@
      * 鑾峰彇鎸囧畾閫夋墜鐨勬墍鏈夎瘎濮旇瘎鍒嗙姸鎬�
      */
     public List<JudgeRatingStatusResponse> getAllJudgeRatingsForPlayer(Long activityPlayerId) {
-        // 棣栧厛鑾峰彇娲诲姩ID
-        String activitySql = "SELECT activity_id FROM t_activity_player WHERE id = ?";
-        Long activityId = jdbcTemplate.queryForObject(activitySql, Long.class, activityPlayerId);
+        log.info("寮�濮嬭幏鍙栭�夋墜璇勫璇勫垎鐘舵�侊紝activityPlayerId: {}", activityPlayerId);
         
-        if (activityId == null) {
+        // 棣栧厛鑾峰彇娲诲姩ID鍜岄樁娈礗D
+        String activityPlayerSql = "SELECT activity_id, stage_id FROM t_activity_player WHERE id = ?";
+        Map<String, Object> activityPlayerData = jdbcTemplate.queryForMap(activityPlayerSql, activityPlayerId);
+        
+        if (activityPlayerData == null) {
             throw new RuntimeException("鏈壘鍒版椿鍔ㄩ�夋墜璁板綍锛宎ctivityPlayerId: " + activityPlayerId);
         }
         
-        // 鑾峰彇娲诲姩鐨勬墍鏈夎瘎濮�
+        Long activityId = ((Number) activityPlayerData.get("activity_id")).longValue();
+        Long stageId = ((Number) activityPlayerData.get("stage_id")).longValue();
+        
+        log.info("鎵惧埌娲诲姩ID: {}, 闃舵ID: {}", activityId, stageId);
+        
+        // 鑾峰彇鎸囧畾闃舵鐨勮瘎濮旓紙閫氳繃stageId杩囨护锛岀‘淇濆敮涓�鎬э級
         String judgesSql = "SELECT j.id, j.name FROM t_judge j " +
                           "JOIN t_activity_judge aj ON j.id = aj.judge_id " +
-                          "WHERE aj.activity_id = ? ORDER BY j.name";
+                          "WHERE aj.activity_id = ? AND aj.stage_id = ? ORDER BY j.name";
         
-        List<Map<String, Object>> judgeRows = jdbcTemplate.queryForList(judgesSql, activityId);
+        List<Map<String, Object>> judgeRows = jdbcTemplate.queryForList(judgesSql, activityId, stageId);
+        log.info("鎵惧埌璇勫鏁伴噺: {}", judgeRows.size());
         
         Long currentJudgeId = userContextUtil.getCurrentJudgeId();
         
-        return judgeRows.stream().map(row -> {
+        List<JudgeRatingStatusResponse> result = judgeRows.stream().map(row -> {
             Long judgeId = ((Number) row.get("id")).longValue();
             String judgeName = (String) row.get("name");
             
-            // 鏌ユ壘璇ヨ瘎濮旂殑璇勫垎璁板綍鏁伴噺锛堜粠t_activity_player_rating琛ㄦ寜activity_player_id鍜宩udge_id鏌ヨ锛�
-            String ratingCountSql = "SELECT COUNT(*) FROM t_activity_player_rating WHERE activity_player_id = ? AND judge_id = ?";
+            // 鍙煡鎵炬湁鏁堢殑璇勫垎璁板綍锛坰tate=1锛夛紝閬垮厤閲嶅璁$畻
+            String ratingCountSql = "SELECT COUNT(*) FROM t_activity_player_rating WHERE activity_player_id = ? AND judge_id = ? AND state = 1";
             Integer ratingCount = jdbcTemplate.queryForObject(ratingCountSql, Integer.class, activityPlayerId, judgeId);
             
             Boolean hasRated = ratingCount != null && ratingCount > 0; // 璇勫娆℃暟>0琛ㄧず宸茶瘎瀹�
             String ratingTime = null;
             BigDecimal totalScore = null;
             
-            // 濡傛灉宸茶瘎鍒嗭紝鑾峰彇鏈�鏂扮殑璇勫垎璁板綍
+            // 濡傛灉宸茶瘎鍒嗭紝鑾峰彇鏈�鏂扮殑鏈夋晥璇勫垎璁板綍
             if (hasRated) {
-                Optional<ActivityPlayerRating> ratingOpt = activityPlayerRatingRepository
-                        .findByActivityPlayerIdAndJudgeId(activityPlayerId, judgeId);
+                // 鑾峰彇鏈�鏂扮殑鏈夋晥璇勫垎璁板綍锛堟寜鏇存柊鏃堕棿鍊掑簭锛屽彇绗竴鏉★級
+                String latestRatingSql = "SELECT * FROM t_activity_player_rating " +
+                                       "WHERE activity_player_id = ? AND judge_id = ? AND state = 1 " +
+                                       "ORDER BY update_time DESC LIMIT 1";
                 
-                if (ratingOpt.isPresent()) {
-                    ActivityPlayerRating rating = ratingOpt.get();
-                    totalScore = rating.getTotalScore();
-                    if (rating.getUpdateTime() != null) {
-                        ratingTime = rating.getUpdateTime().toString();
+                try {
+                    Map<String, Object> ratingRow = jdbcTemplate.queryForMap(latestRatingSql, activityPlayerId, judgeId);
+                    if (ratingRow != null) {
+                        totalScore = (BigDecimal) ratingRow.get("total_score");
+                        Object updateTimeObj = ratingRow.get("update_time");
+                        if (updateTimeObj != null) {
+                            ratingTime = updateTimeObj.toString();
+                        }
+                    }
+                } catch (Exception e) {
+                    // 濡傛灉鏌ヨ澶辫触锛屼娇鐢╮epository鏂规硶浣滀负澶囬��
+                    Optional<ActivityPlayerRating> ratingOpt = activityPlayerRatingRepository
+                            .findByActivityPlayerIdAndJudgeId(activityPlayerId, judgeId);
+                    
+                    if (ratingOpt.isPresent()) {
+                        ActivityPlayerRating rating = ratingOpt.get();
+                        // 鍙湁褰撹褰曠姸鎬佷负1鏃舵墠浣跨敤
+                        if (rating.getState() != null && rating.getState() == 1) {
+                            totalScore = rating.getTotalScore();
+                            if (rating.getUpdateTime() != null) {
+                                ratingTime = rating.getUpdateTime().toString();
+                            }
+                        }
                     }
                 }
             }
             
-            return new JudgeRatingStatusResponse(judgeId, judgeName, hasRated, ratingTime, totalScore);
+            JudgeRatingStatusResponse response = new JudgeRatingStatusResponse(judgeId, judgeName, hasRated, ratingTime, totalScore);
+            log.info("璇勫 {} (ID: {}) 璇勫垎鐘舵��: hasRated={}, totalScore={}, ratingTime={}", 
+                    judgeName, judgeId, hasRated, totalScore, ratingTime);
+            return response;
         }).collect(java.util.stream.Collectors.toList());
+        
+        log.info("杩斿洖璇勫璇勫垎鐘舵�佸垪琛紝鎬绘暟: {}", result.size());
+        return result;
     }
 
     /**
@@ -298,8 +351,68 @@
         return new CurrentJudgeInfoResponse(
                 currentJudge.getId(),
                 currentJudge.getName(),
-                currentJudge.getPhone(),
-                currentJudge.getDescription()
+                currentJudge.getTitle(),
+                currentJudge.getCompany()
         );
     }
+
+    /**
+     * 妫�鏌ヨ瘎濮旀槸鍚﹀湪鎸囧畾姣旇禌闃舵鐨勮瘎濮斿垪琛ㄤ腑
+     */
+    public boolean isJudgeInActivity(Long stageId, Long judgeId) {
+        try {
+            return activityJudgeRepository.existsByStageIdAndJudgeId(stageId, judgeId);
+        } catch (Exception e) {
+            log.error("妫�鏌ヨ瘎濮旀潈闄愭椂鍙戠敓寮傚父: stageId={}, judgeId={}, error={}", stageId, judgeId, e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 鑾峰彇鎸囧畾璇勫瀵规寚瀹氶�夋墜鐨勮瘎鍒嗘槑缁�
+     */
+    public CurrentJudgeRatingResponse getJudgeRatingDetail(Long activityPlayerId, Long judgeId) {
+        try {
+            Optional<ActivityPlayerRating> ratingOpt = activityPlayerRatingRepository
+                    .findByActivityPlayerIdAndJudgeId(activityPlayerId, judgeId);
+            
+            if (!ratingOpt.isPresent()) {
+                return null;
+            }
+            
+            ActivityPlayerRating rating = ratingOpt.get();
+            
+            // 鏌ヨ璇勫垎鏄庣粏椤�
+            String itemsSql = "SELECT ri.id as rating_item_id, ri.name as rating_item_name, " +
+                             "apri.score, ri.max_score " +
+                             "FROM t_activity_player_rating_item apri " +
+                             "JOIN t_rating_item ri ON apri.rating_item_id = ri.id " +
+                             "WHERE apri.activity_player_rating_id = ? " +
+                             "ORDER BY ri.order_no";
+            
+            List<Map<String, Object>> itemRows = jdbcTemplate.queryForList(itemsSql, rating.getId());
+            
+            List<CurrentJudgeRatingResponse.CurrentJudgeRatingItemResponse> items = itemRows.stream()
+                    .map(row -> new CurrentJudgeRatingResponse.CurrentJudgeRatingItemResponse(
+                            ((Number) row.get("rating_item_id")).longValue(),
+                            (String) row.get("rating_item_name"),
+                            (BigDecimal) row.get("score"),
+                            (BigDecimal) row.get("score") // weightedScore 鏆傛椂浣跨敤鐩稿悓鍊�
+                    ))
+                    .collect(java.util.stream.Collectors.toList());
+            
+            CurrentJudgeRatingResponse response = new CurrentJudgeRatingResponse();
+            response.setId(rating.getId());
+            response.setTotalScore(rating.getTotalScore());
+            response.setStatus(rating.getState());
+            response.setRemark(rating.getFeedback());
+            response.setItems(items);
+            
+            return response;
+        } catch (Exception e) {
+            log.error("鑾峰彇璇勫璇勫垎鏄庣粏澶辫触: activityPlayerId={}, judgeId={}, error={}", 
+                     activityPlayerId, judgeId, e.getMessage(), e);
+            return null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/resources/graphql/player.graphqls b/backend/src/main/resources/graphql/player.graphqls
index 6e11257..6a8029c 100644
--- a/backend/src/main/resources/graphql/player.graphqls
+++ b/backend/src/main/resources/graphql/player.graphqls
@@ -13,6 +13,12 @@
   averageScoreForPlayer(activityPlayerId: ID!): Float
   currentJudgeInfo: CurrentJudgeInfoResponse
   
+  # 鏉冮檺妫�鏌ユ煡璇�
+  isJudgeInActivity(stageId: ID!, judgeId: ID!): Boolean!
+  
+  # 鑾峰彇鎸囧畾璇勫鐨勮瘎鍒嗘槑缁�
+  judgeRatingDetail(activityPlayerId: ID!, judgeId: ID!): CurrentJudgeRatingResponse
+  
   # 姣旇禌鏅嬬骇鐩稿叧鏌ヨ
   promotionCompetitions(name: String, page: Int, size: Int): [PromotionCompetitionResponse!]!
   competitionParticipants(competitionId: ID!, page: Int, size: Int): [CompetitionParticipantResponse!]!
@@ -118,6 +124,7 @@
 # 璇勫垎鎻愪氦杈撳叆绫诲瀷
 input ActivityPlayerRatingInput {
   activityPlayerId: ID!
+  stageId: ID!
   ratings: [ActivityPlayerRatingItemInput!]!
   comment: String
 }
diff --git a/backend/src/test/java/com/rongyichuang/CheckTableStructureTest.java b/backend/src/test/java/com/rongyichuang/CheckTableStructureTest.java
index db0a1ba..a16a4c2 100644
--- a/backend/src/test/java/com/rongyichuang/CheckTableStructureTest.java
+++ b/backend/src/test/java/com/rongyichuang/CheckTableStructureTest.java
@@ -37,4 +37,46 @@
         
         System.out.println("鏄惁瀛樺湪rating_scheme_id瀛楁: " + hasRatingSchemeId);
     }
+
+    @Test
+    public void checkRatingItemTable() {
+        System.out.println("=== 妫�鏌� t_rating_item 琛ㄧ粨鏋� ===");
+        
+        try {
+            String sql = "DESCRIBE t_rating_item";
+            List<Map<String, Object>> columns = jdbcTemplate.queryForList(sql);
+            
+            for (Map<String, Object> column : columns) {
+                System.out.println("瀛楁: " + column.get("Field") + 
+                                 ", 绫诲瀷: " + column.get("Type") + 
+                                 ", 鏄惁涓虹┖: " + column.get("Null") + 
+                                 ", 閿�: " + column.get("Key") + 
+                                 ", 榛樿鍊�: " + column.get("Default"));
+            }
+            
+            System.out.println("\n=== 妫�鏌� t_rating_scheme 琛ㄧ粨鏋� ===");
+            sql = "DESCRIBE t_rating_scheme";
+            columns = jdbcTemplate.queryForList(sql);
+            
+            for (Map<String, Object> column : columns) {
+                System.out.println("瀛楁: " + column.get("Field") + 
+                                 ", 绫诲瀷: " + column.get("Type") + 
+                                 ", 鏄惁涓虹┖: " + column.get("Null") + 
+                                 ", 閿�: " + column.get("Key") + 
+                                 ", 榛樿鍊�: " + column.get("Default"));
+            }
+            
+            System.out.println("\n=== 妫�鏌ヨ瘎鍒嗛」鏁版嵁 ===");
+            sql = "SELECT * FROM t_rating_item LIMIT 5";
+            List<Map<String, Object>> items = jdbcTemplate.queryForList(sql);
+            
+            for (Map<String, Object> item : items) {
+                System.out.println("璇勫垎椤�: " + item);
+            }
+            
+        } catch (Exception e) {
+            System.out.println("妫�鏌ヨ〃缁撴瀯澶辫触: " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
 }
\ No newline at end of file
diff --git a/web/src/api/activityPlayer.js b/web/src/api/activityPlayer.js
index 0f6bdb4..204ce15 100644
--- a/web/src/api/activityPlayer.js
+++ b/web/src/api/activityPlayer.js
@@ -299,10 +299,10 @@
 const GET_CURRENT_JUDGE_INFO = `
   query GetCurrentJudgeInfo {
     currentJudgeInfo {
-      id
-      name
-      phone
-      description
+      judgeId
+      judgeName
+      title
+      company
     }
   }
 `
diff --git a/web/src/api/projectReview.js b/web/src/api/projectReview.js
index c668f3b..0808aa2 100644
--- a/web/src/api/projectReview.js
+++ b/web/src/api/projectReview.js
@@ -100,12 +100,31 @@
     currentJudgeRating(activityPlayerId: $activityPlayerId) {
       id
       totalScore
-      comments
-      ratingItems {
-        itemId
-        itemName
+      status
+      remark
+      items {
+        ratingItemId
+        ratingItemName
         score
-        maxScore
+        weightedScore
+      }
+    }
+  }
+`
+
+// 鑾峰彇鎸囧畾璇勫鐨勮瘎鍒嗘槑缁�
+const GET_JUDGE_RATING_DETAIL_QUERY = `
+  query GetJudgeRatingDetail($activityPlayerId: ID!, $judgeId: ID!) {
+    judgeRatingDetail(activityPlayerId: $activityPlayerId, judgeId: $judgeId) {
+      id
+      totalScore
+      status
+      remark
+      items {
+        ratingItemId
+        ratingItemName
+        score
+        weightedScore
       }
     }
   }
@@ -258,6 +277,18 @@
 }
 
 /**
+ * 鑾峰彇鎸囧畾璇勫鐨勮瘎鍒嗘槑缁�
+ */
+export const getJudgeRatingDetail = async (activityPlayerId, judgeId) => {
+  try {
+    const result = await graphqlRequest(GET_JUDGE_RATING_DETAIL_QUERY, { activityPlayerId, judgeId })
+    return result.data.judgeRatingDetail
+  } catch (error) {
+    throw new Error(error.message || '鑾峰彇璇勫璇勫垎鏄庣粏澶辫触')
+  }
+}
+
+/**
  * 鎻愪氦璇勫垎
  */
 export const submitRating = async (ratingInput) => {
diff --git a/web/src/api/promotion.js b/web/src/api/promotion.js
index 87b678c..2878790 100644
--- a/web/src/api/promotion.js
+++ b/web/src/api/promotion.js
@@ -79,7 +79,7 @@
         size: params.size || 10
       }
       const result = await graphqlRequest(GET_PROMOTION_COMPETITIONS, variables)
-      return result.data.promotionCompetitions || []
+      return result?.data?.promotionCompetitions || []
     } catch (error) {
       console.error('鑾峰彇姣旇禌鏅嬬骇鍒楄〃澶辫触:', error)
       throw error
@@ -95,7 +95,7 @@
         size: params.size || 10
       }
       const result = await graphqlRequest(GET_COMPETITION_PARTICIPANTS, variables)
-      return result.data.competitionParticipants || []
+      return result?.data?.competitionParticipants || []
     } catch (error) {
       console.error('鑾峰彇姣旇禌鍙傝禌浜哄憳澶辫触:', error)
       throw error
@@ -106,8 +106,8 @@
   async getPromotableParticipants(currentStageId) {
     try {
       const variables = { currentStageId }
-      const data = await graphqlRequest(GET_PROMOTABLE_PARTICIPANTS, variables)
-      return data.promotableParticipants
+      const result = await graphqlRequest(GET_PROMOTABLE_PARTICIPANTS, variables)
+      return result?.data?.promotableParticipants
     } catch (error) {
       console.error('鑾峰彇鍙檵绾у弬璧涜�呭垪琛ㄥけ璐�:', error)
       throw error
@@ -124,8 +124,8 @@
           targetStageId
         }
       }
-      const data = await graphqlRequest(PROMOTE_PARTICIPANTS, variables)
-      return data.promoteParticipants
+      const result = await graphqlRequest(PROMOTE_PARTICIPANTS, variables)
+      return result?.data?.promoteParticipants
     } catch (error) {
       console.error('鎵ц鏅嬬骇鎿嶄綔澶辫触:', error)
       throw error
diff --git a/web/src/api/user.js b/web/src/api/user.js
new file mode 100644
index 0000000..e4332f4
--- /dev/null
+++ b/web/src/api/user.js
@@ -0,0 +1,92 @@
+import { graphqlRequest } from '@/config/api'
+
+// GraphQL 鏌ヨ璇彞
+
+// 鑾峰彇褰撳墠鐢ㄦ埛妗f
+const GET_USER_PROFILE = `
+  query GetUserProfile {
+    userProfile {
+      id
+      name
+      avatar
+      phone
+      email
+      roles
+      employee {
+        id
+        name
+        roleId
+      }
+      judge {
+        id
+        name
+        title
+        company
+        description
+      }
+      player {
+        id
+        name
+        phone
+        description
+      }
+    }
+  }
+`
+
+// 鑾峰彇褰撳墠璇勫淇℃伅
+const GET_CURRENT_JUDGE_INFO = `
+  query GetCurrentJudgeInfo {
+    currentJudgeInfo {
+      judgeId
+      judgeName
+      title
+      company
+    }
+  }
+`
+
+// 妫�鏌ヨ瘎濮旀槸鍚﹀湪鎸囧畾姣旇禌闃舵鐨勮瘎濮斿垪琛ㄤ腑
+const CHECK_JUDGE_IN_ACTIVITY = `
+  query CheckJudgeInActivity($stageId: ID!, $judgeId: ID!) {
+    isJudgeInActivity(stageId: $stageId, judgeId: $judgeId)
+  }
+`
+
+// API 鍑芥暟
+export const userApi = {
+  // 鑾峰彇褰撳墠鐢ㄦ埛妗f
+  async getUserProfile() {
+    try {
+      const result = await graphqlRequest(GET_USER_PROFILE)
+      return result?.data?.userProfile
+    } catch (error) {
+      console.error('鑾峰彇鐢ㄦ埛妗f澶辫触:', error)
+      throw new Error(error.message || '鑾峰彇鐢ㄦ埛妗f澶辫触')
+    }
+  },
+
+  // 鑾峰彇褰撳墠璇勫淇℃伅
+  async getCurrentJudgeInfo() {
+    try {
+      const result = await graphqlRequest(GET_CURRENT_JUDGE_INFO)
+      return result?.data?.currentJudgeInfo
+    } catch (error) {
+      console.error('鑾峰彇褰撳墠璇勫淇℃伅澶辫触:', error)
+      throw new Error(error.message || '鑾峰彇褰撳墠璇勫淇℃伅澶辫触')
+    }
+  },
+
+  // 妫�鏌ヨ瘎濮旀槸鍚﹀湪鎸囧畾姣旇禌闃舵鐨勮瘎濮斿垪琛ㄤ腑
+  async checkJudgeInActivity(stageId, judgeId) {
+    try {
+      const result = await graphqlRequest(CHECK_JUDGE_IN_ACTIVITY, { stageId, judgeId })
+      return result?.data?.isJudgeInActivity || false
+    } catch (error) {
+      console.error('妫�鏌ヨ瘎濮旀潈闄愬け璐�:', error)
+      throw new Error(error.message || '妫�鏌ヨ瘎濮旀潈闄愬け璐�')
+    }
+  }
+}
+
+export default userApi
\ No newline at end of file
diff --git a/web/src/layout/index.vue b/web/src/layout/index.vue
index 406c232..9fdfd8d 100644
--- a/web/src/layout/index.vue
+++ b/web/src/layout/index.vue
@@ -90,14 +90,11 @@
 import { computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { House, Calendar, User, Document, UserFilled, Files, TrendCharts, Picture, Location, Avatar, ArrowDown } from '@element-plus/icons-vue'
+import { clearAuth, getCurrentUserDisplayName } from '@/utils/auth'
 
 const router = useRouter()
 
-const userInfo = computed(() => ({
-  name: '绠$悊鍛�'
-}))
-
-const currentUserName = computed(() => userInfo.value.name || '鐢ㄦ埛')
+const currentUserName = computed(() => getCurrentUserDisplayName())
 
 const handleCommand = (command: string) => {
   switch (command) {
@@ -105,7 +102,9 @@
       router.push('/profile')
       break
     case 'logout':
-      localStorage.removeItem('token')
+      // 娓呴櫎鎵�鏈夎璇佹暟鎹�
+      clearAuth()
+      // 璺宠浆鍒扮櫥褰曢〉闈�
       router.push('/login')
       break
   }
diff --git a/web/src/views/next-list.vue b/web/src/views/next-list.vue
index 6d65381..addeb22 100644
--- a/web/src/views/next-list.vue
+++ b/web/src/views/next-list.vue
@@ -270,62 +270,19 @@
 const loadData = async () => {
   loading.value = true
   try {
-    // 灏濊瘯浣跨敤鐪熷疄API
     const data = await PromotionApi.getPromotionCompetitions({
       name: searchForm.name,
       page: pagination.page,
       size: pagination.size
     })
     
-    competitions.value = data
-    pagination.total = data.length
+    competitions.value = data || []
+    pagination.total = data ? data.length : 0
   } catch (error) {
-    console.warn('API璋冪敤澶辫触锛屼娇鐢ㄦā鎷熸暟鎹�:', error)
-    
-    // API澶辫触鏃朵娇鐢ㄦā鎷熸暟鎹�
-    const mockData = [
-      {
-        id: 1,
-        competitionName: '2027鍒涙柊鍒涗笟澶ц禌',
-        stageName: '绗竴闃舵',
-        maxParticipants: null,
-        currentCount: 15,
-        status: 1,
-        startTime: '2024-01-01T00:00:00',
-        endTime: '2024-03-31T23:59:59'
-      },
-      {
-        id: 2,
-        competitionName: '2027鍒涙柊鍒涗笟澶ц禌',
-        stageName: '绗簩闃舵',
-        maxParticipants: 50,
-        currentCount: 8,
-        status: 1,
-        startTime: '2024-04-01T00:00:00',
-        endTime: '2024-06-30T23:59:59'
-      },
-      {
-        id: 3,
-        competitionName: '2027鍒涙柊鍒涗笟澶ц禌',
-        stageName: '绗笁闃舵',
-        maxParticipants: 10,
-        currentCount: 0,
-        status: 0,
-        startTime: '2024-07-01T00:00:00',
-        endTime: '2024-08-31T23:59:59'
-      }
-    ]
-    
-    let filteredData = mockData
-    if (searchForm.name) {
-      filteredData = mockData.filter(item => 
-        item.competitionName.includes(searchForm.name) || 
-        item.stageName.includes(searchForm.name)
-      )
-    }
-    
-    competitions.value = filteredData
-    pagination.total = filteredData.length
+    console.error('鑾峰彇姣旇禌鏅嬬骇鍒楄〃澶辫触:', error)
+    ElMessage.error('鑾峰彇姣旇禌鏁版嵁澶辫触: ' + (error.message || '鏈煡閿欒'))
+    competitions.value = []
+    pagination.total = 0
   } finally {
     loading.value = false
   }
@@ -380,59 +337,37 @@
 const loadPromotableParticipants = async (currentStageId) => {
   participantsLoading.value = true
   try {
-    // 浣跨敤鏂扮殑API鑾峰彇鍙檵绾у弬璧涜��
     const data = await PromotionApi.getPromotableParticipants(currentStageId)
-    promotableData.value = data
-    originalParticipants.value = data.participants
-    participants.value = data.participants
-  } catch (error) {
-    console.warn('鑾峰彇鍙檵绾у弬璧涜�匒PI澶辫触锛屼娇鐢ㄦā鎷熸暟鎹�:', error)
-    // API澶辫触鏃朵娇鐢ㄦā鎷熸暟鎹�
-    const mockData = {
-      participants: [
-        {
-          id: 1,
-          playerId: 101,
-          playerName: 'UK2025',
-          projectName: '鏅鸿兘瀹跺眳绯荤粺',
-          phone: '13800138001',
-          averageScore: 85.5,
-          ratingCount: 3,
-          applyTime: '2024-01-15T10:30:00',
-          state: 1
-        },
-        {
-          id: 2,
-          playerId: 102,
-          playerName: '寮犱笁',
-          projectName: 'AI鍥惧儚璇嗗埆',
-          phone: '13800138002',
-          averageScore: 92.0,
-          ratingCount: 3,
-          applyTime: '2024-01-16T14:20:00',
-          state: 1
-        },
-        {
-          id: 3,
-          playerId: 103,
-          playerName: '鏉庡洓',
-          projectName: '鍖哄潡閾惧簲鐢�',
-          phone: '13800138003',
-          averageScore: 78.3,
-          ratingCount: 2,
-          applyTime: '2024-01-17T09:15:00',
-          state: 1
-        }
-      ],
-      selectableCount: 10,
-      totalCount: 15,
-      previousStageName: '绗竴闃舵',
-      currentStageName: '绗簩闃舵'
+    if (data) {
+      promotableData.value = data
+      originalParticipants.value = data.participants || []
+      participants.value = data.participants || []
+    } else {
+      // 濡傛灉娌℃湁鏁版嵁锛屽垵濮嬪寲涓虹┖
+      promotableData.value = {
+        participants: [],
+        selectableCount: 0,
+        totalCount: 0,
+        previousStageName: '',
+        currentStageName: ''
+      }
+      originalParticipants.value = []
+      participants.value = []
     }
+  } catch (error) {
+    console.error('鑾峰彇鍙檵绾у弬璧涜�呭け璐�:', error)
+    ElMessage.error('鑾峰彇鍙檵绾у弬璧涜�呮暟鎹け璐�: ' + (error.message || '鏈煡閿欒'))
     
-    promotableData.value = mockData
-    originalParticipants.value = mockData.participants
-    participants.value = mockData.participants
+    // 閿欒鏃跺垵濮嬪寲涓虹┖鏁版嵁
+    promotableData.value = {
+      participants: [],
+      selectableCount: 0,
+      totalCount: 0,
+      previousStageName: '',
+      currentStageName: ''
+    }
+    originalParticipants.value = []
+    participants.value = []
   } finally {
     participantsLoading.value = false
   }
@@ -442,43 +377,12 @@
 const loadParticipants = async (competitionId) => {
   participantsLoading.value = true
   try {
-    // 灏濊瘯浣跨敤鐪熷疄API鑾峰彇鍙傝禌浜哄憳
     const participantsData = await PromotionApi.getCompetitionParticipants(competitionId)
-    participants.value = participantsData
+    participants.value = participantsData || []
   } catch (error) {
-    console.warn('鑾峰彇鍙傝禌浜哄憳API澶辫触锛屼娇鐢ㄦā鎷熸暟鎹�:', error)
-    // API澶辫触鏃朵娇鐢ㄦā鎷熸暟鎹�
-    const mockParticipants = [
-      {
-        id: 1,
-        playerName: 'UK2025',
-        projectName: '鏅鸿兘瀹跺眳绯荤粺',
-        phone: '13800138001',
-        averageScore: 85.5,
-        ratingCount: 3,
-        applyTime: '2024-01-15T10:30:00'
-      },
-      {
-        id: 2,
-        playerName: '寮犱笁',
-        projectName: 'AI鍥惧儚璇嗗埆',
-        phone: '13800138002',
-        averageScore: 92.0,
-        ratingCount: 3,
-        applyTime: '2024-01-16T14:20:00'
-      },
-      {
-        id: 3,
-        playerName: '鏉庡洓',
-        projectName: '鍖哄潡閾惧簲鐢�',
-        phone: '13800138003',
-        averageScore: 78.3,
-        ratingCount: 2,
-        applyTime: '2024-01-17T09:15:00'
-      }
-    ]
-    
-    participants.value = mockParticipants
+    console.error('鑾峰彇鍙傝禌浜哄憳澶辫触:', error)
+    ElMessage.error('鑾峰彇鍙傝禌浜哄憳鏁版嵁澶辫触: ' + (error.message || '鏈煡閿欒'))
+    participants.value = []
   } finally {
     participantsLoading.value = false
   }
@@ -510,24 +414,26 @@
     promotionLoading.value = true
     
     try {
-      // 灏濊瘯浣跨敤鐪熷疄API鎵ц鏅嬬骇
+      // 鎻愬彇鍙傝禌鑰匢D
       const participantIds = selectedParticipants.value.map(p => p.id)
+      
       const result = await PromotionApi.promoteParticipants(
         selectedCompetition.value.id,
         participantIds,
         null // 鐩爣闃舵ID锛岃繖閲屽彲浠ユ牴鎹渶瑕佽缃�
       )
       
-      ElMessage.success(result.message || `鎴愬姛鏅嬬骇 ${result.promotedCount} 鍚嶄汉鍛榒)
+      if (result && result.success) {
+        ElMessage.success(result.message || `鎴愬姛鏅嬬骇 ${result.promotedCount} 鍚嶄汉鍛榒)
+        handlePromotionDialogClose()
+        loadData() // 閲嶆柊鍔犺浇鏁版嵁
+      } else {
+        ElMessage.error(result?.message || '鏅嬬骇鎿嶄綔澶辫触')
+      }
     } catch (error) {
-      console.warn('鏅嬬骇API澶辫触锛屼娇鐢ㄦā鎷熸搷浣�:', error)
-      // API澶辫触鏃舵ā鎷熸垚鍔�
-      await new Promise(resolve => setTimeout(resolve, 1000))
-      ElMessage.success(`鎴愬姛鏅嬬骇 ${selectedParticipants.value.length} 鍚嶄汉鍛榒)
+      console.error('鏅嬬骇鎿嶄綔澶辫触:', error)
+      ElMessage.error('鏅嬬骇鎿嶄綔澶辫触: ' + (error.message || '鏈煡閿欒'))
     }
-    
-    handlePromotionDialogClose()
-    loadData() // 閲嶆柊鍔犺浇鏁版嵁
   } catch {
     // 鐢ㄦ埛鍙栨秷
   } finally {
diff --git a/web/src/views/review-detail.vue b/web/src/views/review-detail.vue
index f59bbe1..2dcace8 100644
--- a/web/src/views/review-detail.vue
+++ b/web/src/views/review-detail.vue
@@ -118,6 +118,7 @@
                   :step="0.5"
                   size="small"
                   style="width: 100%; margin-top: 8px;"
+                  :disabled="!canModifyRating"
                 />
               </div>
 
@@ -128,17 +129,29 @@
                   v-model="ratingComment"
                   type="textarea"
                   :rows="4"
-                  placeholder="璇疯緭鍏ヨ瘎璇紙鍙�夛級"
+                  :placeholder="canModifyRating ? '璇疯緭鍏ヨ瘎璇紙鍙�夛級' : '璇勮锛堝彧璇伙級'"
                   maxlength="500"
                   show-word-limit
+                  :disabled="!canModifyRating"
                 />
               </div>
 
               <!-- 鎻愪氦鎸夐挳 -->
-              <div class="submit-section">
+              <div class="submit-section" v-if="canModifyRating">
                 <el-button type="primary" @click="handleSubmitRating" :loading="submitting" style="width: 100%;">
                   鎻愪氦璇勫垎
                 </el-button>
+              </div>
+              
+              <!-- Employee鐢ㄦ埛鎻愮ず -->
+              <div class="readonly-notice" v-if="isEmployee && !canModifyRating">
+                <el-alert
+                  title="鍙妯″紡"
+                  description="鎮ㄤ互鍛樺伐韬唤鏌ョ湅姝よ瘎瀹¤鎯咃紝鍙兘鏌ョ湅涓嶈兘淇敼璇勫垎"
+                  type="info"
+                  :closable="false"
+                  show-icon
+                />
               </div>
             </div>
             <div v-else class="no-template">
@@ -170,7 +183,9 @@
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Document, UserFilled } from '@element-plus/icons-vue'
-import { getProjectDetail, getRatingStats, submitRating } from '@/api/projectReview'
+import { getProjectDetail, getRatingStats, submitRating, getCurrentJudgeRating } from '@/api/projectReview'
+import { userApi } from '@/api/user'
+import { getUserInfo } from '@/utils/auth'
 
 const route = useRoute()
 const router = useRouter()
@@ -185,8 +200,114 @@
 const previewVisible = ref(false)
 const previewUrl = ref('')
 
+// 鏉冮檺楠岃瘉鐩稿叧
+const currentJudge = ref(null)
+const hasJudgePermission = ref(false)
+const isJudgeInActivity = ref(false)
+const permissionChecked = ref(false)
+const existingRating = ref(null)
+const isEmployee = ref(false)
+const canModifyRating = ref(false)
+
 // 璁$畻灞炴��
 const projectId = computed(() => route.params.id)
+const stageId = computed(() => route.query.stageId)
+
+// 鏉冮檺楠岃瘉鏂规硶
+const checkPermissions = async () => {
+  try {
+    // 鑾峰彇褰撳墠鐢ㄦ埛淇℃伅
+    const userInfo = getUserInfo()
+    
+    if (!userInfo) {
+      ElMessage.error('鐢ㄦ埛淇℃伅鑾峰彇澶辫触锛岃閲嶆柊鐧诲綍')
+      router.push('/project-review')
+      return false
+    }
+    
+    // 妫�鏌ユ槸鍚︽湁employee韬唤
+    if (userInfo.employee) {
+      isEmployee.value = true
+      canModifyRating.value = false // employee鍙兘鏌ョ湅锛屼笉鑳戒慨鏀�
+      permissionChecked.value = true
+      ElMessage.info('鎮ㄤ互鍛樺伐韬唤鏌ョ湅璇勫璇︽儏锛屽彧鑳芥煡鐪嬩笉鑳戒慨鏀硅瘎鍒�')
+      return true
+    }
+    
+    // 濡傛灉娌℃湁employee韬唤锛屾鏌udge韬唤鍜屾潈闄�
+    const judgeInfo = await userApi.getCurrentJudgeInfo()
+    
+    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
+    ElMessage.error('鏉冮檺楠岃瘉澶辫触锛岃閲嶆柊鐧诲綍')
+    router.push('/project-review')
+    return false
+  }
+}
+
+// 鍔犺浇褰撳墠璇勫宸叉湁鐨勮瘎瀹℃暟鎹�
+const loadExistingRating = async () => {
+  // employee鐢ㄦ埛涓嶉渶瑕佸姞杞借瘎濮旂殑璇勫垎鏁版嵁
+  if (isEmployee.value || !hasJudgePermission.value) return
+  
+  try {
+    const rating = await getCurrentJudgeRating(parseInt(projectId.value))
+    if (rating) {
+      existingRating.value = rating
+      
+      // 濡傛灉鏈夊凡鏈夎瘎鍒嗭紝濉厖鍒拌〃鍗曚腑
+      if (rating.items && rating.items.length > 0) {
+        ratingItems.value = rating.items.map(item => ({
+          id: item.ratingItemId,
+          name: item.ratingItemName,
+          score: item.score,
+          maxScore: item.maxScore || 100
+        }))
+      }
+      
+      // 濉厖璇勮
+      if (rating.remark) {
+        ratingComment.value = rating.remark
+      }
+      
+      ElMessage.success('宸插姞杞芥偍涔嬪墠鐨勮瘎鍒嗘暟鎹紝鍙互缁х画缂栬緫')
+    }
+  } catch (error) {
+    console.error('鍔犺浇宸叉湁璇勫垎澶辫触:', error)
+    // 涓嶆樉绀洪敊璇秷鎭紝鍥犱负鍙兘鏄涓�娆¤瘎鍒�
+  }
+}
 
 // 鍔犺浇椤圭洰璇︽儏
 const loadProjectDetail = async () => {
@@ -202,6 +323,14 @@
         score: 0
       }))
     }
+    
+    // 椤圭洰璇︽儏鍔犺浇瀹屾垚鍚庯紝杩涜鏉冮檺楠岃瘉
+    const hasPermission = await checkPermissions()
+    if (hasPermission) {
+      // 鏉冮檺楠岃瘉閫氳繃鍚庯紝鍔犺浇宸叉湁璇勫垎鏁版嵁
+      await loadExistingRating()
+    }
+    
   } catch (error) {
     ElMessage.error('鍔犺浇椤圭洰璇︽儏澶辫触')
     console.error(error)
@@ -222,6 +351,18 @@
 
 // 鎻愪氦璇勫垎
 const handleSubmitRating = async () => {
+  // 鏉冮檺妫�鏌ワ細employee鐢ㄦ埛涓嶈兘鎻愪氦璇勫垎
+  if (!canModifyRating.value) {
+    ElMessage.error('鎮ㄦ病鏈夋潈闄愭彁浜よ瘎鍒�')
+    return
+  }
+  
+  // 楠岃瘉stageId
+  if (!stageId.value) {
+    ElMessage.error('缂哄皯姣旇禌闃舵淇℃伅锛岃閲嶆柊杩涘叆椤甸潰')
+    return
+  }
+  
   // 楠岃瘉璇勫垎
   const hasEmptyScore = ratingItems.value.some(item => item.score === 0 || item.score === null)
   if (hasEmptyScore) {
@@ -239,7 +380,8 @@
     submitting.value = true
     
     const ratingData = {
-      activityPlayerId: projectId.value,
+      activityPlayerId: parseInt(projectId.value),
+      stageId: parseInt(stageId.value),
       ratings: ratingItems.value.map(item => ({
         itemId: item.id,
         score: item.score
diff --git a/web/src/views/review-list.vue b/web/src/views/review-list.vue
index 1df7c38..c63b301 100644
--- a/web/src/views/review-list.vue
+++ b/web/src/views/review-list.vue
@@ -60,18 +60,24 @@
       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">
@@ -119,6 +125,107 @@
         @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>
 
@@ -127,8 +234,10 @@
 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()
 
@@ -139,6 +248,16 @@
 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)
@@ -251,8 +370,147 @@
 }
 
 // 鏌ョ湅璇︽儏
-const viewDetails = (projectId) => {
-  router.push(`/project-review/${projectId}/detail`)
+const viewDetails = async (projectId) => {
+  // 浼犻�抯tageId鍙傛暟锛宻electedActivity.value灏辨槸褰撳墠閫変腑鐨剆tageId
+  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) {
+      // 濡傛灉鐢ㄦ埛鏈塭mployee韬唤锛岀洿鎺ュ厑璁告煡鐪嬶紙涓嶉渶瑕佹潈闄愭鏌ワ級
+      console.log('鐢ㄦ埛鍏锋湁鍛樺伐韬唤锛屽厑璁告煡鐪嬫墍鏈夎瘎鍒嗚褰�')
+      router.push(`/project-review/${projectId}/detail?stageId=${stageId}`)
+      return
+    }
+
+    // 濡傛灉鐢ㄦ埛鍙湁judge韬唤锛堟病鏈塭mployee韬唤锛夛紝闇�瑕佹鏌ヨ瘎濮旀潈闄�
+    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)
+    
+    // 浣跨敤姝g‘鐨勫瓧娈靛悕
+    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('鍔犺浇璇勫垎鏄庣粏锛宎ctivityPlayerId:', 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 = []
 }
 
 // 鏍煎紡鍖栨棩鏈�
@@ -350,6 +608,84 @@
   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;

--
Gitblit v1.8.0