From d74e05a327c3b9aacf6128f9de9cb802d89c33de Mon Sep 17 00:00:00 2001 From: shenmo Date: Sun, 5 Apr 2026 09:52:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(store):=20=E5=AE=9E=E7=8E=B0=E6=B7=B7?= =?UTF-8?q?=E5=90=88=E4=BB=93=E5=BA=93=E4=BC=98=E5=85=88=E7=BA=A7=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加优先级配置文件支持,根据配置决定默认展示的仓库版本 新增优先级规则匹配逻辑,支持包名、分类和标签匹配 修改应用详情和合并应用的默认来源判断逻辑 --- priority-config.example.json | 27 ++++ src/App.vue | 15 ++- src/components/AppDetailModal.vue | 17 ++- src/global/storeConfig.ts | 201 ++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 priority-config.example.json diff --git a/priority-config.example.json b/priority-config.example.json new file mode 100644 index 00000000..6128b2a6 --- /dev/null +++ b/priority-config.example.json @@ -0,0 +1,27 @@ +{ + "_comment": "优先级配置文件 - 放在服务器 ${arch}-store/ 目录下,文件名为 priority-config.json", + "_comment2": "默认 APM 优先,此文件配置例外规则", + "_comment3": "判断优先级(从高到低):apmPriority.pkgnames > sparkPriority.pkgnames > apmPriority.categories > sparkPriority.categories > apmPriority.tags > sparkPriority.tags > 默认APM", + "_comment4": "如果服务器没有这个文件,所有应用都默认优先 APM", + + "sparkPriority": { + "_comment": "优先使用 Spark 的规则(例外于默认 APM 优先)", + "pkgnames": [], + "categories": [ + "development" + ], + "tags": [ + "prefer-spark" + ] + }, + + "apmPriority": { + "_comment": "优先使用 APM 的规则(例外于 sparkPriority,具有更高优先级)", + "_comment2": "例如:games 分类默认优先 Spark,但其中的某些应用仍优先 APM", + "pkgnames": [], + "categories": [], + "tags": [ + "prefer-apm" + ] + } +} diff --git a/src/App.vue b/src/App.vue index 330af3b3..0b0deec0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -169,6 +169,8 @@ import { currentAppSparkInstalled, currentAppApmInstalled, currentStoreMode, + getHybridDefaultOrigin, + loadPriorityConfig, } from "./global/storeConfig"; import { downloads, @@ -473,12 +475,14 @@ const openDetail = async (app: App | Record) => { if (sparkApp || apmApp) { // 如果两个仓库都有这个应用,创建合并对象 if (sparkApp && apmApp) { + // 根据优先级配置决定默认显示哪个版本 + const defaultOrigin = getHybridDefaultOrigin(sparkApp); finalApp = { - ...sparkApp, // 默认使用 Spark 的信息作为主显示 + ...(defaultOrigin === "spark" ? sparkApp : apmApp), // 根据优先级选择主显示 isMerged: true, sparkApp: sparkApp, apmApp: apmApp, - viewingOrigin: "spark", // 默认查看 Spark 版本 + viewingOrigin: defaultOrigin, // 默认查看优先级高的版本 }; } else if (sparkApp) { finalApp = sparkApp; @@ -559,8 +563,10 @@ const openDetail = async (app: App | Record) => { finalApp.viewingOrigin = "spark"; } else if (apmInstalled && !sparkInstalled) { finalApp.viewingOrigin = "apm"; + } else { + // 若都安装或都未安装,根据优先级配置决定默认展示 + finalApp.viewingOrigin = getHybridDefaultOrigin(finalApp.sparkApp || finalApp); } - // 若都安装或都未安装,默认展示 spark } const displayAppForScreenshots = @@ -1113,6 +1119,9 @@ const loadCategories = async () => { } } categories.value = categoryData; + + // 加载优先级配置(从 spark 目录) + await loadPriorityConfig(arch); } catch (error) { logger.error(`读取 categories 失败: ${error}`); } diff --git a/src/components/AppDetailModal.vue b/src/components/AppDetailModal.vue index f0b1e80b..ed43c2ee 100644 --- a/src/components/AppDetailModal.vue +++ b/src/components/AppDetailModal.vue @@ -441,7 +441,7 @@ import { computed, useAttrs, ref, watch } from "vue"; import axios from "axios"; import { useInstallFeedback, downloads } from "../global/downloadStatus"; -import { APM_STORE_BASE_URL } from "../global/storeConfig"; +import { APM_STORE_BASE_URL, getHybridDefaultOrigin } from "../global/storeConfig"; import type { App } from "../global/typedefinition"; const attrs = useAttrs(); @@ -482,13 +482,20 @@ const closeMetaModal = () => { watch( () => props.app, - (newApp) => { + (newApp: App | null) => { isIconLoaded.value = false; if (newApp) { if (newApp.isMerged) { - // 若父组件已根据安装状态设置了优先展示的版本,则使用;否则默认 Spark - viewingOrigin.value = - newApp.viewingOrigin ?? (newApp.sparkApp ? "spark" : "apm"); + // 若父组件已根据安装状态设置了优先展示的版本,则使用 + // 否则根据优先级配置决定默认来源 + 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; } diff --git a/src/global/storeConfig.ts b/src/global/storeConfig.ts index 7ea92177..1fe503af 100644 --- a/src/global/storeConfig.ts +++ b/src/global/storeConfig.ts @@ -13,3 +13,204 @@ export const currentAppSparkInstalled = ref(false); export const currentAppApmInstalled = ref(false); export const currentStoreMode = ref("hybrid"); + +// 混合模式下默认优先安装的来源(当没有服务器配置或配置获取失败时使用) +export const HYBRID_DEFAULT_PRIORITY: "apm" | "spark" = "apm"; + +// 优先级规则配置接口 +export interface PriorityRules { + // 优先使用 Spark 的规则(例外于默认 APM 优先) + sparkPriority: { + pkgnames: string[]; // 包名列表 + categories: string[]; // 分类列表 + tags: string[]; // 标签列表 + }; + // 优先使用 APM 的规则(例外于 sparkPriority,具有更高优先级) + apmPriority: { + pkgnames: string[]; // 包名列表(即使在 sparkPriority 分类中也优先 APM) + categories: string[]; // 分类列表 + tags: string[]; // 标签列表 + }; +} + +// 动态获取的优先级配置(从服务器加载) +export let dynamicPriorityConfig: PriorityRules = { + sparkPriority: { + pkgnames: [], + categories: [], + tags: [], + }, + apmPriority: { + pkgnames: [], + categories: [], + tags: [], + }, +}; + +// 标记是否已从服务器加载配置 +export let isPriorityConfigLoaded = false; + +/** + * 从服务器加载优先级配置 + * 配置文件路径: ${arch}-store/priority-config.json (放在 spark 下) + * @param arch 架构,如 "amd64" + */ +export async function loadPriorityConfig(arch: string): Promise { + try { + const configUrl = `${APM_STORE_BASE_URL}/${arch}-store/priority-config.json`; + const response = await fetch(configUrl, { + method: "GET", + cache: "no-cache", + }); + + if (response.ok) { + const config = await response.json(); + // 支持新旧两种配置格式 + if (config.sparkPriority || config.apmPriority) { + // 新格式:双向配置 + dynamicPriorityConfig = { + sparkPriority: { + pkgnames: config.sparkPriority?.pkgnames || [], + categories: config.sparkPriority?.categories || [], + tags: config.sparkPriority?.tags || [], + }, + apmPriority: { + pkgnames: config.apmPriority?.pkgnames || [], + categories: config.apmPriority?.categories || [], + tags: config.apmPriority?.tags || [], + }, + }; + } else { + // 旧格式:只配置 sparkPriority(兼容旧配置) + dynamicPriorityConfig = { + sparkPriority: { + pkgnames: config.pkgnames || [], + categories: config.categories || [], + tags: config.tags || [], + }, + apmPriority: { + pkgnames: [], + categories: [], + tags: [], + }, + }; + } + isPriorityConfigLoaded = true; + console.log("[PriorityConfig] 已从服务器加载优先级配置:", dynamicPriorityConfig); + } else { + // 配置文件不存在,使用默认空配置(APM 优先) + console.log("[PriorityConfig] 服务器无配置文件,使用默认 APM 优先"); + resetPriorityConfig(); + } + } catch (error) { + // 获取失败,使用默认空配置 + console.warn("[PriorityConfig] 加载配置失败,使用默认 APM 优先:", error); + resetPriorityConfig(); + } +} + +/** + * 重置优先级配置为默认值 + */ +function resetPriorityConfig(): void { + dynamicPriorityConfig = { + sparkPriority: { + pkgnames: [], + categories: [], + tags: [], + }, + apmPriority: { + pkgnames: [], + categories: [], + tags: [], + }, + }; + isPriorityConfigLoaded = true; +} + +/** + * 检查应用是否匹配规则 + */ +function matchesRule(app: App, pkgnames: string[], categories: string[], tags: string[]): boolean { + // 检查包名 + if (pkgnames.includes(app.pkgname)) { + return true; + } + + // 检查分类 + if (categories.includes(app.category)) { + return true; + } + + // 检查标签(tags 是逗号分隔的字符串) + if (app.tags && tags.length > 0) { + const appTags = app.tags.split(";").map((t) => t.trim().toLowerCase()); + for (const ruleTag of tags) { + if (appTags.includes(ruleTag.toLowerCase())) { + return true; + } + } + } + + return false; +} + +/** + * 获取混合模式下应用的默认优先来源 + * 判断优先级(从高到低): + * 1. apmPriority.pkgnames - 强制优先 APM + * 2. sparkPriority.pkgnames - 强制优先 Spark + * 3. apmPriority.categories - 该分类优先 APM + * 4. sparkPriority.categories - 该分类优先 Spark + * 5. apmPriority.tags - 包含标签优先 APM + * 6. sparkPriority.tags - 包含标签优先 Spark + * 7. 默认 - 优先 APM + * @param app 应用信息 + * @returns "apm" 或 "spark" + */ +export function getHybridDefaultOrigin(app: App): "apm" | "spark" { + const { sparkPriority, apmPriority } = dynamicPriorityConfig; + + // 1. 检查 APM 优先的包名(最高优先级) + if (apmPriority.pkgnames.includes(app.pkgname)) { + return "apm"; + } + + // 2. 检查 Spark 优先的包名 + if (sparkPriority.pkgnames.includes(app.pkgname)) { + return "spark"; + } + + // 3. 检查 APM 优先的分类 + if (apmPriority.categories.includes(app.category)) { + return "apm"; + } + + // 4. 检查 Spark 优先的分类 + if (sparkPriority.categories.includes(app.category)) { + return "spark"; + } + + // 5. 检查 APM 优先的标签 + if (app.tags && apmPriority.tags.length > 0) { + const appTags = app.tags.split(";").map((t) => t.trim().toLowerCase()); + for (const ruleTag of apmPriority.tags) { + if (appTags.includes(ruleTag.toLowerCase())) { + return "apm"; + } + } + } + + // 6. 检查 Spark 优先的标签 + if (app.tags && sparkPriority.tags.length > 0) { + const appTags = app.tags.split(";").map((t) => t.trim().toLowerCase()); + for (const ruleTag of sparkPriority.tags) { + if (appTags.includes(ruleTag.toLowerCase())) { + return "spark"; + } + } + } + + // 7. 默认优先 APM + return "apm"; +}