update
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
import { settingsRepository, Setting, getSidebarConfig as getSidebarConfigFromRepo, setSidebarConfig as setSidebarConfigInRepo } from '../repositories/settings.repository'; // Import specific repo functions
|
import { settingsRepository, Setting, getSidebarConfig as getSidebarConfigFromRepo, setSidebarConfig as setSidebarConfigInRepo } from '../repositories/settings.repository'; // Import specific repo functions
|
||||||
import { SidebarConfig, PaneName, UpdateSidebarConfigDto } from '../types/settings.types';
|
import { SidebarConfig, PaneName, UpdateSidebarConfigDto } from '../types/settings.types';
|
||||||
|
|
||||||
// +++ 定义焦点切换配置项接口 (与前端 store 保持一致) +++
|
// +++ 定义焦点切换完整配置接口 (与前端 store 保持一致) +++
|
||||||
interface ConfigurableFocusableItem {
|
interface FocusItemConfig { // 单个项目的配置
|
||||||
id: string;
|
|
||||||
shortcut?: string;
|
shortcut?: string;
|
||||||
}
|
}
|
||||||
|
interface FocusSwitcherFullConfig { // 完整配置结构
|
||||||
|
sequence: string[];
|
||||||
|
shortcuts: Record<string, FocusItemConfig>;
|
||||||
|
}
|
||||||
|
|
||||||
// --- 移除旧的默认字符串数组 ---
|
const FOCUS_SEQUENCE_KEY = 'focusSwitcherSequence'; // 设置键保持不变
|
||||||
// const DEFAULT_FOCUS_SEQUENCE = ["quickCommandsSearch", "commandHistorySearch", "fileManagerSearch", "commandInput", "terminalSearch"];
|
|
||||||
const FOCUS_SEQUENCE_KEY = 'focusSwitcherSequence'; // 焦点切换顺序设置键
|
|
||||||
const NAV_BAR_VISIBLE_KEY = 'navBarVisible'; // 导航栏可见性设置键
|
const NAV_BAR_VISIBLE_KEY = 'navBarVisible'; // 导航栏可见性设置键
|
||||||
const LAYOUT_TREE_KEY = 'layoutTree'; // 布局树设置键
|
const LAYOUT_TREE_KEY = 'layoutTree'; // 布局树设置键
|
||||||
const AUTO_COPY_ON_SELECT_KEY = 'autoCopyOnSelect'; // 终端选中自动复制设置键
|
const AUTO_COPY_ON_SELECT_KEY = 'autoCopyOnSelect'; // 终端选中自动复制设置键
|
||||||
@@ -95,52 +96,60 @@ export const settingsService = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取焦点切换顺序
|
* 获取焦点切换顺序
|
||||||
* @returns 返回存储的焦点切换顺序数组,如果未设置或无效则返回默认顺序
|
* @returns 返回存储的完整焦点切换配置对象,如果未设置或无效则返回默认空配置
|
||||||
*/
|
*/
|
||||||
async getFocusSwitcherSequence(): Promise<ConfigurableFocusableItem[]> { // +++ 更新返回类型 +++
|
async getFocusSwitcherSequence(): Promise<FocusSwitcherFullConfig> { // +++ 更新返回类型 +++
|
||||||
console.log(`[Service] Attempting to get setting for key: ${FOCUS_SEQUENCE_KEY}`);
|
console.log(`[Service] Attempting to get setting for key: ${FOCUS_SEQUENCE_KEY}`);
|
||||||
|
const defaultConfig: FocusSwitcherFullConfig = { sequence: [], shortcuts: {} }; // 默认值
|
||||||
try {
|
try {
|
||||||
const configJson = await settingsRepository.getSetting(FOCUS_SEQUENCE_KEY);
|
const configJson = await settingsRepository.getSetting(FOCUS_SEQUENCE_KEY);
|
||||||
console.log(`[Service] Raw value from repository for ${FOCUS_SEQUENCE_KEY}:`, configJson);
|
console.log(`[Service] Raw value from repository for ${FOCUS_SEQUENCE_KEY}:`, configJson);
|
||||||
if (configJson) {
|
if (configJson) {
|
||||||
const config = JSON.parse(configJson);
|
const config = JSON.parse(configJson);
|
||||||
// +++ 验证新的数据结构 +++
|
// +++ 验证 FocusSwitcherFullConfig 结构 +++
|
||||||
if (Array.isArray(config) && config.every(item =>
|
if (
|
||||||
typeof item === 'object' && item !== null && typeof item.id === 'string' &&
|
typeof config === 'object' && config !== null &&
|
||||||
(item.shortcut === undefined || typeof item.shortcut === 'string')
|
Array.isArray(config.sequence) && config.sequence.every((item: any) => typeof item === 'string') &&
|
||||||
)) {
|
typeof config.shortcuts === 'object' && config.shortcuts !== null &&
|
||||||
console.log('[Service] Fetched and validated focus switcher config:', JSON.stringify(config));
|
Object.values(config.shortcuts).every((sc: any) => typeof sc === 'object' && sc !== null && (sc.shortcut === undefined || typeof sc.shortcut === 'string'))
|
||||||
return config as ConfigurableFocusableItem[];
|
) {
|
||||||
|
console.log('[Service] Fetched and validated full focus switcher config:', JSON.stringify(config));
|
||||||
|
// TODO: 可能需要进一步验证 sequence 中的 id 是否仍然有效 (存在于某个地方定义的可用 ID 列表)
|
||||||
|
// TODO: 可能需要进一步验证 shortcuts 中的 key 是否是有效的 ID
|
||||||
|
return config as FocusSwitcherFullConfig;
|
||||||
} else {
|
} else {
|
||||||
console.warn('[Service] Invalid focus switcher config format found in settings. Returning empty array.');
|
console.warn('[Service] Invalid full focus switcher config format found in settings. Returning default.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('[Service] No focus switcher config found in settings. Returning empty array.');
|
console.log('[Service] No focus switcher config found in settings. Returning default.');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Service] Error parsing focus switcher config from settings (key: ${FOCUS_SEQUENCE_KEY}):`, error);
|
console.error(`[Service] Error parsing full focus switcher config from settings (key: ${FOCUS_SEQUENCE_KEY}):`, error);
|
||||||
}
|
}
|
||||||
// +++ 返回空数组作为默认值 +++
|
console.log('[Service] Returning default focus config:', JSON.stringify(defaultConfig));
|
||||||
console.log('[Service] Returning empty array as default focus config.');
|
return defaultConfig;
|
||||||
return [];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置焦点切换顺序
|
* 设置完整的焦点切换配置
|
||||||
* @param sequence 要保存的焦点切换顺序数组
|
* @param fullConfig 包含 sequence 和 shortcuts 的完整配置对象
|
||||||
*/
|
*/
|
||||||
async setFocusSwitcherSequence(config: ConfigurableFocusableItem[]): Promise<void> { // +++ 更新参数类型 +++
|
async setFocusSwitcherSequence(fullConfig: FocusSwitcherFullConfig): Promise<void> { // +++ 更新参数类型 +++
|
||||||
console.log('[Service] setFocusSwitcherSequence called with new config format:', JSON.stringify(config));
|
console.log('[Service] setFocusSwitcherSequence called with full config:', JSON.stringify(fullConfig));
|
||||||
// +++ 验证新的数据结构 (虽然控制器层已验证,服务层再次验证更健壮) +++
|
// +++ 验证 FocusSwitcherFullConfig 结构 (控制器层已做基本验证) +++
|
||||||
if (!Array.isArray(config) || !config.every(item =>
|
if (
|
||||||
typeof item === 'object' && item !== null && typeof item.id === 'string' &&
|
!(typeof fullConfig === 'object' && fullConfig !== null &&
|
||||||
(item.shortcut === undefined || typeof item.shortcut === 'string')
|
Array.isArray(fullConfig.sequence) && fullConfig.sequence.every((item: any) => typeof item === 'string') &&
|
||||||
)) {
|
typeof fullConfig.shortcuts === 'object' && fullConfig.shortcuts !== null &&
|
||||||
console.error('[Service] Attempted to save invalid focus switcher config format:', config);
|
Object.values(fullConfig.shortcuts).every((sc: any) => typeof sc === 'object' && sc !== null && (sc.shortcut === undefined || typeof sc.shortcut === 'string')))
|
||||||
throw new Error('Invalid config format provided.');
|
) {
|
||||||
}
|
console.error('[Service] Attempted to save invalid full focus switcher config format:', fullConfig);
|
||||||
|
throw new Error('Invalid full config format provided.');
|
||||||
|
}
|
||||||
|
// TODO: 可能需要进一步验证 sequence 中的 id 和 shortcuts 中的 key 是否有效
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const configJson = JSON.stringify(config); // +++ 序列化新的结构 +++
|
const configJson = JSON.stringify(fullConfig); // +++ 序列化完整结构 +++
|
||||||
console.log(`[Service] Attempting to save setting. Key: ${FOCUS_SEQUENCE_KEY}, Value: ${configJson}`);
|
console.log(`[Service] Attempting to save setting. Key: ${FOCUS_SEQUENCE_KEY}, Value: ${configJson}`);
|
||||||
await settingsRepository.setSetting(FOCUS_SEQUENCE_KEY, configJson);
|
await settingsRepository.setSetting(FOCUS_SEQUENCE_KEY, configJson);
|
||||||
console.log(`[Service] Successfully saved setting for key: ${FOCUS_SEQUENCE_KEY}`);
|
console.log(`[Service] Successfully saved setting for key: ${FOCUS_SEQUENCE_KEY}`);
|
||||||
|
|||||||
@@ -87,27 +87,29 @@ export const settingsController = {
|
|||||||
async setFocusSwitcherSequence(req: Request, res: Response): Promise<void> {
|
async setFocusSwitcherSequence(req: Request, res: Response): Promise<void> {
|
||||||
console.log('[Controller] Received request to set focus switcher sequence.');
|
console.log('[Controller] Received request to set focus switcher sequence.');
|
||||||
try {
|
try {
|
||||||
// +++ 修改:直接获取请求体作为配置数组 +++
|
// +++ 修改:获取请求体并验证其是否符合 FocusSwitcherFullConfig 结构 +++
|
||||||
const focusConfig = req.body;
|
const fullConfig = req.body;
|
||||||
console.log('[Controller] Request body focusConfig:', JSON.stringify(focusConfig));
|
console.log('[Controller] Request body fullConfig:', JSON.stringify(fullConfig));
|
||||||
|
|
||||||
// +++ 修改:验证新的数据结构 (ConfigurableFocusableItem[]) +++
|
// +++ 验证 FocusSwitcherFullConfig 结构 +++
|
||||||
if (!Array.isArray(focusConfig) || !focusConfig.every(item =>
|
if (
|
||||||
typeof item === 'object' && item !== null && typeof item.id === 'string' &&
|
!(typeof fullConfig === 'object' && fullConfig !== null &&
|
||||||
(item.shortcut === undefined || typeof item.shortcut === 'string')
|
Array.isArray(fullConfig.sequence) && fullConfig.sequence.every((item: any) => typeof item === 'string') &&
|
||||||
)) {
|
typeof fullConfig.shortcuts === 'object' && fullConfig.shortcuts !== null &&
|
||||||
console.warn('[Controller] Invalid focus config format received:', focusConfig);
|
Object.values(fullConfig.shortcuts).every((sc: any) => typeof sc === 'object' && sc !== null && (sc.shortcut === undefined || typeof sc.shortcut === 'string')))
|
||||||
res.status(400).json({ message: '无效的请求体,必须是包含 id (string) 和可选 shortcut (string) 的对象数组' });
|
) {
|
||||||
|
console.warn('[Controller] Invalid full focus config format received:', fullConfig);
|
||||||
|
res.status(400).json({ message: '无效的请求体,必须是包含 sequence (string[]) 和 shortcuts (Record<string, {shortcut?: string}>) 的对象' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Controller] Calling settingsService.setFocusSwitcherSequence with new config format...');
|
console.log('[Controller] Calling settingsService.setFocusSwitcherSequence with validated full config...');
|
||||||
// +++ 修改:传递完整的配置数组给服务层 +++
|
// +++ 传递验证后的 fullConfig 给服务层 +++
|
||||||
await settingsService.setFocusSwitcherSequence(focusConfig);
|
await settingsService.setFocusSwitcherSequence(fullConfig);
|
||||||
console.log('[Controller] settingsService.setFocusSwitcherSequence completed successfully.');
|
console.log('[Controller] settingsService.setFocusSwitcherSequence completed successfully.');
|
||||||
|
|
||||||
console.log('[Controller] Logging audit action: FOCUS_SWITCHER_SEQUENCE_UPDATED');
|
console.log('[Controller] Logging audit action: FOCUS_SWITCHER_SEQUENCE_UPDATED');
|
||||||
auditLogService.logAction('FOCUS_SWITCHER_SEQUENCE_UPDATED', { config: focusConfig }); // +++ 修改审计日志内容 +++
|
auditLogService.logAction('FOCUS_SWITCHER_SEQUENCE_UPDATED', { config: fullConfig }); // +++ 修改审计日志内容 +++
|
||||||
|
|
||||||
console.log('[Controller] Sending success response.');
|
console.log('[Controller] Sending success response.');
|
||||||
res.status(200).json({ message: '焦点切换顺序已成功更新' });
|
res.status(200).json({ message: '焦点切换顺序已成功更新' });
|
||||||
|
|||||||
@@ -177,14 +177,14 @@ const handleGlobalKeyUp = async (event: KeyboardEvent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const configuredItems = focusSwitcherStore.configuredItems;
|
const order = focusSwitcherStore.sequenceOrder; // ++ 使用新的 sequenceOrder state ++
|
||||||
if (configuredItems.length === 0) {
|
if (order.length === 0) { // ++ 检查新的 state ++
|
||||||
console.log('[App] No focus sequence configured.');
|
console.log('[App] No focus sequence configured.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let focused = false;
|
let focused = false;
|
||||||
for (let i = 0; i < configuredItems.length; i++) {
|
for (let i = 0; i < order.length; i++) { // ++ Use order.length for loop condition ++
|
||||||
const nextFocusId = focusSwitcherStore.getNextFocusTargetId(currentFocusId);
|
const nextFocusId = focusSwitcherStore.getNextFocusTargetId(currentFocusId);
|
||||||
if (!nextFocusId) {
|
if (!nextFocusId) {
|
||||||
console.warn('[App] Could not determine next focus target ID in sequence.');
|
console.warn('[App] Could not determine next focus target ID in sequence.');
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import { ref, computed, watch, reactive, type Ref } from 'vue'; // 添加 Ref
|
import { ref, computed, watch, reactive, type Ref } from 'vue'; // 添加 Ref
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
import { useFocusSwitcherStore, type FocusableInput, type ConfiguredFocusableInput } from '../stores/focusSwitcher.store'; // +++ 导入新接口 +++
|
import { useFocusSwitcherStore, type FocusableInput, type FocusItemConfig, type FocusSwitcherFullConfig } from '../stores/focusSwitcher.store'; // ++ 导入新接口 ++
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
// --- 移除本地类型定义 ---
|
|
||||||
|
|
||||||
|
|
||||||
|
// 本地接口,仅用于右侧列表显示
|
||||||
|
interface SequenceDisplayItem extends FocusableInput {}
|
||||||
|
|
||||||
|
|
||||||
// --- Props ---
|
// --- Props ---
|
||||||
@@ -36,27 +36,50 @@ const dialogStyle = reactive({
|
|||||||
position: 'absolute' as 'absolute',
|
position: 'absolute' as 'absolute',
|
||||||
});
|
});
|
||||||
const hasChanges = ref(false);
|
const hasChanges = ref(false);
|
||||||
// 本地副本,用于在弹窗内编辑而不直接修改 store
|
// 本地副本,用于在弹窗内编辑
|
||||||
const localSequence: Ref<ConfiguredFocusableInput[]> = ref([]); // +++ 使用导入的接口 +++
|
const localSequence = ref<SequenceDisplayItem[]>([]); // 右侧列表,只关心顺序和基础信息
|
||||||
// +++ 存储原始序列(包含 ID 和快捷键),用于比较 +++
|
const localItemConfigs = ref<Record<string, FocusItemConfig>>({}); // 所有项目的配置 (快捷键)
|
||||||
const originalSequence: Ref<ConfiguredFocusableInput[]> = ref([]); // +++ 使用导入的接口 +++
|
const originalConfig = ref<FocusSwitcherFullConfig | null>(null); // 存储原始完整配置用于比较
|
||||||
|
|
||||||
// --- Watchers ---
|
// --- Watchers ---
|
||||||
watch(() => props.isVisible, (newValue) => {
|
watch(() => props.isVisible, async (newValue) => { // ++ Make async for potential backend load ++
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
// 从 Store 加载当前配置到本地副本
|
// --- 加载完整配置 ---
|
||||||
// 从 Store 加载当前配置到本地副本
|
// 确保 Store 已初始化 (如果 Store 还没有加载完)
|
||||||
// !!! 注意:Store 现在也需要支持快捷键,这里暂时假设它返回的数据包含 shortcut !!!
|
// await focusSwitcherStore.loadConfigurationFromBackend(); // 如果 Store 初始化时未加载,则在此加载
|
||||||
// 假设 getConfiguredInputs 返回的是 LocalFocusableInput[] 或能转换的类型
|
|
||||||
const loadedSequenceFromStore = focusSwitcherStore.getConfiguredInputs; // 这个 getter 可能需要修改
|
const currentSequenceOrder = focusSwitcherStore.sequenceOrder;
|
||||||
console.log('[FocusSwitcherConfigurator] Loading sequence from store getter...');
|
const currentItemConfigs = focusSwitcherStore.itemConfigs;
|
||||||
// 深拷贝,并确保每个项目都有 shortcut 属性(可能为 undefined)
|
const allAvailableInputs = focusSwitcherStore.availableInputs; // 获取所有可用输入的基础信息
|
||||||
// Store getter 现在返回正确的类型,可以直接深拷贝
|
const inputsMap = new Map(allAvailableInputs.map(input => [input.id, input]));
|
||||||
localSequence.value = JSON.parse(JSON.stringify(loadedSequenceFromStore));
|
|
||||||
originalSequence.value = JSON.parse(JSON.stringify(loadedSequenceFromStore)); // 同样直接拷贝
|
console.log('[FocusSwitcherConfigurator] Loading full config from store...');
|
||||||
|
console.log('[FocusSwitcherConfigurator] Store sequenceOrder:', JSON.stringify(currentSequenceOrder));
|
||||||
|
console.log('[FocusSwitcherConfigurator] Store itemConfigs:', JSON.stringify(currentItemConfigs));
|
||||||
|
|
||||||
|
// 构建本地右侧列表 (localSequence)
|
||||||
|
localSequence.value = currentSequenceOrder
|
||||||
|
.map(id => inputsMap.get(id)) // 获取基础信息
|
||||||
|
.filter((input): input is SequenceDisplayItem => input !== undefined); // 过滤掉无效 ID 并断言类型
|
||||||
|
|
||||||
|
// 构建本地所有项目配置 (localItemConfigs) - 深拷贝
|
||||||
|
// 确保所有 availableInputs 都有一个条目,即使没有快捷键
|
||||||
|
const initialConfigs: Record<string, FocusItemConfig> = {};
|
||||||
|
allAvailableInputs.forEach(input => {
|
||||||
|
initialConfigs[input.id] = { ... (currentItemConfigs[input.id] || {}) }; // 复制 store 中的配置,或创建空对象
|
||||||
|
});
|
||||||
|
localItemConfigs.value = JSON.parse(JSON.stringify(initialConfigs));
|
||||||
|
|
||||||
|
// 存储原始完整配置用于比较
|
||||||
|
originalConfig.value = JSON.parse(JSON.stringify({
|
||||||
|
sequence: currentSequenceOrder,
|
||||||
|
shortcuts: currentItemConfigs
|
||||||
|
}));
|
||||||
|
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
console.log('[FocusSwitcherConfigurator] Dialog opened. Loaded sequence to local copy:', localSequence.value);
|
console.log('[FocusSwitcherConfigurator] Dialog opened. Loaded localSequence:', JSON.stringify(localSequence.value));
|
||||||
console.log('[FocusSwitcherConfigurator] Original sequence stored:', originalSequence.value);
|
console.log('[FocusSwitcherConfigurator] Loaded localItemConfigs:', JSON.stringify(localItemConfigs.value));
|
||||||
|
console.log('[FocusSwitcherConfigurator] Original full config stored:', JSON.stringify(originalConfig.value));
|
||||||
// 重置/计算初始位置和大小
|
// 重置/计算初始位置和大小
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (dialogRef.value) {
|
if (dialogRef.value) {
|
||||||
@@ -76,17 +99,30 @@ watch(() => props.isVisible, (newValue) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 监听本地序列(包括快捷键)变化,标记未保存更改
|
// 监听本地序列(包括快捷键)变化,标记未保存更改
|
||||||
watch(localSequence, (currentLocalSequence) => {
|
// --- 修改:监听 localSequence 和 localItemConfigs 的变化 ---
|
||||||
// 比较当前本地序列和原始序列的 JSON 字符串
|
watch([localSequence, localItemConfigs], ([currentSequence, currentConfigs]) => {
|
||||||
const hasChanged = JSON.stringify(currentLocalSequence) !== JSON.stringify(originalSequence.value);
|
if (!originalConfig.value) return; // 尚未加载完成
|
||||||
if (hasChanged) {
|
|
||||||
// console.log('[FocusSwitcherConfigurator] Local sequence changed.'); // +++ Log: Changed +++
|
// 比较序列顺序
|
||||||
hasChanges.value = true;
|
const sequenceChanged = JSON.stringify(currentSequence.map(item => item.id)) !== JSON.stringify(originalConfig.value.sequence);
|
||||||
} else {
|
|
||||||
// console.log('[FocusSwitcherConfigurator] Local sequence reverted to original.'); // +++ Log: Reverted +++
|
// 比较快捷键配置 (需要过滤掉原始配置中不存在的键,以防初始化时加入)
|
||||||
// 如果序列变回和原来一样,则标记为无更改
|
const currentShortcuts: Record<string, FocusItemConfig> = {};
|
||||||
hasChanges.value = false;
|
for(const id in currentConfigs) {
|
||||||
|
// 只比较原始配置中存在的 ID 或当前序列中的 ID 的快捷键是否有变化
|
||||||
|
if (originalConfig.value.shortcuts[id] !== undefined || currentSequence.some(item => item.id === id)) {
|
||||||
|
currentShortcuts[id] = { shortcut: currentConfigs[id].shortcut };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const originalShortcuts: Record<string, FocusItemConfig> = {};
|
||||||
|
for(const id in originalConfig.value.shortcuts) {
|
||||||
|
originalShortcuts[id] = { shortcut: originalConfig.value.shortcuts[id].shortcut };
|
||||||
|
}
|
||||||
|
const shortcutsChanged = JSON.stringify(currentShortcuts) !== JSON.stringify(originalShortcuts);
|
||||||
|
|
||||||
|
hasChanges.value = sequenceChanged || shortcutsChanged;
|
||||||
|
// console.log(`[FocusSwitcherConfigurator] Changes detected: sequence=${sequenceChanged}, shortcuts=${shortcutsChanged}, hasChanges=${hasChanges.value}`);
|
||||||
|
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
|
|
||||||
@@ -102,27 +138,41 @@ const closeDialog = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveConfiguration = () => {
|
const saveConfiguration = () => {
|
||||||
// 提取仅包含 id 和 shortcut 的配置项数组
|
// 构造 FocusSwitcherFullConfig 对象
|
||||||
const configToSave = localSequence.value.map(item => ({
|
const newSequence = localSequence.value.map(item => item.id);
|
||||||
id: item.id,
|
// 清理 shortcuts,移除没有快捷键的条目 (可选,取决于后端是否需要)
|
||||||
shortcut: item.shortcut || undefined, // 空字符串视为未设置
|
const newShortcuts: Record<string, FocusItemConfig> = {};
|
||||||
}));
|
for (const id in localItemConfigs.value) {
|
||||||
console.log('[FocusSwitcherConfigurator] Saving configuration. Config to save:', configToSave);
|
if (localItemConfigs.value[id]?.shortcut) {
|
||||||
// 调用 Store 中正确的更新函数
|
newShortcuts[id] = { shortcut: localItemConfigs.value[id].shortcut };
|
||||||
focusSwitcherStore.updateConfiguration(configToSave);
|
}
|
||||||
console.log('[FocusSwitcherConfigurator] Configuration save process triggered via updateConfiguration.');
|
// 如果需要保存空快捷键的记录,则取消 if 条件
|
||||||
|
// newShortcuts[id] = { shortcut: localItemConfigs.value[id]?.shortcut };
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullConfigToSave: FocusSwitcherFullConfig = {
|
||||||
|
sequence: newSequence,
|
||||||
|
shortcuts: newShortcuts,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[FocusSwitcherConfigurator] Saving full configuration:', JSON.stringify(fullConfigToSave));
|
||||||
|
focusSwitcherStore.updateConfiguration(fullConfigToSave); // 调用 Store 更新函数
|
||||||
|
console.log('[FocusSwitcherConfigurator] Configuration save process triggered.');
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
emit('close'); // 保存后关闭
|
emit('close'); // 保存后关闭
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Computed ---
|
// --- Computed ---
|
||||||
// 新的计算属性:基于本地已配置列表动态计算可用输入框
|
// ++ 修改:计算属性,获取不在右侧序列中的项目 (用于左侧列表) ++
|
||||||
const localAvailableInputs = computed(() => {
|
const localAvailableInputs = computed(() => {
|
||||||
// 获取本地已配置项的 ID 集合
|
const sequenceIds = new Set(localSequence.value.map(item => item.id));
|
||||||
const configuredIds = new Set(localSequence.value.map(item => item.id));
|
// 从所有可用输入中过滤掉已在序列中的,并合并本地快捷键配置
|
||||||
// 从 store 的 availableInputs state 中过滤掉已在本地配置的项
|
return focusSwitcherStore.availableInputs
|
||||||
// 注意:直接访问 store 的 state ref
|
.filter(input => !sequenceIds.has(input.id))
|
||||||
return focusSwitcherStore.availableInputs.filter(input => !configuredIds.has(input.id));
|
.map(input => ({
|
||||||
|
...input,
|
||||||
|
shortcut: localItemConfigs.value[input.id]?.shortcut // 从本地配置获取快捷键
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注意:已配置的列表直接使用 localSequence ref
|
// 注意:已配置的列表直接使用 localSequence ref
|
||||||
@@ -149,15 +199,23 @@ const localAvailableInputs = computed(() => {
|
|||||||
:group="{ name: 'focus-inputs', pull: true, put: false }"
|
:group="{ name: 'focus-inputs', pull: true, put: false }"
|
||||||
:sort="false"
|
:sort="false"
|
||||||
>
|
>
|
||||||
<template #item="{ element }: { element: FocusableInput }">
|
<template #item="{ element }: { element: FocusableInput & FocusItemConfig }">
|
||||||
<li class="draggable-item">
|
<li class="draggable-item">
|
||||||
<i class="fas fa-grip-vertical drag-handle"></i>
|
<i class="fas fa-grip-vertical drag-handle"></i>
|
||||||
<span class="item-label">{{ element.label }}</span>
|
<span class="item-label">{{ element.label }}</span>
|
||||||
|
<!-- ++ 在左侧列表添加快捷键输入框 ++ -->
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="localItemConfigs[element.id].shortcut"
|
||||||
|
class="shortcut-input"
|
||||||
|
:placeholder="t('focusSwitcher.shortcutPlaceholder')"
|
||||||
|
@keydown.prevent="captureShortcut($event, localItemConfigs[element.id])"
|
||||||
|
/> <!-- Correctly close the input tag -->
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<li v-if="localAvailableInputs.length === 0" class="no-items-placeholder"> <!-- 判断条件也更新 -->
|
<li v-if="localAvailableInputs.length === 0" class="no-items-placeholder">
|
||||||
{{ t('focusSwitcher.allInputsConfigured', '所有输入框都已配置') }}
|
<span>{{ t('focusSwitcher.allInputsConfigured', '所有输入框都已配置') }}</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
@@ -173,23 +231,11 @@ const localAvailableInputs = computed(() => {
|
|||||||
:group="{ name: 'focus-inputs', put: true }"
|
:group="{ name: 'focus-inputs', put: true }"
|
||||||
handle=".drag-handle"
|
handle=".drag-handle"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }: { element: ConfiguredFocusableInput, index: number }">
|
<template #item="{ element, index }: { element: SequenceDisplayItem, index: number }">
|
||||||
<div> <!-- Wrap the content in a single div -->
|
<li class="draggable-item">
|
||||||
<li class="draggable-item">
|
<i class="fas fa-grip-vertical drag-handle"></i>
|
||||||
<i class="fas fa-grip-vertical drag-handle"></i>
|
<span class="item-label">{{ element.label }}</span>
|
||||||
<span class="item-label">{{ element.label }}</span>
|
</li>
|
||||||
<!-- +++ 添加快捷键输入框 +++ -->
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
v-model="element.shortcut"
|
|
||||||
class="shortcut-input"
|
|
||||||
:placeholder="t('focusSwitcher.shortcutPlaceholder')"
|
|
||||||
@keydown.prevent="captureShortcut($event, element)"
|
|
||||||
/>
|
|
||||||
<!-- 添加移除按钮 -->
|
|
||||||
<button @click="localSequence.splice(index, 1)" class="remove-button" :title="t('common.remove', '移除')">×</button>
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<li v-if="localSequence.length === 0" class="no-items-placeholder">
|
<li v-if="localSequence.length === 0" class="no-items-placeholder">
|
||||||
@@ -214,8 +260,8 @@ const localAvailableInputs = computed(() => {
|
|||||||
// +++ 在 <script setup> 之外定义辅助函数(如果需要更复杂的逻辑或重用)+++
|
// +++ 在 <script setup> 之外定义辅助函数(如果需要更复杂的逻辑或重用)+++
|
||||||
// 或者直接在 setup 内部定义 captureShortcut
|
// 或者直接在 setup 内部定义 captureShortcut
|
||||||
|
|
||||||
// 内部定义 captureShortcut
|
// ++ 修改:captureShortcut 现在接收 FocusItemConfig 并更新它 ++
|
||||||
const captureShortcut = (event: KeyboardEvent, element: ConfiguredFocusableInput) => { // +++ 使用导入的接口 +++
|
const captureShortcut = (event: KeyboardEvent, itemConfig: FocusItemConfig) => {
|
||||||
if (event.key === 'Alt' || event.key === 'Control' || event.key === 'Shift' || event.key === 'Meta') {
|
if (event.key === 'Alt' || event.key === 'Control' || event.key === 'Shift' || event.key === 'Meta') {
|
||||||
// 忽略单独的修饰键按下
|
// 忽略单独的修饰键按下
|
||||||
return;
|
return;
|
||||||
@@ -229,16 +275,16 @@ const captureShortcut = (event: KeyboardEvent, element: ConfiguredFocusableInput
|
|||||||
}
|
}
|
||||||
// 可以添加更多验证,例如只允许字母、数字等
|
// 可以添加更多验证,例如只允许字母、数字等
|
||||||
if (/^[a-zA-Z0-9]$/.test(key)) { // 简化:只允许单个字母或数字
|
if (/^[a-zA-Z0-9]$/.test(key)) { // 简化:只允许单个字母或数字
|
||||||
element.shortcut = `Alt+${key}`;
|
itemConfig.shortcut = `Alt+${key}`; // 直接修改传入的配置对象
|
||||||
} else if (key === 'Backspace' || key === 'Delete') {
|
} else if (key === 'Backspace' || key === 'Delete') {
|
||||||
element.shortcut = ''; // 允许使用 Backspace 或 Delete 清空
|
itemConfig.shortcut = undefined; // 使用 undefined 清空
|
||||||
} else {
|
} else {
|
||||||
// 可选:提示不支持的键
|
// 可选:提示不支持的键
|
||||||
console.warn(`[FocusSwitcherConfigurator] Unsupported key for shortcut: ${key}`);
|
console.warn(`[FocusSwitcherConfigurator] Unsupported key for shortcut: ${key}`);
|
||||||
}
|
}
|
||||||
} else if (event.key === 'Backspace' || event.key === 'Delete') {
|
} else if (event.key === 'Backspace' || event.key === 'Delete') {
|
||||||
// 允许单独按 Backspace 或 Delete 清空 (即使没有 Alt)
|
// 允许单独按 Backspace 或 Delete 清空 (即使没有 Alt)
|
||||||
element.shortcut = '';
|
itemConfig.shortcut = undefined; // 使用 undefined 清空
|
||||||
} else {
|
} else {
|
||||||
// 可选:如果按下非 Alt 组合键,可以清空或提示
|
// 可选:如果按下非 Alt 组合键,可以清空或提示
|
||||||
// console.log('[FocusSwitcherConfigurator] Invalid shortcut combination.');
|
// console.log('[FocusSwitcherConfigurator] Invalid shortcut combination.');
|
||||||
@@ -385,4 +431,4 @@ h3 {
|
|||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
.button-secondary:hover { background-color: var(--secondary-button-hover-bg-color, #dee2e6); }
|
.button-secondary:hover { background-color: var(--secondary-button-hover-bg-color, #dee2e6); }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,33 +2,35 @@ import { defineStore } from 'pinia';
|
|||||||
import { ref, computed, nextTick } from 'vue';
|
import { ref, computed, nextTick } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
// 假设有一个 API 客户端或辅助函数,这里我们直接使用 fetch
|
// 假设有一个 API 客户端或辅助函数,这里我们直接使用 fetch
|
||||||
// import apiClient from '@/services/api'; // 理想情况下
|
// import apiClient from '@/services/api';
|
||||||
|
|
||||||
// 定义输入框的接口
|
// 基础输入框接口 (保持不变)
|
||||||
export interface FocusableInput {
|
export interface FocusableInput {
|
||||||
id: string; // 唯一标识符
|
|
||||||
label: string; // 用户友好的名称
|
|
||||||
// componentPath 和 selector 不再需要,聚焦完全依赖 focusAction
|
|
||||||
focusAction: () => boolean | Promise<boolean>;
|
|
||||||
// shortcut?: string; // --- 从基础接口移除快捷键,因为它只在配置中相关 ---
|
|
||||||
}
|
|
||||||
|
|
||||||
// +++ 新增:定义包含快捷键的完整配置项接口 (用于 Store 内部和配置器) +++
|
|
||||||
export interface ConfiguredFocusableInput extends FocusableInput {
|
|
||||||
shortcut?: string; // 快捷键是可选的
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义存储在后端的配置项接口 (仅 ID 和 shortcut)
|
|
||||||
export interface ConfigurableFocusableItem {
|
|
||||||
id: string;
|
id: string;
|
||||||
shortcut?: string; // 快捷键是可选的
|
label: string;
|
||||||
|
focusAction: () => boolean | Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义 Store 的 State 接口 (可选但推荐)
|
// --- 移除 ConfiguredFocusableInput ---
|
||||||
|
|
||||||
|
// 单个项目配置接口 (主要用于快捷键)
|
||||||
|
export interface FocusItemConfig {
|
||||||
|
shortcut?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储在后端或发送给后端的完整配置结构
|
||||||
|
export interface FocusSwitcherFullConfig {
|
||||||
|
sequence: string[]; // 顺序切换的 ID 列表
|
||||||
|
shortcuts: Record<string, FocusItemConfig>; // 所有项目的快捷键配置 (id -> config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 移除 ConfigurableFocusableItem ---
|
||||||
|
|
||||||
|
// Store State 接口
|
||||||
interface FocusSwitcherState {
|
interface FocusSwitcherState {
|
||||||
availableInputs: FocusableInput[]; // 保持不变,定义所有可用项及其基础信息
|
availableInputs: FocusableInput[]; // 所有可用项的基础信息
|
||||||
// configuredSequence: string[]; // --- 移除旧的状态 ---
|
sequenceOrder: string[]; // 顺序切换的 ID 列表
|
||||||
configuredItems: ConfigurableFocusableItem[]; // +++ 新增:存储已配置项的 ID 和快捷键 +++
|
itemConfigs: Record<string, FocusItemConfig>; // 所有项目的配置 (id -> config)
|
||||||
isConfiguratorVisible: boolean;
|
isConfiguratorVisible: boolean;
|
||||||
activateFileManagerSearchTrigger: number;
|
activateFileManagerSearchTrigger: number;
|
||||||
activateTerminalSearchTrigger: number;
|
activateTerminalSearchTrigger: number;
|
||||||
@@ -51,8 +53,8 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
{ id: 'connectionListSearch', label: t('focusSwitcher.input.connectionListSearch', '连接列表搜索'), focusAction: () => false },
|
{ id: 'connectionListSearch', label: t('focusSwitcher.input.connectionListSearch', '连接列表搜索'), focusAction: () => false },
|
||||||
{ id: 'fileEditorActive', label: t('focusSwitcher.input.fileEditorActive', '文件编辑器'), focusAction: () => false },
|
{ id: 'fileEditorActive', label: t('focusSwitcher.input.fileEditorActive', '文件编辑器'), focusAction: () => false },
|
||||||
]);
|
]);
|
||||||
// const configuredSequence = ref<string[]>([]); // --- 移除旧的状态 ref ---
|
const sequenceOrder = ref<string[]>([]); // +++ 新增:存储顺序 +++
|
||||||
const configuredItems = ref<ConfigurableFocusableItem[]>([]); // +++ 新增:存储配置项的状态 ref +++
|
const itemConfigs = ref<Record<string, FocusItemConfig>>({}); // +++ 新增:存储所有配置 +++
|
||||||
const isConfiguratorVisible = ref(false);
|
const isConfiguratorVisible = ref(false);
|
||||||
const activateFileManagerSearchTrigger = ref(0);
|
const activateFileManagerSearchTrigger = ref(0);
|
||||||
const activateTerminalSearchTrigger = ref(0);
|
const activateTerminalSearchTrigger = ref(0);
|
||||||
@@ -63,9 +65,9 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
// --- Actions ---
|
// --- Actions ---
|
||||||
|
|
||||||
// +++ 修改:从后端加载配置(包括快捷键) +++
|
// +++ 修改:从后端加载配置(包括快捷键) +++
|
||||||
async function loadConfigurationFromBackend() { // 重命名以反映加载的是完整配置
|
async function loadConfigurationFromBackend() {
|
||||||
const apiUrl = '/api/v1/settings/focus-switcher-sequence'; // 假设 API 端点不变
|
const apiUrl = '/api/v1/settings/focus-switcher-sequence'; // 假设 API 端点不变,但返回结构改变
|
||||||
console.log(`[FocusSwitcherStore] Attempting to load configuration (sequence & shortcuts) from backend via: ${apiUrl}`);
|
console.log(`[FocusSwitcherStore] Attempting to load full configuration (sequence & shortcuts) from backend via: ${apiUrl}`);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(apiUrl);
|
const response = await fetch(apiUrl);
|
||||||
console.log(`[FocusSwitcherStore] Received response from ${apiUrl}. Status: ${response.status}`);
|
console.log(`[FocusSwitcherStore] Received response from ${apiUrl}. Status: ${response.status}`);
|
||||||
@@ -75,68 +77,70 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// *** 假设后端返回 ConfigurableFocusableItem[] 结构 ***
|
// *** 假设后端返回 FocusSwitcherFullConfig 结构 ***
|
||||||
const loadedConfig = await response.json();
|
const loadedFullConfig: FocusSwitcherFullConfig = await response.json();
|
||||||
console.log(`[FocusSwitcherStore] Raw JSON received from backend:`, JSON.stringify(loadedConfig));
|
console.log(`[FocusSwitcherStore] Raw JSON received from backend:`, JSON.stringify(loadedFullConfig));
|
||||||
|
|
||||||
// --- 验证和过滤 ---
|
// --- 验证和设置 ---
|
||||||
if (Array.isArray(loadedConfig) && loadedConfig.every(item => typeof item?.id === 'string')) {
|
const availableIds = new Set(availableInputs.value.map(input => input.id));
|
||||||
console.log('[FocusSwitcherStore] Configuration format seems valid (array of objects with id). Filtering against available inputs...');
|
|
||||||
const availableIds = new Set(availableInputs.value.map(input => input.id));
|
|
||||||
const filteredConfig: ConfigurableFocusableItem[] = loadedConfig
|
|
||||||
.filter((item: any) => {
|
|
||||||
const isValid = availableIds.has(item.id);
|
|
||||||
if (!isValid) {
|
|
||||||
console.warn(`[FocusSwitcherStore] Filtered out invalid ID during load: ${item.id}`);
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
})
|
|
||||||
.map((item: any) => ({ // 确保只保留 id 和 shortcut
|
|
||||||
id: item.id,
|
|
||||||
shortcut: typeof item.shortcut === 'string' && item.shortcut.startsWith('Alt+') ? item.shortcut : undefined // 验证快捷键格式
|
|
||||||
}));
|
|
||||||
|
|
||||||
configuredItems.value = filteredConfig;
|
// 验证 sequence
|
||||||
console.log('[FocusSwitcherStore] Successfully loaded and set configuredItems:', JSON.stringify(configuredItems.value));
|
if (Array.isArray(loadedFullConfig?.sequence) && loadedFullConfig.sequence.every(id => typeof id === 'string' && availableIds.has(id))) {
|
||||||
|
sequenceOrder.value = loadedFullConfig.sequence;
|
||||||
|
console.log('[FocusSwitcherStore] Successfully loaded and set sequenceOrder:', JSON.stringify(sequenceOrder.value));
|
||||||
} else {
|
} else {
|
||||||
// --- 处理旧格式 (仅 ID 数组) 或无效格式 ---
|
console.warn('[FocusSwitcherStore] Invalid or missing sequence in loaded config. Resetting to empty array.');
|
||||||
if (Array.isArray(loadedConfig) && loadedConfig.every(item => typeof item === 'string')) {
|
sequenceOrder.value = [];
|
||||||
console.warn('[FocusSwitcherStore] Received old format (string array) from backend. Converting to new structure without shortcuts.');
|
|
||||||
const availableIds = new Set(availableInputs.value.map(input => input.id));
|
|
||||||
const filteredSequence = loadedConfig.filter((id: string) => availableIds.has(id));
|
|
||||||
configuredItems.value = filteredSequence.map(id => ({ id })); // 转换为新结构,无快捷键
|
|
||||||
console.log('[FocusSwitcherStore] Converted old format to configuredItems:', JSON.stringify(configuredItems.value));
|
|
||||||
// 可以考虑触发一次保存,将转换后的新格式存回后端
|
|
||||||
// saveSequenceToBackend();
|
|
||||||
} else {
|
|
||||||
console.error('[FocusSwitcherStore] Invalid configuration format received from backend:', loadedConfig);
|
|
||||||
configuredItems.value = []; // 使用空数组作为回退
|
|
||||||
console.log('[FocusSwitcherStore] Set configuredItems to empty array due to invalid format.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证 shortcuts (itemConfigs)
|
||||||
|
if (typeof loadedFullConfig?.shortcuts === 'object' && loadedFullConfig.shortcuts !== null) {
|
||||||
|
const validConfigs: Record<string, FocusItemConfig> = {};
|
||||||
|
for (const id in loadedFullConfig.shortcuts) {
|
||||||
|
if (availableIds.has(id)) { // 只保留有效的 ID
|
||||||
|
const config = loadedFullConfig.shortcuts[id];
|
||||||
|
if (typeof config === 'object' && config !== null && (config.shortcut === undefined || (typeof config.shortcut === 'string' && config.shortcut.startsWith('Alt+')))) {
|
||||||
|
validConfigs[id] = { shortcut: config.shortcut }; // 只保留 shortcut
|
||||||
|
} else {
|
||||||
|
console.warn(`[FocusSwitcherStore] Invalid shortcut config for ID ${id}. Ignoring shortcut.`);
|
||||||
|
validConfigs[id] = {}; // 保留 ID 但清空无效快捷键
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`[FocusSwitcherStore] Ignoring shortcut config for unknown ID: ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemConfigs.value = validConfigs;
|
||||||
|
console.log('[FocusSwitcherStore] Successfully loaded and set itemConfigs:', JSON.stringify(itemConfigs.value));
|
||||||
|
} else {
|
||||||
|
console.warn('[FocusSwitcherStore] Invalid or missing shortcuts in loaded config. Resetting to empty object.');
|
||||||
|
itemConfigs.value = {};
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[FocusSwitcherStore] Failed to load or parse configuration from backend (${apiUrl}):`, error);
|
console.error(`[FocusSwitcherStore] Failed to load or parse configuration from backend (${apiUrl}):`, error);
|
||||||
configuredItems.value = [];
|
sequenceOrder.value = [];
|
||||||
console.log('[FocusSwitcherStore] Set configuredItems to empty array due to loading error.');
|
itemConfigs.value = {};
|
||||||
|
console.log('[FocusSwitcherStore] Reset sequenceOrder and itemConfigs due to loading error.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +++ 修改:保存配置到后端(包括快捷键) +++
|
async function saveConfigurationToBackend() {
|
||||||
async function saveConfigurationToBackend() { // 重命名以反映保存的是完整配置
|
const apiUrl = '/api/v1/settings/focus-switcher-sequence'; // 假设 API 端点不变,但接受结构改变
|
||||||
const apiUrl = '/api/v1/settings/focus-switcher-sequence'; // 假设 API 端点不变
|
console.log(`[FocusSwitcherStore] Attempting to save full configuration (sequence & shortcuts) to backend via PUT: ${apiUrl}`);
|
||||||
console.log(`[FocusSwitcherStore] Attempting to save configuration (sequence & shortcuts) to backend via PUT: ${apiUrl}`);
|
|
||||||
try {
|
try {
|
||||||
// *** 发送 configuredItems 结构到后端 (包含快捷键) ***
|
// *** 构造 FocusSwitcherFullConfig 结构发送给后端 ***
|
||||||
// !!! 注意:这需要后端 API 支持接收此结构 !!!
|
const configToSave: FocusSwitcherFullConfig = {
|
||||||
const configToSave = configuredItems.value;
|
sequence: sequenceOrder.value,
|
||||||
console.log('[FocusSwitcherStore] Configuration data to save (including shortcuts):', JSON.stringify(configToSave));
|
shortcuts: itemConfigs.value,
|
||||||
|
};
|
||||||
|
console.log('[FocusSwitcherStore] Full configuration data to save:', JSON.stringify(configToSave));
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
// Auth headers if needed
|
// Auth headers if needed
|
||||||
},
|
},
|
||||||
body: JSON.stringify(configToSave), // *** 发送完整的配置对象数组 ***
|
body: JSON.stringify(configToSave), // *** 发送包含 sequence 和 shortcuts 的对象 ***
|
||||||
});
|
});
|
||||||
console.log(`[FocusSwitcherStore] Received response from PUT ${apiUrl}. Status: ${response.status}`);
|
console.log(`[FocusSwitcherStore] Received response from PUT ${apiUrl}. Status: ${response.status}`);
|
||||||
|
|
||||||
@@ -184,23 +188,40 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// +++ 修改:更新配置(包括顺序和快捷键) +++
|
// +++ 修改:更新完整配置(包括顺序和所有快捷键) +++
|
||||||
function updateConfiguration(newConfig: ConfigurableFocusableItem[]) { // 重命名并修改参数类型
|
function updateConfiguration(newFullConfig: FocusSwitcherFullConfig) {
|
||||||
console.log('[FocusSwitcherStore] updateConfiguration called with new configuration:', JSON.stringify(newConfig));
|
console.log('[FocusSwitcherStore] updateConfiguration called with new full configuration:', JSON.stringify(newFullConfig));
|
||||||
const availableIds = new Set(availableInputs.value.map(input => input.id));
|
const availableIds = new Set(availableInputs.value.map(input => input.id));
|
||||||
|
|
||||||
// 过滤掉无效的 ID,并验证/清理快捷键
|
// 更新 sequenceOrder (过滤无效 ID)
|
||||||
const filteredConfig = newConfig
|
if (Array.isArray(newFullConfig?.sequence)) {
|
||||||
.filter(item => availableIds.has(item.id))
|
sequenceOrder.value = newFullConfig.sequence.filter(id => availableIds.has(id));
|
||||||
.map(item => ({
|
console.log('[FocusSwitcherStore] sequenceOrder updated locally to:', JSON.stringify(sequenceOrder.value));
|
||||||
id: item.id,
|
} else {
|
||||||
shortcut: typeof item.shortcut === 'string' && item.shortcut.startsWith('Alt+') ? item.shortcut : undefined
|
console.warn('[FocusSwitcherStore] Invalid sequence provided in updateConfiguration. Keeping existing sequence.');
|
||||||
}));
|
}
|
||||||
|
|
||||||
|
// 更新 itemConfigs (过滤无效 ID 和快捷键)
|
||||||
|
if (typeof newFullConfig?.shortcuts === 'object' && newFullConfig.shortcuts !== null) {
|
||||||
|
const validConfigs: Record<string, FocusItemConfig> = {};
|
||||||
|
for (const id in newFullConfig.shortcuts) {
|
||||||
|
if (availableIds.has(id)) {
|
||||||
|
const config = newFullConfig.shortcuts[id];
|
||||||
|
if (typeof config === 'object' && config !== null && (config.shortcut === undefined || (typeof config.shortcut === 'string' && config.shortcut.startsWith('Alt+')))) {
|
||||||
|
validConfigs[id] = { shortcut: config.shortcut };
|
||||||
|
} else {
|
||||||
|
validConfigs[id] = {}; // 保留 ID 但清空无效快捷键
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemConfigs.value = validConfigs;
|
||||||
|
console.log('[FocusSwitcherStore] itemConfigs updated locally to:', JSON.stringify(itemConfigs.value));
|
||||||
|
} else {
|
||||||
|
console.warn('[FocusSwitcherStore] Invalid shortcuts provided in updateConfiguration. Keeping existing configs.');
|
||||||
|
}
|
||||||
|
|
||||||
configuredItems.value = filteredConfig;
|
|
||||||
console.log('[FocusSwitcherStore] configuredItems updated locally to:', JSON.stringify(configuredItems.value));
|
|
||||||
// 更新后立即保存到后端
|
// 更新后立即保存到后端
|
||||||
saveConfigurationToBackend(); // 调用重命名后的保存函数
|
saveConfigurationToBackend();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册聚焦动作 (现在更新 availableInputs 中的 focusAction)
|
// 注册聚焦动作 (现在更新 availableInputs 中的 focusAction)
|
||||||
@@ -257,49 +278,75 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
// --- 修改 Getters ---
|
// --- 修改 Getters ---
|
||||||
// +++ 修改:获取完整的已配置输入框信息(合并快捷键)+++
|
// +++ 修改:获取完整的已配置输入框信息(合并快捷键)+++
|
||||||
// 返回类型现在包含 shortcut,所以需要调整或确认 FocusableInput 定义
|
// 返回类型现在包含 shortcut,所以需要调整或确认 FocusableInput 定义
|
||||||
const getConfiguredInputs = computed((): ConfiguredFocusableInput[] => { // +++ 更新返回类型 +++
|
// +++ 修改:获取在序列中的输入框信息(包含快捷键)+++
|
||||||
|
const getSequenceInputs = computed((): (FocusableInput & FocusItemConfig)[] => {
|
||||||
const inputsMap = new Map(availableInputs.value.map(input => [input.id, input]));
|
const inputsMap = new Map(availableInputs.value.map(input => [input.id, input]));
|
||||||
return configuredItems.value
|
const configs = itemConfigs.value;
|
||||||
.map(item => {
|
// Step 1: Map sequenceOrder to potential objects or undefined
|
||||||
const baseInput = inputsMap.get(item.id);
|
const mappedInputs = sequenceOrder.value
|
||||||
|
.map(id => {
|
||||||
|
const baseInput = inputsMap.get(id);
|
||||||
if (!baseInput) return undefined;
|
if (!baseInput) return undefined;
|
||||||
// 创建一个新对象,类型为 ConfiguredFocusableInput
|
const config = configs[id] || {};
|
||||||
const configuredInput: ConfiguredFocusableInput = { // +++ 使用新类型 +++
|
// ++ Explicitly create object with the intersection type ++
|
||||||
|
const combinedInput: FocusableInput & FocusItemConfig = {
|
||||||
...baseInput,
|
...baseInput,
|
||||||
shortcut: item.shortcut,
|
shortcut: config.shortcut,
|
||||||
};
|
};
|
||||||
return configuredInput;
|
return combinedInput; // Return the correctly typed object
|
||||||
})
|
});
|
||||||
.filter((input): input is ConfiguredFocusableInput => input !== undefined); // +++ 更新类型断言 +++
|
|
||||||
|
// Step 2: Filter out any undefined values using the type predicate
|
||||||
|
const filteredInputs = mappedInputs.filter(
|
||||||
|
(input): input is FocusableInput & FocusItemConfig => input !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
return filteredInputs; // Return the correctly typed array
|
||||||
});
|
});
|
||||||
|
|
||||||
// +++ 修改:获取配置器中“可用”的输入框列表 +++
|
// +++ 修改:获取配置器中“可用”的输入框列表 +++
|
||||||
const getAvailableInputsForConfigurator = computed((): FocusableInput[] => {
|
// +++ 修改:获取不在序列中的输入框信息(也包含快捷键,因为现在全局管理) +++
|
||||||
const configuredIds = new Set(configuredItems.value.map(item => item.id));
|
const getAvailableInputsForConfigurator = computed((): (FocusableInput & FocusItemConfig)[] => {
|
||||||
// 返回尚未配置的基础输入框信息(不需要快捷键)
|
const sequenceIds = new Set(sequenceOrder.value);
|
||||||
return availableInputs.value.filter(input => !configuredIds.has(input.id));
|
const configs = itemConfigs.value;
|
||||||
|
return availableInputs.value
|
||||||
|
.filter(input => !sequenceIds.has(input.id)) // 过滤掉已在序列中的
|
||||||
|
.map(input => {
|
||||||
|
const config = configs[input.id] || {};
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
shortcut: config.shortcut, // 合并快捷键
|
||||||
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// +++ 修改:获取序列中的下一个聚焦目标 ID +++
|
// +++ 修改:获取序列中的下一个聚焦目标 ID +++
|
||||||
|
// +++ 修改:根据 sequenceOrder 获取下一个聚焦目标 ID +++
|
||||||
function getNextFocusTargetId(currentFocusedId: string | null): string | null {
|
function getNextFocusTargetId(currentFocusedId: string | null): string | null {
|
||||||
if (configuredItems.value.length === 0) {
|
const order = sequenceOrder.value;
|
||||||
|
if (order.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (currentFocusedId === null) {
|
if (currentFocusedId === null) {
|
||||||
return configuredItems.value[0].id; // 返回第一个配置项的 ID
|
return order[0]; // 返回序列中的第一个 ID
|
||||||
}
|
}
|
||||||
const currentIndex = configuredItems.value.findIndex(item => item.id === currentFocusedId);
|
const currentIndex = order.findIndex(id => id === currentFocusedId);
|
||||||
if (currentIndex === -1) {
|
if (currentIndex === -1) {
|
||||||
return configuredItems.value[0].id; // 如果当前 ID 不在配置中,返回第一个
|
return order[0]; // 如果当前 ID 不在序列中,返回第一个
|
||||||
}
|
}
|
||||||
const nextIndex = (currentIndex + 1) % configuredItems.value.length;
|
const nextIndex = (currentIndex + 1) % order.length;
|
||||||
return configuredItems.value[nextIndex].id; // 返回下一个配置项的 ID
|
return order[nextIndex]; // 返回序列中的下一个 ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// +++ 新增:根据快捷键获取目标 ID +++
|
// +++ 新增:根据快捷键获取目标 ID +++
|
||||||
|
// +++ 修改:根据 itemConfigs 获取快捷键对应的目标 ID +++
|
||||||
function getFocusTargetIdByShortcut(shortcut: string): string | null {
|
function getFocusTargetIdByShortcut(shortcut: string): string | null {
|
||||||
const foundItem = configuredItems.value.find(item => item.shortcut === shortcut);
|
for (const id in itemConfigs.value) {
|
||||||
return foundItem?.id ?? null;
|
if (itemConfigs.value[id]?.shortcut === shortcut) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -314,8 +361,8 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
availableInputs,
|
availableInputs,
|
||||||
// configuredSequence, // --- 移除 ---
|
sequenceOrder, // +++ 暴露新状态 +++
|
||||||
configuredItems, // +++ 暴露新状态 +++
|
itemConfigs, // +++ 暴露新状态 +++
|
||||||
isConfiguratorVisible,
|
isConfiguratorVisible,
|
||||||
activateFileManagerSearchTrigger,
|
activateFileManagerSearchTrigger,
|
||||||
activateTerminalSearchTrigger,
|
activateTerminalSearchTrigger,
|
||||||
@@ -323,14 +370,14 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
|||||||
toggleConfigurator,
|
toggleConfigurator,
|
||||||
triggerFileManagerSearchActivation,
|
triggerFileManagerSearchActivation,
|
||||||
triggerTerminalSearchActivation,
|
triggerTerminalSearchActivation,
|
||||||
loadConfigurationFromBackend, // +++ 使用新名称 +++
|
loadConfigurationFromBackend,
|
||||||
saveConfigurationToBackend, // +++ 使用新名称 +++
|
saveConfigurationToBackend,
|
||||||
updateConfiguration, // +++ 使用新名称 +++
|
updateConfiguration, // 已修改为接收完整配置
|
||||||
// Getters / Methods
|
// Getters / Methods
|
||||||
getConfiguredInputs, // 已修改
|
getSequenceInputs, // +++ 重命名并修改 +++
|
||||||
getAvailableInputsForConfigurator, // 已修改
|
getAvailableInputsForConfigurator, // 已修改
|
||||||
getNextFocusTargetId, // 已修改
|
getNextFocusTargetId, // 已修改
|
||||||
getFocusTargetIdByShortcut, // +++ 新增 +++
|
getFocusTargetIdByShortcut, // 已修改
|
||||||
registerFocusAction,
|
registerFocusAction,
|
||||||
unregisterFocusAction,
|
unregisterFocusAction,
|
||||||
focusTarget,
|
focusTarget,
|
||||||
|
|||||||
Reference in New Issue
Block a user