Files
novalon-manage-system/novalon-manage-web/src/pages/system/user/index.tsx
T
张翔 1c5145f1a5 fix(user): 应用统一验证规则到用户管理表单
用户名添加长度(3-50)和格式校验,密码添加长度(8-20)和强度校验,
邮箱添加格式和长度校验,手机添加格式校验,昵称添加长度校验。
2026-05-06 15:35:40 +08:00

200 lines
6.4 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Table, Button, Modal, Form, Input, Select, Tag, Space, message, Popconfirm } from 'antd'
import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons'
import type { ColumnsType } from 'antd/es/table'
import { userApi } from '@/api/user.api'
import { roleApi } from '@/api/role.api'
import type { User, CreateUserRequest, UpdateUserRequest, UserPageRequest } from '@/api/user.api'
import type { Role } from '@/api/role.api'
import type { PageResponse } from '@/api/user.api'
import { UserStatus, userStatusMap } from '@/constants/status'
import { VALIDATION } from '@/constants/validation-rules'
import PermissionGuard from '@/components/PermissionGuard'
export default function UserManagement() {
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(false)
const [modalOpen, setModalOpen] = useState(false)
const [editingUser, setEditingUser] = useState<User | null>(null)
const [roles, setRoles] = useState<Role[]>([])
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })
const [form] = Form.useForm()
useEffect(() => {
loadUsers()
loadRoles()
}, [])
async function loadUsers() {
setLoading(true)
try {
const params: UserPageRequest = { page: pagination.current - 1, size: pagination.pageSize }
const res = await userApi.getPage(params)
const data = res as unknown as PageResponse<User>
setUsers(data.content)
setPagination((prev) => ({ ...prev, total: data.totalElements }))
} catch {
message.error('加载用户列表失败')
} finally {
setLoading(false)
}
}
async function loadRoles() {
try {
const res = await roleApi.getAll()
setRoles(Array.isArray(res) ? res : [])
} catch { /* ignored */ }
}
function handleAdd() {
setEditingUser(null)
form.resetFields()
setModalOpen(true)
}
function handleEdit(record: User) {
setEditingUser(record)
form.setFieldsValue({
nickname: record.nickname,
email: record.email,
phone: record.phone,
roles: record.roles,
status: record.status,
})
setModalOpen(true)
}
async function handleDelete(id: string) {
try {
await userApi.delete(id)
message.success('删除成功')
loadUsers()
} catch {
message.error('删除失败')
}
}
async function handleSubmit() {
try {
const values = await form.validateFields()
if (editingUser) {
const data: UpdateUserRequest = { ...values }
await userApi.update(editingUser.id, data)
message.success('更新成功')
} else {
const data: CreateUserRequest = { ...values }
await userApi.create(data)
message.success('创建成功')
}
setModalOpen(false)
loadUsers()
} catch { /* ignored */ }
}
const columns: ColumnsType<User> = [
{ title: '用户名', dataIndex: 'username', key: 'username' },
{ title: '昵称', dataIndex: 'nickname', key: 'nickname' },
{ title: '邮箱', dataIndex: 'email', key: 'email' },
{ title: '手机', dataIndex: 'phone', key: 'phone' },
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: UserStatus) => {
const info = userStatusMap[status]
return <Tag color={info?.color || 'default'}>{info?.label || status}</Tag>
},
},
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
render: (roles: string[]) => roles?.map((r) => <Tag key={r}>{r}</Tag>),
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Space>
<PermissionGuard permission="system:user:edit">
<Button type="link" icon={<EditOutlined />} onClick={() => handleEdit(record)} />
</PermissionGuard>
<PermissionGuard permission="system:user:delete">
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
<Button type="link" danger icon={<DeleteOutlined />} />
</Popconfirm>
</PermissionGuard>
</Space>
),
},
]
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
<Space>
<PermissionGuard permission="system:user:add">
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}></Button>
</PermissionGuard>
<Button icon={<ReloadOutlined />} onClick={loadUsers}></Button>
</Space>
</div>
<Table<User>
rowKey="id"
columns={columns}
dataSource={users}
loading={loading}
pagination={{
...pagination,
showSizeChanger: true,
showTotal: (total) => `${total}`,
onChange: (page, pageSize) => {
setPagination((prev) => ({ ...prev, current: page, pageSize }))
setTimeout(loadUsers, 0)
},
}}
/>
<Modal
title={editingUser ? '编辑用户' : '新增用户'}
open={modalOpen}
onOk={handleSubmit}
onCancel={() => setModalOpen(false)}
destroyOnHidden
>
<Form form={form} layout="vertical">
{!editingUser && (
<Form.Item name="username" label="用户名" rules={VALIDATION.username.rules}>
<Input />
</Form.Item>
)}
{!editingUser && (
<Form.Item name="password" label="密码" rules={VALIDATION.password.rules}>
<Input.Password />
</Form.Item>
)}
<Form.Item name="nickname" label="昵称" rules={VALIDATION.nickname.rules}>
<Input />
</Form.Item>
<Form.Item name="email" label="邮箱" rules={VALIDATION.email.rules}>
<Input />
</Form.Item>
<Form.Item name="phone" label="手机" rules={VALIDATION.phone.rules}>
<Input />
</Form.Item>
<Form.Item name="roles" label="角色">
<Select mode="multiple" options={roles.map((r) => ({ label: r.roleName, value: r.roleKey }))} />
</Form.Item>
{editingUser && (
<Form.Item name="status" label="状态">
<Select options={Object.entries(userStatusMap).map(([value, info]) => ({ label: info.label, value }))} />
</Form.Item>
)}
</Form>
</Modal>
</div>
)
}