Files
oxygen-ui/src/pages/system/User.tsx

1043 lines
39 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<RoleVo[]>([])
const [groupData, setGroupData] = useState<GroupVo[]>([])
const [isLoadingRole, setIsLoadingRole] = useState(false)
const [isLoadingGroup, setIsLoadingGroup] = useState(false)
const [avatar, setAvatar] = useState('')
const [form] = AntdForm.useForm<UserAddEditParam>()
const formValues = AntdForm.useWatch([], form)
const [newFormValues, setNewFormValues] = useState<UserAddEditParam>()
const [changePasswordForm] = AntdForm.useForm<ChangePasswordFields>()
const [tableParams, setTableParams] = useState<TableParam>({
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<React.Key[]>([])
const [userData, setUserData] = useState<UserWithRoleInfoVo[]>([])
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<UserWithRoleInfoVo> = [
{
dataIndex: 'username',
title: '用户',
render: (value, record) => <AntdTooltip title={record.id}>{value}</AntdTooltip>,
width: '0'
},
{
dataIndex: ['userInfo', 'avatar'],
title: '头像',
render: (value) => (
<AntdAvatar
src={
<AntdImage
preview={{ mask: <Icon component={IconOxygenEye}></Icon> }}
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' ? (
<AntdTag color={'blue'}></AntdTag>
) : value.length ? (
value.map((role) => (
<AntdTag key={role.id} color={role.enable ? 'purple' : 'orange'}>
{role.name}
</AntdTag>
))
) : (
<AntdTag></AntdTag>
),
align: 'center'
},
{
dataIndex: ['groups'],
title: '用户组',
render: (value: GroupVo[], record) =>
record.id === '0' ? (
<AntdTag color={'blue'}></AntdTag>
) : value.length ? (
value.map((role) => (
<AntdTag key={role.id} color={role.enable ? 'purple' : 'orange'}>
{role.name}
</AntdTag>
))
) : (
<AntdTag></AntdTag>
),
align: 'center'
},
{
title: '最近登录',
render: (_, record) =>
record.currentLoginTime
? `${utcToLocalTime(record.currentLoginTime)}${record.currentLoginIp}`
: '无',
align: 'center'
},
{
title: '状态',
render: (_, record) => (
<AntdTooltip
title={
<>
<p>{utcToLocalTime(record.createTime)}</p>
<p>{utcToLocalTime(record.updateTime)}</p>
</>
}
>
{!record.verify &&
!record.locking &&
(!record.expiration || !isPastTime(record.expiration)) &&
(!record.credentialsExpiration || !isPastTime(record.credentialsExpiration)) &&
record.enable ? (
<AntdTag color={'green'}></AntdTag>
) : (
<>
{record.verify ? (
<>
<AntdPopover content={record.verify} trigger={'click'}>
<AntdTag style={{ cursor: 'pointer' }}></AntdTag>
</AntdPopover>
</>
) : undefined}
{record.locking ? <AntdTag></AntdTag> : undefined}
{record.expiration && isPastTime(record.expiration) ? (
<AntdTag></AntdTag>
) : undefined}
{record.credentialsExpiration &&
isPastTime(record.credentialsExpiration) ? (
<AntdTag></AntdTag>
) : undefined}
{!record.enable ? <AntdTag></AntdTag> : undefined}
</>
)}
</AntdTooltip>
),
align: 'center'
},
{
title: '操作',
width: '14em',
align: 'center',
render: (_, record) => (
<>
<AntdSpace size={'middle'}>
<Permission operationCode={'system:user:modify:password'}>
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnChangePasswordBtnClick(record)}
>
</a>
</Permission>
<Permission operationCode={'system:user:modify:one'}>
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnEditBtnClick(record)}
>
</a>
</Permission>
<Permission operationCode={'system:user:delete:one'}>
{record.id !== '0' ? (
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnDeleteBtnClick(record)}
>
</a>
) : undefined}
</Permission>
</AntdSpace>
</>
)
}
]
const handleOnTableChange = (
pagination: _TablePaginationConfig,
filters: Record<string, _FilterValue | null>,
sorter: _SorterResult<UserWithRoleInfoVo> | _SorterResult<UserWithRoleInfoVo>[]
) => {
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: (
<>
<Icon
style={{ color: COLOR_PRODUCTION, marginRight: 10 }}
component={IconOxygenSetting}
/>
{value.username}
</>
),
content: (
<AntdForm
form={changePasswordForm}
style={{ marginTop: 20 }}
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
>
<AntdForm.Item name={'id'} label={'ID'} labelAlign={'right'}>
<AntdInput disabled />
</AntdForm.Item>
<AntdForm.Item
name={'password'}
label={'密码'}
rules={[
{
required: true
}
]}
>
<AntdInput.Password />
</AntdForm.Item>
<AntdForm.Item
name={'passwordConfirm'}
label={'确认密码'}
rules={[
{
required: true
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve()
}
return Promise.reject(new Error('两次密码输入不一致'))
}
})
]}
>
<AntdInput.Password />
</AntdForm.Item>
{value.id !== '0' ? (
<AntdForm.Item
name={'needChangePassword'}
label={'需改密'}
valuePropName={'checked'}
rules={[{ type: 'boolean' }]}
>
<AntdSwitch />
</AntdForm.Item>
) : undefined}
</AntdForm>
),
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<HTMLInputElement>) => {
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 = (
<AntdForm
form={form}
disabled={isDrawerSubmitting}
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginBottom: 20
}}
>
<AntdTooltip title={'点击获取新头像'}>
<AntdAvatar
src={
<img
src={`data:image/png;base64,${
isDrawerEdit ? formValues?.avatar : avatar
}`}
alt={'Avatar'}
/>
}
size={100}
style={{ background: COLOR_BACKGROUND, cursor: 'pointer' }}
onClick={getAvatar}
/>
</AntdTooltip>
</div>
<AntdForm.Item hidden name={'avatar'}>
<AntdInput />
</AntdForm.Item>
<AntdForm.Item hidden={!isDrawerEdit} name={'id'} label={'ID'}>
<AntdInput disabled />
</AntdForm.Item>
<AntdForm.Item
name={'username'}
label={'用户名'}
rules={[{ required: true, whitespace: false }]}
>
<AntdInput allowClear />
</AntdForm.Item>
{!isDrawerEdit ? (
<>
<AntdForm.Item
name={'password'}
label={'密码'}
rules={[{ required: true, whitespace: false }]}
>
<AntdInput.Password allowClear />
</AntdForm.Item>
</>
) : undefined}
<AntdForm.Item name={'nickname'} label={'昵称'} rules={[{ whitespace: false }]}>
<AntdInput allowClear />
</AntdForm.Item>
<AntdForm.Item
name={'email'}
label={'邮箱'}
rules={[{ required: true, whitespace: false, type: 'email' }]}
>
<AntdInput type={'email'} allowClear />
</AntdForm.Item>
{formValues?.id !== '0' ? (
<>
<AntdForm.Item name={'roleIds'} label={'角色'}>
<AntdSelect
mode={'multiple'}
allowClear
showSearch
filterOption={filterOption}
options={roleData.map((value) => ({
value: value.id,
label: `${value.name}${!value.enable ? '(已禁用)' : ''}`
}))}
/>
</AntdForm.Item>
<AntdForm.Item name={'groupIds'} label={'用户组'}>
<AntdSelect
mode={'multiple'}
allowClear
showSearch
filterOption={filterOption}
options={groupData.map((value) => ({
value: value.id,
label: `${value.name}${!value.enable ? '(已禁用)' : ''}`
}))}
/>
</AntdForm.Item>
<AntdForm.Item name={'verified'} label={'已验证'}>
<AntdSwitch />
</AntdForm.Item>
<AntdForm.Item
valuePropName={'checked'}
name={'locking'}
label={'锁定'}
rules={[{ type: 'boolean' }]}
>
<AntdSwitch />
</AntdForm.Item>
<AntdForm.Item
name={'expiration'}
label={'过期时间'}
getValueProps={(date: string) => (date ? { value: dayjs(date) } : {})}
getValueFromEvent={(date: dayjs.Dayjs | null) =>
date ? dayjsToUtc(date) : undefined
}
>
<AntdDatePicker
showTime
allowClear
changeOnBlur
style={{ width: '100%' }}
/>
</AntdForm.Item>
<AntdForm.Item
name={'credentialsExpiration'}
label={'认证过期时间'}
getValueProps={(date: string) => (date ? { value: dayjs(date) } : {})}
getValueFromEvent={(date: dayjs.Dayjs | null) =>
date ? dayjsToUtc(date) : undefined
}
>
<AntdDatePicker
showTime
allowClear
changeOnBlur
style={{ width: '100%' }}
/>
</AntdForm.Item>
<AntdForm.Item
valuePropName={'checked'}
name={'enable'}
label={'启用'}
rules={[{ type: 'boolean' }]}
>
<AntdSwitch />
</AntdForm.Item>
</>
) : undefined}
</AntdForm>
)
const toolbar = (
<FlexBox direction={'horizontal'} gap={10}>
<Permission operationCode={'system:user:add:one'}>
<Card style={{ overflow: 'inherit', flex: '0 0 auto' }}>
<AntdButton
type={'primary'}
style={{ padding: '4px 8px' }}
onClick={handleOnAddBtnClick}
>
<Icon component={IconOxygenPlus} style={{ fontSize: '1.2em' }} />
</AntdButton>
</Card>
</Permission>
<Card
hidden={tableSelectedItem.length === 0}
style={{ overflow: 'inherit', flex: '0 0 auto' }}
>
<AntdButton style={{ padding: '4px 8px' }} onClick={handleOnListDeleteBtnClick}>
<Icon component={IconOxygenDelete} style={{ fontSize: '1.2em' }} />
</AntdButton>
</Card>
<Card style={{ overflow: 'inherit' }}>
<AntdInput
addonBefore={
<AntdSelect
value={searchType}
onChange={handleOnSearchTypeChange}
style={{ width: '6em' }}
dropdownStyle={{ textAlign: 'center' }}
>
<AntdSelect.Option value={'ALL'}></AntdSelect.Option>
<AntdSelect.Option value={'ID'}>ID</AntdSelect.Option>
<AntdSelect.Option value={'USERNAME'}></AntdSelect.Option>
<AntdSelect.Option value={'NICKNAME'}></AntdSelect.Option>
<AntdSelect.Option value={'EMAIL'}></AntdSelect.Option>
</AntdSelect>
}
suffix={
<>
{!isRegexLegal ? (
<span style={{ color: COLOR_ERROR_SECONDARY }}></span>
) : undefined}
<AntdCheckbox checked={isUseRegex} onChange={handleOnUseRegexChange}>
<AntdTooltip title={'正则表达式'}>.*</AntdTooltip>
</AntdCheckbox>
</>
}
allowClear
value={searchValue}
onChange={handleOnSearchValueChange}
onKeyDown={handleOnSearchNameKeyDown}
status={isRegexLegal ? undefined : 'error'}
/>
</Card>
<Card style={{ overflow: 'inherit', flex: '0 0 auto' }}>
<AntdButton onClick={handleOnQueryBtnClick} type={'primary'}>
</AntdButton>
</Card>
</FlexBox>
)
const table = (
<Card>
<AntdTable
dataSource={userData}
columns={dataColumns}
rowKey={(record) => 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
}
/>
</Card>
)
const drawerToolbar = (
<AntdSpace>
<AntdTooltip title={'刷新角色和用户组列表'}>
<AntdButton onClick={handleOnDrawerRefresh} disabled={isDrawerSubmitting}>
<Icon component={IconOxygenRefresh} />
</AntdButton>
</AntdTooltip>
<AntdButton onClick={handleOnDrawerClose} disabled={isDrawerSubmitting}>
</AntdButton>
<AntdButton
type={'primary'}
disabled={!isDrawerSubmittable}
loading={isDrawerSubmitting}
onClick={handleOnSubmit}
>
</AntdButton>
</AntdSpace>
)
return (
<>
<FitFullscreen>
<HideScrollbar
style={{ padding: 30 }}
isShowVerticalScrollbar
autoHideWaitingTime={500}
>
<FlexBox gap={20}>
{toolbar}
{table}
</FlexBox>
</HideScrollbar>
</FitFullscreen>
<AntdDrawer
title={isDrawerEdit ? '编辑用户' : '新增用户'}
width={'36vw'}
onClose={handleOnDrawerClose}
open={isDrawerOpen}
closable={!isDrawerSubmitting}
maskClosable={!isDrawerSubmitting}
extra={drawerToolbar}
>
{addAndEditForm}
</AntdDrawer>
{contextHolder}
</>
)
}
export default User