fix: 标签策略启动初始化加固 + 已安装应用过滤/统计性能优化

- 标签优先显示策略 initTagPriorityStrategy 提到 App.vue onMounted 统一调用,
  消除组件挂载顺序导致先读内存默认 auto 的竞态窗口(AppDetailModal 移除重复调用)
- InstalledAppsModal 过滤逻辑合并为单次遍历(保留 hasOrigin 双来源语义)
- 统计 apm/spark/total 改为基于 searchFilteredApps 单次遍历
- canOpenDetail 补充业务含义注释与可读性变量
- 将 App.vue 重构的 9 个 composable 纳入版本管理
- 验证:vue-tsc 0 / eslint 0 / dpkg-buildpackage 打包 spark-store_5.2.1.24-test 成功
This commit is contained in:
xiyidaiwa
2026-08-12 23:16:17 +08:00
parent 5e7d88ecc5
commit d7bdcf3680
13 changed files with 3367 additions and 2672 deletions
+233
View File
@@ -0,0 +1,233 @@
/**
* useDownloads —— 下载队列控制与安装触发。
*
* 从原 App.vue 原样搬移(pendingDownloadRecords / onDetailInstall /
* handleInstallCompleteForDownloadRecord / onDetailRemove / onDetailFavorite /
* pauseDownload / resumeDownload / cancelDownload / retryDownload /
* clearCompletedDownloads / showDownloadDetailModalFunc / closeDownloadDetail /
* openDownloadedApp / installCompleteCallback / watchDownloadsChange 注册),逻辑零改动。
*
* 共享状态来自 useAppState;handleInstall / handleRetry 来自 modules/processInstall;
* openFavoriteSelector 来自 useFavorites。
*/
import type {
App,
DownloadItem,
DownloadResult,
DownloadedAppRecord,
} from "../global/typedefinition";
import type { IpcRendererEvent } from "electron";
import {
downloads,
currentDownload,
showDownloadDetailModal,
currentApp,
} from "./useAppState";
import { handleInstall, handleRetry } from "../modules/processInstall";
import { watchDownloadsChange } from "../global/downloadStatus";
import { buildFavoriteAppKey, parsePackageArch } from "../modules/appIdentity";
import { openFavoriteSelector } from "./useFavorites";
import { recordDownloadedApp } from "../modules/backendApi";
import { isLoggedIn, currentUser } from "../global/authState";
interface PendingDownloadRecord {
userId: number;
appKey: string;
pkgname: string;
name: string;
category: string;
selectedOrigin: "spark" | "apm";
version: string;
packageArch: string;
}
const pendingDownloadRecords = new Map<number, PendingDownloadRecord>();
const onDetailInstall = async (app: App) => {
const initiatingUserId = currentUser.value?.id ?? null;
const download = await handleInstall(app);
if (
!download ||
initiatingUserId === null ||
!isLoggedIn.value ||
currentUser.value?.id !== initiatingUserId
) {
return;
}
pendingDownloadRecords.set(download.id, {
userId: initiatingUserId,
appKey: buildFavoriteAppKey(app),
pkgname: app.pkgname,
name: app.name,
category: app.category,
selectedOrigin: app.origin,
version: app.version,
packageArch: app.arch || parsePackageArch(app.filename),
});
};
const handleInstallCompleteForDownloadRecord = async (
_event: IpcRendererEvent,
result: DownloadResult,
) => {
const pendingRecord = pendingDownloadRecords.get(result.id);
if (!pendingRecord) return;
if (result.success) {
pendingDownloadRecords.delete(result.id);
}
if (
!result.success ||
!isLoggedIn.value ||
currentUser.value?.id !== pendingRecord.userId
) {
return;
}
const downloadRecord: Omit<DownloadedAppRecord, "id" | "downloadedAt"> = {
appKey: pendingRecord.appKey,
pkgname: pendingRecord.pkgname,
name: pendingRecord.name,
category: pendingRecord.category,
selectedOrigin: pendingRecord.selectedOrigin,
version: pendingRecord.version,
packageArch: pendingRecord.packageArch,
};
try {
await recordDownloadedApp(downloadRecord);
} catch (error: unknown) {
console.warn({ err: error }, "记录下载应用失败");
}
};
const onDetailRemove = (app: App) => {
requestUninstallRef(app);
};
const onDetailFavorite = async (app: App) => {
await openFavoriteSelector(app);
};
// TODO: 目前 APM 商店不能暂停下载
const pauseDownload = (id: DownloadItem) => {
const download = downloads.value.find((d) => d.id === id.id);
if (download && download.status === "installing") {
// 'installing' matches type definition, previously 'downloading'
download.status = "paused";
download.logs.push({
time: Date.now(),
message: "下载已暂停",
});
}
};
// TODO: 同理,暂未实现
const resumeDownload = (id: DownloadItem) => {
const download = downloads.value.find((d) => d.id === id.id);
if (download && download.status === "paused") {
download.status = "installing"; // previously 'downloading'
download.logs.push({
time: Date.now(),
message: "继续下载...",
});
// simulateDownload(download); // removed or undefined?
}
};
const cancelDownload = (id: DownloadItem) => {
const index = downloads.value.findIndex((d) => d.id === id.id);
if (index !== -1) {
const download = downloads.value[index];
// 发送到主进程取消
window.ipcRenderer.send("cancel-install", download.id);
download.status = "failed";
download.logs.push({
time: Date.now(),
message: "下载已取消",
});
// 保留在队列中以便用户可以重试或查看日志
}
};
const retryDownload = (id: DownloadItem) => {
const download = downloads.value.find((d) => d.id === id.id);
if (download && download.status === "failed") {
download.status = "queued";
download.progress = 0;
download.downloadedSize = 0;
download.logs.push({
time: Date.now(),
message: "重新开始下载...",
});
handleRetry(download);
}
};
const clearCompletedDownloads = () => {
downloads.value = downloads.value.filter((d) => d.status !== "completed");
};
const showDownloadDetailModalFunc = (download: DownloadItem) => {
currentDownload.value = download;
showDownloadDetailModal.value = true;
};
const closeDownloadDetail = () => {
showDownloadDetailModal.value = false;
currentDownload.value = null;
};
const openDownloadedApp = (pkgname: string, origin?: "spark" | "apm") => {
// const encodedPkg = encodeURIComponent(download.pkgname);
// openApmStoreUrl(`apmstore://launch?pkg=${encodedPkg}`, {
// fallbackText: `打开应用: ${download.pkgname}`
// });
window.ipcRenderer
.invoke("launch-app", { pkgname, origin })
.catch((err) => console.error("启动应用失败 (launch-app):", err));
};
const installCompleteCallback = (pkgname?: string) => {
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname)) {
checkAppInstalledRef(currentApp.value);
}
};
watchDownloadsChange(installCompleteCallback);
// checkAppInstalled 由 useAppDetail 持有,通过注入引用获取,避免循环依赖
let checkAppInstalledRef: (app: App) => void = () => undefined;
export const registerCheckAppInstalled = (fn: (app: App) => void) => {
checkAppInstalledRef = fn;
};
// requestUninstall 由 useInstalledApps 持有,通过注入引用获取,避免循环依赖
let requestUninstallRef: (app: App) => void = () => undefined;
export const registerRequestUninstall = (fn: (app: App) => void) => {
requestUninstallRef = fn;
};
// 登出时清空待下载记录(原 App.vue logout 中的 pendingDownloadRecords.clear())
export const clearPendingDownloadRecords = (): void => {
pendingDownloadRecords.clear();
};
// onDetailInstall 需要被 useFavorites / useAccountSync 调用,此处导出
export {
pendingDownloadRecords,
onDetailInstall,
handleInstallCompleteForDownloadRecord,
onDetailRemove,
onDetailFavorite,
pauseDownload,
resumeDownload,
cancelDownload,
retryDownload,
clearCompletedDownloads,
showDownloadDetailModalFunc,
closeDownloadDetail,
openDownloadedApp,
installCompleteCallback,
};