From 6ec51d51b6d1c360aaf256a609cd20c8626b84e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E6=9E=97=E8=80=80?= Date: Mon, 23 Sep 2024 18:30:37 +0800 Subject: [PATCH] optimize: Operation buttons on the user management interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 孙林耀 --- public/src/components/ButtonGroup/index.tsx | 127 +++++++++++++++ .../User/components/MultiStatusButton.tsx | 48 ------ public/src/pages/User/index.less | 5 + public/src/pages/User/index.tsx | 154 ++++++++++++++---- 4 files changed, 250 insertions(+), 84 deletions(-) create mode 100644 public/src/components/ButtonGroup/index.tsx delete mode 100644 public/src/pages/User/components/MultiStatusButton.tsx diff --git a/public/src/components/ButtonGroup/index.tsx b/public/src/components/ButtonGroup/index.tsx new file mode 100644 index 0000000..0d603b5 --- /dev/null +++ b/public/src/components/ButtonGroup/index.tsx @@ -0,0 +1,127 @@ +import { Dropdown, Button as AntdButton, DropdownProps, Space } from 'antd'; +import type { ButtonProps as AntdButtonProps } from 'antd'; +import { message } from 'antd'; +import { ButtonType } from 'antd/lib/button'; +import { MenuItemType } from 'antd/lib/menu/hooks/useItems'; +import { isBoolean } from 'lodash'; +import { MenuInfo } from 'rc-menu/lib/interface'; +import { ReactNode } from 'react'; +import { useState } from 'react'; + +import { DownOutlined } from '@ant-design/icons'; + +interface ButtonProps extends AntdButtonProps { + success?: React.ReactElement; + failed?: React.ReactElement; + onClick: (event: React.MouseEvent) => Promise; +} + +export const Button: React.FC = ({ success, failed, onClick, ...props }) => { + const [loading, setLoading] = useState(false); + const [status, setStatus] = useState<0 | 1 | 2>(0); + return ( + { + if (onClick) { + try { + setLoading(true); + const ok = await onClick(e); + if (isBoolean(ok) && !ok) { + setStatus(2); + } else { + setStatus(1); + } + } catch (error) { + message.error(`${error}`, 3); + setStatus(2); + } finally { + setLoading(false); + } + } + }} + > + {status === 1 && success ? success : status === 2 && failed ? failed : props.children} + + ); +}; + +interface ButtonItemType extends MenuItemType { + hidden?: boolean; + failed?: React.ReactElement; + success?: React.ReactElement; + type?: ButtonType; + onClick?: (info: Omit) => void; +} + +interface ButtonGroupProps extends DropdownProps { + maxItems?: number; + items: ButtonItemType[]; + moreLabel?: ReactNode; +} + +const GroupButtonItem: React.FC = ({ + success, + failed, + key, + style, + type = 'link', + onClick, + label, +}) => { + return ( + + ); +}; + +export const ButtonGroup: React.FC = ({ + maxItems, + items, + moreLabel = 'More', +}) => { + const visibleItems = items.filter((item) => item && !item.hidden); + if (maxItems === undefined || maxItems >= visibleItems.length) { + const buttons = visibleItems.map(GroupButtonItem); + if (maxItems === undefined) { + return {buttons}; + } + return {buttons}; + } + const dropdownItems = visibleItems.slice(maxItems - 1).map((item) => { + return { + ...item, + label:
{item.label}
, + }; + }); + return ( + + {visibleItems.slice(0, maxItems - 1).map(GroupButtonItem)} + + e.preventDefault()} style={{ gap: 3, display: 'inline-flex' }}> + {moreLabel} + + + + + ); +}; + +export default ButtonGroup; diff --git a/public/src/pages/User/components/MultiStatusButton.tsx b/public/src/pages/User/components/MultiStatusButton.tsx deleted file mode 100644 index 3c3523e..0000000 --- a/public/src/pages/User/components/MultiStatusButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { ButtonProps } from 'antd'; -import { message } from 'antd'; -import { Button } from 'antd'; -import { isBoolean } from 'lodash'; -import { useState } from 'react'; - -interface MultiStatusButtonProps extends ButtonProps { - success?: React.ReactElement; - failed?: React.ReactElement; - onClick: (event: React.MouseEvent) => Promise; -} - -export const MultiStatusButton: React.FC = ({ - success, - failed, - onClick, - ...props -}) => { - const [loading, setLoading] = useState(false); - const [status, setStatus] = useState<0 | 1 | 2>(0); - return ( - - ); -}; -export default MultiStatusButton; diff --git a/public/src/pages/User/index.less b/public/src/pages/User/index.less index 2f98ee5..1f9b90d 100644 --- a/public/src/pages/User/index.less +++ b/public/src/pages/User/index.less @@ -8,6 +8,11 @@ } } } +.UserDetails { + :global .ant-descriptions-extra > .ant-space { + flex-flow: row-reverse; + } +} :global { .ant-modal:has(.ant-pro-steps-form-step-active > .grant-view) { top: 30px; diff --git a/public/src/pages/User/index.tsx b/public/src/pages/User/index.tsx index 1836efc..17c15b8 100644 --- a/public/src/pages/User/index.tsx +++ b/public/src/pages/User/index.tsx @@ -3,6 +3,7 @@ import moment from 'moment'; import React, { useState, useRef, useEffect } from 'react'; import { useIntl } from 'umi'; +import ButtonGroup from '@/components/ButtonGroup'; import { UserStatus } from '@/services/idas/enums'; import { getSessions as getUserSessions, deleteSession } from '@/services/idas/sessions'; import { @@ -33,7 +34,7 @@ import ProTable from '@ant-design/pro-table'; import type { FormValueType } from './components/CreateOrUpdateForm'; import CreateOrUpdateForm from './components/CreateOrUpdateForm'; import GrantView from './components/GrantView'; -import MultiStatusButton from './components/MultiStatusButton'; +import styles from './index.less'; /** * @en-US Add node @@ -353,42 +354,111 @@ const UserList: React.FC = () => { dataIndex: 'option', valueType: 'option', render: (_, record) => { - const options: JSX.Element[] = [ - { - handleModalVisible(true); - setCurrentRow(record); - }} - > - {intl.t('button.edit', 'Edit')} - , - } - onClick={async () => { - if (!record.email) { - throw new Error(intl.t('activate.no-email', ' The user has no email.')); - } - const resp = await sendActivateMail({ - userId: record.id, - }); - if (resp.success) { - message.success(intl.t('activate.succcess', 'Email sent successfully.')); - } else { - throw new Error('Email sent failed.'); - } - }} - hidden={record.status !== UserStatus.user_inactive} - > - {intl.t('button.activate', 'Activate')} - , + return [ + { + handleModalVisible(true); + setCurrentRow(record); + }, + }, + { + key: 'activate', + label: intl.t('button.activate', 'Activate'), + hidden: record.status !== UserStatus.user_inactive, + success: , + style: { flex: 'unset' }, + onClick: async () => { + if (!record.email) { + throw new Error(intl.t('activate.no-email', ' The user has no email.')); + } + const resp = await sendActivateMail({ + userId: record.id, + }); + if (resp.success) { + message.success(intl.t('activate.succcess', 'Email sent successfully.')); + } else { + throw new Error(intl.t('activate.failed', 'Email sent failed.')); + } + }, + }, + { + key: 'delete', + label: intl.t('button.delete', 'Delete'), + style: { flex: 'unset' }, + onClick: () => { + Modal.confirm({ + title: intl.t( + 'deleteConfirm', + 'Are you sure you want to delete the following users? ', + ), + icon: , + async onOk() { + await handleRemove([record]); + }, + content: ( + + dataSource={[record]} + rowKey={'id'} + renderItem={(item) => ( + + {item.username} + {item.fullName + ? `(${item.fullName})` + : item.email + ? `(${item.email})` + : ''} + + )} + /> + ), + }); + }, + }, + { + key: 'disable', + label: intl.t('button.disable', 'Disable'), + hidden: record.status === UserStatus.disabled, + style: { flex: 'unset' }, + onClick: () => { + Modal.confirm({ + title: intl.t( + 'disableConfirm', + 'Are you sure you want to disable the following users?', + ), + icon: , + async onOk() { + await handleDisable([record]); + actionRef.current?.reloadAndRest?.(); + }, + content: ( + + dataSource={[record].filter((user) => user.status !== UserStatus.disabled)} + rowKey={'id'} + renderItem={(item) => ( + + {item.username} + {item.fullName + ? `(${item.fullName})` + : item.email + ? `(${item.email})` + : ''} + + )} + /> + ), + }); + }, + }, + ]} + />, ]; - - return options; }, }, ]; @@ -399,6 +469,7 @@ const UserList: React.FC = () => { actionRef={actionRef} rowKey="id" search={false} + tableAlertRender={false} toolbar={{ search: { onSearch: (kws) => { @@ -633,6 +704,7 @@ const UserList: React.FC = () => { params={{ id: currentRow?.id, }} + className={styles.UserDetails} extra={ <> { > {intl.t('button.save', 'Save')} + { + setGranting(false); + }} + style={{ flex: 'unset' }} + hidden={!granting} + > + {intl.t('button.cancel', 'Cancel')} + } columns={columns as ProDescriptionsItemProps[]}