Browse Source

添加卸载功能

黄中银 2 weeks ago
parent
commit
fea525bd7a

+ 12 - 0
src-tauri/src/commands/install.rs

@@ -343,6 +343,12 @@ pub async fn uninstall_software(software: String) -> Result<bool, String> {
                     .map_err(|e| e.to_string())?;
                 Ok(output.status.success())
             }
+            "claudeCode" => {
+                // 使用 npm 卸载 Claude Code
+                let output = run_shell_hidden("npm uninstall -g @anthropic-ai/claude-code")
+                    .map_err(|e| e.to_string())?;
+                Ok(output.status.success())
+            }
             _ => Err(format!("Uninstall not supported for: {}", software)),
         }
     }
@@ -356,6 +362,12 @@ pub async fn uninstall_software(software: String) -> Result<bool, String> {
                     .map_err(|e| e.to_string())?;
                 Ok(output.status.success())
             }
+            "claudeCode" => {
+                // 使用 npm 卸载 Claude Code
+                let output = run_shell_hidden("npm uninstall -g @anthropic-ai/claude-code")
+                    .map_err(|e| e.to_string())?;
+                Ok(output.status.success())
+            }
             _ => Err(format!("Uninstall not supported for: {}", software)),
         }
     }

+ 41 - 0
src-tauri/src/commands/vscode.rs

