0
0

修改了重命名的bug

This commit is contained in:
xubing
2026-01-30 11:26:15 +08:00
parent ad34dca525
commit 10f6f34127
6 changed files with 264 additions and 905 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -52,11 +52,10 @@ class ProjectService {
* 文件名/文件夹名净化:保留大小写,允许中文、字母、数字、-、_、. * 文件名/文件夹名净化:保留大小写,允许中文、字母、数字、-、_、.
*/ */
sanitizeFileName(name) { sanitizeFileName(name) {
let fileName = name.trim(); // 移除 .toLowerCase() 以保留大小写 let fileName = name.trim();
// 1. 替换所有空格为下划线 // 1. 替换所有空格为下划线
fileName = fileName.replace(/\s+/g, '_'); fileName = fileName.replace(/\s+/g, '_');
// 2. 移除特殊字符 // 2. 移除特殊字符
// 在正则中增加了 A-Z 以及 \. (注意:\u4e00-\u9fa5 匹配中文)
fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_\-\.]/g, ''); fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_\-\.]/g, '');
// 3. 确保不为空 // 3. 确保不为空
if (fileName.length === 0) { if (fileName.length === 0) {
@@ -64,6 +63,31 @@ class ProjectService {
} }
return fileName; return fileName;
} }
// =============== 查重工具方法 (新增) ===============
/** 检查项目名是否重复 */
isProjectNameExists(name) {
return this.projects.some(p => p.name === name);
}
/** 检查指定项目下的飞行器名是否重复 */
isAircraftNameExists(projectId, name) {
return this.aircrafts.some(a => a.projectId === projectId && a.name === name);
}
/** 检查指定飞行器下的容器名是否重复 */
isContainerNameExists(aircraftId, name) {
return this.containers.some(c => c.aircraftId === aircraftId && c.name === name);
}
/** 检查指定容器下的配置名或文件名是否重复 */
isConfigOrFolderConflict(containerId, name, fileName, excludeId) {
const configs = this.getConfigsByContainer(containerId);
const folders = this.getModuleFoldersByContainer(containerId);
// 检查显示名称冲突 (排除自身)
const nameConflict = configs.some(c => c.id !== excludeId && c.name === name) ||
folders.some(f => f.id !== excludeId && f.name === name);
// 检查磁盘文件名/文件夹名冲突 (排除自身)
const fileConflict = configs.some(c => c.id !== excludeId && c.fileName === fileName) ||
folders.some(f => f.id !== excludeId && f.localPath.split('/').pop() === fileName);
return { nameConflict, fileConflict };
}
// =============== 项目相关方法 =============== // =============== 项目相关方法 ===============
getProjects() { getProjects() {
return this.projects; return this.projects;
@@ -98,12 +122,10 @@ class ProjectService {
const project = this.projects.find(p => p.id === projectId); const project = this.projects.find(p => p.id === projectId);
if (!project) if (!project)
return false; return false;
// 先把要删的 id 都算出来,再统一过滤
const relatedAircrafts = this.aircrafts.filter(a => a.projectId === projectId); const relatedAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = relatedAircrafts.map(a => a.id); const aircraftIds = relatedAircrafts.map(a => a.id);
const relatedContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId)); const relatedContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId));
const containerIds = relatedContainers.map(c => c.id); const containerIds = relatedContainers.map(c => c.id);
// 真正删除数据
this.projects = this.projects.filter(p => p.id !== projectId); this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId); this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId)); this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
@@ -127,7 +149,6 @@ class ProjectService {
projectId: projectId projectId: projectId
}; };
this.aircrafts.push(newAircraft); this.aircrafts.push(newAircraft);
// 创建飞行器目录
await this.createAircraftDirectory(newAircraft); await this.createAircraftDirectory(newAircraft);
return newId; return newId;
} }
@@ -143,23 +164,15 @@ class ProjectService {
const aircraft = this.aircrafts.find(a => a.id === aircraftId); const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft) if (!aircraft)
return false; return false;
// ⚠️ 先算出要删的 containerIds
const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId); const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId);
const containerIds = relatedContainers.map(c => c.id); const containerIds = relatedContainers.map(c => c.id);
// 删除飞机自身和容器
this.aircrafts = this.aircrafts.filter(a => a.id !== aircraftId); this.aircrafts = this.aircrafts.filter(a => a.id !== aircraftId);
this.containers = this.containers.filter(c => c.aircraftId !== aircraftId); this.containers = this.containers.filter(c => c.aircraftId !== aircraftId);
// 再删关联的 config 和 moduleFolder
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId)); this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId)); this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
return true; return true;
} }
/**
* 从已存在的磁盘目录导入飞行器
* 不会创建/删除任何文件,只在内存中补充 Aircraft / Container / ModuleFolder 数据
*/
async importAircraftFromExistingFolder(projectId, aircraftName) { async importAircraftFromExistingFolder(projectId, aircraftName) {
// 已存在同名飞行器直接返回
const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName); const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
if (existed) { if (existed) {
return existed.id; return existed.id;
@@ -177,10 +190,8 @@ class ProjectService {
} }
const aircraftDir = path.join(projectPath, aircraftName); const aircraftDir = path.join(projectPath, aircraftName);
if (!fs.existsSync(aircraftDir)) { if (!fs.existsSync(aircraftDir)) {
// 目录不存在就不再解析子目录
return newId; return newId;
} }
// 每个子目录视为一个容器(排除 .git
const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true }); const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true });
for (const entry of entries) { for (const entry of entries) {
if (!entry.isDirectory()) if (!entry.isDirectory())
@@ -192,12 +203,6 @@ class ProjectService {
return newId; return newId;
} }
// =============== 容器相关方法 =============== // =============== 容器相关方法 ===============
/**
* ⚠️ 这里不再做 .git 过滤:
* - 我们只会通过 createContainer / importContainerFromExistingFolder 创建容器
* - importContainerFromExistingFolder 内部已经排除了 .git
* 所以不会出现名字叫 ".git" 的容器
*/
getContainersByAircraft(aircraftId) { getContainersByAircraft(aircraftId) {
return this.containers.filter(c => c.aircraftId === aircraftId); return this.containers.filter(c => c.aircraftId === aircraftId);
} }
@@ -213,7 +218,6 @@ class ProjectService {
}; };
this.containers.push(newContainer); this.containers.push(newContainer);
await this.createContainerDirectory(newContainer); await this.createContainerDirectory(newContainer);
// UI 手动创建的容器,仍然保留“默认两个配置”的行为
await this.createDefaultConfigs(newContainer); await this.createDefaultConfigs(newContainer);
return newId; return newId;
} }
@@ -234,15 +238,6 @@ class ProjectService {
this.moduleFolders = this.moduleFolders.filter(folder => folder.containerId !== containerId); this.moduleFolders = this.moduleFolders.filter(folder => folder.containerId !== containerId);
return true; return true;
} }
/**
* 从已存在的磁盘目录导入容器
*
* ✅ 你的最新需求:
* - 不创建“默认两个配置”
* - 自动扫描容器目录下的『子文件夹』
* - 每个子文件夹创建一个 ModuleFoldertype: 'local'
* - 排除 `.git` 子文件夹
*/
async importContainerFromExistingFolder(aircraftId, containerName) { async importContainerFromExistingFolder(aircraftId, containerName) {
if (containerName === '.git') { if (containerName === '.git') {
throw new Error('不能将 .git 导入为容器'); throw new Error('不能将 .git 导入为容器');
@@ -258,15 +253,9 @@ class ProjectService {
aircraftId aircraftId
}; };
this.containers.push(container); this.containers.push(container);
// 🚩 关键逻辑:扫描容器目录下的子文件夹 -> 创建 ModuleFolder
await this.scanContainerModuleFolders(container); await this.scanContainerModuleFolders(container);
// 不再创建默认两个配置(不调用 createDefaultConfigs
// 也不创建目录(目录是 Git 克隆出来的,本来就存在)
return newId; return newId;
} }
/**
* 扫描容器目录中的子文件夹(不含 .git将其作为本地模块文件夹记录
*/
async scanContainerModuleFolders(container) { async scanContainerModuleFolders(container) {
try { try {
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId); const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
@@ -308,10 +297,8 @@ class ProjectService {
getConfig(configId) { getConfig(configId) {
return this.configs.find(c => c.id === configId); return this.configs.find(c => c.id === configId);
} }
// src/panels/services/ProjectService.ts
async createConfig(name, containerId) { async createConfig(name, containerId) {
const newId = this.generateUniqueId('cfg', this.configs); const newId = this.generateUniqueId('cfg', this.configs);
// 使用净化后的文件名
const newFileName = this.sanitizeFileName(name); const newFileName = this.sanitizeFileName(name);
const newConfig = { const newConfig = {
id: newId, id: newId,
@@ -320,15 +307,11 @@ class ProjectService {
containerId: containerId containerId: containerId
}; };
this.configs.push(newConfig); this.configs.push(newConfig);
// 1. 首先确保父级容器目录存在
await this.ensureContainerDirectoryExists(containerId); await this.ensureContainerDirectoryExists(containerId);
// 2. 获取文件的完整磁盘路径
const filePath = this.getConfigFilePath(newId); const filePath = this.getConfigFilePath(newId);
if (filePath) { if (filePath) {
try { try {
const fileUri = vscode.Uri.file(filePath); const fileUri = vscode.Uri.file(filePath);
// 3. 真实创建文件:写入一个空的 Uint8Array 即可创建一个空文件
// 如果你希望有默认内容,可以在这里自定义 TextEncoder().encode("...")
await vscode.workspace.fs.writeFile(fileUri, new Uint8Array()); await vscode.workspace.fs.writeFile(fileUri, new Uint8Array());
console.log(`✅ 磁盘文件已真实创建: ${filePath}`); console.log(`✅ 磁盘文件已真实创建: ${filePath}`);
} }
@@ -338,9 +321,6 @@ class ProjectService {
} }
return newId; return newId;
} }
/**
* [修改] 同时更新配置显示名称和文件名称
*/
updateConfigName(configId, newName, newFileName) { updateConfigName(configId, newName, newFileName) {
const config = this.configs.find(c => c.id === configId); const config = this.configs.find(c => c.id === configId);
if (config) { if (config) {
@@ -357,19 +337,14 @@ class ProjectService {
this.configs = this.configs.filter(c => c.id !== configId); this.configs = this.configs.filter(c => c.id !== configId);
return true; return true;
} }
/**
* [新增] 重命名磁盘上的配置文件
*/
async renameConfigFileOnDisk(configId, newFileName) { async renameConfigFileOnDisk(configId, newFileName) {
const config = this.configs.find(c => c.id === configId); const config = this.configs.find(c => c.id === configId);
if (!config) if (!config)
return false; return false;
const oldFilePath = this.getConfigFilePath(configId); const oldFilePath = this.getConfigFilePath(configId);
// 为了计算新路径,临时使用新的文件名
const oldFileName = config.fileName; const oldFileName = config.fileName;
config.fileName = newFileName; config.fileName = newFileName;
const newFilePath = this.getConfigFilePath(configId); const newFilePath = this.getConfigFilePath(configId);
// 恢复旧的文件名,因为 ProjectService.updateConfigName 会在 ConfigPanel 中调用
config.fileName = oldFileName; config.fileName = oldFileName;
if (!oldFilePath || !newFilePath) { if (!oldFilePath || !newFilePath) {
return false; return false;
@@ -415,23 +390,16 @@ class ProjectService {
this.moduleFolders = this.moduleFolders.filter(f => f.id !== folderId); this.moduleFolders = this.moduleFolders.filter(f => f.id !== folderId);
return true; return true;
} }
// =============== 文件系统操作 (新增/修改) =============== // =============== 文件系统操作 ===============
/**
* 获取项目目录重命名所需的旧路径和新路径(新增)
*/
getProjectOldAndNewPaths(projectId, newName) { getProjectOldAndNewPaths(projectId, newName) {
const project = this.projects.find(p => p.id === projectId); const project = this.projects.find(p => p.id === projectId);
const oldPath = this.projectPaths.get(projectId); const oldPath = this.projectPaths.get(projectId);
if (!project || !oldPath) if (!project || !oldPath)
return null; return null;
const parentDir = path.dirname(oldPath); const parentDir = path.dirname(oldPath);
// 新路径使用新的项目名称作为文件夹名
const newPath = path.join(parentDir, newName); const newPath = path.join(parentDir, newName);
return { oldPath, newPath }; return { oldPath, newPath };
} }
/**
* 获取飞行器目录的完整路径
*/
getAircraftDirectoryPath(aircraftId) { getAircraftDirectoryPath(aircraftId) {
const aircraft = this.getAircraft(aircraftId); const aircraft = this.getAircraft(aircraftId);
if (!aircraft) if (!aircraft)
@@ -441,10 +409,6 @@ class ProjectService {
return null; return null;
return path.join(projectPath, aircraft.name); return path.join(projectPath, aircraft.name);
} }
/**
* 获取飞行器目录重命名所需的旧路径和新路径(新增)
* 注意oldPath使用内存中当前的名称newPath使用将要更新的新名称
*/
getAircraftOldAndNewPaths(aircraftId, newName) { getAircraftOldAndNewPaths(aircraftId, newName) {
const aircraft = this.aircrafts.find(a => a.id === aircraftId); const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft) if (!aircraft)
@@ -456,9 +420,6 @@ class ProjectService {
const newPath = path.join(projectPath, newName); const newPath = path.join(projectPath, newName);
return { oldPath, newPath }; return { oldPath, newPath };
} }
/**
* 获取容器目录的完整路径
*/
getContainerDirectoryPath(containerId) { getContainerDirectoryPath(containerId) {
const container = this.getContainer(containerId); const container = this.getContainer(containerId);
if (!container) if (!container)
@@ -471,10 +432,6 @@ class ProjectService {
return null; return null;
return path.join(projectPath, aircraft.name, container.name); return path.join(projectPath, aircraft.name, container.name);
} }
/**
* 获取容器目录重命名所需的旧路径和新路径(新增)
* 注意oldPath使用内存中当前的名称newPath使用将要更新的新名称
*/
getContainerOldAndNewPaths(containerId, newName) { getContainerOldAndNewPaths(containerId, newName) {
const container = this.containers.find(c => c.id === containerId); const container = this.containers.find(c => c.id === containerId);
if (!container) if (!container)
@@ -490,9 +447,6 @@ class ProjectService {
const newPath = path.join(aircraftDir, newName); const newPath = path.join(aircraftDir, newName);
return { oldPath, newPath }; return { oldPath, newPath };
} }
/**
* 递归删除目录
*/
async deleteDirectoryFromDisk(directoryPath) { async deleteDirectoryFromDisk(directoryPath) {
if (!directoryPath) if (!directoryPath)
return false; return false;
@@ -506,12 +460,9 @@ class ProjectService {
} }
catch (error) { catch (error) {
console.error(`删除目录失败: ${error}`); console.error(`删除目录失败: ${error}`);
throw error; // Re-throw to be handled by ConfigPanel throw error;
} }
} }
/**
* 重命名磁盘上的目录(新增)
*/
async renameDirectoryOnDisk(oldPath, newPath) { async renameDirectoryOnDisk(oldPath, newPath) {
if (!oldPath || !newPath) if (!oldPath || !newPath)
return false; return false;
@@ -526,7 +477,7 @@ class ProjectService {
} }
catch (error) { catch (error) {
console.error(`重命名目录失败: ${error}`); console.error(`重命名目录失败: ${error}`);
throw error; // Re-throw to be handled by ConfigPanel throw error;
} }
} }
async createAircraftDirectory(aircraft) { async createAircraftDirectory(aircraft) {
@@ -586,10 +537,6 @@ class ProjectService {
console.error(`确保容器目录存在失败: ${error}`); console.error(`确保容器目录存在失败: ${error}`);
} }
} }
/**
* 仅用于「新建容器」时的默认两个配置
* (导入容器时不会调用)
*/
async createDefaultConfigs(container) { async createDefaultConfigs(container) {
this.configs.push({ this.configs.push({
id: this.generateUniqueId('cfg', this.configs), id: this.generateUniqueId('cfg', this.configs),
@@ -630,18 +577,15 @@ class ProjectService {
if (!projectId) { if (!projectId) {
return null; return null;
} }
// 先根据旧的 projectId 算出要清理的 aircraft / container / config / moduleFolder
const existingAircrafts = this.aircrafts.filter(a => a.projectId === projectId); const existingAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = existingAircrafts.map(a => a.id); const aircraftIds = existingAircrafts.map(a => a.id);
const existingContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId)); const existingContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId));
const containerIds = existingContainers.map(c => c.id); const containerIds = existingContainers.map(c => c.id);
// 清理旧数据(同一个 projectId 的)
this.projects = this.projects.filter(p => p.id !== projectId); this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId); this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId)); this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId)); this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId)); this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
// ⭐ 载入新数据时顺便过滤掉名字为 ".git" 的容器(避免历史数据带进来)
const cleanedContainers = data.containers.filter(c => c.name !== '.git'); const cleanedContainers = data.containers.filter(c => c.name !== '.git');
this.projects.push(...data.projects); this.projects.push(...data.projects);
this.aircrafts.push(...data.aircrafts); this.aircrafts.push(...data.aircrafts);
@@ -694,7 +638,6 @@ class ProjectService {
const pathParts = folder.localPath.split('/').filter(part => part); const pathParts = folder.localPath.split('/').filter(part => part);
if (pathParts.length < 4) if (pathParts.length < 4)
return null; return null;
// 路径的最后一部分是文件夹的名称
const folderName = pathParts[pathParts.length - 1]; const folderName = pathParts[pathParts.length - 1];
return path.join(projectPath, aircraft.name, container.name, folderName); return path.join(projectPath, aircraft.name, container.name, folderName);
} }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -34,13 +34,12 @@ export class ProjectService {
* 文件名/文件夹名净化:保留大小写,允许中文、字母、数字、-、_、. * 文件名/文件夹名净化:保留大小写,允许中文、字母、数字、-、_、.
*/ */
public sanitizeFileName(name: string): string { public sanitizeFileName(name: string): string {
let fileName = name.trim(); // 移除 .toLowerCase() 以保留大小写 let fileName = name.trim();
// 1. 替换所有空格为下划线 // 1. 替换所有空格为下划线
fileName = fileName.replace(/\s+/g, '_'); fileName = fileName.replace(/\s+/g, '_');
// 2. 移除特殊字符 // 2. 移除特殊字符
// 在正则中增加了 A-Z 以及 \. (注意:\u4e00-\u9fa5 匹配中文)
fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_\-\.]/g, ''); fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_\-\.]/g, '');
// 3. 确保不为空 // 3. 确保不为空
@@ -50,6 +49,39 @@ export class ProjectService {
return fileName; return fileName;
} }
// =============== 查重工具方法 (新增) ===============
/** 检查项目名是否重复 */
isProjectNameExists(name: string): boolean {
return this.projects.some(p => p.name === name);
}
/** 检查指定项目下的飞行器名是否重复 */
isAircraftNameExists(projectId: string, name: string): boolean {
return this.aircrafts.some(a => a.projectId === projectId && a.name === name);
}
/** 检查指定飞行器下的容器名是否重复 */
isContainerNameExists(aircraftId: string, name: string): boolean {
return this.containers.some(c => c.aircraftId === aircraftId && c.name === name);
}
/** 检查指定容器下的配置名或文件名是否重复 */
isConfigOrFolderConflict(containerId: string, name: string, fileName: string, excludeId?: string): { nameConflict: boolean, fileConflict: boolean } {
const configs = this.getConfigsByContainer(containerId);
const folders = this.getModuleFoldersByContainer(containerId);
// 检查显示名称冲突 (排除自身)
const nameConflict = configs.some(c => c.id !== excludeId && c.name === name) ||
folders.some(f => f.id !== excludeId && f.name === name);
// 检查磁盘文件名/文件夹名冲突 (排除自身)
const fileConflict = configs.some(c => c.id !== excludeId && c.fileName === fileName) ||
folders.some(f => f.id !== excludeId && f.localPath.split('/').pop() === fileName);
return { nameConflict, fileConflict };
}
// =============== 项目相关方法 =============== // =============== 项目相关方法 ===============
getProjects(): Project[] { getProjects(): Project[] {
@@ -91,14 +123,12 @@ export class ProjectService {
const project = this.projects.find(p => p.id === projectId); const project = this.projects.find(p => p.id === projectId);
if (!project) return false; if (!project) return false;
// 先把要删的 id 都算出来,再统一过滤
const relatedAircrafts = this.aircrafts.filter(a => a.projectId === projectId); const relatedAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = relatedAircrafts.map(a => a.id); const aircraftIds = relatedAircrafts.map(a => a.id);
const relatedContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId)); const relatedContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId));
const containerIds = relatedContainers.map(c => c.id); const containerIds = relatedContainers.map(c => c.id);
// 真正删除数据
this.projects = this.projects.filter(p => p.id !== projectId); this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId); this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId)); this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
@@ -129,7 +159,6 @@ export class ProjectService {
}; };
this.aircrafts.push(newAircraft); this.aircrafts.push(newAircraft);
// 创建飞行器目录
await this.createAircraftDirectory(newAircraft); await this.createAircraftDirectory(newAircraft);
return newId; return newId;
} }
@@ -147,27 +176,19 @@ export class ProjectService {
const aircraft = this.aircrafts.find(a => a.id === aircraftId); const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft) return false; if (!aircraft) return false;
// ⚠️ 先算出要删的 containerIds
const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId); const relatedContainers = this.containers.filter(c => c.aircraftId === aircraftId);
const containerIds = relatedContainers.map(c => c.id); const containerIds = relatedContainers.map(c => c.id);
// 删除飞机自身和容器
this.aircrafts = this.aircrafts.filter(a => a.id !== aircraftId); this.aircrafts = this.aircrafts.filter(a => a.id !== aircraftId);
this.containers = this.containers.filter(c => c.aircraftId !== aircraftId); this.containers = this.containers.filter(c => c.aircraftId !== aircraftId);
// 再删关联的 config 和 moduleFolder
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId)); this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId)); this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
return true; return true;
} }
/**
* 从已存在的磁盘目录导入飞行器
* 不会创建/删除任何文件,只在内存中补充 Aircraft / Container / ModuleFolder 数据
*/
async importAircraftFromExistingFolder(projectId: string, aircraftName: string): Promise<string> { async importAircraftFromExistingFolder(projectId: string, aircraftName: string): Promise<string> {
// 已存在同名飞行器直接返回
const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName); const existed = this.getAircraftsByProject(projectId).find(a => a.name === aircraftName);
if (existed) { if (existed) {
return existed.id; return existed.id;
@@ -188,11 +209,9 @@ export class ProjectService {
const aircraftDir = path.join(projectPath, aircraftName); const aircraftDir = path.join(projectPath, aircraftName);
if (!fs.existsSync(aircraftDir)) { if (!fs.existsSync(aircraftDir)) {
// 目录不存在就不再解析子目录
return newId; return newId;
} }
// 每个子目录视为一个容器(排除 .git
const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true }); const entries = await fs.promises.readdir(aircraftDir, { withFileTypes: true });
for (const entry of entries) { for (const entry of entries) {
if (!entry.isDirectory()) continue; if (!entry.isDirectory()) continue;
@@ -206,12 +225,6 @@ export class ProjectService {
// =============== 容器相关方法 =============== // =============== 容器相关方法 ===============
/**
* ⚠️ 这里不再做 .git 过滤:
* - 我们只会通过 createContainer / importContainerFromExistingFolder 创建容器
* - importContainerFromExistingFolder 内部已经排除了 .git
* 所以不会出现名字叫 ".git" 的容器
*/
getContainersByAircraft(aircraftId: string): Container[] { getContainersByAircraft(aircraftId: string): Container[] {
return this.containers.filter(c => c.aircraftId === aircraftId); return this.containers.filter(c => c.aircraftId === aircraftId);
} }
@@ -230,7 +243,6 @@ export class ProjectService {
this.containers.push(newContainer); this.containers.push(newContainer);
await this.createContainerDirectory(newContainer); await this.createContainerDirectory(newContainer);
// UI 手动创建的容器,仍然保留“默认两个配置”的行为
await this.createDefaultConfigs(newContainer); await this.createDefaultConfigs(newContainer);
return newId; return newId;
@@ -256,15 +268,6 @@ export class ProjectService {
return true; return true;
} }
/**
* 从已存在的磁盘目录导入容器
*
* ✅ 你的最新需求:
* - 不创建“默认两个配置”
* - 自动扫描容器目录下的『子文件夹』
* - 每个子文件夹创建一个 ModuleFoldertype: 'local'
* - 排除 `.git` 子文件夹
*/
async importContainerFromExistingFolder(aircraftId: string, containerName: string): Promise<string> { async importContainerFromExistingFolder(aircraftId: string, containerName: string): Promise<string> {
if (containerName === '.git') { if (containerName === '.git') {
throw new Error('不能将 .git 导入为容器'); throw new Error('不能将 .git 导入为容器');
@@ -283,18 +286,11 @@ export class ProjectService {
}; };
this.containers.push(container); this.containers.push(container);
// 🚩 关键逻辑:扫描容器目录下的子文件夹 -> 创建 ModuleFolder
await this.scanContainerModuleFolders(container); await this.scanContainerModuleFolders(container);
// 不再创建默认两个配置(不调用 createDefaultConfigs
// 也不创建目录(目录是 Git 克隆出来的,本来就存在)
return newId; return newId;
} }
/**
* 扫描容器目录中的子文件夹(不含 .git将其作为本地模块文件夹记录
*/
private async scanContainerModuleFolders(container: Container): Promise<void> { private async scanContainerModuleFolders(container: Container): Promise<void> {
try { try {
const aircraft = this.aircrafts.find(a => a.id === container.aircraftId); const aircraft = this.aircrafts.find(a => a.id === container.aircraftId);
@@ -343,11 +339,8 @@ export class ProjectService {
return this.configs.find(c => c.id === configId); return this.configs.find(c => c.id === configId);
} }
// src/panels/services/ProjectService.ts
async createConfig(name: string, containerId: string): Promise<string> { async createConfig(name: string, containerId: string): Promise<string> {
const newId = this.generateUniqueId('cfg', this.configs); const newId = this.generateUniqueId('cfg', this.configs);
// 使用净化后的文件名
const newFileName = this.sanitizeFileName(name); const newFileName = this.sanitizeFileName(name);
const newConfig: Config = { const newConfig: Config = {
@@ -358,16 +351,12 @@ export class ProjectService {
}; };
this.configs.push(newConfig); this.configs.push(newConfig);
// 1. 首先确保父级容器目录存在
await this.ensureContainerDirectoryExists(containerId); await this.ensureContainerDirectoryExists(containerId);
// 2. 获取文件的完整磁盘路径
const filePath = this.getConfigFilePath(newId); const filePath = this.getConfigFilePath(newId);
if (filePath) { if (filePath) {
try { try {
const fileUri = vscode.Uri.file(filePath); const fileUri = vscode.Uri.file(filePath);
// 3. 真实创建文件:写入一个空的 Uint8Array 即可创建一个空文件
// 如果你希望有默认内容,可以在这里自定义 TextEncoder().encode("...")
await vscode.workspace.fs.writeFile(fileUri, new Uint8Array()); await vscode.workspace.fs.writeFile(fileUri, new Uint8Array());
console.log(`✅ 磁盘文件已真实创建: ${filePath}`); console.log(`✅ 磁盘文件已真实创建: ${filePath}`);
} catch (error) { } catch (error) {
@@ -378,9 +367,6 @@ export class ProjectService {
return newId; return newId;
} }
/**
* [修改] 同时更新配置显示名称和文件名称
*/
updateConfigName(configId: string, newName: string, newFileName: string): boolean { updateConfigName(configId: string, newName: string, newFileName: string): boolean {
const config = this.configs.find(c => c.id === configId); const config = this.configs.find(c => c.id === configId);
if (config) { if (config) {
@@ -399,19 +385,14 @@ export class ProjectService {
return true; return true;
} }
/**
* [新增] 重命名磁盘上的配置文件
*/
async renameConfigFileOnDisk(configId: string, newFileName: string): Promise<boolean> { async renameConfigFileOnDisk(configId: string, newFileName: string): Promise<boolean> {
const config = this.configs.find(c => c.id === configId); const config = this.configs.find(c => c.id === configId);
if (!config) return false; if (!config) return false;
const oldFilePath = this.getConfigFilePath(configId); const oldFilePath = this.getConfigFilePath(configId);
// 为了计算新路径,临时使用新的文件名
const oldFileName = config.fileName; const oldFileName = config.fileName;
config.fileName = newFileName; config.fileName = newFileName;
const newFilePath = this.getConfigFilePath(configId); const newFilePath = this.getConfigFilePath(configId);
// 恢复旧的文件名,因为 ProjectService.updateConfigName 会在 ConfigPanel 中调用
config.fileName = oldFileName; config.fileName = oldFileName;
if (!oldFilePath || !newFilePath) { if (!oldFilePath || !newFilePath) {
@@ -467,11 +448,8 @@ export class ProjectService {
return true; return true;
} }
// =============== 文件系统操作 (新增/修改) =============== // =============== 文件系统操作 ===============
/**
* 获取项目目录重命名所需的旧路径和新路径(新增)
*/
getProjectOldAndNewPaths(projectId: string, newName: string): { oldPath: string, newPath: string } | null { getProjectOldAndNewPaths(projectId: string, newName: string): { oldPath: string, newPath: string } | null {
const project = this.projects.find(p => p.id === projectId); const project = this.projects.find(p => p.id === projectId);
const oldPath = this.projectPaths.get(projectId); const oldPath = this.projectPaths.get(projectId);
@@ -479,15 +457,11 @@ export class ProjectService {
if (!project || !oldPath) return null; if (!project || !oldPath) return null;
const parentDir = path.dirname(oldPath); const parentDir = path.dirname(oldPath);
// 新路径使用新的项目名称作为文件夹名
const newPath = path.join(parentDir, newName); const newPath = path.join(parentDir, newName);
return { oldPath, newPath }; return { oldPath, newPath };
} }
/**
* 获取飞行器目录的完整路径
*/
getAircraftDirectoryPath(aircraftId: string): string | null { getAircraftDirectoryPath(aircraftId: string): string | null {
const aircraft = this.getAircraft(aircraftId); const aircraft = this.getAircraft(aircraftId);
if (!aircraft) return null; if (!aircraft) return null;
@@ -498,10 +472,6 @@ export class ProjectService {
return path.join(projectPath, aircraft.name); return path.join(projectPath, aircraft.name);
} }
/**
* 获取飞行器目录重命名所需的旧路径和新路径(新增)
* 注意oldPath使用内存中当前的名称newPath使用将要更新的新名称
*/
getAircraftOldAndNewPaths(aircraftId: string, newName: string): { oldPath: string, newPath: string } | null { getAircraftOldAndNewPaths(aircraftId: string, newName: string): { oldPath: string, newPath: string } | null {
const aircraft = this.aircrafts.find(a => a.id === aircraftId); const aircraft = this.aircrafts.find(a => a.id === aircraftId);
if (!aircraft) return null; if (!aircraft) return null;
@@ -516,9 +486,6 @@ export class ProjectService {
} }
/**
* 获取容器目录的完整路径
*/
getContainerDirectoryPath(containerId: string): string | null { getContainerDirectoryPath(containerId: string): string | null {
const container = this.getContainer(containerId); const container = this.getContainer(containerId);
if (!container) return null; if (!container) return null;
@@ -532,10 +499,6 @@ export class ProjectService {
return path.join(projectPath, aircraft.name, container.name); return path.join(projectPath, aircraft.name, container.name);
} }
/**
* 获取容器目录重命名所需的旧路径和新路径(新增)
* 注意oldPath使用内存中当前的名称newPath使用将要更新的新名称
*/
getContainerOldAndNewPaths(containerId: string, newName: string): { oldPath: string, newPath: string } | null { getContainerOldAndNewPaths(containerId: string, newName: string): { oldPath: string, newPath: string } | null {
const container = this.containers.find(c => c.id === containerId); const container = this.containers.find(c => c.id === containerId);
if (!container) return null; if (!container) return null;
@@ -554,9 +517,6 @@ export class ProjectService {
} }
/**
* 递归删除目录
*/
async deleteDirectoryFromDisk(directoryPath: string): Promise<boolean> { async deleteDirectoryFromDisk(directoryPath: string): Promise<boolean> {
if (!directoryPath) return false; if (!directoryPath) return false;
@@ -569,13 +529,10 @@ export class ProjectService {
return false; return false;
} catch (error) { } catch (error) {
console.error(`删除目录失败: ${error}`); console.error(`删除目录失败: ${error}`);
throw error; // Re-throw to be handled by ConfigPanel throw error;
} }
} }
/**
* 重命名磁盘上的目录(新增)
*/
async renameDirectoryOnDisk(oldPath: string, newPath: string): Promise<boolean> { async renameDirectoryOnDisk(oldPath: string, newPath: string): Promise<boolean> {
if (!oldPath || !newPath) return false; if (!oldPath || !newPath) return false;
@@ -589,7 +546,7 @@ export class ProjectService {
return false; return false;
} catch (error) { } catch (error) {
console.error(`重命名目录失败: ${error}`); console.error(`重命名目录失败: ${error}`);
throw error; // Re-throw to be handled by ConfigPanel throw error;
} }
} }
@@ -659,10 +616,6 @@ export class ProjectService {
} }
} }
/**
* 仅用于「新建容器」时的默认两个配置
* (导入容器时不会调用)
*/
private async createDefaultConfigs(container: Container): Promise<void> { private async createDefaultConfigs(container: Container): Promise<void> {
this.configs.push({ this.configs.push({
id: this.generateUniqueId('cfg', this.configs), id: this.generateUniqueId('cfg', this.configs),
@@ -709,21 +662,18 @@ export class ProjectService {
return null; return null;
} }
// 先根据旧的 projectId 算出要清理的 aircraft / container / config / moduleFolder
const existingAircrafts = this.aircrafts.filter(a => a.projectId === projectId); const existingAircrafts = this.aircrafts.filter(a => a.projectId === projectId);
const aircraftIds = existingAircrafts.map(a => a.id); const aircraftIds = existingAircrafts.map(a => a.id);
const existingContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId)); const existingContainers = this.containers.filter(c => aircraftIds.includes(c.aircraftId));
const containerIds = existingContainers.map(c => c.id); const containerIds = existingContainers.map(c => c.id);
// 清理旧数据(同一个 projectId 的)
this.projects = this.projects.filter(p => p.id !== projectId); this.projects = this.projects.filter(p => p.id !== projectId);
this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId); this.aircrafts = this.aircrafts.filter(a => a.projectId !== projectId);
this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId)); this.containers = this.containers.filter(c => !aircraftIds.includes(c.aircraftId));
this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId)); this.configs = this.configs.filter(cfg => !containerIds.includes(cfg.containerId));
this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId)); this.moduleFolders = this.moduleFolders.filter(folder => !containerIds.includes(folder.containerId));
// ⭐ 载入新数据时顺便过滤掉名字为 ".git" 的容器(避免历史数据带进来)
const cleanedContainers = data.containers.filter(c => c.name !== '.git'); const cleanedContainers = data.containers.filter(c => c.name !== '.git');
this.projects.push(...data.projects); this.projects.push(...data.projects);
@@ -778,7 +728,6 @@ export class ProjectService {
const pathParts = folder.localPath.split('/').filter(part => part); const pathParts = folder.localPath.split('/').filter(part => part);
if (pathParts.length < 4) return null; if (pathParts.length < 4) return null;
// 路径的最后一部分是文件夹的名称
const folderName = pathParts[pathParts.length - 1]; const folderName = pathParts[pathParts.length - 1];
return path.join(projectPath, aircraft.name, container.name, folderName); return path.join(projectPath, aircraft.name, container.name, folderName);
} }