diff --git a/src-tauri/src/domain/models.rs b/src-tauri/src/domain/models.rs index 5002e60..3717efa 100644 --- a/src-tauri/src/domain/models.rs +++ b/src-tauri/src/domain/models.rs @@ -75,6 +75,7 @@ pub struct EssentialsStatusItem { pub id: String, pub name: String, pub description: Option, + pub category: Option, pub version: Option, pub recommended_version: Option, pub available_version: Option, @@ -92,6 +93,7 @@ pub struct UpdateCandidate { pub id: String, pub name: String, pub description: Option, + pub category: Option, pub version: Option, pub available_version: Option, pub icon_url: Option, diff --git a/src-tauri/src/services/reconcile_service.rs b/src-tauri/src/services/reconcile_service.rs index 06c5d71..626877c 100644 --- a/src-tauri/src/services/reconcile_service.rs +++ b/src-tauri/src/services/reconcile_service.rs @@ -63,6 +63,7 @@ pub fn build_essentials_status( id: definition.id.clone(), name: definition.name.clone(), description: definition.description.clone(), + category: definition.category.clone(), version: current_version, recommended_version, available_version, @@ -95,6 +96,7 @@ pub fn build_update_candidates( id: update.id.clone(), name: update.name.clone(), description: definition.and_then(|item| item.description.clone()), + category: definition.and_then(|item| item.category.clone()), version: update.version.clone(), available_version: update.available_version.clone(), icon_url: update.icon_url.clone().or_else(|| definition.and_then(|item| item.icon_url.clone())), diff --git a/src-tauri/src/winget.rs b/src-tauri/src/winget.rs index b00093d..bfe7b52 100644 --- a/src-tauri/src/winget.rs +++ b/src-tauri/src/winget.rs @@ -47,6 +47,7 @@ pub struct Software { pub id: String, pub name: String, pub description: Option, + pub category: Option, pub version: Option, pub available_version: Option, pub icon_url: Option, @@ -387,6 +388,7 @@ fn map_package(p: WingetPackage) -> Software { id: p.id, name: p.name, description: None, + category: None, version: p.installed_version, available_version: p.available_versions.and_then(|v| v.first().cloned()), icon_url: p.icon_url, diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index adce4c3..6508915 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -12,6 +12,17 @@ 装机必备 + + + + + + + + + + 其他软件 + diff --git a/src/router/index.ts b/src/router/index.ts index ae980c8..6cb17f3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -11,6 +11,10 @@ const router = createRouter({ path: '/essentials', component: () => import('../views/Essentials.vue') }, + { + path: '/other-software', + component: () => import('../views/OtherSoftware.vue') + }, { path: '/updates', component: () => import('../views/Updates.vue') diff --git a/src/store/software.ts b/src/store/software.ts index a649e01..5867fbc 100644 --- a/src/store/software.ts +++ b/src/store/software.ts @@ -22,12 +22,13 @@ export const useSoftwareStore = defineStore('software', () => { const { activeTasks, selectedEssentialIds, + selectedSpecialIds, selectedUpdateIds, logs, postInstallPrefs } = storeToRefs(taskRuntime) - const mergedEssentials = computed(() => essentials.value.map(item => { + const mergeSoftwareItem = (item: typeof essentials.value[number]) => { const task = activeTasks.value[item.id] const enablePostInstall = postInstallPrefs.value[item.id] !== false const baseStatus = item.action_label === '已安装' ? 'installed' : 'idle' @@ -40,13 +41,27 @@ export const useSoftwareStore = defineStore('software', () => { icon_url: item.icon_url ?? undefined, manifest_url: item.manifest_url ?? undefined, post_install_url: item.post_install_url ?? undefined, + category: item.category ?? 'general', actionLabel: item.action_label, targetVersion: item.target_version ?? undefined, status: task ? task.status : baseStatus, progress: task ? task.progress : 0, enablePostInstall } - })) + } + + const mergedEssentials = computed(() => essentials.value.map(mergeSoftwareItem)) + + const categorizedEssentials = computed(() => { + return mergedEssentials.value.reduce((acc, item) => { + const category = item.category === 'special' ? 'special' : 'general' + acc[category].push(item) + return acc + }, { general: [] as typeof mergedEssentials.value, special: [] as typeof mergedEssentials.value }) + }) + + const generalEssentials = computed(() => categorizedEssentials.value.general) + const specialEssentials = computed(() => categorizedEssentials.value.special) const sortedUpdates = computed(() => [...updates.value].map(item => { const task = activeTasks.value[item.id] @@ -70,33 +85,44 @@ export const useSoftwareStore = defineStore('software', () => { const isBusy = computed(() => loading.value || taskRuntime.isTaskBusy) - const toggleSelection = (id: string, type: 'essential' | 'update') => { + const toggleSelection = (id: string, type: 'essential' | 'special' | 'update') => { if (isBusy.value) return taskRuntime.toggleSelection(id, type) } - const selectAll = (type: 'essential' | 'update') => { + const selectAll = (type: 'essential' | 'special' | 'update') => { if (type === 'essential') { - const selectable = mergedEssentials.value.filter(item => item.actionLabel !== '已安装') + const selectable = generalEssentials.value.filter(item => item.actionLabel !== '已安装') taskRuntime.setSelection('essential', selectable.map(item => item.id)) + } else if (type === 'special') { + const selectable = specialEssentials.value.filter(item => item.actionLabel !== '已安装') + taskRuntime.setSelection('special', selectable.map(item => item.id)) } else { taskRuntime.setSelection('update', updates.value.map(item => item.id)) } } - const deselectAll = (type: 'essential' | 'update') => { + const deselectAll = (type: 'essential' | 'special' | 'update') => { taskRuntime.setSelection(type, []) } - const invertSelection = (type: 'essential' | 'update') => { + const invertSelection = (type: 'essential' | 'special' | 'update') => { if (type === 'essential') { - const selectable = mergedEssentials.value + const selectable = generalEssentials.value .filter(item => item.actionLabel !== '已安装') .map(item => item.id) taskRuntime.setSelection( 'essential', selectable.filter(id => !selectedEssentialIds.value.includes(id)) ) + } else if (type === 'special') { + const selectable = specialEssentials.value + .filter(item => item.actionLabel !== '已安装') + .map(item => item.id) + taskRuntime.setSelection( + 'special', + selectable.filter(id => !selectedSpecialIds.value.includes(id)) + ) } else { const selectable = updates.value.map(item => item.id) taskRuntime.setSelection( @@ -116,11 +142,13 @@ export const useSoftwareStore = defineStore('software', () => { if (isBusy.value) return await catalog.syncDataIfNeeded(force) if (selectedEssentialIds.value.length === 0) selectAll('essential') + if (selectedSpecialIds.value.length === 0) selectAll('special') } const fetchAllData = async () => { await catalog.fetchAllData() if (selectedEssentialIds.value.length === 0) selectAll('essential') + if (selectedSpecialIds.value.length === 0) selectAll('special') } return { @@ -129,6 +157,7 @@ export const useSoftwareStore = defineStore('software', () => { updates, allSoftware, selectedEssentialIds, + selectedSpecialIds, selectedUpdateIds, logs, settings, @@ -137,6 +166,8 @@ export const useSoftwareStore = defineStore('software', () => { isInitialized, initStatus, mergedEssentials, + generalEssentials, + specialEssentials, sortedUpdates, isBusy, initializeApp: catalog.initializeApp, diff --git a/src/store/taskRuntime.ts b/src/store/taskRuntime.ts index 6514457..b95e512 100644 --- a/src/store/taskRuntime.ts +++ b/src/store/taskRuntime.ts @@ -9,6 +9,7 @@ export const useTaskRuntimeStore = defineStore('task-runtime', { state: () => ({ taskRecords: {} as Record, selectedEssentialIds: [] as string[], + selectedSpecialIds: [] as string[], selectedUpdateIds: [] as string[], logs: [] as LogEntry[], refreshTimer: null as ReturnType | null, @@ -33,15 +34,20 @@ export const useTaskRuntimeStore = defineStore('task-runtime', { } }, actions: { - toggleSelection(id: string, type: 'essential' | 'update') { - const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds + toggleSelection(id: string, type: 'essential' | 'special' | 'update') { + const list = type === 'essential' + ? this.selectedEssentialIds + : type === 'special' + ? this.selectedSpecialIds + : this.selectedUpdateIds const index = list.indexOf(id) if (index === -1) list.push(id) else list.splice(index, 1) }, - setSelection(type: 'essential' | 'update', ids: string[]) { + setSelection(type: 'essential' | 'special' | 'update', ids: string[]) { if (type === 'essential') this.selectedEssentialIds = ids + else if (type === 'special') this.selectedSpecialIds = ids else this.selectedUpdateIds = ids }, @@ -148,6 +154,7 @@ export const useTaskRuntimeStore = defineStore('task-runtime', { } this.selectedEssentialIds = this.selectedEssentialIds.filter(item => item !== payload.software_id) + this.selectedSpecialIds = this.selectedSpecialIds.filter(item => item !== payload.software_id) this.selectedUpdateIds = this.selectedUpdateIds.filter(item => item !== payload.software_id) setTimeout(() => { diff --git a/src/store/types.ts b/src/store/types.ts index 0c19813..dbb6f95 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -22,6 +22,7 @@ export interface SoftwareListItem { id: string name: string description?: string + category?: string | null version?: string | null recommended_version?: string | null available_version?: string | null diff --git a/src/views/Essentials.vue b/src/views/Essentials.vue index 3d141f6..717da44 100644 --- a/src/views/Essentials.vue +++ b/src/views/Essentials.vue @@ -50,14 +50,14 @@
-
+

正在读取必备软件列表...

{ - return store.mergedEssentials.filter(s => s.status !== 'installed'); + return store.generalEssentials.filter(s => s.status !== 'installed'); }); const installSelected = () => { const ids = [...store.selectedEssentialIds]; store.startBatch(ids); ids.forEach(id => { - const item = store.mergedEssentials.find(s => s.id === id); + const item = store.generalEssentials.find(s => s.id === id); if (item) { store.install(id, item.targetVersion); } diff --git a/src/views/OtherSoftware.vue b/src/views/OtherSoftware.vue new file mode 100644 index 0000000..5cfcab0 --- /dev/null +++ b/src/views/OtherSoftware.vue @@ -0,0 +1,293 @@ + + + + +