ui: 骨架屏替代全部页面级加载 spinner

- 新增 Skeleton 组件库:Skeleton、SkeletonCircle 基础元素 + 5 个业务骨架
  (SwipeDeck、ProfileCard、RecordItem、BlindboxRoom、BlindboxList、RoomCard)
- 替换 room、profile、blindbox 列表、blindbox 房间、invite 5 个页面的加载态
- 替换 profile 历史记录 / 收藏列表的内联加载 spinner
- 更新 project-conventions.mdc:新增 Loading States 规范,
  要求页面级和列表级加载必须使用骨架屏
This commit is contained in:
2026-02-26 16:11:01 +08:00
parent 798b883250
commit b2b18327cc
7 changed files with 200 additions and 29 deletions
+27 -6
View File
@@ -25,6 +25,7 @@ import {
} from "lucide-react";
import EmptyState from "@/components/EmptyState";
import RestaurantImage from "@/components/RestaurantImage";
import { ProfileCardSkeleton, RecordItemSkeleton } from "@/components/Skeleton";
import { getUserId, getCachedProfile, setCachedProfile, setCachedPreferences, logout } from "@/lib/userId";
import { getAvatarBg, AVATARS } from "@/lib/avatars";
import type { UserProfile, UserPreferences, DecisionRecord, FavoriteRecord, Restaurant } from "@/types";
@@ -256,8 +257,26 @@ export default function ProfilePage() {
if (loading) {
return (
<div className="flex min-h-dvh items-center justify-center bg-background">
<Loader2 size={24} className="animate-spin text-muted" />
<div className="h-dvh bg-background pb-16 overflow-y-auto scrollbar-none">
<nav className="sticky top-0 z-10 flex h-14 items-center gap-3 bg-background/80 px-4 backdrop-blur-sm">
<div className="h-8 w-8" />
<h1 className="flex-1 text-base font-bold text-white"></h1>
</nav>
<div className="mx-auto max-w-sm px-5">
<ProfileCardSkeleton />
<div className="mt-4 rounded-2xl bg-surface p-4 ring-1 ring-border">
<div className="flex flex-col gap-2">
<RecordItemSkeleton />
<RecordItemSkeleton />
</div>
</div>
<div className="mt-4 rounded-2xl bg-surface p-4 ring-1 ring-border">
<div className="flex flex-col gap-2">
<RecordItemSkeleton />
<RecordItemSkeleton />
</div>
</div>
</div>
</div>
);
}
@@ -534,8 +553,9 @@ export default function ProfilePage() {
className="overflow-hidden"
>
{historyLoading ? (
<div className="flex justify-center py-6">
<Loader2 size={18} className="animate-spin text-muted" />
<div className="mt-3 flex flex-col gap-2">
<RecordItemSkeleton />
<RecordItemSkeleton />
</div>
) : history.length === 0 ? (
<EmptyState
@@ -616,8 +636,9 @@ export default function ProfilePage() {
className="overflow-hidden"
>
{favLoading ? (
<div className="flex justify-center py-6">
<Loader2 size={18} className="animate-spin text-muted" />
<div className="mt-3 flex flex-col gap-2">
<RecordItemSkeleton />
<RecordItemSkeleton />
</div>
) : favorites.length === 0 ? (
<EmptyState