From afeeed281e60466b576fbe74d339634cc5d07b82 Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期三, 08 十月 2025 08:56:42 +0800
Subject: [PATCH] 修复评审功能和用户认证问题

---
 wx/pages/judge/review.js                                                           |  520 +++++++-------
 backend/src/main/java/com/rongyichuang/employee/entity/Employee.java               |   12 
 web/src/views/review-detail.vue                                                    |  114 ++
 backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java             |   32 
 backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java          |    9 
 backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java                |   73 ++
 backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java    |   76 +
 backend/src/main/java/com/rongyichuang/auth/service/AuthService.java               |   44 +
 wx/pages/review/index.js                                                           |  122 +-
 backend/src/main/resources/graphql/user.graphqls                                   |    1 
 wx/pages/registration/registration.js                                              |   15 
 backend/src/main/resources/graphql/auth.graphqls                                   |   18 
 backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java       |   47 +
 backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java             |  224 +++++
 wx/app.js                                                                          |  196 +----
 backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java         |   18 
 backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java |   31 
 backend/src/main/java/com/rongyichuang/user/service/UserService.java               |   16 
 web/src/views/check-detail.vue                                                     |  154 ++++
 backend/src/main/resources/application.yml                                         |    2 
 wx/pages/profile/personal-info.js                                                  |   94 ++
 backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java                      |   36 
 wx/pages/project/detail.js                                                         |  106 +-
 wx/pages/message/message.js                                                        |   18 
 backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java            |  103 ++
 25 files changed, 1,373 insertions(+), 708 deletions(-)

diff --git a/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java b/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java
new file mode 100644
index 0000000..4738fae
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java
@@ -0,0 +1,73 @@
+package com.rongyichuang.auth.api;
+
+import com.rongyichuang.auth.dto.PhoneDecryptResponse;
+import com.rongyichuang.auth.dto.WxLoginRequest;
+import com.rongyichuang.auth.dto.WxLoginResponse;
+import com.rongyichuang.auth.dto.LoginRequest;
+import com.rongyichuang.auth.dto.LoginResponse;
+import com.rongyichuang.auth.service.AuthService;
+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;
+
+    /**
+     * 寰俊鐧诲綍
+     */
+    @MutationMapping
+    public WxLoginResponse wxLogin(@Argument WxLoginRequest input) {
+        try {
+            return authService.wxLogin(input);
+        } catch (Exception e) {
+            throw new RuntimeException("寰俊鐧诲綍澶辫触: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Web绔櫥褰�
+     */
+    @MutationMapping
+    public LoginResponse webLogin(@Argument LoginRequest input) {
+        try {
+            return authService.login(input);
+        } catch (Exception e) {
+            throw new RuntimeException("鐧诲綍澶辫触: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 瑙e瘑寰俊鎵嬫満鍙凤紙鏃х増API锛�
+     */
+    @MutationMapping
+    public PhoneDecryptResponse decryptPhoneNumber(
+            @Argument String encryptedData,
+            @Argument String iv,
+            @Argument String sessionKey) {
+        try {
+            return authService.decryptPhoneNumber(encryptedData, iv, sessionKey);
+        } catch (Exception e) {
+            throw new RuntimeException("鎵嬫満鍙疯В瀵嗗け璐�: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 鑾峰彇寰俊鎵嬫満鍙凤紙鏂扮増API锛�
+     */
+    @MutationMapping
+    public PhoneDecryptResponse getPhoneNumberByCode(@Argument String code) {
+        try {
+            return authService.getPhoneNumberByCode(code);
+        } catch (Exception e) {
+            throw new RuntimeException("鑾峰彇鎵嬫満鍙峰け璐�: " + e.getMessage(), e);
+        }
+    }
+}
\ 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 c0f9452..328fe4f 100644
--- a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
+++ b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
@@ -169,7 +169,7 @@
      * 杩斿洖鏉冮檺閿欒鍝嶅簲
      */
     private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException {
-        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
         response.setContentType("application/json;charset=UTF-8");
         response.getWriter().write("{\"errors\":[{\"message\":\"娌℃湁鏉冮檺璁块棶锛岃鍏堢櫥褰昞",\"extensions\":{\"code\":\"UNAUTHORIZED\"}}]}");
     }
@@ -247,23 +247,37 @@
                     return;
                 }
                 
-                // 鏌ユ壘鐢ㄦ埛淇℃伅骞惰缃璇�
-                Optional<User> userOpt = userRepository.findById(userId);
-                if (userOpt.isPresent()) {
-                    User user = userOpt.get();
+                // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛锛堣礋鏁扮敤鎴稩D锛�
+                if (userId < 0) {
+                    // 鍖垮悕鐢ㄦ埛锛岃缃壒娈婄殑璁よ瘉淇℃伅
                     UsernamePasswordAuthenticationToken authToken = 
                         new UsernamePasswordAuthenticationToken(
-                            user.getId().toString(), 
+                            "anonymous_" + userId, 
                             null, 
-                            new ArrayList<>()
+                            Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
                         );
                     authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                     SecurityContextHolder.getContext().setAuthentication(authToken);
-                    logger.debug("GraphQL璇锋眰璁よ瘉鎴愬姛: userId={}", user.getId());
+                    logger.debug("GraphQL璇锋眰鍖垮悕鐢ㄦ埛璁よ瘉鎴愬姛: userId={}", userId);
                 } else {
-                    logger.warn("GraphQL璇锋眰鐨勭敤鎴蜂笉瀛樺湪: userId={}", userId);
-                    sendUnauthorizedResponse(response);
-                    return;
+                    // 姝e父鐢ㄦ埛锛屾煡鎵剧敤鎴蜂俊鎭苟璁剧疆璁よ瘉
+                    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());
@@ -306,26 +320,40 @@
             if (jwtUtil.validateToken(token)) {
                 logger.debug("Token楠岃瘉鎴愬姛锛屾煡鎵剧敤鎴蜂俊鎭�");
                 
-                // 鏌ユ壘鐢ㄦ埛淇℃伅
-                Optional<User> userOpt = userRepository.findById(userId);
-                if (userOpt.isPresent()) {
-                    User user = userOpt.get();
-                    logger.debug("鎵惧埌鐢ㄦ埛: userId={}, phone={}", user.getId(), user.getPhone());
-                    
-                    // 鍒涘缓璁よ瘉瀵硅薄
+                // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛锛堣礋鏁扮敤鎴稩D锛�
+                if (userId < 0) {
+                    // 鍖垮悕鐢ㄦ埛锛岃缃壒娈婄殑璁よ瘉淇℃伅
                     UsernamePasswordAuthenticationToken authToken = 
                         new UsernamePasswordAuthenticationToken(
-                            user.getId().toString(), 
+                            "anonymous_" + userId, 
                             null, 
-                            new ArrayList<>() // 鏆傛椂涓嶈缃潈闄愶紝鍚庣画鍙互鏍规嵁瑙掕壊璁剧疆
+                            Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
                         );
-                    
                     authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                     SecurityContextHolder.getContext().setAuthentication(authToken);
-                    
-                    logger.info("鐢ㄦ埛璁よ瘉鎴愬姛: userId={}, phone={}", user.getId(), user.getPhone());
+                    logger.info("鍖垮悕鐢ㄦ埛璁よ瘉鎴愬姛: userId={}", userId);
                 } else {
-                    logger.warn("鐢ㄦ埛涓嶅瓨鍦�: userId={}", userId);
+                    // 姝e父鐢ㄦ埛锛屾煡鎵剧敤鎴蜂俊鎭�
+                    Optional<User> userOpt = userRepository.findById(userId);
+                    if (userOpt.isPresent()) {
+                        User user = userOpt.get();
+                        logger.debug("鎵惧埌鐢ㄦ埛: userId={}, phone={}", user.getId(), user.getPhone());
+                        
+                        // 鍒涘缓璁よ瘉瀵硅薄
+                        UsernamePasswordAuthenticationToken authToken = 
+                            new UsernamePasswordAuthenticationToken(
+                                user.getId().toString(), 
+                                null, 
+                                new ArrayList<>() // 鏆傛椂涓嶈缃潈闄愶紝鍚庣画鍙互鏍规嵁瑙掕壊璁剧疆
+                            );
+                        
+                        authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                        SecurityContextHolder.getContext().setAuthentication(authToken);
+                        
+                        logger.info("鐢ㄦ埛璁よ瘉鎴愬姛: userId={}, phone={}", user.getId(), user.getPhone());
+                    } else {
+                        logger.warn("鐢ㄦ埛涓嶅瓨鍦�: userId={}", userId);
+                    }
                 }
             } else {
                 logger.warn("Token楠岃瘉澶辫触");
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 e9deed6..756a3a4 100644
--- a/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java
+++ b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java
@@ -293,9 +293,9 @@
             logger.warn("鈿狅笍 unionid涓虹┖鎴栫敤鎴峰凡鎵惧埌锛岃烦杩噓nionid鏌ユ壘");
         }
 
-        // 4. 濡傛灉閮芥病鎵惧埌鐢ㄦ埛锛屼笉鍒涘缓鏂扮敤鎴凤紝鍙褰曠櫥褰曚俊鎭�
+        // 4. 濡傛灉閮芥病鎵惧埌鐢ㄦ埛锛屼负灏忕▼搴忚闂垱寤哄尶鍚嶇敤鎴峰璞★紙涓嶄繚瀛樺埌鏁版嵁搴擄級
         if (user == null) {
-            logger.info("鏈壘鍒扮幇鏈夌敤鎴凤紝鏅�氳闂敤鎴蜂笉鍒涘缓user璁板綍锛屽彧璁板綍鐧诲綍淇℃伅");
+            logger.info("鏈壘鍒扮幇鏈夌敤鎴凤紝涓哄皬绋嬪簭璁块棶鍒涘缓鍖垮悕鐢ㄦ埛瀵硅薄");
             logger.info("璁块棶鐢ㄦ埛淇℃伅:");
             logger.info("- openid: {}", wxLoginRequest.getWxOpenid());
             logger.info("- unionid: {}", wxLoginRequest.getWxUnionid());
@@ -321,17 +321,44 @@
                 throw new RuntimeException("鍒涘缓鐧诲綍璁板綍澶辫触: " + e.getMessage(), e);
             }
             
-            // 杩斿洖璁块棶鐢ㄦ埛鐨勫搷搴旓紙鏃犵敤鎴蜂俊鎭紝鏃爐oken锛�
+            // 6. 涓哄尶鍚嶇敤鎴风敓鎴愮壒娈婄殑JWT token锛堜娇鐢ㄤ笓闂ㄧ殑wxopenid瀛楁锛�
+            logger.info("姝ラ4: 涓哄尶鍚嶇敤鎴风敓鎴怞WT token");
+            String token = null;
+            try {
+                // 浣跨敤鐗规畩鐨勭敤鎴稩D锛堣礋鏁帮級鏉ユ爣璇嗗尶鍚嶇敤鎴凤紝閬垮厤涓庣湡瀹炵敤鎴稩D鍐茬獊
+                Long anonymousUserId = -Math.abs(wxLoginRequest.getWxOpenid().hashCode()) % 1000000L;
+                // 浣跨敤鏂扮殑涓夊弬鏁版柟娉曪細userId, phone(null), wxopenid
+                token = jwtUtil.generateToken(anonymousUserId, null, wxLoginRequest.getWxOpenid());
+                logger.info("鉁� 鎴愬姛鐢熸垚鍖垮悕鐢ㄦ埛JWT token锛寃xopenid: {}", wxLoginRequest.getWxOpenid());
+            } catch (Exception e) {
+                logger.error("鉂� 鐢熸垚鍖垮悕鐢ㄦ埛JWT token澶辫触: {}", e.getMessage());
+                logger.error("寮傚父鍫嗘爤:", e);
+                throw new RuntimeException("鐢熸垚JWT token澶辫触: " + e.getMessage(), e);
+            }
+            
+            // 7. 鏋勫缓鍖垮悕鐢ㄦ埛淇℃伅
+            logger.info("姝ラ5: 鏋勫缓鍖垮悕鐢ㄦ埛淇℃伅鍝嶅簲");
+            LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo(
+                    null, // userId涓簄ull锛岃〃绀哄尶鍚嶇敤鎴�
+                    "寰俊鐢ㄦ埛", // 榛樿鍚嶇О
+                    null, // 娌℃湁鐢佃瘽鍙风爜
+                    "anonymous" // 鐢ㄦ埛绫诲瀷涓哄尶鍚�
+            );
+            
+            // 杩斿洖鍖垮悕鐢ㄦ埛鐨勫搷搴旓紙鍖呭惈鐢ㄦ埛淇℃伅鍜宼oken锛�
             WxLoginResponse response = new WxLoginResponse();
             response.setSuccess(true);
-            response.setMessage("璁块棶鎴愬姛");
+            response.setMessage("鍖垮悕璁块棶鎴愬姛");
+            response.setToken(token);
+            response.setUserInfo(userInfo);
             response.setSessionKey(wxLoginRequest.getSessionKey());
             response.setIsNewUser(false);
             response.setHasEmployee(false);
             response.setHasJudge(false);
             response.setHasPlayer(false);
+            response.setLoginRecordId(loginRecord.getId());
             
-            logger.info("=== 寰俊璁块棶娴佺▼瀹屾垚锛堟棤鐢ㄦ埛鍒涘缓锛� ===");
+            logger.info("=== 寰俊鍖垮悕璁块棶娴佺▼瀹屾垚 ===");
             return response;
         }
 
@@ -408,12 +435,13 @@
 
         // 6. 鐢熸垚JWT token
         logger.info("姝ラ5: 鐢熸垚JWT token");
-        String tokenIdentifier = user.getPhone() != null ? user.getPhone() : user.getWxOpenid();
-        logger.info("Token鏍囪瘑绗�: {}", tokenIdentifier);
+        logger.info("鐢ㄦ埛鎵嬫満鍙�: {}", user.getPhone());
+        logger.info("鐢ㄦ埛wxopenid: {}", user.getWxOpenid());
         
         String token = null;
         try {
-            token = jwtUtil.generateToken(user.getId(), tokenIdentifier);
+            // 浣跨敤鏂扮殑涓夊弬鏁版柟娉曪細userId, phone, wxopenid
+            token = jwtUtil.generateToken(user.getId(), user.getPhone(), user.getWxOpenid());
             logger.info("鉁� 鎴愬姛鐢熸垚JWT token锛岄暱搴�: {}", token != null ? token.length() : 0);
         } catch (Exception e) {
             logger.error("鉂� 鐢熸垚JWT token澶辫触: {}", e.getMessage());
diff --git a/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java b/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java
index 91b828e..489d0f8 100644
--- a/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java
+++ b/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java
@@ -25,21 +25,37 @@
     private long jwtExpiration;
 
     /**
-     * 鐢熸垚JWT token
+     * 鐢熸垚JWT token锛堟棫鐗堟湰锛屼繚鎸佸吋瀹规�э級
      */
     public String generateToken(Long userId, String phone) {
+        return generateToken(userId, phone, null);
+    }
+
+    /**
+     * 鐢熸垚JWT token锛堟柊鐗堟湰锛屾敮鎸亀xopenid锛�
+     */
+    public String generateToken(Long userId, String phone, String wxopenid) {
         Date now = new Date();
         Date expiryDate = new Date(now.getTime() + jwtExpiration);
 
         SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes());
 
-        return Jwts.builder()
+        JwtBuilder builder = Jwts.builder()
                 .setSubject(userId.toString())
-                .claim("phone", phone)
                 .setIssuedAt(now)
-                .setExpiration(expiryDate)
-                .signWith(key, SignatureAlgorithm.HS256)
-                .compact();
+                .setExpiration(expiryDate);
+
+        // 鍙湁褰損hone涓嶄负null鏃舵墠娣诲姞phone claim
+        if (phone != null) {
+            builder.claim("phone", phone);
+        }
+
+        // 鍙湁褰搘xopenid涓嶄负null鏃舵墠娣诲姞wxopenid claim
+        if (wxopenid != null) {
+            builder.claim("wxopenid", wxopenid);
+        }
+
+        return builder.signWith(key, SignatureAlgorithm.HS256).compact();
     }
 
     /**
@@ -59,6 +75,14 @@
     }
 
     /**
+     * 浠巘oken涓幏鍙栧井淇penid
+     */
+    public String getWxOpenidFromToken(String token) {
+        Claims claims = getClaimsFromToken(token);
+        return claims.get("wxopenid", String.class);
+    }
+
+    /**
      * 楠岃瘉token鏄惁鏈夋晥
      */
     public boolean validateToken(String token) {
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 2337aab..0ce0566 100644
--- a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
+++ b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java
@@ -36,19 +36,18 @@
     private JwtUtil jwtUtil;
 
     /**
-     * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID
-     * 浠嶫WT token涓В鏋愮敤鎴稩D
+     * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID锛堝寘鎷尶鍚嶇敤鎴凤級
+     * 浠嶫WT token涓В鏋愮敤鎴稩D锛屽寘鎷礋鏁扮殑鍖垮悕鐢ㄦ埛ID
      * 
-     * @return 鐢ㄦ埛ID
-     * @throws SecurityException 褰撴病鏈夋湁鏁堣璇佹椂鎶涘嚭
+     * @return 鐢ㄦ埛ID锛屽寘鎷尶鍚嶇敤鎴风殑璐熸暟ID
      */
-    public Long getCurrentUserId() {
+    public Long getCurrentUserIdIncludingAnonymous() {
         try {
             // 棣栧厛灏濊瘯浠嶩TTP璇锋眰澶翠腑鑾峰彇JWT token
             String token = getTokenFromRequest();
             if (token != null && jwtUtil.validateToken(token)) {
                 Long userId = jwtUtil.getUserIdFromToken(token);
-                logger.debug("浠嶫WT token涓幏鍙栧埌鐢ㄦ埛ID: {}", userId);
+                logger.debug("浠嶫WT token涓幏鍙栧埌鐢ㄦ埛ID锛堝寘鎷尶鍚嶇敤鎴凤級: {}", userId);
                 return userId;
             }
 
@@ -60,29 +59,103 @@
 
             // 濡傛灉娌℃湁鏈夋晥鐨凧WT token锛屽皾璇曚粠Spring Security涓婁笅鏂囪幏鍙�
             Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-            if (authentication != null && authentication.isAuthenticated() && 
-                !"anonymousUser".equals(authentication.getPrincipal())) {
-                logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", authentication.getName());
+            if (authentication != null && authentication.isAuthenticated()) {
+                String principal = authentication.getName();
+                logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", principal);
+                
+                // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛
+                if ("anonymousUser".equals(principal)) {
+                    logger.debug("妫�娴嬪埌Spring榛樿鍖垮悕鐢ㄦ埛锛岃繑鍥瀗ull");
+                    return null;
+                } else if (principal.startsWith("anonymous_")) {
+                    // 浠� "anonymous_-833488" 涓彁鍙栫敤鎴稩D
+                    try {
+                        String userIdStr = principal.substring("anonymous_".length());
+                        Long userId = Long.parseLong(userIdStr);
+                        logger.debug("浠庡尶鍚嶈璇佷腑瑙f瀽鍒扮敤鎴稩D: {}", userId);
+                        return userId;
+                    } catch (NumberFormatException e) {
+                        logger.warn("鏃犳硶浠庡尶鍚嶈璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", principal);
+                    }
+                }
+                
                 // 浠嶴pring Security涓婁笅鏂囦腑鑾峰彇鐢ㄦ埛ID
                 try {
-                    return Long.parseLong(authentication.getName());
+                    return Long.parseLong(principal);
                 } catch (NumberFormatException e) {
-                    logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", authentication.getName());
+                    logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", principal);
                 }
             }
         } catch (Exception e) {
             logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage());
         }
         
-        // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝鎶涘嚭鏉冮檺寮傚父
-        logger.warn("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝鎷掔粷璁块棶");
-        throw new SecurityException("娌℃湁鏉冮檺");
+        // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null
+        logger.debug("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null");
+        return null;
+    }
+
+    /**
+     * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID
+     * 浠嶫WT token涓В鏋愮敤鎴稩D
+     * 
+     * @return 鐢ㄦ埛ID锛屽鏋滄槸鍖垮悕鐢ㄦ埛鍒欒繑鍥瀗ull
+     */
+    public Long getCurrentUserId() {
+        try {
+            // 棣栧厛灏濊瘯浠嶩TTP璇锋眰澶翠腑鑾峰彇JWT token
+            String token = getTokenFromRequest();
+            if (token != null && jwtUtil.validateToken(token)) {
+                Long userId = jwtUtil.getUserIdFromToken(token);
+                logger.debug("浠嶫WT token涓幏鍙栧埌鐢ㄦ埛ID: {}", userId);
+                
+                // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛锛堣礋鏁扮敤鎴稩D锛�
+                if (userId != null && userId < 0) {
+                    logger.debug("妫�娴嬪埌鍖垮悕鐢ㄦ埛锛岃繑鍥瀗ull");
+                    return null;
+                }
+                
+                return userId;
+            }
+
+            if (token == null) {
+                logger.debug("鏈兘浠庤姹傚ご鑾峰彇鍒癑WT token");
+            } else {
+                logger.debug("浠庤姹傚ご鑾峰彇鍒皌oken浣嗘牎楠屽け璐�");
+            }
+
+            // 濡傛灉娌℃湁鏈夋晥鐨凧WT token锛屽皾璇曚粠Spring Security涓婁笅鏂囪幏鍙�
+            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+            if (authentication != null && authentication.isAuthenticated()) {
+                String principal = authentication.getName();
+                logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", principal);
+                
+                // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛
+                if ("anonymousUser".equals(principal) || principal.startsWith("anonymous_")) {
+                    logger.debug("妫�娴嬪埌鍖垮悕鐢ㄦ埛璁よ瘉锛岃繑鍥瀗ull");
+                    return null;
+                }
+                
+                // 浠嶴pring Security涓婁笅鏂囦腑鑾峰彇鐢ㄦ埛ID
+                try {
+                    return Long.parseLong(principal);
+                } catch (NumberFormatException e) {
+                    logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", principal);
+                }
+            }
+        } catch (Exception e) {
+            logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage());
+        }
+        
+        // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null锛堟敮鎸佸尶鍚嶈闂級
+        logger.debug("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null锛堝尶鍚嶇敤鎴凤級");
+        return null;
     }
 
     /**
      * 浠嶩TTP璇锋眰涓幏鍙朖WT token
      */
-    private String getTokenFromRequest() {
+    public String getTokenFromRequest() {
         try {
             ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
             if (attributes == null) {
diff --git a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java
index 27a88e5..e79257c 100644
--- a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java
+++ b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java
@@ -1,6 +1,7 @@
 package com.rongyichuang.employee.dto.response;
 
 import com.rongyichuang.employee.entity.Employee;
+import com.rongyichuang.user.entity.User;
 
 /**
  * 鍛樺伐鍝嶅簲DTO
@@ -11,18 +12,38 @@
     private String phone;
     private String roleId;
     private String description;
+    private Integer state;
     private String createTime;
     private String updateTime;
 
     // 鏋勯�犲嚱鏁�
     public EmployeeResponse() {}
 
+    /**
+     * @deprecated 姝ゆ瀯閫犲嚱鏁板凡搴熷純锛岃浣跨敤 EmployeeResponse(Employee, User) 鏋勯�犲嚱鏁�
+     */
+    @Deprecated
     public EmployeeResponse(Employee employee) {
         this.id = employee.getId();
         this.name = employee.getName();
-        this.phone = employee.getPhone();
+        this.phone = employee.getPhone(); // 杩欓噷浼氳繑鍥瀗ull锛屽洜涓簆hone瀛楁宸插簾寮�
         this.roleId = employee.getRoleId();
         this.description = employee.getDescription();
+        this.state = employee.getState();
+        this.createTime = employee.getCreateTime() != null ? employee.getCreateTime().toString() : null;
+        this.updateTime = employee.getUpdateTime() != null ? employee.getUpdateTime().toString() : null;
+    }
+
+    /**
+     * 鎺ㄨ崘浣跨敤鐨勬瀯閫犲嚱鏁帮紝浠嶶ser瀵硅薄鑾峰彇phone淇℃伅
+     */
+    public EmployeeResponse(Employee employee, User user) {
+        this.id = employee.getId();
+        this.name = employee.getName();
+        this.phone = user != null ? user.getPhone() : null; // 浠嶶ser瀵硅薄鑾峰彇phone
+        this.roleId = employee.getRoleId();
+        this.description = employee.getDescription();
+        this.state = employee.getState();
         this.createTime = employee.getCreateTime() != null ? employee.getCreateTime().toString() : null;
         this.updateTime = employee.getUpdateTime() != null ? employee.getUpdateTime().toString() : null;
     }
@@ -68,6 +89,14 @@
         this.description = description;
     }
 
+    public Integer getState() {
+        return state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
     public String getCreateTime() {
         return createTime;
     }
diff --git a/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java b/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java
index aa680e8..0076ce1 100644
--- a/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java
+++ b/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java
@@ -20,8 +20,10 @@
 
     /**
      * 鎵嬫満鍙风爜
+     * @deprecated 姝ゅ瓧娈靛凡搴熷純锛岀數璇濆彿鐮佺粺涓�淇濆瓨鍦╰_user琛ㄤ腑锛屾瀛楁灏嗚缃负null
      */
-    @Column(name = "phone", length = 32, nullable = false)
+    @Deprecated
+    @Column(name = "phone", length = 32, nullable = true)
     private String phone;
 
 
@@ -68,10 +70,18 @@
         this.name = name;
     }
 
+    /**
+     * @deprecated 姝ゆ柟娉曞凡搴熷純锛岃閫氳繃鍏宠仈鐨刄ser瀵硅薄鑾峰彇鐢佃瘽鍙风爜
+     */
+    @Deprecated
     public String getPhone() {
         return phone;
     }
 
+    /**
+     * @deprecated 姝ゆ柟娉曞凡搴熷純锛岀數璇濆彿鐮佺粺涓�淇濆瓨鍦║ser琛ㄤ腑
+     */
+    @Deprecated
     public void setPhone(String phone) {
         this.phone = phone;
     }
diff --git a/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java b/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java
index 417cbce..5b7be2e 100644
--- a/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java
+++ b/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java
@@ -43,12 +43,25 @@
     private static final Pattern PASSWORD_PATTERN = Pattern.compile("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*?&]{6,}$");
 
     /**
+     * 杈呭姪鏂规硶锛氬皢Employee杞崲涓篍mployeeResponse
+     */
+    private EmployeeResponse convertToResponse(Employee employee) {
+        Optional<User> userOpt = userService.findById(employee.getUserId());
+        if (userOpt.isPresent()) {
+            User user = userOpt.get();
+            return new EmployeeResponse(employee, user);
+        } else {
+            throw new BusinessException("USER_NOT_FOUND", "鍛樺伐瀵瑰簲鐨勭敤鎴蜂笉瀛樺湪锛屽憳宸D: " + employee.getId());
+        }
+    }
+
+    /**
      * 鑾峰彇鎵�鏈夊憳宸ュ垪琛�
      */
     public List<EmployeeResponse> findAllEmployees() {
         List<Employee> employees = employeeRepository.findAll();
         return employees.stream()
-                .map(EmployeeResponse::new)
+                .map(this::convertToResponse)
                 .collect(Collectors.toList());
     }
 
@@ -58,7 +71,7 @@
     public Page<EmployeeResponse> findEmployees(String name, int page, int size) {
         Pageable pageable = PageRequest.of(page, size);
         Page<Employee> employeePage = employeeRepository.findByNameContainingOrderByCreateTimeDesc(name, pageable);
-        return employeePage.map(EmployeeResponse::new);
+        return employeePage.map(this::convertToResponse);
     }
 
     /**
@@ -70,7 +83,7 @@
         }
         List<Employee> employees = employeeRepository.findByNameContaining(name.trim());
         return employees.stream()
-                .map(EmployeeResponse::new)
+                .map(this::convertToResponse)
                 .collect(Collectors.toList());
     }
 
@@ -80,7 +93,14 @@
     public EmployeeResponse findById(Long id) {
         Optional<Employee> employee = employeeRepository.findById(id);
         if (employee.isPresent()) {
-            return new EmployeeResponse(employee.get());
+            Employee emp = employee.get();
+            Optional<User> userOpt = userService.findById(emp.getUserId());
+            if (userOpt.isPresent()) {
+                User user = userOpt.get();
+                return new EmployeeResponse(emp, user);
+            } else {
+                throw new BusinessException("USER_NOT_FOUND", "鍛樺伐瀵瑰簲鐨勭敤鎴蜂笉瀛樺湪锛屽憳宸D: " + id);
+            }
         }
         throw new BusinessException("EMPLOYEE_NOT_FOUND", "鍛樺伐涓嶅瓨鍦�");
     }
@@ -116,21 +136,24 @@
             employee = employeeRepository.findById(input.getId())
                     .orElseThrow(() -> new BusinessException("EMPLOYEE_NOT_FOUND", "鍛樺伐涓嶅瓨鍦�"));
             
-            // 妫�鏌ユ墜鏈哄彿鏄惁琚叾浠栧憳宸ヤ娇鐢�
-            if (employeeRepository.existsByPhoneAndIdNot(input.getPhone(), input.getId())) {
-                throw new BusinessException("PHONE_ALREADY_EXISTS", "鎵嬫満鍙峰凡琚叾浠栧憳宸ヤ娇鐢�");
+            // 妫�鏌ョ敤鎴稩D鏄惁琚叾浠栧憳宸ヤ娇鐢紙鎺掗櫎褰撳墠鍛樺伐锛�
+            Optional<Employee> existingEmployee = employeeRepository.findByUserId(user.getId());
+            if (existingEmployee.isPresent() && !existingEmployee.get().getId().equals(input.getId())) {
+                throw new BusinessException("PHONE_ALREADY_EXISTS", "璇ユ墜鏈哄彿宸茶鍏朵粬鍛樺伐浣跨敤");
             }
         } else {
-            // 鏂板鍛樺伐
-            if (employeeRepository.existsByPhone(input.getPhone())) {
-                throw new BusinessException("PHONE_ALREADY_EXISTS", "鎵嬫満鍙峰凡瀛樺湪");
+            // 鏂板鍛樺伐 - 妫�鏌ヨ鐢ㄦ埛鏄惁宸茬粡鏄憳宸�
+            Optional<Employee> existingEmployee = employeeRepository.findByUserId(user.getId());
+            if (existingEmployee.isPresent()) {
+                throw new BusinessException("PHONE_ALREADY_EXISTS", "璇ユ墜鏈哄彿宸茶鍏朵粬鍛樺伐浣跨敤");
             }
             employee = new Employee();
         }
 
         // 璁剧疆鍩烘湰淇℃伅
         employee.setName(input.getName());
-        employee.setPhone(input.getPhone());
+        // 涓嶅啀璁剧疆phone瀛楁锛屼繚鎸佷负null
+        employee.setPhone(null);
         employee.setRoleId(input.getRoleId());
         employee.setDescription(input.getDescription());
         employee.setUserId(user.getId()); // 璁剧疆鍏宠仈鐨勭敤鎴稩D
@@ -138,7 +161,7 @@
         Employee savedEmployee = employeeRepository.save(employee);
         logger.info("鍛樺伐淇濆瓨鎴愬姛: {}", savedEmployee.getName());
         
-        return new EmployeeResponse(savedEmployee);
+        return new EmployeeResponse(savedEmployee, user);
     }
 
     /**
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 c4d82ec..dfc89f0 100644
--- a/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java
+++ b/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java
@@ -159,18 +159,28 @@
         }
 
         if (input.getId() != null) {
+            // 鏇存柊鐜版湁璇勫
             judge = judgeRepository.findById(input.getId())
                     .orElseThrow(() -> new BusinessException("璇勫涓嶅瓨鍦�"));
+            
+            // 妫�鏌ョ敤鎴稩D鏄惁琚叾浠栬瘎濮斾娇鐢�
+            Optional<Judge> existingJudgeByUserId = judgeRepository.findByUserId(user.getId());
+            if (existingJudgeByUserId.isPresent() && !existingJudgeByUserId.get().getId().equals(input.getId())) {
+                throw new BusinessException("璇ユ墜鏈哄彿宸茶鍏朵粬璇勫浣跨敤");
+            }
         } else {
+            // 鏂板璇勫
             judge = new Judge();
-            // 鏂板璇勫鏃舵鏌ユ墜鏈哄彿鏄惁宸插瓨鍦�
-            if (judgeRepository.existsByPhone(input.getPhone())) {
-                throw new BusinessException("PHONE_EXISTS", "鎵嬫満鍙风爜宸插瓨鍦紝璇蜂娇鐢ㄥ叾浠栨墜鏈哄彿鐮�");
+            
+            // 妫�鏌ヨ鐢ㄦ埛鏄惁宸叉槸璇勫
+            Optional<Judge> existingJudge = judgeRepository.findByUserId(user.getId());
+            if (existingJudge.isPresent()) {
+                throw new BusinessException("璇ユ墜鏈哄彿宸茶鍏朵粬璇勫浣跨敤");
             }
         }
 
         judge.setName(input.getName());
-        judge.setPhone(input.getPhone());
+        judge.setPhone(null); // 搴熷純judge.phone瀛楁锛岃缃负null
         judge.setGender(input.getGender());
         judge.setDescription(input.getDescription());
         judge.setTitle(input.getTitle());
@@ -204,7 +214,19 @@
         JudgeResponse response = new JudgeResponse();
         response.setId(judge.getId());
         response.setName(judge.getName());
-        response.setPhone(judge.getPhone());
+        
+        // 閫氳繃鍏宠仈鐨剈ser鑾峰彇phone
+        if (judge.getUserId() != null) {
+            Optional<User> userOpt = userService.findById(judge.getUserId());
+            if (userOpt.isPresent()) {
+                response.setPhone(userOpt.get().getPhone());
+            } else {
+                response.setPhone(null);
+            }
+        } else {
+            response.setPhone(null);
+        }
+        
         response.setGender(judge.getGender());
         response.setDescription(judge.getDescription());
         response.setTitle(judge.getTitle());
diff --git a/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java b/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java
index 0f513a3..e8c8503 100644
--- a/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java
+++ b/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java
@@ -31,10 +31,10 @@
      */
     @QueryMapping
     public ReviewProjectPageResponse unReviewedProjects(
+            @Argument String searchKeyword,
             @Argument int page,
-            @Argument int pageSize,
-            @Argument String searchKeyword) {
-        log.info("鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛紝page: {}, pageSize: {}, searchKeyword: {}", page, pageSize, searchKeyword);
+            @Argument int pageSize) {
+        log.info("鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛紝searchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize);
         
         Long currentJudgeId = userContextUtil.getCurrentJudgeId();
         if (currentJudgeId == null) {
@@ -49,10 +49,10 @@
      */
     @QueryMapping
     public ReviewProjectPageResponse reviewedProjects(
+            @Argument String searchKeyword,
             @Argument int page,
-            @Argument int pageSize,
-            @Argument String searchKeyword) {
-        log.info("鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛紝page: {}, pageSize: {}, searchKeyword: {}", page, pageSize, searchKeyword);
+            @Argument int pageSize) {
+        log.info("鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛紝searchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize);
         
         Long currentJudgeId = userContextUtil.getCurrentJudgeId();
         if (currentJudgeId == null) {
@@ -67,10 +67,10 @@
      */
     @QueryMapping
     public ReviewProjectPageResponse studentUnReviewedProjects(
+            @Argument String searchKeyword,
             @Argument int page,
-            @Argument int pageSize,
-            @Argument String searchKeyword) {
-        log.info("鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃锛宲age: {}, pageSize: {}, searchKeyword: {}", page, pageSize, searchKeyword);
+            @Argument int pageSize) {
+        log.info("鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃锛宻earchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize);
         
         Long currentJudgeId = userContextUtil.getCurrentJudgeId();
         if (currentJudgeId == null) {
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 e62996a..0011f3d 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
@@ -21,6 +21,7 @@
     private String gender;
     private String birthday;
     private List<String> roles;
+    private String userType;
     private String createdAt;
     private EmployeeInfo employee;
     private JudgeInfo judge;
@@ -115,6 +116,14 @@
         this.roles = roles;
     }
 
+    public String getUserType() {
+        return userType;
+    }
+
+    public void setUserType(String userType) {
+        this.userType = userType;
+    }
+
     public String getCreatedAt() {
         return createdAt;
     }
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 dff7fd5..7381c53 100644
--- a/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java
+++ b/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java
@@ -4,6 +4,7 @@
 import com.rongyichuang.auth.dto.LoginResponse.JudgeInfo;
 import com.rongyichuang.auth.dto.LoginResponse.PlayerInfo;
 import com.rongyichuang.common.util.UserContextUtil;
+import com.rongyichuang.auth.util.JwtUtil;
 import com.rongyichuang.employee.entity.Employee;
 import com.rongyichuang.employee.service.EmployeeService;
 import com.rongyichuang.judge.entity.Judge;
@@ -14,6 +15,9 @@
 import com.rongyichuang.common.repository.MediaRepository;
 import com.rongyichuang.common.entity.Media;
 import com.rongyichuang.common.enums.MediaTargetType;
+import com.rongyichuang.media.service.MediaV2Service;
+import com.rongyichuang.media.dto.MediaSaveInput;
+import com.rongyichuang.media.dto.MediaSaveResponse;
 import com.rongyichuang.user.dto.response.UserProfile;
 import com.rongyichuang.user.dto.response.UserStats;
 import com.rongyichuang.user.dto.response.UserRegistration;
@@ -73,6 +77,12 @@
     @Autowired
     private UserContextUtil userContextUtil;
 
+    @Autowired
+    private JwtUtil jwtUtil;
+
+    @Autowired
+    private MediaV2Service mediaV2Service;
+
     @Value("${app.media-url}")
     private String mediaBaseUrl;
 
@@ -84,8 +94,17 @@
         try {
             Long userId = userContextUtil.getCurrentUserId();
             logger.debug("杩涘叆userProfile锛岃В鏋愬埌鐢ㄦ埛ID: {}", userId);
+            
+            // 濡傛灉鏄尶鍚嶇敤鎴凤紝杩斿洖鍩烘湰鐨勭敤鎴锋。妗�
             if (userId == null) {
-                throw new RuntimeException("鐢ㄦ埛鏈櫥褰�");
+                logger.debug("鍖垮悕鐢ㄦ埛璁块棶userProfile锛岃繑鍥炲熀鏈。妗�");
+                UserProfile profile = new UserProfile();
+                profile.setId("0"); // 鍖垮悕鐢ㄦ埛ID璁句负0
+                profile.setName("鍖垮悕鐢ㄦ埛");
+                profile.setRoles(new ArrayList<>());
+                profile.setUserType("user"); // 鍖垮悕鐢ㄦ埛绫诲瀷涓烘櫘閫氱敤鎴�
+                profile.setCreatedAt(java.time.LocalDateTime.now().toString());
+                return profile;
             }
 
             UserProfile profile = new UserProfile();
@@ -96,6 +115,9 @@
             User user = null;
             if (userOpt.isPresent()) {
                 user = userOpt.get();
+                // 璁剧疆鍩烘湰淇℃伅锛堝鍚嶅拰鐢佃瘽鍙风爜锛�
+                profile.setName(user.getName());
+                profile.setPhone(user.getPhone());
                 // 璁剧疆鎬у埆
                 if (user.getGender() != null) {
                     profile.setGender(user.getGender() == 1 ? "MALE" : "FEMALE");
@@ -113,8 +135,13 @@
             Employee employee = employeeService.findByUserId(userId);
             logger.debug("鍛樺伐鏌ヨ缁撴灉: {}", employee != null ? ("id=" + employee.getId() + ", name=" + employee.getName()) : "鏃�");
             if (employee != null) {
-                profile.setName(employee.getName());
-                profile.setPhone(employee.getPhone());
+                // 濡傛灉鍛樺伐淇℃伅瀛樺湪锛屽垯瑕嗙洊鍩烘湰淇℃伅
+                if (employee.getName() != null) {
+                    profile.setName(employee.getName());
+                }
+                if (employee.getPhone() != null) {
+                    profile.setPhone(employee.getPhone());
+                }
                 roles.add("EMPLOYEE");
                 
                 EmployeeInfo employeeInfo = new EmployeeInfo(
@@ -131,8 +158,11 @@
             Judge judge = judgeService.findByUserId(userId);
             logger.debug("璇勫鏌ヨ缁撴灉: {}", judge != null ? ("id=" + judge.getId() + ", name=" + judge.getName()) : "鏃�");
             if (judge != null) {
-                if (profile.getName() == null) {
+                // 濡傛灉璇勫淇℃伅瀛樺湪涓斿熀鏈俊鎭负绌猴紝鍒欒缃瘎濮斾俊鎭�
+                if (profile.getName() == null && judge.getName() != null) {
                     profile.setName(judge.getName());
+                }
+                if (profile.getPhone() == null && judge.getPhone() != null) {
                     profile.setPhone(judge.getPhone());
                 }
                 roles.add("JUDGE");
@@ -152,8 +182,11 @@
             Player player = playerService.findByUserId(userId);
             logger.debug("瀛﹀憳鏌ヨ缁撴灉: {}", player != null ? ("id=" + player.getId() + ", name=" + player.getName()) : "鏃�");
             if (player != null) {
-                if (profile.getName() == null) {
+                // 濡傛灉瀛﹀憳淇℃伅瀛樺湪涓斿熀鏈俊鎭负绌猴紝鍒欒缃鍛樹俊鎭�
+                if (profile.getName() == null && player.getName() != null) {
                     profile.setName(player.getName());
+                }
+                if (profile.getPhone() == null && player.getPhone() != null) {
                     profile.setPhone(player.getPhone());
                 }
                 roles.add("PLAYER");
@@ -168,6 +201,20 @@
             }
             
             profile.setRoles(roles);
+            
+            // 纭畾涓昏瑙掕壊绫诲瀷锛堜紭鍏堢骇锛歟mployee > judge > player锛�
+            String userType;
+            if (employee != null) {
+                userType = "employee";
+            } else if (judge != null) {
+                userType = "judge";
+            } else if (player != null) {
+                userType = "player";
+            } else {
+                userType = "user"; // 鏅�氱敤鎴�
+            }
+            profile.setUserType(userType);
+            logger.debug("璁剧疆鐢ㄦ埛绫诲瀷: {}", userType);
             
             // 鑾峰彇鐢ㄦ埛澶村儚
             try {
@@ -429,30 +476,99 @@
     @MutationMapping
     public UserProfileInfo saveUserInfo(@Argument UserInput input) {
         try {
-            Long userId = userContextUtil.getCurrentUserId();
-            logger.debug("杩涘叆saveUserInfo锛岃В鏋愬埌鐢ㄦ埛ID: {}", userId);
+            Long userId = userContextUtil.getCurrentUserIdIncludingAnonymous();
+            logger.debug("杩涘叆saveUserInfo锛岃В鏋愬埌鐢ㄦ埛ID锛堝寘鎷尶鍚嶇敤鎴凤級: {}", userId);
             if (userId == null) {
                 throw new RuntimeException("鐢ㄦ埛鏈櫥褰�");
             }
 
-            // 鏌ユ壘鐜版湁鐢ㄦ埛
-            Optional<User> userOpt = userRepository.findById(userId);
             User user;
+            boolean isNewUser = false;
             
-            if (userOpt.isPresent()) {
-                // 鐢ㄦ埛瀛樺湪锛屾洿鏂颁俊鎭�
-                user = userOpt.get();
-                logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId);
+            // 妫�鏌ユ墜鏈哄彿鏄惁瀛樺湪
+            if (StringUtils.hasText(input.getPhone())) {
+                Optional<User> existingUserByPhone = userRepository.findByPhone(input.getPhone());
+                if (existingUserByPhone.isPresent()) {
+                    // 鎵嬫満鍙峰凡瀛樺湪锛屾洿鏂扮幇鏈夌敤鎴�
+                    user = existingUserByPhone.get();
+                    logger.debug("鎵嬫満鍙穥}宸插瓨鍦紝鏇存柊鐜版湁鐢ㄦ埛锛岀敤鎴稩D: {}", input.getPhone(), user.getId());
+                    
+                    // 濡傛灉褰撳墠鐢ㄦ埛ID涓庢墜鏈哄彿瀵瑰簲鐨勭敤鎴稩D涓嶅悓锛岄渶瑕佸鐞嗗尶鍚嶇敤鎴疯浆姝g殑鎯呭喌
+                    if (!user.getId().equals(userId)) {
+                        logger.debug("鍖垮悕鐢ㄦ埛{}杞涓烘寮忕敤鎴穥}", userId, user.getId());
+                        
+                        // 妫�鏌ュ尶鍚嶇敤鎴锋槸鍚︽湁闇�瑕佸悎骞剁殑淇℃伅
+                        if (userId < 0) {
+                            // 杩欐槸鍖垮悕鐢ㄦ埛杞锛屾鏌ユ槸鍚︽湁涓存椂淇℃伅闇�瑕佸悎骞�
+                            Optional<User> anonymousUserOpt = userRepository.findById(userId);
+                            if (anonymousUserOpt.isPresent()) {
+                                User anonymousUser = anonymousUserOpt.get();
+                                logger.debug("鍙戠幇鍖垮悕鐢ㄦ埛璁板綍锛屽噯澶囧悎骞朵俊鎭�");
+                                
+                                // 鍚堝苟鍖垮悕鐢ㄦ埛鐨勪俊鎭埌姝e紡鐢ㄦ埛锛堝鏋滄寮忕敤鎴锋病鏈夎繖浜涗俊鎭級
+                                if (!StringUtils.hasText(user.getName()) && StringUtils.hasText(anonymousUser.getName())) {
+                                    user.setName(anonymousUser.getName());
+                                    logger.debug("鍚堝苟鍖垮悕鐢ㄦ埛鐨勫鍚�: {}", anonymousUser.getName());
+                                }
+                                
+                                if (user.getGender() == null && anonymousUser.getGender() != null) {
+                                    user.setGender(anonymousUser.getGender());
+                                    logger.debug("鍚堝苟鍖垮悕鐢ㄦ埛鐨勬�у埆: {}", anonymousUser.getGender());
+                                }
+                                
+                                if (user.getBirthday() == null && anonymousUser.getBirthday() != null) {
+                                    user.setBirthday(anonymousUser.getBirthday());
+                                    logger.debug("鍚堝苟鍖垮悕鐢ㄦ埛鐨勭敓鏃�: {}", anonymousUser.getBirthday());
+                                }
+                                
+                                // 鍒犻櫎鍖垮悕鐢ㄦ埛璁板綍锛堝彲閫夛紝閬垮厤鏁版嵁鍐椾綑锛�
+                                try {
+                                    userRepository.delete(anonymousUser);
+                                    logger.debug("鍒犻櫎鍖垮悕鐢ㄦ埛璁板綍: {}", userId);
+                                } catch (Exception e) {
+                                    logger.warn("鍒犻櫎鍖垮悕鐢ㄦ埛璁板綍澶辫触: {}", e.getMessage());
+                                }
+                            }
+                        }
+                        
+                        userId = user.getId(); // 浣跨敤姝e紡鐢ㄦ埛ID
+                    }
+                } else {
+                    // 鎵嬫満鍙蜂笉瀛樺湪锛屾鏌ュ綋鍓嶇敤鎴稩D鏄惁瀛樺湪
+                    Optional<User> userOpt = userRepository.findById(userId);
+                    if (userOpt.isPresent()) {
+                        // 鐢ㄦ埛ID瀛樺湪锛屾洿鏂颁俊鎭�
+                        user = userOpt.get();
+                        logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId);
+                    } else {
+                        // 鐢ㄦ埛ID涓嶅瓨鍦紝鍒涘缓鏂扮敤鎴�
+                        user = new User();
+                        user.setId(userId);
+                        isNewUser = true;
+                        logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId);
+                    }
+                }
             } else {
-                // 鐢ㄦ埛涓嶅瓨鍦紝鍒涘缓鏂扮敤鎴�
-                user = new User();
-                user.setId(userId);
-                logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId);
+                // 娌℃湁鎻愪緵鎵嬫満鍙凤紝鐩存帴鏍规嵁鐢ㄦ埛ID鏌ユ壘鎴栧垱寤�
+                Optional<User> userOpt = userRepository.findById(userId);
+                if (userOpt.isPresent()) {
+                    user = userOpt.get();
+                    logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId);
+                } else {
+                    user = new User();
+                    user.setId(userId);
+                    isNewUser = true;
+                    logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId);
+                }
             }
 
             // 鏇存柊鐢ㄦ埛鍩烘湰淇℃伅锛堜笉鍖呭惈澶村儚锛�
-            user.setName(input.getName());
-            user.setPhone(input.getPhone());
+            if (StringUtils.hasText(input.getName())) {
+                user.setName(input.getName());
+            }
+            if (StringUtils.hasText(input.getPhone())) {
+                user.setPhone(input.getPhone());
+            }
             
             // 澶勭悊鎬у埆杞崲锛歁ALE -> 1, FEMALE -> 0
             if ("MALE".equals(input.getGender())) {
@@ -470,10 +586,75 @@
                     logger.warn("鐢熸棩鏍煎紡瑙f瀽澶辫触: {}", input.getBirthday(), e);
                 }
             }
+            
+            // 澶勭悊wxopenid鏇存柊锛氫粠JWT token涓幏鍙栧綋鍓嶇敤鎴风殑wxopenid
+            try {
+                String token = userContextUtil.getTokenFromRequest();
+                if (token != null && jwtUtil.validateToken(token)) {
+                    Long currentUserId = jwtUtil.getUserIdFromToken(token);
+                    String wxopenidFromToken = jwtUtil.getWxOpenidFromToken(token);
+                    
+                    // 濡傛灉token涓寘鍚玾xopenid锛屽垯鏇存柊鐢ㄦ埛鐨剋xopenid
+                    if (StringUtils.hasText(wxopenidFromToken)) {
+                        logger.debug("浠巘oken涓幏鍙栧埌wxopenid: {}", wxopenidFromToken);
+                        
+                        // 妫�鏌ヨ繖涓猳penid鏄惁宸茬粡琚叾浠栫敤鎴蜂娇鐢�
+                        Optional<User> existingUserWithOpenid = userRepository.findByWxOpenid(wxopenidFromToken);
+                        if (existingUserWithOpenid.isEmpty() || existingUserWithOpenid.get().getId().equals(user.getId())) {
+                            user.setWxOpenid(wxopenidFromToken);
+                            logger.debug("璁剧疆鐢ㄦ埛wxopenid: {}", wxopenidFromToken);
+                        } else {
+                            logger.warn("wxopenid {} 宸茶鍏朵粬鐢ㄦ埛浣跨敤锛岀敤鎴稩D: {}", wxopenidFromToken, existingUserWithOpenid.get().getId());
+                        }
+                    } else {
+                        logger.debug("token涓湭鍖呭惈wxopenid淇℃伅");
+                    }
+                }
+            } catch (Exception e) {
+                logger.warn("澶勭悊wxopenid鏇存柊鏃跺彂鐢熷紓甯�: {}", e.getMessage(), e);
+            }
 
             // 淇濆瓨鐢ㄦ埛鍩烘湰淇℃伅
             user = userRepository.save(user);
-            logger.debug("鐢ㄦ埛淇℃伅淇濆瓨鎴愬姛锛岀敤鎴稩D: {}", user.getId());
+            logger.debug("鐢ㄦ埛淇℃伅淇濆瓨鎴愬姛锛岀敤鎴稩D: {}, 鏄惁鏂扮敤鎴�: {}", user.getId(), isNewUser);
+
+            // 澶勭悊澶村儚淇濆瓨
+            if (StringUtils.hasText(input.getAvatar())) {
+                try {
+                    logger.debug("寮�濮嬩繚瀛樼敤鎴峰ご鍍忥紝璺緞: {}", input.getAvatar());
+                    
+                    // 鏋勫缓MediaSaveInput
+                    MediaSaveInput mediaSaveInput = new MediaSaveInput();
+                    mediaSaveInput.setTargetType("player"); // 浣跨敤"player"浣滀负鐩爣绫诲瀷
+                    mediaSaveInput.setTargetId(user.getId());
+                    mediaSaveInput.setPath(input.getAvatar());
+                    mediaSaveInput.setMediaType(1); // 1琛ㄧず鍥剧墖
+                    mediaSaveInput.setFileSize(0L); // 璁剧疆榛樿鏂囦欢澶у皬涓�0锛岄伩鍏嶆暟鎹簱绾︽潫閿欒
+                    
+                    // 浠庤矾寰勪腑鎻愬彇鏂囦欢鍚嶅拰鎵╁睍鍚�
+                    String fileName = input.getAvatar();
+                    if (fileName.contains("/")) {
+                        fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
+                    }
+                    mediaSaveInput.setFileName(fileName);
+                    
+                    if (fileName.contains(".")) {
+                        String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1);
+                        mediaSaveInput.setFileExt(fileExt);
+                    }
+                    
+                    // 淇濆瓨澶村儚濯掍綋璁板綍
+                    MediaSaveResponse saveResponse = mediaV2Service.saveMedia(mediaSaveInput);
+                    if (saveResponse.getSuccess()) {
+                        logger.debug("澶村儚淇濆瓨鎴愬姛锛屽獟浣揑D: {}", saveResponse.getMediaId());
+                    } else {
+                        logger.warn("澶村儚淇濆瓨澶辫触: {}", saveResponse.getMessage());
+                    }
+                } catch (Exception e) {
+                    logger.error("淇濆瓨澶村儚鏃跺彂鐢熼敊璇�", e);
+                    // 澶村儚淇濆瓨澶辫触涓嶅奖鍝嶇敤鎴蜂俊鎭繚瀛�
+                }
+            }
 
             // 鏋勫缓杩斿洖缁撴灉
             UserProfileInfo result = new UserProfileInfo();
@@ -494,12 +675,13 @@
             try {
                 List<Media> avatarMedias = mediaRepository.findByTargetTypeAndTargetIdAndState(
                     MediaTargetType.USER_AVATAR.getValue(), 
-                    userId, 
+                    user.getId(), 
                     1
                 );
                 if (!avatarMedias.isEmpty()) {
                     Media avatarMedia = avatarMedias.get(0);
                     result.setAvatar(buildFullMediaUrl(avatarMedia.getPath()));
+                    logger.debug("璁剧疆澶村儚URL: {}", result.getAvatar());
                 }
             } catch (Exception e) {
                 logger.warn("鑾峰彇澶村儚澶辫触: {}", e.getMessage(), e);
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 a4c4913..0e8f24a 100644
--- a/backend/src/main/java/com/rongyichuang/user/service/UserService.java
+++ b/backend/src/main/java/com/rongyichuang/user/service/UserService.java
@@ -75,14 +75,22 @@
                         boolean needUpdateWx = false;
                         if (currentWxOpenid != null && !currentWxOpenid.trim().isEmpty()) {
                             if (user.getWxOpenid() == null || !currentWxOpenid.equals(user.getWxOpenid())) {
-                                user.setWxOpenid(currentWxOpenid);
-                                needUpdateWx = true;
+                                // 妫�鏌ヨ繖涓猳penid鏄惁宸茬粡琚叾浠栫敤鎴蜂娇鐢�
+                                Optional<User> existingUserWithOpenid = userRepository.findByWxOpenid(currentWxOpenid);
+                                if (existingUserWithOpenid.isEmpty() || existingUserWithOpenid.get().getId().equals(user.getId())) {
+                                    user.setWxOpenid(currentWxOpenid);
+                                    needUpdateWx = true;
+                                }
                             }
                         }
                         if (currentWxUnionid != null && !currentWxUnionid.trim().isEmpty()) {
                             if (user.getWxUnionid() == null || !currentWxUnionid.equals(user.getWxUnionid())) {
-                                user.setWxUnionid(currentWxUnionid);
-                                needUpdateWx = true;
+                                // 妫�鏌ヨ繖涓猽nionid鏄惁宸茬粡琚叾浠栫敤鎴蜂娇鐢�
+                                Optional<User> existingUserWithUnionid = userRepository.findByWxUnionid(currentWxUnionid);
+                                if (existingUserWithUnionid.isEmpty() || existingUserWithUnionid.get().getId().equals(user.getId())) {
+                                    user.setWxUnionid(currentWxUnionid);
+                                    needUpdateWx = true;
+                                }
                             }
                         }
                         if (needUpdateWx) {
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
index 9f5dff8..f4b9dda 100644
--- a/backend/src/main/resources/application.yml
+++ b/backend/src/main/resources/application.yml
@@ -86,7 +86,7 @@
   base-url: https://rych.9village.cn
   jwt:
     secret: ryc-jwt-secret-key-2024-secure-256bit-hmac-sha-algorithm-compatible
-    expiration: 7200000 # 2灏忔椂
+    expiration: 86400000 # 24灏忔椂
   media-url:  https://ryc-1379367838.cos.ap-chengdu.myqcloud.com
 
 # 寰俊灏忕▼搴忛厤缃�
diff --git a/backend/src/main/resources/graphql/auth.graphqls b/backend/src/main/resources/graphql/auth.graphqls
index aa790e5..7addfa7 100644
--- a/backend/src/main/resources/graphql/auth.graphqls
+++ b/backend/src/main/resources/graphql/auth.graphqls
@@ -4,8 +4,17 @@
 }
 
 extend type Mutation {
-    # 璁よ瘉鐩稿叧鎺ュ彛宸茶縼绉诲埌RESTful API (/auth/*)
-    _: Boolean
+    # 寰俊鐧诲綍
+    wxLogin(input: WxLoginRequest!): WxLoginResponse
+    
+    # Web绔櫥褰�
+    webLogin(input: LoginRequest!): LoginResponse
+    
+    # 瑙e瘑寰俊鎵嬫満鍙凤紙鏃х増API锛�
+    decryptPhoneNumber(encryptedData: String!, iv: String!, sessionKey: String!): PhoneDecryptResponse
+    
+    # 鑾峰彇寰俊鎵嬫満鍙凤紙鏂扮増API锛�
+    getPhoneNumberByCode(code: String!): PhoneDecryptResponse
 }
 
 input LoginRequest {
@@ -35,6 +44,11 @@
     isNewUser: Boolean
     loginRecordId: Long
     sessionKey: String
+    success: Boolean
+    message: String
+    hasEmployee: Boolean
+    hasJudge: Boolean
+    hasPlayer: Boolean
 }
 
 type UserInfo {
diff --git a/backend/src/main/resources/graphql/user.graphqls b/backend/src/main/resources/graphql/user.graphqls
index 58628d0..36d96e3 100644
--- a/backend/src/main/resources/graphql/user.graphqls
+++ b/backend/src/main/resources/graphql/user.graphqls
@@ -13,6 +13,7 @@
     gender: String
     birthday: String
     roles: [String!]!
+    userType: String
     createdAt: String
     # 瑙掕壊鐩稿叧淇℃伅
     employee: EmployeeInfo
diff --git a/web/src/views/check-detail.vue b/web/src/views/check-detail.vue
index 139047a..d60d400 100644
--- a/web/src/views/check-detail.vue
+++ b/web/src/views/check-detail.vue
@@ -115,6 +115,13 @@
               <el-button 
                 type="primary" 
                 size="small" 
+                @click="previewAttachment(attachment)"
+              >
+                棰勮
+              </el-button>
+              <el-button 
+                type="primary" 
+                size="small" 
                 @click="downloadAttachment(attachment)"
               >
                 涓嬭浇
@@ -130,7 +137,24 @@
           <div class="card-header">
             <span>瀹℃牳绠$悊</span>
           </div>
-        </template>
+          <!-- 闄勪欢棰勮瀵硅瘽妗� -->
+  <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center>
+    <div class="preview-content">
+      <!-- 鍥剧墖棰勮 -->
+      <img v-if="previewType === 'image' && previewUrl" :src="previewUrl" style="max-width: 100%; max-height: 70vh; object-fit: contain;" />
+      <!-- 瑙嗛棰勮 -->
+      <video v-else-if="previewType === 'video' && previewUrl" :src="previewUrl" controls style="width: 100%; max-height: 70vh;"></video>
+      <!-- PDF 棰勮 -->
+      <iframe v-else-if="previewType === 'pdf' && previewUrl" :src="previewUrl" style="width: 100%; height: 70vh; border: none;"></iframe>
+      <!-- DOCX 棰勮 -->
+      <div v-else-if="previewType === 'docx'" ref="docxContainer" class="docx-preview"></div>
+      <!-- 鍏跺畠涓嶆敮鎸� -->
+      <div v-else class="preview-error">
+        <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨嬶紝璇蜂笅杞芥煡鐪�" />
+      </div>
+    </div>
+  </el-dialog>
+</template>
         
         <div class="review-section">
           <div class="review-status">
@@ -223,6 +247,12 @@
 const playerData = ref<any>(null)
 const activityPlayerData = ref<any>(null)
 const attachments = ref<any[]>([])
+
+// 棰勮鐩稿叧
+const previewVisible = ref(false)
+const previewUrl = ref('')
+const previewType = ref<'' | 'image' | 'video' | 'pdf' | 'docx' | 'unknown'>('')
+const docxContainer = ref<HTMLElement | null>(null)
 
 // 瀹℃牳鐩稿叧鏁版嵁
 const feedbackText = ref('')
@@ -422,6 +452,113 @@
 const downloadAttachment = (attachment: any) => {
   // TODO: 瀹炵幇闄勪欢涓嬭浇鍔熻兘
   window.open(attachment.url, '_blank')
+}
+
+/**
+ * 棰勮闄勪欢锛氭寜鎵╁睍鍚嶅垎鍙� 鍥剧墖/瑙嗛/PDF/DOCX
+ */
+const previewAttachment = async (attachment: any) => {
+  const name: string = attachment.originalName || attachment.name || ''
+  const url: string = attachment.url
+  const ext = (name.split('.').pop() || '').toLowerCase()
+
+  previewVisible.value = true
+  previewUrl.value = ''
+  previewType.value = ''
+
+  const imageExts = ['jpg','jpeg','png','gif','bmp','webp']
+  const videoExts = ['mp4','webm','ogg','avi','mov','wmv','flv','mkv']
+
+  if (imageExts.includes(ext)) {
+    previewType.value = 'image'
+    previewUrl.value = url
+    return
+  }
+  if (videoExts.includes(ext)) {
+    previewType.value = 'video'
+    previewUrl.value = url
+    return
+  }
+  if (ext === 'pdf') {
+    previewType.value = 'pdf'
+    previewUrl.value = url
+    return
+  }
+  if (ext === 'docx') {
+    previewType.value = 'docx'
+    try {
+      await renderDocx(url)
+    } catch (e: any) {
+      console.error('DOCX 棰勮澶辫触:', e)
+      ElMessage.warning('DOCX 棰勮澶辫触锛屽缓璁笅杞芥煡鐪�')
+      previewType.value = 'unknown'
+    }
+    return
+  }
+  if (ext === 'doc') {
+    ElMessage.info('鏆備笉鏀寔 .doc 棰勮锛岃涓嬭浇鏌ョ湅')
+    previewType.value = 'unknown'
+    return
+  }
+
+  ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅')
+  previewType.value = 'unknown'
+}
+
+/**
+ * 鍔ㄦ�佸姞杞� docx-preview 骞舵覆鏌� DOCX
+ */
+const renderDocx = async (url: string) => {
+  // 鍔ㄦ�佸姞杞� docx-preview
+  const ensureScript = () => new Promise((resolve, reject) => {
+    if ((window as any).docx && (window as any).docx.renderAsync) return resolve(true)
+    const existed = document.querySelector('script[data-docx-preview]')
+    if (existed) {
+      existed.addEventListener('load', () => resolve(true))
+      existed.addEventListener('error', reject)
+      return
+    }
+    const s = document.createElement('script')
+    s.src = 'https://unpkg.com/docx-preview/dist/docx-preview.min.js'
+    s.async = true
+    s.setAttribute('data-docx-preview', '1')
+    s.onload = () => resolve(true)
+    s.onerror = reject
+    document.head.appendChild(s)
+  })
+
+  // 瑙勮寖鍖� URL锛堟敮鎸佺浉瀵硅矾寰勶級
+  const buildUrl = (u: string) => {
+    if (!u) return u
+    if (u.startsWith('http://') || u.startsWith('https://')) return u
+    if (u.startsWith('/')) return location.origin + u
+    return u
+  }
+
+  // 鎼哄甫閴存潈澶磋闂檮浠讹紙寰堝鏂囦欢鏈嶅姟涓嶈蛋 cookie锛岃�屾槸 JWT Header锛�
+  const { getToken } = await import('@/utils/auth')
+  const token = getToken ? getToken() : ''
+  const requestUrl = buildUrl(url)
+
+  let res: Response
+  try {
+    res = await fetch(requestUrl, {
+      credentials: 'include',
+      headers: token ? { Authorization: `Bearer ${token}` } : undefined,
+      mode: 'cors'
+    } as RequestInit)
+  } catch (e) {
+    throw new Error('鑾峰彇 DOCX 澶辫触: 缃戠粶涓嶅彲杈炬垨琚嫤鎴�')
+  }
+  if (!res.ok) throw new Error('鑾峰彇 DOCX 澶辫触: ' + res.status)
+
+  const blob = await res.blob()
+
+  await ensureScript()
+  if (docxContainer.value) {
+    docxContainer.value.innerHTML = ''
+    await (window as any).docx.renderAsync(blob, docxContainer.value, null, { inWrapper: true })
+  }
 }
 
 // 瀹℃牳閫氳繃
@@ -770,4 +907,19 @@
     grid-template-columns: 1fr;
   }
 }
+  .preview-content {
+    text-align: center;
+  }
+
+  .preview-error {
+    padding: 40px 0;
+  }
+
+  .docx-preview {
+    text-align: left;
+    max-height: 70vh;
+    overflow: auto;
+    background: #fff;
+    padding: 12px;
+  }
 </style>
\ No newline at end of file
diff --git a/web/src/views/review-detail.vue b/web/src/views/review-detail.vue
index 848948b..fd0a3be 100644
--- a/web/src/views/review-detail.vue
+++ b/web/src/views/review-detail.vue
@@ -168,13 +168,17 @@
     <!-- 鏂囦欢棰勮瀵硅瘽妗� -->
     <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center>
       <div class="preview-content">
-        <iframe 
-          v-if="previewUrl" 
-          :src="previewUrl" 
-          style="width: 100%; height: 500px; border: none;"
-        ></iframe>
+        <!-- 鍥剧墖棰勮 -->
+        <img v-if="previewType === 'image' && previewUrl" :src="previewUrl" style="max-width: 100%; max-height: 70vh; object-fit: contain;" />
+        <!-- 瑙嗛棰勮 -->
+        <video v-else-if="previewType === 'video' && previewUrl" :src="previewUrl" controls style="width: 100%; max-height: 70vh;"></video>
+        <!-- PDF 棰勮 -->
+        <iframe v-else-if="previewType === 'pdf' && previewUrl" :src="previewUrl" style="width: 100%; height: 70vh; border: none;"></iframe>
+        <!-- DOCX 棰勮 -->
+        <div v-else-if="previewType === 'docx'" ref="docxContainer" class="docx-preview"></div>
+        <!-- 鍏跺畠涓嶆敮鎸� -->
         <div v-else class="preview-error">
-          <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨�" />
+          <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨嬶紝璇蜂笅杞芥煡鐪�" />
         </div>
       </div>
     </el-dialog>
@@ -204,6 +208,8 @@
 const ratingComment = ref('')
 const previewVisible = ref(false)
 const previewUrl = ref('')
+const previewType = ref('') // image | video | pdf | docx | unknown
+const docxContainer = ref(null)
 
 // 鏉冮檺楠岃瘉鐩稿叧
 const currentJudge = ref(null)
@@ -446,17 +452,84 @@
   }
 }
 
-// 鏂囦欢棰勮
-const previewFile = (file) => {
-  // 鏍规嵁鏂囦欢绫诲瀷鍐冲畾棰勮鏂瑰紡
-  const fileExtension = file.name.split('.').pop().toLowerCase()
-  const previewableTypes = ['pdf', 'txt', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm']
-  
-  if (previewableTypes.includes(fileExtension)) {
-    // 鍦ㄦ柊绐楀彛涓墦寮�棰勮
-    window.open(file.url, '_blank')
-  } else {
-    ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅')
+/**
+ * 鏂囦欢棰勮锛氭寜鎵╁睍鍚嶅垎鍙戝埌鍥剧墖/瑙嗛/PDF/DOCX
+ */
+const previewFile = async (file) => {
+  const ext = (file.name?.split('.').pop() || '').toLowerCase()
+  previewVisible.value = true
+  previewUrl.value = ''
+  previewType.value = ''
+  // 缁熶竴鍙栧彲鐢ㄧ殑瀹屾暣 URL
+  const url = file.url || file.fullUrl || file.full_path || file.path
+
+  const imageExts = ['jpg','jpeg','png','gif','bmp','webp']
+  const videoExts = ['mp4','webm','ogg','avi','mov','wmv','flv','mkv']
+
+  if (imageExts.includes(ext)) {
+    previewType.value = 'image'
+    previewUrl.value = url
+    return
+  }
+  if (videoExts.includes(ext)) {
+    previewType.value = 'video'
+    previewUrl.value = url
+    return
+  }
+  if (ext === 'pdf') {
+    previewType.value = 'pdf'
+    previewUrl.value = url
+    return
+  }
+  if (ext === 'docx') {
+    previewType.value = 'docx'
+    try {
+      await renderDocx(url)
+    } catch (e) {
+      console.error('DOCX 棰勮澶辫触:', e)
+      ElMessage.warning('DOCX 棰勮澶辫触锛屽缓璁笅杞芥煡鐪�')
+      previewType.value = 'unknown'
+    }
+    return
+  }
+  if (ext === 'doc') {
+    ElMessage.info('鏆備笉鏀寔 .doc 棰勮锛岃涓嬭浇鏌ョ湅')
+    previewType.value = 'unknown'
+    return
+  }
+
+  ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅')
+  previewType.value = 'unknown'
+}
+
+/**
+ * 鍔ㄦ�佸姞杞� docx-preview 骞舵覆鏌� DOCX
+ */
+const renderDocx = async (url) => {
+  const ensureScript = () => new Promise((resolve, reject) => {
+    if (window.docx && window.docx.renderAsync) return resolve(true)
+    const existed = document.querySelector('script[data-docx-preview]')
+    if (existed) {
+      existed.addEventListener('load', () => resolve(true))
+      existed.addEventListener('error', reject)
+      return
+    }
+    const s = document.createElement('script')
+    s.src = 'https://unpkg.com/docx-preview/dist/docx-preview.min.js'
+    s.async = true
+    s.setAttribute('data-docx-preview', '1')
+    s.onload = () => resolve(true)
+    s.onerror = reject
+    document.head.appendChild(s)
+  })
+
+  await ensureScript()
+  const res = await fetch(url, { credentials: 'include' })
+  if (!res.ok) throw new Error('鑾峰彇 DOCX 澶辫触: ' + res.status)
+  const blob = await res.blob()
+  if (docxContainer.value) {
+    docxContainer.value.innerHTML = ''
+    await window.docx.renderAsync(blob, docxContainer.value, null, { inWrapper: true })
   }
 }
 
@@ -699,6 +772,13 @@
 .preview-content {
   text-align: center;
 }
+.docx-preview {
+  text-align: left;
+  max-height: 70vh;
+  overflow: auto;
+  background: #fff;
+  padding: 12px;
+}
 
 .preview-error {
   padding: 40px 0;
diff --git a/wx/app.js b/wx/app.js
index 57d5a4b..4145d1c 100644
--- a/wx/app.js
+++ b/wx/app.js
@@ -163,7 +163,7 @@
         }
         
         // 妫�鏌ユ槸鍚︽湁閿欒淇℃伅锛堥�傞厤涓嶅悓鐨勯敊璇搷搴旀牸寮忥級
-        if (res.data.error || res.data.message || res.data.success === false) {
+        if (res.data.error || res.data.success === false) {
           const errorMsg = res.data.error || res.data.message || '鐧诲綍澶辫触'
           console.error('鉂� 鐧诲綍澶辫触:', errorMsg)
           wx.showToast({
@@ -276,174 +276,56 @@
   // GraphQL璇锋眰灏佽
   graphqlRequest(query, variables = {}) {
     return new Promise((resolve, reject) => {
-      this._makeGraphQLRequest(query, variables, resolve, reject, false)
-    })
-  },
-
-  // 鍐呴儴GraphQL璇锋眰鏂规硶锛屾敮鎸侀噸璇曟満鍒�
-  _makeGraphQLRequest(query, variables, resolve, reject, isRetry = false) {
-    // 纭繚token鐨勪竴鑷存�э細浼樺厛浣跨敤globalData涓殑token锛屽鏋滄病鏈夊垯浠巗torage鑾峰彇
-    let token = this.globalData.token
-    if (!token) {
-      token = wx.getStorageSync('token')
-      if (token) {
-        this.globalData.token = token // 鍚屾鍒癵lobalData
+      // 纭繚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': token ? `Bearer ${token}` : ''
-      },
-      data: {
-        query: query,
-        variables: variables
-      },
-      success: (res) => {
-        console.log('GraphQL鍝嶅簲:', res.data)
-        
-        // 妫�鏌TTP鐘舵�佺爜
-        if (res.statusCode !== 200) {
-          // 瀵逛簬401鐘舵�佺爜锛屽彲鑳芥槸璁よ瘉閿欒锛岄渶瑕佹鏌ュ搷搴斿唴瀹�
-          if (res.statusCode === 401 && res.data && res.data.errors) {
-            console.log('鏀跺埌401鐘舵�佺爜锛屾鏌ユ槸鍚︿负璁よ瘉閿欒')
-            // 缁х画澶勭悊锛岃涓嬮潰鐨凣raphQL閿欒妫�鏌ラ�昏緫澶勭悊璁よ瘉閿欒
-          } else {
+      wx.request({
+        url: this.globalData.baseUrl,
+        method: 'POST',
+        header: {
+          'Content-Type': 'application/json',
+          'Authorization': token ? `Bearer ${token}` : ''
+        },
+        data: {
+          query: query,
+          variables: variables
+        },
+        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 && res.data.errors) {
-          console.error('GraphQL閿欒:', res.data.errors)
-          
-          // 妫�鏌ユ槸鍚︽槸璁よ瘉閿欒锛坱oken杩囨湡鎴栨棤鏁堬級
-          const authErrors = res.data.errors.filter(error => 
-            error.message && (
-              error.message.includes('娌℃湁鏉冮檺璁块棶') ||
-              error.message.includes('璇峰厛鐧诲綍') ||
-              error.message.includes('UNAUTHORIZED') ||
-              error.extensions?.code === 'UNAUTHORIZED'
-            )
-          )
-          
-          if (authErrors.length > 0 && !isRetry) {
-            console.log('馃攧 妫�娴嬪埌璁よ瘉閿欒锛屽皾璇曢噸鏂扮櫥褰�...')
-            // 娓呴櫎杩囨湡鐨勮璇佷俊鎭�
-            this.globalData.token = null
-            this.globalData.userInfo = null
-            this.globalData.sessionKey = null
-            wx.removeStorageSync('token')
-            wx.removeStorageSync('userInfo')
-            wx.removeStorageSync('sessionKey')
-            
-            // 閲嶆柊鐧诲綍
-            wx.login({
-              success: (loginRes) => {
-                if (loginRes.code) {
-                  console.log('馃攧 閲嶆柊鑾峰彇寰俊code鎴愬姛锛岃皟鐢ㄥ悗绔櫥褰�...')
-                  this._retryAfterLogin(loginRes.code, query, variables, resolve, reject)
-                } else {
-                  console.error('鉂� 閲嶆柊鑾峰彇寰俊code澶辫触')
-                  reject(new Error('閲嶆柊鐧诲綍澶辫触'))
-                }
-              },
-              fail: (err) => {
-                console.error('鉂� 閲嶆柊鐧诲綍澶辫触:', err)
-                reject(new Error('閲嶆柊鐧诲綍澶辫触'))
-              }
-            })
+          // 妫�鏌raphQL閿欒
+          if (res.data.errors) {
+            console.error('GraphQL閿欒:', res.data.errors)
+            reject(new Error(res.data.errors[0]?.message || 'GraphQL璇锋眰閿欒'))
             return
           }
-          
-          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(new Error('GraphQL鍝嶅簲鏁版嵁寮傚父'))
-        }
-      },
-      fail: (err) => {
-        console.error('GraphQL缃戠粶璇锋眰澶辫触:', err)
-        reject(new Error('缃戠粶璇锋眰澶辫触'))
-      }
-    })
-  },
-
-  // 閲嶆柊鐧诲綍鍚庨噸璇旼raphQL璇锋眰
-  _retryAfterLogin(code, query, variables, resolve, reject) {
-    const that = this
-    const deviceInfo = this.getDeviceInfo()
-    const requestData = {
-      code: code,
-      loginIp: '127.0.0.1', // 灏忕▼搴忔棤娉曡幏鍙栫湡瀹濱P锛屼娇鐢ㄩ粯璁ゅ��
-      deviceInfo: deviceInfo
-    }
-    
-    wx.request({
-      url: 'http://localhost:8080/api/auth/wx-login',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json'
-      },
-      data: requestData,
-      success: (res) => {
-        console.log('馃攧 閲嶆柊鐧诲綍鍝嶅簲:', res.data)
-        
-        if (res.statusCode !== 200 || res.data.error) {
-          console.error('鉂� 閲嶆柊鐧诲綍澶辫触:', res.data.error || res.data.message)
-          reject(new Error('閲嶆柊鐧诲綍澶辫触'))
-          return
-        }
-        
-        // 妫�鏌ュ搷搴旀暟鎹牸寮�
-        let loginResult = null
-        if (res.data.token && res.data.userInfo) {
-          loginResult = res.data
-        } else if (res.data.success && res.data.data) {
-          loginResult = res.data.data
-        }
-        
-        if (loginResult && loginResult.token) {
-          console.log('鉁� 閲嶆柊鐧诲綍鎴愬姛锛屾洿鏂皌oken')
-          
-          // 淇濆瓨鏂扮殑鐧诲綍淇℃伅
-          try {
-            wx.setStorageSync('token', loginResult.token)
-            wx.setStorageSync('userInfo', loginResult.userInfo)
-            if (loginResult.sessionKey) {
-              wx.setStorageSync('sessionKey', loginResult.sessionKey)
-            }
-          } catch (storageErr) {
-            console.error('鉂� 淇濆瓨閲嶆柊鐧诲綍淇℃伅澶辫触:', storageErr)
+          // 妫�鏌ユ暟鎹�
+          if (res.data.data !== undefined) {
+            resolve(res.data.data)
+          } else {
+            console.error('GraphQL鍝嶅簲寮傚父:', res.data)
+            reject(new Error('GraphQL鍝嶅簲鏁版嵁寮傚父'))
           }
-          
-          that.globalData.token = loginResult.token
-          that.globalData.userInfo = loginResult.userInfo
-          that.globalData.sessionKey = loginResult.sessionKey
-          
-          // 浣跨敤鏂皌oken閲嶈瘯鍘熷璇锋眰
-          console.log('馃攧 浣跨敤鏂皌oken閲嶈瘯GraphQL璇锋眰...')
-          that._makeGraphQLRequest(query, variables, resolve, reject, true)
-        } else {
-          console.error('鉂� 閲嶆柊鐧诲綍鍝嶅簲鏍煎紡閿欒')
-          reject(new Error('閲嶆柊鐧诲綍鍝嶅簲鏍煎紡閿欒'))
+        },
+        fail: (err) => {
+          console.error('GraphQL缃戠粶璇锋眰澶辫触:', err)
+          reject(new Error('缃戠粶璇锋眰澶辫触'))
         }
-      },
-      fail: (err) => {
-        console.error('鉂� 閲嶆柊鐧诲綍缃戠粶璇锋眰澶辫触:', err)
-        reject(new Error('閲嶆柊鐧诲綍缃戠粶璇锋眰澶辫触'))
-      }
+      })
     })
   }
 })
\ No newline at end of file
diff --git a/wx/pages/judge/review.js b/wx/pages/judge/review.js
index 1fd19b0..27afd37 100644
--- a/wx/pages/judge/review.js
+++ b/wx/pages/judge/review.js
@@ -1,6 +1,6 @@
 // pages/judge/review.js
 const app = getApp()
-const { graphqlRequest, formatDate: formatDateUtil } = require('../../lib/utils')
+const { graphqlRequest, formatDate } = require('../../lib/utils')
 
 Page({
   data: {
@@ -10,15 +10,13 @@
     // 鎻愪氦浣滃搧淇℃伅
     submission: null,
     activityPlayerId: '',
-    stageId: null,
-    submissionId: null,
-
+    
     // 娲诲姩淇℃伅
     activity: null,
-
+    
     // 璇勫鏍囧噯
     criteria: [],
-
+    
     // 璇勫垎鏁版嵁
     scores: {},
     
@@ -33,7 +31,24 @@
     reviewStatus: 'PENDING', // PENDING, COMPLETED
     
     // 宸叉湁璇勫璁板綍
-    existingReview: null
+    existingReview: null,
+    
+    // 濯掍綋棰勮
+    showMediaPreview: false,
+    currentMedia: null,
+    mediaType: 'image',
+    
+    // 鏂囦欢涓嬭浇
+    downloadingFiles: [],
+    
+    // 璇勫垎绛夌骇
+    scoreOptions: [
+      { value: 1, label: '1鍒� - 寰堝樊' },
+      { value: 2, label: '2鍒� - 杈冨樊' },
+      { value: 3, label: '3鍒� - 涓�鑸�' },
+      { value: 4, label: '4鍒� - 鑹ソ' },
+      { value: 5, label: '5鍒� - 浼樼' }
+    ]
   },
 
   onLoad(options) {
@@ -47,32 +62,6 @@
     // 椤甸潰鏄剧ず鏃舵鏌ヨ瘎瀹$姸鎬�
     if (this.data.submissionId) {
       this.checkReviewStatus()
-    }
-  },
-
-  transformMediaFile(file) {
-    const url = file.fullUrl || file.url
-    const thumbUrl = file.fullThumbUrl || url
-    const ext = (file.fileExt || '').toLowerCase()
-    let mediaType = 'file'
-
-    if (file.mediaType === 1 || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic'].includes(ext)) {
-      mediaType = 'image'
-    } else if (file.mediaType === 2 || ['mp4', 'mov', 'avi', 'wmv', 'mkv', 'webm', 'flv'].includes(ext)) {
-      mediaType = 'video'
-    } else if (ext === 'pdf') {
-      mediaType = 'pdf'
-    } else if (['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'wps', 'txt', 'rtf'].includes(ext)) {
-      mediaType = 'word'
-    }
-
-    return {
-      id: file.id,
-      name: file.name,
-      url,
-      thumbUrl,
-      mediaType,
-      size: file.fileSize || 0
     }
   },
 
@@ -113,11 +102,13 @@
             submissionFiles {
               id
               name
+              url
               fullUrl
-              fullThumbUrl
               fileExt
               fileSize
               mediaType
+              thumbUrl
+              fullThumbUrl
             }
             ratingForm {
               schemeId
@@ -126,8 +117,10 @@
               items {
                 id
                 name
+                description
                 maxScore
-                orderNo
+                weight
+                sortOrder
               }
             }
           }
@@ -144,64 +137,62 @@
           id: detail.id,
           title: detail.projectName,
           description: detail.description,
-          submittedAt: detail.submitTime || null,
-          team: detail.team || null,
+          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,
-            phone: detail.playerInfo?.phone || detail.playerInfo?.userInfo?.phone || '',
-            avatar: detail.playerInfo?.userInfo?.avatarUrl || '/images/default-avatar.svg',
-            gender: this.getGenderLabel(detail.playerInfo?.gender),
-            birthday: this.getBirthdayText(detail.playerInfo?.birthday),
-            region: detail.regionInfo?.fullPath || detail.regionInfo?.name || '',
-            education: detail.playerInfo?.education || '',
+            id: detail.playerInfo.id,
+            name: detail.playerInfo.name,
             school: detail.regionInfo ? detail.regionInfo.name : '',
-            major: detail.playerInfo?.education || ''
+            major: detail.playerInfo.education || '',
+            avatar: detail.playerInfo.userInfo?.avatarUrl || '/images/default-avatar.svg'
           },
-          status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING',
-          mediaList: (detail.submissionFiles || []).map(file => this.transformMediaFile(file))
+          status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING'
         }
-
-        const criteria = (detail.ratingForm?.items || []).map(item => {
-          const maxScore = item.maxScore || 0
-          return {
-            id: item.id,
-            name: item.name,
-            maxScore,
-            description: item.description || '鏆傛棤鎻忚堪',
-            step: maxScore > 20 ? 1 : 0.5,
-            currentScore: 0
-          }
-        })
-
+        
+        // 鏋勫缓activity瀵硅薄
+        const activity = {
+          id: detail.stageId,
+          title: detail.activityName,
+          description: detail.description,
+          judgeCriteria: detail.ratingForm ? detail.ratingForm.items || [] : []
+        }
+        
+        // 鍒濆鍖栬瘎鍒嗘暟鎹�
         const scores = {}
-        criteria.forEach(criterion => {
-          scores[criterion.id] = 0
-        })
-
-        const maxScore = detail.ratingForm?.totalMaxScore || criteria.reduce((sum, item) => sum + (item.maxScore || 0), 0)
-
+        let maxScore = 0
+        
+        if (activity.judgeCriteria) {
+          activity.judgeCriteria.forEach(criterion => {
+            scores[criterion.id] = 0 // 鏆傛椂璁句负0锛屽悗缁渶瑕佹煡璇㈠凡鏈夎瘎鍒�
+            maxScore += criterion.maxScore
+          })
+        }
+        
         this.setData({
           submission,
-          activity: {
-            id: detail.id,
-            stageId: detail.stageId,
-            ratingSchemeId: detail.ratingForm?.schemeId || null,
-            totalMaxScore: maxScore
-          },
-          stageId: detail.stageId || null,
-          submissionId: detail.id,
-          criteria,
+          activity,
+          criteria: activity.judgeCriteria || [],
           scores,
           maxScore,
-          totalScore: 0,
-          existingReview: null,
+          existingReview: null, // 鏆傛椂璁句负null锛屽悗缁渶瑕佹煡璇㈠凡鏈夎瘎鍒�
           reviewStatus: 'PENDING',
           comment: ''
         })
-
+        
         this.calculateTotalScore()
-
+        
         // 妫�鏌ユ槸鍚﹀凡鏈夎瘎鍒�
         this.checkReviewStatus()
       }
@@ -224,7 +215,7 @@
           currentJudgeRating(activityPlayerId: $activityPlayerId) {
             id
             totalScore
-            remark
+            comment
             status
             ratedAt
             items {
@@ -248,98 +239,38 @@
         
         if (rating.items) {
           rating.items.forEach(item => {
-            const numericScore = item.score !== undefined && item.score !== null ? Number(item.score) : 0
-            scores[item.ratingItemId] = numericScore
-            totalScore += numericScore
+            scores[item.ratingItemId] = item.score
+            totalScore += item.score
           })
         }
-
-        const updatedCriteria = this.data.criteria.map(criterion => {
-          const value = scores[criterion.id] !== undefined ? scores[criterion.id] : 0
-          return {
-            ...criterion,
-            currentScore: value
-          }
-        })
-
-        const normalizedTotal = Number(totalScore.toFixed(2))
-
+        
         this.setData({
           scores,
-          criteria: updatedCriteria,
-          totalScore: normalizedTotal,
-          comment: rating.remark || rating.comment || '',
-          existingReview: {
-            ...rating,
-            totalScore: rating.totalScore ? Number(rating.totalScore) : normalizedTotal,
-            reviewedAt: rating.ratedAt || rating.reviewedAt || rating.updateTime || null
-          },
-          reviewStatus: 'COMPLETED'
+          totalScore,
+          comment: rating.comment || '',
+          existingReview: rating,
+          reviewStatus: rating.status || 'COMPLETED'
         })
         
         console.log('宸插姞杞界幇鏈夎瘎鍒�:', rating)
       } else {
         console.log('褰撳墠璇勫灏氭湭璇勫垎')
       }
-
-      this.calculateTotalScore()
     } catch (error) {
       console.error('妫�鏌ヨ瘎瀹$姸鎬佸け璐�:', error)
     }
   },
 
-  normalizeScore(value, criterion) {
-    const maxScore = Number(criterion.maxScore || 0)
-    const step = Number(criterion.step || (maxScore > 20 ? 1 : 0.5))
-    if (Number.isNaN(value)) {
-      value = 0
-    }
-    let normalized = Math.round(value / step) * step
-    if (normalized < 0) normalized = 0
-    if (normalized > maxScore) normalized = maxScore
-    return Number(normalized.toFixed(2))
-  },
-
-  updateCriterionScore(criterionId, index, value) {
-    const criterion = this.data.criteria[index]
-    if (!criterion) return
-    const normalized = this.normalizeScore(value, criterion)
-
-    this.setData({
-      [`scores.${criterionId}`]: normalized,
-      [`criteria[${index}].currentScore`]: normalized
-    })
-
-    this.calculateTotalScore()
-  },
-
   // 璇勫垎鏀瑰彉
   onScoreChange(e) {
-    const { criterionId, index } = e.currentTarget.dataset
-    const criterion = this.data.criteria[index]
-    if (!criterion) return
-
-    const inputValue = Number(e.detail.value)
-    const newScore = this.normalizeScore(inputValue, criterion)
-    this.updateCriterionScore(criterionId, index, newScore)
-  },
-
-  increaseScore(e) {
-    const { criterionId, index } = e.currentTarget.dataset
-    const criterion = this.data.criteria[index]
-    if (!criterion) return
-    const current = Number(this.data.scores[criterionId] || criterion.currentScore || 0)
-    const step = criterion.step || (criterion.maxScore > 20 ? 1 : 0.5)
-    this.updateCriterionScore(criterionId, index, current + step)
-  },
-
-  decreaseScore(e) {
-    const { criterionId, index } = e.currentTarget.dataset
-    const criterion = this.data.criteria[index]
-    if (!criterion) return
-    const current = Number(this.data.scores[criterionId] || criterion.currentScore || 0)
-    const step = criterion.step || (criterion.maxScore > 20 ? 1 : 0.5)
-    this.updateCriterionScore(criterionId, index, current - step)
+    const { criterionId } = e.currentTarget.dataset
+    const { value } = e.detail
+    
+    this.setData({
+      [`scores.${criterionId}`]: parseInt(value)
+    })
+    
+    this.calculateTotalScore()
   },
 
   // 璁$畻鎬诲垎
@@ -348,11 +279,11 @@
     let totalScore = 0
     
     criteria.forEach(criterion => {
-      const score = Number(scores[criterion.id] || 0)
-      totalScore += score
+      const score = scores[criterion.id] || 0
+      totalScore += score * (criterion.weight || 1)
     })
     
-    this.setData({ totalScore: Number(totalScore.toFixed(2)) })
+    this.setData({ totalScore })
   },
 
   // 璇勫鎰忚杈撳叆
@@ -364,58 +295,106 @@
 
   // 濯掍綋鐐瑰嚮
   onMediaTap(e) {
-    const index = Number(e.currentTarget.dataset.index)
-    const mediaList = this.data.submission?.mediaList || []
-    const media = mediaList[index]
-    if (!media) return
-
-    if (media.mediaType === 'image') {
-      const imageUrls = mediaList
-        .filter(item => item.mediaType === 'image')
-        .map(item => item.url)
+    const { url, type } = e.currentTarget.dataset
+    
+    if (type === 'image') {
       wx.previewImage({
-        current: media.url,
-        urls: imageUrls
+        current: url,
+        urls: this.data.submission.images || []
       })
-    } else if (media.mediaType === 'video') {
-      wx.navigateTo({
-        url: `/pages/video/video?url=${encodeURIComponent(media.url)}&title=${encodeURIComponent(media.name)}`
+    } else if (type === 'video') {
+      this.setData({
+        showMediaPreview: true,
+        currentMedia: url,
+        mediaType: 'video'
       })
-    } else {
-      this.openDocumentMedia(media)
     }
   },
 
-  async openDocumentMedia(media) {
+  // 鍏抽棴濯掍綋棰勮
+  onCloseMediaPreview() {
+    this.setData({
+      showMediaPreview: false,
+      currentMedia: null
+    })
+  },
+
+  // 涓嬭浇鏂囦欢
+  async onDownloadFile(e) {
+    const { fileId, fileName, fileUrl } = e.currentTarget.dataset
+    
     try {
-      wx.showLoading({ title: '鎵撳紑涓�...' })
-      const downloadRes = await new Promise((resolve, reject) => {
-        wx.downloadFile({
-          url: media.url,
-          success: resolve,
-          fail: reject
-        })
-      })
-
-      if (downloadRes.statusCode !== 200) {
-        throw new Error('鏂囦欢涓嬭浇澶辫触')
+      // 娣诲姞鍒颁笅杞戒腑鍒楄〃
+      const downloadingFiles = [...this.data.downloadingFiles, fileId]
+      
+      // 鍚屾椂鏇存柊鏂囦欢鐨刬sDownloading瀛楁
+      const submission = { ...this.data.submission }
+      if (submission.files) {
+        submission.files = submission.files.map(file => ({
+          ...file,
+          isDownloading: file.id === fileId ? true : file.isDownloading
+        }))
       }
-
-      await new Promise((resolve, reject) => {
-        wx.openDocument({
-          filePath: downloadRes.tempFilePath,
-          showMenu: true,
-          success: resolve,
-          fail: reject
-        })
+      
+      this.setData({ 
+        downloadingFiles,
+        submission
+      })
+      
+      wx.showLoading({ title: '涓嬭浇涓�...' })
+      
+      const result = await wx.downloadFile({
+        url: fileUrl,
+        success: (res) => {
+          if (res.statusCode === 200) {
+            // 淇濆瓨鍒扮浉鍐屾垨鏂囦欢
+            wx.saveFile({
+              tempFilePath: res.tempFilePath,
+              success: () => {
+                wx.showToast({
+                  title: '涓嬭浇鎴愬姛',
+                  icon: 'success'
+                })
+              },
+              fail: () => {
+                wx.showToast({
+                  title: '淇濆瓨澶辫触',
+                  icon: 'error'
+                })
+              }
+            })
+          }
+        },
+        fail: () => {
+          wx.showToast({
+            title: '涓嬭浇澶辫触',
+            icon: 'error'
+          })
+        }
       })
     } catch (error) {
-      console.error('鎵撳紑鏂囦欢澶辫触:', error)
+      console.error('涓嬭浇鏂囦欢澶辫触:', error)
       wx.showToast({
-        title: '鏃犳硶鎵撳紑鏂囦欢',
+        title: '涓嬭浇澶辫触',
         icon: 'error'
       })
     } finally {
+      // 浠庝笅杞戒腑鍒楄〃绉婚櫎
+      const downloadingFiles = this.data.downloadingFiles.filter(id => id !== fileId)
+      
+      // 鍚屾椂鏇存柊鏂囦欢鐨刬sDownloading瀛楁
+      const submission = { ...this.data.submission }
+      if (submission.files) {
+        submission.files = submission.files.map(file => ({
+          ...file,
+          isDownloading: file.id === fileId ? false : file.isDownloading
+        }))
+      }
+      
+      this.setData({ 
+        downloadingFiles,
+        submission
+      })
       wx.hideLoading()
     }
   },
@@ -423,7 +402,6 @@
   // 楠岃瘉璇勫鏁版嵁
   validateReview() {
     const { scores, criteria, comment } = this.data
-    const commentText = (comment || '').trim()
     
     // 妫�鏌ユ槸鍚︽墍鏈夋爣鍑嗛兘宸茶瘎鍒�
     for (let criterion of criteria) {
@@ -437,7 +415,7 @@
     }
     
     // 妫�鏌ヨ瘎瀹℃剰瑙�
-    if (!commentText) {
+    if (!comment.trim()) {
       wx.showToast({
         title: '璇峰~鍐欒瘎瀹℃剰瑙�',
         icon: 'error'
@@ -445,7 +423,7 @@
       return false
     }
     
-    if (commentText.length < 10) {
+    if (comment.trim().length < 10) {
       wx.showToast({
         title: '璇勫鎰忚鑷冲皯10涓瓧绗�',
         icon: 'error'
@@ -454,6 +432,54 @@
     }
     
     return true
+  },
+
+  // 淇濆瓨鑽夌
+  async onSaveDraft() {
+    try {
+      wx.showLoading({ title: '淇濆瓨涓�...' })
+      
+      const { activityPlayerId, scores, comment, criteria, activity } = this.data
+      
+      // 鏋勫缓璇勫垎椤规暟缁�
+      const ratings = criteria.map(criterion => ({
+        itemId: criterion.id,
+        score: scores[criterion.id] || 0
+      }))
+      
+      const mutation = `
+        mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) {
+          saveActivityPlayerRating(input: $input)
+        }
+      `
+      
+      const input = {
+        activityPlayerId,
+        stageId: activity.stageId,
+        ratings,
+        comment: comment.trim()
+      }
+      
+      const result = await graphqlRequest(mutation, { input })
+      
+      if (result && result.saveActivityPlayerRating) {
+        wx.showToast({
+          title: '鑽夌宸蹭繚瀛�',
+          icon: 'success'
+        })
+        
+        // 閲嶆柊鍔犺浇璇勫垎鐘舵��
+        await this.checkReviewStatus()
+      }
+    } catch (error) {
+      console.error('淇濆瓨鑽夌澶辫触:', error)
+      wx.showToast({
+        title: '淇濆瓨澶辫触',
+        icon: 'error'
+      })
+    } finally {
+      wx.hideLoading()
+    }
   },
 
   // 鎻愪氦璇勫
@@ -479,23 +505,12 @@
       this.setData({ submitting: true })
       wx.showLoading({ title: '鎻愪氦涓�...' })
       
-      const { activityPlayerId, scores, comment, criteria, stageId } = this.data
-      const commentText = (comment || '').trim()
-
-      if (!stageId) {
-        wx.showToast({
-          title: '缂哄皯闃舵淇℃伅锛屾棤娉曟彁浜�',
-          icon: 'none'
-        })
-        this.setData({ submitting: false })
-        wx.hideLoading()
-        return
-      }
+      const { activityPlayerId, scores, comment, criteria, activity } = this.data
       
       // 鏋勫缓璇勫垎椤规暟缁�
       const ratings = criteria.map(criterion => ({
         itemId: criterion.id,
-        score: Number(scores[criterion.id] || 0)
+        score: scores[criterion.id] || 0
       }))
       
       const mutation = `
@@ -506,9 +521,9 @@
       
       const input = {
         activityPlayerId,
-        stageId,
+        stageId: activity.stageId,
         ratings,
-        comment: commentText
+        comment: comment.trim()
       }
       
       const result = await graphqlRequest(mutation, { input })
@@ -554,17 +569,34 @@
   // 鑱旂郴鍙傝禌鑰�
   onContactParticipant() {
     const { submission } = this.data
-    const phone = submission?.participant?.phone
-    if (phone) {
-      wx.makePhoneCall({
-        phoneNumber: phone
-      })
-    } else {
-      wx.showToast({
-        title: '鏆傛棤鑱旂郴鏂瑰紡',
-        icon: 'none'
+    
+    if (submission.participant) {
+      wx.showActionSheet({
+        itemList: ['鍙戦�佹秷鎭�', '鏌ョ湅璇︽儏'],
+        success: (res) => {
+          switch (res.tapIndex) {
+            case 0:
+              // 鍙戦�佹秷鎭姛鑳�
+              wx.navigateTo({
+                url: `/pages/chat/chat?userId=${submission.participant.id}`
+              })
+              break
+            case 1:
+              // 鏌ョ湅鐢ㄦ埛璇︽儏
+              wx.navigateTo({
+                url: `/pages/user/profile?userId=${submission.participant.id}`
+              })
+              break
+          }
+        }
       })
     }
+  },
+
+  // 鑾峰彇璇勫垎绛夌骇鏂囨湰
+  getScoreLabel(score) {
+    const option = this.data.scoreOptions.find(opt => opt.value === score)
+    return option ? option.label : `${score}鍒哷
   },
 
   // 鑾峰彇鏂囦欢澶у皬鏂囨湰
@@ -578,47 +610,9 @@
     }
   },
 
-  // 缁熶竴澶勭悊鎬у埆鏄剧ず鏂囨湰
-  getGenderLabel(gender) {
-    if (gender === null || gender === undefined || gender === '') {
-      return '鏈~鍐�'
-    }
-
-    const normalized = String(gender).trim().toLowerCase()
-
-    if (normalized === '') {
-      return '鏈~鍐�'
-    }
-
-    if (normalized === 'male' || normalized === 'm') {
-      return '鐢�'
-    }
-    if (normalized === 'female' || normalized === 'f') {
-      return '濂�'
-    }
-
-    if (/^-?\d+$/.test(normalized)) {
-      const numeric = Number(normalized)
-      if (numeric === 1) return '鐢�'
-      if (numeric === 0) return '濂�'
-      if (numeric === 2) return '濂�'
-    }
-
-    return gender === undefined || gender === null ? '鏈~鍐�' : String(gender)
-  },
-
-  // 缁熶竴澶勭悊鍑虹敓鏃ユ湡鏄剧ず鏂囨湰
-  getBirthdayText(dateString) {
-    if (!dateString) {
-      return '鏈~鍐�'
-    }
-    const formatted = formatDateUtil(dateString, 'YYYY-MM-DD')
-    return formatted || '鏈~鍐�'
-  },
-
   // 鏍煎紡鍖栨棩鏈�
   formatDate(dateString) {
-    return formatDateUtil(dateString, 'YYYY-MM-DD HH:mm')
+    return formatDate(dateString, 'YYYY-MM-DD HH:mm')
   },
 
   // 鍒嗕韩椤甸潰
diff --git a/wx/pages/message/message.js b/wx/pages/message/message.js
index e390706..4fa1f29 100644
--- a/wx/pages/message/message.js
+++ b/wx/pages/message/message.js
@@ -21,22 +21,8 @@
 
   // 鍔犺浇娑堟伅鍒楄〃
   loadMessages() {
-    // 妫�鏌ョ敤鎴锋槸鍚﹀凡鐧诲綍锛屽鏋済lobalData涓病鏈夛紝灏濊瘯浠庢湰鍦板瓨鍌ㄦ仮澶�
-    let userInfo = app.globalData.userInfo
-    if (!userInfo || !userInfo.userId) {
-      console.log('globalData涓病鏈塽serInfo锛屽皾璇曚粠鏈湴瀛樺偍鎭㈠')
-      try {
-        const storedUserInfo = wx.getStorageSync('userInfo')
-        if (storedUserInfo && storedUserInfo.userId) {
-          console.log('浠庢湰鍦板瓨鍌ㄦ仮澶島serInfo鎴愬姛')
-          app.globalData.userInfo = storedUserInfo
-          userInfo = storedUserInfo
-        }
-      } catch (error) {
-        console.error('浠庢湰鍦板瓨鍌ㄦ仮澶島serInfo澶辫触:', error)
-      }
-    }
-    
+    // 妫�鏌ョ敤鎴锋槸鍚﹀凡鐧诲綍
+    const userInfo = app.globalData.userInfo
     if (!userInfo || !userInfo.userId) {
       console.error('鐢ㄦ埛鏈櫥褰曟垨userId涓嶅瓨鍦�')
       wx.showToast({
diff --git a/wx/pages/profile/personal-info.js b/wx/pages/profile/personal-info.js
index 1fbdb5f..b9f5f07 100644
--- a/wx/pages/profile/personal-info.js
+++ b/wx/pages/profile/personal-info.js
@@ -157,10 +157,17 @@
           title: '鑾峰彇涓�...'
         })
         
-        // TODO: 璋冪敤鍚庣API瑙e瘑鎵嬫満鍙�
+        // 璋冪敤鍚庣API瑙e瘑鎵嬫満鍙�
+        const app = getApp()
+        const sessionKey = app.globalData.sessionKey
+        
+        if (!sessionKey) {
+          throw new Error('SessionKey涓嶅瓨鍦紝璇烽噸鏂扮櫥褰�')
+        }
+        
         const mutation = `
-          mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!) {
-            decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv) {
+          mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!, $sessionKey: String!) {
+            decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv, sessionKey: $sessionKey) {
               phoneNumber
             }
           }
@@ -168,7 +175,8 @@
         
         const variables = {
           encryptedData: e.detail.encryptedData,
-          iv: e.detail.iv
+          iv: e.detail.iv,
+          sessionKey: sessionKey
         }
         
         const result = await graphqlRequest(mutation, variables)
@@ -229,10 +237,13 @@
       return
     }
 
-    if (!userInfo.phone) {
-      wx.showToast({
-        title: '璇疯幏鍙栨墜鏈哄彿',
-        icon: 'none'
+    // 寮哄埗瑕佹眰鎺堟潈鐢佃瘽鍙风爜
+    if (!userInfo.phone || userInfo.phone.trim() === '') {
+      wx.showModal({
+        title: '闇�瑕佹巿鏉冩墜鏈哄彿',
+        content: '鏍规嵁骞冲彴瑙勫畾锛屽繀椤绘巿鏉冩墜鏈哄彿鐮佹墠鑳戒繚瀛樼敤鎴蜂俊鎭�傝鍏堣幏鍙栨墜鏈哄彿鐮佹巿鏉冦��',
+        showCancel: false,
+        confirmText: '鎴戠煡閬撲簡'
       })
       return
     }
@@ -284,15 +295,17 @@
         // 淇濆瓨鍒版湰鍦板瓨鍌�
         wx.setStorageSync('userInfo', app.globalData.userInfo)
 
-        wx.showToast({
+        // 鏄剧ず淇濆瓨鎴愬姛鎻愮ず锛屽苟鎻愰啋闇�瑕侀噸鏂扮櫥褰�
+        wx.showModal({
           title: '淇濆瓨鎴愬姛',
-          icon: 'success'
+          content: '鐢ㄦ埛淇℃伅宸蹭繚瀛樻垚鍔熴�備负浜嗚幏鍙栨渶鏂扮殑鍏宠仈淇℃伅锛堝鍙傝禌鑰呫�佽瘎濮斻�佸憳宸ヨ韩浠斤級锛岀郴缁熷皢閲嶆柊鐧诲綍銆�',
+          showCancel: false,
+          confirmText: '纭畾',
+          success: () => {
+            // 寮哄埗閲嶆柊鐧诲綍浠ヨ幏鍙栨渶鏂扮殑鐢ㄦ埛鍏宠仈淇℃伅
+            this.forceRelogin()
+          }
         })
-
-        // 寤惰繜杩斿洖涓婁竴椤�
-        setTimeout(() => {
-          wx.navigateBack()
-        }, 1500)
       } else {
         throw new Error('淇濆瓨澶辫触')
       }
@@ -385,5 +398,56 @@
   // 鑾峰彇鏂囦欢鎵╁睍鍚�
   getFileExtension(fileName) {
     return fileName.split('.').pop().toLowerCase()
+  },
+
+  // 寮哄埗閲嶆柊鐧诲綍
+  forceRelogin() {
+    wx.showLoading({
+      title: '閲嶆柊鐧诲綍涓�...'
+    })
+
+    try {
+      // 娓呴櫎鏈湴瀛樺偍鐨勭櫥褰曚俊鎭�
+      wx.removeStorageSync('token')
+      wx.removeStorageSync('userInfo')
+      wx.removeStorageSync('sessionKey')
+      
+      // 娓呴櫎鍏ㄥ眬鏁版嵁
+      app.globalData.token = null
+      app.globalData.userInfo = null
+      app.globalData.sessionKey = null
+      
+      console.log('宸叉竻闄ゆ湰鍦扮櫥褰曚俊鎭紝鍑嗗閲嶆柊鐧诲綍')
+      
+      // 閲嶆柊璋冪敤鐧诲綍娴佺▼
+      app.login()
+      
+      // 寤惰繜涓�娈垫椂闂村悗闅愯棌loading骞惰繑鍥炰笂涓�椤�
+      setTimeout(() => {
+        wx.hideLoading()
+        wx.showToast({
+          title: '閲嶆柊鐧诲綍瀹屾垚',
+          icon: 'success'
+        })
+        
+        // 杩斿洖涓婁竴椤�
+        setTimeout(() => {
+          wx.navigateBack()
+        }, 1000)
+      }, 2000)
+      
+    } catch (error) {
+      console.error('寮哄埗閲嶆柊鐧诲綍澶辫触:', error)
+      wx.hideLoading()
+      wx.showToast({
+        title: '閲嶆柊鐧诲綍澶辫触',
+        icon: 'none'
+      })
+      
+      // 鍗充娇澶辫触涔熻繑鍥炰笂涓�椤�
+      setTimeout(() => {
+        wx.navigateBack()
+      }, 1500)
+    }
   }
 })
\ No newline at end of file
diff --git a/wx/pages/project/detail.js b/wx/pages/project/detail.js
index 5e56ab7..9e95a97 100644
--- a/wx/pages/project/detail.js
+++ b/wx/pages/project/detail.js
@@ -43,8 +43,6 @@
         if (projectDetail.submissionFiles) {
           projectDetail.submissionFiles.forEach(file => {
             file.fileSizeText = this.formatFileSize(file.fileSize)
-            // 瀛楁宸茬粡鏄纭殑鍚嶇О锛屾棤闇�鏄犲皠
-            // fullUrl, fullThumbUrl, fileSize, fileExt 閮芥槸姝g‘鐨勫瓧娈靛悕
           })
         }
 
@@ -87,62 +85,42 @@
       query GetProjectDetail($id: ID!) {
         activityPlayerDetail(id: $id) {
           id
-          playerInfo {
-            id
-            name
-            phone
-            gender
-            birthday
-            education
-            introduction
-            description
-            avatarUrl
-            avatar {
-              id
-              fullUrl
-              fullThumbUrl
-              name
-              fileSize
-              fileExt
-            }
-            userInfo {
-              userId
-              name
-              phone
-              avatarUrl
-            }
-          }
-          regionInfo {
-            id
-            name
-            fullPath
-          }
-          activityName
+          activityId
+          playerId
+          playerName
+          playerGender
+          playerPhone
+          playerEducation
+          playerBirthDate
+          playerIdCard
+          playerAddress
           projectName
-          description
-          feedback
-          state
-          stageId
-          submissionFiles {
+          projectDescription
+          projectCategory
+          projectTags
+          projectFiles {
             id
-            fullUrl
-            fullThumbUrl
-            name
+            fileName
+            fileUrl
             fileSize
-            fileExt
-            mediaType
+            fileType
+            uploadTime
           }
-          ratingForm {
-            schemeId
-            schemeName
-            items {
-              id
-              name
-              maxScore
-              orderNo
-            }
-            totalMaxScore
+          submitTime
+          reviewTime
+          reviewerId
+          reviewerName
+          score
+          rating {
+            id
+            judgeId
+            judgeName
+            score
+            feedback
+            ratingTime
           }
+          state
+          feedback
         }
       }
     `
@@ -157,14 +135,22 @@
 
   // 鑾峰彇璇勫垎缁熻
   async getRatingStatsFromAPI(projectId) {
-    // 鏆傛椂杩斿洖绌虹殑璇勫垎鏁版嵁锛岄伩鍏岹raphQL鏌ヨ閿欒
-    // TODO: 闇�瑕佸悗绔彁渚涘悎閫傜殑璇勫垎缁熻鏌ヨ鎺ュ彛
-    try {
-      return {
-        averageScore: null,
-        ratingCount: 0,
-        judgeRatings: []
+    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
     }
diff --git a/wx/pages/registration/registration.js b/wx/pages/registration/registration.js
index 05a6728..3fd91ec 100644
--- a/wx/pages/registration/registration.js
+++ b/wx/pages/registration/registration.js
@@ -1094,8 +1094,8 @@
       errors.name = '璇疯緭鍏ュ鍚�';
     }
 
-    if (!formData.phone.trim()) {
-      errors.phone = '璇疯緭鍏ユ墜鏈哄彿';
+    if (!formData.phone || !formData.phone.trim()) {
+      errors.phone = '璇峰厛鎺堟潈鑾峰彇鎵嬫満鍙�';
     } else if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
       errors.phone = '璇疯緭鍏ユ纭殑鎵嬫満鍙�';
     }
@@ -1200,6 +1200,17 @@
       })
       return
     }
+
+    // 棰濆妫�鏌ワ細纭繚蹇呴』鎺堟潈鐢佃瘽鍙风爜
+    if (!this.data.formData.phone || !this.data.formData.phone.trim()) {
+      wx.showModal({
+        title: '闇�瑕佹巿鏉冩墜鏈哄彿',
+        content: '鏍规嵁骞冲彴瑙勫畾锛屽繀椤绘巿鏉冩墜鏈哄彿鐮佹墠鑳芥姤鍚嶅弬璧涖�傝鍏堣幏鍙栨墜鏈哄彿鐮佹巿鏉冦��',
+        showCancel: false,
+        confirmText: '鎴戠煡閬撲簡'
+      })
+      return
+    }
     
     this.setData({ isSubmitting: true })
     
diff --git a/wx/pages/review/index.js b/wx/pages/review/index.js
index b469a0b..cc40350 100644
--- a/wx/pages/review/index.js
+++ b/wx/pages/review/index.js
@@ -2,14 +2,6 @@
 const app = getApp()
 const { graphqlRequest, formatDate } = require('../../lib/utils')
 
-const GET_RATING_STATS_QUERY = `
-  query GetRatingStats($activityPlayerId: ID!) {
-    judgeRatingsForPlayer(activityPlayerId: $activityPlayerId) {
-      hasRated
-    }
-  }
-`
-
 Page({
   data: {
     loading: false,
@@ -63,7 +55,7 @@
 
   // 鍒囨崲閫夐」鍗�
   switchTab(e) {
-    const index = parseInt(e.currentTarget.dataset.index) || 0
+    const index = parseInt(e.currentTarget.dataset.index) // 灏嗗瓧绗︿覆杞崲涓烘暟瀛�
     if (index === this.data.currentTab) return
     
     this.setData({
@@ -118,29 +110,10 @@
       ])
     } catch (error) {
       console.error('鍔犺浇鏁版嵁澶辫触:', error)
-      
-      // 妫�鏌ユ槸鍚︽槸璁よ瘉鐩稿叧閿欒
-      if (error.message && (
-        error.message.includes('娌℃湁鏉冮檺璁块棶') ||
-        error.message.includes('璇峰厛鐧诲綍') ||
-        error.message.includes('閲嶆柊鐧诲綍')
-      )) {
-        wx.showToast({
-          title: '姝e湪閲嶆柊鐧诲綍...',
-          icon: 'loading',
-          duration: 2000
-        })
-        
-        // 绛夊緟涓�娈垫椂闂村悗閲嶈瘯
-        setTimeout(() => {
-          this.loadData()
-        }, 2000)
-      } else {
-        wx.showToast({
-          title: '鍔犺浇澶辫触锛岃閲嶈瘯',
-          icon: 'none'
-        })
-      }
+      wx.showToast({
+        title: '鍔犺浇澶辫触',
+        icon: 'none'
+      })
     } finally {
       this.setData({ loading: false })
       wx.stopPullDownRefresh()
@@ -174,7 +147,7 @@
     }
 
     // 鏍规嵁褰撳墠閫夐」鍗℃瀯寤轰笉鍚岀殑鏌ヨ
-    switch (currentTab) {
+    switch (parseInt(currentTab)) { // 纭繚currentTab鏄暟瀛�
       case 0: // 鎴戞湭璇勫
         query = `
           query GetUnReviewedProjects($page: Int!, $pageSize: Int!, $searchKeyword: String) {
@@ -234,13 +207,39 @@
           }
         `
         break
+      default:
+        console.error('鏃犳晥鐨勯�夐」鍗$储寮�:', currentTab)
+        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
+    }
+
+    // 妫�鏌uery鏄惁涓虹┖
+    if (!query || query.trim() === '') {
+      console.error('GraphQL鏌ヨ涓虹┖锛屾棤娉曟墽琛岃姹�')
+      return
     }
 
     const result = await graphqlRequest(query, variables)
     
     if (result) {
-      const dataKey = currentTab === 0 ? 'unReviewedProjects' : 
-                     currentTab === 1 ? 'reviewedProjects' : 
+      const dataKey = parseInt(currentTab) === 0 ? 'unReviewedProjects' : 
+                     parseInt(currentTab) === 1 ? 'reviewedProjects' : 
                      'studentUnReviewedProjects'
       
       const data = result[dataKey]
@@ -250,13 +249,13 @@
         const projects = data.items.map(item => ({
           ...item,
           submitTime: item.submitTime ? formatDate(item.submitTime) : '',
-          reviewTime: item.reviewTime ? formatDate(item.reviewTime) : ''
+          reviewTime: item.reviewTime ? formatDate(item.reviewTime) : '',
+          statusText: this.getStatusText(item.status),
+          statusType: this.getStatusType(item.status)
         }))
 
-        const projectsWithRatingCount = await this.enrichProjectsWithRatingCounts(projects)
-
         this.setData({
-          projectList: isLoadMore ? [...this.data.projectList, ...projectsWithRatingCount] : projectsWithRatingCount,
+          projectList: isLoadMore ? [...this.data.projectList, ...projects] : projects,
           hasMore: data.hasMore || false,
           currentPage: variables.page
         })
@@ -301,39 +300,26 @@
     }
   },
 
-  async enrichProjectsWithRatingCounts(projects) {
-    if (!Array.isArray(projects) || projects.length === 0) {
-      return projects || []
+  // 鑾峰彇鐘舵�佹枃鏈�
+  getStatusText(status) {
+    const statusMap = {
+      'SUBMITTED': '宸叉彁浜�',
+      'UNDER_REVIEW': '璇勫涓�',
+      'REVIEWED': '宸茶瘎瀹�',
+      'REJECTED': '宸叉嫆缁�'
     }
-
-    try {
-      const counts = await Promise.all(projects.map(project => this.getProjectRatingCount(project.id)))
-      return projects.map((project, index) => ({
-        ...project,
-        ratingCount: counts[index]
-      }))
-    } catch (error) {
-      console.error('鎵归噺鑾峰彇璇勫娆℃暟澶辫触:', error)
-      return projects.map(project => ({
-        ...project,
-        ratingCount: typeof project.ratingCount === 'number' ? project.ratingCount : 0
-      }))
-    }
+    return statusMap[status] || status
   },
 
-  async getProjectRatingCount(activityPlayerId) {
-    if (!activityPlayerId) {
-      return 0
+  // 鑾峰彇鐘舵�佺被鍨�
+  getStatusType(status) {
+    const typeMap = {
+      'SUBMITTED': 'info',
+      'UNDER_REVIEW': 'warning',
+      'REVIEWED': 'success',
+      'REJECTED': 'danger'
     }
-
-    try {
-      const result = await graphqlRequest(GET_RATING_STATS_QUERY, { activityPlayerId })
-      const ratings = result?.judgeRatingsForPlayer || []
-      return ratings.filter(item => item?.hasRated).length
-    } catch (error) {
-      console.error(`鑾峰彇椤圭洰 ${activityPlayerId} 鐨勮瘎瀹℃鏁板け璐�:`, error)
-      return 0
-    }
+    return typeMap[status] || 'info'
   },
 
   // 鑾峰彇绌虹姸鎬佹枃鏈�

--
Gitblit v1.8.0