e.preventDefault()}
+ onWheel={(e) => e.preventDefault()}
+ style={{ touchAction: 'none' }}
+ />
+
+
+ >,
+ document.body
+ );
+}
diff --git a/src/components/PersonalCenterPanel.tsx b/src/components/PersonalCenterPanel.tsx
new file mode 100644
index 0000000..70fe778
--- /dev/null
+++ b/src/components/PersonalCenterPanel.tsx
@@ -0,0 +1,123 @@
+'use client';
+
+import { Mail, Monitor, X } from 'lucide-react';
+import { createPortal } from 'react-dom';
+
+interface PersonalCenterPanelProps {
+ isOpen: boolean;
+ mounted: boolean;
+ onClose: () => void;
+ username: string;
+ roleText: string;
+ showRoleBadge: boolean;
+ avatarText: string;
+ roleBadgeClassName: string;
+ showDeviceManagement: boolean;
+ onOpenEmailSettings: () => void;
+ onOpenDeviceManagement: () => void;
+}
+
+export function PersonalCenterPanel({
+ isOpen,
+ mounted,
+ onClose,
+ username,
+ roleText,
+ showRoleBadge,
+ avatarText,
+ roleBadgeClassName,
+ showDeviceManagement,
+ onOpenEmailSettings,
+ onOpenDeviceManagement,
+}: PersonalCenterPanelProps) {
+ if (!isOpen || !mounted) return null;
+
+ return createPortal(
+ <>
+
{
+ e.preventDefault();
+ }}
+ onWheel={(e) => {
+ e.preventDefault();
+ }}
+ style={{ touchAction: 'none' }}
+ />
+
+
+
{
+ e.stopPropagation();
+ }}
+ style={{ touchAction: 'auto' }}
+ >
+
+
+
+ {avatarText}
+
+ {showRoleBadge && (
+
+ {roleText}
+
+ )}
+
+ {username}
+
+
+
+
+
+
+ {showDeviceManagement && (
+
+ )}
+
+
+
+ >,
+ document.body
+ );
+}
diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx
index c52dda5..fc477e8 100644
--- a/src/components/UserMenu.tsx
+++ b/src/components/UserMenu.tsx
@@ -17,7 +17,6 @@ import {
Home,
KeyRound,
LogOut,
- Mail,
MessageSquare,
Monitor,
MoveDown,
@@ -46,6 +45,9 @@ import { UpdateStatus } from '@/lib/version_check';
import { FavoritesPanel } from './FavoritesPanel';
import { NotificationPanel } from './NotificationPanel';
import { OfflineDownloadPanel } from './OfflineDownloadPanel';
+import { DeviceManagementPanel } from './DeviceManagementPanel';
+import { EmailSettingsPanel } from './EmailSettingsPanel';
+import { PersonalCenterPanel } from './PersonalCenterPanel';
import { useVersionCheck } from './VersionCheckProvider';
import { VersionPanel } from './VersionPanel';
import { DownloadManagementPanel } from './DownloadManagementPanel';
@@ -59,6 +61,7 @@ export const UserMenu: React.FC = () => {
const router = useRouter();
const { updateStatus, isChecking } = useVersionCheck();
const [isOpen, setIsOpen] = useState(false);
+ const [isProfileCenterOpen, setIsProfileCenterOpen] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
const [isSubscribeOpen, setIsSubscribeOpen] = useState(false);
@@ -89,7 +92,7 @@ export const UserMenu: React.FC = () => {
// Body 滚动锁定 - 使用 overflow 方式避免布局问题
useEffect(() => {
- if (isSettingsOpen || isChangePasswordOpen || isSubscribeOpen || isOfflineDownloadPanelOpen || isEmailSettingsOpen || isDeviceManagementOpen || isEcoAppsOpen || isReportOpen || isDownloadManagementOpen) {
+ if (isProfileCenterOpen || isSettingsOpen || isChangePasswordOpen || isSubscribeOpen || isOfflineDownloadPanelOpen || isEmailSettingsOpen || isDeviceManagementOpen || isEcoAppsOpen || isReportOpen || isDownloadManagementOpen) {
const body = document.body;
const html = document.documentElement;
@@ -108,7 +111,7 @@ export const UserMenu: React.FC = () => {
html.style.overflow = originalHtmlOverflow;
};
}
- }, [isSettingsOpen, isChangePasswordOpen, isSubscribeOpen, isOfflineDownloadPanelOpen, isEmailSettingsOpen, isDeviceManagementOpen, isEcoAppsOpen]);
+ }, [isProfileCenterOpen, isSettingsOpen, isChangePasswordOpen, isSubscribeOpen, isOfflineDownloadPanelOpen, isEmailSettingsOpen, isDeviceManagementOpen, isEcoAppsOpen, isReportOpen, isDownloadManagementOpen]);
// 设置相关状态
const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true);
@@ -149,6 +152,10 @@ export const UserMenu: React.FC = () => {
const [emailNotifications, setEmailNotifications] = useState(false);
const [emailSettingsLoading, setEmailSettingsLoading] = useState(false);
const [emailSettingsSaving, setEmailSettingsSaving] = useState(false);
+ const [emailSettingsMessage, setEmailSettingsMessage] = useState('');
+ const [emailSettingsMessageType, setEmailSettingsMessageType] = useState<
+ 'success' | 'error' | null
+ >(null);
// 设备管理状态
const [devices, setDevices] = useState
([]);
@@ -618,6 +625,8 @@ export const UserMenu: React.FC = () => {
// 加载邮件通知设置
const loadEmailSettings = async () => {
setEmailSettingsLoading(true);
+ setEmailSettingsMessage('');
+ setEmailSettingsMessageType(null);
try {
const response = await fetch('/api/user/email-settings');
if (response.ok) {
@@ -635,6 +644,8 @@ export const UserMenu: React.FC = () => {
// 保存邮件通知设置
const handleSaveEmailSettings = async () => {
setEmailSettingsSaving(true);
+ setEmailSettingsMessage('');
+ setEmailSettingsMessageType(null);
try {
const response = await fetch('/api/user/email-settings', {
method: 'POST',
@@ -645,32 +656,22 @@ export const UserMenu: React.FC = () => {
}),
});
- 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);
- }
+ setEmailSettingsMessage('保存成功!');
+ setEmailSettingsMessageType('success');
+ setTimeout(() => {
+ setEmailSettingsMessage('');
+ setEmailSettingsMessageType(null);
+ }, 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');
- }
+ setEmailSettingsMessage(data.error || '保存失败');
+ setEmailSettingsMessageType('error');
}
} 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');
- }
+ setEmailSettingsMessage('保存失败,请重试');
+ setEmailSettingsMessageType('error');
} finally {
setEmailSettingsSaving(false);
}
@@ -1434,6 +1435,24 @@ export const UserMenu: React.FC = () => {
}
};
+ const currentUsername = authInfo?.username || 'default';
+ const currentRole = authInfo?.role || 'user';
+ const currentRoleText = getRoleText(currentRole);
+ const shouldShowRoleBadge = currentRole !== 'user';
+ const avatarText = currentUsername.trim().charAt(0).toUpperCase() || 'D';
+
+ const roleBadgeClassName =
+ currentRole === 'owner'
+ ? 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300'
+ : currentRole === 'admin'
+ ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300'
+ : 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300';
+
+ const handleOpenProfileCenter = () => {
+ setIsOpen(false);
+ setIsProfileCenterOpen(true);
+ };
+
// 菜单面板内容
const menuPanel = (
<>
@@ -1446,62 +1465,35 @@ export const UserMenu: React.FC = () => {
{/* 菜单面板 */}
{/* 用户信息区域 */}
-
-
-
-
-
- 当前用户
-
- {/* 邮件设置图标按钮 */}
-
- {/* 设备管理图标按钮 */}
- {storageType !== 'localstorage' && (
-