fix: 标签优先显示策略 auto 模式按服务端优先级配置展示

- auto 模式用合并应用自身(含 sparkApp/apmApp)匹配 priority-config.json,
  不再仅取单一子版,避免漏匹配。
- openDetail 区分「已安装页显式来源强制」与「其他页面按策略」;
  非强制分支清除 forceViewingOrigin 粘性标志,避免跨入口状态泄漏。
- AppDetailModal 按 TagPriorityStrategy 响应式重算默认来源;
  手动切标签仅本次会话预览,不持久。
- storeConfig.loadPriorityConfig 改用 axios(与渲染进程一致),
  修复 Electron 下全局 fetch 失败导致配置为空、auto 全部回退 APM 的问题。
- 首页推荐列表 parseAppList 从 jsonUrl 提取分类,避免 category 误为 unknown。
This commit is contained in:
xiyidaiwa
2026-08-07 18:21:39 +08:00
parent 88499a56f8
commit b3eb1d01d9
6 changed files with 188 additions and 149 deletions
+99 -107
View File
@@ -1,9 +1,17 @@
import { ref } from "vue";
import axios from "axios";
import type { App, StoreMode } from "./typedefinition";
export const APM_STORE_BASE_URL: string =
import.meta.env.VITE_APM_STORE_BASE_URL || "";
// 加载优先级配置专用客户端:与渲染进程其余请求共用同一套 HTTP 客户端,
// 避免 Electron 渲染进程中全局 fetch 失败导致配置始终为空、auto 模式全部回退 APM。
const priorityConfigAxios = axios.create({
baseURL: APM_STORE_BASE_URL,
timeout: 10000,
});
export const APM_STORE_STATS_BASE_URL: string =
import.meta.env.VITE_APM_STORE_STATS_BASE_URL || "";
@@ -77,59 +85,54 @@ export let hasPriorityConfigFile = false;
*/
export async function loadPriorityConfig(arch: string): Promise<void> {
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();
hasPriorityConfigFile = true;
// 支持新旧两种配置格式
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,
);
const configPath = `/${arch}-store/priority-config.json`;
console.log(
`[PriorityConfig] 开始加载优先级配置: ${APM_STORE_BASE_URL}${configPath}`,
);
const response = await priorityConfigAxios.get(configPath);
const config = response.data;
hasPriorityConfigFile = true;
// 支持新旧两种配置格式
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 {
// 配置文件不存在,默认优先 Spark
console.log("[PriorityConfig] 服务器无配置文件,使用默认 Spark 优先");
hasPriorityConfigFile = false;
resetPriorityConfig();
// 旧格式:只配置 sparkPriority(兼容旧配置)
dynamicPriorityConfig = {
sparkPriority: {
pkgnames: config.pkgnames || [],
categories: config.categories || [],
tags: config.tags || [],
},
apmPriority: {
pkgnames: [],
categories: [],
tags: [],
},
};
}
isPriorityConfigLoaded = true;
console.log(
"[PriorityConfig] 已从服务器加载优先级配置:",
JSON.stringify(dynamicPriorityConfig),
);
} catch (error) {
// 获取失败,默认优先 Spark
console.warn("[PriorityConfig] 加载配置失败,使用默认 Spark 优先:", error);
// 获取失败(含 404:服务器无配置文件),默认优先 APM
console.warn(
`[PriorityConfig] 加载配置失败(${APM_STORE_BASE_URL}/${arch}-store/priority-config.json),使用默认 APM 优先:`,
error,
);
hasPriorityConfigFile = false;
resetPriorityConfig();
}
@@ -155,69 +158,58 @@ function resetPriorityConfig(): void {
}
/**
* 获取混合模式下应用的默认优先来源
* 判断优先级(从高到低):
* 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. 默认行为:
* - 没有配置文件 → 优先 Spark
* - 有配置文件但规则为空 → 优先 APM
* 按优先级配置规则判断单个应用标识是否命中「优先来源」。
* 命中则返回 "apm" / "spark",未命中返回 null。
* 规则优先级(从高到低):apmPriority.pkgnames > sparkPriority.pkgnames >
* apmPriority.categories > sparkPriority.categories > apmPriority.tags > sparkPriority.tags
*/
function matchPriority(app: App): "apm" | "spark" | null {
const { sparkPriority, apmPriority } = dynamicPriorityConfig;
if (apmPriority.pkgnames.includes(app.pkgname)) return "apm";
if (sparkPriority.pkgnames.includes(app.pkgname)) return "spark";
if (apmPriority.categories.includes(app.category)) return "apm";
if (sparkPriority.categories.includes(app.category)) return "spark";
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";
}
}
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";
}
}
return null;
}
/**
* 获取混合模式下应用的默认优先来源(即「应用配置优先级」)。
* 对合并应用,依次用顶层标识、sparkApp、apmApp 去匹配优先级配置,
* 任一来源命中即生效(避免顶层 pkgname 取自某一子版而漏掉另一子版的规则)。
* 均不命中时回退到 HYBRID_DEFAULT_PRIORITY(默认优先 APM)。
* @param app 应用信息
* @returns "apm" 或 "spark"
*/
export function getHybridDefaultOrigin(app: App): "apm" | "spark" {
const { sparkPriority, apmPriority } = dynamicPriorityConfig;
const result = matchPriority(app);
if (result) return result;
// 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";
}
// 合并应用:两个子版分别匹配,任一命中即采用
if (app.isMerged) {
if (app.sparkApp) {
const r = matchPriority(app.sparkApp);
if (r) return r;
}
if (app.apmApp) {
const r = matchPriority(app.apmApp);
if (r) return r;
}
}
// 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. 默认行为
if (!hasPriorityConfigFile) {
// 没有配置文件 → 默认优先 Spark
return "spark";
} else {
// 有配置文件但规则为空 → 默认优先 APM
return "apm";
}
// 默认行为:与 HYBRID_DEFAULT_PRIORITY 保持一致(默认优先 APM)
return HYBRID_DEFAULT_PRIORITY;
}