131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import { defineStore } from 'pinia'
|
||
import { ref, computed } from 'vue'
|
||
|
||
/**
|
||
* 模块标签栏跨组件状态。
|
||
*
|
||
* 用于在模块详情页的 TabsList 因滚动消失时,
|
||
* 在顶部 TitleBar 中显示一组浮动切换按钮。
|
||
*
|
||
* 工作流程:
|
||
* 1. 模块(如 DownloaderModule)onMounted 时调用 registerTabs(),传入标签定义和当前值
|
||
* 2. 模块用 IntersectionObserver 或 scroll 监听检测 TabsList 可见性,调用 setFloatingVisible()
|
||
* 3. 模块通过 watch 将本地 activeTab 同步到 store
|
||
* 4. TitleBar 读取 store 的 tabs / activeTab / floatingVisible 渲染浮动切换器
|
||
* 5. 用户点击浮动切换器时调用 setActiveTab(),模块监听 store.activeTab 变化更新本地值
|
||
* 6. 模块 onUnmounted 时调用 unregisterTabs() 清理状态
|
||
*/
|
||
export interface ModuleTab {
|
||
value: string
|
||
label: string
|
||
}
|
||
|
||
export const useModuleTabsStore = defineStore('moduleTabs', () => {
|
||
/** 当前模块注册的标签列表(空表示无模块注册,TitleBar 不渲染) */
|
||
const tabs = ref<ModuleTab[]>([])
|
||
/** 当前活跃标签值 */
|
||
const activeTab = ref<string>('')
|
||
/** 是否显示浮动切换器(TabsList 滚出可视区时为 true) */
|
||
const floatingVisible = ref<boolean>(false)
|
||
|
||
/** 待跳转 tab(搜索导航设置):{ moduleId, tab },模块挂载/已挂载时消费 */
|
||
const pendingTab = ref<{ moduleId: string; tab: string } | null>(null)
|
||
|
||
/** 设置待跳转 tab(搜索结果点击时调用) */
|
||
const setPendingTab = (moduleId: string, tab: string) => {
|
||
pendingTab.value = { moduleId, tab }
|
||
}
|
||
|
||
/**
|
||
* 消费指定模块的待跳转 tab(返回 tab 值并清除)。
|
||
* 仅在 moduleId 匹配时消费,避免误切当前已挂载模块的 tab。
|
||
*/
|
||
const consumePendingTab = (moduleId: string): string | null => {
|
||
if (pendingTab.value && pendingTab.value.moduleId === moduleId) {
|
||
const tab = pendingTab.value.tab
|
||
pendingTab.value = null
|
||
return tab
|
||
}
|
||
return null
|
||
}
|
||
|
||
/** 当前模块注册的保存处理函数(null 表示无保存按钮,如自动保存模块) */
|
||
const saveHandler = ref<(() => unknown) | null>(null)
|
||
/** 保存中状态(驱动按钮 disabled + loading 图标) */
|
||
const saving = ref<boolean>(false)
|
||
/** 是否显示保存按钮(当前在设置 tab 且注册了保存处理函数) */
|
||
const saveVisible = computed(() => saveHandler.value !== null && activeTab.value === 'settings')
|
||
|
||
/** 模块注册标签(onMounted 时调用) */
|
||
const registerTabs = (tabList: ModuleTab[], current: string) => {
|
||
tabs.value = tabList
|
||
activeTab.value = current
|
||
floatingVisible.value = false
|
||
}
|
||
|
||
/** 模块注销标签(onUnmounted 时调用) */
|
||
const unregisterTabs = () => {
|
||
tabs.value = []
|
||
activeTab.value = ''
|
||
floatingVisible.value = false
|
||
saveHandler.value = null
|
||
saving.value = false
|
||
}
|
||
|
||
/** 设置浮动切换器可见性 */
|
||
const setFloatingVisible = (visible: boolean) => {
|
||
// 仅在有注册标签时才允许显示
|
||
if (!visible) {
|
||
floatingVisible.value = false
|
||
return
|
||
}
|
||
if (tabs.value.length > 0) {
|
||
floatingVisible.value = true
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置活跃标签(双向同步用)。
|
||
* - 模块本地 activeTab 变化时调用此方法同步到 store
|
||
* - TitleBar 点击时也调用此方法,模块通过 watch 感知变化
|
||
*/
|
||
const setActiveTab = (value: string) => {
|
||
activeTab.value = value
|
||
}
|
||
|
||
/** 模块注册保存处理函数(setup 中定义保存方法后调用) */
|
||
const registerSave = (handler: (() => unknown) | null) => {
|
||
saveHandler.value = handler
|
||
saving.value = false
|
||
}
|
||
|
||
/** 执行保存(TitleBar 按钮点击时调用) */
|
||
const runSave = async () => {
|
||
if (!saveHandler.value || saving.value) return
|
||
saving.value = true
|
||
try {
|
||
await saveHandler.value()
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
return {
|
||
tabs,
|
||
activeTab,
|
||
floatingVisible,
|
||
pendingTab,
|
||
saveHandler,
|
||
saving,
|
||
saveVisible,
|
||
registerTabs,
|
||
unregisterTabs,
|
||
setFloatingVisible,
|
||
setActiveTab,
|
||
setPendingTab,
|
||
consumePendingTab,
|
||
registerSave,
|
||
runSave
|
||
}
|
||
})
|