1- import { Dropdown , type MenuProps , Tabs , TabsProps } from 'antd' ;
1+ import { Dropdown , type MenuProps , Tabs , type TabsProps } from 'antd' ;
22import { useCallback , useMemo , useRef , useState } from 'react' ;
3+ import { DragDropContext , Draggable , Droppable , type OnDragEndResponder } from 'react-beautiful-dnd' ;
34import { useTranslation } from 'react-i18next' ;
45
56import IconifyIcon from '@/components/iconify-icon' ;
67
78import useKeepAlive , { type KeepAliveTab } from '@/hooks/web/useKeepAlive' ;
89
10+ import { useRouter } from '@/router/hooks' ;
11+ import { replaceDynamicParams } from '@/router/hooks/use-match-route-meta' ;
12+
13+ import useStyles from './style' ;
14+
915import { MultiTabOperation , ThemeLayout } from '#/enum' ;
1016
1117type Props = {
@@ -14,7 +20,10 @@ type Props = {
1420
1521export default function MultiTabs ( { offsetTop = true } : Props ) {
1622 const { t } = useTranslation ( ) ;
23+ const { push } = useRouter ( ) ;
24+ const { styles } = useStyles ( ) ;
1725 const tabContentRef = useRef ( null ) ;
26+ const scrollContainer = useRef < HTMLDivElement > ( null ) ;
1827 const [ openDropdownTabKey , setopenDropdownTabKey ] = useState ( '' ) ;
1928
2029 const [ hoveringTabKey , setHoveringTabKey ] = useState ( '' ) ;
@@ -160,6 +169,66 @@ export default function MultiTabs({ offsetTop = true }: Props) {
160169 ) ,
161170 } ) ) ;
162171 } , [ tabs , renderTabLabel ] ) ;
172+ /**
173+ * 拖拽结束事件
174+ */
175+ const onDragEnd : OnDragEndResponder = ( { destination, source } ) => {
176+ // 拖拽到非法非 droppable区域
177+ if ( ! destination ) {
178+ return ;
179+ }
180+ // 原地放下
181+ if ( destination . droppableId === source . droppableId && destination . index === source . index ) {
182+ return ;
183+ }
184+
185+ const newTabs = Array . from ( tabs ) ;
186+ const [ movedTab ] = newTabs . splice ( source . index , 1 ) ;
187+ newTabs . splice ( destination . index , 0 , movedTab ) ;
188+ setTabs ( newTabs ) ;
189+ } ;
190+ const handleTabClick = ( { key, params = { } } : KeepAliveTab ) => {
191+ const tabKey = replaceDynamicParams ( key , params ) ;
192+ push ( tabKey ) ;
193+ } ;
194+ const renderTabBar : TabsProps [ 'renderTabBar' ] = ( ) => {
195+ return (
196+ < div className = { styles . tab_bar } >
197+ < DragDropContext onDragEnd = { onDragEnd } >
198+ < Droppable droppableId = 'tabsDroppable' direction = 'horizontal' >
199+ { ( provided ) => (
200+ < div ref = { provided . innerRef } { ...provided . droppableProps } className = 'flex w-full' >
201+ < div ref = { scrollContainer } className = 'hide-scrollbar flex w-full px-2' >
202+ { tabs . map ( ( tab , index ) => (
203+ < div
204+ id = { `tab-${ index } ` }
205+ className = 'flex-shrink-0'
206+ key = { tab . key }
207+ onClick = { ( ) => handleTabClick ( tab ) }
208+ >
209+ < Draggable key = { tab . key } draggableId = { tab . key } index = { index } >
210+ { ( provided ) => (
211+ < div
212+ ref = { provided . innerRef }
213+ { ...provided . draggableProps }
214+ { ...provided . dragHandleProps }
215+ className = 'w-auto'
216+ >
217+ { renderTabLabel ( tab ) }
218+ </ div >
219+ ) }
220+ </ Draggable >
221+ </ div >
222+ ) ) }
223+ </ div >
224+ { provided . placeholder }
225+ </ div >
226+ ) }
227+ </ Droppable >
228+ </ DragDropContext >
229+ </ div >
230+ ) ;
231+ } ;
163232
164233 return (
165234 < Tabs
@@ -168,7 +237,7 @@ export default function MultiTabs({ offsetTop = true }: Props) {
168237 tabBarGutter = { 4 }
169238 activeKey = { activeTabRoutePath }
170239 items = { tabItems }
171- renderTabBar = { ( ) => < > </ > }
240+ renderTabBar = { renderTabBar }
172241 />
173242 ) ;
174243}
0 commit comments