Files
planet/frontend/src/pages/Users/Users.tsx

195 lines
5.9 KiB
TypeScript

import { useEffect, useRef, useState } from 'react'
import { Table, Button, Tag, Space, message, Modal, Form, Input, Select } from 'antd'
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
import axios from 'axios'
import AppLayout from '../../components/AppLayout/AppLayout'
interface User {
id: number
username: string
email: string
role: string
is_active: boolean
created_at: string
}
function Users() {
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(false)
const [modalVisible, setModalVisible] = useState(false)
const [editingUser, setEditingUser] = useState<User | null>(null)
const tableRegionRef = useRef<HTMLDivElement | null>(null)
const [tableHeight, setTableHeight] = useState(360)
const [form] = Form.useForm()
const fetchUsers = async () => {
setLoading(true)
try {
const res = await axios.get('/api/v1/users')
setUsers(res.data.data || [])
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchUsers()
}, [])
useEffect(() => {
const updateTableHeight = () => {
const regionHeight = tableRegionRef.current?.offsetHeight || 0
setTableHeight(Math.max(220, regionHeight - 56))
}
updateTableHeight()
if (typeof ResizeObserver === 'undefined') {
return undefined
}
const observer = new ResizeObserver(updateTableHeight)
if (tableRegionRef.current) observer.observe(tableRegionRef.current)
return () => observer.disconnect()
}, [users.length])
const handleAdd = () => {
setEditingUser(null)
form.resetFields()
setModalVisible(true)
}
const handleEdit = (user: User) => {
setEditingUser(user)
form.setFieldsValue(user)
setModalVisible(true)
}
const handleDelete = async (id: number) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除此用户吗?',
onOk: async () => {
await axios.delete(`/api/v1/users/${id}`)
message.success('删除成功')
fetchUsers()
},
})
}
const handleSubmit = async (values: Record<string, unknown>) => {
try {
if (editingUser) {
await axios.put(`/api/v1/users/${editingUser.id}`, values)
message.success('更新成功')
} else {
await axios.post('/api/v1/users', values)
message.success('创建成功')
}
setModalVisible(false)
fetchUsers()
} catch (error: unknown) {
const err = error as { response?: { data?: { detail?: string } } }
message.error(err.response?.data?.detail || '操作失败')
}
}
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
{ title: '用户名', dataIndex: 'username', key: 'username', width: 180 },
{ title: '邮箱', dataIndex: 'email', key: 'email', width: 260, ellipsis: true },
{
title: '角色',
dataIndex: 'role',
key: 'role',
width: 140,
render: (role: string) => {
const colors: Record<string, string> = {
super_admin: 'red',
admin: 'orange',
operator: 'blue',
viewer: 'green',
}
return <Tag color={colors[role] || 'default'}>{role}</Tag>
},
},
{
title: '状态',
dataIndex: 'is_active',
key: 'is_active',
width: 120,
render: (active: boolean) => (
<Tag color={active ? 'green' : 'red'}>{active ? '活跃' : '禁用'}</Tag>
),
},
{
title: '操作',
key: 'action',
width: 180,
render: (_: unknown, record: User) => (
<Space>
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)}></Button>
<Button type="link" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)}></Button>
</Space>
),
},
]
return (
<AppLayout>
<div className="page-shell">
<div className="page-shell__header">
<h2 style={{ margin: 0 }}></h2>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}></Button>
</div>
<div className="page-shell__body">
<div ref={tableRegionRef} className="table-scroll-region data-source-table-region users-table-region" style={{ height: '100%' }}>
<Table
columns={columns}
dataSource={users}
rowKey="id"
loading={loading}
scroll={{ x: 'max-content', y: tableHeight }}
tableLayout="fixed"
/>
</div>
</div>
</div>
<Modal
title={editingUser ? '编辑用户' : '添加用户'}
open={modalVisible}
onCancel={() => setModalVisible(false)}
footer={null}
>
<Form form={form} onFinish={handleSubmit} layout="vertical">
<Form.Item name="username" label="用户名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="email" label="邮箱" rules={[{ required: true, type: 'email' }]}>
<Input />
</Form.Item>
{!editingUser && (
<Form.Item name="password" label="密码" rules={[{ required: true, min: 8 }]}>
<Input.Password />
</Form.Item>
)}
<Form.Item name="role" label="角色" rules={[{ required: true }]}>
<Select>
<Select.Option value="super_admin"></Select.Option>
<Select.Option value="admin"></Select.Option>
<Select.Option value="operator"></Select.Option>
<Select.Option value="viewer"></Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block></Button>
</Form.Item>
</Form>
</Modal>
</AppLayout>
)
}
export default Users