新增: 多工程管理(编辑/删除确认/记忆选中/自动扫描子仓库)
- 工程编辑入口:工具栏编辑按钮+弹窗(名称/路径/Git地址) - 工程删除二次确认:防误删 - 记住上次选中工程:localStorage 持久化 - 自动扫描子仓库:下拉菜单加扫描入口,发现 .git 子目录自动建工程
This commit is contained in:
@@ -768,6 +768,88 @@ pub async fn get_module_file_diff(
|
||||
}))
|
||||
}
|
||||
|
||||
/// 扫描项目绑定目录下的子仓库(含 .git 的直接子目录),自动创建工程记录。
|
||||
/// 返回新创建的工程数量。幂等:已存在的路径不重复创建。
|
||||
#[tauri::command]
|
||||
pub async fn scan_project_modules(
|
||||
state: State<'_, AppState>,
|
||||
project_id: String,
|
||||
) -> Result<i64, String> {
|
||||
let project_id = project_id.trim().to_string();
|
||||
if project_id.is_empty() {
|
||||
return Err("project_id 不能为空".to_string());
|
||||
}
|
||||
// 取项目记录拿 path
|
||||
let project = state
|
||||
.projects
|
||||
.get_by_id(&project_id)
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("项目 {project_id} 不存在"))?;
|
||||
let root_path = project
|
||||
.path
|
||||
.as_ref()
|
||||
.filter(|p| !p.trim().is_empty())
|
||||
.ok_or_else(|| "项目未绑定目录".to_string())?;
|
||||
let root = PathBuf::from(root_path);
|
||||
// 取已有工程路径集(避免重复创建)
|
||||
let existing = state
|
||||
.project_modules
|
||||
.list_by_project(&project_id)
|
||||
.await
|
||||
.map_err(err_str)?;
|
||||
let existing_paths: std::collections::HashSet<String> = existing
|
||||
.iter()
|
||||
.map(|m| m.path.to_lowercase().replace("\\", "/"))
|
||||
.collect();
|
||||
// 扫描一级子目录(含 .git)
|
||||
let mut new_count = 0i64;
|
||||
let entries = std::fs::read_dir(&root)
|
||||
.map_err(|e| format!("读取项目目录失败: {e}"))?;
|
||||
for entry in entries.flatten() {
|
||||
let ft = match entry.file_type() { Ok(t) => t, Err(_) => continue };
|
||||
if !ft.is_dir() { continue; }
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
// 跳过隐藏目录和噪音目录
|
||||
if name.starts_with('.') || name == "node_modules" || name == "target" || name == "__pycache__" || name == "dist" {
|
||||
continue;
|
||||
}
|
||||
let child_path = entry.path();
|
||||
// 必须含 .git(独立仓库)
|
||||
if !child_path.join(".git").exists() { continue; }
|
||||
let child_path_str = child_path.to_string_lossy().replace("\\", "/");
|
||||
if existing_paths.contains(&child_path_str.to_lowercase()) { continue; }
|
||||
// 获取远程地址(失败忽略)
|
||||
let git_url = std::process::Command::new("git")
|
||||
.args(["remote", "get-url", "origin"])
|
||||
.current_dir(&child_path)
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
let now_str = now_millis();
|
||||
let record = ProjectModuleRecord {
|
||||
id: new_id(),
|
||||
project_id: project_id.clone(),
|
||||
name: name.clone(),
|
||||
path: child_path.to_string_lossy().to_string(),
|
||||
git_url,
|
||||
stack: None,
|
||||
auto_detected: true,
|
||||
sort_order: (existing.len() + new_count as usize) as i32,
|
||||
created_at: now_str.clone(),
|
||||
updated_at: now_str,
|
||||
};
|
||||
if let Err(e) = state.project_modules.insert(record).await {
|
||||
tracing::warn!("自动探测工程 {} 失败: {}", child_path_str, e);
|
||||
continue;
|
||||
}
|
||||
new_count += 1;
|
||||
}
|
||||
Ok(new_count)
|
||||
}
|
||||
|
||||
/// 采集工程目录 git status --porcelain 的 {posix_rel_path: status} 映射。
|
||||
/// 非 git 仓库/命令失败 → 空映射(不阻断文件树展示,仅 git_status 字段全 None)。
|
||||
fn collect_git_status_map(dir: &str) -> HashMap<String, String> {
|
||||
|
||||
@@ -324,6 +324,7 @@ pub fn run() {
|
||||
commands::module::update_project_module,
|
||||
commands::module::remove_project_module,
|
||||
commands::module::list_project_modules,
|
||||
commands::module::scan_project_modules,
|
||||
commands::module::get_module_git_status,
|
||||
// 工程文件浏览(Batch 10):文件树 + 单文件预览
|
||||
commands::module::get_module_file_tree,
|
||||
|
||||
Reference in New Issue
Block a user