1、将每个代码仓库进行了隔离。2、优化了每个页面的ui对齐。3、增加了飞行器页面的所属项目显示。4、切换页面时对项目进行扫描,实时更新项目的结构,避免复制导致ui不显示的bug
This commit is contained in:
105
src/panels/ConfigPanel.ts
Executable file → Normal file
105
src/panels/ConfigPanel.ts
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
// src/panels/ConfigPanel.ts
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { ProjectView } from './views/ProjectView';
|
||||
@@ -8,21 +7,7 @@ import { ConfigView } from './views/ConfigView';
|
||||
import { ProjectService } from './services/ProjectService';
|
||||
import { GitService } from './services/GitService';
|
||||
import { StorageService } from './services/StorageService';
|
||||
import { ModuleFolder } from './types/CommonTypes';
|
||||
|
||||
interface RepoConfigItem {
|
||||
name: string;
|
||||
url: string;
|
||||
username?: string;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
interface GitFileTree {
|
||||
name: string;
|
||||
type: 'file' | 'folder';
|
||||
path: string;
|
||||
children?: GitFileTree[];
|
||||
}
|
||||
import { ModuleFolder, RepoConfigItem , GitFileTree} from './types/CommonTypes';
|
||||
|
||||
type CloneScope = 'project' | 'aircraft' | 'container' | 'config';
|
||||
|
||||
@@ -124,17 +109,14 @@ export class ConfigPanel {
|
||||
private async openRepoSelectForScope(scope: CloneScope): Promise<void> {
|
||||
this.currentCloneScope = scope;
|
||||
await this.loadRepoConfigs();
|
||||
|
||||
if (this.repoConfigs.length === 0) {
|
||||
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const repos = this.getRepoConfigsForScope(scope);
|
||||
|
||||
if (this.isWebviewDisposed) return;
|
||||
|
||||
|
||||
this.panel.webview.postMessage({
|
||||
type: 'showRepoSelect',
|
||||
repos: this.repoConfigs.map(r => ({ name: r.name }))
|
||||
repos: repos.map(r => ({ name: r.name }))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -144,19 +126,16 @@ export class ConfigPanel {
|
||||
|
||||
private async openUploadRepoSelect(folderId: string, folderType: 'git' | 'local'): Promise<void> {
|
||||
await this.loadRepoConfigs();
|
||||
|
||||
if (this.repoConfigs.length === 0) {
|
||||
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.isWebviewDisposed) return;
|
||||
|
||||
|
||||
const repos = this.getRepoConfigsForScope('config');
|
||||
|
||||
this.panel.webview.postMessage({
|
||||
type: 'showUploadRepoSelect',
|
||||
repos: this.repoConfigs.map(r => ({ name: r.name })),
|
||||
repos: repos.map(r => ({ name: r.name })),
|
||||
folderId,
|
||||
folderType
|
||||
folderType
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,19 +144,16 @@ export class ConfigPanel {
|
||||
id: string
|
||||
): Promise<void> {
|
||||
await this.loadRepoConfigs();
|
||||
|
||||
if (this.repoConfigs.length === 0) {
|
||||
vscode.window.showWarningMessage('尚未配置任何仓库,请先点击右上角 "仓库配置" 按钮编辑 dcsp-repos.json。');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const repos = this.getRepoConfigsForScope(scope);
|
||||
|
||||
if (this.isWebviewDisposed) return;
|
||||
|
||||
|
||||
this.panel.webview.postMessage({
|
||||
type: 'showUploadRepoSelect',
|
||||
repos: this.repoConfigs.map(r => ({ name: r.name })),
|
||||
repos: repos.map(r => ({ name: r.name })),
|
||||
folderId: id,
|
||||
folderType: scope
|
||||
folderType: scope
|
||||
});
|
||||
}
|
||||
|
||||
@@ -390,7 +366,7 @@ export class ConfigPanel {
|
||||
}
|
||||
|
||||
private async handleOpenProject(data: any): Promise<void> {
|
||||
// [新增] 在进入视图前,主动扫描磁盘同步数据
|
||||
// 在进入视图前,主动扫描磁盘同步数据
|
||||
const changed = await this.projectService.refreshProjectContent(data.projectId);
|
||||
if (changed) {
|
||||
await this.saveCurrentProjectData(); // 如果有变动,立即保存
|
||||
@@ -398,11 +374,12 @@ export class ConfigPanel {
|
||||
|
||||
this.currentView = 'aircrafts';
|
||||
this.currentProjectId = data.projectId;
|
||||
|
||||
this.updateWebview();
|
||||
}
|
||||
|
||||
private async handleOpenAircraftConfig(data: any): Promise<void> {
|
||||
// [新增] 进入飞行器详情前,主动同步容器列表
|
||||
// 进入飞行器详情前,主动同步容器列表
|
||||
const changed = await this.projectService.refreshAircraftContent(data.aircraftId);
|
||||
if (changed) {
|
||||
await this.saveCurrentProjectData();
|
||||
@@ -415,7 +392,7 @@ export class ConfigPanel {
|
||||
}
|
||||
|
||||
private async handleOpenContainerConfig(data: any): Promise<void> {
|
||||
// [新增] 进入容器详情前,主动同步配置和模块
|
||||
// 进入容器详情前,主动同步配置和模块
|
||||
const changed = await this.projectService.refreshContainerContent(data.containerId);
|
||||
if (changed) {
|
||||
await this.saveCurrentProjectData();
|
||||
@@ -661,7 +638,7 @@ export class ConfigPanel {
|
||||
}
|
||||
|
||||
const containerId = await this.projectService.createContainer(name, this.currentAircraftId);
|
||||
vscode.window.showInformationMessage(`新建容器: ${name} (包含2个默认配置文件)`);
|
||||
vscode.window.showInformationMessage(`新建容器: ${name}`);
|
||||
await this.saveCurrentProjectData();
|
||||
this.updateWebview();
|
||||
}
|
||||
@@ -1874,7 +1851,7 @@ export class ConfigPanel {
|
||||
this.currentProjectId = projectId;
|
||||
this.currentView = 'aircrafts';
|
||||
|
||||
// [新增] 项目加载成功后,启动文件监听
|
||||
// 项目加载成功后,启动文件监听
|
||||
this.setupFileWatcher(projectPath, projectId);
|
||||
|
||||
vscode.window.showInformationMessage(`项目数据已从 ${projectPath} 加载`);
|
||||
@@ -1889,17 +1866,16 @@ export class ConfigPanel {
|
||||
}
|
||||
|
||||
private setupFileWatcher(projectPath: string, projectId: string) {
|
||||
// 1. 如果已有监听器,先销毁
|
||||
// 如果已有监听器,先销毁
|
||||
if (this.fileWatcher) {
|
||||
this.fileWatcher.dispose();
|
||||
}
|
||||
|
||||
// 2. 创建新的监听器:监听项目目录下的所有变化
|
||||
// Pattern: /path/to/project/**/*
|
||||
// 创建新的监听器:监听项目目录下的所有变化
|
||||
const pattern = new vscode.RelativePattern(projectPath, '**/*');
|
||||
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern, false, true, false); // 忽略 onChange,只监听 Create/Delete
|
||||
|
||||
// 3. 定义处理逻辑(使用防抖,避免连续创建文件导致多次刷新)
|
||||
// 定义处理逻辑(使用防抖,避免连续创建文件导致多次刷新)
|
||||
const handleCreate = async (uri: vscode.Uri) => {
|
||||
if (this.isWebviewDisposed) return;
|
||||
|
||||
@@ -1914,7 +1890,6 @@ export class ConfigPanel {
|
||||
|
||||
// 层级 1: 飞行器 (Folder)
|
||||
if (parts.length === 1) {
|
||||
// 检查是文件还是文件夹
|
||||
const stat = await vscode.workspace.fs.stat(uri);
|
||||
if ((stat.type & vscode.FileType.Directory) === vscode.FileType.Directory) {
|
||||
await this.projectService.importAircraftFromExistingFolder(projectId, parts[0]);
|
||||
@@ -1970,7 +1945,7 @@ export class ConfigPanel {
|
||||
}
|
||||
};
|
||||
|
||||
// 4. 绑定事件 (防抖建议设为 500ms 左右)
|
||||
// 绑定事件 (防抖建议设为 500ms 左右)
|
||||
const debouncedCreate = debounce(handleCreate, 500);
|
||||
const debouncedDelete = debounce(handleDelete, 500);
|
||||
|
||||
@@ -1994,6 +1969,12 @@ export class ConfigPanel {
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
private getRepoConfigsForScope(scope: CloneScope): RepoConfigItem[] {
|
||||
return (this.repoConfigs || []).filter(r =>
|
||||
Array.isArray(r.scopes) && r.scopes.includes(scope)
|
||||
);
|
||||
}
|
||||
|
||||
private getWebviewContent(): string {
|
||||
switch (this.currentView) {
|
||||
case 'projects':
|
||||
@@ -2001,12 +1982,20 @@ export class ConfigPanel {
|
||||
projects: this.projectService.getProjects(),
|
||||
projectPaths: this.projectService.getProjectPaths()
|
||||
});
|
||||
case 'aircrafts':
|
||||
const projectAircrafts = this.projectService.getAircraftsByProject(this.currentProjectId);
|
||||
case 'aircrafts':{
|
||||
const project = this.projectService.getProjects()
|
||||
.find(p => p.id === this.currentProjectId);
|
||||
|
||||
const projectAircrafts = this.projectService
|
||||
.getAircraftsByProject(this.currentProjectId)
|
||||
.filter(a => a.name !== '.git');
|
||||
|
||||
return this.aircraftView.render({
|
||||
project: project,
|
||||
aircrafts: projectAircrafts
|
||||
});
|
||||
case 'containers':
|
||||
}
|
||||
case 'containers':{
|
||||
const project = this.projectService.getProjects().find(p => p.id === this.currentProjectId);
|
||||
const currentAircraft = this.projectService.getAircraftsByProject(this.currentProjectId)
|
||||
.find(a => a.id === this.currentAircraftId);
|
||||
@@ -2020,7 +2009,8 @@ export class ConfigPanel {
|
||||
aircraft: currentAircraft,
|
||||
containers: projectContainers
|
||||
});
|
||||
case 'configs':
|
||||
}
|
||||
case 'configs':{
|
||||
const currentContainer = this.projectService.getContainersByAircraft(this.currentAircraftId)
|
||||
.find(c => c.id === this.currentContainerId);
|
||||
const currentModuleFolder = this.projectService.getModuleFolder(this.currentModuleFolderId);
|
||||
@@ -2035,6 +2025,7 @@ export class ConfigPanel {
|
||||
moduleFolderFileTree: this.currentModuleFolderFileTree,
|
||||
moduleFolderLoading: false
|
||||
});
|
||||
}
|
||||
default:
|
||||
return this.projectView.render({
|
||||
projects: this.projectService.getProjects(),
|
||||
|
||||
17
src/panels/services/GitService.ts
Executable file → Normal file
17
src/panels/services/GitService.ts
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
// src/panels/services/GitService.ts
|
||||
import * as fs from 'fs';
|
||||
import git from 'isomorphic-git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
@@ -88,8 +87,7 @@ export class GitService {
|
||||
|
||||
if (dirExists) {
|
||||
const dirContents = await fs.promises.readdir(localPath);
|
||||
// 修正后的逻辑:过滤掉所有以 "." 开头的隐藏文件/目录。
|
||||
// 只有当存在非 "." 开头的文件或目录时,才阻止克隆。
|
||||
// 过滤掉所有以 "." 开头的隐藏文件/目录。
|
||||
const nonBenignFiles = dirContents.filter(item => !item.startsWith('.'));
|
||||
|
||||
if (nonBenignFiles.length > 0) {
|
||||
@@ -210,7 +208,6 @@ export class GitService {
|
||||
if (username || token) {
|
||||
const u = encodeURIComponent(username || '');
|
||||
const t = encodeURIComponent(token || '');
|
||||
// 注意: 仅在 URL 是 http/https 时添加 auth
|
||||
if (repoUrl.startsWith('http')) {
|
||||
finalUrl = repoUrl.replace('://', `://${u}:${t}@`);
|
||||
}
|
||||
@@ -246,7 +243,7 @@ export class GitService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Git命令提交并推送 (用于原有的 Git 模块更新)
|
||||
* 使用Git命令提交并推送
|
||||
*/
|
||||
static async commitAndPush(localPath: string): Promise<void> {
|
||||
await execAsync('git add .', { cwd: localPath });
|
||||
@@ -297,12 +294,12 @@ export class GitService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交并推送到指定分支和远程 (用于 Project/Aircraft/Container 上传)
|
||||
* 提交并推送到指定分支和远程
|
||||
*/
|
||||
static async commitAndPushToBranch(
|
||||
localPath: string,
|
||||
branchName: string,
|
||||
repoUrl?: string, // 这里的 repoUrl 是为了构造带 auth 的 finalUrl
|
||||
repoUrl?: string,
|
||||
username?: string,
|
||||
token?: string
|
||||
): Promise<void> {
|
||||
@@ -311,7 +308,6 @@ export class GitService {
|
||||
if (repoUrl && (username || token)) {
|
||||
const u = encodeURIComponent(username || '');
|
||||
const t = encodeURIComponent(token || '');
|
||||
// 注意: 仅在 URL 是 http/https 时添加 auth
|
||||
if (repoUrl.startsWith('http')) {
|
||||
finalUrl = repoUrl.replace('://', `://${u}:${t}@`);
|
||||
}
|
||||
@@ -358,13 +354,8 @@ export class GitService {
|
||||
const tree: GitFileTree[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
// 1. 不要解析 .git
|
||||
if (file === '.git') continue;
|
||||
|
||||
// 2. 不要解析项目数据文件
|
||||
if (file === 'dcsp-data.json') continue;
|
||||
|
||||
// 3. 其它所有隐藏文件/目录统统忽略
|
||||
if (file.startsWith('.')) continue;
|
||||
|
||||
const filePath = path.join(dir, file);
|
||||
|
||||
67
src/panels/services/ProjectService.ts
Executable file → Normal file
67
src/panels/services/ProjectService.ts
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
// src/panels/services/ProjectService.ts
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
@@ -36,20 +35,20 @@ export class ProjectService {
|
||||
public sanitizeFileName(name: string): string {
|
||||
let fileName = name.trim();
|
||||
|
||||
// 1. 替换所有空格为下划线
|
||||
// 替换所有空格为下划线
|
||||
fileName = fileName.replace(/\s+/g, '_');
|
||||
|
||||
// 2. 移除特殊字符
|
||||
// 移除特殊字符
|
||||
fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_\-\.]/g, '');
|
||||
|
||||
// 3. 确保不为空
|
||||
// 确保不为空
|
||||
if (fileName.length === 0) {
|
||||
return 'config_file';
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// =============== 查重工具方法 (新增) ===============
|
||||
// =============== 查重工具方法 ===============
|
||||
|
||||
/** 检查项目名是否重复 */
|
||||
isProjectNameExists(name: string): boolean {
|
||||
@@ -243,7 +242,6 @@ export class ProjectService {
|
||||
this.containers.push(newContainer);
|
||||
|
||||
await this.createContainerDirectory(newContainer);
|
||||
await this.createDefaultConfigs(newContainer);
|
||||
|
||||
return newId;
|
||||
}
|
||||
@@ -616,24 +614,7 @@ export class ProjectService {
|
||||
}
|
||||
}
|
||||
|
||||
private async createDefaultConfigs(container: Container): Promise<void> {
|
||||
this.configs.push({
|
||||
id: this.generateUniqueId('cfg', this.configs),
|
||||
name: '配置1',
|
||||
fileName: 'dockerfile',
|
||||
containerId: container.id
|
||||
});
|
||||
|
||||
this.configs.push({
|
||||
id: this.generateUniqueId('cfg', this.configs),
|
||||
name: '配置2',
|
||||
fileName: 'docker-compose.yml',
|
||||
containerId: container.id
|
||||
});
|
||||
}
|
||||
|
||||
// =============== 数据持久化 ===============
|
||||
|
||||
async saveCurrentProjectData(projectId: string): Promise<boolean> {
|
||||
try {
|
||||
const projectPath = this.projectPaths.get(projectId);
|
||||
@@ -781,7 +762,7 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
/**
|
||||
* [新方法] 仅在内存中注册模块文件夹(用于监测到磁盘文件夹创建时)
|
||||
* 仅在内存中注册模块文件夹(用于监测到磁盘文件夹创建时)
|
||||
*/
|
||||
registerModuleFolderFromDisk(
|
||||
folderName: string,
|
||||
@@ -794,8 +775,7 @@ export class ProjectService {
|
||||
): boolean {
|
||||
const relativePath = `/${projectId}/${aircraftName}/${containerName}/${folderName}`;
|
||||
|
||||
// 查重:只要同一个容器下,有任何一个模块指向了相同的文件夹名,就视为已存在
|
||||
// 这样即使旧数据的 localPath 路径字符串是过时的,也不会重复添加
|
||||
// 只要同一个容器下,有任何一个模块指向了相同的文件夹名,就视为已存在
|
||||
const existing = this.moduleFolders.find(f => {
|
||||
if (f.containerId !== containerId) return false;
|
||||
// 从 localPath 中提取最后一部分(文件夹名)进行比对
|
||||
@@ -805,7 +785,7 @@ export class ProjectService {
|
||||
|
||||
if (existing) return false;
|
||||
|
||||
// 智能检测:如果文件夹下有 .git,则标记为 git 类型
|
||||
// 如果文件夹下有 .git,则标记为 git 类型
|
||||
let type: 'local' | 'git' = 'local';
|
||||
if (fullPath) {
|
||||
const gitDir = path.join(fullPath, '.git');
|
||||
@@ -827,12 +807,9 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
/**
|
||||
* [新方法] 处理磁盘删除事件:根据路径移除对应的 Config 或 ModuleFolder
|
||||
* 处理磁盘删除事件:根据路径移除对应的 Config 或 ModuleFolder
|
||||
*/
|
||||
removeEntityByPath(filePath: string, projectId: string): boolean {
|
||||
// 这里的 filePath 是绝对路径,需要反解出是哪个 Config 或 Folder
|
||||
// 这是一个比较繁琐的过程,简化逻辑如下:
|
||||
|
||||
removeEntityByPath(filePath: string, projectId: string): boolean {
|
||||
let changed = false;
|
||||
const projectPath = this.projectPaths.get(projectId);
|
||||
if (!projectPath || !filePath.startsWith(projectPath)) return false;
|
||||
@@ -871,7 +848,7 @@ export class ProjectService {
|
||||
|
||||
let changed = false;
|
||||
|
||||
// 1. 扫描磁盘,添加缺失的飞行器
|
||||
// 扫描磁盘,添加缺失的飞行器
|
||||
try {
|
||||
const entries = await fs.promises.readdir(projectPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
@@ -890,7 +867,7 @@ export class ProjectService {
|
||||
console.error('扫描项目目录失败:', e);
|
||||
}
|
||||
|
||||
// 2. 检查内存,移除磁盘不存在的飞行器 (清理无效数据)
|
||||
// 检查内存,移除磁盘不存在的飞行器 (清理无效数据)
|
||||
const currentAircrafts = this.getAircraftsByProject(projectId);
|
||||
for (const aircraft of currentAircrafts) {
|
||||
const aircraftPath = path.join(projectPath, aircraft.name);
|
||||
@@ -912,7 +889,7 @@ export class ProjectService {
|
||||
|
||||
let changed = false;
|
||||
|
||||
// 1. 扫描磁盘,添加缺失的容器
|
||||
// 扫描磁盘,添加缺失的容器
|
||||
try {
|
||||
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
@@ -929,7 +906,7 @@ export class ProjectService {
|
||||
console.error('扫描飞行器目录失败:', e);
|
||||
}
|
||||
|
||||
// 2. 检查内存,移除磁盘不存在的容器
|
||||
// 检查内存,移除磁盘不存在的容器
|
||||
const currentContainers = this.getContainersByAircraft(aircraftId);
|
||||
for (const container of currentContainers) {
|
||||
const containerPath = path.join(dirPath, container.name);
|
||||
@@ -957,7 +934,7 @@ export class ProjectService {
|
||||
|
||||
let changed = false;
|
||||
|
||||
// 1. 扫描磁盘,添加缺失的配置和模块
|
||||
// 扫描磁盘,添加缺失的配置和模块
|
||||
try {
|
||||
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
@@ -984,7 +961,7 @@ export class ProjectService {
|
||||
console.error('扫描容器目录失败:', e);
|
||||
}
|
||||
|
||||
// 2. 检查内存,移除磁盘不存在的 配置
|
||||
// 检查内存,移除磁盘不存在的 配置
|
||||
const currentConfigs = this.getConfigsByContainer(containerId);
|
||||
for (const config of currentConfigs) {
|
||||
const configPath = path.join(dirPath, config.fileName);
|
||||
@@ -994,12 +971,9 @@ export class ProjectService {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查内存,移除磁盘不存在的 模块文件夹
|
||||
// 检查内存,移除磁盘不存在的 模块文件夹
|
||||
const currentModules = this.getModuleFoldersByContainer(containerId);
|
||||
for (const folder of currentModules) {
|
||||
// 注意:Git 类型的模块文件夹,其 localPath 也是指向这个目录的
|
||||
// 这里我们简单拼接路径来检查
|
||||
// folder.localPath 格式: /projectId/aircraft/container/folderName
|
||||
const folderName = folder.localPath.split('/').pop();
|
||||
if (folderName) {
|
||||
const folderPath = path.join(dirPath, folderName);
|
||||
@@ -1014,7 +988,7 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
/**
|
||||
* [新增] 递归清理目录下所有的嵌套 .git 文件夹
|
||||
* 递归清理目录下所有的嵌套 .git 文件夹
|
||||
* 作用:保留 rootDir 下的 .git,但删除所有子目录中的 .git
|
||||
* 解决父级上传时因包含子级仓库导致的冲突
|
||||
*/
|
||||
@@ -1025,10 +999,10 @@ export class ProjectService {
|
||||
const entries = await fs.promises.readdir(rootDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
// 1. 如果遍历到的是当前目录下的 .git,直接跳过(这是我们要保留的主仓库)
|
||||
// 如果遍历到的是当前目录下的 .git,直接跳过
|
||||
if (entry.name === '.git') continue;
|
||||
|
||||
// 2. 只处理目录
|
||||
// 只处理目录
|
||||
if (entry.isDirectory()) {
|
||||
const subDir = path.join(rootDir, entry.name);
|
||||
const nestedGit = path.join(subDir, '.git');
|
||||
@@ -1036,7 +1010,7 @@ export class ProjectService {
|
||||
// 检查子目录下是否有 .git
|
||||
if (fs.existsSync(nestedGit)) {
|
||||
console.log(`🧹 发现嵌套 Git 仓库,正在移除以支持父级上传: ${nestedGit}`);
|
||||
// 使用您之前实现的强力删除方法(带权限处理)
|
||||
// 使用强力删除方法(带权限处理)
|
||||
await this.deleteDirectoryFromDisk(nestedGit);
|
||||
}
|
||||
|
||||
@@ -1046,7 +1020,6 @@ export class ProjectService {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`清理嵌套 Git 失败: ${error}`);
|
||||
// 不抛出错误,尽力而为,以免打断上传流程
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/panels/services/StorageService.ts
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
11
src/panels/types/CommonTypes.ts
Executable file → Normal file
11
src/panels/types/CommonTypes.ts
Executable file → Normal file
@@ -1,5 +1,3 @@
|
||||
// src/panels/types/CommonTypes.ts
|
||||
// 统一的核心类型定义
|
||||
|
||||
// =============================================
|
||||
// 基础实体接口
|
||||
@@ -83,10 +81,11 @@ export interface GitBranch {
|
||||
|
||||
// 仓库配置项
|
||||
export interface RepoConfigItem {
|
||||
name: string;
|
||||
url: string;
|
||||
username?: string;
|
||||
token?: string;
|
||||
name: string;
|
||||
url: string;
|
||||
username?: string;
|
||||
token?: string;
|
||||
scopes?: Array<'project' | 'aircraft' | 'container' | 'config'>;
|
||||
}
|
||||
|
||||
// =============================================
|
||||
|
||||
0
src/panels/types/DataModel.ts
Executable file → Normal file
0
src/panels/types/DataModel.ts
Executable file → Normal file
1
src/panels/types/ViewTypes.ts
Executable file → Normal file
1
src/panels/types/ViewTypes.ts
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
// src/panels/types/ViewTypes.ts
|
||||
import { ModuleFolder, GitFileTree } from './CommonTypes';
|
||||
|
||||
export interface ProjectViewData {
|
||||
|
||||
1
src/panels/types/WebviewMessage.ts
Executable file → Normal file
1
src/panels/types/WebviewMessage.ts
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
// src/panels/types/WebviewMessage.ts
|
||||
export interface WebviewMessage {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
|
||||
36
src/panels/views/AircraftView.ts
Executable file → Normal file
36
src/panels/views/AircraftView.ts
Executable file → Normal file
@@ -2,7 +2,7 @@ import { BaseView } from './BaseView';
|
||||
import { AircraftViewData } from '../types/ViewTypes';
|
||||
|
||||
export class AircraftView extends BaseView {
|
||||
render(data?: { aircrafts: AircraftViewData[] }): string {
|
||||
render(data?: { project?: any; aircrafts: AircraftViewData[] }): string {
|
||||
const aircrafts = data?.aircrafts || [];
|
||||
|
||||
const aircraftsHtml = aircrafts.map(aircraft => `
|
||||
@@ -25,7 +25,7 @@ export class AircraftView extends BaseView {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>飞行器配置</title>
|
||||
<title>飞行器管理</title>
|
||||
${this.getStyles()}
|
||||
${this.getRepoSelectScript()}
|
||||
<style>
|
||||
@@ -154,16 +154,20 @@ export class AircraftView extends BaseView {
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>🚀飞行器配置</h2>
|
||||
<h2>🚀 飞行器管理 -
|
||||
<span style="color: var(--vscode-textLink-foreground);">
|
||||
${data?.project?.name || '未知项目'}
|
||||
</span>
|
||||
</h2>
|
||||
<button class="back-btn" onclick="goBackToProjects()">← 返回项目</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">飞行器</th>
|
||||
<th width="40%">配置</th>
|
||||
<th width="20%">操作</th>
|
||||
<th width="43%" style="padding-left: 40px;">飞行器</th>
|
||||
<th width="43%">配置</th>
|
||||
<th width="14%" style="padding-left: 45px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -177,13 +181,13 @@ export class AircraftView extends BaseView {
|
||||
</table>
|
||||
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">🛫 飞行器云仓库</h3>
|
||||
<h3 class="section-title">📚 飞行器云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取飞行器仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForAircraft()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将飞行器代码克隆到当前项目下(以分支名作为飞行器名称)
|
||||
从仓库配置中选择 Git 仓库,随后可以选择分支并克隆到本地
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
@@ -239,18 +243,12 @@ export class AircraftView extends BaseView {
|
||||
);
|
||||
}
|
||||
|
||||
// 新增上传功能
|
||||
// 上传功能
|
||||
function uploadAircraft(aircraftId, aircraftName) {
|
||||
showConfirmDialog(
|
||||
'上传飞行器仓库',
|
||||
'确定将此飞行器目录(包括所有子文件夹和文件)上传到一个新的 Git 仓库分支吗?',
|
||||
function() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForAircraftUpload',
|
||||
aircraftId: aircraftId
|
||||
});
|
||||
}
|
||||
);
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForAircraftUpload',
|
||||
aircraftId: aircraftId
|
||||
});
|
||||
}
|
||||
|
||||
// 飞行器名称编辑功能
|
||||
|
||||
9
src/panels/views/BaseView.ts
Executable file → Normal file
9
src/panels/views/BaseView.ts
Executable file → Normal file
@@ -283,7 +283,7 @@ export abstract class BaseView {
|
||||
}
|
||||
window.__dcspRepoDialogInitialized = true;
|
||||
|
||||
// ① 仅用于“获取仓库 -> 拉取分支”的弹窗
|
||||
// 仅用于“获取仓库 -> 拉取分支”的弹窗
|
||||
function showRepoSelectDialog(repos) {
|
||||
// 移除已有弹窗
|
||||
var existing = document.getElementById('repoSelectModal');
|
||||
@@ -367,7 +367,7 @@ export abstract class BaseView {
|
||||
}
|
||||
}
|
||||
|
||||
// ② 专门用于“上传代码”的仓库+分支弹窗
|
||||
// 专门用于“上传代码”的仓库+分支弹窗
|
||||
function showUploadRepoSelectDialog(repos, folderId, folderType) {
|
||||
var existing = document.getElementById('uploadRepoSelectModal');
|
||||
if (existing) {
|
||||
@@ -476,11 +476,10 @@ export abstract class BaseView {
|
||||
var message = event.data;
|
||||
if (!message || !message.type) return;
|
||||
if (message.type === 'showRepoSelect') {
|
||||
// 旧逻辑:仅用于获取分支
|
||||
// 仅用于获取分支
|
||||
showRepoSelectDialog(message.repos || []);
|
||||
} else if (message.type === 'showUploadRepoSelect') {
|
||||
// 新逻辑:上传代码用(仓库 + 分支)
|
||||
// 这里的 folderId/folderType 现在可能是 Project/Aircraft/Container 的 ID/Type
|
||||
// 上传代码用(仓库 + 分支)
|
||||
showUploadRepoSelectDialog(message.repos || [], message.folderId, message.folderType);
|
||||
}
|
||||
});
|
||||
|
||||
52
src/panels/views/ConfigView.ts
Executable file → Normal file
52
src/panels/views/ConfigView.ts
Executable file → Normal file
@@ -1,22 +1,6 @@
|
||||
import { BaseView } from './BaseView';
|
||||
import { ContainerConfigData, ConfigViewData } from '../types/ViewTypes';
|
||||
import { ModuleFolder } from '../types/CommonTypes';
|
||||
|
||||
interface GitBranch {
|
||||
name: string;
|
||||
isCurrent: boolean;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
interface BranchTreeNode {
|
||||
name: string;
|
||||
fullName: string;
|
||||
isLeaf: boolean;
|
||||
children: BranchTreeNode[];
|
||||
level: number;
|
||||
expanded?: boolean;
|
||||
branch?: GitBranch;
|
||||
}
|
||||
import { ModuleFolder, GitBranch, BranchTreeNode} from '../types/CommonTypes';
|
||||
|
||||
export class ConfigView extends BaseView {
|
||||
render(data?: ContainerConfigData & {
|
||||
@@ -289,25 +273,23 @@ export class ConfigView extends BaseView {
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>⚙️ 模块管理 - <span style="color: var(--vscode-textLink-foreground);">${container?.name || '未知容器'}</span></h2>
|
||||
<h2>📁 模块管理 - <span style="color: var(--vscode-textLink-foreground);">${container?.name || '未知容器'}</span></h2>
|
||||
<button class="back-btn" onclick="goBackToContainers()">← 返回容器管理</button>
|
||||
</div>
|
||||
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">📋 配置文件管理</h3>
|
||||
|
||||
<div class="config-section">
|
||||
<div class="action-buttons">
|
||||
<button class="btn-new" onclick="createNewConfig()">+ 新建配置</button>
|
||||
<button class="btn-merge" id="mergeButton" onclick="mergeSelectedConfigs()" disabled>合并选中配置</button>
|
||||
<button class="btn-new" onclick="createNewConfig()">+ 新建文件</button>
|
||||
<button class="btn-merge" id="mergeButton" onclick="mergeSelectedConfigs()" disabled>合并选中文件</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="25%">模块</th>
|
||||
<th width="15%">类别</th>
|
||||
<th width="35%" style="padding-left: 50px;">模型</th>
|
||||
<th width="16%">类别</th>
|
||||
<th width="35%">文件/文件夹</th>
|
||||
<th width="25%">操作</th>
|
||||
<th width="14%" style="padding-left: 45px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -341,7 +323,7 @@ export class ConfigView extends BaseView {
|
||||
let branchTreeData = [];
|
||||
let selectedConfigs = new Set();
|
||||
|
||||
// [新增] 配置文件重命名弹窗
|
||||
// 配置文件重命名弹窗
|
||||
function showConfigRenameDialog(configId, currentName, currentFileName) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
@@ -441,13 +423,12 @@ export class ConfigView extends BaseView {
|
||||
}
|
||||
}
|
||||
|
||||
// [修改] editConfigName:使用新的双输入弹窗
|
||||
function editConfigName(configId, currentName, currentFileName) {
|
||||
// currentFileName 现在已从模板传入
|
||||
showConfigRenameDialog(configId, currentName, currentFileName);
|
||||
}
|
||||
|
||||
// [新增] 模块文件夹重命名双输入弹窗
|
||||
// 模块文件夹重命名双输入弹窗
|
||||
function showModuleFolderRenameDialog(folderId, currentName, currentFolderName) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
@@ -458,16 +439,15 @@ export class ConfigView extends BaseView {
|
||||
<div class="modal-title">重命名模块文件夹</div>
|
||||
<div class="merge-dialog-content">
|
||||
<div class="merge-input-section">
|
||||
<label class="merge-input-label">显示名称</label>
|
||||
<label class="merge-input-label">模型名称</label>
|
||||
<input type="text" id="moduleDisplayNameInput" class="merge-input-field"
|
||||
placeholder="在配置栏显示的名称" value="\${currentName}">
|
||||
<div class="merge-input-help">在左侧配置栏显示的名称</div>
|
||||
</div>
|
||||
<div class="merge-input-section">
|
||||
<label class="merge-input-label">磁盘文件夹名称 (将同步修改磁盘目录)</label>
|
||||
<label class="merge-input-label">文件/文件夹夹名称</label>
|
||||
<input type="text" id="moduleFolderNameInput" class="merge-input-field"
|
||||
placeholder="磁盘上的实际文件夹名" value="\${currentFolderName}">
|
||||
<div class="merge-input-help">请使用英文、数字、中文、-、_,不要使用空格和/</div>
|
||||
<div class="merge-input-help">禁止使用空格和"/"</div>
|
||||
</div>
|
||||
<div id="moduleFolderNameValidationMessage" style="color: var(--vscode-inputValidation-errorForeground); font-size: 12px; margin-top: 5px;"></div>
|
||||
</div>
|
||||
@@ -547,10 +527,9 @@ export class ConfigView extends BaseView {
|
||||
}
|
||||
}
|
||||
|
||||
// [修改] renameModuleFolder:获取磁盘文件夹名并使用双输入弹窗
|
||||
// 获取磁盘文件夹名并使用双输入弹窗
|
||||
function renameModuleFolder(folderId, currentName) {
|
||||
// 通过 data-folder-id 找到模块文件夹的行
|
||||
// 修复:避免在 JS 字符串中使用内嵌模板字符串,改用拼接以解决编译问题
|
||||
const editableSpan = document.querySelector('.module-folder-name[data-folder-id="' + folderId + '"]');
|
||||
if (!editableSpan) return;
|
||||
|
||||
@@ -562,9 +541,6 @@ export class ConfigView extends BaseView {
|
||||
showModuleFolderRenameDialog(folderId, currentName, currentFolderName);
|
||||
}
|
||||
|
||||
// 模块管理功能
|
||||
// 原始的 editConfigName 已被修改为接受三个参数
|
||||
|
||||
// 在 VSCode 中打开配置文件
|
||||
function openConfigFileInVSCode(configId) {
|
||||
vscode.postMessage({
|
||||
|
||||
27
src/panels/views/ContainerView.ts
Executable file → Normal file
27
src/panels/views/ContainerView.ts
Executable file → Normal file
@@ -142,16 +142,16 @@ export class ContainerView extends BaseView {
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>📋 容器管理 - <span style="color: var(--vscode-textLink-foreground);">${aircraft?.name || '未知飞行器'}</span></h2>
|
||||
<h2>📦 容器管理 - <span style="color: var(--vscode-textLink-foreground);">${aircraft?.name || '未知飞行器'}</span></h2>
|
||||
<button class="back-btn" onclick="goBackToAircrafts()">← 返回飞行器管理</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">容器</th>
|
||||
<th width="40%">打开</th>
|
||||
<th width="20%">操作</th>
|
||||
<th width="43%" style="padding-left: 50px;">容器</th>
|
||||
<th width="43%">打开</th>
|
||||
<th width="14%" style="padding-left: 45px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -165,13 +165,13 @@ export class ContainerView extends BaseView {
|
||||
</table>
|
||||
|
||||
<div class="config-section">
|
||||
<h3 class="section-title">📦 容器云仓库</h3>
|
||||
<h3 class="section-title">📚 容器云仓库</h3>
|
||||
<div class="url-input-section">
|
||||
<h4>🔗 获取容器仓库</h4>
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForContainer()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将容器代码克隆到当前飞行器下(以分支名作为容器名称)
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将完整容器克隆到本地
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
@@ -241,18 +241,11 @@ export class ContainerView extends BaseView {
|
||||
);
|
||||
}
|
||||
|
||||
// 新增上传功能
|
||||
function uploadContainer(containerId, containerName) {
|
||||
showConfirmDialog(
|
||||
'上传容器仓库',
|
||||
'确定将此容器目录(包括所有子文件夹和文件)上传到一个新的 Git 仓库分支吗?',
|
||||
function() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForContainerUpload',
|
||||
containerId: containerId
|
||||
});
|
||||
}
|
||||
);
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForContainerUpload',
|
||||
containerId: containerId
|
||||
});
|
||||
}
|
||||
|
||||
// ======== Git 仓库 & 分支树 - ContainerView 作用域 ========
|
||||
|
||||
27
src/panels/views/ProjectView.ts
Executable file → Normal file
27
src/panels/views/ProjectView.ts
Executable file → Normal file
@@ -192,9 +192,9 @@ export class ProjectView extends BaseView {
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">项目</th>
|
||||
<th width="40%">配置</th>
|
||||
<th width="20%">操作</th>
|
||||
<th width="43%" style="padding-left: 50px;">项目</th>
|
||||
<th width="43%">配置</th>
|
||||
<th width="14%" style="padding-left: 45px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -221,7 +221,7 @@ export class ProjectView extends BaseView {
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px; align-items: center;">
|
||||
<button class="btn-new" onclick="openRepoSelectForProject()">获取仓库</button>
|
||||
<span style="font-size: 12px; color: var(--vscode-descriptionForeground);">
|
||||
从仓库配置中选择 Git 仓库,选择分支后可将完整项目克隆到本地
|
||||
从仓库配置中选择 Git 仓库,随后可以选择分支并克隆到本地
|
||||
</span>
|
||||
</div>
|
||||
<div id="branchSelectionContainer"></div>
|
||||
@@ -241,7 +241,8 @@ export class ProjectView extends BaseView {
|
||||
if (isConfigured) {
|
||||
vscode.postMessage({
|
||||
type: 'openProject',
|
||||
projectId: projectId
|
||||
projectId: projectId,
|
||||
projectName: projectName
|
||||
});
|
||||
} else {
|
||||
vscode.postMessage({
|
||||
@@ -290,18 +291,12 @@ export class ProjectView extends BaseView {
|
||||
);
|
||||
}
|
||||
|
||||
// 新增上传功能
|
||||
// 上传功能
|
||||
function uploadProject(projectId, projectName) {
|
||||
showConfirmDialog(
|
||||
'上传项目仓库',
|
||||
'确定将此项目目录(包括所有子文件夹和文件)上传到一个新的 Git 仓库分支吗?',
|
||||
function() {
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForProjectUpload',
|
||||
projectId: projectId
|
||||
});
|
||||
}
|
||||
);
|
||||
vscode.postMessage({
|
||||
type: 'openRepoSelectForProjectUpload',
|
||||
projectId: projectId
|
||||
});
|
||||
}
|
||||
|
||||
// 项目名称编辑功能
|
||||
|
||||
Reference in New Issue
Block a user