@@ -89,3 +89,44 @@ pub fn install_vscode_extension_internal(extension_id: &str) -> CommandResult {
 pub async fn install_vscode_extension(extension_id: String) -> CommandResult {
     install_vscode_extension_internal(&extension_id)
 }
+
+/// 卸载 VS Code 扩展的内部实现(可复用)
+pub fn uninstall_vscode_extension_internal(extension_id: &str) -> CommandResult {
+    // 使用 shell 执行以获取最新的 PATH 环境变量
+    let cmd = format!("code --uninstall-extension {}", extension_id);
+    let output = run_shell_hidden(&cmd);
+
+    match output {
+        Ok(out) if out.status.success() => CommandResult {
+            success: true,
+            error: None,
+        },
+        Ok(out) => {
+            let stderr = String::from_utf8_lossy(&out.stderr);
+            let stdout = String::from_utf8_lossy(&out.stdout);
+
+            // 有时候即使返回非零状态码,扩展也可能已卸载成功
+            if stdout.contains("successfully uninstalled") || stdout.contains("was successfully uninstalled") {
+                CommandResult {
+                    success: true,
+                    error: None,
+                }
+            } else {
+                CommandResult {
+                    success: false,
+                    error: Some(if stderr.is_empty() { stdout.to_string() } else { stderr.to_string() }),
+                }
+            }
+        }
+        Err(e) => CommandResult {
+            success: false,
+            error: Some(e.to_string()),
+        },
+    }
+}
+
+/// 卸载 VS Code 扩展
+#[tauri::command]
+pub async fn uninstall_vscode_extension(extension_id: String) -> CommandResult {
+    uninstall_vscode_extension_internal(&extension_id)
+}

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

@@ -65,6 +65,7 @@ pub fn run() {
             // VS Code 扩展
             commands::vscode::check_vscode_extension,
             commands::vscode::install_vscode_extension,
+            commands::vscode::uninstall_vscode_extension,
             // 配置
             commands::config::get_git_mirror_config,
             commands::config::set_git_mirror,

+ 4 - 0
src/api/tauri.ts

@@ -282,6 +282,10 @@ export const tauriAPI = {
     return await invoke('install_vscode_extension', { extensionId })
   },
 
+  async uninstallVscodeExtension(extensionId: string): Promise<{ success: boolean; error?: string }> {
+    return await invoke('uninstall_vscode_extension', { extensionId })
+  },
+
   // ==================== 事件监听 ====================
   // 注意:这些方法是同步的,以保持与原 Electron API 的兼容性
   // 内部使用 Promise 但不返回它

+ 9 - 0
src/i18n/en-US.ts

@@ -71,6 +71,10 @@ export default {
       claudeCodeExtRequiresCli: 'Requires Claude Code to be installed first (install in Claude Code tab)',
       claudeCodeExtRequiresVscodeAndCli: 'Requires VS Code and Claude Code',
       installClaudeCodeExt: 'Install Extension',
+      uninstallClaudeCodeExt: 'Uninstall Extension',
+      uninstallingClaudeCodeExt: 'Uninstalling...',
+      claudeCodeExtUninstallSuccess: 'Claude Code for VS Code extension has been uninstalled',
+      claudeCodeExtUninstallFailed: 'Claude Code for VS Code extension uninstall failed',
       installVscodeFirst: 'Please install VS Code first',
       installingClaudeCodeExt: 'Installing Claude Code for VS Code extension...',
       claudeCodeExtInstallSuccess: 'Claude Code for VS Code extension installed successfully',
@@ -100,6 +104,11 @@ export default {
     install: 'Install Claude Code',
     installing: 'Installing...',
     installFailed: 'Installation failed',
+    uninstall: 'Uninstall Claude Code',
+    uninstalling: 'Uninstalling...',
+    uninstallSuccess: 'Claude Code has been uninstalled',
+    uninstallFailed: 'Uninstall failed',
+    uninstallConfirm: 'Are you sure you want to uninstall Claude Code?',
     nodejsRequired: 'Node.js installation required',
     nodejsRequiredDesc: 'Please install Node.js first in the Node.js tab before installing Claude Code',
     gitRequired: 'Git installation required',

+ 9 - 0
src/i18n/zh-CN.ts

@@ -71,6 +71,10 @@ export default {
       claudeCodeExtRequiresCli: '需要先安装 Claude Code(在 Claude Code 标签页安装)',
       claudeCodeExtRequiresVscodeAndCli: '需要 VS Code 和 Claude Code',
       installClaudeCodeExt: '安装插件',
+      uninstallClaudeCodeExt: '卸载插件',
+      uninstallingClaudeCodeExt: '正在卸载...',
+      claudeCodeExtUninstallSuccess: 'Claude Code for VS Code 插件已卸载',
+      claudeCodeExtUninstallFailed: 'Claude Code for VS Code 插件卸载失败',
       installVscodeFirst: '请先安装 VS Code',
       installingClaudeCodeExt: '正在安装 Claude Code for VS Code 插件...',
       claudeCodeExtInstallSuccess: 'Claude Code for VS Code 插件安装成功',
@@ -100,6 +104,11 @@ export default {
     install: '安装 Claude Code',
     installing: '正在安装...',
     installFailed: '安装失败',
+    uninstall: '卸载 Claude Code',
+    uninstalling: '正在卸载...',
+    uninstallSuccess: 'Claude Code 已卸载',
+    uninstallFailed: '卸载失败',
+    uninstallConfirm: '确定要卸载 Claude Code 吗?',
     nodejsRequired: '需要先安装 Node.js',
     nodejsRequiredDesc: '请先在 Node.js 标签页安装 Node.js,安装完成后才能安装 Claude Code',
     gitRequired: '需要先安装 Git',

+ 59 - 10
src/views/ClaudeCodeView.vue

@@ -9,9 +9,12 @@ const installStore = useInstallStore()
 
 const isLaunching = ref(false)
 const isInstalling = ref(false)
+const isUninstalling = ref(false)
 const launchError = ref('')
 const launchSuccess = ref(false)
 const installError = ref('')
+const uninstallError = ref('')
+const uninstallSuccess = ref(false)
 const isChecking = ref(false)
 
 const nodejsInstalled = computed(() => installStore.isInstalled('nodejs'))
@@ -20,18 +23,24 @@ const claudeCodeInstalled = computed(() => installStore.isInstalled('claudeCode'
 const claudeCodeVersion = computed(() => installStore.getInstalledVersion('claudeCode'))
 
 // 启动按钮只有在 Claude Code 已安装时才可点击
-const canLaunch = computed(() => gitInstalled.value && claudeCodeInstalled.value && !isLaunching.value && !isChecking.value && !isInstalling.value)
+const canLaunch = computed(() => gitInstalled.value && claudeCodeInstalled.value && !isLaunching.value && !isChecking.value && !isInstalling.value && !isUninstalling.value)
 
 // 安装按钮只有在 Node.js 已安装且 Claude Code 未安装时才可点击
-const canInstall = computed(() => nodejsInstalled.value && !claudeCodeInstalled.value && !isInstalling.value && !isChecking.value)
+const canInstall = computed(() => nodejsInstalled.value && !claudeCodeInstalled.value && !isInstalling.value && !isChecking.value && !isUninstalling.value)
+
+// 卸载按钮只有在 Claude Code 已安装时才可点击
+const canUninstall = computed(() => claudeCodeInstalled.value && !isUninstalling.value && !isChecking.value && !isInstalling.value)
 
 const statusText = computed(() => {
   if (isChecking.value) return t('common.loading')
   if (isInstalling.value) return t('claudeCode.installing')
+  if (isUninstalling.value) return t('claudeCode.uninstalling')
   if (isLaunching.value) return t('claudeCode.launching')
   if (launchSuccess.value) return t('claudeCode.launchSuccess')
+  if (uninstallSuccess.value) return t('claudeCode.uninstallSuccess')
   if (launchError.value) return launchError.value
   if (installError.value) return installError.value
+  if (uninstallError.value) return uninstallError.value
   if (!nodejsInstalled.value) return t('claudeCode.nodejsRequired')
   if (!gitInstalled.value) return t('claudeCode.gitRequired')
   if (!claudeCodeInstalled.value) return t('claudeCode.notInstalled')
@@ -82,6 +91,26 @@ async function handleLaunch() {
     isLaunching.value = false
   }
 }
+
+async function handleUninstall() {
+  if (!canUninstall.value) return
+
+  isUninstalling.value = true
+  uninstallError.value = ''
+  uninstallSuccess.value = false
+  launchSuccess.value = false
+
+  try {
+    await window.electronAPI.uninstall('claudeCode')
+    uninstallSuccess.value = true
+    // 卸载成功后刷新状态
+    await refreshStatus()
+  } catch (error) {
+    uninstallError.value = (error as Error).message || t('claudeCode.uninstallFailed')
+  } finally {
+    isUninstalling.value = false
+  }
+}
 </script>
 
 <template>
@@ -94,12 +123,25 @@ async function handleLaunch() {
           <span>{{ t('software.claudeCode.name') }}</span>
         </h2>
         <p>{{ t('software.claudeCode.description') }}</p>
-        <el-tag v-if="claudeCodeInstalled && claudeCodeVersion" type="success" size="small">
-          {{ t('common.installed') }} v{{ claudeCodeVersion }}
-        </el-tag>
-        <el-tag v-else-if="claudeCodeInstalled" type="success" size="small">
-          {{ t('common.installed') }}
-        </el-tag>
+        <div class="install-status">
+          <el-tag v-if="claudeCodeInstalled && claudeCodeVersion" type="success" size="small">
+            {{ t('common.installed') }} v{{ claudeCodeVersion }}
+          </el-tag>
+          <el-tag v-else-if="claudeCodeInstalled" type="success" size="small">
+            {{ t('common.installed') }}
+          </el-tag>
+          <el-button
+            v-if="claudeCodeInstalled"
+            type="danger"
+            size="small"
+            plain
+            :disabled="!canUninstall"
+            :loading="isUninstalling"
+            @click="handleUninstall"
+          >
+            {{ isUninstalling ? t('claudeCode.uninstalling') : t('claudeCode.uninstall') }}
+          </el-button>
+        </div>
       </div>
 
       <div v-if="!nodejsInstalled" class="warning-card">
@@ -130,13 +172,13 @@ async function handleLaunch() {
         >
           {{ isInstalling ? t('claudeCode.installing') : t('claudeCode.install') }}
         </el-button>
-        <el-button :disabled="isChecking || isInstalling" @click="refreshStatus">
+        <el-button :disabled="isChecking || isInstalling || isUninstalling" @click="refreshStatus">
           {{ t('claudeCode.refresh') }}
         </el-button>
       </div>
 
       <div class="status-container">
-        <p :class="['status-text', { success: launchSuccess, error: !!launchError || !!installError }]">
+        <p :class="['status-text', { success: launchSuccess || uninstallSuccess, error: !!launchError || !!installError || !!uninstallError }]">
           {{ statusText }}
         </p>
       </div>
@@ -202,6 +244,13 @@ async function handleLaunch() {
       font-size: 0.9em;
       margin-top: var(--spacing-xs);
     }
+
+    .install-status {
+      display: flex;
+      align-items: center;
+      gap: var(--spacing-sm);
+      margin-top: var(--spacing-xs);
+    }
   }
 
   .info-card {

+ 44 - 4
src/views/VscodeView.vue

@@ -78,8 +78,9 @@ function handleCancel() {
   emit('cancel')
 }
 
-// Claude Code for VS Code 插件安装相关
+// Claude Code for VS Code 插件安装/卸载相关
 const claudeCodeExtInstalling = ref(false)
+const claudeCodeExtUninstalling = ref(false)
 const claudeCodeExtError = ref('')
 
 // 使用 store 中的共享数据
@@ -116,6 +117,33 @@ async function handleInstallClaudeCodeExt() {
   }
 }
 
+async function handleUninstallClaudeCodeExt() {
+  if (claudeCodeExtUninstalling.value) return
+
+  claudeCodeExtUninstalling.value = true
+  claudeCodeExtError.value = ''
+
+  // 记录开始卸载日志
+  installStore.addLog(t('software.vscode.uninstallingClaudeCodeExt'))
+
+  try {
+    const result = await window.electronAPI.uninstallVscodeExtension('anthropic.claude-code')
+    if (result.success) {
+      // 重新检查以更新状态
+      await installStore.checkClaudeCodeExtInstalled()
+      installStore.addLog(t('software.vscode.claudeCodeExtUninstallSuccess'))
+    } else {
+      claudeCodeExtError.value = result.error || t('common.error')
+      installStore.addLog(`${t('software.vscode.claudeCodeExtUninstallFailed')}: ${claudeCodeExtError.value}`, 'error')
+    }
+  } catch (error) {
+    claudeCodeExtError.value = (error as Error).message
+    installStore.addLog(`${t('software.vscode.claudeCodeExtUninstallFailed')}: ${claudeCodeExtError.value}`, 'error')
+  } finally {
+    claudeCodeExtUninstalling.value = false
+  }
+}
+
 // 初始化检查插件状态
 installStore.checkClaudeCodeExtInstalled()
 </script>
@@ -188,13 +216,25 @@ installStore.checkClaudeCodeExtInstalled()
         </div>
         <div v-else class="ext-actions">
           <el-button
+            v-if="!claudeCodeExtInstalled"
             size="small"
-            :type="claudeCodeExtInstalled ? 'success' : 'primary'"
-            :disabled="claudeCodeExtInstalled || claudeCodeExtInstalling || !isInstalled"
+            type="primary"
+            :disabled="claudeCodeExtInstalling || !isInstalled"
             :loading="claudeCodeExtInstalling"
             @click="handleInstallClaudeCodeExt"
           >
-            {{ claudeCodeExtInstalled ? t('common.installed') : claudeCodeExtInstalling ? t('common.installing') : t('software.vscode.installClaudeCodeExt') }}
+            {{ claudeCodeExtInstalling ? t('common.installing') : t('software.vscode.installClaudeCodeExt') }}
+          </el-button>
+          <el-button
+            v-if="claudeCodeExtInstalled"
+            size="small"
+            type="danger"
+            plain
+            :disabled="claudeCodeExtUninstalling"
+            :loading="claudeCodeExtUninstalling"
+            @click="handleUninstallClaudeCodeExt"
+          >
+            {{ claudeCodeExtUninstalling ? t('software.vscode.uninstallingClaudeCodeExt') : t('software.vscode.uninstallClaudeCodeExt') }}
           </el-button>
           <span v-if="!isInstalled" class="ext-hint">{{ t('software.vscode.installVscodeFirst') }}</span>
         </div>