Tune home page layout

This commit is contained in:
leowang 2026-05-24 16:23:19 +08:00
parent 9389be8b97
commit f8eb7cea4c
5 changed files with 1244 additions and 1058 deletions

View File

@ -199,10 +199,7 @@ html {
body { body {
margin: 0; margin: 0;
background: background: rgb(var(--color-background));
radial-gradient(circle at 18% -12%, rgb(var(--color-accent) / 0.1), transparent 34rem),
linear-gradient(180deg, rgb(var(--color-background)) 0%, rgb(var(--color-surface-secondary)) 100%);
background-attachment: fixed;
color: rgb(var(--color-foreground)); color: rgb(var(--color-foreground));
font-family: var(--font-body); font-family: var(--font-body);
font-feature-settings: "tnum" 1, "cv02" 1, "cv03" 1, "cv04" 1; font-feature-settings: "tnum" 1, "cv02" 1, "cv03" 1, "cv04" 1;

View File

@ -2,7 +2,13 @@
'use client'; 'use client';
import { Button, Card, EmptyState, Link as HeroLink, Skeleton } from '@heroui/react'; import {
Button,
Card,
EmptyState,
Link as HeroLink,
Skeleton,
} from '@heroui/react';
import { Suspense, useEffect, useState } from 'react'; import { Suspense, useEffect, useState } from 'react';
import { import {
@ -175,9 +181,10 @@ function HomeClient() {
<CapsuleSwitch <CapsuleSwitch
options={[ options={[
{ label: '首页', value: 'home' }, { label: '首页', value: 'home' },
{ label: '收藏', value: 'favorites' }, { label: '收藏', value: 'favorites' },
]} ]}
active={activeTab} active={activeTab}
compact
onChange={(value) => setActiveTab(value as 'home' | 'favorites')} onChange={(value) => setActiveTab(value as 'home' | 'favorites')}
/> />
</div> </div>
@ -203,7 +210,7 @@ function HomeClient() {
</Button> </Button>
)} )}
</Card.Header> </Card.Header>
<div className='grid justify-start grid-cols-3 gap-x-3 gap-y-14 px-0 sm:grid-cols-[repeat(auto-fill,_minmax(11rem,_1fr))] sm:gap-x-8 sm:gap-y-20'> <div className='grid grid-cols-[repeat(auto-fill,_minmax(96px,_96px))] justify-start gap-x-3 gap-y-10 px-0 pt-2 pb-5 sm:grid-cols-[repeat(auto-fill,_minmax(180px,_180px))] sm:gap-x-5 sm:gap-y-12 sm:pb-3'>
{favoriteItems.map((item) => ( {favoriteItems.map((item) => (
<div key={item.id + item.source} className='w-full'> <div key={item.id + item.source} className='w-full'>
<VideoCard <VideoCard
@ -234,41 +241,37 @@ function HomeClient() {
<Card.Description></Card.Description> <Card.Description></Card.Description>
<Card.Title></Card.Title> <Card.Title></Card.Title>
</div> </div>
<HeroLink <HeroLink href='/douban?type=movie'></HeroLink>
href='/douban?type=movie'
>
</HeroLink>
</Card.Header> </Card.Header>
<ScrollableRow> <ScrollableRow>
{loading {loading
? // 加载状态显示灰色占位数据 ? // 加载状态显示灰色占位数据
Array.from({ length: 8 }).map((_, index) => ( Array.from({ length: 8 }).map((_, index) => (
<div <div
key={index} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<Skeleton className='aspect-[2/3] w-full' /> <Skeleton className='aspect-[2/3] w-full' />
<Skeleton className='mt-3 h-4' /> <Skeleton className='mt-3 h-4' />
</div> </div>
)) ))
: // 显示真实数据 : // 显示真实数据
hotMovies.map((movie, index) => ( hotMovies.map((movie, index) => (
<div <div
key={index} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<VideoCard <VideoCard
from='douban' from='douban'
title={movie.title} title={movie.title}
poster={movie.poster} poster={movie.poster}
douban_id={Number(movie.id)} douban_id={Number(movie.id)}
rate={movie.rate} rate={movie.rate}
year={movie.year} year={movie.year}
type='movie' type='movie'
/> />
</div> </div>
))} ))}
</ScrollableRow> </ScrollableRow>
</Card> </Card>
@ -279,38 +282,36 @@ function HomeClient() {
<Card.Description>Series</Card.Description> <Card.Description>Series</Card.Description>
<Card.Title></Card.Title> <Card.Title></Card.Title>
</div> </div>
<HeroLink href='/douban?type=tv'> <HeroLink href='/douban?type=tv'></HeroLink>
</HeroLink>
</Card.Header> </Card.Header>
<ScrollableRow> <ScrollableRow>
{loading {loading
? // 加载状态显示灰色占位数据 ? // 加载状态显示灰色占位数据
Array.from({ length: 8 }).map((_, index) => ( Array.from({ length: 8 }).map((_, index) => (
<div <div
key={index} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<Skeleton className='aspect-[2/3] w-full' /> <Skeleton className='aspect-[2/3] w-full' />
<Skeleton className='mt-3 h-4' /> <Skeleton className='mt-3 h-4' />
</div> </div>
)) ))
: // 显示真实数据 : // 显示真实数据
hotTvShows.map((show, index) => ( hotTvShows.map((show, index) => (
<div <div
key={index} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<VideoCard <VideoCard
from='douban' from='douban'
title={show.title} title={show.title}
poster={show.poster} poster={show.poster}
douban_id={Number(show.id)} douban_id={Number(show.id)}
rate={show.rate} rate={show.rate}
year={show.year} year={show.year}
/> />
</div> </div>
))} ))}
</ScrollableRow> </ScrollableRow>
</Card> </Card>
@ -321,69 +322,65 @@ function HomeClient() {
<Card.Description>Bangumi</Card.Description> <Card.Description>Bangumi</Card.Description>
<Card.Title></Card.Title> <Card.Title></Card.Title>
</div> </div>
<HeroLink <HeroLink href='/douban?type=anime'></HeroLink>
href='/douban?type=anime'
>
</HeroLink>
</Card.Header> </Card.Header>
<ScrollableRow> <ScrollableRow>
{loading {loading
? // 加载状态显示灰色占位数据 ? // 加载状态显示灰色占位数据
Array.from({ length: 8 }).map((_, index) => ( Array.from({ length: 8 }).map((_, index) => (
<div
key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
>
<Skeleton className='aspect-[2/3] w-full' />
<Skeleton className='mt-3 h-4' />
</div>
))
: // 展示当前日期的番剧
(() => {
// 获取当前日期对应的星期
const today = new Date();
const weekdays = [
'Sun',
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
];
const currentWeekday = weekdays[today.getDay()];
// 找到当前星期对应的番剧数据
const todayAnimes =
bangumiCalendarData.find(
(item) => item.weekday.en === currentWeekday
)?.items || [];
return todayAnimes.map((anime, index) => (
<div <div
key={`${anime.id || 0}-${index}`} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<VideoCard <Skeleton className='aspect-[2/3] w-full' />
from='douban' <Skeleton className='mt-3 h-4' />
title={anime.name_cn || anime.name || '未知标题'}
poster={
anime.images?.large ||
anime.images?.common ||
anime.images?.medium ||
anime.images?.small ||
anime.images?.grid ||
'' // 空字符串,让 VideoCard 组件处理图片加载失败
}
douban_id={anime.id || 0}
rate={anime.rating?.score?.toFixed(1) || ''}
year={anime.air_date?.split('-')?.[0] || ''}
isBangumi={true}
/>
</div> </div>
)); ))
})()} : // 展示当前日期的番剧
(() => {
// 获取当前日期对应的星期
const today = new Date();
const weekdays = [
'Sun',
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
];
const currentWeekday = weekdays[today.getDay()];
// 找到当前星期对应的番剧数据
const todayAnimes =
bangumiCalendarData.find(
(item) => item.weekday.en === currentWeekday
)?.items || [];
return todayAnimes.map((anime, index) => (
<div
key={`${anime.id || 0}-${index}`}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
>
<VideoCard
from='douban'
title={anime.name_cn || anime.name || '未知标题'}
poster={
anime.images?.large ||
anime.images?.common ||
anime.images?.medium ||
anime.images?.small ||
anime.images?.grid ||
'' // 空字符串,让 VideoCard 组件处理图片加载失败
}
douban_id={anime.id || 0}
rate={anime.rating?.score?.toFixed(1) || ''}
year={anime.air_date?.split('-')?.[0] || ''}
isBangumi={true}
/>
</div>
));
})()}
</ScrollableRow> </ScrollableRow>
</Card> </Card>
@ -394,40 +391,36 @@ function HomeClient() {
<Card.Description>Shows</Card.Description> <Card.Description>Shows</Card.Description>
<Card.Title></Card.Title> <Card.Title></Card.Title>
</div> </div>
<HeroLink <HeroLink href='/douban?type=show'></HeroLink>
href='/douban?type=show'
>
</HeroLink>
</Card.Header> </Card.Header>
<ScrollableRow> <ScrollableRow>
{loading {loading
? // 加载状态显示灰色占位数据 ? // 加载状态显示灰色占位数据
Array.from({ length: 8 }).map((_, index) => ( Array.from({ length: 8 }).map((_, index) => (
<div <div
key={index} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<Skeleton className='aspect-[2/3] w-full' /> <Skeleton className='aspect-[2/3] w-full' />
<Skeleton className='mt-3 h-4' /> <Skeleton className='mt-3 h-4' />
</div> </div>
)) ))
: // 显示真实数据 : // 显示真实数据
hotVarietyShows.map((show, index) => ( hotVarietyShows.map((show, index) => (
<div <div
key={index} key={index}
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44' className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
> >
<VideoCard <VideoCard
from='douban' from='douban'
title={show.title} title={show.title}
poster={show.poster} poster={show.poster}
douban_id={Number(show.id)} douban_id={Number(show.id)}
rate={show.rate} rate={show.rate}
year={show.year} year={show.year}
/> />
</div> </div>
))} ))}
</ScrollableRow> </ScrollableRow>
</Card> </Card>
</> </>

View File

@ -7,6 +7,7 @@ interface CapsuleSwitchProps {
active: string; active: string;
onChange: (value: string) => void; onChange: (value: string) => void;
className?: string; className?: string;
compact?: boolean;
} }
const CapsuleSwitch: React.FC<CapsuleSwitchProps> = ({ const CapsuleSwitch: React.FC<CapsuleSwitchProps> = ({
@ -14,13 +15,20 @@ const CapsuleSwitch: React.FC<CapsuleSwitchProps> = ({
active, active,
onChange, onChange,
className, className,
compact = false,
}) => { }) => {
const compactClasses =
'mx-auto w-fit [&_.tabs__list]:w-fit [&_.tabs__tab]:h-9 [&_.tabs__tab]:w-auto [&_.tabs__tab]:min-w-16 [&_.tabs__tab]:px-4 [&_.tabs__tab]:text-sm';
return ( return (
<AppFilterTabs <AppFilterTabs
ariaLabel='内容切换' ariaLabel='内容切换'
className={className} className={[compact ? compactClasses : '', className]
.filter(Boolean)
.join(' ')}
items={options.map((opt) => ({ key: opt.value, label: opt.label }))} items={options.map((opt) => ({ key: opt.value, label: opt.label }))}
selectedKey={active} selectedKey={active}
variant={compact ? 'primary' : 'secondary'}
onSelectionChange={onChange} onSelectionChange={onChange}
/> />
); );

View File

@ -103,7 +103,7 @@ export default function ScrollableRow({
> >
<div <div
ref={containerRef} ref={containerRef}
className='scrollbar-hide flex space-x-4 overflow-x-auto px-1 py-2 pb-12 sm:space-x-5 sm:pb-14' className='scrollbar-hide flex space-x-4 overflow-x-auto px-1 pt-2 pb-5 sm:space-x-5 sm:pb-3'
onScroll={checkScroll} onScroll={checkScroll}
> >
{children} {children}

File diff suppressed because it is too large Load Diff