fix install logix
This commit is contained in:
@@ -34,29 +34,26 @@
|
|||||||
<span class="id-badge">{{ software.id }}</span>
|
<span class="id-badge">{{ software.id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
<!-- 情况 1: 已安装软件 (包含待更新状态) -->
|
<!-- 情况 1: 已安装且有推荐/最新版本 -->
|
||||||
<template v-if="isInstalled">
|
<template v-if="software.version">
|
||||||
<span class="version-tag">当前: {{ software.version || '--' }}</span>
|
<span class="version-tag">当前: {{ software.version }}</span>
|
||||||
<!-- 仅在装机必备且当前版本低于推荐版本时显示 -->
|
<span v-if="software.recommended_version && software.actionLabel === '更新'" class="version-tag recommended">
|
||||||
<span
|
|
||||||
v-if="software.recommended_version && isVersionLower(software.version, software.recommended_version)"
|
|
||||||
class="version-tag recommended"
|
|
||||||
>
|
|
||||||
推荐: {{ software.recommended_version }}
|
推荐: {{ software.recommended_version }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
<span v-if="software.available_version && !software.recommended_version" class="version-tag available">
|
||||||
|
|
||||||
<!-- 情况 2: 未安装软件 -->
|
|
||||||
<template v-else>
|
|
||||||
<span class="version-tag recommended">
|
|
||||||
推荐: {{ software.recommended_version || '最新版' }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 情况 3: WinGet 检测到的仓库最新版本 -->
|
|
||||||
<span class="version-tag available" v-if="software.available_version">
|
|
||||||
最新: {{ software.available_version }}
|
最新: {{ software.available_version }}
|
||||||
</span>
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 情况 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +62,7 @@
|
|||||||
<div class="action-wrapper">
|
<div class="action-wrapper">
|
||||||
<button
|
<button
|
||||||
v-if="software.status === 'idle'"
|
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"
|
class="action-btn install-btn"
|
||||||
>
|
>
|
||||||
{{ actionLabel }}
|
{{ actionLabel }}
|
||||||
@@ -104,7 +101,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
v-else-if="software.status === 'error'"
|
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"
|
class="action-btn retry-btn"
|
||||||
>
|
>
|
||||||
重试
|
重试
|
||||||
@@ -128,8 +125,8 @@ const props = defineProps<{
|
|||||||
icon_url?: string;
|
icon_url?: string;
|
||||||
status: string;
|
status: string;
|
||||||
progress: number;
|
progress: number;
|
||||||
use_manifest?: boolean;
|
actionLabel?: string;
|
||||||
manifest_url?: string;
|
targetVersion?: string;
|
||||||
},
|
},
|
||||||
actionLabel?: string,
|
actionLabel?: string,
|
||||||
selectable?: boolean,
|
selectable?: boolean,
|
||||||
@@ -143,37 +140,6 @@ const displayProgress = computed(() => {
|
|||||||
return Math.round(props.software.progress * 100) + '%';
|
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 placeholderColor = computed(() => {
|
||||||
const colors = ['#FF9500', '#FF3B30', '#34C759', '#007AFF', '#5856D6', '#AF52DE'];
|
const colors = ['#FF9500', '#FF3B30', '#34C759', '#007AFF', '#5856D6', '#AF52DE'];
|
||||||
let hash = 0;
|
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 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 {
|
export interface LogEntry {
|
||||||
id: string; // 日志唯一标识
|
id: string; // 日志唯一标识
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -24,7 +41,7 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
settings: {
|
settings: {
|
||||||
repo_url: 'https://karlblue.github.io/winget-repo'
|
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,
|
loading: false,
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
initStatus: '正在检查系统环境...',
|
initStatus: '正在检查系统环境...',
|
||||||
@@ -34,36 +51,44 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
mergedEssentials: (state) => {
|
mergedEssentials: (state) => {
|
||||||
return state.essentials.map(item => {
|
return state.essentials.map(item => {
|
||||||
const installedInfo = state.allSoftware.find(s => s.id.toLowerCase() === item.id.toLowerCase());
|
const installedInfo = state.allSoftware.find(s => s.id.toLowerCase() === item.id.toLowerCase());
|
||||||
const isInstalled = !!installedInfo;
|
const wingetUpdate = state.updates.find(s => s.id.toLowerCase() === item.id.toLowerCase());
|
||||||
const hasUpdate = state.updates.some(s => s.id.toLowerCase() === item.id.toLowerCase());
|
|
||||||
|
|
||||||
// 优先使用 activeTasks 中的实时状态
|
|
||||||
const task = state.activeTasks[item.id];
|
const task = state.activeTasks[item.id];
|
||||||
let displayStatus = task ? task.status : item.status;
|
const isInstalled = !!installedInfo;
|
||||||
let displayProgress = task ? task.progress : item.progress;
|
const currentVersion = installedInfo?.version;
|
||||||
|
const recommendedVersion = item.version; // 清单里的推荐版本
|
||||||
|
const availableVersion = wingetUpdate?.available_version; // Winget 查到的最新版
|
||||||
|
|
||||||
|
let displayStatus = task ? task.status : 'idle';
|
||||||
let actionLabel = '安装';
|
let actionLabel = '安装';
|
||||||
|
let targetVersion = recommendedVersion || availableVersion;
|
||||||
// 统一字段:version 始终代表当前安装的版本,recommended_version 代表清单推荐的版本
|
|
||||||
const currentVersion = installedInfo ? installedInfo.version : null;
|
|
||||||
const recommendedVersion = item.version;
|
|
||||||
|
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
if (hasUpdate) {
|
// 逻辑:已安装 >= 推荐 -> 已安装(禁用)
|
||||||
actionLabel = '更新';
|
// 逻辑:已安装 < 推荐 -> 更新
|
||||||
} else if (displayStatus === 'idle') {
|
const comp = compareVersions(currentVersion, recommendedVersion);
|
||||||
displayStatus = 'installed';
|
if (comp >= 0) {
|
||||||
|
displayStatus = task ? task.status : 'installed';
|
||||||
actionLabel = '已安装';
|
actionLabel = '已安装';
|
||||||
|
targetVersion = undefined; // 禁用安装
|
||||||
|
} else {
|
||||||
|
actionLabel = '更新';
|
||||||
|
targetVersion = recommendedVersion;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
actionLabel = '安装';
|
||||||
|
targetVersion = recommendedVersion || availableVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
version: currentVersion,
|
version: currentVersion,
|
||||||
recommended_version: recommendedVersion,
|
recommended_version: recommendedVersion,
|
||||||
|
available_version: availableVersion,
|
||||||
status: displayStatus,
|
status: displayStatus,
|
||||||
progress: displayProgress,
|
progress: task ? task.progress : 0,
|
||||||
actionLabel
|
actionLabel,
|
||||||
|
targetVersion // 传递给视图,用于点击安装
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -72,8 +97,10 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
const task = state.activeTasks[item.id];
|
const task = state.activeTasks[item.id];
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
status: task ? task.status : item.status,
|
status: task ? task.status : 'idle',
|
||||||
progress: task ? task.progress : item.progress
|
progress: task ? task.progress : 0,
|
||||||
|
actionLabel: '更新',
|
||||||
|
targetVersion: item.available_version // 更新页面永远追求最新版
|
||||||
};
|
};
|
||||||
}).sort(sortByName);
|
}).sort(sortByName);
|
||||||
},
|
},
|
||||||
@@ -84,6 +111,7 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
// ... (initializeApp, saveSettings, syncEssentials stay the same)
|
||||||
async initializeApp() {
|
async initializeApp() {
|
||||||
if (this.isInitialized) return;
|
if (this.isInitialized) return;
|
||||||
this.initStatus = '正在加载应用配置...';
|
this.initStatus = '正在加载应用配置...';
|
||||||
@@ -113,7 +141,6 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ... (Selection methods stay the same)
|
|
||||||
toggleSelection(id: string, type: 'essential' | 'update') {
|
toggleSelection(id: string, type: 'essential' | 'update') {
|
||||||
if (this.isBusy) return;
|
if (this.isBusy) return;
|
||||||
const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds;
|
const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds;
|
||||||
@@ -123,7 +150,7 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
},
|
},
|
||||||
selectAll(type: 'essential' | 'update') {
|
selectAll(type: 'essential' | 'update') {
|
||||||
if (type === 'essential') {
|
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);
|
this.selectedEssentialIds = selectable.map(s => s.id);
|
||||||
} else {
|
} else {
|
||||||
this.selectedUpdateIds = this.updates.map(s => s.id);
|
this.selectedUpdateIds = this.updates.map(s => s.id);
|
||||||
@@ -135,7 +162,7 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
},
|
},
|
||||||
invertSelection(type: 'essential' | 'update') {
|
invertSelection(type: 'essential' | 'update') {
|
||||||
if (type === 'essential') {
|
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));
|
this.selectedEssentialIds = selectable.filter(id => !this.selectedEssentialIds.includes(id));
|
||||||
} else {
|
} else {
|
||||||
const selectable = this.updates.map(s => s.id);
|
const selectable = this.updates.map(s => s.id);
|
||||||
@@ -145,8 +172,6 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
|
|
||||||
async fetchEssentials() {
|
async fetchEssentials() {
|
||||||
let repo = await invoke('get_essentials') as any;
|
let repo = await invoke('get_essentials') as any;
|
||||||
|
|
||||||
// 如果本地没有文件,则尝试联网获取一次
|
|
||||||
if (!repo) {
|
if (!repo) {
|
||||||
try {
|
try {
|
||||||
await invoke('sync_essentials');
|
await invoke('sync_essentials');
|
||||||
@@ -155,7 +180,6 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
console.error('Initial sync failed:', err);
|
console.error('Initial sync failed:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repo) {
|
if (repo) {
|
||||||
this.essentials = repo.essentials;
|
this.essentials = repo.essentials;
|
||||||
this.essentialsVersion = repo.version;
|
this.essentialsVersion = repo.version;
|
||||||
@@ -188,15 +212,11 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
async fetchAllData() {
|
async fetchAllData() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
// 先确保加载了必备清单(内部处理本地缺失逻辑)
|
|
||||||
await this.fetchEssentials();
|
await this.fetchEssentials();
|
||||||
|
|
||||||
// 然后同步本地软件安装/更新状态,不再强制联网下载 JSON
|
|
||||||
const [all, updates] = await Promise.all([
|
const [all, updates] = await Promise.all([
|
||||||
invoke('get_installed_software'),
|
invoke('get_installed_software'),
|
||||||
invoke('get_updates')
|
invoke('get_updates')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.allSoftware = all as any[];
|
this.allSoftware = all as any[];
|
||||||
this.updates = updates as any[];
|
this.updates = updates as any[];
|
||||||
this.lastFetched = Date.now();
|
this.lastFetched = Date.now();
|
||||||
@@ -205,16 +225,15 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async install(id: string) {
|
async install(id: string, targetVersion?: string) {
|
||||||
const software = this.findSoftware(id)
|
const software = this.findSoftware(id)
|
||||||
if (software) {
|
if (software) {
|
||||||
// 使用 activeTasks 记录任务,而不是修改原始对象
|
this.activeTasks[id] = { status: 'pending', progress: 0, targetVersion };
|
||||||
this.activeTasks[id] = { status: 'pending', progress: 0 };
|
|
||||||
try {
|
try {
|
||||||
await invoke('install_software', {
|
await invoke('install_software', {
|
||||||
task: {
|
task: {
|
||||||
id,
|
id,
|
||||||
version: software.recommended_version || software.available_version || software.version,
|
version: targetVersion,
|
||||||
use_manifest: software.use_manifest || false,
|
use_manifest: software.use_manifest || false,
|
||||||
manifest_url: software.manifest_url || null
|
manifest_url: software.manifest_url || null
|
||||||
}
|
}
|
||||||
@@ -226,7 +245,6 @@ export const useSoftwareStore = defineStore('software', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
findSoftware(id: string) {
|
findSoftware(id: string) {
|
||||||
// findSoftware 现在仅用于获取软件的基础元数据(ID, 清单 URL 等)
|
|
||||||
return this.essentials.find(s => s.id === id) ||
|
return this.essentials.find(s => s.id === id) ||
|
||||||
this.updates.find(s => s.id === id) ||
|
this.updates.find(s => s.id === id) ||
|
||||||
this.allSoftware.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) => {
|
listen('install-status', (event: any) => {
|
||||||
const { id, status, progress } = event.payload
|
const { id, status, progress } = event.payload
|
||||||
|
const task = this.activeTasks[id];
|
||||||
// 直接更新 activeTasks,Getter 会自动响应
|
this.activeTasks[id] = { status, progress, targetVersion: task?.targetVersion };
|
||||||
this.activeTasks[id] = { status, progress };
|
|
||||||
|
|
||||||
if (status === 'success') {
|
if (status === 'success') {
|
||||||
this.lastFetched = 0;
|
this.lastFetched = 0;
|
||||||
this.selectedEssentialIds = this.selectedEssentialIds.filter(i => i !== id);
|
this.selectedEssentialIds = this.selectedEssentialIds.filter(i => i !== id);
|
||||||
this.selectedUpdateIds = this.selectedUpdateIds.filter(i => i !== id);
|
this.selectedUpdateIds = this.selectedUpdateIds.filter(i => i !== id);
|
||||||
|
|
||||||
// 成功后延迟清理任务状态,让用户看到“已完成”
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 如果在此期间没有开始新任务,则移除
|
|
||||||
if (this.activeTasks[id]?.status === 'success') {
|
if (this.activeTasks[id]?.status === 'success') {
|
||||||
delete this.activeTasks[id];
|
delete this.activeTasks[id];
|
||||||
// 重新拉取数据以刷新已安装/待更新状态
|
|
||||||
this.fetchAllData();
|
this.fetchAllData();
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 日志监听:根据 ID 追加内容
|
|
||||||
listen('log-event', (event: any) => {
|
listen('log-event', (event: any) => {
|
||||||
const payload = event.payload as LogEntry;
|
const payload = event.payload as LogEntry;
|
||||||
const existingLog = this.logs.find(l => l.id === payload.id);
|
const existingLog = this.logs.find(l => l.id === payload.id);
|
||||||
|
|
||||||
if (existingLog) {
|
if (existingLog) {
|
||||||
// 如果是增量更新
|
if (payload.output) existingLog.output += '\n' + payload.output;
|
||||||
if (payload.output) {
|
if (payload.status !== 'info') existingLog.status = payload.status;
|
||||||
existingLog.output += '\n' + payload.output;
|
|
||||||
}
|
|
||||||
if (payload.status !== 'info') {
|
|
||||||
existingLog.status = payload.status;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 如果是新日志
|
|
||||||
this.logs.unshift(payload);
|
this.logs.unshift(payload);
|
||||||
if (this.logs.length > 100) this.logs.pop();
|
if (this.logs.length > 100) this.logs.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ const selectableItems = computed(() => {
|
|||||||
|
|
||||||
const installSelected = () => {
|
const installSelected = () => {
|
||||||
store.selectedEssentialIds.forEach(id => {
|
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 = () => {
|
const updateSelected = () => {
|
||||||
store.selectedUpdateIds.forEach(id => {
|
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