Skip to content

Commit 583f050

Browse files
author
awesomeYG
committed
优化错误处理和header点击逻辑
- 修复序列化错误问题,创建安全的错误处理工具 - 改进ServerErrorBoundary组件,避免序列化失败 - 优化header组件logo点击逻辑,当forum为空时跳转到登录页 - 统一API调用的错误处理,提供优雅降级 - 删除不再使用的RootRedirect组件
1 parent 91d26c7 commit 583f050

File tree

6 files changed

+174
-35
lines changed

6 files changed

+174
-35
lines changed

ui/front/src/app/layout.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AuthProvider, CommonProvider } from '@/components'
55
import { ForumProvider } from '@/contexts/ForumContext'
66
import { AuthConfigProvider } from '@/contexts/AuthConfigContext'
77
import ServerErrorBoundary from '@/components/ServerErrorBoundary'
8+
import { safeApiCall, safeLogError } from '@/lib/error-utils'
89
import theme from '@/theme'
910
import '@ctzhian/tiptap/dist/index.css'
1011
import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter'
@@ -45,7 +46,7 @@ export async function generateMetadata(): Promise<Metadata> {
4546
brandName = brand?.text || 'Koala QA'
4647
} catch (error) {
4748
// 构建时如果无法获取品牌信息,使用默认值
48-
console.warn('Failed to fetch brand info during build:', error)
49+
safeLogError('Failed to fetch brand info during build', error)
4950
}
5051

5152
return {
@@ -102,10 +103,8 @@ async function getUserData() {
102103
const userData = await getUser()
103104
return userData
104105
} catch (error) {
105-
// 静默失败,不影响页面渲染
106-
if (process.env.NODE_ENV === 'development') {
107-
console.error('Failed to fetch user data:', error)
108-
}
106+
// 使用安全的错误处理
107+
safeLogError('Failed to fetch user data', error)
109108
return null
110109
}
111110
}
@@ -116,10 +115,8 @@ async function getForumData() {
116115
const forumData = await getForum()
117116
return forumData || []
118117
} catch (error) {
119-
// 静默失败,不影响页面渲染
120-
if (process.env.NODE_ENV === 'development') {
121-
console.error('Failed to fetch forum data:', error)
122-
}
118+
// 使用安全的错误处理
119+
safeLogError('Failed to fetch forum data', error)
123120
return []
124121
}
125122
}
@@ -130,10 +127,8 @@ async function getAuthConfigData() {
130127
const authConfigData = await getUserLoginMethod()
131128
return authConfigData
132129
} catch (error) {
133-
// 静默失败,不影响页面渲染
134-
if (process.env.NODE_ENV === 'development') {
135-
console.error('Failed to fetch auth config data:', error)
136-
}
130+
// 使用安全的错误处理
131+
safeLogError('Failed to fetch auth config data', error)
137132
return null
138133
}
139134
}

ui/front/src/app/page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { getForum } from '@/api'
2+
import { safeApiCall } from '@/lib/error-utils'
23
import { redirect } from 'next/navigation'
34

45
// 根路径重定向到第一个板块 - 服务端重定向
56
export default async function RootPage() {
6-
const forums = await getForum()
7+
const forums = await safeApiCall(
8+
() => getForum(),
9+
[],
10+
'Failed to fetch forums for root page redirect'
11+
)
712

813
if (forums && forums.length > 0) {
914
redirect(`/forum/${forums[0].id}`)
1015
} else {
11-
redirect('/not-found')
16+
redirect('/login')
1217
}
1318
}

ui/front/src/components/RootRedirect.tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.

ui/front/src/components/ServerErrorBoundary.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,34 @@ export class ServerErrorBoundary extends React.Component<Props, State> {
2929
}
3030

3131
static getDerivedStateFromError(error: Error): State {
32+
// 创建一个可序列化的错误对象,避免序列化问题
33+
const serializableError = {
34+
message: error.message || 'An unknown error occurred',
35+
name: error.name || 'Error',
36+
stack: error.stack || undefined,
37+
} as Error;
38+
3239
return {
3340
hasError: true,
34-
error,
41+
error: serializableError,
3542
};
3643
}
3744

3845
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
39-
console.error('ServerErrorBoundary caught an error:', error, errorInfo);
46+
// 安全地记录错误信息,避免序列化问题
47+
const errorMessage = error?.message || 'Unknown error';
48+
const errorName = error?.name || 'Error';
49+
50+
console.error('ServerErrorBoundary caught an error:', {
51+
message: errorMessage,
52+
name: errorName,
53+
componentStack: errorInfo?.componentStack || 'No component stack available'
54+
});
4055

4156
// 服务端错误处理逻辑
4257
if (typeof window === 'undefined') {
43-
// 服务端错误处理
44-
console.error('Server-side error:', error.message);
58+
// 服务端错误处理 - 可以在这里添加错误上报逻辑
59+
console.error('Server-side error occurred:', errorMessage);
4560
}
4661
}
4762

ui/front/src/components/header/index.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ const Header = ({ brandConfig, initialForums = [] }: HeaderProps) => {
4545
}
4646
}, [])
4747

