From f3728eae004b04a864940d25c5f2b6d6b917708a Mon Sep 17 00:00:00 2001 From: shenmo Date: Sat, 15 Aug 2026 16:46:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(detail):=20=E5=BA=94=E7=94=A8=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=BB=98=E8=AE=A4=E5=B1=95=E5=BC=80=E5=B7=B2=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + src/components/AppDetailModal.vue | 157 +++++++++++++++++------------- src/composables/useAppDetail.ts | 25 +++-- src/global/tagPriority.ts | 1 + 4 files changed, 112 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5eee54a..c19f213a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - `loong64` 架构下尊重 `--no-spark` 启动参数,不再硬编码覆盖。 - `openDownloadedApp` 的 IPC 调用补充 `.catch` 错误处理。 +7. **应用详情默认展开已安装版本**:打开应用详情时,若本地只安装了 Spark / APM 中的某一版本,则默认展开该已装来源(例如 WeChat 默认 APM,但本机只装 Spark 版时默认展开 Spark);若两版都已安装,则按优先级/标签策略展示。 + 相关提交:`1d9d25d6`、`c75938aa`、`39a358bb`、`51473cac`、`cf0e72ff`、`b0d60737`、`d3705ed4`。 ## [1.1.1](https://github.com/elysia-best/apm-app-store/compare/v1.1.0...v1.1.1) (2026-02-17) diff --git a/src/components/AppDetailModal.vue b/src/components/AppDetailModal.vue index 93c791b1..e144fba1 100644 --- a/src/components/AppDetailModal.vue +++ b/src/components/AppDetailModal.vue @@ -501,69 +501,69 @@ + - - + +
-
-
-
- -
-
-

- 打开应用 -

-

- 该应用同时通过 APM 与 Spark 安装,请选择要打开的来源: -

-
+
+
+
-
- - -
-
- +
+

+ 打开应用 +

+

+ 该应用同时通过 APM 与 Spark 安装,请选择要打开的来源: +

