import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { Table, Modal, Form, Input, Select, Switch, Space, Tag, Popconfirm } from 'antd'; import { PlusOutlined, EditOutlined, KeyOutlined, IdcardOutlined, InboxOutlined, } from '@ant-design/icons'; import dayjs from 'dayjs'; import api from '../../api'; import PermissionButton from '../../components/PermissionButton'; import EditableCell from '../../components/EditableCell'; import { message } from '../../ui/app-message'; import { userProfileResponseToFormValues, type UserProfileResponse } from './user-profile-form'; const UsersPage: React.FC = () => { const [data, setData] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [pwdModalOpen, setPwdModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [resetTarget, setResetTarget] = useState(null); const [profileModalOpen, setProfileModalOpen] = useState(false); const [profileUser, setProfileUser] = useState(null); const [profileForm] = Form.useForm(); const [form] = Form.useForm(); const [pwdForm] = Form.useForm(); const [saving, setSaving] = useState(false); const [showArchived, setShowArchived] = useState(false); const handleOpenProfile = async (record: any) => { setProfileUser(record); try { const res = await api.get(`/rbac/users/${record.id}/profile`); profileForm.setFieldsValue(userProfileResponseToFormValues(res)); } catch { profileForm.setFieldsValue({}); } setProfileModalOpen(true); }; const handleProfileSubmit = async () => { setSaving(true); const values = await profileForm.validateFields(); try { await api.put(`/rbac/users/${profileUser.id}/profile`, values); message.success('档案更新成功'); setProfileModalOpen(false); fetchData(); } catch (e: any) { message.error(e.message || '操作失败'); } finally { setSaving(false); } }; const fetchData = useCallback(async () => { setLoading(true); try { const [users, rolesRes] = await Promise.all([ api.get(`/rbac/users?isArchived=${showArchived}`) as Promise, api.get('/rbac/roles') as Promise, ]); setData(users); setRoles(rolesRes); } catch (e: unknown) { const err = e as { message?: string }; message.error(err?.message || '加载失败,请稍后重试'); } setLoading(false); }, [showArchived]); useEffect(() => { fetchData(); }, [fetchData]); const handleAdd = () => { setEditing(null); form.resetFields(); setModalOpen(true); }; const handleEdit = (record: any) => { setEditing(record); form.setFieldsValue({ username: record.username, name: record.name, isActive: record.isActive, roleIds: record.roles?.map((r: any) => r.id) || [], }); setModalOpen(true); }; const handleSubmit = async () => { setSaving(true); const values = await form.validateFields(); try { if (editing) { await api.put(`/rbac/users/${editing.id}`, { username: values.username, name: values.name, isActive: values.isActive, roleIds: values.roleIds || [], }); message.success('更新成功'); } else { await api.post('/rbac/users', { username: values.username, password: values.password, name: values.name, roleIds: values.roleIds || [], }); message.success('创建成功'); } setModalOpen(false); fetchData(); } catch (e: any) { message.error(e.message || '操作失败'); } finally { setSaving(false); } }; const handleArchive = async (id: number, archive: boolean) => { try { await api.put(`/rbac/users/${id}/${archive ? 'archive' : 'restore'}`); message.success(archive ? '已归档' : '已恢复'); fetchData(); } catch (e: unknown) { const err = e as { message?: string }; message.error(err?.message || '操作失败'); } }; const handleResetPwd = (record: any) => { setResetTarget(record); pwdForm.resetFields(); setPwdModalOpen(true); }; const handlePwdSubmit = async () => { setSaving(true); const values = await pwdForm.validateFields(); try { await api.put(`/rbac/users/${resetTarget.id}/password`, { password: values.password }); message.success('密码已重置'); setPwdModalOpen(false); } catch (e: unknown) { const err = e as { message?: string }; message.error(err?.message || '操作失败'); } finally { setSaving(false); } }; const saveCell = useCallback( async (record: any, field: string, value: unknown) => { await api.put(`/rbac/users/${record.id}`, { [field]: value }); message.success('已保存'); await fetchData(); }, [fetchData], ); const columns = useMemo( () => [ { title: 'ID', dataIndex: 'id', width: 60 }, { title: '用户名', dataIndex: 'username', width: 120, render: (v: string, r: any) => ( saveCell(r, 'username', next)} > {v} ), }, { title: '姓名', dataIndex: 'name', width: 120, render: (v: string, r: any) => ( saveCell(r, 'name', next)} > {v} ), }, { title: '角色', dataIndex: 'roles', width: 200, render: (v: any[], record: any) => ( item.id) || []} editor="multi-select" options={roles.map((item) => ({ value: item.id, label: item.name }))} permission="user:edit" disabled={record.isArchived} onSave={(next) => saveCell(record, 'roleIds', next)} > {v && v.length > 0 ? ( v.map((r: any) => ( {r.name} )) ) : ( 无角色 )} ), }, { title: '状态', dataIndex: 'isActive', width: 80, render: (v: boolean, r: any) => ( saveCell(r, 'isActive', String(next) === 'true')} > {v ? '启用' : '禁用'} ), }, { title: '最后登录', dataIndex: 'lastLoginAt', width: 170, render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-'), }, { title: '创建时间', dataIndex: 'createdAt', width: 170, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'), }, { title: '操作', width: 240, fixed: 'right' as const, render: (_: unknown, record: any) => ( } onClick={() => handleOpenProfile(record)} > 档案 } onClick={() => handleEdit(record)} > 编辑 } onClick={() => handleResetPwd(record)} > 重置密码 {record.isArchived ? ( handleArchive(record.id, false)}> 恢复 ) : ( handleArchive(record.id, true)} > 归档 )} ), }, ], [roles, saveCell], ); return (

账号管理

} onClick={handleAdd} > 新增账号 归档
setModalOpen(false)} destroyOnHidden confirmLoading={saving} >
{!editing && ( )} {editing && ( )}