/* eslint-disable no-console,@typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ 'use client'; import { Bell, Check, ChevronDown, ChevronUp, Copy, Download, ExternalLink, Eye, EyeOff, Home, KeyRound, LogOut, Mail, MoveDown, MoveUp, Rss, Settings, Shield, Star, User, X, } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { clearAllDanmakuCache } from '@/lib/danmaku/api'; import { CURRENT_VERSION } from '@/lib/version'; import { UpdateStatus } from '@/lib/version_check'; import { useVersionCheck } from './VersionCheckProvider'; import { VersionPanel } from './VersionPanel'; import { OfflineDownloadPanel } from './OfflineDownloadPanel'; import { NotificationPanel } from './NotificationPanel'; import { FavoritesPanel } from './FavoritesPanel'; interface AuthInfo { username?: string; role?: 'owner' | 'admin' | 'user'; } export const UserMenu: React.FC = () => { const router = useRouter(); const { updateStatus, isChecking } = useVersionCheck(); const [isOpen, setIsOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false); const [isSubscribeOpen, setIsSubscribeOpen] = useState(false); const [isVersionPanelOpen, setIsVersionPanelOpen] = useState(false); const [isOfflineDownloadPanelOpen, setIsOfflineDownloadPanelOpen] = useState(false); const [isNotificationPanelOpen, setIsNotificationPanelOpen] = useState(false); const [isFavoritesPanelOpen, setIsFavoritesPanelOpen] = useState(false); const [isEmailSettingsOpen, setIsEmailSettingsOpen] = useState(false); const [authInfo, setAuthInfo] = useState(null); const [storageType, setStorageType] = useState('localstorage'); const [mounted, setMounted] = useState(false); const [unreadCount, setUnreadCount] = useState(0); // 订阅相关状态 const [subscribeEnabled, setSubscribeEnabled] = useState(false); const [subscribeUrl, setSubscribeUrl] = useState(''); const [copySuccess, setCopySuccess] = useState(false); const [adFilterEnabled, setAdFilterEnabled] = useState(true); // 去广告开关,默认开启 // Body 滚动锁定 - 使用 overflow 方式避免布局问题 useEffect(() => { if (isSettingsOpen || isChangePasswordOpen || isSubscribeOpen || isOfflineDownloadPanelOpen || isEmailSettingsOpen) { const body = document.body; const html = document.documentElement; // 保存原始样式 const originalBodyOverflow = body.style.overflow; const originalHtmlOverflow = html.style.overflow; // 只设置 overflow 来阻止滚动 body.style.overflow = 'hidden'; html.style.overflow = 'hidden'; return () => { // 恢复所有原始样式 body.style.overflow = originalBodyOverflow; html.style.overflow = originalHtmlOverflow; }; } }, [isSettingsOpen, isChangePasswordOpen, isSubscribeOpen, isOfflineDownloadPanelOpen, isEmailSettingsOpen]); // 设置相关状态 const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true); const [doubanProxyUrl, setDoubanProxyUrl] = useState(''); const [enableOptimization, setEnableOptimization] = useState(true); const [fluidSearch, setFluidSearch] = useState(true); const [liveDirectConnect, setLiveDirectConnect] = useState(false); const [tmdbBackdropDisabled, setTmdbBackdropDisabled] = useState(false); const [enableTrailers, setEnableTrailers] = useState(false); const [doubanDataSource, setDoubanDataSource] = useState('cmliussss-cdn-tencent'); const [doubanImageProxyType, setDoubanImageProxyType] = useState('cmliussss-cdn-tencent'); const [doubanImageProxyUrl, setDoubanImageProxyUrl] = useState(''); const [isDoubanDropdownOpen, setIsDoubanDropdownOpen] = useState(false); const [isDoubanImageProxyDropdownOpen, setIsDoubanImageProxyDropdownOpen] = useState(false); const [bufferStrategy, setBufferStrategy] = useState('medium'); const [nextEpisodePreCache, setNextEpisodePreCache] = useState(true); const [nextEpisodeDanmakuPreload, setNextEpisodeDanmakuPreload] = useState(true); const [searchTraditionalToSimplified, setSearchTraditionalToSimplified] = useState(false); // 邮件通知设置 const [userEmail, setUserEmail] = useState(''); const [emailNotifications, setEmailNotifications] = useState(false); const [emailSettingsLoading, setEmailSettingsLoading] = useState(false); const [emailSettingsSaving, setEmailSettingsSaving] = useState(false); // 折叠面板状态 const [isDoubanSectionOpen, setIsDoubanSectionOpen] = useState(true); 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 = [ { value: 'direct', label: '直连(服务器直接请求豆瓣)' }, { value: 'cors-proxy-zwei', label: 'Cors Proxy By Zwei' }, { value: 'cmliussss-cdn-tencent', label: '豆瓣 CDN By CMLiussss(腾讯云)', }, { value: 'cmliussss-cdn-ali', label: '豆瓣 CDN By CMLiussss(阿里云)' }, { value: 'custom', label: '自定义代理' }, ]; // 豆瓣图片代理选项 const doubanImageProxyTypeOptions = [ { value: 'server', label: '服务器代理(由服务器代理请求豆瓣)' }, { value: 'cmliussss-cdn-tencent', label: '豆瓣 CDN By CMLiussss(腾讯云)', }, { value: 'cmliussss-cdn-ali', label: '豆瓣 CDN By CMLiussss(阿里云)' }, { value: 'custom', label: '自定义代理' }, { value: 'direct', label: '直连(浏览器直接请求豆瓣,可能需要浏览器插件才能正常显示)' }, { value: 'img3', label: '豆瓣官方精品 CDN(阿里云,可能需要浏览器插件才能正常显示)' }, ]; // 缓冲策略选项 const bufferStrategyOptions = [ { value: 'low', label: '低缓冲(省流量)' }, { value: 'medium', label: '中缓冲(推荐)' }, { value: 'high', label: '高缓冲(流畅播放)' }, { value: 'ultra', label: '超高缓冲(极速体验)' }, ]; // 修改密码相关状态 const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [passwordLoading, setPasswordLoading] = useState(false); const [passwordError, setPasswordError] = useState(''); // 清除弹幕缓存相关状态 const [isClearingCache, setIsClearingCache] = useState(false); const [clearCacheMessage, setClearCacheMessage] = useState(null); // 确保组件已挂载 useEffect(() => { setMounted(true); }, []); // 加载未读通知数量 const loadUnreadCount = async () => { try { const response = await fetch('/api/notifications'); if (response.ok) { const data = await response.json(); const count = data.unreadCount || 0; setUnreadCount(count); // 同步到全局,让其他 UserMenu 实例也能获取 if (typeof window !== 'undefined') { (window as any).__unreadNotificationCount = count; } } } catch (error) { console.error('加载未读通知数量失败:', error); } }; // 首次加载时检查未读通知数量(使用全局标记避免多个实例重复请求) useEffect(() => { if (typeof window === 'undefined') return; // 检查是否已经有其他实例在加载 const globalWindow = window as any; if (globalWindow.__loadingNotifications) { // 如果正在加载,等待加载完成后获取结果 const checkInterval = setInterval(() => { if (!globalWindow.__loadingNotifications && globalWindow.__unreadNotificationCount !== undefined) { setUnreadCount(globalWindow.__unreadNotificationCount); clearInterval(checkInterval); } }, 100); return () => clearInterval(checkInterval); } // 检查是否已经加载过 if (globalWindow.__unreadNotificationCount !== undefined) { setUnreadCount(globalWindow.__unreadNotificationCount); return; } // 标记正在加载 globalWindow.__loadingNotifications = true; loadUnreadCount().finally(() => { globalWindow.__loadingNotifications = false; }); }, []); // 监听通知更新事件 useEffect(() => { const handleNotificationsUpdated = () => { // 清除缓存,强制重新加载 if (typeof window !== 'undefined') { delete (window as any).__unreadNotificationCount; } loadUnreadCount(); }; window.addEventListener('notificationsUpdated', handleNotificationsUpdated); return () => { window.removeEventListener('notificationsUpdated', handleNotificationsUpdated); }; }, []); // 从运行时配置读取订阅是否启用 useEffect(() => { if (typeof window !== 'undefined') { const enabled = (window as any).RUNTIME_CONFIG?.ENABLE_TVBOX_SUBSCRIBE || false; setSubscribeEnabled(enabled); } }, []); // 懒加载订阅 URL - 只在打开订阅面板时请求 const fetchSubscribeUrl = async () => { try { const currentOrigin = window.location.origin; const response = await fetch(`/api/tvbox/config?origin=${encodeURIComponent(currentOrigin)}&adFilter=${adFilterEnabled}`); if (response.ok) { const data = await response.json(); setSubscribeUrl(data.url); } } catch (error) { console.error('获取订阅URL失败:', error); } }; // 获取认证信息和存储类型 useEffect(() => { if (typeof window !== 'undefined') { const auth = getAuthInfoFromBrowserCookie(); setAuthInfo(auth); const type = (window as any).RUNTIME_CONFIG?.STORAGE_TYPE || 'localstorage'; setStorageType(type); } }, []); // 从 localStorage 读取设置 useEffect(() => { if (typeof window !== 'undefined') { const savedAggregateSearch = localStorage.getItem( 'defaultAggregateSearch' ); if (savedAggregateSearch !== null) { setDefaultAggregateSearch(JSON.parse(savedAggregateSearch)); } const savedDoubanDataSource = localStorage.getItem('doubanDataSource'); const defaultDoubanProxyType = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || 'cmliussss-cdn-tencent'; if (savedDoubanDataSource !== null) { setDoubanDataSource(savedDoubanDataSource); } else if (defaultDoubanProxyType) { setDoubanDataSource(defaultDoubanProxyType); } const savedDoubanProxyUrl = localStorage.getItem('doubanProxyUrl'); const defaultDoubanProxy = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || ''; if (savedDoubanProxyUrl !== null) { setDoubanProxyUrl(savedDoubanProxyUrl); } else if (defaultDoubanProxy) { setDoubanProxyUrl(defaultDoubanProxy); } const savedDoubanImageProxyType = localStorage.getItem( 'doubanImageProxyType' ); const defaultDoubanImageProxyType = (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || 'cmliussss-cdn-tencent'; if (savedDoubanImageProxyType !== null) { setDoubanImageProxyType(savedDoubanImageProxyType); } else if (defaultDoubanImageProxyType) { setDoubanImageProxyType(defaultDoubanImageProxyType); } const savedDoubanImageProxyUrl = localStorage.getItem( 'doubanImageProxyUrl' ); const defaultDoubanImageProxyUrl = (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY || ''; if (savedDoubanImageProxyUrl !== null) { setDoubanImageProxyUrl(savedDoubanImageProxyUrl); } else if (defaultDoubanImageProxyUrl) { setDoubanImageProxyUrl(defaultDoubanImageProxyUrl); } const savedEnableOptimization = localStorage.getItem('enableOptimization'); if (savedEnableOptimization !== null) { setEnableOptimization(JSON.parse(savedEnableOptimization)); } const savedFluidSearch = localStorage.getItem('fluidSearch'); const defaultFluidSearch = (window as any).RUNTIME_CONFIG?.FLUID_SEARCH !== false; if (savedFluidSearch !== null) { setFluidSearch(JSON.parse(savedFluidSearch)); } else if (defaultFluidSearch !== undefined) { setFluidSearch(defaultFluidSearch); } const savedLiveDirectConnect = localStorage.getItem('liveDirectConnect'); if (savedLiveDirectConnect !== null) { setLiveDirectConnect(JSON.parse(savedLiveDirectConnect)); } const savedTmdbBackdropDisabled = localStorage.getItem('tmdb_backdrop_disabled'); if (savedTmdbBackdropDisabled !== null) { setTmdbBackdropDisabled(savedTmdbBackdropDisabled === 'true'); } const savedEnableTrailers = localStorage.getItem('enableTrailers'); if (savedEnableTrailers !== null) { setEnableTrailers(savedEnableTrailers === 'true'); } const savedBufferStrategy = localStorage.getItem('bufferStrategy'); if (savedBufferStrategy !== null) { setBufferStrategy(savedBufferStrategy); } const savedNextEpisodePreCache = localStorage.getItem('nextEpisodePreCache'); if (savedNextEpisodePreCache !== null) { setNextEpisodePreCache(savedNextEpisodePreCache === 'true'); } const savedNextEpisodeDanmakuPreload = localStorage.getItem('nextEpisodeDanmakuPreload'); if (savedNextEpisodeDanmakuPreload !== null) { setNextEpisodeDanmakuPreload(savedNextEpisodeDanmakuPreload === 'true'); } // 加载首页模块配置 const savedHomeModules = localStorage.getItem('homeModules'); if (savedHomeModules !== null) { try { setHomeModules(JSON.parse(savedHomeModules)); } catch (error) { console.error('解析首页模块配置失败:', error); } } // 加载搜索繁体转简体设置 const savedSearchTraditionalToSimplified = localStorage.getItem('searchTraditionalToSimplified'); if (savedSearchTraditionalToSimplified !== null) { setSearchTraditionalToSimplified(savedSearchTraditionalToSimplified === 'true'); } } }, []); // 加载邮件通知设置 const loadEmailSettings = async () => { setEmailSettingsLoading(true); try { const response = await fetch('/api/user/email-settings'); if (response.ok) { const data = await response.json(); setUserEmail(data.email || ''); setEmailNotifications(data.emailNotifications || false); } } catch (error) { console.error('加载邮件设置失败:', error); } finally { setEmailSettingsLoading(false); } }; // 保存邮件通知设置 const handleSaveEmailSettings = async () => { setEmailSettingsSaving(true); try { const response = await fetch('/api/user/email-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: userEmail, emailNotifications, }), }); const messageEl = document.getElementById('email-settings-message'); if (response.ok) { if (messageEl) { messageEl.textContent = '保存成功!'; messageEl.className = 'text-xs text-center text-green-600 dark:text-green-400'; messageEl.classList.remove('hidden'); setTimeout(() => { messageEl.classList.add('hidden'); }, 3000); } } else { const data = await response.json(); if (messageEl) { messageEl.textContent = data.error || '保存失败'; messageEl.className = 'text-xs text-center text-red-600 dark:text-red-400'; messageEl.classList.remove('hidden'); } } } catch (error) { console.error('保存邮件设置失败:', error); const messageEl = document.getElementById('email-settings-message'); if (messageEl) { messageEl.textContent = '保存失败,请重试'; messageEl.className = 'text-xs text-center text-red-600 dark:text-red-400'; messageEl.classList.remove('hidden'); } } finally { setEmailSettingsSaving(false); } }; // 点击外部区域关闭下拉框 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (isDoubanDropdownOpen) { const target = event.target as Element; if (!target.closest('[data-dropdown="douban-datasource"]')) { setIsDoubanDropdownOpen(false); } } }; if (isDoubanDropdownOpen) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [isDoubanDropdownOpen]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (isDoubanImageProxyDropdownOpen) { const target = event.target as Element; if (!target.closest('[data-dropdown="douban-image-proxy"]')) { setIsDoubanImageProxyDropdownOpen(false); } } }; if (isDoubanImageProxyDropdownOpen) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [isDoubanImageProxyDropdownOpen]); const handleMenuClick = () => { setIsOpen(!isOpen); }; const handleCloseMenu = () => { setIsOpen(false); }; const handleLogout = async () => { try { await fetch('/api/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, }); } catch (error) { console.error('注销请求失败:', error); } window.location.href = '/'; }; const handleAdminPanel = () => { router.push('/admin'); }; const handleChangePassword = () => { setIsOpen(false); setIsChangePasswordOpen(true); setNewPassword(''); setConfirmPassword(''); setPasswordError(''); }; const handleCloseChangePassword = () => { setIsChangePasswordOpen(false); setNewPassword(''); setConfirmPassword(''); setPasswordError(''); }; const handleSubscribe = async () => { setIsOpen(false); setIsSubscribeOpen(true); setCopySuccess(false); // 懒加载:打开面板时才请求订阅URL await fetchSubscribeUrl(); }; const handleCloseSubscribe = () => { setIsSubscribeOpen(false); setCopySuccess(false); }; const handleAdFilterToggle = async (checked: boolean) => { setAdFilterEnabled(checked); // 当去广告开关改变时,重新获取订阅URL await fetchSubscribeUrl(); }; const handleCopySubscribeUrl = async () => { try { await navigator.clipboard.writeText(subscribeUrl); setCopySuccess(true); setTimeout(() => { setCopySuccess(false); }, 2000); } catch (error) { console.error('复制失败:', error); } }; const handleSubmitChangePassword = async () => { setPasswordError(''); // 验证密码 if (!newPassword) { setPasswordError('新密码不得为空'); return; } if (newPassword !== confirmPassword) { setPasswordError('两次输入的密码不一致'); return; } setPasswordLoading(true); try { const response = await fetch('/api/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ newPassword, }), }); const data = await response.json(); if (!response.ok) { setPasswordError(data.error || '修改密码失败'); return; } // 修改成功,关闭弹窗并登出 setIsChangePasswordOpen(false); await handleLogout(); } catch (error) { setPasswordError('网络错误,请稍后重试'); } finally { setPasswordLoading(false); } }; const handleSettings = () => { setIsOpen(false); setIsSettingsOpen(true); }; const handleCloseSettings = () => { setIsSettingsOpen(false); }; // 设置相关的处理函数 const handleAggregateToggle = (value: boolean) => { setDefaultAggregateSearch(value); if (typeof window !== 'undefined') { localStorage.setItem('defaultAggregateSearch', JSON.stringify(value)); } }; const handleDoubanProxyUrlChange = (value: string) => { setDoubanProxyUrl(value); if (typeof window !== 'undefined') { localStorage.setItem('doubanProxyUrl', value); } }; const handleOptimizationToggle = (value: boolean) => { setEnableOptimization(value); if (typeof window !== 'undefined') { localStorage.setItem('enableOptimization', JSON.stringify(value)); } }; const handleFluidSearchToggle = (value: boolean) => { setFluidSearch(value); if (typeof window !== 'undefined') { localStorage.setItem('fluidSearch', JSON.stringify(value)); } }; const handleLiveDirectConnectToggle = (value: boolean) => { setLiveDirectConnect(value); if (typeof window !== 'undefined') { localStorage.setItem('liveDirectConnect', JSON.stringify(value)); } }; const handleTmdbBackdropDisabledToggle = (value: boolean) => { setTmdbBackdropDisabled(value); if (typeof window !== 'undefined') { localStorage.setItem('tmdb_backdrop_disabled', String(value)); } }; const handleEnableTrailersToggle = (value: boolean) => { setEnableTrailers(value); if (typeof window !== 'undefined') { localStorage.setItem('enableTrailers', String(value)); } }; const handleDoubanDataSourceChange = (value: string) => { setDoubanDataSource(value); if (typeof window !== 'undefined') { localStorage.setItem('doubanDataSource', value); } }; const handleDoubanImageProxyTypeChange = (value: string) => { setDoubanImageProxyType(value); if (typeof window !== 'undefined') { localStorage.setItem('doubanImageProxyType', value); } }; const handleDoubanImageProxyUrlChange = (value: string) => { setDoubanImageProxyUrl(value); if (typeof window !== 'undefined') { localStorage.setItem('doubanImageProxyUrl', value); } }; const handleBufferStrategyChange = (value: string) => { setBufferStrategy(value); if (typeof window !== 'undefined') { localStorage.setItem('bufferStrategy', value); } }; // 将滑块值转换为策略值 const getBufferStrategyFromSlider = (sliderValue: number): string => { const strategies = ['low', 'medium', 'high', 'ultra']; return strategies[sliderValue] || 'medium'; }; // 将策略值转换为滑块值 const getSliderValueFromStrategy = (strategy: string): number => { const strategies = ['low', 'medium', 'high', 'ultra']; const index = strategies.indexOf(strategy); return index >= 0 ? index : 1; // 默认返回 1 (medium) }; const handleNextEpisodePreCacheToggle = (value: boolean) => { setNextEpisodePreCache(value); if (typeof window !== 'undefined') { localStorage.setItem('nextEpisodePreCache', String(value)); } }; const handleNextEpisodeDanmakuPreloadToggle = (value: boolean) => { setNextEpisodeDanmakuPreload(value); if (typeof window !== 'undefined') { localStorage.setItem('nextEpisodeDanmakuPreload', String(value)); } }; const handleSearchTraditionalToSimplifiedToggle = (value: boolean) => { setSearchTraditionalToSimplified(value); if (typeof window !== 'undefined') { localStorage.setItem('searchTraditionalToSimplified', String(value)); } }; // 首页模块配置处理函数 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) { case 'cors-proxy-zwei': return { text: 'Thanks to @Zwei', url: 'https://github.com/bestzwei', }; case 'cmliussss-cdn-tencent': case 'cmliussss-cdn-ali': return { text: 'Thanks to @CMLiussss', url: 'https://github.com/cmliu', }; default: return null; } }; const handleResetSettings = () => { const defaultDoubanProxyType = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY_TYPE || 'cmliussss-cdn-tencent'; const defaultDoubanProxy = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || ''; const defaultDoubanImageProxyType = (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY_TYPE || 'cmliussss-cdn-tencent'; const defaultDoubanImageProxyUrl = (window as any).RUNTIME_CONFIG?.DOUBAN_IMAGE_PROXY || ''; const defaultFluidSearch = (window as any).RUNTIME_CONFIG?.FLUID_SEARCH !== false; setDefaultAggregateSearch(true); setEnableOptimization(true); setFluidSearch(defaultFluidSearch); setLiveDirectConnect(false); setTmdbBackdropDisabled(false); setEnableTrailers(false); setDoubanProxyUrl(defaultDoubanProxy); setDoubanDataSource(defaultDoubanProxyType); setDoubanImageProxyType(defaultDoubanImageProxyType); setDoubanImageProxyUrl(defaultDoubanImageProxyUrl); setBufferStrategy('medium'); setNextEpisodePreCache(true); setNextEpisodeDanmakuPreload(true); setHomeModules(defaultHomeModules); setSearchTraditionalToSimplified(false); if (typeof window !== 'undefined') { localStorage.setItem('defaultAggregateSearch', JSON.stringify(true)); localStorage.setItem('enableOptimization', JSON.stringify(true)); localStorage.setItem('fluidSearch', JSON.stringify(defaultFluidSearch)); localStorage.setItem('liveDirectConnect', JSON.stringify(false)); localStorage.setItem('tmdb_backdrop_disabled', 'false'); localStorage.setItem('enableTrailers', 'false'); localStorage.setItem('doubanProxyUrl', defaultDoubanProxy); localStorage.setItem('doubanDataSource', defaultDoubanProxyType); localStorage.setItem('doubanImageProxyType', defaultDoubanImageProxyType); localStorage.setItem('doubanImageProxyUrl', defaultDoubanImageProxyUrl); localStorage.setItem('bufferStrategy', 'medium'); localStorage.setItem('nextEpisodePreCache', 'true'); localStorage.setItem('nextEpisodeDanmakuPreload', 'true'); localStorage.setItem('homeModules', JSON.stringify(defaultHomeModules)); localStorage.setItem('searchTraditionalToSimplified', 'false'); window.dispatchEvent(new CustomEvent('homeModulesUpdated')); } }; // 清除弹幕缓存 const handleClearDanmakuCache = async () => { setIsClearingCache(true); setClearCacheMessage(null); try { await clearAllDanmakuCache(); setClearCacheMessage('弹幕缓存已清除成功!'); console.log('弹幕缓存已清除'); // 3秒后自动清除提示 setTimeout(() => { setClearCacheMessage(null); }, 3000); } catch (error) { console.error('清除弹幕缓存失败:', error); setClearCacheMessage('清除失败,请重试'); // 3秒后自动清除提示 setTimeout(() => { setClearCacheMessage(null); }, 3000); } finally { setIsClearingCache(false); } }; // 检查是否显示管理面板按钮 const showAdminPanel = authInfo?.role === 'owner' || authInfo?.role === 'admin'; // 检查是否显示离线下载按钮 const showOfflineDownload = (authInfo?.role === 'owner' || authInfo?.role === 'admin') && typeof window !== 'undefined' && (window as any).RUNTIME_CONFIG?.ENABLE_OFFLINE_DOWNLOAD === true; // 检查是否显示修改密码按钮 const showChangePassword = authInfo?.role !== 'owner' && storageType !== 'localstorage'; // 角色中文映射 const getRoleText = (role?: string) => { switch (role) { case 'owner': return '站长'; case 'admin': return '管理员'; case 'user': return '用户'; default: return ''; } }; // 菜单面板内容 const menuPanel = ( <> {/* 背景遮罩 - 普通菜单无需模糊 */}
{/* 菜单面板 */}
{/* 用户信息区域 */}
当前用户 {/* 邮件设置图标按钮 */}
{getRoleText(authInfo?.role || 'user')}
{authInfo?.username || 'default'}
数据存储: {storageType === 'localstorage' ? '本地' : storageType}
{/* 菜单项 */}
{/* 通知按钮 */} {/* 我的收藏按钮 */} {/* 设置按钮 */} {/* 管理面板按钮 */} {showAdminPanel && ( )} {/* 离线下载按钮 */} {showOfflineDownload && ( )} {/* 修改密码按钮 */} {showChangePassword && ( )} {/* 订阅按钮 */} {subscribeEnabled && ( )} {/* 分割线 */}
{/* 登出按钮 */} {/* 分割线 */}
{/* 版本信息 */}
); // 设置面板内容 const settingsPanel = ( <> {/* 背景遮罩 */}
{ // 只阻止滚动,允许其他触摸事件 e.preventDefault(); }} onWheel={(e) => { // 阻止滚轮滚动 e.preventDefault(); }} style={{ touchAction: 'none', }} /> {/* 设置面板 */}
{/* 内容容器 - 独立的滚动区域 */}
{/* 标题栏 */}

本地设置

{/* 设置项 */}
{/* 豆瓣设置 */}
{isDoubanSectionOpen && (
{/* 豆瓣数据源选择 */}

豆瓣数据代理

选择获取豆瓣数据的方式

{/* 自定义下拉选择框 */} {/* 下拉箭头 */}
{/* 下拉选项列表 */} {isDoubanDropdownOpen && (
{doubanDataSourceOptions.map((option) => ( ))}
)}
{/* 感谢信息 */} {getThanksInfo(doubanDataSource) && (
)}
{/* 豆瓣代理地址设置 - 仅在选择自定义代理时显示 */} {doubanDataSource === 'custom' && (

豆瓣代理地址

自定义代理服务器地址

handleDoubanProxyUrlChange(e.target.value)} />
)} {/* 分割线 */}
{/* 豆瓣图片代理设置 */}

豆瓣图片代理

选择获取豆瓣图片的方式

{/* 自定义下拉选择框 */} {/* 下拉箭头 */}
{/* 下拉选项列表 */} {isDoubanImageProxyDropdownOpen && (
{doubanImageProxyTypeOptions.map((option) => ( ))}
)}
{/* 感谢信息 */} {getThanksInfo(doubanImageProxyType) && (
)}
{/* 豆瓣图片代理地址设置 - 仅在选择自定义代理时显示 */} {doubanImageProxyType === 'custom' && (

豆瓣图片代理地址

自定义图片代理服务器地址

handleDoubanImageProxyUrlChange(e.target.value) } />
)}
)}
{isUsageSectionOpen && (
{/* 默认聚合搜索结果 */}

默认聚合搜索结果

搜索时默认按标题和年份聚合显示结果

{/* 优选和测速 */}

优选和测速

如出现播放器劫持问题可关闭

{/* 流式搜索 */}

流式搜索输出

启用搜索结果实时流式输出,关闭后使用传统一次性搜索

{/* 直播视频浏览器直连 */}

IPTV 视频浏览器直连

开启 IPTV 视频浏览器直连时,需要自备 Allow CORS 插件

{/* 禁用背景图渲染 */}

禁用背景图渲染

关闭播放页面的TMDB背景图显示(需手动刷新页面生效)

{/* 启用预告片 */}

首页预告片

在首页轮播图中显示视频预告片(需刷新页面生效)

{/* 搜索繁体转简体 */}

搜索繁体转简体

搜索时自动将繁体中文转换为简体中文

)}
{/* 缓冲设置 */}
{isBufferSectionOpen && (

调整播放器缓冲策略(仅在播放页面生效)

{/* 缓冲策略 */}

缓冲策略

设置视频缓冲块大小,影响播放流畅度和流量消耗

{/* 滑块控件 */}
{ const sliderValue = parseInt(e.target.value); const strategy = getBufferStrategyFromSlider(sliderValue); handleBufferStrategyChange(strategy); }} className='w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-green-500' style={{ background: `linear-gradient(to right, rgb(34 197 94) 0%, rgb(34 197 94) ${(getSliderValueFromStrategy(bufferStrategy) / 3) * 100}%, rgb(229 231 235) ${(getSliderValueFromStrategy(bufferStrategy) / 3) * 100}%, rgb(229 231 235) 100%)` }} /> {/* 标签显示 */}
低缓冲 中缓冲 高缓冲 超高缓冲
{/* 当前选择的说明 */}
{ bufferStrategyOptions.find( (option) => option.value === bufferStrategy )?.label }
{/* 下集预缓冲 */}

下集预缓冲

播放进度达到90%时,自动预缓冲下一集内容

)}
{/* 弹幕设置 */}
{isDanmakuSectionOpen && (
{/* 下集弹幕预加载 */}

下集弹幕预加载

播放进度达到90%时,自动预加载下一集弹幕

{/* 清除弹幕缓存 */}

弹幕缓存管理

清除所有已缓存的弹幕数据

{/* 成功/失败提示 */} {clearCacheMessage && (
{clearCacheMessage}
)}
)}
{/* 首页设置 */}
{isHomepageSectionOpen && (

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

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

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

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

这些设置保存在本地浏览器中

); // 订阅面板内容 const subscribePanel = ( <> {/* 背景遮罩 */}
{ e.preventDefault(); }} onWheel={(e) => { e.preventDefault(); }} style={{ touchAction: 'none', }} /> {/* 订阅面板 */}
{ e.stopPropagation(); }} style={{ touchAction: 'auto', }} > {/* 标题栏 */}

