update
This commit is contained in:
@@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user