Files
win-softmgr/src/store/software.ts
2026-03-30 22:31:49 -04:00

250 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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' });
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'
},
loading: false,
isInitialized: false,
initStatus: '正在检查系统环境...',
lastFetched: 0
}),
getters: {
// ... (mergedEssentials, sortedUpdates, sortedAllSoftware, isBusy getters stay the same)
mergedEssentials: (state) => {
return state.essentials.map(item => {
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 = '安装';
// 统一字段version 始终代表当前安装的版本recommended_version 代表清单推荐的版本
const currentVersion = installedInfo ? installedInfo.version : null;
const recommendedVersion = item.version;
if (isInstalled) {
if (hasUpdate) {
actionLabel = '更新';
} else if (displayStatus === 'idle') {
displayStatus = 'installed';
actionLabel = '已安装';
}
}
return {
...item,
version: currentVersion,
recommended_version: recommendedVersion,
status: displayStatus,
actionLabel
};
});
},
sortedUpdates: (state) => [...state.updates].sort(sortByName),
isBusy: (state) => {
const allItems = [...state.essentials, ...state.updates, ...state.allSoftware];
return allItems.some(item => item.status === 'pending' || item.status === 'installing');
}
},
actions: {
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;
}
},
// ... (Selection methods stay the same)
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.status !== 'installed');
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.status !== 'installed').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();
// 然后同步本地软件安装/更新状态,不再强制联网下载 JSON
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) {
const software = this.findSoftware(id)
if (software) {
software.status = 'pending';
await invoke('install_software', {
id,
version: software.version,
use_manifest: software.use_manifest,
manifest_url: software.manifest_url
})
}
},
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 software = this.findSoftware(id)
if (software) {
software.status = status
software.progress = progress
}
if (status === 'success') {
this.lastFetched = 0;
this.selectedEssentialIds = this.selectedEssentialIds.filter(i => i !== id);
this.selectedUpdateIds = this.selectedUpdateIds.filter(i => i !== id);
}
})
// 日志监听:根据 ID 追加内容
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();
}
})
}
}
})