22import zhCn from " element-plus/es/locale/lang/zh-cn"
33import { HomeFilled , Tools , List , InfoFilled , Sunny , Moon , Calendar , Collection } from " @element-plus/icons-vue"
44import ContextMenu from ' @imengyu/vue3-context-menu'
5- import { ElMessage , type MenuItemInstance , type CardConfigContext } from " element-plus"
5+ import { ElMessage , type MenuItemInstance , type CardConfigContext , type TabPaneName } from " element-plus"
66import { request } from " @/api"
77
88defineOptions ({
99 name: " defaultLayout"
1010})
1111
1212const themeStore = useThemeStore ()
13- const route = useRoute ()
14- const router = useRouter ()
13+ const route = useRoute () // 当前路由实例
14+ const router = useRouter () // 路由器实例
1515const cardConfig: CardConfigContext = {
1616 shadow: " always"
1717}
@@ -28,6 +28,14 @@ const messageConfig = {
2828 plain: true
2929}
3030
31+ const activeTabName = ref (" /" )
32+ const activeMenu = ref (" 0" )
33+ const openTabs = ref <TabItem []>([])
34+ // 缓存已打开的tab
35+ const components = computed (() => {
36+ return openTabs .value .map (item => item .componentName ).filter (Boolean )
37+ })
38+
3139const menuItemList = [
3240 {
3341 title: " 点歌板" ,
@@ -133,6 +141,11 @@ const closeTour = async () => {
133141 navSideTour .value = false
134142}
135143
144+ const updateActiveMenu = (routeName : string ) => {
145+ const menuItemIndex = menuItemList .findIndex (item => item .routerName == routeName )
146+ activeMenu .value = menuItemIndex .toString ()
147+ }
148+
136149/**
137150 * 跳转到指定页面
138151 * @param {string} name 路由的name
@@ -144,7 +157,62 @@ const goto = (name: string) => {
144157 return
145158 }
146159
147- router .push ({ path: name })
160+ changeTab (name as TabPaneName )
161+ }
162+
163+ /**
164+ * 切换标签页
165+ * @param name
166+ */
167+ const changeTab = (name : TabPaneName ) => {
168+ const tabName = name .toString ()
169+
170+ const currentRouteTitle = menuItemList .find (item => item .routerName == tabName )?.title || route .path
171+ let tab = openTabs .value .find (tab => tab .name === tabName )
172+
173+ if (! tab ) {
174+ tab = {
175+ name: tabName ,
176+ title: currentRouteTitle ,
177+ path: tabName ,
178+ closable: tabName !== " /" ,
179+ componentName: tabName == " /" ? " home" : tabName .replaceAll (' /' , ' ' )
180+ }
181+ openTabs .value .push (tab )
182+ }
183+
184+ activeTabName .value = tabName
185+ router .push ({ path: tabName })
186+ updateActiveMenu (tabName )
187+ }
188+
189+ /**
190+ * 移除标签页
191+ * @param {TabPaneName} targetTabName 要移除的标签页的name
192+ */
193+ const removeTab = (targetTabName : TabPaneName ) => {
194+ const tabs = openTabs .value
195+ const tabName = targetTabName .toString ()
196+ let newActiveTabName = activeTabName .value
197+
198+ if (newActiveTabName === tabName ) {
199+ tabs .forEach ((tab , index ) => {
200+ if (tab .name === tabName ) {
201+ const nextTab = tabs [index + 1 ] || tabs [index - 1 ]
202+ if (nextTab ) {
203+ newActiveTabName = nextTab .name
204+ } else {
205+ // 如果没有其他标签页,则回到首页
206+ newActiveTabName = " /"
207+ }
208+ }
209+ })
210+ }
211+
212+ openTabs .value = tabs .filter (tab => tab .name !== tabName )
213+ activeTabName .value = newActiveTabName
214+ router .push ({ path: newActiveTabName })
215+ updateActiveMenu (newActiveTabName )
148216}
149217
150218/**
@@ -225,23 +293,35 @@ const isDarktheme = computed(() => {
225293})
226294
227295watch (isFetching , () => {
228- if (globalConfigData .value ) initGlobalConfig ()
296+ if (globalConfigData .value ) initGlobalConfig ()
229297}, { once: true })
298+
299+ onMounted (() => {
300+ const currentRouteTitle = menuItemList .find (item => item .routerName == route .path )?.title || route .name
301+ openTabs .value .push ({
302+ name: route .path ,
303+ title: currentRouteTitle ,
304+ path: route .path ,
305+ closable: route .path !== " /" ,
306+ componentName: route .name as string
307+ })
308+ activeTabName .value = route .path
309+ })
230310 </script >
231311
232312<template >
233313 <el-container class =" layout-container-demo" >
234314 <el-aside :style =" asideStyle" >
235- <el-menu default-active =" 0 " :collapse =" globalConfig.collapse" class =" layout-aside-menu" >
315+ <el-menu : default-active =" activeMenu " :collapse =" globalConfig.collapse" class =" layout-aside-menu" >
236316 <template v-for =" item , index in menuItemList " :key =" index " >
237- <el-menu-item :index =" `${ index}` " @click =" goto(item.routerName)" :ref =" item.ref || undefined" >
317+ <el-menu-item :index =" index.toString() " @click =" goto(item.routerName)" :ref =" item.ref || undefined" >
238318 <el-icon >
239319 <component :is =" item.icon" />
240320 </el-icon >
241321 <template #title >{{ item.title }}</template >
242322 </el-menu-item >
243323 </template >
244- <el-menu-item ref =" themeRef" @click =" updateTheme" >
324+ <el-menu-item :index = " (menuItemList.length + 1).toString() " ref =" themeRef" @click =" updateTheme" >
245325 <el-icon v-if =" !isDarktheme" >
246326 <Sunny />
247327 </el-icon >
@@ -253,11 +333,16 @@ watch(isFetching, () => {
253333 </el-aside >
254334 <el-main @contextmenu =" onContextMenu" :style =" mainStyle" ref =" mainRef" >
255335 <el-config-provider :locale =" zhCn" :card =" cardConfig" :dialog =" dialogConfig" :message =" messageConfig" >
256- <router-view v-slot =" { Component }" >
257- <keep-alive :include =" ['home']" >
258- <component :is =" Component" />
259- </keep-alive >
260- </router-view >
336+ <el-tabs v-model =" activeTabName" type =" card" closable @tab-remove =" removeTab" @tab-change =" changeTab" >
337+ <el-tab-pane v-for =" item in openTabs" :key =" item.name" :label =" item.title" :name =" item.name"
338+ :closable =" item.closable" >
339+ <router-view v-slot =" { Component }" >
340+ <keep-alive :include =" components" >
341+ <component :is =" Component" />
342+ </keep-alive >
343+ </router-view >
344+ </el-tab-pane >
345+ </el-tabs >
261346 <!--
262347 el-tour组件finish和close事件的的使用示例
263348 https://github.com/element-plus/element-plus/issues/22419
0 commit comments