订阅

{/* 内容 */}
{/* 去广告开关 */}

去广告

开启后自动过滤视频广告

{/* TVBOX订阅 */}

TVBOX订阅

{/* 底部说明 */}

将订阅链接复制到TVBOX应用中使用

); // 修改密码面板内容 const changePasswordPanel = ( <> {/* 背景遮罩 */}
{ // 只阻止滚动,允许其他触摸事件 e.preventDefault(); }} onWheel={(e) => { // 阻止滚轮滚动 e.preventDefault(); }} style={{ touchAction: 'none', }} /> {/* 修改密码面板 */}
{/* 内容容器 - 独立的滚动区域 */}
{ // 阻止事件冒泡到遮罩层,但允许内部滚动 e.stopPropagation(); }} style={{ touchAction: 'auto', // 允许所有触摸操作 }} > {/* 标题栏 */}

修改密码

{/* 表单 */}
{/* 新密码输入 */}
setNewPassword(e.target.value)} disabled={passwordLoading} />
{/* 确认密码输入 */}
setConfirmPassword(e.target.value)} disabled={passwordLoading} />
{/* 错误信息 */} {passwordError && (
{passwordError}
)}
{/* 操作按钮 */}
{/* 底部说明 */}

修改密码后需要重新登录

); // 邮件设置面板内容 const emailSettingsPanel = ( <> {/* 背景遮罩 */}
setIsEmailSettingsOpen(false)} onTouchMove={(e) => { e.preventDefault(); }} onWheel={(e) => { e.preventDefault(); }} style={{ touchAction: 'none', }} /> {/* 邮件设置面板 */}
{ e.stopPropagation(); }} style={{ touchAction: 'auto', }} > {/* 标题栏 */}

