| import { useState, useEffect } from 'react'; |
| import { Dropdown, Avatar, Space, Typography, message, Modal, Table, Button, Tag, Popconfirm, Pagination } from 'antd'; |
| import { UserOutlined, LogoutOutlined, TeamOutlined, CrownOutlined } from '@ant-design/icons'; |
| import { authApi, userApi } from '../services/api'; |
| import type { User } from '../types'; |
| import type { MenuProps } from 'antd'; |
|
|
| const { Text } = Typography; |
|
|
| export default function UserMenu() { |
| const [currentUser, setCurrentUser] = useState<User | null>(null); |
| const [showUserManagement, setShowUserManagement] = useState(false); |
| const [users, setUsers] = useState<User[]>([]); |
| const [loading, setLoading] = useState(false); |
| const [currentPage, setCurrentPage] = useState(1); |
| const [pageSize, setPageSize] = useState(10); |
|
|
| useEffect(() => { |
| loadCurrentUser(); |
| }, []); |
|
|
| const loadCurrentUser = async () => { |
| try { |
| const user = await authApi.getCurrentUser(); |
| setCurrentUser(user); |
| } catch (error) { |
| console.error('获取用户信息失败:', error); |
| } |
| }; |
|
|
| const handleLogout = async () => { |
| try { |
| await authApi.logout(); |
| message.success('已退出登录'); |
| window.location.href = '/login'; |
| } catch (error) { |
| console.error('退出登录失败:', error); |
| message.error('退出登录失败'); |
| } |
| }; |
|
|
| const handleShowUserManagement = async () => { |
| if (!currentUser?.is_admin) { |
| message.warning('只有管理员可以访问用户管理'); |
| return; |
| } |
|
|
| setShowUserManagement(true); |
| loadUsers(); |
| }; |
|
|
| const loadUsers = async () => { |
| try { |
| setLoading(true); |
| const userList = await userApi.listUsers(); |
| setUsers(userList); |
| } catch (error) { |
| console.error('获取用户列表失败:', error); |
| message.error('获取用户列表失败'); |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| const handleSetAdmin = async (userId: string, isAdmin: boolean) => { |
| try { |
| await userApi.setAdmin(userId, isAdmin); |
| message.success(isAdmin ? '已设置为管理员' : '已取消管理员权限'); |
| loadUsers(); |
| } catch (error) { |
| console.error('设置管理员失败:', error); |
| message.error('设置管理员失败'); |
| } |
| }; |
|
|
| const handleDeleteUser = async (userId: string) => { |
| try { |
| await userApi.deleteUser(userId); |
| message.success('用户已删除'); |
| loadUsers(); |
| } catch (error) { |
| console.error('删除用户失败:', error); |
| message.error('删除用户失败'); |
| } |
| }; |
|
|
| const menuItems: MenuProps['items'] = [ |
| { |
| key: 'user-info', |
| label: ( |
| <div style={{ padding: '8px 0' }}> |
| <Text strong>{currentUser?.display_name || currentUser?.username}</Text> |
| <br /> |
| <Text type="secondary" style={{ fontSize: 12 }}> |
| Trust Level: {currentUser?.trust_level} |
| {currentUser?.is_admin && ' · 管理员'} |
| </Text> |
| </div> |
| ), |
| disabled: true, |
| }, |
| { |
| type: 'divider', |
| }, |
| ...(currentUser?.is_admin ? [{ |
| key: 'user-management', |
| icon: <TeamOutlined />, |
| label: '用户管理', |
| onClick: handleShowUserManagement, |
| }, { |
| type: 'divider' as const, |
| }] : []), |
| { |
| key: 'logout', |
| icon: <LogoutOutlined />, |
| label: '退出登录', |
| onClick: handleLogout, |
| }, |
| ]; |
|
|
| const columns = [ |
| { |
| title: '用户名', |
| dataIndex: 'username', |
| key: 'username', |
| render: (text: string, record: User) => ( |
| <Space> |
| <Avatar src={record.avatar_url} icon={<UserOutlined />} size="small" /> |
| <div> |
| <div>{record.display_name || text}</div> |
| <Text type="secondary" style={{ fontSize: 12 }}>{text}</Text> |
| </div> |
| </Space> |
| ), |
| }, |
| { |
| title: 'Trust Level', |
| dataIndex: 'trust_level', |
| key: 'trust_level', |
| width: 120, |
| render: (level: number) => <Tag color="blue">{level}</Tag>, |
| }, |
| { |
| title: '角色', |
| dataIndex: 'is_admin', |
| key: 'is_admin', |
| width: 100, |
| render: (isAdmin: boolean) => ( |
| isAdmin ? <Tag color="gold" icon={<CrownOutlined />}>管理员</Tag> : <Tag>普通用户</Tag> |
| ), |
| }, |
| { |
| title: '最后登录', |
| dataIndex: 'last_login', |
| key: 'last_login', |
| width: 180, |
| render: (date: string) => new Date(date).toLocaleString('zh-CN'), |
| }, |
| { |
| title: '操作', |
| key: 'actions', |
| width: 200, |
| render: (_: unknown, record: User) => { |
| const isSelf = record.user_id === currentUser?.user_id; |
| return ( |
| <Space> |
| {record.is_admin ? ( |
| <Popconfirm |
| title="确定要取消管理员权限吗?" |
| onConfirm={() => handleSetAdmin(record.user_id, false)} |
| disabled={isSelf} |
| > |
| <Button size="small" disabled={isSelf}> |
| 取消管理员 |
| </Button> |
| </Popconfirm> |
| ) : ( |
| <Button |
| size="small" |
| type="primary" |
| onClick={() => handleSetAdmin(record.user_id, true)} |
| > |
| 设为管理员 |
| </Button> |
| )} |
| <Popconfirm |
| title="确定要删除该用户吗?此操作不可恢复!" |
| onConfirm={() => handleDeleteUser(record.user_id)} |
| disabled={isSelf} |
| > |
| <Button size="small" danger disabled={isSelf}> |
| 删除 |
| </Button> |
| </Popconfirm> |
| </Space> |
| ); |
| }, |
| }, |
| ]; |
|
|
| if (!currentUser) { |
| return null; |
| } |
|
|
| return ( |
| <> |
| <Dropdown menu={{ items: menuItems }} placement="bottomRight"> |
| <div |
| style={{ |
| cursor: 'pointer', |
| display: 'flex', |
| alignItems: 'center', |
| gap: 12, |
| padding: '8px 16px', |
| background: 'rgba(255, 255, 255, 0.95)', |
| backdropFilter: 'blur(10px)', |
| WebkitBackdropFilter: 'blur(10px)', |
| borderRadius: 24, |
| border: '1px solid rgba(102, 126, 234, 0.2)', |
| transition: 'all 0.3s ease', |
| boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', |
| }} |
| onMouseEnter={(e) => { |
| e.currentTarget.style.background = 'rgba(255, 255, 255, 1)'; |
| e.currentTarget.style.transform = 'translateY(-2px)'; |
| e.currentTarget.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; |
| }} |
| onMouseLeave={(e) => { |
| e.currentTarget.style.background = 'rgba(255, 255, 255, 0.95)'; |
| e.currentTarget.style.transform = 'translateY(0)'; |
| e.currentTarget.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)'; |
| }} |
| > |
| <div style={{ position: 'relative' }}> |
| <Avatar |
| src={currentUser.avatar_url} |
| icon={<UserOutlined />} |
| size={40} |
| style={{ |
| backgroundColor: '#1890ff', |
| border: '3px solid #fff', |
| boxShadow: '0 2px 8px rgba(102, 126, 234, 0.3)', |
| }} |
| /> |
| {currentUser.is_admin && ( |
| <div style={{ |
| position: 'absolute', |
| bottom: -2, |
| right: -2, |
| width: 18, |
| height: 18, |
| background: 'linear-gradient(135deg, #ffd700 0%, #ffaa00 100%)', |
| borderRadius: '50%', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| border: '2px solid white', |
| boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)', |
| }}> |
| <CrownOutlined style={{ fontSize: 9, color: '#fff' }} /> |
| </div> |
| )} |
| </div> |
| <Space direction="vertical" size={0} style={{ display: window.innerWidth <= 768 ? 'none' : 'flex' }}> |
| <Text strong style={{ |
| color: '#262626', |
| fontSize: 14, |
| lineHeight: '20px', |
| }}> |
| {currentUser.display_name || currentUser.username} |
| </Text> |
| <Text style={{ |
| color: '#8c8c8c', |
| fontSize: 12, |
| lineHeight: '18px', |
| }}> |
| {currentUser.is_admin ? '👑 管理员' : `🎖️ Trust Level ${currentUser.trust_level}`} |
| </Text> |
| </Space> |
| </div> |
| </Dropdown> |
| |
| <Modal |
| title="用户管理" |
| open={showUserManagement} |
| onCancel={() => setShowUserManagement(false)} |
| footer={null} |
| width={900} |
| centered |
| styles={{ |
| body: { |
| padding: 0, |
| display: 'flex', |
| flexDirection: 'column', |
| height: 'calc(100vh - 200px)', |
| } |
| }} |
| > |
| <div style={{ |
| display: 'flex', |
| flexDirection: 'column', |
| height: '100%', |
| }}> |
| <div style={{ |
| flex: 1, |
| overflow: 'hidden', |
| padding: '0 12px', |
| display: 'flex', |
| flexDirection: 'column', |
| }}> |
| <Table |
| columns={columns} |
| dataSource={users.slice((currentPage - 1) * pageSize, currentPage * pageSize)} |
| rowKey="user_id" |
| loading={loading} |
| pagination={false} |
| scroll={{ x: 800, y: 'calc(100vh - 340px)' }} |
| sticky |
| /> |
| </div> |
| <div style={{ |
| padding: '16px 24px', |
| borderTop: '1px solid #f0f0f0', |
| background: '#fff', |
| display: 'flex', |
| justifyContent: 'center', |
| flexShrink: 0, |
| }}> |
| <Pagination |
| current={currentPage} |
| pageSize={pageSize} |
| total={users.length} |
| showSizeChanger |
| showTotal={(total) => `共 ${total} 个用户`} |
| pageSizeOptions={['10', '20', '50', '100']} |
| onChange={(page, newPageSize) => { |
| setCurrentPage(page); |
| setPageSize(newPageSize); |
| }} |
| /> |
| </div> |
| </div> |
| </Modal> |
| </> |
| ); |
| } |