diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 817356a..a5554e5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -22,6 +22,12 @@ pub struct EssentialsRepo { pub essentials: Vec, } +#[derive(Clone, Serialize, Deserialize)] +pub struct InstallTask { + pub id: String, + pub version: Option, +} + impl Default for AppSettings { fn default() -> Self { Self { @@ -31,7 +37,7 @@ impl Default for AppSettings { } struct AppState { - install_tx: mpsc::Sender, + install_tx: mpsc::Sender, } #[derive(Clone, Serialize)] @@ -159,8 +165,8 @@ async fn get_updates(app: AppHandle) -> Vec { } #[tauri::command] -async fn install_software(id: String, state: State<'_, AppState>) -> Result<(), String> { - state.install_tx.send(id).await.map_err(|e| e.to_string()) +async fn install_software(id: String, version: Option, state: State<'_, AppState>) -> Result<(), String> { + state.install_tx.send(InstallTask { id, version }).await.map_err(|e| e.to_string()) } #[tauri::command] @@ -180,14 +186,17 @@ pub fn run() { .plugin(tauri_plugin_opener::init()) .setup(move |app| { let handle = app.handle().clone(); - let (tx, mut rx) = mpsc::channel::(100); + let (tx, mut rx) = mpsc::channel::(100); app.manage(AppState { install_tx: tx }); tauri::async_runtime::spawn(async move { let perc_re = Regex::new(r"(\d+)\s*%").unwrap(); let size_re = Regex::new(r"([\d\.]+)\s*[a-zA-Z]+\s*/\s*([\d\.]+)\s*[a-zA-Z]+").unwrap(); - while let Some(id) = rx.recv().await { + while let Some(task) = rx.recv().await { + let id = task.id; + let version = task.version; + let log_id = format!("install-{}", id); let _ = handle.emit("install-status", InstallProgress { id: id.clone(), @@ -195,17 +204,34 @@ pub fn run() { progress: 0.0, }); - emit_log(&handle, &log_id, &format!("Winget Install: {}", id), "Starting...", "info"); + let display_cmd = match &version { + Some(v) => format!("Winget Install: {} (v{})", id, v), + None => format!("Winget Install: {}", id), + }; + emit_log(&handle, &log_id, &display_cmd, "Starting...", "info"); let id_for_cmd = id.clone(); let h = handle.clone(); + let mut args = vec![ + "install".to_string(), + "--id".to_string(), id_for_cmd.clone(), + "-e".to_string(), + "--silent".to_string(), + "--accept-package-agreements".to_string(), + "--accept-source-agreements".to_string(), + "--disable-interactivity".to_string(), + ]; + + if let Some(v) = version { + if !v.is_empty() { + args.push("--version".to_string()); + args.push(v); + } + } + let child = Command::new("winget") - .args([ - "install", "--id", &id_for_cmd, "-e", "--silent", - "--accept-package-agreements", "--accept-source-agreements", - "--disable-interactivity" - ]) + .args(&args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .creation_flags(0x08000000) diff --git a/src/components/SoftwareCard.vue b/src/components/SoftwareCard.vue index 807f2c4..d0d2e62 100644 --- a/src/components/SoftwareCard.vue +++ b/src/components/SoftwareCard.vue @@ -35,8 +35,16 @@

{{ software.description }}

- 当前: {{ software.version || '--' }} - + + + + 最新: {{ software.available_version }}
@@ -276,7 +284,7 @@ const handleCardClick = () => { font-weight: 500; } -.version-tag.available { +.version-tag.available, .version-tag.recommended { color: var(--primary-color); background: rgba(0, 122, 255, 0.08); padding: 0 6px; diff --git a/src/store/software.ts b/src/store/software.ts index f91480f..0ad84d8 100644 --- a/src/store/software.ts +++ b/src/store/software.ts @@ -33,13 +33,16 @@ export const useSoftwareStore = defineStore('software', { // ... (mergedEssentials, sortedUpdates, sortedAllSoftware, isBusy getters stay the same) mergedEssentials: (state) => { return state.essentials.map(item => { - const isInstalled = state.allSoftware.some(s => s.id.toLowerCase() === item.id.toLowerCase()); + const installedInfo = state.allSoftware.find(s => s.id.toLowerCase() === item.id.toLowerCase()); + const isInstalled = !!installedInfo; const hasUpdate = state.updates.some(s => s.id.toLowerCase() === item.id.toLowerCase()); let displayStatus = item.status; let actionLabel = '安装'; + let currentVersion = item.version; // 默认使用清单中的推荐版本 if (isInstalled) { + currentVersion = installedInfo.version; // 如果已安装,显示本地真实版本 if (hasUpdate) { actionLabel = '更新'; } else if (displayStatus === 'idle') { @@ -47,7 +50,14 @@ export const useSoftwareStore = defineStore('software', { actionLabel = '已安装'; } } - return { ...item, status: displayStatus, actionLabel }; + + return { + ...item, + version: currentVersion, + recommended_version: item.version, // 额外保存一个原始推荐版本字段供前端判断 + status: displayStatus, + actionLabel + }; }); }, sortedUpdates: (state) => [...state.updates].sort(sortByName), @@ -191,8 +201,10 @@ export const useSoftwareStore = defineStore('software', { }, async install(id: string) { const software = this.findSoftware(id) - if (software) software.status = 'pending'; - await invoke('install_software', { id }) + if (software) { + software.status = 'pending'; + await invoke('install_software', { id, version: software.version }) + } }, findSoftware(id: string) { return this.essentials.find(s => s.id === id) ||