feat: douban page

This commit is contained in:
shinya
2025-06-18 13:55:27 +08:00
parent a1bf1839ea
commit 3d199261a3
6 changed files with 432 additions and 64 deletions

View File

@@ -1,6 +1,6 @@
import { Film, Folder, Home, Menu, Search, Star, Tv } from 'lucide-react';
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import {
createContext,
useCallback,
@@ -26,7 +26,7 @@ const Logo = () => (
className='flex items-center justify-center h-16 select-none hover:opacity-80 transition-opacity duration-200'
>
<span className='text-2xl font-bold text-green-600 tracking-tight'>
LibreTV
MoonTV
</span>
</Link>
);
@@ -39,6 +39,7 @@ interface SidebarProps {
const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [isCollapsed, setIsCollapsed] = useState(() => {
if (typeof window === 'undefined') return false;
const saved = localStorage.getItem('sidebarCollapsed');
@@ -47,8 +48,19 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
const [active, setActive] = useState(activePath);
useEffect(() => {
setActive(pathname);
}, [pathname]);
// 优先使用传入的 activePath
if (activePath) {
setActive(activePath);
} else {
// 否则使用当前路径
const getCurrentFullPath = () => {
const queryString = searchParams.toString();
return queryString ? `${pathname}?${queryString}` : pathname;
};
const fullPath = getCurrentFullPath();
setActive(fullPath);
}
}, [activePath, pathname, searchParams]);
const handleToggle = useCallback(() => {
const newState = !isCollapsed;
@@ -66,12 +78,25 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
};
const menuItems = [
{ icon: Tv, label: '电视剧', href: '/tv-shows' },
{ icon: Film, label: '电影', href: '/movies' },
{ icon: Star, label: '豆瓣 Top250', href: '/top250' },
{ icon: Folder, label: '合集', href: '/collections' },
{ icon: Star, label: '热门电影', href: '/douban/hot-movies' },
{ icon: Star, label: '热门电视剧', href: '/douban/hot-tv' },
{
icon: Film,
label: '热门电影',
href: '/douban?type=movie&tag=热门&title=热门电影',
},
{
icon: Tv,
label: '热门剧集',
href: '/douban?type=tv&tag=热门&title=热门剧集',
},
{
icon: Star,
label: '豆瓣 Top250',
href: '/douban?type=movie&tag=top250&title=豆瓣 Top250',
},
{ icon: Folder, label: '美剧', href: '/douban?type=tv&tag=美剧' },
{ icon: Folder, label: '韩剧', href: '/douban?type=tv&tag=韩剧' },
{ icon: Folder, label: '日剧', href: '/douban?type=tv&tag=日剧' },
{ icon: Folder, label: '日漫', href: '/douban?type=tv&tag=日本动画' },
];
return (
@@ -158,28 +183,44 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
{/* 菜单项 */}
<div className='flex-1 overflow-y-auto px-2 pt-4'>
<div className='space-y-1'>
{menuItems.map((item) => (
<Link
key={item.href}
href={item.href}
onClick={() => setActive(item.href)}
data-active={active === item.href}
className={`group flex items-center rounded-lg px-2 py-2 pl-4 text-gray-700 hover:bg-gray-100/30 hover:text-green-600 data-[active=true]:bg-green-500/20 data-[active=true]:text-green-700 transition-colors duration-200 min-h-[40px] ${
isCollapsed
? 'w-full max-w-none mx-0'
: 'max-w-[220px] mx-auto'
} gap-3 justify-start`}
>
<div className='w-4 h-4 flex items-center justify-center'>
<item.icon className='h-4 w-4 text-gray-500 group-hover:text-green-600 group-data-[active=true]:text-green-700' />
</div>
{!isCollapsed && (
<span className='whitespace-nowrap transition-opacity duration-200 opacity-100'>
{item.label}
</span>
)}
</Link>
))}
{menuItems.map((item) => {
// 检查当前路径是否匹配这个菜单项
const typeMatch = item.href.match(/type=([^&]+)/)?.[1];
const tagMatch = item.href.match(/tag=([^&]+)/)?.[1];
// 解码URL以进行正确的比较
const decodedActive = decodeURIComponent(active);
const decodedItemHref = decodeURIComponent(item.href);
const isActive =
decodedActive === decodedItemHref ||
(decodedActive.startsWith('/douban') &&
decodedActive.includes(`type=${typeMatch}`) &&
decodedActive.includes(`tag=${tagMatch}`));
return (
<Link
key={item.href}
href={item.href}
onClick={() => setActive(item.href)}
data-active={isActive}
className={`group flex items-center rounded-lg px-2 py-2 pl-4 text-gray-700 hover:bg-gray-100/30 hover:text-green-600 data-[active=true]:bg-green-500/20 data-[active=true]:text-green-700 transition-colors duration-200 min-h-[40px] ${
isCollapsed
? 'w-full max-w-none mx-0'
: 'max-w-[220px] mx-auto'
} gap-3 justify-start`}
>
<div className='w-4 h-4 flex items-center justify-center'>
<item.icon className='h-4 w-4 text-gray-500 group-hover:text-green-600 group-data-[active=true]:text-green-700' />
</div>
{!isCollapsed && (
<span className='whitespace-nowrap transition-opacity duration-200 opacity-100'>
{item.label}
</span>
)}
</Link>
);
})}
</div>
</div>
</div>