From c4a9cad1c50e89365e2a58b50e259af642ed3b8c Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期二, 07 十月 2025 16:12:20 +0800
Subject: [PATCH] feat(review): 调整评审详情展示顺序与样式,描述支持多行,项目信息列宽40/60 fix(auth): 登录页与首页循环跳转保护;api.ts 在登录页不再重定向;401分支在登录页不跳转 fix(router): /login 放行策略优化,避免死循环;评审列表跳转到 /project-review/:id/detail fix(frontend): 补齐 utils/appConfig.ts,避免启动白屏 fix(review): 详情页提交评分缺少stageId时回退使用项目详情的stageId feat(backend): ActivityPlayerDetailResponse.playerInfo 补充 avatarUrl/avatar,服务组装时填充用户头像 chore(dev): 启动脚本注入本地JWT密钥,重启前后端

---
 web/src/config/api.ts |   90 +++++++++++++++++++++++++++++++++++----------
 1 files changed, 70 insertions(+), 20 deletions(-)

diff --git a/web/src/config/api.ts b/web/src/config/api.ts
index 485abb2..ecbdb5e 100644
--- a/web/src/config/api.ts
+++ b/web/src/config/api.ts
@@ -17,36 +17,86 @@
 
 // GraphQL璇锋眰宸ュ叿鍑芥暟
 export const graphqlRequest = async (query: string, variables: any = {}) => {
-  // 鑾峰彇JWT token
-  const { getToken } = await import('@/utils/auth');
+  // 鑾峰彇JWT token涓庡伐鍏�
+  const { getToken, isTokenExpired, clearAuth } = await import('@/utils/auth');
   const token = getToken();
+
+  // 鑻oken杩囨湡锛岀洿鎺ユ竻鐞嗗苟璺崇櫥褰�
+  if (!token || isTokenExpired(token)) {
+    clearAuth();
+    // 閬垮厤鍦ㄧ櫥褰曢〉閲嶅璺宠浆閫犳垚鐧藉睆/寰幆
+    const atLogin = typeof window !== 'undefined' && window.location && window.location.hash?.startsWith('#/login');
+    if (!atLogin) {
+      window.location.href = '/#/login';
+    }
+    throw new Error('Token expired or missing')
+  }
+
+  // 鏋勫缓璇锋眰澶�
   const headers: Record<string, string> = {
     'Content-Type': 'application/json',
   };
+
   if (token) {
     headers['Authorization'] = `Bearer ${token}`;
   }
 
-  const response = await fetch(API_CONFIG.GRAPHQL_ENDPOINT, {
-    method: 'POST',
-    headers: headers,
-    body: JSON.stringify({
-      query,
-      variables,
-    }),
-  })
+  // 鏋勫缓璇锋眰浣�
+  const requestBody = JSON.stringify({
+    query,
+    variables,
+  });
 
-  if (!response.ok) {
-    throw new Error(`HTTP error! status: ${response.status}`)
+  try {
+    // 鍙戦�佽姹�
+    const response = await fetch(API_CONFIG.GRAPHQL_ENDPOINT, {
+      method: 'POST',
+      headers,
+      body: requestBody,
+    });
+
+    if (!response.ok) {
+      // 澶勭悊401鏈巿鏉�
+      if (response.status === 401) {
+        const { clearAuth } = await import('@/utils/auth');
+        clearAuth();
+        const atLogin = typeof window !== 'undefined' && window.location && window.location.hash?.startsWith('#/login');
+        if (!atLogin) {
+          window.location.href = '/#/login';
+        }
+      }
+      throw new Error(`HTTP error! status: ${response.status}`);
+    }
+
+    const result = await response.json();
+
+    if (result.errors) {
+      const msg = JSON.stringify(result.errors) || ''
+      // 璇嗗埆璁よ瘉绫婚敊璇叧閿瓧
+      const isAuthError =
+        msg.includes('Unauthorized') ||
+        msg.includes('璁よ瘉') ||
+        msg.includes('unauthorized') ||
+        msg.includes('invalid token') ||
+        msg.includes('expired')
+
+      if (isAuthError) {
+        const { clearAuth } = await import('@/utils/auth');
+        clearAuth();
+        const atLogin = typeof window !== 'undefined' && window.location && window.location.hash?.startsWith('#/login');
+        if (!atLogin) {
+          window.location.href = '/#/login';
+        }
+      }
+      throw new Error(`GraphQL errors: ${msg}`);
+    }
+
+    return result;
+  } catch (error) {
+    console.error('=== GraphQL璇锋眰澶辫触 ===');
+    console.error('閿欒璇︽儏:', error);
+    throw error;
   }
-
-  const result = await response.json()
-  
-  if (result.errors) {
-    throw new Error(result.errors[0].message)
-  }
-
-  return result.data
 }
 
 // 閫氱敤API璇锋眰宸ュ叿鍑芥暟

--
Gitblit v1.8.0