+
+ + +
+
+ +
- +
@@ -656,13 +656,15 @@ const openWebsite = (url: string) => { } }; -// 根据「标签优先显示策略」与应用的可用来源标签,计算默认展示的来源 +// 根据「标签优先显示策略」、本地安装状态与应用的可用来源标签,计算默认展示的来源 const computeDefaultViewingOrigin = ( app: App, strategy: TagPriorityStrategy, + sparkInstalled: boolean, + apmInstalled: boolean, ): "spark" | "apm" => { // 父组件显式指定展示来源(如从已安装应用页按特定来源打开)时优先级最高, - // 此时强制覆盖用户标签策略;否则交由下方策略逻辑决定。 + // 此时强制覆盖用户标签策略与本地安装状态;否则交由下方策略逻辑决定。 // 注意:被强制的应用(forceViewingOrigin=true)在详情页保持打开期间, // 即使设置页切换标签策略也不会实时变更其展示来源(已安装来源高于策略,属设计行为)。 if (app.forceViewingOrigin && app.viewingOrigin) return app.viewingOrigin; @@ -677,7 +679,15 @@ const computeDefaultViewingOrigin = ( if (available.length === 0) return app.origin; // 兜底 if (available.length === 1) return available[0]; // 仅有一个标签时默认展示该标签 - // 按策略优先选择对应标签 + // 本地只安装了某一个版本时,优先展开已安装的版本,保证打开应用时能直接使用 + // 已安装来源(例如 WeChat 默认 APM,但本机只装了 Spark 版时默认展开 Spark)。 + const installed: Array<"spark" | "apm"> = []; + if (sparkInstalled && app.sparkApp) installed.push("spark"); + if (apmInstalled && app.apmApp) installed.push("apm"); + if (installed.length === 1) return installed[0]; + + // 都安装 / 都未安装时,继续按用户策略或应用配置的优先级展示: + // - 按策略优先选择对应标签 const preferred: "spark" | "apm" | null = strategy === "spark" ? "spark" : strategy === "apm" ? "apm" : null; if (preferred && available.includes(preferred)) { @@ -690,18 +700,29 @@ const computeDefaultViewingOrigin = ( return available.includes(auto) ? auto : available[0]; }; -// 进入详情页时按「标签优先显示策略」计算默认来源标签。 -// 同时监听 props.app 与共享策略 ref: +// 进入详情页时按「标签优先显示策略」与本地安装状态计算默认来源标签。 +// 同时监听 props.app、共享策略 ref 与安装状态: // - props.app 变化(重开/切换应用)→ 始终按当前策略重算(手动切标签仅本次会话预览,不持久) // - 策略变化(在设置页实时切换)→ 已打开且未手动切标签的详情页立即更新默认标签 +// - 安装状态异步返回(如仅 Spark 已安装)→ 已打开且未手动切标签时自动切换到已安装版本 // 手动点标签页(selectOrigin)只是临时预览:置 manualOverride 标记,本次会话内保持, // 既不被策略变化强行拉回,也不跨重开持久(重开后会按设置重算)。 // 注:initTagPriorityStrategy() 已在 App.vue 启动时统一调用,此处不再重复。 const manualOverride = ref(false); // 用户是否手动切换过标签(本次会话) let lastPkgname: string | null = null; // 上一次计算对应的应用 pkgname,用于识别"新打开/重开" watch( - [() => props.app, tagPriorityStrategyRef], - ([newApp]: [App | null, TagPriorityStrategy]) => { + [ + () => props.app, + tagPriorityStrategyRef, + () => props.sparkInstalled, + () => props.apmInstalled, + ], + ([newApp, strategy, sparkInstalled, apmInstalled]: [ + App | null, + TagPriorityStrategy, + boolean, + boolean, + ]) => { isIconLoaded.value = false; if (!newApp) { // 详情页关闭:清空会话状态,确保下次打开按最新策略重算 @@ -710,20 +731,24 @@ watch( return; } if (newApp.pkgname !== lastPkgname) { - // 新应用(含重开后再次打开同一应用):重置手动标记,按当前策略计算 + // 新应用(含重开后再次打开同一应用):重置手动标记,按当前策略/安装状态计算 lastPkgname = newApp.pkgname; manualOverride.value = false; viewingOrigin.value = computeDefaultViewingOrigin( newApp, - tagPriorityStrategyRef.value, + strategy, + sparkInstalled, + apmInstalled, ); } else if (manualOverride.value || newApp.forceViewingOrigin) { // 同一应用且用户正在手动预览 / 父组件强制来源:保留当前标签,不重算 } else { - // 同一应用、无手动预览、策略变化 → 实时按新策略更新默认标签 + // 同一应用、无手动预览、策略/安装状态变化 → 实时按新状态更新默认标签 viewingOrigin.value = computeDefaultViewingOrigin( newApp, - tagPriorityStrategyRef.value, + strategy, + sparkInstalled, + apmInstalled, ); } }, diff --git a/src/composables/useAppDetail.ts b/src/composables/useAppDetail.ts index 3378e0dc..6a9721a1 100644 --- a/src/composables/useAppDetail.ts +++ b/src/composables/useAppDetail.ts @@ -260,6 +260,7 @@ const openDetail = async (app: App | OpenDetailInput) => { } // 检查 Spark/APM 安装状态,已安装的版本优先展示 + let installedStateKnown = false; if (finalApp.isMerged && (finalApp.sparkApp || finalApp.apmApp)) { const [sparkInstalled, apmInstalled] = await Promise.all([ finalApp.sparkApp @@ -275,13 +276,17 @@ const openDetail = async (app: App | OpenDetailInput) => { }) as Promise) : Promise.resolve(false), ]); + installedStateKnown = true; + // 来源默认展示规则: // 1) 已安装页打开:按安装类型打开。 // - 仅一个来源已安装 → 强制展示该已装来源(无需策略覆盖) - // - 多个来源已安装 → 按「设置的优先标签」打开(策略) - // 2) 其他页面:一律按「设置的优先标签」打开(策略 > 服务端混合默认),不强制。 + // - 多个来源已安装 → 按「设置的优先标签/应用优先级」打开(策略) + // 2) 其他页面: + // - 仅一个来源已安装 → 默认展示已装版本(详情页按安装状态优先,保证可直接打开) + // - 都安装/都未安装 → 按「标签优先显示策略/应用优先级」打开 // 注:forceViewingOrigin 仅用于「已安装页 + 唯一安装来源」这一显式安装类型场景, - // 其余情况都不强制,交由详情页按用户标签策略重算。 + // 其余情况都不强制,交由详情页按安装状态/用户标签策略重算。 let forceOrigin: "spark" | "apm" | undefined = undefined; if (fromInstalled) { const installedOrigins = (app as Record).origins as @@ -310,7 +315,7 @@ const openDetail = async (app: App | OpenDetailInput) => { finalApp.viewingOrigin = forceOrigin; finalApp.forceViewingOrigin = true; } else if (sparkInstalled && !apmInstalled) { - // 仅 Spark 安装(其他页面):默认回退展示已装版本,不强制(仍受用户策略覆盖) + // 仅 Spark 安装(其他页面):默认回退展示已装版本,不强制(详情页会按安装状态优先) finalApp.viewingOrigin = "spark"; } else if (apmInstalled && !sparkInstalled) { finalApp.viewingOrigin = "apm"; @@ -322,6 +327,10 @@ const openDetail = async (app: App | OpenDetailInput) => { finalApp.sparkApp || finalApp, ); } + + // 已拿到安装状态时直接写入全局状态,避免详情页先按“都未安装”闪一下再切到已装版本 + currentAppSparkInstalled.value = sparkInstalled; + currentAppApmInstalled.value = apmInstalled; } const displayAppForScreenshots = @@ -345,9 +354,11 @@ const openDetail = async (app: App | OpenDetailInput) => { }); showModal.value = true; - currentAppSparkInstalled.value = false; - currentAppApmInstalled.value = false; - checkAppInstalled(finalApp); + if (!installedStateKnown) { + currentAppSparkInstalled.value = false; + currentAppApmInstalled.value = false; + checkAppInstalled(finalApp); + } if ( isLoggedIn.value && favoriteFolders.value.length === 0 && diff --git a/src/global/tagPriority.ts b/src/global/tagPriority.ts index e4b23f92..54daf98e 100644 --- a/src/global/tagPriority.ts +++ b/src/global/tagPriority.ts @@ -9,6 +9,7 @@ // - "spark" : Spark 优先 —— 默认选中 Spark 标签;若无 Spark 标签则回退优先级策略 // - "apm" : APM 优先 —— 默认选中 APM 标签;若无 APM 标签则回退优先级策略 // 注:当应用仅有一个来源标签时,无论策略如何都默认展示该标签。 +// 注:若本地仅安装了某一个来源版本,详情页会优先展开已安装版本,此策略在都安装/都未安装时生效。 import { ref } from "vue";