黄中银 2 недель назад
Родитель
Сommit
eef3dbec31

+ 2 - 8
release-win.bat

@@ -43,9 +43,6 @@ if "%~1"=="" goto :run_release
 if /i "%~1"=="patch" set "NODE_ARGS=patch" & shift & goto :parse_args
 if /i "%~1"=="minor" set "NODE_ARGS=minor" & shift & goto :parse_args
 if /i "%~1"=="major" set "NODE_ARGS=major" & shift & goto :parse_args
-if /i "%~1"=="--tag" set "NODE_ARGS=%NODE_ARGS% --tag" & shift & goto :parse_args
-if /i "%~1"=="-t" set "NODE_ARGS=%NODE_ARGS% --tag" & shift & goto :parse_args
-if /i "%~1"=="--push" set "NODE_ARGS=%NODE_ARGS% --push" & shift & goto :parse_args
 if /i "%~1"=="--dry-run" set "NODE_ARGS=%NODE_ARGS% --dry-run" & shift & goto :parse_args
 if /i "%~1"=="--platform" set "NODE_ARGS=%NODE_ARGS% --platform %~2" & shift & shift & goto :parse_args
 if /i "%~1"=="-p" set "NODE_ARGS=%NODE_ARGS% --platform %~2" & shift & shift & goto :parse_args
@@ -87,8 +84,6 @@ echo     minor              次版本 +1 (如: 0.0.1 -^> 0.1.0)
 echo     major              主版本 +1 (如: 0.0.1 -^> 1.0.0)
 echo.
 echo   参数:
-echo     --tag, -t          创建 Git tag
-echo     --push             推送 tag 到远程仓库 (需要先指定 --tag)
 echo     --dry-run          仅显示操作,不实际执行
 echo     --platform, -p     指定平台 (逗号分隔): win,mac,linux
 echo     --help, -h         显示此帮助信息
@@ -96,13 +91,12 @@ echo.
 echo   示例:
 echo     release-win.bat                    更新 patch 版本并构建
 echo     release-win.bat minor              更新 minor 版本并构建
-echo     release-win.bat patch --tag        更新版本、构建并创建 tag
-echo     release-win.bat --tag --push       更新版本、构建、创建 tag 并推送
 echo     release-win.bat --dry-run          预览发布操作
 echo.
 echo   输出:
 echo     release\Claude-AI-Installer-x.x.x-win-x64-setup.exe     (NSIS 安装程序)
-echo     release\Claude-AI-Installer-x.x.x-win-x64.exe           (便携版)
+echo     release\Claude-AI-Installer-x.x.x-win-x64.msi           (MSI 安装程序)
+echo     release\Claude-AI-Installer-x.x.x-win-x64-portable.exe  (便携版,无需安装)
 echo.
 echo   注意: 此脚本调用 scripts/release.cjs 执行实际发布
 echo         Windows 上默认只构建 win 平台

+ 55 - 0
scripts/build.js

@@ -151,6 +151,56 @@ function buildTauri(options) {
   log.success('Tauri 构建完成');
 }
 
