@@ -1006,7 +1006,14 @@
|
|||||||
"empty": "No history records",
|
"empty": "No history records",
|
||||||
"confirmClear": "Are you sure you want to clear all history?",
|
"confirmClear": "Are you sure you want to clear all history?",
|
||||||
"copied": "Copied to clipboard",
|
"copied": "Copied to clipboard",
|
||||||
"copyFailed": "Copy failed"
|
"copyFailed": "Copy failed",
|
||||||
|
"actions": {
|
||||||
|
"sendToAllSessions": "Send to All Sessions"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sentToAllSessions": "Command sent to {count} sessions.",
|
||||||
|
"noActiveSshSessions": "No active SSH sessions to send command to."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"quickCommands": {
|
"quickCommands": {
|
||||||
"searchPlaceholder": "Search name or command...",
|
"searchPlaceholder": "Search name or command...",
|
||||||
@@ -1032,6 +1039,13 @@
|
|||||||
"untagged": "Untagged",
|
"untagged": "Untagged",
|
||||||
"tags": {
|
"tags": {
|
||||||
"clickToEditTag": "Click to edit tag name"
|
"clickToEditTag": "Click to edit tag name"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"sendToAllSessions": "Send to All Sessions"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sentToAllSessions": "Command sent to {count} sessions.",
|
||||||
|
"noActiveSshSessions": "No active SSH sessions to send command to."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
|
|||||||
@@ -57,7 +57,14 @@
|
|||||||
"delete": "削除",
|
"delete": "削除",
|
||||||
"empty": "履歴はありません",
|
"empty": "履歴はありません",
|
||||||
"loading": "ロード中...",
|
"loading": "ロード中...",
|
||||||
"searchPlaceholder": "履歴を検索..."
|
"searchPlaceholder": "履歴を検索...",
|
||||||
|
"actions": {
|
||||||
|
"sendToAllSessions": "すべてのセッションに送信"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sentToAllSessions": "コマンドは {count} 個のセッションに送信されました。",
|
||||||
|
"noActiveSshSessions": "コマンドを送信するアクティブな SSH セッションはありません。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"commandInputBar": {
|
"commandInputBar": {
|
||||||
"closeSearch": "ターミナル検索を閉じる",
|
"closeSearch": "ターミナル検索を閉じる",
|
||||||
@@ -572,7 +579,14 @@
|
|||||||
"searchPlaceholder": "名前またはコマンドを検索...",
|
"searchPlaceholder": "名前またはコマンドを検索...",
|
||||||
"sortByName": "名前",
|
"sortByName": "名前",
|
||||||
"sortByUsage": "使用頻度",
|
"sortByUsage": "使用頻度",
|
||||||
"usageCount": "使用回数"
|
"usageCount": "使用回数",
|
||||||
|
"actions": {
|
||||||
|
"sendToAllSessions": "すべてのセッションに送信"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sentToAllSessions": "コマンドは {count} 個のセッションに送信されました。",
|
||||||
|
"noActiveSshSessions": "コマンドを送信するアクティブな SSH セッションはありません。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"remoteDesktopModal": {
|
"remoteDesktopModal": {
|
||||||
"errors": {
|
"errors": {
|
||||||
|
|||||||
@@ -1008,7 +1008,14 @@
|
|||||||
"empty": "没有历史记录",
|
"empty": "没有历史记录",
|
||||||
"confirmClear": "确定要清空所有历史记录吗?",
|
"confirmClear": "确定要清空所有历史记录吗?",
|
||||||
"copied": "已复制到剪贴板",
|
"copied": "已复制到剪贴板",
|
||||||
"copyFailed": "复制失败"
|
"copyFailed": "复制失败",
|
||||||
|
"actions": {
|
||||||
|
"sendToAllSessions": "发送到全部会话"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sentToAllSessions": "指令已发送到 {count} 个会话。",
|
||||||
|
"noActiveSshSessions": "没有活动的 SSH 会话可发送指令。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"quickCommands": {
|
"quickCommands": {
|
||||||
"searchPlaceholder": "搜索名称或指令...",
|
"searchPlaceholder": "搜索名称或指令...",
|
||||||
@@ -1034,6 +1041,13 @@
|
|||||||
"untagged": "未标记",
|
"untagged": "未标记",
|
||||||
"tags": {
|
"tags": {
|
||||||
"clickToEditTag": "点击编辑标签名称"
|
"clickToEditTag": "点击编辑标签名称"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"sendToAllSessions": "发送到全部会话"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"sentToAllSessions": "指令已发送到 {count} 个会话。",
|
||||||
|
"noActiveSshSessions": "没有活动的 SSH 会话可发送指令。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
||||||
:class="{ 'bg-primary/20 font-medium': index === storeSelectedIndex }"
|
:class="{ 'bg-primary/20 font-medium': index === storeSelectedIndex }"
|
||||||
@click="executeCommand(entry.command)"
|
@click="executeCommand(entry.command)"
|
||||||
|
@contextmenu.prevent="showCommandHistoryContextMenu($event, entry)"
|
||||||
:title="entry.command"
|
:title="entry.command"
|
||||||
>
|
>
|
||||||
<!-- Command Text -->
|
<!-- Command Text -->
|
||||||
@@ -59,29 +60,57 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Context Menu for Command History -->
|
||||||
|
<div
|
||||||
|
v-if="commandHistoryContextMenuVisible"
|
||||||
|
class="fixed bg-background border border-border/50 shadow-xl rounded-lg py-1.5 z-50 min-w-[180px]"
|
||||||
|
:style="{ top: `${commandHistoryContextMenuPosition.y}px`, left: `${commandHistoryContextMenuPosition.x}px` }"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<ul class="list-none p-0 m-0">
|
||||||
|
<li
|
||||||
|
v-if="commandHistoryContextTargetEntry"
|
||||||
|
class="group px-4 py-1.5 cursor-pointer flex items-center text-foreground hover:bg-primary/10 hover:text-primary text-sm transition-colors duration-150 rounded-md mx-1"
|
||||||
|
@click="handleCommandHistoryMenuAction('sendToAllSessions', commandHistoryContextTargetEntry!)"
|
||||||
|
>
|
||||||
|
<span>{{ t('commandHistory.actions.sendToAllSessions', '发送到全部会话') }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineExpose, watch } from 'vue'; // Import watch
|
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineExpose, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia'; // Import storeToRefs
|
import { storeToRefs } from 'pinia';
|
||||||
import { useCommandHistoryStore, CommandHistoryEntryFE } from '../stores/commandHistory.store';
|
import { useCommandHistoryStore, CommandHistoryEntryFE } from '../stores/commandHistory.store';
|
||||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
|
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
|
||||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents';
|
||||||
|
import { useSessionStore } from '../stores/session.store';
|
||||||
|
import type { SessionState } from '../stores/session/types';
|
||||||
|
import { useConnectionsStore } from '../stores/connections.store';
|
||||||
|
|
||||||
const commandHistoryStore = useCommandHistoryStore();
|
const commandHistoryStore = useCommandHistoryStore();
|
||||||
const uiNotificationsStore = useUiNotificationsStore();
|
const uiNotificationsStore = useUiNotificationsStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
||||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||||
|
const sessionStore = useSessionStore(); // +++ 实例化 SessionStore +++
|
||||||
|
const connectionsStore = useConnectionsStore(); // +++ 实例化 ConnectionsStore +++
|
||||||
const hoveredItemId = ref<number | null>(null);
|
const hoveredItemId = ref<number | null>(null);
|
||||||
// const selectedIndex = ref<number>(-1); // REMOVED: Use store's selectedIndex
|
// const selectedIndex = ref<number>(-1); // REMOVED: Use store's selectedIndex
|
||||||
const historyListRef = ref<HTMLUListElement | null>(null); // Ref for the history list UL
|
const historyListRef = ref<HTMLUListElement | null>(null); // Ref for the history list UL
|
||||||
const searchInputRef = ref<HTMLInputElement | null>(null); // +++ Ref for the search input +++
|
const searchInputRef = ref<HTMLInputElement | null>(null); // +++ Ref for the search input +++
|
||||||
let unregisterFocus: (() => void) | null = null; // +++ 保存注销函数 +++
|
let unregisterFocus: (() => void) | null = null; // +++ 保存注销函数 +++
|
||||||
|
|
||||||
|
// +++ 右键菜单状态 +++
|
||||||
|
const commandHistoryContextMenuVisible = ref(false);
|
||||||
|
const commandHistoryContextMenuPosition = ref({ x: 0, y: 0 });
|
||||||
|
const commandHistoryContextTargetEntry = ref<CommandHistoryEntryFE | null>(null);
|
||||||
|
|
||||||
// --- 从 Store 获取状态和 Getter ---
|
// --- 从 Store 获取状态和 Getter ---
|
||||||
const searchTerm = computed(() => commandHistoryStore.searchTerm);
|
const searchTerm = computed(() => commandHistoryStore.searchTerm);
|
||||||
// 使用 store 的 filteredHistory getter
|
// 使用 store 的 filteredHistory getter
|
||||||
@@ -218,5 +247,47 @@ const focusSearchInput = (): boolean => {
|
|||||||
};
|
};
|
||||||
defineExpose({ focusSearchInput });
|
defineExpose({ focusSearchInput });
|
||||||
|
|
||||||
|
// +++ 右键菜单方法 +++
|
||||||
|
const showCommandHistoryContextMenu = (event: MouseEvent, entry: CommandHistoryEntryFE) => {
|
||||||
|
event.preventDefault();
|
||||||
|
commandHistoryContextTargetEntry.value = entry;
|
||||||
|
commandHistoryContextMenuPosition.value = { x: event.clientX, y: event.clientY };
|
||||||
|
commandHistoryContextMenuVisible.value = true;
|
||||||
|
document.addEventListener('click', closeCommandHistoryContextMenu, { once: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeCommandHistoryContextMenu = () => {
|
||||||
|
commandHistoryContextMenuVisible.value = false;
|
||||||
|
commandHistoryContextTargetEntry.value = null;
|
||||||
|
document.removeEventListener('click', closeCommandHistoryContextMenu);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCommandHistoryMenuAction = (action: 'sendToAllSessions', entry: CommandHistoryEntryFE) => {
|
||||||
|
closeCommandHistoryContextMenu();
|
||||||
|
if (action === 'sendToAllSessions') {
|
||||||
|
const activeSshSessions = Array.from(sessionStore.sessions.values()).filter(
|
||||||
|
(s: SessionState) => {
|
||||||
|
if (s.wsManager.connectionStatus.value !== 'connected') return false;
|
||||||
|
const connInfo = connectionsStore.connections.find(c => c.id === Number(s.connectionId));
|
||||||
|
return connInfo?.type === 'SSH';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeSshSessions.length > 0) {
|
||||||
|
activeSshSessions.forEach((session: SessionState) => {
|
||||||
|
emitWorkspaceEvent('terminal:sendCommand', { sessionId: session.sessionId, command: entry.command });
|
||||||
|
});
|
||||||
|
uiNotificationsStore.addNotification({
|
||||||
|
message: t('commandHistory.notifications.sentToAllSessions', { count: activeSshSessions.length }),
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uiNotificationsStore.addNotification({
|
||||||
|
message: t('commandHistory.notifications.noActiveSshSessions'),
|
||||||
|
type: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
||||||
:class="{ 'bg-primary/20 font-medium': isCommandSelected(cmd.id) }"
|
:class="{ 'bg-primary/20 font-medium': isCommandSelected(cmd.id) }"
|
||||||
@click="executeCommand(cmd)"
|
@click="executeCommand(cmd)"
|
||||||
|
@contextmenu.prevent="showQuickCommandContextMenu($event, cmd)"
|
||||||
>
|
>
|
||||||
<!-- Command Info (Structure remains the same) -->
|
<!-- Command Info (Structure remains the same) -->
|
||||||
<div class="flex flex-col overflow-hidden mr-2 flex-grow">
|
<div class="flex flex-col overflow-hidden mr-2 flex-grow">
|
||||||
@@ -125,6 +126,7 @@
|
|||||||
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
class="group flex justify-between items-center px-3 py-2.5 mb-1 cursor-pointer rounded-md hover:bg-primary/10 transition-colors duration-150"
|
||||||
:class="{ 'bg-primary/20 font-medium': isCommandSelected(cmd.id) }"
|
:class="{ 'bg-primary/20 font-medium': isCommandSelected(cmd.id) }"
|
||||||
@click="executeCommand(cmd)"
|
@click="executeCommand(cmd)"
|
||||||
|
@contextmenu.prevent="showQuickCommandContextMenu($event, cmd)"
|
||||||
>
|
>
|
||||||
<!-- Command Info -->
|
<!-- Command Info -->
|
||||||
<div class="flex flex-col overflow-hidden mr-2 flex-grow">
|
<div class="flex flex-col overflow-hidden mr-2 flex-grow">
|
||||||
@@ -153,33 +155,55 @@
|
|||||||
:command-to-edit="commandToEdit"
|
:command-to-edit="commandToEdit"
|
||||||
@close="closeForm"
|
@close="closeForm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Context Menu for Quick Commands -->
|
||||||
|
<div
|
||||||
|
v-if="quickCommandContextMenuVisible"
|
||||||
|
class="fixed bg-background border border-border/50 shadow-xl rounded-lg py-1.5 z-50 min-w-[180px]"
|
||||||
|
:style="{ top: `${quickCommandContextMenuPosition.y}px`, left: `${quickCommandContextMenuPosition.x}px` }"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<ul class="list-none p-0 m-0">
|
||||||
|
<li
|
||||||
|
v-if="quickCommandContextTargetCommand"
|
||||||
|
class="group px-4 py-1.5 cursor-pointer flex items-center text-foreground hover:bg-primary/10 hover:text-primary text-sm transition-colors duration-150 rounded-md mx-1"
|
||||||
|
@click="handleQuickCommandMenuAction('sendToAllSessions', quickCommandContextTargetCommand!)"
|
||||||
|
>
|
||||||
|
<span>{{ t('quickCommands.actions.sendToAllSessions', '发送到全部会话') }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineExpose, watch } from 'vue'; // Import watch
|
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineExpose, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia'; // Import storeToRefs
|
import { storeToRefs } from 'pinia';
|
||||||
import { useQuickCommandsStore, type QuickCommandFE, type QuickCommandSortByType, type GroupedQuickCommands } from '../stores/quickCommands.store'; // Import GroupedQuickCommands
|
import { useQuickCommandsStore, type QuickCommandFE, type QuickCommandSortByType, type GroupedQuickCommands } from '../stores/quickCommands.store';
|
||||||
import { useQuickCommandTagsStore } from '../stores/quickCommandTags.store'; // +++ Import the new tag store +++
|
import { useQuickCommandTagsStore } from '../stores/quickCommandTags.store';
|
||||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import AddEditQuickCommandForm from '../components/AddEditQuickCommandForm.vue'; // 导入表单组件
|
import AddEditQuickCommandForm from '../components/AddEditQuickCommandForm.vue';
|
||||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 导入焦点切换 Store +++
|
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
|
||||||
import { useSettingsStore } from '../stores/settings.store'; // 新增:导入设置 store
|
import { useSettingsStore } from '../stores/settings.store';
|
||||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents'; // +++ 新增导入 +++
|
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents';
|
||||||
|
import { useSessionStore } from '../stores/session.store';
|
||||||
|
import type { SessionState } from '../stores/session/types';
|
||||||
|
import { useConnectionsStore } from '../stores/connections.store';
|
||||||
|
|
||||||
const quickCommandsStore = useQuickCommandsStore();
|
const quickCommandsStore = useQuickCommandsStore();
|
||||||
const quickCommandTagsStore = useQuickCommandTagsStore(); // +++ Instantiate the new tag store +++
|
const quickCommandTagsStore = useQuickCommandTagsStore();
|
||||||
const uiNotificationsStore = useUiNotificationsStore(); // 如果需要显示通知
|
const uiNotificationsStore = useUiNotificationsStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
|
const focusSwitcherStore = useFocusSwitcherStore();
|
||||||
const settingsStore = useSettingsStore(); // 新增:实例化设置 store
|
const settingsStore = useSettingsStore();
|
||||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
const emitWorkspaceEvent = useWorkspaceEventEmitter();
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
const connectionsStore = useConnectionsStore();
|
||||||
|
|
||||||
const hoveredItemId = ref<number | null>(null);
|
const hoveredItemId = ref<number | null>(null);
|
||||||
const isFormVisible = ref(false);
|
const isFormVisible = ref(false);
|
||||||
const commandToEdit = ref<QuickCommandFE | null>(null);
|
const commandToEdit = ref<QuickCommandFE | null>(null);
|
||||||
// const selectedIndex = ref<number>(-1); // REMOVED: Use store's selectedIndex
|
|
||||||
const commandListContainerRef = ref<HTMLDivElement | null>(null); // Changed ref name to match template
|
const commandListContainerRef = ref<HTMLDivElement | null>(null); // Changed ref name to match template
|
||||||
const searchInputRef = ref<HTMLInputElement | null>(null); // +++ Ref for the search input +++
|
const searchInputRef = ref<HTMLInputElement | null>(null); // +++ Ref for the search input +++
|
||||||
let unregisterFocus: (() => void) | null = null; // +++ 保存注销函数 +++
|
let unregisterFocus: (() => void) | null = null; // +++ 保存注销函数 +++
|
||||||
@@ -189,18 +213,23 @@ const editingTagId = ref<number | null | 'untagged'>(null);
|
|||||||
const editedTagName = ref('');
|
const editedTagName = ref('');
|
||||||
const tagInputRefs = ref(new Map<string | number, HTMLInputElement | null>());
|
const tagInputRefs = ref(new Map<string | number, HTMLInputElement | null>());
|
||||||
|
|
||||||
|
// +++ 右键菜单状态 +++
|
||||||
|
const quickCommandContextMenuVisible = ref(false);
|
||||||
|
const quickCommandContextMenuPosition = ref({ x: 0, y: 0 });
|
||||||
|
const quickCommandContextTargetCommand = ref<QuickCommandFE | null>(null);
|
||||||
|
|
||||||
// --- 从 Store 获取状态和 Getter ---
|
// --- 从 Store 获取状态和 Getter ---
|
||||||
const searchTerm = computed(() => quickCommandsStore.searchTerm);
|
const searchTerm = computed(() => quickCommandsStore.searchTerm);
|
||||||
const sortBy = computed(() => quickCommandsStore.sortBy);
|
const sortBy = computed(() => quickCommandsStore.sortBy);
|
||||||
// Use the new grouped getter
|
// Use the new grouped getter
|
||||||
const filteredAndGroupedCommands = computed(() => quickCommandsStore.filteredAndGroupedCommands);
|
const filteredAndGroupedCommands = computed(() => quickCommandsStore.filteredAndGroupedCommands);
|
||||||
const isLoading = computed(() => quickCommandsStore.isLoading);
|
const isLoading = computed(() => quickCommandsStore.isLoading);
|
||||||
// selectedIndex now refers to the index within the flatVisibleCommands list
|
|
||||||
// Also get expandedGroups reactively for the template
|
|
||||||
const { selectedIndex: storeSelectedIndex, flatVisibleCommands, expandedGroups } = storeToRefs(quickCommandsStore);
|
|
||||||
const { showQuickCommandTagsBoolean } = storeToRefs(settingsStore); // 新增:获取设置项
|
|
||||||
|
|
||||||
// 新增:计算属性,仅过滤和排序,不分组
|
|
||||||
|
const { selectedIndex: storeSelectedIndex, flatVisibleCommands, expandedGroups } = storeToRefs(quickCommandsStore);
|
||||||
|
const { showQuickCommandTagsBoolean } = storeToRefs(settingsStore);
|
||||||
|
|
||||||
|
// 计算属性,仅过滤和排序,不分组
|
||||||
const flatFilteredCommands = computed(() => {
|
const flatFilteredCommands = computed(() => {
|
||||||
// 直接使用 store 中的 flatVisibleCommands,因为它已经处理了过滤和排序
|
// 直接使用 store 中的 flatVisibleCommands,因为它已经处理了过滤和排序
|
||||||
return quickCommandsStore.flatVisibleCommands;
|
return quickCommandsStore.flatVisibleCommands;
|
||||||
@@ -525,4 +554,47 @@ const cancelEditingTag = () => {
|
|||||||
editingTagId.value = null;
|
editingTagId.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// +++ 右键菜单方法 +++
|
||||||
|
const showQuickCommandContextMenu = (event: MouseEvent, command: QuickCommandFE) => {
|
||||||
|
event.preventDefault();
|
||||||
|
quickCommandContextTargetCommand.value = command;
|
||||||
|
quickCommandContextMenuPosition.value = { x: event.clientX, y: event.clientY };
|
||||||
|
quickCommandContextMenuVisible.value = true;
|
||||||
|
document.addEventListener('click', closeQuickCommandContextMenu, { once: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeQuickCommandContextMenu = () => {
|
||||||
|
quickCommandContextMenuVisible.value = false;
|
||||||
|
quickCommandContextTargetCommand.value = null;
|
||||||
|
document.removeEventListener('click', closeQuickCommandContextMenu);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuickCommandMenuAction = (action: 'sendToAllSessions', command: QuickCommandFE) => {
|
||||||
|
closeQuickCommandContextMenu();
|
||||||
|
if (action === 'sendToAllSessions') {
|
||||||
|
const activeSshSessions = Array.from(sessionStore.sessions.values()).filter(
|
||||||
|
(s: SessionState) => {
|
||||||
|
if (s.wsManager.connectionStatus.value !== 'connected') return false;
|
||||||
|
const connInfo = connectionsStore.connections.find(c => c.id === Number(s.connectionId));
|
||||||
|
return connInfo?.type === 'SSH';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeSshSessions.length > 0) {
|
||||||
|
activeSshSessions.forEach((session: SessionState) => {
|
||||||
|
emitWorkspaceEvent('terminal:sendCommand', { sessionId: session.sessionId, command: command.command });
|
||||||
|
});
|
||||||
|
uiNotificationsStore.addNotification({
|
||||||
|
message: t('quickCommands.notifications.sentToAllSessions', { count: activeSshSessions.length }),
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uiNotificationsStore.addNotification({
|
||||||
|
message: t('quickCommands.notifications.noActiveSshSessions'),
|
||||||
|
type: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ onMounted(() => {
|
|||||||
// 确保布局已初始化 (layoutStore 内部会处理)
|
// 确保布局已初始化 (layoutStore 内部会处理)
|
||||||
|
|
||||||
// +++ 订阅工作区事件 +++
|
// +++ 订阅工作区事件 +++
|
||||||
subscribeToWorkspaceEvents('terminal:sendCommand', (payload) => handleSendCommand(payload.command));
|
subscribeToWorkspaceEvents('terminal:sendCommand', (payload) => handleSendCommand(payload.command, payload.sessionId));
|
||||||
subscribeToWorkspaceEvents('terminal:input', handleTerminalInput);
|
subscribeToWorkspaceEvents('terminal:input', handleTerminalInput);
|
||||||
subscribeToWorkspaceEvents('terminal:resize', handleTerminalResize);
|
subscribeToWorkspaceEvents('terminal:resize', handleTerminalResize);
|
||||||
subscribeToWorkspaceEvents('terminal:ready', handleTerminalReady);
|
subscribeToWorkspaceEvents('terminal:ready', handleTerminalReady);
|
||||||
@@ -162,7 +162,7 @@ onBeforeUnmount(() => {
|
|||||||
sessionStore.cleanupAllSessions();
|
sessionStore.cleanupAllSessions();
|
||||||
|
|
||||||
// +++ 取消订阅工作区事件 +++
|
// +++ 取消订阅工作区事件 +++
|
||||||
unsubscribeFromWorkspaceEvents('terminal:sendCommand', (payload) => handleSendCommand(payload.command));
|
unsubscribeFromWorkspaceEvents('terminal:sendCommand', (payload) => handleSendCommand(payload.command, payload.sessionId));
|
||||||
unsubscribeFromWorkspaceEvents('terminal:input', handleTerminalInput);
|
unsubscribeFromWorkspaceEvents('terminal:input', handleTerminalInput);
|
||||||
unsubscribeFromWorkspaceEvents('terminal:resize', handleTerminalResize);
|
unsubscribeFromWorkspaceEvents('terminal:resize', handleTerminalResize);
|
||||||
unsubscribeFromWorkspaceEvents('terminal:ready', handleTerminalReady);
|
unsubscribeFromWorkspaceEvents('terminal:ready', handleTerminalReady);
|
||||||
@@ -237,42 +237,45 @@ const unsubscribeFromWorkspaceEvents = useWorkspaceEventOff();
|
|||||||
// --- 事件处理 (传递给 LayoutRenderer 或直接使用) ---
|
// --- 事件处理 (传递给 LayoutRenderer 或直接使用) ---
|
||||||
|
|
||||||
// 处理命令发送 (用于 CommandBar, CommandHistory, QuickCommands)
|
// 处理命令发送 (用于 CommandBar, CommandHistory, QuickCommands)
|
||||||
const handleSendCommand = (command: string) => {
|
const handleSendCommand = (command: string, targetSessionId?: string) => {
|
||||||
const currentSession = activeSession.value;
|
const sessionToCommand = targetSessionId ? sessionStore.sessions.get(targetSessionId) : activeSession.value;
|
||||||
if (!currentSession) {
|
|
||||||
console.warn('[WorkspaceView] Cannot send command, no active session.');
|
if (!sessionToCommand) {
|
||||||
|
const idForLog = targetSessionId || 'active (none found)';
|
||||||
|
console.warn(`[WorkspaceView] Cannot send command, no session found for ID: ${idForLog}.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const terminalManager = currentSession.terminalManager as (SshTerminalInstance | undefined);
|
const terminalManager = sessionToCommand.terminalManager as (SshTerminalInstance | undefined);
|
||||||
|
|
||||||
if (terminalManager?.isSshConnected && !terminalManager.isSshConnected.value && command.trim() === '') {
|
if (terminalManager?.isSshConnected && !terminalManager.isSshConnected.value && command.trim() === '') {
|
||||||
console.log(`[WorkspaceView] Command bar Enter detected in disconnected session ${currentSession.sessionId}, attempting reconnect...`);
|
console.log(`[WorkspaceView] Command bar Enter detected in disconnected session ${sessionToCommand.sessionId}, attempting reconnect...`);
|
||||||
if (terminalManager.terminalInstance?.value) {
|
if (terminalManager.terminalInstance?.value) {
|
||||||
terminalManager.terminalInstance.value.writeln(`\r\n\x1b[33m${t('workspace.terminal.reconnectingMsg')}\x1b[0m`);
|
terminalManager.terminalInstance.value.writeln(`\r\n\x1b[33m${t('workspace.terminal.reconnectingMsg')}\x1b[0m`);
|
||||||
}
|
}
|
||||||
// +++ 修复:传递 ConnectionInfo 而不是 ID +++
|
const connectionInfo = connectionsStore.connections.find(c => c.id === Number(sessionToCommand.connectionId));
|
||||||
const connectionInfo = connectionsStore.connections.find(c => c.id === Number(currentSession.connectionId));
|
|
||||||
if (connectionInfo) {
|
if (connectionInfo) {
|
||||||
sessionStore.handleConnectRequest(connectionInfo);
|
sessionStore.handleConnectRequest(connectionInfo);
|
||||||
} else {
|
} else {
|
||||||
console.error(`[WorkspaceView] handleSendCommand: 未找到 ID 为 ${currentSession.connectionId} 的连接信息。`);
|
console.error(`[WorkspaceView] handleSendCommand: 未找到 ID 为 ${sessionToCommand.connectionId} 的连接信息。`);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (terminalManager && typeof terminalManager.sendData === 'function') {
|
if (terminalManager && typeof terminalManager.sendData === 'function') {
|
||||||
const commandToSend = command.trim();
|
const commandToSend = command.trim(); // Keep trimmed for history
|
||||||
console.log(`[WorkspaceView] Sending command/data to active session ${currentSession.sessionId}: ${JSON.stringify(command)}`); // Log raw command
|
console.log(`[WorkspaceView] Sending command/data to session ${sessionToCommand.sessionId}: ${JSON.stringify(command)}`); // Log raw command
|
||||||
// Only append '\r' for regular commands, not for control characters like Ctrl+C (\x03)
|
// Only append '\r' for regular commands, not for control characters like Ctrl+C (\x03)
|
||||||
|
// Send the raw command as received by the function for control characters
|
||||||
const dataToSend = command === '\x03' ? command : command + '\r';
|
const dataToSend = command === '\x03' ? command : command + '\r';
|
||||||
terminalManager.sendData(dataToSend);
|
terminalManager.sendData(dataToSend);
|
||||||
|
|
||||||
// Add to history only if it's a user-typed command (not just Enter or control chars)
|
// Add to history only if it's a user-typed command (not just Enter or control chars)
|
||||||
const commandToHistory = command.trim();
|
// And only if the command is being sent to the active session (to avoid polluting history from "send to all")
|
||||||
if (commandToHistory.length > 0 && command !== '\x03') {
|
if (commandToSend.length > 0 && command !== '\x03' && sessionToCommand.sessionId === activeSessionId.value) {
|
||||||
commandHistoryStore.addCommand(commandToSend);
|
commandHistoryStore.addCommand(commandToSend);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[WorkspaceView] Cannot send command for session ${currentSession.sessionId}, terminal manager or sendData method not available.`);
|
console.warn(`[WorkspaceView] Cannot send command for session ${sessionToCommand.sessionId}, terminal manager or sendData method not available.`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user