This commit is contained in:
Baobhan Sith
2025-04-16 11:04:29 +08:00
parent 269473c526
commit a83b346956
20 changed files with 922 additions and 350 deletions
@@ -1,6 +1,8 @@
import { ref, readonly, type Ref, type ComputedRef } from 'vue'; // Removed onUnmounted, added ComputedRef
import { ref, readonly, type Ref, type ComputedRef } from 'vue';
import type { FileListItem, EditorFileContent } from '../types/sftp.types';
import type { WebSocketMessage, MessagePayload, MessageHandler } from '../types/websocket.types'; // 从类型文件导入
import type { WebSocketMessage, MessagePayload, MessageHandler } from '../types/websocket.types';
// 导入 UI 通知 store
import { useUiNotificationsStore } from '../stores/uiNotifications.store'; // 更正导入
/**
* @interface WebSocketDependencies
@@ -49,8 +51,9 @@ export function createSftpActionsManager(
const fileList = ref<FileListItem[]>([]);
const isLoading = ref<boolean>(false);
const error = ref<string | null>(null);
// const error = ref<string | null>(null); // 不再使用本地 error ref
const instanceSessionId = sessionId; // 保存会话 ID 用于日志
const uiNotificationsStore = useUiNotificationsStore(); // 初始化 UI 通知 store
// 用于存储注销函数的数组
const unregisterCallbacks: (() => void)[] = [];
@@ -62,25 +65,24 @@ export function createSftpActionsManager(
unregisterCallbacks.length = 0; // 清空数组
};
// 清除错误状态的函数
const clearSftpError = () => {
error.value = null;
};
// 不再需要 clearSftpError 函数
// const clearSftpError = () => { ... };
// --- Action Methods ---
const loadDirectory = (path: string) => {
if (!isSftpReady.value) {
error.value = t('fileManager.errors.sftpNotReady');
// 使用通知 store 显示错误
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
isLoading.value = false;
fileList.value = [];
console.warn(`[SFTP ${instanceSessionId}] Attempted to load directory ${path} but SFTP is not ready.`);
console.warn(`[SFTP ${instanceSessionId}] 尝试加载目录 ${path} SFTP 未就绪。`); // 日志改为中文
return;
}
console.log(`[SFTP ${instanceSessionId}] Loading directory: ${path}`);
console.log(`[SFTP ${instanceSessionId}] 正在加载目录: ${path}`); // 日志改为中文
isLoading.value = true;
error.value = null;
// error.value = null; // 不再需要
currentPathRef.value = path; // 更新外部 ref
const requestId = generateRequestId();
sendMessage({ type: 'sftp:readdir', requestId: requestId, payload: { path } });
@@ -88,8 +90,8 @@ export function createSftpActionsManager(
const createDirectory = (newDirName: string) => {
if (!isSftpReady.value) {
error.value = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] Attempted to create directory ${newDirName} but SFTP is not ready.`);
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
console.warn(`[SFTP ${instanceSessionId}] 尝试创建目录 ${newDirName} SFTP 未就绪。`); // 日志改为中文
return;
}
const newFolderPath = joinPath(currentPathRef.value, newDirName);
@@ -99,8 +101,8 @@ export function createSftpActionsManager(
const createFile = (newFileName: string) => {
if (!isSftpReady.value) {
error.value = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] Attempted to create file ${newFileName} but SFTP is not ready.`);
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
console.warn(`[SFTP ${instanceSessionId}] 尝试创建文件 ${newFileName} SFTP 未就绪。`); // 日志改为中文
return;
}
const newFilePath = joinPath(currentPathRef.value, newFileName);
@@ -114,8 +116,8 @@ export function createSftpActionsManager(
const deleteItems = (items: FileListItem[]) => {
if (!isSftpReady.value) {
error.value = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] Attempted to delete items but SFTP is not ready.`);
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
console.warn(`[SFTP ${instanceSessionId}] 尝试删除项目但 SFTP 未就绪。`); // 日志改为中文
return;
}
if (items.length === 0) return;
@@ -129,8 +131,8 @@ export function createSftpActionsManager(
const renameItem = (item: FileListItem, newName: string) => {
if (!isSftpReady.value) {
error.value = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] Attempted to rename item ${item.filename} but SFTP is not ready.`);
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
console.warn(`[SFTP ${instanceSessionId}] 尝试重命名项目 ${item.filename} SFTP 未就绪。`); // 日志改为中文
return;
}
if (!newName || item.filename === newName) return;
@@ -142,8 +144,8 @@ export function createSftpActionsManager(
const changePermissions = (item: FileListItem, mode: number) => {
if (!isSftpReady.value) {
error.value = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] Attempted to change permissions for ${item.filename} but SFTP is not ready.`);
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 }); // 使用 uiNotificationsStore
console.warn(`[SFTP ${instanceSessionId}] 尝试修改 ${item.filename} 的权限但 SFTP 未就绪。`); // 日志改为中文
return;
}
const targetPath = joinPath(currentPathRef.value, item.filename);
@@ -155,8 +157,10 @@ export function createSftpActionsManager(
const readFile = (path: string): Promise<EditorFileContent> => {
return new Promise((resolve, reject) => {
if (!isSftpReady.value) {
console.warn(`[SFTP ${instanceSessionId}] Attempted to read file ${path} but SFTP is not ready.`);
return reject(new Error(t('fileManager.errors.sftpNotReady')));
const errMsg = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] 尝试读取文件 ${path} 但 SFTP 未就绪。`); // 日志改为中文
uiNotificationsStore.showError(errMsg, { timeout: 5000 }); // 使用 uiNotificationsStore
return reject(new Error(errMsg));
}
const requestId = generateRequestId();
let unregisterSuccess: (() => void) | null = null;
@@ -165,7 +169,9 @@ export function createSftpActionsManager(
const timeoutId = setTimeout(() => {
unregisterSuccess?.();
unregisterError?.();
reject(new Error(t('fileManager.errors.readFileTimeout')));
const errMsg = t('fileManager.errors.readFileTimeout');
uiNotificationsStore.showError(errMsg, { timeout: 5000 }); // 使用 uiNotificationsStore
reject(new Error(errMsg));
}, 20000); // 20 秒超时
unregisterSuccess = onMessage('sftp:readfile:success', (payload: MessagePayload, message: WebSocketMessage) => {
@@ -186,7 +192,9 @@ export function createSftpActionsManager(
clearTimeout(timeoutId);
unregisterSuccess?.();
unregisterError?.();
reject(new Error(errorPayload || 'Failed to read file'));
const errorMsg = errorPayload || t('fileManager.errors.readFileFailed'); // 使用 i18n
uiNotificationsStore.showError(`${t('fileManager.errors.readFileError')}: ${errorMsg}`, { timeout: 5000 }); // 使用 uiNotificationsStore
reject(new Error(errorMsg));
}
});
@@ -197,8 +205,10 @@ export function createSftpActionsManager(
const writeFile = (path: string, content: string): Promise<void> => {
return new Promise((resolve, reject) => {
if (!isSftpReady.value) {
console.warn(`[SFTP ${instanceSessionId}] Attempted to write file ${path} but SFTP is not ready.`);
return reject(new Error(t('fileManager.errors.sftpNotReady')));
const errMsg = t('fileManager.errors.sftpNotReady');
console.warn(`[SFTP ${instanceSessionId}] 尝试写入文件 ${path} 但 SFTP 未就绪。`); // 日志改为中文
uiNotificationsStore.showError(errMsg, { timeout: 5000 }); // 使用 uiNotificationsStore
return reject(new Error(errMsg));
}
const requestId = generateRequestId();
const encoding: 'utf8' | 'base64' = 'utf8'; // 假设总是 utf8
@@ -208,7 +218,9 @@ export function createSftpActionsManager(
const timeoutId = setTimeout(() => {
unregisterSuccess?.();
unregisterError?.();
reject(new Error(t('fileManager.errors.saveTimeout')));
const errMsg = t('fileManager.errors.saveTimeout');
uiNotificationsStore.showError(errMsg, { timeout: 5000 }); // 使用 uiNotificationsStore
reject(new Error(errMsg));
}, 20000); // 20 秒超时
unregisterSuccess = onMessage('sftp:writefile:success', (payload: MessagePayload, message: WebSocketMessage) => {
@@ -227,7 +239,9 @@ export function createSftpActionsManager(
clearTimeout(timeoutId);
unregisterSuccess?.();
unregisterError?.();
reject(new Error(errorPayload || 'Failed to write file'));
const errorMsg = errorPayload || t('fileManager.errors.saveFailed'); // 使用 i18n
uiNotificationsStore.showError(errorMsg, { timeout: 5000 }); // 使用 uiNotificationsStore
reject(new Error(errorMsg));
}
});
@@ -246,12 +260,12 @@ export function createSftpActionsManager(
// 类型断言,因为我们知道 readdir:success 的 payload 是 FileListItem[]
const fileListPayload = payload as FileListItem[];
if (message.path === currentPathRef.value) {
console.log(`[SFTP ${instanceSessionId}] Received file list for ${message.path}`);
console.log(`[SFTP ${instanceSessionId}] 收到目录 ${message.path} 的文件列表`); // 日志改为中文
fileList.value = fileListPayload.sort(sortFiles);
isLoading.value = false;
error.value = null;
// error.value = null; // 不再需要
} else {
console.log(`[SFTP ${instanceSessionId}] Ignoring readdir success for ${message.path} (current: ${currentPathRef.value})`);
console.log(`[SFTP ${instanceSessionId}] 忽略目录 ${message.path} 的 readdir 成功消息 (当前: ${currentPathRef.value})`); // 日志改为中文
}
};
@@ -259,16 +273,17 @@ export function createSftpActionsManager(
// 类型断言,因为我们知道 readdir:error 的 payload 是 string
const errorPayload = payload as string;
if (message.path === currentPathRef.value) {
console.error(`[SFTP ${instanceSessionId}] Error loading directory ${message.path}:`, errorPayload);
error.value = errorPayload;
console.error(`[SFTP ${instanceSessionId}] 加载目录 ${message.path} 出错:`, errorPayload); // 日志改为中文
// error.value = errorPayload; // 使用通知
uiNotificationsStore.showError(`${t('fileManager.errors.loadDirectoryFailed')}: ${errorPayload}`, { timeout: 5000 }); // 使用 uiNotificationsStore, 添加 i18n key
isLoading.value = false;
}
};
const onActionSuccessRefresh = (payload: MessagePayload, message: WebSocketMessage) => {
console.log(`[SFTP ${instanceSessionId}] Action ${message.type} successful. Refreshing current directory: ${currentPathRef.value}`);
console.log(`[SFTP ${instanceSessionId}] 操作 ${message.type} 成功。正在刷新当前目录: ${currentPathRef.value}`); // 日志改为中文
loadDirectory(currentPathRef.value);
error.value = null;
// error.value = null; // 不再需要
};
const onActionError = (payload: MessagePayload, message: WebSocketMessage) => {
@@ -284,7 +299,8 @@ export function createSftpActionsManager(
'sftp:writefile:error': t('fileManager.errors.saveFailed'),
};
const prefix = actionTypeMap[message.type] || t('fileManager.errors.generic');
error.value = `${prefix}: ${errorPayload}`;
// error.value = `${prefix}: ${errorPayload}`; // 使用通知
uiNotificationsStore.showError(`${prefix}: ${errorPayload}`, { timeout: 5000 }); // 使用 uiNotificationsStore
};
// --- Register Handlers & Store Unregister Callbacks ---
@@ -309,7 +325,7 @@ export function createSftpActionsManager(
// State
fileList: readonly(fileList),
isLoading: readonly(isLoading),
error: readonly(error),
// error: readonly(error), // 移除 error
// Methods
loadDirectory,
@@ -321,7 +337,7 @@ export function createSftpActionsManager(
readFile,
writeFile,
joinPath, // 暴露辅助函数
clearSftpError,
// clearSftpError, // 移除 clearSftpError
// Cleanup function
currentPath: readonly(currentPathRef), // 暴露只读的当前路径 ref
@@ -91,9 +91,12 @@ export function createStatusMonitorManager(sessionId: string, wsDeps: StatusMoni
unregisterAllStatusHandlers();
// 连接断开时清除状态
serverStatus.value = null;
statusError.value = '连接已断开'; // 或者使用 i18n
// 只有在之前连接成功的情况下才设置断开错误
if (oldValue === true) {
statusError.value = '连接已断开'; // 或者使用 i18n
}
}
}, { immediate: true }); // immediate: true 确保初始状态下也会执行一次
}); // 移除 immediate: true,避免初始设置错误状态
// --- 清理函数 ---
const cleanup = () => {
@@ -1,5 +1,9 @@
import { ref, shallowRef, computed, readonly } from 'vue';
import type { ConnectionStatus, MessagePayload, WebSocketMessage, MessageHandler } from '../types/websocket.types';
// 从 websocket.types.ts 导入并重新导出 ConnectionStatus
import type { ConnectionStatus as WsConnectionStatusType, MessagePayload, WebSocketMessage, MessageHandler } from '../types/websocket.types';
// 导出类型别名,以便其他模块可以使用
export type WsConnectionStatus = WsConnectionStatusType;
/**
* 创建并管理单个 WebSocket 连接实例。
@@ -14,7 +18,7 @@ export function createWebSocketConnectionManager(sessionId: string, dbConnection
// --- Instance State ---
// 每个实例拥有独立的 WebSocket 对象、状态和消息处理器
const ws = shallowRef<WebSocket | null>(null); // WebSocket 实例
const connectionStatus = ref<ConnectionStatus>('disconnected'); // 连接状态
const connectionStatus = ref<WsConnectionStatus>('disconnected'); // 连接状态 (使用导出的类型)
const statusMessage = ref<string>(''); // 状态描述文本
const isSftpReady = ref<boolean>(false); // SFTP 是否就绪
const messageHandlers = new Map<string, Set<MessageHandler>>(); // 此实例的消息处理器注册表