fix install logix

This commit is contained in:
Julian Freeman
2026-03-31 13:15:50 -04:00
parent 7e550a8d49
commit 145dad23a5
4 changed files with 84 additions and 106 deletions

View File

@@ -4,6 +4,23 @@ 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;
@@ -24,7 +41,7 @@ export const useSoftwareStore = defineStore('software', {
settings: {
repo_url: 'https://karlblue.github.io/winget-repo'
},
activeTasks: {} as Record<string, { status: string, progress: number }>,
activeTasks: {} as Record<string, { status: string, progress: number, targetVersion?: string }>,
loading: false,
isInitialized: false,
initStatus: '正在检查系统环境...',
@@ -34,36 +51,44 @@ export const useSoftwareStore = defineStore('software', {
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());
const wingetUpdate = state.updates.find(s => s.id.toLowerCase() === item.id.toLowerCase());
// 优先使用 activeTasks 中的实时状态
const task = state.activeTasks[item.id];
let displayStatus = task ? task.status : item.status;
let displayProgress = task ? task.progress : item.progress;
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 = '安装';
// 统一字段version 始终代表当前安装的版本recommended_version 代表清单推荐的版本
const currentVersion = installedInfo ? installedInfo.version : null;
const recommendedVersion = item.version;
let targetVersion = recommendedVersion || availableVersion;
if (isInstalled) {
if (hasUpdate) {
actionLabel = '更新';
} else if (displayStatus === 'idle') {
displayStatus = 'installed';
// 逻辑:已安装 >= 推荐 -> 已安装(禁用)
// 逻辑:已安装 < 推荐 -> 更新
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: displayProgress,
actionLabel
progress: task ? task.progress : 0,
actionLabel,
targetVersion // 传递给视图,用于点击安装
};
});
},
@@ -72,8 +97,10 @@ export const useSoftwareStore = defineStore('software', {
const task = state.activeTasks[item.id];
return {
...item,
status: task ? task.status : item.status,
progress: task ? task.progress : item.progress
status: task ? task.status : 'idle',
progress: task ? task.progress : 0,
actionLabel: '更新',
targetVersion: item.available_version // 更新页面永远追求最新版
};
}).sort(sortByName);
},
@@ -84,6 +111,7 @@ export const useSoftwareStore = defineStore('software', {
}
},
actions: {
// ... (initializeApp, saveSettings, syncEssentials stay the same)
async initializeApp() {
if (this.isInitialized) return;
this.initStatus = '正在加载应用配置...';
@@ -113,7 +141,6 @@ export const useSoftwareStore = defineStore('software', {
}
},
// ... (Selection methods stay the same)
toggleSelection(id: string, type: 'essential' | 'update') {
if (this.isBusy) return;
const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds;
@@ -123,7 +150,7 @@ export const useSoftwareStore = defineStore('software', {
},
selectAll(type: 'essential' | 'update') {
if (type === 'essential') {
const selectable = this.mergedEssentials.filter(s => s.status !== 'installed');
const selectable = this.mergedEssentials.filter(s => s.actionLabel !== '已安装');
this.selectedEssentialIds = selectable.map(s => s.id);
} else {
this.selectedUpdateIds = this.updates.map(s => s.id);
@@ -135,7 +162,7 @@ export const useSoftwareStore = defineStore('software', {
},
invertSelection(type: 'essential' | 'update') {
if (type === 'essential') {
const selectable = this.mergedEssentials.filter(s => s.status !== 'installed').map(s => s.id);
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);
@@ -145,8 +172,6 @@ export const useSoftwareStore = defineStore('software', {
async fetchEssentials() {
let repo = await invoke('get_essentials') as any;
// 如果本地没有文件,则尝试联网获取一次
if (!repo) {
try {
await invoke('sync_essentials');
@@ -155,7 +180,6 @@ export const useSoftwareStore = defineStore('software', {
console.error('Initial sync failed:', err);
}
}
if (repo) {
this.essentials = repo.essentials;
this.essentialsVersion = repo.version;
@@ -188,15 +212,11 @@ export const useSoftwareStore = defineStore('software', {
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();
@@ -205,16 +225,15 @@ export const useSoftwareStore = defineStore('software', {
this.loading = false;
}
},
async install(id: string) {
async install(id: string, targetVersion?: string) {
const software = this.findSoftware(id)
if (software) {
// 使用 activeTasks 记录任务,而不是修改原始对象
this.activeTasks[id] = { status: 'pending', progress: 0 };
this.activeTasks[id] = { status: 'pending', progress: 0, targetVersion };
try {
await invoke('install_software', {
task: {
id,
version: software.recommended_version || software.available_version || software.version,
version: targetVersion,
use_manifest: software.use_manifest || false,
manifest_url: software.manifest_url || null
}
@@ -226,7 +245,6 @@ export const useSoftwareStore = defineStore('software', {
}
},
findSoftware(id: string) {
// findSoftware 现在仅用于获取软件的基础元数据ID, 清单 URL 等)
return this.essentials.find(s => s.id === id) ||
this.updates.find(s => s.id === id) ||
this.allSoftware.find(s => s.id === id)
@@ -237,42 +255,30 @@ export const useSoftwareStore = defineStore('software', {
listen('install-status', (event: any) => {
const { id, status, progress } = event.payload
// 直接更新 activeTasksGetter 会自动响应
this.activeTasks[id] = { status, progress };
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);
}
})
// 日志监听:根据 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;
}
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();
}