diff --git a/src/app/page.tsx b/src/app/page.tsx index cc85931..6f82b77 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -23,6 +23,14 @@ import HttpWarningDialog from '@/components/HttpWarningDialog'; import BannerCarousel from '@/components/BannerCarousel'; import AIChatPanel from '@/components/AIChatPanel'; +// 首页模块配置接口 +interface HomeModule { + id: string; + name: string; + enabled: boolean; + order: number; +} + function HomeClient() { // 移除了 activeTab 状态,收藏夹功能已移到 UserMenu const [hotMovies, setHotMovies] = useState([]); @@ -36,12 +44,57 @@ function HomeClient() { const [loading, setLoading] = useState(true); const { announcement } = useSite(); + // 首页模块配置状态 + const [homeModules, setHomeModules] = useState([ + { id: 'hotMovies', name: '热门电影', enabled: true, order: 0 }, + { id: 'hotDuanju', name: '热播短剧', enabled: true, order: 1 }, + { id: 'bangumiCalendar', name: '新番放送', enabled: true, order: 2 }, + { id: 'hotTvShows', name: '热门剧集', enabled: true, order: 3 }, + { id: 'hotVarietyShows', name: '热门综艺', enabled: true, order: 4 }, + { id: 'upcomingContent', name: '即将上映', enabled: true, order: 5 }, + ]); + const [showAnnouncement, setShowAnnouncement] = useState(false); const [showHttpWarning, setShowHttpWarning] = useState(true); const [showAIChat, setShowAIChat] = useState(false); const [aiEnabled, setAiEnabled] = useState(false); const [sourceSearchEnabled, setSourceSearchEnabled] = useState(true); + // 加载首页模块配置 + useEffect(() => { + if (typeof window !== 'undefined') { + const savedHomeModules = localStorage.getItem('homeModules'); + if (savedHomeModules) { + try { + setHomeModules(JSON.parse(savedHomeModules)); + } catch (error) { + console.error('解析首页模块配置失败:', error); + } + } + } + }, []); + + // 监听首页模块配置更新事件 + useEffect(() => { + const handleHomeModulesUpdated = () => { + if (typeof window !== 'undefined') { + const savedHomeModules = localStorage.getItem('homeModules'); + if (savedHomeModules) { + try { + setHomeModules(JSON.parse(savedHomeModules)); + } catch (error) { + console.error('解析首页模块配置失败:', error); + } + } + } + }; + + window.addEventListener('homeModulesUpdated', handleHomeModulesUpdated); + return () => { + window.removeEventListener('homeModulesUpdated', handleHomeModulesUpdated); + }; + }, []); + // 检查AI功能是否启用 useEffect(() => { if (typeof window !== 'undefined') { @@ -185,6 +238,300 @@ function HomeClient() { localStorage.setItem('hasSeenAnnouncement', announcement); // 记录已查看弹窗 }; + // 渲染模块的函数 + const renderModule = (moduleId: string) => { + switch (moduleId) { + case 'hotMovies': + return ( +
+
+

+ 热门电影 +

+ + 查看更多 + + +
+ + {loading + ? Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+ )) + : hotMovies.map((movie) => ( +
+ +
+ ))} + +
+ ); + + case 'hotDuanju': + if (hotDuanju.length === 0) return null; + return ( +
+
+

+ 热播短剧 +

+
+ + {loading + ? Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+ )) + : hotDuanju.map((duanju) => ( +
+ +
+ ))} + +
+ ); + + case 'bangumiCalendar': + return ( +
+
+

+ 新番放送 +

+ + 查看更多 + + +
+ + {loading + ? Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+
+
+ )) + : (() => { + 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.filter((anime) => anime.images) || []; + + return todayAnimes.map((anime, index) => ( +
+ +
+ )); + })()} +
+
+ ); + + case 'hotTvShows': + return ( +
+
+

+ 热门剧集 +

+ + 查看更多 + + +
+ + {loading + ? Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+ )) + : hotTvShows.map((tvShow) => ( +
+ +
+ ))} + +
+ ); + + case 'hotVarietyShows': + return ( +
+
+

+ 热门综艺 +

+ + 查看更多 + + +
+ + {loading + ? Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+ )) + : hotVarietyShows.map((varietyShow) => ( +
+ +
+ ))} + +
+ ); + + case 'upcomingContent': + if (upcomingContent.length === 0) return null; + return ( +
+
+

+ 即将上映 +

+
+ + {upcomingContent.map((item) => ( +
+ 0 + ? item.vote_average.toFixed(1) + : '' + } + type={item.media_type === 'tv' ? 'tv' : 'movie'} + from='douban' + releaseDate={item.release_date} + isUpcoming={true} + /> +
+ ))} +
+
+ ); + + default: + return null; + } + }; + return ( {/* TMDB 热门轮播图 */} @@ -225,295 +572,11 @@ function HomeClient() { {/* 继续观看 */} - {/* 热门电影 */} -
-
-

- 热门电影 -

- - 查看更多 - - -
- - {loading - ? // 加载状态显示灰色占位数据 - Array.from({ length: 8 }).map((_, index) => ( -
-
-
-
- )) - : hotMovies.map((movie) => ( -
- -
- ))} - -
- - {/* 热播短剧 */} - {hotDuanju.length > 0 && ( -
-
-

- 热播短剧 -

-
- - {loading - ? Array.from({ length: 8 }).map((_, index) => ( -
-
-
-
- )) - : hotDuanju.map((duanju) => ( -
- -
- ))} - -
- )} - - {/* 每日新番放送 */} -
-
-

