feat: 为连接列表标签组添加右键菜单及“连接全部”功能

Refs #28
This commit is contained in:
Baobhan Sith
2025-05-11 09:27:49 +08:00
parent 6fba0d1881
commit 54150e5a79
4 changed files with 105 additions and 4 deletions
@@ -38,6 +38,11 @@ const contextMenuVisible = ref(false);
const contextMenuPosition = ref({ x: 0, y: 0 }); const contextMenuPosition = ref({ x: 0, y: 0 });
const contextTargetConnection = ref<ConnectionInfo | null>(null); const contextTargetConnection = ref<ConnectionInfo | null>(null);
// 标签右键菜单状态
const tagContextMenuVisible = ref(false);
const tagContextMenuPosition = ref({ x: 0, y: 0 });
const contextTargetTagGroup = ref<(typeof filteredAndGroupedConnections.value)[0] | null>(null);
// +++ 本地存储键名 +++ // +++ 本地存储键名 +++
const EXPANDED_GROUPS_STORAGE_KEY = 'workspaceConnectionListExpandedGroups'; const EXPANDED_GROUPS_STORAGE_KEY = 'workspaceConnectionListExpandedGroups';
@@ -380,6 +385,50 @@ const handleMenuAction = (action: 'add' | 'edit' | 'delete' | 'clone') => { //
} }
}; };
// 显示标签右键菜单
const showTagContextMenu = (event: MouseEvent, groupData: (typeof filteredAndGroupedConnections.value)[0]) => {
event.preventDefault();
event.stopPropagation(); // 阻止事件冒泡到上层,例如关闭连接右键菜单的 document click listener
closeContextMenu(); // 如果连接的右键菜单是打开的,先关闭它
contextTargetTagGroup.value = groupData;
tagContextMenuPosition.value = { x: event.clientX, y: event.clientY };
tagContextMenuVisible.value = true;
// 添加全局点击监听器以关闭菜单
document.addEventListener('click', closeTagContextMenu, { once: true });
};
// 关闭标签右键菜单
const closeTagContextMenu = () => {
tagContextMenuVisible.value = false;
// contextTargetTagGroup.value = null; // 保留 targetGroup 直到菜单完全消失,以便动画(如果未来添加)
document.removeEventListener('click', closeTagContextMenu);
};
// 处理标签右键菜单操作
const handleTagMenuAction = (action: 'connectAll') => {
const group = contextTargetTagGroup.value;
closeTagContextMenu(); // 先关闭菜单
if (group && action === 'connectAll') {
const sshConnections = group.connections.filter(conn => conn.type === 'SSH');
if (sshConnections.length > 0) {
sshConnections.forEach(conn => {
emitWorkspaceEvent('connection:connect', { connectionId: conn.id });
});
uiNotificationsStore.addNotification({
message: t('workspaceConnectionList.connectingAllSshInGroup', { count: sshConnections.length, groupName: group.groupName }),
type: 'info',
});
} else {
uiNotificationsStore.addNotification({
message: t('workspaceConnectionList.noSshConnectionsInGroup', { groupName: group.groupName }),
type: 'info',
});
}
}
};
// 稍微延迟一下重置,以防是点击列表项导致的失焦 // 稍微延迟一下重置,以防是点击列表项导致的失焦
// 如果用户点击了列表项,handleConnect 会先触发 // 如果用户点击了列表项,handleConnect 会先触发
setTimeout(() => { setTimeout(() => {
@@ -426,7 +475,7 @@ onBeforeUnmount(() => {
// 处理中键点击(在新标签页打开) - 功能已移除 // 处理中键点击(在新标签页打开) - 功能已移除
// 新增:暴露聚焦搜索框的方法 // 暴露聚焦搜索框的方法
const focusSearchInput = (): boolean => { const focusSearchInput = (): boolean => {
if (searchInputRef.value) { if (searchInputRef.value) {
searchInputRef.value.focus(); searchInputRef.value.focus();
@@ -634,6 +683,7 @@ const cancelEditingTag = () => {
class="group px-3 py-2 font-semibold flex items-center text-foreground rounded-md hover:bg-header/80 transition-colors duration-150" class="group px-3 py-2 font-semibold flex items-center text-foreground rounded-md hover:bg-header/80 transition-colors duration-150"
:class="{ 'cursor-pointer': editingTagId !== (groupData.tagId === null ? 'untagged' : groupData.tagId) }" :class="{ 'cursor-pointer': editingTagId !== (groupData.tagId === null ? 'untagged' : groupData.tagId) }"
@click="editingTagId !== (groupData.tagId === null ? 'untagged' : groupData.tagId) ? toggleGroup(groupData.groupName) : null" @click="editingTagId !== (groupData.tagId === null ? 'untagged' : groupData.tagId) ? toggleGroup(groupData.groupName) : null"
@contextmenu.prevent="showTagContextMenu($event, groupData)"
> >
<i <i
:class="['fas', expandedGroups[groupData.groupName] ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 w-4 text-center text-text-secondary group-hover:text-foreground transition-transform duration-200 ease-in-out', {'transform rotate-0': !expandedGroups[groupData.groupName]}]" :class="['fas', expandedGroups[groupData.groupName] ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 w-4 text-center text-text-secondary group-hover:text-foreground transition-transform duration-200 ease-in-out', {'transform rotate-0': !expandedGroups[groupData.groupName]}]"
@@ -736,6 +786,33 @@ const cancelEditingTag = () => {
</ul> </ul>
</div> </div>
<!-- 标签右键菜单 -->
<div
v-if="tagContextMenuVisible"
class="fixed bg-background border border-border/50 shadow-xl rounded-lg py-1.5 z-50 min-w-[200px]"
:style="{ top: `${tagContextMenuPosition.y}px`, left: `${tagContextMenuPosition.x}px` }"
@click.stop
>
<ul class="list-none p-0 m-0">
<li
v-if="contextTargetTagGroup && contextTargetTagGroup.connections.some((c: ConnectionInfo) => c.type === 'SSH')"
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="handleTagMenuAction('connectAll')"
>
<i class="fas fa-network-wired mr-3 w-4 text-center text-text-secondary group-hover:text-primary"></i>
<span>{{ t('workspaceConnectionList.connectAllSshInGroupMenu') }}</span>
</li>
<li
v-else-if="contextTargetTagGroup"
class="group px-4 py-1.5 flex items-center text-text-disabled text-sm rounded-md mx-1 cursor-not-allowed"
>
<i class="fas fa-ban mr-3 w-4 text-center text-text-disabled"></i>
<span>{{ t('workspaceConnectionList.noSshConnectionsToConnectMenu') }}</span>
</li>
<!-- Future: Add "Rename Tag" or "Delete Tag (if empty)" options here -->
</ul>
</div>
<!-- --- 移除 RDP Modal 渲染 --- --> <!-- --- 移除 RDP Modal 渲染 --- -->
<!-- <RemoteDesktopModal <!-- <RemoteDesktopModal
v-if="showRdpModal" v-if="showRdpModal"
+9 -1
View File
@@ -920,7 +920,15 @@
"noResults": "No connections found matching \"{searchTerm}\".", "noResults": "No connections found matching \"{searchTerm}\".",
"allConnectionsTaggedSuccess": "All connections tagged successfully.", "allConnectionsTaggedSuccess": "All connections tagged successfully.",
"noConnectionsToTag": "No connections to tag.", "noConnectionsToTag": "No connections to tag.",
"clickToEditTag": "Click to edit tag name" "clickToEditTag": "Click to edit tag name",
"connectAllInGroup": "Connect All in Group (SSH)",
"connectingAllInGroup": "Connecting all in group '{groupName}'...",
"noConnectionsInGroup": "No connections to connect in group '{groupName}'.",
"noConnectionsToConnect": "No connections to connect",
"connectingAllSshInGroup": "Connecting {count} SSH connections in group '{groupName}'...",
"noSshConnectionsInGroup": "No SSH connections to connect in group '{groupName}'.",
"connectAllSshInGroupMenu": "Connect All",
"noSshConnectionsToConnectMenu": "No SSH Connections"
}, },
"remoteDesktopModal": { "remoteDesktopModal": {
"title": "Remote Desktop", "title": "Remote Desktop",
+9 -1
View File
@@ -1125,7 +1125,15 @@
"untagged": "タグなし", "untagged": "タグなし",
"allConnectionsTaggedSuccess": "すべての接続にタグが正常に追加されました。", "allConnectionsTaggedSuccess": "すべての接続にタグが正常に追加されました。",
"noConnectionsToTag": "タグ付けする接続はありません。", "noConnectionsToTag": "タグ付けする接続はありません。",
"clickToEditTag": "クリックしてタグ名を編集" "clickToEditTag": "クリックしてタグ名を編集",
"connectAllInGroup": "グループ内のすべてに接続 (SSH)",
"connectingAllInGroup": "グループ '{groupName}' 内のすべてに接続しています...",
"noConnectionsInGroup": "グループ '{groupName}' 内に接続可能な項目がありません。",
"noConnectionsToConnect": "接続可能な項目がありません",
"connectingAllSshInGroup": "グループ '{groupName}' 内の {count} 個の SSH 接続に接続しています...",
"noSshConnectionsInGroup": "グループ '{groupName}' 内に接続可能な SSH 接続がありません。",
"connectAllSshInGroupMenu": "すべて接続",
"noSshConnectionsToConnectMenu": "SSH 接続なし"
}, },
"sshKeys": { "sshKeys": {
"selector": { "selector": {
+9 -1
View File
@@ -922,7 +922,15 @@
"noResults": "未找到匹配 \"{searchTerm}\" 的连接。", "noResults": "未找到匹配 \"{searchTerm}\" 的连接。",
"allConnectionsTaggedSuccess": "所有连接已成功添加标签。", "allConnectionsTaggedSuccess": "所有连接已成功添加标签。",
"noConnectionsToTag": "没有需要添加标签的连接。", "noConnectionsToTag": "没有需要添加标签的连接。",
"clickToEditTag": "点击编辑标签名称" "clickToEditTag": "点击编辑标签名称",
"connectAllInGroup": "连接组内全部 (SSH)",
"connectingAllInGroup": "正在连接组 '{groupName}' 中的所有连接...",
"noConnectionsInGroup": "组 '{groupName}' 中没有可连接的项。",
"noConnectionsToConnect": "没有可连接的项",
"connectingAllSshInGroup": "正在连接组 '{groupName}' 中的 {count} 个 SSH 连接...",
"noSshConnectionsInGroup": "组 '{groupName}' 中没有 SSH 类型的连接可供连接。",
"connectAllSshInGroupMenu": "连接全部",
"noSshConnectionsToConnectMenu": "无 SSH 连接"
}, },
"remoteDesktopModal": { "remoteDesktopModal": {
"title": "远程桌面", "title": "远程桌面",