import React, { useEffect, useState } from 'react' import Icon from '@ant-design/icons' import dayjs from 'dayjs' import { COLOR_BACKGROUND, COLOR_ERROR_SECONDARY, COLOR_PRODUCTION, DATABASE_DELETE_SUCCESS, DATABASE_DUPLICATE_KEY, DATABASE_INSERT_SUCCESS, DATABASE_SELECT_SUCCESS, DATABASE_UPDATE_SUCCESS } from '@/constants/common.constants' import { useUpdatedEffect } from '@/util/hooks' import { hasPermission } from '@/util/auth' import { utcToLocalTime, isPastTime, localTimeToUtc, dayjsToUtc, getNowUtc } from '@/util/datetime' import { r_sys_group_get_list, r_sys_role_get_list, r_sys_user_add, r_sys_user_change_password, r_sys_user_delete, r_sys_user_delete_list, r_sys_user_get, r_sys_user_update } from '@/services/system' import Permission from '@/components/common/Permission' import { r_api_avatar_random_base64 } from '@/services/api/avatar' import FitFullscreen from '@/components/common/FitFullscreen' import HideScrollbar from '@/components/common/HideScrollbar' import FlexBox from '@/components/common/FlexBox' import Card from '@/components/common/Card' interface ChangePasswordFields extends UserChangePasswordParam { passwordConfirm: string needChangePassword: boolean } const User: React.FC = () => { const [modal, contextHolder] = AntdModal.useModal() const [isDrawerOpen, setIsDrawerOpen] = useState(false) const [isDrawerEdit, setIsDrawerEdit] = useState(false) const [isDrawerSubmittable, setIsDrawerSubmittable] = useState(false) const [isDrawerSubmitting, setIsDrawerSubmitting] = useState(false) const [roleData, setRoleData] = useState([]) const [groupData, setGroupData] = useState([]) const [isLoadingRole, setIsLoadingRole] = useState(false) const [isLoadingGroup, setIsLoadingGroup] = useState(false) const [avatar, setAvatar] = useState('') const [form] = AntdForm.useForm() const formValues = AntdForm.useWatch([], form) const [newFormValues, setNewFormValues] = useState() const [changePasswordForm] = AntdForm.useForm() const [tableParams, setTableParams] = useState({ pagination: { current: 1, pageSize: 20, position: ['bottomCenter'], showTotal: (total, range) => `第 ${ range[0] === range[1] ? `${range[0]}` : `${range[0]}~${range[1]}` } 项 共 ${total} 项` } }) const [tableSelectedItem, setTableSelectedItem] = useState([]) const [userData, setUserData] = useState([]) const [isLoadingUserData, setIsLoadingUserData] = useState(false) const [searchType, setSearchType] = useState('ALL') const [searchValue, setSearchValue] = useState('') const [isUseRegex, setIsUseRegex] = useState(false) const [isRegexLegal, setIsRegexLegal] = useState(true) const dataColumns: _ColumnsType = [ { dataIndex: 'username', title: '用户', render: (value, record) => {value}, width: '0' }, { dataIndex: ['userInfo', 'avatar'], title: '头像', render: (value) => ( }} src={`data:image/png;base64,${value}`} alt={'Avatar'} /> } style={{ background: COLOR_BACKGROUND }} /> ), width: '0', align: 'center' }, { dataIndex: ['userInfo', 'nickname'], title: '昵称' }, { dataIndex: ['userInfo', 'email'], title: '邮箱' }, { dataIndex: ['roles'], title: '角色', render: (value: RoleVo[], record) => record.id === '0' ? ( 管理员 ) : value.length ? ( value.map((role) => ( {role.name} )) ) : ( ), align: 'center' }, { dataIndex: ['groups'], title: '用户组', render: (value: GroupVo[], record) => record.id === '0' ? ( 管理员 ) : value.length ? ( value.map((role) => ( {role.name} )) ) : ( ), align: 'center' }, { title: '最近登录', render: (_, record) => record.currentLoginTime ? `${utcToLocalTime(record.currentLoginTime)}【${record.currentLoginIp}】` : '无', align: 'center' }, { title: '状态', render: (_, record) => (

创建:{utcToLocalTime(record.createTime)}

修改:{utcToLocalTime(record.updateTime)}

} > {!record.verify && !record.locking && (!record.expiration || !isPastTime(record.expiration)) && (!record.credentialsExpiration || !isPastTime(record.credentialsExpiration)) && record.enable ? ( 正常 ) : ( <> {record.verify ? ( <> 未验证 ) : undefined} {record.locking ? 锁定 : undefined} {record.expiration && isPastTime(record.expiration) ? ( 过期 ) : undefined} {record.credentialsExpiration && isPastTime(record.credentialsExpiration) ? ( 改密 ) : undefined} {!record.enable ? 禁用 : undefined} )}
), align: 'center' }, { title: '操作', width: '14em', align: 'center', render: (_, record) => ( <> 修改密码 编辑 {record.id !== '0' ? ( 删除 ) : undefined} ) } ] const handleOnTableChange = ( pagination: _TablePaginationConfig, filters: Record, sorter: _SorterResult | _SorterResult[] ) => { pagination = { ...tableParams.pagination, ...pagination } if (Array.isArray(sorter)) { setTableParams({ pagination, filters, sortField: sorter.map((value) => value.field).join(',') }) } else { setTableParams({ pagination, filters, sortField: sorter.field, sortOrder: sorter.order }) } if (pagination.pageSize !== tableParams.pagination?.pageSize) { setGroupData([]) } } const handleOnTableSelectChange = (selectedRowKeys: React.Key[]) => { setTableSelectedItem(selectedRowKeys) } const handleOnAddBtnClick = () => { setIsDrawerEdit(false) setIsDrawerOpen(true) form.setFieldValue('id', undefined) form.setFieldValue('username', newFormValues?.username) form.setFieldValue('password', undefined) form.setFieldValue('locking', newFormValues?.locking ?? false) form.setFieldValue('expiration', newFormValues?.expiration) form.setFieldValue('credentialsExpiration', newFormValues?.credentialsExpiration) form.setFieldValue('enable', newFormValues?.enable ?? true) form.setFieldValue('nickname', newFormValues?.nickname) form.setFieldValue('avatar', newFormValues?.avatar) form.setFieldValue('email', newFormValues?.email) form.setFieldValue('roleIds', newFormValues?.roleIds) form.setFieldValue('groupIds', newFormValues?.groupIds) if (!roleData || !roleData.length) { getRoleData() } if (!groupData || !groupData.length) { getGroupData() } getAvatar() } const handleOnListDeleteBtnClick = () => { modal .confirm({ title: '确定删除', content: `确定删除选中的 ${tableSelectedItem.length} 个用户吗?` }) .then( (confirmed) => { if (confirmed) { setIsLoadingUserData(true) void r_sys_user_delete_list(tableSelectedItem) .then((res) => { const response = res.data if (response.code === DATABASE_DELETE_SUCCESS) { void message.success('删除成功') setTimeout(() => { getUser() }) } else { void message.error('删除失败,请稍后重试') } }) .finally(() => { setIsLoadingUserData(false) }) } }, () => {} ) } const handleOnChangePasswordBtnClick = (value: UserWithRoleInfoVo) => { return () => { changePasswordForm.setFieldValue('id', value.id) changePasswordForm.setFieldValue('password', undefined) changePasswordForm.setFieldValue('passwordConfirm', undefined) changePasswordForm.setFieldValue( 'needChangePassword', value.credentialsExpiration && isPastTime(value.credentialsExpiration) ) void modal.confirm({ icon: <>, title: ( <> 修改用户 {value.username} 的密码 ), content: ( ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve() } return Promise.reject(new Error('两次密码输入不一致')) } }) ]} > {value.id !== '0' ? ( ) : undefined} ), onOk: () => changePasswordForm .validateFields() .then( () => { return new Promise((resolve, reject) => { void r_sys_user_change_password({ id: changePasswordForm.getFieldValue('id') as string, password: changePasswordForm.getFieldValue( 'password' ) as string, credentialsExpiration: (changePasswordForm.getFieldValue( 'needChangePassword' ) as boolean) ? getNowUtc() : undefined }).then((res) => { const response = res.data if (response.success) { void message.success('修改密码成功') resolve(true) } else { reject(response.msg) } }) }) }, () => { return new Promise((_, reject) => { reject('输入有误') }) } ) .then(() => { getUser() }) }) } } const handleOnEditBtnClick = (value: UserWithRoleInfoVo) => { return () => { setIsDrawerEdit(true) setIsDrawerOpen(true) form.setFieldValue('id', value.id) form.setFieldValue('username', value.username) form.setFieldValue('password', undefined) form.setFieldValue('verified', !value.verify?.length) form.setFieldValue('locking', value.locking) form.setFieldValue('expiration', value.expiration) form.setFieldValue('credentialsExpiration', value.credentialsExpiration) form.setFieldValue('enable', value.enable) form.setFieldValue('nickname', value.userInfo.nickname) form.setFieldValue('avatar', value.userInfo.avatar) form.setFieldValue('email', value.userInfo.email) form.setFieldValue( 'roleIds', value.roles.map((role) => role.id) ) form.setFieldValue( 'groupIds', value.groups.map((group) => group.id) ) if (!roleData || !roleData.length) { getRoleData() } if (!groupData || !groupData.length) { getGroupData() } void form.validateFields() } } const handleOnDeleteBtnClick = (value: UserWithRoleInfoVo) => { return () => { modal .confirm({ title: '确定删除', content: `确定删除用户 ${value.username} 吗?` }) .then( (confirmed) => { if (confirmed) { setIsLoadingUserData(true) void r_sys_user_delete(value.id) .then((res) => { const response = res.data if (response.code === DATABASE_DELETE_SUCCESS) { void message.success('删除成功') setTimeout(() => { getUser() }) } else { void message.error('删除失败,请稍后重试') } }) .finally(() => { setIsLoadingUserData(false) }) } }, () => {} ) } } const handleOnDrawerClose = () => { setIsDrawerOpen(false) } const filterOption = (input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) const handleOnSubmit = () => { if (isDrawerSubmitting) { return } setIsDrawerSubmitting(true) if (isDrawerEdit) { void r_sys_user_update({ ...formValues, expiration: formValues.expiration ? localTimeToUtc(formValues.expiration) : undefined, credentialsExpiration: formValues.credentialsExpiration ? localTimeToUtc(formValues.credentialsExpiration) : undefined }) .then((res) => { const response = res.data switch (response.code) { case DATABASE_UPDATE_SUCCESS: setIsDrawerOpen(false) void message.success('更新成功') getUser() break case DATABASE_DUPLICATE_KEY: void message.error('已存在相同用户名或邮箱') break default: void message.error('更新失败,请稍后重试') } }) .finally(() => { setIsDrawerSubmitting(false) }) } else { void r_sys_user_add({ ...formValues, expiration: formValues.expiration ? localTimeToUtc(formValues.expiration) : undefined, credentialsExpiration: formValues.credentialsExpiration ? localTimeToUtc(formValues.credentialsExpiration) : undefined }) .then((res) => { const response = res.data switch (response.code) { case DATABASE_INSERT_SUCCESS: setIsDrawerOpen(false) void message.success('添加成功') setNewFormValues(undefined) getUser() break case DATABASE_DUPLICATE_KEY: void message.error('已存在相同用户名或邮箱') break default: void message.error('添加失败,请稍后重试') } }) .finally(() => { setIsDrawerSubmitting(false) }) } } const handleOnSearchValueChange = (e: React.ChangeEvent) => { setSearchValue(e.target.value) if (isUseRegex) { try { RegExp(e.target.value) setIsRegexLegal(!(e.target.value.includes('{}') || e.target.value.includes('[]'))) } catch (e) { setIsRegexLegal(false) } } else { setIsRegexLegal(true) } } const handleOnSearchNameKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { getUser() } } const handleOnSearchTypeChange = (value: string) => { setSearchType(value) } const handleOnUseRegexChange = (e: _CheckboxChangeEvent) => { setIsUseRegex(e.target.checked) if (e.target.checked) { try { RegExp(searchValue) setIsRegexLegal(!(searchValue.includes('{}') || searchValue.includes('[]'))) } catch (e) { setIsRegexLegal(false) } } else { setIsRegexLegal(true) } } const handleOnQueryBtnClick = () => { getUser() } const getUser = () => { if (isLoadingUserData) { return } if (!isRegexLegal) { void message.error('非法正则表达式') return } setIsLoadingUserData(true) void r_sys_user_get({ currentPage: tableParams.pagination?.current, pageSize: tableParams.pagination?.pageSize, sortField: tableParams.sortField && tableParams.sortOrder ? (tableParams.sortField as string) : undefined, sortOrder: tableParams.sortField && tableParams.sortOrder ? tableParams.sortOrder : undefined, searchType, searchValue: searchValue.trim().length ? searchValue : undefined, searchRegex: isUseRegex ? isUseRegex : undefined, ...tableParams.filters }) .then((res) => { const response = res.data if (response.code === DATABASE_SELECT_SUCCESS) { const records = response.data?.records records && setUserData(records) response.data && setTableParams({ ...tableParams, pagination: { ...tableParams.pagination, total: response.data.total } }) } else { void message.error('获取失败,请稍后重试') } }) .finally(() => { setIsLoadingUserData(false) }) } const handleOnDrawerRefresh = () => { getRoleData() getGroupData() } const getRoleData = () => { if (isLoadingRole) { return } setIsLoadingRole(true) void r_sys_role_get_list() .then((res) => { const response = res.data if (response.code === DATABASE_SELECT_SUCCESS) { response.data && setRoleData(response.data) } else { void message.error('获取角色列表失败,请稍后重试') } }) .finally(() => { setIsLoadingRole(false) }) } const getGroupData = () => { if (isLoadingGroup) { return } setIsLoadingGroup(true) void r_sys_group_get_list() .then((res) => { const response = res.data if (response.code === DATABASE_SELECT_SUCCESS) { response.data && setGroupData(response.data) } else { void message.error('获取用户组列表失败,请稍后重试') } }) .finally(() => { setIsLoadingGroup(false) }) } const getAvatar = () => { void r_api_avatar_random_base64().then((res) => { const response = res.data if (response.success) { response.data && setAvatar(response.data.base64) response.data && form.setFieldValue('avatar', response.data.base64) } }) } useEffect(() => { form.validateFields({ validateOnly: true }).then( () => { setIsDrawerSubmittable(true) }, () => { setIsDrawerSubmittable(false) } ) if (!isDrawerEdit && formValues) { setNewFormValues({ username: formValues.username, verified: formValues.verified, locking: formValues.locking, expiration: formValues.expiration, credentialsExpiration: formValues.credentialsExpiration, enable: formValues.enable, nickname: formValues.nickname, avatar: formValues.avatar, email: formValues.email, roleIds: formValues.roleIds, groupIds: formValues.groupIds }) } }, [formValues]) useUpdatedEffect(() => { getUser() }, [ JSON.stringify(tableParams.filters), JSON.stringify(tableParams.sortField), JSON.stringify(tableParams.sortOrder), JSON.stringify(tableParams.pagination?.pageSize), JSON.stringify(tableParams.pagination?.current) ]) const addAndEditForm = (
} size={100} style={{ background: COLOR_BACKGROUND, cursor: 'pointer' }} onClick={getAvatar} />
{!isDrawerEdit ? ( <> ) : undefined} {formValues?.id !== '0' ? ( <> ({ value: value.id, label: `${value.name}${!value.enable ? '(已禁用)' : ''}` }))} /> ({ value: value.id, label: `${value.name}${!value.enable ? '(已禁用)' : ''}` }))} /> (date ? { value: dayjs(date) } : {})} getValueFromEvent={(date: dayjs.Dayjs | null) => date ? dayjsToUtc(date) : undefined } > (date ? { value: dayjs(date) } : {})} getValueFromEvent={(date: dayjs.Dayjs | null) => date ? dayjsToUtc(date) : undefined } > ) : undefined}
) const toolbar = ( 全部 ID 用户名 昵称 邮箱 } suffix={ <> {!isRegexLegal ? ( 非法表达式 ) : undefined} .* } allowClear value={searchValue} onChange={handleOnSearchValueChange} onKeyDown={handleOnSearchNameKeyDown} status={isRegexLegal ? undefined : 'error'} /> 查询 ) const table = ( record.id} pagination={tableParams.pagination} loading={isLoadingUserData} onChange={handleOnTableChange} rowSelection={ hasPermission('system:user:delete:multiple') ? { type: 'checkbox', onChange: handleOnTableSelectChange, getCheckboxProps: (record) => ({ disabled: record.id === '0' }) } : undefined } /> ) const drawerToolbar = ( 取消 提交 ) return ( <> {toolbar} {table} {addAndEditForm} {contextHolder} ) } export default User