48+
// 统一的 logo 点击处理函数
49+
const handleLogoClick = () => {
50+
if (isAuthPage && initialForums.length > 0) {
51+
// 如果在登录/注册页面,跳转到首页
52+
router.push(`/forum/${initialForums[0].id}`)
53+
} else if (initialForums && initialForums.length > 0) {
54+
// 如果有论坛数据,跳转到当前论坛或第一个论坛
55+
if (forum_id) {
56+
router.push(`/forum/${forum_id}`)
57+
} else {
58+
router.push(`/forum/${initialForums[0].id}`)
59+
}
60+
} else {
61+
// 如果没有论坛数据,跳转到登录页面
62+
plainRouter.push('/login')
63+
}
64+
}
65+
4866
return (
4967
<AppBar
5068
position='fixed'
@@ -73,13 +91,7 @@ const Header = ({ brandConfig, initialForums = [] }: HeaderProps) => {
7391
alignItems='center'
7492
gap={1}
7593
sx={{ cursor: 'pointer' }}
76-
onClick={() => {
77-
if (isAuthPage) {
78-
plainRouter.push('/')
79-
} else {
80-
router.push(forum_id ? `/forum/${forum_id}` : '/')
81-
}
82-
}}
94+
onClick={handleLogoClick}
8395
>
8496
<Image
8597
src={brandConfig.logo}
@@ -112,16 +124,15 @@ const Header = ({ brandConfig, initialForums = [] }: HeaderProps) => {
112124
width={120}
113125
height={20}
114126
style={{ cursor: 'pointer' }}
115-
onClick={() => {
116-
if (isAuthPage) {
117-
plainRouter.push('/')
118-
} else {
119-
router.push(forum_id ? `/forum/${forum_id}` : '/')
120-
}
121-
}}
127+
onClick={handleLogoClick}
128+
/>
129+
)}
130+
{Boolean(initialForums?.length) && (
131+
<ForumSelector
132+
selectedForumId={forum_id ? parseInt(forum_id as string) : undefined}
133+
forums={initialForums}
122134
/>
123135
)}
124-
<ForumSelector selectedForumId={forum_id ? parseInt(forum_id as string) : undefined} forums={initialForums} />
125136
</Stack>
126137

127138
<Stack

ui/front/src/lib/error-utils.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* 错误处理工具函数
3+
* 用于安全地处理错误对象,避免序列化问题
4+
*/
5+
6+
/**
7+
* 创建一个可序列化的错误对象
8+
* 移除不可序列化的属性,如函数、循环引用等
9+
*/
10+
export function createSerializableError(error: any): Error {
11+
if (!error) {
12+
return new Error('Unknown error');
13+
}
14+
15+
// 如果是 Error 对象,提取基本信息
16+
if (error instanceof Error) {
17+
return {
18+
name: error.name || 'Error',
19+
message: error.message || 'An error occurred',
20+
stack: error.stack || undefined,
21+
} as Error;
22+
}
23+
24+
// 如果是普通对象,尝试提取错误信息
25+
if (typeof error === 'object') {
26+
const message = error.message || error.error || error.msg || 'An error occurred';
27+
return {
28+
name: error.name || 'Error',
29+
message: String(message),
30+
stack: error.stack || undefined,
31+
} as Error;
32+
}
33+
34+
// 如果是字符串或其他类型
35+
return {
36+
name: 'Error',
37+
message: String(error),
38+
stack: undefined,
39+
} as Error;
40+
}
41+
42+
/**
43+
* 安全地记录错误信息
44+
* 避免在服务端渲染时出现序列化问题
45+
*/
46+
export function safeLogError(context: string, error: any): void {
47+
if (process.env.NODE_ENV === 'development') {
48+
const serializableError = createSerializableError(error);
49+
console.error(`${context}:`, {
50+
message: serializableError.message,
51+
name: serializableError.name,
52+
stack: serializableError.stack,
53+
});
54+
}
55+
}
56+
57+
/**
58+
* 安全地获取错误消息
59+
* 确保返回的字符串是可序列化的
60+
*/
61+
export function getErrorMessage(error: any): string {
62+
if (!error) {
63+
return 'Unknown error';
64+
}
65+
66+
if (error instanceof Error) {
67+
return error.message || 'An error occurred';
68+
}
69+
70+
if (typeof error === 'object') {
71+
return error.message || error.error || error.msg || 'An error occurred';
72+
}
73+
74+
return String(error);
75+
}
76+
77+
/**
78+
* 包装 API 调用,提供安全的错误处理
79+
*/
80+
export async function safeApiCall<T>(
81+
apiCall: () => Promise<T>,
82+
fallback: T | null = null,
83+
context: string = 'API call'
84+
): Promise<T | null> {
85+
try {
86+
return await apiCall();
87+
} catch (error) {
88+
safeLogError(context, error);
89+
return fallback;
90+
}
91+
}
92+
93+
/**
94+
* 包装多个 API 调用,提供安全的错误处理
95+
*/
96+
export async function safeBatchApiCalls<T extends Record<string, () => Promise<any>>>(
97+
calls: T,
98+
context: string = 'Batch API calls'
99+
): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>> | null }> {
100+
const results = {} as { [K in keyof T]: Awaited<ReturnType<T[K]>> | null };
101+
102+
await Promise.all(
103+
Object.entries(calls).map(async ([key, apiCall]) => {
104+
try {
105+
results[key as keyof T] = await apiCall();
106+
} catch (error) {
107+
safeLogError(`${context} - ${key}`, error);
108+
results[key as keyof T] = null;
109+
}
110+
})
111+
);
112+
113+
return results;
114+
}

0 commit comments

Comments
 (0)