Skip to content

Commit 6e2e3db

Browse files
committed
feat: 添加用户自定义数据输入功能
- 在标题下方添加数据输入组件 - 支持用户自定义输入L1和L2链表数据 - 内置4个预设数据样例(示例1-4) - 支持随机生成合法链表数据 - 输入数据合法性校验(0-9数字,长度限制) - 添加完整的单元测试
1 parent 7542a2b commit 6e2e3db

5 files changed

Lines changed: 397 additions & 1 deletion

File tree

src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Header } from './components/Header'
2+
import { DataInput } from './components/DataInput'
23
import { CodePanel } from './components/CodePanel'
34
import { VisualizationPanel } from './components/VisualizationPanel'
45
import { ControlPanel } from './components/ControlPanel'
@@ -14,12 +15,15 @@ function App() {
1415
currentStepData,
1516
isPlaying,
1617
totalSteps,
18+
l1Input,
19+
l2Input,
1720
goToPrevious,
1821
goToNext,
1922
togglePlayPause,
2023
pause,
2124
reset,
2225
goToStep,
26+
updateInputs,
2327
} = useAlgorithmState()
2428

2529
// 自动播放
@@ -51,6 +55,7 @@ function App() {
5155
return (
5256
<div className="app">
5357
<Header />
58+
<DataInput l1={l1Input} l2={l2Input} onDataChange={updateInputs} />
5459
<main className="main-content">
5560
<div className="left-panel">
5661
<CodePanel currentLine={codeLine} variables={variables} />

src/components/DataInput.css

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
.data-input {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 8px;
5+
padding: 8px 24px;
6+
background-color: #1a1a2e;
7+
border-bottom: 1px solid #2d2d44;
8+
}
9+
10+
.data-input-row {
11+
display: flex;
12+
align-items: center;
13+
gap: 12px;
14+
flex-wrap: wrap;
15+
}
16+
17+
.input-group {
18+
display: flex;
19+
align-items: center;
20+
gap: 6px;
21+
position: relative;
22+
}
23+
24+
.input-label {
25+
font-size: 13px;
26+
font-weight: 600;
27+
color: #9ca3af;
28+
min-width: 24px;
29+
}
30+
31+
.data-input-field {
32+
width: 120px;
33+
padding: 4px 8px;
34+
font-size: 13px;
35+
background-color: #2d2d44;
36+
border: 1px solid #3d3d5c;
37+
border-radius: 4px;
38+
color: #ffffff;
39+
outline: none;
40+
transition: border-color 0.2s ease;
41+
}
42+
43+
.data-input-field:focus {
44+
border-color: #ffa116;
45+
}
46+
47+
.data-input-field.error {
48+
border-color: #ef4444;
49+
}
50+
51+
.data-input-field::placeholder {
52+
color: #6b7280;
53+
}
54+
55+
.error-tip {
56+
position: absolute;
57+
top: 100%;
58+
left: 30px;
59+
font-size: 11px;
60+
color: #ef4444;
61+
white-space: nowrap;
62+
margin-top: 2px;
63+
}
64+
65+
.apply-btn {
66+
padding: 4px 12px;
67+
font-size: 12px;
68+
font-weight: 500;
69+
background-color: #ffa116;
70+
color: #1a1a2e;
71+
border: none;
72+
border-radius: 4px;
73+
cursor: pointer;
74+
transition: background-color 0.2s ease;
75+
}
76+
77+
.apply-btn:hover {
78+
background-color: #ffb84d;
79+
}
80+
81+
.random-btn {
82+
padding: 4px 12px;
83+
font-size: 12px;
84+
font-weight: 500;
85+
background-color: #3d3d5c;
86+
color: #ffffff;
87+
border: 1px solid #4d4d6c;
88+
border-radius: 4px;
89+
cursor: pointer;
90+
transition: all 0.2s ease;
91+
}
92+
93+
.random-btn:hover {
94+
background-color: #4d4d6c;
95+
border-color: #5d5d7c;
96+
}
97+
98+
.preset-row {
99+
display: flex;
100+
align-items: center;
101+
gap: 8px;
102+
flex-wrap: wrap;
103+
}
104+
105+
.preset-label {
106+
font-size: 12px;
107+
color: #6b7280;
108+
}
109+
110+
.preset-btn {
111+
padding: 2px 10px;
112+
font-size: 11px;
113+
background-color: transparent;
114+
color: #9ca3af;
115+
border: 1px solid #3d3d5c;
116+
border-radius: 12px;
117+
cursor: pointer;
118+
transition: all 0.2s ease;
119+
}
120+
121+
.preset-btn:hover {
122+
background-color: #2d2d44;
123+
color: #ffa116;
124+
border-color: #ffa116;
125+
}

src/components/DataInput.test.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, it, expect, vi } from 'vitest'
2+
import { render, screen, fireEvent } from '@testing-library/react'
3+
import { DataInput } from './DataInput'
4+
5+
describe('DataInput', () => {
6+
const defaultProps = {
7+
l1: [2, 4, 3],
8+
l2: [5, 6, 4],
9+
onDataChange: vi.fn(),
10+
}
11+
12+
it('应该渲染输入框和按钮', () => {
13+
render(<DataInput {...defaultProps} />)
14+
15+
expect(screen.getByTestId('l1-input')).toBeInTheDocument()
16+
expect(screen.getByTestId('l2-input')).toBeInTheDocument()
17+
expect(screen.getByTestId('apply-btn')).toBeInTheDocument()
18+
expect(screen.getByTestId('random-btn')).toBeInTheDocument()
19+
})
20+
21+
it('应该显示初始值', () => {
22+
render(<DataInput {...defaultProps} />)
23+
24+
expect(screen.getByTestId('l1-input')).toHaveValue('2, 4, 3')
25+
expect(screen.getByTestId('l2-input')).toHaveValue('5, 6, 4')
26+
})
27+
28+
it('应该渲染预设样例按钮', () => {
29+
render(<DataInput {...defaultProps} />)
30+
31+
expect(screen.getByTestId('preset-0')).toBeInTheDocument()
32+
expect(screen.getByTestId('preset-1')).toBeInTheDocument()
33+
expect(screen.getByTestId('preset-2')).toBeInTheDocument()
34+
expect(screen.getByTestId('preset-3')).toBeInTheDocument()
35+
})
36+
37+
it('点击预设样例应该更新输入并调用回调', () => {
38+
const onDataChange = vi.fn()
39+
render(<DataInput {...defaultProps} onDataChange={onDataChange} />)
40+
41+
fireEvent.click(screen.getByTestId('preset-1'))
42+
43+
expect(screen.getByTestId('l1-input')).toHaveValue('0')
44+
expect(screen.getByTestId('l2-input')).toHaveValue('0')
45+
expect(onDataChange).toHaveBeenCalledWith([0], [0])
46+
})
47+
48+
it('点击随机按钮应该生成新数据', () => {
49+
const onDataChange = vi.fn()
50+
render(<DataInput {...defaultProps} onDataChange={onDataChange} />)
51+
52+
fireEvent.click(screen.getByTestId('random-btn'))
53+
54+
expect(onDataChange).toHaveBeenCalled()
55+
})
56+
57+
it('输入无效数据应该显示错误', () => {
58+
render(<DataInput {...defaultProps} />)
59+
60+
const l1Input = screen.getByTestId('l1-input')
61+
fireEvent.change(l1Input, { target: { value: 'abc' } })
62+
fireEvent.click(screen.getByTestId('apply-btn'))
63+
64+
expect(screen.getByText(/"abc" /)).toBeInTheDocument()
65+
})
66+
67+
it('输入超出范围的数字应该显示错误', () => {
68+
render(<DataInput {...defaultProps} />)
69+
70+
const l1Input = screen.getByTestId('l1-input')
71+
fireEvent.change(l1Input, { target: { value: '10' } })
72+
fireEvent.click(screen.getByTestId('apply-btn'))
73+
74+
expect(screen.getByText(/0-9/)).toBeInTheDocument()
75+
})
76+
77+
it('输入有效数据应该调用回调', () => {
78+
const onDataChange = vi.fn()
79+
render(<DataInput {...defaultProps} onDataChange={onDataChange} />)
80+
81+
const l1Input = screen.getByTestId('l1-input')
82+
const l2Input = screen.getByTestId('l2-input')
83+
84+
fireEvent.change(l1Input, { target: { value: '1, 2, 3' } })
85+
fireEvent.change(l2Input, { target: { value: '4, 5, 6' } })
86+
fireEvent.click(screen.getByTestId('apply-btn'))
87+
88+
expect(onDataChange).toHaveBeenCalledWith([1, 2, 3], [4, 5, 6])
89+
})
90+
})

0 commit comments

Comments
 (0)