style: 优化设置界面布局,添加导航栏
This commit is contained in:
@@ -890,7 +890,17 @@
|
|||||||
"noReleases": "No releases found",
|
"noReleases": "No releases found",
|
||||||
"rateLimit": "GitHub API rate limit exceeded, please try again later"
|
"rateLimit": "GitHub API rate limit exceeded, please try again later"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"tabs": {
|
||||||
|
"security": "Security",
|
||||||
|
"ipControl": "IP Control",
|
||||||
|
"workspace": "Workspace",
|
||||||
|
"system": "System",
|
||||||
|
"dataManagement": "Data Management",
|
||||||
|
"appearance": "Appearance",
|
||||||
|
"about": "About"
|
||||||
|
},
|
||||||
|
"loading": "Loading..."
|
||||||
},
|
},
|
||||||
"notificationController": {
|
"notificationController": {
|
||||||
"errorFetchSettings": "Failed to fetch notification settings",
|
"errorFetchSettings": "Failed to fetch notification settings",
|
||||||
|
|||||||
@@ -1080,6 +1080,16 @@
|
|||||||
"decryptKeyInfo": "解凍パスワードは、data/.env ファイル内の ENCRYPTION_KEY です。このファイルを安全に保管してください。",
|
"decryptKeyInfo": "解凍パスワードは、data/.env ファイル内の ENCRYPTION_KEY です。このファイルを安全に保管してください。",
|
||||||
"buttonText": "エクスポートを開始"
|
"buttonText": "エクスポートを開始"
|
||||||
},
|
},
|
||||||
|
"tabs": {
|
||||||
|
"security": "セキュリティ",
|
||||||
|
"ipControl": "IP制御",
|
||||||
|
"workspace": "ワークスペース",
|
||||||
|
"system": "システム",
|
||||||
|
"dataManagement": "データ管理",
|
||||||
|
"appearance": "外観",
|
||||||
|
"about": "バージョン情報"
|
||||||
|
},
|
||||||
|
"loading": "読み込み中...",
|
||||||
"setup": {
|
"setup": {
|
||||||
"confirmPassword": "パスワードを再入力",
|
"confirmPassword": "パスワードを再入力",
|
||||||
"confirmPasswordPlaceholder": "パスワードを再入力して確認",
|
"confirmPasswordPlaceholder": "パスワードを再入力して確認",
|
||||||
|
|||||||
@@ -887,11 +887,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exportConnections": {
|
"exportConnections": {
|
||||||
"title": "导出连接数据",
|
"title": "导出连接数据",
|
||||||
"decryptKeyInfo": "解压密码是您 data/.env 文件中的 ENCRYPTION_KEY。请妥善保管此文件。",
|
"decryptKeyInfo": "解压密码是您 data/.env 文件中的 ENCRYPTION_KEY。请妥善保管此文件。",
|
||||||
"buttonText": "开始导出"
|
"buttonText": "开始导出"
|
||||||
}
|
},
|
||||||
},
|
"tabs": {
|
||||||
|
"security": "安全",
|
||||||
|
"ipControl": "IP 管控",
|
||||||
|
"workspace": "工作区",
|
||||||
|
"system": "系统",
|
||||||
|
"dataManagement": "数据管理",
|
||||||
|
"appearance": "外观",
|
||||||
|
"about": "关于"
|
||||||
|
},
|
||||||
|
"loading": "加载中..."
|
||||||
|
},
|
||||||
|
|
||||||
"notificationController": {
|
"notificationController": {
|
||||||
"errorFetchSettings": "获取通知设置失败",
|
"errorFetchSettings": "获取通知设置失败",
|
||||||
|
|||||||
@@ -1,68 +1,82 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-4 bg-background text-foreground min-h-screen"> <!-- Outer container -->
|
<div class="p-4 bg-background text-foreground min-h-screen"> <!-- Outer container -->
|
||||||
<div class="max-w-7xl mx-auto"> <!-- Inner container for max-width -->
|
<div class="max-w-7xl mx-auto"> <!-- Inner container for max-width -->
|
||||||
<h1 class="text-2xl font-semibold text-foreground mb-6 pb-3 border-b border-border"> <!-- Main Title -->
|
<!-- Tabs Navigation -->
|
||||||
{{ $t('settings.title') }}
|
<div class="mb-6 flex space-x-1 sticky top-0 bg-background z-10 py-2">
|
||||||
</h1>
|
<button
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
@click="activeTab = tab.key"
|
||||||
|
:class="['px-4 py-2 text-sm font-medium rounded-md focus:outline-none transition-colors duration-150 ease-in-out',
|
||||||
|
activeTab === tab.key ? 'bg-primary text-white' : 'text-muted-foreground hover:bg-muted/50 hover:text-foreground']"
|
||||||
|
>
|
||||||
|
{{ tab.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Error state (Show first if error exists) -->
|
<!-- Error state (Show first if error exists) -->
|
||||||
<div v-if="settingsError" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded">
|
<div v-if="settingsError" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded">
|
||||||
{{ settingsError }}
|
{{ settingsError }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Sections Grid (Render grid structure always if no error) -->
|
<!-- Settings Content based on activeTab -->
|
||||||
<div v-else class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <!-- Changed to 2 columns on large screens -->
|
<div v-else class="space-y-6">
|
||||||
<!-- Removed global loading state, content will show/hide based on individual loading states -->
|
<!-- Security Tab Content -->
|
||||||
|
<div v-if="activeTab === 'security'">
|
||||||
<!-- Column 1: Security -->
|
|
||||||
<div class="space-y-6"> <!-- Removed col-span -->
|
|
||||||
<!-- Security Sections: Only show if settings data is loaded -->
|
|
||||||
<div v-if="settings" class="bg-background border border-border rounded-lg shadow-sm overflow-hidden">
|
<div v-if="settings" class="bg-background border border-border rounded-lg shadow-sm overflow-hidden">
|
||||||
<h2 class="text-lg font-semibold text-foreground px-6 py-4 border-b border-border bg-header/50">{{ $t('settings.category.security') }}</h2>
|
<h2 class="text-lg font-semibold text-foreground px-6 py-4 border-b border-border bg-header/50">{{ $t('settings.category.security') }}</h2>
|
||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<!-- Change Password -->
|
|
||||||
<ChangePasswordForm />
|
<ChangePasswordForm />
|
||||||
<hr class="border-border/50">
|
<hr class="border-border/50">
|
||||||
<!-- Passkey Management -->
|
|
||||||
<PasskeyManagement />
|
<PasskeyManagement />
|
||||||
<hr class="border-border/50">
|
<hr class="border-border/50">
|
||||||
<!-- 2FA -->
|
|
||||||
<TwoFactorAuthSettings />
|
<TwoFactorAuthSettings />
|
||||||
<hr class="border-border/50"> <!-- Separator -->
|
<hr class="border-border/50">
|
||||||
<!-- CAPTCHA Settings -->
|
|
||||||
<CaptchaSettingsForm />
|
<CaptchaSettingsForm />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="p-4 text-center text-muted-foreground">{{ $t('settings.loading', '加载中...') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- IP Whitelist Section: Only show if settings data is loaded -->
|
<!-- IP Control Tab Content -->
|
||||||
<div v-if="settings" class="bg-background border border-border rounded-lg shadow-sm overflow-hidden">
|
<div v-if="activeTab === 'ipControl'">
|
||||||
|
<div v-if="settings" class="bg-background border border-border rounded-lg shadow-sm overflow-hidden mb-6">
|
||||||
<h2 class="text-lg font-semibold text-foreground px-6 py-4 border-b border-border bg-header/50">{{ $t('settings.ipWhitelist.title') }}</h2>
|
<h2 class="text-lg font-semibold text-foreground px-6 py-4 border-b border-border bg-header/50">{{ $t('settings.ipWhitelist.title') }}</h2>
|
||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<IpWhitelistSettings />
|
<IpWhitelistSettings />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- IP Blacklist Section: Only show if settings data is loaded (as config depends on it) -->
|
|
||||||
<IpBlacklistSettings v-if="settings" />
|
<IpBlacklistSettings v-if="settings" />
|
||||||
|
<div v-else-if="!settings && activeTab === 'ipControl'" class="p-4 text-center text-muted-foreground">{{ $t('settings.loading', '加载中...') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- About Section -->
|
<!-- Workspace Tab Content -->
|
||||||
<AboutSection />
|
<div v-if="activeTab === 'workspace'">
|
||||||
</div>
|
<WorkspaceSettingsSection v-if="settings" />
|
||||||
|
<div v-else class="p-4 text-center text-muted-foreground">{{ $t('settings.loading', '加载中...') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Column 2: Appearance, Workspace, System -->
|
<!-- System Tab Content -->
|
||||||
<div class="space-y-6"> <!-- Removed col-span -->
|
<div v-if="activeTab === 'system'">
|
||||||
|
<SystemSettingsSection v-if="settings" />
|
||||||
|
<div v-else class="p-4 text-center text-muted-foreground">{{ $t('settings.loading', '加载中...') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Workspace Section: Only show if settings data is loaded -->
|
<!-- Data Management Tab Content -->
|
||||||
<WorkspaceSettingsSection />
|
<div v-if="activeTab === 'dataManagement'">
|
||||||
|
<DataManagementSection v-if="settings" />
|
||||||
|
<div v-else class="p-4 text-center text-muted-foreground">{{ $t('settings.loading', '加载中...') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- System Section: Only show if settings data is loaded -->
|
<!-- Appearance Tab Content -->
|
||||||
<SystemSettingsSection />
|
<div v-if="activeTab === 'appearance'">
|
||||||
<!-- Data Management Section (including Export) -->
|
<AppearanceSection v-if="settings" />
|
||||||
<DataManagementSection />
|
<div v-else class="p-4 text-center text-muted-foreground">{{ $t('settings.loading', '加载中...') }}</div>
|
||||||
|
</div>
|
||||||
<!-- Appearance Section: Only show if settings data is loaded -->
|
|
||||||
<AppearanceSection />
|
|
||||||
|
|
||||||
|
<!-- About Tab Content -->
|
||||||
|
<div v-if="activeTab === 'about'">
|
||||||
|
<AboutSection />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +84,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'; // Simplified Vue imports
|
import { onMounted, ref } from 'vue'; // Simplified Vue imports
|
||||||
import { useAuthStore } from '../stores/auth.store';
|
import { useAuthStore } from '../stores/auth.store';
|
||||||
import { useSettingsStore } from '../stores/settings.store';
|
import { useSettingsStore } from '../stores/settings.store';
|
||||||
import { useAppearanceStore } from '../stores/appearance.store'; // 导入外观 store
|
import { useAppearanceStore } from '../stores/appearance.store'; // 导入外观 store
|
||||||
@@ -93,6 +107,18 @@ const settingsStore = useSettingsStore();
|
|||||||
const appearanceStore = useAppearanceStore(); // 实例化外观 store
|
const appearanceStore = useAppearanceStore(); // 实例化外观 store
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// Define tabs for settings sections
|
||||||
|
const tabs = ref([
|
||||||
|
{ key: 'security', label: t('settings.tabs.security', '安全') },
|
||||||
|
{ key: 'ipControl', label: t('settings.tabs.ipControl', 'IP 管控') },
|
||||||
|
{ key: 'workspace', label: t('settings.tabs.workspace', '工作区') },
|
||||||
|
{ key: 'system', label: t('settings.tabs.system', '系统') },
|
||||||
|
{ key: 'dataManagement', label: t('settings.tabs.dataManagement', '数据管理') },
|
||||||
|
{ key: 'appearance', label: t('settings.tabs.appearance', '外观') },
|
||||||
|
{ key: 'about', label: t('settings.tabs.about', '关于') },
|
||||||
|
]);
|
||||||
|
const activeTab = ref(tabs.value[0].key);
|
||||||
|
|
||||||
// --- Reactive state from store ---
|
// --- Reactive state from store ---
|
||||||
// 使用 storeToRefs 获取响应式 getter,包括 language
|
// 使用 storeToRefs 获取响应式 getter,包括 language
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onBeforeUnmount, computed, ref } from 'vue';
|
import { onMounted, onBeforeUnmount, computed, ref, shallowRef, type PropType } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'; // +++ 引入 useBreakpoints +++
|
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core';
|
||||||
import { useLayoutStore } from '../stores/layout.store';
|
import { useLayoutStore } from '../stores/layout.store';
|
||||||
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store';
|
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store';
|
||||||
import AddConnectionFormComponent from '../components/AddConnectionForm.vue';
|
import AddConnectionFormComponent from '../components/AddConnectionForm.vue';
|
||||||
import TerminalTabBar from '../components/TerminalTabBar.vue';
|
import TerminalTabBar from '../components/TerminalTabBar.vue';
|
||||||
import LayoutRenderer from '../components/LayoutRenderer.vue';
|
import LayoutRenderer from '../components/LayoutRenderer.vue';
|
||||||
import LayoutConfigurator from '../components/LayoutConfigurator.vue';
|
import LayoutConfigurator from '../components/LayoutConfigurator.vue';
|
||||||
import RemoteDesktopModal from '../components/RemoteDesktopModal.vue';
|
import Terminal from '../components/Terminal.vue';
|
||||||
import VncModal from '../components/VncModal.vue'; // +++ 引入 VncModal 组件 +++
|
import CommandInputBar from '../components/CommandInputBar.vue';
|
||||||
import Terminal from '../components/Terminal.vue'; // +++ 引入 Terminal 组件 +++
|
import VirtualKeyboard from '../components/VirtualKeyboard.vue';
|
||||||
import CommandInputBar from '../components/CommandInputBar.vue'; // +++ 引入 CommandInputBar 组件 +++
|
import FileManager from '../components/FileManager.vue';
|
||||||
import VirtualKeyboard from '../components/VirtualKeyboard.vue'; // +++ 引入 VirtualKeyboard 组件 +++
|
|
||||||
import { useSessionStore } from '../stores/session.store';
|
import { useSessionStore } from '../stores/session.store';
|
||||||
import type { SessionTabInfoWithStatus, SshTerminalInstance } from '../stores/session/types';
|
import type { SessionTabInfoWithStatus, SshTerminalInstance } from '../stores/session/types';
|
||||||
import { useSettingsStore } from '../stores/settings.store';
|
import { useSettingsStore } from '../stores/settings.store';
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
useWorkspaceEventOff,
|
useWorkspaceEventOff,
|
||||||
type WorkspaceEventPayloads
|
type WorkspaceEventPayloads
|
||||||
} from '../composables/workspaceEvents';
|
} from '../composables/workspaceEvents';
|
||||||
|
import type { WebSocketDependencies } from '../composables/useSftpActions';
|
||||||
|
|
||||||
// --- Setup ---
|
// --- Setup ---
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -75,6 +75,15 @@ const currentSearchTerm = ref(''); // 当前搜索的关键词
|
|||||||
const mobileTerminalRef = ref<InstanceType<typeof Terminal> | null>(null);
|
const mobileTerminalRef = ref<InstanceType<typeof Terminal> | null>(null);
|
||||||
const isVirtualKeyboardVisible = ref(true); // +++ State for virtual keyboard visibility +++
|
const isVirtualKeyboardVisible = ref(true); // +++ State for virtual keyboard visibility +++
|
||||||
|
|
||||||
|
// --- 文件管理器模态框状态 ---
|
||||||
|
const showFileManagerModal = ref(false);
|
||||||
|
const fileManagerProps = shallowRef<null | {
|
||||||
|
sessionId: string;
|
||||||
|
instanceId: string;
|
||||||
|
dbConnectionId: string;
|
||||||
|
wsDeps: WebSocketDependencies;
|
||||||
|
}>(null);
|
||||||
|
|
||||||
// --- 处理全局键盘事件 ---
|
// --- 处理全局键盘事件 ---
|
||||||
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||||
// 检查是否按下了 Alt 键以及上/下箭头键
|
// 检查是否按下了 Alt 键以及上/下箭头键
|
||||||
@@ -154,6 +163,7 @@ onMounted(() => {
|
|||||||
subscribeToWorkspaceEvents('session:closeToRight', (payload) => handleCloseSessionsToRight(payload.targetSessionId));
|
subscribeToWorkspaceEvents('session:closeToRight', (payload) => handleCloseSessionsToRight(payload.targetSessionId));
|
||||||
subscribeToWorkspaceEvents('session:closeToLeft', (payload) => handleCloseSessionsToLeft(payload.targetSessionId));
|
subscribeToWorkspaceEvents('session:closeToLeft', (payload) => handleCloseSessionsToLeft(payload.targetSessionId));
|
||||||
subscribeToWorkspaceEvents('ui:openLayoutConfigurator', handleOpenLayoutConfigurator);
|
subscribeToWorkspaceEvents('ui:openLayoutConfigurator', handleOpenLayoutConfigurator);
|
||||||
|
subscribeToWorkspaceEvents('fileManager:openModalRequest', handleFileManagerOpenRequest); // +++ 订阅文件管理器打开请求 +++
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -196,6 +206,7 @@ onBeforeUnmount(() => {
|
|||||||
unsubscribeFromWorkspaceEvents('session:closeToRight', (payload) => handleCloseSessionsToRight(payload.targetSessionId));
|
unsubscribeFromWorkspaceEvents('session:closeToRight', (payload) => handleCloseSessionsToRight(payload.targetSessionId));
|
||||||
unsubscribeFromWorkspaceEvents('session:closeToLeft', (payload) => handleCloseSessionsToLeft(payload.targetSessionId));
|
unsubscribeFromWorkspaceEvents('session:closeToLeft', (payload) => handleCloseSessionsToLeft(payload.targetSessionId));
|
||||||
unsubscribeFromWorkspaceEvents('ui:openLayoutConfigurator', handleOpenLayoutConfigurator);
|
unsubscribeFromWorkspaceEvents('ui:openLayoutConfigurator', handleOpenLayoutConfigurator);
|
||||||
|
unsubscribeFromWorkspaceEvents('fileManager:openModalRequest', handleFileManagerOpenRequest); // +++ 取消订阅文件管理器打开请求 +++
|
||||||
});
|
});
|
||||||
|
|
||||||
const subscribeToWorkspaceEvents = useWorkspaceEventSubscriber(); // +++ 定义订阅和取消订阅函数 +++
|
const subscribeToWorkspaceEvents = useWorkspaceEventSubscriber(); // +++ 定义订阅和取消订阅函数 +++
|
||||||
@@ -634,6 +645,64 @@ const toggleVirtualKeyboard = () => {
|
|||||||
tabsToClose.forEach(id => handleCloseEditorTab(id));
|
tabsToClose.forEach(id => handleCloseEditorTab(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- 文件管理器模态框处理 ---
|
||||||
|
const handleFileManagerOpenRequest = (payload: { sessionId: string }) => {
|
||||||
|
const { sessionId } = payload;
|
||||||
|
const session = sessionStore.sessions.get(sessionId);
|
||||||
|
if (!session) {
|
||||||
|
console.error(`[WorkspaceView] Cannot open file manager: Session ${sessionId} not found.`);
|
||||||
|
// TODO: Show error notification
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 获取 dbConnectionId
|
||||||
|
const dbConnectionId = session.connectionId;
|
||||||
|
if (!dbConnectionId) {
|
||||||
|
console.error(`[WorkspaceView] Cannot open file manager: Missing dbConnectionId for session ${sessionId}.`);
|
||||||
|
// TODO: Show error notification
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取 wsDeps (从 session.wsManager 获取)
|
||||||
|
if (!session.wsManager) {
|
||||||
|
console.error(`[WorkspaceView] Cannot open file manager: wsManager not found for session ${sessionId}.`);
|
||||||
|
// TODO: Show error notification
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wsDeps: WebSocketDependencies = {
|
||||||
|
sendMessage: session.wsManager.sendMessage,
|
||||||
|
onMessage: session.wsManager.onMessage,
|
||||||
|
isConnected: session.wsManager.isConnected,
|
||||||
|
isSftpReady: session.wsManager.isSftpReady,
|
||||||
|
};
|
||||||
|
console.log(`[WorkspaceView] Retrieved wsDeps from session.wsManager for session ${sessionId}.`);
|
||||||
|
|
||||||
|
if (!wsDeps) {
|
||||||
|
// 如果 wsDeps 仍然为 null,则无法继续
|
||||||
|
console.error(`[WorkspaceView] Cannot open file manager: wsDeps are null after attempting retrieval for session ${sessionId}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 生成 instanceId
|
||||||
|
const instanceId = `fm-modal-${sessionId}-${Date.now()}`;
|
||||||
|
|
||||||
|
// 4. 设置 props 并显示模态框
|
||||||
|
fileManagerProps.value = {
|
||||||
|
sessionId,
|
||||||
|
instanceId,
|
||||||
|
dbConnectionId: String(dbConnectionId), // 确保是 string
|
||||||
|
wsDeps,
|
||||||
|
};
|
||||||
|
showFileManagerModal.value = true;
|
||||||
|
console.log(`[WorkspaceView] Opening FileManager modal with props:`, fileManagerProps.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeFileManagerModal = () => {
|
||||||
|
showFileManagerModal.value = false;
|
||||||
|
fileManagerProps.value = null; // 清理 props
|
||||||
|
console.log('[WorkspaceView] FileManager modal closed.');
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -726,6 +795,28 @@ const toggleVirtualKeyboard = () => {
|
|||||||
|
|
||||||
<!-- RDP Modal is now rendered in App.vue -->
|
<!-- RDP Modal is now rendered in App.vue -->
|
||||||
<!-- VNC Modal is now rendered in App.vue -->
|
<!-- VNC Modal is now rendered in App.vue -->
|
||||||
|
|
||||||
|
<!-- FileManager Modal -->
|
||||||
|
<div v-if="showFileManagerModal && fileManagerProps" class="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 p-4">
|
||||||
|
<div class="bg-background rounded-lg shadow-xl w-full max-w-4xl h-[85vh] flex flex-col overflow-hidden border border-border">
|
||||||
|
<div class="flex justify-between items-center p-3 border-b border-border flex-shrink-0 bg-header">
|
||||||
|
<h2 class="text-lg font-semibold text-foreground">{{ t('fileManager.modalTitle', '文件管理器') }} ({{ fileManagerProps.sessionId }})</h2>
|
||||||
|
<button @click="closeFileManagerModal" class="text-text-secondary hover:text-foreground transition-colors">
|
||||||
|
<i class="fas fa-times text-xl"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow overflow-hidden">
|
||||||
|
<FileManager
|
||||||
|
:session-id="fileManagerProps.sessionId"
|
||||||
|
:instance-id="fileManagerProps.instanceId"
|
||||||
|
:db-connection-id="fileManagerProps.dbConnectionId"
|
||||||
|
:ws-deps="fileManagerProps.wsDeps"
|
||||||
|
class="h-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user