0
0

卫星模块获取功能已实现,仅简单测试,未深度测试

This commit is contained in:
xubing
2025-12-02 16:30:53 +08:00
parent 07acf9f617
commit 3f512e8646
9 changed files with 475 additions and 297 deletions

BIN
dsc-platform-1.4.1.vsix Normal file

Binary file not shown.

View File

@@ -27,7 +27,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigPanel = void 0; exports.ConfigPanel = void 0;
// src/panels/ConfigPanel.ts
const vscode = __importStar(require("vscode")); const vscode = __importStar(require("vscode"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
@@ -72,7 +71,9 @@ class ConfigPanel {
this.moduleFolders = []; this.moduleFolders = [];
this.currentModuleFolderFileTree = []; this.currentModuleFolderFileTree = [];
this.projectPaths = new Map(); this.projectPaths = new Map();
this.pendingUploadFolderId = null; // 上传相关local 上传 / git 上传
this.pendingUploadFolderId = null; // local -> git
this.pendingGitUploadFolderId = null; // git -> repo更新或新分支
// 仓库配置 // 仓库配置
this.repoConfigs = []; this.repoConfigs = [];
// 状态管理 // 状态管理
@@ -99,7 +100,6 @@ class ConfigPanel {
// ============================================= // =============================================
generateUniqueId(prefix, existingItems) { generateUniqueId(prefix, existingItems) {
let idNumber = 1; let idNumber = 1;
// 先找到当前最大的数字
const existingIds = existingItems.map(item => item.id); const existingIds = existingItems.map(item => item.id);
const numberPattern = /\d+$/; const numberPattern = /\d+$/;
for (const id of existingIds) { for (const id of existingIds) {
@@ -117,7 +117,6 @@ class ConfigPanel {
// 仓库配置相关 // 仓库配置相关
// ============================================= // =============================================
getRepoConfigPath() { getRepoConfigPath() {
// 按你的需求:配置文件保存在插件安装位置
return path.join(this.extensionUri.fsPath, 'dcsp-repos.json'); return path.join(this.extensionUri.fsPath, 'dcsp-repos.json');
} }
async loadRepoConfigs() { async loadRepoConfigs() {
@@ -134,7 +133,6 @@ class ConfigPanel {
} }
const parsed = JSON.parse(content); const parsed = JSON.parse(content);
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
// 兼容老格式:直接是数组
this.repoConfigs = parsed; this.repoConfigs = parsed;
} }
else if (parsed && Array.isArray(parsed.repos)) { else if (parsed && Array.isArray(parsed.repos)) {
@@ -178,6 +176,9 @@ class ConfigPanel {
vscode.window.showErrorMessage(`打开仓库配置文件失败: ${error}`); vscode.window.showErrorMessage(`打开仓库配置文件失败: ${error}`);
} }
} }
/**
* 通用:弹出仓库选择弹窗(不区分“获取分支 / 上传”,使用 pendingXXX 做上下文判断)
*/
async openRepoSelect() { async openRepoSelect() {
await this.loadRepoConfigs(); await this.loadRepoConfigs();
if (!this.repoConfigs || this.repoConfigs.length === 0) { if (!this.repoConfigs || this.repoConfigs.length === 0) {
@@ -185,12 +186,17 @@ class ConfigPanel {
} }
if (this.isWebviewDisposed) if (this.isWebviewDisposed)
return; return;
// 仅传递仓库名给前端,用于下拉选择
this.panel.webview.postMessage({ this.panel.webview.postMessage({
type: 'showRepoSelect', type: 'showRepoSelect',
repos: this.repoConfigs.map(r => ({ name: r.name })) repos: this.repoConfigs.map(r => ({ name: r.name }))
}); });
} }
/**
* 仓库选择确认后的统一入口
* - local 上传pendingUploadFolderId 有值
* - git 上传pendingGitUploadFolderId 有值
* - 其余:当作“获取分支”处理
*/
async handleRepoSelectedForBranches(repoName) { async handleRepoSelectedForBranches(repoName) {
await this.loadRepoConfigs(); await this.loadRepoConfigs();
const repo = this.repoConfigs.find(r => r.name === repoName); const repo = this.repoConfigs.find(r => r.name === repoName);
@@ -198,34 +204,84 @@ class ConfigPanel {
vscode.window.showErrorMessage(`在仓库配置中未找到名为 "${repoName}" 的仓库,请检查 dcsp-repos.json。`); vscode.window.showErrorMessage(`在仓库配置中未找到名为 "${repoName}" 的仓库,请检查 dcsp-repos.json。`);
return; return;
} }
// --------------------------------------------------- // 1⃣ Local 模块上传local -> git
// 1⃣ 新逻辑Local 上传pendingUploadFolderId 不为空)
// ---------------------------------------------------
if (this.pendingUploadFolderId) { if (this.pendingUploadFolderId) {
const localFolderId = this.pendingUploadFolderId; const localFolderId = this.pendingUploadFolderId;
this.pendingUploadFolderId = null; // 必须清空,避免影响其他操作 this.pendingUploadFolderId = null;
console.log("🚀 Local 上传流程repo =", repoName, "folderId =", localFolderId); console.log("🚀 Local 上传流程repo =", repoName, "folderId =", localFolderId);
const folder = this.moduleFolders.find(f => f.id === localFolderId); const folder = this.moduleFolders.find(f => f.id === localFolderId);
if (!folder) { if (!folder || folder.type !== 'local') {
vscode.window.showErrorMessage("未找到要上传的本地模块文件夹"); vscode.window.showErrorMessage("未找到要上传的本地模块文件夹");
return; return;
} }
// 生成本地路径
const fullPath = this.getModuleFolderFullPath(folder); const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) { if (!fullPath) {
vscode.window.showErrorMessage("无法确定本地模块文件夹路径"); vscode.window.showErrorMessage("无法确定本地模块文件夹路径");
return; return;
} }
// 自动从本地文件夹名生成分支名
const branchName = path.basename(fullPath); const branchName = path.basename(fullPath);
console.log("🌿 自动生成分支名:", branchName); console.log("🌿 Local 上传自动生成分支名:", branchName);
// 正式执行上传
await this.uploadLocalModuleFolder(localFolderId, repo.url, branchName); await this.uploadLocalModuleFolder(localFolderId, repo.url, branchName);
return; return;
} }
// --------------------------------------------------- // 2⃣ Git 模块上传git -> repo
// 2⃣ 旧逻辑:获取分支 if (this.pendingGitUploadFolderId) {
// --------------------------------------------------- const gitFolderId = this.pendingGitUploadFolderId;
this.pendingGitUploadFolderId = null;
console.log("🚀 Git 上传流程repo =", repoName, "folderId =", gitFolderId);
const folder = this.moduleFolders.find(f => f.id === gitFolderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage("未找到要上传的 Git 模块文件夹");
return;
}
const newFolderName = folder.localPath.split('/').pop() ?? '';
const oldFolderName = folder.originalFolderName ?? newFolderName;
const isRenamed = newFolderName !== oldFolderName;
const isSameRepo = !!folder.originalRepoUrl && folder.originalRepoUrl === repo.url;
console.log('🔍 Git 上传判断:');
console.log(' oldFolderName:', oldFolderName);
console.log(' newFolderName:', newFolderName);
console.log(' isRenamed :', isRenamed);
console.log(' folderRepo :', folder.originalRepoUrl);
console.log(' selectedRepo :', repo.url);
console.log(' isSameRepo :', isSameRepo);
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage("无法确定 Git 模块文件夹路径");
return;
}
// 2.1 未改名 + 还是原来的仓库 → 直接在原分支上 push更新代码
if (!isRenamed && isSameRepo) {
console.log('✅ 未改名 & 同仓库:执行原始 push 逻辑');
await this.uploadGitModuleFolder(gitFolderId);
return;
}
// 2.2 改了名字 或 换了仓库 → 使用“当前文件夹名”作为分支,推送到选中仓库
console.log('🆕 名称改变或仓库改变:以文件夹名作为新分支上传');
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在上传 Git 仓库: ${folder.name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '准备上传...' });
await this.pushGitModuleFolderToRepoWithBranch(fullPath, repo.url, newFolderName);
folder.uploaded = true;
folder.originalFolderName = newFolderName;
folder.originalRepoUrl = repo.url;
await this.saveCurrentProjectData();
progress.report({ increment: 100, message: '完成' });
vscode.window.showInformationMessage(`✅ Git 仓库已上传到 ${repo.name} 的分支 ${newFolderName}`);
this.updateWebview();
}
catch (error) {
console.error('❌ Git 上传到新仓库/分支失败:', error);
vscode.window.showErrorMessage(`推送失败: ${error.message || error}`);
}
});
return;
}
// 3⃣ 默认:获取分支(用于“获取仓库”-> 克隆)
this.currentRepoForBranches = repo; this.currentRepoForBranches = repo;
await this.fetchBranchesForRepo(repo); await this.fetchBranchesForRepo(repo);
} }
@@ -297,7 +353,9 @@ class ConfigPanel {
// 上传功能 // 上传功能
'uploadGitModuleFolder': (data) => this.uploadGitModuleFolder(data.folderId, data.username, data.password), 'uploadGitModuleFolder': (data) => this.uploadGitModuleFolder(data.folderId, data.username, data.password),
'openRepoSelectForUpload': (data) => this.handleOpenRepoSelectForUpload(data.folderId), 'openRepoSelectForUpload': (data) => this.handleOpenRepoSelectForUpload(data.folderId),
'uploadLocalModuleFolder': (data) => this.uploadLocalModuleFolder(data.folderId, data.repoUrl, data.branchName) 'uploadLocalModuleFolder': (data) => this.uploadLocalModuleFolder(data.folderId, data.repoUrl, data.branchName),
'openRepoSelectForGitUpload': (data) => this.openRepoSelectForGitUpload(data.folderId),
'repoSelectedForGitUpload': (data) => this.handleRepoSelectedForGitUpload(data.folderId, data.repoName)
}; };
const handler = messageHandlers[data.type]; const handler = messageHandlers[data.type];
if (handler) { if (handler) {
@@ -510,7 +568,6 @@ class ConfigPanel {
async handleOpenRepoSelectForUpload(folderId) { async handleOpenRepoSelectForUpload(folderId) {
console.log("📌 Local 上传:收到 openRepoSelectForUploadfolderId =", folderId); console.log("📌 Local 上传:收到 openRepoSelectForUploadfolderId =", folderId);
this.pendingUploadFolderId = folderId; this.pendingUploadFolderId = folderId;
// 复用你现有的仓库选择弹窗
await this.openRepoSelect(); await this.openRepoSelect();
} }
async deleteConfig(configId) { async deleteConfig(configId) {
@@ -741,7 +798,9 @@ class ConfigPanel {
name: displayName, name: displayName,
type: 'git', type: 'git',
localPath: relativePath, localPath: relativePath,
containerId: this.currentContainerId containerId: this.currentContainerId,
originalFolderName: folderName,
originalRepoUrl: url
}; };
await vscode.window.withProgress({ await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification, location: vscode.ProgressLocation.Notification,
@@ -948,6 +1007,10 @@ class ConfigPanel {
// ============================================= // =============================================
// 上传功能方法 // 上传功能方法
// ============================================= // =============================================
/**
* 旧的 Git 模块上传逻辑:直接在当前远程 / 当前分支上 commit + push
* 现在只在“未改名 + 同仓库”的场景下被调用
*/
async uploadGitModuleFolder(folderId, username, password) { async uploadGitModuleFolder(folderId, username, password) {
const folder = this.moduleFolders.find(f => f.id === folderId); const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder || folder.type !== 'git') { if (!folder || folder.type !== 'git') {
@@ -1011,6 +1074,8 @@ class ConfigPanel {
progress.report({ increment: 100, message: '完成' }); progress.report({ increment: 100, message: '完成' });
folder.type = 'git'; folder.type = 'git';
folder.uploaded = true; folder.uploaded = true;
folder.originalFolderName = branchName;
folder.originalRepoUrl = repoUrl;
await this.saveCurrentProjectData(); await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`本地文件夹成功上传到 Git 仓库: ${folder.name} -> ${branchName}`); vscode.window.showInformationMessage(`本地文件夹成功上传到 Git 仓库: ${folder.name} -> ${branchName}`);
this.updateWebview(); this.updateWebview();
@@ -1030,6 +1095,56 @@ class ConfigPanel {
} }
}); });
} }
/**
* Git 模块以“当前文件夹名”为分支,推送到任意仓库 URL
* 不修改现有 remote 配置,直接用 URL 形式 push
*/
async pushGitModuleFolderToRepoWithBranch(fullPath, repoUrl, branchName) {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('🚀 准备以新分支推送到仓库:', { fullPath, repoUrl, branchName });
// 先检查是否有未提交修改
exec('git status --porcelain', {
cwd: fullPath,
encoding: 'utf8'
}, (statusError, statusStdout, statusStderr) => {
if (statusError) {
console.error('❌ 检查 Git 状态失败:', statusError);
reject(new Error(`检查 Git 状态失败: ${statusStderr || statusError.message}`));
return;
}
const hasChanges = !!statusStdout.trim();
console.log('📋 有未提交更改?:', hasChanges, ', status =\n', statusStdout);
const commands = [];
// 获取完整的仓库历史
commands.push('git fetch --unshallow');
// 切换 / 创建分支
commands.push(`git checkout -B "${branchName}"`);
// 有变更就先提交
if (hasChanges) {
commands.push('git add .');
commands.push(`git commit -m "Auto commit from DCSP - ${new Date().toLocaleString()}"`);
}
// 直接用 URL 形式推送,不依赖 remote 名称
commands.push(`git push -u "${repoUrl}" "${branchName}" --force`);
console.log('🧰 即将执行命令链:', commands.join(' && '));
exec(commands.join(' && '), {
cwd: fullPath,
encoding: 'utf8'
}, (error, stdout, stderr) => {
console.log('📋 Git push stdout:', stdout);
console.log('📋 Git push stderr:', stderr);
if (error) {
console.error('❌ Git 推送失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ Git 推送到指定仓库/分支成功');
resolve();
});
});
});
}
// ============================================= // =============================================
// Git 命令行工具方法 // Git 命令行工具方法
// ============================================= // =============================================
@@ -1079,7 +1194,7 @@ class ConfigPanel {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { exec } = require('child_process'); const { exec } = require('child_process');
console.log('📁 初始化 Git 仓库...'); console.log('📁 初始化 Git 仓库...');
exec(`git init && git checkout -b ${branchName}`, { exec(`git init && git checkout -b "${branchName}"`, {
cwd: fullPath, cwd: fullPath,
encoding: 'utf8' encoding: 'utf8'
}, (error, stdout, stderr) => { }, (error, stdout, stderr) => {
@@ -1097,7 +1212,7 @@ class ConfigPanel {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { exec } = require('child_process'); const { exec } = require('child_process');
console.log('📡 添加远程仓库...'); console.log('📡 添加远程仓库...');
exec(`git remote add origin ${repoUrl}`, { exec(`git remote add origin "${repoUrl}"`, {
cwd: fullPath, cwd: fullPath,
encoding: 'utf8' encoding: 'utf8'
}, (error, stdout, stderr) => { }, (error, stdout, stderr) => {
@@ -1117,7 +1232,7 @@ class ConfigPanel {
console.log('💾 提交初始文件...'); console.log('💾 提交初始文件...');
const commands = [ const commands = [
'git add .', 'git add .',
`git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`, `git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`
]; ];
exec(commands.join(' && '), { exec(commands.join(' && '), {
cwd: fullPath, cwd: fullPath,
@@ -1142,7 +1257,7 @@ class ConfigPanel {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { exec } = require('child_process'); const { exec } = require('child_process');
console.log('🚀 强制推送到远程仓库...'); console.log('🚀 强制推送到远程仓库...');
exec(`git push -u origin ${branchName} --force`, { exec(`git push -u origin "${branchName}" --force`, {
cwd: fullPath, cwd: fullPath,
encoding: 'utf8' encoding: 'utf8'
}, (error, stdout, stderr) => { }, (error, stdout, stderr) => {
@@ -1552,6 +1667,11 @@ class ConfigPanel {
return []; return [];
} }
} }
async handleRepoSelectedForGitUpload(folderId, repoName) {
// 兼容:如果前端用的是 repoSelectedForGitUpload 事件,则这里转成统一逻辑
this.pendingGitUploadFolderId = folderId;
await this.handleRepoSelectedForBranches(repoName);
}
// ============================================= // =============================================
// 工具方法 // 工具方法
// ============================================= // =============================================
@@ -1681,7 +1801,7 @@ class ConfigPanel {
const newFullPath = path.join(path.dirname(fullPath), newName); const newFullPath = path.join(path.dirname(fullPath), newName);
try { try {
await fs.promises.rename(fullPath, newFullPath); await fs.promises.rename(fullPath, newFullPath);
// 更新 localPath // 更新 localPath注意originalFolderName 不更新,用于判断“是否改名过”)
folder.localPath = folder.localPath.replace(/\/[^/]+$/, '/' + newName); folder.localPath = folder.localPath.replace(/\/[^/]+$/, '/' + newName);
await this.saveCurrentProjectData(); await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`已重命名文件夹: ${oldName}${newName}`); vscode.window.showInformationMessage(`已重命名文件夹: ${oldName}${newName}`);
@@ -1706,6 +1826,11 @@ class ConfigPanel {
console.error('更新 Webview 失败:', error); console.error('更新 Webview 失败:', error);
} }
} }
async openRepoSelectForGitUpload(folderId) {
console.log('📌 Git 上传:收到 openRepoSelectForGitUploadfolderId =', folderId);
this.pendingGitUploadFolderId = folderId;
await this.openRepoSelect();
}
getWebviewContent() { getWebviewContent() {
switch (this.currentView) { switch (this.currentView) {
case 'projects': case 'projects':

File diff suppressed because one or more lines are too long

View File

@@ -29,27 +29,29 @@ class ConfigView extends BaseView_1.BaseView {
`).join(''); `).join('');
// 生成模块文件夹的 HTML - 按类别分类显示 // 生成模块文件夹的 HTML - 按类别分类显示
const moduleFoldersHtml = moduleFolders.map((folder) => { const moduleFoldersHtml = moduleFolders.map((folder) => {
const icon = folder.type === 'git' ? '📁' : '📁'; const icon = '📁';
// 根据类型和上传状态确定类别显示 // 根据类型和上传状态确定类别显示
let category = folder.type === 'git' ? 'git' : 'local'; let category = folder.type === 'git' ? 'git' : 'local';
if (folder.uploaded) { if (folder.uploaded) {
category += '(已上传)'; category += '(已上传)';
} }
const fileName = folder.localPath.split('/').pop() || '';
return ` return `
<tr> <tr>
<td> <td>
<span class="editable clickable" onclick="renameModuleFolder('${folder.id}')"> <!-- 左侧:📁 动力学仓库 / test仅用于重命名 -->
<span class="editable clickable" onclick="renameModuleFolder('${folder.id}', '${folder.name}')">
${icon} ${folder.name} ${icon} ${folder.name}
</span> </span>
</td> </td>
<td class="category-${folder.type}">${category}</td> <td class="category-${folder.type}">${category}</td>
<td> <td>
<!-- 右侧:文件/文件夹栏,只负责选择并打开文件,不再触发重命名 -->
<span class="clickable" <span class="clickable"
onclick="renameModuleFolder('${folder.id}', '${folder.localPath.split('/').pop()}')"> onclick="openTheModuleFolder('${folder.id}', '${folder.type}')">
📄 ${folder.localPath.split('/').pop()} 📄 ${fileName}
</span> </span>
</td> </td>
<td> <td>
<button class="btn-upload" onclick="uploadModuleFolder('${folder.id}', '${folder.type}')" style="margin-right: 5px;">上传</button> <button class="btn-upload" onclick="uploadModuleFolder('${folder.id}', '${folder.type}')" style="margin-right: 5px;">上传</button>
<button class="btn-delete" onclick="deleteModuleFolder('${folder.id}')">删除</button> <button class="btn-delete" onclick="deleteModuleFolder('${folder.id}')">删除</button>
@@ -322,11 +324,12 @@ class ConfigView extends BaseView_1.BaseView {
let branchTreeData = []; let branchTreeData = [];
let selectedConfigs = new Set(); let selectedConfigs = new Set();
// 只负责“改名”的函数
function renameModuleFolder(folderId, oldName) { function renameModuleFolder(folderId, oldName) {
showPromptDialog( showPromptDialog(
'重命名模块文件夹', '重命名模块文件夹',
'请输入新的名称:', '请输入新的名称:',
oldName, oldName || '',
function(newName) { function(newName) {
if (newName && newName.trim() && newName !== oldName) { if (newName && newName.trim() && newName !== oldName) {
vscode.postMessage({ vscode.postMessage({
@@ -339,8 +342,6 @@ class ConfigView extends BaseView_1.BaseView {
); );
} }
// 配置管理功能 // 配置管理功能
function editConfigName(configId, currentName) { function editConfigName(configId, currentName) {
showPromptDialog( showPromptDialog(
@@ -368,7 +369,7 @@ class ConfigView extends BaseView_1.BaseView {
}); });
} }
// 统一功能:打开模块文件夹 // 统一功能:打开模块文件夹(这里只负责打开,而不是改名)
function openTheModuleFolder(id, type) { function openTheModuleFolder(id, type) {
console.log('📂 打开模块文件夹:', { id, type }); console.log('📂 打开模块文件夹:', { id, type });
vscode.postMessage({ vscode.postMessage({
@@ -406,7 +407,6 @@ class ConfigView extends BaseView_1.BaseView {
}); });
}, },
function() { function() {
// 用户取消删除
console.log('❌ 用户取消删除配置文件'); console.log('❌ 用户取消删除配置文件');
} }
); );
@@ -427,34 +427,23 @@ class ConfigView extends BaseView_1.BaseView {
}); });
}, },
function() { function() {
// 用户取消删除
console.log('❌ 用户取消删除模块文件夹'); console.log('❌ 用户取消删除模块文件夹');
} }
); );
} }
// 上传模块文件夹功能 // 上传模块文件夹功能(区分 local / git
function uploadModuleFolder(folderId, folderType) { function uploadModuleFolder(folderId, folderType) {
console.log('📤 上传模块文件夹:', { folderId, folderType }); console.log('📤 上传模块文件夹:', { folderId, folderType });
if (folderType === 'git') { if (folderType === 'git') {
// Git 类型:直接上传(相当于 push // git 类型:先选仓库,然后后端根据是否改名/是否同仓库判断是更新原分支还是新分支上传
showConfirmDialog(
'确认上传',
'确定要将 Git 文件夹的内容上传到远程仓库吗?这将执行 git push 操作。',
function() {
vscode.postMessage({ vscode.postMessage({
type: 'uploadGitModuleFolder', type: 'openRepoSelectForGitUpload',
folderId: folderId folderId: folderId
}); });
}, } else {
function() { // local 类型:走 local → git 的初始化上传逻辑
console.log('❌ 用户取消上传 Git 文件夹');
}
);
} else if (folderType === 'local') {
// Local 类型:不再手动输入 URL改为复用“获取仓库”弹窗
// 后端将根据选择的仓库 + 本地文件夹名 自动确定 repoUrl 和 分支名
vscode.postMessage({ vscode.postMessage({
type: 'openRepoSelectForUpload', type: 'openRepoSelectForUpload',
folderId: folderId folderId: folderId
@@ -497,12 +486,10 @@ class ConfigView extends BaseView_1.BaseView {
branches: Array.from(selectedBranches) branches: Array.from(selectedBranches)
}); });
// 重置选择
selectedBranches.clear(); selectedBranches.clear();
const checkboxes = document.querySelectorAll('input[type="checkbox"]'); const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => checkbox.checked = false); checkboxes.forEach(checkbox => checkbox.checked = false);
// 隐藏分支选择区域
document.getElementById('branchSelectionContainer').innerHTML = ''; document.getElementById('branchSelectionContainer').innerHTML = '';
} }
@@ -511,7 +498,6 @@ class ConfigView extends BaseView_1.BaseView {
const checkboxes = document.querySelectorAll('input[type="checkbox"]'); const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => checkbox.checked = false); checkboxes.forEach(checkbox => checkbox.checked = false);
// 隐藏分支选择区域
document.getElementById('branchSelectionContainer').innerHTML = ''; document.getElementById('branchSelectionContainer').innerHTML = '';
vscode.postMessage({ vscode.postMessage({
@@ -528,7 +514,6 @@ class ConfigView extends BaseView_1.BaseView {
selectedConfigs.delete(configId); selectedConfigs.delete(configId);
} }
// 更新合并按钮状态
updateMergeButtonState(); updateMergeButtonState();
console.log('选中的配置文件:', Array.from(selectedConfigs)); console.log('选中的配置文件:', Array.from(selectedConfigs));
} }
@@ -548,7 +533,6 @@ class ConfigView extends BaseView_1.BaseView {
console.log('🔄 开始合并选中的配置文件:', Array.from(selectedConfigs)); console.log('🔄 开始合并选中的配置文件:', Array.from(selectedConfigs));
// 弹出新的合并对话框
showMergeDialog(); showMergeDialog();
} }
@@ -584,7 +568,6 @@ class ConfigView extends BaseView_1.BaseView {
document.body.appendChild(overlay); document.body.appendChild(overlay);
// 自动聚焦到第一个输入框
setTimeout(() => { setTimeout(() => {
const displayNameInput = document.getElementById('displayNameInput'); const displayNameInput = document.getElementById('displayNameInput');
if (displayNameInput) { if (displayNameInput) {
@@ -619,7 +602,6 @@ class ConfigView extends BaseView_1.BaseView {
folderName: folderName folderName: folderName
}); });
// 重置选择
selectedConfigs.clear(); selectedConfigs.clear();
const checkboxes = document.querySelectorAll('.config-checkbox'); const checkboxes = document.querySelectorAll('.config-checkbox');
checkboxes.forEach(checkbox => checkbox.checked = false); checkboxes.forEach(checkbox => checkbox.checked = false);
@@ -689,7 +671,6 @@ class ConfigView extends BaseView_1.BaseView {
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">'; let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>'; html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
// 展开/收起按钮
html += '<div style="margin-bottom: 8px;">'; html += '<div style="margin-bottom: 8px;">';
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>'; html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>'; html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
@@ -714,7 +695,6 @@ class ConfigView extends BaseView_1.BaseView {
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-'); const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
if (node.isLeaf) { if (node.isLeaf) {
// 叶子节点(实际分支)
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" '; html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">'; html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
@@ -725,7 +705,6 @@ class ConfigView extends BaseView_1.BaseView {
html += '</span>'; html += '</span>';
html += '</div>'; html += '</div>';
} else { } else {
// 文件夹节点
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">'; html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
html += (node.expanded ? '🪵' : '🪵'); html += (node.expanded ? '🪵' : '🪵');
@@ -744,18 +723,6 @@ class ConfigView extends BaseView_1.BaseView {
return html; return html;
} }
function countLeafNodes(nodes) {
let count = 0;
nodes.forEach(node => {
if (node.isLeaf) {
count++;
} else if (node.children) {
count += countLeafNodes(node.children);
}
});
return count;
}
// 对话框函数 // 对话框函数
function showConfirmDialog(title, message, onConfirm, onCancel) { function showConfirmDialog(title, message, onConfirm, onCancel) {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
@@ -853,7 +820,6 @@ class ConfigView extends BaseView_1.BaseView {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
console.log('📄 ConfigView 页面加载完成'); console.log('📄 ConfigView 页面加载完成');
// 为所有配置复选框添加事件监听
document.addEventListener('change', function(event) { document.addEventListener('change', function(event) {
if (event.target.classList.contains('config-checkbox')) { if (event.target.classList.contains('config-checkbox')) {
const configId = event.target.getAttribute('data-id'); const configId = event.target.getAttribute('data-id');
@@ -868,7 +834,6 @@ class ConfigView extends BaseView_1.BaseView {
generateBranchesTreeHtml(branches) { generateBranchesTreeHtml(branches) {
if (branches.length === 0) if (branches.length === 0)
return ''; return '';
// 构建分支树
const branchTree = this.buildBranchTree(branches); const branchTree = this.buildBranchTree(branches);
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">'; let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>'; html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
@@ -885,9 +850,6 @@ class ConfigView extends BaseView_1.BaseView {
html += '</div></div>'; html += '</div></div>';
return html; return html;
} }
/**
* 构建分支树状结构
*/
buildBranchTree(branches) { buildBranchTree(branches) {
const root = []; const root = [];
branches.forEach(branch => { branches.forEach(branch => {
@@ -904,31 +866,25 @@ class ConfigView extends BaseView_1.BaseView {
fullName: fullName, fullName: fullName,
isLeaf: isLeaf, isLeaf: isLeaf,
children: [], children: [],
level: i level: i,
expanded: true
}; };
currentLevel.push(node); currentLevel.push(node);
} }
if (isLeaf) { if (isLeaf) {
node.branch = branch; node.branch = branch;
} }
else {
node.expanded = node.expanded || true; // 默认展开
}
currentLevel = node.children; currentLevel = node.children;
} }
}); });
return root; return root;
} }
/**
* 渲染分支树节点
*/
renderBranchTreeNodes(nodes, level) { renderBranchTreeNodes(nodes, level) {
let html = ''; let html = '';
nodes.forEach(node => { nodes.forEach(node => {
const indent = level * 20; const indent = level * 20;
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-'); const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
if (node.isLeaf) { if (node.isLeaf) {
// 叶子节点(实际分支)
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" '; html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\'' + node.fullName + '\')">'; html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\'' + node.fullName + '\')">';
@@ -940,7 +896,6 @@ class ConfigView extends BaseView_1.BaseView {
html += '</div>'; html += '</div>';
} }
else { else {
// 文件夹节点
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
html += '<span class="branch-expand" onclick="toggleBranchNode(\'' + node.fullName + '\')">'; html += '<span class="branch-expand" onclick="toggleBranchNode(\'' + node.fullName + '\')">';
html += (node.expanded ? '🪵' : '🪵'); html += (node.expanded ? '🪵' : '🪵');
@@ -957,9 +912,6 @@ class ConfigView extends BaseView_1.BaseView {
}); });
return html; return html;
} }
/**
* 计算叶子节点数量
*/
countLeafNodes(nodes) { countLeafNodes(nodes) {
let count = 0; let count = 0;
nodes.forEach(node => { nodes.forEach(node => {

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
{ {
"name": "dsc-platform", "name": "dsc-platform",
"displayName": "数字卫星构建平台", "displayName": "数字卫星构建平台",
"version": "1.3.1", "version": "1.4.1",
"publisher": "njust-micro-nano-lab", "publisher": "njust-micro-nano-lab",
"description": "一个用于快速构建数字卫星的平台", "description": "一个用于快速构建数字卫星的平台",
"repository": { "repository": {

View File

@@ -1,4 +1,3 @@
// src/panels/ConfigPanel.ts
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
@@ -10,7 +9,6 @@ import { ContainerView } from './views/ContainerView';
import { ConfigView } from './views/ConfigView'; import { ConfigView } from './views/ConfigView';
import { ModuleFolder } from './types/CommonTypes'; import { ModuleFolder } from './types/CommonTypes';
// ============================================= // =============================================
// 数据模型接口 // 数据模型接口
// ============================================= // =============================================
@@ -93,7 +91,10 @@ export class ConfigPanel {
private moduleFolders: ModuleFolder[] = []; private moduleFolders: ModuleFolder[] = [];
private currentModuleFolderFileTree: GitFileTree[] = []; private currentModuleFolderFileTree: GitFileTree[] = [];
private projectPaths: Map<string, string> = new Map(); private projectPaths: Map<string, string> = new Map();
private pendingUploadFolderId: string | null = null;
// 上传相关local 上传 / git 上传
private pendingUploadFolderId: string | null = null; // local -> git
private pendingGitUploadFolderId: string | null = null; // git -> repo更新或新分支
// 仓库配置 // 仓库配置
private repoConfigs: RepoConfigItem[] = []; private repoConfigs: RepoConfigItem[] = [];
@@ -164,7 +165,6 @@ export class ConfigPanel {
private generateUniqueId(prefix: string, existingItems: any[]): string { private generateUniqueId(prefix: string, existingItems: any[]): string {
let idNumber = 1; let idNumber = 1;
// 先找到当前最大的数字
const existingIds = existingItems.map(item => item.id); const existingIds = existingItems.map(item => item.id);
const numberPattern = /\d+$/; const numberPattern = /\d+$/;
@@ -186,7 +186,6 @@ export class ConfigPanel {
// ============================================= // =============================================
private getRepoConfigPath(): string { private getRepoConfigPath(): string {
// 按你的需求:配置文件保存在插件安装位置
return path.join(this.extensionUri.fsPath, 'dcsp-repos.json'); return path.join(this.extensionUri.fsPath, 'dcsp-repos.json');
} }
@@ -206,7 +205,6 @@ export class ConfigPanel {
const parsed = JSON.parse(content); const parsed = JSON.parse(content);
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
// 兼容老格式:直接是数组
this.repoConfigs = parsed; this.repoConfigs = parsed;
} else if (parsed && Array.isArray(parsed.repos)) { } else if (parsed && Array.isArray(parsed.repos)) {
this.repoConfigs = parsed.repos; this.repoConfigs = parsed.repos;
@@ -253,6 +251,9 @@ export class ConfigPanel {
} }
} }
/**
* 通用:弹出仓库选择弹窗(不区分“获取分支 / 上传”,使用 pendingXXX 做上下文判断)
*/
private async openRepoSelect(): Promise<void> { private async openRepoSelect(): Promise<void> {
await this.loadRepoConfigs(); await this.loadRepoConfigs();
@@ -262,13 +263,18 @@ export class ConfigPanel {
if (this.isWebviewDisposed) return; if (this.isWebviewDisposed) return;
// 仅传递仓库名给前端,用于下拉选择
this.panel.webview.postMessage({ this.panel.webview.postMessage({
type: 'showRepoSelect', type: 'showRepoSelect',
repos: this.repoConfigs.map(r => ({ name: r.name })) repos: this.repoConfigs.map(r => ({ name: r.name }))
}); });
} }
/**
* 仓库选择确认后的统一入口
* - local 上传pendingUploadFolderId 有值
* - git 上传pendingGitUploadFolderId 有值
* - 其余:当作“获取分支”处理
*/
private async handleRepoSelectedForBranches(repoName: string): Promise<void> { private async handleRepoSelectedForBranches(repoName: string): Promise<void> {
await this.loadRepoConfigs(); await this.loadRepoConfigs();
const repo = this.repoConfigs.find(r => r.name === repoName); const repo = this.repoConfigs.find(r => r.name === repoName);
@@ -278,46 +284,108 @@ export class ConfigPanel {
return; return;
} }
// --------------------------------------------------- // 1⃣ Local 模块上传local -> git
// 1⃣ 新逻辑Local 上传pendingUploadFolderId 不为空)
// ---------------------------------------------------
if (this.pendingUploadFolderId) { if (this.pendingUploadFolderId) {
const localFolderId = this.pendingUploadFolderId; const localFolderId = this.pendingUploadFolderId;
this.pendingUploadFolderId = null; // 必须清空,避免影响其他操作 this.pendingUploadFolderId = null;
console.log("🚀 Local 上传流程repo =", repoName, "folderId =", localFolderId); console.log("🚀 Local 上传流程repo =", repoName, "folderId =", localFolderId);
const folder = this.moduleFolders.find(f => f.id === localFolderId); const folder = this.moduleFolders.find(f => f.id === localFolderId);
if (!folder) { if (!folder || folder.type !== 'local') {
vscode.window.showErrorMessage("未找到要上传的本地模块文件夹"); vscode.window.showErrorMessage("未找到要上传的本地模块文件夹");
return; return;
} }
// 生成本地路径
const fullPath = this.getModuleFolderFullPath(folder); const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) { if (!fullPath) {
vscode.window.showErrorMessage("无法确定本地模块文件夹路径"); vscode.window.showErrorMessage("无法确定本地模块文件夹路径");
return; return;
} }
// 自动从本地文件夹名生成分支名
const branchName = path.basename(fullPath); const branchName = path.basename(fullPath);
console.log("🌿 自动生成分支名:", branchName); console.log("🌿 Local 上传自动生成分支名:", branchName);
// 正式执行上传
await this.uploadLocalModuleFolder(localFolderId, repo.url, branchName); await this.uploadLocalModuleFolder(localFolderId, repo.url, branchName);
return;
}
// 2⃣ Git 模块上传git -> repo
if (this.pendingGitUploadFolderId) {
const gitFolderId = this.pendingGitUploadFolderId;
this.pendingGitUploadFolderId = null;
console.log("🚀 Git 上传流程repo =", repoName, "folderId =", gitFolderId);
const folder = this.moduleFolders.find(f => f.id === gitFolderId);
if (!folder || folder.type !== 'git') {
vscode.window.showErrorMessage("未找到要上传的 Git 模块文件夹");
return;
}
const newFolderName = folder.localPath.split('/').pop() ?? '';
const oldFolderName = folder.originalFolderName ?? newFolderName;
const isRenamed = newFolderName !== oldFolderName;
const isSameRepo = !!folder.originalRepoUrl && folder.originalRepoUrl === repo.url;
console.log('🔍 Git 上传判断:');
console.log(' oldFolderName:', oldFolderName);
console.log(' newFolderName:', newFolderName);
console.log(' isRenamed :', isRenamed);
console.log(' folderRepo :', folder.originalRepoUrl);
console.log(' selectedRepo :', repo.url);
console.log(' isSameRepo :', isSameRepo);
const fullPath = this.getModuleFolderFullPath(folder);
if (!fullPath) {
vscode.window.showErrorMessage("无法确定 Git 模块文件夹路径");
return;
}
// 2.1 未改名 + 还是原来的仓库 → 直接在原分支上 push更新代码
if (!isRenamed && isSameRepo) {
console.log('✅ 未改名 & 同仓库:执行原始 push 逻辑');
await this.uploadGitModuleFolder(gitFolderId);
return;
}
// 2.2 改了名字 或 换了仓库 → 使用“当前文件夹名”作为分支,推送到选中仓库
console.log('🆕 名称改变或仓库改变:以文件夹名作为新分支上传');
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `正在上传 Git 仓库: ${folder.name}`,
cancellable: false
}, async (progress) => {
try {
progress.report({ increment: 0, message: '准备上传...' });
await this.pushGitModuleFolderToRepoWithBranch(fullPath, repo.url, newFolderName);
folder.uploaded = true;
folder.originalFolderName = newFolderName;
folder.originalRepoUrl = repo.url;
await this.saveCurrentProjectData();
progress.report({ increment: 100, message: '完成' });
vscode.window.showInformationMessage(`✅ Git 仓库已上传到 ${repo.name} 的分支 ${newFolderName}`);
this.updateWebview();
} catch (error: any) {
console.error('❌ Git 上传到新仓库/分支失败:', error);
vscode.window.showErrorMessage(`推送失败: ${error.message || error}`);
}
});
return; return;
} }
// --------------------------------------------------- // 3⃣ 默认:获取分支(用于“获取仓库”-> 克隆)
// 2⃣ 旧逻辑:获取分支
// ---------------------------------------------------
this.currentRepoForBranches = repo; this.currentRepoForBranches = repo;
await this.fetchBranchesForRepo(repo); await this.fetchBranchesForRepo(repo);
} }
// ============================================= // =============================================
// Webview 消息处理 // Webview 消息处理
// ============================================= // =============================================
@@ -397,7 +465,9 @@ export class ConfigPanel {
// 上传功能 // 上传功能
'uploadGitModuleFolder': (data) => this.uploadGitModuleFolder(data.folderId, data.username, data.password), 'uploadGitModuleFolder': (data) => this.uploadGitModuleFolder(data.folderId, data.username, data.password),
'openRepoSelectForUpload': (data) => this.handleOpenRepoSelectForUpload(data.folderId), 'openRepoSelectForUpload': (data) => this.handleOpenRepoSelectForUpload(data.folderId),
'uploadLocalModuleFolder': (data) => this.uploadLocalModuleFolder(data.folderId, data.repoUrl, data.branchName) 'uploadLocalModuleFolder': (data) => this.uploadLocalModuleFolder(data.folderId, data.repoUrl, data.branchName),
'openRepoSelectForGitUpload': (data) => this.openRepoSelectForGitUpload(data.folderId),
'repoSelectedForGitUpload': (data) => this.handleRepoSelectedForGitUpload(data.folderId, data.repoName)
}; };
const handler = messageHandlers[data.type]; const handler = messageHandlers[data.type];
@@ -651,12 +721,9 @@ export class ConfigPanel {
private async handleOpenRepoSelectForUpload(folderId: string): Promise<void> { private async handleOpenRepoSelectForUpload(folderId: string): Promise<void> {
console.log("📌 Local 上传:收到 openRepoSelectForUploadfolderId =", folderId); console.log("📌 Local 上传:收到 openRepoSelectForUploadfolderId =", folderId);
this.pendingUploadFolderId = folderId; this.pendingUploadFolderId = folderId;
// 复用你现有的仓库选择弹窗
await this.openRepoSelect(); await this.openRepoSelect();
} }
private async deleteConfig(configId: string): Promise<void> { private async deleteConfig(configId: string): Promise<void> {
const config = this.configs.find(c => c.id === configId); const config = this.configs.find(c => c.id === configId);
if (!config) return; if (!config) return;
@@ -939,7 +1006,9 @@ export class ConfigPanel {
name: displayName, name: displayName,
type: 'git', type: 'git',
localPath: relativePath, localPath: relativePath,
containerId: this.currentContainerId containerId: this.currentContainerId,
originalFolderName: folderName,
originalRepoUrl: url
}; };
await vscode.window.withProgress({ await vscode.window.withProgress({
@@ -1190,6 +1259,10 @@ export class ConfigPanel {
// 上传功能方法 // 上传功能方法
// ============================================= // =============================================
/**
* 旧的 Git 模块上传逻辑:直接在当前远程 / 当前分支上 commit + push
* 现在只在“未改名 + 同仓库”的场景下被调用
*/
private async uploadGitModuleFolder(folderId: string, username?: string, password?: string): Promise<void> { private async uploadGitModuleFolder(folderId: string, username?: string, password?: string): Promise<void> {
const folder = this.moduleFolders.find(f => f.id === folderId); const folder = this.moduleFolders.find(f => f.id === folderId);
if (!folder || folder.type !== 'git') { if (!folder || folder.type !== 'git') {
@@ -1269,6 +1342,9 @@ export class ConfigPanel {
folder.type = 'git'; folder.type = 'git';
folder.uploaded = true; folder.uploaded = true;
folder.originalFolderName = branchName;
folder.originalRepoUrl = repoUrl;
await this.saveCurrentProjectData(); await this.saveCurrentProjectData();
vscode.window.showInformationMessage(`本地文件夹成功上传到 Git 仓库: ${folder.name} -> ${branchName}`); vscode.window.showInformationMessage(`本地文件夹成功上传到 Git 仓库: ${folder.name} -> ${branchName}`);
@@ -1290,6 +1366,70 @@ export class ConfigPanel {
}); });
} }
/**
* Git 模块以“当前文件夹名”为分支,推送到任意仓库 URL
* 不修改现有 remote 配置,直接用 URL 形式 push
*/
private async pushGitModuleFolderToRepoWithBranch(fullPath: string, repoUrl: string, branchName: string): Promise<void> {
return new Promise((resolve, reject) => {
const { exec } = require('child_process');
console.log('🚀 准备以新分支推送到仓库:', { fullPath, repoUrl, branchName });
// 先检查是否有未提交修改
exec('git status --porcelain', {
cwd: fullPath,
encoding: 'utf8'
}, (statusError: any, statusStdout: string, statusStderr: string) => {
if (statusError) {
console.error('❌ 检查 Git 状态失败:', statusError);
reject(new Error(`检查 Git 状态失败: ${statusStderr || statusError.message}`));
return;
}
const hasChanges = !!statusStdout.trim();
console.log('📋 有未提交更改?:', hasChanges, ', status =\n', statusStdout);
const commands: string[] = [];
// 获取完整的仓库历史
commands.push('git fetch --unshallow');
// 切换 / 创建分支
commands.push(`git checkout -B "${branchName}"`);
// 有变更就先提交
if (hasChanges) {
commands.push('git add .');
commands.push(`git commit -m "Auto commit from DCSP - ${new Date().toLocaleString()}"`);
}
// 直接用 URL 形式推送,不依赖 remote 名称
commands.push(`git push -u "${repoUrl}" "${branchName}" --force`);
console.log('🧰 即将执行命令链:', commands.join(' && '));
exec(commands.join(' && '), {
cwd: fullPath,
encoding: 'utf8'
}, (error: any, stdout: string, stderr: string) => {
console.log('📋 Git push stdout:', stdout);
console.log('📋 Git push stderr:', stderr);
if (error) {
console.error('❌ Git 推送失败:', error);
reject(new Error(stderr || error.message));
return;
}
console.log('✅ Git 推送到指定仓库/分支成功');
resolve();
});
});
});
}
// ============================================= // =============================================
// Git 命令行工具方法 // Git 命令行工具方法
// ============================================= // =============================================
@@ -1351,7 +1491,7 @@ export class ConfigPanel {
console.log('📁 初始化 Git 仓库...'); console.log('📁 初始化 Git 仓库...');
exec(`git init && git checkout -b ${branchName}`, { exec(`git init && git checkout -b "${branchName}"`, {
cwd: fullPath, cwd: fullPath,
encoding: 'utf8' encoding: 'utf8'
}, (error: any, stdout: string, stderr: string) => { }, (error: any, stdout: string, stderr: string) => {
@@ -1373,7 +1513,7 @@ export class ConfigPanel {
console.log('📡 添加远程仓库...'); console.log('📡 添加远程仓库...');
exec(`git remote add origin ${repoUrl}`, { exec(`git remote add origin "${repoUrl}"`, {
cwd: fullPath, cwd: fullPath,
encoding: 'utf8' encoding: 'utf8'
}, (error: any, stdout: string, stderr: string) => { }, (error: any, stdout: string, stderr: string) => {
@@ -1397,7 +1537,7 @@ export class ConfigPanel {
const commands = [ const commands = [
'git add .', 'git add .',
`git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`, `git commit -m "Initial commit from DCSP - ${new Date().toLocaleString()}"`
]; ];
exec(commands.join(' && '), { exec(commands.join(' && '), {
@@ -1428,7 +1568,7 @@ export class ConfigPanel {
console.log('🚀 强制推送到远程仓库...'); console.log('🚀 强制推送到远程仓库...');
exec(`git push -u origin ${branchName} --force`, { exec(`git push -u origin "${branchName}" --force`, {
cwd: fullPath, cwd: fullPath,
encoding: 'utf8' encoding: 'utf8'
}, (error: any, stdout: string, stderr: string) => { }, (error: any, stdout: string, stderr: string) => {
@@ -1893,6 +2033,12 @@ export class ConfigPanel {
} }
} }
private async handleRepoSelectedForGitUpload(folderId: string, repoName: string): Promise<void> {
// 兼容:如果前端用的是 repoSelectedForGitUpload 事件,则这里转成统一逻辑
this.pendingGitUploadFolderId = folderId;
await this.handleRepoSelectedForBranches(repoName);
}
// ============================================= // =============================================
// 工具方法 // 工具方法
// ============================================= // =============================================
@@ -2047,7 +2193,7 @@ export class ConfigPanel {
try { try {
await fs.promises.rename(fullPath, newFullPath); await fs.promises.rename(fullPath, newFullPath);
// 更新 localPath // 更新 localPath注意originalFolderName 不更新,用于判断“是否改名过”)
folder.localPath = folder.localPath.replace(/\/[^/]+$/, '/' + newName); folder.localPath = folder.localPath.replace(/\/[^/]+$/, '/' + newName);
await this.saveCurrentProjectData(); await this.saveCurrentProjectData();
@@ -2060,8 +2206,6 @@ export class ConfigPanel {
} }
} }
// ============================================= // =============================================
// Webview 更新方法 // Webview 更新方法
// ============================================= // =============================================
@@ -2079,6 +2223,12 @@ export class ConfigPanel {
} }
} }
private async openRepoSelectForGitUpload(folderId: string): Promise<void> {
console.log('📌 Git 上传:收到 openRepoSelectForGitUploadfolderId =', folderId);
this.pendingGitUploadFolderId = folderId;
await this.openRepoSelect();
}
private getWebviewContent(): string { private getWebviewContent(): string {
switch (this.currentView) { switch (this.currentView) {
case 'projects': case 'projects':

7
src/panels/types/CommonTypes.ts Normal file → Executable file
View File

@@ -35,8 +35,15 @@ export interface ModuleFolder {
localPath: string; localPath: string;
containerId: string; containerId: string;
uploaded?: boolean; uploaded?: boolean;
/** 🟦 记录原始文件夹名(用于判断用户是否重命名) */
originalFolderName?: string;
/** 🟦 记录创建时使用的仓库 URL用于判断是否同仓库 */
originalRepoUrl?: string;
} }
// 完整数据模型接口 // 完整数据模型接口
export interface ProjectData { export interface ProjectData {
projects: Project[]; projects: Project[];

View File

@@ -9,16 +9,6 @@ interface GitBranch {
selected?: boolean; selected?: boolean;
} }
interface BranchTreeNode {
name: string;
fullName: string;
isLeaf: boolean;
children: BranchTreeNode[];
level: number;
expanded?: boolean;
branch?: GitBranch;
}
// Git 文件树接口 // Git 文件树接口
interface GitFileTree { interface GitFileTree {
name: string; name: string;
@@ -73,28 +63,31 @@ export class ConfigView extends BaseView {
// 生成模块文件夹的 HTML - 按类别分类显示 // 生成模块文件夹的 HTML - 按类别分类显示
const moduleFoldersHtml = moduleFolders.map((folder: ModuleFolder) => { const moduleFoldersHtml = moduleFolders.map((folder: ModuleFolder) => {
const icon = folder.type === 'git' ? '📁' : '📁'; const icon = '📁';
// 根据类型和上传状态确定类别显示 // 根据类型和上传状态确定类别显示
let category = folder.type === 'git' ? 'git' : 'local'; let category = folder.type === 'git' ? 'git' : 'local';
if (folder.uploaded) { if (folder.uploaded) {
category += '(已上传)'; category += '(已上传)';
} }
const fileName = folder.localPath.split('/').pop() || '';
return ` return `
<tr> <tr>
<td> <td>
<span class="editable clickable" onclick="renameModuleFolder('${folder.id}')"> <!-- 左侧:📁 动力学仓库 / test仅用于重命名 -->
<span class="editable clickable" onclick="renameModuleFolder('${folder.id}', '${folder.name}')">
${icon} ${folder.name} ${icon} ${folder.name}
</span> </span>
</td> </td>
<td class="category-${folder.type}">${category}</td> <td class="category-${folder.type}">${category}</td>
<td> <td>
<!-- 右侧:文件/文件夹栏,只负责选择并打开文件,不再触发重命名 -->
<span class="clickable" <span class="clickable"
onclick="renameModuleFolder('${folder.id}', '${folder.localPath.split('/').pop()}')"> onclick="openTheModuleFolder('${folder.id}', '${folder.type}')">
📄 ${folder.localPath.split('/').pop()} 📄 ${fileName}
</span> </span>
</td> </td>
<td> <td>
<button class="btn-upload" onclick="uploadModuleFolder('${folder.id}', '${folder.type}')" style="margin-right: 5px;">上传</button> <button class="btn-upload" onclick="uploadModuleFolder('${folder.id}', '${folder.type}')" style="margin-right: 5px;">上传</button>
<button class="btn-delete" onclick="deleteModuleFolder('${folder.id}')">删除</button> <button class="btn-delete" onclick="deleteModuleFolder('${folder.id}')">删除</button>
@@ -369,11 +362,12 @@ export class ConfigView extends BaseView {
let branchTreeData = []; let branchTreeData = [];
let selectedConfigs = new Set(); let selectedConfigs = new Set();
// 只负责“改名”的函数
function renameModuleFolder(folderId, oldName) { function renameModuleFolder(folderId, oldName) {
showPromptDialog( showPromptDialog(
'重命名模块文件夹', '重命名模块文件夹',
'请输入新的名称:', '请输入新的名称:',
oldName, oldName || '',
function(newName) { function(newName) {
if (newName && newName.trim() && newName !== oldName) { if (newName && newName.trim() && newName !== oldName) {
vscode.postMessage({ vscode.postMessage({
@@ -386,8 +380,6 @@ export class ConfigView extends BaseView {
); );
} }
// 配置管理功能 // 配置管理功能
function editConfigName(configId, currentName) { function editConfigName(configId, currentName) {
showPromptDialog( showPromptDialog(
@@ -415,7 +407,7 @@ export class ConfigView extends BaseView {
}); });
} }
// 统一功能:打开模块文件夹 // 统一功能:打开模块文件夹(这里只负责打开,而不是改名)
function openTheModuleFolder(id, type) { function openTheModuleFolder(id, type) {
console.log('📂 打开模块文件夹:', { id, type }); console.log('📂 打开模块文件夹:', { id, type });
vscode.postMessage({ vscode.postMessage({
@@ -453,7 +445,6 @@ export class ConfigView extends BaseView {
}); });
}, },
function() { function() {
// 用户取消删除
console.log('❌ 用户取消删除配置文件'); console.log('❌ 用户取消删除配置文件');
} }
); );
@@ -474,34 +465,23 @@ export class ConfigView extends BaseView {
}); });
}, },
function() { function() {
// 用户取消删除
console.log('❌ 用户取消删除模块文件夹'); console.log('❌ 用户取消删除模块文件夹');
} }
); );
} }
// 上传模块文件夹功能 // 上传模块文件夹功能(区分 local / git
function uploadModuleFolder(folderId, folderType) { function uploadModuleFolder(folderId, folderType) {
console.log('📤 上传模块文件夹:', { folderId, folderType }); console.log('📤 上传模块文件夹:', { folderId, folderType });
if (folderType === 'git') { if (folderType === 'git') {
// Git 类型:直接上传(相当于 push // git 类型:先选仓库,然后后端根据是否改名/是否同仓库判断是更新原分支还是新分支上传
showConfirmDialog(
'确认上传',
'确定要将 Git 文件夹的内容上传到远程仓库吗?这将执行 git push 操作。',
function() {
vscode.postMessage({ vscode.postMessage({
type: 'uploadGitModuleFolder', type: 'openRepoSelectForGitUpload',
folderId: folderId folderId: folderId
}); });
}, } else {
function() { // local 类型:走 local → git 的初始化上传逻辑
console.log('❌ 用户取消上传 Git 文件夹');
}
);
} else if (folderType === 'local') {
// Local 类型:不再手动输入 URL改为复用“获取仓库”弹窗
// 后端将根据选择的仓库 + 本地文件夹名 自动确定 repoUrl 和 分支名
vscode.postMessage({ vscode.postMessage({
type: 'openRepoSelectForUpload', type: 'openRepoSelectForUpload',
folderId: folderId folderId: folderId
@@ -544,12 +524,10 @@ export class ConfigView extends BaseView {
branches: Array.from(selectedBranches) branches: Array.from(selectedBranches)
}); });
// 重置选择
selectedBranches.clear(); selectedBranches.clear();
const checkboxes = document.querySelectorAll('input[type="checkbox"]'); const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => checkbox.checked = false); checkboxes.forEach(checkbox => checkbox.checked = false);
// 隐藏分支选择区域
document.getElementById('branchSelectionContainer').innerHTML = ''; document.getElementById('branchSelectionContainer').innerHTML = '';
} }
@@ -558,7 +536,6 @@ export class ConfigView extends BaseView {
const checkboxes = document.querySelectorAll('input[type="checkbox"]'); const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => checkbox.checked = false); checkboxes.forEach(checkbox => checkbox.checked = false);
// 隐藏分支选择区域
document.getElementById('branchSelectionContainer').innerHTML = ''; document.getElementById('branchSelectionContainer').innerHTML = '';
vscode.postMessage({ vscode.postMessage({
@@ -575,7 +552,6 @@ export class ConfigView extends BaseView {
selectedConfigs.delete(configId); selectedConfigs.delete(configId);
} }
// 更新合并按钮状态
updateMergeButtonState(); updateMergeButtonState();
console.log('选中的配置文件:', Array.from(selectedConfigs)); console.log('选中的配置文件:', Array.from(selectedConfigs));
} }
@@ -595,7 +571,6 @@ export class ConfigView extends BaseView {
console.log('🔄 开始合并选中的配置文件:', Array.from(selectedConfigs)); console.log('🔄 开始合并选中的配置文件:', Array.from(selectedConfigs));
// 弹出新的合并对话框
showMergeDialog(); showMergeDialog();
} }
@@ -631,7 +606,6 @@ export class ConfigView extends BaseView {
document.body.appendChild(overlay); document.body.appendChild(overlay);
// 自动聚焦到第一个输入框
setTimeout(() => { setTimeout(() => {
const displayNameInput = document.getElementById('displayNameInput'); const displayNameInput = document.getElementById('displayNameInput');
if (displayNameInput) { if (displayNameInput) {
@@ -666,7 +640,6 @@ export class ConfigView extends BaseView {
folderName: folderName folderName: folderName
}); });
// 重置选择
selectedConfigs.clear(); selectedConfigs.clear();
const checkboxes = document.querySelectorAll('.config-checkbox'); const checkboxes = document.querySelectorAll('.config-checkbox');
checkboxes.forEach(checkbox => checkbox.checked = false); checkboxes.forEach(checkbox => checkbox.checked = false);
@@ -736,7 +709,6 @@ export class ConfigView extends BaseView {
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">'; let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>'; html += '<h4 class="branch-tree-title" style="margin: 0 0 8px 0;"><span class="tree-icon">🌳</span> 分支树</h4>';
// 展开/收起按钮
html += '<div style="margin-bottom: 8px;">'; html += '<div style="margin-bottom: 8px;">';
html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>'; html += '<button class="back-btn" onclick="expandAllBranches()" style="margin-right: 8px; padding: 2px 6px; font-size: 11px;">展开全部</button>';
html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>'; html += '<button class="back-btn" onclick="collapseAllBranches()" style="padding: 2px 6px; font-size: 11px;">收起全部</button>';
@@ -761,7 +733,6 @@ export class ConfigView extends BaseView {
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-'); const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
if (node.isLeaf) { if (node.isLeaf) {
// 叶子节点(实际分支)
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" '; html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">'; html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\\'' + node.fullName + '\\')">';
@@ -772,7 +743,6 @@ export class ConfigView extends BaseView {
html += '</span>'; html += '</span>';
html += '</div>'; html += '</div>';
} else { } else {
// 文件夹节点
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">'; html += '<span class="branch-expand" onclick="toggleBranchNode(\\'' + node.fullName + '\\')">';
html += (node.expanded ? '🪵' : '🪵'); html += (node.expanded ? '🪵' : '🪵');
@@ -791,18 +761,6 @@ export class ConfigView extends BaseView {
return html; return html;
} }
function countLeafNodes(nodes) {
let count = 0;
nodes.forEach(node => {
if (node.isLeaf) {
count++;
} else if (node.children) {
count += countLeafNodes(node.children);
}
});
return count;
}
// 对话框函数 // 对话框函数
function showConfirmDialog(title, message, onConfirm, onCancel) { function showConfirmDialog(title, message, onConfirm, onCancel) {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
@@ -900,7 +858,6 @@ export class ConfigView extends BaseView {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
console.log('📄 ConfigView 页面加载完成'); console.log('📄 ConfigView 页面加载完成');
// 为所有配置复选框添加事件监听
document.addEventListener('change', function(event) { document.addEventListener('change', function(event) {
if (event.target.classList.contains('config-checkbox')) { if (event.target.classList.contains('config-checkbox')) {
const configId = event.target.getAttribute('data-id'); const configId = event.target.getAttribute('data-id');
@@ -916,7 +873,6 @@ export class ConfigView extends BaseView {
private generateBranchesTreeHtml(branches: GitBranch[]): string { private generateBranchesTreeHtml(branches: GitBranch[]): string {
if (branches.length === 0) return ''; if (branches.length === 0) return '';
// 构建分支树
const branchTree = this.buildBranchTree(branches); const branchTree = this.buildBranchTree(branches);
let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">'; let html = '<div class="branch-selection" style="margin: 10px 0; padding: 10px; background: var(--vscode-panel-background); border-radius: 4px;">';
@@ -936,9 +892,6 @@ export class ConfigView extends BaseView {
return html; return html;
} }
/**
* 构建分支树状结构
*/
private buildBranchTree(branches: GitBranch[]): BranchTreeNode[] { private buildBranchTree(branches: GitBranch[]): BranchTreeNode[] {
const root: BranchTreeNode[] = []; const root: BranchTreeNode[] = [];
@@ -959,15 +912,14 @@ export class ConfigView extends BaseView {
fullName: fullName, fullName: fullName,
isLeaf: isLeaf, isLeaf: isLeaf,
children: [], children: [],
level: i level: i,
expanded: true
}; };
currentLevel.push(node); currentLevel.push(node);
} }
if (isLeaf) { if (isLeaf) {
node.branch = branch; node.branch = branch;
} else {
node.expanded = node.expanded || true; // 默认展开
} }
currentLevel = node.children; currentLevel = node.children;
@@ -977,9 +929,6 @@ export class ConfigView extends BaseView {
return root; return root;
} }
/**
* 渲染分支树节点
*/
private renderBranchTreeNodes(nodes: BranchTreeNode[], level: number): string { private renderBranchTreeNodes(nodes: BranchTreeNode[], level: number): string {
let html = ''; let html = '';
@@ -988,7 +937,6 @@ export class ConfigView extends BaseView {
const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-'); const nodeId = node.fullName.replace(/[^a-zA-Z0-9-]/g, '-');
if (node.isLeaf) { if (node.isLeaf) {
// 叶子节点(实际分支)
html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node branch-leaf" style="margin-left: ' + indent + 'px;">';
html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" '; html += '<input type="checkbox" id="branch-' + nodeId + '" class="branch-checkbox" ';
html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\'' + node.fullName + '\')">'; html += (node.branch && node.branch.selected ? 'checked' : '') + ' onchange="toggleBranch(\'' + node.fullName + '\')">';
@@ -999,7 +947,6 @@ export class ConfigView extends BaseView {
html += '</span>'; html += '</span>';
html += '</div>'; html += '</div>';
} else { } else {
// 文件夹节点
html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">'; html += '<div class="branch-node" style="margin-left: ' + indent + 'px;">';
html += '<span class="branch-expand" onclick="toggleBranchNode(\'' + node.fullName + '\')">'; html += '<span class="branch-expand" onclick="toggleBranchNode(\'' + node.fullName + '\')">';
html += (node.expanded ? '🪵' : '🪵'); html += (node.expanded ? '🪵' : '🪵');
@@ -1019,9 +966,6 @@ export class ConfigView extends BaseView {
return html; return html;
} }
/**
* 计算叶子节点数量
*/
private countLeafNodes(nodes: BranchTreeNode[]): number { private countLeafNodes(nodes: BranchTreeNode[]): number {
let count = 0; let count = 0;
nodes.forEach(node => { nodes.forEach(node => {