import { defineStore } from 'pinia' import { invoke } from '@tauri-apps/api/core' import { listen } from '@tauri-apps/api/event' const sortByName = (a: any, b: any) => a.name.localeCompare(b.name, 'zh-CN', { sensitivity: 'accent' }); // 版本比对工具函数 const compareVersions = (v1: string | null | undefined, v2: string | null | undefined) => { if (!v1 || !v2) return 0; if (v1 === v2) return 0; const cleanV = (v: string) => v.replace(/^v/i, '').split(/[-+]/)[0].split('.'); const p1 = cleanV(v1); const p2 = cleanV(v2); const len = Math.max(p1.length, p2.length); for (let i = 0; i < len; i++) { const n1 = parseInt(p1[i] || '0', 10); const n2 = parseInt(p2[i] || '0', 10); if (n1 < n2) return -1; if (n1 > n2) return 1; } return 0; }; export interface LogEntry { id: string; // 日志唯一标识 timestamp: string; command: string; output: string; status: 'info' | 'success' | 'error'; } export const useSoftwareStore = defineStore('software', { state: () => ({ essentials: [] as any[], essentialsVersion: '', updates: [] as any[], allSoftware: [] as any[], selectedEssentialIds: [] as string[], selectedUpdateIds: [] as string[], logs: [] as LogEntry[], settings: { repo_url: 'https://karlblue.github.io/winget-repo' }, activeTasks: {} as Record, loading: false, isInitialized: false, initStatus: '正在检查系统环境...', lastFetched: 0 }), getters: { mergedEssentials: (state) => { return state.essentials.map(item => { const installedInfo = state.allSoftware.find(s => s.id.toLowerCase() === item.id.toLowerCase()); const wingetUpdate = state.updates.find(s => s.id.toLowerCase() === item.id.toLowerCase()); const task = state.activeTasks[item.id]; const isInstalled = !!installedInfo; const currentVersion = installedInfo?.version; const recommendedVersion = item.version; // 清单里的推荐版本 const availableVersion = wingetUpdate?.available_version; // Winget 查到的最新版 let displayStatus = task ? task.status : 'idle'; let actionLabel = '安装'; let targetVersion = recommendedVersion || availableVersion; if (isInstalled) { // 逻辑:已安装 >= 推荐 -> 已安装(禁用) // 逻辑:已安装 < 推荐 -> 更新 const comp = compareVersions(currentVersion, recommendedVersion); if (comp >= 0) { displayStatus = task ? task.status : 'installed'; actionLabel = '已安装'; targetVersion = undefined; // 禁用安装 } else { actionLabel = '更新'; targetVersion = recommendedVersion; } } else { actionLabel = '安装'; targetVersion = recommendedVersion || availableVersion; } return { ...item, version: currentVersion, recommended_version: recommendedVersion, available_version: availableVersion, status: displayStatus, progress: task ? task.progress : 0, actionLabel, targetVersion // 传递给视图,用于点击安装 }; }); }, sortedUpdates: (state) => { return [...state.updates].map(item => { const task = state.activeTasks[item.id]; return { ...item, status: task ? task.status : 'idle', progress: task ? task.progress : 0, actionLabel: '更新', targetVersion: item.available_version // 更新页面永远追求最新版 }; }).sort(sortByName); }, isBusy: (state) => { return state.loading || Object.values(state.activeTasks).some(task => task.status === 'pending' || task.status === 'installing' ); } }, actions: { // ... (initializeApp, saveSettings, syncEssentials stay the same) async initializeApp() { if (this.isInitialized) return; this.initStatus = '正在加载应用配置...'; try { this.settings = await invoke('get_settings'); this.initStatus = '正在同步 Winget 模块...'; await invoke('initialize_app'); this.isInitialized = true; } catch (err) { this.initStatus = '环境配置失败,请检查运行日志'; setTimeout(() => { this.isInitialized = true; }, 2000); } }, async saveSettings(newSettings: any) { await invoke('save_settings', { settings: newSettings }); this.settings = newSettings; }, async syncEssentials() { this.loading = true; try { await invoke('sync_essentials'); await this.fetchEssentials(); } finally { this.loading = false; } }, toggleSelection(id: string, type: 'essential' | 'update') { if (this.isBusy) return; const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds; const index = list.indexOf(id); if (index === -1) list.push(id); else list.splice(index, 1); }, selectAll(type: 'essential' | 'update') { if (type === 'essential') { const selectable = this.mergedEssentials.filter(s => s.actionLabel !== '已安装'); this.selectedEssentialIds = selectable.map(s => s.id); } else { this.selectedUpdateIds = this.updates.map(s => s.id); } }, deselectAll(type: 'essential' | 'update') { if (type === 'essential') this.selectedEssentialIds = []; else this.selectedUpdateIds = []; }, invertSelection(type: 'essential' | 'update') { if (type === 'essential') { const selectable = this.mergedEssentials.filter(s => s.actionLabel !== '已安装').map(s => s.id); this.selectedEssentialIds = selectable.filter(id => !this.selectedEssentialIds.includes(id)); } else { const selectable = this.updates.map(s => s.id); this.selectedUpdateIds = selectable.filter(id => !this.selectedUpdateIds.includes(id)); } }, async fetchEssentials() { let repo = await invoke('get_essentials') as any; if (!repo) { try { await invoke('sync_essentials'); repo = await invoke('get_essentials') as any; } catch (err) { console.error('Initial sync failed:', err); } } if (repo) { this.essentials = repo.essentials; this.essentialsVersion = repo.version; } else { this.essentials = []; this.essentialsVersion = ''; } }, async fetchUpdates() { if (this.isBusy) return; this.loading = true try { const res = await invoke('get_updates') this.updates = res as any[] if (this.selectedUpdateIds.length === 0) this.selectAll('update'); } finally { this.loading = false } }, async syncDataIfNeeded(force = false) { if (this.isBusy) return; const now = Date.now(); const CACHE_TIMEOUT = 5 * 60 * 1000; if (!force && this.allSoftware.length > 0 && (now - this.lastFetched < CACHE_TIMEOUT)) { if (this.essentials.length === 0) await this.fetchEssentials(); return; } await this.fetchAllData(); }, async fetchAllData() { this.loading = true; try { await this.fetchEssentials(); const [all, updates] = await Promise.all([ invoke('get_installed_software'), invoke('get_updates') ]); this.allSoftware = all as any[]; this.updates = updates as any[]; this.lastFetched = Date.now(); if (this.selectedEssentialIds.length === 0) this.selectAll('essential'); } finally { this.loading = false; } }, async install(id: string, targetVersion?: string) { const software = this.findSoftware(id) if (software) { this.activeTasks[id] = { status: 'pending', progress: 0, targetVersion }; try { await invoke('install_software', { task: { id, version: targetVersion, use_manifest: software.use_manifest || false, manifest_url: software.manifest_url || null } }) } catch (err) { console.error('Invoke install failed:', err); this.activeTasks[id] = { status: 'error', progress: 0 }; } } }, findSoftware(id: string) { return this.essentials.find(s => s.id === id) || this.updates.find(s => s.id === id) || this.allSoftware.find(s => s.id === id) }, initListener() { if ((window as any).__tauri_listener_init) return; (window as any).__tauri_listener_init = true; listen('install-status', (event: any) => { const { id, status, progress } = event.payload const task = this.activeTasks[id]; this.activeTasks[id] = { status, progress, targetVersion: task?.targetVersion }; if (status === 'success') { this.lastFetched = 0; this.selectedEssentialIds = this.selectedEssentialIds.filter(i => i !== id); this.selectedUpdateIds = this.selectedUpdateIds.filter(i => i !== id); setTimeout(() => { if (this.activeTasks[id]?.status === 'success') { delete this.activeTasks[id]; this.fetchAllData(); } }, 3000); } }) listen('log-event', (event: any) => { const payload = event.payload as LogEntry; const existingLog = this.logs.find(l => l.id === payload.id); if (existingLog) { if (payload.output) existingLog.output += '\n' + payload.output; if (payload.status !== 'info') existingLog.status = payload.status; } else { this.logs.unshift(payload); if (this.logs.length > 100) this.logs.pop(); } }) } } })