feat(ui): P3 页面级应用(作品库卡片/空态/加载/设置/次级页)

- 作品库卡片重设计:书脊封面字块 + 衬线书名 + 一句话故事(缺失给占位) +
  元信息行(题材徽章/StatusDot 待审/mono 时间),Card tone=card 浮起消塌陷
- EmptyState 中性化 + inline/card/bare 变体 + eyebrow(向后兼容)
- loading 骨架化(Skeleton 拼作品库结构,motion-safe)
- 设置页拆分 ProvidersSettings 489→198 + 5 个子组件,凭据点用 StatusDot
- 次级页(向导/模板/codex/大纲)排版字阶与圆角 token 收敛
This commit is contained in:
Yaojia Wang
2026-07-11 07:36:32 +02:00
parent d3e9ae972f
commit 67e30a6863
14 changed files with 826 additions and 370 deletions

View File

@@ -1,9 +1,79 @@
import { ThinkingIndicator } from "@/components/ThinkingIndicator";
import { Card } from "@/components/ui/Card";
import { PageContainer } from "@/components/ui/PageContainer";
import { Skeleton } from "@/components/ui/Skeleton";
// 全站加载骨架:与作品库结构近似(标题条 + 工具条 + 卡片网格),
// motion-safe 脉冲,尊重 prefers-reduced-motion。
export default function Loading() {
return (
<main className="flex min-h-screen items-center justify-center bg-bg px-6 py-16">
<ThinkingIndicator label="加载中…" className="text-ink-soft" />
<main className="min-h-screen bg-bg">
<PageContainer>
<div role="status" aria-label="加载中" className="space-y-8">
<span className="sr-only"></span>
<HeaderSkeleton />
<ToolbarSkeleton />
<GridSkeleton />
</div>
</PageContainer>
</main>
);
}
// 标题区:大标题条 + 说明条 + 右侧两枚按钮位。
function HeaderSkeleton() {
return (
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-3">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-72 max-w-full" />
</div>
<div className="flex gap-2">
<Skeleton className="h-9 w-28" />
<Skeleton className="h-9 w-24" />
</div>
</div>
);
}
// 工具条:搜索框 + 三枚控件位。
function ToolbarSkeleton() {
return (
<Card flat className="grid gap-3 p-3 md:grid-cols-[1fr_auto_auto_auto]">
<Skeleton className="h-9 w-full" />
<Skeleton className="h-9 w-28" />
<Skeleton className="h-9 w-28" />
<Skeleton className="h-9 w-20" />
</Card>
);
}
// 卡片网格6 张与作品卡等高的占位卡。
function GridSkeleton() {
return (
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<CardSkeleton key={i} />
))}
</div>
);
}
// 单张作品卡骨架:标题 + 徽章 + 三行简介 + 底部时间。
function CardSkeleton() {
return (
<Card flat className="flex h-full min-h-[176px] flex-col p-6">
<div className="mb-4 flex items-start justify-between gap-3">
<div className="min-w-0 flex-1 space-y-3">
<Skeleton className="h-6 w-3/4" />
<Skeleton className="h-5 w-16 rounded-full" />
</div>
<Skeleton className="h-8 w-8 shrink-0" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
</div>
<Skeleton className="mt-auto h-3 w-28" />
</Card>
);
}