Skip to content

Commit be633bf

Browse files
committed
feat: 完善翻转二叉树算法可视化网站
- 修改标题为力扣风格,添加跳转链接 - 右上角添加GitHub徽标 - 添加Java代码展示,支持语法高亮和debug效果 - 添加键盘快捷键支持(←上一步、→下一步、空格播放/暂停) - 添加可拖拽进度条 - 添加用户数据输入功能,支持预设样例和随机生成 - 添加微信交流群悬浮球 - 修复ESLint配置
1 parent 12574dd commit be633bf

17 files changed

+1505
-347
lines changed

eslint.config.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,41 @@ import js from '@eslint/js'
22
import globals from 'globals'
33
import reactHooks from 'eslint-plugin-react-hooks'
44
import reactRefresh from 'eslint-plugin-react-refresh'
5-
import tseslint from 'typescript-eslint'
5+
import tsParser from '@typescript-eslint/parser'
6+
import tsPlugin from '@typescript-eslint/eslint-plugin'
67

7-
export default tseslint.config(
8+
export default [
89
{ ignores: ['dist'] },
910
{
10-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
1111
files: ['**/*.{ts,tsx}'],
1212
languageOptions: {
1313
ecmaVersion: 2020,
1414
globals: globals.browser,
15+
parser: tsParser,
16+
parserOptions: {
17+
ecmaFeatures: {
18+
jsx: true
19+
}
20+
}
1521
},
1622
plugins: {
1723
'react-hooks': reactHooks,
1824
'react-refresh': reactRefresh,
25+
'@typescript-eslint': tsPlugin,
1926
},
2027
rules: {
28+
...js.configs.recommended.rules,
29+
...tsPlugin.configs.recommended.rules,
2130
...reactHooks.configs.recommended.rules,
2231
'react-refresh/only-export-components': [
2332
'warn',
2433
{ allowConstantExport: true },
2534
],
35+
'no-unused-vars': 'off',
36+
'@typescript-eslint/no-unused-vars': 'warn',
37+
'@typescript-eslint/no-non-null-assertion': 'off',
38+
'@typescript-eslint/no-inferrable-types': 'off',
39+
'no-useless-catch': 'off',
2640
},
2741
},
28-
)
42+
]

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<!doctype html>
2-
<html lang="en">
2+
<html lang="zh-CN">
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
7+
<title>226. 翻转二叉树 - LeetCode 算法可视化</title>
88
</head>
99
<body>
1010
<div id="root"></div>

public/wechat-group.png

328 KB
Loading

src/App.css

