u
This commit is contained in:
1 parent
d917b0f6e3
commit
d37d92996a
3 files changed
+105
-98
No files matched your search
@@ -40,6 +40,7 @@ $btn-prefix: '#{$prefix}-btn';
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
.#{$modal-prefix} {
|
.#{$modal-prefix} {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -59,7 +60,6 @@ $btn-prefix: '#{$prefix}-btn';
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border-bottom: 1px solid $separator-color;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,6 @@ $btn-prefix: '#{$prefix}-btn';
|
|||||||
&-footer {
|
&-footer {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-top: 1px solid $separator-color;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
.#{$btn-prefix}:not(:first-child) {
|
.#{$btn-prefix}:not(:first-child) {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PropType, Ref } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { computed, defineComponent, onMounted, ref, Teleport, Transition, watch } from 'vue'
|
import { computed, defineComponent, onMounted, ref, Teleport, Transition, watch } from 'vue'
|
||||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||||
import Button from '../button/index'
|
import Button from '../button/index'
|
||||||
@@ -7,32 +7,81 @@ import Icon from '../icon/index'
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Modal',
|
name: 'Modal',
|
||||||
props: {
|
props: {
|
||||||
visible: { type: Boolean, default: undefined },
|
visible: {
|
||||||
defaultVisible: { type: Boolean, default: false },
|
type: Boolean,
|
||||||
mask: { type: Boolean, default: true },
|
default: undefined,
|
||||||
maskClosable: { type: Boolean, default: true },
|
},
|
||||||
position: { type: String as PropType<'top' | 'center' | 'bottom'>, default: 'center' },
|
defaultVisible: {
|
||||||
type: { type: String as PropType<'default' | 'info' | 'success' | 'warning' | 'danger' | 'question'>, default: 'default' },
|
type: Boolean,
|
||||||
showClose: { type: Boolean, default: true },
|
default: false,
|
||||||
confirmBtn: { type: null, default: () => ({ label: '确认', type: 'solid', status: 'primary', size: 'small' }) },
|
},
|
||||||
cancelBtn: { type: null, default: () => ({ label: '取消', type: 'solid', status: 'default', size: 'small' }) },
|
mask: {
|
||||||
onBeforeConfirm: { type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise<boolean>> },
|
type: Boolean,
|
||||||
onBeforeCancel: { type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise<boolean>> },
|
default: true,
|
||||||
destroyOnClose: { type: Boolean, default: false },
|
},
|
||||||
appendTo: { type: [String, Object] as PropType<string | HTMLElement>, default: 'body' },
|
maskClosable: {
|
||||||
fullscreen: { type: Boolean, default: false },
|
type: Boolean,
|
||||||
draggable: { type: Boolean, default: false },
|
default: true,
|
||||||
draggableBoundary: { type: Boolean, default: false },
|
},
|
||||||
draggableRef: { type: Object as PropType<Ref<HTMLElement | null> | null>, default: null },
|
position: {
|
||||||
class: { type: [String, Array, Object], default: '' },
|
type: String as PropType<'top' | 'center' | 'bottom'>,
|
||||||
style: { type: Object, default: () => ({}) },
|
default: 'center',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String as PropType<'default' | 'info' | 'success' | 'warning' | 'danger' | 'question'>,
|
||||||
|
default: 'default',
|
||||||
|
},
|
||||||
|
showClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
confirmBtn: {
|
||||||
|
type: null,
|
||||||
|
default: () => ({ label: '确认', type: 'solid', status: 'primary', size: 'small' }),
|
||||||
|
},
|
||||||
|
cancelBtn: {
|
||||||
|
type: null,
|
||||||
|
default: () => ({ label: '取消', type: 'solid', status: 'default', size: 'small' }),
|
||||||
|
},
|
||||||
|
destroyOnClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
appendTo: {
|
||||||
|
type: [String, Object] as PropType<string | HTMLElement>,
|
||||||
|
default: 'body',
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
draggableBoundary: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
draggableSelector: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
}, // 拖拽区域选择器
|
||||||
|
class: {
|
||||||
|
type: [String, Array, Object],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:visible', 'confirm', 'cancel', 'open', 'close', 'beforeOpen', 'beforeClose'],
|
emits: ['update:visible', 'confirm', 'cancel', 'open', 'close', 'beforeOpen', 'beforeClose'],
|
||||||
setup(props, { slots, emit }) {
|
setup(props, { slots, emit }) {
|
||||||
const prefixCls = getPrefixCls('modal')
|
const prefixCls = getPrefixCls('modal')
|
||||||
const targetEl = ref<HTMLElement>(document.body)
|
const targetEl = ref<HTMLElement>(document.body)
|
||||||
const confirmLoading = ref(false)
|
|
||||||
const modalRef = ref<HTMLElement>()
|
const modalRef = ref<HTMLElement>()
|
||||||
|
const confirmLoading = ref(false)
|
||||||
|
|
||||||
const _visible = ref(props.defaultVisible)
|
const _visible = ref(props.defaultVisible)
|
||||||
const isVisible = computed(() => props.visible ?? _visible.value)
|
const isVisible = computed(() => props.visible ?? _visible.value)
|
||||||
@@ -69,48 +118,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
let allow = true
|
|
||||||
if (props.onBeforeCancel) {
|
|
||||||
try {
|
|
||||||
allow = await new Promise<boolean>((resolve) => {
|
|
||||||
const result = props.onBeforeCancel!(ok => resolve(ok))
|
|
||||||
if (typeof result === 'boolean')
|
|
||||||
resolve(result)
|
|
||||||
else if (result instanceof Promise)
|
|
||||||
result.then(resolve).catch(() => resolve(false))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch { allow = false }
|
|
||||||
}
|
|
||||||
if (allow) {
|
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
emit('cancel')
|
emit('cancel')
|
||||||
confirmLoading.value = false
|
confirmLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
let allow = true
|
|
||||||
if (props.onBeforeConfirm) {
|
|
||||||
confirmLoading.value = true
|
|
||||||
try {
|
|
||||||
allow = await new Promise<boolean>((resolve) => {
|
|
||||||
const result = props.onBeforeConfirm!(ok => resolve(ok))
|
|
||||||
if (typeof result === 'boolean')
|
|
||||||
resolve(result)
|
|
||||||
else if (result instanceof Promise)
|
|
||||||
result.then(resolve).catch(() => resolve(false))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch { allow = false }
|
|
||||||
}
|
|
||||||
if (allow) {
|
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
emit('confirm')
|
emit('confirm')
|
||||||
}
|
|
||||||
confirmLoading.value = false
|
confirmLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAfterEnter = () => emit('open')
|
const handleAfterEnter = () => emit('open')
|
||||||
const handleAfterLeave = () => {
|
const handleAfterLeave = () => {
|
||||||
isRootVisible.value = false
|
isRootVisible.value = false
|
||||||
@@ -125,28 +141,21 @@ export default defineComponent({
|
|||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (!dragData.dragging || !modalRef.value)
|
if (!dragData.dragging || !modalRef.value)
|
||||||
return
|
return
|
||||||
const modal = modalRef.value
|
const rect = modalRef.value.getBoundingClientRect()
|
||||||
const rect = modal.getBoundingClientRect()
|
let left = e.clientX - dragData.offsetX
|
||||||
let newLeft = e.clientX - dragData.offsetX
|
let top = e.clientY - dragData.offsetY
|
||||||
let newTop = e.clientY - dragData.offsetY
|
|
||||||
|
|
||||||
if (props.draggableBoundary) {
|
if (props.draggableBoundary) {
|
||||||
const winWidth = window.innerWidth
|
const w = window.innerWidth
|
||||||
const winHeight = window.innerHeight
|
const h = window.innerHeight
|
||||||
if (newLeft < 0)
|
left = Math.min(Math.max(0, left), w - rect.width)
|
||||||
newLeft = 0
|
top = Math.min(Math.max(0, top), h - rect.height)
|
||||||
if (newTop < 0)
|
|
||||||
newTop = 0
|
|
||||||
if (newLeft + rect.width > winWidth)
|
|
||||||
newLeft = winWidth - rect.width
|
|
||||||
if (newTop + rect.height > winHeight)
|
|
||||||
newTop = winHeight - rect.height
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.style.left = `${newLeft}px`
|
modalRef.value.style.left = `${left}px`
|
||||||
modal.style.top = `${newTop}px`
|
modalRef.value.style.top = `${top}px`
|
||||||
modal.style.position = 'absolute'
|
modalRef.value.style.position = 'absolute'
|
||||||
modal.style.transform = ''
|
modalRef.value.style.transform = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
@@ -156,21 +165,23 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
if (!modalRef.value)
|
if (!props.draggable || !modalRef.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
let dragTarget: HTMLElement | null = null
|
// 自动选择拖拽区域
|
||||||
|
let dragEl: HTMLElement | null = null
|
||||||
|
|
||||||
// 如果传了 ref
|
if (props.draggableSelector) {
|
||||||
if (props.draggableRef?.value) {
|
dragEl = modalRef.value.querySelector(props.draggableSelector)
|
||||||
dragTarget = props.draggableRef.value
|
|
||||||
}
|
}
|
||||||
// 否则默认 header
|
|
||||||
else {
|
else {
|
||||||
dragTarget = modalRef.value.querySelector(`.${prefixCls}-header`)
|
dragEl = modalRef.value.querySelector(`.${prefixCls}-header`)
|
||||||
}
|
}
|
||||||
|
// 如果没找到,默认拖动整个 modal
|
||||||
|
if (!dragEl)
|
||||||
|
dragEl = modalRef.value
|
||||||
|
|
||||||
if (!dragTarget || !dragTarget.contains(e.target as Node))
|
if (!dragEl.contains(e.target as Node))
|
||||||
return
|
return
|
||||||
|
|
||||||
dragData.dragging = true
|
dragData.dragging = true
|
||||||
@@ -199,22 +210,22 @@ export default defineComponent({
|
|||||||
<div v-show={isVisible.value} class={`${prefixCls}-mask`} onClick={handleMaskClick} />
|
<div v-show={isVisible.value} class={`${prefixCls}-mask`} onClick={handleMaskClick} />
|
||||||
</Transition>
|
</Transition>
|
||||||
)}
|
)}
|
||||||
<Transition
|
<Transition name={`${prefixCls}-content-fade`} appear onAfterEnter={handleAfterEnter} onAfterLeave={handleAfterLeave}>
|
||||||
name={`${prefixCls}-content-fade`}
|
|
||||||
appear
|
|
||||||
onAfterEnter={handleAfterEnter}
|
|
||||||
onAfterLeave={handleAfterLeave}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-show={isVisible.value}
|
v-show={isVisible.value}
|
||||||
class={[`${prefixCls}-wrapper`, `${prefixCls}-position-${props.position}`, props.class]}
|
class={[`${prefixCls}-wrapper`, `${prefixCls}-position-${props.position}`, props.class]}
|
||||||
style={{ ...props.style }}
|
style={{ ...props.style }}
|
||||||
>
|
>
|
||||||
<div ref={modalRef} class={`${prefixCls} ${props.fullscreen ? `${prefixCls}-fullscreen` : ''}`}>
|
<div
|
||||||
|
ref={modalRef}
|
||||||
|
onMousedown={handleMouseDown}
|
||||||
|
class={`${prefixCls} ${props.fullscreen ? `${prefixCls}-fullscreen` : ''}`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class={`${prefixCls}-header`}
|
class={`${prefixCls}-header`}
|
||||||
onMousedown={handleMouseDown}
|
style={{
|
||||||
style={{ cursor: props.draggable ? 'move' : undefined }}
|
cursor: props.draggable && (!props.draggableSelector || props.draggableSelector === `${prefixCls}-header`) ? 'move' : undefined,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{props.type !== 'default' && <Icon class={[`${prefixCls}-icon`, `${prefixCls}-icon-${props.type}`]} icon={typeIconMap[props.type]} />}
|
{props.type !== 'default' && <Icon class={[`${prefixCls}-icon`, `${prefixCls}-icon-${props.type}`]} icon={typeIconMap[props.type]} />}
|
||||||
<span class={`${prefixCls}-title`}>{slots.title?.()}</span>
|
<span class={`${prefixCls}-title`}>{slots.title?.()}</span>
|
||||||
|
|||||||
@@ -18,16 +18,13 @@ const dragHandle = ref<HTMLElement | null>(null)
|
|||||||
打开 Header 拖拽
|
打开 Header 拖拽
|
||||||
</AButton>
|
</AButton>
|
||||||
|
|
||||||
<!-- ref 拖拽 -->
|
<AModal v-model:visible="visible2" :draggable="true" draggable-selector=".drag-area">
|
||||||
<AModal v-model:visible="visible2" :draggable="true" :draggable-ref="dragHandle">
|
<div class="drag-area" style="padding:10px; background:#ffdede; cursor:move">
|
||||||
<template #title>
|
拖拽这里
|
||||||
Ref 拖拽示例
|
|
||||||
</template>
|
|
||||||
<div ref="dragHandle" style="padding:10px; background:#ffdede; cursor:move;">
|
|
||||||
拖拽这个区域 (ref)
|
|
||||||
</div>
|
</div>
|
||||||
<p>Modal 内容</p>
|
<p>Modal 内容</p>
|
||||||
</AModal>
|
</AModal>
|
||||||
|
|
||||||
<AButton @click="visible2 = true">
|
<AButton @click="visible2 = true">
|
||||||
打开 Ref 拖拽
|
打开 Ref 拖拽
|
||||||
</AButton>
|
</AButton>
|
||||||
|
|||||||
Reference in new issue
Block a user