+// 重命名构建产物,使用与 Electron 版本一致的命名格式
+function renameArtifacts(options) {
+  log.step('重命名构建产物...');
+
+  const mode = options.debug ? 'debug' : 'release';
+  const targetDir = path.join(TAURI_DIR, 'target', mode);
+  const bundleDir = path.join(targetDir, 'bundle');
+  const version = getVersion();
+
+  // 重命名 NSIS 安装程序
+  const nsisDir = path.join(bundleDir, 'nsis');
+  if (fs.existsSync(nsisDir)) {
+    const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe'));
+    for (const file of files) {
+      const oldPath = path.join(nsisDir, file);
+      // 新文件名格式: Claude-AI-Installer-{version}-win-x64-setup.exe
+      const newName = `Claude-AI-Installer-${version}-win-x64-setup.exe`;
+      const newPath = path.join(nsisDir, newName);
+      if (file !== newName) {
+        // 如果目标文件已存在,先删除
+        if (fs.existsSync(newPath)) {
+          fs.unlinkSync(newPath);
+        }
+        fs.renameSync(oldPath, newPath);
+        log.success(`重命名: ${file} -> ${newName}`);
+      }
+    }
+  }
+
+  // 重命名 MSI 安装程序
+  const msiDir = path.join(bundleDir, 'msi');
+  if (fs.existsSync(msiDir)) {
+    const files = fs.readdirSync(msiDir).filter(f => f.endsWith('.msi'));
+    for (const file of files) {
+      const oldPath = path.join(msiDir, file);
+      // 新文件名格式: Claude-AI-Installer-{version}-win-x64.msi
+      const newName = `Claude-AI-Installer-${version}-win-x64.msi`;
+      const newPath = path.join(msiDir, newName);
+      if (file !== newName) {
+        // 如果目标文件已存在,先删除
+        if (fs.existsSync(newPath)) {
+          fs.unlinkSync(newPath);
+        }
+        fs.renameSync(oldPath, newPath);
+        log.success(`重命名: ${file} -> ${newName}`);
+      }
+    }
+  }
+}
+
 // 显示构建结果
 function showResults(options) {
   log.step('构建结果:');
@@ -228,6 +278,11 @@ ${colors.bright}╔════════════════════
     // 构建 Tauri 应用
     buildTauri(options);
 
+    // 重命名构建产物(仅发布模式)
+    if (!options.debug) {
+      renameArtifacts(options);
+    }
+
     // 显示结果
     showResults(options);
 

+ 100 - 81
scripts/release.cjs

@@ -57,8 +57,6 @@ function parseArgs() {
   const defaultPlatforms = [currentPlatform];
   const options = {
     version: null,
-    tag: false,
-    push: false,
     platforms: defaultPlatforms,
     dryRun: false,
     help: false,
@@ -67,13 +65,6 @@ function parseArgs() {
   for (let i = 0; i < args.length; i++) {
     const arg = args[i];
     switch (arg) {
-      case '--tag':
-      case '-t':
-        options.tag = true;
-        break;
-      case '--push':
-        options.push = true;
-        break;
       case '--platform':
       case '-p':
         options.platforms = args[++i].split(',');
@@ -101,7 +92,7 @@ function showHelp() {
 ${colors.bright}Claude AI Installer 发布脚本${colors.reset}
 
 ${colors.cyan}用法:${colors.reset}
-  node scripts/release.js [version] [options]
+  node scripts/release.cjs [version] [options]
 
 ${colors.cyan}版本参数:${colors.reset}
   patch              补丁版本 (0.0.1 -> 0.0.2)
@@ -110,17 +101,15 @@ ${colors.cyan}版本参数:${colors.reset}
   x.y.z              指定版本号
 
 ${colors.cyan}选项:${colors.reset}
-  --tag, -t          创建 Git tag
-  --push             推送 tag 到远程仓库
   --platform, -p     指定平台 (逗号分隔): win,mac,linux
   --dry-run          仅显示将要执行的操作,不实际执行
   --help, -h         显示此帮助信息
 
 ${colors.cyan}示例:${colors.reset}
-  node scripts/release.js patch                    # 更新补丁版本并构建
-  node scripts/release.js 1.0.0 --tag              # 设置版本为 1.0.0 并创建 tag
-  node scripts/release.js minor -p win,mac         # 更新次版本,仅构建 Win 和 Mac
-  node scripts/release.js patch --tag --push       # 更新版本、创建 tag 并推送
+  node scripts/release.cjs patch                   # 更新补丁版本并构建
+  node scripts/release.cjs 1.0.0                   # 设置版本为 1.0.0 并构建
+  node scripts/release.cjs minor -p win,mac        # 更新次版本,仅构建 Win 和 Mac
+  node scripts/release.cjs --dry-run               # 预览发布操作
 `);
 }
 
@@ -177,44 +166,38 @@ function exec(command, options = {}) {
   }
 }
 
-// 检查 Git 状态
-function checkGitStatus() {
-  log.step('检查 Git 状态...');
-
-  try {
-    const status = execSync('git status --porcelain', {
-      cwd: projectRoot,
-      encoding: 'utf-8',
-    });
-
-    if (status.trim()) {
-      log.warn('工作目录有未提交的更改:');
-      console.log(status);
-      return false;
-    }
-
-    log.success('Git 工作目录干净');
-    return true;
-  } catch (error) {
-    log.warn('无法检查 Git 状态');
-    return true;
-  }
-}
-
 // 更新版本号
 function updateVersion(newVersion, dryRun) {
   log.step(`更新版本号到 ${newVersion}...`);
 
   if (dryRun) {
-    log.info('[DRY RUN] 将更新 package.json 版本号');
+    log.info('[DRY RUN] 将更新 package.json 和 tauri.conf.json 版本号');
     return;
   }
 
+  // 更新 package.json
   const pkg = readPackageJson();
   pkg.version = newVersion;
   writePackageJson(pkg);
+  log.success(`package.json 版本号已更新: ${newVersion}`);
+
+  // 更新 tauri.conf.json
+  const tauriConfPath = path.join(projectRoot, 'src-tauri', 'tauri.conf.json');
+  if (fs.existsSync(tauriConfPath)) {
+    const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf-8'));
+    tauriConf.version = newVersion;
+    fs.writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, 2) + '\n');
+    log.success(`tauri.conf.json 版本号已更新: ${newVersion}`);
+  }
 
-  log.success(`版本号已更新: ${newVersion}`);
+  // 更新 Cargo.toml
+  const cargoTomlPath = path.join(projectRoot, 'src-tauri', 'Cargo.toml');
+  if (fs.existsSync(cargoTomlPath)) {
+    let cargoToml = fs.readFileSync(cargoTomlPath, 'utf-8');
+    cargoToml = cargoToml.replace(/^version = ".*"$/m, `version = "${newVersion}"`);
+    fs.writeFileSync(cargoTomlPath, cargoToml);
+    log.success(`Cargo.toml 版本号已更新: ${newVersion}`);
+  }
 }
 
 // 构建所有平台
@@ -229,44 +212,93 @@ function buildAllPlatforms(platforms, dryRun) {
       continue;
     }
 
-    exec(`node scripts/build.js -p ${platform}`);
+    // Tauri 构建(build.js 不支持 -p 参数,直接调用)
+    exec(`node scripts/build.js`);
+  }
+
+  // 复制构建产物到 release 目录
+  if (!dryRun) {
+    copyArtifactsToRelease();
   }
 
   log.success('所有平台构建完成');
 }
 
-// 创建 Git tag
-function createGitTag(version, dryRun) {
-  log.step(`创建 Git tag v${version}...`);
+// 复制 Tauri 构建产物到 release 目录
+function copyArtifactsToRelease() {
+  log.step('复制构建产物到 release 目录...');
 
-  if (dryRun) {
-    log.info(`[DRY RUN] 将创建 tag: v${version}`);
-    return;
+  const releaseDir = path.join(projectRoot, 'release');
+  const tauriDir = path.join(projectRoot, 'src-tauri');
+  const bundleDir = path.join(tauriDir, 'target', 'release', 'bundle');
+
+  // 确保 release 目录存在
+  if (!fs.existsSync(releaseDir)) {
+    fs.mkdirSync(releaseDir, { recursive: true });
+  }
+
+  // 复制 NSIS 安装程序
+  const nsisDir = path.join(bundleDir, 'nsis');
+  if (fs.existsSync(nsisDir)) {
+    const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe'));
+    for (const file of files) {
+      const src = path.join(nsisDir, file);
+      const dest = path.join(releaseDir, file);
+      fs.copyFileSync(src, dest);
+      log.success(`复制: ${file}`);
+    }
   }
 
-  // 提交版本更新
-  exec('git add package.json');
-  exec(`git commit -m "chore: release v${version}"`);
+  // 复制 MSI 安装程序
+  const msiDir = path.join(bundleDir, 'msi');
+  if (fs.existsSync(msiDir)) {
+    const files = fs.readdirSync(msiDir).filter(f => f.endsWith('.msi'));
+    for (const file of files) {
+      const src = path.join(msiDir, file);
+      const dest = path.join(releaseDir, file);
+      fs.copyFileSync(src, dest);
+      log.success(`复制: ${file}`);
+    }
+  }
 
-  // 创建 tag
-  exec(`git tag -a v${version} -m "Release v${version}"`);
+  // 复制更新签名文件(如果存在)
+  if (fs.existsSync(nsisDir)) {
+    const sigFiles = fs.readdirSync(nsisDir).filter(f => f.endsWith('.sig'));
+    for (const file of sigFiles) {
+      const src = path.join(nsisDir, file);
+      const dest = path.join(releaseDir, file);
+      fs.copyFileSync(src, dest);
+      log.success(`复制: ${file}`);
+    }
+  }
 
-  log.success(`Git tag v${version} 已创建`);
+  // 复制 Portable exe(独立可执行文件)
+  const tauriReleaseDir = path.join(tauriDir, 'target', 'release');
+  const pkg = readPackageJson();
+  const version = pkg.version;
+  const portableExeName = 'Claude AI Installer.exe';
+  const portableExePath = path.join(tauriReleaseDir, portableExeName);
+  if (fs.existsSync(portableExePath)) {
+    const destName = `Claude-AI-Installer-${version}-win-x64-portable.exe`;
+    const dest = path.join(releaseDir, destName);
+    fs.copyFileSync(portableExePath, dest);
+    log.success(`复制: ${destName}`);
+  }
 }
 
-// 推送到远程
-function pushToRemote(version, dryRun) {
-  log.step('推送到远程仓库...');
+// 提交版本更新
+function commitVersionUpdate(version) {
+  log.step(`提交版本更新 v${version}...`);
 
-  if (dryRun) {
-    log.info('[DRY RUN] 将推送 commits 和 tags');
-    return;
-  }
+  // 添加所有版本相关文件
+  exec('git add package.json', { ignoreError: true });
+  exec('git add src-tauri/tauri.conf.json', { ignoreError: true });
+  exec('git add src-tauri/Cargo.toml', { ignoreError: true });
 
-  exec('git push');
-  exec('git push --tags');
+  // 提交
+  exec(`git commit -m "本地发布版本: ${version}"`, { ignoreError: true });
 
-  log.success('已推送到远程仓库');
+  log.success(`版本更新已提交: v${version}`);
 }
 
 // 生成发布说明
@@ -413,14 +445,6 @@ ${colors.bright}${colors.magenta}╔══════════════
     const newVersion = calculateNewVersion(currentVersion, options.version);
     log.info(`目标版本: ${newVersion}`);
 
-    // 检查 Git 状态
-    if (options.tag && !options.dryRun) {
-      const isClean = checkGitStatus();
-      if (!isClean) {
-        log.warn('建议先提交或暂存更改');
-      }
-    }
-
     // 更新版本号
     if (options.version) {
       updateVersion(newVersion, options.dryRun);
@@ -434,14 +458,9 @@ ${colors.bright}${colors.magenta}╔══════════════
       generateReleaseNotes(newVersion);
     }
 
-    // 创建 Git tag
-    if (options.tag) {
-      createGitTag(newVersion, options.dryRun);
-    }
-
-    // 推送到远程
-    if (options.push) {
-      pushToRemote(newVersion, options.dryRun);
+    // 提交版本更新
+    if (options.version && !options.dryRun) {
+      commitVersionUpdate(newVersion);
     }
 
     // 显示摘要

+ 2 - 0
shared/types.ts

@@ -192,6 +192,8 @@ export interface ElectronAPI {
   windowMaximize: () => Promise<boolean>
   windowClose: () => Promise<void>
   windowIsMaximized: () => Promise<boolean>
+  saveWindowState: () => Promise<void>
+  restoreWindowState: () => Promise<void>
 
   // Claude Code
   checkClaudeCode: () => Promise<{ installed: boolean; version: string | null }>

+ 3 - 0
src-tauri/capabilities/default.json

@@ -15,6 +15,9 @@
     "core:window:allow-set-title",
     "core:window:allow-show",
     "core:window:allow-hide",
+    "core:window:allow-inner-size",
+    "core:window:allow-outer-position",
+    "core:window:allow-is-maximized",
     "dialog:default",
     "dialog:allow-open",
     "dialog:allow-save",

+ 57 - 55
src-tauri/src/commands/install.rs

@@ -197,12 +197,13 @@ pub async fn install_software(
     }
 
     // 发送安装状态事件
-    let emit_status = |message: &str, progress: f64, i18n_key: Option<&str>| {
+    let emit_status = |message: &str, progress: f64, i18n_key: Option<&str>, skip_log: bool| {
         let _ = app.emit("install-status", serde_json::json!({
             "software": software,
             "message": message,
             "progress": progress,
             "i18nKey": i18n_key,
+            "skipLog": skip_log,
         }));
     };
 
@@ -304,10 +305,10 @@ async fn install_nodejs<F, C>(
     is_cancelled: C,
 ) -> Result<InstallResult, String>
 where
-    F: Fn(&str, f64, Option<&str>),
+    F: Fn(&str, f64, Option<&str>, bool),
     C: Fn() -> bool,
 {
-    emit_status("Preparing to install Node.js...", 0.0, Some("install.preparing"));
+    emit_status("Preparing to install Node.js...", 0.0, Some("install.preparing"), false);
 
     if is_cancelled() {
         return Ok(InstallResult {
@@ -321,7 +322,7 @@ where
 
     #[cfg(target_os = "windows")]
     {
-        emit_status("Downloading Node.js...", 10.0, Some("install.downloading"));
+        emit_status("Downloading Node.js...", 10.0, Some("install.downloading"), false);
 
         // 构建下载 URL
         let download_url = format!(
@@ -342,13 +343,13 @@ where
             let downloaded_mb = downloaded as f64 / 1024.0 / 1024.0;
             let total_mb = total as f64 / 1024.0 / 1024.0;
             let current_percent = percent as i32;
-            // 每 5% 记录一次日志,但每次都更新进度条
-            let should_log = current_percent - last_logged_percent >= 5;
+            // 每 10% 记录一次日志,但每次都更新进度条
+            let should_log = current_percent - last_logged_percent >= 10;
             if should_log {
-                last_logged_percent = (current_percent / 5) * 5;
+                last_logged_percent = (current_percent / 10) * 10;
             }
-            let message = format!("Downloading Node.js... ({:.1}MB / {:.1}MB)", downloaded_mb, total_mb);
-            emit_status(&message, progress, Some("install.downloading.progress"));
+            let message = format!("下载中... {:.0}% ({:.1}MB / {:.1}MB)", percent, downloaded_mb, total_mb);
+            emit_status(&message, progress, Some("install.downloading"), !should_log);
         }).await?;
 
         if is_cancelled() {
@@ -360,7 +361,7 @@ where
             });
         }
 
-        emit_status("Installing Node.js...", 55.0, Some("install.installing"));
+        emit_status("安装中...", 55.0, Some("install.installing"), false);
 
         // 执行安装
         let mut args = vec!["/i", msi_path.to_str().unwrap(), "/qn"];
@@ -378,7 +379,7 @@ where
         let _ = std::fs::remove_file(&msi_path);
 
         if output.status.success() {
-            emit_status("Node.js installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "Node.js installed successfully".to_string(),
@@ -391,7 +392,7 @@ where
 
     #[cfg(target_os = "macos")]
     {
-        emit_status("Installing Node.js via Homebrew...", 10.0, Some("install.installing"));
+        emit_status("Installing Node.js via Homebrew...", 10.0, Some("install.installing"), false);
 
         // 使用 shell 执行以获取最新的 PATH(包括 Homebrew 路径)
         let major_version = version.trim_start_matches('v').split('.').next().unwrap_or("22");
@@ -400,7 +401,7 @@ where
             .map_err(|e| e.to_string())?;
 
         if output.status.success() {
-            emit_status("Node.js installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "Node.js installed successfully".to_string(),
@@ -413,7 +414,7 @@ where
 
     #[cfg(target_os = "linux")]
     {
-        emit_status("Installing Node.js...", 10.0, Some("install.installing"));
+        emit_status("Installing Node.js...", 10.0, Some("install.installing"), false);
 
         // 使用 NodeSource 安装
         let major_version = version.trim_start_matches('v').split('.').next().unwrap_or("22");
@@ -431,7 +432,7 @@ where
             .map_err(|e| e.to_string())?;
 
         if install_output.status.success() {
-            emit_status("Node.js installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "Node.js installed successfully".to_string(),
@@ -451,10 +452,10 @@ async fn install_pnpm<F, C>(
     is_cancelled: C,
 ) -> Result<InstallResult, String>
 where
-    F: Fn(&str, f64, Option<&str>),
+    F: Fn(&str, f64, Option<&str>, bool),
     C: Fn() -> bool,
 {
-    emit_status("Installing pnpm...", 10.0, Some("install.installing"));
+    emit_status("安装中...", 10.0, Some("install.installing"), false);
 
     if is_cancelled() {
         return Ok(InstallResult {
@@ -469,7 +470,7 @@ where
         .map_err(|e| e.to_string())?;
 
     if output.status.success() {
-        emit_status("pnpm installed successfully", 100.0, Some("install.success"));
+        emit_status("安装成功", 100.0, Some("install.success"), false);
         Ok(InstallResult {
             success: true,
             message: "pnpm installed successfully".to_string(),
@@ -489,10 +490,10 @@ async fn install_vscode<F, C>(
     is_cancelled: C,
 ) -> Result<InstallResult, String>
 where
-    F: Fn(&str, f64, Option<&str>),
+    F: Fn(&str, f64, Option<&str>, bool),
     C: Fn() -> bool,
 {
-    emit_status("Preparing to install VS Code...", 0.0, Some("install.preparing"));
+    emit_status("正在准备安装...", 0.0, Some("install.preparing"), false);
 
     if is_cancelled() {
         return Ok(InstallResult {
@@ -504,7 +505,7 @@ where
 
     #[cfg(target_os = "windows")]
     {
-        emit_status("Downloading VS Code...", 10.0, Some("install.downloading"));
+        emit_status("正在下载...", 10.0, Some("install.downloading"), false);
 
         let download_url = "https://code.visualstudio.com/sha/download?build=stable&os=win32-x64";
 
@@ -520,16 +521,16 @@ where
             let downloaded_mb = downloaded as f64 / 1024.0 / 1024.0;
             let total_mb = total as f64 / 1024.0 / 1024.0;
             let current_percent = percent as i32;
-            // 每 5% 记录一次日志
-            let should_log = current_percent - last_logged_percent >= 5;
+            // 每 10% 记录一次日志
+            let should_log = current_percent - last_logged_percent >= 10;
             if should_log {
-                last_logged_percent = (current_percent / 5) * 5;
+                last_logged_percent = (current_percent / 10) * 10;
             }
-            let message = format!("Downloading VS Code... ({:.1}MB / {:.1}MB)", downloaded_mb, total_mb);
-            emit_status(&message, progress, Some("install.downloading.progress"));
+            let message = format!("下载中... {:.0}% ({:.1}MB / {:.1}MB)", percent, downloaded_mb, total_mb);
+            emit_status(&message, progress, Some("install.downloading"), !should_log);
         }).await?;
 
-        emit_status("Installing VS Code...", 65.0, Some("install.installing"));
+        emit_status("安装中...", 65.0, Some("install.installing"), false);
 
         let mut args = vec!["/VERYSILENT", "/NORESTART", "/MERGETASKS=!runcode"];
 
@@ -544,7 +545,7 @@ where
         let _ = std::fs::remove_file(&exe_path);
 
         if output.status.success() {
-            emit_status("VS Code installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "VS Code installed successfully".to_string(),
@@ -557,14 +558,14 @@ where
 
     #[cfg(target_os = "macos")]
     {
-        emit_status("Installing VS Code via Homebrew...", 10.0, Some("install.installing"));
+        emit_status("Installing VS Code via Homebrew...", 10.0, Some("install.installing"), false);
 
         // 使用 shell 执行以获取最新的 PATH(包括 Homebrew 路径)
         let output = run_shell_hidden("brew install --cask visual-studio-code")
             .map_err(|e| e.to_string())?;
 
         if output.status.success() {
-            emit_status("VS Code installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "VS Code installed successfully".to_string(),
@@ -577,7 +578,7 @@ where
 
     #[cfg(target_os = "linux")]
     {
-        emit_status("Downloading VS Code...", 10.0, Some("install.downloading"));
+        emit_status("正在下载...", 10.0, Some("install.downloading"), false);
 
         // 下载并安装 .deb 包
         let download_url = "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64";
@@ -592,15 +593,16 @@ where
             let downloaded_mb = downloaded as f64 / 1024.0 / 1024.0;
             let total_mb = total as f64 / 1024.0 / 1024.0;
             let current_percent = percent as i32;
-            let should_log = current_percent - last_logged_percent >= 5;
+            // 每 10% 记录一次日志
+            let should_log = current_percent - last_logged_percent >= 10;
             if should_log {
-                last_logged_percent = (current_percent / 5) * 5;
+                last_logged_percent = (current_percent / 10) * 10;
             }
-            let message = format!("Downloading VS Code... ({:.1}MB / {:.1}MB)", downloaded_mb, total_mb);
-            emit_status(&message, progress, Some("install.downloading.progress"));
+            let message = format!("下载中... {:.0}% ({:.1}MB / {:.1}MB)", percent, downloaded_mb, total_mb);
+            emit_status(&message, progress, Some("install.downloading"), !should_log);
         }).await?;
 
-        emit_status("Installing VS Code...", 65.0, Some("install.installing"));
+        emit_status("安装中...", 65.0, Some("install.installing"), false);
 
         // 使用 shell 执行以获取最新的 PATH
         let cmd = format!("sudo dpkg -i {}", deb_path.to_str().unwrap());
@@ -610,7 +612,7 @@ where
         let _ = std::fs::remove_file(&deb_path);
 
         if output.status.success() {
-            emit_status("VS Code installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "VS Code installed successfully".to_string(),
@@ -631,10 +633,10 @@ async fn install_git<F, C>(
     is_cancelled: C,
 ) -> Result<InstallResult, String>
 where
-    F: Fn(&str, f64, Option<&str>),
+    F: Fn(&str, f64, Option<&str>, bool),
     C: Fn() -> bool,
 {
-    emit_status("Preparing to install Git...", 0.0, Some("install.preparing"));
+    emit_status("正在准备安装...", 0.0, Some("install.preparing"), false);
 
     if is_cancelled() {
         return Ok(InstallResult {
@@ -646,7 +648,7 @@ where
 
     #[cfg(target_os = "windows")]
     {
-        emit_status("Downloading Git...", 10.0, Some("install.downloading"));
+        emit_status("正在下载...", 10.0, Some("install.downloading"), false);
 
         // 获取最新版本
         let client = reqwest::Client::new();
@@ -685,16 +687,16 @@ where
             let downloaded_mb = downloaded as f64 / 1024.0 / 1024.0;
             let total_mb = total as f64 / 1024.0 / 1024.0;
             let current_percent = percent as i32;
-            // 每 5% 记录一次日志
-            let should_log = current_percent - last_logged_percent >= 5;
+            // 每 10% 记录一次日志
+            let should_log = current_percent - last_logged_percent >= 10;
             if should_log {
-                last_logged_percent = (current_percent / 5) * 5;
+                last_logged_percent = (current_percent / 10) * 10;
             }
-            let message = format!("Downloading Git... ({:.1}MB / {:.1}MB)", downloaded_mb, total_mb);
-            emit_status(&message, progress, Some("install.downloading.progress"));
+            let message = format!("下载中... {:.0}% ({:.1}MB / {:.1}MB)", percent, downloaded_mb, total_mb);
+            emit_status(&message, progress, Some("install.downloading"), !should_log);
         }).await?;
 
-        emit_status("Installing Git...", 65.0, Some("install.installing"));
+        emit_status("安装中...", 65.0, Some("install.installing"), false);
 
         let mut args = vec!["/VERYSILENT", "/NORESTART"];
 
@@ -709,7 +711,7 @@ where
         let _ = std::fs::remove_file(&exe_path);
 
         if output.status.success() {
-            emit_status("Git installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "Git installed successfully".to_string(),
@@ -722,14 +724,14 @@ where
 
     #[cfg(target_os = "macos")]
     {
-        emit_status("Installing Git via Homebrew...", 10.0, Some("install.installing"));
+        emit_status("Installing Git via Homebrew...", 10.0, Some("install.installing"), false);
 
         // 使用 shell 执行以获取最新的 PATH(包括 Homebrew 路径)
         let output = run_shell_hidden("brew install git")
             .map_err(|e| e.to_string())?;
 
         if output.status.success() {
-            emit_status("Git installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "Git installed successfully".to_string(),
@@ -742,14 +744,14 @@ where
 
     #[cfg(target_os = "linux")]
     {
-        emit_status("Installing Git...", 10.0, Some("install.installing"));
+        emit_status("Installing Git...", 10.0, Some("install.installing"), false);
 
         // 使用 shell 执行以获取最新的 PATH
         let output = run_shell_hidden("sudo apt-get install -y git")
             .map_err(|e| e.to_string())?;
 
         if output.status.success() {
-            emit_status("Git installed successfully", 100.0, Some("install.success"));
+            emit_status("安装成功", 100.0, Some("install.success"), false);
             Ok(InstallResult {
                 success: true,
                 message: "Git installed successfully".to_string(),
@@ -769,10 +771,10 @@ async fn install_claude_code_software<F, C>(
     is_cancelled: C,
 ) -> Result<InstallResult, String>
 where
-    F: Fn(&str, f64, Option<&str>),
+    F: Fn(&str, f64, Option<&str>, bool),
     C: Fn() -> bool,
 {
-    emit_status("Installing Claude Code...", 10.0, Some("install.installing"));
+    emit_status("安装中...", 10.0, Some("install.installing"), false);
 
     if is_cancelled() {
         return Ok(InstallResult {
@@ -787,7 +789,7 @@ where
         .map_err(|e| e.to_string())?;
 
     if output.status.success() {
-        emit_status("Claude Code installed successfully", 100.0, Some("install.success"));
+        emit_status("安装成功", 100.0, Some("install.success"), false);
         Ok(InstallResult {
             success: true,
             message: "Claude Code installed successfully".to_string(),
@@ -807,7 +809,7 @@ async fn install_all<F, C>(
     is_cancelled: C,
 ) -> Result<InstallResult, String>
 where
-    F: Fn(&str, f64, Option<&str>) + Copy,
+    F: Fn(&str, f64, Option<&str>, bool) + Copy,
     C: Fn() -> bool + Copy,
 {
     let mut results = Vec::new();

+ 5 - 5
src-tauri/tauri.conf.json

@@ -14,10 +14,10 @@
     "windows": [
       {
         "title": "Claude AI Installer",
-        "width": 1000,
-        "height": 700,
-        "minWidth": 800,
-        "minHeight": 600,
+        "width": 1200,
+        "height": 800,
+        "minWidth": 1000,
+        "minHeight": 800,
         "resizable": true,
         "fullscreen": false,
         "decorations": false,
@@ -31,7 +31,7 @@
   },
   "bundle": {
     "active": true,
-    "targets": "all",
+    "targets": ["msi", "nsis", "exe"],
     "icon": [
       "icons/32x32.png",
       "icons/128x128.png",

+ 16 - 0
src/App.vue

@@ -120,6 +120,12 @@ onMounted(async () => {
   bindInstallListeners()
   settingsStore.watchSystemTheme()
 
+  // 恢复窗口状态(位置和大小)
+  await window.electronAPI.restoreWindowState()
+
+  // 开始监听窗口状态变化,实时保存位置和大小
+  window.electronAPI.startWindowStateListener()
+
   // 先加载代理设置,确保版本获取时使用正确的代理配置
   await settingsStore.initSettings()
 
@@ -149,9 +155,19 @@ onMounted(async () => {
 
   // 设置窗口标题
   window.electronAPI.setWindowTitle(t('app.title'))
+
+  // 监听窗口关闭前事件,保存窗口状态
+  window.addEventListener('beforeunload', handleBeforeUnload)
 })
 
+// 窗口关闭前保存状态
+function handleBeforeUnload() {
+  // 立即保存窗口状态(取消防抖定时器并立即执行)
+  window.electronAPI.saveWindowStateImmediate()
+}
+
 onUnmounted(() => {
+  window.removeEventListener('beforeunload', handleBeforeUnload)
   window.electronAPI.removeAllListeners()
 })
 </script>

+ 112 - 0
src/api/tauri.ts

@@ -1,6 +1,26 @@
 // Tauri API 封装 - 替代原来的 Electron API
 import { invoke } from '@tauri-apps/api/core'
 import { listen, type UnlistenFn } from '@tauri-apps/api/event'
+import { getCurrentWindow, type PhysicalPosition, type PhysicalSize } from '@tauri-apps/api/window'
+import { Store } from '@tauri-apps/plugin-store'
+
+// 窗口状态存储
+let windowStore: Store | null = null
+
+interface WindowState {
+  width: number
+  height: number
+  x: number
+  y: number
+  maximized: boolean
+}
+
+async function getWindowStore(): Promise<Store> {
+  if (!windowStore) {
+    windowStore = await Store.load('window-state.json')
+  }
+  return windowStore
+}
 import type {
   SoftwareType,
   SoftwareTypeWithAll,
@@ -24,6 +44,10 @@ import type {
 // 存储事件监听器的取消函数
 const listeners: UnlistenFn[] = []
 
+// 窗口状态保存的防抖定时器
+let saveWindowStateTimer: ReturnType<typeof setTimeout> | null = null
+const SAVE_DEBOUNCE_MS = 500 // 500ms 防抖延迟
+
 export const tauriAPI = {
   // ==================== 安装相关 ====================
 
@@ -146,6 +170,94 @@ export const tauriAPI = {
     return await invoke('window_is_maximized')
   },
 
+  // 保存窗口状态(内部实现)
+  async _doSaveWindowState(): Promise<void> {
+    try {
+      const window = getCurrentWindow()
+      const size = await window.innerSize()
+      const position = await window.outerPosition()
+      const maximized = await window.isMaximized()
+
+      const state: WindowState = {
+        width: size.width,
+        height: size.height,
+        x: position.x,
+        y: position.y,
+        maximized,
+      }
+
+      const store = await getWindowStore()
+      await store.set('windowState', state)
+      await store.save()
+    } catch (error) {
+      console.error('Failed to save window state:', error)
+    }
+  },
+
+  // 保存窗口状态(带防抖)
+  saveWindowState(): void {
+    if (saveWindowStateTimer) {
+      clearTimeout(saveWindowStateTimer)
+    }
+    saveWindowStateTimer = setTimeout(() => {
+      this._doSaveWindowState()
+      saveWindowStateTimer = null
+    }, SAVE_DEBOUNCE_MS)
+  },
+
+  // 立即保存窗口状态(用于关闭前)
+  async saveWindowStateImmediate(): Promise<void> {
+    if (saveWindowStateTimer) {
+      clearTimeout(saveWindowStateTimer)
+      saveWindowStateTimer = null
+    }
+    await this._doSaveWindowState()
+  },
+
+  // 开始监听窗口状态变化(resize 和 move)
+  startWindowStateListener(): void {
+    const window = getCurrentWindow()
+
+    // 监听窗口大小变化
+    window.onResized(() => {
+      this.saveWindowState()
+    }).then((unlisten) => {
+      listeners.push(unlisten)
+    })
+
+    // 监听窗口位置变化
+    window.onMoved(() => {
+      this.saveWindowState()
+    }).then((unlisten) => {
+      listeners.push(unlisten)
+    })
+  },
+
+  // 恢复窗口状态
+  async restoreWindowState(): Promise<void> {
+    try {
+      const store = await getWindowStore()
+      const state = await store.get<WindowState>('windowState')
+
+      if (state) {
+        const window = getCurrentWindow()
+
+        // 先设置位置和大小
+        if (!state.maximized) {
+          await window.setPosition(new PhysicalPosition(state.x, state.y))
+          await window.setSize(new PhysicalSize(state.width, state.height))
+        }
+
+        // 如果之前是最大化状态,则最大化
+        if (state.maximized) {
+          await window.maximize()
+        }
+      }
+    } catch (error) {
+      console.error('Failed to restore window state:', error)
+    }
+  },
+
   // ==================== Claude Code ====================
 
   async checkClaudeCode(): Promise<{ installed: boolean; version: string | null }> {