feat: 添加 APM 应用管理功能并优化界面

- 新增 APM 应用管理功能,支持显示已安装应用及其依赖项
- 优化已安装应用列表界面,增加应用图标和名称显示
- 调整顶部操作栏布局,将设置和关于按钮移至搜索框旁
- 修复类型定义,增加 isDependency 字段和更多应用信息
- 改进暗色模式下的界面显示效果
This commit is contained in:
2026-03-24 20:47:55 +08:00
parent 480a7f3b77
commit 7ff079276e
13 changed files with 326 additions and 158 deletions

View File

@@ -33,7 +33,9 @@
class="h-24 w-24 rounded-3xl bg-white p-4 shadow-lg ring-1 ring-slate-900/5 dark:bg-slate-800"
/>
</div>
<h2 class="mb-2 text-2xl font-bold text-slate-900 dark:text-white">
<h2
class="mb-2 text-2xl font-bold text-slate-900 dark:text-white"
>
星火应用商店
</h2>
<p class="mb-4 text-sm text-slate-500 dark:text-slate-400">
@@ -42,16 +44,23 @@
<div
class="mb-6 inline-flex items-center gap-2 rounded-full bg-slate-100 px-4 py-2 dark:bg-slate-800"
>
<span class="text-sm text-slate-600 dark:text-slate-300">版本号</span>
<span class="text-sm text-slate-600 dark:text-slate-300"
>版本号</span
>
<span
class="font-mono text-sm font-semibold text-brand dark:text-brand"
>{{ version }}</span
>
</div>
<p class="mb-6 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
星火应用商店是专为 Linux 设计的应用商店提供丰富的应用资源和便捷的安装体验
<p
class="mb-6 text-sm leading-relaxed text-slate-600 dark:text-slate-400"
>
星火应用商店是专为 Linux
设计的应用商店提供丰富的应用资源和便捷的安装体验
</p>
<div class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400">
<div
class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400"
>
<a
href="https://gitee.com/spark-store-project/spark-store"
target="_blank"

View File

