import { CheckCircleOutline, ErrorOutlineOutlined, WarningAmberOutlined } from '@mui/icons-material'; import { LoadingButton } from '@mui/lab'; import { AlertColor, Box, ButtonProps, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle, Modal } from '@mui/material'; import { cloneElement, createElement, ReactElement, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { createRoot as _createRoot } from 'react-dom/client'; export let createRoot = _createRoot; /** * 用于同步组件树上下文,只需要将该组件放在应用组件树中渲染即可 */ export const ModalRoot: React.FC = () => { const [modals, setModals] = useState>({}); const rootRef = useRef(null); if (!rootRef.current) { rootRef.current = document.createElement('div')!; document.body.appendChild(rootRef.current); } useEffect(() => { createRoot = () => { const id = `mui-modal${Date.now() + Math.random()}`; return { render(element: ReactElement) { setModals(modals => ({ ...modals, [id]: cloneElement(element, { key: id }) })); }, unmount() { setModals(modals => { delete modals[id]; return { ...modals }; }); } }; }; return () => { if (rootRef.current) { document.body.appendChild(rootRef.current); } createRoot = _createRoot; }; }, []); return createPortal(Object.values(modals), rootRef.current); }; /** * 扩展Modal组件,使其类似于antd中的Modal组件,以支持以下快速调用: * * Modal.success - 创建成功提示框 * Modal.error * Modal.info * Modal.warn * * Modal.confirm - 创建具有ok和cancel两个按钮的确认框 * * Modal.open - 快速创建Modal弹窗,该方法是对Modal的一个命令式封装 * * 所有的方法均返回promise,可以通过promise获取用户关闭窗口的回调 */ export interface DialogSettings extends Omit { title?: React.ReactNode; // 如果传入字符串,自动使用DialogTitle包装 content?: React.ReactNode; // 如果传入字符串,自动使用DialogContent包装 okButtonProps?: ButtonProps; cancelButtonProps?: ButtonProps; okText?: string; cancelText?: string; onOK?(): any; onCancel?(): any; } export type ExtendModalObject = typeof Modal & { success(settings: DialogSettings): Promise; error(settings: DialogSettings): Promise; info(settings: DialogSettings): Promise; warning(settings: DialogSettings): Promise; confirm(settings: DialogSettings): Promise; open(settings: ModalSettings): Promise; }; type Scenes = AlertColor | 'confirm'; const IconPresets: Record = { success: , error: , info: , warning: , confirm: }; const _Modal = Modal as ExtendModalObject; _Modal.success = createDialog('success'); _Modal.error = createDialog('error'); _Modal.info = createDialog('info'); _Modal.warning = createDialog('warning'); _Modal.confirm = createDialog('confirm'); _Modal.open = openModal; export { _Modal as Modal }; const ExtendDialog: React.FC< DialogSettings & { type: Scenes; } > = ({ title, content, okText, cancelText, okButtonProps, cancelButtonProps, onOK, onCancel, type, ...dialogProps }) => { const [visible, setVisible] = useState(true); const [loading, setLoading] = useState(false); const hideDialog = () => setVisible(false); const isConfirm = type === 'confirm'; const handleOKButton = async () => { setLoading('ok'); try { await onOK?.(); hideDialog(); } finally { setLoading(false); } }; const handleCancelButton = async () => { setLoading('cancel'); try { await onCancel?.(); hideDialog(); } finally { setLoading(false); } }; return ( {cloneElement(IconPresets[type], { fontSize: 'large', sx: { mt: 2 } })} {title && (typeof title === 'string' ? {title} : title)} {content && (typeof content === 'string' ? {content} : content)} {isConfirm && ( {cancelText} )} {okText} ); }; function createDialog(type: Scenes) { return async (settings: DialogSettings) => { let clearContainer; const result = new Promise((resolve, reject) => { const container = document.createElement('div'); const root = createRoot(container); document.body.appendChild(container); clearContainer = () => { setTimeout(() => { root.unmount(); document.body.removeChild(container); }, 500); }; const updateDialog = () => { root.render( { const data = await settings.onOK?.(); resolve(data); }} onCancel={async () => { const reason = await settings.onCancel?.(); reject(reason); }} /> ); }; updateDialog(); }); result.finally(clearContainer).catch(() => {}); return result; }; } // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Modal // // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// export interface ModalSettings extends Omit { component: React.ComponentType; maskClosable?: boolean; // 禁用backdrop和ESC关闭 } export interface ModalHandler { close(data?: any): void; dismiss(reason?: any): void; } const ExtendModal: React.FC< ModalSettings & { modalHandler: ModalHandler; } > = ({ component, maskClosable, modalHandler, ...props }) => { const [visible, setVisible] = useState(true); const hideModal = () => setVisible(false); return ( { if (maskClosable || (reason !== 'backdropClick' && reason !== 'escapeKeyDown')) { modalHandler.dismiss(reason); hideModal(); } }}> {createElement(component, { close(data) { modalHandler.close(data); hideModal(); }, dismiss(reason) { modalHandler.dismiss(reason); hideModal(); } })} ); }; async function openModal(settings: ModalSettings) { let clearContainer; const result = new Promise((resolve, reject) => { const container = document.createElement('div'); const root = createRoot(container); document.body.appendChild(container); clearContainer = () => { setTimeout(() => { root.unmount(); document.body.removeChild(container); }, 500); }; const updateDialog = () => { root.render( ); }; updateDialog(); }); result.finally(clearContainer).catch(() => {}); return result; }