Files
voidraft/frontend/src/stores/tabStore.ts
2025-10-04 14:31:06 +08:00

259 lines
7.5 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 {computed, readonly, ref} from 'vue';
import {useConfigStore} from './configStore';
import {useDocumentStore} from './documentStore';
import type {Document} from '@/../bindings/voidraft/internal/models/models';
export interface Tab {
documentId: number; // 直接使用文档ID作为唯一标识
title: string; // 标签页标题
}
export const useTabStore = defineStore('tab', () => {
// === 依赖store ===
const configStore = useConfigStore();
const documentStore = useDocumentStore();
// === 核心状态 ===
const tabsMap = ref<Map<number, Tab>>(new Map());
const tabOrder = ref<number[]>([]); // 维护标签页顺序
const draggedTabId = ref<number | null>(null);
// === 计算属性 ===
const isTabsEnabled = computed(() => configStore.config.general.enableTabs);
const canCloseTab = computed(() => tabOrder.value.length > 1);
const currentDocumentId = computed(() => documentStore.currentDocumentId);
// 按顺序返回标签页数组用于UI渲染
const tabs = computed(() => {
return tabOrder.value
.map(documentId => tabsMap.value.get(documentId))
.filter(tab => tab !== undefined) as Tab[];
});
// === 私有方法 ===
const hasTab = (documentId: number): boolean => {
return tabsMap.value.has(documentId);
};
const getTab = (documentId: number): Tab | undefined => {
return tabsMap.value.get(documentId);
};
const updateTabTitle = (documentId: number, title: string) => {
const tab = tabsMap.value.get(documentId);
if (tab) {
tab.title = title;
}
};
// === 公共方法 ===
/**
* 添加或激活标签页
*/
const addOrActivateTab = (document: Document) => {
const documentId = document.id;
if (hasTab(documentId)) {
// 标签页已存在,无需重复添加
return;
}
// 创建新标签页
const newTab: Tab = {
documentId,
title: document.title
};
tabsMap.value.set(documentId, newTab);
tabOrder.value.push(documentId);
};
/**
* 关闭标签页
*/
const closeTab = (documentId: number) => {
if (!hasTab(documentId)) return;
const tabIndex = tabOrder.value.indexOf(documentId);
if (tabIndex === -1) return;
// 从映射和顺序数组中移除
tabsMap.value.delete(documentId);
tabOrder.value.splice(tabIndex, 1);
// 如果关闭的是当前文档,需要切换到其他文档
if (documentStore.currentDocument?.id === documentId) {
// 优先选择下一个标签页,如果没有则选择上一个
let nextIndex = tabIndex;
if (nextIndex >= tabOrder.value.length) {
nextIndex = tabOrder.value.length - 1;
}
if (nextIndex >= 0 && tabOrder.value[nextIndex]) {
const nextDocumentId = tabOrder.value[nextIndex];
switchToTabAndDocument(nextDocumentId);
}
}
};
/**
* 批量关闭标签页
* @param documentIds 要关闭的文档ID数组
*/
const closeTabs = (documentIds: number[]) => {
documentIds.forEach(documentId => {
if (!hasTab(documentId)) return;
const tabIndex = tabOrder.value.indexOf(documentId);
if (tabIndex === -1) return;
// 从映射和顺序数组中移除
tabsMap.value.delete(documentId);
tabOrder.value.splice(tabIndex, 1);
});
};
/**
* 切换到指定标签页并打开对应文档
*/
const switchToTabAndDocument = (documentId: number) => {
if (!hasTab(documentId)) return;
// 如果点击的是当前已激活的文档,不需要重复请求
if (documentStore.currentDocumentId === documentId) {
return;
}
documentStore.openDocument(documentId);
};
/**
* 移动标签页位置
*/
const moveTab = (fromIndex: number, toIndex: number) => {
if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 ||
fromIndex >= tabOrder.value.length || toIndex >= tabOrder.value.length) {
return;
}
const [movedTab] = tabOrder.value.splice(fromIndex, 1);
tabOrder.value.splice(toIndex, 0, movedTab);
};
/**
* 获取标签页在顺序中的索引
*/
const getTabIndex = (documentId: number): number => {
return tabOrder.value.indexOf(documentId);
};
/**
* 初始化标签页(当前文档)
*/
const initializeTab = () => {
if (isTabsEnabled.value) {
const currentDoc = documentStore.currentDocument;
if (currentDoc) {
addOrActivateTab(currentDoc);
}
}
};
// === 公共方法 ===
/**
* 关闭其他标签页(除了指定的标签页)
*/
const closeOtherTabs = (keepDocumentId: number) => {
if (!hasTab(keepDocumentId)) return;
// 获取所有其他标签页的ID
const otherTabIds = tabOrder.value.filter(id => id !== keepDocumentId);
// 批量关闭其他标签页
closeTabs(otherTabIds);
// 如果当前打开的文档在被关闭的标签中,需要切换到保留的文档
if (otherTabIds.includes(documentStore.currentDocumentId!)) {
switchToTabAndDocument(keepDocumentId);
}
};
/**
* 关闭指定标签页右侧的所有标签页
*/
const closeTabsToRight = (documentId: number) => {
const index = getTabIndex(documentId);
if (index === -1) return;
// 获取右侧所有标签页的ID
const rightTabIds = tabOrder.value.slice(index + 1);
// 批量关闭右侧标签页
closeTabs(rightTabIds);
// 如果当前打开的文档在被关闭的右侧标签中,需要切换到指定的文档
if (rightTabIds.includes(documentStore.currentDocumentId!)) {
switchToTabAndDocument(documentId);
}
};
/**
* 关闭指定标签页左侧的所有标签页
*/
const closeTabsToLeft = (documentId: number) => {
const index = getTabIndex(documentId);
if (index <= 0) return;
// 获取左侧所有标签页的ID
const leftTabIds = tabOrder.value.slice(0, index);
// 批量关闭左侧标签页
closeTabs(leftTabIds);
// 如果当前打开的文档在被关闭的左侧标签中,需要切换到指定的文档
if (leftTabIds.includes(documentStore.currentDocumentId!)) {
switchToTabAndDocument(documentId);
}
};
/**
* 清空所有标签页
*/
const clearAllTabs = () => {
tabsMap.value.clear();
tabOrder.value = [];
};
// === 公共API ===
return {
// 状态
tabs: readonly(tabs),
draggedTabId,
// 计算属性
isTabsEnabled,
canCloseTab,
currentDocumentId,
// 方法
addOrActivateTab,
closeTab,
closeOtherTabs,
closeTabsToLeft,
closeTabsToRight,
switchToTabAndDocument,
moveTab,
getTabIndex,
initializeTab,
clearAllTabs,
updateTabTitle,
// 工具方法
hasTab,
getTab
};
});