Added tab functionality and optimized related configurations

This commit is contained in:
2025-10-04 02:27:32 +08:00
parent 2d02bf7f1f
commit 45968cd353
21 changed files with 689 additions and 166 deletions

View File

@@ -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),

View 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
};
});