黄中银 пре 2 недеља
родитељ
комит
86a9c59eaa

+ 1 - 0
src-tauri/Cargo.toml

@@ -35,6 +35,7 @@ dirs = "5"
 which = "6"
 thiserror = "1"
 anyhow = "1"
+regex = "1"
 
 [target.'cfg(windows)'.dependencies]
 winreg = "0.52"

+ 10 - 1
src-tauri/src/commands/claude_code.rs

@@ -16,7 +16,16 @@ pub struct CommandResult {
 /// 检查 Claude Code 是否已安装
 #[tauri::command]
 pub async fn check_claude_code() -> ClaudeCodeStatus {
-    match Command::new("claude").args(["--version"]).output() {
+    // 在 Windows 上使用 cmd /c 来执行命令,确保能正确加载 PATH 环境变量
+    #[cfg(target_os = "windows")]
+    let output = Command::new("cmd")
+        .args(["/c", "claude --version"])
+        .output();
+
+    #[cfg(not(target_os = "windows"))]
+    let output = Command::new("claude").args(["--version"]).output();
+
+    match output {
         Ok(output) if output.status.success() => {
             let version = String::from_utf8_lossy(&output.stdout)
                 .lines()

+ 234 - 28
src-tauri/src/commands/software.rs

@@ -1,5 +1,7 @@
 use serde::{Deserialize, Serialize};
 use std::process::Command;
+use regex::Regex;
+use super::config::get_git_mirror_config;
 
 #[derive(Serialize, Deserialize)]
 pub struct InstalledInfo {
@@ -75,12 +77,49 @@ async fn check_single_software(software: &str) -> InstalledInfo {
         _ => return InstalledInfo { installed: false, version: None },
     };
 
-    match Command::new(cmd).args(&args).output() {
+    // 在 Windows 上使用 cmd /c 来执行命令,确保能正确加载 PATH 环境变量
+    #[cfg(target_os = "windows")]
+    let output = {
+        let full_cmd = format!("{} {}", cmd, args.join(" "));
+        Command::new("cmd")
+            .args(["/c", &full_cmd])
+            .output()
+    };
+
+    #[cfg(not(target_os = "windows"))]
+    let output = Command::new(cmd).args(&args).output();
+
+    match output {
         Ok(output) if output.status.success() => {
-            let version = String::from_utf8_lossy(&output.stdout)
-                .lines()
-                .next()
-                .map(|s| s.trim().replace("v", "").to_string());
+            let raw_output = String::from_utf8_lossy(&output.stdout);
+            let first_line = raw_output.lines().next().unwrap_or("").trim();
+
+            // 根据软件类型解析版本号
+            let version = match software {
+                "git" => {
+                    // git --version 输出格式: "git version 2.51.1.windows.1"
+                    // 提取版本号部分
+                    if let Some(caps) = Regex::new(r"(\d+\.\d+\.\d+)").ok().and_then(|re| re.captures(first_line)) {
+                        caps.get(1).map(|m| m.as_str().to_string())
+                    } else {
+                        None
+                    }
+                }
+                "claudeCode" => {
+                    // claude --version 输出格式: "2.0.60 (Claude Code)"
+                    // 提取版本号部分
+                    if let Some(caps) = Regex::new(r"(\d+\.\d+\.\d+)").ok().and_then(|re| re.captures(first_line)) {
+                        caps.get(1).map(|m| m.as_str().to_string())
+                    } else {
+                        None
+                    }
+                }
+                _ => {
+                    // 其他软件:去掉开头的 'v' 前缀
+                    Some(first_line.trim_start_matches('v').to_string())
+                }
+            };
+
             InstalledInfo {
                 installed: true,
                 version,
@@ -155,41 +194,208 @@ async fn get_nodejs_versions() -> Result<VersionResult, String> {
 }
 
 async fn get_git_versions() -> Result<VersionResult, String> {
-    // Git 版本通常从 GitHub releases 获取
+    // 获取当前镜像配置
+    let mirror_config = get_git_mirror_config().await;
+    let mirror = mirror_config.mirror.as_str();
+
     let client = reqwest::Client::new();
-    let response = client
-        .get("https://api.github.com/repos/git-for-windows/git/releases")
-        .header("User-Agent", "Claude-AI-Installer")
-        .timeout(std::time::Duration::from_secs(10))
-        .send()
-        .await
-        .map_err(|e| e.to_string())?;
 
-    let releases: Vec<serde_json::Value> = response.json().await.map_err(|e| e.to_string())?;
+    // 根据镜像类型选择不同的获取方式
+    let (versions, mirror_name) = match mirror {
+        "huaweicloud" => {
+            let versions = get_git_versions_from_huaweicloud(&client).await;
+            (versions, "华为云镜像")
+        }
+        _ => {
+            // 默认使用 GitHub
+            let versions = get_git_versions_from_github(&client).await;
+            (versions, "GitHub 官方")
+        }
+    };
 
-    let version_items: Vec<VersionItem> = releases
-        .iter()
-        .take(10)
-        .filter_map(|r| {
-            let tag = r.get("tag_name")?.as_str()?.to_string();
-            let version = tag.trim_start_matches('v').to_string();
+    // 如果获取失败,使用备用版本列表
+    if versions.is_empty() {
+        return Ok(VersionResult {
+            versions: get_fallback_git_versions(),
+            warning: Some(format!("无法从 {} 获取版本列表, 显示预设版本", mirror_name)),
+        });
+    }
 
-            Some(VersionItem {
-                value: version.clone(),
-                label: version,
-                lts: None,
-                disabled: None,
-                separator: None,
-            })
+    // 构建版本列表
+    let version_items: Vec<VersionItem> = versions
+        .into_iter()
+        .take(15)
+        .map(|version| VersionItem {
+            value: version.clone(),
+            label: format!("Git {}", version),
+            lts: None,
+            disabled: None,
+            separator: None,
         })
         .collect();
 
     Ok(VersionResult {
         versions: version_items,
-        warning: None,
+        warning: Some(format!("下载源: {}", mirror_name)),
     })
 }
 
+/// 从 GitHub API 获取 Git 版本列表
+async fn get_git_versions_from_github(client: &reqwest::Client) -> Vec<String> {
+    let response = match client
+        .get("https://api.github.com/repos/git-for-windows/git/releases")
+        .header("User-Agent", "Claude-AI-Installer")
+        .header("Accept", "application/vnd.github.v3+json")
+        .timeout(std::time::Duration::from_secs(10))
+        .send()
+        .await
+    {
+        Ok(resp) => resp,
+        Err(e) => {
+            eprintln!("GitHub API 请求失败: {}", e);
+            return vec![];
+        }
+    };
+
+    if !response.status().is_success() {
+        eprintln!("GitHub API 返回错误状态: {}", response.status());
+        return vec![];
+    }
+
+    let releases: Vec<serde_json::Value> = match response.json().await {
+        Ok(r) => r,
+        Err(e) => {
+            eprintln!("解析 GitHub 响应失败: {}", e);
+            return vec![];
+        }
+    };
+
+    let mut versions: Vec<String> = vec![];
+    for release in releases {
+        if let Some(tag) = release.get("tag_name").and_then(|t| t.as_str()) {
+            // 跳过预发布版本
+            if release.get("prerelease").and_then(|p| p.as_bool()).unwrap_or(false) {
+                continue;
+            }
+            // 提取版本号: v2.47.1.windows.1 -> 2.47.1
+            if let Some(caps) = Regex::new(r"^v?(\d+\.\d+\.\d+)").ok().and_then(|re| re.captures(tag)) {
+                if let Some(version) = caps.get(1) {
+                    let v = version.as_str().to_string();
+                    if !versions.contains(&v) {
+                        versions.push(v);
+                    }
+                }
+            }
+        }
+    }
+
+    versions
+}
+
+/// 从华为云镜像获取 Git 版本列表
+async fn get_git_versions_from_huaweicloud(client: &reqwest::Client) -> Vec<String> {
+    let response = match client
+        .get("https://mirrors.huaweicloud.com/git-for-windows/")
+        .header("User-Agent", "Claude-AI-Installer")
+        .timeout(std::time::Duration::from_secs(10))
+        .send()
+        .await
+    {
+        Ok(resp) => resp,
+        Err(e) => {
+            eprintln!("华为云镜像请求失败: {}", e);
+            return vec![];
+        }
+    };
+
+    if !response.status().is_success() {
+        eprintln!("华为云镜像返回错误状态: {}", response.status());
+        return vec![];
+    }
+
+    let html = match response.text().await {
+        Ok(h) => h,
+        Err(e) => {
+            eprintln!("读取华为云响应失败: {}", e);
+            return vec![];
+        }
+    };
+
+    // 解析 HTML 页面,查找版本目录链接
+    // 格式: <a href="v2.47.1.windows.1/">v2.47.1.windows.1/</a>
+    let re = match Regex::new(r#"href="v(\d+\.\d+\.\d+)\.windows\.\d+/""#) {
+        Ok(r) => r,
+        Err(_) => return vec![],
+    };
+
+    let mut versions: Vec<String> = vec![];
+    for caps in re.captures_iter(&html) {
+        if let Some(version) = caps.get(1) {
+            let v = version.as_str().to_string();
+            if !versions.contains(&v) {
+                versions.push(v);
+            }
+        }
+    }
+
+    // 按版本号降序排序
+    versions.sort_by(|a, b| {
+        let a_parts: Vec<u32> = a.split('.').filter_map(|s| s.parse().ok()).collect();
+        let b_parts: Vec<u32> = b.split('.').filter_map(|s| s.parse().ok()).collect();
+        for i in 0..3 {
+            let a_val = a_parts.get(i).unwrap_or(&0);
+            let b_val = b_parts.get(i).unwrap_or(&0);
+            if a_val != b_val {
+                return b_val.cmp(a_val);
+            }
+        }
+        std::cmp::Ordering::Equal
+    });
+
+    versions
+}
+
+/// 获取预设的 Git 版本列表(当 API 不可用时使用)
+fn get_fallback_git_versions() -> Vec<VersionItem> {
+    vec![
+        VersionItem {
+            value: "2.47.1.windows.1".to_string(),
+            label: "2.47.1.windows.1".to_string(),
+            lts: None,
+            disabled: None,
+            separator: None,
+        },
+        VersionItem {
+            value: "2.47.0.windows.2".to_string(),
+            label: "2.47.0.windows.2".to_string(),
+            lts: None,
+            disabled: None,
+            separator: None,
+        },
+        VersionItem {
+            value: "2.46.2.windows.1".to_string(),
+            label: "2.46.2.windows.1".to_string(),
+            lts: None,
+            disabled: None,
+            separator: None,
+        },
+        VersionItem {
+            value: "2.46.1.windows.1".to_string(),
+            label: "2.46.1.windows.1".to_string(),
+            lts: None,
+            disabled: None,
+            separator: None,
+        },
+        VersionItem {
+            value: "2.46.0.windows.1".to_string(),
+            label: "2.46.0.windows.1".to_string(),
+            lts: None,
+            disabled: None,
+            separator: None,
+        },
+    ]
+}
+
 async fn get_vscode_versions() -> Result<VersionResult, String> {
     // VS Code 通常只提供最新版本
     Ok(VersionResult {

+ 61 - 14
src-tauri/src/commands/system.rs

@@ -10,22 +10,35 @@ pub struct PackageManagerResult {
 /// 获取当前平台
 #[tauri::command]
 pub fn get_platform() -> String {
+    let platform;
     #[cfg(target_os = "windows")]
-    return "win32".to_string();
+    {
+        platform = "win32".to_string();
+    }
 
     #[cfg(target_os = "macos")]
-    return "darwin".to_string();
+    {
+        platform = "darwin".to_string();
+    }
 
     #[cfg(target_os = "linux")]
-    return "linux".to_string();
+    {
+        platform = "linux".to_string();
+    }
 
     #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
-    return "unknown".to_string();
+    {
+        platform = "unknown".to_string();
+    }
+
+    log::debug!("get_platform: {}", platform);
+    platform
 }
 
 /// 检查是否具有管理员权限
 #[tauri::command]
 pub async fn check_admin() -> bool {
+    let is_admin;
     #[cfg(target_os = "windows")]
     {
         // Windows: 使用 PowerShell 检查管理员权限
@@ -36,20 +49,23 @@ pub async fn check_admin() -> bool {
             ])
             .output();
 
-        match output {
+        is_admin = match output {
             Ok(out) => {
                 let result = String::from_utf8_lossy(&out.stdout).trim().to_lowercase();
                 result == "true"
             }
             Err(_) => false,
-        }
+        };
     }
 
     #[cfg(not(target_os = "windows"))]
     {
         // Unix: 检查 UID 是否为 0
-        unsafe { libc::getuid() == 0 }
+        is_admin = unsafe { libc::getuid() == 0 };
     }
+
+    log::info!("check_admin: {}", is_admin);
+    is_admin
 }
 
 /// 检查网络连接
@@ -70,25 +86,45 @@ pub async fn check_network() -> bool {
             .await
         {
             if response.status().is_success() || response.status().is_redirection() {
+                log::info!("check_network: online (connected to {})", url);
                 return true;
             }
         }
     }
 
+    log::warn!("check_network: offline");
     false
 }
 
 /// 检查包管理器 (macOS/Linux)
+/// Windows 使用直接下载方式,不需要包管理器
 #[tauri::command]
 pub async fn check_package_manager() -> PackageManagerResult {
+    #[cfg(target_os = "windows")]
+    {
+        // Windows 使用直接下载安装,不需要包管理器
+        log::info!("check_package_manager: Windows uses direct download, no package manager needed");
+        PackageManagerResult {
+            exists: true,
+            manager: "none".to_string(),
+        }
+    }
+
     #[cfg(target_os = "macos")]
     {
         // 检查 Homebrew
         if which::which("brew").is_ok() {
-            return PackageManagerResult {
+            log::info!("check_package_manager: Homebrew found");
+            PackageManagerResult {
                 exists: true,
                 manager: "brew".to_string(),
-            };
+            }
+        } else {
+            log::warn!("check_package_manager: Homebrew not found");
+            PackageManagerResult {
+                exists: false,
+                manager: "none".to_string(),
+            }
         }
     }
 
@@ -96,16 +132,27 @@ pub async fn check_package_manager() -> PackageManagerResult {
     {
         // 检查 apt
         if which::which("apt").is_ok() || which::which("apt-get").is_ok() {
-            return PackageManagerResult {
+            log::info!("check_package_manager: APT found");
+            PackageManagerResult {
                 exists: true,
                 manager: "apt".to_string(),
-            };
+            }
+        } else {
+            log::warn!("check_package_manager: APT not found");
+            PackageManagerResult {
+                exists: false,
+                manager: "none".to_string(),
+            }
         }
     }
 
-    PackageManagerResult {
-        exists: false,
-        manager: "none".to_string(),
+    #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
+    {
+        log::warn!("check_package_manager: unsupported platform");
+        PackageManagerResult {
+            exists: false,
+            manager: "none".to_string(),
+        }
     }
 }
 

+ 24 - 1
src-tauri/src/lib.rs

@@ -2,6 +2,7 @@ mod commands;
 mod utils;
 
 use tauri::Manager;
+use tauri_plugin_log::{Target, TargetKind, TimezoneStrategy};
 
 #[cfg_attr(mobile, tauri::mobile_entry_point)]
 pub fn run() {
@@ -12,12 +13,34 @@ pub fn run() {
         .plugin(tauri_plugin_http::init())
         .plugin(tauri_plugin_process::init())
         .plugin(tauri_plugin_updater::Builder::new().build())
-        .plugin(tauri_plugin_log::Builder::new().build())
+        .plugin(
+            tauri_plugin_log::Builder::new()
+                .targets([
+                    Target::new(TargetKind::Stdout),
+                    Target::new(TargetKind::LogDir { file_name: Some("app.log".to_string()) }),
+                    Target::new(TargetKind::Webview),
+                ])
+                .timezone_strategy(TimezoneStrategy::UseLocal)
+                .level(log::LevelFilter::Info)
+                .build(),
+        )
         .plugin(tauri_plugin_store::Builder::new().build())
         .plugin(tauri_plugin_os::init())
         .setup(|app| {
             // 初始化应用状态
             app.manage(commands::AppState::default());
+
+            // 获取并打印日志目录路径
+            if let Ok(log_dir) = app.path().app_log_dir() {
+                println!("Log directory: {:?}", log_dir);
+                log::info!("Log directory: {:?}", log_dir);
+            }
+
+            // 记录应用启动日志
+            log::info!("Claude AI Installer started");
+            log::info!("Platform: {}", std::env::consts::OS);
+            log::info!("Arch: {}", std::env::consts::ARCH);
+
             Ok(())
         })
         .invoke_handler(tauri::generate_handler![

+ 0 - 1
src/api/index.ts

@@ -2,7 +2,6 @@
 // 使用 Tauri API 替代 Electron API
 
 import { tauriAPI } from './tauri'
-import type { ElectronAPI } from '@shared/types'
 
 // 将 Tauri API 作为默认 API 导出
 // 这样现有代码只需要修改导入路径即可

+ 3 - 2
tsconfig.json

@@ -17,7 +17,6 @@
     "noFallthroughCasesInSwitch": true,
     "paths": {
       "@/*": ["./src/*"],
-      "@electron/*": ["./electron/*"],
       "@shared/*": ["./shared/*"]
     },
     "types": ["node", "vite/client"]
@@ -26,8 +25,10 @@
     "src/**/*.ts",
     "src/**/*.tsx",
     "src/**/*.vue",
-    "electron/**/*.ts",
     "shared/**/*.ts"
   ],
+  "exclude": [
+    "electron/**/*"
+  ],
   "references": [{ "path": "./tsconfig.node.json" }]
 }