fix install logix
This commit is contained in:
@@ -34,29 +34,26 @@
|
||||
<span class="id-badge">{{ software.id }}</span>
|
||||
</div>
|
||||
<div class="version-info">
|
||||
<!-- 情况 1: 已安装软件 (包含待更新状态) -->
|
||||
<template v-if="isInstalled">
|
||||
<span class="version-tag">当前: {{ software.version || '--' }}</span>
|
||||
<!-- 仅在装机必备且当前版本低于推荐版本时显示 -->
|
||||
<span
|
||||
v-if="software.recommended_version && isVersionLower(software.version, software.recommended_version)"
|
||||
class="version-tag recommended"
|
||||
>
|
||||
<!-- 情况 1: 已安装且有推荐/最新版本 -->
|
||||
<template v-if="software.version">
|
||||
<span class="version-tag">当前: {{ software.version }}</span>
|
||||
<span v-if="software.recommended_version && software.actionLabel === '更新'" class="version-tag recommended">
|
||||
推荐: {{ software.recommended_version }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 情况 2: 未安装软件 -->
|
||||
<template v-else>
|
||||
<span class="version-tag recommended">
|
||||
推荐: {{ software.recommended_version || '最新版' }}
|
||||
<span v-if="software.available_version && !software.recommended_version" class="version-tag available">
|
||||
最新: {{ software.available_version }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 情况 3: WinGet 检测到的仓库最新版本 -->
|
||||
<span class="version-tag available" v-if="software.available_version">
|
||||
最新: {{ software.available_version }}
|
||||
</span>
|
||||
<!-- 情况 2: 未安装 -->
|
||||
<template v-else>
|
||||
<span v-if="software.recommended_version" class="version-tag recommended">
|
||||
推荐: {{ software.recommended_version }}
|
||||
</span>
|
||||
<span v-else-if="software.available_version" class="version-tag available">
|
||||
最新: {{ software.available_version }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,7 +62,7 @@
|
||||
<div class="action-wrapper">
|
||||
<button
|
||||
v-if="software.status === 'idle'"
|
||||
@click.stop="$emit('install', software.id)"
|
||||
@click.stop="$emit('install', software.id, (software as any).targetVersion)"
|
||||
class="action-btn install-btn"
|
||||
>
|
||||
{{ actionLabel }}
|
||||
@@ -104,7 +101,7 @@
|
||||
|
||||
<button
|
||||
v-else-if="software.status === 'error'"
|
||||
@click.stop="$emit('install', software.id)"
|
||||
@click.stop="$emit('install', software.id, (software as any).targetVersion)"
|
||||
class="action-btn retry-btn"
|
||||
>
|
||||
重试
|
||||
@@ -128,8 +125,8 @@ const props = defineProps<{
|
||||
icon_url?: string;
|
||||
status: string;
|
||||
progress: number;
|
||||
use_manifest?: boolean;
|
||||
manifest_url?: string;
|
||||
actionLabel?: string;
|
||||
targetVersion?: string;
|
||||
},
|
||||
actionLabel?: string,
|
||||
selectable?: boolean,
|
||||
@@ -143,37 +140,6 @@ const displayProgress = computed(() => {
|
||||
return Math.round(props.software.progress * 100) + '%';
|
||||
});
|
||||
|
||||
const isInstalled = computed(() => {
|
||||
return props.software.status === 'installed' ||
|
||||
(props.software.status === 'idle' && props.actionLabel === '更新') ||
|
||||
(props.software.status === 'idle' && !props.actionLabel && props.software.version);
|
||||
});
|
||||
|
||||
const isVersionLower = (current: string | undefined | null, target: string | undefined | null) => {
|
||||
if (!current || !target) return false;
|
||||
if (current === target) return false;
|
||||
|
||||
// 处理常见的版本号格式,如 "v1.2.3" -> "1.2.3"
|
||||
const cleanV = (v: string) => v.replace(/^v/i, '').split(/[-+]/)[0];
|
||||
const v1 = cleanV(current).split('.');
|
||||
const v2 = cleanV(target).split('.');
|
||||
const len = Math.max(v1.length, v2.length);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const n1 = parseInt(v1[i] || '0', 10);
|
||||
const n2 = parseInt(v2[i] || '0', 10);
|
||||
|
||||
if (n1 < n2) return true;
|
||||
if (n1 > n2) return false;
|
||||
|
||||
// 如果数字相等但字符串不等(如 1.0b vs 1.0a),进行字符串比对
|
||||
if (isNaN(n1) || isNaN(n2)) {
|
||||
if (v1[i] !== v2[i]) return (v1[i] || '') < (v2[i] || '');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const placeholderColor = computed(() => {
|
||||
const colors = ['#FF9500', '#FF3B30', '#34C759', '#007AFF', '#5856D6', '#AF52DE'];
|
||||
let hash = 0;
|
||||
|
||||
@@ -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
|
||||
|
||||
// 直接更新 activeTasks,Getter 会自动响应
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -78,7 +78,10 @@ const selectableItems = computed(() => {
|
||||
|
||||
const installSelected = () => {
|
||||
store.selectedEssentialIds.forEach(id => {
|
||||
store.install(id);
|
||||
const item = store.mergedEssentials.find(s => s.id === id);
|
||||
if (item && item.targetVersion) {
|
||||
store.install(id, item.targetVersion);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,10 @@ const store = useSoftwareStore();
|
||||
|
||||
const updateSelected = () => {
|
||||
store.selectedUpdateIds.forEach(id => {
|
||||
store.install(id);
|
||||
const item = store.sortedUpdates.find(s => s.id === id);
|
||||
if (item && item.targetVersion) {
|
||||
store.install(id, item.targetVersion);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user