Files
MoonTVPlus/src/components/MobileBottomNav.tsx

177 lines
4.6 KiB
TypeScript
Raw Normal View History

2025-08-12 21:50:58 +08:00
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
2025-12-06 21:39:37 +08:00
import { Cat, Clover, Film, Home, Radio, Star, Tv, Users } from 'lucide-react';
2025-08-12 21:50:58 +08:00
import Link from 'next/link';
2025-11-30 21:33:05 +08:00
import { usePathname, useSearchParams } from 'next/navigation';
2025-08-12 21:50:58 +08:00
import { useEffect, useState } from 'react';
2025-12-08 10:03:25 +08:00
import { useWatchRoomContextSafe } from './WatchRoomProvider';
2025-08-12 21:50:58 +08:00
interface MobileBottomNavProps {
/**
* 使 usePathname()
*/
activePath?: string;
}
const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
const pathname = usePathname();
2025-11-30 21:33:05 +08:00
const searchParams = useSearchParams();
2025-12-08 10:03:25 +08:00
const watchRoomContext = useWatchRoomContextSafe();
2025-08-12 21:50:58 +08:00
2025-11-30 21:33:05 +08:00
// 直接使用当前路由状态,确保立即响应路由变化
const getCurrentFullPath = () => {
const queryString = searchParams.toString();
return queryString ? `${pathname}?${queryString}` : pathname;
};
const currentActive = activePath ?? getCurrentFullPath();
2025-08-12 21:50:58 +08:00
const [navItems, setNavItems] = useState([
{ icon: Home, label: '首页', href: '/' },
{
icon: Film,
label: '电影',
href: '/douban?type=movie',
},
{
icon: Tv,
label: '剧集',
href: '/douban?type=tv',
},
{
icon: Cat,
label: '动漫',
href: '/douban?type=anime',
},
{
icon: Clover,
label: '综艺',
href: '/douban?type=show',
},
2025-08-24 00:26:48 +08:00
{
icon: Radio,
label: '直播',
href: '/live',
},
2025-08-12 21:50:58 +08:00
]);
useEffect(() => {
2025-08-13 22:07:28 +08:00
const runtimeConfig = (window as any).RUNTIME_CONFIG;
2025-12-08 10:03:25 +08:00
// 基础导航项(不包括观影室)
let items = [
{ icon: Home, label: '首页', href: '/' },
{
icon: Film,
label: '电影',
href: '/douban?type=movie',
},
{
icon: Tv,
label: '剧集',
href: '/douban?type=tv',
},
{
icon: Cat,
label: '动漫',
href: '/douban?type=anime',
},
{
icon: Clover,
label: '综艺',
href: '/douban?type=show',
},
{
icon: Radio,
label: '直播',
href: '/live',
},
];
// 如果启用观影室,添加观影室入口
if (watchRoomContext?.isEnabled) {
items.push({
icon: Users,
label: '观影室',
href: '/watch-room',
});
}
// 添加自定义分类(如果有)
2025-08-13 22:07:28 +08:00
if (runtimeConfig?.CUSTOM_CATEGORIES?.length > 0) {
2025-12-08 10:03:25 +08:00
items.push({
icon: Star,
label: '自定义',
href: '/douban?type=custom',
});
2025-08-13 22:07:28 +08:00
}
2025-12-08 10:03:25 +08:00
setNavItems(items);
}, [watchRoomContext?.isEnabled]);
2025-08-12 21:50:58 +08:00
const isActive = (href: string) => {
const typeMatch = href.match(/type=([^&]+)/)?.[1];
// 解码URL以进行正确的比较
const decodedActive = decodeURIComponent(currentActive);
const decodedItemHref = decodeURIComponent(href);
return (
decodedActive === decodedItemHref ||
(decodedActive.startsWith('/douban') &&
decodedActive.includes(`type=${typeMatch}`))
);
};
return (
<nav
className='md:hidden fixed left-0 right-0 z-[600] bg-white/90 backdrop-blur-xl border-t border-gray-200/50 overflow-hidden dark:bg-gray-900/80 dark:border-gray-700/50'
style={{
/* 紧贴视口底部,同时在内部留出安全区高度 */
bottom: 0,
paddingBottom: 'env(safe-area-inset-bottom)',
minHeight: 'calc(3.5rem + env(safe-area-inset-bottom))',
}}
>
<ul className='flex items-center overflow-x-auto scrollbar-hide'>
{navItems.map((item) => {
const active = isActive(item.href);
return (
<li
key={item.href}
className='flex-shrink-0'
style={{ width: '20vw', minWidth: '20vw' }}
>
<Link
href={item.href}
2025-11-30 21:33:05 +08:00
prefetch={false}
2025-08-12 21:50:58 +08:00
className='flex flex-col items-center justify-center w-full h-14 gap-1 text-xs'
>
<item.icon
2025-08-13 00:30:31 +08:00
className={`h-6 w-6 ${active
2025-08-13 22:07:28 +08:00
? 'text-green-600 dark:text-green-400'
: 'text-gray-500 dark:text-gray-400'
2025-08-13 00:30:31 +08:00
}`}
2025-08-12 21:50:58 +08:00
/>
<span
className={
active
? 'text-green-600 dark:text-green-400'
: 'text-gray-600 dark:text-gray-300'
}
>
{item.label}
</span>
</Link>
</li>
);
})}
</ul>
</nav>
);
};
export default MobileBottomNav;