下载模块 Init
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch, type Ref } from 'vue'
|
||||
import { useModuleTabsStore, type ModuleTab } from '@/stores/moduleTabsStore'
|
||||
|
||||
/**
|
||||
* 模块标签栏通用 composable。
|
||||
*
|
||||
* 让任何模块的 TabsList 在滚动被 TitleBar 遮挡时,
|
||||
* 自动在 TitleBar 中显示一组浮动切换按钮。
|
||||
*
|
||||
* ## 使用方式
|
||||
*
|
||||
* ```ts
|
||||
* // 模块 <script setup> 顶部
|
||||
* const activeTab = ref('overview')
|
||||
* const tabsListRef = useModuleTabs(activeTab, [
|
||||
* { value: 'overview', label: '概览' },
|
||||
* { value: 'settings', label: '设置' }
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* ```vue
|
||||
* <!-- 模板中给 TabsList 包一层带 ref 的 div -->
|
||||
* <div ref="tabsListRef">
|
||||
* <TabsList>...</TabsList>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* ## 工作原理
|
||||
* 1. onMounted 时注册标签到 moduleTabsStore,TitleBar 据此渲染浮动切换器
|
||||
* 2. 用 IntersectionObserver 监听 TabsList 可见性(rootMargin 裁剪 TitleBar 高度)
|
||||
* 3. 双向 watch 同步本地 activeTab 与 store.activeTab
|
||||
* 4. onUnmounted 时清理 observer 并注销标签
|
||||
*
|
||||
* ## 约束
|
||||
* - TitleBar 高度固定为 40px (h-10),composable 内部已用 44px 裁剪(含缓冲)
|
||||
* - 一个模块同一时间只能注册一组标签(store 是单例)
|
||||
* - 模块卸载时务必让 composable 的 onUnmounted 执行(已自动处理,无需手动调用)
|
||||
*/
|
||||
export function useModuleTabs(
|
||||
activeTab: Ref<string>,
|
||||
tabs: ModuleTab[]
|
||||
): Ref<HTMLElement | null> {
|
||||
const tabsStore = useModuleTabsStore()
|
||||
const tabsListRef = ref<HTMLElement | null>(null)
|
||||
|
||||
let observer: IntersectionObserver | null = null
|
||||
|
||||
const setupObserver = () => {
|
||||
const el = tabsListRef.value
|
||||
if (!el || observer) return
|
||||
observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
tabsStore.setFloatingVisible(!entry.isIntersecting)
|
||||
}
|
||||
},
|
||||
{
|
||||
// root=null 表示视口;顶部裁剪 44px(TitleBar 高度 40px + 4px 缓冲)
|
||||
rootMargin: '-44px 0px 0px 0px',
|
||||
threshold: 0
|
||||
}
|
||||
)
|
||||
observer.observe(el)
|
||||
}
|
||||
|
||||
// 本地 activeTab → store(用户点击模块内 TabsTrigger 时同步)
|
||||
watch(activeTab, (val) => {
|
||||
tabsStore.setActiveTab(val)
|
||||
})
|
||||
|
||||
// store activeTab → 本地(用户点击 TitleBar 浮动切换器时同步)
|
||||
watch(() => tabsStore.activeTab, (val) => {
|
||||
if (val && val !== activeTab.value) {
|
||||
activeTab.value = val
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
tabsStore.registerTabs(tabs, activeTab.value)
|
||||
await nextTick()
|
||||
setupObserver()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
observer = null
|
||||
}
|
||||
tabsStore.unregisterTabs()
|
||||
})
|
||||
|
||||
return tabsListRef
|
||||
}
|
||||
Reference in New Issue
Block a user