feat : dialog支持拖拽移动位置

This commit is contained in:
renjie.yin 2023-11-16 18:27:15 +08:00
parent e3ebd970a2
commit 764a155e9f
4 changed files with 140 additions and 15 deletions

View File

@ -261,6 +261,11 @@ export class DialogPlugin extends BasePlugin {
name: 'showLoading',
value: true
}),
getSchemaTpl('switch', {
label: '是否可拖拽',
name: 'draggable',
value: false
}),
getSchemaTpl('dataMap')
]
}

View File

@ -65,6 +65,10 @@
}
}
&-draggable > &-header {
cursor: move;
}
&-overlay {
transition: ease-in-out opacity var(--animation-duration);
position: fixed;

View File

@ -17,6 +17,13 @@ import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
import {Icon} from './icons';
import {LocaleProps, localeable} from 'amis-core';
import {autobind, getScrollbarWidth} from 'amis-core';
import {
DraggableCore,
type DraggableBounds,
type DraggableData,
type DraggableEvent
} from 'react-draggable';
import isNumber from 'lodash/isNumber';
export const getContainerWithFullscreen =
(container?: () => HTMLElement | HTMLElement | null) => () => {
@ -53,8 +60,14 @@ export interface ModalProps extends ThemeProps, LocaleProps {
children?: React.ReactNode | Array<React.ReactNode>;
modalClassName?: string;
modalMaskClassName?: string;
draggable?: boolean;
}
export interface ModalState {}
export interface ModalState {
bounds?: DraggableBounds;
dragPos?: {x: number; y: number};
}
const fadeStyles: {
[propName: string]: string;
} = {
@ -62,6 +75,7 @@ const fadeStyles: {
[ENTERED]: 'in',
[EXITING]: 'out'
};
const contentFadeStyles: {
[propName: string]: string;
} = {
@ -69,11 +83,13 @@ const contentFadeStyles: {
[ENTERED]: '',
[EXITING]: 'out'
};
export class Modal extends React.Component<ModalProps, ModalState> {
static defaultProps = {
container: document.body,
size: '',
overlay: true
overlay: true,
draggable: false
};
isRootClosed = false;
@ -173,6 +189,8 @@ export class Modal extends React.Component<ModalProps, ModalState> {
)
);
state: Readonly<ModalState> = {dragPos: undefined};
componentDidMount() {
if (this.props.show) {
this.handleEnter();
@ -283,6 +301,87 @@ export class Modal extends React.Component<ModalProps, ModalState> {
this.isRootClosed && !e.defaultPrevented && onHide(e);
}
// #region 处理dialog拖动
handleDragStart = (_event: DraggableEvent, uiData: DraggableData) => {
const node = uiData.node;
const {offsetParent} = node;
if (!node || !offsetParent) {
return;
}
const {clientWidth, clientHeight} = window.document.documentElement;
const nodeStyle = getComputedStyle(node);
const marginTop = parseInt(nodeStyle.marginTop, 10);
const nodeWidth = parseInt(nodeStyle.width, 10);
const nodeHeight = parseInt(nodeStyle.height, 10);
const bounds = {
left: 0,
right: clientWidth - nodeWidth,
top: -marginTop,
bottom: clientHeight - nodeHeight - marginTop
};
const parentRect = offsetParent.getBoundingClientRect();
const clientRect = node.getBoundingClientRect();
const cLeft = clientRect.left;
const pLeft = parentRect.left;
const cTop = clientRect.top;
const pTop = parentRect.top;
const left = cLeft - pLeft + offsetParent.scrollLeft;
const top = cTop - pTop + offsetParent.scrollTop - marginTop;
this.setState({dragPos: {x: left, y: top}, bounds});
// 阻止冒泡 存在弹窗里面套弹窗
_event.stopPropagation();
};
handleDrag = (e: DraggableEvent, {deltaX, deltaY}: DraggableData) => {
e.stopPropagation();
if (!this.state.dragPos) {
return;
}
const {
dragPos: {x, y},
bounds
} = this.state;
let calcY = y + deltaY;
let calcX = x + deltaX;
// 防止拖动到屏幕外 处理边界
if (isNumber(bounds?.right)) {
calcX = Math.min(calcX, bounds!.right);
}
if (isNumber(bounds?.bottom)) {
calcY = Math.min(calcY, bounds!.bottom);
}
if (isNumber(bounds?.left)) {
calcX = Math.max(calcX, bounds!.left);
}
if (isNumber(bounds?.top)) {
calcY = Math.max(calcY, bounds!.top);
}
this.setState({dragPos: {x: calcX, y: calcY}});
};
handleDragStop = (e: DraggableEvent) => {
e.stopPropagation();
};
getDragStyle = (): React.CSSProperties => {
const {draggable} = this.props;
const {dragPos} = this.state;
if (!dragPos || !draggable) {
return {};
}
const {x, y} = dragPos;
return {
top: `${y}px`,
left: `${x}px`,
position: 'absolute'
};
};
// #endregion
render() {
const {
className,
@ -297,7 +396,10 @@ export class Modal extends React.Component<ModalProps, ModalState> {
height,
modalClassName,
modalMaskClassName,
classnames: cx
classnames: cx,
mobileUI,
draggable,
classPrefix
} = this.props;
let _style = {
@ -338,18 +440,27 @@ export class Modal extends React.Component<ModalProps, ModalState> {
)}
/>
) : null}
<div
className={cx(
`Modal-content`,
size === 'custom' ? 'Modal-content-custom' : '',
contentClassName,
modalClassName,
contentFadeStyles[status]
)}
style={_style}
<DraggableCore
disabled={!draggable || mobileUI}
onStart={this.handleDragStart}
onDrag={this.handleDrag}
onStop={this.handleDragStop}
handle={`.${classPrefix}Modal-header`}
>
{status === EXITED ? null : children}
</div>
<div
className={cx(
`Modal-content`,
draggable && !mobileUI ? 'Modal-draggable' : '',
size === 'custom' ? 'Modal-content-custom' : '',
contentClassName,
modalClassName,
contentFadeStyles[status]
)}
style={{..._style, ...this.getDragStyle()}}
>
{status === EXITED ? null : children}
</div>
</DraggableCore>
</div>
</Portal>
)}

View File

@ -123,6 +123,10 @@ export interface DialogSchema extends BaseSchema {
* confirm
*/
dialogType?: 'confirm';
/**
*
*/
draggable?: boolean;
}
export type DialogSchemaBase = Omit<DialogSchema, 'type'>;
@ -164,7 +168,8 @@ export default class Dialog extends React.Component<DialogProps> {
'showErrorMsg',
'actions',
'popOverContainer',
'overlay'
'overlay',
'draggable'
];
static defaultProps = {
title: 'Dialog.title',