mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-09-20 21:50:11 +08:00
feat: 新增「标签优先显示策略」设置(自动选择/Spark优先/APM优先,默认自动选择)
需求:在应用设置中新增标签优先显示策略配置,进入应用详情页时按所选策略决定
默认展示的来源(Spark/APM)标签;选中的标签即为安装时对应的来源。
实现:
- 新增 src/global/tagPriority.ts:策略('auto'|'spark'|'apm')共享模块,localStorage 持久化,
通过共享 ref 在设置页与详情页间同步(纯前端显示偏好,不触及后端安装脚本)。
- SettingsModal.vue:在「安装设置」中新增三选项分段控件(默认选中「自动选择」),
打开时加载、变更时即时保存。
- AppDetailModal.vue:新增 computeDefaultViewingOrigin,按策略计算默认 viewingOrigin:
自动选择→getHybridDefaultOrigin(应用配置优先级);Spark/APM 优先→优先选中对应标签,
若应用无该来源标签则回退优先级策略;仅有一个来源标签时默认展示该标签;
父组件显式指定的 viewingOrigin 仍具最高优先级。
vue-tsc --noEmit 通过。
This commit is contained in:
@@ -577,6 +577,10 @@ import {
|
|||||||
APM_STORE_BASE_URL,
|
APM_STORE_BASE_URL,
|
||||||
getHybridDefaultOrigin,
|
getHybridDefaultOrigin,
|
||||||
} from "../global/storeConfig";
|
} from "../global/storeConfig";
|
||||||
|
import {
|
||||||
|
getTagPriorityStrategy,
|
||||||
|
type TagPriorityStrategy,
|
||||||
|
} from "../global/tagPriority";
|
||||||
// 评论功能暂时关闭
|
// 评论功能暂时关闭
|
||||||
// import { buildReviewAppKey, buildReviewTags } from "../modules/appIdentity";
|
// import { buildReviewAppKey, buildReviewTags } from "../modules/appIdentity";
|
||||||
import type { App, AppReview, ReviewTags } from "../global/typedefinition";
|
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(
|
watch(
|
||||||
() => props.app,
|
() => props.app,
|
||||||
(newApp: App | null) => {
|
(newApp: App | null) => {
|
||||||
isIconLoaded.value = false;
|
isIconLoaded.value = false;
|
||||||
if (newApp) {
|
if (newApp) {
|
||||||
if (newApp.isMerged) {
|
viewingOrigin.value = computeDefaultViewingOrigin(
|
||||||
// 若父组件已根据安装状态设置了优先展示的版本,则使用
|
newApp,
|
||||||
// 否则根据优先级配置决定默认来源
|
getTagPriorityStrategy(),
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
|
|||||||
@@ -118,6 +118,51 @@
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 标签优先显示策略 -->
|
||||||
|
<div
|
||||||
|
class="rounded-2xl border border-slate-200/60 bg-slate-50/50 px-4 py-4 dark:border-slate-800/60 dark:bg-slate-800/50"
|
||||||
|
>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div
|
||||||
|
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
|
>
|
||||||
|
<i class="fas fa-tags"></i>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p
|
||||||
|
class="text-sm font-medium text-slate-800 dark:text-slate-200"
|
||||||
|
>
|
||||||
|
标签优先显示策略
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
进入应用详情页时默认展示的来源标签
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mt-3 inline-flex w-full overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700"
|
||||||
|
role="radiogroup"
|
||||||
|
aria-label="标签优先显示策略"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="opt in strategyOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
type="button"
|
||||||
|
role="radio"
|
||||||
|
:aria-checked="tagPriorityStrategy === opt.value"
|
||||||
|
class="flex-1 px-2 py-1.5 text-xs font-medium transition-colors"
|
||||||
|
:class="
|
||||||
|
tagPriorityStrategy === opt.value
|
||||||
|
? 'bg-brand text-white'
|
||||||
|
: 'bg-white text-slate-500 hover:bg-slate-100 dark:bg-slate-700 dark:text-slate-400 dark:hover:bg-slate-600'
|
||||||
|
"
|
||||||
|
@click="selectStrategy(opt.value)"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部提示 -->
|
<!-- 底部提示 -->
|
||||||
@@ -135,6 +180,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, onMounted } from "vue";
|
import { ref, watch, onMounted } from "vue";
|
||||||
|
import {
|
||||||
|
getTagPriorityStrategy,
|
||||||
|
setTagPriorityStrategy,
|
||||||
|
type TagPriorityStrategy,
|
||||||
|
} from "../global/tagPriority";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@@ -149,6 +199,26 @@ interface Settings {
|
|||||||
enableCreateDesktop: boolean;
|
enableCreateDesktop: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 标签优先显示策略选项(默认选中「自动选择」)
|
||||||
|
const strategyOptions: Array<{ value: TagPriorityStrategy; label: string }> = [
|
||||||
|
{ value: "auto", label: "自动选择" },
|
||||||
|
{ value: "spark", label: "Spark 优先" },
|
||||||
|
{ value: "apm", label: "APM 优先" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const tagPriorityStrategy = ref<TagPriorityStrategy>("auto");
|
||||||
|
|
||||||
|
// 加载标签优先显示策略(从持久化读取)
|
||||||
|
const loadTagPriority = () => {
|
||||||
|
tagPriorityStrategy.value = getTagPriorityStrategy();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择并保存策略
|
||||||
|
const selectStrategy = (value: TagPriorityStrategy) => {
|
||||||
|
tagPriorityStrategy.value = value;
|
||||||
|
setTagPriorityStrategy(value);
|
||||||
|
};
|
||||||
|
|
||||||
const settings = ref<Settings>({
|
const settings = ref<Settings>({
|
||||||
enableUpdateCheck: true,
|
enableUpdateCheck: true,
|
||||||
enableCreateDesktop: true,
|
enableCreateDesktop: true,
|
||||||
@@ -208,6 +278,7 @@ watch(
|
|||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
loadTagPriority();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -215,6 +286,7 @@ watch(
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.show) {
|
if (props.show) {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
loadTagPriority();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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<TagPriorityStrategy>("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 不可用时仅更新内存态,忽略写入失败
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user