From 93eb6b470773bc49ea6e1a9d4cbd914eb95d525b Mon Sep 17 00:00:00 2001 From: lrj <owen.stl@gmail.com> Date: 星期二, 30 九月 2025 17:38:04 +0800 Subject: [PATCH] feat: 完善比赛晋级功能并清理测试文件 --- web/src/views/project-review/detail.vue | 530 ++ backend/src/main/java/com/rongyichuang/media/service/MediaV2Service.java | 58 web/src/api/player.js | 121 backend/src/main/java/com/rongyichuang/activity/entity/Activity.java | 17 backend/src/main/java/com/rongyichuang/media/dto/MediaSaveInput.java | 20 backend/src/test/java/com/rongyichuang/CreateJudgeForUser2Test.java | 107 backend/src/main/java/com/rongyichuang/common/enums/MediaTargetType.java | 5 web/src/components/SubmissionFiles.vue | 130 backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java | 12 backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantResponse.java | 113 web/src/api/graphql.ts | 2 backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantsResponse.java | 71 web/src/views/project-review/index.vue | 317 + backend/src/main/resources/graphql/rating.graphqls | 5 web/src/views/test/graphql-test.vue | 192 wx/pages/registration/registration.wxss | 6 backend/src/test/java/com/rongyichuang/CheckUserPermissionTest.java | 97 web/src/api/region.js | 255 web/src/router/index.ts | 44 web/src/api/activityPlayer.js | 192 backend/src/main/java/com/rongyichuang/player/dto/response/SubmissionMediaResponse.java | 4 backend/src/main/resources/graphql/activity.graphqls | 2 backend/src/test/java/com/rongyichuang/CheckMediaRecordsTest.java | 121 web/src/api/judge.ts | 28 backend/src/main/java/com/rongyichuang/message/entity/Message.java | 154 web/src/layout/index.vue | 4 web/src/views/player/detail.vue | 774 ++++ web/src/api/activity.js | 103 backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java | 86 backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerDetailResponse.java | 12 backend/src/main/java/com/rongyichuang/common/api/DataCleanupController.java | 40 backend/src/test/java/com/rongyichuang/DatabaseSchemaTest.java | 16 backend/src/main/resources/graphql/player.graphqls | 100 web/src/views/player/index.vue | 115 backend/src/main/java/com/rongyichuang/activity/dto/ActivityResponse.java | 10 wx/lib/cosUtil-example.md | 2 wx/lib/cosUtil.js | 94 web/src/api/rating.js | 227 腾讯云COS文档预览方案调研.md | 107 web/src/api/media.js | 74 backend/src/main/java/com/rongyichuang/activity/entity/ActivityStatus.java | 17 backend/src/test/java/com/rongyichuang/AssignActivityPermissionTest.java | 96 backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java | 134 backend/src/main/java/com/rongyichuang/player/dto/PromotionInput.java | 47 backend/src/main/java/com/rongyichuang/player/dto/response/JudgeRatingStatusResponse.java | 44 backend/src/main/java/com/rongyichuang/player/service/PromotionService.java | 328 + web/src/views/ActivityForm.vue | 248 web/src/views/region/index.vue | 1 db.sql | 210 web/src/views/review/detail.vue | 566 ++ backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java | 15 backend/src/main/java/com/rongyichuang/message/repository/MessageRepository.java | 53 backend/src/main/java/com/rongyichuang/player/dto/CompetitionParticipantResponse.java | 108 backend/src/test/java/com/rongyichuang/SimpleUserCheckTest.java | 79 backend/src/test/java/com/rongyichuang/CheckJudgeTableTest.java | 50 web/src/config/api.ts | 69 backend/src/main/java/com/rongyichuang/activity/dto/ActivityInput.java | 9 clear_tables.sql | 42 web/src/api/carousel.js | 28 backend/src/main/java/com/rongyichuang/player/dto/response/PlayerInfoResponse.java | 24 web/src/utils/appConfig.js | 2 backend/src/main/java/com/rongyichuang/player/entity/ActivityPlayer.java | 5 backend/src/main/java/com/rongyichuang/player/repository/ActivityPlayerRepository.java | 21 backend/src/main/java/com/rongyichuang/rating/dto/response/RatingSchemeResponse.java | 32 wx/pages/registration/registration.js | 230 + backend/src/test/java/com/rongyichuang/CheckActivityJudgeTableTest.java | 50 backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java | 112 web/src/api/config.js | 52 backend/src/main/java/com/rongyichuang/rating/service/RatingSchemeService.java | 2 backend/db.sql | 2 backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java | 26 web/src/views/competition-promotion/index.vue | 727 +++ backend/src/main/java/com/rongyichuang/media/api/MediaV2GraphqlApi.java | 25 web/src/utils/graphql.ts | 2 web/src/api/dashboard.js | 20 web/src/views/review/index.vue | 290 + backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerApplicationResponse.java | 4 web/src/api/projectReview.js | 279 + web/src/constants/mediaTargetType.ts | 4 backend/src/main/java/com/rongyichuang/player/dto/PromotionResult.java | 53 backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java | 185 backend/src/main/java/com/rongyichuang/config/SecurityConfig.java | 20 backend/src/main/java/com/rongyichuang/rating/entity/RatingItem.java | 8 backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java | 257 + backend/src/main/java/com/rongyichuang/activity/entity/ActivityPlayerRating.java | 19 t_media.sql | 57 backend/src/main/java/com/rongyichuang/player/dto/PromotionCompetitionResponse.java | 123 backend/src/main/java/com/rongyichuang/message/entity/MessageType.java | 49 backend/src/main/resources/graphql/media.graphqls | 8 web/src/components/PlayerInfoCard.vue | 40 web/src/views/ActivityDetail.vue | 48 页面bug.png | 0 backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java | 199 web/src/api/promotion.js | 136 web/src/api/role.ts | 156 wx/pages/registration/registration.wxml | 1 backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java.backup | 1589 ++++++++ /dev/null | 86 web/src/api/employee.ts | 98 web/src/api/judge.js | 2 backend/src/main/java/com/rongyichuang/activity/dto/ActivityStageInput.java | 9 101 files changed, 10,280 insertions(+), 1,283 deletions(-) diff --git a/backend/db.sql b/backend/db.sql index 63ae387..e5f882b 100644 --- a/backend/db.sql +++ b/backend/db.sql @@ -13,6 +13,7 @@ `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, `rating_scheme_id` bigint NOT NULL, `player_max` int DEFAULT NULL COMMENT '浜烘暟涓婇檺', + `sort_order` int DEFAULT NULL COMMENT '闃舵鎺掑簭锛屼粠1寮�濮嬭繛缁�', `state` int NOT NULL DEFAULT '1' COMMENT '0:鏈彂甯冿紝 1锛氬彂甯冿細2锛氬叧闂�', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_user_id` bigint DEFAULT NULL, @@ -22,6 +23,7 @@ PRIMARY KEY (`id`) USING BTREE, KEY `fk_t_activity_rating_scheme` (`rating_scheme_id`) USING BTREE, KEY `idx_t_activity_deadline` (`signup_deadline`) USING BTREE, + KEY `idx_t_activity_sort` (`pid`, `sort_order`) USING BTREE, CONSTRAINT `fk_t_activity_rating_scheme` FOREIGN KEY (`rating_scheme_id`) REFERENCES `t_rating_scheme` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE=InnoDB AUTO_INCREMENT=62 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/backend/src/main/java/com/rongyichuang/activity/dto/ActivityInput.java b/backend/src/main/java/com/rongyichuang/activity/dto/ActivityInput.java index 3b2317f..41eb4e5 100644 --- a/backend/src/main/java/com/rongyichuang/activity/dto/ActivityInput.java +++ b/backend/src/main/java/com/rongyichuang/activity/dto/ActivityInput.java @@ -14,6 +14,7 @@ private String address; private Long ratingSchemeId; private Integer playerMax; + private Integer sortOrder; private Integer state = 1; // 姣旇禌闃舵鍒楄〃锛堜粎鐢ㄤ簬姣旇禌鍒涘缓/缂栬緫锛� @@ -98,6 +99,14 @@ this.playerMax = playerMax; } + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + public Integer getState() { return state; } diff --git a/backend/src/main/java/com/rongyichuang/activity/dto/ActivityResponse.java b/backend/src/main/java/com/rongyichuang/activity/dto/ActivityResponse.java index c8cd3e2..7673ca3 100644 --- a/backend/src/main/java/com/rongyichuang/activity/dto/ActivityResponse.java +++ b/backend/src/main/java/com/rongyichuang/activity/dto/ActivityResponse.java @@ -19,6 +19,7 @@ private String address; private Long ratingSchemeId; private Integer playerMax; + private Integer sortOrder; private Integer state; private LocalDateTime createTime; private LocalDateTime updateTime; @@ -52,6 +53,7 @@ this.address = activity.getAddress(); this.ratingSchemeId = activity.getRatingSchemeId(); this.playerMax = activity.getPlayerMax(); + this.sortOrder = activity.getSortOrder(); this.state = activity.getState(); this.createTime = activity.getCreateTime(); this.updateTime = activity.getUpdateTime(); @@ -158,6 +160,14 @@ this.playerMax = playerMax; } + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + public Integer getState() { return state; } diff --git a/backend/src/main/java/com/rongyichuang/activity/dto/ActivityStageInput.java b/backend/src/main/java/com/rongyichuang/activity/dto/ActivityStageInput.java index 2b66b52..8445cd0 100644 --- a/backend/src/main/java/com/rongyichuang/activity/dto/ActivityStageInput.java +++ b/backend/src/main/java/com/rongyichuang/activity/dto/ActivityStageInput.java @@ -11,6 +11,7 @@ private String address; private Long ratingSchemeId; private Integer playerMax; + private Integer sortOrder; private Integer state = 1; // 鏋勯�犲嚱鏁� @@ -73,6 +74,14 @@ this.playerMax = playerMax; } + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + public Integer getState() { return state; } diff --git a/backend/src/main/java/com/rongyichuang/activity/entity/Activity.java b/backend/src/main/java/com/rongyichuang/activity/entity/Activity.java index 8a77723..41ac1e7 100644 --- a/backend/src/main/java/com/rongyichuang/activity/entity/Activity.java +++ b/backend/src/main/java/com/rongyichuang/activity/entity/Activity.java @@ -11,7 +11,6 @@ @Entity @Table(name = "t_activity") -@Where(clause = "state = 1") public class Activity extends BaseEntity { @Column(name = "pid", nullable = false) @@ -42,7 +41,13 @@ private Integer playerMax; /** - * 鐘舵�侊細1-姝e父锛�0-鍒犻櫎 + * 闃舵鎺掑簭锛屼粠1寮�濮嬭繛缁� + */ + @Column(name = "sort_order") + private Integer sortOrder; + + /** + * 鐘舵�侊細0-鏈彂甯冿紝1-鍙戝竷锛�2-鍏抽棴 */ @Column(name = "state", nullable = false) private Integer state = 1; @@ -151,6 +156,14 @@ this.playerMax = playerMax; } + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + public RatingScheme getRatingScheme() { return ratingScheme; } diff --git a/backend/src/main/java/com/rongyichuang/activity/entity/ActivityPlayerRating.java b/backend/src/main/java/com/rongyichuang/activity/entity/ActivityPlayerRating.java index 44a9c24..7977978 100644 --- a/backend/src/main/java/com/rongyichuang/activity/entity/ActivityPlayerRating.java +++ b/backend/src/main/java/com/rongyichuang/activity/entity/ActivityPlayerRating.java @@ -63,11 +63,7 @@ @Column(name = "feedback", columnDefinition = "TEXT") private String feedback; - /** - * 璇勫垎鐘舵�侊細0-鏈瘎鍒嗭紝1-宸茶瘎鍒� - */ - @Column(name = "rating_state", nullable = false) - private Integer ratingState = 0; + /** * 鐘舵�侊細1-姝e父锛�0-鍒犻櫎 @@ -154,6 +150,8 @@ this.feedback = feedback; } + + public Integer getState() { return state; } @@ -161,17 +159,6 @@ public void setState(Integer state) { this.state = state; } - - // 涓轰簡鍏煎鎬э紝淇濈暀status鐩稿叧鏂规硶 - public Integer getStatus() { - return state; - } - - public void setStatus(Integer status) { - this.state = status; - } - - @Override public String toString() { diff --git a/backend/src/main/java/com/rongyichuang/activity/entity/ActivityStatus.java b/backend/src/main/java/com/rongyichuang/activity/entity/ActivityStatus.java new file mode 100644 index 0000000..8155682 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/activity/entity/ActivityStatus.java @@ -0,0 +1,17 @@ +package com.rongyichuang.activity.entity; + +public enum ActivityStatus { + UNPUBLISHED(0), + PUBLISHED(1), + CLOSED(2); + + private final int value; + + ActivityStatus(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java b/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java index 787a541..c2d9d43 100644 --- a/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java +++ b/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java @@ -6,36 +6,94 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.List; -@Repository public interface ActivityRepository extends JpaRepository<Activity, Long> { - - // 鏌ヨ姣旇禌鍒楄〃锛坧id=0琛ㄧず姣旇禌锛岄潪0琛ㄧず闃舵锛� - Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, Integer state, Pageable pageable); - - // 鎸夊悕绉版ā绯婃煡璇㈡瘮璧涘垪琛� - Page<Activity> findByPidAndStateAndNameContainingOrderByCreateTimeDesc(Long pid, Integer state, String name, Pageable pageable); - - // 鏌ヨ姣旇禌鐨勬墍鏈夐樁娈� - List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, Integer state); - - // 鏌ヨ鎵�鏈夋湁鏁堟瘮璧涳紙鐢ㄤ簬涓嬫媺閫夋嫨锛� - List<Activity> findByPidAndStateOrderByNameAsc(Long pid, Integer state); - - // 涓存椂娴嬭瘯锛氭煡璇㈡墍鏈夋瘮璧涳紙涓嶄娇鐢╯tate瀛楁锛� - List<Activity> findByPidOrderByNameAsc(Long pid); - - // 鏌ヨ鎵�鏈夋湁鏁堟椿鍔紙鍖呮嫭姣旇禌鍜岄樁娈碉級锛屾寜pid鍜屽悕绉版帓搴� - List<Activity> findByStateOrderByPidAscNameAsc(Integer state); - - // 缁熻姣旇禌鏁伴噺 - @Query("SELECT COUNT(a) FROM Activity a WHERE a.pid = 0 AND a.state = 1") - long countActiveActivities(); - - // 鏌ヨ杩涜涓殑姣旇禌 - @Query("SELECT a FROM Activity a WHERE a.pid = 0 AND a.state = 1 AND a.matchTime <= CURRENT_TIMESTAMP AND a.signupDeadline >= CURRENT_TIMESTAMP") + + Page<Activity> findByPidAndStateAndNameContainingOrderByCreateTimeDesc(Long pid, int state, String name, Pageable pageable); + + Page<Activity> findByPidAndNameContainingOrderByCreateTimeDesc(Long pid, String name, Pageable pageable); + + Page<Activity> findByPidOrderByCreateTimeDesc(Long pid, Pageable pageable); + + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁娲诲姩ID鏌ヨ娲诲姩 + */ + @Query("SELECT a FROM Activity a WHERE a.id = :id") + Activity findActivityById(@Param("id") Long id); + + /** + * 鏍规嵁鐘舵�佹煡璇㈡椿鍔ㄦ暟閲� + */ + @Query("SELECT COUNT(a) FROM Activity a WHERE a.state = :state") + Long countByState(@Param("state") int state); + + /** + * 缁熻褰撳墠杩涜涓殑娲诲姩鏁伴噺锛堢姸鎬佷负1涓攑id=0鐨勪富娲诲姩锛� + */ + @Query("SELECT COUNT(a) FROM Activity a WHERE a.state = 1 AND a.pid = 0") + Long countActiveActivities(); + + /** + * 鏌ヨ鎵�鏈夌埗绾ф椿鍔紙pid = 0锛� + */ + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + List<Activity> findAllParentActivities(); + + /** + * 鏍规嵁鐖剁骇ID鏌ヨ瀛愭椿鍔� + */ + @Query("SELECT a FROM Activity a WHERE a.pid = :pid ORDER BY a.createTime DESC") + List<Activity> findChildActivitiesByPid(@Param("pid") Long pid); + + /** + * 鏍规嵁鍚嶇О妯$硦鏌ヨ娲诲姩 + */ + @Query("SELECT a FROM Activity a WHERE a.name LIKE %:name% ORDER BY a.createTime DESC") + List<Activity> findByNameContaining(@Param("name") String name); + + /** + * 鏌ヨ鎸囧畾鐘舵�佺殑娲诲姩锛屾寜鍒涘缓鏃堕棿鎺掑簭 + */ + @Query("SELECT a FROM Activity a WHERE a.state = :state ORDER BY a.createTime DESC") + List<Activity> findByStateOrderByCreateTimeDesc(@Param("state") int state); + + /** + * 鍒嗛〉鏌ヨ鎸囧畾鐘舵�佺殑娲诲姩 + */ + @Query("SELECT a FROM Activity a WHERE a.state = :state ORDER BY a.createTime DESC") + Page<Activity> findByStateOrderByCreateTimeDesc(@Param("state") int state, Pageable pageable); + + /** + * 鏌ヨ娲诲姩鍙婂叾璇勫垎鏂规 + */ + @Query("SELECT a FROM Activity a LEFT JOIN FETCH a.ratingScheme WHERE a.id = :id") + Activity findActivityWithRatingScheme(@Param("id") Long id); + + /** + * 鏌ヨ杩涜涓殑娲诲姩锛堢姸鎬佷负1鐨勬椿鍔級 + */ + @Query("SELECT a FROM Activity a WHERE a.state = 1 ORDER BY a.createTime DESC") List<Activity> findOngoingActivities(); + + /** + * 鏌ユ壘鎸囧畾娲诲姩鐨勭涓�闃舵锛坰ortOrder=1锛� + */ + @Query("SELECT a FROM Activity a WHERE a.pid = :activityId AND a.sortOrder = 1 AND a.state = 1") + Activity findFirstStageByActivityId(@Param("activityId") Long activityId); + + /** + * 鏍规嵁鐖剁骇ID鍜岀姸鎬佹煡璇㈡椿鍔紝鎸塻ortOrder鎺掑簭 + */ + List<Activity> findByPidAndStateOrderBySortOrderAsc(Long pid, Integer state); } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java.backup b/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java.backup new file mode 100644 index 0000000..d67f2b3 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/activity/repository/ActivityRepository.java.backup @@ -0,0 +1,1589 @@ +package com.rongyichuang.activity.repository; + +import com.rongyichuang.activity.entity.Activity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ActivityRepository extends JpaRepository<Activity, Long> { + + Page<Activity> findByPidAndStateAndNameContainingOrderByCreateTimeDesc(Long pid, int state, String name, Pageable pageable); + + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + */ + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List<Activity> findByPidAndStateOrderByCreateTimeAsc(Long pid, int state); + + List<Activity> findByStateOrderByPidAscNameAsc(int state); + + @Query("SELECT a FROM Activity a WHERE a.pid = 0 ORDER BY a.createTime DESC") + Page<Activity> findRecentActivities(Pageable pageable); + + /** + * 鏍规嵁ID鍒楄〃鏌ヨ姣旇禌 + * + Page<Activity> findByPidAndStateOrderByCreateTimeDesc(Long pid, int state, Pageable pageable); + + List \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java b/backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java index 508e233..a0ad78b 100644 --- a/backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java +++ b/backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java @@ -10,12 +10,15 @@ import com.rongyichuang.activity.repository.ActivityJudgeRepository; import com.rongyichuang.activity.repository.ActivityRepository; import com.rongyichuang.judge.entity.Judge; +import com.rongyichuang.player.repository.ActivityPlayerRepository; import com.rongyichuang.judge.repository.JudgeRepository; import com.rongyichuang.common.dto.PageRequest; import com.rongyichuang.common.dto.PageResponse; import com.rongyichuang.rating.entity.RatingScheme; import com.rongyichuang.rating.repository.RatingSchemeRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -23,9 +26,12 @@ import org.springframework.util.StringUtils; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; @Service @@ -44,21 +50,32 @@ @Autowired private RatingSchemeRepository ratingSchemeRepository; + @Autowired + private ActivityPlayerRepository activityPlayerRepository; + /** * 鍒嗛〉鏌ヨ姣旇禌鍒楄〃 */ public PageResponse<ActivityResponse> findActivities(PageRequest pageRequest, String name) { Pageable pageable = pageRequest.toPageable(); Page<Activity> page; - + if (StringUtils.hasText(name)) { - page = activityRepository.findByPidAndStateAndNameContainingOrderByCreateTimeDesc(0L, 1, name, pageable); + page = activityRepository.findByPidAndNameContainingOrderByCreateTimeDesc(0L, name, pageable); } else { - page = activityRepository.findByPidAndStateOrderByCreateTimeDesc(0L, 1, pageable); + // 鏌ヨ鎵�鏈変富娲诲姩锛坧id = 0锛� + page = activityRepository.findByPidOrderByCreateTimeDesc(0L, pageable); } - + List<ActivityResponse> content = page.getContent().stream() - .map(ActivityResponse::new) + .map(activity -> { + ActivityResponse response = new ActivityResponse(activity); + // 璁剧疆鍙傝禌浜烘暟锛堝鏍搁�氳繃鐨勬姤鍚嶆暟閲忥級 + Long playerCountLong = activityPlayerRepository.countByActivityId(activity.getId()); + int playerCount = playerCountLong != null ? playerCountLong.intValue() : 0; + response.setPlayerCount(playerCount); + return response; + }) .collect(Collectors.toList()); return new PageResponse<>(content, page.getTotalElements(), page.getNumber(), page.getSize()); @@ -73,11 +90,23 @@ Activity activity = activityOpt.get(); ActivityResponse response = new ActivityResponse(activity); + // 璁剧疆鍙傝禌浜烘暟锛堝鏍搁�氳繃鐨勬姤鍚嶆暟閲忥級 + Long playerCountLong = activityPlayerRepository.countByActivityId(activity.getId()); + int playerCount = playerCountLong != null ? playerCountLong.intValue() : 0; + response.setPlayerCount(playerCount); + // 濡傛灉鏄瘮璧涳紝鍔犺浇鍏堕樁娈� if (activity.isMainActivity()) { List<Activity> stages = activityRepository.findByPidAndStateOrderByCreateTimeAsc(id, 1); List<ActivityResponse> stageResponses = stages.stream() - .map(ActivityResponse::new) + .map(stage -> { + ActivityResponse stageResponse = new ActivityResponse(stage); + // 璁剧疆闃舵鐨勫弬璧涗汉鏁� + Long stagePlayerCountLong = activityPlayerRepository.countByActivityId(stage.getId()); + int stagePlayerCount = stagePlayerCountLong != null ? stagePlayerCountLong.intValue() : 0; + stageResponse.setPlayerCount(stagePlayerCount); + return stageResponse; + }) .collect(Collectors.toList()); response.setStages(stageResponses); } @@ -127,6 +156,21 @@ if (!schemeOpt.isPresent()) { throw new RuntimeException("璇勫垎妯℃澘涓嶅瓨鍦�"); } + } + + // 濡傛灉鏄瘮璧涳紝楠岃瘉蹇呴』鑷冲皯鏈変竴涓樁娈� + if (input.isMainActivity()) { + if (input.getStages() == null || input.getStages().isEmpty()) { + throw new RuntimeException("姣旇禌蹇呴』鑷冲皯鏈変竴涓樁娈�"); + } + + // 楠岃瘉闃舵鏁伴噺涓嶈秴杩�5涓� + if (input.getStages().size() > 5) { + throw new RuntimeException("姣旇禌闃舵鏁伴噺涓嶈兘瓒呰繃5涓�"); + } + + // 楠岃瘉sort_order杩炵画鎬� + validateSortOrderContinuity(input.getStages()); } // 淇濆瓨姣旇禌 @@ -198,6 +242,7 @@ stage.setMatchTime(stageInput.getMatchTime()); stage.setAddress(stageInput.getAddress()); stage.setPlayerMax(stageInput.getPlayerMax()); + stage.setSortOrder(stageInput.getSortOrder()); stage.setState(stageInput.getState()); // 闃舵缁ф壙姣旇禌鐨勬姤鍚嶆埅姝㈡椂闂� @@ -246,7 +291,9 @@ } else { // 涓烘瘡涓寚瀹氱殑闃舵鍒涘缓鍏宠仈 for (Long stageId : judgeInput.getStageIds()) { - ActivityJudge activityJudge = new ActivityJudge(activityId, judgeInput.getJudgeId(), stageId); + // 濡傛灉stageId绛変簬褰撳墠姣旇禌ID锛岃〃绀鸿瘎濮旇礋璐f暣涓瘮璧涳紝stage_id璁句负null + Long actualStageId = stageId.equals(activityId) ? null : stageId; + ActivityJudge activityJudge = new ActivityJudge(activityId, judgeInput.getJudgeId(), actualStageId); activityJudgeRepository.save(activityJudge); } } @@ -279,61 +326,46 @@ } /** - * 鑾峰彇鎵�鏈夋湁鏁堟瘮璧涘拰闃舵锛堢敤浜庝笅鎷夐�夋嫨锛� + * 鑾峰彇鎵�鏈夋湁鏁堜富姣旇禌锛堢敤浜庝笅鎷夐�夋嫨锛� */ public List<ActivityResponse> findAllActivitiesForSelection() { - // 鑾峰彇鎵�鏈夋椿鍔紙鍖呮嫭姣旇禌鍜岄樁娈碉級 - List<Activity> activities = activityRepository.findByStateOrderByPidAscNameAsc(1); + // 鑾峰彇鎵�鏈夌姸鎬佷负1鐨勬椿鍔� + List<Activity> allActivities = activityRepository.findByStateOrderByPidAscNameAsc(1); - // 鍒涘缓姣旇禌ID鍒版瘮璧涘璞$殑鏄犲皠锛岀敤浜庡揩閫熸煡鎵剧埗姣旇禌 - Map<Long, Activity> mainActivityMap = activities.stream() - .filter(activity -> activity.getPid() == 0) - .collect(Collectors.toMap(Activity::getId, activity -> activity)); - - // 杞崲涓篈ctivityResponse骞跺~鍏卲arent淇℃伅 - List<ActivityResponse> result = activities.stream() - .map(activity -> { - ActivityResponse response = new ActivityResponse(activity); - // 濡傛灉鏄樁娈碉紙pid > 0锛夛紝濉厖parent淇℃伅 - if (activity.getPid() > 0) { - Activity parentActivity = mainActivityMap.get(activity.getPid()); - if (parentActivity != null) { - response.setParent(new ActivityResponse(parentActivity)); - } - } - return response; - }) + // 杩囨护锛氬彧淇濈暀姣旇禌闃舵锛坧id>0锛� + List<Activity> filteredActivities = allActivities.stream() + .filter(activity -> activity.getPid() > 0) .collect(Collectors.toList()); - // 鑷畾涔夋帓搴忥細姣旇禌鍜屽叾闃舵鏀惧湪涓�璧� - result.sort((a, b) -> { - // 濡傛灉閮芥槸姣旇禌锛坧id=0锛夛紝鎸夊悕绉版帓搴� - if (a.getPid() == 0 && b.getPid() == 0) { - return a.getName().compareTo(b.getName()); - } - // 濡傛灉閮芥槸闃舵锛屽厛鎸夌埗姣旇禌鍚嶇О鎺掑簭锛屽啀鎸夐樁娈靛悕绉版帓搴� - if (a.getPid() > 0 && b.getPid() > 0) { - String aParentName = a.getParent() != null ? a.getParent().getName() : ""; - String bParentName = b.getParent() != null ? b.getParent().getName() : ""; - int parentCompare = aParentName.compareTo(bParentName); - if (parentCompare != 0) { - return parentCompare; + // 杞崲涓篈ctivityResponse锛屽寘鍚埗姣旇禌淇℃伅 + List<ActivityResponse> result = filteredActivities.stream() + .map(activity -> { + ActivityResponse response = new ActivityResponse(activity); + // 璁剧疆鍙傝禌浜烘暟锛堝鏍搁�氳繃鐨勬姤鍚嶆暟閲忥級 + Long playerCountLong = activityPlayerRepository.countByActivityId(activity.getId()); + int playerCount = playerCountLong != null ? playerCountLong.intValue() : 0; + response.setPlayerCount(playerCount); + + // 璁剧疆鐖舵瘮璧涗俊鎭� + Optional<Activity> parentOpt = activityRepository.findById(activity.getPid()); + if (parentOpt.isPresent()) { + Activity parent = parentOpt.get(); + response.setParent(new ActivityResponse(parent)); + } + + return response; + }) + .sorted((a, b) -> { + // 鍏堟寜鐖舵瘮璧涘悕绉版帓搴忥紝鍐嶆寜闃舵鍚嶇О鎺掑簭 + if (a.getParent() != null && b.getParent() != null) { + int parentCompare = a.getParent().getName().compareTo(b.getParent().getName()); + if (parentCompare != 0) { + return parentCompare; + } } return a.getName().compareTo(b.getName()); - } - // 濡傛灉涓�涓槸姣旇禌锛屼竴涓槸闃舵 - if (a.getPid() == 0 && b.getPid() > 0) { - String bParentName = b.getParent() != null ? b.getParent().getName() : ""; - int compare = a.getName().compareTo(bParentName); - return compare <= 0 ? -1 : 1; // 姣旇禌鎺掑湪鍏堕樁娈靛墠闈� - } - if (a.getPid() > 0 && b.getPid() == 0) { - String aParentName = a.getParent() != null ? a.getParent().getName() : ""; - int compare = aParentName.compareTo(b.getName()); - return compare < 0 ? -1 : 1; // 闃舵鎺掑湪鍏舵瘮璧涘悗闈� - } - return 0; - }); + }) + .collect(Collectors.toList()); return result; } @@ -344,7 +376,14 @@ public List<ActivityResponse> findStagesByActivityId(Long activityId) { List<Activity> stages = activityRepository.findByPidAndStateOrderByCreateTimeAsc(activityId, 1); return stages.stream() - .map(ActivityResponse::new) + .map(activity -> { + ActivityResponse response = new ActivityResponse(activity); + // 璁剧疆鍙傝禌浜烘暟锛堝鏍搁�氳繃鐨勬姤鍚嶆暟閲忥級 + Long playerCountLong = activityPlayerRepository.countByActivityId(activity.getId()); + int playerCount = playerCountLong != null ? playerCountLong.intValue() : 0; + response.setPlayerCount(playerCount); + return response; + }) .collect(Collectors.toList()); } @@ -361,7 +400,14 @@ public List<ActivityResponse> findOngoingActivities() { List<Activity> activities = activityRepository.findOngoingActivities(); return activities.stream() - .map(ActivityResponse::new) + .map(activity -> { + ActivityResponse response = new ActivityResponse(activity); + // 璁剧疆鍙傝禌浜烘暟锛堝鏍搁�氳繃鐨勬姤鍚嶆暟閲忥級 + Long playerCountLong = activityPlayerRepository.countByActivityId(activity.getId()); + int playerCount = playerCountLong != null ? playerCountLong.intValue() : 0; + response.setPlayerCount(playerCount); + return response; + }) .collect(Collectors.toList()); } @@ -391,7 +437,10 @@ Map<Long, List<Long>> judgeStageMap = activityJudges.stream() .collect(Collectors.groupingBy( ActivityJudge::getJudgeId, - Collectors.mapping(ActivityJudge::getStageId, Collectors.toList()) + Collectors.mapping( + aj -> aj.getStageId() != null ? aj.getStageId() : activityId, // 濡傛灉stage_id涓簄ull锛岃〃绀鸿礋璐f暣涓瘮璧涳紝浣跨敤activityId + Collectors.toList() + ) )); // 鏌ヨ璇勫璇︾粏淇℃伅骞舵瀯寤哄搷搴� @@ -416,4 +465,38 @@ return result; } + + /** + * 楠岃瘉闃舵鐨剆ort_order杩炵画鎬� + */ + private void validateSortOrderContinuity(List<ActivityStageInput> stages) { + if (stages == null || stages.isEmpty()) { + return; + } + + // 鏀堕泦鎵�鏈夌殑sortOrder鍊� + List<Integer> sortOrders = stages.stream() + .map(ActivityStageInput::getSortOrder) + .filter(Objects::nonNull) + .sorted() + .collect(Collectors.toList()); + + // 妫�鏌ユ槸鍚︿粠1寮�濮� + if (sortOrders.isEmpty() || sortOrders.get(0) != 1) { + throw new RuntimeException("闃舵鎺掑簭蹇呴』浠�1寮�濮�"); + } + + // 妫�鏌ヨ繛缁�� + for (int i = 0; i < sortOrders.size(); i++) { + if (sortOrders.get(i) != i + 1) { + throw new RuntimeException("闃舵鎺掑簭蹇呴』杩炵画锛屼笉鑳借烦璺冿紙濡傦細1,2,3...锛�"); + } + } + + // 妫�鏌ユ槸鍚︽湁閲嶅鐨剆ortOrder + Set<Integer> uniqueSortOrders = new HashSet<>(sortOrders); + if (uniqueSortOrders.size() != sortOrders.size()) { + throw new RuntimeException("闃舵鎺掑簭涓嶈兘閲嶅"); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..32c4aa5 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java @@ -0,0 +1,86 @@ +package com.rongyichuang.auth.filter; + +import com.rongyichuang.auth.util.JwtUtil; +import com.rongyichuang.user.entity.User; +import com.rongyichuang.user.repository.UserRepository; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Optional; + +/** + * JWT璁よ瘉杩囨护鍣� + */ +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private UserRepository userRepository; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String authHeader = request.getHeader("Authorization"); + String token = null; + Long userId = null; + + // 浠庤姹傚ご涓彁鍙朖WT token + if (authHeader != null && authHeader.startsWith("Bearer ")) { + token = authHeader.substring(7); + try { + userId = jwtUtil.getUserIdFromToken(token); + } catch (Exception e) { + logger.debug("JWT token瑙f瀽澶辫触: {}", e.getMessage()); + } + } + + // 濡傛灉token鏈夋晥涓斿綋鍓嶆病鏈夎璇佷俊鎭� + if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { + + // 楠岃瘉token鏄惁鏈夋晥 + if (jwtUtil.validateToken(token)) { + + // 鏌ユ壘鐢ㄦ埛淇℃伅 + Optional<User> userOpt = userRepository.findById(userId); + if (userOpt.isPresent()) { + User user = userOpt.get(); + + // 鍒涘缓璁よ瘉瀵硅薄 + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + user.getId().toString(), + null, + new ArrayList<>() // 鏆傛椂涓嶈缃潈闄愶紝鍚庣画鍙互鏍规嵁瑙掕壊璁剧疆 + ); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + + logger.debug("鐢ㄦ埛璁よ瘉鎴愬姛: userId={}, phone={}", user.getId(), user.getPhone()); + } + } + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/common/api/DataCleanupController.java b/backend/src/main/java/com/rongyichuang/common/api/DataCleanupController.java index d3cdf9f..4be41a6 100644 --- a/backend/src/main/java/com/rongyichuang/common/api/DataCleanupController.java +++ b/backend/src/main/java/com/rongyichuang/common/api/DataCleanupController.java @@ -6,6 +6,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.HashMap; +import java.util.Map; + @RestController @RequestMapping("/cleanup") public class DataCleanupController { @@ -23,4 +26,41 @@ return "娓呯悊澶辫触: " + e.getMessage(); } } + + @PostMapping("/clear-all-test-data") + public Map<String, Object> clearAllTestData() { + Map<String, Object> result = new HashMap<>(); + try { + // 绂佺敤澶栭敭妫�鏌� + jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS = 0"); + + // 鎸夌収澶栭敭渚濊禆鍏崇郴鐨勯『搴忓垹闄� + int deletedRatingItems = jdbcTemplate.update("DELETE FROM t_activity_rating_item"); + int deletedRatings = jdbcTemplate.update("DELETE FROM t_activity_rating"); + int deletedActivityPlayers = jdbcTemplate.update("DELETE FROM t_activity_player"); + int deletedActivityJudges = jdbcTemplate.update("DELETE FROM t_activity_judge"); + int deletedPlayers = jdbcTemplate.update("DELETE FROM t_player"); + int deletedActivities = jdbcTemplate.update("DELETE FROM t_activity"); + + // 閲嶆柊鍚敤澶栭敭妫�鏌� + jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS = 1"); + + result.put("success", true); + result.put("message", "鎵�鏈夋祴璇曟暟鎹凡娓呯┖"); + result.put("deletedCounts", Map.of( + "t_activity_rating_item", deletedRatingItems, + "t_activity_rating", deletedRatings, + "t_activity_player", deletedActivityPlayers, + "t_activity_judge", deletedActivityJudges, + "t_player", deletedPlayers, + "t_activity", deletedActivities + )); + + } catch (Exception e) { + result.put("success", false); + result.put("message", "娓呯悊澶辫触: " + e.getMessage()); + } + + return result; + } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/common/enums/MediaTargetType.java b/backend/src/main/java/com/rongyichuang/common/enums/MediaTargetType.java index 1a2f481..04bce05 100644 --- a/backend/src/main/java/com/rongyichuang/common/enums/MediaTargetType.java +++ b/backend/src/main/java/com/rongyichuang/common/enums/MediaTargetType.java @@ -28,11 +28,6 @@ ACTIVITY_PLAYER_SUBMISSION(5, "鍙傝禌鎶ュ悕璧勬枡"), /** - * 瀛﹀憳澶村儚 - */ - STUDENT_AVATAR(6, "瀛﹀憳澶村儚"), - - /** * 鐢ㄦ埛澶村儚 */ USER_AVATAR(7, "鐢ㄦ埛澶村儚"); 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 0bf65c7..e659917 100644 --- a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java +++ b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java @@ -56,8 +56,19 @@ if (authentication != null && authentication.isAuthenticated() && !"anonymousUser".equals(authentication.getPrincipal())) { logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", authentication.getName()); - // 濡傛灉璁よ瘉淇℃伅涓寘鍚敤鎴稩D锛屽彲浠ュ湪杩欓噷瑙f瀽 - // 鏆傛椂杩斿洖鍥哄畾鐢ㄦ埛ID鐢ㄤ簬鍏煎鎬� + // 鍦ㄥ紑鍙戠幆澧冧笅锛岃繑鍥炰竴涓湁鏁堢殑璇勫鐢ㄦ埛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); + return userId; + } + } catch (Exception e) { + logger.warn("鏌ユ壘璇勫鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage()); + } + // 濡傛灉娌℃湁鎵惧埌璇勫锛岃繑鍥炲浐瀹氱敤鎴稩D return 1L; } } catch (Exception e) { diff --git a/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java b/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java index 2e1b542..1ae6ced 100644 --- a/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java +++ b/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java @@ -1,5 +1,7 @@ package com.rongyichuang.config; +import com.rongyichuang.auth.filter.JwtAuthenticationFilter; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -11,6 +13,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -24,6 +27,9 @@ @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public PasswordEncoder passwordEncoder() { @@ -42,14 +48,12 @@ .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/**").permitAll() - .requestMatchers("/api/graphql/**").permitAll() - .requestMatchers("/graphql/**").permitAll() - .requestMatchers("/graphql").permitAll() - .requestMatchers("/api/graphiql").permitAll() - .requestMatchers("/api/test/**").permitAll() - .anyRequest().permitAll() - ); + .requestMatchers("/api/auth/**", "/api/actuator/**", "/api/test/**", "/api/cleanup/**").permitAll() + .requestMatchers("/api/graphql", "/api/graphql/**", "/api/graphiql").permitAll() + .requestMatchers("/graphql", "/graphql/**").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/backend/src/main/java/com/rongyichuang/media/api/MediaV2GraphqlApi.java b/backend/src/main/java/com/rongyichuang/media/api/MediaV2GraphqlApi.java index 3a280cf..d154bbd 100644 --- a/backend/src/main/java/com/rongyichuang/media/api/MediaV2GraphqlApi.java +++ b/backend/src/main/java/com/rongyichuang/media/api/MediaV2GraphqlApi.java @@ -39,17 +39,24 @@ * @return 淇濆瓨缁撴灉 */ @MutationMapping - public MediaSaveResponse savePlayerAvatar(@Argument Long playerId, @Argument String url, + public MediaSaveResponse savePlayerAvatar(@Argument Long playerId, @Argument String path, @Argument String fileName, @Argument Long fileSize) { log.info("鏀跺埌淇濆瓨閫夋墜澶村儚璇锋眰锛岄�夋墜ID: {}", playerId); MediaSaveInput input = new MediaSaveInput(); input.setTargetType("player"); input.setTargetId(playerId); - input.setPath(url); - input.setName(fileName); + input.setPath(path); + input.setFileName(fileName); input.setFileSize(fileSize); - input.setMediaType(1); // 澶村儚绫诲瀷 + input.setMediaType(1); // 澶村儚榛樿涓哄浘鐗囩被鍨� + + // 浠庢枃浠跺悕涓彁鍙栨枃浠舵墿灞曞悕 + String fileExt = ""; + if (fileName != null && fileName.contains(".")) { + fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + } + input.setFileExt(fileExt); return mediaService.saveMedia(input); } @@ -64,19 +71,19 @@ * @return 淇濆瓨缁撴灉 */ @MutationMapping - public MediaSaveResponse saveActivityPlayerAttachment(@Argument Long activityPlayerId, @Argument String url, - @Argument String fileName, @Argument Long fileSize, - @Argument Integer mediaType) { + public MediaSaveResponse saveActivityPlayerAttachment(@Argument Long activityPlayerId, @Argument String path, + @Argument String fileName, @Argument Long fileSize, @Argument Integer mediaType) { log.info("鏀跺埌淇濆瓨娲诲姩鎶ュ悕闄勪欢璇锋眰锛屾椿鍔ㄦ姤鍚岻D: {}", activityPlayerId); MediaSaveInput input = new MediaSaveInput(); input.setTargetType("activity_player"); input.setTargetId(activityPlayerId); - input.setPath(url); - input.setName(fileName); + input.setPath(path); + input.setFileName(fileName); input.setFileSize(fileSize); input.setMediaType(mediaType); + return mediaService.saveMedia(input); } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/media/dto/MediaSaveInput.java b/backend/src/main/java/com/rongyichuang/media/dto/MediaSaveInput.java index b133951..dc411a7 100644 --- a/backend/src/main/java/com/rongyichuang/media/dto/MediaSaveInput.java +++ b/backend/src/main/java/com/rongyichuang/media/dto/MediaSaveInput.java @@ -7,8 +7,8 @@ private String targetType; // 鐩爣绫诲瀷锛歱layer, activity_player private Long targetId; // 鐩爣ID - private String url; // COS鏂囦欢URL - private String thumbUrl; // 缂╃暐鍥綰RL锛堝彲閫夛級 + private String path; // COS鏂囦欢璺緞 + private String thumbPath; // 缂╃暐鍥捐矾寰勶紙鍙�夛級 private String fileName; // 鏂囦欢鍚� private String fileExt; // 鏂囦欢鎵╁睍鍚� private Long fileSize; // 鏂囦欢澶у皬锛堝瓧鑺傦級 @@ -36,20 +36,20 @@ this.targetId = targetId; } - public String getUrl() { - return url; + public String getPath() { + return path; } - public void setUrl(String url) { - this.url = url; + public void setPath(String path) { + this.path = path; } - public String getThumbUrl() { - return thumbUrl; + public String getThumbPath() { + return thumbPath; } - public void setThumbUrl(String thumbUrl) { - this.thumbUrl = thumbUrl; + public void setThumbPath(String thumbPath) { + this.thumbPath = thumbPath; } public String getFileName() { diff --git a/backend/src/main/java/com/rongyichuang/media/service/MediaV2Service.java b/backend/src/main/java/com/rongyichuang/media/service/MediaV2Service.java index b1329cf..7c6cf58 100644 --- a/backend/src/main/java/com/rongyichuang/media/service/MediaV2Service.java +++ b/backend/src/main/java/com/rongyichuang/media/service/MediaV2Service.java @@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; + /** * 濯掍綋鏈嶅姟绫� */ @@ -37,32 +39,58 @@ if (input.getTargetId() == null) { return MediaSaveResponse.error("鐩爣ID涓嶈兘涓虹┖"); } - if (input.getUrl() == null || input.getUrl().trim().isEmpty()) { - return MediaSaveResponse.error("鏂囦欢URL涓嶈兘涓虹┖"); + if (input.getPath() == null || input.getPath().trim().isEmpty()) { + return MediaSaveResponse.error("鏂囦欢璺緞涓嶈兘涓虹┖"); } if (input.getMediaType() == null) { return MediaSaveResponse.error("濯掍綋绫诲瀷涓嶈兘涓虹┖"); } - // 鍒涘缓Media瀹炰綋 - Media media = new Media(); - // 灏唗argetType瀛楃涓茶浆鎹负鏁存暟 Integer targetTypeInt = convertTargetTypeToInt(input.getTargetType()); - media.setTargetType(targetTypeInt); - media.setTargetId(input.getTargetId()); - media.setPath(input.getUrl()); - media.setThumbPath(input.getThumbUrl()); + + Media media; + boolean isUpdate = false; + + // 鏍规嵁鐩爣绫诲瀷鍐冲畾澶勭悊绛栫暐 + if (targetTypeInt == 7) { // USER_AVATAR - 澶村儚锛岄渶瑕佹洿鏂扮幇鏈夎褰� + List<Media> existingMedias = mediaRepository.findByTargetTypeAndTargetIdAndState( + targetTypeInt, input.getTargetId(), 1); + + if (!existingMedias.isEmpty()) { + // 瀛樺湪澶村儚璁板綍锛屾洿鏂扮涓�涓褰� + media = existingMedias.get(0); + isUpdate = true; + log.info("鎵惧埌鐜版湁澶村儚璁板綍锛屽皢杩涜鏇存柊锛孖D: {}", media.getId()); + } else { + // 涓嶅瓨鍦ㄥご鍍忚褰曪紝鍒涘缓鏂拌褰� + media = new Media(); + media.setTargetType(targetTypeInt); + media.setTargetId(input.getTargetId()); + media.setState(1); + log.info("鏈壘鍒扮幇鏈夊ご鍍忚褰曪紝灏嗗垱寤烘柊鐨勫ご鍍忚褰�"); + } + } else { + // 鍏朵粬绫诲瀷锛堝闄勪欢锛夛紝鎬绘槸鍒涘缓鏂拌褰� + media = new Media(); + media.setTargetType(targetTypeInt); + media.setTargetId(input.getTargetId()); + media.setState(1); // 1琛ㄧず鏈夋晥鐘舵�� + log.info("鍒涘缓鏂扮殑濯掍綋璁板綍锛岀被鍨�: {}", input.getTargetType()); + } + + // 鏇存柊鎴栬缃獟浣撲俊鎭� + media.setPath(input.getPath()); + media.setThumbPath(input.getThumbPath()); media.setName(input.getFileName()); media.setFileExt(input.getFileExt()); media.setFileSize(input.getFileSize() != null ? input.getFileSize().intValue() : null); - media.setDuration(input.getDuration()); - media.setMediaType(input.getMediaType()); - media.setState(1); // 1琛ㄧず鏈夋晥鐘舵�� + media.setDuration(input.getDuration()); + media.setMediaType(input.getMediaType()); // 淇濆瓨鍒版暟鎹簱 Media savedMedia = mediaRepository.save(media); - log.info("濯掍綋鏂囦欢淇濆瓨鎴愬姛锛孖D: {}", savedMedia.getId()); + log.info("濯掍綋鏂囦欢{}鎴愬姛锛孖D: {}", isUpdate ? "鏇存柊" : "淇濆瓨", savedMedia.getId()); return MediaSaveResponse.success("濯掍綋鏂囦欢淇濆瓨鎴愬姛", savedMedia.getId()); @@ -82,9 +110,9 @@ private Integer convertTargetTypeToInt(String targetType) { switch (targetType) { case "player": - return 1; + return 7; // USER_AVATAR - 鐢ㄦ埛澶村儚 case "activity_player": - return 2; + return 5; // ACTIVITY_PLAYER_SUBMISSION - 鍙傝禌鎶ュ悕璧勬枡 default: throw new IllegalArgumentException("涓嶆敮鎸佺殑鐩爣绫诲瀷: " + targetType); } diff --git a/backend/src/main/java/com/rongyichuang/message/entity/Message.java b/backend/src/main/java/com/rongyichuang/message/entity/Message.java new file mode 100644 index 0000000..94ddbe1 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/message/entity/Message.java @@ -0,0 +1,154 @@ +package com.rongyichuang.message.entity; + +import com.rongyichuang.common.entity.BaseEntity; +import jakarta.persistence.*; + +/** + * 娑堟伅瀹炰綋绫� + * 瀵瑰簲鏁版嵁搴撹〃锛歵_msg + */ +@Entity +@Table(name = "t_msg") +public class Message extends BaseEntity { + + /** + * 鐩爣绫诲瀷 + */ + @Column(name = "target_type", nullable = false) + private Integer targetType; + + /** + * 鐩爣ID + */ + @Column(name = "target_id", nullable = false) + private Integer targetId; + + /** + * 瀛﹀憳ID + */ + @Column(name = "player_id", nullable = false) + private Integer playerId; + + /** + * 鐢ㄦ埛ID + */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** + * 娑堟伅鍐呭 + */ + @Column(name = "content", length = 200, nullable = false) + private String content; + + /** + * 妯℃澘鍐呭 + */ + @Column(name = "template_content", length = 200) + private String templateContent; + + /** + * 寰俊娑堟伅鍙戦�佹垚鍔熸爣蹇� + */ + @Column(name = "wx_msg_success", nullable = false) + private Boolean wxMsgSuccess; + + /** + * 寰俊娑堟伅閿欒娆℃暟 + */ + @Column(name = "wx_msg_err_count", nullable = false) + private Integer wxMsgErrCount = 0; + + /** + * 寰俊娑堟伅鏈�鍚庨敊璇俊鎭� + */ + @Column(name = "wx_msg_last_err", length = 255) + private String wxMsgLastErr; + + /** + * 鐘舵�侊細0-鏆傛椂涓嶅彂甯冿紝1-鍙互鍙戝竷锛�2-宸茬粡鍙戝竷 + */ + @Column(name = "state", nullable = false) + private Integer state; + + // Getters and Setters + public Integer getTargetType() { + return targetType; + } + + public void setTargetType(Integer targetType) { + this.targetType = targetType; + } + + public Integer getTargetId() { + return targetId; + } + + public void setTargetId(Integer targetId) { + this.targetId = targetId; + } + + public Integer getPlayerId() { + return playerId; + } + + public void setPlayerId(Integer playerId) { + this.playerId = playerId; + } + + public Integer getUserId() { + return userId; + } + + public void setUserId(Integer userId) { + this.userId = userId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getTemplateContent() { + return templateContent; + } + + public void setTemplateContent(String templateContent) { + this.templateContent = templateContent; + } + + public Boolean getWxMsgSuccess() { + return wxMsgSuccess; + } + + public void setWxMsgSuccess(Boolean wxMsgSuccess) { + this.wxMsgSuccess = wxMsgSuccess; + } + + public Integer getWxMsgErrCount() { + return wxMsgErrCount; + } + + public void setWxMsgErrCount(Integer wxMsgErrCount) { + this.wxMsgErrCount = wxMsgErrCount; + } + + public String getWxMsgLastErr() { + return wxMsgLastErr; + } + + public void setWxMsgLastErr(String wxMsgLastErr) { + this.wxMsgLastErr = wxMsgLastErr; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/message/entity/MessageType.java b/backend/src/main/java/com/rongyichuang/message/entity/MessageType.java new file mode 100644 index 0000000..5c3fe24 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/message/entity/MessageType.java @@ -0,0 +1,49 @@ +package com.rongyichuang.message.entity; + +/** + * 娑堟伅绫诲瀷鏋氫妇 + */ +public enum MessageType { + /** + * 瀹℃牳閫氳繃 + */ + REVIEW_APPROVED(1, "瀹℃牳閫氳繃"), + + /** + * 瀹℃牳椹冲洖 + */ + REVIEW_REJECTED(2, "瀹℃牳椹冲洖"), + + /** + * 姣旇禌鏅嬬骇 + */ + COMPETITION_PROMOTED(3, "姣旇禌鏅嬬骇"); + + private final int value; + private final String description; + + MessageType(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + + /** + * 鏍规嵁鍊艰幏鍙栨灇涓� + */ + public static MessageType fromValue(int value) { + for (MessageType type : MessageType.values()) { + if (type.getValue() == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown MessageType value: " + value); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/message/repository/MessageRepository.java b/backend/src/main/java/com/rongyichuang/message/repository/MessageRepository.java new file mode 100644 index 0000000..17980b5 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/message/repository/MessageRepository.java @@ -0,0 +1,53 @@ +package com.rongyichuang.message.repository; + +import com.rongyichuang.message.entity.Message; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 娑堟伅Repository鎺ュ彛 + */ +@Repository +public interface MessageRepository extends JpaRepository<Message, Long> { + + /** + * 鏍规嵁瀛﹀憳ID鏌ユ壘娑堟伅 + */ + List<Message> findByPlayerId(Integer playerId); + + /** + * 鏍规嵁鐢ㄦ埛ID鏌ユ壘娑堟伅 + */ + List<Message> findByUserId(Integer userId); + + /** + * 鏍规嵁鐩爣绫诲瀷鍜岀洰鏍嘔D鏌ユ壘娑堟伅 + */ + List<Message> findByTargetTypeAndTargetId(Integer targetType, Integer targetId); + + /** + * 鏍规嵁鐘舵�佹煡鎵炬秷鎭� + */ + List<Message> findByState(Integer state); + + /** + * 鏍规嵁瀛﹀憳ID鍜岀姸鎬佹煡鎵炬秷鎭� + */ + List<Message> findByPlayerIdAndState(Integer playerId, Integer state); + + /** + * 鏌ユ壘寰俊娑堟伅鍙戦�佸け璐ョ殑娑堟伅 + */ + @Query("SELECT m FROM Message m WHERE m.wxMsgSuccess = false AND m.wxMsgErrCount < :maxErrCount") + List<Message> findFailedWxMessages(@Param("maxErrCount") Integer maxErrCount); + + /** + * 鏍规嵁瀛﹀憳ID鏌ユ壘鏈彂甯冪殑娑堟伅 + */ + @Query("SELECT m FROM Message m WHERE m.playerId = :playerId AND m.state IN (0, 1)") + List<Message> findUnpublishedMessagesByPlayerId(@Param("playerId") Integer playerId); +} \ No newline at end of file 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 1bbe844..adce16f 100644 --- a/backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java +++ b/backend/src/main/java/com/rongyichuang/player/api/PlayerGraphqlApi.java @@ -9,10 +9,16 @@ import com.rongyichuang.player.dto.response.CurrentJudgeRatingResponse; import com.rongyichuang.player.dto.response.CurrentJudgeInfoResponse; import com.rongyichuang.player.dto.response.PlayerRegistrationResponse; +import com.rongyichuang.player.dto.PromotionCompetitionResponse; +import com.rongyichuang.player.dto.CompetitionParticipantResponse; +import com.rongyichuang.player.dto.PromotionInput; +import com.rongyichuang.player.dto.PromotionResult; +import com.rongyichuang.player.dto.PromotableParticipantsResponse; import com.rongyichuang.player.service.PlayerApplicationService; import com.rongyichuang.player.service.ActivityPlayerDetailService; import com.rongyichuang.player.service.ActivityPlayerRatingService; import com.rongyichuang.player.service.ActivityPlayerService; +import com.rongyichuang.player.service.PromotionService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.graphql.data.method.annotation.Argument; @@ -32,25 +38,29 @@ private final ActivityPlayerDetailService detailService; private final ActivityPlayerRatingService ratingService; private final ActivityPlayerService activityPlayerService; + private final PromotionService promotionService; public PlayerGraphqlApi(PlayerApplicationService service, ActivityPlayerDetailService detailService, ActivityPlayerRatingService ratingService, - ActivityPlayerService activityPlayerService) { + ActivityPlayerService activityPlayerService, + PromotionService promotionService) { this.service = service; this.detailService = detailService; this.ratingService = ratingService; this.activityPlayerService = activityPlayerService; + this.promotionService = promotionService; } @QueryMapping public List<ActivityPlayerApplicationResponse> activityPlayerApplications( @Argument String name, @Argument Long activityId, + @Argument Integer state, @Argument Integer page, @Argument Integer size ) { - return service.listApplications(name, activityId, page, size); + return service.listApplications(name, activityId, state, page, size); } /** @@ -142,4 +152,124 @@ return response; } } + + /** + * 瀹℃牳閫氳繃 + */ + @MutationMapping + public Boolean approveActivityPlayer(@Argument Long activityPlayerId, @Argument String feedback) { + log.info("瀹℃牳閫氳繃璇锋眰锛宎ctivityPlayerId: {}, feedback: {}", activityPlayerId, feedback); + try { + return activityPlayerService.approveActivityPlayer(activityPlayerId, feedback); + } catch (Exception e) { + log.error("瀹℃牳閫氳繃澶辫触: {}", e.getMessage(), e); + throw e; + } + } + + /** + * 瀹℃牳椹冲洖 + */ + @MutationMapping + public Boolean rejectActivityPlayer(@Argument Long activityPlayerId, @Argument String feedback) { + log.info("瀹℃牳椹冲洖璇锋眰锛宎ctivityPlayerId: {}, feedback: {}", activityPlayerId, feedback); + try { + return activityPlayerService.rejectActivityPlayer(activityPlayerId, feedback); + } catch (Exception e) { + log.error("瀹℃牳椹冲洖澶辫触: {}", e.getMessage(), e); + throw e; + } + } + + /** + * 鏇存柊瀹℃牳鎰忚 + */ + @MutationMapping + public Boolean updatePlayerFeedback(@Argument Long activityPlayerId, @Argument String feedback) { + log.info("鏇存柊瀹℃牳鎰忚璇锋眰锛宎ctivityPlayerId: {}, feedback: {}", activityPlayerId, feedback); + try { + return activityPlayerService.updateFeedback(activityPlayerId, feedback); + } catch (Exception e) { + log.error("鏇存柊瀹℃牳鎰忚澶辫触: {}", e.getMessage(), e); + throw e; + } + } + + @MutationMapping + public ActivityRegistrationResponse updateActivityRegistration(@Argument Long activityPlayerId, @Argument ActivityRegistrationInput input) { + try { + log.info("鏀跺埌鏇存柊鎶ュ悕璇锋眰锛屾姤鍚岻D: {}", activityPlayerId); + return activityPlayerService.updateActivityRegistration(activityPlayerId, input); + } catch (Exception e) { + log.error("鏇存柊鎶ュ悕澶辫触", e); + ActivityRegistrationResponse response = new ActivityRegistrationResponse(); + response.setSuccess(false); + response.setMessage("鏇存柊鎶ュ悕澶辫触: " + e.getMessage()); + return response; + } + } + + /** + * 鑾峰彇姣旇禌鏅嬬骇鍒楄〃 + */ + @QueryMapping + public List<PromotionCompetitionResponse> promotionCompetitions( + @Argument String name, + @Argument Integer page, + @Argument Integer size) { + try { + log.info("鑾峰彇姣旇禌鏅嬬骇鍒楄〃锛屽悕绉拌繃婊�: {}, 椤电爜: {}, 椤靛ぇ灏�: {}", name, page, size); + return promotionService.getPromotionCompetitions(name, page, size); + } catch (Exception e) { + log.error("鑾峰彇姣旇禌鏅嬬骇鍒楄〃澶辫触: {}", e.getMessage(), e); + throw e; + } + } + + /** + * 鑾峰彇姣旇禌鍙傝禌浜哄憳 + */ + @QueryMapping + public List<CompetitionParticipantResponse> competitionParticipants( + @Argument Long competitionId, + @Argument Integer page, + @Argument Integer size) { + try { + log.info("鑾峰彇姣旇禌鍙傝禌浜哄憳锛屾瘮璧汭D: {}, 椤电爜: {}, 椤靛ぇ灏�: {}", competitionId, page, size); + return promotionService.getCompetitionParticipants(competitionId, page, size); + } catch (Exception e) { + log.error("鑾峰彇姣旇禌鍙傝禌浜哄憳澶辫触: {}", e.getMessage(), e); + throw e; + } + } + + /** + * 鑾峰彇鍙檵绾у弬璧涜�呭垪琛� + */ + @QueryMapping + public PromotableParticipantsResponse promotableParticipants(@Argument Long currentStageId) { + try { + log.info("鑾峰彇鍙檵绾у弬璧涜�呭垪琛紝褰撳墠闃舵ID: {}", currentStageId); + return promotionService.getPromotableParticipants(currentStageId); + } catch (Exception e) { + log.error("鑾峰彇鍙檵绾у弬璧涜�呭垪琛ㄥけ璐�: {}", e.getMessage(), e); + throw e; + } + } + + /** + * 鎵ц鏅嬬骇鎿嶄綔 + */ + @MutationMapping + public PromotionResult promoteParticipants(@Argument PromotionInput input) { + try { + log.info("鎵ц鏅嬬骇鎿嶄綔锛屾瘮璧汭D: {}, 鍙傝禌鑰呮暟閲�: {}", + input.getCompetitionId(), + input.getParticipantIds() != null ? input.getParticipantIds().size() : 0); + return promotionService.promoteParticipants(input); + } catch (Exception e) { + log.error("鎵ц鏅嬬骇鎿嶄綔澶辫触: {}", e.getMessage(), e); + return PromotionResult.failure("鏅嬬骇鎿嶄綔澶辫触: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/CompetitionParticipantResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/CompetitionParticipantResponse.java new file mode 100644 index 0000000..741e1f7 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/dto/CompetitionParticipantResponse.java @@ -0,0 +1,108 @@ +package com.rongyichuang.player.dto; + +import com.rongyichuang.player.entity.ActivityPlayer; + +import java.math.BigDecimal; +import java.time.format.DateTimeFormatter; + +/** + * 姣旇禌鍙傝禌鑰呭搷搴旂被 + */ +public class CompetitionParticipantResponse { + + private Long id; + private String playerName; + private String projectName; + private String phone; + private BigDecimal averageScore; + private Integer ratingCount; + private String applyTime; + private Integer state; + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public CompetitionParticipantResponse() {} + + public CompetitionParticipantResponse(ActivityPlayer activityPlayer) { + this.id = activityPlayer.getId(); + this.playerName = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getName() : ""; + this.projectName = activityPlayer.getProjectName(); + this.phone = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getPhone() : ""; + this.averageScore = activityPlayer.getTotalScore(); + this.ratingCount = 0; // 闇�瑕佷粠璇勫垎琛ㄤ腑缁熻 + this.applyTime = activityPlayer.getCreateTime() != null ? + activityPlayer.getCreateTime().format(FORMATTER) : null; + this.state = activityPlayer.getState(); + } + + public CompetitionParticipantResponse(ActivityPlayer activityPlayer, Integer ratingCount) { + this(activityPlayer); + this.ratingCount = ratingCount != null ? ratingCount : 0; + } + + // Getters and Setters + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPlayerName() { + return playerName; + } + + public void setPlayerName(String playerName) { + this.playerName = playerName; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public BigDecimal getAverageScore() { + return averageScore; + } + + public void setAverageScore(BigDecimal averageScore) { + this.averageScore = averageScore; + } + + public Integer getRatingCount() { + return ratingCount; + } + + public void setRatingCount(Integer ratingCount) { + this.ratingCount = ratingCount; + } + + public String getApplyTime() { + return applyTime; + } + + public void setApplyTime(String applyTime) { + this.applyTime = applyTime; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantResponse.java new file mode 100644 index 0000000..e59d920 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantResponse.java @@ -0,0 +1,113 @@ +package com.rongyichuang.player.dto; + +import com.rongyichuang.player.entity.ActivityPlayer; + +import java.math.BigDecimal; +import java.time.format.DateTimeFormatter; + +/** + * 鍙檵绾у弬璧涜�呭搷搴旂被 + */ +public class PromotableParticipantResponse { + + private Long id; + private String playerName; + private String projectName; + private String phone; + private BigDecimal averageScore; + private Integer ratingCount; + private String applyTime; + private Integer state; + private Long playerId; + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public PromotableParticipantResponse() {} + + public PromotableParticipantResponse(ActivityPlayer activityPlayer, BigDecimal averageScore, Integer ratingCount) { + this.id = activityPlayer.getId(); + this.playerName = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getName() : ""; + this.projectName = activityPlayer.getProjectName(); + this.phone = activityPlayer.getPlayer() != null ? activityPlayer.getPlayer().getPhone() : ""; + this.averageScore = averageScore; + this.ratingCount = ratingCount != null ? ratingCount : 0; + this.applyTime = activityPlayer.getCreateTime() != null ? + activityPlayer.getCreateTime().format(FORMATTER) : null; + this.state = activityPlayer.getState(); + this.playerId = activityPlayer.getPlayerId(); + } + + // Getters and Setters + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPlayerName() { + return playerName; + } + + public void setPlayerName(String playerName) { + this.playerName = playerName; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public BigDecimal getAverageScore() { + return averageScore; + } + + public void setAverageScore(BigDecimal averageScore) { + this.averageScore = averageScore; + } + + public Integer getRatingCount() { + return ratingCount; + } + + public void setRatingCount(Integer ratingCount) { + this.ratingCount = ratingCount; + } + + public String getApplyTime() { + return applyTime; + } + + public void setApplyTime(String applyTime) { + this.applyTime = applyTime; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } + + public Long getPlayerId() { + return playerId; + } + + public void setPlayerId(Long playerId) { + this.playerId = playerId; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantsResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantsResponse.java new file mode 100644 index 0000000..6c05437 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/dto/PromotableParticipantsResponse.java @@ -0,0 +1,71 @@ +package com.rongyichuang.player.dto; + +import java.util.List; + +/** + * 鍙檵绾у弬璧涜�呭垪琛ㄥ搷搴旂被 + */ +public class PromotableParticipantsResponse { + + private List<PromotableParticipantResponse> participants; + private Integer selectableCount; + private Integer totalCount; + private String previousStageName; + private String currentStageName; + + public PromotableParticipantsResponse() {} + + public PromotableParticipantsResponse(List<PromotableParticipantResponse> participants, + Integer selectableCount, + Integer totalCount, + String previousStageName, + String currentStageName) { + this.participants = participants; + this.selectableCount = selectableCount; + this.totalCount = totalCount; + this.previousStageName = previousStageName; + this.currentStageName = currentStageName; + } + + // Getters and Setters + + public List<PromotableParticipantResponse> getParticipants() { + return participants; + } + + public void setParticipants(List<PromotableParticipantResponse> participants) { + this.participants = participants; + } + + public Integer getSelectableCount() { + return selectableCount; + } + + public void setSelectableCount(Integer selectableCount) { + this.selectableCount = selectableCount; + } + + public Integer getTotalCount() { + return totalCount; + } + + public void setTotalCount(Integer totalCount) { + this.totalCount = totalCount; + } + + public String getPreviousStageName() { + return previousStageName; + } + + public void setPreviousStageName(String previousStageName) { + this.previousStageName = previousStageName; + } + + public String getCurrentStageName() { + return currentStageName; + } + + public void setCurrentStageName(String currentStageName) { + this.currentStageName = currentStageName; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/PromotionCompetitionResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/PromotionCompetitionResponse.java new file mode 100644 index 0000000..d5521b4 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/dto/PromotionCompetitionResponse.java @@ -0,0 +1,123 @@ +package com.rongyichuang.player.dto; + +import com.rongyichuang.activity.entity.Activity; + +import java.time.format.DateTimeFormatter; + +/** + * 姣旇禌鏅嬬骇鍒楄〃鍝嶅簲绫� + */ +public class PromotionCompetitionResponse { + + private Long id; + private String competitionName; + private String stageName; + private Integer maxParticipants; + private Integer currentCount; + private Integer status; + private String startTime; + private String endTime; + private Integer sortOrder; + private Integer state; + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public PromotionCompetitionResponse() {} + + public PromotionCompetitionResponse(Activity competition, Activity stage, Integer currentCount) { + this.id = stage.getId(); + this.competitionName = competition.getName(); + this.stageName = stage.getName(); + this.maxParticipants = stage.getPlayerMax(); + this.currentCount = currentCount != null ? currentCount : 0; + this.status = stage.getState(); + this.startTime = stage.getMatchTime() != null ? stage.getMatchTime().format(FORMATTER) : null; + this.endTime = stage.getSignupDeadline() != null ? stage.getSignupDeadline().format(FORMATTER) : null; + this.sortOrder = stage.getSortOrder(); + this.state = stage.getState(); + } + + + + // Getters and Setters + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCompetitionName() { + return competitionName; + } + + public void setCompetitionName(String competitionName) { + this.competitionName = competitionName; + } + + public String getStageName() { + return stageName; + } + + public void setStageName(String stageName) { + this.stageName = stageName; + } + + public Integer getMaxParticipants() { + return maxParticipants; + } + + public void setMaxParticipants(Integer maxParticipants) { + this.maxParticipants = maxParticipants; + } + + public Integer getCurrentCount() { + return currentCount; + } + + public void setCurrentCount(Integer currentCount) { + this.currentCount = currentCount; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public Integer getSortOrder() { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) { + this.sortOrder = sortOrder; + } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/PromotionInput.java b/backend/src/main/java/com/rongyichuang/player/dto/PromotionInput.java new file mode 100644 index 0000000..8094094 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/dto/PromotionInput.java @@ -0,0 +1,47 @@ +package com.rongyichuang.player.dto; + +import java.util.List; + +/** + * 鏅嬬骇鎿嶄綔杈撳叆绫� + */ +public class PromotionInput { + + private Long competitionId; + private List<Long> participantIds; + private Long targetStageId; + + public PromotionInput() {} + + public PromotionInput(Long competitionId, List<Long> participantIds, Long targetStageId) { + this.competitionId = competitionId; + this.participantIds = participantIds; + this.targetStageId = targetStageId; + } + + // Getters and Setters + + public Long getCompetitionId() { + return competitionId; + } + + public void setCompetitionId(Long competitionId) { + this.competitionId = competitionId; + } + + public List<Long> getParticipantIds() { + return participantIds; + } + + public void setParticipantIds(List<Long> participantIds) { + this.participantIds = participantIds; + } + + public Long getTargetStageId() { + return targetStageId; + } + + public void setTargetStageId(Long targetStageId) { + this.targetStageId = targetStageId; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/PromotionResult.java b/backend/src/main/java/com/rongyichuang/player/dto/PromotionResult.java new file mode 100644 index 0000000..1ef4759 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/dto/PromotionResult.java @@ -0,0 +1,53 @@ +package com.rongyichuang.player.dto; + +/** + * 鏅嬬骇鎿嶄綔缁撴灉绫� + */ +public class PromotionResult { + + private Boolean success; + private String message; + private Integer promotedCount; + + public PromotionResult() {} + + public PromotionResult(Boolean success, String message, Integer promotedCount) { + this.success = success; + this.message = message; + this.promotedCount = promotedCount; + } + + public static PromotionResult success(String message, Integer promotedCount) { + return new PromotionResult(true, message, promotedCount); + } + + public static PromotionResult failure(String message) { + return new PromotionResult(false, message, 0); + } + + // Getters and Setters + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Integer getPromotedCount() { + return promotedCount; + } + + public void setPromotedCount(Integer promotedCount) { + this.promotedCount = promotedCount; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerApplicationResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerApplicationResponse.java index 4942a01..1651395 100644 --- a/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerApplicationResponse.java +++ b/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerApplicationResponse.java @@ -4,6 +4,7 @@ private Long id; // activity_player_id private String playerName; private String activityName; + private String projectName; // 椤圭洰鍚嶇О private String phone; private String applyTime; // ISO瀛楃涓� private Integer state; @@ -17,6 +18,9 @@ public String getActivityName() { return activityName; } public void setActivityName(String activityName) { this.activityName = activityName; } + public String getProjectName() { return projectName; } + public void setProjectName(String projectName) { this.projectName = projectName; } + public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerDetailResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerDetailResponse.java index 890437e..f06a247 100644 --- a/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerDetailResponse.java +++ b/backend/src/main/java/com/rongyichuang/player/dto/response/ActivityPlayerDetailResponse.java @@ -10,7 +10,10 @@ private PlayerInfoResponse playerInfo; // 瀛﹀憳淇℃伅 private RegionInfoResponse regionInfo; // 鍖哄煙淇℃伅 private String activityName; // 姣旇禌鍚嶇О + private String projectName; // 椤圭洰鍚嶇О private String description; // 鍙傝禌椤圭洰绠�浠� + private String feedback; // 瀹℃牳鎰忚 + private Integer state; // 瀹℃牳鐘舵�侊細0=鏈鏍革紝1=閫氳繃锛�2=椹冲洖 private List<SubmissionMediaResponse> submissionFiles; // 鎻愪氦鐨勮祫鏂� private RatingFormResponse ratingForm; // 璇勫垎琛ㄥ崟 @@ -30,9 +33,18 @@ public String getActivityName() { return activityName; } public void setActivityName(String activityName) { this.activityName = activityName; } + public String getProjectName() { return projectName; } + public void setProjectName(String projectName) { this.projectName = projectName; } + public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } + public String getFeedback() { return feedback; } + public void setFeedback(String feedback) { this.feedback = feedback; } + + public Integer getState() { return state; } + public void setState(Integer state) { this.state = state; } + public List<SubmissionMediaResponse> getSubmissionFiles() { return submissionFiles; } public void setSubmissionFiles(List<SubmissionMediaResponse> submissionFiles) { this.submissionFiles = submissionFiles; } diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/JudgeRatingStatusResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/JudgeRatingStatusResponse.java index 0782aa9..dac2ebe 100644 --- a/backend/src/main/java/com/rongyichuang/player/dto/response/JudgeRatingStatusResponse.java +++ b/backend/src/main/java/com/rongyichuang/player/dto/response/JudgeRatingStatusResponse.java @@ -9,19 +9,19 @@ private Long judgeId; private String judgeName; + private Boolean hasRated; + private String ratingTime; private BigDecimal totalScore; - private Integer status; - private Boolean isCurrentJudge; public JudgeRatingStatusResponse() {} - public JudgeRatingStatusResponse(Long judgeId, String judgeName, BigDecimal totalScore, - Integer status, Boolean isCurrentJudge) { + public JudgeRatingStatusResponse(Long judgeId, String judgeName, Boolean hasRated, + String ratingTime, BigDecimal totalScore) { this.judgeId = judgeId; this.judgeName = judgeName; + this.hasRated = hasRated; + this.ratingTime = ratingTime; this.totalScore = totalScore; - this.status = status; - this.isCurrentJudge = isCurrentJudge; } public Long getJudgeId() { @@ -40,27 +40,27 @@ this.judgeName = judgeName; } + public Boolean getHasRated() { + return hasRated; + } + + public void setHasRated(Boolean hasRated) { + this.hasRated = hasRated; + } + + public String getRatingTime() { + return ratingTime; + } + + public void setRatingTime(String ratingTime) { + this.ratingTime = ratingTime; + } + public BigDecimal getTotalScore() { return totalScore; } public void setTotalScore(BigDecimal totalScore) { this.totalScore = totalScore; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public Boolean getIsCurrentJudge() { - return isCurrentJudge; - } - - public void setIsCurrentJudge(Boolean isCurrentJudge) { - this.isCurrentJudge = isCurrentJudge; } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerInfoResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerInfoResponse.java index ea7ee50..cb88e4a 100644 --- a/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerInfoResponse.java +++ b/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerInfoResponse.java @@ -1,5 +1,7 @@ package com.rongyichuang.player.dto.response; +import com.rongyichuang.common.dto.response.MediaResponse; + /** * 瀛﹀憳淇℃伅鍝嶅簲 */ @@ -7,8 +9,13 @@ private Long id; private String name; private String phone; + private Integer gender; // 鎬у埆 + private String birthday; // 鍑虹敓鏃ユ湡 + private String education; // 瀛﹀巻 + private String introduction; // 涓汉浠嬬粛 private String description; // 绠�浠� - private String avatarUrl; // 澶村儚URL锛堜粠 t_media 鑾峰彇锛屼娇鐢� MediaTargetType.STUDENT_AVATAR锛屽�间负6锛� + private String avatarUrl; // 澶村儚URL锛堜粠 t_media 鑾峰彇锛屼娇鐢� MediaTargetType.USER_AVATAR锛屽�间负7锛� + private MediaResponse avatar; // 澶村儚Media瀵硅薄 // Constructors public PlayerInfoResponse() {} @@ -23,9 +30,24 @@ public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } + public Integer getGender() { return gender; } + public void setGender(Integer gender) { this.gender = gender; } + + public String getBirthday() { return birthday; } + public void setBirthday(String birthday) { this.birthday = birthday; } + + public String getEducation() { return education; } + public void setEducation(String education) { this.education = education; } + + public String getIntroduction() { return introduction; } + public void setIntroduction(String introduction) { this.introduction = introduction; } + public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getAvatarUrl() { return avatarUrl; } public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; } + + public MediaResponse getAvatar() { return avatar; } + public void setAvatar(MediaResponse avatar) { this.avatar = avatar; } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/SubmissionMediaResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/SubmissionMediaResponse.java index d387fde..d534642 100644 --- a/backend/src/main/java/com/rongyichuang/player/dto/response/SubmissionMediaResponse.java +++ b/backend/src/main/java/com/rongyichuang/player/dto/response/SubmissionMediaResponse.java @@ -10,6 +10,7 @@ private String fileExt; // 鏂囦欢鎵╁睍鍚� private Long fileSize; // 鏂囦欢澶у皬 private Integer mediaType; // 濯掍綋绫诲瀷锛�1=鍥剧墖锛�2=瑙嗛锛�3=鏂囨。绛夛級 + private String thumbUrl; // 缂╃暐鍥綰RL锛堣棰戝皝闈㈠浘锛� // Constructors public SubmissionMediaResponse() {} @@ -32,4 +33,7 @@ public Integer getMediaType() { return mediaType; } public void setMediaType(Integer mediaType) { this.mediaType = mediaType; } + + public String getThumbUrl() { return thumbUrl; } + public void setThumbUrl(String thumbUrl) { this.thumbUrl = thumbUrl; } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/entity/ActivityPlayer.java b/backend/src/main/java/com/rongyichuang/player/entity/ActivityPlayer.java index 6d8f241..7cc8f7b 100644 --- a/backend/src/main/java/com/rongyichuang/player/entity/ActivityPlayer.java +++ b/backend/src/main/java/com/rongyichuang/player/entity/ActivityPlayer.java @@ -13,7 +13,6 @@ */ @Entity @Table(name = "t_activity_player") -@Where(clause = "state = 1") public class ActivityPlayer extends BaseEntity { /** @@ -83,10 +82,10 @@ private Integer rank; /** - * 鐘舵�侊細1-姝e父锛�0-鍒犻櫎 + * 鐘舵�侊細0-鏈鏍革紝1-瀹℃牳閫氳繃锛�2-瀹℃牳椹冲洖 */ @Column(name = "state", nullable = false) - private Integer state = 1; + private Integer state = 0; // JPA鍏宠仈鍏崇郴 /** diff --git a/backend/src/main/java/com/rongyichuang/player/repository/ActivityPlayerRepository.java b/backend/src/main/java/com/rongyichuang/player/repository/ActivityPlayerRepository.java index 588707e..cd84782 100644 --- a/backend/src/main/java/com/rongyichuang/player/repository/ActivityPlayerRepository.java +++ b/backend/src/main/java/com/rongyichuang/player/repository/ActivityPlayerRepository.java @@ -90,7 +90,26 @@ List<ActivityPlayer> findTopRankedPlayers(@Param("activityId") Long activityId); /** - * 鏍规嵁鐘舵�佺粺璁℃姤鍚嶆暟閲� + * 缁熻鎸囧畾鐘舵�佺殑鍙傝禌閫夋墜鏁伴噺 */ long countByState(Integer state); + + /** + * 鏍规嵁闃舵ID鍜岀姸鎬佺粺璁″弬璧涢�夋墜鏁伴噺 + */ + Long countByStageIdAndState(Long stageId, Integer state); + + /** + * 鏍规嵁闃舵ID鍜岀姸鎬佹煡鎵惧弬璧涢�夋墜锛堝寘鍚�夋墜淇℃伅锛� + */ + @Query("SELECT ap FROM ActivityPlayer ap " + + "LEFT JOIN FETCH ap.player p " + + "WHERE ap.stageId = :stageId AND ap.state = :state " + + "ORDER BY ap.createTime DESC") + List<ActivityPlayer> findByStageIdAndStateWithPlayerOrderByCreateTimeDesc(@Param("stageId") Long stageId, @Param("state") Integer state); + + /** + * 妫�鏌ラ�夋墜鏄惁宸插湪鎸囧畾闃舵鎶ュ悕 + */ + boolean existsByStageIdAndPlayerId(Long stageId, Long playerId); } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java index 0eb0337..5f56ef0 100644 --- a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java +++ b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java @@ -8,14 +8,20 @@ import com.rongyichuang.common.entity.Media; import com.rongyichuang.common.enums.MediaTargetType; import com.rongyichuang.common.repository.MediaRepository; +import com.rongyichuang.common.dto.response.MediaResponse; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; import java.util.stream.Collectors; /** @@ -31,6 +37,9 @@ private final MediaRepository mediaRepository; private final RatingSchemeRepository ratingSchemeRepository; + + @Value("${app.media-url}") + private String mediaBaseUrl; public ActivityPlayerDetailService(MediaRepository mediaRepository, RatingSchemeRepository ratingSchemeRepository) { @@ -48,60 +57,141 @@ // 鏌ヨ鍩烘湰淇℃伅锛屽寘鍚尯鍩熶俊鎭� String sql = """ - SELECT ap.id, ap.description, ap.activity_id, ap.region_id, + SELECT ap.id as ap_id, ap.description as ap_description, ap.activity_id, ap.region_id, + ap.project_name, ap.feedback, ap.state as ap_state, p.id as player_id, p.name as player_name, p.phone, p.description as player_desc, + p.gender, u.birthday, p.education, p.introduction, u.id as user_id, a.name as activity_name, a.rating_scheme_id, - r.id as r_id, r.name as region_name, r.full_path as region_path + r.id as region_id, r.name as region_name, r.full_path as region_path FROM t_activity_player ap JOIN t_player p ON p.id = ap.player_id + JOIN t_user u ON u.id = p.user_id JOIN t_activity a ON a.id = ap.activity_id LEFT JOIN t_region r ON r.id = ap.region_id WHERE ap.id = ? """; @SuppressWarnings("unchecked") - List<Object[]> results = em.createNativeQuery(sql) - .setParameter(1, activityPlayerId) - .getResultList(); + Query query = em.createNativeQuery(sql); + query.setParameter(1, activityPlayerId); + + // 浣跨敤 Tuple 鏉ヨ幏鍙栧懡鍚嶅瓧娈� + query.unwrap(org.hibernate.query.NativeQuery.class).setTupleTransformer( + (tuple, aliases) -> { + Map<String, Object> result = new HashMap<>(); + for (int i = 0; i < aliases.length; i++) { + result.put(aliases[i], tuple[i]); + } + return result; + } + ); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> results = query.getResultList(); if (results.isEmpty()) { log.warn("鏈壘鍒版姤鍚嶈褰曪紝activityPlayerId: {}", activityPlayerId); return null; } - Object[] row = results.get(0); + Map<String, Object> row = results.get(0); ActivityPlayerDetailResponse response = new ActivityPlayerDetailResponse(); response.setId(activityPlayerId); - response.setDescription(row[1] != null ? row[1].toString() : ""); - response.setActivityName(row[8] != null ? row[8].toString() : ""); + response.setDescription(row.get("ap_description") != null ? row.get("ap_description").toString() : ""); + response.setProjectName(row.get("project_name") != null ? row.get("project_name").toString() : ""); + response.setFeedback(row.get("feedback") != null ? row.get("feedback").toString() : ""); + + Object stateObj = row.get("ap_state"); + if (stateObj != null) { + if (stateObj instanceof Number) { + response.setState(((Number) stateObj).intValue()); + } else { + log.warn("鐘舵�佺被鍨嬩笉鍖归厤: {}, 绫诲瀷: {}", stateObj, stateObj.getClass().getName()); + response.setState(Integer.valueOf(stateObj.toString())); + } + } else { + response.setState(0); + } + response.setActivityName(row.get("activity_name") != null ? row.get("activity_name").toString() : ""); // 鏋勫缓瀛﹀憳淇℃伅 PlayerInfoResponse playerInfo = new PlayerInfoResponse(); - playerInfo.setId(row[4] != null ? ((Number) row[4]).longValue() : null); - playerInfo.setName(row[5] != null ? row[5].toString() : ""); - playerInfo.setPhone(row[6] != null ? row[6].toString() : ""); - playerInfo.setDescription(row[7] != null ? row[7].toString() : ""); + Object playerIdObj = row.get("player_id"); + if (playerIdObj != null) { + if (playerIdObj instanceof Number) { + playerInfo.setId(((Number) playerIdObj).longValue()); + } else { + log.warn("瀛﹀憳ID绫诲瀷涓嶅尮閰�: {}, 绫诲瀷: {}", playerIdObj, playerIdObj.getClass().getName()); + playerInfo.setId(Long.valueOf(playerIdObj.toString())); + } + } else { + playerInfo.setId(null); + } + playerInfo.setName(row.get("player_name") != null ? row.get("player_name").toString() : ""); + playerInfo.setPhone(row.get("phone") != null ? row.get("phone").toString() : ""); + playerInfo.setDescription(row.get("player_desc") != null ? row.get("player_desc").toString() : ""); + + Object genderObj = row.get("gender"); + if (genderObj != null) { + if (genderObj instanceof Number) { + playerInfo.setGender(((Number) genderObj).intValue()); + } else { + log.warn("鎬у埆绫诲瀷涓嶅尮閰�: {}, 绫诲瀷: {}", genderObj, genderObj.getClass().getName()); + playerInfo.setGender(Integer.valueOf(genderObj.toString())); + } + } else { + playerInfo.setGender(null); + } + + Object birthdayObj = row.get("birthday"); + playerInfo.setBirthday(birthdayObj != null ? + (birthdayObj instanceof java.sql.Date ? ((java.sql.Date) birthdayObj).toString() : birthdayObj.toString()) : null); + playerInfo.setEducation(row.get("education") != null ? row.get("education").toString() : ""); + playerInfo.setIntroduction(row.get("introduction") != null ? row.get("introduction").toString() : ""); // 鏋勫缓鍖哄煙淇℃伅 - RegionInfoResponse regionInfo = new RegionInfoResponse(); - if (row[10] != null) { - regionInfo.setId(((Number) row[10]).longValue()); - regionInfo.setName(row[11] != null ? row[11].toString() : ""); - regionInfo.setFullPath(row[12] != null ? row[12].toString() : ""); + Object regionIdObj = row.get("region_id"); + if (regionIdObj != null) { + RegionInfoResponse regionInfo = new RegionInfoResponse(); + if (regionIdObj instanceof Number) { + regionInfo.setId(((Number) regionIdObj).longValue()); + } else { + log.warn("鍖哄煙ID绫诲瀷涓嶅尮閰�: {}, 绫诲瀷: {}", regionIdObj, regionIdObj.getClass().getName()); + regionInfo.setId(Long.valueOf(regionIdObj.toString())); + } + regionInfo.setName(row.get("region_name") != null ? row.get("region_name").toString() : ""); + regionInfo.setFullPath(row.get("region_path") != null ? row.get("region_path").toString() : ""); response.setRegionInfo(regionInfo); log.info("鎵惧埌鍖哄煙淇℃伅: {}", regionInfo.getName()); } - // 鏌ヨ瀛﹀憳澶村儚锛堜娇鐢ㄦ灇涓惧父閲忚〃绀哄鍛樺ご鍍忕被鍨嬶級 - if (playerInfo.getId() != null) { + // 鏌ヨ鐢ㄦ埛澶村儚锛堜娇鐢║SER_AVATAR鍏宠仈鍒扮敤鎴凤級 + Object userIdObj = row.get("user_id"); + log.info("璋冭瘯锛氫粠鏌ヨ缁撴灉涓幏鍙栫殑user_id: {}", userIdObj); + if (userIdObj != null) { + Long userId; + if (userIdObj instanceof Number) { + userId = ((Number) userIdObj).longValue(); + } else { + userId = Long.valueOf(userIdObj.toString()); + } + log.info("璋冭瘯锛氳В鏋愬悗鐨剈serId: {}", userId); + List<Media> avatarMedias = mediaRepository.findByTargetTypeAndTargetIdAndState( - MediaTargetType.STUDENT_AVATAR.getValue(), playerInfo.getId(), 1); + MediaTargetType.USER_AVATAR.getValue(), userId, 1); + log.info("璋冭瘯锛氭煡璇㈠埌鐨勫ご鍍忓獟浣撴暟閲�: {}", avatarMedias.size()); if (!avatarMedias.isEmpty()) { Media avatar = avatarMedias.get(0); - String avatarUrl = avatar.getPath(); + String avatarUrl = buildFullMediaUrl(avatar.getPath()); playerInfo.setAvatarUrl(avatarUrl); - log.info("鎵惧埌瀛﹀憳澶村儚: {}", avatarUrl); + // 璁剧疆avatar瀵硅薄 + playerInfo.setAvatar(convertToMediaResponse(avatar)); + log.info("鎵惧埌鐢ㄦ埛澶村儚: {}", avatarUrl); + } else { + log.info("璋冭瘯锛氭湭鎵惧埌鐢ㄦ埛澶村儚锛寀serId: {}, targetType: {}", userId, MediaTargetType.USER_AVATAR.getValue()); } + } else { + log.warn("璋冭瘯锛歶ser_id涓簄ull"); } response.setPlayerInfo(playerInfo); @@ -115,7 +205,8 @@ log.info("鎵惧埌鎻愪氦璧勬枡 {} 涓�", submissionFiles.size()); // 鏌ヨ璇勫垎妯℃澘 - Long ratingSchemeId = row[9] != null ? ((Number) row[9]).longValue() : null; + Object ratingSchemeIdObj = row.get("rating_scheme_id"); + Long ratingSchemeId = ratingSchemeIdObj != null ? ((Number) ratingSchemeIdObj).longValue() : null; if (ratingSchemeId != null) { RatingFormResponse ratingForm = buildRatingForm(ratingSchemeId); response.setRatingForm(ratingForm); @@ -131,10 +222,11 @@ SubmissionMediaResponse response = new SubmissionMediaResponse(); response.setId(media.getId()); response.setName(media.getName()); - response.setUrl(media.getPath()); + response.setUrl(buildFullMediaUrl(media.getPath())); response.setFileExt(media.getFileExt()); response.setFileSize(media.getFileSize() != null ? media.getFileSize().longValue() : null); response.setMediaType(media.getMediaType()); + response.setThumbUrl(buildFullMediaUrl(media.getThumbPath())); return response; } @@ -168,4 +260,49 @@ return response; } + + /** + * 杞崲濯掍綋鏂囦欢涓篗ediaResponse + */ + private MediaResponse convertToMediaResponse(Media media) { + MediaResponse response = new MediaResponse(); + response.setId(media.getId()); + response.setName(media.getName()); + response.setPath(media.getPath()); + response.setFileSize(media.getFileSize()); + response.setFileExt(media.getFileExt()); + response.setThumbPath(media.getThumbPath()); + response.setDuration(media.getDuration()); + response.setDescription(media.getDescription()); + response.setTargetType(media.getTargetType()); + response.setTargetId(media.getTargetId()); + response.setMediaType(media.getMediaType()); + + // 璁剧疆瀹屾暣URL + response.setFullUrl(buildFullMediaUrl(media.getPath())); + response.setFullThumbUrl(buildFullMediaUrl(media.getThumbPath())); + + return response; + } + + private String buildFullMediaUrl(String path) { + if (!StringUtils.hasText(path)) { + return null; + } + + // 濡傛灉宸茬粡鏄畬鏁碪RL锛岀洿鎺ヨ繑鍥� + if (path.startsWith("http://") || path.startsWith("https://")) { + return path; + } + + // 濡傛灉娌℃湁閰嶇疆mediaBaseUrl锛岃繑鍥炲師璺緞 + if (!StringUtils.hasText(mediaBaseUrl)) { + return path; + } + + // 鎷兼帴瀹屾暣URL + String baseUrl = mediaBaseUrl.endsWith("/") ? mediaBaseUrl : mediaBaseUrl + "/"; + String relativePath = path.startsWith("/") ? path.substring(1) : path; + return baseUrl + relativePath; + } } \ 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 e26369e..6726e7c 100644 --- a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java +++ b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerRatingService.java @@ -263,18 +263,20 @@ Optional<ActivityPlayerRating> ratingOpt = activityPlayerRatingRepository .findByActivityPlayerIdAndJudgeId(activityPlayerId, judgeId); + Boolean hasRated = false; + String ratingTime = null; BigDecimal totalScore = null; - Integer status = 0; if (ratingOpt.isPresent()) { ActivityPlayerRating rating = ratingOpt.get(); + hasRated = rating.getState() != null && rating.getState() == 1; // 浣跨敤state鍒ゆ柇鏄惁宸茶瘎鍒� totalScore = rating.getTotalScore(); - status = rating.getState(); + if (rating.getUpdateTime() != null) { + ratingTime = rating.getUpdateTime().toString(); + } } - Boolean isCurrentJudge = judgeId.equals(currentJudgeId); - - return new JudgeRatingStatusResponse(judgeId, judgeName, totalScore, status, isCurrentJudge); + return new JudgeRatingStatusResponse(judgeId, judgeName, hasRated, ratingTime, totalScore); }).collect(java.util.stream.Collectors.toList()); } diff --git a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java index 85464e6..9c2a940 100644 --- a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java +++ b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java @@ -8,6 +8,8 @@ import com.rongyichuang.player.entity.Player; import com.rongyichuang.player.repository.ActivityPlayerRepository; import com.rongyichuang.player.repository.PlayerRepository; +import com.rongyichuang.activity.repository.ActivityRepository; +import com.rongyichuang.activity.entity.Activity; import com.rongyichuang.common.entity.Media; import com.rongyichuang.common.repository.MediaRepository; import com.rongyichuang.common.enums.MediaTargetType; @@ -40,6 +42,9 @@ @Autowired private PlayerRepository playerRepository; + + @Autowired + private ActivityRepository activityRepository; @Autowired private MediaRepository mediaRepository; @@ -99,11 +104,24 @@ } log.info("鏈彂鐜伴噸澶嶆姤鍚�"); - // 4. 鍒涘缓鎶ュ悕璁板綍 + // 4. 鏌ユ壘绗竴闃舵锛屽鏋滄病鏈夊垯浣跨敤娲诲姩鏈韩 + log.info("鏌ユ壘娲诲姩鐨勭涓�闃舵锛屾椿鍔↖D: {}", input.getActivityId()); + Activity firstStage = activityRepository.findFirstStageByActivityId(input.getActivityId()); + Long stageId; + if (firstStage != null) { + stageId = firstStage.getId(); + log.info("鎵惧埌绗竴闃舵锛岄樁娈礗D: {}, 闃舵鍚嶇О: {}", firstStage.getId(), firstStage.getName()); + } else { + // 濡傛灉娌℃湁鎵惧埌绗竴闃舵锛屼娇鐢ㄦ椿鍔ㄦ湰韬綔涓洪樁娈� + stageId = input.getActivityId(); + log.info("鏈壘鍒扮涓�闃舵锛屼娇鐢ㄦ椿鍔ㄦ湰韬綔涓洪樁娈碉紝娲诲姩ID: {}", input.getActivityId()); + } + + // 5. 鍒涘缓鎶ュ悕璁板綍 log.info("寮�濮嬪垱寤烘姤鍚嶈褰�"); ActivityPlayer activityPlayer = new ActivityPlayer(); activityPlayer.setActivityId(input.getActivityId()); - activityPlayer.setStageId(input.getActivityId()); // 鏍规嵁鏂囨。锛氬鏋滄瘮璧涙湭瀹氫箟闃舵锛宻tage_id璁句负activity_id + activityPlayer.setStageId(stageId); // 缁戝畾鍒扮涓�闃舵鎴栨椿鍔ㄦ湰韬� activityPlayer.setPlayerId(player.getId()); activityPlayer.setRegionId(input.getRegionId()); activityPlayer.setProjectName(input.getProjectName()); // 璁剧疆椤圭洰鍚嶇О @@ -114,18 +132,19 @@ ActivityPlayer savedActivityPlayer = activityPlayerRepository.save(activityPlayer); log.info("鎶ュ悕璁板綍鍒涘缓鎴愬姛锛孖D: {}", savedActivityPlayer.getId()); - // 5. 淇濆瓨鍏朵粬濯掍綋鏂囦欢锛堝吋瀹规棫鐗堟湰锛� + // 6. 淇濆瓨鍏朵粬濯掍綋鏂囦欢锛堝吋瀹规棫鐗堟湰锛� if (input.getMediaFiles() != null && !input.getMediaFiles().isEmpty()) { saveMediaFiles(savedActivityPlayer.getId(), input.getMediaFiles()); } - // 6. 淇濆瓨澶村儚濯掍綋璁板綍 + // 7. 淇濆瓨澶村儚濯掍綋璁板綍 if (input.getPlayerInfo().getAvatarMediaId() != null && !input.getPlayerInfo().getAvatarMediaId().trim().isEmpty()) { savePlayerAvatarMedia(player.getId(), input.getPlayerInfo().getAvatarMediaId()); } - // 7. 淇濆瓨闄勪欢濯掍綋璁板綍 + // 8. 淇濆瓨闄勪欢濯掍綋璁板綍 if (input.getAttachmentMediaIds() != null && !input.getAttachmentMediaIds().isEmpty()) { + log.info("寮�濮嬩繚瀛橀檮浠跺獟浣撹褰曪紝鎶ュ悕ID: {}, 闄勪欢鏁伴噺: {}", savedActivityPlayer.getId(), input.getAttachmentMediaIds().size()); saveAttachmentMediaRecords(savedActivityPlayer.getId(), input.getAttachmentMediaIds()); } @@ -329,26 +348,234 @@ } /** - * 淇濆瓨瀛﹀憳澶村儚 - * 鍙傝�僯udge妯″潡鐨勫疄鐜帮紝灏嗗凡涓婁紶鐨勫ご鍍忓獟浣撴枃浠跺叧鑱斿埌瀛﹀憳 + * 瀹℃牳閫氳繃 + */ + public Boolean approveActivityPlayer(Long activityPlayerId, String feedback) { + try { + Optional<ActivityPlayer> activityPlayerOpt = activityPlayerRepository.findById(activityPlayerId); + if (!activityPlayerOpt.isPresent()) { + throw new RuntimeException("鎶ュ悕璁板綍涓嶅瓨鍦�"); + } + + ActivityPlayer activityPlayer = activityPlayerOpt.get(); + activityPlayer.setState(1); // 1=瀹℃牳閫氳繃 + activityPlayer.setFeedback(feedback); + activityPlayerRepository.save(activityPlayer); + + log.info("瀹℃牳閫氳繃鎴愬姛锛宎ctivityPlayerId: {}", activityPlayerId); + return true; + } catch (Exception e) { + log.error("瀹℃牳閫氳繃澶辫触锛宎ctivityPlayerId: {}", activityPlayerId, e); + throw new RuntimeException("瀹℃牳閫氳繃澶辫触", e); + } + } + + /** + * 瀹℃牳椹冲洖 + */ + public Boolean rejectActivityPlayer(Long activityPlayerId, String feedback) { + try { + Optional<ActivityPlayer> activityPlayerOpt = activityPlayerRepository.findById(activityPlayerId); + if (!activityPlayerOpt.isPresent()) { + throw new RuntimeException("鎶ュ悕璁板綍涓嶅瓨鍦�"); + } + + ActivityPlayer activityPlayer = activityPlayerOpt.get(); + activityPlayer.setState(2); // 2=瀹℃牳椹冲洖 + activityPlayer.setFeedback(feedback); + activityPlayerRepository.save(activityPlayer); + + log.info("瀹℃牳椹冲洖鎴愬姛锛宎ctivityPlayerId: {}", activityPlayerId); + return true; + } catch (Exception e) { + log.error("瀹℃牳椹冲洖澶辫触锛宎ctivityPlayerId: {}", activityPlayerId, e); + throw new RuntimeException("瀹℃牳椹冲洖澶辫触", e); + } + } + + /** + * 鏇存柊鎶ュ悕淇℃伅 + */ + public ActivityRegistrationResponse updateActivityRegistration(Long activityPlayerId, ActivityRegistrationInput input) { + try { + log.info("寮�濮嬫洿鏂版姤鍚嶄俊鎭紝鎶ュ悕ID: {}", activityPlayerId); + + // 1. 鏌ユ壘鐜版湁鎶ュ悕璁板綍 + Optional<ActivityPlayer> activityPlayerOpt = activityPlayerRepository.findById(activityPlayerId); + if (!activityPlayerOpt.isPresent()) { + throw new RuntimeException("鎶ュ悕璁板綍涓嶅瓨鍦�"); + } + + ActivityPlayer activityPlayer = activityPlayerOpt.get(); + + // 2. 鏇存柊閫夋墜淇℃伅 + Player player = playerRepository.findById(activityPlayer.getPlayerId()).orElse(null); + if (player != null) { + player.setName(input.getPlayerInfo().getName()); + if (input.getPlayerInfo().getGender() != null) { + player.setGender(input.getPlayerInfo().getGender()); + } + if (input.getPlayerInfo().getEducation() != null) { + player.setEducation(input.getPlayerInfo().getEducation()); + } + if (input.getPlayerInfo().getIntroduction() != null) { + player.setIntroduction(input.getPlayerInfo().getIntroduction()); + } + if (input.getPlayerInfo().getDescription() != null) { + player.setDescription(input.getPlayerInfo().getDescription()); + } + playerRepository.save(player); + + // 鏇存柊鐢ㄦ埛淇℃伅 + updateUserInfo(player, input); + } + + // 3. 鏇存柊鎶ュ悕璁板綍鍩烘湰淇℃伅 + if (input.getProjectName() != null) { + activityPlayer.setProjectName(input.getProjectName()); + } + if (input.getDescription() != null) { + activityPlayer.setDescription(input.getDescription()); + } + if (input.getRegionId() != null) { + activityPlayer.setRegionId(input.getRegionId()); + } + + activityPlayerRepository.save(activityPlayer); + + // 4. 澶勭悊澶村儚鏇存柊 + if (input.getPlayerInfo().getAvatarMediaId() != null && !input.getPlayerInfo().getAvatarMediaId().isEmpty()) { + // 淇濆瓨鏂板ご鍍� + savePlayerAvatarMedia(player.getId(), input.getPlayerInfo().getAvatarMediaId()); + } + + // 5. 澶勭悊姣旇禌鍥剧墖鍜岃棰戞洿鏂� + if (input.getMediaFiles() != null) { + // 鍒犻櫎鏃х殑姣旇禌鍥剧墖鍜岃棰戣褰� + deleteOldSubmissionMedia(activityPlayerId); + // 淇濆瓨鏂扮殑姣旇禌鍥剧墖鍜岃棰� + saveMediaFiles(activityPlayerId, input.getMediaFiles()); + } + + // 6. 澶勭悊闄勪欢鏇存柊 + if (input.getAttachmentMediaIds() != null) { + // 鍒犻櫎鏃ч檮浠惰褰� + deleteOldAttachmentMedia(activityPlayerId); + // 淇濆瓨鏂伴檮浠� + saveAttachmentMediaRecords(activityPlayerId, input.getAttachmentMediaIds()); + } + + log.info("鏇存柊鎶ュ悕淇℃伅鎴愬姛锛屾姤鍚岻D: {}", activityPlayerId); + + ActivityRegistrationResponse response = new ActivityRegistrationResponse(); + response.setSuccess(true); + response.setMessage("鏇存柊鎶ュ悕淇℃伅鎴愬姛"); + response.setRegistrationId(activityPlayerId); + response.setPlayerId(player.getId()); + response.setUserId(player.getUserId()); + response.setActivityPlayerId(activityPlayerId); + + return response; + + } catch (Exception e) { + log.error("鏇存柊鎶ュ悕淇℃伅澶辫触锛屾姤鍚岻D: {}", activityPlayerId, e); + throw new RuntimeException("鏇存柊鎶ュ悕淇℃伅澶辫触: " + e.getMessage(), e); + } + } + + + + /** + * 鍒犻櫎鏃х殑姣旇禌鍥剧墖鍜岃棰戣褰� + */ + private void deleteOldSubmissionMedia(Long activityPlayerId) { + try { + List<Media> oldSubmissionMedia = mediaRepository.findByTargetTypeAndTargetIdAndState( + MediaTargetType.ACTIVITY_PLAYER_SUBMISSION.getValue(), activityPlayerId, 1); + for (Media media : oldSubmissionMedia) { + media.setState(0); // 璁剧疆涓哄垹闄ょ姸鎬� + mediaRepository.save(media); + } + log.info("鍒犻櫎鏃ф瘮璧涘浘鐗�/瑙嗛璁板綍鎴愬姛锛屾姤鍚岻D: {}, 鍒犻櫎鏁伴噺: {}", activityPlayerId, oldSubmissionMedia.size()); + } catch (Exception e) { + log.error("鍒犻櫎鏃ф瘮璧涘浘鐗�/瑙嗛璁板綍澶辫触锛屾姤鍚岻D: {}", activityPlayerId, e); + } + } + + /** + * 鍒犻櫎鏃х殑闄勪欢濯掍綋璁板綍 + */ + private void deleteOldAttachmentMedia(Long activityPlayerId) { + try { + List<Media> oldAttachments = mediaRepository.findByTargetTypeAndTargetIdAndState( + 5, activityPlayerId, 1); // target_type=5 鏄檮浠� + for (Media oldAttachment : oldAttachments) { + oldAttachment.setState(0); // 璁剧疆涓哄垹闄ょ姸鎬� + mediaRepository.save(oldAttachment); + } + log.info("鍒犻櫎鏃ч檮浠惰褰曟垚鍔燂紝鎶ュ悕ID: {}, 鍒犻櫎鏁伴噺: {}", activityPlayerId, oldAttachments.size()); + } catch (Exception e) { + log.error("鍒犻櫎鏃ч檮浠惰褰曞け璐ワ紝鎶ュ悕ID: {}", activityPlayerId, e); + } + } + + /** + * 鏇存柊瀹℃牳鎰忚 + */ + public Boolean updateFeedback(Long activityPlayerId, String feedback) { + try { + Optional<ActivityPlayer> activityPlayerOpt = activityPlayerRepository.findById(activityPlayerId); + if (!activityPlayerOpt.isPresent()) { + throw new RuntimeException("鎶ュ悕璁板綍涓嶅瓨鍦�"); + } + + ActivityPlayer activityPlayer = activityPlayerOpt.get(); + activityPlayer.setFeedback(feedback); + activityPlayerRepository.save(activityPlayer); + + log.info("鏇存柊瀹℃牳鎰忚鎴愬姛锛宎ctivityPlayerId: {}", activityPlayerId); + return true; + } catch (Exception e) { + log.error("鏇存柊瀹℃牳鎰忚澶辫触锛宎ctivityPlayerId: {}", activityPlayerId, e); + throw new RuntimeException("鏇存柊瀹℃牳鎰忚澶辫触", e); + } + } + + /** + * 淇濆瓨鐢ㄦ埛澶村儚 + * 灏嗗凡涓婁紶鐨勫ご鍍忓獟浣撴枃浠跺叧鑱斿埌鐢ㄦ埛 */ private void savePlayerAvatar(Long playerId, Long avatarMediaId) { try { - // 鏌ユ壘鐜版湁鐨勫ご鍍忚褰曞苟鍒犻櫎锛堢‘淇濅竴涓鍛樺彧鏈変竴涓ご鍍忥級 - mediaRepository.deleteByTargetTypeAndTargetId(MediaTargetType.STUDENT_AVATAR.getValue(), playerId); + // 鑾峰彇player瀵瑰簲鐨勭敤鎴稩D + Optional<Player> playerOpt = playerRepository.findById(playerId); + if (!playerOpt.isPresent()) { + log.warn("鏈壘鍒板鍛樿褰曪紝瀛﹀憳ID: {}", playerId); + return; + } + + Player player = playerOpt.get(); + Long userId = player.getUserId(); + if (userId == null) { + log.warn("瀛﹀憳鏈叧鑱旂敤鎴凤紝瀛﹀憳ID: {}", playerId); + return; + } + + // 鏌ユ壘鐜版湁鐨勫ご鍍忚褰曞苟鍒犻櫎锛堢‘淇濅竴涓敤鎴峰彧鏈変竴涓ご鍍忥級 + mediaRepository.deleteByTargetTypeAndTargetId(MediaTargetType.USER_AVATAR.getValue(), userId); // 鏇存柊濯掍綋鏂囦欢鐨則arget淇℃伅 Media avatarMedia = mediaRepository.findById(avatarMediaId).orElse(null); if (avatarMedia != null) { - avatarMedia.setTargetType(MediaTargetType.STUDENT_AVATAR.getValue()); - avatarMedia.setTargetId(playerId); + avatarMedia.setTargetType(MediaTargetType.USER_AVATAR.getValue()); + avatarMedia.setTargetId(userId); mediaRepository.save(avatarMedia); - log.info("瀛﹀憳澶村儚淇濆瓨鎴愬姛锛屽鍛業D: {}, 濯掍綋ID: {}", playerId, avatarMediaId); + log.info("鐢ㄦ埛澶村儚淇濆瓨鎴愬姛锛岀敤鎴稩D: {}, 濯掍綋ID: {}", userId, avatarMediaId); } else { log.warn("鏈壘鍒板ご鍍忓獟浣撴枃浠讹紝濯掍綋ID: {}", avatarMediaId); } } catch (Exception e) { - log.error("淇濆瓨瀛﹀憳澶村儚鏃跺彂鐢熼敊璇紝瀛﹀憳ID: {}, 濯掍綋ID: {}", playerId, avatarMediaId, e); + log.error("淇濆瓨鐢ㄦ埛澶村儚鏃跺彂鐢熼敊璇紝瀛﹀憳ID: {}, 濯掍綋ID: {}", playerId, avatarMediaId, e); } } @@ -385,7 +612,7 @@ MediaSaveInput input = new MediaSaveInput(); input.setTargetType("player"); input.setTargetId(playerId); - input.setUrl(avatarMediaId); // COS璺緞 + input.setPath(avatarMediaId); // COS璺緞 input.setFileName("avatar.jpg"); // 榛樿澶村儚鏂囦欢鍚� input.setFileExt("jpg"); // 鏂囦欢鎵╁睍鍚� input.setFileSize(0L); // 鏂囦欢澶у皬鏆傛椂璁句负0 @@ -411,7 +638,7 @@ MediaSaveInput input = new MediaSaveInput(); input.setTargetType("activity_player"); input.setTargetId(activityPlayerId); - input.setUrl(mediaId); // COS璺緞 + input.setPath(mediaId); // COS璺緞 input.setFileName("attachment"); // 榛樿闄勪欢鏂囦欢鍚� input.setFileExt("jpg"); // 鏂囦欢鎵╁睍鍚� input.setFileSize(0L); // 鏂囦欢澶у皬鏆傛椂璁句负0 diff --git a/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java b/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java index 962cf57..f78ffc1 100644 --- a/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java +++ b/backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java @@ -18,9 +18,9 @@ * 鏌ヨ娲诲姩鎶ュ悕淇℃伅 */ @SuppressWarnings("unchecked") - public List<ActivityPlayerApplicationResponse> listApplications(String name, Long activityId, Integer page, Integer size) { + public List<ActivityPlayerApplicationResponse> listApplications(String name, Long activityId, Integer state, Integer page, Integer size) { String baseSql = - "SELECT ap.id, p.name AS player_name, a.name AS activity_name, p.phone AS phone, ap.create_time AS apply_time, p.state AS state " + + "SELECT ap.id, p.name AS player_name, a.name AS activity_name, ap.project_name AS project_name, p.phone AS phone, ap.create_time AS apply_time, ap.state AS state " + "FROM t_activity_player ap " + "JOIN t_player p ON p.id = ap.player_id " + "JOIN t_activity a ON a.id = ap.activity_id "; @@ -37,7 +37,15 @@ if (hasCondition) { whereClause.append(" AND "); } - whereClause.append("ap.activity_id = :activityId"); + whereClause.append("ap.stage_id = :activityId"); + hasCondition = true; + } + + if (state != null) { + if (hasCondition) { + whereClause.append(" AND "); + } + whereClause.append("ap.state = :state"); hasCondition = true; } @@ -56,6 +64,9 @@ if (activityId != null) { q.setParameter("activityId", activityId); } + if (state != null) { + q.setParameter("state", state); + } List<Object[]> rows = q.getResultList(); List<ActivityPlayerApplicationResponse> list = new ArrayList<>(); for (Object[] r : rows) { @@ -63,10 +74,11 @@ dto.setId(r[0] != null ? Long.valueOf(r[0].toString()) : null); // activity_player_id dto.setPlayerName(r[1] != null ? r[1].toString() : ""); dto.setActivityName(r[2] != null ? r[2].toString() : ""); - dto.setPhone(r[3] != null ? r[3].toString() : ""); - dto.setApplyTime(r[4] != null ? r[4].toString() : ""); - // 鏄犲皠鐘舵�侊細浣跨敤 t_player.state锛�1=鏈夋晥锛�0=鏃犳晥锛� - dto.setState(r[5] != null ? Integer.valueOf(r[5].toString()) : 1); + dto.setProjectName(r[3] != null ? r[3].toString() : ""); // project_name + dto.setPhone(r[4] != null ? r[4].toString() : ""); + dto.setApplyTime(r[5] != null ? r[5].toString() : ""); + // 鏄犲皠鐘舵�侊細浣跨敤 t_activity_player.state锛�0=鏈鏍革紝1=瀹℃牳閫氳繃锛�2=瀹℃牳椹冲洖锛� + dto.setState(r[6] != null ? Integer.valueOf(r[6].toString()) : 0); list.add(dto); } return list; diff --git a/backend/src/main/java/com/rongyichuang/player/service/PromotionService.java b/backend/src/main/java/com/rongyichuang/player/service/PromotionService.java new file mode 100644 index 0000000..f34c9ac --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/player/service/PromotionService.java @@ -0,0 +1,328 @@ +package com.rongyichuang.player.service; + +import com.rongyichuang.activity.entity.Activity; +import com.rongyichuang.activity.entity.ActivityPlayerRating; +import com.rongyichuang.activity.repository.ActivityRepository; +import com.rongyichuang.activity.repository.ActivityPlayerRatingRepository; +import com.rongyichuang.common.entity.Media; +import com.rongyichuang.common.repository.MediaRepository; +import com.rongyichuang.player.dto.*; +import com.rongyichuang.player.entity.ActivityPlayer; +import com.rongyichuang.player.repository.ActivityPlayerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 姣旇禌鏅嬬骇鏈嶅姟绫� + */ +@Service +public class PromotionService { + + @Autowired + private ActivityRepository activityRepository; + + @Autowired + private ActivityPlayerRepository activityPlayerRepository; + + @Autowired + private ActivityPlayerRatingRepository activityPlayerRatingRepository; + + @Autowired + private MediaRepository mediaRepository; + + /** + * 鑾峰彇姣旇禌鏅嬬骇鍒楄〃 + */ + public List<PromotionCompetitionResponse> getPromotionCompetitions(String name, Integer page, Integer size) { + List<PromotionCompetitionResponse> result = new ArrayList<>(); + + // 鏌ヨ鎵�鏈夋湁鏁堢殑涓绘瘮璧涳紙pid = 0锛� + List<Activity> competitions = activityRepository.findByPidAndStateOrderByCreateTimeAsc(0L, 1); + + // 濡傛灉鏈夊悕绉拌繃婊ゆ潯浠讹紝杩涜杩囨护 + if (name != null && !name.trim().isEmpty()) { + competitions = competitions.stream() + .filter(comp -> comp.getName().contains(name.trim())) + .collect(Collectors.toList()); + } + + // 涓烘瘡涓瘮璧涙煡璇㈠叾闃舵 + for (Activity competition : competitions) { + List<Activity> stages = activityRepository.findByPidAndStateOrderByCreateTimeAsc(competition.getId(), 1); + + for (Activity stage : stages) { + // 缁熻褰撳墠闃舵鐨勫弬璧涗汉鏁� + Long playerCountLong = activityPlayerRepository.countByStageIdAndState(stage.getId(), 1); + Integer currentCount = playerCountLong != null ? playerCountLong.intValue() : 0; + + PromotionCompetitionResponse response = new PromotionCompetitionResponse(competition, stage, currentCount); + result.add(response); + } + } + + // 绠�鍗曞垎椤靛鐞嗭紙瀹為檯椤圭洰涓缓璁娇鐢ㄦ暟鎹簱鍒嗛〉锛� + if (page != null && size != null && page > 0 && size > 0) { + int start = (page - 1) * size; + int end = Math.min(start + size, result.size()); + if (start < result.size()) { + result = result.subList(start, end); + } else { + result = new ArrayList<>(); + } + } + + return result; + } + + /** + * 鑾峰彇姣旇禌鍙傝禌浜哄憳 + */ + public List<CompetitionParticipantResponse> getCompetitionParticipants(Long competitionId, Integer page, Integer size) { + // 鏌ヨ鎸囧畾闃舵鐨勬墍鏈夊弬璧涜�咃紙宸插鏍搁�氳繃鐨勶級 + List<ActivityPlayer> participants = activityPlayerRepository.findByStageIdAndStateWithPlayerOrderByCreateTimeDesc(competitionId, 1); + + // 杞崲涓哄搷搴斿璞� + List<CompetitionParticipantResponse> result = participants.stream() + .map(participant -> { + // 杩欓噷鍙互娣诲姞璇勫垎娆℃暟鐨勭粺璁¢�昏緫 + Integer ratingCount = 0; // TODO: 浠庤瘎鍒嗚〃涓粺璁� + return new CompetitionParticipantResponse(participant, ratingCount); + }) + .collect(Collectors.toList()); + + // 绠�鍗曞垎椤靛鐞� + if (page != null && size != null && page > 0 && size > 0) { + int start = (page - 1) * size; + int end = Math.min(start + size, result.size()); + if (start < result.size()) { + result = result.subList(start, end); + } else { + result = new ArrayList<>(); + } + } + + return result; + } + + /** + * 鑾峰彇鍙檵绾х殑鍙傝禌鑰呭垪琛� + */ + public PromotableParticipantsResponse getPromotableParticipants(Long currentStageId) { + try { + // 鏌ヨ褰撳墠闃舵淇℃伅 + Activity currentStage = activityRepository.findById(currentStageId).orElse(null); + if (currentStage == null) { + return new PromotableParticipantsResponse(new ArrayList<>(), 0, 0, "", ""); + } + + // 鏌ユ壘涓婁竴涓樁娈� + Activity previousStage = findPreviousStage(currentStage); + if (previousStage == null) { + return new PromotableParticipantsResponse(new ArrayList<>(), 0, 0, "", currentStage.getName()); + } + + // 鏌ヨ涓婁竴闃舵鐨勬墍鏈夋湁鏁堝弬璧涜�� + List<ActivityPlayer> previousParticipants = activityPlayerRepository + .findByStageIdAndStateWithPlayerOrderByCreateTimeDesc(previousStage.getId(), 1); + + // 鏌ヨ褰撳墠闃舵宸叉湁鐨勫弬璧涜�匢D鍒楄〃 + List<ActivityPlayer> currentParticipants = activityPlayerRepository + .findByStageIdAndStateWithPlayerOrderByCreateTimeDesc(currentStageId, 1); + List<Long> currentPlayerIds = currentParticipants.stream() + .map(ActivityPlayer::getPlayerId) + .collect(Collectors.toList()); + + // 杩囨护鎺夊凡缁忓湪褰撳墠闃舵鐨勫弬璧涜�咃紝骞惰绠楀钩鍧囧垎 + List<PromotableParticipantResponse> promotableList = new ArrayList<>(); + + for (ActivityPlayer participant : previousParticipants) { + // 鎺掗櫎宸茬粡鍦ㄥ綋鍓嶉樁娈电殑鍙傝禌鑰� + if (!currentPlayerIds.contains(participant.getPlayerId())) { + // 璁$畻骞冲潎鍒� + BigDecimal averageScore = calculateAverageScore(participant.getId()); + + // 鍙寘鍚湁璇勫垎鐨勫弬璧涜�� + if (averageScore != null && averageScore.compareTo(BigDecimal.ZERO) > 0) { + // 缁熻璇勫垎娆℃暟 + long ratingCount = activityPlayerRatingRepository + .countCompletedRatingsByActivityPlayerId(participant.getId()); + + PromotableParticipantResponse response = new PromotableParticipantResponse( + participant, averageScore, (int) ratingCount); + promotableList.add(response); + } + } + } + + // 鎸夊钩鍧囧垎闄嶅簭鎺掑簭 + promotableList.sort((a, b) -> { + if (a.getAverageScore() == null && b.getAverageScore() == null) return 0; + if (a.getAverageScore() == null) return 1; + if (b.getAverageScore() == null) return -1; + return b.getAverageScore().compareTo(a.getAverageScore()); + }); + + // 璁$畻鍙�夋嫨浜烘暟锛堝綋鍓嶉樁娈垫渶澶т汉鏁� - 褰撳墠闃舵宸叉湁浜烘暟锛� + Integer maxParticipants = currentStage.getPlayerMax() != null ? currentStage.getPlayerMax() : 0; + Integer selectableCount = Math.max(0, maxParticipants - currentParticipants.size()); + + return new PromotableParticipantsResponse( + promotableList, + selectableCount, + promotableList.size(), + previousStage.getName(), + currentStage.getName() + ); + + } catch (Exception e) { + return new PromotableParticipantsResponse(new ArrayList<>(), 0, 0, "", ""); + } + } + + /** + * 璁$畻鍙傝禌鑰呯殑骞冲潎鍒� + */ + private BigDecimal calculateAverageScore(Long activityPlayerId) { + List<ActivityPlayerRating> ratings = activityPlayerRatingRepository + .findCompletedRatingsByActivityPlayerId(activityPlayerId); + + if (ratings.isEmpty()) { + return null; + } + + BigDecimal totalScore = BigDecimal.ZERO; + int count = 0; + + for (ActivityPlayerRating rating : ratings) { + if (rating.getTotalScore() != null) { + totalScore = totalScore.add(rating.getTotalScore()); + count++; + } + } + + if (count == 0) { + return null; + } + + return totalScore.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP); + } + + /** + * 鏌ユ壘涓婁竴涓樁娈� + */ + private Activity findPreviousStage(Activity currentStage) { + // 鏌ヨ鍚屼竴姣旇禌涓嬬殑鎵�鏈夐樁娈碉紝鎸夋帓搴忛『搴� + List<Activity> stages = activityRepository.findByPidAndStateOrderBySortOrderAsc(currentStage.getPid(), 1); + + // 鎵惧埌褰撳墠闃舵鐨勪綅缃紝杩斿洖涓婁竴涓樁娈� + for (int i = 0; i < stages.size(); i++) { + if (stages.get(i).getId().equals(currentStage.getId()) && i > 0) { + return stages.get(i - 1); + } + } + + return null; // 娌℃湁涓婁竴涓樁娈� + } + + /** + * 鎵ц鏅嬬骇鎿嶄綔 + */ + @Transactional + public PromotionResult promoteParticipants(PromotionInput input) { + try { + if (input.getParticipantIds() == null || input.getParticipantIds().isEmpty()) { + return PromotionResult.failure("璇烽�夋嫨瑕佹檵绾х殑鍙傝禌鑰�"); + } + + // 鏌ヨ鐩爣鏅嬬骇闃舵淇℃伅锛堢敤鎴风偣鍑荤殑闃舵灏辨槸瑕佹檵绾у埌鐨勯樁娈碉級 + Activity targetStage = activityRepository.findById(input.getCompetitionId()).orElse(null); + if (targetStage == null) { + return PromotionResult.failure("鐩爣鏅嬬骇闃舵涓嶅瓨鍦�"); + } + + // 鏌ヨ瑕佹檵绾х殑鍙傝禌鑰� + List<ActivityPlayer> participants = activityPlayerRepository.findAllById(input.getParticipantIds()); + if (participants.isEmpty()) { + return PromotionResult.failure("娌℃湁鎵惧埌瑕佹檵绾х殑鍙傝禌鑰�"); + } + + int promotedCount = 0; + + // 涓烘瘡涓弬璧涜�呭垱寤虹洰鏍囬樁娈电殑鎶ュ悕璁板綍 + for (ActivityPlayer participant : participants) { + // 妫�鏌ユ槸鍚﹀凡缁忓湪鐩爣闃舵鎶ュ悕 + boolean alreadyPromoted = activityPlayerRepository.existsByStageIdAndPlayerId(targetStage.getId(), participant.getPlayerId()); + if (!alreadyPromoted) { + // 鍒涘缓鏂扮殑鎶ュ悕璁板綍 + ActivityPlayer newParticipant = new ActivityPlayer(); + newParticipant.setActivityId(targetStage.getPid()); // 涓绘瘮璧汭D + newParticipant.setStageId(targetStage.getId()); // 鐩爣闃舵ID + newParticipant.setPlayerId(participant.getPlayerId()); + newParticipant.setRegionId(participant.getRegionId()); + newParticipant.setProjectName(participant.getProjectName()); + newParticipant.setDescription(participant.getDescription()); + newParticipant.setState(1); // 鐩存帴璁句负瀹℃牳閫氳繃 + + ActivityPlayer savedParticipant = activityPlayerRepository.save(newParticipant); + + // 澶嶅埗濯掍綋鏂囦欢 + copyMediaFiles(participant.getId(), savedParticipant.getId()); + + promotedCount++; + } + } + + return PromotionResult.success( + String.format("鎴愬姛鏅嬬骇 %d 鍚嶅弬璧涜�呭埌 %s", promotedCount, targetStage.getName()), + promotedCount + ); + + } catch (Exception e) { + return PromotionResult.failure("鏅嬬骇鎿嶄綔澶辫触锛�" + e.getMessage()); + } + } + + /** + * 澶嶅埗濯掍綋鏂囦欢 + */ + private void copyMediaFiles(Long sourceActivityPlayerId, Long targetActivityPlayerId) { + try { + // 鑾峰彇婧愬弬璧涜�呯殑鎵�鏈夊獟浣撴枃浠� (targetType=5 琛ㄧず ACTIVITY_PLAYER) + List<Media> sourceMediaFiles = mediaRepository.findByTargetTypeAndTargetIdAndState(5, sourceActivityPlayerId, 1); + + for (Media sourceMedia : sourceMediaFiles) { + // 鍒涘缓鏂扮殑濯掍綋璁板綍 + Media newMedia = new Media(); + newMedia.setName(sourceMedia.getName()); + newMedia.setPath(sourceMedia.getPath()); + newMedia.setFileSize(sourceMedia.getFileSize()); + newMedia.setFileExt(sourceMedia.getFileExt()); + newMedia.setMediaType(sourceMedia.getMediaType()); + newMedia.setTargetType(5); // ACTIVITY_PLAYER + newMedia.setTargetId(targetActivityPlayerId); + newMedia.setThumbPath(sourceMedia.getThumbPath()); + newMedia.setDuration(sourceMedia.getDuration()); + newMedia.setDescription(sourceMedia.getDescription()); + + // 淇濆瓨鏂扮殑濯掍綋璁板綍 + mediaRepository.save(newMedia); + } + } catch (Exception e) { + // 璁板綍閿欒浣嗕笉涓柇鏅嬬骇娴佺▼ + System.err.println("澶嶅埗濯掍綋鏂囦欢澶辫触: " + e.getMessage()); + } + } + + /** + * 鏌ユ壘涓嬩竴涓樁娈� + */ + +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/rating/dto/response/RatingSchemeResponse.java b/backend/src/main/java/com/rongyichuang/rating/dto/response/RatingSchemeResponse.java index fe90677..c5aebb8 100644 --- a/backend/src/main/java/com/rongyichuang/rating/dto/response/RatingSchemeResponse.java +++ b/backend/src/main/java/com/rongyichuang/rating/dto/response/RatingSchemeResponse.java @@ -15,6 +15,8 @@ private String name; private String description; private Integer totalScore; + private Integer state; + private String stateName; private List<RatingItemResponse> items; private LocalDateTime createTime; private LocalDateTime updateTime; @@ -27,6 +29,8 @@ this.name = scheme.getName(); this.description = scheme.getDescription(); this.totalScore = scheme.getTotalScore(); + this.state = scheme.getState(); + this.stateName = getStateNameByValue(scheme.getState()); this.createTime = scheme.getCreateTime(); this.updateTime = scheme.getUpdateTime(); @@ -93,4 +97,32 @@ public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; } + + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } + + public String getStateName() { + return stateName; + } + + public void setStateName(String stateName) { + this.stateName = stateName; + } + + /** + * 鏍规嵁鐘舵�佸�艰幏鍙栫姸鎬佸悕绉� + */ + private String getStateNameByValue(Integer state) { + if (state == null) return "鏈煡"; + switch (state) { + case 1: return "姝e父"; + case 0: return "宸插垹闄�"; + default: return "鏈煡"; + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/rating/entity/RatingItem.java b/backend/src/main/java/com/rongyichuang/rating/entity/RatingItem.java index 40eb649..a6f3673 100644 --- a/backend/src/main/java/com/rongyichuang/rating/entity/RatingItem.java +++ b/backend/src/main/java/com/rongyichuang/rating/entity/RatingItem.java @@ -59,6 +59,14 @@ } // Getter鍜孲etter鏂规硶 + public Long getSchemeId() { + return schemeId; + } + + public void setSchemeId(Long schemeId) { + this.schemeId = schemeId; + } + public RatingScheme getScheme() { return scheme; } diff --git a/backend/src/main/java/com/rongyichuang/rating/service/RatingSchemeService.java b/backend/src/main/java/com/rongyichuang/rating/service/RatingSchemeService.java index 12131a9..9bc3347 100644 --- a/backend/src/main/java/com/rongyichuang/rating/service/RatingSchemeService.java +++ b/backend/src/main/java/com/rongyichuang/rating/service/RatingSchemeService.java @@ -139,6 +139,7 @@ // 鏂板鏉$洰 item = new RatingItem(); item.setScheme(scheme); + item.setSchemeId(scheme.getId()); } else { // 淇敼鏉$洰 item = existingItems.stream() @@ -147,6 +148,7 @@ .orElse(new RatingItem()); if (item.getScheme() == null) { item.setScheme(scheme); + item.setSchemeId(scheme.getId()); } } diff --git a/backend/src/main/resources/graphql/activity.graphqls b/backend/src/main/resources/graphql/activity.graphqls index 78e63c3..d6eeb5b 100644 --- a/backend/src/main/resources/graphql/activity.graphqls +++ b/backend/src/main/resources/graphql/activity.graphqls @@ -13,6 +13,7 @@ ratingSchemeId: ID! playerMax: Int state: Int! + sortOrder: Int createTime: String! updateTime: String! @@ -75,6 +76,7 @@ ratingSchemeId: ID playerMax: Int state: Int + sortOrder: Int } # 姣旇禌璇勫杈撳叆绫诲瀷 diff --git a/backend/src/main/resources/graphql/media.graphqls b/backend/src/main/resources/graphql/media.graphqls index 3b73a15..8964117 100644 --- a/backend/src/main/resources/graphql/media.graphqls +++ b/backend/src/main/resources/graphql/media.graphqls @@ -11,10 +11,10 @@ saveMediaV2(input: MediaSaveInput!): MediaSaveResponse! "淇濆瓨閫夋墜澶村儚" - savePlayerAvatar(playerId: ID!, url: String!, fileName: String, fileSize: Long): MediaSaveResponse! + savePlayerAvatar(playerId: ID!, path: String!, fileName: String, fileSize: Long): MediaSaveResponse! "淇濆瓨娲诲姩鎶ュ悕闄勪欢" - saveActivityPlayerAttachment(activityPlayerId: ID!, url: String!, fileName: String, fileSize: Long, mediaType: Int!): MediaSaveResponse! + saveActivityPlayerAttachment(activityPlayerId: ID!, path: String!, fileName: String, fileSize: Long, mediaType: Int!): MediaSaveResponse! } extend type Query { @@ -94,8 +94,8 @@ input MediaSaveInput { targetType: String! # 鐩爣绫诲瀷锛歱layer, activity_player targetId: ID! # 鐩爣ID - url: String! # COS鏂囦欢URL - thumbUrl: String # 缂╃暐鍥綰RL锛堝彲閫夛級 + path: String! # COS鏂囦欢璺緞 + thumbPath: String # 缂╃暐鍥捐矾寰勶紙鍙�夛級 fileName: String # 鏂囦欢鍚� fileExt: String # 鏂囦欢鎵╁睍鍚� fileSize: Long # 鏂囦欢澶у皬锛堝瓧鑺傦級 diff --git a/backend/src/main/resources/graphql/player.graphqls b/backend/src/main/resources/graphql/player.graphqls index b8e992f..6246211 100644 --- a/backend/src/main/resources/graphql/player.graphqls +++ b/backend/src/main/resources/graphql/player.graphqls @@ -1,5 +1,5 @@ extend type Query { - activityPlayerApplications(name: String, activityId: ID, page: Int, size: Int): [ActivityPlayerApplicationResponse!]! + activityPlayerApplications(name: String, activityId: ID, state: Int, page: Int, size: Int): [ActivityPlayerApplicationResponse!]! activityPlayerDetail(id: ID!): ActivityPlayerDetailResponse # 鎶ュ悕鐘舵�佹煡璇� @@ -10,17 +10,32 @@ currentJudgeRating(activityPlayerId: ID!): CurrentJudgeRatingResponse averageScoreForPlayer(activityPlayerId: ID!): Float currentJudgeInfo: CurrentJudgeInfoResponse + + # 姣旇禌鏅嬬骇鐩稿叧鏌ヨ + promotionCompetitions(name: String, page: Int, size: Int): [PromotionCompetitionResponse!]! + competitionParticipants(competitionId: ID!, page: Int, size: Int): [CompetitionParticipantResponse!]! + promotableParticipants(currentStageId: ID!): PromotableParticipantsResponse! } extend type Mutation { saveActivityPlayerRating(input: ActivityPlayerRatingInput!): Boolean! submitActivityRegistration(input: ActivityRegistrationInput!): ActivityRegistrationResponse! + updateActivityRegistration(activityPlayerId: ID!, input: ActivityRegistrationInput!): ActivityRegistrationResponse! + + # 瀹℃牳鐩稿叧mutations + approveActivityPlayer(activityPlayerId: ID!, feedback: String): Boolean! + rejectActivityPlayer(activityPlayerId: ID!, feedback: String!): Boolean! + updatePlayerFeedback(activityPlayerId: ID!, feedback: String!): Boolean! + + # 姣旇禌鏅嬬骇鐩稿叧mutations + promoteParticipants(input: PromotionInput!): PromotionResult! } type ActivityPlayerApplicationResponse { id: ID playerName: String! activityName: String! + projectName: String phone: String applyTime: String! state: Int @@ -32,18 +47,26 @@ playerInfo: PlayerInfoResponse! regionInfo: RegionInfoResponse activityName: String! + projectName: String description: String + feedback: String + state: Int submissionFiles: [SubmissionMediaResponse!]! ratingForm: RatingFormResponse } # 瀛﹀憳淇℃伅鍝嶅簲 type PlayerInfoResponse { - id: ID! - name: String! - phone: String - description: String - avatarUrl: String + id: ID + name: String + phone: String + description: String + avatarUrl: String + avatar: MediaResponse + gender: Int + birthday: String + education: String + introduction: String } # 鍖哄煙淇℃伅鍝嶅簲 @@ -61,6 +84,7 @@ fileExt: String fileSize: Int mediaType: Int + thumbUrl: String } # 璇勫垎琛ㄥ崟鍝嶅簲 @@ -177,4 +201,68 @@ registrationTime: String reviewStatus: Int reviewComment: String +} + +# 姣旇禌鏅嬬骇鐩稿叧绫诲瀷瀹氫箟 + +# 姣旇禌鏅嬬骇鍒楄〃鍝嶅簲绫诲瀷 +type PromotionCompetitionResponse { + id: ID! + competitionName: String! + stageName: String! + maxParticipants: Int + currentCount: Int! + status: Int! + startTime: String + endTime: String + sortOrder: Int + state: Int +} + +# 姣旇禌鍙傝禌鑰呭搷搴旂被鍨� +type CompetitionParticipantResponse { + id: ID! + playerName: String! + projectName: String + phone: String + averageScore: Float + ratingCount: Int! + applyTime: String! + state: Int! +} + +# 鏅嬬骇鎿嶄綔杈撳叆绫诲瀷 +input PromotionInput { + competitionId: ID! + participantIds: [ID!]! + targetStageId: ID +} + +# 鏅嬬骇鎿嶄綔缁撴灉绫诲瀷 +type PromotionResult { + success: Boolean! + message: String! + promotedCount: Int! +} + +# 鍙檵绾у弬璧涜�呭搷搴旂被鍨� +type PromotableParticipantResponse { + id: ID! + playerId: ID! + playerName: String! + projectName: String + phone: String + averageScore: Float + ratingCount: Int! + applyTime: String! + state: Int! +} + +# 鍙檵绾у弬璧涜�呭垪琛ㄥ搷搴旂被鍨� +type PromotableParticipantsResponse { + participants: [PromotableParticipantResponse!]! + selectableCount: Int! + totalCount: Int! + previousStageName: String! + currentStageName: String! } \ No newline at end of file diff --git a/backend/src/main/resources/graphql/rating.graphqls b/backend/src/main/resources/graphql/rating.graphqls index c22dfae..d563e9a 100644 --- a/backend/src/main/resources/graphql/rating.graphqls +++ b/backend/src/main/resources/graphql/rating.graphqls @@ -27,6 +27,8 @@ name: String! description: String totalScore: Int! + state: Int! + stateName: String! items: [RatingItemResponse!] createTime: String updateTime: String @@ -38,6 +40,8 @@ name: String! description: String totalScore: Int! + state: Int! + stateName: String! items: [RatingItemResponse!] createTime: String updateTime: String @@ -57,6 +61,7 @@ totalElements: ID! totalPages: Int! number: Int! + page: Int! size: Int! first: Boolean! last: Boolean! diff --git a/backend/src/test/java/com/rongyichuang/AssignActivityPermissionTest.java b/backend/src/test/java/com/rongyichuang/AssignActivityPermissionTest.java new file mode 100644 index 0000000..f21a80c --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/AssignActivityPermissionTest.java @@ -0,0 +1,96 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +@ActiveProfiles("test") +public class AssignActivityPermissionTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void assignActivityPermissionToJudge68() { + System.out.println("=== 涓鸿瘎濮擨D=68鍒嗛厤娲诲姩鏉冮檺 ==="); + + try { + Long judgeId = 68L; + + // 1. 纭璇勫瀛樺湪 + String checkJudgeSql = "SELECT id, name, user_id FROM t_judge WHERE id = ?"; + Map<String, Object> judge = jdbcTemplate.queryForMap(checkJudgeSql, judgeId); + System.out.println("璇勫淇℃伅: ID=" + judge.get("id") + + ", 濮撳悕=" + judge.get("name") + + ", 鐢ㄦ埛ID=" + judge.get("user_id")); + + // 2. 鏌ョ湅鏈�鏂扮殑鍑犱釜娲诲姩 + String getActivitiesSql = "SELECT id, name, state FROM t_activity WHERE state = 1 ORDER BY id DESC LIMIT 5"; + List<Map<String, Object>> activities = jdbcTemplate.queryForList(getActivitiesSql); + + System.out.println("鏈�鏂扮殑娲诲姩鍒楄〃:"); + for (Map<String, Object> activity : activities) { + System.out.println(" 娲诲姩ID: " + activity.get("id") + + ", 娲诲姩鍚嶇О: " + activity.get("name") + + ", 鐘舵��: " + activity.get("state")); + } + + // 3. 涓鸿瘎濮斿垎閰嶆渶鏂版椿鍔ㄧ殑鏉冮檺 + if (!activities.isEmpty()) { + Map<String, Object> latestActivity = activities.get(0); + Long activityId = ((Number) latestActivity.get("id")).longValue(); + String activityName = (String) latestActivity.get("name"); + + // 妫�鏌ユ槸鍚﹀凡缁忔湁鏉冮檺 + String checkPermissionSql = "SELECT COUNT(*) as count FROM t_activity_judge WHERE judge_id = ? AND activity_id = ?"; + Map<String, Object> permissionExists = jdbcTemplate.queryForMap(checkPermissionSql, judgeId, activityId); + + if (((Number) permissionExists.get("count")).intValue() == 0) { + // 鍩轰簬鐜版湁璁板綍澶嶅埗stage_id锛屼娇鐢ㄦ椿鍔↖D浣滀负stage_id锛堢畝鍖栧鐞嗭級 + String insertPermissionSql = "INSERT INTO t_activity_judge (judge_id, activity_id, stage_id, state, version) VALUES (?, ?, ?, 1, 0)"; + int result = jdbcTemplate.update(insertPermissionSql, judgeId, activityId, activityId); + + if (result > 0) { + System.out.println("鉁� 鎴愬姛涓鸿瘎濮斿垎閰嶆椿鍔ㄦ潈闄�: " + activityName + " (ID=" + activityId + ")"); + } else { + System.out.println("鉂� 鍒嗛厤娲诲姩鏉冮檺澶辫触"); + } + } else { + System.out.println("璇勫宸茬粡鏈夎娲诲姩鐨勬潈闄�"); + } + + // 4. 楠岃瘉鏉冮檺鍒嗛厤缁撴灉 + String verifyPermissionSql = "SELECT aj.activity_id, a.name as activity_name, aj.state " + + "FROM t_activity_judge aj " + + "JOIN t_activity a ON aj.activity_id = a.id " + + "WHERE aj.judge_id = ?"; + List<Map<String, Object>> permissions = jdbcTemplate.queryForList(verifyPermissionSql, judgeId); + + System.out.println("璇勫ID=" + judgeId + " 鐨勬墍鏈夋椿鍔ㄦ潈闄�:"); + for (Map<String, Object> permission : permissions) { + System.out.println(" 娲诲姩ID: " + permission.get("activity_id") + + ", 娲诲姩鍚嶇О: " + permission.get("activity_name") + + ", 鏉冮檺鐘舵��: " + permission.get("state")); + } + + if (permissions.isEmpty()) { + System.out.println("鉂� 璇ヨ瘎濮斾粛鐒舵病鏈変换浣曟椿鍔ㄦ潈闄愶紒"); + } else { + System.out.println("鉁� 鏉冮檺鍒嗛厤鎴愬姛锛岃瘎濮旂幇鍦ㄦ湁 " + permissions.size() + " 涓椿鍔ㄧ殑璇勫鏉冮檺"); + } + } else { + System.out.println("鉂� 娌℃湁鎵惧埌鍙敤鐨勬椿鍔�"); + } + + } catch (Exception e) { + System.out.println("鍒嗛厤鏉冮檺鏃跺彂鐢熷紓甯�: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CheckActivityJudgeTableTest.java b/backend/src/test/java/com/rongyichuang/CheckActivityJudgeTableTest.java new file mode 100644 index 0000000..3861d97 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CheckActivityJudgeTableTest.java @@ -0,0 +1,50 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +@ActiveProfiles("test") +public class CheckActivityJudgeTableTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void checkActivityJudgeTableStructure() { + System.out.println("=== 妫�鏌_activity_judge琛ㄧ粨鏋� ==="); + + try { + // 鏌ョ湅琛ㄧ粨鏋� + String describeTableSql = "DESCRIBE t_activity_judge"; + List<Map<String, Object>> columns = jdbcTemplate.queryForList(describeTableSql); + + System.out.println("t_activity_judge琛ㄥ瓧娈�:"); + for (Map<String, Object> column : columns) { + System.out.println(" 瀛楁鍚�: " + column.get("Field") + + ", 绫诲瀷: " + column.get("Type") + + ", 鏄惁涓虹┖: " + column.get("Null") + + ", 榛樿鍊�: " + column.get("Default")); + } + + // 鏌ョ湅鐜版湁鐨勬椿鍔�-璇勫鍏宠仈璁板綍 + String selectRecordsSql = "SELECT * FROM t_activity_judge LIMIT 3"; + List<Map<String, Object>> records = jdbcTemplate.queryForList(selectRecordsSql); + + System.out.println("\n鐜版湁娲诲姩-璇勫鍏宠仈璁板綍绀轰緥:"); + for (Map<String, Object> record : records) { + System.out.println(" " + record); + } + + } catch (Exception e) { + System.out.println("妫�鏌ヨ〃缁撴瀯鏃跺彂鐢熷紓甯�: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CheckJudgeTableTest.java b/backend/src/test/java/com/rongyichuang/CheckJudgeTableTest.java new file mode 100644 index 0000000..125552d --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CheckJudgeTableTest.java @@ -0,0 +1,50 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +@ActiveProfiles("test") +public class CheckJudgeTableTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void checkJudgeTableStructure() { + System.out.println("=== 妫�鏌_judge琛ㄧ粨鏋� ==="); + + try { + // 鏌ョ湅琛ㄧ粨鏋� + String describeTableSql = "DESCRIBE t_judge"; + List<Map<String, Object>> columns = jdbcTemplate.queryForList(describeTableSql); + + System.out.println("t_judge琛ㄥ瓧娈�:"); + for (Map<String, Object> column : columns) { + System.out.println(" 瀛楁鍚�: " + column.get("Field") + + ", 绫诲瀷: " + column.get("Type") + + ", 鏄惁涓虹┖: " + column.get("Null") + + ", 榛樿鍊�: " + column.get("Default")); + } + + // 鏌ョ湅鐜版湁鐨勮瘎濮旇褰� + String selectJudgesSql = "SELECT * FROM t_judge LIMIT 3"; + List<Map<String, Object>> judges = jdbcTemplate.queryForList(selectJudgesSql); + + System.out.println("\n鐜版湁璇勫璁板綍绀轰緥:"); + for (Map<String, Object> judge : judges) { + System.out.println(" " + judge); + } + + } catch (Exception e) { + System.out.println("妫�鏌ヨ〃缁撴瀯鏃跺彂鐢熷紓甯�: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CheckMediaRecordsTest.java b/backend/src/test/java/com/rongyichuang/CheckMediaRecordsTest.java new file mode 100644 index 0000000..8f6fc60 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CheckMediaRecordsTest.java @@ -0,0 +1,121 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +public class CheckMediaRecordsTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void checkMediaRecords() { + System.out.println("=== 妫�鏌ユ暟鎹簱琛ㄧ粨鏋� ==="); + + try { + // 棣栧厛鏌ョ湅鎵�鏈夎〃 + String showTables = "SHOW TABLES"; + List<Map<String, Object>> tables = jdbcTemplate.queryForList(showTables); + + System.out.println("鏁版嵁搴撲腑鐨勮〃:"); + for (Map<String, Object> table : tables) { + System.out.println("- " + table.values().iterator().next()); + } + + // 妫�鏌ユ槸鍚︽湁media鐩稿叧鐨勮〃 + System.out.println("\n=== 妫�鏌edia鐩稿叧琛� ==="); + for (Map<String, Object> table : tables) { + String tableName = table.values().iterator().next().toString(); + if (tableName.toLowerCase().contains("media")) { + System.out.println("鎵惧埌media鐩稿叧琛�: " + tableName); + + try { + // 鏌ョ湅琛ㄧ粨鏋� + String descTable = "DESC " + tableName; + List<Map<String, Object>> columns = jdbcTemplate.queryForList(descTable); + System.out.println("琛ㄧ粨鏋�:"); + for (Map<String, Object> column : columns) { + System.out.println(" " + column.get("Field") + " - " + column.get("Type")); + } + + // 鏌ョ湅璁板綍鏁伴噺 + String countSql = "SELECT COUNT(*) as count FROM " + tableName; + Map<String, Object> countResult = jdbcTemplate.queryForMap(countSql); + System.out.println("璁板綍鏁伴噺: " + countResult.get("count")); + + // 濡傛灉鏈夎褰曪紝鏌ョ湅鏈�杩戝嚑鏉� + Long count = (Long) countResult.get("count"); + if (count > 0) { + String sampleSql = "SELECT * FROM " + tableName + " ORDER BY id DESC LIMIT 3"; + List<Map<String, Object>> samples = jdbcTemplate.queryForList(sampleSql); + System.out.println("鏈�杩�3鏉¤褰�:"); + for (Map<String, Object> sample : samples) { + System.out.println(" " + sample); + } + + // 濡傛灉鏄痶_media琛紝涓撻棬鏌ヨtargetType=5鐨勮褰� + if ("t_media".equals(tableName)) { + try { + List<Map<String, Object>> targetType5Records = jdbcTemplate.queryForList( + "SELECT * FROM t_media WHERE target_type = 5 ORDER BY create_time DESC" + ); + System.out.println("targetType=5鐨勮褰曟暟閲�: " + targetType5Records.size()); + if (!targetType5Records.isEmpty()) { + System.out.println("targetType=5鐨勮褰�:"); + for (Map<String, Object> record : targetType5Records) { + System.out.println(" " + record); + } + } else { + System.out.println("娌℃湁鎵惧埌targetType=5鐨勮褰�"); + } + } catch (Exception e) { + System.out.println("鏌ヨtargetType=5璁板綍澶辫触: " + e.getMessage()); + } + } + } + } catch (Exception e) { + System.out.println("鏌ヨ琛� " + tableName + " 鏃跺嚭閿�: " + e.getMessage()); + } + System.out.println(); + } + } + + // 鏌ョ湅activity_player鐩稿叧鐨勮〃 + System.out.println("=== 妫�鏌ctivity_player鐩稿叧琛� ==="); + for (Map<String, Object> table : tables) { + String tableName = table.values().iterator().next().toString(); + if (tableName.toLowerCase().contains("activity") && tableName.toLowerCase().contains("player")) { + System.out.println("鎵惧埌activity_player鐩稿叧琛�: " + tableName); + + try { + // 鏌ョ湅璁板綍鏁伴噺 + String countSql = "SELECT COUNT(*) as count FROM " + tableName; + Map<String, Object> countResult = jdbcTemplate.queryForMap(countSql); + System.out.println("璁板綍鏁伴噺: " + countResult.get("count")); + + // 鏌ョ湅鏈�杩戝嚑鏉¤褰� + String sampleSql = "SELECT * FROM " + tableName + " ORDER BY id DESC LIMIT 3"; + List<Map<String, Object>> samples = jdbcTemplate.queryForList(sampleSql); + System.out.println("鏈�杩�3鏉¤褰�:"); + for (Map<String, Object> sample : samples) { + System.out.println(" " + sample); + } + } catch (Exception e) { + System.out.println("鏌ヨ琛� " + tableName + " 鏃跺嚭閿�: " + e.getMessage()); + } + System.out.println(); + } + } + + } catch (Exception e) { + System.out.println("鏁版嵁搴撴煡璇㈠嚭閿�: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CheckUserPermissionTest.java b/backend/src/test/java/com/rongyichuang/CheckUserPermissionTest.java new file mode 100644 index 0000000..deb5eaf --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CheckUserPermissionTest.java @@ -0,0 +1,97 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +@ActiveProfiles("test") +public class CheckUserPermissionTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void checkUserJudgePermission() { + System.out.println("=== 妫�鏌ョ敤鎴稩D=2鐨勮瘎濮旀潈闄� ==="); + + try { + // 鍏堟煡鐪嬬敤鎴疯〃缁撴瀯 + String describeUserSql = "DESCRIBE t_user"; + List<Map<String, Object>> userTableStructure = jdbcTemplate.queryForList(describeUserSql); + System.out.println("鐢ㄦ埛琛ㄧ粨鏋�:"); + for (Map<String, Object> column : userTableStructure) { + System.out.println("瀛楁: " + column.get("Field") + ", 绫诲瀷: " + column.get("Type")); + } + + // 1. 妫�鏌ョ敤鎴稩D=2鏄惁鍏宠仈浜嗚瘎濮旇褰� + String userJudgeSql = "SELECT u.id as user_id, u.phone, j.id as judge_id, j.name as judge_name " + + "FROM t_user u LEFT JOIN t_judge j ON u.id = j.user_id WHERE u.id = 2"; + List<Map<String, Object>> userJudgeData = jdbcTemplate.queryForList(userJudgeSql); + + System.out.println("\n鐢ㄦ埛璇勫鍏宠仈鏁版嵁:"); + for (Map<String, Object> row : userJudgeData) { + System.out.println("鐢ㄦ埛ID: " + row.get("user_id") + + ", 鎵嬫満鍙�: " + row.get("phone") + + ", 璇勫ID: " + row.get("judge_id") + + ", 璇勫濮撳悕: " + row.get("judge_name")); + } + + // 2. 濡傛灉鏈夎瘎濮旇褰曪紝妫�鏌ヨ瘎濮旂殑娲诲姩鏉冮檺 + if (!userJudgeData.isEmpty() && userJudgeData.get(0).get("judge_id") != null) { + Long judgeId = ((Number) userJudgeData.get(0).get("judge_id")).longValue(); + + String activityJudgeSql = "SELECT aj.activity_id, aj.judge_id, aj.state, a.name as activity_name " + + "FROM t_activity_judge aj " + + "LEFT JOIN t_activity a ON aj.activity_id = a.id " + + "WHERE aj.judge_id = ?"; + List<Map<String, Object>> activityJudgeData = jdbcTemplate.queryForList(activityJudgeSql, judgeId); + + System.out.println("\n璇勫娲诲姩鏉冮檺鏁版嵁:"); + for (Map<String, Object> row : activityJudgeData) { + System.out.println("娲诲姩ID: " + row.get("activity_id") + + ", 娲诲姩鍚嶇О: " + row.get("activity_name") + + ", 璇勫ID: " + row.get("judge_id") + + ", 鐘舵��: " + row.get("state")); + } + + if (activityJudgeData.isEmpty()) { + System.out.println("鉂� 璇勫ID=" + judgeId + " 娌℃湁浠讳綍娲诲姩鐨勮瘎瀹℃潈闄�"); + } + } else { + System.out.println("鉂� 鐢ㄦ埛ID=2 娌℃湁鍏宠仈璇勫璁板綍"); + } + + // 3. 妫�鏌ユ墍鏈夋椿鍔ㄧ殑淇℃伅 + String allActivitiesSql = "SELECT id, name, state FROM t_activity ORDER BY id"; + List<Map<String, Object>> allActivities = jdbcTemplate.queryForList(allActivitiesSql); + + System.out.println("\n鎵�鏈夋椿鍔ㄤ俊鎭�:"); + for (Map<String, Object> row : allActivities) { + System.out.println("娲诲姩ID: " + row.get("id") + + ", 娲诲姩鍚嶇О: " + row.get("name") + + ", 鐘舵��: " + row.get("state")); + } + + // 4. 妫�鏌ユ墍鏈夎瘎濮斾俊鎭� + String allJudgesSql = "SELECT id, name, user_id FROM t_judge ORDER BY id"; + List<Map<String, Object>> allJudges = jdbcTemplate.queryForList(allJudgesSql); + + System.out.println("\n鎵�鏈夎瘎濮斾俊鎭�:"); + for (Map<String, Object> row : allJudges) { + System.out.println("璇勫ID: " + row.get("id") + + ", 璇勫濮撳悕: " + row.get("name") + + ", 鐢ㄦ埛ID: " + row.get("user_id")); + } + + } catch (Exception e) { + System.out.println("妫�鏌ユ潈闄愭椂鍙戠敓寮傚父: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CreateJudgeForUser2Test.java b/backend/src/test/java/com/rongyichuang/CreateJudgeForUser2Test.java new file mode 100644 index 0000000..272a3ec --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CreateJudgeForUser2Test.java @@ -0,0 +1,107 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +@ActiveProfiles("test") +public class CreateJudgeForUser2Test { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void createJudgeForUser2() { + System.out.println("=== 涓虹敤鎴稩D=2鍒涘缓璇勫璁板綍 ==="); + + try { + // 1. 棣栧厛妫�鏌ョ敤鎴稩D=2鐨勫熀鏈俊鎭� + String getUserInfoSql = "SELECT id, phone, name FROM t_user WHERE id = 2"; + Map<String, Object> userInfo = jdbcTemplate.queryForMap(getUserInfoSql); + System.out.println("鐢ㄦ埛淇℃伅: ID=" + userInfo.get("id") + + ", 鎵嬫満鍙�=" + userInfo.get("phone") + + ", 濮撳悕=" + userInfo.get("name")); + + // 2. 妫�鏌ユ槸鍚﹀凡缁忔湁璇勫璁板綍 + String checkJudgeSql = "SELECT COUNT(*) as count FROM t_judge WHERE user_id = 2"; + Map<String, Object> judgeExists = jdbcTemplate.queryForMap(checkJudgeSql); + + if (((Number) judgeExists.get("count")).intValue() > 0) { + System.out.println("鐢ㄦ埛ID=2宸茬粡鏈夎瘎濮旇褰曪紝鏃犻渶閲嶅鍒涘缓"); + return; + } + + // 3. 鍒涘缓璇勫璁板綍 + String userName = (String) userInfo.get("name"); + String userPhone = (String) userInfo.get("phone"); + if (userName == null || userName.trim().isEmpty()) { + userName = "璇勫_" + userPhone; // 濡傛灉娌℃湁濮撳悕锛屼娇鐢ㄦ墜鏈哄彿 + } + + // 鍏堟煡鐪嬬幇鏈夎瘎濮旇褰曠殑瀛楁锛屽鍒朵竴涓被浼肩殑璁板綍 + String copyExistingJudgeSql = "INSERT INTO t_judge (name, user_id, phone, gender, state, description, version, stage_id) " + + "SELECT ?, ?, ?, gender, state, '绯荤粺鑷姩鍒涘缓鐨勮瘎濮�', 0, stage_id FROM t_judge WHERE id = 65 LIMIT 1"; + int result = jdbcTemplate.update(copyExistingJudgeSql, userName, 2, userPhone); + + if (result > 0) { + System.out.println("鉁� 鎴愬姛涓虹敤鎴稩D=2鍒涘缓璇勫璁板綍"); + + // 4. 鑾峰彇鏂板垱寤虹殑璇勫ID + String getNewJudgeIdSql = "SELECT id, name FROM t_judge WHERE user_id = 2"; + Map<String, Object> newJudge = jdbcTemplate.queryForMap(getNewJudgeIdSql); + Long judgeId = ((Number) newJudge.get("id")).longValue(); + String judgeName = (String) newJudge.get("name"); + + System.out.println("鏂拌瘎濮斾俊鎭�: ID=" + judgeId + ", 濮撳悕=" + judgeName); + + // 5. 鏌ョ湅褰撳墠鏈夊摢浜涙椿鍔ㄥ彲浠ュ垎閰嶆潈闄� + String getActivitiesSql = "SELECT id, name, state FROM t_activity WHERE state = 1 ORDER BY id DESC LIMIT 5"; + List<Map<String, Object>> activities = jdbcTemplate.queryForList(getActivitiesSql); + + System.out.println("褰撳墠鍙敤鐨勬椿鍔紙鏈�鏂�5涓級:"); + for (Map<String, Object> activity : activities) { + System.out.println(" 娲诲姩ID: " + activity.get("id") + + ", 娲诲姩鍚嶇О: " + activity.get("name") + + ", 鐘舵��: " + activity.get("state")); + } + + // 6. 涓鸿瘎濮斿垎閰嶆渶鏂版椿鍔ㄧ殑鏉冮檺锛堝亣璁惧垎閰嶆渶鏂扮殑娲诲姩锛� + if (!activities.isEmpty()) { + Map<String, Object> latestActivity = activities.get(0); + Long activityId = ((Number) latestActivity.get("id")).longValue(); + String activityName = (String) latestActivity.get("name"); + + // 妫�鏌ユ槸鍚﹀凡缁忔湁鏉冮檺 + String checkPermissionSql = "SELECT COUNT(*) as count FROM t_activity_judge WHERE judge_id = ? AND activity_id = ?"; + Map<String, Object> permissionExists = jdbcTemplate.queryForMap(checkPermissionSql, judgeId, activityId); + + if (((Number) permissionExists.get("count")).intValue() == 0) { + String insertPermissionSql = "INSERT INTO t_activity_judge (judge_id, activity_id, state, create_time, update_time) VALUES (?, ?, 1, NOW(), NOW())"; + int permissionResult = jdbcTemplate.update(insertPermissionSql, judgeId, activityId); + + if (permissionResult > 0) { + System.out.println("鉁� 鎴愬姛涓鸿瘎濮斿垎閰嶆椿鍔ㄦ潈闄�: " + activityName + " (ID=" + activityId + ")"); + } else { + System.out.println("鉂� 鍒嗛厤娲诲姩鏉冮檺澶辫触"); + } + } else { + System.out.println("璇勫宸茬粡鏈夎娲诲姩鐨勬潈闄�"); + } + } + + } else { + System.out.println("鉂� 鍒涘缓璇勫璁板綍澶辫触"); + } + + } catch (Exception e) { + System.out.println("鍒涘缓璇勫璁板綍鏃跺彂鐢熷紓甯�: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/DatabaseSchemaTest.java b/backend/src/test/java/com/rongyichuang/DatabaseSchemaTest.java index 74d8b40..39b5ef9 100644 --- a/backend/src/test/java/com/rongyichuang/DatabaseSchemaTest.java +++ b/backend/src/test/java/com/rongyichuang/DatabaseSchemaTest.java @@ -58,4 +58,20 @@ System.out.println(table); } } + + @Test + public void testForeignKeyConstraints() { + try { + String sql = "SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME " + + "FROM information_schema.KEY_COLUMN_USAGE " + + "WHERE TABLE_SCHEMA = 'ryc' AND REFERENCED_TABLE_NAME IS NOT NULL"; + List<Map<String, Object>> result = jdbcTemplate.queryForList(sql); + System.out.println("=== 澶栭敭绾︽潫 ==="); + for (Map<String, Object> row : result) { + System.out.println(row); + } + } catch (Exception e) { + System.out.println("鏌ヨ澶栭敭绾︽潫澶辫触: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/SimpleUserCheckTest.java b/backend/src/test/java/com/rongyichuang/SimpleUserCheckTest.java new file mode 100644 index 0000000..a72da69 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/SimpleUserCheckTest.java @@ -0,0 +1,79 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Map; + +@SpringBootTest +@ActiveProfiles("test") +public class SimpleUserCheckTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void checkUser2Permission() { + System.out.println("=== 妫�鏌ョ敤鎴稩D=2鐨勮瘎濮旀潈闄愰棶棰� ==="); + + try { + // 1. 妫�鏌ョ敤鎴稩D=2鏄惁瀛樺湪 + String userExistsSql = "SELECT COUNT(*) as count FROM t_user WHERE id = 2"; + Map<String, Object> userExists = jdbcTemplate.queryForMap(userExistsSql); + System.out.println("鐢ㄦ埛ID=2鏄惁瀛樺湪: " + userExists.get("count")); + + // 2. 妫�鏌ョ敤鎴稩D=2鏄惁鍏宠仈浜嗚瘎濮� + String judgeExistsSql = "SELECT COUNT(*) as count FROM t_judge WHERE user_id = 2"; + Map<String, Object> judgeExists = jdbcTemplate.queryForMap(judgeExistsSql); + System.out.println("鐢ㄦ埛ID=2鍏宠仈鐨勮瘎濮旀暟閲�: " + judgeExists.get("count")); + + // 3. 濡傛灉鏈夎瘎濮旓紝鑾峰彇璇勫ID + if (((Number) judgeExists.get("count")).intValue() > 0) { + String getJudgeIdSql = "SELECT id, name FROM t_judge WHERE user_id = 2"; + List<Map<String, Object>> judges = jdbcTemplate.queryForList(getJudgeIdSql); + for (Map<String, Object> judge : judges) { + Long judgeId = ((Number) judge.get("id")).longValue(); + String judgeName = (String) judge.get("name"); + System.out.println("璇勫ID: " + judgeId + ", 璇勫濮撳悕: " + judgeName); + + // 4. 妫�鏌ヨ璇勫鐨勬椿鍔ㄦ潈闄� + String activityPermissionSql = "SELECT COUNT(*) as count FROM t_activity_judge WHERE judge_id = ?"; + Map<String, Object> activityPermission = jdbcTemplate.queryForMap(activityPermissionSql, judgeId); + System.out.println("璇勫ID=" + judgeId + " 鐨勬椿鍔ㄦ潈闄愭暟閲�: " + activityPermission.get("count")); + + // 5. 鍒楀嚭鍏蜂綋鐨勬椿鍔ㄦ潈闄� + if (((Number) activityPermission.get("count")).intValue() > 0) { + String activityDetailsSql = "SELECT activity_id, state FROM t_activity_judge WHERE judge_id = ?"; + List<Map<String, Object>> activities = jdbcTemplate.queryForList(activityDetailsSql, judgeId); + System.out.println("鍏蜂綋娲诲姩鏉冮檺:"); + for (Map<String, Object> activity : activities) { + System.out.println(" 娲诲姩ID: " + activity.get("activity_id") + ", 鐘舵��: " + activity.get("state")); + } + } else { + System.out.println("鉂� 璇ヨ瘎濮旀病鏈変换浣曟椿鍔ㄧ殑璇勫鏉冮檺锛�"); + } + } + } else { + System.out.println("鉂� 鐢ㄦ埛ID=2 娌℃湁鍏宠仈浠讳綍璇勫璁板綍锛�"); + + // 鏌ョ湅鎵�鏈夋湁鐢ㄦ埛ID鐨勮瘎濮� + String allJudgesWithUserSql = "SELECT id, name, user_id FROM t_judge WHERE user_id IS NOT NULL"; + List<Map<String, Object>> allJudges = jdbcTemplate.queryForList(allJudgesWithUserSql); + System.out.println("鎵�鏈夋湁鐢ㄦ埛ID鐨勮瘎濮�:"); + for (Map<String, Object> judge : allJudges) { + System.out.println(" 璇勫ID: " + judge.get("id") + + ", 濮撳悕: " + judge.get("name") + + ", 鐢ㄦ埛ID: " + judge.get("user_id")); + } + } + + } catch (Exception e) { + System.out.println("妫�鏌ユ潈闄愭椂鍙戠敓寮傚父: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/test_media_api_integration.js b/backend/test_media_api_integration.js deleted file mode 100644 index d553cb2..0000000 --- a/backend/test_media_api_integration.js +++ /dev/null @@ -1,133 +0,0 @@ -// 浣跨敤鍐呯疆鐨刦etch API (Node.js 18+) - -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; - -// 鐢熸垚鍞竴鐨勬祴璇曟暟鎹� -const timestamp = Date.now(); -const uniquePhone = `1380000${timestamp.toString().slice(-4)}`; -const uniqueAvatarMediaId = `avatar_${timestamp}`; -const uniqueAttachmentMediaIds = [`attachment_${timestamp}_1`, `attachment_${timestamp}_2`]; - -async function testMediaApiIntegration() { - console.log('寮�濮嬫祴璇曞獟浣揂PI闆嗘垚...'); - - try { - // 1. 鎻愪氦娲诲姩鎶ュ悕锛堝寘鍚玜vatarMediaId鍜宎ttachmentMediaIds锛� - console.log('1. 鎻愪氦娲诲姩鎶ュ悕...'); - const registrationMutation = ` - mutation { - submitActivityRegistration(input: { - activityId: 1, - playerInfo: { - name: "娴嬭瘯鐢ㄦ埛${timestamp}", - phone: "${uniquePhone}", - gender: 1, - birthDate: "1990-01-01", - avatarMediaId: "${uniqueAvatarMediaId}" - }, - regionId: 1, - projectName: "娴嬭瘯椤圭洰", - description: "娴嬭瘯鎻忚堪", - attachmentMediaIds: ${JSON.stringify(uniqueAttachmentMediaIds)} - }) { - success - message - registrationId - playerId - userId - } - } - `; - - const registrationResponse = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: registrationMutation - }) - }); - - const registrationResult = await registrationResponse.json(); - console.log('鎶ュ悕缁撴灉:', JSON.stringify(registrationResult, null, 2)); - - if (registrationResult.errors) { - console.error('鎶ュ悕澶辫触:', registrationResult.errors); - return; - } - - const result = registrationResult.data.submitActivityRegistration; - if (!result.success) { - console.error('鎶ュ悕澶辫触:', result.message); - return; - } - - console.log(`鎶ュ悕鎴愬姛! 鎶ュ悕ID: ${result.registrationId}, 閫夋墜ID: ${result.playerId}`); - - // 2. 鏌ヨ閫夋墜鍏宠仈鐨勫獟浣撹褰曪紙澶村儚锛� - console.log('\\n2. 鏌ヨ閫夋墜澶村儚濯掍綋璁板綍...'); - const playerMediaQuery = ` - query { - medias(targetType: 1, targetId: ${result.playerId}) { - id - name - path - fileSize - mediaType - targetType - targetId - } - } - `; - - const playerMediaResponse = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: playerMediaQuery - }) - }); - - const playerMediaResult = await playerMediaResponse.json(); - console.log('閫夋墜濯掍綋璁板綍:', JSON.stringify(playerMediaResult, null, 2)); - - // 3. 鏌ヨ娲诲姩鎶ュ悕鍏宠仈鐨勫獟浣撹褰曪紙闄勪欢锛� - console.log('\\n3. 鏌ヨ娲诲姩鎶ュ悕闄勪欢濯掍綋璁板綍...'); - const attachmentMediaQuery = ` - query { - medias(targetType: 2, targetId: ${result.registrationId}) { - id - name - path - fileSize - mediaType - targetType - targetId - } - } - `; - - const attachmentMediaResponse = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: attachmentMediaQuery - }) - }); - - const attachmentMediaResult = await attachmentMediaResponse.json(); - console.log('闄勪欢濯掍綋璁板綍:', JSON.stringify(attachmentMediaResult, null, 2)); - - console.log('\\n娴嬭瘯瀹屾垚锛佽妫�鏌ュ悗绔棩蹇楃‘璁ゅ獟浣撹褰曚繚瀛樻儏鍐点��'); - - } catch (error) { - console.error('娴嬭瘯杩囩▼涓彂鐢熼敊璇�:', error); - } -} - -testMediaApiIntegration(); \ No newline at end of file diff --git a/backend/test_wechat_login.py b/backend/test_wechat_login.py deleted file mode 100644 index e029a1d..0000000 --- a/backend/test_wechat_login.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -娴嬭瘯寰俊鐧诲綍GraphQL鎺ュ彛 -""" - -import requests -import json - -def test_wechat_login(): - """娴嬭瘯寰俊鐧诲綍鎺ュ彛""" - - # GraphQL绔偣 - url = "http://localhost:8080/api/graphql" - - # 娴嬭瘯鏌ヨ - query = """ - mutation { - wxLogin(input: { - code: "test_code_123" - loginIp: "127.0.0.1" - deviceInfo: "Test Device" - phoneAuthorized: false - }) { - token - userInfo { - userId - name - userType - } - isNewUser - loginRecordId - } - } - """ - - # 璇锋眰鏁版嵁 - data = { - "query": query - } - - # 鍙戦�佽姹� - headers = { - "Content-Type": "application/json" - } - - try: - print("=== 娴嬭瘯寰俊鐧诲綍鎺ュ彛 ===") - print(f"璇锋眰URL: {url}") - print(f"璇锋眰鏁版嵁: {json.dumps(data, indent=2, ensure_ascii=False)}") - - response = requests.post(url, json=data, headers=headers) - - print(f"鍝嶅簲鐘舵�佺爜: {response.status_code}") - print(f"鍝嶅簲鍐呭: {json.dumps(response.json(), indent=2, ensure_ascii=False)}") - - if response.status_code == 200: - result = response.json() - if "errors" in result: - print("鉂� GraphQL閿欒:") - for error in result["errors"]: - print(f" - {error.get('message', '鏈煡閿欒')}") - if "extensions" in error: - print(f" 鍒嗙被: {error['extensions'].get('classification', '鏈煡')}") - else: - print("鉁� 璇锋眰鎴愬姛") - else: - print(f"鉂� HTTP閿欒: {response.status_code}") - - except Exception as e: - print(f"鉂� 璇锋眰寮傚父: {e}") - -if __name__ == "__main__": - test_wechat_login() \ No newline at end of file diff --git a/check_activity_dates.js b/check_activity_dates.js deleted file mode 100644 index 0e1417b..0000000 --- a/check_activity_dates.js +++ /dev/null @@ -1,94 +0,0 @@ -const mysql = require('mysql2/promise'); - -async function checkActivityDates() { - let connection; - - try { - // 鍒涘缓鏁版嵁搴撹繛鎺� - connection = await mysql.createConnection({ - host: '139.155.104.10', - port: 3306, - user: 'ryc', - password: 'KiYap3E8X8RLcM6T', - database: 'ryc', - connectTimeout: 60000, - acquireTimeout: 60000, - timeout: 60000 - }); - - console.log('鏁版嵁搴撹繛鎺ユ垚鍔燂紒'); - - // 鏌ヨ娲诲姩鏁版嵁 - const [rows] = await connection.execute(` - SELECT - id, - name, - signup_deadline, - match_time, - address, - player_max, - state, - create_time, - update_time - FROM t_activity - WHERE state = 1 - ORDER BY id DESC - LIMIT 10 - `); - - console.log('\n=== 娲诲姩鏁版嵁妫�鏌� ==='); - console.log(`鎵惧埌 ${rows.length} 鏉℃椿鍔ㄨ褰曪細\n`); - - rows.forEach((row, index) => { - console.log(`${index + 1}. 娲诲姩ID: ${row.id}`); - console.log(` 鍚嶇О: ${row.name}`); - console.log(` 鎶ュ悕鎴鏃堕棿: ${row.signup_deadline || '鏈缃�'}`); - console.log(` 姣旇禌鏃堕棿: ${row.match_time || '鏈缃�'}`); - console.log(` 鍦板潃: ${row.address || '鏈缃�'}`); - console.log(` 鏈�澶т汉鏁�: ${row.player_max || '鏈檺鍒�'}`); - console.log(` 鐘舵��: ${row.state}`); - console.log(` 鍒涘缓鏃堕棿: ${row.create_time}`); - console.log(` 鏇存柊鏃堕棿: ${row.update_time}`); - console.log(' ---'); - }); - - // 缁熻鏃堕棿瀛楁涓虹┖鐨勮褰� - const [emptyDates] = await connection.execute(` - SELECT - COUNT(*) as total, - SUM(CASE WHEN signup_deadline IS NULL THEN 1 ELSE 0 END) as empty_signup_deadline, - SUM(CASE WHEN match_time IS NULL THEN 1 ELSE 0 END) as empty_match_time - FROM t_activity - WHERE state = 1 - `); - - console.log('\n=== 鏃堕棿瀛楁缁熻 ==='); - console.log(`鎬绘椿鍔ㄦ暟: ${emptyDates[0].total}`); - console.log(`鎶ュ悕鎴鏃堕棿涓虹┖: ${emptyDates[0].empty_signup_deadline}`); - console.log(`姣旇禌鏃堕棿涓虹┖: ${emptyDates[0].empty_match_time}`); - - // 妫�鏌ユ渶杩戠殑娲诲姩璇︽儏 - const [latestActivity] = await connection.execute(` - SELECT * FROM t_activity WHERE state = 1 ORDER BY id DESC LIMIT 1 - `); - - if (latestActivity.length > 0) { - console.log('\n=== 鏈�鏂版椿鍔ㄨ鎯� ==='); - const activity = latestActivity[0]; - console.log('瀹屾暣娲诲姩淇℃伅:'); - console.log(JSON.stringify(activity, null, 2)); - } - - } catch (error) { - console.error('鏁版嵁搴撴煡璇㈠け璐�:', error.message); - console.error('閿欒璇︽儏:', error); - } finally { - if (connection) { - await connection.end(); - console.log('\n鏁版嵁搴撹繛鎺ュ凡鍏抽棴'); - } - } -} - -// 杩愯鏌ヨ -checkActivityDates(); \ No newline at end of file diff --git a/check_login_records.js b/check_login_records.js deleted file mode 100644 index df8f183..0000000 --- a/check_login_records.js +++ /dev/null @@ -1,99 +0,0 @@ -const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); - -// 閰嶇疆 -const GRAPHQL_URL = 'http://localhost:8080/api/graphql'; - -// 妫�鏌ョ櫥褰曡褰曠殑GraphQL鏌ヨ -const CHECK_LOGIN_RECORDS_QUERY = ` - query { - loginRecords: getAllLoginRecords { - id - userId - wxOpenid - wxSessionKey - loginTime - loginIp - deviceInfo - user { - id - username - phone - } - } - } -`; - -async function checkLoginRecords() { - console.log('馃攳 妫�鏌ユ暟鎹簱涓殑鐧诲綍璁板綍...\n'); - - try { - const response = await fetch(GRAPHQL_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: CHECK_LOGIN_RECORDS_QUERY - }) - }); - - const result = await response.json(); - - if (result.errors) { - console.log('鉂� GraphQL鏌ヨ澶辫触:', result.errors[0].message); - return; - } - - const records = result.data.loginRecords; - console.log(`馃搳 鎵惧埌 ${records.length} 鏉$櫥褰曡褰�:\n`); - - records.forEach((record, index) => { - console.log(`璁板綍 ${index + 1}:`); - console.log(` - ID: ${record.id}`); - console.log(` - 鐢ㄦ埛ID: ${record.userId}`); - console.log(` - 寰俊OpenID: ${record.wxOpenid}`); - console.log(` - SessionKey: ${record.wxSessionKey ? record.wxSessionKey.substring(0, 10) + '...' : '鏃�'}`); - console.log(` - 鐧诲綍鏃堕棿: ${record.loginTime}`); - console.log(` - 鐧诲綍IP: ${record.loginIp}`); - console.log(` - 璁惧淇℃伅: ${record.deviceInfo}`); - if (record.user) { - console.log(` - 鐢ㄦ埛鍚�: ${record.user.username}`); - console.log(` - 鎵嬫満鍙�: ${record.user.phone || '鏈缃�'}`); - } - console.log(''); - }); - - // 鍒嗘瀽鏁版嵁 - const testDataRecords = records.filter(r => - r.wxOpenid && (r.wxOpenid.includes('test') || r.wxOpenid.includes('mock')) - ); - - const realDataRecords = records.filter(r => - r.wxOpenid && !r.wxOpenid.includes('test') && !r.wxOpenid.includes('mock') - ); - - console.log('馃搱 鏁版嵁鍒嗘瀽:'); - console.log(` - 娴嬭瘯鏁版嵁璁板綍: ${testDataRecords.length} 鏉); - console.log(` - 鐪熷疄鏁版嵁璁板綍: ${realDataRecords.length} 鏉); - - if (testDataRecords.length > 0) { - console.log('\n鈿狅笍 鍙戠幇娴嬭瘯鏁版嵁璁板綍:'); - testDataRecords.forEach(record => { - console.log(` - OpenID: ${record.wxOpenid}`); - }); - } - - if (realDataRecords.length > 0) { - console.log('\n鉁� 鍙戠幇鐪熷疄鏁版嵁璁板綍:'); - realDataRecords.forEach(record => { - console.log(` - OpenID: ${record.wxOpenid.substring(0, 10)}...`); - }); - } - - } catch (error) { - console.error('鉂� 妫�鏌ョ櫥褰曡褰曞け璐�:', error.message); - } -} - -// 杩愯妫�鏌� -checkLoginRecords(); \ No newline at end of file diff --git a/clear_tables.sql b/clear_tables.sql new file mode 100644 index 0000000..e9ab42b --- /dev/null +++ b/clear_tables.sql @@ -0,0 +1,42 @@ +-- 娓呯┖鎸囧畾琛ㄧ殑鏁版嵁 +-- 鎸夌収澶栭敭渚濊禆鍏崇郴鐨勯『搴忓垹闄� + +SET FOREIGN_KEY_CHECKS = 0; + +-- 娓呯┖璇勫垎鐩稿叧琛� +DELETE FROM t_activity_rating_item; +DELETE FROM t_activity_rating; + +-- 娓呯┖娲诲姩閫夋墜琛� +DELETE FROM t_activity_player; + +-- 娓呯┖娲诲姩璇勫琛� +DELETE FROM t_activity_judge; + +-- 娓呯┖閫夋墜琛� +DELETE FROM t_player; + +-- 娓呯┖娲诲姩琛� +DELETE FROM t_activity; + +SET FOREIGN_KEY_CHECKS = 1; + +-- 楠岃瘉娓呯┖缁撴灉 +SELECT 'Tables cleared successfully' as result; +SELECT + 't_activity' as table_name, COUNT(*) as record_count FROM t_activity +UNION ALL +SELECT + 't_activity_player' as table_name, COUNT(*) as record_count FROM t_activity_player +UNION ALL +SELECT + 't_activity_judge' as table_name, COUNT(*) as record_count FROM t_activity_judge +UNION ALL +SELECT + 't_activity_rating' as table_name, COUNT(*) as record_count FROM t_activity_rating +UNION ALL +SELECT + 't_activity_rating_item' as table_name, COUNT(*) as record_count FROM t_activity_rating_item +UNION ALL +SELECT + 't_player' as table_name, COUNT(*) as record_count FROM t_player; \ No newline at end of file diff --git a/db.sql b/db.sql index b67c704..7b2b5b0 100644 --- a/db.sql +++ b/db.sql @@ -1,7 +1,16 @@ --- Database schema for ryc --- Generated at: 2025/9/27 19:34:25 +-- 鏁版嵁搴撶粨鏋勫鍑� +-- 鏁版嵁搴�: ryc +-- 瀵煎嚭鏃堕棿: 2025/9/30 08:39:43 +-- +-- 娉ㄦ剰锛氭鏂囦欢浠呭寘鍚〃缁撴瀯锛屼笉鍖呭惈鏁版嵁 --- Table: t_activity +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_activity +-- ---------------------------- +DROP TABLE IF EXISTS `t_activity`; CREATE TABLE `t_activity` ( `id` bigint NOT NULL AUTO_INCREMENT, `pid` bigint NOT NULL DEFAULT '0', @@ -23,9 +32,12 @@ KEY `fk_t_activity_rating_scheme` (`rating_scheme_id`) USING BTREE, KEY `idx_t_activity_deadline` (`signup_deadline`) USING BTREE, CONSTRAINT `fk_t_activity_rating_scheme` FOREIGN KEY (`rating_scheme_id`) REFERENCES `t_rating_scheme` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE=InnoDB AUTO_INCREMENT=62 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_activity_judge +-- ---------------------------- +-- Table structure for t_activity_judge +-- ---------------------------- +DROP TABLE IF EXISTS `t_activity_judge`; CREATE TABLE `t_activity_judge` ( `id` bigint NOT NULL AUTO_INCREMENT, `activity_id` bigint NOT NULL, @@ -40,9 +52,12 @@ `version` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uq_stage_judge` (`stage_id`,`judge_id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_activity_player +-- ---------------------------- +-- Table structure for t_activity_player +-- ---------------------------- +DROP TABLE IF EXISTS `t_activity_player`; CREATE TABLE `t_activity_player` ( `id` bigint NOT NULL AUTO_INCREMENT, `activity_id` bigint NOT NULL, @@ -63,9 +78,12 @@ `update_user_id` bigint DEFAULT NULL, `version` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_activity_player_rating +-- ---------------------------- +-- Table structure for t_activity_player_rating +-- ---------------------------- +DROP TABLE IF EXISTS `t_activity_player_rating`; CREATE TABLE `t_activity_player_rating` ( `id` bigint NOT NULL AUTO_INCREMENT, `activity_id` bigint NOT NULL, @@ -85,7 +103,10 @@ PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- Table: t_activity_player_rating_item +-- ---------------------------- +-- Table structure for t_activity_player_rating_item +-- ---------------------------- +DROP TABLE IF EXISTS `t_activity_player_rating_item`; CREATE TABLE `t_activity_player_rating_item` ( `id` bigint NOT NULL AUTO_INCREMENT, `activity_id` bigint NOT NULL, @@ -107,7 +128,10 @@ PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_carousel +-- ---------------------------- +-- Table structure for t_carousel +-- ---------------------------- +DROP TABLE IF EXISTS `t_carousel`; CREATE TABLE `t_carousel` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -122,7 +146,10 @@ PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='杞挱鍥�'; --- Table: t_employee +-- ---------------------------- +-- Table structure for t_employee +-- ---------------------------- +DROP TABLE IF EXISTS `t_employee`; CREATE TABLE `t_employee` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -138,9 +165,12 @@ `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `phone` (`phone`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_employee_role +-- ---------------------------- +-- Table structure for t_employee_role +-- ---------------------------- +DROP TABLE IF EXISTS `t_employee_role`; CREATE TABLE `t_employee_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `employee_id` bigint NOT NULL, @@ -154,7 +184,10 @@ KEY `fk_t_user_role_role` (`role_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_judge +-- ---------------------------- +-- Table structure for t_judge +-- ---------------------------- +DROP TABLE IF EXISTS `t_judge`; CREATE TABLE `t_judge` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -174,9 +207,12 @@ `introduction` text COMMENT '涓汉浠嬬粛', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `phone` (`phone`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_judge_tag +-- ---------------------------- +-- Table structure for t_judge_tag +-- ---------------------------- +DROP TABLE IF EXISTS `t_judge_tag`; CREATE TABLE `t_judge_tag` ( `id` bigint NOT NULL AUTO_INCREMENT, `judge_id` bigint NOT NULL, @@ -189,9 +225,12 @@ `version` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE, KEY `fk_t_judge_major_tag` (`tag_id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=97 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_media +-- ---------------------------- +-- Table structure for t_media +-- ---------------------------- +DROP TABLE IF EXISTS `t_media`; CREATE TABLE `t_media` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, @@ -210,10 +249,62 @@ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_user_id` bigint DEFAULT NULL, `version` bigint NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + PRIMARY KEY (`id`) USING BTREE, + KEY `uq_type_id` (`target_type`,`target_id`) +) ENGINE=InnoDB AUTO_INCREMENT=116 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_notification_task +-- ---------------------------- +-- Table structure for t_media_backup_avatar_migration +-- ---------------------------- +DROP TABLE IF EXISTS `t_media_backup_avatar_migration`; +CREATE TABLE `t_media_backup_avatar_migration` ( + `id` int NOT NULL DEFAULT '0', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `target_type` int NOT NULL, + `target_id` bigint NOT NULL, + `media_type` int NOT NULL, + `path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '鑵捐浜戠殑瀛樺偍妗跺湴鍧�', + `thumb_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `file_ext` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `file_size` int NOT NULL, + `duration` int DEFAULT NULL COMMENT '瑙嗛鐨勯暱搴︾', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `state` int NOT NULL DEFAULT '1', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `create_user_id` bigint DEFAULT NULL, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `update_user_id` bigint DEFAULT NULL, + `version` bigint NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table structure for t_msg +-- ---------------------------- +DROP TABLE IF EXISTS `t_msg`; +CREATE TABLE `t_msg` ( + `id` int NOT NULL AUTO_INCREMENT, + `target_type` int NOT NULL, + `target_id` int NOT NULL, + `player_id` int NOT NULL, + `user_id` int NOT NULL, + `content` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `template_content` varchar(200) COLLATE utf8mb4_general_ci DEFAULT NULL, + `wx_msg_success` bit(1) NOT NULL, + `wx_msg_err_count` int NOT NULL DEFAULT '0', + `wx_msg_last_err` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, + `state` int NOT NULL COMMENT '0:鏆傛椂涓嶅彂甯冿紝 1锛氬彲浠ュ彂甯冿紝2锛氬凡缁忓彂甯�', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `create_user_id` bigint DEFAULT NULL, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `update_user_id` bigint DEFAULT NULL, + `version` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Table structure for t_notification_task +-- ---------------------------- +DROP TABLE IF EXISTS `t_notification_task`; CREATE TABLE `t_notification_task` ( `id` bigint NOT NULL AUTO_INCREMENT, `type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -233,7 +324,10 @@ PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_permission +-- ---------------------------- +-- Table structure for t_permission +-- ---------------------------- +DROP TABLE IF EXISTS `t_permission`; CREATE TABLE `t_permission` ( `id` bigint NOT NULL AUTO_INCREMENT, `code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -251,7 +345,10 @@ KEY `idx_t_permission_code` (`code`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_player +-- ---------------------------- +-- Table structure for t_player +-- ---------------------------- +DROP TABLE IF EXISTS `t_player`; CREATE TABLE `t_player` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, @@ -272,9 +369,12 @@ `user_id` bigint NOT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `phone` (`phone`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_rating_item +-- ---------------------------- +-- Table structure for t_rating_item +-- ---------------------------- +DROP TABLE IF EXISTS `t_rating_item`; CREATE TABLE `t_rating_item` ( `id` bigint NOT NULL AUTO_INCREMENT, `scheme_id` bigint NOT NULL, @@ -288,11 +388,13 @@ `update_user_id` bigint DEFAULT NULL, `version` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE, - KEY `idx_t_rating_item_scheme` (`scheme_id`) USING BTREE, - CONSTRAINT `fk_t_rating_item_scheme` FOREIGN KEY (`scheme_id`) REFERENCES `t_rating_scheme` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT -) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + KEY `idx_t_rating_item_scheme` (`scheme_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_rating_scheme +-- ---------------------------- +-- Table structure for t_rating_scheme +-- ---------------------------- +DROP TABLE IF EXISTS `t_rating_scheme`; CREATE TABLE `t_rating_scheme` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -304,9 +406,12 @@ `update_user_id` bigint DEFAULT NULL, `version` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_region +-- ---------------------------- +-- Table structure for t_region +-- ---------------------------- +DROP TABLE IF EXISTS `t_region`; CREATE TABLE `t_region` ( `id` bigint NOT NULL AUTO_INCREMENT, `pid` bigint NOT NULL COMMENT '鑷叧鑱�', @@ -323,9 +428,12 @@ `version` bigint NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `code` (`code`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=142 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_role +-- ---------------------------- +-- Table structure for t_role +-- ---------------------------- +DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -342,7 +450,10 @@ KEY `idx_t_role_code` (`code`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_role_permission +-- ---------------------------- +-- Table structure for t_role_permission +-- ---------------------------- +DROP TABLE IF EXISTS `t_role_permission`; CREATE TABLE `t_role_permission` ( `id` int NOT NULL AUTO_INCREMENT, `role_id` bigint NOT NULL, @@ -356,7 +467,10 @@ KEY `fk_t_role_permission_perm` (`permission_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_tag +-- ---------------------------- +-- Table structure for t_tag +-- ---------------------------- +DROP TABLE IF EXISTS `t_tag`; CREATE TABLE `t_tag` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -374,7 +488,10 @@ KEY `idx_t_tag_category` (`category`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_user +-- ---------------------------- +-- Table structure for t_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, @@ -395,9 +512,12 @@ PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uq_wx_open_id` (`wx_openid`) USING BTREE, UNIQUE KEY `uq_phone` (`phone`) -) ENGINE=InnoDB AUTO_INCREMENT=62 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=133 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- Table: t_wx_login_record +-- ---------------------------- +-- Table structure for t_wx_login_record +-- ---------------------------- +DROP TABLE IF EXISTS `t_wx_login_record`; CREATE TABLE `t_wx_login_record` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID', `wx_openid` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT '寰俊openid', @@ -411,12 +531,18 @@ `phone_auth_time` datetime DEFAULT NULL COMMENT '鎵嬫満鍙锋巿鏉冩椂闂�', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿', - `state` tinyint(1) DEFAULT '1' COMMENT '鐘舵�侊細0-绂佺敤锛�1-鍚敤', + `state` int DEFAULT '1' COMMENT '鐘舵�侊細0-绂佺敤锛�1-鍚敤', + `create_user_id` bigint DEFAULT NULL COMMENT '鍒涘缓鐢ㄦ埛ID', + `update_user_id` bigint DEFAULT NULL COMMENT '鏇存柊鐢ㄦ埛ID', + `version` bigint NOT NULL DEFAULT '0' COMMENT '鐗堟湰鍙�', PRIMARY KEY (`id`), KEY `idx_wx_openid` (`wx_openid`), KEY `idx_wx_unionid` (`wx_unionid`), KEY `idx_user_id` (`user_id`), KEY `idx_login_time` (`login_time`), - KEY `idx_phone_authorized` (`phone_authorized`) -) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='寰俊鐧诲綍璁板綍琛�'; + KEY `idx_phone_authorized` (`phone_authorized`), + KEY `idx_create_user_id` (`create_user_id`), + KEY `idx_update_user_id` (`update_user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=116 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='寰俊鐧诲綍璁板綍琛�'; +SET FOREIGN_KEY_CHECKS = 1; diff --git a/debug_phone_error.js b/debug_phone_error.js deleted file mode 100644 index 562ac0f..0000000 --- a/debug_phone_error.js +++ /dev/null @@ -1,158 +0,0 @@ -const axios = require('axios'); - -async function debugPhoneError() { - console.log('馃攳 璇婃柇鎵嬫満鍙疯幏鍙朅PI閿欒'); - console.log('='.repeat(50)); - - const backendUrl = 'http://localhost:8080/api/graphql'; - - // 娴嬭瘯1: 妫�鏌ユ柊鐗圓PI鐨勮缁嗛敊璇� - console.log('\n1锔忊儯 娴嬭瘯鏂扮増API (getPhoneNumberByCode)'); - try { - const newApiResponse = await axios.post(backendUrl, { - query: ` - mutation GetPhoneNumberByCode($code: String!) { - getPhoneNumberByCode(code: $code) { - phoneNumber - purePhoneNumber - countryCode - } - } - `, - variables: { code: "test_code_from_wechat" } - }, { - headers: { 'Content-Type': 'application/json' } - }); - - console.log('鍝嶅簲鐘舵��:', newApiResponse.status); - console.log('鍝嶅簲鏁版嵁:', JSON.stringify(newApiResponse.data, null, 2)); - - if (newApiResponse.data.errors) { - console.log('\n鉂� 鏂扮増API閿欒璇︽儏:'); - newApiResponse.data.errors.forEach((error, index) => { - console.log(`閿欒 ${index + 1}:`, error.message); - if (error.extensions) { - console.log('鎵╁睍淇℃伅:', error.extensions); - } - }); - } - - } catch (error) { - console.error('鉂� 鏂扮増API璇锋眰澶辫触:', error.message); - if (error.response) { - console.log('閿欒鍝嶅簲:', error.response.data); - } - } - - // 娴嬭瘯2: 妫�鏌ユ棫鐗圓PI鐨勮缁嗛敊璇� - console.log('\n2锔忊儯 娴嬭瘯鏃х増API (decryptPhoneNumber)'); - try { - const oldApiResponse = await axios.post(backendUrl, { - query: ` - mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!, $sessionKey: String!) { - decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv, sessionKey: $sessionKey) { - phoneNumber - purePhoneNumber - countryCode - } - } - `, - variables: { - encryptedData: "test_encrypted_data", - iv: "test_iv_value", - sessionKey: "test_session_key" - } - }, { - headers: { 'Content-Type': 'application/json' } - }); - - console.log('鍝嶅簲鐘舵��:', oldApiResponse.status); - console.log('鍝嶅簲鏁版嵁:', JSON.stringify(oldApiResponse.data, null, 2)); - - if (oldApiResponse.data.errors) { - console.log('\n鉂� 鏃х増API閿欒璇︽儏:'); - oldApiResponse.data.errors.forEach((error, index) => { - console.log(`閿欒 ${index + 1}:`, error.message); - if (error.extensions) { - console.log('鎵╁睍淇℃伅:', error.extensions); - } - }); - } - - } catch (error) { - console.error('鉂� 鏃х増API璇锋眰澶辫触:', error.message); - if (error.response) { - console.log('閿欒鍝嶅簲:', error.response.data); - } - } - - // 娴嬭瘯3: 妫�鏌ュ井淇PI閰嶇疆 - console.log('\n3锔忊儯 妫�鏌ュ彲鑳界殑閰嶇疆闂'); - console.log('甯歌閿欒鍘熷洜:'); - console.log('- 寰俊AppSecret鏈厤缃垨閰嶇疆閿欒'); - console.log('- 寰俊API璋冪敤棰戠巼闄愬埗'); - console.log('- code鍙傛暟鏃犳晥鎴栧凡杩囨湡'); - console.log('- access_token鑾峰彇澶辫触'); - console.log('- 缃戠粶杩炴帴闂'); - - // 娴嬭瘯4: 妯℃嫙鐪熷疄鐨勫皬绋嬪簭璋冪敤 - console.log('\n4锔忊儯 妯℃嫙灏忕▼搴忚皟鐢ㄥ満鏅�'); - - // 鍦烘櫙1: 鍙湁code鍙傛暟锛堟柊鐗圓PI锛� - console.log('\n鍦烘櫙1: 鍙湁code鍙傛暟'); - try { - const scenario1 = await axios.post(backendUrl, { - query: ` - mutation GetPhoneNumberByCode($code: String!) { - getPhoneNumberByCode(code: $code) { - phoneNumber - purePhoneNumber - countryCode - } - } - `, - variables: { code: "" } // 绌篶ode - }, { - headers: { 'Content-Type': 'application/json' } - }); - - if (scenario1.data.errors) { - console.log('绌篶ode閿欒:', scenario1.data.errors[0].message); - } - } catch (error) { - console.log('绌篶ode娴嬭瘯閿欒:', error.message); - } - - // 鍦烘櫙2: 缂哄皯蹇呰鍙傛暟 - console.log('\n鍦烘櫙2: 缂哄皯蹇呰鍙傛暟'); - try { - const scenario2 = await axios.post(backendUrl, { - query: ` - mutation GetPhoneNumberByCode { - getPhoneNumberByCode { - phoneNumber - purePhoneNumber - countryCode - } - } - ` - }, { - headers: { 'Content-Type': 'application/json' } - }); - - if (scenario2.data.errors) { - console.log('缂哄皯鍙傛暟閿欒:', scenario2.data.errors[0].message); - } - } catch (error) { - console.log('缂哄皯鍙傛暟娴嬭瘯閿欒:', error.message); - } - - console.log('\n馃敡 寤鸿鐨勮В鍐虫柟妗�:'); - console.log('1. 妫�鏌ュ井淇″皬绋嬪簭鐨凙ppSecret鐜鍙橀噺閰嶇疆'); - console.log('2. 纭浼犲叆鐨刢ode鍙傛暟鏄湁鏁堢殑寰俊杩斿洖鍊�'); - console.log('3. 妫�鏌ョ綉缁滆繛鎺ュ拰寰俊API鏈嶅姟鐘舵��'); - console.log('4. 鏌ョ湅鍚庣鏃ュ織鑾峰彇鏇磋缁嗙殑閿欒淇℃伅'); - console.log('5. 楠岃瘉寰俊灏忕▼搴忕殑鍩虹搴撶増鏈拰API鏉冮檺'); -} - -debugPhoneError().catch(console.error); \ No newline at end of file diff --git a/fix_wx_login_table.js b/fix_wx_login_table.js deleted file mode 100644 index 0f38476..0000000 --- a/fix_wx_login_table.js +++ /dev/null @@ -1,154 +0,0 @@ -const mysql = require('mysql2/promise'); - -async function fixWxLoginTable() { - let connection; - - try { - // 鍒涘缓鏁版嵁搴撹繛鎺� - connection = await mysql.createConnection({ - host: '139.155.104.10', - port: 3306, - user: 'ryc', - password: 'KiYap3E8X8RLcM6T', - database: 'ryc' - }); - - console.log('鉁� 鏁版嵁搴撹繛鎺ユ垚鍔�'); - - // 妫�鏌ヨ〃鏄惁瀛樺湪 - const [tables] = await connection.execute( - "SHOW TABLES LIKE 't_wx_login_record'" - ); - - if (tables.length === 0) { - console.log('鉂� 琛� t_wx_login_record 涓嶅瓨鍦�'); - return; - } - - console.log('鉁� 琛� t_wx_login_record 瀛樺湪'); - - // 妫�鏌ュ綋鍓嶈〃缁撴瀯 - const [columns] = await connection.execute( - "DESCRIBE t_wx_login_record" - ); - - console.log('褰撳墠琛ㄧ粨鏋�:'); - columns.forEach(col => { - console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'YES' ? 'NULL' : 'NOT NULL'} ${col.Default ? `DEFAULT ${col.Default}` : ''}`); - }); - - // 妫�鏌ラ渶瑕佹坊鍔犵殑瀛楁 - const existingColumns = columns.map(col => col.Field); - const fieldsToAdd = []; - - if (!existingColumns.includes('create_user_id')) { - fieldsToAdd.push({ - name: 'create_user_id', - sql: 'ADD COLUMN create_user_id BIGINT NULL COMMENT \'鍒涘缓鐢ㄦ埛ID\'' - }); - } - - if (!existingColumns.includes('update_user_id')) { - fieldsToAdd.push({ - name: 'update_user_id', - sql: 'ADD COLUMN update_user_id BIGINT NULL COMMENT \'鏇存柊鐢ㄦ埛ID\'' - }); - } - - if (!existingColumns.includes('version')) { - fieldsToAdd.push({ - name: 'version', - sql: 'ADD COLUMN version BIGINT NOT NULL DEFAULT 0 COMMENT \'鐗堟湰鍙穃'' - }); - } - - if (fieldsToAdd.length === 0) { - console.log('鉁� 鎵�鏈夊繀闇�瀛楁閮藉凡瀛樺湪锛屾棤闇�淇敼'); - return; - } - - console.log(`闇�瑕佹坊鍔� ${fieldsToAdd.length} 涓瓧娈�:`); - fieldsToAdd.forEach(field => { - console.log(` - ${field.name}`); - }); - - // 娣诲姞瀛楁 - for (const field of fieldsToAdd) { - console.log(`姝e湪娣诲姞瀛楁: ${field.name}...`); - - try { - await connection.execute(`ALTER TABLE t_wx_login_record ${field.sql}`); - console.log(`鉁� 鎴愬姛娣诲姞瀛楁: ${field.name}`); - } catch (error) { - console.error(`鉂� 娣诲姞瀛楁 ${field.name} 澶辫触:`, error.message); - throw error; - } - } - - // 娣诲姞绱㈠紩 - console.log('姝e湪娣诲姞绱㈠紩...'); - - try { - // 妫�鏌ョ储寮曟槸鍚﹀凡瀛樺湪 - const [indexes] = await connection.execute( - "SHOW INDEX FROM t_wx_login_record WHERE Key_name = 'idx_create_user_id'" - ); - - if (indexes.length === 0) { - await connection.execute( - 'CREATE INDEX idx_create_user_id ON t_wx_login_record(create_user_id)' - ); - console.log('鉁� 鎴愬姛娣诲姞 create_user_id 绱㈠紩'); - } else { - console.log('鉁� create_user_id 绱㈠紩宸插瓨鍦�'); - } - } catch (error) { - console.log('鈿狅笍 娣诲姞 create_user_id 绱㈠紩澶辫触:', error.message); - } - - try { - const [indexes] = await connection.execute( - "SHOW INDEX FROM t_wx_login_record WHERE Key_name = 'idx_update_user_id'" - ); - - if (indexes.length === 0) { - await connection.execute( - 'CREATE INDEX idx_update_user_id ON t_wx_login_record(update_user_id)' - ); - console.log('鉁� 鎴愬姛娣诲姞 update_user_id 绱㈠紩'); - } else { - console.log('鉁� update_user_id 绱㈠紩宸插瓨鍦�'); - } - } catch (error) { - console.log('鈿狅笍 娣诲姞 update_user_id 绱㈠紩澶辫触:', error.message); - } - - // 楠岃瘉淇敼缁撴灉 - console.log('楠岃瘉淇敼缁撴灉...'); - const [newColumns] = await connection.execute( - "DESCRIBE t_wx_login_record" - ); - - console.log('淇敼鍚庣殑琛ㄧ粨鏋�:'); - newColumns.forEach(col => { - console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'YES' ? 'NULL' : 'NOT NULL'} ${col.Default !== null ? `DEFAULT ${col.Default}` : ''}`); - }); - - console.log('鉁� 琛ㄧ粨鏋勪慨澶嶅畬鎴愶紒'); - - } catch (error) { - console.error('鉂� 淇琛ㄧ粨鏋勬椂鍙戠敓閿欒:', error); - throw error; - } finally { - if (connection) { - await connection.end(); - console.log('鏁版嵁搴撹繛鎺ュ凡鍏抽棴'); - } - } -} - -// 杩愯淇鑴氭湰 -fixWxLoginTable().catch(error => { - console.error('鑴氭湰鎵ц澶辫触:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/t_media.sql b/t_media.sql new file mode 100644 index 0000000..89bcc10 --- /dev/null +++ b/t_media.sql @@ -0,0 +1,57 @@ +/* + Navicat Premium Dump SQL + + Source Server : 钃夋槗鍒� + Source Server Type : MySQL + Source Server Version : 80405 (8.4.5) + Source Host : 139.155.104.10:3306 + Source Schema : ryc + + Target Server Type : MySQL + Target Server Version : 80405 (8.4.5) + File Encoding : 65001 + + Date: 30/09/2025 09:55:45 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_media +-- ---------------------------- +DROP TABLE IF EXISTS `t_media`; +CREATE TABLE `t_media` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `target_type` int NOT NULL, + `target_id` bigint NOT NULL, + `media_type` int NOT NULL, + `path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '鑵捐浜戠殑瀛樺偍妗跺湴鍧�', + `thumb_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `file_ext` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `file_size` int NOT NULL, + `duration` int NULL DEFAULT NULL COMMENT '瑙嗛鐨勯暱搴︾', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `state` int NOT NULL DEFAULT 1, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `create_user_id` bigint NULL DEFAULT NULL, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `update_user_id` bigint NULL DEFAULT NULL, + `version` bigint NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE, + INDEX `uq_type_id`(`target_type` ASC, `target_id` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 119 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of t_media +-- ---------------------------- +INSERT INTO `t_media` VALUES (112, 'judge_avatar.jpg', 1, 65, 1, 'avatars/judge_avatar_1759142940287.jpg', NULL, 'jpg', 1024, NULL, NULL, 1, '2025-09-29 18:48:59', NULL, '2025-09-29 18:48:59', NULL, 0); +INSERT INTO `t_media` VALUES (113, 'logo.jpg', 1, 66, 1, '20250929/b5840d47-b046-4461-9d78-27881c917f03.jpg', NULL, 'jpg', 67671, NULL, NULL, 1, '2025-09-29 18:49:02', NULL, '2025-09-29 18:49:02', NULL, 0); +INSERT INTO `t_media` VALUES (114, 'a1.png', 2, 73, 1, '20250929/bc8be85a-c0d9-4d75-885e-cb19d613e00f.png', NULL, 'png', 3628657, NULL, NULL, 1, '2025-09-29 19:16:11', NULL, '2025-09-29 19:16:11', NULL, 0); +INSERT INTO `t_media` VALUES (115, 'bug1.png', 2, 73, 1, '20250929/23aee0a8-dc1e-4ec5-b4a6-2b80370940cb.png', NULL, 'png', 84959, NULL, NULL, 1, '2025-09-29 19:16:11', NULL, '2025-09-29 19:16:11', NULL, 0); +INSERT INTO `t_media` VALUES (116, 'mg5v05lk-vchro9dh-dn24k81r-ibqm.jpg', 7, 2, 1, 'avatars/20250930/mg5v05lk-vchro9dh-dn24k81r-ibqm.jpg', NULL, 'jpg', 3624223, NULL, NULL, 1, '2025-09-30 09:08:55', NULL, '2025-09-30 09:08:55', NULL, 0); +INSERT INTO `t_media` VALUES (117, 'mg5v06il-rei95zgu-f9okwt82-iruh.png', 5, 51, 1, 'attachments/20250930/mg5v06il-rei95zgu-f9okwt82-iruh.png', NULL, 'png', 93686, NULL, NULL, 1, '2025-09-30 09:08:55', NULL, '2025-09-30 09:08:55', NULL, 0); +INSERT INTO `t_media` VALUES (118, 'test-avatar.jpg', 7, 1, 1, 'avatars/test-avatar.jpg', NULL, 'jpg', 50000, NULL, NULL, 1, '2025-09-30 09:32:13', NULL, '2025-09-30 09:32:13', NULL, 0); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/verify_birthday_data.js b/verify_birthday_data.js deleted file mode 100644 index b40c20a..0000000 --- a/verify_birthday_data.js +++ /dev/null @@ -1,86 +0,0 @@ -const mysql = require('mysql2/promise'); - -// 鏁版嵁搴撹繛鎺ラ厤缃� -const dbConfig = { - host: '139.155.104.10', - port: 3306, - user: 'ryc', - password: 'KiYap3E8X8RLcM6T', - database: 'ryc', - connectTimeout: 60000, - acquireTimeout: 60000, - timeout: 60000 -}; - -async function verifyBirthdayData() { - let connection; - - try { - console.log('杩炴帴鏁版嵁搴�...'); - connection = await mysql.createConnection(dbConfig); - - // 鏌ヨ鏈�杩戠殑鎶ュ悕璁板綍鍙婂叾鍏宠仈鐨勭敤鎴蜂俊鎭� - const query = ` - SELECT - ap.id as registration_id, - ap.project_name, - ap.description as registration_description, - p.name as player_name, - p.phone as player_phone, - p.gender, - p.education, - p.introduction, - u.id as user_id, - u.name as user_name, - u.phone as user_phone, - u.birthday, - ap.create_time - FROM t_activity_player ap - LEFT JOIN t_player p ON ap.player_id = p.id - LEFT JOIN t_user u ON p.user_id = u.id - WHERE ap.id IN (25, 26) - ORDER BY ap.create_time DESC - LIMIT 5 - `; - - console.log('鏌ヨ鏈�杩戠殑鎶ュ悕璁板綍...'); - const [rows] = await connection.execute(query); - - if (rows.length > 0) { - console.log('\n鉁� 鎵惧埌鎶ュ悕璁板綍:'); - rows.forEach((row, index) => { - console.log(`\n--- 璁板綍 ${index + 1} ---`); - console.log(`鎶ュ悕ID: ${row.registration_id}`); - console.log(`閫夋墜濮撳悕: ${row.player_name}`); - console.log(`閫夋墜鐢佃瘽: ${row.player_phone}`); - console.log(`鎬у埆: ${row.gender === 0 ? '鐢�' : '濂�'}`); - console.log(`瀛﹀巻: ${row.education}`); - console.log(`椤圭洰鍚嶇О: ${row.project_name}`); - console.log(`鎶ュ悕鎻忚堪: ${row.registration_description}`); - console.log(`鐢ㄦ埛ID: ${row.user_id}`); - console.log(`鐢ㄦ埛濮撳悕: ${row.user_name}`); - console.log(`鐢ㄦ埛鐢佃瘽: ${row.user_phone}`); - console.log(`鐢熸棩: ${row.birthday}`); - console.log(`鎶ュ悕鏃堕棿: ${row.create_time}`); - - if (row.birthday) { - console.log('鉁� 鐢熸棩瀛楁宸叉纭繚瀛樺埌鐢ㄦ埛琛�'); - } else { - console.log('鉂� 鐢熸棩瀛楁鏈繚瀛樺埌鐢ㄦ埛琛�'); - } - }); - } else { - console.log('鉂� 鏈壘鍒版姤鍚嶈褰�'); - } - - } catch (error) { - console.error('鉂� 鏁版嵁搴撴煡璇㈠け璐�:', error.message); - } finally { - if (connection) { - await connection.end(); - console.log('\n鏁版嵁搴撹繛鎺ュ凡鍏抽棴'); - } - } -} - -verifyBirthdayData(); \ No newline at end of file diff --git a/web/src/api/activity.js b/web/src/api/activity.js index 9039862..6ea223a 100644 --- a/web/src/api/activity.js +++ b/web/src/api/activity.js @@ -1,6 +1,6 @@ // 姣旇禌绠$悊 API -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; +import { API_CONFIG, graphqlRequest } from '@/config/api'; // GraphQL 鏌ヨ璇彞 const GET_ACTIVITIES_QUERY = ` @@ -61,6 +61,7 @@ playerMax state stateName + sortOrder ratingScheme { id name @@ -85,6 +86,7 @@ name state stateName + sortOrder parent { id name @@ -118,95 +120,46 @@ // API 鍑芥暟 export const getActivities = async (page = 0, size = 10, name = '') => { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: GET_ACTIVITIES_QUERY, - variables: { page, size, name } - }) - }); - - const result = await response.json(); - if (result.errors) { - throw new Error(result.errors[0].message); + try { + const data = await graphqlRequest(GET_ACTIVITIES_QUERY, { page, size, name }); + return data.activities; + } catch (error) { + throw new Error(error.message || '鑾峰彇姣旇禌鍒楄〃澶辫触'); } - return result.data.activities; }; export const getActivity = async (id) => { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: GET_ACTIVITY_QUERY, - variables: { id } - }) - }); - - const result = await response.json(); - if (result.errors) { - throw new Error(result.errors[0].message); + try { + const data = await graphqlRequest(GET_ACTIVITY_QUERY, { id }); + return data.activity; + } catch (error) { + throw new Error(error.message || '鑾峰彇姣旇禌璇︽儏澶辫触'); } - return result.data.activity; }; export const getAllActivities = async () => { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: GET_ALL_ACTIVITIES_QUERY - }) - }); - - const result = await response.json(); - if (result.errors) { - throw new Error(result.errors[0].message); + try { + const data = await graphqlRequest(GET_ALL_ACTIVITIES_QUERY); + return data.allActivities; + } catch (error) { + throw new Error(error.message || '鑾峰彇鎵�鏈夋瘮璧涘け璐�'); } - return result.data.allActivities; }; export const saveActivity = async (activityData) => { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: SAVE_ACTIVITY_MUTATION, - variables: { input: activityData } - }) - }); - - const result = await response.json(); - if (result.errors) { - throw new Error(result.errors[0].message); + try { + const data = await graphqlRequest(SAVE_ACTIVITY_MUTATION, { input: activityData }); + return data.saveActivity; + } catch (error) { + throw new Error(error.message || '淇濆瓨姣旇禌澶辫触'); } - return result.data.saveActivity; }; export const deleteActivity = async (id) => { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: DELETE_ACTIVITY_MUTATION, - variables: { id } - }) - }); - - const result = await response.json(); - if (result.errors) { - throw new Error(result.errors[0].message); + try { + const data = await graphqlRequest(DELETE_ACTIVITY_MUTATION, { id }); + return data.deleteActivity; + } catch (error) { + throw new Error(error.message || '鍒犻櫎姣旇禌澶辫触'); } - return result.data.deleteActivity; }; \ No newline at end of file diff --git a/web/src/api/activityPlayer.js b/web/src/api/activityPlayer.js index 6c0ef11..0f6bdb4 100644 --- a/web/src/api/activityPlayer.js +++ b/web/src/api/activityPlayer.js @@ -8,25 +8,145 @@ createMockResponse } from './mockData.js' -const GRAPHQL_ENDPOINT = '/api/graphql' +import { API_CONFIG, graphqlRequest } from '@/config/api' // 妯℃嫙鏁版嵁寮�鍏� - 璁剧疆涓簍rue鏃朵娇鐢ㄦā鎷熸暟鎹� -// 宸插垏鎹㈠埌鐪熷疄鏁版嵁妯″紡 const USE_MOCK_DATA = false -async function graphqlRequest(query, variables = {}) { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) - }) - if (!response.ok) { - const text = await response.text() - throw new Error(`HTTP ${response.status}: ${text}`) +// GraphQL鏌ヨ璇彞 +const GET_ACTIVITY_PLAYERS_QUERY = ` + query GetActivityPlayers($activityId: ID!, $page: Int!, $size: Int!, $name: String) { + activityPlayers(activityId: $activityId, page: $page, size: $size, name: $name) { + content { + id + playerId + activityId + signupTime + state + stateName + player { + id + name + phone + regionId + region { + id + name + } + } + } + totalElements + page + size + } } - const result = await response.json() - if (result.errors) throw new Error(result.errors.map(e => e.message).join('\n')) - return result.data +` + +const GET_ACTIVITY_PLAYER_QUERY = ` + query GetActivityPlayer($id: ID!) { + activityPlayer(id: $id) { + id + playerId + activityId + signupTime + state + stateName + player { + id + name + phone + regionId + region { + id + name + } + } + activity { + id + name + description + } + attachments { + id + fileName + fileUrl + fileSize + uploadTime + } + } + } +` + +const SAVE_ACTIVITY_PLAYER_MUTATION = ` + mutation SaveActivityPlayer($input: ActivityPlayerInput!) { + saveActivityPlayer(input: $input) { + id + playerId + activityId + signupTime + state + stateName + } + } +` + +const DELETE_ACTIVITY_PLAYER_MUTATION = ` + mutation DeleteActivityPlayer($id: ID!) { + deleteActivityPlayer(id: $id) + } +` + +// API鍑芥暟 +export const getActivityPlayers = async (activityId, page = 0, size = 10, name = '') => { + if (USE_MOCK_DATA) { + return mockActivityPlayers + } + + try { + const data = await graphqlRequest(GET_ACTIVITY_PLAYERS_QUERY, { activityId, page, size, name }) + return data.activityPlayers + } catch (error) { + throw new Error(error.message || '鑾峰彇姣旇禌鎶ュ悕鍒楄〃澶辫触') + } +} + +export const getActivityPlayer = async (id) => { + if (USE_MOCK_DATA) { + return mockActivityPlayerDetail + } + + try { + const data = await graphqlRequest(GET_ACTIVITY_PLAYER_QUERY, { id }) + return data.activityPlayer + } catch (error) { + throw new Error(error.message || '鑾峰彇姣旇禌鎶ュ悕璇︽儏澶辫触') + } +} + +export const saveActivityPlayer = async (activityPlayerData) => { + if (USE_MOCK_DATA) { + return { ...activityPlayerData, id: Date.now().toString() } + } + + try { + const data = await graphqlRequest(SAVE_ACTIVITY_PLAYER_MUTATION, { input: activityPlayerData }) + return data.saveActivityPlayer + } catch (error) { + throw new Error(error.message || '淇濆瓨姣旇禌鎶ュ悕澶辫触') + } +} + +export const deleteActivityPlayer = async (id) => { + if (USE_MOCK_DATA) { + return true + } + + try { + const data = await graphqlRequest(DELETE_ACTIVITY_PLAYER_MUTATION, { id }) + return data.deleteActivityPlayer + } catch (error) { + throw new Error(error.message || '鍒犻櫎姣旇禌鎶ュ悕澶辫触') + } } const GET_ACTIVITY_PLAYER_DETAIL = ` @@ -46,11 +166,15 @@ fullPath } activityName + projectName description + feedback + state submissionFiles { id name url + thumbUrl fileExt fileSize mediaType @@ -193,4 +317,44 @@ })) } return graphqlRequest(GET_CURRENT_JUDGE_INFO) +} + +// 瀹℃牳鐩稿叧mutations +const APPROVE_ACTIVITY_PLAYER = ` + mutation ApproveActivityPlayer($activityPlayerId: ID!, $feedback: String) { + approveActivityPlayer(activityPlayerId: $activityPlayerId, feedback: $feedback) + } +` + +const REJECT_ACTIVITY_PLAYER = ` + mutation RejectActivityPlayer($activityPlayerId: ID!, $feedback: String!) { + rejectActivityPlayer(activityPlayerId: $activityPlayerId, feedback: $feedback) + } +` + +const UPDATE_PLAYER_FEEDBACK = ` + mutation UpdatePlayerFeedback($activityPlayerId: ID!, $feedback: String!) { + updatePlayerFeedback(activityPlayerId: $activityPlayerId, feedback: $feedback) + } +` + +/** + * 瀹℃牳閫氳繃 + */ +export function approveActivityPlayer(activityPlayerId, feedback = '') { + return graphqlRequest(APPROVE_ACTIVITY_PLAYER, { activityPlayerId, feedback }) +} + +/** + * 瀹℃牳椹冲洖 + */ +export function rejectActivityPlayer(activityPlayerId, feedback) { + return graphqlRequest(REJECT_ACTIVITY_PLAYER, { activityPlayerId, feedback }) +} + +/** + * 鏇存柊瀹℃牳鎰忚 + */ +export function updatePlayerFeedback(activityPlayerId, feedback) { + return graphqlRequest(UPDATE_PLAYER_FEEDBACK, { activityPlayerId, feedback }) } \ No newline at end of file diff --git a/web/src/api/carousel.js b/web/src/api/carousel.js index 773f117..802b3f6 100644 --- a/web/src/api/carousel.js +++ b/web/src/api/carousel.js @@ -1,30 +1,4 @@ -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql' - -// GraphQL璇锋眰鍑芥暟 -async function graphqlRequest(query, variables = {}) { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query, - variables, - }), - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - const result = await response.json() - - if (result.errors) { - throw new Error(result.errors[0].message) - } - - return result.data -} +import { graphqlRequest } from '../config/api.ts' // GraphQL 鏌ヨ鍜屽彉鏇� const GET_CAROUSELS = ` diff --git a/web/src/api/config.js b/web/src/api/config.js index 3197b69..09f9fa0 100644 --- a/web/src/api/config.js +++ b/web/src/api/config.js @@ -1,21 +1,49 @@ // 搴旂敤閰嶇疆 API -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; +import { API_CONFIG, graphqlRequest } from '@/config/api'; const GET_APP_CONFIG = ` - query AppConfig { + query GetAppConfig { appConfig { - mediaBaseUrl + id + name + value + description + type + createTime + updateTime } } `; -export const fetchAppConfig = async () => { - const resp = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query: GET_APP_CONFIG }) - }); - const result = await resp.json(); - if (result.errors) throw new Error(result.errors[0].message); - return result.data.appConfig; +const SAVE_APP_CONFIG = ` + mutation SaveAppConfig($input: AppConfigInput!) { + saveAppConfig(input: $input) { + id + name + value + description + type + createTime + updateTime + } + } +`; + +// API 鍑芥暟 +export const getAppConfig = async () => { + try { + const data = await graphqlRequest(GET_APP_CONFIG); + return data.appConfig || []; + } catch (error) { + throw new Error(error.message || '鑾峰彇搴旂敤閰嶇疆澶辫触'); + } +}; + +export const saveAppConfig = async (configData) => { + try { + const data = await graphqlRequest(SAVE_APP_CONFIG, { input: configData }); + return data.saveAppConfig; + } catch (error) { + throw new Error(error.message || '淇濆瓨搴旂敤閰嶇疆澶辫触'); + } }; \ No newline at end of file diff --git a/web/src/api/dashboard.js b/web/src/api/dashboard.js index 90aa3c9..ef2e5d1 100644 --- a/web/src/api/dashboard.js +++ b/web/src/api/dashboard.js @@ -1,5 +1,4 @@ -// Dashboard API -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; +import { graphqlRequest } from '../config/api' // GraphQL 鏌ヨ璇彞 const GET_DASHBOARD_STATS_QUERY = ` @@ -15,19 +14,6 @@ // API 鍑芥暟 export const getDashboardStats = async () => { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: GET_DASHBOARD_STATS_QUERY - }) - }); - - const result = await response.json(); - if (result.errors) { - throw new Error(result.errors[0].message); - } - return result.data.dashboardStats; + const data = await graphqlRequest(GET_DASHBOARD_STATS_QUERY); + return data.dashboardStats; }; \ No newline at end of file diff --git a/web/src/api/employee.ts b/web/src/api/employee.ts index dd55bc7..75c9dac 100644 --- a/web/src/api/employee.ts +++ b/web/src/api/employee.ts @@ -1,5 +1,7 @@ // 鍛樺伐绠$悊API +import { API_CONFIG, graphqlRequest } from '@/config/api' + // 鍛樺伐鐩稿叧鐨� GraphQL 鏌ヨ鍜屽彉鏇� export const EMPLOYEE_QUERIES = { // 鑾峰彇鎵�鏈夊憳宸� @@ -94,92 +96,41 @@ // API 鍑芥暟 export const employeeApi = { - // 鑾峰彇鎵�鏈夊憳宸� + // 鑾峰彇鍛樺伐鍒楄〃 async getEmployees(): Promise<Employee[]> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: EMPLOYEE_QUERIES.GET_EMPLOYEES - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.employees || [] + const data = await graphqlRequest(EMPLOYEE_QUERIES.GET_EMPLOYEES) + return data?.employees || [] } catch (error: any) { throw new Error(error.message || '鑾峰彇鍛樺伐鍒楄〃澶辫触') } }, - // 鏍规嵁鍚嶇О鎼滅储鍛樺伐 - async searchEmployees(name?: string): Promise<Employee[]> { + // 鎼滅储鍛樺伐 + async searchEmployees(keyword: string): Promise<Employee[]> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: EMPLOYEE_QUERIES.SEARCH_EMPLOYEES, - variables: { name } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.employeesByName || [] + const data = await graphqlRequest(EMPLOYEE_QUERIES.SEARCH_EMPLOYEES, { keyword }) + return data?.searchEmployees || [] } catch (error: any) { throw new Error(error.message || '鎼滅储鍛樺伐澶辫触') } }, - // 鑾峰彇鍛樺伐璇︽儏 + // 鏍规嵁ID鑾峰彇鍛樺伐璇︽儏 async getEmployee(id: string): Promise<Employee | null> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: EMPLOYEE_QUERIES.GET_EMPLOYEE, - variables: { id } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.employee || null + const data = await graphqlRequest(EMPLOYEE_QUERIES.GET_EMPLOYEE, { id }) + return data?.employee || null } catch (error: any) { throw new Error(error.message || '鑾峰彇鍛樺伐璇︽儏澶辫触') } }, - // 淇濆瓨鍛樺伐 - async saveEmployee(input: EmployeeInput): Promise<Employee> { + // 淇濆瓨鍛樺伐锛堟柊澧炴垨鏇存柊锛� + async saveEmployee(employee: EmployeeInput): Promise<Employee> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: EMPLOYEE_MUTATIONS.SAVE_EMPLOYEE, - variables: { input } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.saveEmployee + const data = await graphqlRequest(EMPLOYEE_MUTATIONS.SAVE_EMPLOYEE, { input: employee }) + return data?.saveEmployee } catch (error: any) { throw new Error(error.message || '淇濆瓨鍛樺伐澶辫触') } @@ -188,21 +139,8 @@ // 鍒犻櫎鍛樺伐 async deleteEmployee(id: string): Promise<boolean> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: EMPLOYEE_MUTATIONS.DELETE_EMPLOYEE, - variables: { id } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.deleteEmployee || false + const data = await graphqlRequest(EMPLOYEE_MUTATIONS.DELETE_EMPLOYEE, { id }) + return data?.deleteEmployee || false } catch (error: any) { throw new Error(error.message || '鍒犻櫎鍛樺伐澶辫触') } diff --git a/web/src/api/graphql.ts b/web/src/api/graphql.ts index 7dd2e56..865cdd5 100644 --- a/web/src/api/graphql.ts +++ b/web/src/api/graphql.ts @@ -2,7 +2,7 @@ // GraphQL 瀹㈡埛绔厤缃� export const graphqlClient = new Client({ - url: 'http://localhost:8080/api/graphql', + url: '/api/graphql', exchanges: [cacheExchange, fetchExchange], }) diff --git a/web/src/api/judge.js b/web/src/api/judge.js index de2a207..21ff463 100644 --- a/web/src/api/judge.js +++ b/web/src/api/judge.js @@ -1,6 +1,6 @@ // 璇勫绠$悊 API -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; +const GRAPHQL_ENDPOINT = '/api/graphql'; // GraphQL 鏌ヨ璇彞 const GET_ALL_JUDGES_QUERY = ` diff --git a/web/src/api/judge.ts b/web/src/api/judge.ts index 9319200..73fa1d9 100644 --- a/web/src/api/judge.ts +++ b/web/src/api/judge.ts @@ -1,33 +1,9 @@ import { JUDGE_QUERIES, JUDGE_MUTATIONS } from './graphql' import type { Judge, JudgeInput, CosCredentials, Tag, Media } from './graphql' -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql' +import { API_CONFIG, graphqlRequest } from '@/config/api' -// GraphQL璇锋眰鍑芥暟 -async function graphqlRequest(query: string, variables: any = {}) { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query, - variables, - }), - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - const result = await response.json() - - if (result.errors) { - throw new Error(result.errors[0]?.message || 'GraphQL error') - } - - return result.data -} +// 浣跨敤缁熶竴鐨凣raphQL璇锋眰鍑芥暟 export class JudgeApi { // 鑾峰彇鎵�鏈夎瘎濮� diff --git a/web/src/api/media.js b/web/src/api/media.js index 1934b37..6c7afdc 100644 --- a/web/src/api/media.js +++ b/web/src/api/media.js @@ -1,5 +1,7 @@ // 濯掍綋鏌ヨ API -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; +import { graphqlRequest, API_CONFIG } from '../config/api.ts'; + +const GRAPHQL_ENDPOINT = API_CONFIG.GRAPHQL_ENDPOINT; const MEDIAS_BY_TARGET_QUERY = ` query MediasByTarget($targetType: Int!, $targetId: ID!) { @@ -36,9 +38,17 @@ `; export const getMediasByTarget = async (targetType, targetId) => { + // 鑾峰彇JWT token + const { getToken } = await import('@/utils/auth'); + const token = getToken(); + const headers = { 'Content-Type': 'application/json' }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const res = await fetch(GRAPHQL_ENDPOINT, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: headers, body: JSON.stringify({ query: MEDIAS_BY_TARGET_QUERY, variables: { targetType, targetId } @@ -52,9 +62,17 @@ }; export const saveMedia = async (input) => { + // 鑾峰彇JWT token + const { getToken } = await import('@/utils/auth'); + const token = getToken(); + const headers = { 'Content-Type': 'application/json' }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const res = await fetch(GRAPHQL_ENDPOINT, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: headers, body: JSON.stringify({ query: SAVE_MEDIA_MUTATION, variables: { input } @@ -72,9 +90,17 @@ console.log('瑕佸垹闄ょ殑濯掍綋ID:', id); console.log('GraphQL鏌ヨ:', DELETE_MEDIA_MUTATION); + // 鑾峰彇JWT token + const { getToken } = await import('@/utils/auth'); + const token = getToken(); + const headers = { 'Content-Type': 'application/json' }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const res = await fetch(GRAPHQL_ENDPOINT, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: headers, body: JSON.stringify({ query: DELETE_MEDIA_MUTATION, variables: { id: id.toString() } @@ -99,8 +125,17 @@ const formData = new FormData(); formData.append('file', file); + // 鑾峰彇JWT token + const { getToken } = await import('@/utils/auth'); + const token = getToken(); + const headers = {}; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const response = await fetch('http://localhost:8080/api/upload/image', { method: 'POST', + headers: headers, body: formData }); @@ -151,4 +186,35 @@ console.error('瑙嗛澶勭悊澶辫触:', error); throw new Error(`瑙嗛澶勭悊澶辫触: ${error.message}`); } +}; + +// 鏂扮増淇濆瓨濯掍綋锛圫aveMediaV2锛夛細浣跨敤瀛楃涓� targetType 涓� MediaSaveInput +// 娉ㄦ剰锛氱洰鍓嶅悗绔粎鏀寔 targetType: "player"锛堝鍛樺ご鍍� -> 6锛変笌 "activity_player"锛堟姤鍚嶈祫鏂� -> 5锛夈�� +// 瀛楁鍛藉悕涓庢棫鐗堜笉鍚岋細fileName 鏇夸唬 name锛涘彲閫� thumbPath锛沵ediaType: 1鍥剧墖銆�2瑙嗛銆�3闊抽銆�4鏂囨。銆� +const SAVE_MEDIA_V2_MUTATION = ` + mutation SaveMediaV2($input: MediaSaveInput!) { + saveMediaV2(input: $input) { + success + message + mediaId + } + } +`; + +// 缁熶竴鐨� V2 淇濆瓨鎺ュ彛锛堣繑鍥� { success, message, mediaId }锛夛紝绀轰緥锛� +// await saveMediaV2({ targetType: 'player', targetId: 123, path: 'avatar/xxx.jpg', fileName: 'avatar.jpg', fileExt: 'jpg', fileSize: 2048, mediaType: 1 }) +export const saveMediaV2 = async (input) => { + const res = await fetch(GRAPHQL_ENDPOINT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query: SAVE_MEDIA_V2_MUTATION, + variables: { input } + }) + }); + const result = await res.json(); + if (result.errors) { + throw new Error(result.errors[0].message); + } + return result.data.saveMediaV2; }; \ No newline at end of file diff --git a/web/src/api/player.js b/web/src/api/player.js index a1ada54..34cec08 100644 --- a/web/src/api/player.js +++ b/web/src/api/player.js @@ -1,31 +1,118 @@ -const GRAPHQL_ENDPOINT = '/api/graphql' +import { API_CONFIG, graphqlRequest } from '@/config/api' -async function graphqlRequest(query, variables = {}) { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, variables }) - }) - if (!response.ok) { - const text = await response.text() - throw new Error(`HTTP ${response.status}: ${text}`) +// 浣跨敤缁熶竴鐨刧raphqlRequest鍑芥暟 + +// GraphQL鏌ヨ璇彞 +const GET_PLAYERS_QUERY = ` + query GetPlayers($page: Int!, $size: Int!, $name: String) { + players(page: $page, size: $size, name: $name) { + content { + id + name + phone + regionId + region { + id + name + } + createTime + updateTime + } + totalElements + page + size + } } - const result = await response.json() - if (result.errors) throw new Error(result.errors.map(e => e.message).join('\\n')) - return result.data +` + +const GET_PLAYER_QUERY = ` + query GetPlayer($id: ID!) { + player(id: $id) { + id + name + phone + regionId + region { + id + name + } + createTime + updateTime + } + } +` + +const SAVE_PLAYER_MUTATION = ` + mutation SavePlayer($input: PlayerInput!) { + savePlayer(input: $input) { + id + name + phone + regionId + region { + id + name + } + createTime + updateTime + } + } +` + +const DELETE_PLAYER_MUTATION = ` + mutation DeletePlayer($id: ID!) { + deletePlayer(id: $id) + } +` + +// API鍑芥暟 +export const getPlayers = async (page = 0, size = 10, name = '') => { + try { + const data = await graphqlRequest(GET_PLAYERS_QUERY, { page, size, name }) + return data.players + } catch (error) { + throw new Error(error.message || '鑾峰彇瀛﹀憳鍒楄〃澶辫触') + } +} + +export const getPlayer = async (id) => { + try { + const data = await graphqlRequest(GET_PLAYER_QUERY, { id }) + return data.player + } catch (error) { + throw new Error(error.message || '鑾峰彇瀛﹀憳璇︽儏澶辫触') + } +} + +export const savePlayer = async (playerData) => { + try { + const data = await graphqlRequest(SAVE_PLAYER_MUTATION, { input: playerData }) + return data.savePlayer + } catch (error) { + throw new Error(error.message || '淇濆瓨瀛﹀憳澶辫触') + } +} + +export const deletePlayer = async (id) => { + try { + const data = await graphqlRequest(DELETE_PLAYER_MUTATION, { id }) + return data.deletePlayer + } catch (error) { + throw new Error(error.message || '鍒犻櫎瀛﹀憳澶辫触') + } } const GET_APPLICATIONS = ` - query GetApplications($name: String, $activityId: ID, $page: Int, $size: Int) { - activityPlayerApplications(name: $name, activityId: $activityId, page: $page, size: $size) { + query GetApplications($name: String, $activityId: ID, $state: Int, $page: Int, $size: Int) { + activityPlayerApplications(name: $name, activityId: $activityId, state: $state, page: $page, size: $size) { id playerName activityName phone applyTime state } } ` export const PlayerApi = { - getApplications: async (name, activityId, page, size) => { - const data = await graphqlRequest(GET_APPLICATIONS, { name, activityId, page, size }) + getApplications: async (name, activityId, state, page, size) => { + const data = await graphqlRequest(GET_APPLICATIONS, { name, activityId, state, page, size }) return data.activityPlayerApplications || [] } } \ No newline at end of file diff --git a/web/src/api/projectReview.js b/web/src/api/projectReview.js new file mode 100644 index 0000000..cbfaa9e --- /dev/null +++ b/web/src/api/projectReview.js @@ -0,0 +1,279 @@ +import { graphqlRequest } from '@/config/api' + +// GraphQL 鏌ヨ璇彞 + +// 鑾峰彇杩涜涓殑姣旇禌鍒楄〃锛堝寘鎷樁娈碉級 +const GET_ACTIVE_ACTIVITIES_QUERY = ` + query GetActiveActivities { + allActivities { + id + pid + name + state + parent { + id + name + } + } + } +` + +// 鑾峰彇姣旇禌椤圭洰鍒楄〃锛堝垎椤碉級 +const GET_COMPETITION_PROJECTS_QUERY = ` + query GetCompetitionProjects($activityId: ID, $page: Int, $size: Int) { + activityPlayerApplications(activityId: $activityId, page: $page, size: $size) { + id + playerName + activityName + projectName + phone + applyTime + state + } + } +` + +// 鑾峰彇椤圭洰璇︽儏 +export const GET_PROJECT_DETAIL_QUERY = ` + query GetProjectDetail($id: ID!) { + activityPlayerDetail(id: $id) { + id + playerInfo { + id + name + phone + gender + education + introduction + avatarUrl + birthday + } + regionInfo { + name + } + activityName + projectName + description + submissionFiles { + id + name + url + thumbUrl + fileExt + fileSize + mediaType + } + ratingForm { + schemeId + schemeName + totalMaxScore + items { + id + name + maxScore + orderNo + } + } + state + feedback + } + } +` + +// 鑾峰彇璇勫缁熻淇℃伅 +export const GET_RATING_STATS_QUERY = ` + query GetRatingStats($activityPlayerId: ID!) { + judgeRatingsForPlayer(activityPlayerId: $activityPlayerId) { + judgeId + judgeName + hasRated + totalScore + ratingTime + } + averageScoreForPlayer(activityPlayerId: $activityPlayerId) + } +` + +// 鑾峰彇褰撳墠璇勫鐨勮瘎鍒嗕俊鎭� +const GET_CURRENT_JUDGE_RATING_QUERY = ` + query GetCurrentJudgeRating($activityPlayerId: ID!) { + currentJudgeRating(activityPlayerId: $activityPlayerId) { + id + totalScore + comments + ratingItems { + itemId + itemName + score + maxScore + } + } + } +` + +// 鎻愪氦璇勫垎 +const SUBMIT_RATING_MUTATION = ` + mutation SubmitRating($input: ActivityPlayerRatingInput!) { + saveActivityPlayerRating(input: $input) + } +` + +// API 鍑芥暟 + +/** + * 鑾峰彇杩涜涓殑姣旇禌闃舵鍒楄〃锛坰tate=1涓攑id>0鐨勬瘮璧涢樁娈碉級 + * 鎸夋瘮璧涘垎缁勬帓搴忥紝鏄剧ず鏍煎紡涓�"姣旇禌鍚� + 闃舵鍚�" + */ +export const getActiveActivities = async () => { + try { + const data = await graphqlRequest(GET_ACTIVE_ACTIVITIES_QUERY) + + // 杩囨护鍑簊tate=1涓攑id>0鐨勬瘮璧涢樁娈� + const stages = data.allActivities.filter(activity => + activity.state === 1 && activity.pid > 0 + ) + + // 鎸夋瘮璧汭D(pid)鍒嗙粍鎺掑簭锛岀‘淇濆悓涓�姣旇禌鐨勪笉鍚岄樁娈垫斁鍦ㄤ竴璧� + stages.sort((a, b) => { + // 棣栧厛鎸夋瘮璧汭D鎺掑簭 + if (a.pid !== b.pid) { + return a.pid - b.pid + } + // 鍚屼竴姣旇禌鍐呮寜闃舵ID鎺掑簭 + return a.id - b.id + }) + + return stages + } catch (error) { + console.error('鑾峰彇娲诲姩鍒楄〃澶辫触:', error) + throw new Error(error.message || '鑾峰彇娲诲姩鍒楄〃澶辫触') + } +} + +/** + * 鑾峰彇姣旇禌椤圭洰鍒楄〃 + */ +export const getCompetitionProjects = async (activityId, page = 0, size = 10) => { + try { + const data = await graphqlRequest(GET_COMPETITION_PROJECTS_QUERY, { + activityId, + page, + size + }) + + const projects = data.activityPlayerApplications || [] + + // 涓烘瘡涓」鐩幏鍙栬瘎鍒嗙粺璁� + const enrichedProjects = await Promise.all( + projects.map(async (project) => { + try { + const stats = await getRatingStats(project.id) + return { + id: project.id, + playerName: project.playerName, + activityName: project.activityName, + phone: project.phone, + applyTime: project.applyTime, + state: project.state, + projectName: project.projectName || project.playerName + '鐨勯」鐩�', // 浣跨敤鐪熷疄椤圭洰鍚嶇О锛屽鏋滀负绌哄垯浣跨敤榛樿鍚嶇О + ratingCount: stats.ratingCount, + averageScore: stats.averageScore + } + } catch (error) { + console.warn(`Failed to get rating stats for project ${project.id}:`, error) + return { + id: project.id, + playerName: project.playerName, + activityName: project.activityName, + phone: project.phone, + applyTime: project.applyTime, + state: project.state, + projectName: project.projectName || project.playerName + '鐨勯」鐩�', // 浣跨敤鐪熷疄椤圭洰鍚嶇О锛屽鏋滀负绌哄垯浣跨敤榛樿鍚嶇О + ratingCount: 0, + averageScore: 0 + } + } + }) + ) + + // 妯℃嫙鍒嗛〉淇℃伅锛屽洜涓篈PI杩斿洖鐨勬槸鏁扮粍鑰屼笉鏄垎椤靛璞� + const totalElements = projects.length + const totalPages = Math.ceil(totalElements / size) + + return { + content: enrichedProjects, + totalElements, + totalPages, + number: page, + size + } + } catch (error) { + console.error('Error fetching competition projects:', error) + throw error + } +} + +/** + * 鑾峰彇椤圭洰璇︽儏 + */ +export const getProjectDetail = async (id) => { + try { + const data = await graphqlRequest(GET_PROJECT_DETAIL_QUERY, { id }) + return data.activityPlayerDetail + } catch (error) { + throw new Error(error.message || '鑾峰彇椤圭洰璇︽儏澶辫触') + } +} + +/** + * 鑾峰彇璇勫缁熻淇℃伅 + */ +export const getRatingStats = async (activityPlayerId) => { + try { + const data = await graphqlRequest(GET_RATING_STATS_QUERY, { activityPlayerId }) + + const ratings = data.judgeRatingsForPlayer || [] + const averageScore = data.averageScoreForPlayer || 0 + + // 鍙绠楀凡璇勫垎鐨勮瘎濮� + const ratedJudges = ratings.filter(rating => rating.hasRated) + + return { + ratingCount: ratedJudges.length, + averageScore: averageScore, + ratings: ratings + } + } catch (error) { + console.error('Error fetching rating stats:', error) + // 杩斿洖榛樿鍊艰�屼笉鏄姏鍑洪敊璇紝閬垮厤褰卞搷涓昏鍔熻兘 + return { + ratingCount: 0, + averageScore: 0, + ratings: [] + } + } +} + +/** + * 鑾峰彇褰撳墠璇勫鐨勮瘎鍒嗕俊鎭� + */ +export const getCurrentJudgeRating = async (activityPlayerId) => { + try { + const data = await graphqlRequest(GET_CURRENT_JUDGE_RATING_QUERY, { activityPlayerId }) + return data.currentJudgeRating + } catch (error) { + throw new Error(error.message || '鑾峰彇褰撳墠璇勫璇勫垎澶辫触') + } +} + +/** + * 鎻愪氦璇勫垎 + */ +export const submitRating = async (ratingInput) => { + try { + const data = await graphqlRequest(SUBMIT_RATING_MUTATION, { input: ratingInput }) + return data.saveActivityPlayerRating + } catch (error) { + throw new Error(error.message || '鎻愪氦璇勫垎澶辫触') + } +} \ No newline at end of file diff --git a/web/src/api/promotion.js b/web/src/api/promotion.js new file mode 100644 index 0000000..6094476 --- /dev/null +++ b/web/src/api/promotion.js @@ -0,0 +1,136 @@ +import { API_CONFIG, graphqlRequest } from '@/config/api' + +// GraphQL 鏌ヨ锛氳幏鍙栨瘮璧涙檵绾у垪琛� +const GET_PROMOTION_COMPETITIONS = ` + query GetPromotionCompetitions($name: String, $page: Int, $size: Int) { + promotionCompetitions(name: $name, page: $page, size: $size) { + id + competitionName + stageName + maxParticipants + currentCount + status + startTime + endTime + sortOrder + state + } + } +` + +// GraphQL 鏌ヨ锛氳幏鍙栨瘮璧涘弬璧涗汉鍛� +const GET_COMPETITION_PARTICIPANTS = ` + query GetCompetitionParticipants($competitionId: ID!, $page: Int, $size: Int) { + competitionParticipants(competitionId: $competitionId, page: $page, size: $size) { + id + playerName + projectName + phone + averageScore + ratingCount + applyTime + state + } + } +` + +// GraphQL 鏌ヨ锛氳幏鍙栧彲鏅嬬骇鍙傝禌鑰� +const GET_PROMOTABLE_PARTICIPANTS = ` + query GetPromotableParticipants($currentStageId: ID!) { + promotableParticipants(currentStageId: $currentStageId) { + participants { + id + playerId + playerName + projectName + phone + averageScore + ratingCount + applyTime + state + } + selectableCount + totalCount + previousStageName + currentStageName + } + } +` + +// GraphQL 鍙樻洿锛氭檵绾у弬璧涜�� +const PROMOTE_PARTICIPANTS = ` + mutation PromoteParticipants($input: PromotionInput!) { + promoteParticipants(input: $input) { + success + message + promotedCount + } + } +` + +// 姣旇禌鏅嬬骇 API +export const PromotionApi = { + // 鑾峰彇姣旇禌鏅嬬骇鍒楄〃 + async getPromotionCompetitions(params = {}) { + try { + const variables = { + name: params.name || null, + page: params.page || 1, + size: params.size || 10 + } + const data = await graphqlRequest(GET_PROMOTION_COMPETITIONS, variables) + return data.promotionCompetitions || [] + } catch (error) { + console.error('鑾峰彇姣旇禌鏅嬬骇鍒楄〃澶辫触:', error) + throw error + } + }, + + // 鑾峰彇姣旇禌鍙傝禌浜哄憳 + async getCompetitionParticipants(competitionId, params = {}) { + try { + const variables = { + competitionId, + page: params.page || 1, + size: params.size || 10 + } + const data = await graphqlRequest(GET_COMPETITION_PARTICIPANTS, variables) + return data.competitionParticipants || [] + } catch (error) { + console.error('鑾峰彇姣旇禌鍙傝禌浜哄憳澶辫触:', error) + throw error + } + }, + + // 鑾峰彇鍙檵绾у弬璧涜�呭垪琛� + async getPromotableParticipants(currentStageId) { + try { + const variables = { currentStageId } + const data = await graphqlRequest(GET_PROMOTABLE_PARTICIPANTS, variables) + return data.promotableParticipants + } catch (error) { + console.error('鑾峰彇鍙檵绾у弬璧涜�呭垪琛ㄥけ璐�:', error) + throw error + } + }, + + // 鎵ц鏅嬬骇鎿嶄綔 + async promoteParticipants(competitionId, participantIds, targetStageId) { + try { + const variables = { + input: { + competitionId, + participantIds, + targetStageId + } + } + const data = await graphqlRequest(PROMOTE_PARTICIPANTS, variables) + return data.promoteParticipants + } catch (error) { + console.error('鎵ц鏅嬬骇鎿嶄綔澶辫触:', error) + throw error + } + } +} + +export default PromotionApi \ No newline at end of file diff --git a/web/src/api/rating.js b/web/src/api/rating.js index a2a5d72..542ca97 100644 --- a/web/src/api/rating.js +++ b/web/src/api/rating.js @@ -1,138 +1,125 @@ -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql' +import { API_CONFIG, graphqlRequest } from '@/config/api' -// GraphQL璇锋眰鍑芥暟 -async function graphqlRequest(query, variables = {}) { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query, - variables, - }), - }) +// 浣跨敤缁熶竴鐨凣raphQL璇锋眰鍑芥暟 - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) +// GraphQL鏌ヨ璇彞 +const GET_RATING_SCHEMES_QUERY = ` + query GetRatingSchemes($page: Int!, $size: Int!, $name: String) { + ratingSchemes(page: $page, size: $size, name: $name) { + content { + id + name + description + totalScore + state + stateName + createTime + updateTime + } + totalElements + page + size + } } +` - const result = await response.json() - - if (result.errors) { - throw new Error(result.errors[0].message) +const GET_ALL_RATING_SCHEMES_QUERY = ` + query GetAllRatingSchemes { + allRatingSchemes { + id + name + description + totalScore + state + stateName + } } +` - return result.data -} +const GET_RATING_SCHEME_QUERY = ` + query GetRatingScheme($id: ID!) { + ratingScheme(id: $id) { + id + name + description + totalScore + state + stateName + createTime + updateTime + items { + id + name + maxScore + orderNo + } + } + } +` -// 鍒嗛〉鏌ヨ璇勫垎妯℃澘鍒楄〃 +const SAVE_RATING_SCHEME_MUTATION = ` + mutation SaveRatingScheme($input: RatingSchemeInput!) { + saveRatingScheme(input: $input) { + id + name + description + totalScore + state + stateName + createTime + updateTime + } + } +` + +const DELETE_RATING_SCHEME_MUTATION = ` + mutation DeleteRatingScheme($id: ID!) { + deleteRatingScheme(id: $id) + } +` + +// API鍑芥暟 export const getRatingSchemes = async (page = 0, size = 10, name = '') => { - const query = ` - query GetRatingSchemes($page: Int, $size: Int, $name: String) { - ratingSchemes(page: $page, size: $size, name: $name) { - content { - id - name - description - totalScore - createTime - updateTime - } - totalElements - totalPages - number - size - first - last - } - } - ` - - const variables = { page, size } - if (name) { - variables.name = name + try { + const data = await graphqlRequest(GET_RATING_SCHEMES_QUERY, { page, size, name }) + return data.ratingSchemes + } catch (error) { + throw new Error(error.message || '鑾峰彇璇勫垎鏂规鍒楄〃澶辫触') } - - const data = await graphqlRequest(query, variables) - return data.ratingSchemes } -// 鏍规嵁ID鑾峰彇璇勫垎妯℃澘璇︽儏 -export const getRatingScheme = async (id) => { - const query = ` - query GetRatingScheme($id: ID!) { - ratingScheme(id: $id) { - id - name - description - totalScore - items { - id - name - maxScore - orderNo - } - createTime - updateTime - } - } - ` - - const data = await graphqlRequest(query, { id }) - return data.ratingScheme -} - -// 鑾峰彇鎵�鏈夎瘎鍒嗘ā鏉匡紙鐢ㄤ簬涓嬫媺閫夋嫨锛� export const getAllRatingSchemes = async () => { - const query = ` - query GetAllRatingSchemes { - allRatingSchemes { - id - name - description - totalScore - } - } - ` - - const data = await graphqlRequest(query) - return data.allRatingSchemes + try { + const data = await graphqlRequest(GET_ALL_RATING_SCHEMES_QUERY) + return data.allRatingSchemes || [] + } catch (error) { + throw new Error(error.message || '鑾峰彇鎵�鏈夎瘎鍒嗘柟妗堝け璐�') + } } -// 淇濆瓨璇勫垎妯℃澘 -export const saveRatingScheme = async (input) => { - const mutation = ` - mutation SaveRatingScheme($input: RatingSchemeInput!) { - saveRatingScheme(input: $input) { - id - name - description - totalScore - items { - id - name - maxScore - orderNo - } - createTime - updateTime - } - } - ` - - const data = await graphqlRequest(mutation, { input }) - return data.saveRatingScheme +export const getRatingScheme = async (id) => { + try { + const data = await graphqlRequest(GET_RATING_SCHEME_QUERY, { id }) + return data.ratingScheme + } catch (error) { + throw new Error(error.message || '鑾峰彇璇勫垎鏂规璇︽儏澶辫触') + } } -// 鍒犻櫎璇勫垎妯℃澘 +export const saveRatingScheme = async (ratingSchemeData) => { + try { + const data = await graphqlRequest(SAVE_RATING_SCHEME_MUTATION, { input: ratingSchemeData }) + return data.saveRatingScheme + } catch (error) { + throw new Error(error.message || '淇濆瓨璇勫垎鏂规澶辫触') + } +} + export const deleteRatingScheme = async (id) => { - const mutation = ` - mutation DeleteRatingScheme($id: ID!) { - deleteRatingScheme(id: $id) - } - ` - - const data = await graphqlRequest(mutation, { id }) - return data.deleteRatingScheme + try { + const data = await graphqlRequest(DELETE_RATING_SCHEME_MUTATION, { id }) + return data.deleteRatingScheme + } catch (error) { + throw new Error(error.message || '鍒犻櫎璇勫垎鏂规澶辫触') + } } \ No newline at end of file diff --git a/web/src/api/region.js b/web/src/api/region.js index e61e32d..87cbcdf 100644 --- a/web/src/api/region.js +++ b/web/src/api/region.js @@ -1,138 +1,159 @@ -const GRAPHQL_ENDPOINT = '/api/graphql' +import { API_CONFIG, graphqlRequest } from '@/config/api' -// GraphQL璇锋眰鍑芥暟 -async function graphqlRequest(query, variables = {}) { - const response = await fetch(GRAPHQL_ENDPOINT, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query, - variables, - }), - }) +// GraphQL璇锋眰鍑芥暟 - 浣跨敤缁熶竴鐨刧raphqlRequest - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`) - } - - const result = await response.json() +// 鑾峰彇鎵�鏈夊湴鍖� +export const getRegions = async () => { + const query = ` + query GetRegions { + regions { + id + name + description + createTime + updateTime + } + } + ` - if (result.errors) { - throw new Error(result.errors.map(e => e.message).join('\n')) + try { + const data = await graphqlRequest(query) + return data.regions || [] + } catch (error) { + throw new Error(error.message || '鑾峰彇鍦板尯鍒楄〃澶辫触') } - - return result.data } -// --- GraphQL Query Strings --- - -const GET_REGIONS = ` - query GetRegions($name: String, $state: Int, $page: Int!, $size: Int!) { - regions(name: $name, state: $state, page: $page, size: $size) { - content { id name pid code level leafFlag fullPath state createTime createUserId updateTime updateUserId version } - totalElements totalPages currentPage pageSize +// 鏍规嵁ID鑾峰彇鍦板尯 +export const getRegion = async (id) => { + const query = ` + query GetRegion($id: ID!) { + region(id: $id) { + id + name + description + createTime + updateTime + } } + ` + + try { + const data = await graphqlRequest(query, { id }) + return data.region + } catch (error) { + throw new Error(error.message || '鑾峰彇鍦板尯璇︽儏澶辫触') } -` +} -const GET_ALL_REGIONS = ` - query GetAllRegions { - allRegions { id name pid code level leafFlag fullPath state createTime } +// 淇濆瓨鍦板尯锛堟柊澧炴垨鏇存柊锛� +export const saveRegion = async (regionData) => { + const mutation = ` + mutation SaveRegion($input: RegionInput!) { + saveRegion(input: $input) { + id + name + description + createTime + updateTime + } + } + ` + + try { + const data = await graphqlRequest(mutation, { input: regionData }) + return data.saveRegion + } catch (error) { + throw new Error(error.message || '淇濆瓨鍦板尯澶辫触') } -` +} -const GET_REGION = ` - query GetRegion($id: ID!) { - region(id: $id) { id name pid code level leafFlag fullPath state createTime createUserId updateTime updateUserId version } +// 鍒犻櫎鍦板尯 +export const deleteRegion = async (id) => { + const mutation = ` + mutation DeleteRegion($id: ID!) { + deleteRegion(id: $id) + } + ` + + try { + const data = await graphqlRequest(mutation, { id }) + return data.deleteRegion + } catch (error) { + throw new Error(error.message || '鍒犻櫎鍦板尯澶辫触') } -` +} -const GET_PROVINCES = ` - query GetProvinces { - provinces { id name pid code level state } +// 鑾峰彇鎵�鏈夌渷浠� +export const getProvinces = async () => { + const query = ` + query GetProvinces { + provinces { + id + name + pid + level + } + } + ` + + try { + const data = await graphqlRequest(query) + return data.provinces || [] + } catch (error) { + throw new Error(error.message || '鑾峰彇鐪佷唤鍒楄〃澶辫触') } -` +} -const GET_CITIES = ` - query GetCities($provinceId: ID!) { - cities(provinceId: $provinceId) { id name pid code level state } +// 鏍规嵁鐪佷唤ID鑾峰彇鍩庡競 +export const getCities = async (provinceId) => { + const query = ` + query GetCities($provinceId: ID!) { + cities(provinceId: $provinceId) { + id + name + pid + level + } + } + ` + + try { + const data = await graphqlRequest(query, { provinceId }) + return data.cities || [] + } catch (error) { + throw new Error(error.message || '鑾峰彇鍩庡競鍒楄〃澶辫触') } -` +} -const GET_DISTRICTS = ` - query GetDistricts($cityId: ID!) { - districts(cityId: $cityId) { id name pid code level state } +// 鏍规嵁鍩庡競ID鑾峰彇鍖哄幙 +export const getDistricts = async (cityId) => { + const query = ` + query GetDistricts($cityId: ID!) { + districts(cityId: $cityId) { + id + name + pid + level + } + } + ` + + try { + const data = await graphqlRequest(query, { cityId }) + return data.districts || [] + } catch (error) { + throw new Error(error.message || '鑾峰彇鍖哄幙鍒楄〃澶辫触') } -` +} -const GET_REGION_CHILDREN = ` - query GetRegionChildren($parentId: ID!) { - regionChildren(parentId: $parentId) { id name pid code level leafFlag state } - } -` - -const SAVE_REGION = ` - mutation SaveRegion($input: RegionInput!) { - saveRegion(input: $input) { id name pid code level leafFlag fullPath state createTime updateTime version } - } -` - -const DELETE_REGION = ` - mutation DeleteRegion($id: ID!) { - deleteRegion(id: $id) - } -` - -const TOGGLE_REGION_STATE = ` - mutation ToggleRegionState($id: ID!) { - toggleRegionState(id: $id) { id name state updateTime } - } -` - -// --- API Functions --- - +// RegionApi 瀵硅薄锛屽寘鍚墍鏈夊尯鍩熺浉鍏崇殑API鏂规硶 export const RegionApi = { - getRegions: async (name, state, page, size) => { - const data = await graphqlRequest(GET_REGIONS, { name, state, page, size }); - return data.regions; - }, - getAllRegions: async () => { - const data = await graphqlRequest(GET_ALL_REGIONS); - return data.allRegions; - }, - getRegion: async (id) => { - const data = await graphqlRequest(GET_REGION, { id }); - return data.region; - }, - getProvinces: async () => { - const data = await graphqlRequest(GET_PROVINCES); - return data.provinces || []; - }, - getCities: async (provinceId) => { - const data = await graphqlRequest(GET_CITIES, { provinceId }); - return data.cities || []; - }, - getDistricts: async (cityId) => { - const data = await graphqlRequest(GET_DISTRICTS, { cityId }); - return data.districts || []; - }, - getChildren: async (parentId) => { - const data = await graphqlRequest(GET_REGION_CHILDREN, { parentId }); - return data.regionChildren; - }, - saveRegion: async (input) => { - const data = await graphqlRequest(SAVE_REGION, { input }); - return data.saveRegion; - }, - deleteRegion: async (id) => { - const data = await graphqlRequest(DELETE_REGION, { id }); - return data.deleteRegion; - }, - toggleRegionState: async (id) => { - const data = await graphqlRequest(TOGGLE_REGION_STATE, { id }); - return data.toggleRegionState; - } + getRegions, + getRegion, + saveRegion, + deleteRegion, + getProvinces, + getCities, + getDistricts } \ No newline at end of file diff --git a/web/src/api/role.ts b/web/src/api/role.ts index 46aed90..434ee33 100644 --- a/web/src/api/role.ts +++ b/web/src/api/role.ts @@ -1,5 +1,7 @@ // 瑙掕壊绠$悊API +import { API_CONFIG, graphqlRequest } from '@/config/api' + // 瑙掕壊鐩稿叧鐨� GraphQL 鏌ヨ export const ROLE_QUERIES = { // 鑾峰彇鎵�鏈夎鑹� @@ -78,19 +80,19 @@ `, // 鏍规嵁鍚嶇О妯$硦鏌ヨ瑙掕壊 - GET_ROLES_BY_NAME: ` - query GetRolesByName($name: String!) { - rolesByName(name: $name) { - id - code - name - description - state - createTime - updateTime - } - } - ` + SEARCH_ROLES_BY_NAME: ` + query SearchRolesByName($name: String!) { + searchRolesByName(name: $name) { + id + code + name + description + state + createTime + updateTime + } + } + ` } // 绫诲瀷瀹氫箟 @@ -109,136 +111,60 @@ // 鑾峰彇鎵�鏈夎鑹� async getRoles(): Promise<Role[]> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ROLE_QUERIES.GET_ROLES - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.roles || [] + const data = await graphqlRequest(ROLE_QUERIES.GET_ROLES) + return data?.roles || [] } catch (error: any) { throw new Error(error.message || '鑾峰彇瑙掕壊鍒楄〃澶辫触') } }, - // 鑾峰彇鎵�鏈夊惎鐢ㄧ姸鎬佺殑瑙掕壊 + // 鑾峰彇婵�娲荤姸鎬佺殑瑙掕壊 async getActiveRoles(): Promise<Role[]> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ROLE_QUERIES.GET_ACTIVE_ROLES - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.activeRoles || [] + const data = await graphqlRequest(ROLE_QUERIES.GET_ACTIVE_ROLES) + return data?.activeRoles || [] } catch (error: any) { - throw new Error(error.message || '鑾峰彇鍚敤瑙掕壊鍒楄〃澶辫触') + throw new Error(error.message || '鑾峰彇婵�娲昏鑹插垪琛ㄥけ璐�') } }, - // 鏍规嵁ID鑾峰彇瑙掕壊璇︽儏 - async getRoleById(id: string): Promise<Role | null> { - try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ROLE_QUERIES.GET_ROLE, - variables: { id } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.role || null - } catch (error: any) { - throw new Error(error.message || '鑾峰彇瑙掕壊璇︽儏澶辫触') - } - }, + // 鏍规嵁ID鑾峰彇瑙掕壊 + async getRoleById(id: string): Promise<Role | null> { + try { + const data = await graphqlRequest(ROLE_QUERIES.GET_ROLE, { id }) + return data?.role || null + } catch (error: any) { + throw new Error(error.message || '鑾峰彇瑙掕壊璇︽儏澶辫触') + } + }, - // 鏍规嵁瑙掕壊浠g爜鑾峰彇瑙掕壊 + // 鏍规嵁浠g爜鑾峰彇瑙掕壊 async getRoleByCode(code: string): Promise<Role | null> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ROLE_QUERIES.GET_ROLE_BY_CODE, - variables: { code } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.roleByCode || null + const data = await graphqlRequest(ROLE_QUERIES.GET_ROLE_BY_CODE, { code }) + return data?.roleByCode || null } catch (error: any) { - throw new Error(error.message || '鏍规嵁浠g爜鑾峰彇瑙掕壊澶辫触') + throw new Error(error.message || '鑾峰彇瑙掕壊璇︽儏澶辫触') } }, // 鏍规嵁鐘舵�佽幏鍙栬鑹� async getRolesByState(state: number): Promise<Role[]> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ROLE_QUERIES.GET_ROLES_BY_STATE, - variables: { state } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.rolesByState || [] + const data = await graphqlRequest(ROLE_QUERIES.GET_ROLES_BY_STATE, { state }) + return data?.rolesByState || [] } catch (error: any) { - throw new Error(error.message || '鏍规嵁鐘舵�佽幏鍙栬鑹插け璐�') + throw new Error(error.message || '鑾峰彇瑙掕壊鍒楄〃澶辫触') } }, - // 鏍规嵁鍚嶇О妯$硦鏌ヨ瑙掕壊 + // 鏍规嵁鍚嶇О鎼滅储瑙掕壊 async searchRolesByName(name: string): Promise<Role[]> { try { - const response = await fetch('http://localhost:8080/api/graphql', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ROLE_QUERIES.GET_ROLES_BY_NAME, - variables: { name } - }) - }) - const result = await response.json() - if (result.errors) { - throw new Error(result.errors[0].message) - } - return result.data?.rolesByName || [] + const data = await graphqlRequest(ROLE_QUERIES.SEARCH_ROLES_BY_NAME, { name }) + return data?.searchRolesByName || [] } catch (error: any) { - throw new Error(error.message || '鏍规嵁鍚嶇О鎼滅储瑙掕壊澶辫触') + throw new Error(error.message || '鎼滅储瑙掕壊澶辫触') } } } \ No newline at end of file diff --git a/web/src/components/PlayerInfoCard.vue b/web/src/components/PlayerInfoCard.vue index 88f82e4..8e9765d 100644 --- a/web/src/components/PlayerInfoCard.vue +++ b/web/src/components/PlayerInfoCard.vue @@ -9,6 +9,8 @@ :size="80" :src="playerInfo.avatarUrl" :style="{ backgroundColor: avatarBgColor }" + class="clickable-avatar" + @click="handleAvatarClick" > {{ playerInfo.name ? playerInfo.name.charAt(0) : '?' }} </el-avatar> @@ -35,13 +37,35 @@ <p>{{ playerInfo.description }}</p> </div> </div> + + <!-- 澶村儚棰勮瀵硅瘽妗� --> + <el-dialog + v-model="previewVisible" + title="澶村儚棰勮" + width="500px" + :show-close="true" + center + > + <div class="avatar-preview-container"> + <img + v-if="playerInfo.avatarUrl" + :src="playerInfo.avatarUrl" + class="preview-image" + alt="澶村儚棰勮" + /> + <div v-else class="no-avatar"> + <el-icon size="80"><Picture /></el-icon> + <p>鏆傛棤澶村儚</p> + </div> + </div> + </el-dialog> </div> </template> <script setup> -import { computed } from 'vue' -import { ElAvatar, ElIcon } from 'element-plus' -import { Phone } from '@element-plus/icons-vue' +import { computed, ref } from 'vue' +import { ElAvatar, ElIcon, ElDialog } from 'element-plus' +import { Phone, Picture } from '@element-plus/icons-vue' const props = defineProps({ playerInfo: { @@ -56,6 +80,16 @@ } }) +// 鍝嶅簲寮忔暟鎹� +const previewVisible = ref(false) + +// 澶村儚鐐瑰嚮澶勭悊 +const handleAvatarClick = () => { + if (props.playerInfo.avatarUrl) { + previewVisible.value = true + } +} + // 鏍规嵁濮撳悕鐢熸垚澶村儚鑳屾櫙鑹� const avatarBgColor = computed(() => { if (!props.playerInfo.name) return '#409EFF' diff --git a/web/src/components/SubmissionFiles.vue b/web/src/components/SubmissionFiles.vue index a3e9c38..be768b9 100644 --- a/web/src/components/SubmissionFiles.vue +++ b/web/src/components/SubmissionFiles.vue @@ -25,15 +25,20 @@ </div> <!-- 瑙嗛棰勮 --> - <div v-else-if="isVideo(file)" class="video-preview"> - <video - :src="file.url" - controls - preload="metadata" - class="preview-video" - > - 鎮ㄧ殑娴忚鍣ㄤ笉鏀寔瑙嗛鎾斁 - </video> + <div v-else-if="isVideo(file)" class="video-preview" @click="playVideo(file)"> + <div class="video-thumbnail"> + <el-image + :src="file.thumbUrl || file.url" + :alt="file.name" + fit="cover" + class="preview-image" + /> + <div class="play-overlay"> + <el-icon :size="40" class="play-icon"> + <VideoPlay /> + </el-icon> + </div> + </div> <div class="file-info"> <span class="file-name">{{ file.name }}</span> <span class="file-size">{{ formatFileSize(file.fileSize) }}</span> @@ -54,10 +59,10 @@ <el-button type="primary" size="small" - @click="downloadFile(file)" + @click="previewOrDownloadFile(file)" class="download-btn" > - 涓嬭浇 + {{ isImage(file) || isVideo(file) ? '棰勮' : '涓嬭浇' }} </el-button> </div> </div> @@ -67,13 +72,33 @@ <div v-else class="no-files"> <el-empty description="鏆傛棤鎻愪氦璧勬枡" :image-size="80" /> </div> + + <!-- 瑙嗛鎾斁瀵硅瘽妗� --> + <el-dialog + v-model="videoDialogVisible" + :title="currentVideoFile?.name || '瑙嗛鎾斁'" + width="80%" + center + > + <div class="video-player-container"> + <video + v-if="currentVideoFile" + :src="currentVideoFile.url" + controls + autoplay + class="video-player" + > + 鎮ㄧ殑娴忚鍣ㄤ笉鏀寔瑙嗛鎾斁 + </video> + </div> + </el-dialog> </div> </template> <script setup> -import { computed } from 'vue' -import { ElImage, ElButton, ElIcon, ElEmpty } from 'element-plus' -import { Document, Files } from '@element-plus/icons-vue' +import { computed, ref } from 'vue' +import { ElImage, ElButton, ElIcon, ElEmpty, ElDialog } from 'element-plus' +import { Document, Files, VideoPlay } from '@element-plus/icons-vue' const props = defineProps({ files: { @@ -81,6 +106,10 @@ default: () => [] } }) + +// 瑙嗛鎾斁瀵硅瘽妗� +const videoDialogVisible = ref(false) +const currentVideoFile = ref(null) // 鍥剧墖鏂囦欢URL鍒楄〃锛堢敤浜庨瑙堬級 const imageUrls = computed(() => { @@ -123,15 +152,27 @@ return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i] } -// 涓嬭浇鏂囦欢 -const downloadFile = (file) => { - const link = document.createElement('a') - link.href = file.url - link.download = file.name - link.target = '_blank' - document.body.appendChild(link) - link.click() - document.body.removeChild(link) +// 鎾斁瑙嗛 +const playVideo = (file) => { + currentVideoFile.value = file + videoDialogVisible.value = true +} + +// 棰勮鎴栦笅杞芥枃浠� +const previewOrDownloadFile = (file) => { + if (isImage(file) || isVideo(file)) { + // 鍥剧墖鍜岃棰戝湪鏂扮獥鍙d腑棰勮 + window.open(file.url, '_blank') + } else { + // 鍏朵粬鏂囦欢绫诲瀷涓嬭浇 + const link = document.createElement('a') + link.href = file.url + link.download = file.name + link.target = '_blank' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } } </script> @@ -172,11 +213,37 @@ flex-direction: column; } -.preview-image, -.preview-video { +.preview-image { width: 100%; height: 120px; object-fit: cover; +} + +.video-thumbnail { + position: relative; + width: 100%; + height: 120px; + cursor: pointer; +} + +.play-overlay { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.6); + border-radius: 50%; + padding: 12px; + transition: all 0.3s; +} + +.play-overlay:hover { + background: rgba(0, 0, 0, 0.8); + transform: translate(-50%, -50%) scale(1.1); +} + +.play-icon { + color: white; } .document-preview { @@ -235,4 +302,17 @@ .file-document { border-color: #E6A23C; } + +.video-player-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 300px; +} + +.video-player { + width: 100%; + max-width: 800px; + height: auto; +} </style> \ No newline at end of file diff --git a/web/src/config/api.ts b/web/src/config/api.ts new file mode 100644 index 0000000..485abb2 --- /dev/null +++ b/web/src/config/api.ts @@ -0,0 +1,69 @@ +// API閰嶇疆鏂囦欢 - 缁熶竴绠$悊鎵�鏈堿PI鐩稿叧閰嶇疆 +export const API_CONFIG = { + // 鍩虹URL閰嶇疆 + BASE_URL: '/api', + + // GraphQL绔偣 + GRAPHQL_ENDPOINT: '/api/graphql', + + // 鍏朵粬API绔偣 + ENDPOINTS: { + MEDIA_UPLOAD: '/api/media/upload', + MEDIA_DOWNLOAD: '/api/media/download', + WECHAT_LOGIN: '/api/wechat/login', + WECHAT_PHONE: '/api/wechat/phone' + } +} + +// GraphQL璇锋眰宸ュ叿鍑芥暟 +export const graphqlRequest = async (query: string, variables: any = {}) => { + // 鑾峰彇JWT token + const { getToken } = await import('@/utils/auth'); + const token = getToken(); + const headers: Record<string, string> = { + 'Content-Type': 'application/json', + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const response = await fetch(API_CONFIG.GRAPHQL_ENDPOINT, { + method: 'POST', + headers: headers, + body: JSON.stringify({ + query, + variables, + }), + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const result = await response.json() + + if (result.errors) { + throw new Error(result.errors[0].message) + } + + return result.data +} + +// 閫氱敤API璇锋眰宸ュ叿鍑芥暟 +export const apiRequest = async (endpoint: string, options: RequestInit = {}) => { + const url = endpoint.startsWith('http') ? endpoint : `${API_CONFIG.BASE_URL}${endpoint}` + + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + ...options, + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return response.json() +} \ No newline at end of file diff --git a/web/src/constants/mediaTargetType.ts b/web/src/constants/mediaTargetType.ts index d19fadd..2a38e8e 100644 --- a/web/src/constants/mediaTargetType.ts +++ b/web/src/constants/mediaTargetType.ts @@ -7,7 +7,7 @@ ACTIVITY: 2, // 娲诲姩 CAROUSEL: 4, // 杞挱鍥� ACTIVITY_PLAYER_SUBMISSION: 5, // 鍙傝禌鎶ュ悕璧勬枡 - STUDENT_AVATAR: 6, // 瀛﹀憳澶村儚 + USER_AVATAR: 7 // 鐢ㄦ埛澶村儚 } as const; @@ -21,6 +21,6 @@ [MediaTargetType.ACTIVITY]: '娲诲姩', [MediaTargetType.CAROUSEL]: '杞挱鍥�', [MediaTargetType.ACTIVITY_PLAYER_SUBMISSION]: '鍙傝禌鎶ュ悕璧勬枡', - [MediaTargetType.STUDENT_AVATAR]: '瀛﹀憳澶村儚', + [MediaTargetType.USER_AVATAR]: '鐢ㄦ埛澶村儚' } as const; \ No newline at end of file diff --git a/web/src/layout/index.vue b/web/src/layout/index.vue index 75d787e..0d417e6 100644 --- a/web/src/layout/index.vue +++ b/web/src/layout/index.vue @@ -74,7 +74,9 @@ { path: '/activity', title: '姣旇禌绠$悊', icon: 'Trophy' }, { path: '/judge', title: '璇勫绠$悊', icon: 'User' }, { path: '/rating-scheme', title: '璇勫垎妯℃澘', icon: 'Document' }, - { path: '/player', title: '姣旇禌鎶ュ悕', icon: 'UserFilled' }, + { path: '/player', title: '鎶ュ悕瀹℃牳', icon: 'UserFilled' }, + { path: '/project-review', title: '椤圭洰璇勫', icon: 'View' }, + { path: '/competition-promotion', title: '姣旇禌鏅嬬骇', icon: 'Promotion' }, { path: '/carousel', title: '鏂伴椈涓庢帹骞�', icon: 'Picture' }, { path: '/region', title: '鍖哄煙绠$悊', icon: 'Location' }, { path: '/employee', title: '鍛樺伐绠$悊', icon: 'Avatar' } diff --git a/web/src/router/index.ts b/web/src/router/index.ts index 731a900..b052ee7 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -65,7 +65,13 @@ path: '/player', name: 'Player', component: () => import('@/views/player/index.vue'), - meta: { title: '姣旇禌鎶ュ悕', icon: 'UserFilled' } + meta: { title: '鎶ュ悕瀹℃牳', icon: 'UserFilled' } + }, + { + path: '/player/:id/detail', + name: 'PlayerDetail', + component: () => import('@/views/player/detail.vue'), + meta: { title: '鎶ュ悕璇︽儏', icon: 'UserFilled' } }, { path: '/activity-player/:id/rating', @@ -90,6 +96,42 @@ name: 'Employee', component: () => import('@/views/employee/index.vue'), meta: { title: '鍛樺伐绠$悊', icon: 'Avatar' } + }, + { + path: '/project-review', + name: 'ProjectReview', + component: () => import('@/views/project-review/index.vue'), + meta: { title: '椤圭洰璇勫', icon: 'View' } + }, + { + path: '/project-review/:id/detail', + name: 'ProjectReviewDetail', + component: () => import('@/views/project-review/detail.vue'), + meta: { title: '椤圭洰璇勫璇︽儏', hidden: true } + }, + { + path: '/review', + name: 'Review', + component: () => import('@/views/review/index.vue'), + meta: { title: '椤圭洰璇勫', icon: 'Edit' } + }, + { + path: '/review/:id/detail', + name: 'ReviewDetail', + component: () => import('@/views/review/detail.vue'), + meta: { title: '椤圭洰璇勫璇︽儏', hidden: true } + }, + { + path: '/competition-promotion', + name: 'CompetitionPromotion', + component: () => import('@/views/competition-promotion/index.vue'), + meta: { title: '姣旇禌鏅嬬骇', icon: 'Promotion' } + }, + { + path: '/test/graphql', + name: 'GraphQLTest', + component: () => import('@/views/test/graphql-test.vue'), + meta: { title: 'GraphQL娴嬭瘯', icon: 'Connection' } } ] }, diff --git a/web/src/utils/appConfig.js b/web/src/utils/appConfig.js index ebfb020..aea2f43 100644 --- a/web/src/utils/appConfig.js +++ b/web/src/utils/appConfig.js @@ -1,4 +1,4 @@ -const GRAPHQL_ENDPOINT = 'http://localhost:8080/api/graphql'; +const GRAPHQL_ENDPOINT = '/api/graphql'; const GET_APP_CONFIG = ` query AppConfig { diff --git a/web/src/utils/graphql.ts b/web/src/utils/graphql.ts index 4641b2d..e37ecdf 100644 --- a/web/src/utils/graphql.ts +++ b/web/src/utils/graphql.ts @@ -97,7 +97,6 @@ name: string phone?: string description?: string - auditState?: number } } } @@ -131,7 +130,6 @@ name phone description - auditState } } } diff --git a/web/src/views/ActivityDetail.vue b/web/src/views/ActivityDetail.vue index 14f60e2..886b867 100644 --- a/web/src/views/ActivityDetail.vue +++ b/web/src/views/ActivityDetail.vue @@ -21,8 +21,6 @@ <el-descriptions-item label="鎶ュ悕鎴鏃堕棿">{{ formatDateTime(activity.signupDeadline) }}</el-descriptions-item> <el-descriptions-item label="姣旇禌寮�濮嬫椂闂�">{{ formatDateTime(activity.matchTime) }}</el-descriptions-item> <el-descriptions-item label="姣旇禌鍦板潃">{{ activity.address || '-' }}</el-descriptions-item> - <el-descriptions-item label="浜烘暟">{{ activity.playerMax || '-' }}</el-descriptions-item> - <el-descriptions-item label="褰撳墠鎶ュ悕浜烘暟">{{ activity.playerCount || 0 }}</el-descriptions-item> <el-descriptions-item label="璇勫垎妯℃澘"> {{ activity.ratingScheme ? activity.ratingScheme.name : '-' }} </el-descriptions-item> @@ -38,26 +36,17 @@ <!-- 姣旇禌闃舵 --> <div v-if="activity.stages && activity.stages.length > 0" class="stages-section"> <h3>姣旇禌闃舵</h3> - <el-table :data="activity.stages" style="width: 100%"> - <el-table-column prop="name" label="闃舵鍚嶇О" min-width="150" /> + <el-table :data="sortedStages" style="width: 100%" table-layout="auto"> + <el-table-column prop="sortOrder" label="椤哄簭" width="80" align="center" /> + <el-table-column prop="name" label="闃舵鍚嶇О" width="200" show-overflow-tooltip /> <el-table-column prop="matchTime" label="寮�濮嬫椂闂�" width="180"> <template #default="{ row }"> {{ formatDateTime(row.matchTime) }} </template> </el-table-column> - <el-table-column prop="address" label="鍦板潃" min-width="150"> + <el-table-column prop="address" label="鍦板潃" min-width="120" show-overflow-tooltip> <template #default="{ row }"> {{ row.address || '-' }} - </template> - </el-table-column> - <el-table-column prop="playerMax" label="鏈�澶т汉鏁�" width="100"> - <template #default="{ row }"> - {{ row.playerMax || '-' }} - </template> - </el-table-column> - <el-table-column prop="playerCount" label="瀹為檯浜烘暟" width="100"> - <template #default="{ row }"> - {{ row.playerCount || 0 }} </template> </el-table-column> <el-table-column prop="stateName" label="鐘舵��" width="100"> @@ -65,7 +54,7 @@ <el-tag :type="getStateType(row.state)">{{ row.stateName }}</el-tag> </template> </el-table-column> - <el-table-column label="鎿嶄綔" width="200"> + <el-table-column label="鎿嶄綔" width="220" fixed="right"> <template #default="{ row }"> <el-button size="small" @click="viewStageDetail(row)">鏌ョ湅璇︽儏</el-button> <el-button size="small" type="warning" @click="closeStage(row)" v-if="row.state === 1">鍏抽棴</el-button> @@ -121,7 +110,6 @@ </el-descriptions-item> <el-descriptions-item label="寮�濮嬫椂闂�">{{ formatDateTime(selectedStage.matchTime) }}</el-descriptions-item> <el-descriptions-item label="鍦板潃">{{ selectedStage.address || '-' }}</el-descriptions-item> - <el-descriptions-item label="浜烘暟">{{ selectedStage.playerMax || '-' }}</el-descriptions-item> <el-descriptions-item label="璇勫垎妯℃澘"> {{ selectedStage.ratingScheme ? selectedStage.ratingScheme.name : '缁ф壙姣旇禌妯℃澘' }} </el-descriptions-item> @@ -137,7 +125,7 @@ </template> <script setup> -import { ref, onMounted } from 'vue' +import { ref, onMounted, computed } from 'vue' import { useRouter, useRoute } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import { getActivity } from '@/api/activity' @@ -150,6 +138,16 @@ const activity = ref(null) const stageDialogVisible = ref(false) const selectedStage = ref(null) + +// 璁$畻灞炴�� +const sortedStages = computed(() => { + if (!activity.value || !activity.value.stages) return [] + return [...activity.value.stages].sort((a, b) => { + const orderA = a.sortOrder || 999 + const orderB = b.sortOrder || 999 + return orderA - orderB + }) +}) // 鍔犺浇姣旇禌璇︽儏 const loadActivity = async () => { @@ -233,9 +231,17 @@ } const getStageName = (stageId) => { - if (!activity.value || !activity.value.stages) return '鏈煡闃舵' - const stage = activity.value.stages.find(s => s.id === stageId) - return stage ? stage.name : '鏈煡闃舵' + if (!activity.value) return '鏈煡闃舵' + + // 鍙鏌ユ瘮璧涢樁娈� + if (activity.value.stages) { + const stage = activity.value.stages.find(s => s.id === stageId) + if (stage) { + return stage.name + } + } + + return '鏈煡闃舵' } // 鐢熷懡鍛ㄦ湡 diff --git a/web/src/views/ActivityForm.vue b/web/src/views/ActivityForm.vue index 140c631..5cadcfc 100644 --- a/web/src/views/ActivityForm.vue +++ b/web/src/views/ActivityForm.vue @@ -65,19 +65,9 @@ </el-col> </el-row> - <el-row :gutter="20"> - <el-col :span="12"> - <el-form-item label="姣旇禌鍦板潃" prop="address"> - <el-input v-model="form.address" placeholder="璇疯緭鍏ユ瘮璧涘湴鍧�" /> - </el-form-item> - </el-col> - - <el-col :span="12"> - <el-form-item label="浜烘暟" prop="playerMax"> - <el-input-number v-model="form.playerMax" :min="1" :max="9999" style="width: 100%" /> - </el-form-item> - </el-col> - </el-row> + <el-form-item label="姣旇禌鍦板潃" prop="address"> + <el-input v-model="form.address" placeholder="璇疯緭鍏ユ瘮璧涘湴鍧�" /> + </el-form-item> <el-form-item label="姣旇禌鎻忚堪" prop="description"> <el-input @@ -146,35 +136,33 @@ <el-tab-pane label="姣旇禌闃舵" name="stages"> <div class="stages-header"> <span>姣旇禌闃舵</span> - <el-button size="small" type="primary" @click="addStage">娣诲姞闃舵</el-button> + <div class="stages-controls"> + <el-button size="small" type="primary" @click="addStage">娣诲姞闃舵</el-button> + </div> </div> <div v-if="form.stages && form.stages.length > 0" class="stages-list"> - <div v-for="(stage, index) in form.stages" :key="index" class="stage-item"> + <div v-for="(stage, index) in sortedFormStages" :key="index" class="stage-item"> <div class="stage-info"> - <div class="stage-name">{{ stage.name || '鏈懡鍚嶉樁娈�' }}</div> + <div class="stage-header"> + <span class="stage-order">{{ stage.sortOrder || '-' }}</span> + <span class="stage-name">{{ stage.name || '鏈懡鍚嶉樁娈�' }}</span> + </div> <div class="stage-details"> <span class="detail-item"> <el-icon><Clock /></el-icon> {{ formatDateTime(stage.matchTime) }} </span> - <span class="detail-item"> - <el-icon><User /></el-icon> - {{ stage.playerMax || 0 }} 浜� - </span> - <span class="detail-item"> - <el-icon><UserFilled /></el-icon> - 瀹為檯: {{ stage.actualPlayerCount || 0 }} 浜� - </span> + <el-tag :type="stage.state === 1 ? 'success' : 'info'" size="small"> {{ stage.state === 1 ? '杩涜涓�' : '鏈紑濮�' }} </el-tag> </div> </div> <div class="stage-actions"> - <el-button size="small" @click="editStage(stage, index)">缂栬緫</el-button> + <el-button size="small" @click="editStage(stage, getOriginalStageIndex(stage))">缂栬緫</el-button> <el-button size="small" @click="closeStage(stage)" v-if="stage.state === 1">鍏抽棴</el-button> - <el-button size="small" type="danger" @click="removeStage(index)">鍒犻櫎</el-button> + <el-button size="small" type="danger" @click="removeStage(getOriginalStageIndex(stage))">鍒犻櫎</el-button> </div> </div> </div> @@ -214,37 +202,7 @@ <el-empty v-if="!form.judges || form.judges.length === 0" description="鏆傛棤璇勫" /> </el-tab-pane> - <!-- 瀛﹀憳鍒楄〃 --> - <el-tab-pane label="瀛﹀憳鍒楄〃" name="students"> - <div class="students-header"> - <span>瀛﹀憳鍒楄〃</span> - </div> - - <el-table :data="form.students" style="width: 100%" border> - <el-table-column label="瀛﹀憳鍚嶇О" prop="name" /> - <el-table-column label="鏈�鍚庡弬涓庣殑姣旇禌闃舵" width="200"> - <template #default="{ row }"> - {{ getLastStage(row) }} - </template> - </el-table-column> - <el-table-column label="鎿嶄綔" width="250" align="center"> - <template #default="{ row, $index }"> - <el-button size="small" @click="viewStudent(row, $index)">鏌ョ湅</el-button> - <el-button size="small" type="primary" @click="rateStudent(row, $index)">璇勫垎</el-button> - <el-button size="small" @click="commentStudent(row, $index)">鐐硅瘎</el-button> - <el-button - size="small" - :type="row.isAdvanced ? 'success' : 'warning'" - @click="toggleAdvancement(row, $index)" - > - {{ row.isAdvanced ? '宸叉檵绾�' : '鏅嬬骇' }} - </el-button> - </template> - </el-table-column> - </el-table> - - <el-empty v-if="!form.students || form.students.length === 0" description="鏆傛棤瀛﹀憳" /> - </el-tab-pane> + </el-tabs> </div> @@ -274,6 +232,26 @@ <el-input v-model="currentStage.name" placeholder="璇疯緭鍏ラ樁娈靛悕绉�" maxlength="30" /> </el-form-item> + <el-form-item label="姣旇禌闃舵椤哄簭" prop="sortOrder"> + <el-select v-model="currentStage.sortOrder" placeholder="璇烽�夋嫨闃舵椤哄簭" style="width: 100%"> + <el-option label="1" :value="1" /> + <el-option label="2" :value="2" /> + <el-option label="3" :value="3" /> + <el-option label="4" :value="4" /> + <el-option label="5" :value="5" /> + </el-select> + </el-form-item> + + <el-form-item label="瀛﹀憳浜烘暟" prop="playerMax"> + <el-input-number + v-model="currentStage.playerMax" + :min="1" + :max="1000" + placeholder="璇疯緭鍏ュ鍛樹汉鏁�" + style="width: 100%" + /> + </el-form-item> + <el-form-item label="璇勫垎妯℃澘"> <el-select v-model="currentStage.ratingSchemeId" placeholder="缁ф壙姣旇禌妯℃澘" style="width: 100%"> <el-option label="缁ф壙姣旇禌妯℃澘" :value="null" /> @@ -299,10 +277,6 @@ <el-form-item label="闃舵鍦板潃"> <el-input v-model="currentStage.address" placeholder="璇疯緭鍏ラ樁娈靛湴鍧�" /> - </el-form-item> - - <el-form-item label="浜烘暟"> - <el-input-number v-model="currentStage.playerMax" :min="1" :max="9999" style="width: 100%" /> </el-form-item> <el-form-item label="闃舵鎻忚堪"> @@ -349,7 +323,7 @@ <div style="margin-bottom: 16px;"> <el-form-item label="娣诲姞鍒伴樁娈碉細" label-width="100px"> <el-select v-model="selectedStageOption" style="width: 100%;" @change="handleStageChange"> - <el-option label="鎵�鏈夐樁娈�" value="all" /> + <!-- 鍙樉绀烘瘮璧涢樁娈� --> <el-option v-for="stage in form.stages" :key="stage.id" @@ -462,6 +436,9 @@ // Tab鐩稿叧 const activeTab = ref('stages') +// 闃舵鏁伴噺閫夋嫨 +const selectedStageCount = ref(1) + // 闃舵缂栬緫寮圭獥鐩稿叧 const stageDialogVisible = ref(false) const currentStageIndex = ref(-1) @@ -472,7 +449,6 @@ matchTime: '', address: '', ratingSchemeId: null, - playerMax: null, state: 1, actualPlayerCount: 0 }) @@ -524,7 +500,7 @@ matchTime: '', address: '', ratingSchemeId: null, - playerMax: 100, + playerMax: null, state: 1, stages: [], judges: [], @@ -534,6 +510,16 @@ // 璁$畻灞炴�� const isEdit = computed(() => !!route.params.id) + +// 鎸塻ortOrder鎺掑簭鐨勯樁娈靛垪琛� +const sortedFormStages = computed(() => { + if (!form.value.stages) return [] + return [...form.value.stages].sort((a, b) => { + const orderA = a.sortOrder || 999 + const orderB = b.sortOrder || 999 + return orderA - orderB + }) +}) // 琛ㄥ崟楠岃瘉瑙勫垯 const rules = { @@ -553,6 +539,10 @@ name: [ { required: true, message: '璇疯緭鍏ラ樁娈靛悕绉�', trigger: 'blur' }, { max: 30, message: '闃舵鍚嶇О涓嶈兘瓒呰繃30涓瓧绗�', trigger: 'blur' } + ], + playerMax: [ + { required: true, message: '璇疯緭鍏ュ鍛樹汉鏁�', trigger: 'blur' }, + { type: 'number', min: 1, max: 1000, message: '瀛﹀憳浜烘暟蹇呴』鍦�1-1000涔嬮棿', trigger: 'blur' } ] } @@ -599,7 +589,7 @@ matchTime: activity.matchTime || '', address: activity.address || '', ratingSchemeId: activity.ratingSchemeId, - playerMax: activity.playerMax || 100, + playerMax: activity.playerMax, state: activity.state, stages: activity.stages || [], judges: activity.judges || [], @@ -629,6 +619,9 @@ return mediaItem }) console.log('鏈�缁堢殑mediaFiles:', form.value.mediaFiles) + + // 璁剧疆闃舵鏁伴噺閫夋嫨鍣ㄧ殑鍊� + selectedStageCount.value = form.value.stages.length || 1 } catch (e) { console.error('鍔犺浇娲诲姩濯掍綋澶辫触:', e) } @@ -642,6 +635,45 @@ } // 闃舵绠$悊 +// 闃舵鏁伴噺鍙樺寲澶勭悊 +const onStageCountChange = (count) => { + if (!count) return + + // 濡傛灉褰撳墠闃舵鏁伴噺灏戜簬閫夋嫨鐨勬暟閲忥紝鑷姩娣诲姞闃舵 + while (form.value.stages.length < count) { + const stageIndex = form.value.stages.length + 1 + form.value.stages.push({ + id: null, + name: getDefaultStageName(stageIndex), + description: '', + matchTime: '', + address: form.value.address || '', + ratingSchemeId: form.value.ratingSchemeId, + sortOrder: stageIndex, + state: 1, + actualPlayerCount: 0 + }) + } + + // 濡傛灉褰撳墠闃舵鏁伴噺澶氫簬閫夋嫨鐨勬暟閲忥紝鍒犻櫎澶氫綑鐨勯樁娈� + if (form.value.stages.length > count) { + form.value.stages = form.value.stages.slice(0, count) + } + + ElMessage.success(`宸茶缃负${count}涓樁娈礰) +} + +// 鑾峰彇榛樿闃舵鍚嶇О +const getDefaultStageName = (index) => { + const stageNames = ['', '娴烽��', '澶嶈禌', '鍗婂喅璧�', '鍐宠禌', '鎬诲喅璧�'] + return stageNames[index] || `绗�${index}闃舵` +} + +// 鑾峰彇闃舵鍦ㄥ師濮嬫暟缁勪腑鐨勭储寮� +const getOriginalStageIndex = (stage) => { + return form.value.stages.findIndex(s => s === stage) +} + const addStage = () => { currentStageIndex.value = -1 resetStageForm() @@ -662,6 +694,15 @@ type: 'warning' }) form.value.stages.splice(index, 1) + + // 閲嶆柊鎺掑簭sortOrder + form.value.stages.forEach((stage, idx) => { + stage.sortOrder = idx + 1 + }) + + // 鏇存柊閫夋嫨鐨勯樁娈垫暟閲� + selectedStageCount.value = form.value.stages.length + ElMessage.success('鍒犻櫎鎴愬姛') } catch { // 鐢ㄦ埛鍙栨秷鍒犻櫎 @@ -687,8 +728,10 @@ await stageFormRef.value.validate() if (currentStageIndex.value === -1) { - // 鏂板闃舵 - form.value.stages.push({ ...currentStage.value }) + // 鏂板闃舵 - 璁剧疆姝g‘鐨剆ortOrder + const newStage = { ...currentStage.value } + newStage.sortOrder = form.value.stages.length + 1 + form.value.stages.push(newStage) } else { // 缂栬緫闃舵 form.value.stages[currentStageIndex.value] = { ...currentStage.value } @@ -710,6 +753,7 @@ address: '', ratingSchemeId: null, playerMax: null, + sortOrder: null, // 灏嗗湪saveStage涓缃纭殑鍊� state: 1, actualPlayerCount: 0 } @@ -756,13 +800,30 @@ } const getJudgeStages = (judge) => { - if (!judge.stageIds || !form.value.stages) return [] - return form.value.stages.filter(stage => judge.stageIds.includes(stage.id)) + if (!judge.stageIds) return [] + + const stages = [] + + // 鍙鏌ユ瘮璧涢樁娈� + if (form.value.stages) { + const matchedStages = form.value.stages.filter(stage => judge.stageIds.includes(stage.id)) + matchedStages.forEach(stage => { + stages.push({ + id: stage.id, + name: stage.name + }) + }) + } + + return stages } const resetJudgeDialog = () => { judgeSearchText.value = '' - selectedStageOption.value = 'all' + // 榛樿閫夋嫨绗竴涓樁娈� + selectedStageOption.value = form.value.stages && form.value.stages.length > 0 + ? form.value.stages[0].id?.toString() || '' + : '' selectedJudges.value = [] } @@ -806,22 +867,18 @@ const existingJudge = form.value.judges.find(j => j.id === judgeId) if (existingJudge) { // 鏇存柊鐜版湁璇勫鐨勯樁娈� - if (selectedStageOption.value === 'all') { - existingJudge.stageIds = form.value.stages.map(s => s.id).filter(id => id != null) - } else { - const stageId = parseInt(selectedStageOption.value) - if (!existingJudge.stageIds.includes(stageId)) { - existingJudge.stageIds.push(stageId) - } + const stageId = parseInt(selectedStageOption.value) + if (!existingJudge.stageIds.includes(stageId)) { + existingJudge.stageIds.push(stageId) } } else { // 娣诲姞鏂拌瘎濮� + const stageIds = [parseInt(selectedStageOption.value)] + const newJudge = { id: judge.id, name: judge.name, - stageIds: selectedStageOption.value === 'all' - ? form.value.stages.map(s => s.id).filter(id => id != null) - : [parseInt(selectedStageOption.value)] + stageIds: stageIds } form.value.judges.push(newJudge) addedCount++ @@ -1084,6 +1141,7 @@ address: stage.address, ratingSchemeId: stage.ratingSchemeId, playerMax: stage.playerMax, + sortOrder: stage.sortOrder, state: stage.state || 1 })) : [], judges: form.value.judges ? form.value.judges.map(judge => ({ @@ -1134,6 +1192,11 @@ await loadRatingSchemes() await loadAllJudges() await loadActivity() + + // 濡傛灉鏄柊寤烘ā寮忎笖娌℃湁闃舵锛岃嚜鍔ㄥ垱寤轰竴涓樁娈� + if (!isEdit.value && form.value.stages.length === 0) { + onStageCountChange(1) + } }) </script> @@ -1169,7 +1232,7 @@ .stage-header { display: flex; - justify-content: space-between; + justify-content: flex-start; align-items: center; } @@ -1271,11 +1334,32 @@ flex: 1; } +.stage-header { + display: flex; + align-items: center; + margin-bottom: 8px; +} + +.stage-order { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background-color: #409eff; + color: white; + border-radius: 50%; + font-size: 12px; + font-weight: 600; + flex-shrink: 0; +} + .stage-name { font-size: 16px; font-weight: 500; color: #303133; - margin-bottom: 8px; + margin: 0; + margin-left: 4px; } .stage-details { diff --git a/web/src/views/competition-promotion/index.vue b/web/src/views/competition-promotion/index.vue new file mode 100644 index 0000000..d2cebc3 --- /dev/null +++ b/web/src/views/competition-promotion/index.vue @@ -0,0 +1,727 @@ +<template> + <div class="promotion-container"> + <el-card> + <template #header> + <div class="card-header"> + <h3 class="card-title">姣旇禌鏅嬬骇绠$悊</h3> + </div> + </template> + + <!-- 鎼滅储鏍� --> + <el-form :inline="true" class="search-form"> + <el-form-item label="姣旇禌鍚嶇О"> + <el-input + v-model="searchForm.name" + placeholder="璇疯緭鍏ユ瘮璧涘悕绉�" + @keyup.enter="loadData" + style="width: 200px" + clearable + /> + </el-form-item> + + <el-form-item> + <el-button type="primary" @click="loadData" :loading="loading"> + 鎼滅储 + </el-button> + <el-button @click="resetSearch">閲嶇疆</el-button> + </el-form-item> + </el-form> + + <!-- 鏁版嵁琛ㄦ牸 --> + <el-table + :data="competitions" + style="width: 100%" + v-loading="loading" + empty-text="鏆傛棤姣旇禌鏁版嵁" + > + <el-table-column prop="competitionName" label="姣旇禌鍚嶇О" min-width="150"> + <template #default="scope"> + <div class="competition-info"> + <div class="main-name">{{ scope.row.competitionName }}</div> + <div class="stage-name">{{ scope.row.stageName }}</div> + </div> + </template> + </el-table-column> + + <el-table-column prop="maxParticipants" label="鏈�澶т汉鏁�" width="100" align="center"> + <template #default="scope"> + <el-tag type="info">{{ scope.row.maxParticipants || '涓嶉檺' }}</el-tag> + </template> + </el-table-column> + + <el-table-column prop="currentCount" label="褰撳墠鏁伴噺" width="100" align="center"> + <template #default="scope"> + <el-button + type="text" + @click="viewParticipants(scope.row)" + class="count-link" + > + {{ scope.row.currentCount }} + </el-button> + </template> + </el-table-column> + + <el-table-column prop="status" label="鐘舵��" width="100" align="center"> + <template #default="scope"> + <el-tag :type="getStatusType(scope.row.status)"> + {{ getStatusText(scope.row.status) }} + </el-tag> + </template> + </el-table-column> + + <el-table-column prop="startTime" label="寮�濮嬫椂闂�" width="180"> + <template #default="scope"> + {{ formatDate(scope.row.startTime) }} + </template> + </el-table-column> + + <el-table-column prop="endTime" label="缁撴潫鏃堕棿" width="180"> + <template #default="scope"> + {{ formatDate(scope.row.endTime) }} + </template> + </el-table-column> + + <el-table-column label="鎿嶄綔" width="150" fixed="right"> + <template #default="scope"> + <!-- 鍙湁闈炵涓�闃舵鎵嶆樉绀烘檵绾ф寜閽� --> + <el-button + v-if="scope.row.sortOrder > 1" + type="primary" + size="small" + @click="selectPromotionCandidates(scope.row)" + :disabled="scope.row.state !== 1" + > + 閫夋嫨鏅嬬骇浜哄憳 + </el-button> + <!-- 绗竴闃舵鏄剧ず鎻愮ず鏂囨湰 --> + <span v-else class="no-promotion-text">棣栬疆姣旇禌</span> + </template> + </el-table-column> + </el-table> + + <!-- 鍒嗛〉 --> + <div class="pagination-container"> + <el-pagination + v-model:current-page="pagination.page" + v-model:page-size="pagination.size" + :page-sizes="[10, 20, 50, 100]" + :total="pagination.total" + layout="total, sizes, prev, pager, next, jumper" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> + </el-card> + + <!-- 鏅嬬骇浜哄憳閫夋嫨瀵硅瘽妗� --> + <el-dialog + v-model="promotionDialogVisible" + title="閫夋嫨鏅嬬骇浜哄憳" + width="80%" + :before-close="handlePromotionDialogClose" + > + <div v-if="selectedCompetition"> + <div class="dialog-header"> + <h4>{{ selectedCompetition.competitionName }} - {{ selectedCompetition.stageName }}</h4> + <div class="promotion-info"> + <p>浠� <strong>{{ promotableData.previousStageName }}</strong> 鏅嬬骇鍒� <strong>{{ promotableData.currentStageName }}</strong></p> + <p>涓婁竴闃舵鎬讳汉鏁帮細{{ promotableData.totalCount }}浜猴紝鍙�夋嫨鏅嬬骇锛歿{ promotableData.selectableCount }}浜�</p> + </div> + </div> + + <!-- 鎼滅储妗� --> + <div class="search-container"> + <el-input + v-model="searchKeyword" + placeholder="鎼滅储椤圭洰鍚嶇О鎴栧弬璧涗汉鍛樺鍚�..." + clearable + style="width: 300px; margin-bottom: 15px" + @input="handleSearch" + > + <template #prefix> + <el-icon><Search /></el-icon> + </template> + </el-input> + </div> + + <!-- 鍙傝禌浜哄憳鍒楄〃 --> + <div class="table-info"> + <el-alert + title="鎻愮ず" + :description="`鍙傝禌鑰呭凡鎸夊钩鍧囧垎浠庨珮鍒颁綆鎺掑簭锛屽缓璁�夋嫨鍓� ${promotableData.selectableCount} 鍚嶈繘琛屾檵绾" + type="info" + show-icon + :closable="false" + style="margin-bottom: 15px" + /> + </div> + + <el-table + :data="participants" + v-loading="participantsLoading" + @selection-change="handleSelectionChange" + style="width: 100%" + :row-class-name="getRowClassName" + > + <el-table-column type="selection" width="55" /> + <el-table-column type="index" label="鎺掑悕" width="60" align="center" /> + <el-table-column prop="playerName" label="鍙傝禌鑰呭鍚�" min-width="120" /> + <el-table-column prop="projectName" label="椤圭洰鍚嶇О" min-width="150" /> + <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" width="120" /> + <el-table-column prop="averageScore" label="骞冲潎鍒�" width="100" align="center" sortable> + <template #default="scope"> + <span v-if="scope.row.averageScore > 0" class="score"> + {{ scope.row.averageScore.toFixed(1) }} + </span> + <span v-else class="no-score">鏈瘎鍒�</span> + </template> + </el-table-column> + <el-table-column prop="ratingCount" label="璇勫娆℃暟" width="100" align="center"> + <template #default="scope"> + <el-tag :type="scope.row.ratingCount > 0 ? 'success' : 'info'"> + {{ scope.row.ratingCount }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="applyTime" label="鎶ュ悕鏃堕棿" width="180"> + <template #default="scope"> + {{ formatDate(scope.row.applyTime) }} + </template> + </el-table-column> + </el-table> + </div> + + <template #footer> + <div class="dialog-footer"> + <span class="selected-info">宸查�夋嫨 {{ selectedParticipants.length }} 浜�</span> + <el-button @click="handlePromotionDialogClose">鍙栨秷</el-button> + <el-button + type="primary" + @click="confirmPromotion" + :disabled="selectedParticipants.length === 0" + :loading="promotionLoading" + > + 纭鏅嬬骇 + </el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, onMounted } from 'vue' +import { useRouter } from 'vue-router' +import { ElMessage, ElMessageBox } from 'element-plus' +import { Search } from '@element-plus/icons-vue' +import PromotionApi from '@/api/promotion' + +const router = useRouter() + +// 鍝嶅簲寮忔暟鎹� +const loading = ref(false) +const participantsLoading = ref(false) +const promotionLoading = ref(false) +const promotionDialogVisible = ref(false) + +// 鎼滅储琛ㄥ崟 +const searchForm = reactive({ + name: '' +}) + +// 姣旇禌鏁版嵁 +const competitions = ref([]) + +// 鍒嗛〉鏁版嵁 +const pagination = reactive({ + page: 1, + size: 10, + total: 0 +}) + +// 閫変腑鐨勬瘮璧� +const selectedCompetition = ref(null) + +// 鍙傝禌浜哄憳鏁版嵁 +const participants = ref([]) +const originalParticipants = ref([]) // 淇濆瓨鍘熷鍙傝禌鑰呮暟鎹敤浜庢悳绱� +const selectedParticipants = ref([]) + +// 鎼滅储鍏抽敭璇� +const searchKeyword = ref('') + +// 鍙檵绾у弬璧涜�呮暟鎹� +const promotableData = ref({ + participants: [], + selectableCount: 0, + totalCount: 0, + previousStageName: '', + currentStageName: '' +}) + +// 鍔犺浇姣旇禌鏁版嵁 +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 + } 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 + } finally { + loading.value = false + } +} + +// 閲嶇疆鎼滅储 +const resetSearch = () => { + searchForm.name = '' + pagination.page = 1 + loadData() +} + +// 鍒嗛〉澶勭悊 +const handleSizeChange = (size) => { + pagination.size = size + pagination.page = 1 + loadData() +} + +const handleCurrentChange = (page) => { + pagination.page = page + loadData() +} + +// 鏌ョ湅鍙傝禌浜哄憳 +const viewParticipants = (competition) => { + router.push({ + path: '/project-review', + query: { + competitionId: competition.id, + competitionName: competition.competitionName, + stageName: competition.stageName + } + }) +} + +// 閫夋嫨鏅嬬骇浜哄憳 +const selectPromotionCandidates = async (competition) => { + selectedCompetition.value = competition + promotionDialogVisible.value = true + await loadPromotableParticipants(competition.id) +} + +// 鍔犺浇鍙檵绾у弬璧涗汉鍛� +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: '鍒濊禌' + } + + promotableData.value = mockData + originalParticipants.value = mockData.participants + participants.value = mockData.participants + } finally { + participantsLoading.value = false + } +} + +// 鍔犺浇鍙傝禌浜哄憳锛堜繚鐣欏師鏂规硶浠ュ吋瀹瑰叾浠栧姛鑳斤級 +const loadParticipants = async (competitionId) => { + participantsLoading.value = true + try { + // 灏濊瘯浣跨敤鐪熷疄API鑾峰彇鍙傝禌浜哄憳 + const participantsData = await PromotionApi.getCompetitionParticipants(competitionId) + 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 + } finally { + participantsLoading.value = false + } +} + +// 澶勭悊閫夋嫨鍙樺寲 +const handleSelectionChange = (selection) => { + selectedParticipants.value = selection +} + +// 纭鏅嬬骇 +const confirmPromotion = async () => { + if (selectedParticipants.value.length === 0) { + ElMessage.warning('璇烽�夋嫨瑕佹檵绾х殑浜哄憳') + return + } + + try { + await ElMessageBox.confirm( + `纭畾瑕佸皢閫変腑鐨� ${selectedParticipants.value.length} 鍚嶅弬璧涜�呮檵绾у埌涓嬩竴闃舵鍚楋紵`, + '纭鏅嬬骇', + { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + } + ) + + promotionLoading.value = true + + try { + // 灏濊瘯浣跨敤鐪熷疄API鎵ц鏅嬬骇 + 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} 鍚嶄汉鍛榒) + } catch (error) { + console.warn('鏅嬬骇API澶辫触锛屼娇鐢ㄦā鎷熸搷浣�:', error) + // API澶辫触鏃舵ā鎷熸垚鍔� + await new Promise(resolve => setTimeout(resolve, 1000)) + ElMessage.success(`鎴愬姛鏅嬬骇 ${selectedParticipants.value.length} 鍚嶄汉鍛榒) + } + + handlePromotionDialogClose() + loadData() // 閲嶆柊鍔犺浇鏁版嵁 + } catch { + // 鐢ㄦ埛鍙栨秷 + } finally { + promotionLoading.value = false + } +} + +// 鍏抽棴鏅嬬骇瀵硅瘽妗� +const handlePromotionDialogClose = () => { + promotionDialogVisible.value = false + selectedCompetition.value = null + participants.value = [] + originalParticipants.value = [] + selectedParticipants.value = [] + searchKeyword.value = '' // 閲嶇疆鎼滅储鍏抽敭璇� + promotableData.value = { + participants: [], + selectableCount: 0, + totalCount: 0, + previousStageName: '', + currentStageName: '' + } +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStatusType = (status) => { + switch (status) { + case 1: return 'success' // 杩涜涓� + case 0: return 'warning' // 鏈彂甯� + case 2: return 'danger' // 鍏抽棴 + default: return 'info' + } +} + +// 鑾峰彇鐘舵�佹枃鏈� +const getStatusText = (status) => { + switch (status) { + case 1: return '杩涜涓�' + case 0: return '鏈彂甯�' + case 2: return '鍏抽棴' + default: return '鏈煡' + } +} + +// 鏍煎紡鍖栨棩鏈� +const formatDate = (dateString) => { + if (!dateString) return '-' + const date = new Date(dateString) + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }) +} + +// 鑾峰彇琛ㄦ牸琛屾牱寮忕被鍚� +const getRowClassName = ({ rowIndex }) => { + // 楂樹寒鎺ㄨ崘鏅嬬骇鐨勪汉鍛橈紙鍓峴electableCount鍚嶏級 + if (rowIndex < promotableData.value.selectableCount) { + return 'recommended-row' + } + return '' +} + +// 鎼滅储澶勭悊鏂规硶 +const handleSearch = () => { + if (!searchKeyword.value.trim()) { + // 濡傛灉鎼滅储鍏抽敭璇嶄负绌猴紝鏄剧ず鎵�鏈夊師濮嬫暟鎹� + participants.value = originalParticipants.value + } else { + // 鏍规嵁鍏抽敭璇嶈繃婊ゅ弬璧涜�� + const keyword = searchKeyword.value.toLowerCase().trim() + participants.value = originalParticipants.value.filter(participant => { + const projectName = participant.projectName?.toLowerCase() || '' + const playerName = participant.playerName?.toLowerCase() || '' + + return projectName.includes(keyword) || playerName.includes(keyword) + }) + } + + // 娓呯┖宸查�夋嫨鐨勫弬璧涜�咃紙鍥犱负鍒楄〃宸叉敼鍙橈級 + selectedParticipants.value = [] +} + +// 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹� +onMounted(() => { + loadData() +}) +</script> + +<style lang="scss" scoped> +.promotion-container { + padding: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-title { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #303133; +} + +.search-form { + margin-bottom: 20px; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; +} + +.competition-info { + .main-name { + font-weight: 600; + color: #303133; + margin-bottom: 4px; + } + + .stage-name { + font-size: 12px; + color: #409eff; + background: #ecf5ff; + padding: 2px 8px; + border-radius: 12px; + display: inline-block; + } +} + +.count-link { + font-weight: 600; + color: #409eff; + + &:hover { + color: #66b1ff; + } +} + +.score { + font-weight: 600; + color: #67c23a; +} + +.no-score { + color: #909399; + font-size: 12px; +} + +.no-promotion-text { + color: #909399; + font-size: 12px; + font-style: italic; +} + +.pagination-container { + margin-top: 20px; + display: flex; + justify-content: center; +} + +.dialog-header { + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 1px solid #ebeef5; + + h4 { + margin: 0 0 8px 0; + color: #303133; + font-size: 16px; + font-weight: 600; + } + + .promotion-info { + p { + margin: 4px 0; + color: #606266; + font-size: 14px; + + strong { + color: #409eff; + font-weight: 600; + } + } + } +} + +// 鎺ㄨ崘鏅嬬骇琛屾牱寮� +:deep(.recommended-row) { + background-color: #f0f9ff !important; + + &:hover { + background-color: #e1f5fe !important; + } +} + +.dialog-footer { + display: flex; + justify-content: space-between; + align-items: center; + + .selected-info { + color: #606266; + font-size: 14px; + } +} +</style> \ No newline at end of file diff --git a/web/src/views/player/detail.vue b/web/src/views/player/detail.vue new file mode 100644 index 0000000..6843618 --- /dev/null +++ b/web/src/views/player/detail.vue @@ -0,0 +1,774 @@ +<template> + <div class="player-detail"> + <div class="page-header"> + <el-button @click="goBack" type="primary" plain> + <el-icon><ArrowLeft /></el-icon> + 杩斿洖鍒楄〃 + </el-button> + <h2>鎶ュ悕璇︽儏</h2> + </div> + + <div v-if="loading" class="loading-container"> + <el-skeleton :rows="8" animated /> + </div> + + <div v-else-if="playerData" class="detail-content"> + <!-- 鍩烘湰淇℃伅鍗$墖 --> + <el-card class="info-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>鍩烘湰淇℃伅</span> + </div> + </template> + + <div class="basic-info"> + <div class="avatar-section"> + <el-avatar + :size="120" + :src="playerData.avatarUrl" + :icon="UserFilled" + class="player-avatar" + /> + </div> + + <div class="info-grid"> + <div class="info-item"> + <label>濮撳悕锛�</label> + <span>{{ playerData.name || '-' }}</span> + </div> + <div class="info-item"> + <label>鎬у埆锛�</label> + <span>{{ getGenderText(playerData.gender) }}</span> + </div> + <div class="info-item"> + <label>鍑虹敓鏃ユ湡锛�</label> + <span>{{ formatDate(playerData.birthday) }}</span> + </div> + <div class="info-item"> + <label>鎵嬫満鍙凤細</label> + <span>{{ playerData.phone || '-' }}</span> + </div> + <div class="info-item"> + <label>鍖哄煙锛�</label> + <span>{{ playerData.regionName || '-' }}</span> + </div> + <div class="info-item"> + <label>瀛﹀巻锛�</label> + <span>{{ getEducationText(playerData.education) }}</span> + </div> + </div> + </div> + + <div v-if="playerData.introduction" class="introduction-section"> + <h3>涓汉浠嬬粛</h3> + <div class="introduction-content">{{ playerData.introduction }}</div> + </div> + </el-card> + + <!-- 鍙傝禌椤圭洰淇℃伅鍗$墖 --> + <el-card class="info-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>鍙傝禌椤圭洰淇℃伅</span> + </div> + </template> + + <div class="project-info"> + <div class="info-item"> + <label>姣旇禌鍚嶇О锛�</label> + <span>{{ activityPlayerData.activityName || '-' }}</span> + </div> + <div class="info-item"> + <label>鎶ュ悕鏃堕棿锛�</label> + <span>{{ formatDateTime(activityPlayerData.createTime) }}</span> + </div> + <div class="info-item"> + <label>瀹℃牳鐘舵�侊細</label> + <el-tag :type="getStatusType(activityPlayerData.state)"> + {{ getStatusText(activityPlayerData.state) }} + </el-tag> + </div> + </div> + + <!-- 椤圭洰鍚嶇О鍗曠嫭涓�琛� --> + <div v-if="activityPlayerData.projectName" class="project-name-section"> + <h4>椤圭洰鍚嶇О</h4> + <div class="project-name-content">{{ activityPlayerData.projectName }}</div> + </div> + + <div v-if="activityPlayerData.description" class="description-section"> + <h4>椤圭洰鎻忚堪</h4> + <div class="description-content">{{ activityPlayerData.description }}</div> + </div> + + <!-- 闄勪欢鍒楄〃 --> + <div v-if="attachments.length > 0" class="attachments-section"> + <h4>椤圭洰闄勪欢</h4> + <div class="attachments-list"> + <div + v-for="attachment in attachments" + :key="attachment.id" + class="attachment-item" + > + <el-icon class="attachment-icon"><Document /></el-icon> + <span class="attachment-name">{{ attachment.originalName }}</span> + <el-button + type="primary" + size="small" + @click="downloadAttachment(attachment)" + > + 涓嬭浇 + </el-button> + </div> + </div> + </div> + </el-card> + + <!-- 瀹℃牳鍔熻兘鍗$墖 --> + <el-card class="info-card review-card" shadow="hover"> + <template #header> + <div class="card-header"> + <span>瀹℃牳绠$悊</span> + </div> + </template> + + <div class="review-section"> + <div class="review-status"> + <label>褰撳墠鐘舵�侊細</label> + <el-tag :type="getStatusType(activityPlayerData.state)" size="large"> + {{ getStatusText(activityPlayerData.state) }} + </el-tag> + </div> + + <div class="feedback-section"> + <label>瀹℃牳鎰忚锛�</label> + <el-input + v-model="feedbackText" + type="textarea" + :rows="4" + placeholder="璇疯緭鍏ュ鏍告剰瑙�..." + maxlength="500" + show-word-limit + class="feedback-input" + /> + </div> + + <div class="review-actions"> + <el-button + type="success" + @click="handleApprove" + :loading="approving" + :disabled="activityPlayerData.state === 1" + > + 閫氳繃 + </el-button> + <el-button + type="danger" + @click="handleReject" + :loading="rejecting" + :disabled="activityPlayerData.state === 2" + > + 椹冲洖 + </el-button> + <el-button + type="primary" + @click="handleUpdateFeedback" + :loading="updating" + > + 鏇存柊鎰忚 + </el-button> + <el-button + type="info" + @click="handleClose" + > + 鍏抽棴 + </el-button> + </div> + + <div v-if="activityPlayerData.feedback" class="current-feedback"> + <label>褰撳墠瀹℃牳鎰忚锛�</label> + <div class="feedback-content">{{ activityPlayerData.feedback }}</div> + </div> + </div> + </el-card> + </div> + + <div v-else class="error-container"> + <el-result + icon="warning" + title="鏁版嵁鍔犺浇澶辫触" + sub-title="鏃犳硶鑾峰彇鍙傝禌浜哄憳璇︽儏淇℃伅" + > + <template #extra> + <el-button type="primary" @click="loadData">閲嶆柊鍔犺浇</el-button> + </template> + </el-result> + </div> + </div> +</template> + +<script setup lang="ts"> +import { ref, onMounted } from 'vue' +import { useRoute, useRouter } from 'vue-router' +import { graphqlRequest } from '@/config/api' +import { ElMessage, ElMessageBox } from 'element-plus' +import { ArrowLeft, UserFilled, Document } from '@element-plus/icons-vue' +import { approveActivityPlayer, rejectActivityPlayer, updatePlayerFeedback } from '@/api/activityPlayer.js' + +const route = useRoute() +const router = useRouter() + +// 鍝嶅簲寮忔暟鎹� +const loading = ref(true) +const playerData = ref<any>(null) +const activityPlayerData = ref<any>(null) +const attachments = ref<any[]>([]) + +// 瀹℃牳鐩稿叧鏁版嵁 +const feedbackText = ref('') +const approving = ref(false) +const rejecting = ref(false) +const updating = ref(false) + +// 椤甸潰鍔犺浇 +onMounted(() => { + loadData() +}) + +// 鍔犺浇鏁版嵁 +const loadData = async () => { + try { + loading.value = true + const playerId = route.params.id as string + + // 杩欓噷搴旇璋冪敤API鑾峰彇鏁版嵁 + // 鏆傛椂浣跨敤妯℃嫙鏁版嵁 + await loadPlayerData(playerId) + + } catch (error) { + console.error('鍔犺浇鏁版嵁澶辫触:', error) + ElMessage.error('鍔犺浇鏁版嵁澶辫触') + } finally { + loading.value = false + } +} + +// GraphQL鏌ヨ +const ACTIVITY_PLAYER_DETAIL_QUERY = ` + query ActivityPlayerDetail($id: ID!) { + activityPlayerDetail(id: $id) { + id + playerInfo { + id + name + phone + gender + birthday + education + introduction + description + avatarUrl + avatar { + id + name + path + fullUrl + fullThumbUrl + fileSize + fileExt + mediaType + } + } + regionInfo { + id + name + fullPath + } + activityName + projectName + description + feedback + state + submissionFiles { + id + name + url + fileExt + fileSize + mediaType + } + } + } +` + +// 浣跨敤缁熶竴鐨凣raphQL璇锋眰鍑芥暟 + +// 鍔犺浇鎵�鏈夋暟鎹� +const loadPlayerData = async (playerId: string) => { + try { + const data = await graphqlRequest(ACTIVITY_PLAYER_DETAIL_QUERY, { id: playerId }) + const detail = data.activityPlayerDetail + + if (detail) { + // 璁剧疆player鍩烘湰淇℃伅 + playerData.value = { + id: detail.playerInfo.id, + name: detail.playerInfo.name, + phone: detail.playerInfo.phone, + gender: detail.playerInfo.gender, + birthday: detail.playerInfo.birthday, + education: detail.playerInfo.education, + introduction: detail.playerInfo.introduction, + description: detail.playerInfo.description, + avatarUrl: detail.playerInfo.avatar?.fullUrl || detail.playerInfo.avatarUrl, + regionName: detail.regionInfo?.fullPath || detail.regionInfo?.name || '-' + } + + // 璁剧疆activity_player鏁版嵁 + activityPlayerData.value = { + id: detail.id, + activityName: detail.activityName, + projectName: detail.projectName || '-', + description: detail.description, + feedback: detail.feedback || '', + state: detail.state || 0, + createTime: new Date().toISOString() + } + + // 璁剧疆闄勪欢鏁版嵁 + attachments.value = detail.submissionFiles.map((file: any) => ({ + id: file.id, + originalName: file.name, + url: file.url, + fileSize: file.fileSize ? `${(file.fileSize / 1024 / 1024).toFixed(1)}MB` : '-' + })) + + // 鍒濆鍖栧鏍告剰瑙� + feedbackText.value = detail.feedback || '' + } + } catch (error) { + console.error('鍔犺浇鏁版嵁澶辫触:', error) + throw error + } +} + +// 绉婚櫎鍗曠嫭鐨勫姞杞藉嚱鏁帮紝缁熶竴鍦╨oadPlayerData涓鐞� +const loadActivityPlayerData = async (playerId: string) => { + // 宸插湪loadPlayerData涓鐞� +} + +const loadAttachments = async (playerId: string) => { + // 宸插湪loadPlayerData涓鐞� +} + +// 杩斿洖鍒楄〃 +const goBack = () => { + router.push('/player') +} + +// 鏍煎紡鍖栨棩鏈� +const formatDate = (dateStr: string) => { + if (!dateStr) return '-' + return new Date(dateStr).toLocaleDateString('zh-CN') +} + +// 鏍煎紡鍖栨棩鏈熸椂闂� +const formatDateTime = (dateStr: string) => { + if (!dateStr) return '-' + return new Date(dateStr).toLocaleString('zh-CN') +} + +// 鑾峰彇鎬у埆鏂囨湰 +const getGenderText = (gender: number) => { + const genderMap: Record<number, string> = { + 1: '鐢�', + 2: '濂�' + } + return genderMap[gender] || '-' +} + +// 鑾峰彇瀛﹀巻鏂囨湰 +const getEducationText = (education: number) => { + const educationMap: Record<number, string> = { + 1: '楂樹腑', + 2: '澶т笓', + 3: '鏈', + 4: '纭曞+', + 5: '鍗氬+' + } + return educationMap[education] || '-' +} + +// 鑾峰彇鐘舵�佹枃鏈� +const getStatusText = (state: number) => { + const statusMap: Record<number, string> = { + 0: '鏈鏍�', + 1: '瀹℃牳閫氳繃', + 2: '瀹℃牳椹冲洖' + } + return statusMap[state] || '-' +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStatusType = (state: number) => { + const typeMap: Record<number, string> = { + 0: 'warning', + 1: 'success', + 2: 'danger' + } + return typeMap[state] || 'info' +} + +// 涓嬭浇闄勪欢 +const downloadAttachment = (attachment: any) => { + // TODO: 瀹炵幇闄勪欢涓嬭浇鍔熻兘 + window.open(attachment.url, '_blank') +} + +// 瀹℃牳閫氳繃 +const handleApprove = async () => { + try { + await ElMessageBox.confirm('纭瀹℃牳閫氳繃璇ユ姤鍚嶇敵璇凤紵', '纭鎿嶄綔', { + confirmButtonText: '纭', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + approving.value = true + const result = await approveActivityPlayer(activityPlayerData.value.id, feedbackText.value) + + if (result.approveActivityPlayer) { + ElMessage.success('瀹℃牳閫氳繃鎴愬姛') + activityPlayerData.value.state = 1 + activityPlayerData.value.feedback = feedbackText.value + } else { + ElMessage.error('瀹℃牳閫氳繃澶辫触') + } + } catch (error) { + if (error !== 'cancel') { + console.error('瀹℃牳閫氳繃澶辫触:', error) + ElMessage.error('瀹℃牳閫氳繃澶辫触') + } + } finally { + approving.value = false + } +} + +// 瀹℃牳椹冲洖 +const handleReject = async () => { + if (!feedbackText.value.trim()) { + ElMessage.warning('椹冲洖鏃跺繀椤诲~鍐欏鏍告剰瑙�') + return + } + + try { + await ElMessageBox.confirm('纭椹冲洖璇ユ姤鍚嶇敵璇凤紵', '纭鎿嶄綔', { + confirmButtonText: '纭', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + rejecting.value = true + const result = await rejectActivityPlayer(activityPlayerData.value.id, feedbackText.value) + + if (result.rejectActivityPlayer) { + ElMessage.success('瀹℃牳椹冲洖鎴愬姛') + activityPlayerData.value.state = 2 + activityPlayerData.value.feedback = feedbackText.value + } else { + ElMessage.error('瀹℃牳椹冲洖澶辫触') + } + } catch (error) { + if (error !== 'cancel') { + console.error('瀹℃牳椹冲洖澶辫触:', error) + ElMessage.error('瀹℃牳椹冲洖澶辫触') + } + } finally { + rejecting.value = false + } +} + +// 鏇存柊瀹℃牳鎰忚 +const handleUpdateFeedback = async () => { + if (!feedbackText.value.trim()) { + ElMessage.warning('璇峰~鍐欏鏍告剰瑙�') + return + } + + try { + updating.value = true + const result = await updatePlayerFeedback(activityPlayerData.value.id, feedbackText.value) + + if (result.updatePlayerFeedback) { + ElMessage.success('瀹℃牳鎰忚鏇存柊鎴愬姛') + activityPlayerData.value.feedback = feedbackText.value + } else { + ElMessage.error('瀹℃牳鎰忚鏇存柊澶辫触') + } + } catch (error) { + console.error('鏇存柊瀹℃牳鎰忚澶辫触:', error) + ElMessage.error('鏇存柊瀹℃牳鎰忚澶辫触') + } finally { + updating.value = false + } +} + +// 鍏抽棴椤甸潰 +const handleClose = () => { + goBack() +} +</script> + +<style scoped lang="scss"> +.player-detail { + padding: 20px; + + .page-header { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 24px; + + h2 { + margin: 0; + color: #303133; + } + } + + .loading-container { + padding: 40px; + } + + .detail-content { + display: flex; + flex-direction: column; + gap: 24px; + } + + .info-card { + .card-header { + font-weight: 600; + color: #303133; + } + } + + .basic-info { + display: flex; + gap: 32px; + margin-bottom: 24px; + + .avatar-section { + flex-shrink: 0; + + .player-avatar { + border: 2px solid #e4e7ed; + } + } + + .info-grid { + flex: 1; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + + .info-item { + display: flex; + align-items: center; + + label { + font-weight: 500; + color: #606266; + min-width: 80px; + } + + span { + color: #303133; + } + } + } + } + + .introduction-section, .description-section { + margin-top: 24px; + + h4 { + margin: 0 0 12px 0; + color: #303133; + font-weight: 600; + } + + .introduction-content, .description-content { + padding: 16px; + background-color: #f8f9fa; + border-radius: 6px; + line-height: 1.6; + color: #606266; + } + } + + .project-info { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + + .info-item { + display: flex; + align-items: center; + + label { + font-weight: 500; + color: #606266; + min-width: 100px; + } + + span { + color: #303133; + } + } + } + + .project-name-section { + margin-top: 24px; + + h4 { + margin: 0 0 12px 0; + color: #303133; + font-weight: 600; + } + + .project-name-content { + padding: 12px; + background-color: #f8f9fa; + border-radius: 6px; + border-left: 4px solid #409eff; + color: #303133; + font-weight: 500; + } + } + + .attachments-section { + margin-top: 24px; + + h4 { + margin: 0 0 16px 0; + color: #303133; + font-weight: 600; + } + + .attachments-list { + display: flex; + flex-direction: column; + gap: 12px; + + .attachment-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e4e7ed; + + .attachment-icon { + color: #409eff; + font-size: 20px; + } + + .attachment-name { + flex: 1; + color: #303133; + } + } + } + } + + .error-container { + padding: 40px; + text-align: center; + } + + .review-card { + margin-top: 24px; + + .review-status { + margin-bottom: 16px; + + .status-label { + font-weight: 600; + color: #303133; + margin-right: 8px; + } + } + + .feedback-section { + margin-bottom: 16px; + + .feedback-label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #303133; + } + + .feedback-textarea { + width: 100%; + min-height: 100px; + resize: vertical; + } + + .char-count { + text-align: right; + margin-top: 4px; + font-size: 12px; + color: #909399; + } + } + + .review-actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + + .el-button { + min-width: 80px; + } + } + + .current-feedback { + margin-top: 16px; + padding: 12px; + background-color: #f8f9fa; + border-radius: 6px; + border-left: 4px solid #409eff; + + .feedback-label { + font-weight: 600; + color: #303133; + margin-bottom: 8px; + } + + .feedback-content { + color: #606266; + line-height: 1.6; + white-space: pre-wrap; + } + } + } +} + +@media (max-width: 768px) { + .basic-info { + flex-direction: column; + align-items: center; + text-align: center; + + .info-grid { + grid-template-columns: 1fr; + } + } + + .project-info { + grid-template-columns: 1fr; + } +} +</style> \ No newline at end of file diff --git a/web/src/views/player/index.vue b/web/src/views/player/index.vue index 71f6665..3531e92 100644 --- a/web/src/views/player/index.vue +++ b/web/src/views/player/index.vue @@ -1,7 +1,7 @@ <template> <div class="player-page"> <div class="page-card"> - <h3 class="card-title">姣旇禌鎶ュ悕</h3> + <h3 class="card-title">鎶ュ悕瀹℃牳</h3> <!-- 鎼滅储鍜屾搷浣滄爮 --> <div class="toolbar"> @@ -26,13 +26,10 @@ <el-option v-for="activity in activityOptions" :key="activity.id" - :label="getActivityDisplayName(activity)" + :label="activity.name" :value="activity.id" > - <span>{{ getActivityName(activity) }}</span> - <span v-if="activity.pid > 0" style="color: #409eff; margin-left: 8px;"> - {{ activity.name }} - </span> + {{ activity.name }} </el-option> </el-select> <el-select @@ -41,10 +38,9 @@ style="width: 150px" clearable > - <el-option label="鏈鏍�" value="0" /> - <el-option label="杩涜涓�" value="1" /> - <el-option label="宸查┏鍥�" value="2" /> - <el-option label="宸茬粨鏉�" value="3" /> + <el-option label="寰呭鏍�" value="0" /> + <el-option label="閫氳繃" value="1" /> + <el-option label="椹冲洖" value="2" /> </el-select> <el-button type="primary" @click="handleSearch"> <el-icon><Search /></el-icon> @@ -70,27 +66,12 @@ <el-tag :type="getStateType(row.state)">{{ getStateText(row.state) }}</el-tag> </template> </el-table-column> - <el-table-column label="鎿嶄綔" width="200" fixed="right"> + <el-table-column label="鎿嶄綔" width="120" fixed="right"> <template #default="{ row }"> <div class="table-actions"> - <el-button - v-if="row.state === 1" - type="success" - size="small" - @click="handleApprove(row)" - > - 瀹℃牳閫氳繃 - </el-button> - <el-button - v-if="row.state === 1" - type="danger" - size="small" - @click="handleReject(row)" - > - 瀹℃牳鎷掔粷 - </el-button> - <el-button type="primary" size="small" @click="handleView(row)"> - 璇勫垎璇︽儏 + <!-- 鍙繚鐣欒鎯呮寜閽� --> + <el-button type="primary" size="small" @click="handleViewDetail(row)"> + 璇︽儏 </el-button> </div> </template> @@ -149,7 +130,7 @@ activityName: '2024骞村垱鏂板垱涓氬ぇ璧�', phone: '13800138001', applyTime: '2024-01-05 14:30:00', - state: 1 // 1-寰呭鏍�, 2-杩涜涓�, 3-宸茬粨鏉� + state: 0 // 0-鏈鏍� }, { id: 2, @@ -158,7 +139,7 @@ activityName: '涔︽硶姣旇禌', phone: '13900139002', applyTime: '2024-01-16 10:30:00', - state: 2 + state: 1 // 1-瀹℃牳閫氳繃 }, { id: 3, @@ -167,16 +148,25 @@ activityName: '缁樼敾姣旇禌', phone: '13900139003', applyTime: '2024-01-17 14:20:00', - state: 1 + state: 2 // 2-瀹℃牳椹冲洖 + }, + { + id: 4, + name: '璧靛叚', + avatar: '', + activityName: '闊充箰姣旇禌', + phone: '13900139004', + applyTime: '2024-01-18 09:15:00', + state: 0 // 0-鏈鏍� } ]) // 鑾峰彇鐘舵�佹爣绛剧被鍨� const getStateType = (state: number | null | undefined) => { const typeMap: Record<number, string> = { - 0: 'warning', // 寰呭鏍� - 1: 'success', // 杩涜涓� - 2: 'danger', // 鏈�氳繃 + 0: 'warning', // 鏈鏍� + 1: 'success', // 瀹℃牳閫氳繃 + 2: 'danger', // 瀹℃牳椹冲洖 3: 'info' // 宸茬粨鏉� } return state != null ? (typeMap[state] || 'info') : 'info' @@ -185,9 +175,9 @@ // 鑾峰彇鐘舵�佹枃鏈� const getStateText = (state: number | null | undefined) => { const textMap: Record<number, string> = { - 0: '寰呭鏍�', - 1: '杩涜涓�', - 2: '鏈�氳繃', + 0: '鏈鏍�', + 1: '瀹℃牳閫氳繃', + 2: '瀹℃牳椹冲洖', 3: '宸茬粨鏉�' } return state != null ? (textMap[state] || '鏈煡') : '鏈煡' @@ -199,35 +189,16 @@ loadData() } -// 瀹℃牳閫氳繃 -const handleApprove = async (row: any) => { - try { - await ElMessageBox.confirm(`纭畾瀹℃牳閫氳繃瀛﹀憳"${row.name}"鐨勬姤鍚嶇敵璇峰悧锛焋, '鎻愮ず', { - confirmButtonText: '纭畾', - cancelButtonText: '鍙栨秷', - type: 'success' - }) - - ElMessage.success('瀹℃牳閫氳繃鎴愬姛') - row.state = 2 - } catch { - // 鐢ㄦ埛鍙栨秷 - } -} -// 瀹℃牳鎷掔粷 -const handleReject = (row: any) => { - ElMessageBox.confirm('纭鎷掔粷璇ョ敵璇凤紵', '鎻愮ず', { - confirmButtonText: '纭畾', - cancelButtonText: '鍙栨秷', - type: 'warning' - }).then(() => { - // 杩欓噷搴旇璋冪敤API鏇存柊鐘舵�� - row.state = 3 // 鏇存柊涓哄凡缁撴潫 - ElMessage.success('宸叉嫆缁�') - }).catch(() => { - ElMessage.info('宸插彇娑�') - }) + +// 鏌ョ湅璇︽儏锛堣烦杞埌璇︽儏椤甸潰锛屽彧璇绘ā寮忥級 +const handleViewDetail = (row: any) => { + if (!row.id) { + ElMessage.error('鏃犳硶鑾峰彇鎶ュ悕璁板綍ID') + return + } + // 璺宠浆鍒拌鎯呴〉闈紙鍙妯″紡锛� + router.push(`/player/${row.id}/detail`) } // 鏌ョ湅璇︽儏锛堣烦杞埌璇勫垎椤甸潰锛� @@ -259,13 +230,7 @@ return activity.name } -// 鑾峰彇娲诲姩鏄剧ず鍚嶇О锛堢敤浜庢悳绱㈠拰閫変腑鏃舵樉绀猴級 -const getActivityDisplayName = (activity: any) => { - if (activity.pid > 0 && activity.parent) { - return `${activity.parent.name} - ${activity.name}` - } - return activity.name -} + // 鍔犺浇娲诲姩閫夐」 const loadActivityOptions = async () => { @@ -285,6 +250,7 @@ const list = await PlayerApi.getApplications( searchForm.name || '', searchForm.activityId || null, + searchForm.state !== '' ? parseInt(searchForm.state) : null, pagination.page, pagination.size ) @@ -331,8 +297,11 @@ display: flex; gap: 8px; flex-wrap: wrap; + align-items: center; } + + .pagination { margin-top: 20px; display: flex; diff --git a/web/src/views/project-review/detail.vue b/web/src/views/project-review/detail.vue new file mode 100644 index 0000000..f59bbe1 --- /dev/null +++ b/web/src/views/project-review/detail.vue @@ -0,0 +1,530 @@ +<template> + <div class="detail-container"> + <el-card v-loading="loading"> + <template #header> + <div class="card-header"> + <h3 class="card-title">椤圭洰璇勫璇︽儏</h3> + <el-button @click="goBack">杩斿洖</el-button> + </div> + </template> + + <el-row :gutter="20" v-if="projectDetail"> + <!-- 宸︿晶锛氶」鐩俊鎭� --> + <el-col :span="16"> + <div class="project-section"> + <!-- 椤圭洰鍩烘湰淇℃伅 --> + <h4>椤圭洰淇℃伅</h4> + <el-descriptions :column="2" border> + <el-descriptions-item label="椤圭洰鍚嶇О"> + {{ projectDetail.projectName || '鏈~鍐�' }} + </el-descriptions-item> + <el-descriptions-item label="姣旇禌鍚嶇О"> + {{ projectDetail.activityName }} + </el-descriptions-item> + <el-descriptions-item label="椤圭洰鎻忚堪" :span="2"> + <div class="description-content"> + {{ projectDetail.description || '鏆傛棤鎻忚堪' }} + </div> + </el-descriptions-item> + </el-descriptions> + + <!-- 椤圭洰闄勪欢 --> + <h4 style="margin-top: 20px;">椤圭洰闄勪欢</h4> + <div class="attachments" v-if="projectDetail.submissionFiles && projectDetail.submissionFiles.length > 0"> + <div v-for="file in projectDetail.submissionFiles" :key="file.id" class="attachment-item"> + <div class="file-info"> + <el-icon class="file-icon"><Document /></el-icon> + <span class="file-name">{{ file.name }}</span> + <span class="file-size">{{ formatFileSize(file.fileSize) }}</span> + </div> + <div class="file-actions"> + <el-button type="primary" link @click="previewFile(file)"> + 棰勮 + </el-button> + <el-button type="primary" link @click="downloadFile(file)"> + 涓嬭浇 + </el-button> + </div> + </div> + </div> + <div v-else class="no-attachments"> + <el-empty description="鏆傛棤闄勪欢" /> + </div> + + <!-- 鍙傝禌浜轰俊鎭� --> + <h4 style="margin-top: 20px;">鍙傝禌浜轰俊鎭�</h4> + <el-descriptions :column="2" border v-if="projectDetail.playerInfo"> + <el-descriptions-item label="澶村儚"> + <el-avatar + :src="projectDetail.playerInfo.avatarUrl" + :size="60" + :icon="UserFilled" + /> + </el-descriptions-item> + <el-descriptions-item label="濮撳悕"> + {{ projectDetail.playerInfo.name }} + </el-descriptions-item> + <el-descriptions-item label="鑱旂郴鐢佃瘽"> + {{ projectDetail.playerInfo.phone }} + </el-descriptions-item> + <el-descriptions-item label="鎵�灞炲尯鍩�" v-if="projectDetail.regionInfo"> + {{ projectDetail.regionInfo.name }} + </el-descriptions-item> + <el-descriptions-item label="鎶ュ悕鐘舵��"> + <el-tag :type="getStateType(projectDetail.state)"> + {{ getStateName(projectDetail.state) }} + </el-tag> + </el-descriptions-item> + </el-descriptions> + </div> + </el-col> + + <!-- 鍙充晶锛氳瘎鍒嗕俊鎭� --> + <el-col :span="8"> + <div class="rating-section"> + <!-- 璇勫缁熻 --> + <h4>璇勫淇℃伅</h4> + <el-card class="rating-summary"> + <div class="rating-item"> + <span class="label">宸茶瘎瀹℃鏁帮細</span> + <span class="value">{{ ratingStats.ratingCount }}</span> + </div> + <div class="rating-item"> + <span class="label">褰撳墠骞冲潎鍒嗭細</span> + <span class="value score"> + {{ ratingStats.averageScore > 0 ? ratingStats.averageScore.toFixed(1) : '鏈瘎鍒�' }} + </span> + </div> + </el-card> + + <!-- 璇勫垎妯℃澘 --> + <h4 style="margin-top: 20px;">璇勫垎妯℃澘</h4> + <div class="rating-template" v-if="projectDetail.ratingForm"> + <div class="template-header"> + <span class="template-name">{{ projectDetail.ratingForm.schemeName }}</span> + <span class="template-total">鎬诲垎锛歿{ projectDetail.ratingForm.totalMaxScore }}鍒�</span> + </div> + + <div v-for="item in ratingItems" :key="item.id" class="template-item"> + <div class="item-header"> + <span class="item-name">{{ item.name }}</span> + <span class="item-score">{{ item.maxScore }}鍒�</span> + </div> + <el-input-number + v-model="item.score" + :min="0" + :max="item.maxScore" + :precision="1" + :step="0.5" + size="small" + style="width: 100%; margin-top: 8px;" + /> + </div> + + <!-- 璇勮 --> + <div class="comment-section"> + <h5>璇勮</h5> + <el-input + v-model="ratingComment" + type="textarea" + :rows="4" + placeholder="璇疯緭鍏ヨ瘎璇紙鍙�夛級" + maxlength="500" + show-word-limit + /> + </div> + + <!-- 鎻愪氦鎸夐挳 --> + <div class="submit-section"> + <el-button type="primary" @click="handleSubmitRating" :loading="submitting" style="width: 100%;"> + 鎻愪氦璇勫垎 + </el-button> + </div> + </div> + <div v-else class="no-template"> + <el-empty description="鏆傛棤璇勫垎妯℃澘" /> + </div> + </div> + </el-col> + </el-row> + </el-card> + + <!-- 鏂囦欢棰勮瀵硅瘽妗� --> + <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center> + <div class="preview-content"> + <iframe + v-if="previewUrl" + :src="previewUrl" + style="width: 100%; height: 500px; border: none;" + ></iframe> + <div v-else class="preview-error"> + <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨�" /> + </div> + </div> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, onMounted, computed } from 'vue' +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' + +const route = useRoute() +const router = useRouter() + +// 鍝嶅簲寮忔暟鎹� +const loading = ref(false) +const submitting = ref(false) +const projectDetail = ref(null) +const ratingStats = ref({ ratingCount: 0, averageScore: 0 }) +const ratingItems = ref([]) +const ratingComment = ref('') +const previewVisible = ref(false) +const previewUrl = ref('') + +// 璁$畻灞炴�� +const projectId = computed(() => route.params.id) + +// 鍔犺浇椤圭洰璇︽儏 +const loadProjectDetail = async () => { + loading.value = true + try { + const data = await getProjectDetail(projectId.value) + projectDetail.value = data + + // 鍒濆鍖栬瘎鍒嗛」 + if (data.ratingForm && data.ratingForm.items) { + ratingItems.value = data.ratingForm.items.map(item => ({ + ...item, + score: 0 + })) + } + } catch (error) { + ElMessage.error('鍔犺浇椤圭洰璇︽儏澶辫触') + console.error(error) + } finally { + loading.value = false + } +} + +// 鍔犺浇璇勫垎缁熻 +const loadRatingStats = async () => { + try { + const stats = await getRatingStats(projectId.value) + ratingStats.value = stats + } catch (error) { + console.error('鍔犺浇璇勫垎缁熻澶辫触:', error) + } +} + +// 鎻愪氦璇勫垎 +const handleSubmitRating = async () => { + // 楠岃瘉璇勫垎 + const hasEmptyScore = ratingItems.value.some(item => item.score === 0 || item.score === null) + if (hasEmptyScore) { + ElMessage.warning('璇蜂负鎵�鏈夎瘎鍒嗛」鎵撳垎') + return + } + + try { + await ElMessageBox.confirm('纭畾瑕佹彁浜よ瘎鍒嗗悧锛熸彁浜ゅ悗灏嗘棤娉曚慨鏀广��', '纭鎻愪氦', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + submitting.value = true + + const ratingData = { + activityPlayerId: projectId.value, + ratings: ratingItems.value.map(item => ({ + itemId: item.id, + score: item.score + })), + comment: ratingComment.value + } + + await submitRating(ratingData) + ElMessage.success('璇勫垎鎻愪氦鎴愬姛') + + // 閲嶆柊鍔犺浇璇勫垎缁熻 + await loadRatingStats() + + } catch (error) { + if (error !== 'cancel') { + ElMessage.error('璇勫垎鎻愪氦澶辫触') + console.error(error) + } + } finally { + submitting.value = false + } +} + +// 鏂囦欢棰勮 +const previewFile = (file) => { + // 鏍规嵁鏂囦欢绫诲瀷鍐冲畾棰勮鏂瑰紡 + const fileExtension = file.name.split('.').pop().toLowerCase() + const previewableTypes = ['pdf', 'txt', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'] + + if (previewableTypes.includes(fileExtension)) { + // 鍦ㄦ柊绐楀彛涓墦寮�棰勮 + window.open(file.url, '_blank') + } else { + ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅') + } +} + +// 鏂囦欢涓嬭浇 +const downloadFile = (file) => { + const link = document.createElement('a') + link.href = file.url + link.download = file.name + document.body.appendChild(link) + link.click() + document.body.removeChild(link) +} + +// 鏍煎紡鍖栨枃浠跺ぇ灏� +const formatFileSize = (bytes) => { + if (!bytes) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStateType = (state) => { + const stateMap = { + 0: 'danger', // 宸叉嫆缁� + 1: 'warning', // 寰呭鏍� + 2: 'success', // 宸查�氳繃 + 3: 'info' // 宸茬粨鏉� + } + return stateMap[state] || 'info' +} + +// 鑾峰彇鐘舵�佸悕绉� +const getStateName = (state) => { + const stateMap = { + 0: '宸叉嫆缁�', + 1: '寰呰瘎瀹�', + 2: '宸查�氳繃', + 3: '宸茬粨鏉�' + } + return stateMap[state] || '鏈煡' +} + +// 杩斿洖涓婁竴椤� +const goBack = () => { + router.back() +} + +// 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹� +onMounted(() => { + loadProjectDetail() + loadRatingStats() +}) +</script> + +<style scoped> +.detail-container { + padding: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-title { + margin: 0; + font-size: 18px; + font-weight: 500; +} + +.project-section h4, +.rating-section h4 { + margin: 0 0 16px 0; + font-size: 16px; + font-weight: 600; + color: #303133; + border-left: 4px solid #409eff; + padding-left: 12px; +} + +.description-content { + line-height: 1.6; + color: #606266; +} + +.attachments { + border: 1px solid #dcdfe6; + border-radius: 4px; + padding: 16px; +} + +.attachment-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f0f0f0; +} + +.attachment-item:last-child { + border-bottom: none; +} + +.file-info { + display: flex; + align-items: center; + flex: 1; +} + +.file-icon { + margin-right: 8px; + color: #409eff; +} + +.file-name { + font-weight: 500; + margin-right: 12px; +} + +.file-size { + color: #909399; + font-size: 12px; +} + +.file-actions { + display: flex; + gap: 8px; +} + +.no-attachments { + text-align: center; + padding: 40px 0; +} + +.rating-summary { + margin-bottom: 20px; +} + +.rating-item { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.rating-item:last-child { + margin-bottom: 0; +} + +.rating-item .label { + color: #606266; +} + +.rating-item .value { + font-weight: 600; +} + +.rating-item .score { + color: #67c23a; + font-size: 18px; +} + +.rating-template { + border: 1px solid #dcdfe6; + border-radius: 4px; + padding: 16px; +} + +.template-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid #f0f0f0; +} + +.template-name { + font-weight: 600; + color: #303133; +} + +.template-total { + color: #409eff; + font-weight: 600; +} + +.template-item { + margin-bottom: 16px; + padding: 12px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.template-item:last-child { + margin-bottom: 0; +} + +.item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.item-name { + font-weight: 500; + color: #303133; +} + +.item-score { + color: #909399; + font-size: 12px; +} + +.comment-section { + margin-top: 20px; +} + +.comment-section h5 { + margin: 0 0 12px 0; + font-size: 14px; + font-weight: 600; + color: #303133; +} + +.submit-section { + margin-top: 20px; +} + +.no-template { + text-align: center; + padding: 40px 0; +} + +.preview-content { + text-align: center; +} + +.preview-error { + padding: 40px 0; +} + +:deep(.el-descriptions__label) { + font-weight: 600; +} + +:deep(.el-card__body) { + padding: 16px; +} +</style> \ No newline at end of file diff --git a/web/src/views/project-review/index.vue b/web/src/views/project-review/index.vue new file mode 100644 index 0000000..4420604 --- /dev/null +++ b/web/src/views/project-review/index.vue @@ -0,0 +1,317 @@ +<template> + <div class="review-container"> + <el-card> + <template #header> + <div class="card-header"> + <h3 class="card-title">椤圭洰璇勫</h3> + </div> + </template> + + <el-form :inline="true" class="search-form"> + <el-form-item label="閫夋嫨姣旇禌"> + <el-select + v-model="selectedActivity" + placeholder="璇烽�夋嫨姣旇禌" + @change="handleActivityChange" + style="width: 300px" + clearable + filterable + > + <el-option + v-for="activity in activities" + :key="activity.id" + :label="getActivityDisplayName(activity)" + :value="activity.id" + > + <span>{{ getActivityName(activity) }}</span> + <span v-if="activity.pid > 0" style="color: #409eff; margin-left: 8px;"> + {{ activity.name }} + </span> + </el-option> + </el-select> + </el-form-item> + + <el-form-item label="椤圭洰鍚嶇О"> + <el-input + v-model="searchName" + placeholder="璇疯緭鍏ラ」鐩悕绉�" + @keyup.enter="loadProjects" + style="width: 200px" + /> + </el-form-item> + + <el-form-item> + <el-button type="primary" @click="loadProjects" :loading="projectsLoading"> + 鎼滅储 + </el-button> + <el-button @click="resetSearch">閲嶇疆</el-button> + </el-form-item> + </el-form> + + <el-table + :data="projects" + style="width: 100%" + v-loading="projectsLoading" + empty-text="璇峰厛閫夋嫨姣旇禌" + > + <el-table-column prop="playerName" label="椤圭洰鍚嶇О" min-width="150"> + <template #default="scope"> + {{ scope.row.projectName || scope.row.playerName }} + </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'"> + {{ scope.row.ratingCount }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="averageScore" label="骞冲潎鍒�" width="100" align="center"> + <template #default="scope"> + <span v-if="scope.row.averageScore > 0" class="score"> + {{ scope.row.averageScore.toFixed(1) }} + </span> + <span v-else class="no-score">鏈瘎鍒�</span> + </template> + </el-table-column> + <el-table-column prop="applyTime" label="鎶ュ悕鏃堕棿" width="180"> + <template #default="scope"> + {{ formatDate(scope.row.applyTime) }} + </template> + </el-table-column> + <el-table-column prop="state" label="鐘舵��" width="100" align="center"> + <template #default="scope"> + <el-tag :type="getStateType(scope.row.state)"> + {{ getStateName(scope.row.state) }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="120" fixed="right"> + <template #default="scope"> + <el-button type="primary" link @click="viewDetails(scope.row.id)"> + 璇︽儏璇勫 + </el-button> + </template> + </el-table-column> + </el-table> + + <!-- 鍒嗛〉 --> + <div class="pagination-container" v-if="total > 0"> + <el-pagination + v-model:current-page="currentPage" + v-model:page-size="pageSize" + :page-sizes="[10, 20, 50, 100]" + :total="total" + layout="total, sizes, prev, pager, next, jumper" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> + </el-card> + </div> +</template> + +<script setup> +import { ref, onMounted } from 'vue' +import { useRouter } from 'vue-router' +import { ElMessage } from 'element-plus' +import { getActiveActivities, getCompetitionProjects } from '@/api/projectReview' + +const router = useRouter() + +// 鍝嶅簲寮忔暟鎹� +const activities = ref([]) +const selectedActivity = ref(null) +const projects = ref([]) +const searchName = ref('') +const activitiesLoading = ref(false) +const projectsLoading = ref(false) + +// 鍒嗛〉鏁版嵁 +const currentPage = ref(1) +const pageSize = ref(10) +const total = ref(0) + +// 鍔犺浇姣旇禌鍒楄〃 +const loadActivities = async () => { + activitiesLoading.value = true + try { + const data = await getActiveActivities() + activities.value = data + } catch (error) { + ElMessage.error(error.message) + } finally { + activitiesLoading.value = false + } +} + +// 鍔犺浇椤圭洰鍒楄〃 +const loadProjects = async () => { + if (!selectedActivity.value) { + ElMessage.warning('璇峰厛閫夋嫨姣旇禌') + return + } + + projectsLoading.value = true + try { + const response = await getCompetitionProjects( + selectedActivity.value, + currentPage.value - 1, // 鍚庣浠�0寮�濮� + pageSize.value, + searchName.value + ) + + projects.value = response.content || [] + total.value = response.totalElements || 0 + } catch (error) { + console.error('鍔犺浇椤圭洰鍒楄〃澶辫触:', error) + ElMessage.error('鍔犺浇椤圭洰鍒楄〃澶辫触') + // 濡傛灉API璋冪敤澶辫触锛屾樉绀虹┖鏁版嵁 + projects.value = [] + total.value = 0 + } finally { + projectsLoading.value = false + } +} + +// 澶勭悊姣旇禌閫夋嫨鍙樺寲 +const handleActivityChange = (activityId) => { + currentPage.value = 1 + loadProjects() +} + +// 閲嶇疆鎼滅储 +const resetSearch = () => { + searchName.value = '' + currentPage.value = 1 + if (selectedActivity.value) { + loadProjects() + } +} + +// 鍒嗛〉澶勭悊 +const handleSizeChange = (size) => { + pageSize.value = size + currentPage.value = 1 + loadProjects() +} + +const handleCurrentChange = (page) => { + currentPage.value = page + loadProjects() +} + +// 鏌ョ湅璇︽儏 +const viewDetails = (projectId) => { + router.push(`/project-review/${projectId}/detail`) +} + +// 鏍煎紡鍖栨棩鏈� +const formatDate = (dateString) => { + if (!dateString) return '-' + return new Date(dateString).toLocaleString('zh-CN') +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStateType = (state) => { + const stateMap = { + 0: 'danger', // 宸叉嫆缁� + 1: 'warning', // 寰呭鏍� + 2: 'success', // 宸查�氳繃 + 3: 'info' // 宸茬粨鏉� + } + return stateMap[state] || 'info' +} + +// 鑾峰彇鐘舵�佸悕绉� +const getStateName = (state) => { + const stateMap = { + 0: '宸叉嫆缁�', + 1: '寰呰瘎瀹�', + 2: '宸查�氳繃', + 3: '宸茬粨鏉�' + } + return stateMap[state] || '鏈煡' +} + +// 鑾峰彇姣旇禌鍚嶇О锛堝鏋滄槸闃舵锛岃繑鍥炵埗姣旇禌鍚嶇О锛涘鏋滄槸姣旇禌锛岃繑鍥炶嚜宸辩殑鍚嶇О锛� +const getActivityName = (activity) => { + if (activity.pid > 0 && activity.parent) { + return activity.parent.name + } + return activity.name +} + +// 鑾峰彇娲诲姩鏄剧ず鍚嶇О锛堢敤浜庢悳绱㈠拰閫変腑鏃舵樉绀猴級 +const getActivityDisplayName = (activity) => { + if (activity.pid > 0 && activity.parent) { + return `${activity.parent.name} - ${activity.name}` + } + return activity.name +} + +// 鑾峰彇闃舵鍚嶇О锛堝鏋滄槸闃舵锛岃繑鍥為樁娈靛悕绉帮紱濡傛灉鏄瘮璧涳紝杩斿洖姣旇禌鍚嶇О锛� +const getStageName = (activity) => { + return activity.name +} + +// 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹� +onMounted(() => { + loadActivities() +}) +</script> + +<style scoped> +.review-container { + padding: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-title { + margin: 0; + font-size: 18px; + font-weight: 500; +} + +.search-form { + margin-bottom: 20px; + padding: 20px; + background-color: #f5f7fa; + border-radius: 4px; +} + +.score { + color: #67c23a; + font-weight: 600; +} + +.no-score { + color: #909399; + font-style: italic; +} + +.pagination-container { + margin-top: 20px; + display: flex; + justify-content: center; +} + +:deep(.el-table) { + border-radius: 4px; +} + +:deep(.el-table th) { + background-color: #fafafa; +} + +:deep(.el-tag) { + border-radius: 12px; +} +</style> \ No newline at end of file diff --git a/web/src/views/region/index.vue b/web/src/views/region/index.vue index f53f242..a47da85 100644 --- a/web/src/views/region/index.vue +++ b/web/src/views/region/index.vue @@ -34,7 +34,6 @@ import { RegionApi } from '@/api/region' - // 鏍戦厤缃紙鎳掑姞杞戒笉闇�瑕侀缃� children锛� const treeProps = { label: 'name', diff --git a/web/src/views/review/detail.vue b/web/src/views/review/detail.vue new file mode 100644 index 0000000..da57466 --- /dev/null +++ b/web/src/views/review/detail.vue @@ -0,0 +1,566 @@ +<template> + <div class="detail-container"> + <el-card v-loading="loading"> + <template #header> + <div class="card-header"> + <h3 class="card-title">椤圭洰璇勫璇︽儏</h3> + <el-button @click="goBack">杩斿洖</el-button> + </div> + </template> + + <el-row :gutter="20" v-if="projectDetail"> + <!-- 宸︿晶锛氶」鐩俊鎭� --> + <el-col :span="16"> + <div class="project-section"> + <!-- 椤圭洰鍩烘湰淇℃伅 --> + <h4>椤圭洰淇℃伅</h4> + <el-descriptions :column="2" border> + <el-descriptions-item label="椤圭洰鍚嶇О"> + {{ projectDetail.projectName || '鏈~鍐�' }} + </el-descriptions-item> + <el-descriptions-item label="姣旇禌鍚嶇О"> + {{ projectDetail.activityName }} + </el-descriptions-item> + <el-descriptions-item label="椤圭洰鎻忚堪" :span="2"> + <div class="description-content"> + {{ projectDetail.description || '鏆傛棤鎻忚堪' }} + </div> + </el-descriptions-item> + </el-descriptions> + + <!-- 椤圭洰闄勪欢 --> + <h4 style="margin-top: 20px;">椤圭洰闄勪欢</h4> + <div class="attachments" v-if="projectDetail.submissionFiles && projectDetail.submissionFiles.length > 0"> + <div v-for="file in projectDetail.submissionFiles" :key="file.id" class="attachment-item"> + <div class="file-info"> + <el-icon class="file-icon"> + <Document v-if="isDocument(file.mediaType)" /> + <Picture v-else-if="isImage(file.mediaType)" /> + <VideoPlay v-else-if="isVideo(file.mediaType)" /> + <Document v-else /> + </el-icon> + <div class="file-details"> + <div class="file-name">{{ file.name }}</div> + <div class="file-size">{{ formatFileSize(file.fileSize) }}</div> + </div> + </div> + <div class="file-actions"> + <el-button + type="primary" + link + @click="previewFile(file)" + v-if="canPreview(file.mediaType)" + > + 棰勮 + </el-button> + <el-button type="primary" link @click="downloadFile(file)"> + 涓嬭浇 + </el-button> + </div> + </div> + </div> + <div v-else class="no-attachments"> + <el-empty description="鏆傛棤闄勪欢" :image-size="80" /> + </div> + + <!-- 鍙傝禌浜轰俊鎭� --> + <h4 style="margin-top: 20px;">鍙傝禌浜轰俊鎭�</h4> + <el-descriptions :column="2" border v-if="projectDetail.playerInfo"> + <el-descriptions-item label="澶村儚"> + <el-avatar + :src="projectDetail.playerInfo.avatarUrl" + :size="60" + :icon="UserFilled" + /> + </el-descriptions-item> + <el-descriptions-item label="濮撳悕"> + {{ projectDetail.playerInfo.name }} + </el-descriptions-item> + <el-descriptions-item label="鎬у埆"> + {{ projectDetail.playerInfo.gender || '鏈~鍐�' }} + </el-descriptions-item> + <el-descriptions-item label="鍑虹敓鏃ユ湡"> + {{ formatDate(projectDetail.playerInfo.birthday) }} + </el-descriptions-item> + <el-descriptions-item label="鐢佃瘽"> + {{ projectDetail.playerInfo.phone }} + </el-descriptions-item> + <el-descriptions-item label="瀛﹀巻"> + {{ projectDetail.playerInfo.education || '鏈~鍐�' }} + </el-descriptions-item> + <el-descriptions-item label="鍖哄煙"> + {{ projectDetail.regionInfo?.name || '鏈~鍐�' }} + </el-descriptions-item> + <el-descriptions-item label="涓汉绠�浠�" :span="2"> + <div class="bio-content"> + {{ projectDetail.playerInfo.introduction || '鏆傛棤绠�浠�' }} + </div> + </el-descriptions-item> + </el-descriptions> + </div> + </el-col> + + <!-- 鍙充晶锛氳瘎鍒嗕俊鎭� --> + <el-col :span="8"> + <div class="rating-section"> + <!-- 璇勫缁熻 --> + <h4>璇勫淇℃伅</h4> + <el-card class="rating-summary"> + <div class="rating-item"> + <span class="label">宸茶瘎瀹℃鏁帮細</span> + <span class="value">{{ ratingStats.ratingCount }}</span> + </div> + <div class="rating-item"> + <span class="label">褰撳墠骞冲潎鍒嗭細</span> + <span class="value score"> + {{ ratingStats.averageScore > 0 ? ratingStats.averageScore.toFixed(1) : '鏈瘎鍒�' }} + </span> + </div> + </el-card> + + <!-- 璇勫垎妯℃澘 --> + <h4 style="margin-top: 20px;">璇勫垎妯℃澘</h4> + <div class="rating-template" v-if="projectDetail.ratingForm"> + <div class="template-header"> + <span class="template-name">{{ projectDetail.ratingForm.schemeName }}</span> + <span class="template-total">鎬诲垎锛歿{ projectDetail.ratingForm.totalMaxScore }}鍒�</span> + </div> + + <div v-for="item in ratingItems" :key="item.id" class="template-item"> + <div class="item-header"> + <span class="item-name">{{ item.name }}</span> + <span class="item-score">{{ item.maxScore }}鍒�</span> + </div> + <el-input-number + v-model="item.score" + :min="0" + :max="item.maxScore" + :precision="1" + :step="0.5" + style="width: 100%" + @change="calculateTotalScore" + /> + </div> + + <div class="total-score"> + <span class="label">鎬诲垎锛�</span> + <span class="value">{{ totalScore }} / {{ projectDetail.ratingForm.totalMaxScore }}</span> + </div> + </div> + + <!-- 璇勮 --> + <h4 style="margin-top: 20px;">璇勮</h4> + <el-input + v-model="comments" + type="textarea" + :rows="4" + placeholder="璇疯緭鍏ヨ瘎璇紙鍙�夛級" + maxlength="500" + show-word-limit + /> + + <!-- 鎻愪氦鎸夐挳 --> + <div class="submit-section"> + <el-button + type="primary" + @click="submitRating" + :loading="submitting" + :disabled="!canSubmit" + size="large" + style="width: 100%" + > + 鎻愪氦璇勫垎 + </el-button> + </div> + </div> + </el-col> + </el-row> + </el-card> + + <!-- 鏂囦欢棰勮瀵硅瘽妗� --> + <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center> + <div class="preview-content"> + <img + v-if="previewFile && isImage(previewFile.mediaType)" + :src="previewFile.fullUrl" + style="max-width: 100%; max-height: 500px;" + /> + <video + v-else-if="previewFile && isVideo(previewFile.mediaType)" + :src="previewFile.fullUrl" + controls + style="max-width: 100%; max-height: 500px;" + /> + <div v-else class="unsupported-preview"> + <el-icon size="48"><Document /></el-icon> + <p>璇ユ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鍚庢煡鐪�</p> + </div> + </div> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, computed, onMounted } from 'vue' +import { useRouter, useRoute } from 'vue-router' +import { ElMessage, ElMessageBox } from 'element-plus' +import { Document, Picture, VideoPlay, UserFilled } from '@element-plus/icons-vue' +import { getProjectDetail, getRatingStats, getCurrentJudgeRating, submitRating } from '@/api/projectReview' + +const router = useRouter() +const route = useRoute() + +// 鍝嶅簲寮忔暟鎹� +const loading = ref(true) +const projectDetail = ref(null) +const ratingStats = ref({ ratingCount: 0, averageScore: 0 }) +const ratingItems = ref([]) +const comments = ref('') +const submitting = ref(false) +const previewVisible = ref(false) +const previewFile = ref(null) + +// 璁$畻灞炴�� +const totalScore = computed(() => { + return ratingItems.value.reduce((sum, item) => sum + (item.score || 0), 0) +}) + +const canSubmit = computed(() => { + return ratingItems.value.length > 0 && ratingItems.value.every(item => item.score >= 0) +}) + +// 鍔犺浇椤圭洰璇︽儏 +const loadProjectDetail = async () => { + try { + const projectId = route.params.id + const [detail, stats] = await Promise.all([ + getProjectDetail(projectId), + getRatingStats(projectId) + ]) + + projectDetail.value = detail + ratingStats.value = stats + + // 鍒濆鍖栬瘎鍒嗛」 + if (detail.ratingForm && detail.ratingForm.items) { + ratingItems.value = detail.ratingForm.items.map(item => ({ + ...item, + score: 0 + })) + } + + // 鍔犺浇褰撳墠璇勫鐨勮瘎鍒嗭紙濡傛灉宸茶瘎鍒嗭級 + try { + const currentRating = await getCurrentJudgeRating(projectId) + if (currentRating) { + comments.value = currentRating.comments || '' + // 濉厖宸叉湁璇勫垎 + if (currentRating.ratingItems) { + currentRating.ratingItems.forEach(ratingItem => { + const item = ratingItems.value.find(i => i.id === ratingItem.itemId) + if (item) { + item.score = ratingItem.score + } + }) + } + } + } catch (error) { + // 濡傛灉娌℃湁璇勫垎璁板綍锛屽拷鐣ラ敊璇� + console.log('鏈壘鍒板綋鍓嶈瘎濮旂殑璇勫垎璁板綍') + } + + } catch (error) { + ElMessage.error(error.message) + } finally { + loading.value = false + } +} + +// 璁$畻鎬诲垎 +const calculateTotalScore = () => { + // 鎬诲垎璁$畻鍦╟omputed涓嚜鍔ㄥ畬鎴� +} + +// 鎻愪氦璇勫垎 +const submitRating = async () => { + try { + await ElMessageBox.confirm('纭鎻愪氦璇勫垎鍚楋紵鎻愪氦鍚庝笉鍙慨鏀广��', '纭鎻愪氦', { + confirmButtonText: '纭', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + submitting.value = true + + const ratingInput = { + activityPlayerId: route.params.id, + totalScore: totalScore.value, + comments: comments.value, + ratingItems: ratingItems.value.map(item => ({ + itemId: item.id, + score: item.score + })) + } + + await submitRating(ratingInput) + + ElMessage.success('璇勫垎鎻愪氦鎴愬姛锛�') + + // 閲嶆柊鍔犺浇鏁版嵁 + await loadProjectDetail() + + } catch (error) { + if (error !== 'cancel') { + ElMessage.error(error.message || '鎻愪氦璇勫垎澶辫触') + } + } finally { + submitting.value = false + } +} + +// 鏂囦欢鐩稿叧鏂规硶 +const isImage = (mediaType) => { + return mediaType && mediaType.startsWith('image/') +} + +const isVideo = (mediaType) => { + return mediaType && mediaType.startsWith('video/') +} + +const isDocument = (mediaType) => { + return mediaType && ( + mediaType.includes('pdf') || + mediaType.includes('doc') || + mediaType.includes('text') + ) +} + +const canPreview = (mediaType) => { + return isImage(mediaType) || isVideo(mediaType) +} + +const formatFileSize = (size) => { + if (!size) return '鏈煡澶у皬' + const units = ['B', 'KB', 'MB', 'GB'] + let index = 0 + while (size >= 1024 && index < units.length - 1) { + size /= 1024 + index++ + } + return `${size.toFixed(1)} ${units[index]}` +} + +const previewFile = (file) => { + previewFile.value = file + previewVisible.value = true +} + +const downloadFile = (file) => { + window.open(file.fullUrl, '_blank') +} + +const formatDate = (dateString) => { + if (!dateString) return '鏈~鍐�' + return new Date(dateString).toLocaleDateString('zh-CN') +} + +const goBack = () => { + router.back() +} + +// 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹� +onMounted(() => { + loadProjectDetail() +}) +</script> + +<style scoped> +.detail-container { + padding: 20px; +} +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} +.card-title { + margin: 0; + font-size: 18px; + font-weight: 500; +} + +.project-section { + padding-right: 20px; +} + +.rating-section { + padding-left: 20px; +} + +.description-content, +.bio-content { + line-height: 1.6; + color: #606266; +} + +.attachments { + border: 1px solid #e4e7ed; + border-radius: 4px; + padding: 16px; +} + +.attachment-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f0f0f0; +} + +.attachment-item:last-child { + border-bottom: none; +} + +.file-info { + display: flex; + align-items: center; + flex: 1; +} + +.file-icon { + margin-right: 12px; + font-size: 24px; + color: #409eff; +} + +.file-details { + flex: 1; +} + +.file-name { + font-weight: 500; + color: #303133; + margin-bottom: 4px; +} + +.file-size { + font-size: 12px; + color: #909399; +} + +.file-actions { + display: flex; + gap: 8px; +} + +.no-attachments { + text-align: center; + padding: 40px 0; +} + +.rating-summary { + margin-bottom: 20px; +} + +.rating-item { + display: flex; + justify-content: space-between; + margin-bottom: 12px; +} + +.rating-item:last-child { + margin-bottom: 0; +} + +.rating-item .label { + color: #606266; +} + +.rating-item .value { + font-weight: 500; +} + +.rating-item .score { + color: #409eff; + font-size: 18px; +} + +.rating-template { + border: 1px solid #e4e7ed; + border-radius: 4px; + padding: 16px; +} + +.template-header { + display: flex; + justify-content: space-between; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid #f0f0f0; +} + +.template-name { + font-weight: 500; + color: #303133; +} + +.template-total { + color: #409eff; + font-weight: 500; +} + +.template-item { + margin-bottom: 16px; +} + +.template-item:last-child { + margin-bottom: 0; +} + +.item-header { + display: flex; + justify-content: space-between; + margin-bottom: 8px; +} + +.item-name { + font-weight: 500; + color: #303133; +} + +.item-score { + color: #909399; + font-size: 14px; +} + +.total-score { + display: flex; + justify-content: space-between; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid #f0f0f0; + font-weight: 500; + font-size: 16px; +} + +.total-score .value { + color: #409eff; +} + +.submit-section { + margin-top: 20px; +} + +.preview-content { + text-align: center; +} + +.unsupported-preview { + padding: 40px; + color: #909399; +} + +.unsupported-preview p { + margin-top: 16px; +} +</style> \ No newline at end of file diff --git a/web/src/views/review/index.vue b/web/src/views/review/index.vue new file mode 100644 index 0000000..2967d8c --- /dev/null +++ b/web/src/views/review/index.vue @@ -0,0 +1,290 @@ +<template> + <div class="review-container"> + <el-card> + <template #header> + <div class="card-header"> + <h3 class="card-title">椤圭洰璇勫</h3> + </div> + </template> + + <el-form :inline="true" class="search-form"> + <el-form-item label="閫夋嫨姣旇禌"> + <el-select + v-model="selectedActivity" + placeholder="璇烽�夋嫨姣旇禌" + @change="handleActivityChange" + :loading="activitiesLoading" + style="width: 300px" + > + <el-option + v-for="item in activities" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + + <el-form-item label="椤圭洰鍚嶇О"> + <el-input + v-model="searchName" + placeholder="璇疯緭鍏ラ」鐩悕绉�" + @keyup.enter="loadProjects" + style="width: 200px" + /> + </el-form-item> + + <el-form-item> + <el-button type="primary" @click="loadProjects" :loading="projectsLoading"> + 鎼滅储 + </el-button> + <el-button @click="resetSearch">閲嶇疆</el-button> + </el-form-item> + </el-form> + + <el-table + :data="projects" + style="width: 100%" + v-loading="projectsLoading" + empty-text="璇峰厛閫夋嫨姣旇禌" + > + <el-table-column prop="playerName" label="椤圭洰鍚嶇О" min-width="150"> + <template #default="scope"> + {{ scope.row.projectName || scope.row.playerName }} + </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'"> + {{ scope.row.ratingCount }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="averageScore" label="骞冲潎鍒�" width="100" align="center"> + <template #default="scope"> + <span v-if="scope.row.averageScore > 0" class="score"> + {{ scope.row.averageScore.toFixed(1) }} + </span> + <span v-else class="no-score">鏈瘎鍒�</span> + </template> + </el-table-column> + <el-table-column prop="applyTime" label="鎶ュ悕鏃堕棿" width="180"> + <template #default="scope"> + {{ formatDate(scope.row.applyTime) }} + </template> + </el-table-column> + <el-table-column prop="state" label="鐘舵��" width="100" align="center"> + <template #default="scope"> + <el-tag :type="getStateType(scope.row.state)"> + {{ getStateName(scope.row.state) }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="120" fixed="right"> + <template #default="scope"> + <el-button type="primary" link @click="viewDetails(scope.row.id)"> + 璇︽儏璇勫 + </el-button> + </template> + </el-table-column> + </el-table> + + <!-- 鍒嗛〉 --> + <div class="pagination-container" v-if="total > 0"> + <el-pagination + v-model:current-page="currentPage" + v-model:page-size="pageSize" + :page-sizes="[10, 20, 50, 100]" + :total="total" + layout="total, sizes, prev, pager, next, jumper" + @size-change="handleSizeChange" + @current-change="handleCurrentChange" + /> + </div> + </el-card> + </div> +</template> + +<script setup> +import { ref, onMounted } from 'vue' +import { useRouter } from 'vue-router' +import { ElMessage } from 'element-plus' +import { getActiveActivities, getCompetitionProjects } from '@/api/projectReview' + +const router = useRouter() + +// 鍝嶅簲寮忔暟鎹� +const activities = ref([]) +const selectedActivity = ref(null) +const projects = ref([]) +const searchName = ref('') +const activitiesLoading = ref(false) +const projectsLoading = ref(false) + +// 鍒嗛〉鏁版嵁 +const currentPage = ref(1) +const pageSize = ref(10) +const total = ref(0) + +// 鍔犺浇姣旇禌鍒楄〃 +const loadActivities = async () => { + activitiesLoading.value = true + try { + const data = await getActiveActivities() + activities.value = data + } catch (error) { + ElMessage.error(error.message) + } finally { + activitiesLoading.value = false + } +} + +// 鍔犺浇椤圭洰鍒楄〃 +const loadProjects = async () => { + if (!selectedActivity.value) { + ElMessage.warning('璇峰厛閫夋嫨姣旇禌') + return + } + + projectsLoading.value = true + try { + const response = await getCompetitionProjects( + selectedActivity.value, + currentPage.value - 1, // 鍚庣浠�0寮�濮� + pageSize.value, + searchName.value + ) + + projects.value = response.content || [] + total.value = response.totalElements || 0 + } catch (error) { + console.error('鍔犺浇椤圭洰鍒楄〃澶辫触:', error) + ElMessage.error('鍔犺浇椤圭洰鍒楄〃澶辫触') + // 濡傛灉API璋冪敤澶辫触锛屾樉绀虹┖鏁版嵁 + projects.value = [] + total.value = 0 + } finally { + projectsLoading.value = false + } +} + +// 澶勭悊姣旇禌閫夋嫨鍙樺寲 +const handleActivityChange = (activityId) => { + currentPage.value = 1 + loadProjects() +} + +// 閲嶇疆鎼滅储 +const resetSearch = () => { + searchName.value = '' + currentPage.value = 1 + if (selectedActivity.value) { + loadProjects() + } +} + +// 鍒嗛〉澶勭悊 +const handleSizeChange = (size) => { + pageSize.value = size + currentPage.value = 1 + loadProjects() +} + +const handleCurrentChange = (page) => { + currentPage.value = page + loadProjects() +} + +// 鏌ョ湅璇︽儏 +const viewDetails = (projectId) => { + router.push(`/review/${projectId}/detail`) +} + +// 鏍煎紡鍖栨棩鏈� +const formatDate = (dateString) => { + if (!dateString) return '-' + return new Date(dateString).toLocaleString('zh-CN') +} + +// 鑾峰彇鐘舵�佺被鍨� +const getStateType = (state) => { + const stateMap = { + 0: 'danger', // 宸叉嫆缁� + 1: 'warning', // 寰呭鏍� + 2: 'success', // 宸查�氳繃 + 3: 'info' // 宸茬粨鏉� + } + return stateMap[state] || 'info' +} + +// 鑾峰彇鐘舵�佸悕绉� +const getStateName = (state) => { + const stateMap = { + 0: '宸叉嫆缁�', + 1: '寰呭鏍�', + 2: '宸查�氳繃', + 3: '宸茬粨鏉�' + } + return stateMap[state] || '鏈煡' +} + +// 缁勪欢鎸傝浇鏃跺姞杞芥暟鎹� +onMounted(() => { + loadActivities() +}) +</script> + +<style scoped> +.review-container { + padding: 20px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.card-title { + margin: 0; + font-size: 18px; + font-weight: 500; +} + +.search-form { + margin-bottom: 20px; + padding: 20px; + background-color: #f5f7fa; + border-radius: 4px; +} + +.score { + color: #67c23a; + font-weight: 600; +} + +.no-score { + color: #909399; + font-style: italic; +} + +.pagination-container { + margin-top: 20px; + display: flex; + justify-content: center; +} + +:deep(.el-table) { + border-radius: 4px; +} + +:deep(.el-table th) { + background-color: #fafafa; +} + +:deep(.el-tag) { + border-radius: 12px; +} +</style> \ No newline at end of file diff --git a/web/src/views/test/graphql-test.vue b/web/src/views/test/graphql-test.vue new file mode 100644 index 0000000..47e64f6 --- /dev/null +++ b/web/src/views/test/graphql-test.vue @@ -0,0 +1,192 @@ +<template> + <div class="graphql-test"> + <h2>GraphQL杩炴帴娴嬭瘯</h2> + + <div class="test-section"> + <h3>娴嬭瘯1: 鍩烘湰杩炴帴娴嬭瘯</h3> + <button @click="testBasicConnection" :disabled="loading"> + {{ loading ? '娴嬭瘯涓�...' : '娴嬭瘯鍩烘湰杩炴帴' }} + </button> + <div v-if="basicResult" class="result"> + <h4>缁撴灉:</h4> + <pre>{{ JSON.stringify(basicResult, null, 2) }}</pre> + </div> + <div v-if="basicError" class="error"> + <h4>閿欒:</h4> + <pre>{{ basicError }}</pre> + </div> + </div> + + <div class="test-section"> + <h3>娴嬭瘯2: 娲诲姩鍙傝禌鑰呰鎯呮煡璇�</h3> + <input v-model="testPlayerId" placeholder="杈撳叆鍙傝禌鑰匢D" /> + <button @click="testPlayerDetail" :disabled="loading || !testPlayerId"> + {{ loading ? '鏌ヨ涓�...' : '鏌ヨ鍙傝禌鑰呰鎯�' }} + </button> + <div v-if="playerResult" class="result"> + <h4>缁撴灉:</h4> + <pre>{{ JSON.stringify(playerResult, null, 2) }}</pre> + </div> + <div v-if="playerError" class="error"> + <h4>閿欒:</h4> + <pre>{{ playerError }}</pre> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { ref } from 'vue' +import { graphqlRequest } from '@/config/api' + +const loading = ref(false) +const basicResult = ref(null) +const basicError = ref('') +const playerResult = ref(null) +const playerError = ref('') +const testPlayerId = ref('1') + +// 浣跨敤缁熶竴鐨凣raphQL璇锋眰鍑芥暟 + +// 娴嬭瘯鍩烘湰杩炴帴 +const testBasicConnection = async () => { + loading.value = true + basicResult.value = null + basicError.value = '' + + try { + const query = ` + query { + __schema { + types { + name + } + } + } + ` + + const result = await graphqlRequest(query) + basicResult.value = { + message: 'GraphQL杩炴帴鎴愬姛锛�', + typeCount: result.__schema.types.length, + sampleTypes: result.__schema.types.slice(0, 5).map((t: any) => t.name) + } + } catch (error) { + basicError.value = error instanceof Error ? error.message : '鏈煡閿欒' + } finally { + loading.value = false + } +} + +// 娴嬭瘯鍙傝禌鑰呰鎯呮煡璇� +const testPlayerDetail = async () => { + loading.value = true + playerResult.value = null + playerError.value = '' + + try { + const query = ` + query ActivityPlayerDetail($id: ID!) { + activityPlayerDetail(id: $id) { + id + playerInfo { + id + name + phone + description + avatarUrl + } + regionInfo { + id + name + fullPath + } + activityName + description + submissionFiles { + id + name + url + fileExt + fileSize + mediaType + } + } + } + ` + + const result = await graphqlRequest(query, { id: testPlayerId.value }) + playerResult.value = result + } catch (error) { + playerError.value = error instanceof Error ? error.message : '鏈煡閿欒' + } finally { + loading.value = false + } +} +</script> + +<style scoped> +.graphql-test { + padding: 20px; + max-width: 800px; + margin: 0 auto; +} + +.test-section { + margin-bottom: 30px; + padding: 20px; + border: 1px solid #ddd; + border-radius: 8px; +} + +.test-section h3 { + margin-top: 0; + color: #333; +} + +button { + padding: 8px 16px; + margin: 10px 5px 10px 0; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:disabled { + background-color: #ccc; + cursor: not-allowed; +} + +input { + padding: 8px; + margin: 10px 5px 10px 0; + border: 1px solid #ddd; + border-radius: 4px; + width: 200px; +} + +.result { + margin-top: 15px; + padding: 10px; + background-color: #d4edda; + border: 1px solid #c3e6cb; + border-radius: 4px; +} + +.error { + margin-top: 15px; + padding: 10px; + background-color: #f8d7da; + border: 1px solid #f5c6cb; + border-radius: 4px; +} + +pre { + white-space: pre-wrap; + word-wrap: break-word; + font-size: 12px; + margin: 5px 0 0 0; +} +</style> \ No newline at end of file diff --git a/wx/lib/cosUtil-example.md b/wx/lib/cosUtil-example.md index 64508d6..d301b08 100644 --- a/wx/lib/cosUtil-example.md +++ b/wx/lib/cosUtil-example.md @@ -49,7 +49,7 @@ storageType: 'COS', bucketName: 'ryc-1379367838', region: 'ap-chengdu', - targetType: 6, // STUDENT_AVATAR + targetType: 7, // USER_AVATAR targetId: registrationId }) diff --git a/wx/lib/cosUtil.js b/wx/lib/cosUtil.js index 09220a5..1714af4 100644 --- a/wx/lib/cosUtil.js +++ b/wx/lib/cosUtil.js @@ -71,10 +71,16 @@ * @returns {string} 鍞竴鏂囦欢鍚� */ generateUniqueFileName(originalName) { - const timestamp = Date.now() - const random = Math.random().toString(36).substring(2, 8) + // 鐢熸垚绫讳技UUID鐨勫敮涓�鏍囪瘑绗� + const timestamp = Date.now().toString(36) + const random1 = Math.random().toString(36).substring(2, 10) + const random2 = Math.random().toString(36).substring(2, 10) + const random3 = Math.random().toString(36).substring(2, 6) const extension = originalName.substring(originalName.lastIndexOf('.')) - return `${timestamp}_${random}${extension}` + + // 鏍煎紡: timestamp-random1-random2-random3.ext + // 渚嬪: k8j2l3m4-a1b2c3d4-e5f6g7h8-i9j0.jpg + return `${timestamp}-${random1}-${random2}-${random3}${extension}` } /** @@ -92,43 +98,55 @@ return } - const cosConfig = app.globalData.cos - const uniqueFileName = this.generateUniqueFileName(originalName || 'file.jpg') - const key = this.generateFilePath(uniqueFileName, fileType) + // 棣栧厛鑾峰彇鏂囦欢淇℃伅锛堝寘鎷枃浠跺ぇ灏忥級 + wx.getFileInfo({ + filePath: filePath, + success: (fileInfo) => { + const cosConfig = app.globalData.cos + const uniqueFileName = this.generateUniqueFileName(originalName || 'file.jpg') + const key = this.generateFilePath(uniqueFileName, fileType) - console.log('寮�濮嬩笂浼犳枃浠跺埌COS:', { - filePath, - fileType, - key, - bucket: cosConfig.bucket - }) - - this.cos.uploadFile({ - Bucket: cosConfig.bucket, - Region: cosConfig.region, - Key: key, - FilePath: filePath, - onProgress: (progressData) => { - const percent = Math.round(progressData.percent * 100) - console.log('涓婁紶杩涘害:', percent + '%') - if (onProgress && typeof onProgress === 'function') { - onProgress(percent) - } - } - }, (err, data) => { - if (err) { - console.error('COS涓婁紶澶辫触:', err) - reject(err) - } else { - console.log('COS涓婁紶鎴愬姛:', data) - resolve({ - key: key, - url: `https://${data.Location}`, - etag: data.ETag, - fileName: uniqueFileName, - originalName: originalName, - fileType: fileType + console.log('寮�濮嬩笂浼犳枃浠跺埌COS:', { + filePath, + fileType, + key, + fileSize: fileInfo.size, + bucket: cosConfig.bucket }) + + this.cos.uploadFile({ + Bucket: cosConfig.bucket, + Region: cosConfig.region, + Key: key, + FilePath: filePath, + onProgress: (progressData) => { + const percent = Math.round(progressData.percent * 100) + console.log('涓婁紶杩涘害:', percent + '%') + if (onProgress && typeof onProgress === 'function') { + onProgress(percent) + } + } + }, (err, data) => { + if (err) { + console.error('COS涓婁紶澶辫触:', err) + reject(err) + } else { + console.log('COS涓婁紶鎴愬姛:', data) + resolve({ + key: key, + url: `https://${data.Location}`, + etag: data.ETag, + fileName: uniqueFileName, + originalName: originalName, + fileType: fileType, + fileSize: fileInfo.size + }) + } + }) + }, + fail: (error) => { + console.error('鑾峰彇鏂囦欢淇℃伅澶辫触:', error) + reject(new Error('鑾峰彇鏂囦欢淇℃伅澶辫触: ' + error.errMsg)) } }) }) diff --git a/wx/pages/registration/registration.js b/wx/pages/registration/registration.js index 6de900a..e797579 100644 --- a/wx/pages/registration/registration.js +++ b/wx/pages/registration/registration.js @@ -649,18 +649,21 @@ this.setData({ attachments: updatedAttachments }) try { - const uploadResult = await cosUtil.uploadFile(attachment.path, { - onProgress: (progress) => { + const uploadResult = await cosUtil.uploadFile( + attachment.path, + 'attachment', + attachment.name || 'attachment', + (percent) => { // 鏇存柊涓婁紶杩涘害 const progressAttachments = this.data.attachments.map(item => { if (item.id === attachment.id) { - return { ...item, progress: Math.round(progress.percent) } + return { ...item, progress: Math.round(percent) } } return item }) this.setData({ attachments: progressAttachments }) } - }) + ) // 涓婁紶鎴愬姛 const successAttachments = this.data.attachments.map(item => { @@ -749,6 +752,141 @@ wx.hideLoading() wx.showToast({ title: '鍒犻櫎澶辫触', + icon: 'error' + }) + } + }, + + // 棰勮闄勪欢 + async onPreviewAttachment(e) { + try { + const index = e.currentTarget.dataset.index + const attachment = this.data.attachments[index] + + if (!attachment.uploaded || !attachment.url) { + wx.showToast({ + title: '鏂囦欢鏈笂浼犲畬鎴�', + icon: 'none' + }) + return + } + + const fileType = attachment.type + + if (fileType === 'image') { + // 棰勮鍥剧墖 (media_type = 1) + wx.previewImage({ + current: attachment.url, + urls: [attachment.url] + }) + } else if (fileType === 'video') { + // 鎾斁瑙嗛 (media_type = 2) - 璺宠浆鍒拌棰戞挱鏀鹃〉闈� + wx.navigateTo({ + url: `/pages/video/video?url=${encodeURIComponent(attachment.url)}&title=${encodeURIComponent(attachment.name)}` + }) + } else if (fileType === 'pdf') { + // PDF鏂囦欢 (media_type = 4) - 浣跨敤灏忕▼搴忓唴缃殑鏂囨。棰勮 + wx.showLoading({ + title: '姝e湪鎵撳紑...', + mask: true + }) + + wx.downloadFile({ + url: attachment.url, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200) { + wx.openDocument({ + filePath: res.tempFilePath, + fileType: 'pdf', + success: () => { + console.log('PDF鎵撳紑鎴愬姛') + }, + fail: (err) => { + console.error('PDF鎵撳紑澶辫触:', err) + wx.showToast({ + title: 'PDF鎵撳紑澶辫触', + icon: 'none' + }) + } + }) + } else { + wx.showToast({ + title: '鏂囦欢涓嬭浇澶辫触', + icon: 'none' + }) + } + }, + fail: (err) => { + wx.hideLoading() + console.error('PDF涓嬭浇澶辫触:', err) + wx.showToast({ + title: '鏂囦欢涓嬭浇澶辫触', + icon: 'none' + }) + } + }) + } else if (fileType === 'word' || fileType === 'excel' || fileType === 'ppt') { + // Office鏂囨。 (media_type = 4) - 浣跨敤灏忕▼搴忓唴缃殑鏂囨。棰勮 + wx.showLoading({ + title: '姝e湪鎵撳紑...', + mask: true + }) + + wx.downloadFile({ + url: attachment.url, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200) { + const fileTypeMap = { + 'word': 'doc', + 'excel': 'xls', + 'ppt': 'ppt' + } + + wx.openDocument({ + filePath: res.tempFilePath, + fileType: fileTypeMap[fileType] || 'doc', + success: () => { + console.log('鏂囨。鎵撳紑鎴愬姛') + }, + fail: (err) => { + console.error('鏂囨。鎵撳紑澶辫触:', err) + wx.showToast({ + title: '鏂囨。鎵撳紑澶辫触', + icon: 'none' + }) + } + }) + } else { + wx.showToast({ + title: '鏂囦欢涓嬭浇澶辫触', + icon: 'none' + }) + } + }, + fail: (err) => { + wx.hideLoading() + console.error('鏂囨。涓嬭浇澶辫触:', err) + wx.showToast({ + title: '鏂囦欢涓嬭浇澶辫触', + icon: 'none' + }) + } + }) + } else { + // 鍏朵粬鏂囦欢绫诲瀷 (media_type = 5) - 鎻愮ず鐢ㄦ埛 + wx.showModal({ + title: '鏂囦欢棰勮', + content: `鏆備笉鏀寔棰勮${fileType}绫诲瀷鐨勬枃浠讹紝璇蜂笅杞藉悗鏌ョ湅`, + showCancel: false, + confirmText: '鐭ラ亾浜�' + }) + } + } catch (error) { + console.error('棰勮闄勪欢澶辫触:', error) + wx.showToast({ + title: '棰勮澶辫触', icon: 'error' }) } @@ -904,13 +1042,15 @@ }) // 涓婁紶鍒癈OS - const uploadResult = await cosUtil.uploadAvatar(this.data.localAvatarPath, { - onProgress: (progress) => { - this.setData({ - avatarUploadProgress: Math.round(progress.percent) - }) - } - }) + const uploadResult = await cosUtil.uploadAvatar( + this.data.localAvatarPath, + 'avatar.jpg', + (percent) => { + this.setData({ + avatarUploadProgress: Math.round(percent) + }) + } + ) // 涓婁紶鎴愬姛锛屾洿鏂拌〃鍗曟暟鎹� this.setData({ @@ -1092,18 +1232,23 @@ }) // 绗竴姝ワ細涓婁紶鍒癈OS - const uploadResult = await cosUtil.uploadAvatar(this.data.localAvatarPath, 'avatar.jpg', (progress) => { - this.setData({ - avatarUploadProgress: Math.round(progress.percent) - }) - }) + const uploadResult = await cosUtil.uploadAvatar( + this.data.localAvatarPath, + 'avatar.jpg', + (percent) => { + this.setData({ + avatarUploadProgress: Math.round(percent) + }) + } + ) // 绗簩姝ワ細淇濆瓨濯掍綋璁板綍鍒版暟鎹簱 await this.saveMediaRecord({ - targetType: 7, // USER_AVATAR + targetType: 'player', // V2 浣跨敤瀛楃涓诧細player 琛ㄧず鐢ㄦ埛澶村儚锛屽搴� DB 鐨� 7 (USER_AVATAR) targetId: idInfo.userId, - url: uploadResult.url, + path: uploadResult.key, fileName: uploadResult.fileName || 'avatar.jpg', + fileExt: this.getFileExtension(uploadResult.fileName || 'avatar.jpg'), fileSize: uploadResult.fileSize, mediaType: 1 // 鍥剧墖 }) @@ -1128,21 +1273,27 @@ async uploadAttachmentsWithRegistrationId(idInfo) { for (let i = 0; i < this.data.attachments.length; i++) { const attachment = this.data.attachments[i] - if (!attachment.uploaded && attachment.localPath) { + if (!attachment.uploaded && attachment.path) { try { // 绗竴姝ワ細涓婁紶鍒癈OS - const uploadResult = await cosUtil.uploadFile(attachment.localPath, 'attachment', attachment.name || 'attachment', (progress) => { - this.setData({ - [`attachments[${i}].uploadProgress`]: Math.round(progress.percent) - }) - }) + const uploadResult = await cosUtil.uploadFile( + attachment.path, + 'attachment', + attachment.name || 'attachment', + (percent) => { + this.setData({ + [`attachments[${i}].uploadProgress`]: Math.round(percent) + }) + } + ) // 绗簩姝ワ細淇濆瓨濯掍綋璁板綍鍒版暟鎹簱 await this.saveMediaRecord({ - targetType: 5, // ACTIVITY_PLAYER_SUBMISSION + targetType: 'activity_player', // V2 浣跨敤瀛楃涓诧細activity_player 琛ㄧず鎶ュ悕闄勪欢锛屽搴� DB 鐨� 5 targetId: idInfo.activityPlayerId, - url: uploadResult.url, + path: uploadResult.key, fileName: uploadResult.fileName || attachment.name, + fileExt: this.getFileExtension(uploadResult.fileName || attachment.name), fileSize: uploadResult.fileSize, mediaType: this.getMediaType(attachment.name) // 鏍规嵁鏂囦欢鎵╁睍鍚嶅垽鏂被鍨� }) @@ -1167,11 +1318,11 @@ // 淇濆瓨濯掍綋璁板綍鍒版暟鎹簱 async saveMediaRecord(mediaData) { const mutation = ` - mutation SaveMedia($input: MediaInput!) { - saveMedia(input: $input) { - id - url - fileName + mutation SaveMediaV2($input: MediaSaveInput!) { + saveMediaV2(input: $input) { + success + message + mediaId } } ` @@ -1180,8 +1331,9 @@ input: { targetType: mediaData.targetType, targetId: mediaData.targetId, - url: mediaData.url, - fileName: mediaData.fileName, + path: mediaData.path, + fileName: mediaData.fileName || mediaData.name, // V2 瀛楁涓� fileName锛屽吋瀹规棫瀛楁 name + fileExt: mediaData.fileExt, fileSize: mediaData.fileSize, mediaType: mediaData.mediaType } @@ -1189,14 +1341,20 @@ try { const result = await app.graphqlRequest(mutation, variables) - console.log('濯掍綋璁板綍淇濆瓨鎴愬姛:', result.saveMedia) - return result.saveMedia + console.log('濯掍綋璁板綍淇濆瓨鎴愬姛(V2):', result.saveMediaV2) + return result.saveMediaV2 } catch (error) { - console.error('濯掍綋璁板綍淇濆瓨澶辫触:', error) + console.error('濯掍綋璁板綍淇濆瓨澶辫触(V2):', error) throw error } }, + // 鑾峰彇鏂囦欢鎵╁睍鍚� + getFileExtension(fileName) { + if (!fileName) return '' + return fileName.split('.').pop().toLowerCase() + }, + // 鏍规嵁鏂囦欢鍚嶈幏鍙栧獟浣撶被鍨� getMediaType(fileName) { if (!fileName) return 1 // 榛樿鍥剧墖 diff --git a/wx/pages/registration/registration.wxml b/wx/pages/registration/registration.wxml index a0cdaba..5722221 100644 --- a/wx/pages/registration/registration.wxml +++ b/wx/pages/registration/registration.wxml @@ -280,6 +280,7 @@ </view> </view> <view class="attachment-actions"> + <text wx:if="{{!item.uploading && item.uploaded}}" class="action-btn preview" bindtap="onPreviewAttachment" data-index="{{index}}">棰勮</text> <text wx:if="{{!item.uploading}}" class="action-btn delete" bindtap="onDeleteAttachment" data-index="{{index}}">鍒犻櫎</text> </view> </view> diff --git a/wx/pages/registration/registration.wxss b/wx/pages/registration/registration.wxss index d9c4192..087ca4d 100644 --- a/wx/pages/registration/registration.wxss +++ b/wx/pages/registration/registration.wxss @@ -841,6 +841,12 @@ transition: all 0.3s ease; } +.attachment-actions .action-btn.preview { + color: #1976d2; + background: #f3f8ff; + margin-right: 8rpx; +} + .attachment-actions .action-btn.delete { color: #dc3545; background: #fff5f5; diff --git "a/\350\205\276\350\256\257\344\272\221COS\346\226\207\346\241\243\351\242\204\350\247\210\346\226\271\346\241\210\350\260\203\347\240\224.md" "b/\350\205\276\350\256\257\344\272\221COS\346\226\207\346\241\243\351\242\204\350\247\210\346\226\271\346\241\210\350\260\203\347\240\224.md" new file mode 100644 index 0000000..aab9857 --- /dev/null +++ "b/\350\205\276\350\256\257\344\272\221COS\346\226\207\346\241\243\351\242\204\350\247\210\346\226\271\346\241\210\350\260\203\347\240\224.md" @@ -0,0 +1,107 @@ +# 鑵捐浜慍OS鏂囨。棰勮鏂规璋冪爺 + +## 璋冪爺鑳屾櫙 +涓轰簡瀹炵幇灏忕▼搴忎腑闄勪欢锛圥DF銆乄ord銆丒xcel銆丳PT绛夛級鐨勫湪绾块瑙堝姛鑳斤紝闇�瑕佽皟鐮旇吘璁簯COS鐩稿叧鐨勬枃妗i瑙堣В鍐虫柟妗堛�� + +## 璋冪爺缁撴灉 + +### 1. 鑵捐浜慍OS鍘熺敓鏀寔 +鑵捐浜慍OS鏈韩涓昏鎻愪緵瀵硅薄瀛樺偍鏈嶅姟锛屼笉鐩存帴鎻愪緵鏂囨。棰勮鍔熻兘銆備絾鍙互缁撳悎鍏朵粬鏈嶅姟瀹炵幇鏂囨。棰勮銆� + +### 2. 灏忕▼搴忓師鐢熸敮鎸� +寰俊灏忕▼搴忔彁渚涗簡鍩虹鐨勬枃妗i瑙堣兘鍔涳細 +- **wx.openDocument**: 鏀寔鎵撳紑PDF銆乄ord銆丒xcel銆丳PT绛夋枃妗� +- **wx.previewImage**: 鏀寔鍥剧墖棰勮 +- **video缁勪欢**: 鏀寔瑙嗛鎾斁 + +### 3. 绗笁鏂规枃妗i瑙堟柟妗� + +#### 3.1 寰蒋Office Online棰勮鏈嶅姟 +- **鏈嶅姟鍦板潃**: `http://view.officeapps.live.com/op/view.aspx?src=鏂囨。URL` +- **鏀寔鏍煎紡**: Word(docx, dotx)銆丒xcel(xlsx, xlsb, xls, xlsm)銆丳owerPoint(pptx, ppsx, ppt, pps, potx, ppsm) +- **闄愬埗鏉′欢**: + - 鏂囨。蹇呴』澶栫綉鍙闂� + - Word鍜孭owerPoint鏂囨。灏忎簬10MB + - Excel鏂囨。灏忎簬5MB + - 鏂囨。URL蹇呴』鏄煙鍚嶄笖涓�80绔彛 +- **瀹夊叏椋庨櫓**: 鏂囨。鍙兘琚笂浼犲埌寰蒋鏈嶅姟鍣� + +#### 3.2 寮�婧愭枃妗i瑙堟湇鍔� (kkFileView) +- **椤圭洰鍦板潃**: https://gitee.com/kekingcn/file-online-preview +- **鏀寔鏍煎紡**: doc銆乨ocx銆乸pt銆乸ptx銆亁ls銆亁lsx銆乸df銆亃ip銆乺ar銆乵p4銆乵p3銆乼xt绛� +- **閮ㄧ讲鏂瑰紡**: Spring Boot搴旂敤锛岄渶瑕佽嚜琛岄儴缃� +- **渚濊禆鐜**: Redis(鍙��)銆丱penOffice鎴朙ibreOffice + +#### 3.3 Vue-Office缁勪欢搴� +- **閫傜敤鍦烘櫙**: 鍓嶇鐩存帴棰勮 +- **鏀寔鏍煎紡**: docx銆亁lsx銆乸df銆乸ptx +- **浣跨敤鏂瑰紡**: + - 缃戠粶鍦板潃棰勮 + - ArrayBuffer/Blob鏁版嵁棰勮 + - 鏂囦欢涓婁紶棰勮 + +### 4. 鎺ㄨ崘鏂规 + +#### 鏂规涓�锛氬皬绋嬪簭鍘熺敓 + 寰蒋Office Online锛堟帹鑽愶級 +```javascript +// 鍥剧墖棰勮 +wx.previewImage({ + current: imageUrl, + urls: [imageUrl] +}) + +// 瑙嗛鎾斁 +wx.navigateTo({ + url: `/pages/video/video?url=${encodeURIComponent(videoUrl)}` +}) + +// Office鏂囨。棰勮锛堜娇鐢ㄥ井杞湇鍔★級 +const officeUrl = `http://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(docUrl)}` +wx.navigateTo({ + url: `/pages/webview/webview?url=${encodeURIComponent(officeUrl)}` +}) + +// PDF鏂囨。棰勮 +wx.downloadFile({ + url: pdfUrl, + success: (res) => { + wx.openDocument({ + filePath: res.tempFilePath + }) + } +}) +``` + +#### 鏂规浜岋細鑷缓鏂囨。棰勮鏈嶅姟 +1. 閮ㄧ讲kkFileView鏈嶅姟 +2. 灏咰OS鏂囨。URL浼犻�掔粰棰勮鏈嶅姟 +3. 鍦ㄥ皬绋嬪簭涓�氳繃webview鎵撳紑棰勮椤甸潰 + +#### 鏂规涓夛細鍓嶇鐩存帴棰勮锛堥檺鍒惰緝澶氾級 +浣跨敤Vue-Office绛夊墠绔粍浠跺簱锛屼絾闇�瑕佽�冭檻灏忕▼搴忕幆澧冪殑鍏煎鎬с�� + +## 瀹炴柦寤鸿 + +### 鐭湡鏂规锛堢珛鍗冲彲鐢級 +1. **鍥剧墖**: 浣跨敤`wx.previewImage` +2. **瑙嗛**: 浣跨敤鑷畾涔夎棰戞挱鏀鹃〉闈� +3. **PDF**: 浣跨敤`wx.openDocument`涓嬭浇鍚庨瑙� +4. **Office鏂囨。**: 浣跨敤寰蒋Office Online鏈嶅姟锛堥渶瑕佹枃妗e缃戝彲璁块棶锛� + +### 闀挎湡鏂规锛堟洿濂界殑鐢ㄦ埛浣撻獙锛� +1. 閮ㄧ讲鑷缓鐨勬枃妗i瑙堟湇鍔★紙濡俴kFileView锛� +2. 纭繚鏂囨。鐨勫缃戣闂兘鍔� +3. 鍦ㄥ皬绋嬪簭涓泦鎴恮ebview缁勪欢鐢ㄤ簬鏂囨。棰勮 + +## 瀹夊叏鑰冭檻 +1. 浣跨敤寰蒋Office Online鏈嶅姟鏃讹紝鏂囨。鍙兘琚笂浼犲埌寰蒋鏈嶅姟鍣� +2. 鑷缓棰勮鏈嶅姟鍙互鏇村ソ鍦版帶鍒舵暟鎹畨鍏� +3. 鏁忔劅鏂囨。寤鸿浣跨敤鏈湴涓嬭浇棰勮鏂瑰紡 + +## 鎴愭湰鍒嗘瀽 +1. **寰蒋Office Online**: 鍏嶈垂锛屼絾鏈夊畨鍏ㄩ闄� +2. **鑷缓鏈嶅姟**: 闇�瑕佹湇鍔″櫒璧勬簮鍜岀淮鎶ゆ垚鏈� +3. **灏忕▼搴忓師鐢�**: 鍏嶈垂锛屼絾鍔熻兘鏈夐檺 + +## 缁撹 +寤鸿閲囩敤**鏂规涓�**浣滀负鍒濇湡瀹炵幇锛屽悗缁牴鎹笟鍔¢渶姹傚拰瀹夊叏瑕佹眰鑰冭檻閮ㄧ讲鑷缓鏂囨。棰勮鏈嶅姟銆� \ No newline at end of file diff --git "a/\351\241\265\351\235\242bug.png" "b/\351\241\265\351\235\242bug.png" new file mode 100644 index 0000000..5f76a66 --- /dev/null +++ "b/\351\241\265\351\235\242bug.png" Binary files differ -- Gitblit v1.8.0