diff --git a/packages/frontend/src/App.vue b/packages/frontend/src/App.vue
index 1972d69..bfcec80 100644
--- a/packages/frontend/src/App.vue
+++ b/packages/frontend/src/App.vue
@@ -90,6 +90,9 @@ const handleGlobalKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Alt' && !event.ctrlKey && !event.shiftKey && !event.metaKey) {
event.preventDefault(); // 阻止 Alt 键的默认行为 (例如激活菜单栏)
+ // +++ Log: 打印当前的配置序列 +++
+ console.log('[App] Current configured sequence in store:', JSON.stringify(focusSwitcherStore.configuredSequence));
+
const activeElement = document.activeElement as HTMLElement;
let currentFocusId: string | null = null;
@@ -100,29 +103,98 @@ const handleGlobalKeyDown = (event: KeyboardEvent) => {
console.log(`[App] Alt pressed. Current focus ID: ${currentFocusId}`);
- // 从 Store 获取下一个目标 ID
- const nextFocusId = focusSwitcherStore.getNextFocusTargetId(currentFocusId);
- console.log(`[App] Next focus target ID from store: ${nextFocusId}`);
+ // --- 新的查找逻辑 ---
+ const sequence = focusSwitcherStore.configuredSequence; // 获取完整的配置顺序
+ if (sequence.length === 0) {
+ console.log('[App] No focus sequence configured.');
+ return; // 没有配置,直接返回
+ }
- if (nextFocusId) {
- // 尝试查找下一个目标元素
- // 使用 requestAnimationFrame 确保在 DOM 更新后查找
- requestAnimationFrame(() => {
- const nextElement = document.querySelector(`[data-focus-id="${nextFocusId}"]`) as HTMLElement | null;
+ let startIndex = 0;
+ if (currentFocusId) {
+ const currentIndex = sequence.indexOf(currentFocusId);
+ if (currentIndex !== -1) {
+ startIndex = (currentIndex + 1) % sequence.length; // 从当前焦点的下一个开始查找
+ } else {
+ console.log(`[App] Current focus ID ${currentFocusId} not found in sequence, starting search from beginning.`);
+ }
+ } else {
+ console.log('[App] No current focus ID found, starting search from beginning.');
+ }
- if (nextElement && isElementVisibleAndFocusable(nextElement)) {
- console.log(`[App] Focusing next element:`, nextElement);
- nextElement.focus();
- // 如果是输入框,可能需要选中内容
- if (nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement) {
- nextElement.select();
- }
- } else {
- console.log(`[App] Next element with ID ${nextFocusId} not found or not focusable.`);
- // 可选:如果找不到或不可聚焦,可以尝试查找配置列表中的再下一个,或者直接忽略
- // 这里暂时忽略,避免无限循环
+
+ // 循环查找下一个可聚焦的目标 (最多循环一次完整的序列)
+ let foundFocusable = false;
+ for (let i = 0; i < sequence.length; i++) {
+ const nextIndex = (startIndex + i) % sequence.length;
+ const nextFocusId = sequence[nextIndex];
+ console.log(`[App] Trying to find element with ID: ${nextFocusId}`);
+
+ const nextElement = document.querySelector(`[data-focus-id="${nextFocusId}"]`) as HTMLElement | null;
+
+ if (nextElement && isElementVisibleAndFocusable(nextElement)) {
+ // --- 目标元素找到且可聚焦 ---
+ console.log(`[App] Found focusable element:`, nextElement);
+ nextElement.focus();
+ if (nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement) {
+ nextElement.select();
}
- });
+ foundFocusable = true;
+ break; // 找到并聚焦,跳出循环
+
+ } else if (nextFocusId === 'fileManagerSearch' || nextFocusId === 'terminalSearch') {
+ // --- 特殊处理:目标是文件管理器或终端搜索框 ---
+ const targetElement = document.querySelector(`[data-focus-id="${nextFocusId}"]`) as HTMLElement | null; // 先尝试查找
+
+ if (!targetElement || !isElementVisibleAndFocusable(targetElement)) {
+ // --- 如果元素不存在或不可聚焦,尝试激活 ---
+ console.log(`[App] Target ${nextFocusId} not found or not focusable. Triggering activation via store...`);
+ if (nextFocusId === 'fileManagerSearch') {
+ focusSwitcherStore.triggerFileManagerSearchActivation();
+ } else { // terminalSearch
+ focusSwitcherStore.triggerTerminalSearchActivation();
+ }
+ // --- 关键:触发激活后,不设置 foundFocusable,也不 break,让循环继续查找下一个 ---
+ console.log(`[App] Activation triggered for ${nextFocusId}. Continuing search...`);
+ } else {
+ // --- 如果元素存在且可聚焦 (理论上不应该进入这里,因为前面的 if 会处理,但作为防御性代码保留) ---
+ console.log(`[App] Found focusable element after all:`, targetElement);
+ targetElement.focus();
+ if (targetElement instanceof HTMLInputElement || targetElement instanceof HTMLTextAreaElement) {
+ targetElement.select();
+ }
+ foundFocusable = true;
+ break;
+ }
+
+
+ // --- 旧的逻辑移除 ---
+ /*
+ // 使用 setTimeout 等待 DOM 更新后再尝试聚焦
+ setTimeout(() => {
+ const targetElement = document.querySelector(`[data-focus-id="${nextFocusId}"]`) as HTMLElement | null;
+ if (targetElement && isElementVisibleAndFocusable(targetElement)) {
+ console.log(`[App] Focusing ${nextFocusId} after activation attempt.`);
+ targetElement.focus();
+ if (targetElement instanceof HTMLInputElement || targetElement instanceof HTMLTextAreaElement) {
+ targetElement.select();
+ }
+ } else {
+ console.warn(`[App] Failed to focus ${nextFocusId} even after activation attempt.`);
+ }
+ }, 150); // 稍微增加延迟,确保组件有足够时间响应和渲染
+
+ foundFocusable = true; // 无论是否成功聚焦,都认为这个目标已被尝试处理
+ break; // 处理完文件管理器,跳出循环
+ */
+ } else {
+ // --- 其他元素未找到或不可聚焦 ---
+ console.log(`[App] Element with ID ${nextFocusId} not found or not focusable. Skipping.`);
+ }
+ }
+
+ if (!foundFocusable) {
+ console.log('[App] Cycled through sequence, no focusable element found.');
}
}
};
@@ -183,9 +255,9 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => {
-
+
diff --git a/packages/frontend/src/components/CommandInputBar.vue b/packages/frontend/src/components/CommandInputBar.vue
index b1c3281..3ba4d07 100644
--- a/packages/frontend/src/components/CommandInputBar.vue
+++ b/packages/frontend/src/components/CommandInputBar.vue
@@ -76,6 +76,14 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => {
}
};
+// +++ 监听 Store 中的触发器以激活终端搜索 +++
+watch(() => focusSwitcherStore.activateTerminalSearchTrigger, () => {
+ if (focusSwitcherStore.activateTerminalSearchTrigger > 0 && !isSearching.value) {
+ console.log('[CommandInputBar] Received terminal search activation trigger from store.');
+ toggleSearch(); // 调用组件内部的切换搜索方法来激活
+ }
+});
+
diff --git a/packages/frontend/src/components/FileManager.vue b/packages/frontend/src/components/FileManager.vue
index 6622601..ed79a54 100644
--- a/packages/frontend/src/components/FileManager.vue
+++ b/packages/frontend/src/components/FileManager.vue
@@ -8,8 +8,9 @@ import { createSftpActionsManager, type WebSocketDependencies } from '../composa
import { useFileUploader } from '../composables/useFileUploader';
// import { useFileEditor } from '../composables/useFileEditor'; // 移除旧的 composable 导入
import { useFileEditorStore, type FileInfo } from '../stores/fileEditor.store'; // 导入新的 Store 和 FileInfo 类型
-import { useSessionStore } from '../stores/session.store'; // 导入 Session Store
-import { useSettingsStore } from '../stores/settings.store'; // 导入 Settings Store
+import { useSessionStore } from '../stores/session.store';
+import { useSettingsStore } from '../stores/settings.store';
+import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
// WebSocket composable 不再直接使用
import FileUploadPopup from './FileUploadPopup.vue';
// import FileEditorOverlay from './FileEditorOverlay.vue'; // 不再在此处渲染
@@ -88,8 +89,9 @@ const {
// 实例化 Stores
const fileEditorStore = useFileEditorStore(); // 用于共享模式
-const sessionStore = useSessionStore(); // 用于独立模式
-const settingsStore = useSettingsStore(); // 用于获取设置
+const sessionStore = useSessionStore();
+const settingsStore = useSettingsStore();
+const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
// 从 Settings Store 获取共享设置
const { shareFileEditorTabsBoolean } = storeToRefs(settingsStore); // 使用 storeToRefs 保持响应性
@@ -980,6 +982,15 @@ watchEffect((onCleanup) => {
}
});
+// +++ 监听 Store 中的触发器以激活搜索 +++
+watch(() => focusSwitcherStore.activateFileManagerSearchTrigger, () => {
+ // 确保只在触发器值大于 0 时执行(避免初始加载时触发)
+ if (focusSwitcherStore.activateFileManagerSearchTrigger > 0) {
+ console.log('[FileManager] Received search activation trigger from store.');
+ activateSearch(); // 调用组件内部的激活搜索方法
+ }
+});
+
onBeforeUnmount(() => {
console.log(`[FileManager ${props.sessionId}] 组件即将卸载。`);
diff --git a/packages/frontend/src/components/FocusSwitcherConfigurator.vue b/packages/frontend/src/components/FocusSwitcherConfigurator.vue
index 59825b8..165f52d 100644
--- a/packages/frontend/src/components/FocusSwitcherConfigurator.vue
+++ b/packages/frontend/src/components/FocusSwitcherConfigurator.vue
@@ -34,15 +34,22 @@ const dialogStyle = reactive({
const hasChanges = ref(false);
// 本地副本,用于在弹窗内编辑而不直接修改 store
const localSequence: Ref = ref([]);
+// +++ 存储原始序列 ID,用于比较 +++
+const originalSequenceIds: Ref = ref([]);
// --- Watchers ---
watch(() => props.isVisible, (newValue) => {
if (newValue) {
// 从 Store 加载当前配置到本地副本
// 使用深拷贝确保 localSequence 是独立的
- localSequence.value = JSON.parse(JSON.stringify(focusSwitcherStore.getConfiguredInputs));
+ const loadedSequence = focusSwitcherStore.getConfiguredInputs; // 直接获取 getter 的值
+ console.log('[FocusSwitcherConfigurator] Loading sequence from store getter...'); // +++ Log: Start loading +++
+ localSequence.value = JSON.parse(JSON.stringify(loadedSequence));
+ // +++ 存储原始 ID 序列 +++
+ originalSequenceIds.value = loadedSequence.map(item => item.id);
hasChanges.value = false;
- console.log('[FocusSwitcherConfigurator] 弹窗打开, 已加载配置到本地副本:', localSequence.value);
+ console.log('[FocusSwitcherConfigurator] Dialog opened. Loaded sequence to local copy:', localSequence.value); // +++ Log: Loaded local +++
+ console.log('[FocusSwitcherConfigurator] Original sequence IDs stored:', originalSequenceIds.value); // +++ Log: Stored original +++
// 重置/计算初始位置和大小
requestAnimationFrame(() => {
if (dialogRef.value) {
@@ -62,16 +69,20 @@ watch(() => props.isVisible, (newValue) => {
});
// 监听本地序列变化,标记未保存更改
-watch(localSequence, (newValue, oldValue) => {
- // 确保不是初始化加载触发的 watch
- if (oldValue.length > 0 || (oldValue.length === 0 && newValue.length > 0)) {
- // 比较 ID 序列是否真的改变了
- const oldIds = oldValue.map(item => item.id);
- const newIds = newValue.map(item => item.id);
- if (JSON.stringify(oldIds) !== JSON.stringify(newIds)) {
- hasChanges.value = true;
- console.log('[FocusSwitcherConfigurator] 本地序列已更改。');
- }
+watch(localSequence, (currentLocalSequence) => {
+ // 直接比较当前本地序列的 ID 和原始 ID 序列
+ const currentIds = currentLocalSequence.map(item => item.id);
+ const originalIds = originalSequenceIds.value;
+
+ // 比较 JSON 字符串看是否有变化
+ const hasChanged = JSON.stringify(currentIds) !== JSON.stringify(originalIds); // +++ Calculate change status +++
+ if (hasChanged) {
+ // console.log('[FocusSwitcherConfigurator] Local sequence changed.'); // +++ Log: Changed +++
+ hasChanges.value = true;
+ } else {
+ // console.log('[FocusSwitcherConfigurator] Local sequence reverted to original.'); // +++ Log: Reverted +++
+ // 如果序列变回和原来一样,则标记为无更改
+ hasChanges.value = false;
}
}, { deep: true });
@@ -90,9 +101,10 @@ const closeDialog = () => {
const saveConfiguration = () => {
// 从本地副本提取 ID 序列
const newSequenceIds = localSequence.value.map(item => item.id);
+ console.log('[FocusSwitcherConfigurator] Saving configuration. Sequence IDs to save:', newSequenceIds); // +++ Log: Saving IDs +++
focusSwitcherStore.updateSequence(newSequenceIds); // 更新 Store 中的序列
focusSwitcherStore.saveConfiguration(); // 持久化保存
- console.log('[FocusSwitcherConfigurator] 配置已保存:', newSequenceIds);
+ console.log('[FocusSwitcherConfigurator] Configuration save process completed.'); // +++ Log: Save completed +++
hasChanges.value = false;
emit('close'); // 保存后关闭
};
diff --git a/packages/frontend/src/stores/focusSwitcher.store.ts b/packages/frontend/src/stores/focusSwitcher.store.ts
index 80e951c..ed8234f 100644
--- a/packages/frontend/src/stores/focusSwitcher.store.ts
+++ b/packages/frontend/src/stores/focusSwitcher.store.ts
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
-import { ref, computed } from 'vue';
+import { ref, computed, nextTick } from 'vue'; // +++ 将 nextTick 导入移到这里 +++
import { useI18n } from 'vue-i18n';
// 定义输入框的接口
@@ -15,7 +15,9 @@ export interface FocusableInput {
interface FocusSwitcherState {
availableInputs: FocusableInput[];
configuredSequence: string[]; // 只存储 ID 序列
- isConfiguratorVisible: boolean; // 新增:控制配置器可见性
+ isConfiguratorVisible: boolean;
+ activateFileManagerSearchTrigger: number;
+ activateTerminalSearchTrigger: number; // +++ 新增:终端搜索触发器 +++
}
const LOCAL_STORAGE_KEY = 'focusSwitcherSequence';
@@ -38,10 +40,26 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
// 用户配置的切换顺序 (存储 ID)
const configuredSequence = ref([]);
+ // 控制配置弹窗可见性
// 控制配置弹窗可见性
const isConfiguratorVisible = ref(false);
+ // +++ 触发器状态 +++
+ const activateFileManagerSearchTrigger = ref(0);
+ const activateTerminalSearchTrigger = ref(0); // +++ 终端搜索触发器状态 +++
// --- Actions ---
+ // +++ 新增:触发终端搜索激活的 Action +++
+ function triggerTerminalSearchActivation() {
+ activateTerminalSearchTrigger.value++;
+ console.log('[FocusSwitcherStore] Triggering Terminal search activation.');
+ }
+
+ // +++ 新增:触发文件管理器搜索激活的 Action +++
+ function triggerFileManagerSearchActivation() {
+ activateFileManagerSearchTrigger.value++; // 简单地增加计数器来触发监听
+ console.log('[FocusSwitcherStore] Triggering FileManager search activation.');
+ }
+
// 控制配置器显示/隐藏
function toggleConfigurator(visible?: boolean) {
isConfiguratorVisible.value = visible === undefined ? !isConfiguratorVisible.value : visible;
@@ -50,15 +68,24 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
// 从 localStorage 加载配置
function loadConfiguration() {
+ console.log('[FocusSwitcherStore] Attempting to load configuration...'); // +++ Log: Start loading +++
const savedSequence = localStorage.getItem(LOCAL_STORAGE_KEY);
+ console.log(`[FocusSwitcherStore] Raw data from localStorage ('${LOCAL_STORAGE_KEY}'):`, savedSequence); // +++ Log: Raw data +++
if (savedSequence) {
try {
const parsedSequence = JSON.parse(savedSequence);
+ console.log('[FocusSwitcherStore] Parsed sequence from localStorage:', parsedSequence); // +++ Log: Parsed data +++
// 验证加载的 ID 是否仍然存在于 availableInputs 中
- configuredSequence.value = parsedSequence.filter((id: string) =>
- availableInputs.value.some(input => input.id === id)
- );
- console.log('[FocusSwitcherStore] Configuration loaded:', configuredSequence.value);
+ const availableIds = new Set(availableInputs.value.map(input => input.id)); // +++ Get available IDs +++
+ const filteredSequence = parsedSequence.filter((id: string) => { // +++ Filter and log +++
+ const isValid = availableIds.has(id);
+ if (!isValid) {
+ console.warn(`[FocusSwitcherStore] Filtered out invalid ID during load: ${id}`);
+ }
+ return isValid;
+ });
+ configuredSequence.value = filteredSequence;
+ console.log('[FocusSwitcherStore] Final loaded & filtered configuration:', configuredSequence.value); // +++ Log: Final loaded value +++
} catch (error) {
console.error('[FocusSwitcherStore] Failed to parse saved configuration:', error);
configuredSequence.value = []; // 解析失败则重置
@@ -75,8 +102,12 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
// 保存配置到 localStorage
function saveConfiguration() {
try {
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(configuredSequence.value));
- console.log('[FocusSwitcherStore] Configuration saved:', configuredSequence.value);
+ const sequenceToSave = configuredSequence.value; // +++ Get value to save +++
+ const jsonStringToSave = JSON.stringify(sequenceToSave); // +++ Stringify +++
+ console.log(`[FocusSwitcherStore] Attempting to save sequence to localStorage ('${LOCAL_STORAGE_KEY}'):`, sequenceToSave); // +++ Log: Value being saved +++
+ console.log(`[FocusSwitcherStore] JSON string being saved:`, jsonStringToSave); // +++ Log: String being saved +++
+ localStorage.setItem(LOCAL_STORAGE_KEY, jsonStringToSave);
+ console.log('[FocusSwitcherStore] Configuration successfully saved.'); // +++ Log: Success +++
} catch (error) {
console.error('[FocusSwitcherStore] Failed to save configuration:', error);
}
@@ -84,10 +115,12 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
// 更新切换顺序
function updateSequence(newSequence: string[]) {
+ console.log('[FocusSwitcherStore] updateSequence called with:', newSequence); // +++ Log: Action called +++
// 确保新序列中的 ID 都是有效的
- configuredSequence.value = newSequence.filter(id =>
- availableInputs.value.some(input => input.id === id)
- );
+ const availableIds = new Set(availableInputs.value.map(input => input.id)); // +++ Get available IDs +++
+ const filteredSequence = newSequence.filter(id => availableIds.has(id)); // +++ Filter +++
+ configuredSequence.value = filteredSequence;
+ console.log('[FocusSwitcherStore] configuredSequence updated to:', configuredSequence.value); // +++ Log: State updated +++
// 可以在这里直接保存,或者让用户点击保存按钮再调用 saveConfiguration
// saveConfiguration(); // 取决于设计
}
@@ -129,16 +162,23 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
// --- Initialization ---
- // Store 创建时自动加载配置
- loadConfiguration();
+ // Store 创建时自动加载配置 (使用 nextTick 确保响应式系统就绪)
+ // --- import { nextTick } from 'vue'; // --- 移除这里的导入 ---
+ nextTick(() => {
+ loadConfiguration();
+ });
return {
// State
availableInputs,
configuredSequence,
- isConfiguratorVisible, // 导出状态
+ isConfiguratorVisible,
+ activateFileManagerSearchTrigger,
+ activateTerminalSearchTrigger, // +++ 导出终端搜索触发器状态 +++
// Actions
- toggleConfigurator, // 导出 action
+ toggleConfigurator,
+ triggerFileManagerSearchActivation,
+ triggerTerminalSearchActivation, // +++ 确保导出终端搜索触发器 Action +++
loadConfiguration,
saveConfiguration,
updateSequence,