11import { Select , Divider , Space , Dropdown , Button , Menu , Checkbox , Input } from 'antd' ;
22import { SortAscendingOutlined , SortDescendingOutlined , TagOutlined , FilterOutlined , DownOutlined , SearchOutlined } from '@ant-design/icons' ;
33import { useTranslation } from 'react-i18next' ;
4- import { useState , ChangeEvent } from 'react' ;
4+ import { useState , ChangeEvent , useRef , useEffect } from 'react' ;
55import StarRating from '../StarRating' ;
66import { useMediaQuery } from 'react-responsive' ;
77
@@ -90,10 +90,20 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
9090} ) => {
9191 const { t } = useTranslation ( ) ;
9292 const isMobile = useMediaQuery ( { maxWidth : 768 } ) ;
93+ const containerRef = useRef < HTMLDivElement > ( null ) ;
9394
9495 // 在组件的开头部分添加状态
9596 const [ tagSearchText , setTagSearchText ] = useState ( '' ) ;
9697
98+ // 确保containerRef挂载后才渲染Dropdown
99+ const [ isContainerMounted , setIsContainerMounted ] = useState ( false ) ;
100+
101+ useEffect ( ( ) => {
102+ if ( containerRef . current ) {
103+ setIsContainerMounted ( true ) ;
104+ }
105+ } , [ ] ) ;
106+
97107 // 排序功能菜单
98108 const sortMenu = (
99109 < Menu
@@ -154,13 +164,14 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
154164 maxHeight : isMobile ? '300px' : '400px' ,
155165 overflowY : 'auto' ,
156166 minWidth : isMobile ? '250px' : '300px' ,
157- maxWidth : isMobile ? '80vw' : 'none ' ,
167+ maxWidth : isMobile ? '80vw' : '400px ' ,
158168 backgroundColor : '#fff' ,
159169 boxShadow : '0 2px 8px rgba(0, 0, 0, 0.15)' ,
160170 borderRadius : '4px' ,
161171 border : '1px solid #f0f0f0' ,
162- position : 'relative' ,
163- zIndex : 1050
172+ zIndex : 1050 ,
173+ transform : 'translateZ(0)' , // 开启硬件加速
174+ willChange : 'transform, opacity' // 提示浏览器优化渲染
164175 } } >
165176 { /* 添加标签搜索框 */ }
166177 < Input
@@ -171,6 +182,7 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
171182 setTagSearchText ( e . target . value ) ;
172183 } }
173184 allowClear
185+ autoFocus = { false } // 防止自动聚焦导致的布局计算问题
174186 />
175187
176188 { /* 标签列表,使用Grid布局优化显示 */ }
@@ -179,9 +191,10 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
179191 value = { selectedTags }
180192 onChange = { tags => onTagsChange ( tags as string [ ] ) }
181193 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'
185198 } }
186199 >
187200 { allTags
@@ -191,8 +204,8 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
191204 key = { tag }
192205 value = { tag }
193206 style = { {
194- marginRight : '12px' ,
195- marginBottom : '6px' ,
207+ marginRight : 0 ,
208+ marginBottom : 0 ,
196209 width : 'auto' ,
197210 display : 'inline-flex' ,
198211 alignItems : 'center' ,
@@ -209,90 +222,109 @@ const ChallengeControls: React.FC<ChallengeControlsProps> = ({
209222 ) ;
210223
211224 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 }
229232 >
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
283242 } }
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 }
284285 >
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 >
294326 </ Space >
295- </ Space >
327+ </ div >
296328 ) ;
297329} ;
298330
0 commit comments