feat(update-center): 实现集中式软件更新中心功能

新增更新中心模块,支持管理 APM 和传统 deb 软件更新任务
- 添加更新任务队列管理、状态跟踪和日志记录功能
- 实现更新项忽略配置持久化存储
- 新增更新确认对话框和迁移提示
- 优化主窗口关闭时的任务保护机制
- 添加单元测试覆盖核心逻辑
This commit is contained in:
2026-04-09 08:19:51 +08:00
parent 97bb8e5f59
commit 0b17ada45a
37 changed files with 6389 additions and 342 deletions

182
src/modules/updateCenter.ts Normal file
View File

@@ -0,0 +1,182 @@
import { computed, ref, type ComputedRef, type Ref } from "vue";
import type {
UpdateCenterItem,
UpdateCenterSnapshot,
} from "@/global/typedefinition";
const EMPTY_SNAPSHOT: UpdateCenterSnapshot = {
items: [],
tasks: [],
warnings: [],
hasRunningTasks: false,
};
export interface UpdateCenterStore {
isOpen: Ref<boolean>;
showCloseConfirm: Ref<boolean>;
showMigrationConfirm: Ref<boolean>;
searchQuery: Ref<string>;
selectedTaskKeys: Ref<Set<string>>;
snapshot: Ref<UpdateCenterSnapshot>;
filteredItems: ComputedRef<UpdateCenterItem[]>;
bind: () => void;
unbind: () => void;
open: () => Promise<void>;
refresh: () => Promise<void>;
toggleSelection: (taskKey: string) => void;
getSelectedItems: () => UpdateCenterItem[];
closeNow: () => void;
startSelected: () => Promise<void>;
requestClose: () => void;
}
const matchesSearch = (item: UpdateCenterItem, query: string): boolean => {
if (query.length === 0) {
return true;
}
const normalizedQuery = query.toLowerCase();
return [item.displayName, item.packageName, item.taskKey].some((value) =>
value.toLowerCase().includes(normalizedQuery),
);
};
export const createUpdateCenterStore = (): UpdateCenterStore => {
const isOpen = ref(false);
const showCloseConfirm = ref(false);
const showMigrationConfirm = ref(false);
const searchQuery = ref("");
const selectedTaskKeys = ref(new Set<string>());
const snapshot = ref<UpdateCenterSnapshot>(EMPTY_SNAPSHOT);
const resetSessionState = (): void => {
showCloseConfirm.value = false;
showMigrationConfirm.value = false;
searchQuery.value = "";
selectedTaskKeys.value = new Set();
};
const applySnapshot = (nextSnapshot: UpdateCenterSnapshot): void => {
const selectableTaskKeys = new Set(
nextSnapshot.items
.filter((item) => item.ignored !== true)
.map((item) => item.taskKey),
);
selectedTaskKeys.value = new Set(
[...selectedTaskKeys.value].filter((taskKey) =>
selectableTaskKeys.has(taskKey),
),
);
snapshot.value = nextSnapshot;
};
const filteredItems = computed(() => {
const query = searchQuery.value.trim();
return snapshot.value.items.filter((item) => matchesSearch(item, query));
});
const handleState = (nextSnapshot: UpdateCenterSnapshot): void => {
applySnapshot(nextSnapshot);
};
let isBound = false;
const bind = (): void => {
if (isBound) {
return;
}
window.updateCenter.onState(handleState);
isBound = true;
};
const unbind = (): void => {
if (!isBound) {
return;
}
window.updateCenter.offState(handleState);
isBound = false;
};
const open = async (): Promise<void> => {
resetSessionState();
const nextSnapshot = await window.updateCenter.open();
applySnapshot(nextSnapshot);
isOpen.value = true;
};
const refresh = async (): Promise<void> => {
const nextSnapshot = await window.updateCenter.refresh();
applySnapshot(nextSnapshot);
};
const toggleSelection = (taskKey: string): void => {
const item = snapshot.value.items.find(
(entry) => entry.taskKey === taskKey,
);
if (!item || item.ignored === true) {
return;
}
const nextSelection = new Set(selectedTaskKeys.value);
if (nextSelection.has(taskKey)) {
nextSelection.delete(taskKey);
} else {
nextSelection.add(taskKey);
}
selectedTaskKeys.value = nextSelection;
};
const getSelectedItems = (): UpdateCenterItem[] => {
return snapshot.value.items.filter(
(item) =>
selectedTaskKeys.value.has(item.taskKey) && item.ignored !== true,
);
};
const closeNow = (): void => {
resetSessionState();
isOpen.value = false;
};
const startSelected = async (): Promise<void> => {
const taskKeys = getSelectedItems().map((item) => item.taskKey);
if (taskKeys.length === 0) {
return;
}
await window.updateCenter.start(taskKeys);
};
const requestClose = (): void => {
if (snapshot.value.hasRunningTasks) {
showCloseConfirm.value = true;
return;
}
closeNow();
};
return {
isOpen,
showCloseConfirm,
showMigrationConfirm,
searchQuery,
selectedTaskKeys,
snapshot,
filteredItems,
bind,
unbind,
open,
refresh,
toggleSelection,
getSelectedItems,
closeNow,
startSelected,
requestClose,
};
};