2021SC@SDUSC
首先导入相关的模块
import * as React from 'react';
import Dialog from 'rc-dialog';
import classNames from 'classnames';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import { getConfirmLocale } from './locale';
import Button from '../button';
import { LegacyButtonType, ButtonProps, convertLegacyProps } from '../button/button';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { ConfigContext, DirectionType } from '../config-provider';
import { canUseDocElement } from '../_util/styleChecker';
import { getTransitionName } from '../_util/motion';
设定变量,存储鼠标的点击坐标
let mousePosition: { x: number; y: number } | null;
const getClickPosition = (e: MouseEvent) => {
mousePosition = {
x: e.pageX,
y: e.pageY,
};
// 100ms 内发生过点击事件,则从点击位置动画展示
// 否则直接 zoom 展示
// 这样可以兼容非点击方式展开
setTimeout(() => {
mousePosition = null;
}, 100);
};
// 只有点击事件支持从鼠标位置动画展开
if (canUseDocElement()) {
document.documentElement.addEventListener('click', getClickPosition, true);
}
export interface ModalProps {
/** 对话框是否可见 */
visible?: boolean;
/** 确定按钮 loading */
confirmLoading?: boolean;
/** 标题 */
title?: React.ReactNode | string;
/** 是否显示右上角的关闭按钮 */
closable?: boolean;
/** 点击确定回调 */
onOk?: (e: React.MouseEvent<HTMLElement>) => void;
/** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调 */
onCancel?: (e: React.MouseEvent<HTMLElement>) => void;
/** 关闭对话框之后的事件 */
afterClose?: () => void;
/** 垂直居中 */
centered?: boolean;
/** 宽度 */
width?: string | number;
/** 底部内容 */
footer?: React.ReactNode;
/** 确认按钮文字 */
okText?: React.ReactNode;
/** 确认按钮类型 */
okType?: LegacyButtonType;
/** 取消按钮文字 */
cancelText?: React.ReactNode;
/** 点击蒙层是否允许关闭 */
maskClosable?: boolean;
/** 强制渲染 Modal */
forceRender?: boolean;
/** 对话框中的确认按钮,类型为ButtonProps */
okButtonProps?: ButtonProps;
/** 对话框中的取消按钮,类型为ButtonProps */
cancelButtonProps?: ButtonProps;
/** 关闭后是否完全清除此对话框的内容 */
destroyOnClose?: boolean;
/** 样式 */
style?: React.CSSProperties;
/** 对话框外层容器的类名 */
wrapClassName?: string;
/** 遮罩显示方式的名字 */
maskTransitionName?: string;
/** 对话框显示方式的名字 */
transitionName?: string;
/** 类名 */
className?: string;
/** 获取容器内的字符串或Element */
getContainer?: string | HTMLElement | getContainerFunc | false;
/** 对话框的纵坐标,即位于第几层,形成堆叠的效果 */
zIndex?: number;
/** body的样式 */
bodyStyle?: React.CSSProperties;
/** 遮罩的样式 */
maskStyle?: React.CSSProperties;
/** 是否有遮罩 */
mask?: boolean;
/** 是否可以使用键盘的esc关闭对话框 */
keyboard?: boolean;
/** 对话框外层容器 */
wrapProps?: any;
/** 前缀信息 */
prefixCls?: string;
/** 关闭按钮的图标 */
closeIcon?: React.ReactNode;
/** 对话框渲染样式 */
modalRender?: (node: React.ReactNode) => React.ReactNode;
/** 对话框关闭后是否需要聚焦触发元素 */
focusTriggerAfterClose?: boolean;
}
生成一个默认的对话框Modal
// 声明ModalLocale
export interface ModalLocale {
okText: string;
cancelText: string;
justOkText: string;
}
const Modal: React.FC<ModalProps> = props => {
const {
getPopupContainer: getContextPopupContainer,
getPrefixCls,
direction,
} = React.useContext(ConfigContext);
// 手动取消对话框
const handleCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
const { onCancel } = props;
onCancel?.(e);
};
// 手动确认对话框
const handleOk = (e: React.MouseEvent<HTMLButtonElement>) => {
const { onOk } = props;
onOk?.(e);
};
// 设置页脚,包括取消按钮与确定按钮
const renderFooter = (locale: ModalLocale) => {
const { okText, okType, cancelText, confirmLoading } = props;
return (
<>
<!--取消按钮-->
<Button onClick={handleCancel} {...props.cancelButtonProps}>
{cancelText || locale.cancelText}
</Button>
<!--确认按钮-->
<Button
{...convertLegacyProps(okType)}
loading={confirmLoading}
onClick={handleOk}
{...props.okButtonProps}
>
{okText || locale.okText}
</Button>
</>
);
};
const {
prefixCls: customizePrefixCls,
footer,
visible,
wrapClassName,
centered,
getContainer,
closeIcon,
focusTriggerAfterClose = true,
...restProps
} = props;
const prefixCls = getPrefixCls('modal', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
//默认的页脚
const defaultFooter = (
<LocaleReceiver componentName="Modal" defaultLocale={getConfirmLocale()}>
{renderFooter}
</LocaleReceiver>
);
//关闭图标
const closeIconToRender = (
<span className={`${prefixCls}-close-x`}>
{closeIcon || <CloseOutlined className={`${prefixCls}-close-icon`} />}
</span>
);
//外层类名
const wrapClassNameExtended = classNames(wrapClassName, {
[`${prefixCls}-centered`]: !!centered,
[`${prefixCls}-wrap-rtl`]: direction === 'rtl',
});
//最后返回一个对话框
return (
<Dialog
{...restProps}
getContainer={
getContainer === undefined ? (getContextPopupContainer as getContainerFunc) : getContainer
}
prefixCls={prefixCls}
wrapClassName={wrapClassNameExtended}
footer={footer === undefined ? defaultFooter : footer}
visible={visible}
mousePosition={mousePosition}
onClose={handleCancel}
closeIcon={closeIconToRender}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
/>
);
};
再设置一下对话框的其他默认参数
Modal.defaultProps = {
width: 520,
confirmLoading: false,
visible: false,
okType: 'primary' as LegacyButtonType,
};
再来看ConfirmDialog
首先导入相应需要的模块
import * as React from 'react';
import classNames from 'classnames';
import Dialog, { ModalFuncProps } from './Modal';
import ActionButton from '../_util/ActionButton';
import devWarning from '../_util/devWarning';
import ConfigProvider from '../config-provider';
import { getTransitionName } from '../_util/motion';
首先声明一个自己的实例,使用继承方法从Modal里继承
// 从Modal继承下来的ConfirmDialogProps
interface ConfirmDialogProps extends ModalFuncProps {
afterClose?: () => void;
close: (...args: any[]) => void;
autoFocusButton?: null | 'ok' | 'cancel';
rootPrefixCls: string;
iconPrefixCls?: string;
}
最后定义实例,其中包含的变量已在上面的Modal提出
const ConfirmDialog = (props: ConfirmDialogProps) => {
const {
icon,
onCancel,
onOk,
close,
zIndex,
afterClose,
visible,
keyboard,
centered,
getContainer,
maskStyle,
okText,
okButtonProps,
cancelText,
cancelButtonProps,
direction,
prefixCls,
rootPrefixCls,
iconPrefixCls,
bodyStyle,
closable = false,
closeIcon,
modalRender,
focusTriggerAfterClose,
} = props;
devWarning(
!(typeof icon === 'string' && icon.length > 2),
'Modal',
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
);
// 支持传入{ icon: null }来隐藏`Modal.confirm`默认的Icon
const okType = props.okType || 'primary';
const contentPrefixCls = `${prefixCls}-confirm`;
// 默认为 true,保持向下兼容
const okCancel = 'okCancel' in props ? props.okCancel! : true;
// 默认宽度为416
const width = props.width || 416;
// 默认样式为空
const style = props.style || {};
// 默认存在遮罩
const mask = props.mask === undefined ? true : props.mask;
// 默认为 false,保持旧版默认行为
const maskClosable = props.maskClosable === undefined ? false : props.maskClosable;
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
// 为本Dialog的类名,会被导出
const classString = classNames(
contentPrefixCls,
`${contentPrefixCls}-${props.type}`,
{ [`${contentPrefixCls}-rtl`]: direction === 'rtl' },
props.className,
);
// 定义了一个取消按钮,会被嵌套在Dialog中
const cancelButton = okCancel && (
<ActionButton
actionFn={onCancel}
close={close}
autoFocus={autoFocusButton === 'cancel'}
buttonProps={cancelButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{cancelText}
</ActionButton>
);
// 最后返回一个成型的Dialog
return (
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} direction={direction}>
<!--对话框的本体,使用了上述变量-->
<Dialog
prefixCls={prefixCls}
className={classString}
wrapClassName={classNames({ [`${contentPrefixCls}-centered`]: !!props.centered })}
onCancel={() => close({ triggerCancel: true })}
visible={visible}
title=""
footer=""
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
mask={mask}
maskClosable={maskClosable}
maskStyle={maskStyle}
style={style}
width={width}
zIndex={zIndex}
afterClose={afterClose}
keyboard={keyboard}
centered={centered}
getContainer={getContainer}
closable={closable}
closeIcon={closeIcon}
modalRender={modalRender}
focusTriggerAfterClose={focusTriggerAfterClose}
>
<!--对话框的类名,即包含内容以及标题等-->
<div className={`${contentPrefixCls}-body-wrapper`}>
<div className={`${contentPrefixCls}-body`} style={bodyStyle}>
{icon}
{props.title === undefined ? null : (
<span className={`${contentPrefixCls}-title`}>{props.title}</span>
)}
<div className={`${contentPrefixCls}-content`}>{props.content}</div>
</div>
<div className={`${contentPrefixCls}-btns`}>
<!--取消按钮-->
{cancelButton}
<!--确认按钮-->
<ActionButton
type={okType}
actionFn={onOk}
close={close}
autoFocus={autoFocusButton === 'ok'}
buttonProps={okButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{okText}
</ActionButton>
</div>
</div>
</Dialog>
</ConfigProvider>
);
};