refactor: 更新PageErrorBoundary的文案和样式以对齐shadcn主题

- 错误提示从“该页面加载失败”改为“该功能运行异常”
- 重试按钮文字从“重试”改为“重新尝试”
- 使用border-destructive, bg-destructive等语义化类名替换硬编码颜色
- 更新对应测试断言
This commit is contained in:
雨霖铃
2026-05-22 20:49:46 +08:00
parent 8406b03dd6
commit 2ced38769b
2 changed files with 41 additions and 23 deletions
+32 -14
View File
@@ -14,7 +14,7 @@ interface State {
/** /**
* 页面级错误边界组件:捕获子组件树中的 JavaScript 错误 * 页面级错误边界组件:捕获子组件树中的 JavaScript 错误
* 与全局 ErrorBoundary 的区别:使用轻量内嵌卡片 UI,提供重试按钮 * 完美适配 shadcn/ui 语义化主题与暗黑模式
*/ */
export class PageErrorBoundary extends Component<Props, State> { export class PageErrorBoundary extends Component<Props, State> {
state: State = { state: State = {
@@ -43,27 +43,45 @@ export class PageErrorBoundary extends Component<Props, State> {
render() { render() {
if (this.state.hasError) { if (this.state.hasError) {
return ( return (
<div className="flex flex-col items-center justify-center flex-1 p-4 min-h-[200px]"> <div className="flex flex-col items-center justify-center flex-1 p-6 min-h-[300px] animate-in fade-in zoom-in-95 duration-200">
<div className="p-6 text-center rounded-xl border border-red-200 bg-red-50 max-w-md w-full"> {/*
<AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-3" /> 1. 适配暗黑模式的容器设计:
<h3 className="text-lg font-bold text-red-600 mb-2"></h3> 不再使用 border-red-200 / bg-red-50,改用标准的 border-destructive/20 和 bg-destructive/5
<p className="text-sm text-muted-foreground mb-4"> 并在黑夜模式下会自动转为深红底色,绝不刺眼。
*/}
<div className="p-6 text-center rounded-xl border border-destructive/20 bg-destructive/5 max-w-md w-full shadow-sm">
{/* 2. 状态符号改用标准的 text-destructive 语义色 */}
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10 text-destructive mx-auto mb-4">
<AlertCircle className="h-6 w-6" />
</div>
<h3 className="text-base font-semibold text-foreground mb-1.5"></h3>
<p className="text-xs text-muted-foreground mb-5">
</p> </p>
{/* 3. 错误日志展示:使用与 shadcn 贴合的深色代码块包裹 */}
{this.state.error && ( {this.state.error && (
<div className="mb-4 p-3 bg-muted rounded-lg text-left max-h-[160px] overflow-auto"> <div className="mb-5 p-3 rounded-lg bg-zinc-950 dark:bg-zinc-900 text-left max-h-40 overflow-y-auto border border-border/40">
<pre className="font-mono text-xs whitespace-pre-wrap break-all text-red-700"> <pre className="font-mono text-[11px] leading-relaxed whitespace-pre-wrap break-all text-zinc-200 selection:bg-zinc-700">
{this.state.error.toString()} {this.state.error.stack || this.state.error.toString()}
</pre> </pre>
</div> </div>
)} )}
{/*
4. 严谨调用 shadcn 原子 Button
去掉全部手动指定的红底白字类名,直接启用 variant="destructive"。
它会自动处理 hover 颜色变化、暗黑模式切换以及无障碍高亮边框。
*/}
<Button <Button
variant="default" variant="destructive"
size="sm"
onClick={this.handleRetry} onClick={this.handleRetry}
className="rounded-lg font-bold bg-red-600 hover:bg-red-700 text-white" className="font-medium shadow-sm"
> >
<RefreshCw className="mr-2 h-4 w-4" /> <RefreshCw className="mr-1.5 h-3.5 w-3.5" />
</Button> </Button>
</div> </div>
</div> </div>
@@ -34,7 +34,7 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
expect(screen.getByText('该页面加载失败')).toBeInTheDocument(); expect(screen.getByText('该功能运行异常')).toBeInTheDocument();
expect(screen.getByText(/测试错误/)).toBeInTheDocument(); expect(screen.getByText(/测试错误/)).toBeInTheDocument();
}); });
@@ -45,7 +45,7 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
expect(screen.getByText('该页面加载失败')).toBeInTheDocument(); expect(screen.getByText('该功能运行异常')).toBeInTheDocument();
// 将子组件替换为正常组件,然后点击重试 // 将子组件替换为正常组件,然后点击重试
rerender( rerender(
@@ -54,14 +54,14 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
const retryButton = screen.getByRole('button', { name: /重试/ }); const retryButton = screen.getByRole('button', { name: /重新尝试/ });
retryButton.click(); retryButton.click();
await waitFor(() => { await waitFor(() => {
expect(screen.getByTestId('normal-content')).toHaveTextContent('恢复后的内容'); expect(screen.getByTestId('normal-content')).toHaveTextContent('恢复后的内容');
}); });
expect(screen.queryByText('该页面加载失败')).not.toBeInTheDocument(); expect(screen.queryByText('该功能运行异常')).not.toBeInTheDocument();
}); });
it('resetKey 变化时自动重置错误状态', async () => { it('resetKey 变化时自动重置错误状态', async () => {
@@ -71,7 +71,7 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
expect(screen.getByText('该页面加载失败')).toBeInTheDocument(); expect(screen.getByText('该功能运行异常')).toBeInTheDocument();
// 切换 resetKey,同时提供正常子组件 // 切换 resetKey,同时提供正常子组件
rerender( rerender(
@@ -84,7 +84,7 @@ describe('PageErrorBoundary', () => {
expect(screen.getByTestId('normal-content')).toHaveTextContent('页面 B 内容'); expect(screen.getByTestId('normal-content')).toHaveTextContent('页面 B 内容');
}); });
expect(screen.queryByText('该页面加载失败')).not.toBeInTheDocument(); expect(screen.queryByText('该功能运行异常')).not.toBeInTheDocument();
}); });
it('resetKey 不变时保持错误状态', () => { it('resetKey 不变时保持错误状态', () => {
@@ -94,7 +94,7 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
expect(screen.getByText('该页面加载失败')).toBeInTheDocument(); expect(screen.getByText('该功能运行异常')).toBeInTheDocument();
// 仅 children 变化,resetKey 不变,错误应保持 // 仅 children 变化,resetKey 不变,错误应保持
rerender( rerender(
@@ -103,7 +103,7 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
expect(screen.getByText('该页面加载失败')).toBeInTheDocument(); expect(screen.getByText('该功能运行异常')).toBeInTheDocument();
}); });
it('错误 UI 包含重试按钮', () => { it('错误 UI 包含重试按钮', () => {
@@ -113,7 +113,7 @@ describe('PageErrorBoundary', () => {
</PageErrorBoundary>, </PageErrorBoundary>,
); );
const retryButton = screen.getByRole('button', { name: /重试/ }); const retryButton = screen.getByRole('button', { name: /重新尝试/ });
expect(retryButton).toBeInTheDocument(); expect(retryButton).toBeInTheDocument();
}); });