From bd999ecc09fcacf4016edcba85caf9b9696d2140 Mon Sep 17 00:00:00 2001
From: lrj <owen.stl@gmail.com>
Date: 星期六, 04 十月 2025 18:40:31 +0800
Subject: [PATCH] feat: 同步本地改动(认证/评审/用户/选手模块更新;新增/调整 GraphQL schema;小程序个人信息与评审相关页面、配置与资源等)
---
backend/src/main/java/com/rongyichuang/user/dto/response/UserProfileInfo.java | 112 +
wx/pages/profile/personal-info.json | 6
backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java | 67
wx/pages/review/index.json | 5
backend/src/main/java/com/rongyichuang/auth/service/AuthService.java | 108
backend/src/main/java/com/rongyichuang/player/dto/response/PlayerInfoResponse.java | 10
wx/pages/profile/personal-info.wxss | 249 ++
wx/pages/project/detail.json | 6
backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java | 9
web/src/utils/appConfig.js | 18
wx/pages/review/index.wxss | 289 ++
wx/pages/registration/registration.js | 39
wx/pages/webview/webview.wxml | 8
backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java | 351 +++
wx/app.js | 96
backend/src/main/java/com/rongyichuang/user/dto/response/UserInfo.java | 112 +
wx/pages/project/detail.wxml | 187 +
backend/src/main/java/com/rongyichuang/auth/dto/WxLoginResponse.java | 65
backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectPageResponse.java | 69
wx/pages/profile/profile.wxss | 271 ++
wx/pages/profile/profile.js | 308 +
wx/app.json | 6
wx/pages/webview/webview.js | 62
web/src/views/login/index.vue | 53
backend/src/main/java/com/rongyichuang/user/service/UserService.java | 7
wx/pages/profile/personal-info.js | 389 +++
backend/src/main/java/com/rongyichuang/player/dto/response/SubmissionMediaResponse.java | 12
wx/pages/webview/webview.json | 6
wx/pages/judge/review.js | 269 +
wx/功能实现总结.md | 100
wx/评审流程实现总结.md | 111
backend/src/main/java/com/rongyichuang/user/dto/request/UserInput.java | 73
backend/src/main/java/com/rongyichuang/user/dto/response/UserProject.java | 98
backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java | 18
backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java | 200 +
backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java | 20
backend/src/main/java/com/rongyichuang/config/SecurityConfig.java | 3
wx/pages/profile/personal-info.wxml | 99
wx/images/default-avatar.svg | 10
wx/pages/review/index.js | 326 ++
wx/pages/review/index.wxml | 107
backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java | 48
backend/src/main/resources/graphql/player.graphqls | 12
backend/src/main/resources/graphql/schema.graphqls | 8
backend/src/main/resources/graphql/user.graphqls | 43
backend/src/main/resources/graphql/auth.graphqls | 22
backend/src/main/java/com/rongyichuang/judge/dto/response/JudgeStatsResponse.java | 43
backend/src/main/java/com/rongyichuang/review/service/ReviewService.java | 304 ++
wx/pages/webview/webview.wxss | 9
PasswordTest.java | 17
wx/pages/project/detail.wxss | 539 ++++
backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectResponse.java | 123 +
backend/src/test/java/com/rongyichuang/DatabaseConnectionTest.java | 60
web/src/api/rating.js | 12
web/src/api/media.js | 55
backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java | 97
wx/pages/profile/profile.wxml | 96
wx/pages/registration/registration.wxml | 2
/dev/null | 58
backend/src/main/java/com/rongyichuang/review/dto/response/ReviewStatisticsResponse.java | 43
backend/src/main/java/com/rongyichuang/judge/api/JudgeGraphqlApi.java | 7
web/src/api/judge.js | 69
backend/src/main/java/com/rongyichuang/auth/controller/AuthController.java | 90
backend/src/main/resources/graphql/review.graphqls | 45
wx/pages/project/detail.js | 458 ++++
backend/src/main/java/com/rongyichuang/player/dto/response/PlayerUserInfoResponse.java | 42
backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java | 16
67 files changed, 6,100 insertions(+), 572 deletions(-)
diff --git a/PasswordTest.java b/PasswordTest.java
new file mode 100644
index 0000000..87bd3d1
--- /dev/null
+++ b/PasswordTest.java
@@ -0,0 +1,17 @@
+锘縤mport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+public class PasswordTest {
+ public static void main(String[] args) {
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+ String hashedPassword = "$2a$10$VBHNbQlhM1OnQ8QTLkEVSeXBfLAlD9AJqNjErsYC664SUzMZZxjp.";
+ String plainPassword = "123456";
+
+ boolean matches = encoder.matches(plainPassword, hashedPassword);
+ System.out.println("Password matches: " + matches);
+
+ // 涔熸祴璇曚竴涓嬫柊鐢熸垚鐨勫瘑鐮�
+ String newHash = encoder.encode(plainPassword);
+ System.out.println("New hash: " + newHash);
+ System.out.println("New hash matches: " + encoder.matches(plainPassword, newHash));
+ }
+}
diff --git a/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java b/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java
deleted file mode 100644
index a49349c..0000000
--- a/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.rongyichuang.auth.api;
-
-import com.rongyichuang.auth.dto.LoginRequest;
-import com.rongyichuang.auth.dto.LoginResponse;
-import com.rongyichuang.auth.dto.PhoneDecryptResponse;
-import com.rongyichuang.auth.dto.WxLoginRequest;
-import com.rongyichuang.auth.dto.WxLoginResponse;
-import com.rongyichuang.auth.service.AuthService;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.graphql.data.method.annotation.Argument;
-import org.springframework.graphql.data.method.annotation.MutationMapping;
-import org.springframework.stereotype.Controller;
-
-/**
- * 璁よ瘉GraphQL API
- */
-@Controller
-public class AuthGraphqlApi {
-
- @Autowired
- private AuthService authService;
-
- /**
- * Web绔敤鎴风櫥褰�
- */
- @MutationMapping
- public LoginResponse webLogin(@Argument LoginRequest input) {
- return authService.login(input);
- }
-
- /**
- * 寰俊灏忕▼搴忕櫥褰�
- */
- @MutationMapping
- public WxLoginResponse wxLogin(@Argument WxLoginRequest input) throws JsonProcessingException, JsonMappingException {
- return authService.wxLogin(input);
- }
-
- /**
- * 瑙e瘑寰俊鎵嬫満鍙凤紙鏃х増API锛�
- */
- @MutationMapping
- public PhoneDecryptResponse decryptPhoneNumber(@Argument String encryptedData,
- @Argument String iv,
- @Argument String sessionKey) {
- return authService.decryptPhoneNumber(encryptedData, iv, sessionKey);
- }
-
- /**
- * 鑾峰彇寰俊鎵嬫満鍙凤紙鏂扮増API锛�
- */
- @MutationMapping
- public PhoneDecryptResponse getPhoneNumberByCode(@Argument String code) {
- return authService.getPhoneNumberByCode(code);
- }
-}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/auth/controller/AuthController.java b/backend/src/main/java/com/rongyichuang/auth/controller/AuthController.java
new file mode 100644
index 0000000..d3da50e
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/auth/controller/AuthController.java
@@ -0,0 +1,90 @@
+package com.rongyichuang.auth.controller;
+
+import com.rongyichuang.auth.dto.LoginRequest;
+import com.rongyichuang.auth.dto.LoginResponse;
+import com.rongyichuang.auth.dto.PhoneDecryptResponse;
+import com.rongyichuang.auth.dto.WxLoginRequest;
+import com.rongyichuang.auth.dto.WxLoginResponse;
+import com.rongyichuang.auth.service.AuthService;
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import jakarta.validation.Valid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * RESTful璁よ瘉鎺у埗鍣�
+ */
+@RestController
+@RequestMapping("/auth")
+@CrossOrigin(origins = "*")
+@Validated
+public class AuthController {
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
+
+ @Autowired
+ private AuthService authService;
+
+ /**
+ * Web绔敤鎴风櫥褰�
+ */
+ @PostMapping("/web-login")
+ public ResponseEntity<LoginResponse> webLogin(@Valid @RequestBody LoginRequest request) {
+ logger.info("鏀跺埌Web鐧诲綍璇锋眰锛屾墜鏈哄彿: {}", request.getPhone());
+ try {
+ LoginResponse response = authService.login(request);
+ logger.info("Web鐧诲綍鎴愬姛锛屾墜鏈哄彿: {}", request.getPhone());
+ return ResponseEntity.ok(response);
+ } catch (Exception e) {
+ logger.error("Web鐧诲綍澶辫触锛屾墜鏈哄彿: {}, 閿欒: {}", request.getPhone(), e.getMessage());
+ return ResponseEntity.badRequest().build();
+ }
+ }
+
+ /**
+ * 寰俊灏忕▼搴忕櫥褰�
+ */
+ @PostMapping("/wx-login")
+ public ResponseEntity<WxLoginResponse> wxLogin(@RequestBody WxLoginRequest request) {
+ try {
+ WxLoginResponse response = authService.wxLogin(request);
+ return ResponseEntity.ok(response);
+ } catch (JsonProcessingException e) {
+ return ResponseEntity.badRequest().build();
+ }
+ }
+
+ /**
+ * 瑙e瘑寰俊鎵嬫満鍙凤紙鏃х増API锛�
+ */
+ @PostMapping("/decrypt-phone")
+ public ResponseEntity<PhoneDecryptResponse> decryptPhoneNumber(
+ @RequestParam String encryptedData,
+ @RequestParam String iv,
+ @RequestParam String sessionKey) {
+ try {
+ PhoneDecryptResponse response = authService.decryptPhoneNumber(encryptedData, iv, sessionKey);
+ return ResponseEntity.ok(response);
+ } catch (Exception e) {
+ return ResponseEntity.badRequest().build();
+ }
+ }
+
+ /**
+ * 鑾峰彇寰俊鎵嬫満鍙凤紙鏂扮増API锛�
+ */
+ @PostMapping("/get-phone-by-code")
+ public ResponseEntity<PhoneDecryptResponse> getPhoneNumberByCode(@RequestParam String code) {
+ try {
+ PhoneDecryptResponse response = authService.getPhoneNumberByCode(code);
+ return ResponseEntity.ok(response);
+ } catch (Exception e) {
+ return ResponseEntity.badRequest().build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java b/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java
index 480bc58..44a32df 100644
--- a/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java
+++ b/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java
@@ -39,6 +39,7 @@
private String name;
private String phone;
private String userType; // 涓昏瑙掕壊绫诲瀷锛氫紭鍏坋mployee锛岀劧鍚巎udge锛屾渶鍚巔layer
+ private String avatarUrl; // 鐢ㄦ埛澶村儚URL
private EmployeeInfo employee;
private JudgeInfo judge;
private PlayerInfo player;
@@ -85,6 +86,14 @@
this.userType = userType;
}
+ public String getAvatarUrl() {
+ return avatarUrl;
+ }
+
+ public void setAvatarUrl(String avatarUrl) {
+ this.avatarUrl = avatarUrl;
+ }
+
public EmployeeInfo getEmployee() {
return employee;
}
diff --git a/backend/src/main/java/com/rongyichuang/auth/dto/WxLoginResponse.java b/backend/src/main/java/com/rongyichuang/auth/dto/WxLoginResponse.java
index 7d29897..505c2ed 100644
--- a/backend/src/main/java/com/rongyichuang/auth/dto/WxLoginResponse.java
+++ b/backend/src/main/java/com/rongyichuang/auth/dto/WxLoginResponse.java
@@ -29,6 +29,31 @@
* 寰俊浼氳瘽瀵嗛挜
*/
private String sessionKey;
+
+ /**
+ * 璇锋眰鏄惁鎴愬姛
+ */
+ private Boolean success;
+
+ /**
+ * 鍝嶅簲娑堟伅
+ */
+ private String message;
+
+ /**
+ * 鏄惁鏈夊憳宸ヨ鑹�
+ */
+ private Boolean hasEmployee;
+
+ /**
+ * 鏄惁鏈夎瘎濮旇鑹�
+ */
+ private Boolean hasJudge;
+
+ /**
+ * 鏄惁鏈夐�夋墜瑙掕壊
+ */
+ private Boolean hasPlayer;
public WxLoginResponse() {}
@@ -86,4 +111,44 @@
public void setSessionKey(String sessionKey) {
this.sessionKey = sessionKey;
}
+
+ 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 Boolean getHasEmployee() {
+ return hasEmployee;
+ }
+
+ public void setHasEmployee(Boolean hasEmployee) {
+ this.hasEmployee = hasEmployee;
+ }
+
+ public Boolean getHasJudge() {
+ return hasJudge;
+ }
+
+ public void setHasJudge(Boolean hasJudge) {
+ this.hasJudge = hasJudge;
+ }
+
+ public Boolean getHasPlayer() {
+ return hasPlayer;
+ }
+
+ public void setHasPlayer(Boolean hasPlayer) {
+ this.hasPlayer = hasPlayer;
+ }
}
\ 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
index 96e6413..273489e 100644
--- a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
+++ b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
@@ -10,7 +10,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
@@ -18,9 +20,12 @@
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
/**
@@ -37,6 +42,13 @@
@Autowired
private UserRepository userRepository;
+ // 鍏佽鍖垮悕璁块棶鐨凣raphQL鏌ヨ鍒楄〃
+ private static final List<String> PUBLIC_GRAPHQL_QUERIES = Arrays.asList(
+ "carouselPlayList",
+ "activities",
+ "hello"
+ );
+
/**
* 鍒ゆ柇鏄惁搴旇璺宠繃JWT璁よ瘉
*/
@@ -48,8 +60,8 @@
"/test/",
"/cleanup/",
"/upload/",
- "/graphql",
- "/graphiql"
+ "/graphiql" // GraphiQL寮�鍙戝伐鍏�
+ // 娉ㄦ剰锛�/graphql 涓嶅湪璺宠繃鍒楄〃涓紝闇�瑕佺敱JWT杩囨护鍣ㄥ鐞嗕互鍖哄垎鍏紑鍜岀鏈夋煡璇�
};
for (String path : skipPaths) {
@@ -59,6 +71,113 @@
}
return false;
+ }
+
+ /**
+ * 妫�鏌ユ槸鍚︽槸GraphQL璇锋眰
+ */
+ private boolean isGraphQLRequest(String requestURI) {
+ return "/graphql".equals(requestURI);
+ }
+
+ /**
+ * 妫�鏌raphQL璇锋眰鏄惁涓哄叕寮�鏌ヨ锛堜笉闇�瑕佽璇侊級
+ * 鍙湁鏄庣‘鏍囪涓哄叕寮�鐨勬煡璇㈡墠鍏佽鍖垮悕璁块棶
+ */
+ private boolean isPublicGraphQLQuery(HttpServletRequest request) {
+ try {
+ // 妫�鏌ET鍙傛暟涓殑query
+ String query = request.getParameter("query");
+ if (query != null && !query.trim().isEmpty()) {
+ logger.debug("浠庡弬鏁拌幏鍙朑raphQL鏌ヨ: {}", query);
+ return containsPublicQuery(query);
+ }
+
+ // 瀵逛簬POST璇锋眰锛屽皾璇曡鍙栬姹備綋
+ if ("POST".equalsIgnoreCase(request.getMethod())) {
+ try {
+ // 浣跨敤CachedBodyHttpServletRequest鏉ヨ鍙栬姹備綋
+ String body = getRequestBody(request);
+ if (body != null && !body.trim().isEmpty()) {
+ logger.debug("浠庤姹備綋鑾峰彇GraphQL鏌ヨ: {}", body);
+
+ // 妫�鏌ontent-Type
+ String contentType = request.getContentType();
+ if (contentType != null && contentType.contains("application/graphql")) {
+ // 瀵逛簬application/graphql锛岃姹備綋鐩存帴鏄疓raphQL鏌ヨ
+ return containsPublicQuery(body);
+ } else {
+ // 瀵逛簬application/json锛岀畝鍗曡В鏋怞SON锛屾煡鎵緌uery瀛楁
+ if (body.contains("\"query\"")) {
+ return containsPublicQuery(body);
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.warn("璇诲彇POST璇锋眰浣撳け璐�", e);
+ }
+ }
+
+ return false;
+ } catch (Exception e) {
+ logger.error("瑙f瀽GraphQL璇锋眰澶辫触", e);
+ return false;
+ }
+ }
+
+ /**
+ * 璇诲彇璇锋眰浣撳唴瀹�
+ */
+ private String getRequestBody(HttpServletRequest request) {
+ try {
+ if (request instanceof ContentCachingRequestWrapper) {
+ ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
+ byte[] content = wrapper.getContentAsByteArray();
+ if (content.length > 0) {
+ return new String(content, wrapper.getCharacterEncoding());
+ }
+ }
+
+ // 濡傛灉涓嶆槸鍖呰鍣紝灏濊瘯鐩存帴璇诲彇锛堝彲鑳戒細娑堣�楄姹備綋锛�
+ StringBuilder buffer = new StringBuilder();
+ String line;
+ java.io.BufferedReader reader = request.getReader();
+ while ((line = reader.readLine()) != null) {
+ buffer.append(line);
+ }
+ return buffer.toString();
+ } catch (Exception e) {
+ logger.warn("璇诲彇璇锋眰浣撳け璐�", e);
+ return null;
+ }
+ }
+
+ /**
+ * 妫�鏌ユ煡璇㈠瓧绗︿覆鏄惁鍖呭惈鍏紑鏌ヨ
+ */
+ private boolean containsPublicQuery(String queryString) {
+ if (queryString == null || queryString.trim().isEmpty()) {
+ return false;
+ }
+
+ // 妫�鏌ユ槸鍚﹀寘鍚叕寮�鏌ヨ
+ for (String publicQuery : PUBLIC_GRAPHQL_QUERIES) {
+ if (queryString.contains(publicQuery)) {
+ logger.debug("妫�娴嬪埌鍏紑GraphQL鏌ヨ: {}", publicQuery);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 杩斿洖鏉冮檺閿欒鍝嶅簲
+ */
+ private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException {
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.setContentType("application/json;charset=UTF-8");
+ response.getWriter().write("{\"errors\":[{\"message\":\"娌℃湁鏉冮檺璁块棶锛岃鍏堢櫥褰昞",\"extensions\":{\"code\":\"UNAUTHORIZED\"}}]}");
}
@Override
@@ -83,6 +202,83 @@
return;
}
+ // 瀵笹raphQL璇锋眰杩涜鐗规畩澶勭悊
+ if (isGraphQLRequest(pathWithoutContext)) {
+ logger.debug("妫�娴嬪埌GraphQL璇锋眰");
+
+ // 涓篜OST璇锋眰鍖呰璇锋眰浠ユ敮鎸侀噸澶嶈鍙栬姹備綋
+ HttpServletRequest wrappedRequest = request;
+ if ("POST".equalsIgnoreCase(request.getMethod())) {
+ wrappedRequest = new ContentCachingRequestWrapper(request);
+ }
+
+ // 鍏堟鏌uthorization澶达紝濡傛灉娌℃湁token锛屽啀妫�鏌ユ槸鍚︿负鍏紑鏌ヨ
+ String authHeader = request.getHeader("Authorization");
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ logger.debug("GraphQL璇锋眰娌℃湁Authorization澶达紝妫�鏌ユ槸鍚︿负鍏紑鏌ヨ");
+
+ // 妫�鏌ユ槸鍚︿负鍏紑鏌ヨ
+ if (isPublicGraphQLQuery(wrappedRequest)) {
+ logger.debug("妫�娴嬪埌鍏紑GraphQL鏌ヨ锛屽厑璁稿尶鍚嶈闂�");
+
+ // 璁剧疆鍖垮悕璁よ瘉锛岃Spring Security鐭ラ亾杩欐槸涓�涓凡璁よ瘉鐨勫尶鍚嶇敤鎴�
+ AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken(
+ "anonymous",
+ "anonymous",
+ Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
+ );
+ SecurityContextHolder.getContext().setAuthentication(anonymousAuth);
+ logger.debug("涓哄叕寮�GraphQL鏌ヨ璁剧疆鍖垮悕璁よ瘉");
+
+ filterChain.doFilter(wrappedRequest, response);
+ return;
+ }
+
+ logger.warn("GraphQL璇锋眰缂哄皯鏈夋晥鐨凙uthorization澶翠笖涓嶆槸鍏紑鏌ヨ");
+ sendUnauthorizedResponse(response);
+ return;
+ }
+
+ logger.debug("妫�娴嬪埌闇�瑕佽璇佺殑GraphQL璇锋眰锛屽紑濮嬮獙璇丣WT");
+
+ String token = authHeader.substring(7);
+ try {
+ Long userId = jwtUtil.getUserIdFromToken(token);
+ if (userId == null || !jwtUtil.validateToken(token)) {
+ logger.warn("GraphQL璇锋眰鐨凧WT token鏃犳晥");
+ sendUnauthorizedResponse(response);
+ return;
+ }
+
+ // 鏌ユ壘鐢ㄦ埛淇℃伅骞惰缃璇�
+ 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("GraphQL璇锋眰璁よ瘉鎴愬姛: userId={}", user.getId());
+ } else {
+ logger.warn("GraphQL璇锋眰鐨勭敤鎴蜂笉瀛樺湪: userId={}", userId);
+ sendUnauthorizedResponse(response);
+ return;
+ }
+ } catch (Exception e) {
+ logger.error("GraphQL璇锋眰JWT楠岃瘉澶辫触: {}", e.getMessage());
+ sendUnauthorizedResponse(response);
+ return;
+ }
+
+ // 缁х画澶勭悊璇锋眰
+ filterChain.doFilter(wrappedRequest, response);
+ return;
+ }
+
String authHeader = request.getHeader("Authorization");
String token = null;
Long userId = null;
diff --git a/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java
index bd2fa0f..e9deed6 100644
--- a/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java
+++ b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java
@@ -19,13 +19,18 @@
import com.rongyichuang.player.repository.PlayerRepository;
import com.rongyichuang.user.entity.User;
import com.rongyichuang.user.repository.UserRepository;
+import com.rongyichuang.common.entity.Media;
+import com.rongyichuang.common.repository.MediaRepository;
+import com.rongyichuang.common.enums.MediaTargetType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
+import java.util.List;
import java.util.Optional;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
@@ -64,6 +69,12 @@
@Autowired
private WechatApiService wechatApiService;
+
+ @Autowired
+ private MediaRepository mediaRepository;
+
+ @Value("${app.base-url}")
+ private String baseUrl;
/**
* 鐢ㄦ埛鐧诲綍
@@ -282,35 +293,46 @@
logger.warn("鈿狅笍 unionid涓虹┖鎴栫敤鎴峰凡鎵惧埌锛岃烦杩噓nionid鏌ユ壘");
}
- // 4. 濡傛灉閮芥病鎵惧埌锛屽垱寤烘柊鐢ㄦ埛
+ // 4. 濡傛灉閮芥病鎵惧埌鐢ㄦ埛锛屼笉鍒涘缓鏂扮敤鎴凤紝鍙褰曠櫥褰曚俊鎭�
if (user == null) {
- logger.info("鏈壘鍒扮幇鏈夌敤鎴凤紝寮�濮嬪垱寤烘柊鐢ㄦ埛");
- logger.info("鏂扮敤鎴蜂俊鎭�:");
+ logger.info("鏈壘鍒扮幇鏈夌敤鎴凤紝鏅�氳闂敤鎴蜂笉鍒涘缓user璁板綍锛屽彧璁板綍鐧诲綍淇℃伅");
+ logger.info("璁块棶鐢ㄦ埛淇℃伅:");
logger.info("- openid: {}", wxLoginRequest.getWxOpenid());
logger.info("- unionid: {}", wxLoginRequest.getWxUnionid());
logger.info("- phone: {}", wxLoginRequest.getPhone());
+ // 璁板綍鐧诲綍淇℃伅鍒皌_login_record锛屼絾涓嶅垱寤虹敤鎴�
+ logger.info("姝ラ3: 璁板綍璁块棶鐢ㄦ埛鐨勭櫥褰曚俊鎭�");
+ WxLoginRecord loginRecord = null;
try {
- user = new User();
- user.setWxOpenid(wxLoginRequest.getWxOpenid());
- user.setWxUnionid(wxLoginRequest.getWxUnionid());
- user.setName("寰俊鐢ㄦ埛"); // 榛樿鍚嶇О锛屽悗缁彲浠ユ洿鏂�
- user.setPhone(wxLoginRequest.getPhone()); // 濡傛灉鏈夋巿鏉冩墜鏈哄彿
-
- user = userRepository.save(user);
- isNewUser = true;
-
- logger.info("鉁� 鎴愬姛鍒涘缓鏂扮殑寰俊鐢ㄦ埛");
- logger.info("- 鏂扮敤鎴稩D: {}", user.getId());
- logger.info("- 鏂扮敤鎴峰鍚�: {}", user.getName());
- logger.info("- 鏂扮敤鎴锋墜鏈哄彿: {}", user.getPhone());
-
+ loginRecord = wxLoginRecordService.createLoginRecord(
+ wxLoginRequest.getWxOpenid(),
+ wxLoginRequest.getWxUnionid(),
+ null, // 娌℃湁鐢ㄦ埛ID
+ wxLoginRequest.getLoginIp(),
+ wxLoginRequest.getDeviceInfo(),
+ wxLoginRequest.getSessionKey(),
+ wxLoginRequest.getPhoneAuthorized()
+ );
+ logger.info("鉁� 鎴愬姛鍒涘缓璁块棶鐢ㄦ埛鐧诲綍璁板綍锛岃褰旾D: {}", loginRecord.getId());
} catch (Exception e) {
- logger.error("鉂� 鍒涘缓鏂扮敤鎴峰け璐�");
- logger.error("寮傚父淇℃伅: {}", e.getMessage());
+ logger.error("鉂� 鍒涘缓鐧诲綍璁板綍澶辫触: {}", e.getMessage());
logger.error("寮傚父鍫嗘爤:", e);
- throw new RuntimeException("鍒涘缓鏂扮敤鎴峰け璐�: " + e.getMessage(), e);
+ throw new RuntimeException("鍒涘缓鐧诲綍璁板綍澶辫触: " + e.getMessage(), e);
}
+
+ // 杩斿洖璁块棶鐢ㄦ埛鐨勫搷搴旓紙鏃犵敤鎴蜂俊鎭紝鏃爐oken锛�
+ WxLoginResponse response = new WxLoginResponse();
+ response.setSuccess(true);
+ response.setMessage("璁块棶鎴愬姛");
+ response.setSessionKey(wxLoginRequest.getSessionKey());
+ response.setIsNewUser(false);
+ response.setHasEmployee(false);
+ response.setHasJudge(false);
+ response.setHasPlayer(false);
+
+ logger.info("=== 寰俊璁块棶娴佺▼瀹屾垚锛堟棤鐢ㄦ埛鍒涘缓锛� ===");
+ return response;
}
// 5. 璁板綍鐧诲綍淇℃伅鍒版柊琛�
@@ -437,8 +459,29 @@
throw new RuntimeException("鏋勫缓鍩虹鐢ㄦ埛淇℃伅澶辫触: " + e.getMessage(), e);
}
- // 9. 璁剧疆鎵�鏈夊叧鑱旂殑瑙掕壊淇℃伅
- logger.info("姝ラ8: 璁剧疆瑙掕壊璇︾粏淇℃伅");
+ // 9. 鑾峰彇骞惰缃敤鎴峰ご鍍�
+ logger.info("姝ラ8: 鑾峰彇鐢ㄦ埛澶村儚");
+ try {
+ List<Media> avatarMediaList = mediaRepository.findByTargetTypeAndTargetIdAndState(
+ MediaTargetType.USER_AVATAR.getValue(),
+ user.getId(),
+ 1
+ );
+ if (!avatarMediaList.isEmpty()) {
+ Media avatarMedia = avatarMediaList.get(0);
+ String fullAvatarUrl = buildFullMediaUrl(avatarMedia.getPath());
+ userInfo.setAvatarUrl(fullAvatarUrl);
+ logger.info("鉁� 鎵惧埌鐢ㄦ埛澶村儚: {} -> {}", avatarMedia.getPath(), fullAvatarUrl);
+ } else {
+ logger.info("鐢ㄦ埛{}娌℃湁鎵惧埌澶村儚", user.getId());
+ }
+ } catch (Exception e) {
+ logger.error("鉂� 鑾峰彇鐢ㄦ埛澶村儚澶辫触: {}", e.getMessage());
+ logger.error("寮傚父鍫嗘爤:", e);
+ }
+
+ // 10. 璁剧疆鎵�鏈夊叧鑱旂殑瑙掕壊淇℃伅
+ logger.info("姝ラ9: 璁剧疆瑙掕壊璇︾粏淇℃伅");
if (employeeOpt.isPresent()) {
try {
@@ -520,6 +563,27 @@
}
/**
+ * 鏋勫缓瀹屾暣鐨勫獟浣揢RL
+ * @param path 濯掍綋璺緞
+ * @return 瀹屾暣鐨刄RL
+ */
+ private String buildFullMediaUrl(String path) {
+ if (path == null || path.trim().isEmpty()) {
+ return null;
+ }
+
+ // 濡傛灉璺緞宸茬粡鏄畬鏁碪RL锛岀洿鎺ヨ繑鍥�
+ if (path.startsWith("http://") || path.startsWith("https://")) {
+ return path;
+ }
+
+ // 鏋勫缓瀹屾暣URL
+ String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
+ String cleanPath = path.startsWith("/") ? path : "/" + path;
+ return cleanBaseUrl + cleanPath;
+ }
+
+ /**
* 瑙e瘑寰俊鎵嬫満鍙�
*/
public PhoneDecryptResponse decryptPhoneNumber(String encryptedData, String iv, String sessionKey) {
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 aeea37c..2337aab 100644
--- a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
+++ b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
@@ -40,6 +40,7 @@
* 浠嶫WT token涓В鏋愮敤鎴稩D
*
* @return 鐢ㄦ埛ID
+ * @throws SecurityException 褰撴病鏈夋湁鏁堣璇佹椂鎶涘嚭
*/
public Long getCurrentUserId() {
try {
@@ -62,17 +63,20 @@
if (authentication != null && authentication.isAuthenticated() &&
!"anonymousUser".equals(authentication.getPrincipal())) {
logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", authentication.getName());
- // 鍦ㄥ紑鍙戠幆澧冧笅锛岃繑鍥炵敤鎴稩D=2锛堝搴攋udge_id=68锛�
- logger.debug("寮�鍙戠幆澧冿細浣跨敤榛樿璇勫鐢ㄦ埛ID: 2");
- return 2L;
+ // 浠嶴pring Security涓婁笅鏂囦腑鑾峰彇鐢ㄦ埛ID
+ try {
+ return Long.parseLong(authentication.getName());
+ } catch (NumberFormatException e) {
+ logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", authentication.getName());
+ }
}
} catch (Exception e) {
logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage());
}
- // 鍦ㄦ祴璇曠幆澧冩垨寮�鍙戠幆澧冧腑锛屽鏋滄病鏈夎璇佷俊鎭紝杩斿洖鐢ㄦ埛ID=2锛堝搴攋udge_id=68锛�
- logger.debug("娴嬭瘯/寮�鍙戠幆澧冿細浣跨敤榛樿璇勫鐢ㄦ埛ID: 2");
- return 2L;
+ // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝鎶涘嚭鏉冮檺寮傚父
+ logger.warn("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝鎷掔粷璁块棶");
+ throw new SecurityException("娌℃湁鏉冮檺");
}
/**
diff --git a/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java b/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java
index d599086..8101d60 100644
--- a/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java
+++ b/backend/src/main/java/com/rongyichuang/config/SecurityConfig.java
@@ -50,7 +50,8 @@
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**", "/actuator/**", "/test/**", "/cleanup/**").permitAll()
.requestMatchers("/upload/**").permitAll()
- .requestMatchers("/graphql", "/graphql/**", "/graphiql").permitAll()
+ .requestMatchers("/graphiql/**", "/graphql/**", "/api/graphql/**", "/api/graphiql/**").permitAll() // 鍏佽GraphQL鍜孏raphiQL璁块棶
+ .requestMatchers("/**/graphql", "/**/graphiql").permitAll() // 鏇村娉涚殑GraphQL璺緞鍖归厤
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
diff --git a/backend/src/main/java/com/rongyichuang/judge/api/JudgeGraphqlApi.java b/backend/src/main/java/com/rongyichuang/judge/api/JudgeGraphqlApi.java
index b710cb8..3df6764 100644
--- a/backend/src/main/java/com/rongyichuang/judge/api/JudgeGraphqlApi.java
+++ b/backend/src/main/java/com/rongyichuang/judge/api/JudgeGraphqlApi.java
@@ -2,10 +2,12 @@
import com.rongyichuang.judge.dto.request.JudgeInput;
import com.rongyichuang.judge.dto.response.JudgeResponse;
+import com.rongyichuang.judge.dto.response.JudgeStatsResponse;
import com.rongyichuang.judge.service.JudgeService;
import com.rongyichuang.common.dto.request.MediaInput;
import com.rongyichuang.common.dto.response.MediaResponse;
import com.rongyichuang.common.service.MediaService;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
@@ -40,6 +42,11 @@
return judgeService.findById(id);
}
+ @QueryMapping
+ public JudgeStatsResponse judgeStats() {
+ return judgeService.getJudgeStats();
+ }
+
@MutationMapping
public JudgeResponse saveJudge(@Argument JudgeInput input) {
return judgeService.save(input);
diff --git a/backend/src/main/java/com/rongyichuang/judge/dto/response/JudgeStatsResponse.java b/backend/src/main/java/com/rongyichuang/judge/dto/response/JudgeStatsResponse.java
new file mode 100644
index 0000000..0285f39
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/judge/dto/response/JudgeStatsResponse.java
@@ -0,0 +1,43 @@
+package com.rongyichuang.judge.dto.response;
+
+/**
+ * 璇勫缁熻鍝嶅簲绫诲瀷
+ */
+public class JudgeStatsResponse {
+
+ private Integer pendingReviews;
+ private Integer completedReviews;
+ private Integer totalReviews;
+
+ public JudgeStatsResponse() {}
+
+ public JudgeStatsResponse(Integer pendingReviews, Integer completedReviews, Integer totalReviews) {
+ this.pendingReviews = pendingReviews;
+ this.completedReviews = completedReviews;
+ this.totalReviews = totalReviews;
+ }
+
+ public Integer getPendingReviews() {
+ return pendingReviews;
+ }
+
+ public void setPendingReviews(Integer pendingReviews) {
+ this.pendingReviews = pendingReviews;
+ }
+
+ public Integer getCompletedReviews() {
+ return completedReviews;
+ }
+
+ public void setCompletedReviews(Integer completedReviews) {
+ this.completedReviews = completedReviews;
+ }
+
+ public Integer getTotalReviews() {
+ return totalReviews;
+ }
+
+ public void setTotalReviews(Integer totalReviews) {
+ this.totalReviews = totalReviews;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java b/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java
index 463b3ff..c4d82ec 100644
--- a/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java
+++ b/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java
@@ -2,6 +2,7 @@
import com.rongyichuang.judge.dto.request.JudgeInput;
import com.rongyichuang.judge.dto.response.JudgeResponse;
+import com.rongyichuang.judge.dto.response.JudgeStatsResponse;
import com.rongyichuang.judge.entity.Judge;
import com.rongyichuang.tag.entity.Tag;
import com.rongyichuang.judge.repository.JudgeRepository;
@@ -12,12 +13,14 @@
import com.rongyichuang.common.service.MediaService;
import com.rongyichuang.common.exception.BusinessException;
import com.rongyichuang.common.enums.MediaTargetType;
+import com.rongyichuang.common.util.UserContextUtil;
import com.rongyichuang.tag.repository.TagRepository;
import com.rongyichuang.user.service.UserService;
import com.rongyichuang.user.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.domain.Sort;
+import org.springframework.jdbc.core.JdbcTemplate;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
@@ -39,18 +42,23 @@
private final MediaRepository mediaRepository;
private final MediaService mediaService;
private final UserService userService;
+ private final JdbcTemplate jdbcTemplate;
+ private final UserContextUtil userContextUtil;
@Value("${app.media-url}")
private String mediaBaseUrl;
public JudgeService(JudgeRepository judgeRepository, TagRepository tagRepository,
MediaRepository mediaRepository, MediaService mediaService,
- UserService userService) {
+ UserService userService, JdbcTemplate jdbcTemplate,
+ UserContextUtil userContextUtil) {
this.judgeRepository = judgeRepository;
this.tagRepository = tagRepository;
this.mediaRepository = mediaRepository;
this.mediaService = mediaService;
this.userService = userService;
+ this.jdbcTemplate = jdbcTemplate;
+ this.userContextUtil = userContextUtil;
}
public List<JudgeResponse> findAll() {
@@ -79,6 +87,63 @@
return judge.orElse(null);
}
+ /**
+ * 鑾峰彇褰撳墠璇勫鐨勭粺璁℃暟鎹�
+ */
+ public JudgeStatsResponse getJudgeStats() {
+ log.info("鑾峰彇璇勫缁熻鏁版嵁");
+
+ Long currentJudgeId = userContextUtil.getCurrentJudgeId();
+ if (currentJudgeId == null) {
+ throw new RuntimeException("褰撳墠鐢ㄦ埛涓嶆槸璇勫锛屾棤娉曟煡璇㈣瘎瀹$粺璁�");
+ }
+
+ // 鏌ヨ鎴戞湭璇勫鐨勬暟閲�
+ String unReviewedSql = "SELECT COUNT(*) FROM t_activity_player ap " +
+ "WHERE EXISTS (" +
+ " SELECT 1 FROM t_activity_judge aj " +
+ " WHERE ap.activity_id = aj.activity_id " +
+ " AND ap.stage_id = aj.stage_id " +
+ " AND aj.judge_id = ? " +
+ ") " +
+ "AND NOT EXISTS (" +
+ " SELECT 1 FROM t_activity_player_rating apr " +
+ " WHERE ap.id = apr.activity_player_id " +
+ " AND apr.judge_id = ? " +
+ " AND apr.state = 1 " +
+ ") " +
+ "AND ap.state = 1";
+ Integer unReviewedCount = jdbcTemplate.queryForObject(unReviewedSql, Integer.class, currentJudgeId, currentJudgeId);
+
+ // 鏌ヨ鎴戝凡璇勫鐨勬暟閲�
+ String reviewedSql = "SELECT COUNT(*) FROM t_activity_player ap " +
+ "WHERE EXISTS (" +
+ " SELECT 1 FROM t_activity_judge aj " +
+ " WHERE ap.activity_id = aj.activity_id " +
+ " AND ap.stage_id = aj.stage_id " +
+ " AND aj.judge_id = ? " +
+ ") " +
+ "AND EXISTS (" +
+ " SELECT 1 FROM t_activity_player_rating apr " +
+ " WHERE ap.id = apr.activity_player_id " +
+ " AND apr.judge_id = ? " +
+ " AND apr.state = 1 " +
+ ") " +
+ "AND ap.state = 1";
+ Integer reviewedCount = jdbcTemplate.queryForObject(reviewedSql, Integer.class, currentJudgeId, currentJudgeId);
+
+ // 璁$畻鎬绘暟
+ Integer totalCount = (unReviewedCount != null ? unReviewedCount : 0) + (reviewedCount != null ? reviewedCount : 0);
+
+ log.info("璇勫缁熻鏁版嵁 - 寰呰瘎瀹�: {}, 宸茶瘎瀹�: {}, 鎬绘暟: {}", unReviewedCount, reviewedCount, totalCount);
+
+ return new JudgeStatsResponse(
+ unReviewedCount != null ? unReviewedCount : 0,
+ reviewedCount != null ? reviewedCount : 0,
+ totalCount
+ );
+ }
+
@Transactional
public JudgeResponse save(JudgeInput input) {
Judge judge;
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 cb88e4a..e09ee6f 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
@@ -14,8 +14,7 @@
private String education; // 瀛﹀巻
private String introduction; // 涓汉浠嬬粛
private String description; // 绠�浠�
- private String avatarUrl; // 澶村儚URL锛堜粠 t_media 鑾峰彇锛屼娇鐢� MediaTargetType.USER_AVATAR锛屽�间负7锛�
- private MediaResponse avatar; // 澶村儚Media瀵硅薄
+ private PlayerUserInfoResponse userInfo; // 鍏宠仈鐨勭敤鎴蜂俊鎭紝鍖呭惈澶村儚
// Constructors
public PlayerInfoResponse() {}
@@ -45,9 +44,6 @@
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; }
+ public PlayerUserInfoResponse getUserInfo() { return userInfo; }
+ public void setUserInfo(PlayerUserInfoResponse userInfo) { this.userInfo = userInfo; }
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerUserInfoResponse.java b/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerUserInfoResponse.java
new file mode 100644
index 0000000..8239195
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/player/dto/response/PlayerUserInfoResponse.java
@@ -0,0 +1,42 @@
+package com.rongyichuang.player.dto.response;
+
+import com.rongyichuang.common.dto.response.MediaResponse;
+
+/**
+ * PlayerUserInfo鍝嶅簲DTO
+ * 瀵瑰簲GraphQL schema涓殑PlayerUserInfo绫诲瀷
+ */
+public class PlayerUserInfoResponse {
+ private Long userId;
+ private String name;
+ private String phone;
+ private String avatarUrl;
+ private MediaResponse avatar;
+
+ // Constructors
+ public PlayerUserInfoResponse() {}
+
+ public PlayerUserInfoResponse(Long userId, String name, String phone, String avatarUrl, MediaResponse avatar) {
+ this.userId = userId;
+ this.name = name;
+ this.phone = phone;
+ this.avatarUrl = avatarUrl;
+ this.avatar = avatar;
+ }
+
+ // Getters and Setters
+ public Long getUserId() { return userId; }
+ public void setUserId(Long userId) { this.userId = userId; }
+
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+
+ public String getPhone() { return phone; }
+ public void setPhone(String phone) { this.phone = phone; }
+
+ 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 d534642..1df0c53 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
@@ -6,7 +6,10 @@
public class SubmissionMediaResponse {
private Long id;
private String name; // 鏂囦欢鍚�
+ private String path; // 鏂囦欢璺緞
private String url; // 鏂囦欢URL
+ private String fullUrl; // 瀹屾暣鏂囦欢URL
+ private String fullThumbUrl; // 瀹屾暣缂╃暐鍥綰RL
private String fileExt; // 鏂囦欢鎵╁睍鍚�
private Long fileSize; // 鏂囦欢澶у皬
private Integer mediaType; // 濯掍綋绫诲瀷锛�1=鍥剧墖锛�2=瑙嗛锛�3=鏂囨。绛夛級
@@ -22,9 +25,18 @@
public String getName() { return name; }
public void setName(String name) { this.name = name; }
+ public String getPath() { return path; }
+ public void setPath(String path) { this.path = path; }
+
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
+ public String getFullUrl() { return fullUrl; }
+ public void setFullUrl(String fullUrl) { this.fullUrl = fullUrl; }
+
+ public String getFullThumbUrl() { return fullThumbUrl; }
+ public void setFullThumbUrl(String fullThumbUrl) { this.fullThumbUrl = fullThumbUrl; }
+
public String getFileExt() { return fileExt; }
public void setFileExt(String fileExt) { this.fileExt = fileExt; }
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 d45f0d1..1433984 100644
--- a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java
+++ b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerDetailService.java
@@ -178,7 +178,7 @@
log.info("鎵惧埌鍖哄煙淇℃伅: {}", regionInfo.getName());
}
- // 鏌ヨ鐢ㄦ埛澶村儚锛堜娇鐢║SER_AVATAR鍏宠仈鍒扮敤鎴凤級
+ // 鏋勫缓鐢ㄦ埛淇℃伅瀵硅薄锛屽寘鍚ご鍍�
Object userIdObj = row.get("user_id");
log.info("璋冭瘯锛氫粠鏌ヨ缁撴灉涓幏鍙栫殑user_id: {}", userIdObj);
if (userIdObj != null) {
@@ -190,22 +190,31 @@
}
log.info("璋冭瘯锛氳В鏋愬悗鐨剈serId: {}", userId);
+ // 鍒涘缓PlayerUserInfo瀵硅薄
+ PlayerUserInfoResponse userInfo = new PlayerUserInfoResponse();
+ userInfo.setUserId(userId);
+ userInfo.setName(row.get("user_name") != null ? row.get("user_name").toString() : "");
+ userInfo.setPhone(row.get("user_phone") != null ? row.get("user_phone").toString() : "");
+
+ // 鏌ヨ鐢ㄦ埛澶村儚
List<Media> avatarMedias = mediaRepository.findByTargetTypeAndTargetIdAndState(
MediaTargetType.USER_AVATAR.getValue(), userId, 1);
log.info("璋冭瘯锛氭煡璇㈠埌鐨勫ご鍍忓獟浣撴暟閲�: {}", avatarMedias.size());
if (!avatarMedias.isEmpty()) {
Media avatar = avatarMedias.get(0);
String avatarUrl = buildFullMediaUrl(avatar.getPath());
- playerInfo.setAvatarUrl(avatarUrl);
- // 璁剧疆avatar瀵硅薄
- playerInfo.setAvatar(convertToMediaResponse(avatar));
+ userInfo.setAvatarUrl(avatarUrl);
+ userInfo.setAvatar(convertToMediaResponse(avatar));
log.info("鎵惧埌鐢ㄦ埛澶村儚: {}", avatarUrl);
} else {
log.info("璋冭瘯锛氭湭鎵惧埌鐢ㄦ埛澶村儚锛寀serId: {}, targetType: {}", userId, MediaTargetType.USER_AVATAR.getValue());
}
+
+ playerInfo.setUserInfo(userInfo);
} else {
log.warn("璋冭瘯锛歶ser_id涓簄ull");
}
+
response.setPlayerInfo(playerInfo);
// 鏌ヨ鎻愪氦鐨勮祫鏂欙紙浣跨敤鏋氫妇甯搁噺琛ㄧず鍙傝禌鎶ュ悕璧勬枡绫诲瀷锛�
@@ -235,7 +244,10 @@
SubmissionMediaResponse response = new SubmissionMediaResponse();
response.setId(media.getId());
response.setName(media.getName());
+ response.setPath(media.getPath());
response.setUrl(buildFullMediaUrl(media.getPath()));
+ response.setFullUrl(buildFullMediaUrl(media.getPath()));
+ response.setFullThumbUrl(buildFullMediaUrl(media.getThumbPath()));
response.setFileExt(media.getFileExt());
response.setFileSize(media.getFileSize() != null ? media.getFileSize().longValue() : null);
response.setMediaType(media.getMediaType());
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 c623481..b40491c 100644
--- a/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java
+++ b/backend/src/main/java/com/rongyichuang/player/service/ActivityPlayerService.java
@@ -231,25 +231,47 @@
}
/**
- * 鍒涘缓鎴栨洿鏂扮敤鎴疯褰�
+ * 鍒涘缓鎴栨洿鏂扮敤鎴疯褰曪紙浠呭湪灏忕▼搴忔姤鍚嶆垚鍔熸椂鍒涘缓鏂扮敤鎴凤級
*/
private User createOrUpdateUser(ActivityRegistrationInput input) {
try {
- // 浣跨敤UserService鐨刦indOrCreateUserByPhone鏂规硶
- User user = userService.findOrCreateUserByPhone(
- input.getPlayerInfo().getPhone(),
- input.getPlayerInfo().getName(),
- null // 涓嶈缃瘑鐮侊紝浣跨敤榛樿瀵嗙爜
- );
+ String phone = input.getPlayerInfo().getPhone();
+ String name = input.getPlayerInfo().getName();
- // 鏇存柊鐢ㄦ埛鐨勭敓鏃ヤ俊鎭�
- if (input.getPlayerInfo().getBirthDate() != null) {
- user.setBirthday(input.getPlayerInfo().getBirthDate());
+ // 鍏堟煡鎵剧幇鏈夌敤鎴�
+ Optional<User> existingUserOpt = userService.findByPhone(phone);
+
+ if (existingUserOpt.isPresent()) {
+ // 鐢ㄦ埛瀛樺湪锛屾洿鏂颁俊鎭�
+ User user = existingUserOpt.get();
+ user.setName(name);
+
+ // 鏇存柊鐢ㄦ埛鐨勭敓鏃ヤ俊鎭�
+ if (input.getPlayerInfo().getBirthDate() != null) {
+ user.setBirthday(input.getPlayerInfo().getBirthDate());
+ }
+
user = userService.save(user);
- log.info("鏇存柊鐢ㄦ埛鐢熸棩淇℃伅鎴愬姛锛岀敤鎴稩D: {}", user.getId());
+ log.info("鏇存柊鐜版湁鐢ㄦ埛淇℃伅鎴愬姛锛岀敤鎴稩D: {}", user.getId());
+ return user;
+ } else {
+ // 鐢ㄦ埛涓嶅瓨鍦紝鍒涘缓鏂扮敤鎴凤紙浠呭湪灏忕▼搴忔姤鍚嶆垚鍔熸椂锛�
+ log.info("鐢ㄦ埛涓嶅瓨鍦紝涓哄皬绋嬪簭鎶ュ悕鎴愬姛鍒涘缓鏂扮敤鎴凤紝鎵嬫満鍙�: {}", phone);
+
+ User newUser = new User();
+ newUser.setName(name);
+ newUser.setPhone(phone);
+ newUser.setPassword(userService.getPasswordEncoder().encode("123456")); // 榛樿瀵嗙爜
+
+ // 璁剧疆鐢熸棩淇℃伅
+ if (input.getPlayerInfo().getBirthDate() != null) {
+ newUser.setBirthday(input.getPlayerInfo().getBirthDate());
+ }
+
+ newUser = userService.save(newUser);
+ log.info("涓哄皬绋嬪簭鎶ュ悕鎴愬姛鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", newUser.getId());
+ return newUser;
}
-
- return user;
} catch (Exception e) {
log.error("鍒涘缓鎴栨洿鏂扮敤鎴疯褰曟椂鍙戠敓閿欒", e);
throw new RuntimeException("鍒涘缓鎴栨洿鏂扮敤鎴疯褰曞け璐�", e);
diff --git a/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectPageResponse.java b/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectPageResponse.java
new file mode 100644
index 0000000..9a8a794
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectPageResponse.java
@@ -0,0 +1,69 @@
+package com.rongyichuang.review.dto.response;
+
+import java.util.List;
+
+/**
+ * 璇勫椤圭洰鍒嗛〉鍝嶅簲
+ */
+public class ReviewProjectPageResponse {
+
+ private List<ReviewProjectResponse> items;
+ private int total;
+ private int page;
+ private int size;
+ private int totalPages;
+
+ public ReviewProjectPageResponse() {}
+
+ public ReviewProjectPageResponse(List<ReviewProjectResponse> items, int total, int page, int size) {
+ this.items = items;
+ this.total = total;
+ this.page = page;
+ this.size = size;
+ this.totalPages = (int) Math.ceil((double) total / size);
+ }
+
+ public List<ReviewProjectResponse> getItems() {
+ return items;
+ }
+
+ public void setItems(List<ReviewProjectResponse> items) {
+ this.items = items;
+ }
+
+ public int getTotal() {
+ return total;
+ }
+
+ public void setTotal(int total) {
+ this.total = total;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ public void setPage(int page) {
+ this.page = page;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public int getTotalPages() {
+ return totalPages;
+ }
+
+ public void setTotalPages(int totalPages) {
+ this.totalPages = totalPages;
+ }
+
+ public boolean getHasMore() {
+ return page < totalPages;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectResponse.java b/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectResponse.java
new file mode 100644
index 0000000..22f94a9
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewProjectResponse.java
@@ -0,0 +1,123 @@
+package com.rongyichuang.review.dto.response;
+
+/**
+ * 璇勫椤圭洰鍝嶅簲
+ */
+public class ReviewProjectResponse {
+
+ private Long activityPlayerId;
+ private Long activityId;
+ private Long stageId;
+ private String projectName;
+ private String activityName;
+ private String stageName;
+ private String studentName;
+ private String submitTime;
+ private String reviewTime;
+ private Float score;
+ private String status;
+
+ public ReviewProjectResponse() {}
+
+ public ReviewProjectResponse(Long activityPlayerId, String projectName, String activityName,
+ String stageName, String studentName, String submitTime) {
+ this.activityPlayerId = activityPlayerId;
+ this.projectName = projectName;
+ this.activityName = activityName;
+ this.stageName = stageName;
+ this.studentName = studentName;
+ this.submitTime = submitTime;
+ }
+
+ public Long getId() {
+ return activityPlayerId;
+ }
+
+ public Long getActivityPlayerId() {
+ return activityPlayerId;
+ }
+
+ public void setActivityPlayerId(Long activityPlayerId) {
+ this.activityPlayerId = activityPlayerId;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public void setProjectName(String projectName) {
+ this.projectName = projectName;
+ }
+
+ public String getActivityName() {
+ return activityName;
+ }
+
+ public void setActivityName(String activityName) {
+ this.activityName = activityName;
+ }
+
+ public String getStageName() {
+ return stageName;
+ }
+
+ public void setStageName(String stageName) {
+ this.stageName = stageName;
+ }
+
+ public String getStudentName() {
+ return studentName;
+ }
+
+ public void setStudentName(String studentName) {
+ this.studentName = studentName;
+ }
+
+ public String getSubmitTime() {
+ return submitTime;
+ }
+
+ public void setSubmitTime(String submitTime) {
+ this.submitTime = submitTime;
+ }
+
+ public Long getActivityId() {
+ return activityId;
+ }
+
+ public void setActivityId(Long activityId) {
+ this.activityId = activityId;
+ }
+
+ public Long getStageId() {
+ return stageId;
+ }
+
+ public void setStageId(Long stageId) {
+ this.stageId = stageId;
+ }
+
+ public String getReviewTime() {
+ return reviewTime;
+ }
+
+ public void setReviewTime(String reviewTime) {
+ this.reviewTime = reviewTime;
+ }
+
+ public Float getScore() {
+ return score;
+ }
+
+ public void setScore(Float score) {
+ this.score = score;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewStatisticsResponse.java b/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewStatisticsResponse.java
new file mode 100644
index 0000000..9f98f68
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/review/dto/response/ReviewStatisticsResponse.java
@@ -0,0 +1,43 @@
+package com.rongyichuang.review.dto.response;
+
+/**
+ * 璇勫缁熻鍝嶅簲
+ */
+public class ReviewStatisticsResponse {
+
+ private int unReviewedCount;
+ private int reviewedCount;
+ private int studentUnReviewedCount;
+
+ public ReviewStatisticsResponse() {}
+
+ public ReviewStatisticsResponse(int unReviewedCount, int reviewedCount, int studentUnReviewedCount) {
+ this.unReviewedCount = unReviewedCount;
+ this.reviewedCount = reviewedCount;
+ this.studentUnReviewedCount = studentUnReviewedCount;
+ }
+
+ public int getUnReviewedCount() {
+ return unReviewedCount;
+ }
+
+ public void setUnReviewedCount(int unReviewedCount) {
+ this.unReviewedCount = unReviewedCount;
+ }
+
+ public int getReviewedCount() {
+ return reviewedCount;
+ }
+
+ public void setReviewedCount(int reviewedCount) {
+ this.reviewedCount = reviewedCount;
+ }
+
+ public int getStudentUnReviewedCount() {
+ return studentUnReviewedCount;
+ }
+
+ public void setStudentUnReviewedCount(int studentUnReviewedCount) {
+ this.studentUnReviewedCount = studentUnReviewedCount;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java b/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java
new file mode 100644
index 0000000..e8c8503
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java
@@ -0,0 +1,97 @@
+package com.rongyichuang.review.resolver;
+
+import com.rongyichuang.common.util.UserContextUtil;
+import com.rongyichuang.review.dto.response.ReviewProjectPageResponse;
+import com.rongyichuang.review.dto.response.ReviewProjectResponse;
+import com.rongyichuang.review.dto.response.ReviewStatisticsResponse;
+import com.rongyichuang.review.service.ReviewService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.stereotype.Controller;
+
+/**
+ * 璇勫绠$悊GraphQL瑙f瀽鍣�
+ */
+@Controller
+public class ReviewResolver {
+
+ private static final Logger log = LoggerFactory.getLogger(ReviewResolver.class);
+
+ @Autowired
+ private ReviewService reviewService;
+
+ @Autowired
+ private UserContextUtil userContextUtil;
+
+ /**
+ * 鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛�
+ */
+ @QueryMapping
+ public ReviewProjectPageResponse unReviewedProjects(
+ @Argument String searchKeyword,
+ @Argument int page,
+ @Argument int pageSize) {
+ log.info("鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛紝searchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize);
+
+ Long currentJudgeId = userContextUtil.getCurrentJudgeId();
+ if (currentJudgeId == null) {
+ throw new RuntimeException("褰撳墠鐢ㄦ埛涓嶆槸璇勫锛屾棤娉曟煡璇㈣瘎瀹¢」鐩�");
+ }
+
+ return reviewService.getUnReviewedProjects(currentJudgeId, searchKeyword, page, pageSize);
+ }
+
+ /**
+ * 鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛�
+ */
+ @QueryMapping
+ public ReviewProjectPageResponse reviewedProjects(
+ @Argument String searchKeyword,
+ @Argument int page,
+ @Argument int pageSize) {
+ log.info("鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛紝searchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize);
+
+ Long currentJudgeId = userContextUtil.getCurrentJudgeId();
+ if (currentJudgeId == null) {
+ throw new RuntimeException("褰撳墠鐢ㄦ埛涓嶆槸璇勫锛屾棤娉曟煡璇㈣瘎瀹¢」鐩�");
+ }
+
+ return reviewService.getReviewedProjects(currentJudgeId, searchKeyword, page, pageSize);
+ }
+
+ /**
+ * 鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃
+ */
+ @QueryMapping
+ public ReviewProjectPageResponse studentUnReviewedProjects(
+ @Argument String searchKeyword,
+ @Argument int page,
+ @Argument int pageSize) {
+ log.info("鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃锛宻earchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize);
+
+ Long currentJudgeId = userContextUtil.getCurrentJudgeId();
+ if (currentJudgeId == null) {
+ throw new RuntimeException("褰撳墠鐢ㄦ埛涓嶆槸璇勫锛屾棤娉曟煡璇㈣瘎瀹¢」鐩�");
+ }
+
+ return reviewService.getStudentUnReviewedProjects(currentJudgeId, searchKeyword, page, pageSize);
+ }
+
+ /**
+ * 鏌ヨ璇勫缁熻淇℃伅
+ */
+ @QueryMapping
+ public ReviewStatisticsResponse reviewStatistics() {
+ log.info("鏌ヨ璇勫缁熻淇℃伅");
+
+ Long currentJudgeId = userContextUtil.getCurrentJudgeId();
+ if (currentJudgeId == null) {
+ throw new RuntimeException("褰撳墠鐢ㄦ埛涓嶆槸璇勫锛屾棤娉曟煡璇㈣瘎瀹$粺璁�");
+ }
+
+ return reviewService.getReviewStatistics(currentJudgeId);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/review/service/ReviewService.java b/backend/src/main/java/com/rongyichuang/review/service/ReviewService.java
new file mode 100644
index 0000000..1cffb77
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/review/service/ReviewService.java
@@ -0,0 +1,304 @@
+package com.rongyichuang.review.service;
+
+import com.rongyichuang.review.dto.response.ReviewProjectPageResponse;
+import com.rongyichuang.review.dto.response.ReviewProjectResponse;
+import com.rongyichuang.review.dto.response.ReviewStatisticsResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 璇勫绠$悊鏈嶅姟绫�
+ */
+@Service
+public class ReviewService {
+
+ private static final Logger log = LoggerFactory.getLogger(ReviewService.class);
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ /**
+ * 鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛�
+ * 閫昏緫锛氬瓨鍦ㄨ瘎濮斿叧鑱斾絾褰撳墠璇勫鏈瘎鍒嗙殑椤圭洰
+ */
+ public ReviewProjectPageResponse getUnReviewedProjects(Long judgeId, String searchKeyword, int page, int pageSize) {
+ log.info("鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛紝judgeId: {}, searchKeyword: {}, page: {}, pageSize: {}", judgeId, searchKeyword, page, pageSize);
+
+ StringBuilder sql = new StringBuilder();
+ sql.append("SELECT ap.id as activity_player_id, ap.activity_id, ap.stage_id, ap.project_name, ")
+ .append("a.name as activity_name, stage.name as stage_name, ")
+ .append("p.name as student_name, ap.create_time as submit_time ")
+ .append("FROM t_activity_player ap ")
+ .append("JOIN t_player p ON ap.player_id = p.id ")
+ .append("JOIN t_activity stage ON ap.stage_id = stage.id ")
+ .append("JOIN t_activity a ON stage.pid = a.id ")
+ .append("WHERE EXISTS (")
+ .append(" SELECT 1 FROM t_activity_judge aj ")
+ .append(" WHERE ap.activity_id = aj.activity_id ")
+ .append(" AND ap.stage_id = aj.stage_id ")
+ .append(" AND aj.judge_id = ? ")
+ .append(") ")
+ .append("AND NOT EXISTS (")
+ .append(" SELECT 1 FROM t_activity_player_rating apr ")
+ .append(" WHERE ap.id = apr.activity_player_id ")
+ .append(" AND apr.judge_id = ? ")
+ .append(" AND apr.state = 1 ")
+ .append(") ")
+ .append("AND ap.state = 1 "); // 鍙煡璇㈠鏍搁�氳繃鐨勯」鐩�
+
+ List<Object> params = new ArrayList<>();
+ params.add(judgeId);
+ params.add(judgeId);
+
+ // 娣诲姞鍏抽敭璇嶆悳绱㈡潯浠�
+ if (StringUtils.hasText(searchKeyword)) {
+ sql.append("AND (ap.project_name LIKE ? OR p.name LIKE ?) ");
+ String searchPattern = "%" + searchKeyword + "%";
+ params.add(searchPattern);
+ params.add(searchPattern);
+ }
+
+ // 娣诲姞鎺掑簭
+ sql.append("ORDER BY ap.create_time DESC ");
+
+ // 鏌ヨ鎬绘暟
+ String countSql = "SELECT COUNT(*) " + sql.substring(sql.indexOf("FROM"));
+ Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, params.toArray());
+
+ // 娣诲姞鍒嗛〉
+ sql.append("LIMIT ? OFFSET ?");
+ params.add(pageSize);
+ params.add((page - 1) * pageSize);
+
+ // 鏌ヨ鏁版嵁
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql.toString(), params.toArray());
+ List<ReviewProjectResponse> projects = convertToProjectList(rows);
+
+ return new ReviewProjectPageResponse(projects, total != null ? total : 0, page, pageSize);
+ }
+
+ /**
+ * 鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛�
+ * 閫昏緫锛氬皢"鎴戞湭璇勫"鐨凬OT EXISTS鏀逛负EXISTS
+ */
+ public ReviewProjectPageResponse getReviewedProjects(Long judgeId, String searchKeyword, int page, int pageSize) {
+ log.info("鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛紝judgeId: {}, searchKeyword: {}, page: {}, pageSize: {}", judgeId, searchKeyword, page, pageSize);
+
+ StringBuilder sql = new StringBuilder();
+ sql.append("SELECT ap.id as activity_player_id, ap.activity_id, ap.stage_id, ap.project_name, ")
+ .append("a.name as activity_name, stage.name as stage_name, ")
+ .append("p.name as student_name, ap.create_time as submit_time ")
+ .append("FROM t_activity_player ap ")
+ .append("JOIN t_player p ON ap.player_id = p.id ")
+ .append("JOIN t_activity stage ON ap.stage_id = stage.id ")
+ .append("JOIN t_activity a ON stage.pid = a.id ")
+ .append("WHERE EXISTS (")
+ .append(" SELECT 1 FROM t_activity_judge aj ")
+ .append(" WHERE ap.activity_id = aj.activity_id ")
+ .append(" AND ap.stage_id = aj.stage_id ")
+ .append(" AND aj.judge_id = ? ")
+ .append(") ")
+ .append("AND EXISTS (") // 鏀逛负EXISTS
+ .append(" SELECT 1 FROM t_activity_player_rating apr ")
+ .append(" WHERE ap.id = apr.activity_player_id ")
+ .append(" AND apr.judge_id = ? ")
+ .append(" AND apr.state = 1 ")
+ .append(") ")
+ .append("AND ap.state = 1 "); // 鍙煡璇㈠鏍搁�氳繃鐨勯」鐩�
+
+ List<Object> params = new ArrayList<>();
+ params.add(judgeId);
+ params.add(judgeId);
+
+ // 娣诲姞鍏抽敭璇嶆悳绱㈡潯浠�
+ if (StringUtils.hasText(searchKeyword)) {
+ sql.append("AND (ap.project_name LIKE ? OR p.name LIKE ?) ");
+ String searchPattern = "%" + searchKeyword + "%";
+ params.add(searchPattern);
+ params.add(searchPattern);
+ }
+
+ // 娣诲姞鎺掑簭
+ sql.append("ORDER BY ap.create_time DESC ");
+
+ // 鏌ヨ鎬绘暟
+ String countSql = "SELECT COUNT(*) " + sql.substring(sql.indexOf("FROM"));
+ Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, params.toArray());
+
+ // 娣诲姞鍒嗛〉
+ sql.append("LIMIT ? OFFSET ?");
+ params.add(pageSize);
+ params.add((page - 1) * pageSize);
+
+ // 鏌ヨ鏁版嵁
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql.toString(), params.toArray());
+ List<ReviewProjectResponse> projects = convertToProjectList(rows);
+
+ return new ReviewProjectPageResponse(projects, total != null ? total : 0, page, pageSize);
+ }
+
+ /**
+ * 鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃
+ * 閫昏緫锛氭病鏈変换浣曡瘎鍒嗚褰曠殑椤圭洰
+ */
+ public ReviewProjectPageResponse getStudentUnReviewedProjects(Long judgeId, String searchKeyword, int page, int pageSize) {
+ log.info("鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃锛宩udgeId: {}, searchKeyword: {}, page: {}, pageSize: {}", judgeId, searchKeyword, page, pageSize);
+
+ StringBuilder sql = new StringBuilder();
+ sql.append("SELECT ap.id as activity_player_id, ap.activity_id, ap.stage_id, ap.project_name, ")
+ .append("a.name as activity_name, stage.name as stage_name, ")
+ .append("p.name as student_name, ap.create_time as submit_time ")
+ .append("FROM t_activity_player ap ")
+ .append("JOIN t_player p ON ap.player_id = p.id ")
+ .append("JOIN t_activity stage ON ap.stage_id = stage.id ")
+ .append("JOIN t_activity a ON stage.pid = a.id ")
+ .append("WHERE EXISTS (")
+ .append(" SELECT 1 FROM t_activity_judge aj ")
+ .append(" WHERE ap.activity_id = aj.activity_id ")
+ .append(" AND ap.stage_id = aj.stage_id ")
+ .append(" AND aj.judge_id = ? ")
+ .append(") ")
+ .append("AND NOT EXISTS (")
+ .append(" SELECT 1 FROM t_activity_player_rating apr ")
+ .append(" WHERE ap.id = apr.activity_player_id ")
+ .append(" AND apr.state = 1 ")
+ .append(") ")
+ .append("AND ap.state = 1 "); // 鍙煡璇㈠鏍搁�氳繃鐨勯」鐩�
+
+ List<Object> params = new ArrayList<>();
+ params.add(judgeId);
+
+ // 娣诲姞鍏抽敭璇嶆悳绱㈡潯浠�
+ if (StringUtils.hasText(searchKeyword)) {
+ sql.append("AND (ap.project_name LIKE ? OR p.name LIKE ?) ");
+ String searchPattern = "%" + searchKeyword + "%";
+ params.add(searchPattern);
+ params.add(searchPattern);
+ }
+
+ // 娣诲姞鎺掑簭
+ sql.append("ORDER BY ap.create_time DESC ");
+
+ // 鏌ヨ鎬绘暟
+ String countSql = "SELECT COUNT(*) " + sql.substring(sql.indexOf("FROM"));
+ Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, params.toArray());
+
+ // 娣诲姞鍒嗛〉
+ sql.append("LIMIT ? OFFSET ?");
+ params.add(pageSize);
+ params.add((page - 1) * pageSize);
+
+ // 鏌ヨ鏁版嵁
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql.toString(), params.toArray());
+ List<ReviewProjectResponse> projects = convertToProjectList(rows);
+
+ return new ReviewProjectPageResponse(projects, total != null ? total : 0, page, pageSize);
+ }
+
+ /**
+ * 鏌ヨ璇勫缁熻淇℃伅
+ */
+ public ReviewStatisticsResponse getReviewStatistics(Long judgeId) {
+ log.info("鏌ヨ璇勫缁熻淇℃伅锛宩udgeId: {}", judgeId);
+
+ // 鏌ヨ鎴戞湭璇勫鐨勬暟閲�
+ String unReviewedSql = "SELECT COUNT(*) FROM t_activity_player ap " +
+ "WHERE EXISTS (" +
+ " SELECT 1 FROM t_activity_judge aj " +
+ " WHERE ap.activity_id = aj.activity_id " +
+ " AND ap.stage_id = aj.stage_id " +
+ " AND aj.judge_id = ? " +
+ ") " +
+ "AND NOT EXISTS (" +
+ " SELECT 1 FROM t_activity_player_rating apr " +
+ " WHERE ap.id = apr.activity_player_id " +
+ " AND apr.judge_id = ? " +
+ " AND apr.state = 1 " +
+ ") " +
+ "AND ap.state = 1";
+ Integer unReviewedCount = jdbcTemplate.queryForObject(unReviewedSql, Integer.class, judgeId, judgeId);
+
+ // 鏌ヨ鎴戝凡璇勫鐨勬暟閲�
+ String reviewedSql = "SELECT COUNT(*) FROM t_activity_player ap " +
+ "WHERE EXISTS (" +
+ " SELECT 1 FROM t_activity_judge aj " +
+ " WHERE ap.activity_id = aj.activity_id " +
+ " AND ap.stage_id = aj.stage_id " +
+ " AND aj.judge_id = ? " +
+ ") " +
+ "AND EXISTS (" +
+ " SELECT 1 FROM t_activity_player_rating apr " +
+ " WHERE ap.id = apr.activity_player_id " +
+ " AND apr.judge_id = ? " +
+ " AND apr.state = 1 " +
+ ") " +
+ "AND ap.state = 1";
+ Integer reviewedCount = jdbcTemplate.queryForObject(reviewedSql, Integer.class, judgeId, judgeId);
+
+ // 鏌ヨ瀛﹀憳鏈瘎瀹$殑鏁伴噺
+ String studentUnReviewedSql = "SELECT COUNT(*) FROM t_activity_player ap " +
+ "WHERE EXISTS (" +
+ " SELECT 1 FROM t_activity_judge aj " +
+ " WHERE ap.activity_id = aj.activity_id " +
+ " AND ap.stage_id = aj.stage_id " +
+ " AND aj.judge_id = ? " +
+ ") " +
+ "AND NOT EXISTS (" +
+ " SELECT 1 FROM t_activity_player_rating apr " +
+ " WHERE ap.id = apr.activity_player_id " +
+ " AND apr.state = 1 " +
+ ") " +
+ "AND ap.state = 1";
+ Integer studentUnReviewedCount = jdbcTemplate.queryForObject(studentUnReviewedSql, Integer.class, judgeId);
+
+ return new ReviewStatisticsResponse(
+ unReviewedCount != null ? unReviewedCount : 0,
+ reviewedCount != null ? reviewedCount : 0,
+ studentUnReviewedCount != null ? studentUnReviewedCount : 0
+ );
+ }
+
+ /**
+ * 杞崲鏌ヨ缁撴灉涓洪」鐩垪琛�
+ */
+ private List<ReviewProjectResponse> convertToProjectList(List<Map<String, Object>> rows) {
+ List<ReviewProjectResponse> projects = new ArrayList<>();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ for (Map<String, Object> row : rows) {
+ ReviewProjectResponse project = new ReviewProjectResponse();
+ project.setActivityPlayerId(((Number) row.get("activity_player_id")).longValue());
+ project.setActivityId(((Number) row.get("activity_id")).longValue());
+ project.setStageId(((Number) row.get("stage_id")).longValue());
+ project.setProjectName((String) row.get("project_name"));
+ project.setActivityName((String) row.get("activity_name"));
+ project.setStageName((String) row.get("stage_name"));
+ project.setStudentName((String) row.get("student_name"));
+
+ // 澶勭悊鎻愪氦鏃堕棿
+ Object submitTimeObj = row.get("submit_time");
+ if (submitTimeObj instanceof LocalDateTime) {
+ project.setSubmitTime(((LocalDateTime) submitTimeObj).format(formatter));
+ } else if (submitTimeObj != null) {
+ project.setSubmitTime(submitTimeObj.toString());
+ }
+
+ // 璁剧疆榛樿鐘舵��
+ project.setStatus("PENDING");
+
+ projects.add(project);
+ }
+
+ return projects;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/user/dto/request/UserInput.java b/backend/src/main/java/com/rongyichuang/user/dto/request/UserInput.java
new file mode 100644
index 0000000..00176bc
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/user/dto/request/UserInput.java
@@ -0,0 +1,73 @@
+package com.rongyichuang.user.dto.request;
+
+/**
+ * 鐢ㄦ埛淇℃伅杈撳叆DTO
+ */
+public class UserInput {
+
+ /**
+ * 鐢ㄦ埛濮撳悕
+ */
+ private String name;
+
+ /**
+ * 澶村儚璺緞
+ */
+ private String avatar;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phone;
+
+ /**
+ * 鎬у埆锛歁ALE-鐢�, FEMALE-濂�
+ */
+ private String gender;
+
+ /**
+ * 鐢熸棩
+ */
+ private String birthday;
+
+ // Getters and Setters
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+
+ public String getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(String birthday) {
+ this.birthday = birthday;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/user/dto/response/UserInfo.java b/backend/src/main/java/com/rongyichuang/user/dto/response/UserInfo.java
new file mode 100644
index 0000000..52c7f64
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/user/dto/response/UserInfo.java
@@ -0,0 +1,112 @@
+package com.rongyichuang.user.dto.response;
+
+/**
+ * 鐢ㄦ埛淇℃伅鍝嶅簲DTO
+ */
+public class UserInfo {
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private String id;
+
+ /**
+ * 鐢ㄦ埛濮撳悕
+ */
+ private String name;
+
+ /**
+ * 澶村儚璺緞
+ */
+ private String avatar;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phone;
+
+ /**
+ * 鎬у埆锛歁ALE-鐢�, FEMALE-濂�
+ */
+ private String gender;
+
+ /**
+ * 鐢熸棩
+ */
+ private String birthday;
+
+ /**
+ * 寰俊openid
+ */
+ private String wxOpenId;
+
+ /**
+ * 寰俊unionid
+ */
+ private String unionId;
+
+ // Getters and Setters
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+
+ public String getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(String birthday) {
+ this.birthday = birthday;
+ }
+
+ public String getWxOpenId() {
+ return wxOpenId;
+ }
+
+ public void setWxOpenId(String wxOpenId) {
+ this.wxOpenId = wxOpenId;
+ }
+
+ public String getUnionId() {
+ return unionId;
+ }
+
+ public void setUnionId(String unionId) {
+ this.unionId = unionId;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java
index 52e0e00..e62996a 100644
--- a/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java
+++ b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java
@@ -18,6 +18,8 @@
private String school;
private String major;
private String grade;
+ private String gender;
+ private String birthday;
private List<String> roles;
private String createdAt;
private EmployeeInfo employee;
@@ -89,6 +91,22 @@
this.grade = grade;
}
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+
+ public String getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(String birthday) {
+ this.birthday = birthday;
+ }
+
public List<String> getRoles() {
return roles;
}
diff --git a/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfileInfo.java b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfileInfo.java
new file mode 100644
index 0000000..829d565
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfileInfo.java
@@ -0,0 +1,112 @@
+package com.rongyichuang.user.dto.response;
+
+/**
+ * 鐢ㄦ埛淇℃伅鍝嶅簲DTO
+ */
+public class UserProfileInfo {
+
+ /**
+ * 鐢ㄦ埛ID
+ */
+ private String id;
+
+ /**
+ * 鐢ㄦ埛濮撳悕
+ */
+ private String name;
+
+ /**
+ * 澶村儚璺緞
+ */
+ private String avatar;
+
+ /**
+ * 鎵嬫満鍙风爜
+ */
+ private String phone;
+
+ /**
+ * 鎬у埆锛歁ALE-鐢�, FEMALE-濂�
+ */
+ private String gender;
+
+ /**
+ * 鐢熸棩
+ */
+ private String birthday;
+
+ /**
+ * 寰俊openid
+ */
+ private String wxOpenId;
+
+ /**
+ * 寰俊unionid
+ */
+ private String unionId;
+
+ // Getters and Setters
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+
+ public String getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(String birthday) {
+ this.birthday = birthday;
+ }
+
+ public String getWxOpenId() {
+ return wxOpenId;
+ }
+
+ public void setWxOpenId(String wxOpenId) {
+ this.wxOpenId = wxOpenId;
+ }
+
+ public String getUnionId() {
+ return unionId;
+ }
+
+ public void setUnionId(String unionId) {
+ this.unionId = unionId;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/user/dto/response/UserProject.java b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProject.java
new file mode 100644
index 0000000..dbd62cc
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProject.java
@@ -0,0 +1,98 @@
+package com.rongyichuang.user.dto.response;
+
+import com.rongyichuang.player.dto.response.SubmissionMediaResponse;
+import java.util.List;
+
+/**
+ * 鐢ㄦ埛椤圭洰鍝嶅簲DTO
+ */
+public class UserProject {
+ private String id;
+ private String projectName;
+ private String activityName;
+ private String status;
+ private String statusText;
+ private String createTime;
+ private List<SubmissionMediaResponse> submissionFiles;
+
+ // 鏋勯�犲嚱鏁�
+ public UserProject() {}
+
+ public UserProject(String id, String projectName, String activityName, String status, String createTime) {
+ this.id = id;
+ this.projectName = projectName;
+ this.activityName = activityName;
+ this.status = status;
+ this.createTime = createTime;
+ this.statusText = getStatusText(status);
+ }
+
+ // 鏍规嵁鐘舵�佺爜鑾峰彇鐘舵�佹枃鏈�
+ private String getStatusText(String status) {
+ if (status == null) return "鏈煡";
+ switch (status) {
+ case "0": return "鏈鏍�";
+ case "1": return "瀹℃牳閫氳繃";
+ case "2": return "瀹℃牳椹冲洖";
+ default: return "鏈煡";
+ }
+ }
+
+ // Getters and Setters
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public void setProjectName(String projectName) {
+ this.projectName = projectName;
+ }
+
+ public String getActivityName() {
+ return activityName;
+ }
+
+ public void setActivityName(String activityName) {
+ this.activityName = activityName;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ this.statusText = getStatusText(status);
+ }
+
+ public String getStatusText() {
+ return statusText;
+ }
+
+ public void setStatusText(String statusText) {
+ this.statusText = statusText;
+ }
+
+ public String getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(String createTime) {
+ this.createTime = createTime;
+ }
+
+ public List<SubmissionMediaResponse> getSubmissionFiles() {
+ return submissionFiles;
+ }
+
+ public void setSubmissionFiles(List<SubmissionMediaResponse> submissionFiles) {
+ this.submissionFiles = submissionFiles;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java b/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java
index 2c597ef..dff7fd5 100644
--- a/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java
+++ b/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java
@@ -10,16 +10,37 @@
import com.rongyichuang.judge.service.JudgeService;
import com.rongyichuang.player.entity.Player;
import com.rongyichuang.player.service.PlayerService;
+import com.rongyichuang.player.repository.ActivityPlayerRepository;
+import com.rongyichuang.common.repository.MediaRepository;
+import com.rongyichuang.common.entity.Media;
+import com.rongyichuang.common.enums.MediaTargetType;
import com.rongyichuang.user.dto.response.UserProfile;
import com.rongyichuang.user.dto.response.UserStats;
import com.rongyichuang.user.dto.response.UserRegistration;
+import com.rongyichuang.user.dto.response.UserProject;
+import com.rongyichuang.user.dto.request.UserInput;
+import com.rongyichuang.user.dto.response.UserProfileInfo;
+import com.rongyichuang.user.entity.User;
+import com.rongyichuang.user.repository.UserRepository;
+import com.rongyichuang.player.dto.response.SubmissionMediaResponse;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.persistence.Query;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
/**
* 鐢ㄦ埛GraphQL瑙f瀽鍣�
@@ -38,7 +59,22 @@
private PlayerService playerService;
@Autowired
+ private ActivityPlayerRepository activityPlayerRepository;
+
+ @Autowired
+ private MediaRepository mediaRepository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
private UserContextUtil userContextUtil;
+
+ @Value("${app.media-url}")
+ private String mediaBaseUrl;
/**
* 鑾峰彇褰撳墠鐢ㄦ埛妗f
@@ -54,6 +90,21 @@
UserProfile profile = new UserProfile();
profile.setId(userId.toString());
+
+ // 鑾峰彇鐢ㄦ埛鍩烘湰淇℃伅
+ Optional<User> userOpt = userRepository.findById(userId);
+ User user = null;
+ if (userOpt.isPresent()) {
+ user = userOpt.get();
+ // 璁剧疆鎬у埆
+ if (user.getGender() != null) {
+ profile.setGender(user.getGender() == 1 ? "MALE" : "FEMALE");
+ }
+ // 璁剧疆鐢熸棩
+ if (user.getBirthday() != null) {
+ profile.setBirthday(user.getBirthday().toString());
+ }
+ }
List<String> roles = new ArrayList<>();
@@ -117,7 +168,28 @@
}
profile.setRoles(roles);
- logger.debug("userProfile鏋勫缓瀹屾垚锛宺oles={}, name={}, phone={}", roles, profile.getName(), profile.getPhone());
+
+ // 鑾峰彇鐢ㄦ埛澶村儚
+ try {
+ List<Media> avatarMediaList = mediaRepository.findByTargetTypeAndTargetIdAndState(
+ MediaTargetType.USER_AVATAR.getValue(),
+ userId,
+ 1
+ );
+ if (!avatarMediaList.isEmpty()) {
+ Media avatarMedia = avatarMediaList.get(0);
+ String fullAvatarUrl = buildFullMediaUrl(avatarMedia.getPath());
+ profile.setAvatar(fullAvatarUrl);
+ logger.debug("鎵惧埌鐢ㄦ埛澶村儚: {} -> {}", avatarMedia.getPath(), fullAvatarUrl);
+ } else {
+ logger.debug("鐢ㄦ埛{}娌℃湁鎵惧埌澶村儚", userId);
+ }
+ } catch (Exception e) {
+ logger.warn("鑾峰彇鐢ㄦ埛澶村儚澶辫触: {}", e.getMessage());
+ }
+
+ logger.debug("userProfile鏋勫缓瀹屾垚锛宺oles={}, name={}, phone={}, avatar={}",
+ roles, profile.getName(), profile.getPhone(), profile.getAvatar());
profile.setCreatedAt(java.time.LocalDateTime.now().toString());
return profile;
@@ -162,12 +234,281 @@
return new ArrayList<>();
}
- // 杩欓噷搴旇瀹炵幇鐪熸鐨勬姤鍚嶈褰曟煡璇㈤�昏緫
- // 鏆傛椂杩斿洖绌哄垪琛�
- return new ArrayList<>();
+ // 鏋勫缓SQL鏌ヨ锛岃幏鍙栫敤鎴风殑鎶ュ悕璁板綍
+ StringBuilder sql = new StringBuilder();
+ sql.append("SELECT ap.id, a.id as activity_id, a.name as activity_name, ");
+ sql.append("ap.project_name, ap.state, ap.create_time, ");
+ sql.append("m.path as cover_image_path ");
+ sql.append("FROM t_activity_player ap ");
+ sql.append("INNER JOIN t_player p ON ap.player_id = p.id ");
+ sql.append("INNER JOIN t_activity a ON ap.activity_id = a.id ");
+ sql.append("LEFT JOIN t_media m ON a.cover_image_id = m.id ");
+ sql.append("WHERE p.user_id = :userId ");
+ sql.append("ORDER BY ap.create_time DESC");
+
+ if (limit != null && limit > 0) {
+ sql.append(" LIMIT :limit");
+ }
+
+ Query query = entityManager.createNativeQuery(sql.toString());
+ query.setParameter("userId", userId);
+ if (limit != null && limit > 0) {
+ query.setParameter("limit", limit);
+ }
+
+ @SuppressWarnings("unchecked")
+ List<Object[]> results = query.getResultList();
+
+ List<UserRegistration> registrations = new ArrayList<>();
+ for (Object[] row : results) {
+ UserRegistration registration = new UserRegistration();
+ registration.setId(String.valueOf(row[0]));
+
+ // 鍒涘缓ActivityInfo
+ UserRegistration.ActivityInfo activityInfo = new UserRegistration.ActivityInfo();
+ activityInfo.setId(String.valueOf(row[1]));
+ activityInfo.setTitle((String) row[2]);
+
+ // 璁剧疆灏侀潰鍥剧墖
+ if (row[6] != null) {
+ UserRegistration.MediaInfo mediaInfo = new UserRegistration.MediaInfo();
+ mediaInfo.setPath((String) row[6]);
+ mediaInfo.setFullUrl(buildFullMediaUrl((String) row[6]));
+ activityInfo.setCoverImage(mediaInfo);
+ }
+
+ registration.setActivity(activityInfo);
+
+ // 璁剧疆鐘舵��
+ Integer state = (Integer) row[4];
+ String status = "pending";
+ if (state != null) {
+ switch (state) {
+ case 0: status = "pending"; break;
+ case 1: status = "approved"; break;
+ case 2: status = "rejected"; break;
+ default: status = "unknown"; break;
+ }
+ }
+ registration.setStatus(status);
+
+ // 璁剧疆鎶ュ悕鏃堕棿
+ if (row[5] != null) {
+ registration.setRegistrationTime(row[5].toString());
+ }
+
+ registrations.add(registration);
+ }
+
+ return registrations;
} catch (Exception e) {
- e.printStackTrace();
+ logger.error("鑾峰彇鐢ㄦ埛鎶ュ悕璁板綍澶辫触", e);
return new ArrayList<>();
}
}
+
+ /**
+ * 鑾峰彇鎴戠殑椤圭洰鍒楄〃
+ */
+ @QueryMapping
+ public List<UserProject> myProjects() {
+ try {
+ Long userId = userContextUtil.getCurrentUserId();
+ logger.debug("鑾峰彇鐢ㄦ埛椤圭洰鍒楄〃锛寀serId: {}", userId);
+ if (userId == null) {
+ return new ArrayList<>();
+ }
+
+ // 鏌ヨ鐢ㄦ埛鐨勬墍鏈夐」鐩紙state = 0, 1, 2锛�
+ // 浣跨敤 t_activity_player join t_player join t_user 鐨勬柟寮忛�氳繃鐢ㄦ埛ID鏌ヨ
+ String sql = """
+ SELECT ap.id, ap.project_name, a.name as activity_name, ap.state, ap.create_time
+ FROM t_activity_player ap
+ JOIN t_player p ON ap.player_id = p.id
+ JOIN t_activity a ON a.id = ap.activity_id
+ WHERE p.user_id = ? AND ap.state IN (0, 1, 2)
+ ORDER BY ap.create_time DESC
+ """;
+
+ Query query = entityManager.createNativeQuery(sql);
+ query.setParameter(1, userId);
+
+ @SuppressWarnings("unchecked")
+ List<Object[]> results = query.getResultList();
+
+ List<UserProject> projects = new ArrayList<>();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ for (Object[] row : results) {
+ String id = row[0].toString();
+ String projectName = row[1] != null ? row[1].toString() : "";
+ String activityName = row[2] != null ? row[2].toString() : "";
+ String status = row[3] != null ? row[3].toString() : "0";
+ String createTime = row[4] != null ? row[4].toString() : "";
+
+ UserProject project = new UserProject(id, projectName, activityName, status, createTime);
+
+ // 鏌ヨ椤圭洰鐨勬彁浜ゆ枃浠�
+ List<SubmissionMediaResponse> submissionFiles = getSubmissionFiles(Long.parseLong(id));
+ project.setSubmissionFiles(submissionFiles);
+
+ projects.add(project);
+ }
+
+ logger.debug("鏌ヨ鍒� {} 涓」鐩�", projects.size());
+ return projects;
+ } catch (Exception e) {
+ logger.error("鑾峰彇鐢ㄦ埛椤圭洰鍒楄〃澶辫触", e);
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * 鑾峰彇椤圭洰鐨勬彁浜ゆ枃浠�
+ * @param activityPlayerId 娲诲姩鍙備笌鑰匢D
+ * @return 鎻愪氦鏂囦欢鍒楄〃
+ */
+ private List<SubmissionMediaResponse> getSubmissionFiles(Long activityPlayerId) {
+ try {
+ // 鏌ヨ鎻愪氦鐨勫獟浣撴枃浠� (target_type = 5 琛ㄧず娲诲姩鍙備笌鑰呮彁浜ょ殑鏂囦欢锛宻tate = 1 琛ㄧず鏈夋晥鐘舵��)
+ List<Media> medias = mediaRepository.findByTargetTypeAndTargetIdAndState(5, activityPlayerId, 1);
+
+ List<SubmissionMediaResponse> submissionFiles = new ArrayList<>();
+ for (Media media : medias) {
+ SubmissionMediaResponse response = new SubmissionMediaResponse();
+ response.setId(media.getId());
+ response.setName(media.getName());
+ response.setPath(media.getPath());
+ response.setUrl(buildFullMediaUrl(media.getPath()));
+ response.setFullUrl(buildFullMediaUrl(media.getPath()));
+ response.setFullThumbUrl(buildFullMediaUrl(media.getThumbPath()));
+ response.setFileExt(media.getFileExt());
+ response.setFileSize(media.getFileSize() != null ? media.getFileSize().longValue() : null);
+ response.setMediaType(media.getMediaType());
+ response.setThumbUrl(buildFullMediaUrl(media.getThumbPath()));
+ submissionFiles.add(response);
+ }
+
+ return submissionFiles;
+ } catch (Exception e) {
+ logger.error("鑾峰彇椤圭洰鎻愪氦鏂囦欢澶辫触锛宎ctivityPlayerId: {}", activityPlayerId, e);
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * 鏋勫缓瀹屾暣鐨勫獟浣揢RL
+ * @param path 濯掍綋璺緞
+ * @return 瀹屾暣鐨刄RL
+ */
+ private String buildFullMediaUrl(String path) {
+ if (!StringUtils.hasText(path)) {
+ return null;
+ }
+
+ // 濡傛灉璺緞宸茬粡鏄畬鏁碪RL锛岀洿鎺ヨ繑鍥�
+ if (path.startsWith("http://") || path.startsWith("https://")) {
+ return path;
+ }
+
+ // 鏋勫缓瀹屾暣URL
+ if (StringUtils.hasText(mediaBaseUrl)) {
+ // 纭繚baseUrl浠�/缁撳熬锛宲ath涓嶄互/寮�澶�
+ String baseUrl = mediaBaseUrl.endsWith("/") ? mediaBaseUrl : mediaBaseUrl + "/";
+ String relativePath = path.startsWith("/") ? path.substring(1) : path;
+ return baseUrl + relativePath;
+ }
+
+ // 濡傛灉娌℃湁閰嶇疆baseUrl锛岃繑鍥炲師璺緞
+ return path;
+ }
+
+ /**
+ * 淇濆瓨鐢ㄦ埛淇℃伅
+ */
+ @MutationMapping
+ public UserProfileInfo saveUserInfo(@Argument UserInput input) {
+ try {
+ Long userId = userContextUtil.getCurrentUserId();
+ logger.debug("杩涘叆saveUserInfo锛岃В鏋愬埌鐢ㄦ埛ID: {}", userId);
+ if (userId == null) {
+ throw new RuntimeException("鐢ㄦ埛鏈櫥褰�");
+ }
+
+ // 鏌ユ壘鐜版湁鐢ㄦ埛
+ Optional<User> userOpt = userRepository.findById(userId);
+ User user;
+
+ if (userOpt.isPresent()) {
+ // 鐢ㄦ埛瀛樺湪锛屾洿鏂颁俊鎭�
+ user = userOpt.get();
+ logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId);
+ } else {
+ // 鐢ㄦ埛涓嶅瓨鍦紝鍒涘缓鏂扮敤鎴�
+ user = new User();
+ user.setId(userId);
+ logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId);
+ }
+
+ // 鏇存柊鐢ㄦ埛鍩烘湰淇℃伅锛堜笉鍖呭惈澶村儚锛�
+ user.setName(input.getName());
+ user.setPhone(input.getPhone());
+
+ // 澶勭悊鎬у埆杞崲锛歁ALE -> 1, FEMALE -> 0
+ if ("MALE".equals(input.getGender())) {
+ user.setGender(1);
+ } else if ("FEMALE".equals(input.getGender())) {
+ user.setGender(0);
+ }
+
+ // 澶勭悊鐢熸棩杞崲
+ if (StringUtils.hasText(input.getBirthday())) {
+ try {
+ LocalDate birthday = LocalDate.parse(input.getBirthday());
+ user.setBirthday(birthday);
+ } catch (Exception e) {
+ logger.warn("鐢熸棩鏍煎紡瑙f瀽澶辫触: {}", input.getBirthday(), e);
+ }
+ }
+
+ // 淇濆瓨鐢ㄦ埛鍩烘湰淇℃伅
+ user = userRepository.save(user);
+ logger.debug("鐢ㄦ埛淇℃伅淇濆瓨鎴愬姛锛岀敤鎴稩D: {}", user.getId());
+
+ // 鏋勫缓杩斿洖缁撴灉
+ UserProfileInfo result = new UserProfileInfo();
+ result.setId(user.getId().toString());
+ result.setName(user.getName());
+ result.setPhone(user.getPhone());
+ // 姝g‘澶勭悊鎬у埆杞崲锛�1 -> MALE, 0 -> FEMALE, null -> null
+ if (user.getGender() != null) {
+ result.setGender(user.getGender() == 1 ? "MALE" : "FEMALE");
+ } else {
+ result.setGender(null);
+ }
+ result.setBirthday(user.getBirthday() != null ? user.getBirthday().toString() : null);
+ result.setWxOpenId(user.getWxOpenid());
+ result.setUnionId(user.getWxUnionid());
+
+ // 鏌ユ壘骞惰缃ご鍍廢RL锛堜粠t_media琛ㄨ幏鍙栵級
+ try {
+ List<Media> avatarMedias = mediaRepository.findByTargetTypeAndTargetIdAndState(
+ MediaTargetType.USER_AVATAR.getValue(),
+ userId,
+ 1
+ );
+ if (!avatarMedias.isEmpty()) {
+ Media avatarMedia = avatarMedias.get(0);
+ result.setAvatar(buildFullMediaUrl(avatarMedia.getPath()));
+ }
+ } catch (Exception e) {
+ logger.warn("鑾峰彇澶村儚澶辫触: {}", e.getMessage(), e);
+ }
+
+ return result;
+ } catch (Exception e) {
+ logger.error("淇濆瓨鐢ㄦ埛淇℃伅澶辫触", e);
+ throw new RuntimeException("淇濆瓨鐢ㄦ埛淇℃伅澶辫触: " + e.getMessage());
+ }
+ }
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/user/service/UserService.java b/backend/src/main/java/com/rongyichuang/user/service/UserService.java
index 3f78aa6..a4c4913 100644
--- a/backend/src/main/java/com/rongyichuang/user/service/UserService.java
+++ b/backend/src/main/java/com/rongyichuang/user/service/UserService.java
@@ -132,6 +132,13 @@
}
/**
+ * 鑾峰彇瀵嗙爜缂栫爜鍣�
+ */
+ public BCryptPasswordEncoder getPasswordEncoder() {
+ return passwordEncoder;
+ }
+
+ /**
* 淇濆瓨鐢ㄦ埛鎵嬫満鍙风爜
* 鏍稿績閫昏緫锛�
* 1. 鏌ヨ褰撳墠t_user閲岄潰鏄惁瀛樺湪鐩稿悓鐨勭數璇濆彿鐮侊紝濡傛灉娌℃湁锛屽氨鏇存柊鍒板綋鍓島ser閲岄潰
diff --git a/backend/src/main/resources/graphql/auth.graphqls b/backend/src/main/resources/graphql/auth.graphqls
index 87bc082..aa790e5 100644
--- a/backend/src/main/resources/graphql/auth.graphqls
+++ b/backend/src/main/resources/graphql/auth.graphqls
@@ -1,13 +1,11 @@
extend type Query {
- # 杩欎釜鏂囦欢涓昏鏄� mutation, 鎵�浠� query 鍙兘鏄┖鐨�
+ # 璁よ瘉鐩稿叧鎺ュ彛宸茶縼绉诲埌RESTful API
_: Boolean
}
extend type Mutation {
- webLogin(input: LoginRequest!): LoginResponse
- wxLogin(input: WxLoginRequest!): WxLoginResponse
- decryptPhoneNumber(encryptedData: String!, iv: String!, sessionKey: String!): PhoneDecryptResponse
- getPhoneNumberByCode(code: String!): PhoneDecryptResponse
+ # 璁よ瘉鐩稿叧鎺ュ彛宸茶縼绉诲埌RESTful API (/auth/*)
+ _: Boolean
}
input LoginRequest {
@@ -44,6 +42,7 @@
name: String
phone: String
userType: String
+ avatarUrl: String
employee: EmployeeInfo
judge: JudgeInfo
player: PlayerInfo
@@ -65,10 +64,19 @@
}
type PlayerInfo {
- id: Long
- name: String
+ id: ID!
+ name: String!
phone: String
description: String
+ userInfo: PlayerUserInfo
+}
+
+type PlayerUserInfo {
+ userId: Long
+ name: String
+ phone: String
+ avatarUrl: String
+ avatar: MediaInfo
}
type PhoneDecryptResponse {
diff --git a/backend/src/main/resources/graphql/player.graphqls b/backend/src/main/resources/graphql/player.graphqls
index 8ab03f6..d72f9d7 100644
--- a/backend/src/main/resources/graphql/player.graphqls
+++ b/backend/src/main/resources/graphql/player.graphqls
@@ -140,6 +140,15 @@
description: String
avatarUrl: String
avatar: MediaResponse
+ userInfo: PlayerUserInfoResponse
+}
+
+type PlayerUserInfoResponse {
+ userId: ID
+ name: String
+ phone: String
+ avatarUrl: String
+ avatar: MediaResponse
}
type RegionInfoResponse {
@@ -151,7 +160,10 @@
type SubmissionMediaResponse {
id: ID
name: String
+ path: String
url: String
+ fullUrl: String
+ fullThumbUrl: String
fileExt: String
fileSize: Int
mediaType: Int
diff --git a/backend/src/main/resources/graphql/review.graphqls b/backend/src/main/resources/graphql/review.graphqls
new file mode 100644
index 0000000..6c3e122
--- /dev/null
+++ b/backend/src/main/resources/graphql/review.graphqls
@@ -0,0 +1,45 @@
+# 璇勫绠$悊鐩稿叧鐨凣raphQL Schema瀹氫箟
+
+# 鎵╁睍鏌ヨ绫诲瀷
+extend type Query {
+ # 鑾峰彇鎴戞湭璇勫鐨勯」鐩垪琛�
+ unReviewedProjects(page: Int!, pageSize: Int!, searchKeyword: String): ReviewProjectPageResponse!
+
+ # 鑾峰彇鎴戝凡璇勫鐨勯」鐩垪琛�
+ reviewedProjects(page: Int!, pageSize: Int!, searchKeyword: String): ReviewProjectPageResponse!
+
+ # 鑾峰彇瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃
+ studentUnReviewedProjects(page: Int!, pageSize: Int!, searchKeyword: String): ReviewProjectPageResponse!
+
+ # 鑾峰彇璇勫缁熻鏁版嵁
+ reviewStatistics: ReviewStatisticsResponse!
+}
+
+# 璇勫椤圭洰鍒嗛〉鍝嶅簲绫诲瀷
+type ReviewProjectPageResponse {
+ items: [ReviewProjectResponse!]!
+ total: Int!
+ hasMore: Boolean!
+}
+
+# 璇勫椤圭洰鍝嶅簲绫诲瀷
+type ReviewProjectResponse {
+ id: ID!
+ activityId: ID!
+ stageId: ID!
+ projectName: String!
+ activityName: String!
+ stageName: String!
+ studentName: String!
+ submitTime: String
+ reviewTime: String
+ score: Float
+ status: String!
+}
+
+# 璇勫缁熻鍝嶅簲绫诲瀷
+type ReviewStatisticsResponse {
+ unReviewedCount: Int!
+ reviewedCount: Int!
+ studentUnReviewedCount: Int!
+}
\ No newline at end of file
diff --git a/backend/src/main/resources/graphql/schema.graphqls b/backend/src/main/resources/graphql/schema.graphqls
index df3bbd3..96f318a 100644
--- a/backend/src/main/resources/graphql/schema.graphqls
+++ b/backend/src/main/resources/graphql/schema.graphqls
@@ -7,6 +7,14 @@
type Query {
# 娴嬭瘯鏌ヨ
hello: String
+
+ # 搴旂敤閰嶇疆鏌ヨ
+ appConfig: AppConfig
+}
+
+# 搴旂敤閰嶇疆绫诲瀷
+type AppConfig {
+ mediaBaseUrl: String!
}
# 鍙樻洿鏍圭被鍨�
diff --git a/backend/src/main/resources/graphql/user.graphqls b/backend/src/main/resources/graphql/user.graphqls
index efd3849..58628d0 100644
--- a/backend/src/main/resources/graphql/user.graphqls
+++ b/backend/src/main/resources/graphql/user.graphqls
@@ -10,6 +10,8 @@
school: String
major: String
grade: String
+ gender: String
+ birthday: String
roles: [String!]!
createdAt: String
# 瑙掕壊鐩稿叧淇℃伅
@@ -53,6 +55,38 @@
mediaType: String!
}
+# 鐢ㄦ埛椤圭洰绫诲瀷
+type UserProject {
+ id: ID!
+ projectName: String!
+ activityName: String!
+ status: String!
+ statusText: String!
+ createTime: String!
+ submissionFiles: [SubmissionMediaResponse]
+}
+
+# 鐢ㄦ埛杈撳叆绫诲瀷
+input UserInput {
+ name: String!
+ avatar: String
+ phone: String!
+ gender: String!
+ birthday: String
+}
+
+# 鐢ㄦ埛淇℃伅鍝嶅簲绫诲瀷
+type UserProfileInfo {
+ id: ID!
+ name: String!
+ avatar: String
+ phone: String!
+ gender: String!
+ birthday: String
+ wxOpenId: String
+ unionId: String
+}
+
# 鎵╁睍鏌ヨ绫诲瀷
extend type Query {
# 鑾峰彇褰撳墠鐢ㄦ埛妗f
@@ -63,4 +97,13 @@
# 鑾峰彇鎴戠殑鎶ュ悕璁板綍
myRegistrations(limit: Int): [UserRegistration!]!
+
+ # 鑾峰彇鎴戠殑椤圭洰鍒楄〃
+ myProjects: [UserProject!]!
+}
+
+# 鎵╁睍鍙樻洿绫诲瀷
+extend type Mutation {
+ # 淇濆瓨鐢ㄦ埛淇℃伅
+ saveUserInfo(input: UserInput!): UserProfileInfo!
}
\ No newline at end of file
diff --git a/backend/src/test/java/com/rongyichuang/DatabaseConnectionTest.java b/backend/src/test/java/com/rongyichuang/DatabaseConnectionTest.java
new file mode 100644
index 0000000..3bb1355
--- /dev/null
+++ b/backend/src/test/java/com/rongyichuang/DatabaseConnectionTest.java
@@ -0,0 +1,60 @@
+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 DatabaseConnectionTest {
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ @Test
+ public void testDatabaseConnection() {
+ try {
+ // 妫�鏌ユ暟鎹簱杩炴帴
+ String result = jdbcTemplate.queryForObject("SELECT 1", String.class);
+ System.out.println("鉁� 鏁版嵁搴撹繛鎺ユ垚鍔�: " + result);
+
+ // 妫�鏌_wx_login_record琛ㄦ槸鍚﹀瓨鍦�
+ try {
+ String checkTableSql = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'ryc' AND table_name = 't_wx_login_record'";
+ Integer tableExists = jdbcTemplate.queryForObject(checkTableSql, Integer.class);
+
+ if (tableExists != null && tableExists > 0) {
+ System.out.println("鉁� t_wx_login_record琛ㄥ瓨鍦�");
+
+ // 鏌ョ湅琛ㄧ粨鏋�
+ String describeTableSql = "DESCRIBE t_wx_login_record";
+ List<Map<String, Object>> columns = jdbcTemplate.queryForList(describeTableSql);
+ System.out.println("琛ㄧ粨鏋�:");
+ for (Map<String, Object> column : columns) {
+ System.out.println("- " + column.get("Field") + ": " + column.get("Type") +
+ (column.get("Null").equals("NO") ? " NOT NULL" : " NULL") +
+ (column.get("Default") != null ? " DEFAULT " + column.get("Default") : ""));
+ }
+
+ // 妫�鏌ヨ〃涓槸鍚︽湁鏁版嵁
+ String countSql = "SELECT COUNT(*) FROM t_wx_login_record";
+ Integer recordCount = jdbcTemplate.queryForObject(countSql, Integer.class);
+ System.out.println("琛ㄤ腑璁板綍鏁�: " + recordCount);
+
+ } else {
+ System.out.println("鉂� t_wx_login_record琛ㄤ笉瀛樺湪");
+ }
+ } catch (Exception e) {
+ System.out.println("鉂� 妫�鏌_wx_login_record琛ㄦ椂鍑洪敊: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ } catch (Exception e) {
+ System.out.println("鉂� 鏁版嵁搴撹繛鎺ュけ璐�: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/api/judge.js b/web/src/api/judge.js
index 21ff463..988cd13 100644
--- a/web/src/api/judge.js
+++ b/web/src/api/judge.js
@@ -1,6 +1,6 @@
// 璇勫绠$悊 API
-const GRAPHQL_ENDPOINT = '/api/graphql';
+import { API_CONFIG, graphqlRequest } from '../config/api.ts';
// GraphQL 鏌ヨ璇彞
const GET_ALL_JUDGES_QUERY = `
@@ -93,88 +93,71 @@
}
`;
-// GraphQL 璇锋眰鍑芥暟
-const graphqlRequest = async (query, variables = {}) => {
- const response = await fetch(GRAPHQL_ENDPOINT, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- query,
- variables
- })
- });
-
- const result = await response.json();
- if (result.errors) {
- throw new Error(result.errors[0].message);
- }
- return result.data;
-};
+// 浣跨敤缁熶竴鐨凣raphQL璇锋眰鍑芥暟
// API 鍑芥暟
export const getAllJudges = async () => {
- const data = await graphqlRequest(GET_ALL_JUDGES_QUERY);
- return data.judges || [];
+ const result = await graphqlRequest(GET_ALL_JUDGES_QUERY);
+ return result.data.judges;
};
export const getJudge = async (id) => {
- const data = await graphqlRequest(GET_JUDGE_QUERY, { id });
- return data.judge;
+ const result = await graphqlRequest(GET_JUDGE_QUERY, { id });
+ return result.data.judge;
};
// JudgeApi 瀵硅薄
export const JudgeApi = {
// 鑾峰彇鎵�鏈夎瘎濮�
getJudges: async () => {
- const data = await graphqlRequest(GET_ALL_JUDGES_QUERY);
- return data.judges || [];
+ const result = await graphqlRequest(GET_ALL_JUDGES_QUERY);
+ return result.data.judges || [];
},
// 鑾峰彇鍗曚釜璇勫
getJudge: async (id) => {
- const data = await graphqlRequest(GET_JUDGE_QUERY, { id });
- return data.judge;
+ const result = await graphqlRequest(GET_JUDGE_QUERY, { id });
+ return result.data.judge;
},
// 鎼滅储璇勫
searchJudges: async (name) => {
- // 鍏堣幏鍙栨墍鏈夎瘎濮旓紝鐒跺悗鍦ㄥ鎴风杩囨护锛堝疄闄呴」鐩腑搴旇鍦ㄥ悗绔疄鐜帮級
- const judges = await JudgeApi.getJudges();
- return judges.filter(judge =>
- judge.name && judge.name.toLowerCase().includes(name.toLowerCase())
- );
+ const result = await graphqlRequest(`
+ query SearchJudges($name: String!) {
+ searchJudges(name: $name) { id name title company }
+ }
+ `, { name });
+ return result.data.searchJudges || [];
},
// 淇濆瓨璇勫
saveJudge: async (input) => {
- const data = await graphqlRequest(SAVE_JUDGE_MUTATION, { input });
- return data.saveJudge;
+ const result = await graphqlRequest(SAVE_JUDGE_MUTATION, { input });
+ return result.data.saveJudge;
},
// 鍒犻櫎璇勫
deleteJudge: async (id) => {
- const data = await graphqlRequest(DELETE_JUDGE_MUTATION, { id });
- return data.deleteJudge;
+ const result = await graphqlRequest(DELETE_JUDGE_MUTATION, { id });
+ return result.data.deleteJudge;
},
// 鑾峰彇鎵�鏈夋爣绛�
getTags: async () => {
- const data = await graphqlRequest(GET_TAGS_QUERY);
- return data.tags || [];
+ const result = await graphqlRequest(GET_TAGS_QUERY);
+ return result.data.tags || [];
},
// 鏍规嵁绫诲埆鑾峰彇鏍囩
getTagsByCategory: async (category) => {
- const data = await graphqlRequest(GET_TAGS_BY_CATEGORY_QUERY, { category });
- return data.tagsByCategory || [];
+ const result = await graphqlRequest(GET_TAGS_BY_CATEGORY_QUERY, { category });
+ return result.data.tagsByCategory || [];
},
// 淇濆瓨濯掍綋
saveMedia: async (input) => {
- const data = await graphqlRequest(SAVE_MEDIA_MUTATION, { input });
- return data.saveMedia;
+ const result = await graphqlRequest(SAVE_MEDIA_MUTATION, { input });
+ return result.data.saveMedia;
}
};
diff --git a/web/src/api/media.js b/web/src/api/media.js
index ac6a772..c351e39 100644
--- a/web/src/api/media.js
+++ b/web/src/api/media.js
@@ -38,50 +38,12 @@
`;
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: headers,
- body: JSON.stringify({
- query: MEDIAS_BY_TARGET_QUERY,
- variables: { targetType, targetId }
- })
- });
- const result = await res.json();
- if (result.errors) {
- throw new Error(result.errors[0].message);
- }
+ const result = await graphqlRequest(MEDIAS_BY_TARGET_QUERY, { targetType, targetId });
return result.data.mediasByTarget || [];
};
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: headers,
- body: JSON.stringify({
- query: SAVE_MEDIA_MUTATION,
- variables: { input }
- })
- });
- const result = await res.json();
- if (result.errors) {
- throw new Error(result.errors[0].message);
- }
+ const result = await graphqlRequest(SAVE_MEDIA_MUTATION, { input });
return result.data.saveMedia;
};
@@ -215,17 +177,6 @@
// 缁熶竴鐨� 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);
- }
+ const result = await graphqlRequest(SAVE_MEDIA_V2_MUTATION, { input });
return result.data.saveMediaV2;
};
\ No newline at end of file
diff --git a/web/src/api/rating.js b/web/src/api/rating.js
index cbe6bab..5be5ccc 100644
--- a/web/src/api/rating.js
+++ b/web/src/api/rating.js
@@ -99,8 +99,8 @@
export const getRatingScheme = async (id) => {
try {
- const data = await graphqlRequest(GET_RATING_SCHEME_QUERY, { id })
- return data.ratingScheme
+ const result = await graphqlRequest(GET_RATING_SCHEME_QUERY, { id })
+ return result.data.ratingScheme
} catch (error) {
throw new Error(error.message || '鑾峰彇璇勫垎鏂规璇︽儏澶辫触')
}
@@ -108,8 +108,8 @@
export const saveRatingScheme = async (ratingSchemeData) => {
try {
- const data = await graphqlRequest(SAVE_RATING_SCHEME_MUTATION, { input: ratingSchemeData })
- return data.saveRatingScheme
+ const result = await graphqlRequest(SAVE_RATING_SCHEME_MUTATION, { input: ratingSchemeData })
+ return result.data.saveRatingScheme
} catch (error) {
throw new Error(error.message || '淇濆瓨璇勫垎鏂规澶辫触')
}
@@ -117,8 +117,8 @@
export const deleteRatingScheme = async (id) => {
try {
- const data = await graphqlRequest(DELETE_RATING_SCHEME_MUTATION, { id })
- return data.deleteRatingScheme
+ const result = await graphqlRequest(DELETE_RATING_SCHEME_MUTATION, { id })
+ return result.data.deleteRatingScheme
} catch (error) {
throw new Error(error.message || '鍒犻櫎璇勫垎鏂规澶辫触')
}
diff --git a/web/src/utils/appConfig.js b/web/src/utils/appConfig.js
index aea2f43..48c75f0 100644
--- a/web/src/utils/appConfig.js
+++ b/web/src/utils/appConfig.js
@@ -1,4 +1,4 @@
-const GRAPHQL_ENDPOINT = '/api/graphql';
+import { graphqlRequest } from '../config/api.ts';
const GET_APP_CONFIG = `
query AppConfig {
@@ -10,20 +10,16 @@
export async function loadAppConfig() {
try {
- 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 || 'appConfig query failed');
+ const result = await graphqlRequest(GET_APP_CONFIG);
const mediaBaseUrl = result.data?.appConfig?.mediaBaseUrl || '';
// 浣滀负鍏ㄥ眬鍙橀噺鏆撮湶
window.__APP_MEDIA_BASE_URL__ = mediaBaseUrl;
return mediaBaseUrl;
} catch (e) {
- console.warn('loadAppConfig failed:', e?.message || e);
- window.__APP_MEDIA_BASE_URL__ = '';
- return '';
+ // 濡傛灉GraphQL鏌ヨ澶辫触锛屼娇鐢ㄩ粯璁ら厤缃�
+ console.warn('loadAppConfig failed, using default config:', e?.message || e);
+ const defaultMediaBaseUrl = 'http://localhost:8080';
+ window.__APP_MEDIA_BASE_URL__ = defaultMediaBaseUrl;
+ return defaultMediaBaseUrl;
}
}
\ No newline at end of file
diff --git a/web/src/views/login/index.vue b/web/src/views/login/index.vue
index a8b0433..a714cc8 100644
--- a/web/src/views/login/index.vue
+++ b/web/src/views/login/index.vue
@@ -2,6 +2,7 @@
<div class="login-container">
<div class="login-box">
<div class="login-header">
+ <img src="/logo.jpg" alt="钃夋槗鍒汱ogo" class="logo" />
<h2>钃夋槗鍒涚鐞嗙郴缁�</h2>
<p>姣旇禌绠$悊骞冲彴</p>
</div>
@@ -62,7 +63,7 @@
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
-import { loginApi } from '@/utils/graphql'
+// import { loginApi } from '@/utils/graphql'
import { setToken, setUserInfo } from '@/utils/auth'
const router = useRouter()
@@ -95,15 +96,45 @@
await loginFormRef.value.validate()
loading.value = true
- // 璋冪敤鐧诲綍API
- const response = await loginApi({
- phone: loginForm.phone,
- password: loginForm.password
+ // 璋冪敤RESTful鐧诲綍API
+ const response = await fetch('/api/auth/web-login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ phone: loginForm.phone,
+ password: loginForm.password
+ })
})
+ console.log('Response status:', response.status)
+ console.log('Response headers:', response.headers)
+
+ // 妫�鏌ュ搷搴旀槸鍚︿负绌�
+ const responseText = await response.text()
+ console.log('Response text:', responseText)
+
+ if (!responseText) {
+ throw new Error('鏈嶅姟鍣ㄨ繑鍥炵┖鍝嶅簲')
+ }
+
+ let result
+ try {
+ result = JSON.parse(responseText)
+ } catch (jsonError) {
+ console.error('JSON瑙f瀽閿欒:', jsonError)
+ console.error('鍝嶅簲鍐呭:', responseText)
+ throw new Error('鏈嶅姟鍣ㄥ搷搴旀牸寮忛敊璇�')
+ }
+
+ if (!response.ok) {
+ throw new Error(result.message || '鐧诲綍澶辫触')
+ }
+
// 淇濆瓨token鍜岀敤鎴蜂俊鎭�
- setToken(response.token)
- setUserInfo(response.userInfo)
+ setToken(result.token)
+ setUserInfo(result.userInfo)
ElMessage.success('鐧诲綍鎴愬姛')
router.push('/')
@@ -153,6 +184,14 @@
text-align: center;
margin-bottom: 30px;
+ .logo {
+ width: 80px;
+ height: 80px;
+ object-fit: contain;
+ margin-bottom: 20px;
+ border-radius: 8px;
+ }
+
h2 {
color: #303133;
font-size: 24px;
diff --git a/wx/app.js b/wx/app.js
index 6b689f7..f105b87 100644
--- a/wx/app.js
+++ b/wx/app.js
@@ -134,50 +134,18 @@
}
console.log('=== 鍑嗗璋冪敤鍚庣wxLogin鎺ュ彛 ===')
- console.log('璇锋眰URL:', this.globalData.baseUrl)
+ console.log('璇锋眰URL:', 'http://localhost:8080/api/auth/wx-login')
console.log('璁惧淇℃伅:', deviceInfo)
console.log('璇锋眰鍙傛暟:', requestData)
console.log('璇锋眰寮�濮嬫椂闂�:', new Date().toISOString())
wx.request({
- url: this.globalData.baseUrl,
+ url: 'http://localhost:8080/api/auth/wx-login',
method: 'POST',
header: {
'Content-Type': 'application/json'
},
- data: {
- query: `
- mutation wxLogin($input: WxLoginRequest!) {
- wxLogin(input: $input) {
- token
- userInfo {
- userId
- name
- phone
- userType
- employee {
- id
- name
- }
- judge {
- id
- name
- }
- player {
- id
- name
- }
- }
- isNewUser
- loginRecordId
- sessionKey
- }
- }
- `,
- variables: {
- input: requestData
- }
- },
+ data: requestData,
success: (res) => {
console.log('=== 鍚庣wxLogin鎺ュ彛鍝嶅簲 ===')
console.log('鍝嶅簲鏃堕棿:', new Date().toISOString())
@@ -194,17 +162,28 @@
return
}
- if (res.data.errors) {
- console.error('鉂� GraphQL閿欒:', res.data.errors)
+ // 妫�鏌ユ槸鍚︽湁閿欒淇℃伅锛堥�傞厤涓嶅悓鐨勯敊璇搷搴旀牸寮忥級
+ if (res.data.error || res.data.message || res.data.success === false) {
+ const errorMsg = res.data.error || res.data.message || '鐧诲綍澶辫触'
+ console.error('鉂� 鐧诲綍澶辫触:', errorMsg)
wx.showToast({
- title: '鐧诲綍澶辫触',
+ title: errorMsg,
icon: 'error'
})
return
}
- if (res.data.data && res.data.data.wxLogin) {
- const loginResult = res.data.data.wxLogin
+ // 妫�鏌ュ搷搴旀暟鎹牸寮忥細鏀寔鐩存帴杩斿洖WxLoginResponse鎴栧寘瑁呮牸寮�
+ let loginResult = null
+ if (res.data.token && res.data.userInfo) {
+ // 鐩存帴杩斿洖WxLoginResponse鏍煎紡
+ loginResult = res.data
+ } else if (res.data.success && res.data.data) {
+ // 鍖呰鏍煎紡 {success: true, data: WxLoginResponse}
+ loginResult = res.data.data
+ }
+
+ if (loginResult) {
console.log('鉁� 鐧诲綍鎴愬姛!')
console.log('鐢ㄦ埛ID:', loginResult.userInfo.userId)
@@ -239,10 +218,10 @@
console.log('鈩癸笍 鑰佺敤鎴锋垨宸叉嫆缁濇墜鏈哄彿鎺堟潈锛岃烦杩囨巿鏉冨脊绐�')
}
} else {
- console.error('鉂� 鍝嶅簲鏁版嵁鏍煎紡閿欒锛岀己灏憌xLogin瀛楁')
+ console.error('鉂� 鍝嶅簲鏁版嵁鏍煎紡閿欒锛岀己灏戝繀瑕佸瓧娈�(token鎴杣serInfo)')
console.error('瀹為檯鍝嶅簲:', res.data)
wx.showToast({
- title: '鐧诲綍澶辫触',
+ title: '鐧诲綍鏁版嵁鏍煎紡閿欒',
icon: 'error'
})
}
@@ -297,12 +276,21 @@
// GraphQL璇锋眰灏佽
graphqlRequest(query, variables = {}) {
return new Promise((resolve, reject) => {
+ // 纭繚token鐨勪竴鑷存�э細浼樺厛浣跨敤globalData涓殑token锛屽鏋滄病鏈夊垯浠巗torage鑾峰彇
+ let token = this.globalData.token
+ if (!token) {
+ token = wx.getStorageSync('token')
+ if (token) {
+ this.globalData.token = token // 鍚屾鍒癵lobalData
+ }
+ }
+
wx.request({
url: this.globalData.baseUrl,
method: 'POST',
header: {
'Content-Type': 'application/json',
- 'Authorization': this.globalData.token ? `Bearer ${this.globalData.token}` : ''
+ 'Authorization': token ? `Bearer ${token}` : ''
},
data: {
query: query,
@@ -310,18 +298,32 @@
},
success: (res) => {
console.log('GraphQL鍝嶅簲:', res.data)
+
+ // 妫�鏌TTP鐘舵�佺爜
+ if (res.statusCode !== 200) {
+ console.error('GraphQL HTTP閿欒:', res.statusCode)
+ reject(new Error(`HTTP閿欒: ${res.statusCode}`))
+ return
+ }
+
+ // 妫�鏌raphQL閿欒
if (res.data.errors) {
console.error('GraphQL閿欒:', res.data.errors)
- reject(res.data.errors)
- } else if (res.data.data) {
+ reject(new Error(res.data.errors[0]?.message || 'GraphQL璇锋眰閿欒'))
+ return
+ }
+
+ // 妫�鏌ユ暟鎹�
+ if (res.data.data !== undefined) {
resolve(res.data.data)
} else {
console.error('GraphQL鍝嶅簲寮傚父:', res.data)
- reject('璇锋眰澶辫触')
+ reject(new Error('GraphQL鍝嶅簲鏁版嵁寮傚父'))
}
},
fail: (err) => {
- reject(err)
+ console.error('GraphQL缃戠粶璇锋眰澶辫触:', err)
+ reject(new Error('缃戠粶璇锋眰澶辫触'))
}
})
})
diff --git a/wx/app.json b/wx/app.json
index a2e4035..e5d2a32 100644
--- a/wx/app.json
+++ b/wx/app.json
@@ -4,9 +4,13 @@
"pages/activity/detail",
"pages/registration/registration",
"pages/profile/profile",
+ "pages/profile/personal-info",
+ "pages/project/detail",
"pages/message/message",
+ "pages/review/index",
"pages/judge/review",
- "pages/video/video"
+ "pages/video/video",
+ "pages/webview/webview"
],
"window": {
"backgroundTextStyle": "light",
diff --git a/wx/images/default-avatar.svg b/wx/images/default-avatar.svg
new file mode 100644
index 0000000..0fe237f
--- /dev/null
+++ b/wx/images/default-avatar.svg
@@ -0,0 +1,10 @@
+<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <!-- 鑳屾櫙鍦嗗舰 -->
+ <circle cx="50" cy="50" r="50" fill="#f0f0f0"/>
+
+ <!-- 澶撮儴 -->
+ <circle cx="50" cy="35" r="18" fill="#d0d0d0"/>
+
+ <!-- 韬綋 -->
+ <ellipse cx="50" cy="75" rx="25" ry="20" fill="#d0d0d0"/>
+</svg>
\ No newline at end of file
diff --git a/wx/pages/judge/review.js b/wx/pages/judge/review.js
index babef8a..27afd37 100644
--- a/wx/pages/judge/review.js
+++ b/wx/pages/judge/review.js
@@ -9,7 +9,7 @@
// 鎻愪氦浣滃搧淇℃伅
submission: null,
- submissionId: '',
+ activityPlayerId: '',
// 娲诲姩淇℃伅
activity: null,
@@ -53,7 +53,7 @@
onLoad(options) {
if (options.id) {
- this.setData({ submissionId: options.id })
+ this.setData({ activityPlayerId: options.id })
this.loadSubmissionDetail()
}
},
@@ -71,99 +71,130 @@
this.setData({ loading: true })
const query = `
- query GetSubmissionDetail($id: ID!) {
- submission(id: $id) {
+ query GetActivityPlayerDetail($id: ID!) {
+ activityPlayerDetail(id: $id) {
id
- title
+ projectName
description
- files {
+ activityName
+ stageId
+ state
+ playerInfo {
+ id
+ name
+ phone
+ gender
+ birthday
+ education
+ introduction
+ userInfo {
+ userId
+ name
+ phone
+ avatarUrl
+ }
+ }
+ regionInfo {
+ id
+ name
+ fullPath
+ }
+ submissionFiles {
id
name
url
- type
- size
+ fullUrl
+ fileExt
+ fileSize
+ mediaType
+ thumbUrl
+ fullThumbUrl
}
- images
- videos
- submittedAt
- status
- participant {
- id
- name
- school
- major
- avatar
- }
- team {
- id
- name
- members {
- id
- name
- role
- }
- }
- activity {
- id
- title
- description
- judgeCriteria {
+ ratingForm {
+ schemeId
+ schemeName
+ totalMaxScore
+ items {
id
name
description
maxScore
weight
+ sortOrder
}
- }
- myReview {
- id
- scores
- comment
- totalScore
- status
- reviewedAt
}
}
}
`
- const result = await graphqlRequest(query, { id: this.data.submissionId })
+ const result = await graphqlRequest(query, { id: this.data.activityPlayerId })
- if (result && result.submission) {
- const submission = result.submission
+ if (result && result.activityPlayerDetail) {
+ const detail = result.activityPlayerDetail
- // 涓烘瘡涓枃浠舵坊鍔犱笅杞界姸鎬�
- if (submission.files) {
- submission.files = submission.files.map(file => ({
- ...file,
+ // 鏋勫缓submission瀵硅薄浠ュ吋瀹圭幇鏈夌殑WXML妯℃澘
+ const submission = {
+ id: detail.id,
+ title: detail.projectName,
+ description: detail.description,
+ files: detail.submissionFiles ? detail.submissionFiles.map(file => ({
+ id: file.id,
+ name: file.name,
+ url: file.fullUrl || file.url,
+ type: file.fileExt,
+ size: file.fileSize,
isDownloading: this.data.downloadingFiles.indexOf(file.id) > -1
- }))
+ })) : [],
+ images: detail.submissionFiles ? detail.submissionFiles
+ .filter(file => file.mediaType === 1)
+ .map(file => file.fullUrl || file.url) : [],
+ videos: detail.submissionFiles ? detail.submissionFiles
+ .filter(file => file.mediaType === 2)
+ .map(file => file.fullUrl || file.url) : [],
+ participant: {
+ id: detail.playerInfo.id,
+ name: detail.playerInfo.name,
+ school: detail.regionInfo ? detail.regionInfo.name : '',
+ major: detail.playerInfo.education || '',
+ avatar: detail.playerInfo.userInfo?.avatarUrl || '/images/default-avatar.svg'
+ },
+ status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING'
+ }
+
+ // 鏋勫缓activity瀵硅薄
+ const activity = {
+ id: detail.stageId,
+ title: detail.activityName,
+ description: detail.description,
+ judgeCriteria: detail.ratingForm ? detail.ratingForm.items || [] : []
}
// 鍒濆鍖栬瘎鍒嗘暟鎹�
const scores = {}
let maxScore = 0
- if (submission.activity.judgeCriteria) {
- submission.activity.judgeCriteria.forEach(criterion => {
- scores[criterion.id] = submission.myReview ?
- submission.myReview.scores[criterion.id] || 0 : 0
+ if (activity.judgeCriteria) {
+ activity.judgeCriteria.forEach(criterion => {
+ scores[criterion.id] = 0 // 鏆傛椂璁句负0锛屽悗缁渶瑕佹煡璇㈠凡鏈夎瘎鍒�
maxScore += criterion.maxScore
})
}
this.setData({
submission,
- activity: submission.activity,
- criteria: submission.activity.judgeCriteria || [],
+ activity,
+ criteria: activity.judgeCriteria || [],
scores,
maxScore,
- existingReview: submission.myReview,
- reviewStatus: submission.myReview ? submission.myReview.status : 'PENDING',
- comment: submission.myReview ? submission.myReview.comment : ''
+ existingReview: null, // 鏆傛椂璁句负null锛屽悗缁渶瑕佹煡璇㈠凡鏈夎瘎鍒�
+ reviewStatus: 'PENDING',
+ comment: ''
})
this.calculateTotalScore()
+
+ // 妫�鏌ユ槸鍚﹀凡鏈夎瘎鍒�
+ this.checkReviewStatus()
}
} catch (error) {
console.error('鍔犺浇浣滃搧璇︽儏澶辫触:', error)
@@ -180,30 +211,50 @@
async checkReviewStatus() {
try {
const query = `
- query CheckReviewStatus($submissionId: ID!) {
- reviewStatus(submissionId: $submissionId) {
+ query GetCurrentJudgeRating($activityPlayerId: ID!) {
+ currentJudgeRating(activityPlayerId: $activityPlayerId) {
+ id
+ totalScore
+ comment
status
- canReview
- deadline
+ ratedAt
+ items {
+ ratingItemId
+ ratingItemName
+ score
+ maxScore
+ }
}
}
`
- const result = await graphqlRequest(query, { submissionId: this.data.submissionId })
+ const result = await graphqlRequest(query, { activityPlayerId: this.data.activityPlayerId })
- if (result && result.reviewStatus) {
- const { status, canReview, deadline } = result.reviewStatus
+ if (result && result.currentJudgeRating) {
+ const rating = result.currentJudgeRating
- if (!canReview) {
- wx.showModal({
- title: '鏃犳硶璇勫',
- content: deadline ? `璇勫宸叉埅姝紙鎴鏃堕棿锛�${formatDate(deadline)}锛塦 : '褰撳墠鏃犳硶杩涜璇勫',
- showCancel: false,
- success: () => {
- wx.navigateBack()
- }
+ // 濡傛灉宸叉湁璇勫垎锛屽~鍏呮暟鎹�
+ const scores = {}
+ let totalScore = 0
+
+ if (rating.items) {
+ rating.items.forEach(item => {
+ scores[item.ratingItemId] = item.score
+ totalScore += item.score
})
}
+
+ this.setData({
+ scores,
+ totalScore,
+ comment: rating.comment || '',
+ existingReview: rating,
+ reviewStatus: rating.status || 'COMPLETED'
+ })
+
+ console.log('宸插姞杞界幇鏈夎瘎鍒�:', rating)
+ } else {
+ console.log('褰撳墠璇勫灏氭湭璇勫垎')
}
} catch (error) {
console.error('妫�鏌ヨ瘎瀹$姸鎬佸け璐�:', error)
@@ -388,34 +439,37 @@
try {
wx.showLoading({ title: '淇濆瓨涓�...' })
- const { submissionId, scores, comment, totalScore } = this.data
+ const { activityPlayerId, scores, comment, criteria, activity } = this.data
+
+ // 鏋勫缓璇勫垎椤规暟缁�
+ const ratings = criteria.map(criterion => ({
+ itemId: criterion.id,
+ score: scores[criterion.id] || 0
+ }))
const mutation = `
- mutation SaveReviewDraft($input: ReviewDraftInput!) {
- saveReviewDraft(input: $input) {
- success
- review {
- id
- status
- }
- }
+ mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) {
+ saveActivityPlayerRating(input: $input)
}
`
const input = {
- submissionId,
- scores,
- comment: comment.trim(),
- totalScore
+ activityPlayerId,
+ stageId: activity.stageId,
+ ratings,
+ comment: comment.trim()
}
const result = await graphqlRequest(mutation, { input })
- if (result && result.saveReviewDraft.success) {
+ if (result && result.saveActivityPlayerRating) {
wx.showToast({
title: '鑽夌宸蹭繚瀛�',
icon: 'success'
})
+
+ // 閲嶆柊鍔犺浇璇勫垎鐘舵��
+ await this.checkReviewStatus()
}
} catch (error) {
console.error('淇濆瓨鑽夌澶辫触:', error)
@@ -451,41 +505,42 @@
this.setData({ submitting: true })
wx.showLoading({ title: '鎻愪氦涓�...' })
- const { submissionId, scores, comment, totalScore } = this.data
+ const { activityPlayerId, scores, comment, criteria, activity } = this.data
+
+ // 鏋勫缓璇勫垎椤规暟缁�
+ const ratings = criteria.map(criterion => ({
+ itemId: criterion.id,
+ score: scores[criterion.id] || 0
+ }))
const mutation = `
- mutation SubmitReview($input: ReviewSubmitInput!) {
- submitReview(input: $input) {
- success
- review {
- id
- status
- reviewedAt
- }
- }
+ mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) {
+ saveActivityPlayerRating(input: $input)
}
`
const input = {
- submissionId,
- scores,
- comment: comment.trim(),
- totalScore
+ activityPlayerId,
+ stageId: activity.stageId,
+ ratings,
+ comment: comment.trim()
}
const result = await graphqlRequest(mutation, { input })
- if (result && result.submitReview.success) {
+ if (result && result.saveActivityPlayerRating) {
wx.showToast({
- title: '璇勫鎻愪氦鎴愬姛',
+ title: '璇勫垎鎻愪氦鎴愬姛',
icon: 'success'
})
// 鏇存柊鐘舵��
this.setData({
- reviewStatus: 'COMPLETED',
- existingReview: result.submitReview.review
+ reviewStatus: 'COMPLETED'
})
+
+ // 閲嶆柊鍔犺浇璇勫垎鐘舵��
+ await this.checkReviewStatus()
// 寤惰繜杩斿洖涓婁竴椤�
setTimeout(() => {
@@ -493,7 +548,7 @@
}, 1500)
}
} catch (error) {
- console.error('鎻愪氦璇勫澶辫触:', error)
+ console.error('鎻愪氦璇勫垎澶辫触:', error)
wx.showToast({
title: '鎻愪氦澶辫触',
icon: 'error'
@@ -507,7 +562,7 @@
// 鏌ョ湅鍏朵粬璇勫
onViewOtherReviews() {
wx.navigateTo({
- url: `/pages/judge/reviews?submissionId=${this.data.submissionId}`
+ url: `/pages/judge/reviews?activityPlayerId=${this.data.activityPlayerId}`
})
},
diff --git a/wx/pages/profile/personal-info.js b/wx/pages/profile/personal-info.js
new file mode 100644
index 0000000..1fbdb5f
--- /dev/null
+++ b/wx/pages/profile/personal-info.js
@@ -0,0 +1,389 @@
+// pages/profile/personal-info.js
+const app = getApp()
+const { graphqlRequest } = require('../../lib/utils')
+
+Page({
+ data: {
+ userInfo: {
+ avatar: '',
+ name: '',
+ gender: '',
+ phone: '',
+ birthday: ''
+ },
+ currentDate: '',
+ saving: false
+ },
+
+ onLoad(options) {
+ this.initCurrentDate()
+ this.loadUserInfo()
+ },
+
+ // 鍒濆鍖栧綋鍓嶆棩鏈�
+ initCurrentDate() {
+ const now = new Date()
+ const year = now.getFullYear()
+ const month = String(now.getMonth() + 1).padStart(2, '0')
+ const day = String(now.getDate()).padStart(2, '0')
+ this.setData({
+ currentDate: `${year}-${month}-${day}`
+ })
+ },
+
+ // 鍔犺浇鐢ㄦ埛淇℃伅
+ async loadUserInfo() {
+ try {
+ // 鍏堜粠鍏ㄥ眬鏁版嵁鑾峰彇
+ const globalUserInfo = app.globalData.userInfo || {}
+
+ // 浠庡悗绔幏鍙栨渶鏂扮敤鎴蜂俊鎭�
+ const query = `
+ query GetUserProfile {
+ userProfile {
+ id
+ name
+ avatar
+ phone
+ gender
+ birthday
+ }
+ }
+ `
+
+ const result = await graphqlRequest(query)
+ let userInfo = globalUserInfo
+
+ if (result && result.userProfile) {
+ userInfo = result.userProfile
+ // 鏇存柊鍏ㄥ眬鏁版嵁
+ app.globalData.userInfo = userInfo
+ }
+
+ this.setData({
+ userInfo: {
+ avatar: userInfo.avatar || '',
+ name: userInfo.name || '',
+ gender: userInfo.gender || '',
+ phone: userInfo.phone || '',
+ birthday: userInfo.birthday || ''
+ }
+ })
+ } catch (error) {
+ console.error('鍔犺浇鐢ㄦ埛淇℃伅澶辫触:', error)
+ // 浣跨敤鍏ㄥ眬鏁版嵁浣滀负澶囬��
+ const globalUserInfo = app.globalData.userInfo || {}
+ this.setData({
+ userInfo: {
+ avatar: globalUserInfo.avatar || '',
+ name: globalUserInfo.name || '',
+ gender: globalUserInfo.gender || '',
+ phone: globalUserInfo.phone || '',
+ birthday: globalUserInfo.birthday || ''
+ }
+ })
+ }
+ },
+
+ // 閫夋嫨澶村儚
+ chooseAvatar() {
+ wx.chooseMedia({
+ count: 1,
+ mediaType: ['image'],
+ sourceType: ['album', 'camera'],
+ maxDuration: 30,
+ camera: 'back',
+ success: (res) => {
+ const tempFilePath = res.tempFiles[0].tempFilePath
+ this.uploadAvatar(tempFilePath)
+ },
+ fail: (err) => {
+ console.error('閫夋嫨澶村儚澶辫触:', err)
+ wx.showToast({
+ title: '閫夋嫨澶村儚澶辫触',
+ icon: 'none'
+ })
+ }
+ })
+ },
+
+ // 涓婁紶澶村儚
+ async uploadAvatar(filePath) {
+ wx.showLoading({
+ title: '涓婁紶涓�...'
+ })
+
+ try {
+ // 鏆傛椂淇濆瓨鏈湴璺緞锛屽湪淇濆瓨鐢ㄦ埛淇℃伅鏃朵竴璧峰鐞�
+ this.setData({
+ 'userInfo.avatar': filePath,
+ localAvatarPath: filePath // 淇濆瓨鏈湴璺緞鐢ㄤ簬鍚庣画涓婁紶
+ })
+
+ wx.hideLoading()
+ wx.showToast({
+ title: '澶村儚閫夋嫨鎴愬姛',
+ icon: 'success'
+ })
+ } catch (error) {
+ wx.hideLoading()
+ wx.showToast({
+ title: '澶村儚閫夋嫨澶辫触',
+ icon: 'none'
+ })
+ }
+ },
+
+ // 濮撳悕杈撳叆
+ onNameInput(e) {
+ this.setData({
+ 'userInfo.name': e.detail.value
+ })
+ },
+
+ // 閫夋嫨鎬у埆
+ selectGender(e) {
+ const gender = e.currentTarget.dataset.gender
+ this.setData({
+ 'userInfo.gender': gender
+ })
+ },
+
+ // 鑾峰彇鎵嬫満鍙�
+ async getPhoneNumber(e) {
+ if (e.detail.errMsg === 'getPhoneNumber:ok') {
+ try {
+ wx.showLoading({
+ title: '鑾峰彇涓�...'
+ })
+
+ // TODO: 璋冪敤鍚庣API瑙e瘑鎵嬫満鍙�
+ const mutation = `
+ mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!) {
+ decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv) {
+ phoneNumber
+ }
+ }
+ `
+
+ const variables = {
+ encryptedData: e.detail.encryptedData,
+ iv: e.detail.iv
+ }
+
+ const result = await graphqlRequest(mutation, variables)
+
+ if (result && result.decryptPhoneNumber) {
+ this.setData({
+ 'userInfo.phone': result.decryptPhoneNumber.phoneNumber
+ })
+ wx.showToast({
+ title: '鎵嬫満鍙疯幏鍙栨垚鍔�',
+ icon: 'success'
+ })
+ } else {
+ throw new Error('瑙e瘑澶辫触')
+ }
+ } catch (error) {
+ console.error('鑾峰彇鎵嬫満鍙峰け璐�:', error)
+ wx.showToast({
+ title: '鑾峰彇鎵嬫満鍙峰け璐�',
+ icon: 'none'
+ })
+ } finally {
+ wx.hideLoading()
+ }
+ } else {
+ wx.showToast({
+ title: '鑾峰彇鎵嬫満鍙峰け璐�',
+ icon: 'none'
+ })
+ }
+ },
+
+ // 鐢熸棩閫夋嫨
+ onBirthdayChange(e) {
+ this.setData({
+ 'userInfo.birthday': e.detail.value
+ })
+ },
+
+ // 淇濆瓨鐢ㄦ埛淇℃伅
+ async saveUserInfo() {
+ const { userInfo, localAvatarPath } = this.data
+
+ // 楠岃瘉蹇呭~瀛楁
+ if (!userInfo.name.trim()) {
+ wx.showToast({
+ title: '璇疯緭鍏ュ鍚�',
+ icon: 'none'
+ })
+ return
+ }
+
+ if (!userInfo.gender) {
+ wx.showToast({
+ title: '璇烽�夋嫨鎬у埆',
+ icon: 'none'
+ })
+ return
+ }
+
+ if (!userInfo.phone) {
+ wx.showToast({
+ title: '璇疯幏鍙栨墜鏈哄彿',
+ icon: 'none'
+ })
+ return
+ }
+
+ this.setData({ saving: true })
+
+ try {
+ // 绗竴姝ワ細淇濆瓨鐢ㄦ埛鍩烘湰淇℃伅锛堜笉鍖呭惈澶村儚锛�
+ const mutation = `
+ mutation SaveUserInfo($input: UserInput!) {
+ saveUserInfo(input: $input) {
+ id
+ name
+ avatar
+ phone
+ gender
+ birthday
+ wxOpenId
+ unionId
+ }
+ }
+ `
+
+ const variables = {
+ input: {
+ name: userInfo.name.trim(),
+ phone: userInfo.phone,
+ gender: userInfo.gender,
+ birthday: userInfo.birthday
+ }
+ }
+
+ const result = await graphqlRequest(mutation, variables)
+
+ if (result && result.saveUserInfo) {
+ const savedUserInfo = result.saveUserInfo
+
+ // 绗簩姝ワ細濡傛灉鏈夋柊澶村儚锛屼笂浼犲埌COS骞朵繚瀛樺埌t_media
+ if (localAvatarPath) {
+ await this.uploadAvatarWithUserId(savedUserInfo.id)
+ }
+
+ // 閲嶆柊鑾峰彇鐢ㄦ埛淇℃伅锛堝寘鍚ご鍍忥級
+ await this.loadUserInfo()
+
+ // 鏇存柊鍏ㄥ眬鐢ㄦ埛淇℃伅
+ app.globalData.userInfo = { ...app.globalData.userInfo, ...this.data.userInfo }
+
+ // 淇濆瓨鍒版湰鍦板瓨鍌�
+ wx.setStorageSync('userInfo', app.globalData.userInfo)
+
+ wx.showToast({
+ title: '淇濆瓨鎴愬姛',
+ icon: 'success'
+ })
+
+ // 寤惰繜杩斿洖涓婁竴椤�
+ setTimeout(() => {
+ wx.navigateBack()
+ }, 1500)
+ } else {
+ throw new Error('淇濆瓨澶辫触')
+ }
+ } catch (error) {
+ console.error('淇濆瓨鐢ㄦ埛淇℃伅澶辫触:', error)
+ wx.showToast({
+ title: '淇濆瓨澶辫触',
+ icon: 'none'
+ })
+ } finally {
+ this.setData({ saving: false })
+ }
+ },
+
+ // 浣跨敤鐢ㄦ埛ID涓婁紶澶村儚
+ async uploadAvatarWithUserId(userId) {
+ const { localAvatarPath } = this.data
+ if (!localAvatarPath) return
+
+ try {
+ // 寮曞叆cosUtil
+ const cosUtil = require('../../lib/cosUtil')
+
+ // 绗竴姝ワ細涓婁紶鍒癈OS
+ const uploadResult = await cosUtil.uploadAvatar(
+ localAvatarPath,
+ 'avatar.jpg',
+ (percent) => {
+ console.log('澶村儚涓婁紶杩涘害:', percent + '%')
+ }
+ )
+
+ // 绗簩姝ワ細淇濆瓨濯掍綋璁板綍鍒版暟鎹簱
+ await this.saveMediaRecord({
+ targetType: 'player', // 鐢ㄦ埛澶村儚
+ targetId: parseInt(userId),
+ path: uploadResult.key,
+ fileName: uploadResult.fileName || 'avatar.jpg',
+ fileExt: this.getFileExtension(uploadResult.fileName || 'avatar.jpg'),
+ fileSize: uploadResult.fileSize,
+ mediaType: 1 // 鍥剧墖
+ })
+
+ // 娓呯┖鏈湴璺緞
+ this.setData({
+ localAvatarPath: ''
+ })
+
+ console.log('澶村儚涓婁紶鎴愬姛:', uploadResult)
+ } catch (error) {
+ console.error('澶村儚涓婁紶澶辫触:', error)
+ throw error
+ }
+ },
+
+ // 淇濆瓨濯掍綋璁板綍鍒版暟鎹簱
+ async saveMediaRecord(mediaData) {
+ const mutation = `
+ mutation SaveMediaV2($input: MediaSaveInput!) {
+ saveMediaV2(input: $input) {
+ success
+ message
+ mediaId
+ }
+ }
+ `
+
+ const variables = {
+ input: {
+ targetType: mediaData.targetType,
+ targetId: mediaData.targetId,
+ path: mediaData.path,
+ fileName: mediaData.fileName,
+ fileExt: mediaData.fileExt,
+ fileSize: mediaData.fileSize,
+ mediaType: mediaData.mediaType
+ }
+ }
+
+ try {
+ const result = await graphqlRequest(mutation, variables)
+ console.log('濯掍綋璁板綍淇濆瓨鎴愬姛:', result.saveMediaV2)
+ return result.saveMediaV2
+ } catch (error) {
+ console.error('濯掍綋璁板綍淇濆瓨澶辫触:', error)
+ throw error
+ }
+ },
+
+ // 鑾峰彇鏂囦欢鎵╁睍鍚�
+ getFileExtension(fileName) {
+ return fileName.split('.').pop().toLowerCase()
+ }
+})
\ No newline at end of file
diff --git a/wx/pages/profile/personal-info.json b/wx/pages/profile/personal-info.json
new file mode 100644
index 0000000..cd22cc5
--- /dev/null
+++ b/wx/pages/profile/personal-info.json
@@ -0,0 +1,6 @@
+{
+ "navigationBarTitleText": "涓汉淇℃伅",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f5f5f5"
+}
\ No newline at end of file
diff --git a/wx/pages/profile/personal-info.wxml b/wx/pages/profile/personal-info.wxml
new file mode 100644
index 0000000..093f73f
--- /dev/null
+++ b/wx/pages/profile/personal-info.wxml
@@ -0,0 +1,99 @@
+<!--pages/profile/personal-info.wxml-->
+<view class="container">
+ <!-- 澶村儚璁剧疆鍖哄煙 -->
+ <view class="avatar-section">
+ <view class="avatar-row">
+ <view class="avatar-wrapper" bindtap="chooseAvatar">
+ <image class="avatar" src="{{userInfo.avatar || '/images/default-avatar.svg'}}" mode="aspectFill"></image>
+ <view class="avatar-overlay">
+ <text class="camera-icon">馃摲</text>
+ </view>
+ </view>
+ <text class="avatar-tip" bindtap="chooseAvatar">鐐瑰嚮鏇存崲澶村儚</text>
+ </view>
+ </view>
+
+ <!-- 涓汉淇℃伅琛ㄥ崟 -->
+ <view class="form-section">
+ <!-- 濮撳悕 -->
+ <view class="form-item">
+ <text class="label required">濮撳悕</text>
+ <input
+ class="input"
+ value="{{userInfo.name}}"
+ placeholder="璇疯緭鍏ュ鍚�"
+ bindinput="onNameInput"
+ maxlength="20"
+ />
+ </view>
+
+ <!-- 鎬у埆 -->
+ <view class="form-item">
+ <text class="label required">鎬у埆</text>
+ <view class="gender-selector">
+ <view
+ class="gender-option {{userInfo.gender === 'MALE' || userInfo.gender === '鐢�' ? 'active' : ''}}"
+ bindtap="selectGender"
+ data-gender="MALE"
+ >
+ <text>鐢�</text>
+ </view>
+ <view
+ class="gender-option {{userInfo.gender === 'FEMALE' || userInfo.gender === '濂�' ? 'active' : ''}}"
+ bindtap="selectGender"
+ data-gender="FEMALE"
+ >
+ <text>濂�</text>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鐢佃瘽鍙风爜 -->
+ <view class="form-item">
+ <text class="label required">鐢佃瘽鍙风爜</text>
+ <view class="phone-wrapper">
+ <input
+ class="input phone-input"
+ value="{{userInfo.phone}}"
+ placeholder="璇锋巿鏉冭幏鍙栨墜鏈哄彿"
+ disabled="{{true}}"
+ />
+ <button
+ class="phone-btn"
+ open-type="getPhoneNumber"
+ bindgetphonenumber="getPhoneNumber"
+ wx:if="{{!userInfo.phone}}"
+ >
+ 鑾峰彇鎵嬫満鍙�
+ </button>
+ <text class="phone-status" wx:else>宸茬粦瀹�</text>
+ </view>
+ </view>
+
+ <!-- 鐢熸棩 -->
+ <view class="form-item">
+ <text class="label">鐢熸棩</text>
+ <picker
+ mode="date"
+ value="{{userInfo.birthday}}"
+ bindchange="onBirthdayChange"
+ start="1900-01-01"
+ end="{{currentDate}}"
+ >
+ <view class="picker-wrapper">
+ <text class="picker-text">{{userInfo.birthday || '璇烽�夋嫨鐢熸棩'}}</text>
+ <text class="picker-arrow">></text>
+ </view>
+ </picker>
+ </view>
+
+
+ </view>
+
+ <!-- 淇濆瓨鎸夐挳 -->
+ <view class="save-section">
+ <button class="save-btn" bindtap="saveUserInfo" loading="{{saving}}">
+ {{saving ? '淇濆瓨涓�...' : '淇濆瓨'}}
+ </button>
+ </view>
+</view>
\ No newline at end of file
diff --git a/wx/pages/profile/personal-info.wxss b/wx/pages/profile/personal-info.wxss
new file mode 100644
index 0000000..bca0b32
--- /dev/null
+++ b/wx/pages/profile/personal-info.wxss
@@ -0,0 +1,249 @@
+/* pages/profile/personal-info.wxss */
+.container {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ padding-bottom: 120rpx;
+}
+
+/* 澶村儚璁剧疆鍖哄煙 */
+.avatar-section {
+ background-color: #ffffff;
+ padding: 40rpx;
+ margin-bottom: 20rpx;
+}
+
+.avatar-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 120rpx;
+}
+
+.avatar-wrapper {
+ position: relative;
+ display: inline-block;
+}
+
+.avatar {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 60rpx;
+ border: 4rpx solid #e0e0e0;
+}
+
+.avatar-overlay {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 40rpx;
+ height: 40rpx;
+ background-color: #4CAF50;
+ border-radius: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 3rpx solid #ffffff;
+}
+
+.camera-icon {
+ font-size: 20rpx;
+ color: #ffffff;
+}
+
+.avatar-tip {
+ font-size: 28rpx;
+ color: #4CAF50;
+ font-weight: 500;
+}
+
+/* 琛ㄥ崟鍖哄煙 */
+.form-section {
+ background-color: #ffffff;
+ margin-bottom: 20rpx;
+}
+
+.form-item {
+ padding: 30rpx 40rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+ display: flex;
+ align-items: center;
+}
+
+.form-item:last-child {
+ border-bottom: none;
+}
+
+.label {
+ width: 160rpx;
+ font-size: 32rpx;
+ color: #333333;
+ font-weight: 500;
+ margin-bottom: 20rpx;
+ display: block;
+}
+
+.label.required::after {
+ content: ' *';
+ color: #ff4444;
+}
+
+.input {
+ flex: 1;
+ font-size: 32rpx;
+ color: #333333;
+ text-align: right;
+}
+
+.input[disabled] {
+ color: #999999;
+}
+
+/* 鎬у埆閫夋嫨鍣� */
+.gender-selector {
+ flex: 1;
+ display: flex;
+ justify-content: flex-end;
+ gap: 20rpx;
+}
+
+.gender-option {
+ padding: 16rpx 32rpx;
+ border: 2rpx solid #e0e0e0;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+ color: #666666;
+ background-color: #ffffff;
+ transition: all 0.3s ease;
+}
+
+.gender-option.active {
+ background-color: #4CAF50;
+ border-color: #4CAF50;
+ color: #ffffff;
+}
+
+/* 鎵嬫満鍙峰尯鍩� */
+.phone-wrapper {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 20rpx;
+}
+
+.phone-input {
+ flex: 1;
+ text-align: right;
+}
+
+.phone-btn {
+ padding: 12rpx 24rpx;
+ background-color: #4CAF50;
+ color: #ffffff;
+ border: none;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ line-height: 1;
+}
+
+.phone-btn::after {
+ border: none;
+}
+
+.phone-status {
+ font-size: 28rpx;
+ color: #4CAF50;
+}
+
+/* 閫夋嫨鍣ㄦ牱寮� */
+.picker-wrapper {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10rpx;
+}
+
+.picker-text {
+ font-size: 32rpx;
+ color: #333333;
+}
+
+.picker-arrow {
+ font-size: 24rpx;
+ color: #999999;
+}
+
+/* 鏂囨湰鍩熸牱寮� */
+.form-item:has(.textarea) {
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.textarea {
+ width: 100%;
+ min-height: 120rpx;
+ font-size: 32rpx;
+ color: #333333;
+ margin-top: 20rpx;
+ padding: 20rpx;
+ background-color: #f8f8f8;
+ border-radius: 12rpx;
+ border: 1rpx solid #e0e0e0;
+}
+
+.char-count {
+ align-self: flex-end;
+ font-size: 24rpx;
+ color: #999999;
+ margin-top: 10rpx;
+}
+
+/* 淇濆瓨鎸夐挳 */
+.save-section {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #ffffff;
+ padding: 30rpx 40rpx;
+ border-top: 1rpx solid #e0e0e0;
+}
+
+.save-btn {
+ width: 100%;
+ height: 88rpx;
+ background-color: #4CAF50;
+ color: #ffffff;
+ border: none;
+ border-radius: 44rpx;
+ font-size: 32rpx;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.save-btn::after {
+ border: none;
+}
+
+.save-btn[loading] {
+ background-color: #81C784;
+}
+
+/* 鍝嶅簲寮忛�傞厤 */
+@media (max-width: 375px) {
+ .form-item {
+ padding: 25rpx 30rpx;
+ }
+
+ .label {
+ width: 140rpx;
+ font-size: 30rpx;
+ }
+
+ .input {
+ font-size: 30rpx;
+ }
+}
\ No newline at end of file
diff --git a/wx/pages/profile/profile.js b/wx/pages/profile/profile.js
index 7e68688..15cedc9 100644
--- a/wx/pages/profile/profile.js
+++ b/wx/pages/profile/profile.js
@@ -16,20 +16,23 @@
awards: 0
},
- // 鎴戠殑鎶ュ悕璁板綍
- registrations: [],
- registrationLoading: false,
+
+
+ // 鐢ㄦ埛椤圭洰
+ userProjects: [],
+ projectsLoading: false,
// 瑙掕壊鐩稿叧
userRoles: [],
isJudge: false,
isOrganizer: false,
+ hasPlayer: false,
// 璇勫鐩稿叧鏁版嵁
judgeStats: {
pendingReviews: 0,
completedReviews: 0,
- totalReviews: 0
+ studentUnReviewedCount: 0
},
// 涓诲姙鏂圭浉鍏虫暟鎹�
@@ -141,14 +144,14 @@
this.loadUserInfo()
this.loadUserStats()
- this.loadRecentRegistrations()
+ this.loadUserProjects()
},
onShow() {
// 椤甸潰鏄剧ず鏃跺埛鏂版暟鎹�
this.loadUserInfo()
this.loadUserStats()
- this.loadRecentRegistrations()
+ this.loadUserProjects()
// 鍒濆鍖栬嚜瀹氫箟 tabbar
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().init()
@@ -161,13 +164,18 @@
// 鍒锋柊鏁版嵁
async refreshData() {
+ this.setData({ loading: true })
+
try {
await Promise.all([
this.loadUserInfo(),
this.loadUserStats(),
- this.loadRecentRegistrations()
+ this.loadUserProjects()
])
+ } catch (error) {
+ console.error('鍒锋柊鏁版嵁澶辫触:', error)
} finally {
+ this.setData({ loading: false })
wx.stopPullDownRefresh()
}
},
@@ -190,6 +198,12 @@
grade
roles
createdAt
+ player {
+ id
+ name
+ phone
+ description
+ }
}
}
`
@@ -203,6 +217,7 @@
const userRoles = userInfo.roles || []
const isJudge = userRoles.includes('JUDGE')
const isOrganizer = userRoles.includes('ORGANIZER')
+ const hasPlayer = userInfo.player && userInfo.player.id
// 澶勭悊澶村儚鏂囧瓧
const avatarText = (userInfo.name || '鐢ㄦ埛').substring(0, 1)
@@ -212,7 +227,8 @@
avatarText,
userRoles,
isJudge,
- isOrganizer
+ isOrganizer,
+ hasPlayer
})
// 鏇存柊鍏ㄥ眬鐢ㄦ埛淇℃伅
@@ -265,54 +281,80 @@
},
// 鍔犺浇鏈�杩戞姤鍚嶈褰�
- async loadRecentRegistrations() {
+ // 鍔犺浇鐢ㄦ埛椤圭洰
+ async loadUserProjects() {
+ this.setData({ projectsLoading: true })
+
try {
- this.setData({ registrationLoading: true })
-
const query = `
- query GetRecentRegistrations {
- myRegistrations(limit: 5) {
+ query GetMyProjects {
+ myProjects {
id
- activity {
- id
- title
- coverImage {
- id
- name
- path
- fullUrl
- fullThumbUrl
- mediaType
- }
- startTime
- status
- }
+ projectName
+ activityName
status
- registrationTime
+ statusText
+ createTime
+ submissionFiles {
+ id
+ name
+ path
+ fullUrl
+ fullThumbUrl
+ fileExt
+ mediaType
+ }
}
}
`
const result = await graphqlRequest(query)
- if (result && result.myRegistrations) {
- // 涓烘瘡涓椿鍔ㄦ坊鍔犳爣棰樻枃瀛�
- const registrations = result.myRegistrations.map(registration => ({
- ...registration,
- activity: {
- ...registration.activity,
- titleText: (registration.activity.title || '娲诲姩').substring(0, 2)
+ if (result && result.myProjects) {
+ const projects = result.myProjects.map(project => {
+ // 鑾峰彇椤圭洰鐨勭涓�涓獟浣撴枃浠朵綔涓虹缉鐣ュ浘
+ let thumbnailUrl = ''
+ let iconClass = this.getProjectIcon(project.status)
+
+ if (project.submissionFiles && project.submissionFiles.length > 0) {
+ const firstFile = project.submissionFiles[0]
+
+ // 鏍规嵁鏂囦欢绫诲瀷璁剧疆缂╃暐鍥惧拰鍥炬爣
+ if (firstFile.mediaType === 'IMAGE') {
+ thumbnailUrl = firstFile.fullThumbUrl || firstFile.fullUrl
+ iconClass = 'ic-image'
+ } else if (firstFile.mediaType === 'VIDEO') {
+ thumbnailUrl = firstFile.fullThumbUrl || firstFile.fullUrl
+ iconClass = 'ic-video'
+ } else if (firstFile.fileExt && firstFile.fileExt.toLowerCase() === 'pdf') {
+ iconClass = 'ic-pdf'
+ } else if (firstFile.fileExt && ['doc', 'docx'].includes(firstFile.fileExt.toLowerCase())) {
+ iconClass = 'ic-word'
+ } else {
+ iconClass = 'ic-file'
+ }
}
- }))
-
- this.setData({
- registrations
+
+ return {
+ id: project.id,
+ projectName: project.projectName,
+ activityName: project.activityName,
+ state: project.status,
+ statusText: project.statusText || this.getProjectStatusText(project.status),
+ statusType: this.getProjectStatusType(project.status),
+ icon: iconClass,
+ thumbnailUrl: thumbnailUrl,
+ createTime: project.createTime
+ }
})
+
+ this.setData({ userProjects: projects })
}
} catch (error) {
- console.error('鍔犺浇鎶ュ悕璁板綍澶辫触:', error)
+ console.error('鍔犺浇鐢ㄦ埛椤圭洰澶辫触:', error)
+ // 涓嶆樉绀洪敊璇彁绀猴紝闈欓粯澶辫触
} finally {
- this.setData({ registrationLoading: false })
+ this.setData({ projectsLoading: false })
}
},
@@ -320,19 +362,26 @@
async loadJudgeStats() {
try {
const query = `
- query GetJudgeStats {
- judgeStats {
- pendingReviews
- completedReviews
- totalReviews
+ query GetReviewStatistics {
+ reviewStatistics {
+ unReviewedCount
+ reviewedCount
+ studentUnReviewedCount
}
}
`
const result = await graphqlRequest(query)
- if (result && result.judgeStats) {
- const judgeStats = result.judgeStats
+ if (result && result.reviewStatistics) {
+ const stats = result.reviewStatistics
+
+ // 杞崲瀛楁鍚嶄互鍖归厤鐜版湁鐨勬暟鎹粨鏋�
+ const judgeStats = {
+ pendingReviews: stats.unReviewedCount,
+ completedReviews: stats.reviewedCount,
+ studentUnReviewedCount: stats.studentUnReviewedCount
+ }
this.setData({
judgeStats,
@@ -376,14 +425,47 @@
})
},
- // 鑿滃崟椤圭偣鍑�
- onMenuItemTap(e) {
- const { path } = e.currentTarget.dataset
-
+ // 璺宠浆鍒颁釜浜轰俊鎭〉闈�
+ goToPersonalInfo() {
wx.navigateTo({
- url: path
+ url: '/pages/profile/personal-info'
})
},
+
+ // 璺宠浆鍒伴」鐩鎯呴〉闈�
+ goToProjectDetail(e) {
+ const projectId = e.currentTarget.dataset.projectId
+
+ if (!projectId) {
+ wx.showToast({
+ title: '椤圭洰ID鏃犳晥',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 濡傛灉鏄ず渚嬫暟鎹紝鏄剧ず鎻愮ず
+ if (projectId === 'registrations' || projectId === 'achievements') {
+ wx.showToast({
+ title: '杩欐槸绀轰緥椤圭洰锛岃閫夋嫨鐪熷疄椤圭洰',
+ icon: 'none'
+ })
+ return
+ }
+
+ wx.navigateTo({
+ url: `/pages/project/detail?id=${projectId}`
+ })
+ },
+
+ // 璺宠浆鍒拌瘎瀹¢〉闈�
+ goToReviewPage() {
+ wx.navigateTo({
+ url: '/pages/review/index'
+ })
+ },
+
+
// 鏌ョ湅鎶ュ悕璇︽儏
onRegistrationTap(e) {
@@ -448,53 +530,89 @@
return formatDate(dateString, 'MM-DD HH:mm')
},
- // 閫�鍑虹櫥褰�
- onLogout() {
- wx.showModal({
- title: '纭閫�鍑�',
- content: '纭畾瑕侀��鍑虹櫥褰曞悧锛�',
- success: (res) => {
- if (res.confirm) {
- this.logout()
- }
- }
- })
+ // 鑾峰彇椤圭洰鐘舵�佹枃鏈紙鏀寔鏁板瓧鍜屽瓧绗︿覆鐘舵�侊級
+ getProjectStatusText(status) {
+ // 鏁板瓧鐘舵�佹槧灏勶紙涓巜eb绔繚鎸佷竴鑷达級
+ const numericStatusMap = {
+ 0: '鏈鏍�',
+ 1: '瀹℃牳閫氳繃',
+ 2: '瀹℃牳椹冲洖',
+ 3: '宸茬粨鏉�'
+ }
+
+ // 瀛楃涓茬姸鎬佹槧灏�
+ const stringStatusMap = {
+ 'SUBMITTED': '宸叉彁浜�',
+ 'UNDER_REVIEW': '璇勫涓�',
+ 'REVIEWED': '宸茶瘎瀹�',
+ 'REJECTED': '宸叉嫆缁�',
+ 'DRAFT': '鑽夌'
+ }
+
+ // 浼樺厛浣跨敤鏁板瓧鐘舵�佹槧灏�
+ if (typeof status === 'number' && numericStatusMap[status]) {
+ return numericStatusMap[status]
+ }
+
+ return stringStatusMap[status] || '鏈煡鐘舵��'
},
- // 鎵ц閫�鍑虹櫥褰�
- async logout() {
- try {
- wx.showLoading({ title: '閫�鍑轰腑...' })
-
- // 娓呴櫎鏈湴瀛樺偍
- wx.removeStorageSync('token')
- wx.removeStorageSync('userInfo')
-
- // 娓呴櫎鍏ㄥ眬鏁版嵁
- app.globalData.token = ''
- app.globalData.userInfo = null
- app.globalData.isLoggedIn = false
-
- // 璺宠浆鍒扮櫥褰曢〉
- wx.reLaunch({
- url: '/pages/login/login'
- })
-
- wx.showToast({
- title: '宸查��鍑虹櫥褰�',
- icon: 'success'
- })
- } catch (error) {
- console.error('閫�鍑虹櫥褰曞け璐�:', error)
- wx.showToast({
- title: '閫�鍑哄け璐�',
- icon: 'error'
- })
- } finally {
- wx.hideLoading()
+ // 鑾峰彇椤圭洰鐘舵�佺被鍨嬶紙鐢ㄤ簬鏍峰紡锛�
+ getProjectStatusType(status) {
+ // 鏁板瓧鐘舵�佺被鍨嬫槧灏勶紙涓巜eb绔繚鎸佷竴鑷达級
+ const numericTypeMap = {
+ 0: 'warning', // 鏈鏍�
+ 1: 'success', // 瀹℃牳閫氳繃
+ 2: 'danger', // 瀹℃牳椹冲洖
+ 3: 'info' // 宸茬粨鏉�
}
+
+ // 瀛楃涓茬姸鎬佺被鍨嬫槧灏�
+ const stringTypeMap = {
+ 'SUBMITTED': 'primary',
+ 'UNDER_REVIEW': 'warning',
+ 'REVIEWED': 'success',
+ 'REJECTED': 'danger',
+ 'DRAFT': 'info'
+ }
+
+ // 浼樺厛浣跨敤鏁板瓧鐘舵�佹槧灏�
+ if (typeof status === 'number' && numericTypeMap[status]) {
+ return numericTypeMap[status]
+ }
+
+ return stringTypeMap[status] || 'info'
},
+ // 鑾峰彇椤圭洰鍥炬爣
+ getProjectIcon(status) {
+ // 鏁板瓧鐘舵�佸浘鏍囨槧灏�
+ const numericIconMap = {
+ 0: '鈴�', // 鏈鏍�
+ 1: '鉁�', // 瀹℃牳閫氳繃
+ 2: '鉂�', // 瀹℃牳椹冲洖
+ 3: '馃搵' // 宸茬粨鏉�
+ }
+
+ // 瀛楃涓茬姸鎬佸浘鏍囨槧灏�
+ const stringIconMap = {
+ 'SUBMITTED': '馃搵',
+ 'UNDER_REVIEW': '鈴�',
+ 'REVIEWED': '鉁�',
+ 'REJECTED': '鉂�',
+ 'DRAFT': '馃摑'
+ }
+
+ // 浼樺厛浣跨敤鏁板瓧鐘舵�佹槧灏�
+ if (typeof status === 'number' && numericIconMap[status]) {
+ return numericIconMap[status]
+ }
+
+ return stringIconMap[status] || '馃搵'
+ },
+
+
+
// 鍒嗕韩椤甸潰
onShareAppMessage() {
return {
diff --git a/wx/pages/profile/profile.wxml b/wx/pages/profile/profile.wxml
index f3574a8..fd4c5d4 100644
--- a/wx/pages/profile/profile.wxml
+++ b/wx/pages/profile/profile.wxml
@@ -21,54 +21,82 @@
<text class="avatar-text">{{avatarText}}</text>
</view>
</view>
- <text class="user-name">{{userInfo.name || 'Ethan'}}</text>
- <text class="user-role">{{userRoles.includes('JUDGE') ? '璇勫' : userRoles.includes('ORGANIZER') ? '涓诲姙鏂�' : '鍙傝禌鑰�'}}</text>
+ <view class="user-info">
+ <text class="user-name">{{userInfo.name || 'Ethan'}}</text>
+ <text class="user-role">{{userRoles.includes('JUDGE') ? '璇勫' : userRoles.includes('ORGANIZER') ? '涓诲姙鏂�' : '鍙傝禌鑰�'}}</text>
+ </view>
+ <view class="settings-btn" bindtap="goToPersonalInfo">
+ <text class="icon-settings">鈿欙笍</text>
+ </view>
</view>
<!-- 鎴戠殑椤圭洰鍖哄煙 -->
<view class="projects-section">
<text class="section-title">鎴戠殑椤圭洰</text>
- <view class="project-list">
- <view class="project-card project-a" bindtap="onMenuItemTap" data-path="/pages/profile/registrations">
+ <view class="project-list" wx:if="{{userProjects && userProjects.length > 0}}">
+ <view
+ class="project-card"
+ wx:for="{{userProjects}}"
+ wx:key="id"
+ bindtap="goToProjectDetail"
+ data-project-id="{{item.id}}"
+ >
<view class="project-icon">
- <text class="icon-plant">馃尡</text>
+ <!-- 濡傛灉鏈夌缉鐣ュ浘锛屾樉绀哄浘鐗� -->
+ <image
+ wx:if="{{item.thumbnailUrl}}"
+ class="project-thumbnail"
+ src="{{item.thumbnailUrl}}"
+ mode="aspectFill"
+ />
+ <!-- 鍚﹀垯鏄剧ず鍥炬爣 -->
+ <text wx:else class="icon {{item.icon}}"></text>
</view>
<view class="project-info">
- <text class="project-name">鎴戠殑鎶ュ悕</text>
- <text class="project-desc">鏌ョ湅鎶ュ悕璁板綍</text>
+ <text class="project-title">{{item.projectName || '鏈懡鍚嶉」鐩�'}}</text>
+ <text class="project-subtitle">{{item.activityName || ''}}</text>
+ </view>
+ <view class="project-status">
+ <text class="status-text status-{{item.statusType}}">{{item.statusText}}</text>
</view>
<text class="project-arrow">></text>
</view>
- <view class="project-card project-b" bindtap="onMenuItemTap" data-path="/pages/profile/achievements">
- <view class="project-icon">
- <text class="icon-trophy">馃弳</text>
- </view>
- <view class="project-info">
- <text class="project-name">鎴戠殑鎴愮哗</text>
- <text class="project-desc">鏌ョ湅娲诲姩鎴愮哗</text>
- </view>
- <text class="project-arrow">></text>
+ </view>
+ <view class="empty-projects" wx:else>
+ <text class="empty-icon">馃搵</text>
+ <text class="empty-text">鏆傛棤椤圭洰</text>
+ <text class="empty-desc">鍙傚姞娲诲姩鍚庯紝鎮ㄧ殑椤圭洰灏嗗湪杩欓噷鏄剧ず</text>
+ </view>
+ </view>
+
+ <!-- 鎴戠殑璇勫鍖哄煙 - 浠呰瘎濮斿彲瑙� -->
+ <view class="review-section" wx:if="{{isJudge}}">
+ <view class="section-header">
+ <text class="section-title">鎴戠殑璇勫</text>
+ <view class="review-action-btn" bindtap="goToReviewPage">
+ <text class="action-text">璇勫</text>
+ <text class="action-arrow">></text>
+ </view>
+ </view>
+
+ <view class="review-stats">
+ <view class="stat-item">
+ <text class="stat-number">{{judgeStats.pendingReviews || 0}}</text>
+ <text class="stat-label">寰呰瘎瀹�</text>
+ </view>
+ <view class="stat-divider"></view>
+ <view class="stat-item">
+ <text class="stat-number">{{judgeStats.completedReviews || 0}}</text>
+ <text class="stat-label">宸茶瘎瀹�</text>
+ </view>
+ <view class="stat-divider"></view>
+ <view class="stat-item">
+ <text class="stat-number">{{judgeStats.studentUnReviewedCount || 0}}</text>
+ <text class="stat-label">瀛﹀憳鏈瘎瀹�</text>
</view>
</view>
</view>
- <!-- 鍏朵粬鍔熻兘鑿滃崟 -->
- <view class="other-menu-section">
- <view class="menu-item-simple" bindtap="onMenuItemTap" data-path="/pages/profile/favorites">
- <text class="menu-icon">猸�</text>
- <text class="menu-title">鎴戠殑鏀惰棌</text>
- <text class="menu-arrow">></text>
- </view>
- <view class="menu-item-simple" bindtap="onMenuItemTap" data-path="/pages/profile/settings">
- <text class="menu-icon">鈿欙笍</text>
- <text class="menu-title">璁剧疆</text>
- <text class="menu-arrow">></text>
- </view>
- <view class="menu-item-simple" bindtap="onLogout">
- <text class="menu-icon">馃毆</text>
- <text class="menu-title">閫�鍑虹櫥褰�</text>
- <text class="menu-arrow">></text>
- </view>
- </view>
+
</view>
</view>
\ No newline at end of file
diff --git a/wx/pages/profile/profile.wxss b/wx/pages/profile/profile.wxss
index 5cefb69..7889695 100644
--- a/wx/pages/profile/profile.wxss
+++ b/wx/pages/profile/profile.wxss
@@ -34,35 +34,55 @@
/* 鐢ㄦ埛淇℃伅鍖哄煙 */
.user-section {
display: flex;
- flex-direction: column;
align-items: center;
- padding: 60rpx 0 40rpx 0;
+ padding: 40rpx 30rpx;
+ margin-bottom: 40rpx;
+ justify-content: space-between;
}
.user-avatar-wrapper {
- margin-bottom: 20rpx;
+ position: relative;
}
.user-avatar {
width: 120rpx;
height: 120rpx;
- border-radius: 50%;
+ border-radius: 60rpx;
+ border: 4rpx solid #ffffff;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.user-avatar-placeholder {
width: 120rpx;
height: 120rpx;
- border-radius: 50%;
- background: #ffa726;
+ border-radius: 60rpx;
+ border: 4rpx solid #ffffff;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
}
.avatar-text {
- color: #ffffff;
+ color: white;
font-size: 48rpx;
font-weight: bold;
+}
+
+.avatar {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 60rpx;
+ border: 4rpx solid #ffffff;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+.user-info {
+ flex: 1;
+ margin-left: 30rpx;
+ display: flex;
+ flex-direction: column;
}
.user-name {
@@ -75,6 +95,26 @@
.user-role {
font-size: 28rpx;
color: #999999;
+}
+
+.settings-btn {
+ width: 80rpx;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.05);
+ border-radius: 40rpx;
+ transition: all 0.3s ease;
+}
+
+.settings-btn:active {
+ background: rgba(0, 0, 0, 0.1);
+ transform: scale(0.95);
+}
+
+.icon-settings {
+ font-size: 40rpx;
}
/* 椤圭洰鍖哄煙 */
@@ -97,21 +137,26 @@
}
.project-card {
- background: #ffffff;
- border-radius: 16rpx;
- padding: 30rpx;
display: flex;
align-items: center;
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ padding: 24rpx;
+ margin-bottom: 16rpx;
+ background: white;
+ border-radius: 16rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.project-card:active {
transform: scale(0.98);
+ background: #f8f9fa;
}
-.project-a .project-icon {
- background: #2e7d32;
+.project-card:last-child {
+ margin-bottom: 0;
+}
+
+.project-icon {
width: 80rpx;
height: 80rpx;
border-radius: 16rpx;
@@ -119,45 +164,205 @@
align-items: center;
justify-content: center;
margin-right: 24rpx;
+ font-size: 32rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ overflow: hidden;
}
-.project-b .project-icon {
- background: #66bb6a;
- width: 80rpx;
- height: 80rpx;
+.project-thumbnail {
+ width: 100%;
+ height: 100%;
border-radius: 16rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 24rpx;
}
-.icon-plant,
-.icon-trophy {
- font-size: 36rpx;
+.icon-project {
+ color: white;
}
.project-info {
flex: 1;
+ display: flex;
+ flex-direction: column;
}
-.project-name {
- font-size: 30rpx;
- font-weight: bold;
- color: #333333;
- display: block;
+.project-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #1a1a1a;
margin-bottom: 8rpx;
}
-.project-desc {
+.project-subtitle {
+ font-size: 26rpx;
+ color: #666;
+}
+
+.project-status {
+ margin-right: 16rpx;
+}
+
+.status-text {
font-size: 24rpx;
- color: #999999;
- display: block;
+ padding: 8rpx 16rpx;
+ border-radius: 12rpx;
+ font-weight: 500;
+}
+
+/* 鍩轰簬web绔殑鐘舵�佹牱寮� */
+.status-text.status-warning {
+ background: #fff3e0;
+ color: #f57c00;
+}
+
+.status-text.status-success {
+ background: #e8f5e8;
+ color: #388e3c;
+}
+
+.status-text.status-danger {
+ background: #ffebee;
+ color: #d32f2f;
+}
+
+.status-text.status-info {
+ background: #e3f2fd;
+ color: #1976d2;
+}
+
+.status-text.status-primary {
+ background: #e3f2fd;
+ color: #1976d2;
+}
+
+/* 鍏煎鏃х殑瀛楃涓茬姸鎬佹牱寮� */
+.status-text.status-SUBMITTED {
+ background: #e3f2fd;
+ color: #1976d2;
+}
+
+.status-text.status-UNDER_REVIEW {
+ background: #fff3e0;
+ color: #f57c00;
+}
+
+.status-text.status-REVIEWED {
+ background: #e8f5e8;
+ color: #388e3c;
+}
+
+.status-text.status-REJECTED {
+ background: #ffebee;
+ color: #d32f2f;
}
.project-arrow {
+ font-size: 32rpx;
+ color: #ccc;
+ margin-left: 16rpx;
+}
+
+/* 绌虹姸鎬� */
+.empty-projects {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 80rpx 40rpx;
+ background: white;
+ border-radius: 16rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.empty-icon {
+ font-size: 80rpx;
+ margin-bottom: 24rpx;
+ opacity: 0.5;
+}
+
+.empty-text {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #666;
+ margin-bottom: 12rpx;
+}
+
+.empty-desc {
+ font-size: 26rpx;
+ color: #999;
+ text-align: center;
+ line-height: 1.5;
+}
+
+/* 璇勫鍖哄煙 */
+.review-section {
+ margin-bottom: 40rpx;
+}
+
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 30rpx;
+}
+
+.review-action-btn {
+ display: flex;
+ align-items: center;
+ padding: 12rpx 24rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 24rpx;
+ transition: all 0.3s ease;
+}
+
+.review-action-btn:active {
+ transform: scale(0.95);
+ opacity: 0.8;
+}
+
+.action-text {
font-size: 28rpx;
- color: #cccccc;
+ color: white;
+ font-weight: 500;
+ margin-right: 8rpx;
+}
+
+.action-arrow {
+ font-size: 24rpx;
+ color: white;
+}
+
+.review-stats {
+ display: flex;
+ align-items: center;
+ padding: 32rpx;
+ background: white;
+ border-radius: 16rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.stat-item {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.stat-number {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #667eea;
+ margin-bottom: 8rpx;
+}
+
+.stat-label {
+ font-size: 24rpx;
+ color: #666;
+}
+
+.stat-divider {
+ width: 1rpx;
+ height: 60rpx;
+ background: #e0e0e0;
+ margin: 0 20rpx;
}
/* 鍏朵粬鑿滃崟鍖哄煙 */
diff --git a/wx/pages/project/detail.js b/wx/pages/project/detail.js
new file mode 100644
index 0000000..9e95a97
--- /dev/null
+++ b/wx/pages/project/detail.js
@@ -0,0 +1,458 @@
+// pages/project/detail.js
+const app = getApp()
+
+Page({
+ data: {
+ projectId: '',
+ projectDetail: null,
+ ratingStats: null,
+ loading: true,
+ error: '',
+ statusText: '',
+ genderText: '',
+ educationText: ''
+ },
+
+ onLoad(options) {
+ if (options.id) {
+ this.setData({
+ projectId: options.id
+ })
+ this.loadProjectDetail()
+ } else {
+ this.setData({
+ error: '缂哄皯椤圭洰ID鍙傛暟',
+ loading: false
+ })
+ }
+ },
+
+ // 鍔犺浇椤圭洰璇︽儏
+ async loadProjectDetail() {
+ try {
+ this.setData({
+ loading: true,
+ error: ''
+ })
+
+ // 璋冪敤API鑾峰彇椤圭洰璇︽儏
+ const projectDetail = await this.getProjectDetailFromAPI(this.data.projectId)
+
+ if (projectDetail) {
+ // 澶勭悊鏂囦欢澶у皬鏄剧ず
+ if (projectDetail.submissionFiles) {
+ projectDetail.submissionFiles.forEach(file => {
+ file.fileSizeText = this.formatFileSize(file.fileSize)
+ })
+ }
+
+ // 鑾峰彇璇勫垎缁熻
+ const ratingStats = await this.getRatingStatsFromAPI(this.data.projectId)
+
+ // 澶勭悊璇勫垎鏃堕棿鏄剧ず
+ if (ratingStats && ratingStats.judgeRatings) {
+ ratingStats.judgeRatings.forEach(rating => {
+ if (rating.ratingTime) {
+ rating.ratingTimeText = this.formatDateTime(rating.ratingTime)
+ }
+ })
+ }
+
+ this.setData({
+ projectDetail,
+ ratingStats,
+ statusText: this.getStatusText(projectDetail.state),
+ genderText: this.getGenderText(projectDetail.playerInfo?.gender),
+ educationText: this.getEducationText(projectDetail.playerInfo?.education),
+ loading: false
+ })
+ } else {
+ throw new Error('椤圭洰璇︽儏鑾峰彇澶辫触')
+ }
+ } catch (error) {
+ console.error('鍔犺浇椤圭洰璇︽儏澶辫触:', error)
+ this.setData({
+ error: error.message || '鍔犺浇澶辫触锛岃閲嶈瘯',
+ loading: false
+ })
+ }
+ },
+
+ // 浠嶢PI鑾峰彇椤圭洰璇︽儏
+ async getProjectDetailFromAPI(projectId) {
+ // 鏋勫缓GraphQL鏌ヨ
+ const query = `
+ query GetProjectDetail($id: ID!) {
+ activityPlayerDetail(id: $id) {
+ id
+ activityId
+ playerId
+ playerName
+ playerGender
+ playerPhone
+ playerEducation
+ playerBirthDate
+ playerIdCard
+ playerAddress
+ projectName
+ projectDescription
+ projectCategory
+ projectTags
+ projectFiles {
+ id
+ fileName
+ fileUrl
+ fileSize
+ fileType
+ uploadTime
+ }
+ submitTime
+ reviewTime
+ reviewerId
+ reviewerName
+ score
+ rating {
+ id
+ judgeId
+ judgeName
+ score
+ feedback
+ ratingTime
+ }
+ state
+ feedback
+ }
+ }
+ `
+
+ try {
+ const result = await app.graphqlRequest(query, { id: projectId })
+ return result.activityPlayerDetail
+ } catch (error) {
+ throw error
+ }
+ },
+
+ // 鑾峰彇璇勫垎缁熻
+ async getRatingStatsFromAPI(projectId) {
+ const query = `
+ query GetRatingStats($activityPlayerId: ID!) {
+ ratingStats(activityPlayerId: $activityPlayerId) {
+ averageScore
+ totalRatings
+ scoreDistribution {
+ score
+ count
+ }
+ }
+ }
+ `
+
+ try {
+ const result = await app.graphqlRequest(query, { activityPlayerId: projectId })
+ return result.ratingStats
+ } catch (error) {
+ throw error
+ }
+ },
+
+ // 鑾峰彇璇勫璇勫垎璇︽儏
+ async getJudgeRatingDetail(activityPlayerId, judgeId) {
+ const query = `
+ query GetJudgeRatingDetail($activityPlayerId: ID!, $judgeId: ID!) {
+ judgeRatingDetail(activityPlayerId: $activityPlayerId, judgeId: $judgeId) {
+ remark
+ }
+ }
+ `
+
+ try {
+ const result = await app.graphqlRequest(query, { activityPlayerId, judgeId })
+ return result.judgeRatingDetail
+ } catch (error) {
+ throw error
+ }
+ },
+
+ // 棰勮鏂囦欢
+ previewFile(e) {
+ const file = e.currentTarget.dataset.file
+
+ if (!file || !file.fullUrl) {
+ wx.showToast({
+ title: '鏂囦欢閾炬帴鏃犳晥',
+ icon: 'none'
+ })
+ return
+ }
+
+ // 鏍规嵁鏂囦欢绫诲瀷杩涜涓嶅悓澶勭悊
+ const fileExt = (file.fileExt || '').toLowerCase()
+
+ if (this.isImageFile(fileExt)) {
+ // 鍥剧墖棰勮
+ wx.previewImage({
+ urls: [file.fullUrl],
+ current: file.fullUrl
+ })
+ } else if (this.isVideoFile(fileExt)) {
+ // 瑙嗛棰勮
+ wx.navigateTo({
+ url: `/pages/video/video?url=${encodeURIComponent(file.fullUrl)}&title=${encodeURIComponent(file.name)}`
+ })
+ } else if (this.isPdfFile(fileExt)) {
+ // PDF鏂囨。棰勮
+ this.previewPdfFile(file)
+ } else if (this.isOfficeFile(fileExt)) {
+ // Office鏂囨。棰勮
+ this.previewOfficeFile(file)
+ } else {
+ // 鍏朵粬鏂囦欢绫诲瀷锛屽皾璇曚笅杞芥墦寮�
+ this.downloadAndOpenFile(file)
+ }
+ },
+
+ // PDF鏂囨。棰勮
+ previewPdfFile(file) {
+ wx.showLoading({
+ title: '姝e湪鎵撳紑PDF...'
+ })
+
+ wx.downloadFile({
+ url: file.fullUrl,
+ success: (downloadRes) => {
+ wx.hideLoading()
+ if (downloadRes.statusCode === 200) {
+ wx.openDocument({
+ filePath: downloadRes.tempFilePath,
+ fileType: 'pdf',
+ success: () => {
+ console.log('PDF鎵撳紑鎴愬姛')
+ },
+ fail: (error) => {
+ console.error('PDF鎵撳紑澶辫触:', error)
+ wx.showToast({
+ title: 'PDF鎵撳紑澶辫触',
+ icon: 'none'
+ })
+ }
+ })
+ } else {
+ wx.showToast({
+ title: 'PDF涓嬭浇澶辫触',
+ icon: 'none'
+ })
+ }
+ },
+ fail: (error) => {
+ wx.hideLoading()
+ console.error('PDF涓嬭浇澶辫触:', error)
+ wx.showToast({
+ title: 'PDF涓嬭浇澶辫触',
+ icon: 'none'
+ })
+ }
+ })
+ },
+
+ // Office鏂囨。棰勮
+ previewOfficeFile(file) {
+ const fileExt = (file.fileExt || '').toLowerCase()
+
+ wx.showLoading({
+ title: '姝e湪鎵撳紑鏂囨。...',
+ mask: true
+ })
+
+ wx.downloadFile({
+ url: file.fullUrl,
+ success: (downloadRes) => {
+ wx.hideLoading()
+ if (downloadRes.statusCode === 200) {
+ // 鏂囦欢绫诲瀷鏄犲皠
+ const fileTypeMap = {
+ 'doc': 'doc',
+ 'docx': 'docx',
+ 'xls': 'xls',
+ 'xlsx': 'xlsx',
+ 'ppt': 'ppt',
+ 'pptx': 'pptx'
+ }
+
+ wx.openDocument({
+ filePath: downloadRes.tempFilePath,
+ fileType: fileTypeMap[fileExt] || 'doc',
+ success: () => {
+ console.log('鏂囨。鎵撳紑鎴愬姛')
+ },
+ fail: (error) => {
+ console.error('鏂囨。鎵撳紑澶辫触:', error)
+ wx.showModal({
+ title: '鎵撳紑澶辫触',
+ content: '鏂囨。鎵撳紑澶辫触锛屽彲鑳芥槸鏂囦欢鏍煎紡涓嶆敮鎸佹垨鏂囦欢鎹熷潖',
+ showCancel: false,
+ confirmText: '纭畾'
+ })
+ }
+ })
+ } else {
+ wx.showToast({
+ title: '鏂囦欢涓嬭浇澶辫触',
+ icon: 'none'
+ })
+ }
+ },
+ fail: (error) => {
+ wx.hideLoading()
+ console.error('鏂囨。涓嬭浇澶辫触:', error)
+ wx.showToast({
+ title: '鏂囦欢涓嬭浇澶辫触',
+ icon: 'none'
+ })
+ }
+ })
+ },
+
+ // 涓嬭浇骞舵墦寮�鏂囦欢
+ downloadAndOpenFile(file) {
+ wx.showModal({
+ title: '鏂囦欢棰勮',
+ content: '鏄惁瑕佷笅杞藉苟鎵撳紑姝ゆ枃浠讹紵',
+ confirmText: '涓嬭浇',
+ cancelText: '鍙栨秷',
+ success: (res) => {
+ if (res.confirm) {
+ wx.showLoading({
+ title: '姝e湪涓嬭浇...'
+ })
+
+ wx.downloadFile({
+ url: file.fullUrl,
+ success: (downloadRes) => {
+ wx.hideLoading()
+ if (downloadRes.statusCode === 200) {
+ wx.openDocument({
+ filePath: downloadRes.tempFilePath,
+ success: () => {
+ console.log('鏂囨。鎵撳紑鎴愬姛')
+ },
+ fail: (error) => {
+ console.error('鏂囨。鎵撳紑澶辫触:', error)
+ wx.showToast({
+ title: '鏂囦欢鎵撳紑澶辫触',
+ icon: 'none'
+ })
+ }
+ })
+ } else {
+ wx.showToast({
+ title: '鏂囦欢涓嬭浇澶辫触',
+ icon: 'none'
+ })
+ }
+ },
+ fail: (error) => {
+ wx.hideLoading()
+ console.error('鏂囦欢涓嬭浇澶辫触:', error)
+ wx.showToast({
+ title: '鏂囦欢涓嬭浇澶辫触',
+ icon: 'none'
+ })
+ }
+ })
+ }
+ }
+ })
+ },
+
+ // 鍒ゆ柇鏄惁涓哄浘鐗囨枃浠�
+ isImageFile(fileExt) {
+ const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
+ return imageExts.includes(fileExt)
+ },
+
+ // 鍒ゆ柇鏄惁涓鸿棰戞枃浠�
+ isVideoFile(fileExt) {
+ const videoExts = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm']
+ return videoExts.includes(fileExt)
+ },
+
+ // 鍒ゆ柇鏄惁涓篜DF鏂囦欢
+ isPdfFile(fileExt) {
+ return fileExt === 'pdf'
+ },
+
+ // 鍒ゆ柇鏄惁涓篛ffice鏂囦欢
+ isOfficeFile(fileExt) {
+ const officeExts = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'dotx', 'xlsb', 'xlsm', 'ppsx', 'pps', 'potx', 'ppsm']
+ return officeExts.includes(fileExt)
+ },
+
+ // 鏍煎紡鍖栨枃浠跺ぇ灏�
+ formatFileSize(bytes) {
+ if (!bytes || bytes === 0) 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]
+ },
+
+ // 鏍煎紡鍖栨棩鏈熸椂闂�
+ formatDateTime(dateTimeStr) {
+ if (!dateTimeStr) return ''
+
+ try {
+ const date = new Date(dateTimeStr)
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+
+ return `${year}-${month}-${day} ${hours}:${minutes}`
+ } catch (error) {
+ return dateTimeStr
+ }
+ },
+
+ // 鑾峰彇鐘舵�佹枃鏈�
+ getStatusText(state) {
+ const statusMap = {
+ 0: '鏈鏍�',
+ 1: '瀹℃牳閫氳繃',
+ 2: '瀹℃牳涓嶉�氳繃'
+ }
+ return statusMap[state] || '鏈煡鐘舵��'
+ },
+
+ // 鑾峰彇鎬у埆鏂囨湰
+ getGenderText(gender) {
+ const genderMap = {
+ 'MALE': '鐢�',
+ 'FEMALE': '濂�'
+ }
+ return genderMap[gender] || ''
+ },
+
+ // 鑾峰彇瀛﹀巻鏂囨湰
+ getEducationText(education) {
+ const educationMap = {
+ 'HIGH_SCHOOL': '楂樹腑鍙婁互涓�',
+ 'COLLEGE': '澶т笓',
+ 'BACHELOR': '鏈',
+ 'MASTER': '纭曞+',
+ 'DOCTOR': '鍗氬+'
+ }
+ return educationMap[education] || ''
+ },
+
+ // 鍒嗕韩鍔熻兘
+ onShareAppMessage() {
+ return {
+ title: `椤圭洰璇︽儏 - ${this.data.projectDetail?.projectName || ''}`,
+ path: `/pages/project/detail?id=${this.data.projectId}`
+ }
+ }
+})
\ No newline at end of file
diff --git a/wx/pages/project/detail.json b/wx/pages/project/detail.json
new file mode 100644
index 0000000..e6960eb
--- /dev/null
+++ b/wx/pages/project/detail.json
@@ -0,0 +1,6 @@
+{
+ "navigationBarTitleText": "椤圭洰璇︽儏",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f5f5f5"
+}
\ No newline at end of file
diff --git a/wx/pages/project/detail.wxml b/wx/pages/project/detail.wxml
new file mode 100644
index 0000000..56425fa
--- /dev/null
+++ b/wx/pages/project/detail.wxml
@@ -0,0 +1,187 @@
+<!--pages/project/detail.wxml-->
+<view class="container" wx:if="{{!loading}}">
+ <!-- 椤圭洰鍩烘湰淇℃伅 -->
+ <view class="project-card">
+ <view class="card-header">
+ <text class="card-title">椤圭洰淇℃伅</text>
+ <view class="status-tag status-{{projectDetail.state}}">
+ {{statusText}}
+ </view>
+ </view>
+
+ <view class="project-info">
+ <view class="info-row">
+ <text class="label">椤圭洰鍚嶇О</text>
+ <text class="value">{{projectDetail.projectName || '鏈~鍐�'}}</text>
+ </view>
+ <view class="info-row">
+ <text class="label">姣旇禌鍚嶇О</text>
+ <text class="value">{{projectDetail.activityName}}</text>
+ </view>
+ <view class="info-row full-width" wx:if="{{projectDetail.description}}">
+ <text class="label">椤圭洰鎻忚堪</text>
+ <view class="description">{{projectDetail.description}}</view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 椤圭洰闄勪欢 -->
+ <view class="attachments-card" wx:if="{{projectDetail.submissionFiles && projectDetail.submissionFiles.length > 0}}">
+ <view class="card-header">
+ <text class="card-title">椤圭洰闄勪欢</text>
+ </view>
+
+ <view class="attachments-list">
+ <view
+ class="attachment-item"
+ wx:for="{{projectDetail.submissionFiles}}"
+ wx:key="id"
+ bindtap="previewFile"
+ data-file="{{item}}"
+ >
+ <view class="file-info">
+ <!-- 鍥剧墖棰勮 -->
+ <view class="file-preview" wx:if="{{item.mediaType === 'IMAGE'}}">
+ <image
+ class="preview-image"
+ src="{{item.fullThumbUrl || item.fullUrl}}"
+ mode="aspectFill"
+ />
+ <view class="media-type-badge image-badge">鍥剧墖</view>
+ </view>
+ <!-- 瑙嗛棰勮 -->
+ <view class="file-preview" wx:elif="{{item.mediaType === 'VIDEO'}}">
+ <image
+ class="preview-image"
+ src="{{item.fullThumbUrl || item.fullUrl}}"
+ mode="aspectFill"
+ />
+ <view class="media-type-badge video-badge">瑙嗛</view>
+ <view class="play-icon">鈻�</view>
+ </view>
+ <!-- 鍏朵粬鏂囦欢绫诲瀷 -->
+ <view class="file-icon" wx:else>
+ <text class="icon-file">馃搫</text>
+ </view>
+ <view class="file-details">
+ <text class="file-name">{{item.name}}</text>
+ <text class="file-size">{{item.fileSizeText}}</text>
+ </view>
+ </view>
+ <view class="file-actions">
+ <text class="action-btn">{{item.mediaType === 'IMAGE' ? '棰勮' : item.mediaType === 'VIDEO' ? '鎾斁' : '鏌ョ湅'}}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鍙傝禌浜轰俊鎭� - 宸查殣钘� -->
+ <!--
+ <view class="player-card" wx:if="{{projectDetail.playerInfo}}">
+ <view class="card-header">
+ <text class="card-title">鍙傝禌浜轰俊鎭�</text>
+ </view>
+
+ <view class="player-info">
+ <view class="player-basic">
+ <image
+ class="player-avatar"
+ src="{{projectDetail.playerInfo.userInfo.avatarUrl || '/images/default-avatar.svg'}}"
+ mode="aspectFill"
+ ></image>
+ <view class="player-details">
+ <text class="player-name">{{projectDetail.playerInfo.name}}</text>
+ <text class="player-phone">{{projectDetail.playerInfo.phone}}</text>
+ </view>
+ </view>
+
+ <view class="player-extended">
+ <view class="info-row" wx:if="{{projectDetail.playerInfo.gender}}">
+ <text class="label">鎬у埆</text>
+ <text class="value">{{genderText}}</text>
+ </view>
+ <view class="info-row" wx:if="{{projectDetail.playerInfo.education}}">
+ <text class="label">瀛﹀巻</text>
+ <text class="value">{{educationText}}</text>
+ </view>
+ <view class="info-row" wx:if="{{projectDetail.playerInfo.birthday}}">
+ <text class="label">鐢熸棩</text>
+ <text class="value">{{projectDetail.playerInfo.birthday}}</text>
+ </view>
+ <view class="info-row full-width" wx:if="{{projectDetail.playerInfo.introduction}}">
+ <text class="label">涓汉绠�浠�</text>
+ <view class="description">{{projectDetail.playerInfo.introduction}}</view>
+ </view>
+ </view>
+ </view>
+ </view>
+ -->
+
+ <!-- 瀹℃牳鍙嶉淇℃伅 -->
+ <view class="feedback-card" wx:if="{{projectDetail.feedback}}">
+ <view class="card-header">
+ <text class="card-title">瀹℃牳鍙嶉</text>
+ </view>
+
+ <view class="feedback-content">
+ {{projectDetail.feedback}}
+ </view>
+ </view>
+
+ <!-- 璇勫淇℃伅 -->
+ <view class="rating-card" wx:if="{{ratingStats}}">
+ <view class="card-header">
+ <text class="card-title">璇勫淇℃伅</text>
+ </view>
+
+ <view class="rating-info">
+ <!-- 鎬诲钩鍧囧垎鏄剧ず -->
+ <view class="average-score-section">
+ <view class="average-score-container">
+ <text class="average-score-label">鎬诲钩鍧囧垎</text>
+ <text class="average-score-value">{{ratingStats.averageScore || '鏆傛棤璇勫垎'}}</text>
+ <text class="rating-count-text">锛坽{ratingStats.ratingCount || 0}}浣嶈瘎濮斿凡璇勫垎锛�</text>
+ </view>
+ </view>
+
+ <!-- 鍚勮瘎濮旇瘎鍒嗚鎯� -->
+ <view class="judge-ratings-section" wx:if="{{ratingStats.judgeRatings && ratingStats.judgeRatings.length > 0}}">
+ <text class="section-title">璇勫璇勫垎璇︽儏</text>
+ <view
+ class="judge-rating"
+ wx:for="{{ratingStats.judgeRatings}}"
+ wx:key="judgeId"
+ wx:if="{{item.hasRated}}"
+ >
+ <view class="rating-header">
+ <view class="judge-info">
+ <text class="judge-label">璇勫 {{index + 1}}</text>
+ <text class="rating-status rated">宸茶瘎鍒�</text>
+ </view>
+ <view class="score-info">
+ <text class="score">{{item.totalScore}}鍒�</text>
+ <text class="rating-time">{{item.ratingTimeText}}</text>
+ </view>
+ </view>
+ <view class="rating-comment" wx:if="{{item.remark}}">
+ <text class="comment-label">鐐硅瘎锛�</text>
+ <text class="comment-text">{{item.remark}}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </view>
+</view>
+
+<!-- 鍔犺浇鐘舵�� -->
+<view class="loading-container" wx:if="{{loading}}">
+ <view class="loading-spinner"></view>
+ <text class="loading-text">鍔犺浇涓�...</text>
+</view>
+
+<!-- 閿欒鐘舵�� -->
+<view class="error-container" wx:if="{{error}}">
+ <text class="error-icon">鈿狅笍</text>
+ <text class="error-text">{{error}}</text>
+ <button class="retry-btn" bindtap="loadProjectDetail">閲嶈瘯</button>
+</view>
\ No newline at end of file
diff --git a/wx/pages/project/detail.wxss b/wx/pages/project/detail.wxss
new file mode 100644
index 0000000..899ab52
--- /dev/null
+++ b/wx/pages/project/detail.wxss
@@ -0,0 +1,539 @@
+/* pages/project/detail.wxss */
+.container {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+ padding: 20rpx;
+}
+
+/* 鍗$墖閫氱敤鏍峰紡 */
+.project-card,
+.attachments-card,
+.player-card,
+.rating-card,
+.feedback-card {
+ background-color: #ffffff;
+ border-radius: 16rpx;
+ margin-bottom: 20rpx;
+ padding: 30rpx;
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 30rpx;
+ padding-bottom: 20rpx;
+ border-bottom: 2rpx solid #f0f0f0;
+}
+
+.card-title {
+ font-size: 36rpx;
+ font-weight: 600;
+ color: #333333;
+}
+
+/* 鐘舵�佹爣绛� */
+.status-tag {
+ padding: 8rpx 16rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ font-weight: 500;
+}
+
+/* 鏈鏍� - 鐏拌壊 */
+.status-0 {
+ background-color: #f4f4f5;
+ color: #909399;
+}
+
+/* 瀹℃牳閫氳繃 - 缁胯壊 */
+.status-1 {
+ background-color: #f0f9ff;
+ color: #67c23a;
+}
+
+/* 瀹℃牳涓嶉�氳繃 - 绾㈣壊 */
+.status-2 {
+ background-color: #fef0f0;
+ color: #f56c6c;
+}
+
+/* 椤圭洰淇℃伅 */
+.project-info {
+ display: flex;
+ flex-direction: column;
+ gap: 24rpx;
+}
+
+.info-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 20rpx;
+}
+
+.info-row.full-width {
+ flex-direction: column;
+ gap: 12rpx;
+}
+
+.label {
+ font-size: 28rpx;
+ color: #666666;
+ font-weight: 500;
+ min-width: 140rpx;
+ flex-shrink: 0;
+}
+
+.value {
+ font-size: 28rpx;
+ color: #333333;
+ flex: 1;
+ line-height: 1.5;
+}
+
+.description {
+ font-size: 28rpx;
+ color: #333333;
+ line-height: 1.6;
+ padding: 20rpx;
+ background-color: #f8f9fa;
+ border-radius: 12rpx;
+ border-left: 6rpx solid #4CAF50;
+}
+
+/* 闄勪欢鍒楄〃 */
+.attachments-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.attachment-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 24rpx;
+ background-color: #f8f9fa;
+ border-radius: 12rpx;
+ border: 2rpx solid #e0e0e0;
+ transition: all 0.3s ease;
+}
+
+.attachment-item:active {
+ background-color: #e8f5e8;
+ border-color: #4CAF50;
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+ flex: 1;
+}
+
+/* 鏂囦欢棰勮鏍峰紡 */
+.file-preview {
+ position: relative;
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 12rpx;
+ overflow: hidden;
+}
+
+.preview-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 12rpx;
+}
+
+.media-type-badge {
+ position: absolute;
+ top: 4rpx;
+ right: 4rpx;
+ padding: 2rpx 6rpx;
+ border-radius: 8rpx;
+ font-size: 20rpx;
+ color: #ffffff;
+ font-weight: 500;
+}
+
+.image-badge {
+ background-color: rgba(76, 175, 80, 0.8);
+}
+
+.video-badge {
+ background-color: rgba(255, 152, 0, 0.8);
+}
+
+.play-icon {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 24rpx;
+ height: 24rpx;
+ color: #ffffff;
+ font-size: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, 0.6);
+ border-radius: 50%;
+ padding: 4rpx;
+}
+
+.file-icon {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #4CAF50;
+ border-radius: 12rpx;
+}
+
+.icon-file {
+ font-size: 32rpx;
+ color: #ffffff;
+}
+
+.file-details {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+ flex: 1;
+}
+
+.file-name {
+ font-size: 28rpx;
+ color: #333333;
+ font-weight: 500;
+ line-height: 1.3;
+}
+
+.file-size {
+ font-size: 24rpx;
+ color: #999999;
+}
+
+.file-actions {
+ display: flex;
+ align-items: center;
+}
+
+.action-btn {
+ font-size: 26rpx;
+ color: #4CAF50;
+ font-weight: 500;
+}
+
+/* 鍙傝禌浜轰俊鎭� */
+.player-info {
+ display: flex;
+ flex-direction: column;
+ gap: 30rpx;
+}
+
+.player-basic {
+ display: flex;
+ align-items: center;
+ gap: 24rpx;
+}
+
+.player-avatar {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 60rpx;
+ border: 4rpx solid #e0e0e0;
+}
+
+.player-details {
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+ flex: 1;
+}
+
+.player-name {
+ font-size: 32rpx;
+ color: #333333;
+ font-weight: 600;
+}
+
+.player-phone {
+ font-size: 28rpx;
+ color: #666666;
+}
+
+.player-extended {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+ padding-top: 20rpx;
+ border-top: 2rpx solid #f0f0f0;
+}
+
+/* 璇勫淇℃伅 */
+.rating-info {
+ display: flex;
+ flex-direction: column;
+ gap: 30rpx;
+}
+
+/* 鎬诲钩鍧囧垎鍖哄煙 */
+.average-score-section {
+ padding: 30rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 16rpx;
+ text-align: center;
+}
+
+.average-score-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12rpx;
+}
+
+.average-score-label {
+ font-size: 28rpx;
+ color: #ffffff;
+ opacity: 0.9;
+}
+
+.average-score-value {
+ font-size: 48rpx;
+ color: #ffffff;
+ font-weight: 700;
+ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+}
+
+.rating-count-text {
+ font-size: 24rpx;
+ color: #ffffff;
+ opacity: 0.8;
+}
+
+/* 璇勫璇勫垎鍖哄煙 */
+.judge-ratings-section {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.rating-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12rpx;
+}
+
+.rating-label {
+ font-size: 26rpx;
+ color: #666666;
+}
+
+.rating-score {
+ font-size: 36rpx;
+ color: #4CAF50;
+ font-weight: 600;
+}
+
+.rating-count {
+ font-size: 32rpx;
+ color: #333333;
+ font-weight: 600;
+}
+
+.rating-details {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.section-title {
+ font-size: 30rpx;
+ color: #333333;
+ font-weight: 600;
+ margin-bottom: 10rpx;
+}
+
+.judge-rating {
+ display: flex;
+ flex-direction: column;
+ gap: 16rpx;
+ padding: 24rpx;
+ background-color: #f8f9fa;
+ border-radius: 12rpx;
+ border-left: 6rpx solid #4CAF50;
+}
+
+.rating-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.judge-info {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+
+.judge-label {
+ font-size: 28rpx;
+ color: #333333;
+ font-weight: 600;
+}
+
+.rating-status {
+ font-size: 24rpx;
+ padding: 4rpx 12rpx;
+ border-radius: 12rpx;
+}
+
+.rating-status.rated {
+ background-color: #e8f5e8;
+ color: #4CAF50;
+}
+
+.rating-status.pending {
+ background-color: #fff3cd;
+ color: #856404;
+}
+
+.score-info {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 8rpx;
+}
+
+.rating-comment {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+ padding-top: 16rpx;
+ border-top: 2rpx solid #e0e0e0;
+}
+
+.comment-label {
+ font-size: 26rpx;
+ color: #666666;
+ font-weight: 500;
+}
+
+.comment-text {
+ font-size: 28rpx;
+ color: #333333;
+ line-height: 1.5;
+ padding: 16rpx;
+ background-color: #ffffff;
+ border-radius: 8rpx;
+ border: 2rpx solid #e0e0e0;
+}
+
+.score {
+ font-size: 32rpx;
+ color: #4CAF50;
+ font-weight: 600;
+}
+
+.rating-time {
+ font-size: 24rpx;
+ color: #999999;
+}
+
+/* 鍙嶉淇℃伅 */
+.feedback-content {
+ font-size: 28rpx;
+ color: #333333;
+ line-height: 1.6;
+ padding: 20rpx;
+ background-color: #f8f9fa;
+ border-radius: 12rpx;
+ border-left: 6rpx solid #409eff;
+}
+
+/* 鍔犺浇鐘舵�� */
+.loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 60vh;
+ gap: 30rpx;
+}
+
+.loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 6rpx solid #f0f0f0;
+ border-top: 6rpx solid #4CAF50;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.loading-text {
+ font-size: 28rpx;
+ color: #666666;
+}
+
+/* 閿欒鐘舵�� */
+.error-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 60vh;
+ gap: 30rpx;
+ padding: 40rpx;
+}
+
+.error-icon {
+ font-size: 80rpx;
+}
+
+.error-text {
+ font-size: 28rpx;
+ color: #666666;
+ text-align: center;
+}
+
+.retry-btn {
+ padding: 20rpx 40rpx;
+ background-color: #4CAF50;
+ color: #ffffff;
+ border: none;
+ border-radius: 40rpx;
+ font-size: 28rpx;
+}
+
+.retry-btn::after {
+ border: none;
+}
+
+/* 鍝嶅簲寮忛�傞厤 */
+@media (max-width: 375px) {
+ .container {
+ padding: 15rpx;
+ }
+
+ .project-card,
+ .attachments-card,
+ .player-card,
+ .rating-card,
+ .feedback-card {
+ padding: 25rpx;
+ }
+
+ .card-title {
+ font-size: 32rpx;
+ }
+}
\ No newline at end of file
diff --git a/wx/pages/registration/registration.js b/wx/pages/registration/registration.js
index 68e7b91..05a6728 100644
--- a/wx/pages/registration/registration.js
+++ b/wx/pages/registration/registration.js
@@ -247,7 +247,7 @@
console.log('馃搵 浣跨敤Player淇℃伅鏄剧ず:', userInfo.player)
displayUserInfo.name = userInfo.player.name || userInfo.name || ''
displayUserInfo.phone = userInfo.player.phone || userInfo.phone || ''
- displayUserInfo.avatarUrl = userInfo.player.avatarUrl || userInfo.avatarUrl || ''
+ displayUserInfo.avatarUrl = userInfo.avatarUrl || ''
// 澶勭悊鎬у埆淇℃伅锛�0=鐢凤紝1=濂�
if (userInfo.player.gender !== undefined && userInfo.player.gender !== null) {
@@ -278,7 +278,8 @@
const updateData = {
'formData.name': userInfo.name || '',
'formData.phone': userInfo.phone || '',
- 'formData.email': userInfo.email || ''
+ 'formData.email': userInfo.email || '',
+ 'formData.avatarUrl': userInfo.avatarUrl || ''
}
// 濡傛灉鐢ㄦ埛鏈塒layer淇℃伅锛屼紭鍏堜娇鐢≒layer鐨勮缁嗕俊鎭�
@@ -286,6 +287,7 @@
console.log('馃搵 浣跨敤Player淇℃伅棰勫~鍏�:', userInfo.player)
updateData['formData.name'] = userInfo.player.name || userInfo.name || ''
updateData['formData.phone'] = userInfo.player.phone || userInfo.phone || ''
+ updateData['formData.avatarUrl'] = userInfo.avatarUrl || ''
// 澶勭悊鎬у埆淇℃伅锛�0=鐢凤紝1=濂�
if (userInfo.player.gender !== undefined && userInfo.player.gender !== null) {
@@ -299,6 +301,7 @@
}
console.log('鉁� 棰勫~鍏呮暟鎹�:', updateData)
+ console.log('馃柤锔� 璁剧疆澶村儚URL:', updateData['formData.avatarUrl'])
this.setData(updateData)
} else {
console.log('鈿狅笍 鏈壘鍒扮敤鎴蜂俊鎭紝鏃犳硶棰勫~鍏�')
@@ -921,23 +924,31 @@
success: (res) => {
wx.hideLoading()
if (res.statusCode === 200) {
+ // 鏍规嵁瀹為檯鏂囦欢鎵╁睍鍚嶇‘瀹氭枃浠剁被鍨�
+ const fileName = attachment.name || ''
+ const fileExt = fileName.split('.').pop().toLowerCase()
const fileTypeMap = {
- 'word': 'doc',
- 'excel': 'xls',
- 'ppt': 'ppt'
+ 'doc': 'doc',
+ 'docx': 'docx',
+ 'xls': 'xls',
+ 'xlsx': 'xlsx',
+ 'ppt': 'ppt',
+ 'pptx': 'pptx'
}
wx.openDocument({
filePath: res.tempFilePath,
- fileType: fileTypeMap[fileType] || 'doc',
+ fileType: fileTypeMap[fileExt] || 'doc',
success: () => {
console.log('鏂囨。鎵撳紑鎴愬姛')
},
fail: (err) => {
console.error('鏂囨。鎵撳紑澶辫触:', err)
- wx.showToast({
- title: '鏂囨。鎵撳紑澶辫触',
- icon: 'none'
+ wx.showModal({
+ title: '鎵撳紑澶辫触',
+ content: '鏂囨。鎵撳紑澶辫触锛屽彲鑳芥槸鏂囦欢鏍煎紡涓嶆敮鎸佹垨鏂囦欢鎹熷潖',
+ showCancel: false,
+ confirmText: '纭畾'
})
}
})
@@ -1227,6 +1238,16 @@
activityPlayerId: result.activityPlayerId
})
+ // 绗笁姝ワ細鎶ュ悕鎴愬姛鍚庡己鍒惰皟鐢╳xlogin鑾峰彇鏂扮殑JWT token
+ console.log('馃摫 鎶ュ悕鎴愬姛锛屽紑濮嬪己鍒惰皟鐢╳xlogin鑾峰彇鏂扮殑JWT token')
+ try {
+ await app.wxLogin()
+ console.log('鉁� 鎶ュ悕鎴愬姛鍚巜xlogin璋冪敤鎴愬姛锛屽凡鑾峰彇鏂扮殑JWT token')
+ } catch (wxLoginError) {
+ console.error('鉂� 鎶ュ悕鎴愬姛鍚巜xlogin璋冪敤澶辫触:', wxLoginError)
+ // wxlogin澶辫触涓嶅奖鍝嶆姤鍚嶆垚鍔熺殑鎻愮ず锛屽彧璁板綍閿欒
+ }
+
wx.showToast({
title: '鎶ュ悕鎴愬姛',
icon: 'success'
diff --git a/wx/pages/registration/registration.wxml b/wx/pages/registration/registration.wxml
index 80cd8a8..d7ecf1f 100644
--- a/wx/pages/registration/registration.wxml
+++ b/wx/pages/registration/registration.wxml
@@ -17,7 +17,7 @@
<view class="input-wrapper avatar-wrapper">
<image
class="avatar-image"
- src="{{localAvatarPath || formData.avatarUrl || '../../images/default-avatar.png'}}"
+ src="{{localAvatarPath || formData.avatarUrl || '../../images/default-avatar.svg'}}"
mode="aspectFill"
/>
<text></text>
diff --git a/wx/pages/review/index.js b/wx/pages/review/index.js
new file mode 100644
index 0000000..b30b1cd
--- /dev/null
+++ b/wx/pages/review/index.js
@@ -0,0 +1,326 @@
+// pages/review/index.js
+const app = getApp()
+const { graphqlRequest, formatDate } = require('../../lib/utils')
+
+Page({
+ data: {
+ loading: false,
+ loadingMore: false,
+ hasMore: true,
+
+ // 褰撳墠閫夐」鍗� 0:鎴戞湭璇勫 1:鎴戝凡璇勫 2:瀛﹀憳鏈瘎瀹�
+ currentTab: 0,
+
+ // 鎼滅储鍏抽敭璇�
+ searchKeyword: '',
+
+ // 椤圭洰鍒楄〃
+ projectList: [],
+
+ // 缁熻鏁版嵁
+ unReviewedCount: 0,
+ reviewedCount: 0,
+ studentUnReviewedCount: 0,
+
+ // 鍒嗛〉鍙傛暟
+ pageSize: 10,
+ currentPage: 1
+ },
+
+ onLoad(options) {
+ // 濡傛灉鏈変紶鍏ョ殑tab鍙傛暟锛岃缃綋鍓嶉�夐」鍗�
+ if (options.tab) {
+ this.setData({
+ currentTab: parseInt(options.tab) || 0
+ })
+ }
+
+ this.loadData()
+ },
+
+ onShow() {
+ // 椤甸潰鏄剧ず鏃跺埛鏂版暟鎹�
+ this.loadData()
+ },
+
+ onPullDownRefresh() {
+ this.loadData()
+ },
+
+ onReachBottom() {
+ if (this.data.hasMore && !this.data.loadingMore) {
+ this.loadMoreData()
+ }
+ },
+
+ // 鍒囨崲閫夐」鍗�
+ switchTab(e) {
+ const index = e.currentTarget.dataset.index
+ if (index === this.data.currentTab) return
+
+ this.setData({
+ currentTab: index,
+ projectList: [],
+ currentPage: 1,
+ hasMore: true,
+ searchKeyword: ''
+ })
+
+ this.loadData()
+ },
+
+ // 鎼滅储杈撳叆
+ onSearchInput(e) {
+ this.setData({
+ searchKeyword: e.detail.value
+ })
+ },
+
+ // 鎼滅储纭
+ onSearch() {
+ this.setData({
+ projectList: [],
+ currentPage: 1,
+ hasMore: true
+ })
+ this.loadData()
+ },
+
+ // 娓呴櫎鎼滅储
+ clearSearch() {
+ this.setData({
+ searchKeyword: '',
+ projectList: [],
+ currentPage: 1,
+ hasMore: true
+ })
+ this.loadData()
+ },
+
+ // 鍔犺浇鏁版嵁
+ async loadData() {
+ if (this.data.loading) return
+
+ this.setData({ loading: true })
+
+ try {
+ await Promise.all([
+ this.loadProjectList(),
+ this.loadStatistics()
+ ])
+ } catch (error) {
+ console.error('鍔犺浇鏁版嵁澶辫触:', error)
+ wx.showToast({
+ title: '鍔犺浇澶辫触',
+ icon: 'none'
+ })
+ } finally {
+ this.setData({ loading: false })
+ wx.stopPullDownRefresh()
+ }
+ },
+
+ // 鍔犺浇鏇村鏁版嵁
+ async loadMoreData() {
+ if (this.data.loadingMore || !this.data.hasMore) return
+
+ this.setData({ loadingMore: true })
+
+ try {
+ await this.loadProjectList(true)
+ } catch (error) {
+ console.error('鍔犺浇鏇村澶辫触:', error)
+ } finally {
+ this.setData({ loadingMore: false })
+ }
+ },
+
+ // 鍔犺浇椤圭洰鍒楄〃
+ async loadProjectList(isLoadMore = false) {
+ const { currentTab, searchKeyword, pageSize, currentPage } = this.data
+
+ let query = ''
+ let variables = {
+ page: isLoadMore ? currentPage + 1 : 1,
+ pageSize: pageSize,
+ searchKeyword: searchKeyword || null
+ }
+
+ // 鏍规嵁褰撳墠閫夐」鍗℃瀯寤轰笉鍚岀殑鏌ヨ
+ switch (currentTab) {
+ case 0: // 鎴戞湭璇勫
+ query = `
+ query GetUnReviewedProjects($page: Int!, $pageSize: Int!, $searchKeyword: String) {
+ unReviewedProjects(page: $page, pageSize: $pageSize, searchKeyword: $searchKeyword) {
+ items {
+ id
+ projectName
+ activityName
+ stageName
+ studentName
+ submitTime
+ status
+ }
+ total
+ hasMore
+ }
+ }
+ `
+ break
+ case 1: // 鎴戝凡璇勫
+ query = `
+ query GetReviewedProjects($page: Int!, $pageSize: Int!, $searchKeyword: String) {
+ reviewedProjects(page: $page, pageSize: $pageSize, searchKeyword: $searchKeyword) {
+ items {
+ id
+ projectName
+ activityName
+ stageName
+ studentName
+ submitTime
+ reviewTime
+ score
+ status
+ }
+ total
+ hasMore
+ }
+ }
+ `
+ break
+ case 2: // 瀛﹀憳鏈瘎瀹�
+ query = `
+ query GetStudentUnReviewedProjects($page: Int!, $pageSize: Int!, $searchKeyword: String) {
+ studentUnReviewedProjects(page: $page, pageSize: $pageSize, searchKeyword: $searchKeyword) {
+ items {
+ id
+ projectName
+ activityName
+ stageName
+ studentName
+ submitTime
+ status
+ }
+ total
+ hasMore
+ }
+ }
+ `
+ break
+ }
+
+ const result = await graphqlRequest(query, variables)
+
+ if (result) {
+ const dataKey = currentTab === 0 ? 'unReviewedProjects' :
+ currentTab === 1 ? 'reviewedProjects' :
+ 'studentUnReviewedProjects'
+
+ const data = result[dataKey]
+
+ if (data && data.items) {
+ // 澶勭悊椤圭洰鏁版嵁
+ const projects = data.items.map(item => ({
+ ...item,
+ submitTime: item.submitTime ? formatDate(item.submitTime) : '',
+ reviewTime: item.reviewTime ? formatDate(item.reviewTime) : '',
+ statusText: this.getStatusText(item.status),
+ statusType: this.getStatusType(item.status)
+ }))
+
+ this.setData({
+ projectList: isLoadMore ? [...this.data.projectList, ...projects] : projects,
+ hasMore: data.hasMore || false,
+ currentPage: variables.page
+ })
+ }
+ }
+ },
+
+ // 鍔犺浇缁熻鏁版嵁
+ async loadStatistics() {
+ try {
+ const query = `
+ query GetReviewStatistics {
+ reviewStatistics {
+ unReviewedCount
+ reviewedCount
+ studentUnReviewedCount
+ }
+ }
+ `
+
+ const result = await graphqlRequest(query)
+
+ if (result && result.reviewStatistics) {
+ this.setData({
+ unReviewedCount: result.reviewStatistics.unReviewedCount || 0,
+ reviewedCount: result.reviewStatistics.reviewedCount || 0,
+ studentUnReviewedCount: result.reviewStatistics.studentUnReviewedCount || 0
+ })
+ }
+ } catch (error) {
+ console.error('鍔犺浇缁熻鏁版嵁澶辫触:', error)
+ }
+ },
+
+ // 璺宠浆鍒拌瘎瀹¤鎯呴〉闈�
+ goToReviewDetail(e) {
+ const { activityPlayerId } = e.currentTarget.dataset
+ if (activityPlayerId) {
+ wx.navigateTo({
+ url: `/pages/judge/review?id=${activityPlayerId}`
+ })
+ }
+ },
+
+ // 鑾峰彇鐘舵�佹枃鏈�
+ getStatusText(status) {
+ const statusMap = {
+ 'SUBMITTED': '宸叉彁浜�',
+ 'UNDER_REVIEW': '璇勫涓�',
+ 'REVIEWED': '宸茶瘎瀹�',
+ 'REJECTED': '宸叉嫆缁�'
+ }
+ return statusMap[status] || status
+ },
+
+ // 鑾峰彇鐘舵�佺被鍨�
+ getStatusType(status) {
+ const typeMap = {
+ 'SUBMITTED': 'info',
+ 'UNDER_REVIEW': 'warning',
+ 'REVIEWED': 'success',
+ 'REJECTED': 'danger'
+ }
+ return typeMap[status] || 'info'
+ },
+
+ // 鑾峰彇绌虹姸鎬佹枃鏈�
+ getEmptyText() {
+ const { currentTab, searchKeyword } = this.data
+
+ if (searchKeyword) {
+ return '鏈壘鍒扮浉鍏抽」鐩�'
+ }
+
+ const emptyTexts = ['鏆傛棤寰呰瘎瀹¢」鐩�', '鏆傛棤宸茶瘎瀹¢」鐩�', '鏆傛棤瀛﹀憳鏈瘎瀹¢」鐩�']
+ return emptyTexts[currentTab] || '鏆傛棤鏁版嵁'
+ },
+
+ // 鑾峰彇绌虹姸鎬佹弿杩�
+ getEmptyDesc() {
+ const { currentTab, searchKeyword } = this.data
+
+ if (searchKeyword) {
+ return '璇峰皾璇曞叾浠栧叧閿瘝鎼滅储'
+ }
+
+ const emptyDescs = [
+ '褰撳墠娌℃湁闇�瑕佹偍璇勫鐨勯」鐩�',
+ '鎮ㄨ繕娌℃湁瀹屾垚浠讳綍璇勫',
+ '褰撳墠娌℃湁瀛﹀憳鏈瘎瀹$殑椤圭洰'
+ ]
+ return emptyDescs[currentTab] || ''
+ }
+})
\ No newline at end of file
diff --git a/wx/pages/review/index.json b/wx/pages/review/index.json
new file mode 100644
index 0000000..5a56f6a
--- /dev/null
+++ b/wx/pages/review/index.json
@@ -0,0 +1,5 @@
+{
+ "navigationBarTitleText": "璇勫绠$悊",
+ "enablePullDownRefresh": true,
+ "backgroundTextStyle": "dark"
+}
\ No newline at end of file
diff --git a/wx/pages/review/index.wxml b/wx/pages/review/index.wxml
new file mode 100644
index 0000000..a05628d
--- /dev/null
+++ b/wx/pages/review/index.wxml
@@ -0,0 +1,107 @@
+<!--pages/review/index.wxml-->
+<view class="container">
+ <!-- 鍔犺浇鐘舵�� -->
+ <view class="loading-wrapper" wx:if="{{loading}}">
+ <text class="loading-text">鍔犺浇涓�...</text>
+ </view>
+
+ <!-- 涓昏鍐呭 -->
+ <view class="content" wx:else>
+ <!-- 閫夐」鍗� -->
+ <view class="tabs-container">
+ <view class="tabs">
+ <view
+ class="tab-item {{currentTab === 0 ? 'active' : ''}}"
+ bindtap="switchTab"
+ data-index="0"
+ >
+ <text class="tab-text">鎴戞湭璇勫</text>
+ <view class="tab-badge" wx:if="{{unReviewedCount > 0}}">{{unReviewedCount}}</view>
+ </view>
+ <view
+ class="tab-item {{currentTab === 1 ? 'active' : ''}}"
+ bindtap="switchTab"
+ data-index="1"
+ >
+ <text class="tab-text">鎴戝凡璇勫</text>
+ <view class="tab-badge" wx:if="{{reviewedCount > 0}}">{{reviewedCount}}</view>
+ </view>
+ <view
+ class="tab-item {{currentTab === 2 ? 'active' : ''}}"
+ bindtap="switchTab"
+ data-index="2"
+ >
+ <text class="tab-text">瀛﹀憳鏈瘎瀹�</text>
+ <view class="tab-badge" wx:if="{{studentUnReviewedCount > 0}}">{{studentUnReviewedCount}}</view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鎼滅储妗� -->
+ <view class="search-container">
+ <view class="search-box">
+ <text class="search-icon">馃攳</text>
+ <input
+ class="search-input"
+ placeholder="鎼滅储椤圭洰鍚嶇О鎴栧鍛樺鍚�"
+ value="{{searchKeyword}}"
+ bindinput="onSearchInput"
+ confirm-type="search"
+ bindconfirm="onSearch"
+ />
+ <view class="search-clear" wx:if="{{searchKeyword}}" bindtap="clearSearch">
+ <text class="clear-icon">鉁�</text>
+ </view>
+ </view>
+ </view>
+
+ <!-- 椤圭洰鍒楄〃 -->
+ <view class="project-list">
+ <!-- 绌虹姸鎬� -->
+ <view class="empty-state" wx:if="{{projectList.length === 0 && !loading}}">
+ <text class="empty-icon">馃搵</text>
+ <text class="empty-text">{{getEmptyText()}}</text>
+ <text class="empty-desc">{{getEmptyDesc()}}</text>
+ </view>
+
+ <!-- 椤圭洰鍗$墖 -->
+ <view
+ class="project-card"
+ wx:for="{{projectList}}"
+ wx:key="id"
+ wx:for-item="project"
+ >
+ <view class="project-info">
+ <view class="project-header">
+ <text class="project-name">{{project.projectName || '鏈懡鍚嶉」鐩�'}}</text>
+ <view class="project-status" wx:if="{{project.status}}">
+ <text class="status-text status-{{project.statusType}}">{{project.statusText}}</text>
+ </view>
+ </view>
+
+ <view class="project-details">
+ <text class="activity-info">{{project.activityName}} - {{project.stageName}}</text>
+ <text class="student-info" wx:if="{{project.studentName}}">瀛﹀憳锛歿{project.studentName}}</text>
+ <text class="submit-time" wx:if="{{project.submitTime}}">鎻愪氦鏃堕棿锛歿{project.submitTime}}</text>
+ </view>
+ </view>
+
+ <view class="project-actions">
+ <view
+ class="review-btn"
+ bindtap="goToReviewDetail"
+ data-project="{{project}}"
+ >
+ <text class="btn-text">璇勫</text>
+ </view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鍔犺浇鏇村 -->
+ <view class="load-more" wx:if="{{hasMore && projectList.length > 0}}">
+ <text class="load-more-text" wx:if="{{!loadingMore}}">涓婃媺鍔犺浇鏇村</text>
+ <text class="load-more-text" wx:else>鍔犺浇涓�...</text>
+ </view>
+ </view>
+</view>
\ No newline at end of file
diff --git a/wx/pages/review/index.wxss b/wx/pages/review/index.wxss
new file mode 100644
index 0000000..5023c95
--- /dev/null
+++ b/wx/pages/review/index.wxss
@@ -0,0 +1,289 @@
+/* pages/review/index.wxss */
+
+/* 瀹瑰櫒鏍峰紡 */
+.container {
+ min-height: 100vh;
+ background: #f5f5f5;
+}
+
+/* 鍔犺浇鐘舵�� */
+.loading-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 120rpx 0;
+}
+
+.loading-text {
+ font-size: 28rpx;
+ color: #999999;
+}
+
+/* 涓昏鍐呭 */
+.content {
+ padding-bottom: 40rpx;
+}
+
+/* 閫夐」鍗℃牱寮� */
+.tabs-container {
+ background: white;
+ border-bottom: 1rpx solid #e0e0e0;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.tabs {
+ display: flex;
+ align-items: center;
+}
+
+.tab-item {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 30rpx 20rpx;
+ position: relative;
+ transition: all 0.3s ease;
+}
+
+.tab-item.active {
+ color: #667eea;
+}
+
+.tab-item.active::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 60rpx;
+ height: 4rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 2rpx;
+}
+
+.tab-text {
+ font-size: 30rpx;
+ font-weight: 500;
+}
+
+.tab-badge {
+ position: absolute;
+ top: 20rpx;
+ right: 20rpx;
+ min-width: 32rpx;
+ height: 32rpx;
+ background: #ff4757;
+ border-radius: 16rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20rpx;
+ color: white;
+ padding: 0 8rpx;
+ font-weight: bold;
+}
+
+/* 鎼滅储妗嗘牱寮� */
+.search-container {
+ padding: 30rpx;
+ background: white;
+ border-bottom: 1rpx solid #e0e0e0;
+}
+
+.search-box {
+ display: flex;
+ align-items: center;
+ background: #f8f9fa;
+ border-radius: 24rpx;
+ padding: 0 30rpx;
+ height: 80rpx;
+}
+
+.search-icon {
+ font-size: 32rpx;
+ color: #999;
+ margin-right: 20rpx;
+}
+
+.search-input {
+ flex: 1;
+ font-size: 28rpx;
+ color: #333;
+}
+
+.search-clear {
+ width: 40rpx;
+ height: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #ccc;
+ border-radius: 20rpx;
+ margin-left: 20rpx;
+}
+
+.clear-icon {
+ font-size: 24rpx;
+ color: white;
+}
+
+/* 椤圭洰鍒楄〃鏍峰紡 */
+.project-list {
+ padding: 0 30rpx;
+ margin-top: 20rpx;
+}
+
+/* 绌虹姸鎬� */
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 120rpx 40rpx;
+ background: white;
+ border-radius: 16rpx;
+ margin-bottom: 20rpx;
+}
+
+.empty-icon {
+ font-size: 80rpx;
+ margin-bottom: 24rpx;
+ opacity: 0.5;
+}
+
+.empty-text {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #666;
+ margin-bottom: 12rpx;
+}
+
+.empty-desc {
+ font-size: 26rpx;
+ color: #999;
+ text-align: center;
+ line-height: 1.5;
+}
+
+/* 椤圭洰鍗$墖鏍峰紡 */
+.project-card {
+ display: flex;
+ align-items: center;
+ padding: 30rpx;
+ margin-bottom: 20rpx;
+ background: white;
+ border-radius: 16rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+ transition: all 0.3s ease;
+}
+
+.project-card:active {
+ transform: scale(0.98);
+ background: #f8f9fa;
+}
+
+.project-info {
+ flex: 1;
+ margin-right: 20rpx;
+}
+
+.project-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16rpx;
+}
+
+.project-name {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #1a1a1a;
+ flex: 1;
+ margin-right: 20rpx;
+}
+
+.project-status {
+ flex-shrink: 0;
+}
+
+.status-text {
+ font-size: 24rpx;
+ padding: 6rpx 12rpx;
+ border-radius: 12rpx;
+ font-weight: 500;
+}
+
+.status-text.status-warning {
+ background: #fff3e0;
+ color: #f57c00;
+}
+
+.status-text.status-success {
+ background: #e8f5e8;
+ color: #388e3c;
+}
+
+.status-text.status-info {
+ background: #e3f2fd;
+ color: #1976d2;
+}
+
+.project-details {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+}
+
+.activity-info {
+ font-size: 28rpx;
+ color: #667eea;
+ font-weight: 500;
+}
+
+.student-info {
+ font-size: 26rpx;
+ color: #666;
+}
+
+.submit-time {
+ font-size: 24rpx;
+ color: #999;
+}
+
+/* 鎿嶄綔鎸夐挳鏍峰紡 */
+.project-actions {
+ flex-shrink: 0;
+}
+
+.review-btn {
+ padding: 16rpx 32rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 24rpx;
+ transition: all 0.3s ease;
+}
+
+.review-btn:active {
+ transform: scale(0.95);
+ opacity: 0.8;
+}
+
+.btn-text {
+ font-size: 28rpx;
+ color: white;
+ font-weight: 500;
+}
+
+/* 鍔犺浇鏇村 */
+.load-more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40rpx;
+}
+
+.load-more-text {
+ font-size: 26rpx;
+ color: #999;
+}
\ No newline at end of file
diff --git a/wx/pages/webview/webview.js b/wx/pages/webview/webview.js
new file mode 100644
index 0000000..46c42dd
--- /dev/null
+++ b/wx/pages/webview/webview.js
@@ -0,0 +1,62 @@
+Page({
+ data: {
+ webviewUrl: '',
+ title: ''
+ },
+
+ onLoad(options) {
+ const { url, title } = options
+
+ if (!url) {
+ wx.showToast({
+ title: '缂哄皯URL鍙傛暟',
+ icon: 'none'
+ })
+ wx.navigateBack()
+ return
+ }
+
+ const decodedUrl = decodeURIComponent(url)
+ const decodedTitle = decodeURIComponent(title || '鏂囨。棰勮')
+
+ this.setData({
+ webviewUrl: decodedUrl,
+ title: decodedTitle
+ })
+
+ // 璁剧疆瀵艰埅鏍忔爣棰�
+ wx.setNavigationBarTitle({
+ title: decodedTitle
+ })
+
+ console.log('WebView鍔犺浇URL:', decodedUrl)
+ },
+
+ onLoad() {
+ console.log('WebView椤甸潰鍔犺浇瀹屾垚')
+ },
+
+ onError(e) {
+ console.error('WebView鍔犺浇閿欒:', e.detail)
+ wx.showModal({
+ title: '鍔犺浇澶辫触',
+ content: '鏂囨。棰勮澶辫触锛岃妫�鏌ョ綉缁滆繛鎺ユ垨灏濊瘯涓嬭浇鏂囦欢',
+ showCancel: false,
+ confirmText: '纭畾',
+ success: () => {
+ wx.navigateBack()
+ }
+ })
+ },
+
+ onMessage(e) {
+ console.log('WebView娑堟伅:', e.detail)
+ },
+
+ onShareAppMessage() {
+ return {
+ title: this.data.title,
+ path: `/pages/webview/webview?url=${encodeURIComponent(this.data.webviewUrl)}&title=${encodeURIComponent(this.data.title)}`
+ }
+ }
+})
\ No newline at end of file
diff --git a/wx/pages/webview/webview.json b/wx/pages/webview/webview.json
new file mode 100644
index 0000000..6c49430
--- /dev/null
+++ b/wx/pages/webview/webview.json
@@ -0,0 +1,6 @@
+{
+ "navigationBarTitleText": "鏂囨。棰勮",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f5f5f5"
+}
\ No newline at end of file
diff --git a/wx/pages/webview/webview.wxml b/wx/pages/webview/webview.wxml
new file mode 100644
index 0000000..d69ffa9
--- /dev/null
+++ b/wx/pages/webview/webview.wxml
@@ -0,0 +1,8 @@
+<view class="webview-container">
+ <web-view
+ src="{{webviewUrl}}"
+ bindmessage="onMessage"
+ binderror="onError"
+ bindload="onLoad"
+ ></web-view>
+</view>
\ No newline at end of file
diff --git a/wx/pages/webview/webview.wxss b/wx/pages/webview/webview.wxss
new file mode 100644
index 0000000..0b8dd29
--- /dev/null
+++ b/wx/pages/webview/webview.wxss
@@ -0,0 +1,9 @@
+.webview-container {
+ width: 100%;
+ height: 100vh;
+}
+
+web-view {
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git "a/wx/\345\212\237\350\203\275\345\256\236\347\216\260\346\200\273\347\273\223.md" "b/wx/\345\212\237\350\203\275\345\256\236\347\216\260\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..2216bdc
--- /dev/null
+++ "b/wx/\345\212\237\350\203\275\345\256\236\347\216\260\346\200\273\347\273\223.md"
@@ -0,0 +1,100 @@
+# 寰俊灏忕▼搴忓姛鑳藉疄鐜版�荤粨
+
+## 宸插畬鎴愮殑鍔熻兘
+
+### 1. 涓汉淇℃伅椤甸潰 (personal-info)
+- **鏂囦欢浣嶇疆**: `pages/profile/personal-info.*`
+- **鍔熻兘鐗规��**:
+ - 鐢ㄦ埛澶村儚閫夋嫨鍜屼笂浼�
+ - 涓汉淇℃伅琛ㄥ崟锛堝鍚嶃�佹�у埆銆佹墜鏈哄彿銆佺敓鏃ャ�佸鍘嗐�佷釜浜轰粙缁嶏級
+ - 鎵嬫満鍙锋巿鏉冭幏鍙�
+ - 琛ㄥ崟楠岃瘉鍜屾暟鎹繚瀛�
+ - 鍝嶅簲寮忚璁�
+
+### 2. 椤圭洰璇︽儏椤甸潰 (project/detail)
+- **鏂囦欢浣嶇疆**: `pages/project/detail.*`
+- **鍔熻兘鐗规��**:
+ - 椤圭洰鍩烘湰淇℃伅灞曠ず
+ - 椤圭洰闄勪欢鍒楄〃鍜岄瑙堝姛鑳�
+ - 鍙備笌鑰呬俊鎭睍绀�
+ - 璇勫垎淇℃伅鍜岃瘎濮旇瘎浠�
+ - 鏂囦欢棰勮锛堝浘鐗囥�佽棰戙�佹枃妗o級
+ - 鍔犺浇鐘舵�佸拰閿欒澶勭悊
+
+### 3. 涓汉涓績椤甸潰澧炲己 (profile)
+- **鏂囦欢浣嶇疆**: `pages/profile/profile.*`
+- **鍔熻兘鐗规��**:
+ - 鍔ㄦ�佺敤鎴烽」鐩垪琛�
+ - 椤圭洰鐘舵�佹樉绀猴紙宸叉彁浜ゃ�佽瘎瀹′腑銆佸凡璇勫绛夛級
+ - 椤圭洰鍗$墖鐐瑰嚮璺宠浆鍒拌鎯呴〉
+ - 绌虹姸鎬佸鐞�
+ - 椤圭洰鍥炬爣鍜岀姸鎬佹爣璇�
+
+## 鎶�鏈疄鐜拌鐐�
+
+### 1. 鏁版嵁鑾峰彇
+- 浣跨敤 GraphQL API 鑾峰彇鐢ㄦ埛椤圭洰鏁版嵁
+- 瀹炵幇浜� `loadUserProjects()` 鏂规硶
+- 鏀寔椤圭洰鐘舵�佽繃婊ゅ拰鏁版嵁鏄犲皠
+
+### 2. 椤甸潰瀵艰埅
+- 瀹炵幇浜� `goToProjectDetail()` 鏂规硶
+- 鏀寔椤圭洰ID鍙傛暟浼犻��
+- 閿欒澶勭悊鍜岀敤鎴锋彁绀�
+
+### 3. 鐘舵�佺鐞�
+- 椤圭洰鐘舵�佹枃鏈槧灏�
+- 椤圭洰鍥炬爣鏄犲皠
+- 鍔ㄦ�佹牱寮忕被鍚�
+
+### 4. 鐢ㄦ埛浣撻獙
+- 鍔犺浇鐘舵�佹寚绀�
+- 绌虹姸鎬佸弸濂芥彁绀�
+- 閿欒澶勭悊鍜岀敤鎴峰弽棣�
+- 鍝嶅簲寮忚璁�
+
+## 椤甸潰娉ㄥ唽
+
+鎵�鏈夋柊鍒涘缓鐨勯〉闈㈤兘宸插湪 `app.json` 涓纭敞鍐岋細
+- `pages/profile/personal-info`
+- `pages/project/detail`
+
+## 鏍峰紡璁捐
+
+### 1. 璁捐鍘熷垯
+- 鐜颁唬鍖栧崱鐗囧紡璁捐
+- 涓�鑷寸殑棰滆壊鏂规
+- 娓呮櫚鐨勮瑙夊眰娆�
+- 鑹ソ鐨勫彲璇绘��
+
+### 2. 鍝嶅簲寮忛�傞厤
+- 鏀寔涓嶅悓灞忓箷灏哄
+- 鍚堢悊鐨勯棿璺濆拰瀛椾綋澶у皬
+- 瑙︽懜鍙嬪ソ鐨勪氦浜掑尯鍩�
+
+## 鏁版嵁娴�
+
+```
+鐢ㄦ埛杩涘叆涓汉涓績 鈫� 鍔犺浇鐢ㄦ埛椤圭洰 鈫� 鏄剧ず椤圭洰鍒楄〃 鈫� 鐐瑰嚮椤圭洰鍗$墖 鈫� 璺宠浆鍒伴」鐩鎯呴〉 鈫� 鏄剧ず璇︾粏淇℃伅
+```
+
+## 鍚庣画鎵╁睍寤鸿
+
+1. **椤圭洰鎼滅储鍜岀瓫閫�**: 娣诲姞椤圭洰鎼滅储鍜岀姸鎬佺瓫閫夊姛鑳�
+2. **椤圭洰缂栬緫**: 鍏佽鐢ㄦ埛缂栬緫椤圭洰淇℃伅
+3. **鏂囦欢绠$悊**: 澧炲己鏂囦欢涓婁紶鍜岀鐞嗗姛鑳�
+4. **璇勮绯荤粺**: 娣诲姞椤圭洰璇勮鍜屽弽棣堝姛鑳�
+5. **鍒嗕韩鍔熻兘**: 鏀寔椤圭洰鍒嗕韩鍒扮ぞ浜ゅ钩鍙�
+
+## 娴嬭瘯寤鸿
+
+1. 鍦ㄥ井淇″紑鍙戣�呭伐鍏蜂腑鎵撳紑椤圭洰
+2. 娴嬭瘯涓汉淇℃伅椤甸潰鐨勮〃鍗曞姛鑳�
+3. 楠岃瘉椤圭洰鍒楄〃鐨勬樉绀哄拰璺宠浆
+4. 娴嬭瘯椤圭洰璇︽儏椤电殑鍚勯」鍔熻兘
+5. 妫�鏌ヤ笉鍚岃澶囧昂瀵镐笅鐨勬樉绀烘晥鏋�
+
+---
+
+**寮�鍙戝畬鎴愭椂闂�**: 2024骞�10鏈�
+**鎶�鏈爤**: 寰俊灏忕▼搴忓師鐢熷紑鍙戙�丟raphQL銆佽吘璁簯COS
\ No newline at end of file
diff --git "a/wx/\350\257\204\345\256\241\346\265\201\347\250\213\345\256\236\347\216\260\346\200\273\347\273\223.md" "b/wx/\350\257\204\345\256\241\346\265\201\347\250\213\345\256\236\347\216\260\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..bc9e38f
--- /dev/null
+++ "b/wx/\350\257\204\345\256\241\346\265\201\347\250\213\345\256\236\347\216\260\346\200\273\347\273\223.md"
@@ -0,0 +1,111 @@
+# 璇勫娴佺▼瀹炵幇鎬荤粨
+
+## 瀹屾垚鐨勫姛鑳�
+
+### 1. 璇勫鍒楄〃椤甸潰 (`pages/review/index.js`)
+- 鉁� 淇敼浜� `goToReviewDetail` 鏂规硶
+- 鉁� 浣跨敤 `activityPlayerId` 鍙傛暟瀵艰埅鍒拌瘎瀹¤鎯呴〉闈�
+- 鉁� 璺敱鏇存柊涓猴細`/pages/judge/review?id=${activityPlayerId}`
+
+### 2. 璇勫璇︽儏椤甸潰 (`pages/judge/review.js`)
+- 鉁� 淇敼浜� `onLoad` 鏂规硶锛屾帴鏀� `activityPlayerId` 鍙傛暟
+- 鉁� 鏇存柊浜� `data` 瀛楁锛屽皢 `submissionId` 鏀逛负 `activityPlayerId`
+- 鉁� 閲嶅啓浜� `loadSubmissionDetail` 鏂规硶锛�
+ - 浣跨敤 `activityPlayerDetail` GraphQL 鏌ヨ
+ - 鏋勫缓鍏煎鐨� `submission` 鍜� `activity` 鏁版嵁缁撴瀯
+ - 姝g‘澶勭悊璇勫垎鏍囧噯鍜屾彁浜ゆ枃浠�
+- 鉁� 淇敼浜� `checkReviewStatus` 鏂规硶锛�
+ - 浣跨敤 `currentJudgeRating` 鏌ヨ鑾峰彇鐜版湁璇勫垎
+ - 濡傛灉宸叉湁璇勫垎锛岃嚜鍔ㄥ~鍏呰〃鍗曟暟鎹�
+- 鉁� 鏇存柊浜� `submitReview` 鏂规硶锛�
+ - 浣跨敤 `saveActivityPlayerRating` mutation
+ - 鏋勫缓姝g‘鐨勮瘎鍒嗘暟鎹牸寮�
+- 鉁� 鏇存柊浜� `onSaveDraft` 鏂规硶锛�
+ - 浣跨敤鐩稿悓鐨� `saveActivityPlayerRating` mutation
+ - 鏀寔淇濆瓨鑽夌鍔熻兘
+- 鉁� 淇敼浜� `onViewOtherReviews` 鏂规硶锛屼娇鐢� `activityPlayerId` 鍙傛暟
+
+## 鎶�鏈疄鐜拌鐐�
+
+### GraphQL 鏌ヨ鏇存柊
+1. **鑾峰彇閫夋墜璇︽儏**锛�
+ ```graphql
+ query GetActivityPlayerDetail($activityPlayerId: ID!) {
+ activityPlayerDetail(id: $activityPlayerId) {
+ id
+ playerInfo { ... }
+ regionInfo { ... }
+ activityName
+ projectName
+ description
+ submissionFiles { ... }
+ ratingForm { ... }
+ stageId
+ }
+ }
+ ```
+
+2. **鑾峰彇褰撳墠璇勫璇勫垎**锛�
+ ```graphql
+ query GetCurrentJudgeRating($activityPlayerId: ID!) {
+ currentJudgeRating(activityPlayerId: $activityPlayerId) {
+ id
+ totalScore
+ comment
+ status
+ items { ... }
+ }
+ }
+ ```
+
+3. **淇濆瓨璇勫垎**锛�
+ ```graphql
+ mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) {
+ saveActivityPlayerRating(input: $input)
+ }
+ ```
+
+### 鏁版嵁缁撴瀯閫傞厤
+- 灏� `activityPlayerDetail` 鍝嶅簲鏁版嵁杞崲涓轰笌鍘熸湁 WXML 妯℃澘鍏煎鐨勬牸寮�
+- 姝g‘鏄犲皠閫夋墜淇℃伅銆佹椿鍔ㄤ俊鎭�佹彁浜ゆ枃浠剁瓑鏁版嵁
+- 澶勭悊璇勫垎鏍囧噯鍜岃瘎鍒嗛」鐨勬暟鎹粨鏋�
+
+## 娴嬭瘯寤鸿
+
+### 鍔熻兘娴嬭瘯
+1. **璇勫鍒楄〃椤甸潰**锛�
+ - 楠岃瘉鐐瑰嚮璇勫椤圭洰鑳芥纭烦杞埌璇︽儏椤甸潰
+ - 纭浼犻�掔殑 `activityPlayerId` 鍙傛暟姝g‘
+
+2. **璇勫璇︽儏椤甸潰**锛�
+ - 楠岃瘉閫夋墜淇℃伅鏄剧ず姝g‘
+ - 楠岃瘉鎻愪氦鏂囦欢锛堝浘鐗囥�佽棰戙�佹枃妗o級鏄剧ず鍜屼笅杞藉姛鑳�
+ - 楠岃瘉璇勫垎鏍囧噯鍔犺浇姝g‘
+ - 娴嬭瘯璇勫垎鍔熻兘锛堥�夋嫨鍒嗘暟銆佽緭鍏ヨ瘎璁猴級
+ - 娴嬭瘯淇濆瓨鑽夌鍔熻兘
+ - 娴嬭瘯鎻愪氦璇勫垎鍔熻兘
+ - 楠岃瘉宸叉湁璇勫垎鐨勫姞杞藉拰鏄剧ず
+
+3. **鏁版嵁涓�鑷存��**锛�
+ - 纭璇勫垎鏁版嵁姝g‘淇濆瓨鍒版暟鎹簱
+ - 楠岃瘉璇勫垎鐘舵�佹洿鏂版纭�
+ - 娴嬭瘯澶氭淇濆瓨鍜屾彁浜ょ殑骞傜瓑鎬�
+
+### 閿欒澶勭悊娴嬭瘯
+- 缃戠粶閿欒鏃剁殑鎻愮ず
+- 鏃犳晥鍙傛暟鐨勫鐞�
+- 鏉冮檺楠岃瘉锛堣瘎濮斿彧鑳借瘎鍒嗘寚瀹氭瘮璧涳級
+
+## 娉ㄦ剰浜嬮」
+
+1. **寰俊灏忕▼搴忔祴璇�**锛氶渶瑕佷娇鐢ㄥ井淇″紑鍙戣�呭伐鍏锋墦寮�椤圭洰杩涜娴嬭瘯
+2. **鍚庣鏈嶅姟**锛氱‘淇濆悗绔� Spring Boot 鏈嶅姟姝e湪杩愯
+3. **鏁版嵁搴�**锛氱‘璁ょ浉鍏宠〃缁撴瀯鍜屾祴璇曟暟鎹凡鍑嗗濂�
+4. **鏉冮檺鎺у埗**锛氭祴璇曟椂闇�瑕佷娇鐢ㄨ瘎濮旇韩浠界櫥褰�
+
+## 涓嬩竴姝ュ伐浣�
+
+1. 鍦ㄥ井淇″紑鍙戣�呭伐鍏蜂腑杩涜瀹屾暣鐨勫姛鑳芥祴璇�
+2. 楠岃瘉涓庡悗绔� API 鐨勬暟鎹氦浜�
+3. 娴嬭瘯鍚勭杈圭晫鎯呭喌鍜岄敊璇満鏅�
+4. 鏍规嵁娴嬭瘯缁撴灉杩涜蹇呰鐨勮皟浼樺拰淇
\ No newline at end of file
--
Gitblit v1.8.0