Lines changed: 58 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,90 @@
1-
#root {
2-
max-width: 1280px;
3-
margin: 0 auto;
4-
padding: 2rem;
5-
text-align: center;
6-
}
7-
8-
.logo {
9-
height: 6em;
10-
padding: 1.5em;
11-
will-change: filter;
12-
transition: filter 300ms;
13-
}
14-
.logo:hover {
15-
filter: drop-shadow(0 0 2em #646cffaa);
16-
}
17-
.logo.react:hover {
18-
filter: drop-shadow(0 0 2em #61dafbaa);
1+
* {
2+
box-sizing: border-box;
193
}
204

21-
@keyframes logo-spin {
22-
from {
23-
transform: rotate(0deg);
24-
}
25-
to {
26-
transform: rotate(360deg);
27-
}
28-
}
29-
30-
@media (prefers-reduced-motion: no-preference) {
31-
a:nth-of-type(2) .logo {
32-
animation: logo-spin infinite 20s linear;
33-
}
34-
}
35-
36-
.card {
37-
padding: 2em;
38-
}
39-
40-
.read-the-docs {
41-
color: #888;
5+
html, body, #root {
6+
margin: 0;
7+
padding: 0;
8+
height: 100vh;
9+
overflow: hidden;
4210
}
4311

4412
.app-container {
4513
display: flex;
4614
flex-direction: column;
47-
min-height: 100vh;
48-
font-family: 'Arial', sans-serif;
49-
background-color: #f9f9f9;
15+
height: 100vh;
16+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
17+
background-color: #f5f5f5;
18+
overflow: hidden;
5019
}
5120

5221
.app-header {
53-
background-color: #4682b4;
22+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
5423
color: white;
55-
padding: 20px;
56-
text-align: center;
57-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
24+
padding: 12px 20px;
25+
flex-shrink: 0;
26+
}
27+
28+
.header-content {
29+
display: flex;
30+
justify-content: space-between;
31+
align-items: center;
32+
max-width: 1400px;
33+
margin: 0 auto;
5834
}
5935

6036
.app-header h1 {
6137
margin: 0;
62-
font-size: 28px;
63-
font-weight: bold;
38+
font-size: 22px;
39+
font-weight: 600;
40+
}
41+
42+
.title-link {
43+
color: #ffa116;
44+
text-decoration: none;
45+
transition: color 0.2s;
46+
}
47+
48+
.title-link:hover {
49+
color: #ffb84d;
50+
text-decoration: underline;
6451
}
6552

66-
.app-header p {
67-
margin: 10px 0 0;
68-
font-size: 16px;
53+
.github-link {
54+
color: white;
6955
opacity: 0.9;
56+
transition: opacity 0.2s, transform 0.2s;
57+
display: flex;
58+
align-items: center;
59+
}
60+
61+
.github-link:hover {
62+
opacity: 1;
63+
transform: scale(1.1);
7064
}
7165

7266
.app-content {
7367
flex: 1;
74-
padding: 20px;
7568
display: flex;
7669
flex-direction: column;
77-
align-items: center;
78-
max-width: 1200px;
70+
padding: 12px 20px;
71+
max-width: 1400px;
7972
margin: 0 auto;
8073
width: 100%;
74+
overflow: hidden;
75+
min-height: 0;
8176
}
8277

83-
.app-footer {
84-
background-color: #f0f0f0;
85-
color: #555;
86-
padding: 15px;
87-
text-align: center;
88-
font-size: 14px;
89-
border-top: 1px solid #ddd;
90-
}
91-
92-
.app-footer a {
93-
color: #4682b4;
94-
text-decoration: none;
95-
}
96-
97-
.app-footer a:hover {
98-
text-decoration: underline;
78+
.visualization-area {
79+
flex: 1;
80+
display: flex;
81+
gap: 16px;
82+
min-height: 0;
83+
overflow: hidden;
9984
}
10085

101-
@media (max-width: 768px) {
102-
.app-header h1 {
103-
font-size: 24px;
104-
}
105-
106-
.app-header p {
107-
font-size: 14px;
108-
}
109-
110-
.app-content {
111-
padding: 10px;
86+
@media (max-width: 1024px) {
87+
.visualization-area {
88+
flex-direction: column;
11289
}
11390
}

src/App.tsx

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useCallback } from 'react';
22
import './App.css';
33
import { TreeNode } from './models/TreeNode';
44
import { InversionAlgorithm, Step, AlgorithmType } from './algorithms/TreeInversion';
55
import { useAnimationController } from './utils/AnimationController';
66
import TreeCanvas from './components/TreeCanvas';
77
import ControlPanel from './components/ControlPanel';
8+
import CodeViewer from './components/CodeViewer';
9+
import DataInput from './components/DataInput';
10+
import WeChatFloat from './components/WeChatFloat';
811

912
function App() {
1013
const [algorithmType, setAlgorithmType] = useState<AlgorithmType>('recursive');
@@ -27,7 +30,8 @@ function App() {
2730
pause,
2831
stepForward,
2932
stepBackward,
30-
reset
33+
reset,
34+
goToStep
3135
} = useAnimationController({ steps });
3236

3337
// 处理算法类型变化
@@ -37,18 +41,98 @@ function App() {
3741
};
3842

3943
// 重置树到初始状态
40-
const resetTree = () => {
44+
const resetTree = useCallback(() => {
4145
setTree(TreeNode.createExampleTree());
42-
};
46+
}, []);
47+
48+
// 处理用户输入数据
49+
const handleDataChange = useCallback((newTree: TreeNode) => {
50+
reset();
51+
setTree(newTree);
52+
}, [reset]);
53+
54+
// 键盘快捷键处理
55+
useEffect(() => {
56+
const handleKeyDown = (e: KeyboardEvent) => {
57+
// 如果焦点在输入框中,不处理快捷键
58+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
59+
return;
60+
}
61+
62+
switch (e.key) {
63+
case 'ArrowLeft':
64+
e.preventDefault();
65+
if (!playing && currentStepIndex > 0) {
66+
stepBackward();
67+
}
68+
break;
69+
case 'ArrowRight':
70+
e.preventDefault();
71+
if (!playing && currentStepIndex < steps.length - 1) {
72+
stepForward();
73+
}
74+
break;
75+
case ' ':
76+
e.preventDefault();
77+
if (playing) {
78+
pause();
79+
} else {
80+
play();
81+
}
82+
break;
83+
}
84+
};
85+
86+
window.addEventListener('keydown', handleKeyDown);
87+
return () => window.removeEventListener('keydown', handleKeyDown);
88+
}, [playing, currentStepIndex, steps.length, stepForward, stepBackward, play, pause]);
4389

4490
return (
4591
<div className="app-container">
4692
<header className="app-header">
47-
<h1>LeetCode 226: 翻转二叉树动画演示</h1>
48-
<p>可视化展示不同解法的执行过程</p>
93+
<div className="header-content">
94+
<h1>
95+
<a
96+
href="https://leetcode.cn/problems/invert-binary-tree/"
97+
target="_blank"
98+
rel="noopener noreferrer"
99+
className="title-link"
100+
>
101+
226. 翻转二叉树
102+
</a>
103+
</h1>
104+
<a
105+
href="https://github.com/JSREI/leetcode-226-invert-binary-tree"
106+
target="_blank"
107+
rel="noopener noreferrer"
108+
className="github-link"
109+
title="查看源码"
110+
>
111+
<svg height="28" viewBox="0 0 16 16" width="28" fill="currentColor">
112+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
113+
</svg>
114+
</a>
115+
</div>
49116
</header>
50117

118+
<DataInput onDataChange={handleDataChange} />
119+
51120
<main className="app-content">
121+
<div className="visualization-area">
122+
<TreeCanvas
123+
tree={tree}
124+
steps={steps}
125+
currentStepIndex={currentStepIndex}
126+
width={500}
127+
height={350}
128+
/>
129+
<CodeViewer
130+
algorithmType={algorithmType}
131+
currentStepIndex={currentStepIndex}
132+
steps={steps}
133+
/>
134+
</div>
135+
52136
<ControlPanel
53137
playing={playing}
54138
currentStep={currentStepIndex}
@@ -63,27 +147,12 @@ function App() {
63147
resetTree();
64148
}}
65149
onAlgorithmChange={handleAlgorithmChange}
150+
onProgressChange={goToStep}
66151
message={currentMessage}
67152
/>
68-
69-
<TreeCanvas
70-
tree={tree}
71-
steps={steps}
72-
currentStepIndex={currentStepIndex}
73-
width={800}
74-
height={500}
75-
/>
76153
</main>
77154

78-
<footer className="app-footer">
79-
<p>
80-
<a href="https://github.com/your-username/leetcode-226-invert-binary-tree" target="_blank" rel="noopener noreferrer">
81-
查看源码
82-
</a> | <a href="https://leetcode.com/problems/invert-binary-tree/" target="_blank" rel="noopener noreferrer">
83-
LeetCode 226 题目
84-
</a>
85-
</p>
86-
</footer>
155+
<WeChatFloat />
87156
</div>
88157
);
89158
}

0 commit comments

Comments
 (0)