From 88499a56f8784ae37d02e3048cb6c509e0d06ab8 Mon Sep 17 00:00:00 2001 From: xiyidaiwa Date: Fri, 7 Aug 2026 10:45:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E3=80=8C=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E4=BC=98=E5=85=88=E6=98=BE=E7=A4=BA=E7=AD=96=E7=95=A5?= =?UTF-8?q?=E3=80=8D=E8=AE=BE=E7=BD=AE=EF=BC=88=E8=87=AA=E5=8A=A8=E9=80=89?= =?UTF-8?q?=E6=8B=A9/Spark=E4=BC=98=E5=85=88/APM=E4=BC=98=E5=85=88?= =?UTF-8?q?=EF=BC=8C=E9=BB=98=E8=AE=A4=E8=87=AA=E5=8A=A8=E9=80=89=E6=8B=A9?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 需求:在应用设置中新增标签优先显示策略配置,进入应用详情页时按所选策略决定 默认展示的来源(Spark/APM)标签;选中的标签即为安装时对应的来源。 实现: - 新增 src/global/tagPriority.ts:策略('auto'|'spark'|'apm')共享模块,localStorage 持久化, 通过共享 ref 在设置页与详情页间同步(纯前端显示偏好,不触及后端安装脚本)。 - SettingsModal.vue:在「安装设置」中新增三选项分段控件(默认选中「自动选择」), 打开时加载、变更时即时保存。 - AppDetailModal.vue:新增 computeDefaultViewingOrigin,按策略计算默认 viewingOrigin: 自动选择→getHybridDefaultOrigin(应用配置优先级);Spark/APM 优先→优先选中对应标签, 若应用无该来源标签则回退优先级策略;仅有一个来源标签时默认展示该标签; 父组件显式指定的 viewingOrigin 仍具最高优先级。 vue-tsc --noEmit 通过。 --- src/components/AppDetailModal.vue | 52 ++++++++++++++++------ src/components/SettingsModal.vue | 72 +++++++++++++++++++++++++++++++ src/global/tagPriority.ts | 63 +++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 src/global/tagPriority.ts diff --git a/src/components/AppDetailModal.vue b/src/components/AppDetailModal.vue index 27c65ea2..4c334e8b 100644 --- a/src/components/AppDetailModal.vue +++ b/src/components/AppDetailModal.vue @@ -577,6 +577,10 @@ import { APM_STORE_BASE_URL, getHybridDefaultOrigin, } from "../global/storeConfig"; +import { + getTagPriorityStrategy, + type TagPriorityStrategy, +} from "../global/tagPriority"; // 评论功能暂时关闭 // import { buildReviewAppKey, buildReviewTags } from "../modules/appIdentity"; import type { App, AppReview, ReviewTags } from "../global/typedefinition"; @@ -632,25 +636,45 @@ const openWebsite = (url: string) => { } }; +// 根据「标签优先显示策略」与应用的可用来源标签,计算默认展示的来源 +const computeDefaultViewingOrigin = ( + app: App, + strategy: TagPriorityStrategy, +): "spark" | "apm" => { + // 父组件已显式指定展示来源(如从已安装应用页按特定来源打开),优先级最高 + if (app.viewingOrigin) return app.viewingOrigin; + + // 非合并应用只有一个来源标签,直接展示该标签 + if (!app.isMerged) return app.origin; + + // 合并应用:available 为实际存在的来源标签 + const available: Array<"spark" | "apm"> = []; + if (app.sparkApp) available.push("spark"); + if (app.apmApp) available.push("apm"); + if (available.length === 0) return app.origin; // 兜底 + if (available.length === 1) return available[0]; // 仅有一个标签时默认展示该标签 + + // 按策略优先选择对应标签 + const preferred: "spark" | "apm" | null = + strategy === "spark" ? "spark" : strategy === "apm" ? "apm" : null; + if (preferred && available.includes(preferred)) { + return preferred; + } + + // 回退到应用配置的优先级策略(getHybridDefaultOrigin),并约束在可用标签范围内 + const auto = getHybridDefaultOrigin(app.sparkApp ?? app.apmApp ?? app); + return available.includes(auto) ? auto : available[0]; +}; + watch( () => props.app, (newApp: App | null) => { isIconLoaded.value = false; if (newApp) { - if (newApp.isMerged) { - // 若父组件已根据安装状态设置了优先展示的版本,则使用 - // 否则根据优先级配置决定默认来源 - if (newApp.viewingOrigin) { - viewingOrigin.value = newApp.viewingOrigin; - } else if (newApp.sparkApp) { - // 使用优先级配置决定默认来源 - viewingOrigin.value = getHybridDefaultOrigin(newApp.sparkApp); - } else { - viewingOrigin.value = "apm"; - } - } else { - viewingOrigin.value = newApp.origin; - } + viewingOrigin.value = computeDefaultViewingOrigin( + newApp, + getTagPriorityStrategy(), + ); } }, { immediate: true }, diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue index ddb5f80b..7cc55d35 100644 --- a/src/components/SettingsModal.vue +++ b/src/components/SettingsModal.vue @@ -118,6 +118,51 @@ /> + + +
+
+
+ +
+
+

