✨ Added tab functionality and optimized related configurations
This commit is contained in:
@@ -4,28 +4,28 @@ import {ConfigService, StartupService} from '@/../bindings/voidraft/internal/ser
|
||||
import {
|
||||
AppConfig,
|
||||
AppearanceConfig,
|
||||
AuthMethod,
|
||||
EditingConfig,
|
||||
GeneralConfig,
|
||||
GitBackupConfig,
|
||||
LanguageType,
|
||||
SystemThemeType,
|
||||
TabType,
|
||||
UpdatesConfig,
|
||||
GitBackupConfig,
|
||||
AuthMethod
|
||||
UpdatesConfig
|
||||
} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {ConfigUtils} from '@/common/utils/configUtils';
|
||||
import {FONT_OPTIONS} from '@/common/constant/fonts';
|
||||
import {SUPPORTED_LOCALES} from '@/common/constant/locales';
|
||||
import {
|
||||
NumberConfigKey,
|
||||
GENERAL_CONFIG_KEY_MAP,
|
||||
EDITING_CONFIG_KEY_MAP,
|
||||
APPEARANCE_CONFIG_KEY_MAP,
|
||||
UPDATES_CONFIG_KEY_MAP,
|
||||
BACKUP_CONFIG_KEY_MAP,
|
||||
CONFIG_LIMITS,
|
||||
DEFAULT_CONFIG
|
||||
DEFAULT_CONFIG,
|
||||
EDITING_CONFIG_KEY_MAP,
|
||||
GENERAL_CONFIG_KEY_MAP,
|
||||
NumberConfigKey,
|
||||
UPDATES_CONFIG_KEY_MAP
|
||||
} from '@/common/constant/config';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useConfigStore = defineStore('config', () => {
|
||||
isLoading: false,
|
||||
configLoaded: false
|
||||
});
|
||||
|
||||
|
||||
// Font options (no longer localized)
|
||||
const fontOptions = computed(() => FONT_OPTIONS);
|
||||
|
||||
@@ -205,7 +205,6 @@ export const useConfigStore = defineStore('config', () => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 初始化语言设置
|
||||
const initializeLanguage = async (): Promise<void> => {
|
||||
try {
|
||||
@@ -255,7 +254,7 @@ export const useConfigStore = defineStore('config', () => {
|
||||
configLoaded: computed(() => state.configLoaded),
|
||||
isLoading: computed(() => state.isLoading),
|
||||
fontOptions,
|
||||
|
||||
|
||||
// 限制常量
|
||||
...limits,
|
||||
|
||||
@@ -317,19 +316,26 @@ export const useConfigStore = defineStore('config', () => {
|
||||
// 再调用系统设置API
|
||||
await StartupService.SetEnabled(value);
|
||||
},
|
||||
|
||||
|
||||
// 窗口吸附配置相关方法
|
||||
setEnableWindowSnap: async (value: boolean) => await updateGeneralConfig('enableWindowSnap', value),
|
||||
|
||||
// 加载动画配置相关方法
|
||||
setEnableLoadingAnimation: async (value: boolean) => await updateGeneralConfig('enableLoadingAnimation', value),
|
||||
|
||||
// 标签页配置相关方法
|
||||
setEnableTabs: async (value: boolean) => await updateGeneralConfig('enableTabs', value),
|
||||
|
||||
// 更新配置相关方法
|
||||
setAutoUpdate: async (value: boolean) => await updateUpdatesConfig('autoUpdate', value),
|
||||
|
||||
// 备份配置相关方法
|
||||
setEnableBackup: async (value: boolean) => {await updateBackupConfig('enabled', value);},
|
||||
setAutoBackup: async (value: boolean) => {await updateBackupConfig('auto_backup', value);},
|
||||
setEnableBackup: async (value: boolean) => {
|
||||
await updateBackupConfig('enabled', value);
|
||||
},
|
||||
setAutoBackup: async (value: boolean) => {
|
||||
await updateBackupConfig('auto_backup', value);
|
||||
},
|
||||
setRepoUrl: async (value: string) => await updateBackupConfig('repo_url', value),
|
||||
setAuthMethod: async (value: AuthMethod) => await updateBackupConfig('auth_method', value),
|
||||
setUsername: async (value: string) => await updateBackupConfig('username', value),
|
||||
|
||||
227
frontend/src/stores/tabStore.ts
Normal file
227
frontend/src/stores/tabStore.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换到指定标签页并打开对应文档
|
||||
*/
|
||||
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);
|
||||
|
||||
// 关闭其他标签页
|
||||
otherTabIds.forEach(id => closeTab(id));
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭指定标签页右侧的所有标签页
|
||||
*/
|
||||
const closeTabsToRight = (documentId: number) => {
|
||||
const index = getTabIndex(documentId);
|
||||
if (index === -1) return;
|
||||
|
||||
// 获取右侧所有标签页的ID
|
||||
const rightTabIds = tabOrder.value.slice(index + 1);
|
||||
|
||||
// 关闭右侧标签页
|
||||
rightTabIds.forEach(id => closeTab(id));
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭指定标签页左侧的所有标签页
|
||||
*/
|
||||
const closeTabsToLeft = (documentId: number) => {
|
||||
const index = getTabIndex(documentId);
|
||||
if (index <= 0) return;
|
||||
|
||||
// 获取左侧所有标签页的ID
|
||||
const leftTabIds = tabOrder.value.slice(0, index);
|
||||
|
||||
// 关闭左侧标签页
|
||||
leftTabIds.forEach(id => closeTab(id));
|
||||
};
|
||||
|
||||
/**
|
||||
* 清空所有标签页
|
||||
*/
|
||||
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
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user