init
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user