Files
Thing/src/stores/moduleTabsStore.ts
T
2026-08-11 17:19:36 +08:00

131 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
/**
* 模块标签栏跨组件状态。
*
* 用于在模块详情页的 TabsList 因滚动消失时,
* 在顶部 TitleBar 中显示一组浮动切换按钮。
*
* 工作流程:
* 1. 模块(如 DownloaderModuleonMounted 时调用 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
}
})