1
1
import { Select , Divider , Space , Dropdown , Button , Menu , Checkbox , Input } from 'antd' ;
2
2
import { SortAscendingOutlined , SortDescendingOutlined , TagOutlined , FilterOutlined , DownOutlined , SearchOutlined } from '@ant-design/icons' ;
3
3
import { useTranslation } from 'react-i18next' ;
4
- import { useState , ChangeEvent } from 'react' ;
4
+ import { useState , ChangeEvent , useRef , useEffect } from 'react' ;
5
5
import StarRating from '../StarRating' ;
6
6
import { useMediaQuery } from 'react-responsive' ;
7
7
@@ -90,10 +90,20 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
90
90
} ) => {
91
91
const { t } = useTranslation ( ) ;
92
92
const isMobile = useMediaQuery ( { maxWidth : 768 } ) ;
93
+ const containerRef = useRef < HTMLDivElement > ( null ) ;
93
94
94
95
// 在组件的开头部分添加状态
95
96
const [ tagSearchText , setTagSearchText ] = useState ( '' ) ;
96
97
98
+ // 确保containerRef挂载后才渲染Dropdown
99
+ const [ isContainerMounted , setIsContainerMounted ] = useState ( false ) ;
100
+
101
+ useEffect ( ( ) => {
102
+ if ( containerRef . current ) {
103
+ setIsContainerMounted ( true ) ;
104
+ }
105
+ } , [ ] ) ;
106
+
97
107
// 排序功能菜单
98
108
const sortMenu = (
99
109
< Menu
@@ -154,13 +164,14 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
154
164
maxHeight : isMobile ? '300px' : '400px' ,
155
165
overflowY : 'auto' ,
156
166
minWidth : isMobile ? '250px' : '300px' ,
157
- maxWidth : isMobile ? '80vw' : 'none ' ,
167
+ maxWidth : isMobile ? '80vw' : '400px ' ,
158
168
backgroundColor : '#fff' ,
159
169
boxShadow : '0 2px 8px rgba(0, 0, 0, 0.15)' ,
160
170
borderRadius : '4px' ,
161
171
border : '1px solid #f0f0f0' ,
162
- position : 'relative' ,
163
- zIndex : 1050
172
+ zIndex : 1050 ,
173
+ transform : 'translateZ(0)' , // 开启硬件加速
174
+ willChange : 'transform, opacity' // 提示浏览器优化渲染
164
175
} } >
165
176
{ /* 添加标签搜索框 */ }
166
177
< Input
@@ -171,6 +182,7 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
171
182
setTagSearchText ( e . target . value ) ;
172
183
} }
173
184
allowClear
185
+ autoFocus = { false } // 防止自动聚焦导致的布局计算问题
174
186
/>
175
187
176
188
{ /* 标签列表,使用Grid布局优化显示 */ }
@@ -179,9 +191,10 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
179
191
value = { selectedTags }
180
192
onChange = { tags => onTagsChange ( tags as string [ ] ) }
181
193
style = { {
182
- display : 'flex' ,
183
- flexWrap : 'wrap' ,
184
- width : '100%'
194
+ display : 'grid' ,
195
+ gridTemplateColumns : 'repeat(auto-fill, minmax(120px, 1fr))' ,
196
+ width : '100%' ,
197
+ gap : '6px'
185
198
} }
186
199
>
187
200
{ allTags
@@ -191,8 +204,8 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
191
204
key = { tag }
192
205
value = { tag }
193
206
style = { {
194
- marginRight : '12px' ,
195
- marginBottom : '6px' ,
207
+ marginRight : 0 ,
208
+ marginBottom : 0 ,
196
209
width : 'auto' ,
197
210
display : 'inline-flex' ,
198
211
alignItems : 'center' ,
@@ -209,90 +222,109 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
209
222
) ;
210
223
211
224
return (
212
- < Space
213
- split = { isMobile ? null : < Divider type = "vertical" /> }
214
- style = { { marginBottom : isMobile ? 12 : 20 } }
215
- direction = { isMobile ? "vertical" : "horizontal" }
216
- size = { isMobile ? 8 : "middle" }
217
- wrap = { ! isMobile }
218
- >
219
- { /* 标签过滤 */ }
220
- < Dropdown
221
- overlay = { tagMenu }
222
- trigger = { [ 'click' ] }
223
- placement = { isMobile ? "bottomCenter" : "bottomLeft" }
224
- overlayStyle = { {
225
- position : 'fixed' ,
226
- marginTop : '8px' ,
227
- zIndex : 1050
228
- } }
225
+ < div ref = { containerRef } className = "challenge-controls-container" style = { { position : 'relative' } } >
226
+ < Space
227
+ split = { isMobile ? null : < Divider type = "vertical" /> }
228
+ style = { { marginBottom : isMobile ? 12 : 20 } }
229
+ direction = { isMobile ? "vertical" : "horizontal" }
230
+ size = { isMobile ? 8 : "middle" }
231
+ wrap = { ! isMobile }
229
232
>
230
- < Button
231
- icon = { < TagOutlined /> }
232
- size = { isMobile ? "middle" : "default" }
233
- style = { { width : isMobile ? '100%' : 'auto' , justifyContent : 'space-between' , display : 'flex' , alignItems : 'center' } }
234
- >
235
- { t ( 'challenges.filters.tags' ) } { selectedTags . length > 0 && `(${ selectedTags . length } )` } < DownOutlined />
236
- </ Button >
237
- </ Dropdown >
238
-
239
- { /* 难度过滤 */ }
240
- < Dropdown
241
- overlay = { difficultyMenu }
242
- trigger = { [ 'click' ] }
243
- placement = { isMobile ? "bottomCenter" : "bottomLeft" }
244
- >
245
- < Button
246
- icon = { < FilterOutlined /> }
247
- size = { isMobile ? "middle" : "default" }
248
- style = { { width : isMobile ? '100%' : 'auto' , justifyContent : 'space-between' , display : 'flex' , alignItems : 'center' } }
249
- >
250
- { t ( 'challenges.filters.difficulty' ) } < DownOutlined />
251
- </ Button >
252
- </ Dropdown >
253
-
254
- { /* 平台过滤 */ }
255
- < Dropdown
256
- overlay = { platformMenu }
257
- trigger = { [ 'click' ] }
258
- placement = { isMobile ? "bottomCenter" : "bottomLeft" }
259
- >
260
- < Button
261
- icon = { < FilterOutlined /> }
262
- size = { isMobile ? "middle" : "default" }
263
- style = { { width : isMobile ? '100%' : 'auto' , justifyContent : 'space-between' , display : 'flex' , alignItems : 'center' } }
264
- >
265
- { t ( 'challenges.controls.platform' ) } < DownOutlined />
266
- </ Button >
267
- </ Dropdown >
268
-
269
- { /* 排序控制 - 移到最后 */ }
270
- < Space style = { { width : isMobile ? '100%' : 'auto' } } >
271
- < Dropdown
272
- overlay = { sortMenu }
273
- trigger = { [ 'click' ] }
274
- placement = { isMobile ? "bottomCenter" : "bottomLeft" }
275
- >
276
- < Button
277
- size = { isMobile ? "middle" : "default" }
278
- style = { {
279
- width : isMobile ? 'calc(100% - 32px)' : 'auto' ,
280
- justifyContent : 'space-between' ,
281
- display : 'flex' ,
282
- alignItems : 'center'
233
+ { /* 标签过滤 */ }
234
+ { isContainerMounted && (
235
+ < Dropdown
236
+ overlay = { tagMenu }
237
+ trigger = { [ 'click' ] }
238
+ placement = { isMobile ? "bottomCenter" : "bottomLeft" }
239
+ overlayStyle = { {
240
+ marginTop : '8px' ,
241
+ zIndex : 1050
283
242
} }
243
+ getPopupContainer = { ( ) => containerRef . current || document . body }
244
+ destroyPopupOnHide = { true }
245
+ mouseEnterDelay = { 0.1 }
246
+ mouseLeaveDelay = { 0.1 }
247
+ >
248
+ < Button
249
+ icon = { < TagOutlined /> }
250
+ size = { isMobile ? "middle" : "default" }
251
+ style = { { width : isMobile ? '100%' : 'auto' , justifyContent : 'space-between' , display : 'flex' , alignItems : 'center' } }
252
+ >
253
+ { t ( 'challenges.filters.tags' ) } { selectedTags . length > 0 && `(${ selectedTags . length } )` } < DownOutlined />
254
+ </ Button >
255
+ </ Dropdown >
256
+ ) }
257
+
258
+ { /* 难度过滤 */ }
259
+ { isContainerMounted && (
260
+ < Dropdown
261
+ overlay = { difficultyMenu }
262
+ trigger = { [ 'click' ] }
263
+ placement = { isMobile ? "bottomCenter" : "bottomLeft" }
264
+ getPopupContainer = { ( ) => containerRef . current || document . body }
265
+ destroyPopupOnHide = { true }
266
+ >
267
+ < Button
268
+ icon = { < FilterOutlined /> }
269
+ size = { isMobile ? "middle" : "default" }
270
+ style = { { width : isMobile ? '100%' : 'auto' , justifyContent : 'space-between' , display : 'flex' , alignItems : 'center' } }
271
+ >
272
+ { t ( 'challenges.filters.difficulty' ) } < DownOutlined />
273
+ </ Button >
274
+ </ Dropdown >
275
+ ) }
276
+
277
+ { /* 平台过滤 */ }
278
+ { isContainerMounted && (
279
+ < Dropdown
280
+ overlay = { platformMenu }
281
+ trigger = { [ 'click' ] }
282
+ placement = { isMobile ? "bottomCenter" : "bottomLeft" }
283
+ getPopupContainer = { ( ) => containerRef . current || document . body }
284
+ destroyPopupOnHide = { true }
284
285
>
285
- { isMobile ? t ( `challenges.sort.${ sortBy } ` ) : `${ t ( 'challenges.controls.sortBy' ) } : ${ t ( `challenges.sort.${ sortBy } ` ) } ` } < DownOutlined />
286
- </ Button >
287
- </ Dropdown >
288
- < Button
289
- icon = { sortOrder === 'asc' ? < SortAscendingOutlined /> : < SortDescendingOutlined /> }
290
- onClick = { onSortOrderChange }
291
- title = { sortOrder === 'asc' ? t ( 'challenges.controls.ascending' ) : t ( 'challenges.controls.descending' ) }
292
- size = { isMobile ? "middle" : "default" }
293
- />
286
+ < Button
287
+ icon = { < FilterOutlined /> }
288
+ size = { isMobile ? "middle" : "default" }
289
+ style = { { width : isMobile ? '100%' : 'auto' , justifyContent : 'space-between' , display : 'flex' , alignItems : 'center' } }
290
+ >
291
+ { t ( 'challenges.controls.platform' ) } < DownOutlined />
292
+ </ Button >
293
+ </ Dropdown >
294
+ ) }
295
+
296
+ { /* 排序控制 - 移到最后 */ }
297
+ < Space style = { { width : isMobile ? '100%' : 'auto' } } >
298
+ { isContainerMounted && (
299
+ < Dropdown
300
+ overlay = { sortMenu }
301
+ trigger = { [ 'click' ] }
302
+ placement = { isMobile ? "bottomCenter" : "bottomLeft" }
303
+ getPopupContainer = { ( ) => containerRef . current || document . body }
304
+ destroyPopupOnHide = { true }
305
+ >
306
+ < Button
307
+ size = { isMobile ? "middle" : "default" }
308
+ style = { {
309
+ width : isMobile ? 'calc(100% - 32px)' : 'auto' ,
310
+ justifyContent : 'space-between' ,
311
+ display : 'flex' ,
312
+ alignItems : 'center'
313
+ } }
314
+ >
315
+ { isMobile ? t ( `challenges.sort.${ sortBy } ` ) : `${ t ( 'challenges.controls.sortBy' ) } : ${ t ( `challenges.sort.${ sortBy } ` ) } ` } < DownOutlined />
316
+ </ Button >
317
+ </ Dropdown >
318
+ ) }
319
+ < Button
320
+ icon = { sortOrder === 'asc' ? < SortAscendingOutlined /> : < SortDescendingOutlined /> }
321
+ onClick = { onSortOrderChange }
322
+ title = { sortOrder === 'asc' ? t ( 'challenges.controls.ascending' ) : t ( 'challenges.controls.descending' ) }
323
+ size = { isMobile ? "middle" : "default" }
324
+ />
325
+ </ Space >
294
326
</ Space >
295
- </ Space >
327
+ </ div >
296
328
) ;
297
329
} ;
298
330
0 commit comments