feat: 在连接列表右键菜单添加克隆功能
This commit is contained in:
@@ -210,7 +210,7 @@ const closeContextMenu = () => {
|
||||
};
|
||||
|
||||
// 处理右键菜单操作
|
||||
const handleMenuAction = (action: 'add' | 'edit' | 'delete') => {
|
||||
const handleMenuAction = (action: 'add' | 'edit' | 'delete' | 'clone') => { // 添加 'clone' 类型
|
||||
const conn = contextTargetConnection.value;
|
||||
closeContextMenu(); // 先关闭菜单
|
||||
|
||||
@@ -227,6 +227,30 @@ const handleMenuAction = (action: 'add' | 'edit' | 'delete') => {
|
||||
connectionsStore.deleteConnection(conn.id);
|
||||
// 注意:删除后列表会自动更新,因为 store 是响应式的
|
||||
}
|
||||
} else if (action === 'clone') {
|
||||
// 调用 store 中的 cloneConnection 方法
|
||||
// 需要先生成新名称
|
||||
const allConnections = connectionsStore.connections;
|
||||
let newName = `${conn.name} (1)`;
|
||||
let counter = 1;
|
||||
const baseName = conn.name;
|
||||
const escapedBaseName = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`^${escapedBaseName} \\((\\d+)\\)$`);
|
||||
|
||||
while (allConnections.some(c => c.name === newName)) {
|
||||
counter++;
|
||||
newName = `${baseName} (${counter})`;
|
||||
}
|
||||
if (counter === 1 && allConnections.some(c => c.name === baseName)) {
|
||||
// 处理原始名称已存在的情况
|
||||
}
|
||||
|
||||
connectionsStore.cloneConnection(conn.id, newName)
|
||||
.catch(error => {
|
||||
// 可以在这里处理克隆失败的特定 UI 反馈,如果需要的话
|
||||
console.error("Cloning failed in component:", error);
|
||||
// alert(t('connections.errors.cloneFailed', { error: connectionsStore.error || '未知错误' })); // store 中已有错误处理
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -424,6 +448,10 @@ const scrollToHighlighted = async () => {
|
||||
<i class="fas fa-edit mr-3 w-4 text-center text-text-secondary group-hover:text-primary"></i>
|
||||
<span>{{ t('connections.actions.edit') }}</span>
|
||||
</li>
|
||||
<li v-if="contextTargetConnection" 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="handleMenuAction('clone')">
|
||||
<i class="fas fa-clone mr-3 w-4 text-center text-text-secondary group-hover:text-primary"></i>
|
||||
<span>{{ t('connections.actions.clone') }}</span>
|
||||
</li>
|
||||
<li v-if="contextTargetConnection" class="group px-4 py-1.5 cursor-pointer flex items-center text-error hover:bg-error/10 text-sm transition-colors duration-150 rounded-md mx-1" @click="handleMenuAction('delete')">
|
||||
<i class="fas fa-trash-alt mr-3 w-4 text-center text-error/80 group-hover:text-error"></i>
|
||||
<span>{{ t('connections.actions.delete') }}</span>
|
||||
|
||||
@@ -126,7 +126,8 @@
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"test": "Test",
|
||||
"testing": "Testing..."
|
||||
"testing": "Testing...",
|
||||
"clone": "Clone"
|
||||
},
|
||||
"form": {
|
||||
"title": "Add New Connection",
|
||||
@@ -186,7 +187,9 @@
|
||||
"confirmDelete": "Are you sure you want to delete the connection \"{name}\"? This cannot be undone."
|
||||
},
|
||||
"errors": {
|
||||
"deleteFailed": "Failed to delete connection: {error}"
|
||||
"deleteFailed": "Failed to delete connection: {error}",
|
||||
"createFailed": "Failed to add connection: {error}",
|
||||
"cloneFailed": "Failed to clone connection: {error}"
|
||||
},
|
||||
"status": {
|
||||
"never": "Never"
|
||||
|
||||
@@ -93,14 +93,17 @@
|
||||
"delete": "削除",
|
||||
"edit": "編集",
|
||||
"test": "テスト",
|
||||
"testing": "テスト中..."
|
||||
},
|
||||
"addConnection": "新しい接続を追加",
|
||||
"addFirstConnection": "最初の接続を追加",
|
||||
"testing": "テスト中...",
|
||||
"clone": "クローン"
|
||||
},
|
||||
"addConnection": "新しい接続を追加",
|
||||
"addFirstConnection": "最初の接続を追加",
|
||||
"errors": {
|
||||
"deleteFailed": "接続の削除に失敗しました: {error}"
|
||||
},
|
||||
"form": {
|
||||
"deleteFailed": "接続の削除に失敗しました: {error}",
|
||||
"createFailed": "接続の追加に失敗しました: {error}",
|
||||
"cloneFailed": "接続のクローン作成に失敗しました: {error}"
|
||||
},
|
||||
"form": {
|
||||
"adding": "追加中...",
|
||||
"authMethod": "認証方法:",
|
||||
"authMethodKey": "SSHキー",
|
||||
|
||||
@@ -126,7 +126,8 @@
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"test": "测试",
|
||||
"testing": "测试中..."
|
||||
"testing": "测试中...",
|
||||
"clone": "克隆"
|
||||
},
|
||||
"form": {
|
||||
"title": "添加新连接",
|
||||
@@ -186,7 +187,9 @@
|
||||
"confirmDelete": "确定要删除连接 \"{name}\" 吗?此操作不可撤销。"
|
||||
},
|
||||
"errors": {
|
||||
"deleteFailed": "删除连接失败: {error}"
|
||||
"deleteFailed": "删除连接失败: {error}",
|
||||
"createFailed": "添加连接失败: {error}",
|
||||
"cloneFailed": "克隆连接失败: {error}"
|
||||
},
|
||||
"status": {
|
||||
"never": "从未"
|
||||
|
||||
@@ -207,5 +207,31 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
// this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 新增:克隆连接 Action (调用后端克隆接口)
|
||||
async cloneConnection(originalId: number, newName: string): Promise<boolean> {
|
||||
this.isLoading = true; // 可以考虑为克隆操作设置单独的加载状态
|
||||
this.error = null;
|
||||
try {
|
||||
// 调用后端的克隆接口,例如 POST /connections/:id/clone
|
||||
// 请求体可以包含新名称等信息
|
||||
// 假设后端接口需要 { name: newName } 作为请求体
|
||||
await apiClient.post(`/connections/${originalId}/clone`, { name: newName });
|
||||
|
||||
// 克隆成功后,清除缓存并重新获取列表以显示新连接
|
||||
localStorage.removeItem('connectionsCache');
|
||||
await this.fetchConnections(); // 重新获取以保证数据一致性
|
||||
return true; // 表示成功
|
||||
} catch (err: any) {
|
||||
console.error(`克隆连接 ${originalId} 失败:`, err);
|
||||
this.error = err.response?.data?.message || err.message || `克隆连接时发生未知错误。`;
|
||||
if (err.response?.status === 401) {
|
||||
console.warn('未授权,需要登录才能克隆连接。');
|
||||
}
|
||||
return false; // 表示失败
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user