diff --git a/src/composables/useDynamicHeaderTab.ts b/src/composables/useDynamicHeaderTab.ts new file mode 100644 index 00000000..2cc48156 --- /dev/null +++ b/src/composables/useDynamicHeaderTab.ts @@ -0,0 +1,98 @@ +// 动态标签页相关类型 +interface DynamicHeaderTabButton { + icon: string + color?: string | ComputedRef + variant?: 'flat' | 'text' | 'elevated' | 'tonal' | 'outlined' | 'plain' + size?: string + class?: string + action?: () => void + show?: boolean | ComputedRef + dataAttr?: string // 用于VMenu定位的data属性 +} + +interface DynamicHeaderTabItem { + title: string + icon?: string + tab: string +} + +interface DynamicHeaderTabConfig { + items: DynamicHeaderTabItem[] + modelValue: string + appendButtons?: DynamicHeaderTabButton[] + routePath?: string + onUpdateModelValue?: (value: string) => void +} + +export function useDynamicHeaderTab() { + const route = useRoute() + + // 尝试从inject获取 + const registerDynamicHeaderTab = inject<(tab: DynamicHeaderTabConfig) => void>('registerDynamicHeaderTab') + const unregisterDynamicHeaderTab = inject<() => void>('unregisterDynamicHeaderTab') + + // 注册动态标签页 + const registerHeaderTab = (config: { + items: DynamicHeaderTabItem[] + modelValue: Ref + appendButtons?: DynamicHeaderTabButton[] + }) => { + const tabConfig: DynamicHeaderTabConfig = { + items: config.items, + modelValue: config.modelValue.value, + appendButtons: config.appendButtons, + routePath: route.path, + onUpdateModelValue: (value: string) => { + config.modelValue.value = value + }, + } + + // 监听modelValue变化并更新配置 + watch(config.modelValue, newValue => { + tabConfig.modelValue = newValue + // 重新注册以更新值 + if (registerDynamicHeaderTab) { + registerDynamicHeaderTab(tabConfig) + } else if (typeof window !== 'undefined') { + // 使用全局方法作为备用 + const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__ + if (globalRegister) { + globalRegister(tabConfig) + } + } + }) + + // 在组件卸载时取消注册 + onUnmounted(() => { + if (unregisterDynamicHeaderTab) { + unregisterDynamicHeaderTab() + } + }) + + // 初始注册 + if (registerDynamicHeaderTab) { + registerDynamicHeaderTab(tabConfig) + } else if (typeof window !== 'undefined') { + // 使用全局方法作为备用 + const globalRegister = (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__ + if (globalRegister) { + globalRegister(tabConfig) + } + } + } + + // 取消注册 + const unregisterHeaderTab = () => { + if (unregisterDynamicHeaderTab) { + unregisterDynamicHeaderTab() + } + } + + return { + registerHeaderTab, + unregisterHeaderTab, + } +} + +// 导出类型以供其他地方使用 +export type { DynamicHeaderTabButton, DynamicHeaderTabItem, DynamicHeaderTabConfig } diff --git a/src/layouts/components/DefaultLayout.vue b/src/layouts/components/DefaultLayout.vue index cd03844c..281994e9 100644 --- a/src/layouts/components/DefaultLayout.vue +++ b/src/layouts/components/DefaultLayout.vue @@ -8,6 +8,7 @@ import SearchBar from '@/layouts/components/SearchBar.vue' import ShortcutBar from '@/layouts/components/ShortcutBar.vue' import UserProfile from '@/layouts/components/UserProfile.vue' import QuickAccess from '@/layouts/components/QuickAccess.vue' +import HeaderTab from '@/layouts/components/HeaderTab.vue' import { useUserStore } from '@/stores' import { getNavMenus } from '@/router/i18n-menu' import { NavMenu } from '@/@layouts/types' @@ -64,6 +65,92 @@ const showPluginQuickAccess = ref(false) // 离线状态管理 const { setAppOffline, isOffline } = useGlobalOfflineStatus() +// 动态标签页相关 +// 定义动态标签页类型 +interface DynamicHeaderTab { + items: Array<{ title: string; icon: string; tab: string }> + modelValue: string + appendButtons?: Array<{ + icon: string + color?: string | ComputedRef + variant?: 'flat' | 'text' | 'elevated' | 'tonal' | 'outlined' | 'plain' + size?: string + class?: string + action?: () => void + show?: boolean | ComputedRef + dataAttr?: string + }> + routePath?: string // 用于标识哪个路由注册的 + onUpdateModelValue?: (value: string) => void // 用于通知值更新 +} + +// 提供动态标签页注册和获取的方法 +const dynamicHeaderTab = ref(null) + +// 提供一个方法让其他组件注册动态标签页 +const registerDynamicHeaderTab = (tab: DynamicHeaderTab) => { + // 保存注册标签页的路由路径 + tab.routePath = route.path + dynamicHeaderTab.value = tab +} + +// 提供一个方法让其他组件取消注册动态标签页 +const unregisterDynamicHeaderTab = () => { + dynamicHeaderTab.value = null +} + +// 标签页值更新处理 +const handleTabChange = (newValue: string) => { + if (dynamicHeaderTab.value) { + dynamicHeaderTab.value.modelValue = newValue + // 通知注册的页面更新值 + if (dynamicHeaderTab.value.onUpdateModelValue) { + dynamicHeaderTab.value.onUpdateModelValue(newValue) + } + } +} + +// 添加全局注册方法,解决注入不可用的问题 +if (typeof window !== 'undefined') { + // 确保在浏览器环境中 + ;(window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__ = registerDynamicHeaderTab +} + +// 提供给其他组件使用 +provide('registerDynamicHeaderTab', registerDynamicHeaderTab) +provide('unregisterDynamicHeaderTab', unregisterDynamicHeaderTab) + +// 监听路由变化来清除动态标签页 +watch( + () => route.path, + newPath => { + // 当路由变化时,清除动态标签页(如果不是同一个路由注册的) + if (dynamicHeaderTab.value && dynamicHeaderTab.value.routePath !== newPath) { + dynamicHeaderTab.value = null + } + }, + { immediate: false }, +) + +// 显示动态标签页 +const showDynamicHeaderTab = computed(() => { + return ( + dynamicHeaderTab.value && + dynamicHeaderTab.value.items.length > 0 && + // 确保只在注册的路由路径下显示标签页 + (!dynamicHeaderTab.value.routePath || dynamicHeaderTab.value.routePath === route.path) + ) +}) + +// 在组件销毁时清理 +onUnmounted(() => { + dynamicHeaderTab.value = null + // 清理全局方法 + if (typeof window !== 'undefined') { + delete (window as any).__VUE_INJECT_DYNAMIC_HEADER_TAB__ + } +}) + // 监听Service Worker消息 const handleServiceWorkerMessage = (event: MessageEvent) => { if (event.data && event.data.type === 'OFFLINE_STATUS') { @@ -270,6 +357,30 @@ onMounted(() => { transition: contentTransition, }" > + +
+ + + +
+ diff --git a/src/layouts/components/HeaderTab.vue b/src/layouts/components/HeaderTab.vue index a3bf715e..d1190401 100644 --- a/src/layouts/components/HeaderTab.vue +++ b/src/layouts/components/HeaderTab.vue @@ -117,14 +117,11 @@ onUnmounted(() => {