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