update
This commit is contained in:
@@ -62,6 +62,7 @@ import { useQuickCommandsStore, type QuickCommandFE } from '../stores/quickComma
|
|||||||
import { useQuickCommandTagsStore } from '../stores/quickCommandTags.store';
|
import { useQuickCommandTagsStore } from '../stores/quickCommandTags.store';
|
||||||
import TagInput from './TagInput.vue';
|
import TagInput from './TagInput.vue';
|
||||||
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
||||||
|
import { useAlertDialog } from '../composables/useAlertDialog'; // +++ 导入 useAlertDialog +++
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
commandToEdit?: QuickCommandFE | null; // 接收要编辑的指令对象 (should include tagIds)
|
commandToEdit?: QuickCommandFE | null; // 接收要编辑的指令对象 (should include tagIds)
|
||||||
@@ -71,6 +72,7 @@ const emit = defineEmits(['close']);
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { showConfirmDialog } = useConfirmDialog();
|
const { showConfirmDialog } = useConfirmDialog();
|
||||||
|
const { showAlertDialog } = useAlertDialog(); // +++ 获取 showAlertDialog 函数 +++
|
||||||
const quickCommandsStore = useQuickCommandsStore();
|
const quickCommandsStore = useQuickCommandsStore();
|
||||||
const quickCommandTagsStore = useQuickCommandTagsStore(); // +++ Instantiate tag store +++
|
const quickCommandTagsStore = useQuickCommandTagsStore(); // +++ Instantiate tag store +++
|
||||||
const isSubmitting = ref(false);
|
const isSubmitting = ref(false);
|
||||||
@@ -144,8 +146,7 @@ const handleDeleteTag = async (tagId: number) => {
|
|||||||
formData.tagIds.splice(index, 1);
|
formData.tagIds.splice(index, 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Optional: Show error notification if deletion fails
|
showAlertDialog({ title: t('common.error', '错误'), message: t('tags.errorDelete', { error: quickCommandTagsStore.error || '未知错误' }) });
|
||||||
alert(t('tags.errorDelete', { error: quickCommandTagsStore.error || '未知错误' }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store';
|
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store';
|
||||||
import { useTagsStore } from '../stores/tags.store';
|
import { useTagsStore } from '../stores/tags.store';
|
||||||
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
||||||
|
import { useAlertDialog } from '../composables/useAlertDialog';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const tagsStore = useTagsStore();
|
const tagsStore = useTagsStore();
|
||||||
const { showConfirmDialog } = useConfirmDialog();
|
const { showConfirmDialog } = useConfirmDialog();
|
||||||
|
const { showAlertDialog } = useAlertDialog();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -132,11 +134,9 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
|||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
const success = await connectionsStore.deleteConnection(conn.id);
|
const success = await connectionsStore.deleteConnection(conn.id);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// 如果删除失败,显示 store 中的错误信息 (或自定义错误)
|
|
||||||
// 可以考虑使用更友好的提示方式,例如 toast 通知库
|
showAlertDialog({ title: t('common.error'), message: t('connections.errors.deleteFailed', { error: connectionsStore.error || '未知错误' }) });
|
||||||
alert(t('connections.errors.deleteFailed', { error: connectionsStore.error || '未知错误' }));
|
|
||||||
}
|
}
|
||||||
// 成功时列表会自动更新,无需额外操作
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,9 +149,9 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
|||||||
|
|
||||||
// 显示测试结果
|
// 显示测试结果
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
alert(t('connections.test.success'));
|
showAlertDialog({ title: t('common.success'), message: t('connections.test.success') });
|
||||||
} else {
|
} else {
|
||||||
alert(t('connections.test.failed', { error: result.message || '未知错误' }));
|
showAlertDialog({ title: t('common.error'), message: t('connections.test.failed', { error: result.message || '未知错误' }) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -455,7 +455,6 @@ const handleItemAction = (item: FileListItem) => {
|
|||||||
|
|
||||||
if (!absolutePath) {
|
if (!absolutePath) {
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] sftp:realpath:success for ${itemPath} missing absolutePath. Payload:`, payload);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] sftp:realpath:success for ${itemPath} missing absolutePath. Payload:`, payload);
|
||||||
alert(`Failed to resolve symbolic link "${item.filename}": Server did not return a valid path.`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!targetType) {
|
if (!targetType) {
|
||||||
@@ -475,14 +474,12 @@ const handleItemAction = (item: FileListItem) => {
|
|||||||
const resolvedPathInfo = payload.absolutePath ? ` (Resolved path: ${payload.absolutePath})` : '';
|
const resolvedPathInfo = payload.absolutePath ? ` (Resolved path: ${payload.absolutePath})` : '';
|
||||||
|
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to get realpath or target type for symlink '${itemPath}': ${serverErrorMsg}${resolvedPathInfo}`);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to get realpath or target type for symlink '${itemPath}': ${serverErrorMsg}${resolvedPathInfo}`);
|
||||||
alert(`Failed to resolve symbolic link "${item.filename}": ${serverErrorMsg}.${resolvedPathInfo} Please ensure the target exists and you have permissions.`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
cleanupListeners();
|
cleanupListeners();
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Timeout getting realpath for symlink '${itemPath}' (ID: ${requestId}).`);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Timeout getting realpath for symlink '${itemPath}' (ID: ${requestId}).`);
|
||||||
alert(`Timeout resolving symbolic link "${item.filename}".`);
|
|
||||||
}, 10000); // 10 秒超时
|
}, 10000); // 10 秒超时
|
||||||
wsSend({ type: 'sftp:realpath', requestId: requestId, payload: { path: itemPath } });
|
wsSend({ type: 'sftp:realpath', requestId: requestId, payload: { path: itemPath } });
|
||||||
return; // Handled by async callbacks
|
return; // Handled by async callbacks
|
||||||
@@ -620,13 +617,7 @@ const handleModalConfirm = (value?: string) => {
|
|||||||
case 'newFile':
|
case 'newFile':
|
||||||
if (value) {
|
if (value) {
|
||||||
if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
|
if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
|
||||||
// alert(t('fileManager.errors.fileExists', { name: value })); // Consider using modal for this error too
|
|
||||||
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] File ${value} already exists. Modal should prevent this.`);
|
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] File ${value} already exists. Modal should prevent this.`);
|
||||||
// Re-open modal or show error in modal
|
|
||||||
// For now, we rely on modal's internal logic or a notification system
|
|
||||||
// To prevent closing, we can avoid calling handleModalClose here if an error occurs.
|
|
||||||
// However, the current modal design closes on confirm.
|
|
||||||
// A more robust solution would be for the modal to emit 'error' or handle validation internally.
|
|
||||||
return; // Prevent closing if error
|
return; // Prevent closing if error
|
||||||
}
|
}
|
||||||
manager.createFile(value);
|
manager.createFile(value);
|
||||||
@@ -635,7 +626,6 @@ const handleModalConfirm = (value?: string) => {
|
|||||||
case 'newFolder':
|
case 'newFolder':
|
||||||
if (value) {
|
if (value) {
|
||||||
if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
|
if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
|
||||||
// alert(t('fileManager.errors.folderExists', { name: value }));
|
|
||||||
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Folder ${value} already exists. Modal should prevent this.`);
|
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Folder ${value} already exists. Modal should prevent this.`);
|
||||||
return; // Prevent closing if error
|
return; // Prevent closing if error
|
||||||
}
|
}
|
||||||
@@ -758,20 +748,17 @@ const triggerFileUpload = () => { fileInputRef.value?.click(); };
|
|||||||
const triggerDownload = (items: FileListItem[]) => { // 修改:接受 FileListItem 数组
|
const triggerDownload = (items: FileListItem[]) => { // 修改:接受 FileListItem 数组
|
||||||
// 恢复使用 props.wsDeps.isConnected
|
// 恢复使用 props.wsDeps.isConnected
|
||||||
if (!props.wsDeps.isConnected.value) {
|
if (!props.wsDeps.isConnected.value) {
|
||||||
alert(t('fileManager.errors.notConnected'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// connectionId 仍然从 props 获取
|
// connectionId 仍然从 props 获取
|
||||||
const currentConnectionId = props.dbConnectionId;
|
const currentConnectionId = props.dbConnectionId;
|
||||||
if (!currentConnectionId) {
|
if (!currentConnectionId) {
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download: Missing connection ID.`);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download: Missing connection ID.`);
|
||||||
alert(t('fileManager.errors.missingConnectionId'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 修改:简化检查
|
// 修改:简化检查
|
||||||
if (!currentSftpManager.value) {
|
if (!currentSftpManager.value) {
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download: SFTP manager is not available.`);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download: SFTP manager is not available.`);
|
||||||
alert(t('fileManager.errors.sftpManagerNotFound'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,18 +795,15 @@ const triggerDownload = (items: FileListItem[]) => { // 修改:接受 FileList
|
|||||||
// +++ 文件夹下载触发器 +++
|
// +++ 文件夹下载触发器 +++
|
||||||
const triggerDownloadDirectory = (item: FileListItem) => {
|
const triggerDownloadDirectory = (item: FileListItem) => {
|
||||||
if (!props.wsDeps.isConnected.value) {
|
if (!props.wsDeps.isConnected.value) {
|
||||||
alert(t('fileManager.errors.notConnected'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentConnectionId = props.dbConnectionId;
|
const currentConnectionId = props.dbConnectionId;
|
||||||
if (!currentConnectionId) {
|
if (!currentConnectionId) {
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download directory: Missing connection ID.`);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download directory: Missing connection ID.`);
|
||||||
alert(t('fileManager.errors.missingConnectionId'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!currentSftpManager.value) {
|
if (!currentSftpManager.value) {
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download directory: SFTP manager is not available.`);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download directory: SFTP manager is not available.`);
|
||||||
alert(t('fileManager.errors.sftpManagerNotFound'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,16 +862,10 @@ const triggerDownloadDirectory = (item: FileListItem) => {
|
|||||||
} catch (e2) { /* ignore */}
|
} catch (e2) { /* ignore */}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 404) {
|
|
||||||
alert(t('fileManager.errors.downloadDirectoryNotImplemented', 'Directory download feature is not yet implemented on the server.'));
|
|
||||||
} else {
|
|
||||||
alert(`${t('fileManager.errors.downloadDirectoryFailed', 'Failed to download directory')}: ${errorMsg}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Network error during directory download:`, error);
|
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Network error during directory download:`, error);
|
||||||
alert(`${t('fileManager.errors.downloadDirectoryFailed', 'Failed to download directory')}: Network error.`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { storeToRefs } from 'pinia';
|
|||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
import LayoutNodeEditor from './LayoutNodeEditor.vue';
|
import LayoutNodeEditor from './LayoutNodeEditor.vue';
|
||||||
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
||||||
|
import { useAlertDialog } from '../composables/useAlertDialog';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ const layoutStore = useLayoutStore();
|
|||||||
const settingsStore = useSettingsStore(); // +++ Initialize settings store +++
|
const settingsStore = useSettingsStore(); // +++ Initialize settings store +++
|
||||||
const { layoutLockedBoolean } = storeToRefs(settingsStore); // +++ Get reactive state +++
|
const { layoutLockedBoolean } = storeToRefs(settingsStore); // +++ Get reactive state +++
|
||||||
const { showConfirmDialog } = useConfirmDialog();
|
const { showConfirmDialog } = useConfirmDialog();
|
||||||
|
const { showAlertDialog } = useAlertDialog();
|
||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
||||||
@@ -180,9 +182,6 @@ const handleLayoutLockChange = async () => { // Removed event parameter
|
|||||||
// but the button's appearance relies on layoutLockedBoolean which comes from the store.
|
// but the button's appearance relies on layoutLockedBoolean which comes from the store.
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LayoutConfigurator] Failed to update layout lock setting:', error);
|
console.error('[LayoutConfigurator] Failed to update layout lock setting:', error);
|
||||||
// Optionally show an error message
|
|
||||||
// No UI element state to revert directly here, the button state depends on layoutLockedBoolean
|
|
||||||
alert(t('layoutConfigurator.lockUpdateError', '更新布局锁定状态失败。'));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -219,8 +218,6 @@ const saveLayout = async () => { // Make async
|
|||||||
console.log('[LayoutConfigurator] Layout saved successfully, dialog closed.');
|
console.log('[LayoutConfigurator] Layout saved successfully, dialog closed.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LayoutConfigurator] Error saving layout:', error);
|
console.error('[LayoutConfigurator] Error saving layout:', error);
|
||||||
// Optionally notify the user about the error
|
|
||||||
alert(t('layoutConfigurator.saveError', '保存布局时出错,请稍后再试。')); // Keep default text for now
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { storeToRefs } from 'pinia';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useProxiesStore, ProxyInfo } from '../stores/proxies.store';
|
import { useProxiesStore, ProxyInfo } from '../stores/proxies.store';
|
||||||
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
||||||
|
import { useAlertDialog } from '../composables/useAlertDialog';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const proxiesStore = useProxiesStore();
|
const proxiesStore = useProxiesStore();
|
||||||
const { showConfirmDialog } = useConfirmDialog();
|
const { showConfirmDialog } = useConfirmDialog();
|
||||||
|
const { showAlertDialog } = useAlertDialog();
|
||||||
const { proxies, isLoading, error } = storeToRefs(proxiesStore);
|
const { proxies, isLoading, error } = storeToRefs(proxiesStore);
|
||||||
|
|
||||||
// 定义组件发出的事件
|
// 定义组件发出的事件
|
||||||
@@ -21,7 +23,7 @@ const handleDelete = async (proxy: ProxyInfo) => {
|
|||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
const success = await proxiesStore.deleteProxy(proxy.id);
|
const success = await proxiesStore.deleteProxy(proxy.id);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
alert(t('proxies.errors.deleteFailed', { error: proxiesStore.error || '未知错误' }));
|
showAlertDialog({ title: t('common.error'), message: t('proxies.errors.deleteFailed', { error: proxiesStore.error || t('common.unknownError') }) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -423,7 +423,6 @@ const handleMenuAction = async (action: 'add' | 'edit' | 'delete' | 'clone') =>
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
// 可以在这里处理克隆失败的特定 UI 反馈,如果需要的话
|
// 可以在这里处理克隆失败的特定 UI 反馈,如果需要的话
|
||||||
console.error("Cloning failed in component:", error);
|
console.error("Cloning failed in component:", error);
|
||||||
// alert(t('connections.errors.cloneFailed', { error: connectionsStore.error || '未知错误' })); // store 中已有错误处理
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onBeforeUnmount } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
okText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits(['ok', 'update:visible']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const dialogVisible = ref(props.visible);
|
||||||
|
|
||||||
|
watch(() => props.visible, (newValue) => {
|
||||||
|
dialogVisible.value = newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(dialogVisible, (newValue) => {
|
||||||
|
if (newValue !== props.visible) {
|
||||||
|
emit('update:visible', newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
emit('ok');
|
||||||
|
// 通常点击"确定"后对话框会关闭,如果store管理visible,则由store处理
|
||||||
|
// emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape' && dialogVisible.value) {
|
||||||
|
handleOk();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(dialogVisible, (isVisible) => {
|
||||||
|
if (isVisible) {
|
||||||
|
document.addEventListener('keydown', handleKeydown);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<div
|
||||||
|
v-if="dialogVisible"
|
||||||
|
class="fixed inset-0 z-[9999] flex items-center justify-center bg-overlay p-4"
|
||||||
|
@mousedown.self="handleOk"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-background text-foreground p-5 rounded-lg shadow-xl border border-border w-full max-w-md flex flex-col"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
:aria-labelledby="props.title"
|
||||||
|
>
|
||||||
|
<h3 class="text-xl font-semibold mb-4 text-center flex-shrink-0" :id="props.title">
|
||||||
|
{{ props.title }}
|
||||||
|
</h3>
|
||||||
|
<div class="flex-grow mb-6 text-sm">
|
||||||
|
<p class="text-text-secondary text-center whitespace-pre-wrap">
|
||||||
|
{{ props.message }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-3 flex-shrink-0">
|
||||||
|
<button
|
||||||
|
@click="handleOk"
|
||||||
|
type="button"
|
||||||
|
class="px-4 py-2 text-sm font-medium text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-background-dark transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center bg-primary hover:bg-primary-hover focus:ring-primary"
|
||||||
|
>
|
||||||
|
{{ props.okText || t('common.ok', '确定') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
@@ -64,7 +64,7 @@ onBeforeUnmount(() => {
|
|||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<div
|
<div
|
||||||
v-if="dialogVisible"
|
v-if="dialogVisible"
|
||||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-overlay p-4"
|
class="fixed inset-0 z-[9999] flex items-center justify-center bg-overlay p-4"
|
||||||
@mousedown.self="handleCancel"
|
@mousedown.self="handleCancel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useTagsStore } from '../stores/tags.store';
|
|||||||
import { useSshKeysStore } from '../stores/sshKeys.store';
|
import { useSshKeysStore } from '../stores/sshKeys.store';
|
||||||
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||||
import { useConfirmDialog } from './useConfirmDialog';
|
import { useConfirmDialog } from './useConfirmDialog';
|
||||||
|
import { useAlertDialog } from './useAlertDialog';
|
||||||
|
|
||||||
// Define Props interface based on the component's props
|
// Define Props interface based on the component's props
|
||||||
interface AddConnectionFormProps {
|
interface AddConnectionFormProps {
|
||||||
@@ -27,6 +28,7 @@ export function useAddConnectionForm(props: AddConnectionFormProps, emit: AddCon
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { showConfirmDialog } = useConfirmDialog();
|
const { showConfirmDialog } = useConfirmDialog();
|
||||||
|
const { showAlertDialog } = useAlertDialog();
|
||||||
const connectionsStore = useConnectionsStore();
|
const connectionsStore = useConnectionsStore();
|
||||||
const proxiesStore = useProxiesStore();
|
const proxiesStore = useProxiesStore();
|
||||||
const tagsStore = useTagsStore();
|
const tagsStore = useTagsStore();
|
||||||
@@ -807,7 +809,7 @@ export function useAddConnectionForm(props: AddConnectionFormProps, emit: AddCon
|
|||||||
if (confirmedDeleteTag) {
|
if (confirmedDeleteTag) {
|
||||||
const success = await tagsStore.deleteTag(tagId);
|
const success = await tagsStore.deleteTag(tagId);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
alert(t('tags.errorDelete', { error: tagsStore.error || '未知错误' }));
|
showAlertDialog({ title: t('common.error', '错误'), message: t('tags.errorDelete', { error: tagsStore.error || '未知错误' }) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import AlertDialog from '../components/common/AlertDialog.vue';
|
||||||
|
import { createApp, h } from 'vue';
|
||||||
|
|
||||||
|
interface AlertDialogOptions {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
okText?: string;
|
||||||
|
onOk?: () => void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAlertDialog() {
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const showAlertDialog = (options: AlertDialogOptions): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const { title, message, okText, onOk } = options;
|
||||||
|
|
||||||
|
const
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
render: () =>
|
||||||
|
h(AlertDialog, {
|
||||||
|
visible: true,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
okText: okText || t('common.ok', '确定'),
|
||||||
|
onOk: async () => {
|
||||||
|
if (onOk) {
|
||||||
|
await onOk();
|
||||||
|
}
|
||||||
|
app.unmount();
|
||||||
|
container.remove();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
'onUpdate:visible': (isVisible: boolean) => {
|
||||||
|
if (!isVisible) {
|
||||||
|
// This case handles closing via Escape key or clicking outside
|
||||||
|
app.unmount();
|
||||||
|
container.remove();
|
||||||
|
resolve(); // Resolve promise when dialog is closed without explicit ok
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mount an app with i18n instance
|
||||||
|
const i18n = useI18n();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(app._context.provides as any).i18n = i18n;
|
||||||
|
|
||||||
|
app.mount(container);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
showAlertDialog,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -177,7 +177,6 @@ export function createDockerManager(sessionId: string, wsDeps: DockerManagerDepe
|
|||||||
// How to notify UI? Maybe set an error ref? Or rely on status update?
|
// How to notify UI? Maybe set an error ref? Or rely on status update?
|
||||||
// For now, just log. UI component could show a generic error or use a notification system.
|
// For now, just log. UI component could show a generic error or use a notification system.
|
||||||
// Consider adding a transient commandError ref if needed.
|
// Consider adding a transient commandError ref if needed.
|
||||||
alert(`${t('dockerManager.error.commandFailed', { command: payload?.command || '?' })}: ${payload?.message || 'Unknown error'}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const unsubRequestUpdate = onMessage('request_docker_status_update', (payload, message) => {
|
const unsubRequestUpdate = onMessage('request_docker_status_update', (payload, message) => {
|
||||||
@@ -192,12 +191,10 @@ export function createDockerManager(sessionId: string, wsDeps: DockerManagerDepe
|
|||||||
const sendDockerCommand = (containerId: string, command: 'start' | 'stop' | 'restart' | 'remove') => {
|
const sendDockerCommand = (containerId: string, command: 'start' | 'stop' | 'restart' | 'remove') => {
|
||||||
if (!isConnected.value) {
|
if (!isConnected.value) {
|
||||||
console.warn(`[DockerManager ${sessionId}] Cannot send command, WebSocket not connected.`);
|
console.warn(`[DockerManager ${sessionId}] Cannot send command, WebSocket not connected.`);
|
||||||
alert(t('dockerManager.error.sshNotConnected')); // Use generic disconnected message
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isDockerAvailable.value) {
|
if (!isDockerAvailable.value) {
|
||||||
console.warn(`[DockerManager ${sessionId}] Cannot send command, remote Docker is not available.`);
|
console.warn(`[DockerManager ${sessionId}] Cannot send command, remote Docker is not available.`);
|
||||||
alert(t('dockerManager.notAvailable'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1041,6 +1041,10 @@
|
|||||||
"testMessageUnsaved": "Test triggered for unsaved {channelType} configuration"
|
"testMessageUnsaved": "Test triggered for unsaved {channelType} configuration"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
"ok": "OK",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"alert": "Alert",
|
||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|||||||
@@ -78,6 +78,10 @@
|
|||||||
"clearTerminal": "ターミナルをクリア"
|
"clearTerminal": "ターミナルをクリア"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
"ok": "確認",
|
||||||
|
"success": "成功",
|
||||||
|
"error": "失敗",
|
||||||
|
"alert": "お知らせ",
|
||||||
"apply": "適用",
|
"apply": "適用",
|
||||||
"all": "すべて",
|
"all": "すべて",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
|
|||||||
@@ -1073,6 +1073,10 @@
|
|||||||
"minimize": "最小化",
|
"minimize": "最小化",
|
||||||
"send":"发送",
|
"send":"发送",
|
||||||
"copied": "已复制到剪贴板",
|
"copied": "已复制到剪贴板",
|
||||||
|
"ok":"确认",
|
||||||
|
"success":"成功",
|
||||||
|
"error":"失败",
|
||||||
|
"alert":"提示",
|
||||||
"updateSuccess":"更新成功"
|
"updateSuccess":"更新成功"
|
||||||
},
|
},
|
||||||
"layoutConfigurator": {
|
"layoutConfigurator": {
|
||||||
|
|||||||
@@ -382,10 +382,6 @@ export const useFileEditorStore = defineStore('fileEditor', () => {
|
|||||||
if (tabToClose.isModified) {
|
if (tabToClose.isModified) {
|
||||||
// 这里可以集成 UI 通知库来提示
|
// 这里可以集成 UI 通知库来提示
|
||||||
console.warn(`[文件编辑器 Store] 标签页 ${tabId} (${tabToClose.filename}) 已修改但未保存。正在关闭...`);
|
console.warn(`[文件编辑器 Store] 标签页 ${tabId} (${tabToClose.filename}) 已修改但未保存。正在关闭...`);
|
||||||
// alert(`文件 ${tabToClose.filename} 已修改但未保存。确定要关闭吗?`); // 简单的 alert 示例
|
|
||||||
// if (!confirm(`文件 ${tabToClose.filename} 已修改但未保存。确定要关闭吗?`)) {
|
|
||||||
// return; // 用户取消关闭
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[文件编辑器 Store] 关闭标签页: ${tabId}`);
|
console.log(`[文件编辑器 Store] 关闭标签页: ${tabId}`);
|
||||||
|
|||||||
@@ -465,9 +465,6 @@ function ensureNodeIds(node: LayoutNode | null): LayoutNode | null {
|
|||||||
console.log('[Layout Store] Header visibility saved to backend.');
|
console.log('[Layout Store] Header visibility saved to backend.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Layout Store] Failed to save header visibility to backend:', error);
|
console.error('[Layout Store] Failed to save header visibility to backend:', error);
|
||||||
// 可选:如果保存失败,回滚状态?
|
|
||||||
// isHeaderVisible.value = !newValue;
|
|
||||||
// alert('Failed to save preference.'); // 或者通知用户
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,14 +58,12 @@ apiClient.interceptors.response.use(
|
|||||||
case 403: // 禁止访问
|
case 403: // 禁止访问
|
||||||
// 可以显示一个权限不足的提示
|
// 可以显示一个权限不足的提示
|
||||||
console.error('Forbidden access.');
|
console.error('Forbidden access.');
|
||||||
// alert('您没有权限执行此操作。'); // 或者使用更友好的通知组件
|
|
||||||
break;
|
break;
|
||||||
case 404: // 未找到
|
case 404: // 未找到
|
||||||
console.error('Resource not found.');
|
console.error('Resource not found.');
|
||||||
break;
|
break;
|
||||||
case 500: // 服务器内部错误
|
case 500: // 服务器内部错误
|
||||||
console.error('Internal server error.');
|
console.error('Internal server error.');
|
||||||
// alert('服务器发生错误,请稍后重试。');
|
|
||||||
break;
|
break;
|
||||||
// 可以根据需要添加更多错误状态码的处理
|
// 可以根据需要添加更多错误状态码的处理
|
||||||
default:
|
default:
|
||||||
@@ -74,7 +72,6 @@ apiClient.interceptors.response.use(
|
|||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
// 请求已发出,但没有收到响应 (例如网络问题)
|
// 请求已发出,但没有收到响应 (例如网络问题)
|
||||||
console.error('Network error or no response received:', error.request);
|
console.error('Network error or no response received:', error.request);
|
||||||
// alert('网络错误,请检查您的连接。');
|
|
||||||
} else {
|
} else {
|
||||||
// 发送请求时出了点问题
|
// 发送请求时出了点问题
|
||||||
console.error('Error setting up request:', error.message);
|
console.error('Error setting up request:', error.message);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import type { ConnectionInfo } from '../stores/connections.store';
|
import type { ConnectionInfo } from '../stores/connections.store';
|
||||||
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
import { useConfirmDialog } from '../composables/useConfirmDialog';
|
||||||
|
import { useAlertDialog } from '../composables/useAlertDialog';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { zhCN, enUS, ja } from 'date-fns/locale';
|
import { zhCN, enUS, ja } from 'date-fns/locale';
|
||||||
@@ -18,6 +19,7 @@ import type { Locale } from 'date-fns';
|
|||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const { showConfirmDialog } = useConfirmDialog();
|
const { showConfirmDialog } = useConfirmDialog();
|
||||||
|
const { showAlertDialog } = useAlertDialog();
|
||||||
const connectionsStore = useConnectionsStore();
|
const connectionsStore = useConnectionsStore();
|
||||||
const sessionStore = useSessionStore();
|
const sessionStore = useSessionStore();
|
||||||
const tagsStore = useTagsStore();
|
const tagsStore = useTagsStore();
|
||||||
@@ -254,7 +256,7 @@ const invertSelection = () => {
|
|||||||
const openBatchEditModal = () => {
|
const openBatchEditModal = () => {
|
||||||
if (selectedConnectionIdsForBatch.value.size === 0) {
|
if (selectedConnectionIdsForBatch.value.size === 0) {
|
||||||
// Optionally, show a notification from uiNotificationsStore using your project's method
|
// Optionally, show a notification from uiNotificationsStore using your project's method
|
||||||
alert(t('connections.batchEdit.noSelectionForEdit', '请至少选择一个连接进行编辑。')); // Placeholder
|
showAlertDialog({ title: t('common.alert', '提示'), message: t('connections.batchEdit.noSelectionForEdit', '请至少选择一个连接进行编辑。') }); // Placeholder
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showBatchEditForm.value = true;
|
showBatchEditForm.value = true;
|
||||||
@@ -294,14 +296,14 @@ const handleBatchDeleteConnections = async () => {
|
|||||||
await connectionsStore.deleteBatchConnections(idsToDelete);
|
await connectionsStore.deleteBatchConnections(idsToDelete);
|
||||||
|
|
||||||
|
|
||||||
alert(t('connections.batchEdit.successMessage', '选中的连接已成功删除。'));
|
showAlertDialog({ title: t('common.success', '成功'), message: t('connections.batchEdit.successMessage', '选中的连接已成功删除。') });
|
||||||
|
|
||||||
selectedConnectionIdsForBatch.value.clear();
|
selectedConnectionIdsForBatch.value.clear();
|
||||||
|
|
||||||
await connectionsStore.fetchConnections();
|
await connectionsStore.fetchConnections();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Batch delete connections error:", error);
|
console.error("Batch delete connections error:", error);
|
||||||
alert(t('connections.batchEdit.errorMessage', `批量删除连接失败: ${error.message || '未知错误'}`));
|
showAlertDialog({ title: t('common.error'), message: t('connections.batchEdit.errorMessage', `批量删除连接失败: ${error.message || '未知错误'}`) });
|
||||||
} finally {
|
} finally {
|
||||||
isDeletingSelectedConnections.value = false;
|
isDeletingSelectedConnections.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user