1- import { useMenuStore } from '@/stores/store' ;
2- import { useTabStore , type TabItem } from '@/stores/tabStore' ;
3- import { useUserStore } from '@/stores/userStore' ;
4- import { getIcon } from '@/utils/optimized-icons' ;
5- import { findMenuByPath } from '@/utils/utils' ;
6- import { ArrowLeftOutlined , ArrowRightOutlined , CloseOutlined , CloseSquareOutlined , DownOutlined , ExportOutlined , PushpinOutlined , ReloadOutlined } from '@ant-design/icons' ;
1+ import {
2+ ArrowLeftOutlined ,
3+ ArrowRightOutlined ,
4+ CloseOutlined ,
5+ CloseSquareOutlined ,
6+ DownOutlined ,
7+ ExportOutlined ,
8+ PushpinOutlined ,
9+ ReloadOutlined ,
10+ } from '@ant-design/icons' ;
711import { useNavigate , useRouterState } from '@tanstack/react-router' ;
8- import { Button , Dropdown , Tabs , type MenuProps , type TabsProps } from 'antd' ;
12+ import { Button , Dropdown , type MenuProps , Tabs , type TabsProps } from 'antd' ;
913import { memo , useCallback , useEffect , useMemo } from 'react' ;
1014import { useTranslation } from 'react-i18next' ;
1115import { useShallow } from 'zustand/shallow' ;
16+ import { useMenuStore } from '@/stores/store' ;
17+ import { type TabItem , useTabStore } from '@/stores/tabStore' ;
18+ import { useUserStore } from '@/stores/userStore' ;
19+ import { getIcon } from '@/utils/optimized-icons' ;
20+ import { findMenuByPath } from '@/utils/utils' ;
1221import './tabBar.scss' ;
1322
1423/**
@@ -25,10 +34,12 @@ const TabBar: React.FC = memo(() => {
2534 const pathname = useRouterState ( { select : ( s ) => s . location . pathname } ) ;
2635
2736 // 1. 使用扁平化路由映射 (O(1) 查找
28- const { caches, menus } = useMenuStore ( useShallow ( ( state ) => ( {
29- caches : state . caches ,
30- menus : state . menus ,
31- } ) ) ) ;
37+ const { caches, menus } = useMenuStore (
38+ useShallow ( ( state ) => ( {
39+ caches : state . caches ,
40+ menus : state . menus ,
41+ } ) )
42+ ) ;
3243 const homePath = useUserStore ( ( state ) => state . homePath ) ;
3344
3445 // Zustand Selector 优化
@@ -55,7 +66,9 @@ const TabBar: React.FC = memo(() => {
5566 const createTab = useCallback (
5667 ( path : string ) : TabItem | null => {
5768 const route = findMenuByPath ( path , caches ) ;
58- if ( ! route ) return null ;
69+ if ( ! route ) {
70+ return null ;
71+ }
5972 return {
6073 key : path ,
6174 label : route . meta ?. title || path ,
@@ -71,23 +84,29 @@ const TabBar: React.FC = memo(() => {
7184 // 3. 核心逻辑:同步 URL 和 Tabs 状态
7285 // 合并了初始化和路径变化的逻辑,更加健壮
7386 useEffect ( ( ) => {
74- if ( ! homePath || ! menus . length || pathname === '/login' ) return ;
87+ if ( ! homePath || ! menus . length || pathname === '/login' ) {
88+ return ;
89+ }
7590
7691 const isHome = pathname === homePath ;
7792 const targetTab = tabState . tabs . find ( ( tab ) => tab . key === pathname ) ;
7893
7994 // 场景 A: Tabs 为空 (通常是首次加载或刷新)
8095 if ( tabState . tabs . length === 0 ) {
8196 const newTabs : TabItem [ ] = [ ] ;
82-
97+
8398 // 必须保证有首页
8499 const homeTab = createTab ( homePath ) ;
85- if ( homeTab ) newTabs . push ( homeTab ) ;
100+ if ( homeTab ) {
101+ newTabs . push ( homeTab ) ;
102+ }
86103
87104 // 如果当前不是首页,也加入当前页
88105 if ( ! isHome ) {
89106 const currentTab = createTab ( pathname ) ;
90- if ( currentTab ) newTabs . push ( currentTab ) ;
107+ if ( currentTab ) {
108+ newTabs . push ( currentTab ) ;
109+ }
91110 }
92111
93112 if ( newTabs . length > 0 ) {
@@ -123,102 +142,127 @@ const TabBar: React.FC = memo(() => {
123142 if ( ! userData . isLogin ) {
124143 tabState . resetTabs ( ) ;
125144 }
126- } catch ( e ) { console . error ( e ) ; }
145+ } catch ( e ) {
146+ console . error ( e ) ;
147+ }
127148 }
128149 } ;
129150 window . addEventListener ( 'storage' , handleStorageChange ) ;
130151 return ( ) => window . removeEventListener ( 'storage' , handleStorageChange ) ;
131152 } , [ tabState . resetTabs ] ) ;
132153
133154 // 5. 事件处理器 - 使用 useCallback 保持稳定
134- const handleTabClick = useCallback ( ( key : string ) => {
135- if ( key !== pathname ) {
136- navigate ( { to : key } ) ;
137- }
138- } , [ pathname , navigate ] ) ;
155+ const handleTabClick = useCallback (
156+ ( key : string ) => {
157+ if ( key !== pathname ) {
158+ navigate ( { to : key } ) ;
159+ }
160+ } ,
161+ [ pathname , navigate ]
162+ ) ;
139163
140- const handleTabEdit = useCallback ( ( targetKey : React . MouseEvent | React . KeyboardEvent | string , action : 'add' | 'remove' ) => {
141- if ( action === 'remove' && typeof targetKey === 'string' ) {
142- const nextKey = tabState . removeTab ( targetKey ) ;
143- // 只有当关闭的是当前激活的 tab 时才跳转
144- if ( targetKey === tabState . activeKey && nextKey ) {
145- navigate ( { to : nextKey , replace : true } ) ;
164+ const handleTabEdit = useCallback (
165+ ( targetKey : React . MouseEvent | React . KeyboardEvent | string , action : 'add' | 'remove' ) => {
166+ if ( action === 'remove' && typeof targetKey === 'string' ) {
167+ const nextKey = tabState . removeTab ( targetKey ) ;
168+ // 只有当关闭的是当前激活的 tab 时才跳转
169+ if ( targetKey === tabState . activeKey && nextKey ) {
170+ navigate ( { to : nextKey , replace : true } ) ;
171+ }
146172 }
147- }
148- } , [ tabState , navigate , pathname ] ) ;
173+ } ,
174+ [ tabState , navigate , pathname ]
175+ ) ;
149176
150177 // 6. 菜单 Actions 逻辑
151- const handleMenuAction = useCallback ( ( key : string , tabKey : string ) => {
152- const {
153- removeTab, pinTab, unpinTab, reloadTab,
154- closeLeftTabs, closeRightTabs, closeOtherTabs, closeAllTabs,
155- tabs, activeKey
156- } = tabState ;
178+ const handleMenuAction = useCallback (
179+ ( key : string , tabKey : string ) => {
180+ const {
181+ removeTab,
182+ pinTab,
183+ unpinTab,
184+ reloadTab,
185+ closeLeftTabs,
186+ closeRightTabs,
187+ closeOtherTabs,
188+ closeAllTabs,
189+ tabs,
190+ activeKey,
191+ } = tabState ;
157192
158- const currentTab = tabs . find ( t => t . key === tabKey ) ;
159- let nextActiveKey : string | undefined | null = null ;
193+ const currentTab = tabs . find ( ( t ) => t . key === tabKey ) ;
194+ let nextActiveKey : string | undefined | null = null ;
160195
161- switch ( key ) {
162- case 'close' :
163- nextActiveKey = removeTab ( tabKey ) ;
164- break ;
165- case 'pin' :
166- currentTab ?. closable ? pinTab ( tabKey ) : unpinTab ( tabKey ) ;
167- break ;
168- case 'reload' :
169- reloadTab ( tabKey ) ;
170- break ;
171- case 'openInNewWindow' :
172- if ( currentTab ?. path ) window . open ( currentTab . path , '_blank' ) ;
173- break ;
174- case 'closeLeft' :
175- nextActiveKey = closeLeftTabs ( tabKey , homePath ) ;
176- break ;
177- case 'closeRight' :
178- nextActiveKey = closeRightTabs ( tabKey , homePath ) ;
179- break ;
180- case 'closeOthers' :
181- nextActiveKey = closeOtherTabs ( tabKey , homePath ) ;
182- break ;
183- case 'closeAll' :
184- nextActiveKey = closeAllTabs ( homePath ) ;
185- break ;
186- }
196+ switch ( key ) {
197+ case 'close' :
198+ nextActiveKey = removeTab ( tabKey ) ;
199+ break ;
200+ case 'pin' :
201+ currentTab ?. closable ? pinTab ( tabKey ) : unpinTab ( tabKey ) ;
202+ break ;
203+ case 'reload' :
204+ reloadTab ( tabKey ) ;
205+ break ;
206+ case 'openInNewWindow' :
207+ if ( currentTab ?. path ) {
208+ window . open ( currentTab . path , '_blank' ) ;
209+ }
210+ break ;
211+ case 'closeLeft' :
212+ nextActiveKey = closeLeftTabs ( tabKey , homePath ) ;
213+ break ;
214+ case 'closeRight' :
215+ nextActiveKey = closeRightTabs ( tabKey , homePath ) ;
216+ break ;
217+ case 'closeOthers' :
218+ nextActiveKey = closeOtherTabs ( tabKey , homePath ) ;
219+ break ;
220+ case 'closeAll' :
221+ nextActiveKey = closeAllTabs ( homePath ) ;
222+ break ;
223+ }
187224
188- // 统一处理导航
189- if ( nextActiveKey && nextActiveKey !== activeKey && nextActiveKey !== pathname ) {
190- navigate ( { to : nextActiveKey , replace : true } ) ;
191- }
192- } , [ tabState , homePath , pathname , navigate ] ) ;
225+ // 统一处理导航
226+ if ( nextActiveKey && nextActiveKey !== activeKey && nextActiveKey !== pathname ) {
227+ navigate ( { to : nextActiveKey , replace : true } ) ;
228+ }
229+ } ,
230+ [ tabState , homePath , pathname , navigate ]
231+ ) ;
193232
194233 // 生成菜单项配置
195- const getMenuItems = useCallback ( ( tabKey : string ) : MenuProps [ 'items' ] => {
196- const tab = tabState . tabs . find ( t => t . key === tabKey ) ;
197- if ( ! tab ) return [ ] ;
198- const isClosable = tab . closable ;
234+ const getMenuItems = useCallback (
235+ ( tabKey : string ) : MenuProps [ 'items' ] => {
236+ const tab = tabState . tabs . find ( ( t ) => t . key === tabKey ) ;
237+ if ( ! tab ) {
238+ return [ ] ;
239+ }
240+ const isClosable = tab . closable ;
199241
200- return [
201- { key : 'close' , label : t ( 'common.close' ) , icon : < CloseOutlined /> , disabled : ! isClosable } ,
202- { key : 'pin' , label : isClosable ? t ( 'common.pin' ) : t ( 'common.unpin' ) , icon : < PushpinOutlined /> } ,
203- { key : 'reload' , label : t ( 'common.reload' ) , icon : < ReloadOutlined /> } ,
204- { key : 'openInNewWindow' , label : t ( 'common.openInNewWindow' ) , icon : < ExportOutlined /> } ,
205- { type : 'divider' } ,
206- { key : 'closeLeft' , label : t ( 'common.closeLeftTabs' ) , icon : < ArrowLeftOutlined /> } ,
207- { key : 'closeRight' , label : t ( 'common.closeRightTabs' ) , icon : < ArrowRightOutlined /> } ,
208- { key : 'closeOthers' , label : t ( 'common.closeOtherTabs' ) , icon : < CloseSquareOutlined /> } ,
209- { key : 'closeAll' , label : t ( 'common.closeAllTabs' ) , icon : < CloseSquareOutlined /> } ,
210- ] ;
211- } , [ tabState . tabs , t ] ) ;
242+ return [
243+ { key : 'close' , label : t ( 'common.close' ) , icon : < CloseOutlined /> , disabled : ! isClosable } ,
244+ { key : 'pin' , label : isClosable ? t ( 'common.pin' ) : t ( 'common.unpin' ) , icon : < PushpinOutlined /> } ,
245+ { key : 'reload' , label : t ( 'common.reload' ) , icon : < ReloadOutlined /> } ,
246+ { key : 'openInNewWindow' , label : t ( 'common.openInNewWindow' ) , icon : < ExportOutlined /> } ,
247+ { type : 'divider' } ,
248+ { key : 'closeLeft' , label : t ( 'common.closeLeftTabs' ) , icon : < ArrowLeftOutlined /> } ,
249+ { key : 'closeRight' , label : t ( 'common.closeRightTabs' ) , icon : < ArrowRightOutlined /> } ,
250+ { key : 'closeOthers' , label : t ( 'common.closeOtherTabs' ) , icon : < CloseSquareOutlined /> } ,
251+ { key : 'closeAll' , label : t ( 'common.closeAllTabs' ) , icon : < CloseSquareOutlined /> } ,
252+ ] ;
253+ } ,
254+ [ tabState . tabs , t ]
255+ ) ;
212256
213257 // 7. 渲染 Tab Items
214258 const tabItems = useMemo < TabsProps [ 'items' ] > ( ( ) => {
215259 return tabState . tabs . map ( ( tab ) => ( {
216260 key : tab . key ,
217261 label : (
218262 < Dropdown
219- menu = { {
220- items : getMenuItems ( tab . key ) ,
221- onClick : ( { key } ) => handleMenuAction ( key , tab . key )
263+ menu = { {
264+ items : getMenuItems ( tab . key ) ,
265+ onClick : ( { key } ) => handleMenuAction ( key , tab . key ) ,
222266 } }
223267 trigger = { [ 'contextMenu' ] }
224268 >
@@ -232,7 +276,9 @@ const TabBar: React.FC = memo(() => {
232276 } ) ) ;
233277 } , [ tabState . tabs , t , getMenuItems , handleMenuAction ] ) ;
234278
235- if ( ! tabState . tabs . length ) return null ;
279+ if ( ! tabState . tabs . length ) {
280+ return null ;
281+ }
236282
237283 return (
238284 < div className = "tab-bar flex w-full" >
@@ -247,15 +293,15 @@ const TabBar: React.FC = memo(() => {
247293 tabBarGutter = { 0 }
248294 className = "tab-bar-tabs flex-1"
249295 />
250-
296+
251297 { /* 右侧功能区:下拉菜单操作当前激活的 Tab */ }
252298 < div className = "tab-bar-actions w-[40px] flex items-center justify-center border-l border-gray-200" >
253- < Dropdown
254- menu = { {
255- items : getMenuItems ( tabState . activeKey ) ,
256- onClick : ( { key } ) => handleMenuAction ( key , tabState . activeKey )
257- } }
258- placement = "bottomRight"
299+ < Dropdown
300+ menu = { {
301+ items : getMenuItems ( tabState . activeKey ) ,
302+ onClick : ( { key } ) => handleMenuAction ( key , tabState . activeKey ) ,
303+ } }
304+ placement = "bottomRight"
259305 trigger = { [ 'click' , 'hover' ] }
260306 >
261307 < Button type = "text" size = "small" icon = { < DownOutlined /> } className = "flex items-center justify-center" />
@@ -267,4 +313,4 @@ const TabBar: React.FC = memo(() => {
267313
268314TabBar . displayName = 'TabBar' ;
269315
270- export default TabBar ;
316+ export default TabBar ;
0 commit comments