+ 标签优先显示策略 +

+

+ 进入应用详情页时默认展示的来源标签 +

+
+ +
+
+
+
@@ -135,6 +180,11 @@ diff --git a/src/global/tagPriority.ts b/src/global/tagPriority.ts new file mode 100644 index 00000000..eeed429e --- /dev/null +++ b/src/global/tagPriority.ts @@ -0,0 +1,63 @@ +// 标签优先显示策略 +// +// 用于在「应用详情页」决定进入时默认展示的来源(Spark / APM)标签。 +// 该配置为纯前端显示偏好(选中的标签即安装时对应的来源),不涉及后端安装脚本, +// 因此使用 localStorage 持久化并通过共享 ref 在设置页与详情页之间共享。 +// +// 策略取值: +// - "auto" : 自动选择 —— 按应用配置的优先级(getHybridDefaultOrigin)自动选中 +// - "spark" : Spark 优先 —— 默认选中 Spark 标签;若无 Spark 标签则回退优先级策略 +// - "apm" : APM 优先 —— 默认选中 APM 标签;若无 APM 标签则回退优先级策略 +// 注:当应用仅有一个来源标签时,无论策略如何都默认展示该标签。 + +import { ref } from "vue"; + +export type TagPriorityStrategy = "auto" | "spark" | "apm"; + +const STORAGE_KEY = "spark-store-tag-priority-strategy"; + +const VALID_STRATEGIES: TagPriorityStrategy[] = ["auto", "spark", "apm"]; + +// 共享响应式引用:设置页写入、详情页读取,保证跨组件同步 +const strategy = ref("auto"); + +let loaded = false; + +const readFromStorage = (): TagPriorityStrategy => { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored && (VALID_STRATEGIES as string[]).includes(stored)) { + return stored as TagPriorityStrategy; + } + } catch { + // localStorage 不可用时静默降级为默认值 + } + return "auto"; +}; + +// 首次从持久化读取(仅执行一次),可在应用启动时调用 +export const initTagPriorityStrategy = (): void => { + if (loaded) return; + strategy.value = readFromStorage(); + loaded = true; +}; + +// 同步获取当前策略(确保已加载过持久化值) +export const getTagPriorityStrategy = (): TagPriorityStrategy => { + if (!loaded) { + strategy.value = readFromStorage(); + loaded = true; + } + return strategy.value; +}; + +// 设置并持久化策略 +export const setTagPriorityStrategy = (value: TagPriorityStrategy): void => { + if (!(VALID_STRATEGIES as string[]).includes(value)) return; + strategy.value = value; + try { + localStorage.setItem(STORAGE_KEY, value); + } catch { + // localStorage 不可用时仅更新内存态,忽略写入失败 + } +};