From 764a155e9f6d1a8af91414802fafc2d7aa06163e Mon Sep 17 00:00:00 2001 From: "renjie.yin" <16231189@qq.com> Date: Thu, 16 Nov 2023 18:27:15 +0800 Subject: [PATCH] =?UTF-8?q?feat=20:=20dialog=E6=94=AF=E6=8C=81=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E7=A7=BB=E5=8A=A8=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-editor/src/plugin/Dialog.tsx | 5 + packages/amis-ui/scss/components/_modal.scss | 4 + packages/amis-ui/src/components/Modal.tsx | 139 +++++++++++++++++-- packages/amis/src/renderers/Dialog.tsx | 7 +- 4 files changed, 140 insertions(+), 15 deletions(-) diff --git a/packages/amis-editor/src/plugin/Dialog.tsx b/packages/amis-editor/src/plugin/Dialog.tsx index f3cb95ef1..659293301 100644 --- a/packages/amis-editor/src/plugin/Dialog.tsx +++ b/packages/amis-editor/src/plugin/Dialog.tsx @@ -261,6 +261,11 @@ export class DialogPlugin extends BasePlugin { name: 'showLoading', value: true }), + getSchemaTpl('switch', { + label: '是否可拖拽', + name: 'draggable', + value: false + }), getSchemaTpl('dataMap') ] } diff --git a/packages/amis-ui/scss/components/_modal.scss b/packages/amis-ui/scss/components/_modal.scss index 5f8d2f054..2cea9cc3c 100644 --- a/packages/amis-ui/scss/components/_modal.scss +++ b/packages/amis-ui/scss/components/_modal.scss @@ -65,6 +65,10 @@ } } + &-draggable > &-header { + cursor: move; + } + &-overlay { transition: ease-in-out opacity var(--animation-duration); position: fixed; diff --git a/packages/amis-ui/src/components/Modal.tsx b/packages/amis-ui/src/components/Modal.tsx index c7edd5dd7..4a3c25455 100644 --- a/packages/amis-ui/src/components/Modal.tsx +++ b/packages/amis-ui/src/components/Modal.tsx @@ -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; 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 { static defaultProps = { container: document.body, size: '', - overlay: true + overlay: true, + draggable: false }; isRootClosed = false; @@ -173,6 +189,8 @@ export class Modal extends React.Component { ) ); + state: Readonly = {dragPos: undefined}; + componentDidMount() { if (this.props.show) { this.handleEnter(); @@ -283,6 +301,87 @@ export class Modal extends React.Component { 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 { height, modalClassName, modalMaskClassName, - classnames: cx + classnames: cx, + mobileUI, + draggable, + classPrefix } = this.props; let _style = { @@ -338,18 +440,27 @@ export class Modal extends React.Component { )} /> ) : null} -
- {status === EXITED ? null : children} -
+
+ {status === EXITED ? null : children} +
+ )} diff --git a/packages/amis/src/renderers/Dialog.tsx b/packages/amis/src/renderers/Dialog.tsx index d37a728c4..f28c6ac9d 100644 --- a/packages/amis/src/renderers/Dialog.tsx +++ b/packages/amis/src/renderers/Dialog.tsx @@ -123,6 +123,10 @@ export interface DialogSchema extends BaseSchema { * 弹框类型 confirm 确认弹框 */ dialogType?: 'confirm'; + /** + * 可拖拽 + */ + draggable?: boolean; } export type DialogSchemaBase = Omit; @@ -164,7 +168,8 @@ export default class Dialog extends React.Component { 'showErrorMsg', 'actions', 'popOverContainer', - 'overlay' + 'overlay', + 'draggable' ]; static defaultProps = { title: 'Dialog.title',