feat(remote): 支持浏览系统磁盘目录

- 添加 getDrives() 方法获取磁盘驱动器列表
- 修改 browseDirectory() 支持 allowSystem 参数浏览系统路径
- 添加 /api/files/drives 路由
- 修改前端 RemoteFilePanel 支持显示驱动器和系统目录浏览
This commit is contained in:
2026-03-09 19:21:09 +08:00
parent 49bf8a97d2
commit d65b3e7909
5 changed files with 200 additions and 79 deletions

View File

@@ -17,14 +17,25 @@ router.get('/', (req, res) => {
}
});
router.get('/drives', (req, res) => {
try {
const drives = fileService.getDrives();
res.json({ items: drives });
} catch (error) {
logger.error('Failed to get drives', { error: error.message });
res.status(500).json({ error: 'Failed to get drives' });
}
});
router.get('/browse', (req, res) => {
try {
const path = req.query.path || '';
const result = fileService.browseDirectory(path);
const filePath = req.query.path || '';
const allowSystem = req.query.allowSystem === 'true';
const result = fileService.browseDirectory(filePath, allowSystem);
res.json(result);
} catch (error) {
logger.error('Failed to browse directory', { error: error.message });
res.status(500).json({ error: 'Failed to browse directory' });
logger.error('Failed to browse directory', { error: error.message, stack: error.stack });
res.status(500).json({ error: error.message || 'Failed to browse directory' });
}
});

View File

@@ -136,48 +136,78 @@ class FileService {
}
}
browseDirectory(relativePath = '') {
try {
getDrives() {
const drives = [];
const letters = 'CDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
for (const letter of letters) {
const drivePath = `${letter}:\\`;
try {
fs.accessSync(drivePath);
drives.push({ name: `${letter}:`, isDirectory: true, size: 0 });
} catch {}
}
return drives;
}
browseDirectory(relativePath = '', allowSystem = false) {
let targetDir;
let currentPath;
if (allowSystem) {
currentPath = path.normalize(relativePath || '').replace(/^(\.\.(\/|\\|$))+/, '');
if (!currentPath) {
currentPath = '';
}
targetDir = currentPath || 'C:\\';
} else {
const safePath = path.normalize(relativePath || '').replace(/^(\.\.(\/|\\|$))+/, '');
const targetDir = path.join(this.uploadDir, safePath);
targetDir = path.join(this.uploadDir, safePath);
currentPath = safePath;
if (!targetDir.startsWith(this.uploadDir)) {
return { error: 'Access denied', items: [], currentPath: '' };
}
if (!fs.existsSync(targetDir)) {
return { error: 'Directory not found', items: [], currentPath: safePath };
}
const items = fs.readdirSync(targetDir).map(name => {
const itemPath = path.join(targetDir, name);
const stat = fs.statSync(itemPath);
const isDirectory = stat.isDirectory();
return {
name,
isDirectory,
size: isDirectory ? 0 : stat.size,
modified: stat.mtime,
type: isDirectory ? 'directory' : path.extname(name)
};
});
items.sort((a, b) => {
if (a.isDirectory && !b.isDirectory) return -1;
if (!a.isDirectory && b.isDirectory) return 1;
return a.name.localeCompare(b.name);
});
return {
items,
currentPath: safePath,
parentPath: safePath ? path.dirname(safePath) : null
};
} catch (error) {
logger.error('Failed to browse directory', { error: error.message });
return { error: error.message, items: [], currentPath: relativePath };
}
const items = [];
try {
const files = fs.readdirSync(targetDir);
for (const name of files) {
try {
const itemPath = path.join(targetDir, name);
const stat = fs.statSync(itemPath);
const isDirectory = stat.isDirectory();
items.push({
name,
isDirectory,
size: isDirectory ? 0 : stat.size,
modified: stat.mtime,
type: isDirectory ? 'directory' : path.extname(name)
});
} catch (err) {
logger.debug('Skipped inaccessible file', { name, error: err.message });
}
}
} catch (err) {
logger.warn('Failed to read directory', { targetDir, error: err.message });
return { items: [], currentPath: currentPath, parentPath: path.dirname(currentPath) || null };
}
items.sort((a, b) => {
if (a.isDirectory && !b.isDirectory) return -1;
if (!a.isDirectory && b.isDirectory) return 1;
return a.name.localeCompare(b.name);
});
const parentPath = currentPath ? path.dirname(currentPath) : null;
return {
items,
currentPath: currentPath,
parentPath: parentPath === currentPath ? null : parentPath
};
}
}