下载模块 Init

This commit is contained in:
zhongluofeng
2026-07-20 18:28:34 +08:00
parent e84958e0fc
commit 548b022426
37 changed files with 4795 additions and 65 deletions
+93
View File
@@ -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 时注册标签到 moduleTabsStoreTitleBar 据此渲染浮动切换器
* 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 表示视口;顶部裁剪 44pxTitleBar 高度 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
}