添加主页功能,支持加载和展示首页数据,包括链接和推荐应用列表

This commit is contained in:
2026-02-25 20:46:28 +08:00
parent b72f5f8da6
commit 21b069d7ef
8 changed files with 311 additions and 32 deletions

View File

@@ -10,7 +10,7 @@
>
<i class="fas fa-bars"></i>
</button>
<TopActions @update="$emit('update')" @list="$emit('list')" />
<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>
@@ -24,12 +24,13 @@
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"
placeholder="搜索应用名 / 包名 / 标签,按回车键搜索"
@keydown.enter="handleSearch"
@focus="handleSearchFocus"
/>
</div>
</div>
</div>
<div class="text-sm text-slate-500 dark:text-slate-400" id="currentCount">
{{ appsCount }} 个应用 · 在任何主流 Linux 发行上安装应用
<div v-if="activeCategory !== 'home'" class="text-sm text-slate-500 dark:text-slate-400" id="currentCount">
{{ appsCount }} 个应用
</div>
</div>
</template>
@@ -48,6 +49,8 @@ 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: "toggle-sidebar"): void;
}>();
@@ -57,6 +60,10 @@ const handleSearch = () => {
emit("update-search", localSearchQuery.value);
};
const handleSearchFocus = () => {
emit("search-focus");
};
watch(
() => props.searchQuery,
(newVal) => {

View File

@@ -30,6 +30,19 @@
<ThemeToggle :theme-mode="themeMode" @toggle="toggleTheme" />
<div class="flex-1 space-y-2 overflow-y-auto scrollbar-muted pr-2">
<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"
:class="
activeCategory === 'home'
? 'border-brand/40 bg-brand/10 text-brand dark:bg-brand/15'
: ''
"
@click="selectCategory('home')"
>
<span>主页</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"

View File

@@ -0,0 +1,85 @@
<template>
<div class="space-y-6">
<div v-if="loading" class="text-center text-slate-500">正在加载首页内容</div>
<div v-else-if="error" class="text-center text-rose-600">{{ error }}</div>
<div v-else>
<div class="grid gap-4 auto-fit-grid">
<a
v-for="link in links"
: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"
:title="link.more"
>
<img :src="computedImgUrl(link.imgUrl)" class="h-20 w-full object-contain" />
<div class="text-base font-semibold text-slate-900">{{ link.name }}</div>
<div class="text-sm text-slate-500">{{ link.more }}</div>
</a>
</div>
<div class="space-y-6 mt-6">
<section v-for="section in lists" :key="section.title">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-slate-900">{{ section.title }}</h3>
</div>
<div class="mt-3 grid gap-4 auto-fit-grid">
<AppCard
v-for="app in section.apps"
:key="app.pkgname"
:app="app"
@open-detail="$emit('open-detail', $event)"
/>
</div>
</section>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import AppCard from "./AppCard.vue";
import { APM_STORE_BASE_URL } from "../global/storeConfig";
const props = defineProps<{
links: Array<any>;
lists: Array<{ title: string; apps: any[] }>;
loading: boolean;
error: string;
}>();
const emit = defineEmits<{
(e: "open-detail", app: any): void;
}>();
const computedImgUrl = (imgUrl: string) => {
if (!imgUrl) return "";
// imgUrl is like /home/links/bbs.png -> join with base
return `${APM_STORE_BASE_URL}/${window.apm_store.arch}${imgUrl}`;
};
const onLinkClick = (link: any) => {
if (link.type === "_blank") {
window.open(link.url, "_blank");
} else {
// open in same page: navigate to url
window.location.href = link.url;
}
};
</script>
<style scoped></style>
<style scoped>
.auto-fit-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* slight gap tuning for small screens */
@media (max-width: 640px) {
.auto-fit-grid {
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
}
</style>

View File

@@ -18,29 +18,33 @@
alt="应用截图预览"
class="max-h-[80vh] w-full rounded-3xl border border-slate-200/40 bg-black/40 object-contain shadow-2xl dark:border-slate-700"
/>
<div
class="absolute inset-x-0 top-4 flex items-center justify-between px-6"
>
<div class="flex gap-3">
<button
type="button"
class="inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/80 text-slate-700 shadow-lg transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-50"
@click="prevScreen"
:disabled="currentScreenIndex === 0"
aria-label="上一张"
>
<i class="fas fa-chevron-left"></i>
</button>
<button
type="button"
class="inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/80 text-slate-700 shadow-lg transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-50"
@click="nextScreen"
:disabled="currentScreenIndex === screenshots.length - 1"
aria-label="下一张"
>
<i class="fas fa-chevron-right"></i>
</button>
</div>
<!-- 左右侧按钮分别放置在图片两侧垂直居中 -->
<div class="absolute left-4 top-1/2 transform -translate-y-1/2">
<button
type="button"
class="inline-flex h-12 w-12 items-center justify-center rounded-full bg-white/80 text-slate-700 shadow-lg transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-50"
@click="prevScreen"
:disabled="currentScreenIndex === 0"
aria-label="上一张"
>
<i class="fas fa-chevron-left"></i>
</button>
</div>
<div class="absolute right-4 top-1/2 transform -translate-y-1/2">
<button
type="button"
class="inline-flex h-12 w-12 items-center justify-center rounded-full bg-white/80 text-slate-700 shadow-lg transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-50"
@click="nextScreen"
:disabled="currentScreenIndex === screenshots.length - 1"
aria-label="下一张"
>
<i class="fas fa-chevron-right"></i>
</button>
</div>
<!-- 仍保留右上关闭按钮 -->
<div class="absolute top-4 right-4">
<button
type="button"
class="inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/80 text-slate-700 shadow-lg transition hover:bg-white"

View File

@@ -9,6 +9,15 @@
<i class="fas fa-sync-alt"></i>
<span>软件更新</span>
</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"
@click="handleSettings"
title="安装设置"
>
<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"
@@ -25,6 +34,7 @@
const emit = defineEmits<{
(e: "update"): void;
(e: "list"): void;
(e: "open-install-settings"): void;
}>();
const handleUpdate = () => {
@@ -34,4 +44,8 @@ const handleUpdate = () => {
const handleList = () => {
emit("list");
};
const handleSettings = () => {
emit("open-install-settings");
};
</script>