From c814c4b13dbfef544c797727d2e3a8f470b71a8c Mon Sep 17 00:00:00 2001 From: shenmo Date: Thu, 16 Jul 2026 10:14:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E6=8C=89=E6=9D=A5=E6=BA=90=E4=BC=98=E5=85=88=E7=BA=A7=E5=8E=BB?= =?UTF-8?q?=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 为首页链接和应用列表添加去重逻辑,spark 来源优先覆盖 apm 2. 优化数据处理方式,使用 Set 和 Map 避免重复数据 3. 更新日志输出的统计数字为去重后的实际数量 --- src/App.vue | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/App.vue b/src/App.vue index 8d1a5e79..45a52024 100644 --- a/src/App.vue +++ b/src/App.vue @@ -45,7 +45,7 @@ /> -
+
@@ -1095,6 +1095,9 @@ const loadHome = async () => { const modes: Array<"spark" | "apm"> = storeFilter.value === "both" ? ["spark", "apm"] : [storeFilter.value]; + // 按名称去重,spark 优先:同名链接 spark 覆盖 apm + const seenNames = new Set(); + for (const mode of modes) { const finalArch = mode === "spark" ? `${arch}-store` : `${arch}-apm`; const base = `${APM_STORE_BASE_URL}/${finalArch}/home`; @@ -1104,11 +1107,12 @@ const loadHome = async () => { const res = await fetch(`${base}/homelinks.json`); if (res.ok) { const links = await res.json(); - const taggedLinks = links.map((l: HomeLink) => ({ - ...l, - origin: mode, - })); - homeLinks.value.push(...taggedLinks); + for (const l of links) { + const name = l.Name || l.name || ""; + if (seenNames.has(name)) continue; // 已由更高优先级来源(spark)占据 + seenNames.add(name); + homeLinks.value.push({ ...l, origin: mode }); + } } } catch (e) { console.warn(`Failed to load ${mode} homelinks.json`, e); @@ -1205,7 +1209,8 @@ const loadHomeListApps = async (entryId: string) => { loadingTabs.value = new Set(loadingTabs.value).add(entryId); const arch = window.apm_store.arch || "amd64"; - const loadedApps: App[] = []; + // 按 pkgname 去重,spark 优先(spark 在 modes 数组最前面,先占据 key) + const appMap = new Map(); // 同时加载各来源的推荐列表应用 await Promise.all( @@ -1220,7 +1225,10 @@ const loadHomeListApps = async (entryId: string) => { (await fetchWithRetry[]>(path)) || []; // 直接使用列表数据构建 App 对象,避免为每个应用单独请求 app.json(N+1 问题) // 应用详情会在用户点击时通过 fetchAppFromStore 按需获取 - const apps = rawApps.map((a) => { + for (const a of rawApps) { + const pkgname = a.Pkgname || a.pkgname || ""; + if (!pkgname || appMap.has(pkgname)) continue; // 已由更高优先级来源占据 + const category = a.Category || a.category || "unknown"; let img_urls: string[] = []; @@ -1235,9 +1243,9 @@ const loadHomeListApps = async (entryId: string) => { img_urls = rawImgUrls; } - return { - name: a.Name || a.name || a.Pkgname || a.pkgname || "", - pkgname: a.Pkgname || a.pkgname || "", + appMap.set(pkgname, { + name: a.Name || a.name || pkgname, + pkgname, version: a.Version || "", filename: a.Filename || a.filename || "", torrent_address: a.Torrent_address || "", @@ -1253,23 +1261,22 @@ const loadHomeListApps = async (entryId: string) => { category, origin: mode, currentStatus: "not-installed" as const, - } as App; - }); - loadedApps.push(...apps); + } as App); + } } catch (e) { logger.warn(`加载首页列表 ${entryId} (${mode}) 失败: ${e}`); } }), ); - tabApps.value = { ...tabApps.value, [entryId]: loadedApps }; + tabApps.value = { ...tabApps.value, [entryId]: [...appMap.values()] }; // 移除加载标记 const next = new Set(loadingTabs.value); next.delete(entryId); loadingTabs.value = next; - logger.info(`首页列表 "${entryId}" 加载完成,共 ${loadedApps.length} 个应用`); + logger.info(`首页列表 "${entryId}" 加载完成,共 ${appMap.size} 个应用`); }; // 并行预加载所有侧边栏入口的应用数据,避免点击时才加载导致缓慢