@@ -97,9 +97,7 @@
: 'from-brand to-brand-dark'
"
@click="handleInstall"
:disabled="
installFeedback || isOtherVersionInstalled
"
:disabled="installFeedback || isOtherVersionInstalled"
>
<i
class="fas"
@@ -259,10 +257,7 @@
<script setup lang="ts">
import { computed, useAttrs, ref, watch } from "vue";
import axios from "axios";
import {
useInstallFeedback,
downloads,
} from "../global/downloadStatus";
import { useInstallFeedback, downloads } from "../global/downloadStatus";
import { APM_STORE_BASE_URL } from "../global/storeConfig";
import type { App } from "../global/typedefinition";
@@ -289,7 +284,7 @@ const appPkgname = computed(() => props.app?.pkgname);
const isIconLoaded = ref(false);
const viewingOrigin = ref<"spark" | "apm">("apm");
const viewingOrigin = ref<"spark" | "apm">("spark");
watch(
() => props.app,
@@ -297,9 +292,9 @@ watch(
isIconLoaded.value = false;
if (newApp) {
if (newApp.isMerged) {
// 若父组件已根据安装状态设置了优先展示的版本,则使用;否则默认 APM
// 若父组件已根据安装状态设置了优先展示的版本,则使用;否则默认 Spark
viewingOrigin.value =
newApp.viewingOrigin ?? (newApp.apmApp ? "apm" : "spark");
newApp.viewingOrigin ?? (newApp.sparkApp ? "spark" : "apm");
} else {
viewingOrigin.value = newApp.origin;
}
@@ -348,7 +343,9 @@ const installBtnText = computed(() => {
return "已安装";
}
if (isOtherVersionInstalled.value) {
return viewingOrigin.value === "spark" ? "已安装 APM 版" : "已安装 Spark 版";
return viewingOrigin.value === "spark"
? "已安装 APM 版"
: "已安装 Spark 版";
}
if (installFeedback.value) {
const status = activeDownload.value?.status;

View File

@@ -1,36 +1,45 @@
<template>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-4 lg:flex-row lg:items-center">
<div class="flex items-center gap-3">
<button
type="button"
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 lg:hidden dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
@click="$emit('toggle-sidebar')"
title="切换侧边栏"
>
<i class="fas fa-bars"></i>
</button>
<TopActions
@update="$emit('update')"
@list="$emit('list')"
@open-install-settings="$emit('open-install-settings')"
/>
</div>
<div class="w-full flex-1">
<label for="searchBox" class="sr-only">搜索应用</label>
<div class="relative">
<button
type="button"
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 lg:hidden dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
@click="$emit('toggle-sidebar')"
title="切换侧边栏"
>
<i class="fas fa-bars"></i>
</button>
<div class="flex w-full flex-1 items-center gap-3">
<div class="relative flex-1">
<label for="searchBox" class="sr-only">搜索应用</label>
<i
class="fas fa-search pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"
></i>
<input
id="searchBox"
v-model="localSearchQuery"
class="w-full rounded-2xl border border-slate-200/70 bg-white/80 py-3 pl-12 pr-4 text-sm text-slate-700 shadow-sm outline-none transition placeholder:text-slate-400 focus:border-brand/50 focus:ring-4 focus:ring-brand/10 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-200"
class="w-full rounded-2xl border border-slate-200/70 bg-white/80 py-3 pl-12 pr-24 text-sm text-slate-700 shadow-sm outline-none transition placeholder:text-slate-400 focus:border-brand/50 focus:ring-4 focus:ring-brand/10 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-200"
placeholder="搜索应用名 / 包名 / 标签,按回车键搜索"
@keydown.enter="handleSearch"
@focus="handleSearchFocus"
/>
</div>
<button
type="button"
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
@click="$emit('open-install-settings')"
title="安装设置"
>
<i class="fas fa-cog"></i>
</button>
<button
type="button"
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
@click="$emit('open-about')"
title="关于"
>
<i class="fas fa-info-circle"></i>
</button>
</div>
</div>
<div
@@ -45,20 +54,17 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import TopActions from "./TopActions.vue";
const props = defineProps<{
searchQuery: string;
activeCategory: string;
appsCount: number;
}>();
const emit = defineEmits<{
(e: "update-search", query: string): void;
(e: "update"): void;
(e: "list"): void;
(e: "search-focus"): void;
(e: "open-install-settings"): void;
(e: "open-about"): void;
(e: "toggle-sidebar"): void;
}>();

View File

@@ -89,12 +89,21 @@
<div class="border-t border-slate-200 pt-4 dark:border-slate-800">
<button
v-if="apmAvailable"
type="button"
class="flex w-full items-center gap-3 rounded-2xl border border-transparent px-4 py-3 text-left text-sm font-medium text-slate-600 transition hover:border-brand/30 hover:bg-brand/5 hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-300 dark:hover:bg-slate-800"
@click="openAbout"
@click="$emit('list')"
>
<i class="fas fa-info-circle"></i>
<span>关于</span>
<i class="fas fa-download"></i>
<span>APM 应用管理</span>
</button>
<button
type="button"
class="flex w-full items-center gap-3 rounded-2xl border border-transparent px-4 py-3 text-left text-sm font-medium text-slate-600 transition hover:border-brand/30 hover:bg-brand/5 hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-300 dark:hover:bg-slate-800"
@click="$emit('update')"
>
<i class="fas fa-sync-alt"></i>
<span>软件更新</span>
</button>
</div>
</div>
@@ -110,13 +119,15 @@ defineProps<{
activeCategory: string;
categoryCounts: Record<string, number>;
themeMode: "light" | "dark" | "auto";
apmAvailable: boolean;
}>();
const emit = defineEmits<{
(e: "toggle-theme"): void;
(e: "select-category", category: string): void;
(e: "close"): void;
(e: "open-about"): void;
(e: "list"): void;
(e: "update"): void;
}>();
const toggleTheme = () => {
@@ -126,8 +137,4 @@ const toggleTheme = () => {
const selectCategory = (category: string) => {
emit("select-category", category);
};
const openAbout = () => {
emit("open-about");
};
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class="space-y-8">
<div class="space-y-8">
<div
v-if="loading"
class="flex flex-col items-center justify-center py-12 text-slate-500 dark:text-slate-400"
@@ -21,7 +21,7 @@
:key="link.url + link.name"
:href="link.type === '_blank' ? undefined : link.url"
@click.prevent="onLinkClick(link)"
class="flex flex-col items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm hover:shadow-lg transition"
class="flex flex-col items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm transition hover:shadow-lg dark:border-slate-800/70 dark:bg-slate-900/90"
:title="link.more as string"
>
<img
@@ -29,10 +29,12 @@
class="h-20 w-full object-contain"
loading="lazy"
/>
<div class="text-base font-semibold text-slate-900">
<div class="text-base font-semibold text-slate-900 dark:text-white">
{{ link.name }}
</div>
<div class="text-sm text-slate-500">{{ link.more }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">
{{ link.more }}
</div>
</a>
</div>

View File

@@ -69,32 +69,42 @@
:key="app.pkgname"
class="flex flex-col gap-3 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm dark:border-slate-800/70 dark:bg-slate-900/70 sm:flex-row sm:items-center sm:justify-between"
>
<div>
<div class="flex items-center gap-2">
<p
class="text-base font-semibold text-slate-900 dark:text-white"
>
{{ app.pkgname }}
</p>
<span
v-if="app.flags && app.flags.includes('automatic')"
class="rounded-md bg-rose-100 px-2 py-0.5 text-[11px] font-semibold text-rose-600 dark:bg-rose-500/20 dark:text-rose-400"
>
依赖项
</span>
</div>
<div class="flex items-center gap-3">
<div
class="mt-1 flex flex-wrap items-center gap-2 text-xs text-slate-500 dark:text-slate-400"
v-if="app.icons"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-slate-100 dark:bg-slate-800"
>
<span>{{ app.version }}</span>
<span>·</span>
<span>{{ app.arch }}</span>
<template
v-if="app.flags && !app.flags.includes('automatic')"
<img
v-if="app.icons.startsWith('/')"
:src="`file://${app.icons}`"
class="h-8 w-8 object-contain"
alt=""
/>
<i v-else class="fas fa-cube text-xl text-slate-400"></i>
</div>
<div>
<div class="flex items-center gap-2">
<p
class="text-base font-semibold text-slate-900 dark:text-white"
>
{{ app.name }}
</p>
<span
v-if="app.isDependency"
class="rounded-md bg-rose-100 px-2 py-0.5 text-[11px] font-semibold text-rose-600 dark:bg-rose-500/20 dark:text-rose-400"
>
依赖项
</span>
</div>
<div
class="mt-1 flex flex-wrap items-center gap-2 text-xs text-slate-500 dark:text-slate-400"
>
<span class="font-mono">{{ app.pkgname }}</span>
<span>·</span>
<span>{{ app.flags }}</span>
</template>
<span>{{ app.version }}</span>
<span>·</span>
<span>{{ app.arch }}</span>
</div>
</div>
</div>
<button

View File

@@ -1,5 +1,12 @@
<template>
<div class="flex flex-wrap gap-3">
<div :class="['flex flex-wrap gap-3', $attrs.class]">
<button
v-if="apmAvailable"
type="button"
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-brand-dark px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40"
@click="handleList"
title="已安装应用"
></button>
<button
type="button"
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-brand-dark px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40"
@@ -18,19 +25,19 @@
<i class="fas fa-cog"></i>
<span>安装设置</span>
</button>
<!-- <button
type="button"
class="inline-flex items-center gap-2 rounded-2xl bg-slate-900/90 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-slate-900/40 transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900/40 dark:bg-white/90 dark:text-slate-900"
@click="handleList"
title="启动 apm-installer --list"
>
<i class="fas fa-download"></i>
<span>应用管理</span>
</button> -->
</div>
</template>
<script setup lang="ts">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const props = defineProps<{
apmAvailable: boolean;
}>();
defineOptions({
inheritAttrs: false,
});
const emit = defineEmits<{
(e: "update"): void;
(e: "list"): void;
@@ -44,4 +51,8 @@ const handleUpdate = () => {
const handleSettings = () => {
emit("open-install-settings");
};
const handleList = () => {
emit("list");
};
</script>