mirror of https://github.com/djteang/OrangeTV.git
Tune home page layout
This commit is contained in:
parent
9389be8b97
commit
f8eb7cea4c
|
|
@ -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;
|
||||||
|
|
|
||||||
281
src/app/page.tsx
281
src/app/page.tsx
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue