update
This commit is contained in:
@@ -33,6 +33,9 @@ const route = useRoute();
|
||||
const navRef = ref<HTMLElement | null>(null);
|
||||
const underlineRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// +++ 新增:存储上一次由切换器聚焦的 ID +++
|
||||
const lastFocusedIdBySwitcher = ref<string | null>(null);
|
||||
|
||||
const updateUnderline = async () => {
|
||||
await nextTick(); // 等待 DOM 更新
|
||||
if (navRef.value && underlineRef.value) {
|
||||
@@ -88,7 +91,7 @@ const closeStyleCustomizer = () => {
|
||||
};
|
||||
|
||||
// +++ 全局键盘事件处理函数 +++
|
||||
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||
const handleGlobalKeyDown = async (event: KeyboardEvent) => { // Make the function async
|
||||
// 仅当 Alt 键被按下且没有其他修饰键 (如 Ctrl, Shift, Meta) 时触发
|
||||
if (event.key === 'Alt' && !event.ctrlKey && !event.shiftKey && !event.metaKey) {
|
||||
event.preventDefault(); // 阻止 Alt 键的默认行为 (例如激活菜单栏)
|
||||
@@ -96,108 +99,55 @@ const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||
// +++ Log: 打印当前的配置序列 +++
|
||||
console.log('[App] Current configured sequence in store:', JSON.stringify(focusSwitcherStore.configuredSequence));
|
||||
|
||||
const activeElement = document.activeElement as HTMLElement;
|
||||
let currentFocusId: string | null = null;
|
||||
// --- 确定当前焦点位置 ---
|
||||
// 优先使用上次切换器聚焦的 ID
|
||||
let currentFocusId: string | null = lastFocusedIdBySwitcher.value;
|
||||
console.log(`[App] Alt pressed. Last focused by switcher: ${currentFocusId}`);
|
||||
|
||||
// 检查当前焦点元素是否有我们设置的 data-focus-id
|
||||
// 如果上次切换器聚焦的 ID 不存在,尝试从 document.activeElement 获取
|
||||
if (!currentFocusId) {
|
||||
const activeElement = document.activeElement as HTMLElement;
|
||||
if (activeElement && activeElement.hasAttribute('data-focus-id')) {
|
||||
currentFocusId = activeElement.getAttribute('data-focus-id');
|
||||
console.log(`[App] Found focus ID from activeElement: ${currentFocusId}`);
|
||||
} else {
|
||||
console.log(`[App] Could not determine current focus ID from activeElement either.`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[App] Alt pressed. Current focus ID: ${currentFocusId}`);
|
||||
|
||||
// --- 新的查找逻辑 ---
|
||||
const sequence = focusSwitcherStore.configuredSequence; // 获取完整的配置顺序
|
||||
// --- 重构后的查找和聚焦逻辑 ---
|
||||
const sequence = focusSwitcherStore.configuredSequence;
|
||||
if (sequence.length === 0) {
|
||||
console.log('[App] No focus sequence configured.');
|
||||
return; // 没有配置,直接返回
|
||||
return;
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
|
||||
// 循环查找下一个可聚焦的目标 (最多循环一次完整的序列)
|
||||
let foundFocusable = false;
|
||||
// 尝试聚焦下一个目标,循环最多一次
|
||||
let focused = 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;
|
||||
const nextFocusId = focusSwitcherStore.getNextFocusTargetId(currentFocusId);
|
||||
if (!nextFocusId) { // 如果序列为空或找不到下一个(理论上不应发生,除非序列在迭代中改变)
|
||||
console.warn('[App] Could not determine next focus target ID.');
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`[App] Trying to focus target ID: ${nextFocusId}`);
|
||||
const success = await focusSwitcherStore.focusTarget(nextFocusId);
|
||||
|
||||
// --- 旧的逻辑移除 ---
|
||||
/*
|
||||
// 使用 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();
|
||||
}
|
||||
if (success) {
|
||||
console.log(`[App] Successfully focused ${nextFocusId}.`);
|
||||
lastFocusedIdBySwitcher.value = nextFocusId; // 记住成功聚焦的 ID
|
||||
focused = true;
|
||||
break; // 成功聚焦,退出循环
|
||||
} 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.`);
|
||||
console.log(`[App] Failed to focus ${nextFocusId}. Trying next in sequence...`);
|
||||
currentFocusId = nextFocusId; // 更新当前 ID,以便 getNextFocusTargetId 找到下一个
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFocusable) {
|
||||
console.log('[App] Cycled through sequence, no focusable element found.');
|
||||
if (!focused) {
|
||||
console.log('[App] Cycled through sequence, no target could be focused.');
|
||||
lastFocusedIdBySwitcher.value = null; // 重置记忆
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { ref, watch, nextTick, onMounted, onBeforeUnmount, defineExpose } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // 导入 Store
|
||||
// 假设你有一个图标库,例如 unplugin-icons 或类似库
|
||||
@@ -63,6 +63,7 @@ watch(searchTerm, (newValue) => {
|
||||
|
||||
// 可以在这里添加一个 ref 用于聚焦搜索框
|
||||
const searchInputRef = ref<HTMLInputElement | null>(null);
|
||||
const commandInputRef = ref<HTMLInputElement | null>(null); // Ref for command input
|
||||
|
||||
// Removed debug computed property
|
||||
|
||||
@@ -84,6 +85,46 @@ watch(() => focusSwitcherStore.activateTerminalSearchTrigger, () => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Focus Actions ---
|
||||
const focusCommandInput = (): boolean => {
|
||||
if (commandInputRef.value) {
|
||||
commandInputRef.value.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const focusSearchInput = (): boolean => {
|
||||
if (!isSearching.value) {
|
||||
// If search is not active, activate it first
|
||||
toggleSearch(); // This might need nextTick if toggleSearch is async
|
||||
nextTick(() => { // Ensure DOM is updated after toggleSearch
|
||||
if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
}
|
||||
});
|
||||
// Since focusing might be async after toggle, we optimistically return true
|
||||
// or adjust based on toggleSearch's behavior. For simplicity, assume it works.
|
||||
return true;
|
||||
} else if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
defineExpose({ focusCommandInput, focusSearchInput });
|
||||
|
||||
// --- Register/Unregister Focus Actions ---
|
||||
onMounted(() => {
|
||||
focusSwitcherStore.registerFocusAction('commandInput', focusCommandInput);
|
||||
focusSwitcherStore.registerFocusAction('terminalSearch', focusSearchInput);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
focusSwitcherStore.unregisterFocusAction('commandInput');
|
||||
focusSwitcherStore.unregisterFocusAction('terminalSearch');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -99,6 +140,7 @@ watch(() => focusSwitcherStore.activateTerminalSearchTrigger, () => {
|
||||
v-model="commandInput"
|
||||
:placeholder="t('commandInputBar.placeholder')"
|
||||
class="command-input"
|
||||
ref="commandInputRef"
|
||||
data-focus-id="commandInput"
|
||||
@keydown.enter="sendCommand"
|
||||
@keydown="handleCommandInputKeydown"
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type PropType, ref, watch } from 'vue'; // 添加 ref 和 watch
|
||||
import { computed, type PropType, ref, watch, defineExpose, onMounted, onBeforeUnmount } from 'vue'; // 添加 ref, watch, defineExpose, onMounted, onBeforeUnmount
|
||||
import { useI18n } from 'vue-i18n';
|
||||
// import { storeToRefs } from 'pinia'; // 移除 storeToRefs
|
||||
import MonacoEditor from './MonacoEditor.vue'; // 导入 Monaco Editor 组件
|
||||
import FileEditorTabs from './FileEditorTabs.vue'; // 导入标签栏组件 (路径确认无误)
|
||||
// import { useFileEditorStore } from '../stores/fileEditor.store'; // 移除 Store 导入
|
||||
import type { FileTab } from '../stores/fileEditor.store'; // 保留类型导入
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
|
||||
|
||||
const { t } = useI18n();
|
||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
||||
|
||||
// --- Props ---
|
||||
const props = defineProps({
|
||||
@@ -92,6 +94,28 @@ const handleSaveRequest = () => {
|
||||
// const handleCloseContainer = () => { ... };
|
||||
// const handleMinimizeContainer = () => { ... };
|
||||
|
||||
// 新增:Monaco Editor 组件的引用
|
||||
const monacoEditorRef = ref<InstanceType<typeof MonacoEditor> | null>(null);
|
||||
|
||||
// 新增:聚焦活动编辑器的方法
|
||||
const focusActiveEditor = (): boolean => {
|
||||
if (monacoEditorRef.value) {
|
||||
monacoEditorRef.value.focus();
|
||||
return true; // 聚焦成功
|
||||
}
|
||||
return false; // 聚焦失败
|
||||
};
|
||||
|
||||
// 新增:暴露聚焦方法
|
||||
defineExpose({ focusActiveEditor });
|
||||
|
||||
// +++ 注册/注销自定义聚焦动作 +++
|
||||
onMounted(() => {
|
||||
focusSwitcherStore.registerFocusAction('fileEditorActive', focusActiveEditor);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
focusSwitcherStore.unregisterFocusAction('fileEditorActive');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -135,6 +159,7 @@ const handleSaveRequest = () => {
|
||||
<div v-else-if="currentTabLoadingError" class="editor-error">{{ currentTabLoadingError }}</div>
|
||||
<MonacoEditor
|
||||
v-else-if="activeTab"
|
||||
ref="monacoEditorRef"
|
||||
:key="activeTab.id"
|
||||
v-model="localEditorContent"
|
||||
:language="currentTabLanguage"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, watchEffect, type PropType, readonly } from 'vue'; // 恢复导入, 添加 watch
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, watchEffect, type PropType, readonly, defineExpose } from 'vue'; // 恢复导入, 添加 watch, defineExpose
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router'; // 保留用于生成下载 URL (如果下载逻辑移动则可移除)
|
||||
import { storeToRefs } from 'pinia'; // 导入 storeToRefs
|
||||
@@ -536,6 +536,14 @@ onBeforeUnmount(() => {
|
||||
cleanupSftpHandlers();
|
||||
});
|
||||
|
||||
// +++ 注册/注销自定义聚焦动作 +++
|
||||
onMounted(() => {
|
||||
focusSwitcherStore.registerFocusAction('fileManagerSearch', focusSearchInput);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
focusSwitcherStore.unregisterFocusAction('fileManagerSearch');
|
||||
});
|
||||
|
||||
// --- 列宽调整逻辑 (保持不变) ---
|
||||
const getColumnKeyByIndex = (index: number): keyof typeof colWidths.value | null => {
|
||||
const keys = Object.keys(colWidths.value) as Array<keyof typeof colWidths.value>;
|
||||
@@ -653,6 +661,25 @@ const handleWheel = (event: WheelEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
// +++ 新增:聚焦搜索框的方法 +++
|
||||
const focusSearchInput = (): boolean => {
|
||||
if (!isSearchActive.value) {
|
||||
activateSearch(); // Activate search first
|
||||
nextTick(() => { // Wait for DOM update
|
||||
if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
}
|
||||
});
|
||||
// Assume activation and focus will likely succeed
|
||||
return true;
|
||||
} else if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
defineExpose({ focusSearchInput });
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import { ref, onMounted, onBeforeUnmount, watch, defineExpose } from 'vue';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { useAppearanceStore } from '../stores/appearance.store'; // <-- 导入 Store
|
||||
import { storeToRefs } from 'pinia'; // <-- 导入 storeToRefs
|
||||
@@ -227,6 +227,11 @@ onBeforeUnmount(() => {
|
||||
// getValue: () => editorInstance?.getValue()
|
||||
// });
|
||||
|
||||
// Expose the focus method
|
||||
defineExpose({
|
||||
focus: () => editorInstance?.focus()
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'; // 确保 ref 已导入
|
||||
import { ref, computed, onMounted, onBeforeUnmount, defineExpose } from 'vue'; // 确保 ref, defineExpose, onBeforeUnmount 已导入
|
||||
import { storeToRefs } from 'pinia';
|
||||
// import { useRouter } from 'vue-router'; // 不再需要 router
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store';
|
||||
import { useTagsStore, TagInfo } from '../stores/tags.store';
|
||||
import { useSessionStore } from '../stores/session.store'; // 导入 session store
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits([
|
||||
@@ -20,12 +21,14 @@ const { t } = useI18n();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const tagsStore = useTagsStore();
|
||||
const sessionStore = useSessionStore(); // 获取 session store 实例
|
||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
||||
|
||||
const { connections, isLoading: connectionsLoading, error: connectionsError } = storeToRefs(connectionsStore);
|
||||
const { tags, isLoading: tagsLoading, error: tagsError } = storeToRefs(tagsStore);
|
||||
|
||||
// 搜索词
|
||||
const searchTerm = ref('');
|
||||
const searchInputRef = ref<HTMLInputElement | null>(null); // 新增:搜索输入框的 ref
|
||||
|
||||
// 右键菜单状态
|
||||
const contextMenuVisible = ref(false);
|
||||
@@ -168,6 +171,14 @@ onMounted(() => {
|
||||
tagsStore.fetchTags();
|
||||
});
|
||||
|
||||
// +++ 注册/注销自定义聚焦动作 +++
|
||||
onMounted(() => {
|
||||
focusSwitcherStore.registerFocusAction('connectionListSearch', focusSearchInput);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
focusSwitcherStore.unregisterFocusAction('connectionListSearch');
|
||||
});
|
||||
|
||||
// 处理中键点击(在新标签页打开)
|
||||
const handleOpenInNewTab = (connectionId: number) => {
|
||||
console.log(`[WkspConnList] handleOpenInNewTab (中键/辅助键) called for ID: ${connectionId}`);
|
||||
@@ -176,6 +187,16 @@ const handleOpenInNewTab = (connectionId: number) => {
|
||||
closeContextMenu(); // 如果右键菜单是打开的,也关闭它
|
||||
return false; // 尝试显式阻止进一步处理
|
||||
};
|
||||
|
||||
// 新增:暴露聚焦搜索框的方法
|
||||
const focusSearchInput = (): boolean => {
|
||||
if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
return true; // 聚焦成功
|
||||
}
|
||||
return false; // 聚焦失败
|
||||
};
|
||||
defineExpose({ focusSearchInput });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -193,7 +214,9 @@ const handleOpenInNewTab = (connectionId: number) => {
|
||||
type="text"
|
||||
v-model="searchTerm"
|
||||
:placeholder="t('workspaceConnectionList.searchPlaceholder')"
|
||||
ref="searchInputRef"
|
||||
class="search-input"
|
||||
data-focus-id="connectionListSearch"
|
||||
/>
|
||||
<button
|
||||
class="add-button"
|
||||
|
||||
@@ -8,8 +8,8 @@ import { useI18n } from 'vue-i18n';
|
||||
export interface FocusableInput {
|
||||
id: string; // 唯一标识符
|
||||
label: string; // 用户友好的名称
|
||||
componentPath?: string;
|
||||
selector?: string;
|
||||
// componentPath 和 selector 不再需要,聚焦完全依赖 focusAction
|
||||
focusAction: () => boolean | Promise<boolean>; // 改为必需
|
||||
}
|
||||
|
||||
// 定义 Store 的 State 接口 (可选但推荐)
|
||||
@@ -29,17 +29,23 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
||||
|
||||
// --- State ---
|
||||
const availableInputs = ref<FocusableInput[]>([
|
||||
{ id: 'commandHistorySearch', label: t('focusSwitcher.input.commandHistorySearch', '命令历史搜索'), componentPath: 'CommandHistoryView.vue', selector: 'input[placeholder*="搜索历史记录"]' },
|
||||
{ id: 'quickCommandsSearch', label: t('focusSwitcher.input.quickCommandsSearch', '快捷指令搜索'), componentPath: 'QuickCommandsView.vue', selector: 'input[placeholder*="搜索名称或指令"]' },
|
||||
{ id: 'fileManagerSearch', label: t('focusSwitcher.input.fileManagerSearch', '文件管理器搜索'), componentPath: 'FileManager.vue', selector: '.search-input' },
|
||||
{ id: 'commandInput', label: t('focusSwitcher.input.commandInput', '命令输入'), componentPath: 'CommandInputBar.vue', selector: '.command-input' },
|
||||
{ id: 'terminalSearch', label: t('focusSwitcher.input.terminalSearch', '终端内搜索'), componentPath: 'CommandInputBar.vue', selector: '.search-input' },
|
||||
// 简化定义,移除 componentPath 和 selector,focusAction 将由组件注册
|
||||
{ id: 'commandHistorySearch', label: t('focusSwitcher.input.commandHistorySearch', '命令历史搜索'), focusAction: () => false },
|
||||
{ id: 'quickCommandsSearch', label: t('focusSwitcher.input.quickCommandsSearch', '快捷指令搜索'), focusAction: () => false },
|
||||
{ id: 'fileManagerSearch', label: t('focusSwitcher.input.fileManagerSearch', '文件管理器搜索'), focusAction: () => false },
|
||||
{ id: 'commandInput', label: t('focusSwitcher.input.commandInput', '命令输入'), focusAction: () => false },
|
||||
{ id: 'terminalSearch', label: t('focusSwitcher.input.terminalSearch', '终端内搜索'), focusAction: () => false },
|
||||
{ id: 'connectionListSearch', label: t('focusSwitcher.input.connectionListSearch', '连接列表搜索'), focusAction: () => false },
|
||||
{ id: 'fileEditorActive', label: t('focusSwitcher.input.fileEditorActive', '文件编辑器'), focusAction: () => false },
|
||||
]);
|
||||
const configuredSequence = ref<string[]>([]);
|
||||
const isConfiguratorVisible = ref(false);
|
||||
const activateFileManagerSearchTrigger = ref(0);
|
||||
const activateTerminalSearchTrigger = ref(0);
|
||||
|
||||
// 新增:存储自定义聚焦动作
|
||||
const focusActions = ref<Record<string, () => boolean | Promise<boolean>>>({});
|
||||
|
||||
// --- Actions ---
|
||||
|
||||
// +++ 新增:从后端加载配置 +++
|
||||
@@ -163,6 +169,57 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
||||
saveSequenceToBackend();
|
||||
}
|
||||
|
||||
// 注册聚焦动作 (现在更新 availableInputs 中的 focusAction)
|
||||
function registerFocusAction(id: string, action: () => boolean | Promise<boolean>) {
|
||||
const targetInput = availableInputs.value.find(input => input.id === id);
|
||||
if (targetInput) {
|
||||
targetInput.focusAction = action;
|
||||
console.log(`[FocusSwitcherStore] Registered focus action for ID: ${id}`);
|
||||
} else {
|
||||
console.warn(`[FocusSwitcherStore] Attempted to register focus action for unknown ID: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 注销聚焦动作 (重置为默认返回 false 的函数)
|
||||
function unregisterFocusAction(id: string) {
|
||||
const targetInput = availableInputs.value.find(input => input.id === id);
|
||||
if (targetInput) {
|
||||
targetInput.focusAction = () => false; // Reset to default non-functional action
|
||||
console.log(`[FocusSwitcherStore] Unregistered focus action for ID: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:统一的聚焦目标 Action
|
||||
async function focusTarget(id: string): Promise<boolean> {
|
||||
console.log(`[FocusSwitcherStore] Attempting to focus target ID: ${id}`);
|
||||
const targetInput = availableInputs.value.find(input => input.id === id);
|
||||
if (targetInput?.focusAction) {
|
||||
try {
|
||||
const result = await targetInput.focusAction();
|
||||
if (result) {
|
||||
console.log(`[FocusSwitcherStore] Successfully focused ${id} via action.`);
|
||||
return true;
|
||||
} else {
|
||||
console.log(`[FocusSwitcherStore] Focus action for ${id} returned false.`);
|
||||
// 尝试激活搜索框(如果适用)
|
||||
if (id === 'fileManagerSearch') {
|
||||
triggerFileManagerSearchActivation();
|
||||
// 激活后可能需要短暂延迟再尝试聚焦,但这部分逻辑移到 App.vue 或组件内部更合适
|
||||
} else if (id === 'terminalSearch') {
|
||||
triggerTerminalSearchActivation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[FocusSwitcherStore] Error executing focus action for ${id}:`, error);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
console.warn(`[FocusSwitcherStore] No focus action registered for ID: ${id}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Getters ---
|
||||
const getConfiguredInputs = computed((): FocusableInput[] => {
|
||||
return configuredSequence.value
|
||||
@@ -217,5 +274,9 @@ export const useFocusSwitcherStore = defineStore('focusSwitcher', () => {
|
||||
getConfiguredInputs,
|
||||
getAvailableInputsForConfigurator,
|
||||
getNextFocusTargetId,
|
||||
registerFocusAction, // 暴露注册方法
|
||||
unregisterFocusAction, // 暴露注销方法
|
||||
// focusActions 不再需要暴露
|
||||
focusTarget, // 暴露新的统一聚焦方法
|
||||
};
|
||||
});
|
||||
@@ -12,6 +12,7 @@
|
||||
data-focus-id="commandHistorySearch"
|
||||
@input="updateSearchTerm($event)"
|
||||
@keydown="handleKeydown"
|
||||
ref="searchInputRef"
|
||||
class="search-input"
|
||||
/>
|
||||
<button @click="confirmClearAll" class="clear-button" :title="$t('commandHistory.clear', '清空')">
|
||||
@@ -50,18 +51,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, nextTick } from 'vue';
|
||||
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineExpose } from 'vue';
|
||||
import { useCommandHistoryStore, CommandHistoryEntryFE } from '../stores/commandHistory.store';
|
||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import PaneTitleBar from '../components/PaneTitleBar.vue'; // 导入标题栏
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
|
||||
|
||||
const commandHistoryStore = useCommandHistoryStore();
|
||||
const uiNotificationsStore = useUiNotificationsStore();
|
||||
const { t } = useI18n();
|
||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
||||
const hoveredItemId = ref<number | null>(null);
|
||||
const selectedIndex = ref<number>(-1); // -1 表示没有选中
|
||||
const historyListRef = ref<HTMLUListElement | null>(null); // Ref for the history list UL
|
||||
const searchInputRef = ref<HTMLInputElement | null>(null); // +++ Ref for the search input +++
|
||||
|
||||
// --- 从 Store 获取状态和 Getter ---
|
||||
const searchTerm = computed(() => commandHistoryStore.searchTerm);
|
||||
@@ -83,6 +87,14 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// +++ 注册/注销自定义聚焦动作 +++
|
||||
onMounted(() => {
|
||||
focusSwitcherStore.registerFocusAction('commandHistorySearch', focusSearchInput);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
focusSwitcherStore.unregisterFocusAction('commandHistorySearch');
|
||||
});
|
||||
|
||||
// --- 事件处理 ---
|
||||
|
||||
// 更新搜索词
|
||||
@@ -170,6 +182,16 @@ const executeCommand = (command: string) => {
|
||||
// selectedIndex.value = -1;
|
||||
};
|
||||
|
||||
// +++ 新增:聚焦搜索框的方法 +++
|
||||
const focusSearchInput = (): boolean => {
|
||||
if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
defineExpose({ focusSearchInput });
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
data-focus-id="quickCommandsSearch"
|
||||
@input="updateSearchTerm($event)"
|
||||
@keydown="handleKeydown"
|
||||
ref="searchInputRef"
|
||||
class="search-input"
|
||||
/>
|
||||
<button @click="toggleSortBy" class="sort-toggle-button" :title="sortButtonTitle">
|
||||
@@ -63,21 +64,24 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, nextTick } from 'vue';
|
||||
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineExpose } from 'vue';
|
||||
import { useQuickCommandsStore, type QuickCommandFE, type QuickCommandSortByType } from '../stores/quickCommands.store';
|
||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import AddEditQuickCommandForm from '../components/AddEditQuickCommandForm.vue'; // 导入表单组件
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
|
||||
|
||||
const quickCommandsStore = useQuickCommandsStore();
|
||||
const uiNotificationsStore = useUiNotificationsStore(); // 如果需要显示通知
|
||||
const { t } = useI18n();
|
||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
||||
|
||||
const hoveredItemId = ref<number | null>(null);
|
||||
const isFormVisible = ref(false);
|
||||
const commandToEdit = ref<QuickCommandFE | null>(null);
|
||||
const selectedIndex = ref<number>(-1); // -1 表示没有选中
|
||||
const commandListRef = ref<HTMLUListElement | null>(null); // Ref for the command list UL
|
||||
const searchInputRef = ref<HTMLInputElement | null>(null); // +++ Ref for the search input +++
|
||||
|
||||
// --- 从 Store 获取状态和 Getter ---
|
||||
const searchTerm = computed(() => quickCommandsStore.searchTerm);
|
||||
@@ -95,6 +99,14 @@ onMounted(() => {
|
||||
quickCommandsStore.fetchQuickCommands(); // 组件挂载时获取数据
|
||||
});
|
||||
|
||||
// +++ 注册/注销自定义聚焦动作 +++
|
||||
onMounted(() => {
|
||||
focusSwitcherStore.registerFocusAction('quickCommandsSearch', focusSearchInput);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
focusSwitcherStore.unregisterFocusAction('quickCommandsSearch');
|
||||
});
|
||||
|
||||
// --- 事件处理 ---
|
||||
|
||||
const updateSearchTerm = (event: Event) => {
|
||||
@@ -204,6 +216,16 @@ const executeCommand = (command: QuickCommandFE) => {
|
||||
// selectedIndex.value = -1;
|
||||
};
|
||||
|
||||
// +++ 新增:聚焦搜索框的方法 +++
|
||||
const focusSearchInput = (): boolean => {
|
||||
if (searchInputRef.value) {
|
||||
searchInputRef.value.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
defineExpose({ focusSearchInput });
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user