- 新番放送 -

- - 查看更多 - - -
- - {loading - ? // 加载状态显示灰色占位数据 - Array.from({ length: 8 }).map((_, index) => ( -
-
-
-
-
-
- )) - : // 展示当前日期的番剧 - (() => { - // 获取当前日期对应的星期 - 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.filter((anime) => anime.images) || []; - - return todayAnimes.map((anime, index) => ( -
- -
- )); - })()} -
-
- - {/* 热门剧集 */} -
-
-

- 热门剧集 -

- - 查看更多 - - -
- - {loading - ? Array.from({ length: 8 }).map((_, index) => ( -
-
-
-
- )) - : hotTvShows.map((tvShow) => ( -
- -
- ))} - -
- - {/* 热门综艺 */} -
-
-

- 热门综艺 -

- - 查看更多 - - -
- - {loading - ? Array.from({ length: 8 }).map((_, index) => ( -
-
-
-
- )) - : hotVarietyShows.map((varietyShow) => ( -
- -
- ))} - -
- - {/* 即将上映/播出 (TMDB) */} - {upcomingContent.length > 0 && ( -
-
-

- 即将上映 -

-
- - {upcomingContent.map((item) => ( -
- 0 - ? item.vote_average.toFixed(1) - : '' - } - type={item.media_type === 'tv' ? 'tv' : 'movie'} - from='douban' - releaseDate={item.release_date} - isUpcoming={true} - /> -
- ))} -
-
- )} + {/* 根据配置动态渲染首页模块 */} + {homeModules + .filter(module => module.enabled) + .sort((a, b) => a.order - b.order) + .map(module => renderModule(module.id))} diff --git a/src/components/TopProgressBar.tsx b/src/components/TopProgressBar.tsx index 0e781e5..68d5a7b 100644 --- a/src/components/TopProgressBar.tsx +++ b/src/components/TopProgressBar.tsx @@ -31,14 +31,14 @@ export default function TopProgressBar() { const originalForward = router.forward; // 拦截 router.push - router.push = function (...args: any[]) { + router.push = function (...args: Parameters) { isNavigatingRef.current = true; NProgress.start(); return originalPush.apply(this, args); }; // 拦截 router.replace - router.replace = function (...args: any[]) { + router.replace = function (...args: Parameters) { isNavigatingRef.current = true; NProgress.start(); return originalReplace.apply(this, args); diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 3dbf8a8..e60fbbf 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -10,8 +10,13 @@ import { Copy, Download, ExternalLink, + Eye, + EyeOff, + Home, KeyRound, LogOut, + MoveDown, + MoveUp, Rss, Settings, Shield, @@ -107,6 +112,26 @@ export const UserMenu: React.FC = () => { const [isUsageSectionOpen, setIsUsageSectionOpen] = useState(false); const [isBufferSectionOpen, setIsBufferSectionOpen] = useState(false); const [isDanmakuSectionOpen, setIsDanmakuSectionOpen] = useState(false); + const [isHomepageSectionOpen, setIsHomepageSectionOpen] = useState(false); + + // 首页模块配置 + interface HomeModule { + id: string; + name: string; + enabled: boolean; + order: number; + } + + const defaultHomeModules: HomeModule[] = [ + { id: 'hotMovies', name: '热门电影', enabled: true, order: 0 }, + { id: 'hotDuanju', name: '热播短剧', enabled: true, order: 1 }, + { id: 'bangumiCalendar', name: '新番放送', enabled: true, order: 2 }, + { id: 'hotTvShows', name: '热门剧集', enabled: true, order: 3 }, + { id: 'hotVarietyShows', name: '热门综艺', enabled: true, order: 4 }, + { id: 'upcomingContent', name: '即将上映', enabled: true, order: 5 }, + ]; + + const [homeModules, setHomeModules] = useState(defaultHomeModules); // 豆瓣数据源选项 const doubanDataSourceOptions = [ @@ -343,6 +368,16 @@ export const UserMenu: React.FC = () => { if (savedNextEpisodePreCache !== null) { setNextEpisodePreCache(savedNextEpisodePreCache === 'true'); } + + // 加载首页模块配置 + const savedHomeModules = localStorage.getItem('homeModules'); + if (savedHomeModules !== null) { + try { + setHomeModules(JSON.parse(savedHomeModules)); + } catch (error) { + console.error('解析首页模块配置失败:', error); + } + } } }, []); @@ -606,6 +641,53 @@ export const UserMenu: React.FC = () => { } }; + // 首页模块配置处理函数 + const handleHomeModuleToggle = (id: string, enabled: boolean) => { + const updatedModules = homeModules.map(module => + module.id === id ? { ...module, enabled } : module + ); + setHomeModules(updatedModules); + if (typeof window !== 'undefined') { + localStorage.setItem('homeModules', JSON.stringify(updatedModules)); + // 触发自定义事件通知首页刷新 + window.dispatchEvent(new CustomEvent('homeModulesUpdated')); + } + }; + + const handleHomeModuleMoveUp = (index: number) => { + if (index === 0) return; + const updatedModules = [...homeModules]; + const temp = updatedModules[index]; + updatedModules[index] = updatedModules[index - 1]; + updatedModules[index - 1] = temp; + // 更新order + updatedModules.forEach((module, idx) => { + module.order = idx; + }); + setHomeModules(updatedModules); + if (typeof window !== 'undefined') { + localStorage.setItem('homeModules', JSON.stringify(updatedModules)); + window.dispatchEvent(new CustomEvent('homeModulesUpdated')); + } + }; + + const handleHomeModuleMoveDown = (index: number) => { + if (index === homeModules.length - 1) return; + const updatedModules = [...homeModules]; + const temp = updatedModules[index]; + updatedModules[index] = updatedModules[index + 1]; + updatedModules[index + 1] = temp; + // 更新order + updatedModules.forEach((module, idx) => { + module.order = idx; + }); + setHomeModules(updatedModules); + if (typeof window !== 'undefined') { + localStorage.setItem('homeModules', JSON.stringify(updatedModules)); + window.dispatchEvent(new CustomEvent('homeModulesUpdated')); + } + }; + // 获取感谢信息 const getThanksInfo = (dataSource: string) => { switch (dataSource) { @@ -649,6 +731,7 @@ export const UserMenu: React.FC = () => { setDoubanImageProxyUrl(defaultDoubanImageProxyUrl); setBufferStrategy('medium'); setNextEpisodePreCache(true); + setHomeModules(defaultHomeModules); if (typeof window !== 'undefined') { localStorage.setItem('defaultAggregateSearch', JSON.stringify(true)); @@ -663,6 +746,8 @@ export const UserMenu: React.FC = () => { localStorage.setItem('doubanImageProxyUrl', defaultDoubanImageProxyUrl); localStorage.setItem('bufferStrategy', 'medium'); localStorage.setItem('nextEpisodePreCache', 'true'); + localStorage.setItem('homeModules', JSON.stringify(defaultHomeModules)); + window.dispatchEvent(new CustomEvent('homeModulesUpdated')); } }; @@ -1513,6 +1598,105 @@ export const UserMenu: React.FC = () => { )} + + {/* 首页设置 */} +
+ + {isHomepageSectionOpen && ( +
+
+

+ 配置首页模块的显示顺序和可见性 +

+
+ + {/* 模块列表 */} +
+ {homeModules.map((module, index) => ( +
+ {/* 左侧:显示/隐藏开关 */} + + + {/* 中间:模块名称 */} +
+ + {module.name} + +
+ + {/* 右侧:上下移动按钮 */} +
+ + +
+
+ ))} +
+ + {/* 恢复默认按钮 */} + + + {/* 提示信息 */} +
+

💡 提示:点击眼睛图标可显示/隐藏模块,使用箭头按钮调整模块顺序

+
+
+ )} +
{/* 底部说明 */}