This commit is contained in:
Baobhan Sith
2025-05-16 22:49:07 +08:00
parent 084cc570f4
commit 56833d58e1
23 changed files with 89 additions and 194 deletions
@@ -28,7 +28,7 @@ const initialFormData = {
port: 1080, // 默认 SOCKS5 端口
username: '',
password: '',
tag_ids: [] as number[], // 新增 tag_ids 字段
tag_ids: [] as number[],
};
const formData = reactive({ ...initialFormData });
@@ -1,11 +1,11 @@
<script setup lang="ts">
import { ref, watch, nextTick, type PropType, onUnmounted, computed } from 'vue'; // Added watch, nextTick, computed
import { ref, watch, nextTick, type PropType, onUnmounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import SendFilesModal from './SendFilesModal.vue';
import type { ContextMenuItem } from '../composables/file-manager/useFileManagerContextMenu';
import type { FileListItem } from '../types/sftp.types'; // Import FileListItem
import type { FileListItem } from '../types/sftp.types';
import { useDeviceDetection } from '../composables/useDeviceDetection';
import { useSessionStore } from '../stores/session.store'; // +++ 导入 session store +++
import { useSessionStore } from '../stores/session.store';
const props = defineProps({
isVisible: {
@@ -28,8 +28,8 @@ const connectionStatus = ref<'disconnected' | 'connecting' | 'connected' | 'erro
const isResizing = ref(false);
const resizeStartX = ref(0);
const resizeStartY = ref(0);
const initialModalWidthForResize = ref(0); // Renamed to avoid conflict if other 'initialModalWidth' exists
const initialModalHeightForResize = ref(0); // Renamed
const initialModalWidthForResize = ref(0);
const initialModalHeightForResize = ref(0);
const statusMessage = ref('');
const keyboard = ref<any | null>(null);
const mouse = ref<any | null>(null);
@@ -42,7 +42,7 @@ const isDraggingRestoreButton = ref(false);
const restoreButtonPosition = ref({ x: 16, y: window.innerHeight / 2 - 25 }); // 16px from left, vertically centered
let dragOffsetX = 0;
let dragOffsetY = 0;
let hasDragged = false; // 新增 hasDragged 标志
let hasDragged = false;
const MIN_MODAL_WIDTH = 1024;
const MIN_MODAL_HEIGHT = 768;
@@ -431,7 +431,7 @@ watch(desiredModalWidth, (newWidth, oldWidth) => {
console.log(`[RDP 模态框] 宽度监听触发,但值 (${newWidth}) 未改变。跳过保存。`);
return;
}
console.log(`[RDP 模态框] 监听 desiredModalWidth 触发: ${oldWidth} -> ${newWidth}`); // 添加日志
console.log(`[RDP 模态框] 监听 desiredModalWidth 触发: ${oldWidth} -> ${newWidth}`);
// 保存前验证新宽度
const validatedWidth = Math.max(MIN_MODAL_WIDTH, Number(newWidth) || MIN_MODAL_WIDTH);
// 防抖保存 *验证后* 的宽度
@@ -210,11 +210,6 @@ const getGroupId = (group: GroupedConnection): string => {
const toggleTagGroupExpansion = (group: GroupedConnection) => {
const groupId = getGroupId(group);
// If state is undefined, default to true (expanded), then toggle it.
// So, if undefined, it becomes !(true) = false. If defined, it's just toggled.
// To make it default to expanded and then collapse on first click:
// expandedTagGroups.value[groupId] = !(expandedTagGroups.value[groupId] ?? true);
// To make it default to collapsed and then expand on first click (if true means expanded):
expandedTagGroups.value[groupId] = !(expandedTagGroups.value[groupId] ?? true);
};
@@ -276,8 +271,6 @@ const groupedConnections = computed<GroupedConnection[]>(() => {
groups[tag.id].connections.push(conn);
}
} else {
// Connection has a tag ID that doesn't exist in tagsStore, treat as untagged for this modal
// Or handle as an error, or create a "missing tag" group
if (!untaggedConnections.some(c => c.id === conn.id)) {
untaggedConnections.push(conn);
}
@@ -359,11 +352,6 @@ const toggleTagGroupSelection = (group: GroupedConnection) => {
watch(() => props.visible, (newValue) => {
if (newValue) {
// Reset state when modal becomes visible, except perhaps targetPath if desired
// searchTerm.value = '';
// selectedConnectionIds.value = [];
// transferMethod.value = 'auto';
// If stores might be empty, fetch again or ensure they are fresh
if (connectionsStore.connections.length === 0) {
connectionsStore.fetchConnections().catch(error => console.error(t('sendFilesModal.errorFetchingConnections'), error));
}
@@ -428,7 +416,6 @@ const toggleIndividualConnectionSelection = (connectionId: number) => {
};
const getConnectionIconClass = (connectionType?: string): string => {
// Ensure connectionType is treated as optional and provide a default if undefined
const type = connectionType?.toLowerCase();
switch (type) {
case 'rdp': return 'fas fa-desktop';
@@ -438,9 +425,8 @@ const getConnectionIconClass = (connectionType?: string): string => {
case 'local': return 'fas fa-laptop';
case 'serial': return 'fas fa-microchip';
case 'docker': return 'fab fa-docker';
default: return 'fas fa-server'; // Default icon for unknown or undefined types
default: return 'fas fa-server';
}
};
// Fallback i18n messages are now removed as they are expected to be in the locale JSON files.
</script>
@@ -98,8 +98,8 @@ const initializeEditableState = () => {
editableEditorFontSize.value = currentEditorFontSize.value; // <-- 新增
localTerminalBackgroundEnabled.value = isTerminalBackgroundEnabled.value; // <-- 重新添加:在此处初始化
editableTerminalBackgroundOverlayOpacity.value = currentTerminalBackgroundOverlayOpacity.value; // 初始化蒙版透明度
console.log(`[StyleCustomizer initializeEditableState] Initializing localTerminalBackgroundEnabled to: ${localTerminalBackgroundEnabled.value} (from store: ${isTerminalBackgroundEnabled.value})`); // 添加日志
console.log(`[StyleCustomizer initializeEditableState] Initializing editableTerminalBackgroundOverlayOpacity to: ${editableTerminalBackgroundOverlayOpacity.value} (from store: ${currentTerminalBackgroundOverlayOpacity.value})`); // 新增日志
console.log(`[StyleCustomizer initializeEditableState] Initializing localTerminalBackgroundEnabled to: ${localTerminalBackgroundEnabled.value} (from store: ${isTerminalBackgroundEnabled.value})`);
console.log(`[StyleCustomizer initializeEditableState] Initializing editableTerminalBackgroundOverlayOpacity to: ${editableTerminalBackgroundOverlayOpacity.value} (from store: ${currentTerminalBackgroundOverlayOpacity.value})`);
uploadError.value = null;
importError.value = null;
saveThemeError.value = null;
@@ -143,7 +143,7 @@ watch([
newSettings?.terminalBackgroundEnabled !== oldSettings?.terminalBackgroundEnabled ||
newSettings?.terminalBackgroundOverlayOpacity !== oldSettings?.terminalBackgroundOverlayOpacity; // 检查相关设置是否变化
if (!isEditingTheme.value || newActiveThemeId !== oldActiveThemeId || settingsChanged) {
console.log(`[StyleCustomizer Watch] Triggering re-initialization. isEditing: ${isEditingTheme.value}, themeIdChanged: ${newActiveThemeId !== oldActiveThemeId}, settingsChanged: ${settingsChanged}`); // 添加日志
console.log(`[StyleCustomizer Watch] Triggering re-initialization. isEditing: ${isEditingTheme.value}, themeIdChanged: ${newActiveThemeId !== oldActiveThemeId}, settingsChanged: ${settingsChanged}`);
initializeEditableState(); // 调用修改后的初始化函数
} else {
// 如果正在编辑,只更新非编辑相关的部分 (不包括 UI 主题和终端背景开关,因为它们由 initializeEditableState 处理)
@@ -165,10 +165,10 @@ watch(isTerminalBackgroundEnabled, (newValue) => {
// 只有当本地状态与 store 状态不一致时才更新本地状态
// 这避免了 handleToggleTerminalBackground 触发的不必要更新
if (localTerminalBackgroundEnabled.value !== newValue) {
console.log(`[StyleCustomizer Watch isTerminalBackgroundEnabled] Store changed to ${newValue}, updating local state.`); // 添加日志
console.log(`[StyleCustomizer Watch isTerminalBackgroundEnabled] Store changed to ${newValue}, updating local state.`);
localTerminalBackgroundEnabled.value = newValue;
} else {
console.log(`[StyleCustomizer Watch isTerminalBackgroundEnabled] Store changed to ${newValue}, but local state already matches. No update needed.`); // 添加日志
console.log(`[StyleCustomizer Watch isTerminalBackgroundEnabled] Store changed to ${newValue}, but local state already matches. No update needed.`);
}
});
// 移除单独监听 isTerminalBackgroundEnabled 的 watcher
@@ -6,7 +6,7 @@ import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue';
import TabBarContextMenu from './TabBarContextMenu.vue';
import TransferProgressModal from './TransferProgressModal.vue'; // 导入传输进度模态框
import TransferProgressModal from './TransferProgressModal.vue';
import { useSessionStore } from '../stores/session.store';
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store';
import { useLayoutStore, type PaneName } from '../stores/layout.store';
@@ -15,7 +15,7 @@ import { useWorkspaceEventEmitter, useWorkspaceEventSubscriber, useWorkspaceEven
import type { SessionTabInfoWithStatus } from '../stores/session/types'; // 路径修正
const { t } = useI18n(); // 初始化 i18n
const { t } = useI18n();
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
const onWorkspaceEvent = useWorkspaceEventSubscriber(); // +++ 获取事件订阅器 +++
const offWorkspaceEvent = useWorkspaceEventOff(); // +++ 获取事件取消订阅器 +++
@@ -65,8 +65,8 @@ interface TransferTask {
updatedAt: string | Date;
subTasks: TransferSubTask[];
overallProgress?: number;
sourceConnectionId?: number; // ID ()
remoteTargetPath?: string; // ()
sourceConnectionId?: number;
remoteTargetPath?: string;
}
const transferTasks = ref<TransferTask[]>([]);
@@ -133,8 +133,8 @@ const getDisplayStatus = (status: string): string => {
'partially-completed': 'transferProgressModal.status.partiallyCompleted',
'connecting': 'transferProgressModal.status.connecting',
'transferring': 'transferProgressModal.status.transferring',
'cancelling': 'transferProgressModal.status.cancelling', // +++ +++
'cancelled': 'transferProgressModal.status.cancelled', // +++ +++
'cancelling': 'transferProgressModal.status.cancelling',
'cancelled': 'transferProgressModal.status.cancelled',
};
// 退i18n key
const defaultText = status.charAt(0).toUpperCase() + status.slice(1).replace('-', ' ');
@@ -150,7 +150,7 @@ const formatDate = (dateInput: string | Date): string => {
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
} catch (e) {
return String(dateInput); // Fallback if date is invalid
return String(dateInput);
}
};
@@ -185,7 +185,7 @@ watch(() => props.visible, (newVisible) => {
}
}, { immediate: false }); // immediate: false onMounted
// --- ---
// --- ---
const internalVisible = ref(props.visible);
// props.visible internalVisible
@@ -221,17 +221,10 @@ const handleCancelTask = async (taskId: string) => {
// UI 'cancelling'
const task = transferTasks.value.find(t => t.taskId === taskId);
if (task) {
// : ,
// , : task.status = 'cancelling';
// loading
}
await apiClient.post(`/transfers/cancel/${taskId}`);
//
// uiNotificationsStore.showSuccess(t('transferProgressModal.cancelRequested', ''));
// 'cancelling'
//
const taskBeingCancelled = transferTasks.value.find(t => t.taskId === taskId);
if (taskBeingCancelled && ['queued', 'in-progress', 'connecting', 'transferring'].includes(taskBeingCancelled.status)) {
taskBeingCancelled.status = 'cancelling';
@@ -241,8 +234,6 @@ const handleCancelTask = async (taskId: string) => {
fetchTransferTasks();
} catch (error: any) {
console.error(`Failed to cancel task ${taskId}:`, error);
// uiNotificationsStore.showError(error.response?.data?.message || error.message || t('transferProgressModal.error.cancelFailed', ''));
//
}
};
@@ -383,7 +383,7 @@ const handleMenuAction = (action: 'add' | 'edit' | 'delete' | 'clone') => { //
closeContextMenu(); //
if (action === 'add') {
console.log('[WorkspaceConnectionList] handleMenuAction called with action: add. Emitting request-add-connection...'); //
console.log('[WorkspaceConnectionList] handleMenuAction called with action: add. Emitting request-add-connection...');
// router.push('/connections/add'); //
emitWorkspaceEvent('connection:requestAdd');
} else if (conn) {
@@ -516,7 +516,7 @@ const handleTagMenuAction = (action: 'connectAll' | 'manageTag' | 'deleteAllConn
//
if (group.tagId === null) {
uiNotificationsStore.addNotification({
message: t('workspaceConnectionList.cannotDeleteFromUntagged'), // i18n
message: t('workspaceConnectionList.cannotDeleteFromUntagged'),
type: 'warning',
});
return;
@@ -524,13 +524,13 @@ const handleTagMenuAction = (action: 'connectAll' | 'manageTag' | 'deleteAllConn
//
if (group.connections.length === 0) {
uiNotificationsStore.addNotification({
message: t('workspaceConnectionList.noConnectionsToDeleteInGroup', { groupName: group.groupName }), // i18n
message: t('workspaceConnectionList.noConnectionsToDeleteInGroup', { groupName: group.groupName }),
type: 'info',
});
return;
}
if (confirm(t('workspaceConnectionList.confirmDeleteAllConnectionsInGroup', { count: group.connections.length, groupName: group.groupName }))) { // i18n
if (confirm(t('workspaceConnectionList.confirmDeleteAllConnectionsInGroup', { count: group.connections.length, groupName: group.groupName }))) {
const connectionIdsToDelete = group.connections.map(conn => conn.id);
const deletePromises = connectionIdsToDelete.map(connId =>
@@ -547,13 +547,13 @@ const handleTagMenuAction = (action: 'connectAll' | 'manageTag' | 'deleteAllConn
if (successfulDeletes > 0) {
uiNotificationsStore.addNotification({
message: t('workspaceConnectionList.allConnectionsInGroupDeletedSuccess', { count: successfulDeletes, groupName: group.groupName }), // i18n
message: t('workspaceConnectionList.allConnectionsInGroupDeletedSuccess', { count: successfulDeletes, groupName: group.groupName }),
type: 'success',
});
}
if (failedDeletes > 0) {
uiNotificationsStore.addNotification({
message: t('workspaceConnectionList.someConnectionsInGroupDeleteFailed', { count: failedDeletes, groupName: group.groupName }), // i18n
message: t('workspaceConnectionList.someConnectionsInGroupDeleteFailed', { count: failedDeletes, groupName: group.groupName }),
type: 'error',
});
}
@@ -365,8 +365,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
// --- 返回状态和处理函数 ---
return {
// isDraggingOver, // 不再导出
showExternalDropOverlay, // 新增导出
showExternalDropOverlay,
dragOverTarget,
draggedItem, // 需要暴露以供 handleDragOverRow 等函数内部判断
// --- 事件处理器 ---
@@ -374,7 +373,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
handleDragOver,
handleDragLeave,
handleDrop, // 容器的 drop (主要用于清理)
handleOverlayDrop, // 新增导出:蒙版的 drop
handleOverlayDrop,
handleDragStart,
handleDragEnd,
handleDragOverRow,
@@ -38,20 +38,20 @@ export function useAddConnectionForm(props: AddConnectionFormProps, emit: AddCon
// 表单数据模型
const initialFormData = {
type: 'SSH' as 'SSH' | 'RDP' | 'VNC', // Use uppercase to match ConnectionInfo
type: 'SSH' as 'SSH' | 'RDP' | 'VNC',
name: '',
host: '',
port: 22,
username: '',
auth_method: 'password' as 'password' | 'key', // SSH specific
auth_method: 'password' as 'password' | 'key',
password: '',
private_key: '', // SSH specific (for direct input) - This field seems unused in the new logic, but kept for initialData consistency
passphrase: '', // SSH specific (for direct input) - This field seems unused, kept for consistency
selected_ssh_key_id: null as number | null, // +++ Add field for selected key ID +++
private_key: '',
passphrase: '',
selected_ssh_key_id: null as number | null,
proxy_id: null as number | null,
tag_ids: [] as number[], // 新增 tag_ids 字段
notes: '', // 新增备注字段
vncPassword: '', // VNC specific password
tag_ids: [] as number[],
notes: '',
vncPassword: '',
};
const formData = reactive({ ...initialFormData });
@@ -86,7 +86,6 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
};
const handleTerminalResize = (dimensions: { cols: number; rows: number }) => {
// 添加日志,确认从 WorkspaceView 收到的尺寸
console.log(`[SSH ${sessionId}] handleTerminalResize called with:`, dimensions);
// 只有在连接状态下才发送 resize 命令给后端
if (isConnected.value) {
@@ -235,9 +235,9 @@ export const useAppearanceStore = defineStore('appearance', () => {
* @param enabled
*/
async function setTerminalBackgroundEnabled(enabled: boolean) {
console.log(`[AppearanceStore LOG] setTerminalBackgroundEnabled 调用,准备发送给后端的值: ${enabled}`); // 添加日志
console.log(`[AppearanceStore LOG] setTerminalBackgroundEnabled 调用,准备发送给后端的值: ${enabled}`);
await updateAppearanceSettings({ terminalBackgroundEnabled: enabled });
console.log(`[AppearanceStore LOG] setTerminalBackgroundEnabled 更新后端调用完成。`); // 添加日志
console.log(`[AppearanceStore LOG] setTerminalBackgroundEnabled 更新后端调用完成。`);
}
/**
+5 -5
View File
@@ -55,17 +55,17 @@ interface AuthState {
user: UserInfo | null;
isLoading: boolean;
error: string | null;
loginRequires2FA: boolean; // 新增状态:标记登录是否需要 2FA
loginRequires2FA: boolean;
// 存储 IP 黑名单数据 (虽然 actions 在这里,但 state 结构保持)
ipBlacklist: {
entries: any[]; // TODO: Define a proper type for blacklist entries
total: number;
};
needsSetup: boolean; // 是否需要初始设置
publicCaptchaConfig: PublicCaptchaConfig | null; // Public CAPTCHA config
passkeys: PasskeyInfo[] | null; // Store for user's passkeys
passkeysLoading: boolean; // Loading state for passkeys
hasPasskeysAvailable: boolean; // Indicates if passkeys are available for login
publicCaptchaConfig: PublicCaptchaConfig | null;
passkeys: PasskeyInfo[] | null;
passkeysLoading: boolean;
hasPasskeysAvailable: boolean;
}
export const useAuthStore = defineStore('auth', {
@@ -16,7 +16,7 @@ export interface ConnectionInfo {
created_at: number;
updated_at: number;
last_connected_at: number | null;
notes?: string | null; // 新增备注字段
notes?: string | null;
vncPassword?: string; // VNC specific password
}
+8 -8
View File
@@ -411,7 +411,7 @@ function ensureNodeIds(node: LayoutNode | null): LayoutNode | null {
}
// 新增 Action: 更新特定容器节点的子节点大小
// 更新特定容器节点的子节点大小
function updateNodeSizes(nodeId: string, childrenSizes: { index: number; size: number }[]) {
console.log(`[Layout Store] 请求更新节点 ${nodeId} 的子节点大小:`, childrenSizes);
const originalJson = JSON.stringify(layoutTree.value); // Store original state
@@ -426,14 +426,14 @@ function ensureNodeIds(node: LayoutNode | null): LayoutNode | null {
console.log(`[Layout Store] 未找到节点 ${nodeId} 或大小未改变。`);
}
}
// 新增 Action: 切换布局(Header/Footer)的可见性
// 切换布局(Header/Footer)的可见性
function toggleLayoutVisibility() {
isLayoutVisible.value = !isLayoutVisible.value;
console.log(`[Layout Store] 布局可见性切换为: ${isLayoutVisible.value}`);
// 注意:这个状态目前不与后端同步
}
// 新增 Action: 从后端加载主导航栏可见性设置
// 从后端加载主导航栏可见性设置
async function loadHeaderVisibility() {
console.log('[Layout Store] Attempting to load header visibility from backend...');
try {
@@ -453,7 +453,7 @@ function ensureNodeIds(node: LayoutNode | null): LayoutNode | null {
}
}
// 新增 Action: 切换主导航栏可见性并同步到后端
// 切换主导航栏可见性并同步到后端
async function toggleHeaderVisibility() {
const newValue = !isHeaderVisible.value;
console.log(`[Layout Store] Toggling header visibility to: ${newValue}`);
@@ -471,19 +471,19 @@ function ensureNodeIds(node: LayoutNode | null): LayoutNode | null {
}
}
// 新增 Action: 获取系统内置的默认布局
// 获取系统内置的默认布局
function getSystemDefaultLayout(): LayoutNode {
console.log('[Layout Store] Getting system default layout.');
return getDefaultLayout(); // 直接调用内部函数
}
// 新增 Action: 获取系统内置的默认侧栏配置
// 获取系统内置的默认侧栏配置
function getSystemDefaultSidebarPanes(): { left: PaneName[], right: PaneName[] } {
console.log('[Layout Store] Getting system default sidebar panes.');
return getDefaultSidebarPanes();
}
// 新增 Action: 将当前主布局树持久化到后端和 localStorage
// 将当前主布局树持久化到后端和 localStorage
async function persistLayoutTree() { // Make async
// ... (existing try/catch logic for backend and localStorage) ...
// Ensure apiClient calls are awaited if they return promises
@@ -504,7 +504,7 @@ function ensureNodeIds(node: LayoutNode | null): LayoutNode | null {
}
}
// 新增 Action: 将当前侧栏配置持久化到后端和 localStorage
// 将当前侧栏配置持久化到后端和 localStorage
async function persistSidebarPanes() { // Make async
// ... (existing try/catch logic for backend and localStorage) ...
try {
@@ -214,7 +214,7 @@ const unsubscribeFromWorkspaceEvents = useWorkspaceEventOff();
// --- ( UI ) ---
const handleRequestAddConnection = () => {
console.log('[WorkspaceView] handleRequestAddConnection 被调用!'); //
console.log('[WorkspaceView] handleRequestAddConnection 被调用!');
connectionToEdit.value = null;
showAddEditForm.value = true;
};