/* eslint-disable no-console,@typescript-eslint/no-explicit-any */ 'use client'; import { KeyRound, LogOut, Settings, Shield, User, X } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import { checkForUpdates, CURRENT_VERSION, UpdateStatus } from '@/lib/version'; interface AuthInfo { username?: string; role?: 'owner' | 'admin' | 'user'; } export const UserMenu: React.FC = () => { const router = useRouter(); const [isOpen, setIsOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false); const [authInfo, setAuthInfo] = useState(null); const [storageType, setStorageType] = useState('localstorage'); const [mounted, setMounted] = useState(false); // 设置相关状态 const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true); const [doubanProxyUrl, setDoubanProxyUrl] = useState(''); const [imageProxyUrl, setImageProxyUrl] = useState(''); const [enableOptimization, setEnableOptimization] = useState(true); const [enableImageProxy, setEnableImageProxy] = useState(false); const [enableDoubanProxy, setEnableDoubanProxy] = useState(false); // 修改密码相关状态 const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [passwordLoading, setPasswordLoading] = useState(false); const [passwordError, setPasswordError] = useState(''); // 版本检查相关状态 const [updateStatus, setUpdateStatus] = useState(null); const [isChecking, setIsChecking] = useState(true); // 确保组件已挂载 useEffect(() => { setMounted(true); }, []); // 获取认证信息和存储类型 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 savedEnableDoubanProxy = localStorage.getItem('enableDoubanProxy'); const defaultDoubanProxy = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || ''; if (savedEnableDoubanProxy !== null) { setEnableDoubanProxy(JSON.parse(savedEnableDoubanProxy)); } else if (defaultDoubanProxy) { setEnableDoubanProxy(true); } const savedDoubanProxyUrl = localStorage.getItem('doubanProxyUrl'); if (savedDoubanProxyUrl !== null) { setDoubanProxyUrl(savedDoubanProxyUrl); } else if (defaultDoubanProxy) { setDoubanProxyUrl(defaultDoubanProxy); } const savedEnableImageProxy = localStorage.getItem('enableImageProxy'); const defaultImageProxy = (window as any).RUNTIME_CONFIG?.IMAGE_PROXY || ''; if (savedEnableImageProxy !== null) { setEnableImageProxy(JSON.parse(savedEnableImageProxy)); } else if (defaultImageProxy) { setEnableImageProxy(true); } const savedImageProxyUrl = localStorage.getItem('imageProxyUrl'); if (savedImageProxyUrl !== null) { setImageProxyUrl(savedImageProxyUrl); } else if (defaultImageProxy) { setImageProxyUrl(defaultImageProxy); } const savedEnableOptimization = localStorage.getItem('enableOptimization'); if (savedEnableOptimization !== null) { setEnableOptimization(JSON.parse(savedEnableOptimization)); } } }, []); // 版本检查 useEffect(() => { const checkUpdate = async () => { try { const status = await checkForUpdates(); setUpdateStatus(status); } catch (error) { console.warn('版本检查失败:', error); } finally { setIsChecking(false); } }; checkUpdate(); }, []); 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 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 handleImageProxyUrlChange = (value: string) => { setImageProxyUrl(value); if (typeof window !== 'undefined') { localStorage.setItem('imageProxyUrl', value); } }; const handleOptimizationToggle = (value: boolean) => { setEnableOptimization(value); if (typeof window !== 'undefined') { localStorage.setItem('enableOptimization', JSON.stringify(value)); } }; const handleImageProxyToggle = (value: boolean) => { setEnableImageProxy(value); if (typeof window !== 'undefined') { localStorage.setItem('enableImageProxy', JSON.stringify(value)); } }; const handleDoubanProxyToggle = (value: boolean) => { setEnableDoubanProxy(value); if (typeof window !== 'undefined') { localStorage.setItem('enableDoubanProxy', JSON.stringify(value)); } }; const handleResetSettings = () => { const defaultImageProxy = (window as any).RUNTIME_CONFIG?.IMAGE_PROXY || ''; const defaultDoubanProxy = (window as any).RUNTIME_CONFIG?.DOUBAN_PROXY || ''; setDefaultAggregateSearch(true); setEnableOptimization(true); setDoubanProxyUrl(defaultDoubanProxy); setEnableDoubanProxy(!!defaultDoubanProxy); setEnableImageProxy(!!defaultImageProxy); setImageProxyUrl(defaultImageProxy); if (typeof window !== 'undefined') { localStorage.setItem('defaultAggregateSearch', JSON.stringify(true)); localStorage.setItem('enableOptimization', JSON.stringify(true)); localStorage.setItem('doubanProxyUrl', defaultDoubanProxy); localStorage.setItem( 'enableDoubanProxy', JSON.stringify(!!defaultDoubanProxy) ); localStorage.setItem( 'enableImageProxy', JSON.stringify(!!defaultImageProxy) ); localStorage.setItem('imageProxyUrl', defaultImageProxy); } }; // 检查是否显示管理面板按钮 const showAdminPanel = authInfo?.role === 'owner' || authInfo?.role === 'admin'; // 检查是否显示修改密码按钮 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 && ( )} {/* 修改密码按钮 */} {showChangePassword && ( )} {/* 分割线 */}
{/* 登出按钮 */} {/* 分割线 */}
{/* 版本信息 */}
); // 设置面板内容 const settingsPanel = ( <> {/* 背景遮罩 */}
{/* 设置面板 */}
{/* 标题栏 */}

本地设置

{/* 设置项 */}
{/* 默认聚合搜索结果 */}

默认聚合搜索结果

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

{/* 优选和测速 */}

启用优选和测速

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

{/* 分割线 */}
{/* 豆瓣代理开关 */}

启用豆瓣代理

启用后,豆瓣数据将通过代理服务器获取

{/* 豆瓣代理地址设置 */}

豆瓣代理地址

仅在启用豆瓣代理时生效,留空则使用服务器 API

handleDoubanProxyUrlChange(e.target.value)} disabled={!enableDoubanProxy} />
{/* 分割线 */}
{/* 图片代理开关 */}

启用图片代理

启用后,所有图片加载将通过代理服务器

{/* 图片代理地址设置 */}

图片代理地址

仅在启用图片代理时生效

handleImageProxyUrlChange(e.target.value)} disabled={!enableImageProxy} />
{/* 底部说明 */}

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

); // 修改密码面板内容 const changePasswordPanel = ( <> {/* 背景遮罩 */}
{/* 修改密码面板 */}
{/* 标题栏 */}

修改密码

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

修改密码后需要重新登录

); return ( <>
{updateStatus === UpdateStatus.HAS_UPDATE && (
)}
{/* 使用 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)} ); };