邮件通知设置

{/* 表单 */} {emailSettingsLoading ? (
{/* 加载骨架屏 */}
加载中...
) : (
setUserEmail(e.target.value)} placeholder='输入您的邮箱地址' disabled={emailSettingsSaving} className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed' />

接收收藏更新通知

当收藏的影片有更新时发送邮件通知

)} {/* 提示信息 */}

💡 提示:需要管理员先在管理面板中配置邮件服务

); return ( <>
{/* 版本更新红点 */} {updateStatus === UpdateStatus.HAS_UPDATE && (
)} {/* 未读通知红点 */} {unreadCount > 0 && (
)}
{/* 使用 Portal 将菜单面板渲染到 document.body */} {isOpen && mounted && createPortal(menuPanel, document.body)} {/* 使用 Portal 将设置面板渲染到 document.body */} {isSettingsOpen && mounted && createPortal(settingsPanel, document.body)} {/* 使用 Portal 将修改密码面板渲染到 document.body */} {isChangePasswordOpen && mounted && createPortal(changePasswordPanel, document.body)} {/* 使用 Portal 将订阅面板渲染到 document.body */} {isSubscribeOpen && mounted && createPortal(subscribePanel, document.body)} {/* 版本面板 */} setIsVersionPanelOpen(false)} /> {/* 离线下载面板 */} setIsOfflineDownloadPanelOpen(false)} /> {/* 使用 Portal 将通知面板渲染到 document.body */} {isNotificationPanelOpen && mounted && createPortal( { setIsNotificationPanelOpen(false); // 不需要在这里刷新,NotificationPanel 内部会触发事件 }} />, document.body )} {/* 使用 Portal 将收藏面板渲染到 document.body */} {isFavoritesPanelOpen && mounted && createPortal( setIsFavoritesPanelOpen(false)} />, document.body )} {/* 使用 Portal 将邮件设置面板渲染到 document.body */} {isEmailSettingsOpen && mounted && createPortal(emailSettingsPanel, document.body)} ); };