150 lines
3.8 KiB
TypeScript
150 lines
3.8 KiB
TypeScript
/**
|
|
* User Management Page
|
|
*/
|
|
import { useState, useEffect } from 'react';
|
|
import { message, Modal, Button, Popconfirm } from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import { DataTable } from '../../components/admin/DataTable';
|
|
import { request } from '../../utils/request';
|
|
import { ReloadOutlined } from '@ant-design/icons';
|
|
|
|
interface UserItem {
|
|
id: number;
|
|
username: string;
|
|
full_name: string;
|
|
email: string;
|
|
is_active: boolean;
|
|
roles: string[];
|
|
last_login_at: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export function Users() {
|
|
const [loading, setLoading] = useState(false);
|
|
const [data, setData] = useState<UserItem[]>([]);
|
|
const [filteredData, setFilteredData] = useState<UserItem[]>([]);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const loadData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const { data: result } = await request.get('/users/list');
|
|
setData(result.users || []);
|
|
setFilteredData(result.users || []);
|
|
} catch (error) {
|
|
message.error('加载用户数据失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSearch = (keyword: string) => {
|
|
const lowerKeyword = keyword.toLowerCase();
|
|
const filtered = data.filter(
|
|
(item) =>
|
|
item.username.toLowerCase().includes(lowerKeyword) ||
|
|
item.full_name?.toLowerCase().includes(lowerKeyword) ||
|
|
item.email?.toLowerCase().includes(lowerKeyword)
|
|
);
|
|
setFilteredData(filtered);
|
|
};
|
|
|
|
const handleStatusChange = async (record: UserItem, checked: boolean) => {
|
|
try {
|
|
await request.put(`/users/${record.id}/status`, { is_active: checked });
|
|
message.success(`用户 ${record.username} 状态更新成功`);
|
|
|
|
const newData = data.map(item =>
|
|
item.id === record.id ? { ...item, is_active: checked } : item
|
|
);
|
|
setData(newData);
|
|
setFilteredData(newData);
|
|
} catch (error) {
|
|
message.error('状态更新失败');
|
|
}
|
|
};
|
|
|
|
const handleResetPassword = async (record: UserItem) => {
|
|
try {
|
|
await request.post(`/users/${record.id}/reset-password`);
|
|
message.success(`用户 ${record.username} 密码已重置`);
|
|
} catch (error) {
|
|
message.error('密码重置失败');
|
|
}
|
|
};
|
|
|
|
const columns: ColumnsType<UserItem> = [
|
|
{
|
|
title: 'ID',
|
|
dataIndex: 'id',
|
|
width: 80,
|
|
sorter: (a, b) => a.id - b.id,
|
|
},
|
|
{
|
|
title: '用户名',
|
|
dataIndex: 'username',
|
|
sorter: (a, b) => a.username.localeCompare(b.username),
|
|
},
|
|
{
|
|
title: '姓名',
|
|
dataIndex: 'full_name',
|
|
},
|
|
{
|
|
title: '邮箱',
|
|
dataIndex: 'email',
|
|
},
|
|
{
|
|
title: '角色',
|
|
dataIndex: 'roles',
|
|
render: (roles: string[]) => roles.join(', '),
|
|
},
|
|
{
|
|
title: '最近登录',
|
|
dataIndex: 'last_login_at',
|
|
render: (text) => text ? new Date(text).toLocaleString() : '从未',
|
|
},
|
|
{
|
|
title: '注册时间',
|
|
dataIndex: 'created_at',
|
|
render: (text) => new Date(text).toLocaleDateString(),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: 120,
|
|
render: (_, record) => (
|
|
<Popconfirm
|
|
title="确认重置密码?"
|
|
description="密码将被重置为默认密码"
|
|
onConfirm={() => handleResetPassword(record)}
|
|
okText="确认"
|
|
cancelText="取消"
|
|
>
|
|
<Button type="link" icon={<ReloadOutlined />} size="small">
|
|
重置密码
|
|
</Button>
|
|
</Popconfirm>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<DataTable
|
|
title="用户管理"
|
|
columns={columns}
|
|
dataSource={filteredData}
|
|
loading={loading}
|
|
total={filteredData.length}
|
|
onSearch={handleSearch}
|
|
onStatusChange={handleStatusChange}
|
|
statusField="is_active"
|
|
rowKey="id"
|
|
pageSize={10}
|
|
// No onAdd, No onDelete, No onEdit
|
|
/>
|
|
);
|
|
}
|