mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 01:10:16 +08:00
feat(UI): 优化应用加载体验和界面样式
改进应用网格布局的边距设置 在App.vue中改为非阻塞方式加载数据 为侧边栏添加内边距 在HomeView中优化图片加载状态显示和条件渲染
This commit is contained in:
11
src/App.vue
11
src/App.vue
@@ -1068,8 +1068,12 @@ onMounted(async () => {
|
||||
await loadCategories();
|
||||
|
||||
// 分类目录加载后,并行加载主页数据和所有应用列表
|
||||
// 使用非阻塞方式加载,让UI先展示出来
|
||||
loading.value = true;
|
||||
await Promise.all([
|
||||
homeLoading.value = true;
|
||||
|
||||
// 启动加载任务,但不等待它们完成
|
||||
Promise.all([
|
||||
loadHome(),
|
||||
new Promise<void>((resolve) => {
|
||||
loadApps(() => {
|
||||
@@ -1077,7 +1081,10 @@ onMounted(async () => {
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
]);
|
||||
]).then(() => {
|
||||
// 所有数据加载完成后的回调(可选)
|
||||
logger.info("所有应用数据加载完成");
|
||||
});
|
||||
|
||||
// 设置键盘导航
|
||||
document.addEventListener("keydown", (e) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="!loading"
|
||||
class="mt-6 grid gap-5 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
||||
class="grid gap-5 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
||||
>
|
||||
<AppCard
|
||||
v-for="(app, index) in apps"
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="mt-6 grid gap-5 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
||||
class="grid gap-5 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
|
||||
>
|
||||
<div
|
||||
v-for="n in 8"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<StoreModeSwitcher />
|
||||
|
||||
<div class="flex-1 space-y-2 overflow-y-auto scrollbar-muted pr-2">
|
||||
<div class="flex-1 space-y-2 overflow-y-auto scrollbar-muted px-2 py-1">
|
||||
<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"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 初始加载状态 - 只有在完全没有数据时显示 -->
|
||||
<div
|
||||
v-if="loading"
|
||||
v-if="loading && links.length === 0 && lists.length === 0"
|
||||
class="flex flex-col items-center justify-center py-12 text-slate-500 dark:text-slate-400"
|
||||
>
|
||||
<i class="fas fa-spinner fa-spin text-2xl mb-3"></i>
|
||||
@@ -13,9 +14,10 @@
|
||||
>
|
||||
{{ error }}
|
||||
</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">
|
||||
<!-- Links 区域 -->
|
||||
<div v-if="links.length > 0" class="grid gap-4 auto-fit-grid">
|
||||
<a
|
||||
v-for="link in links"
|
||||
:key="link.url + link.name"
|
||||
@@ -24,11 +26,23 @@
|
||||
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
|
||||
:src="computedImgUrl(link)"
|
||||
class="h-20 w-full object-contain"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="h-20 w-full flex items-center justify-center bg-slate-100/50 dark:bg-slate-800/50 rounded-xl overflow-hidden">
|
||||
<img
|
||||
:src="computedImgUrl(link)"
|
||||
class="h-full w-full object-contain"
|
||||
loading="lazy"
|
||||
@load="onImageLoad(link.url + link.name)"
|
||||
@error="onImageError(link.url + link.name)"
|
||||
:class="{ 'opacity-0': !imageLoaded[link.url + link.name], 'opacity-100 transition-opacity duration-300': imageLoaded[link.url + link.name] }"
|
||||
/>
|
||||
<!-- 图片加载占位符 -->
|
||||
<div
|
||||
v-if="!imageLoaded[link.url + link.name]"
|
||||
class="absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
<div class="h-8 w-8 animate-pulse rounded-full bg-slate-200 dark:bg-slate-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-base font-semibold text-slate-900 dark:text-white">
|
||||
{{ link.name }}
|
||||
</div>
|
||||
@@ -38,7 +52,8 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6 mt-6">
|
||||
<!-- Lists 区域 -->
|
||||
<div v-if="lists.length > 0" 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">
|
||||
@@ -62,6 +77,7 @@
|
||||
<script setup lang="ts">
|
||||
import AppCard from "./AppCard.vue";
|
||||
import { APM_STORE_BASE_URL } from "../global/storeConfig";
|
||||
import { reactive } from "vue";
|
||||
import type { HomeLink, HomeList, App } from "../global/typedefinition";
|
||||
|
||||
defineProps<{
|
||||
@@ -75,6 +91,17 @@ defineEmits<{
|
||||
(e: "open-detail", app: App | Record<string, unknown>): void;
|
||||
}>();
|
||||
|
||||
// 图片加载状态跟踪
|
||||
const imageLoaded = reactive<Record<string, boolean>>({});
|
||||
|
||||
const onImageLoad = (key: string) => {
|
||||
imageLoaded[key] = true;
|
||||
};
|
||||
|
||||
const onImageError = (key: string) => {
|
||||
imageLoaded[key] = true; // 即使加载失败也标记为完成,隐藏占位符
|
||||
};
|
||||
|
||||
const computedImgUrl = (link: HomeLink) => {
|
||||
if (!link.imgUrl) return "";
|
||||
const arch = window.apm_store.arch || "amd64";
|
||||
|
||||
Reference in New Issue
Block a user