u
This commit is contained in:
1 parent
35bc29f356
commit
d917b0f6e3
2 files changed
+123
-124
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
import type { PropType } from 'vue'
|
import type { PropType, Ref } 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,90 +7,43 @@ import Icon from '../icon/index'
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Modal',
|
name: 'Modal',
|
||||||
props: {
|
props: {
|
||||||
visible: {
|
visible: { type: Boolean, default: undefined },
|
||||||
type: Boolean,
|
defaultVisible: { type: Boolean, default: false },
|
||||||
default: undefined,
|
mask: { type: Boolean, default: true },
|
||||||
},
|
maskClosable: { type: Boolean, default: true },
|
||||||
defaultVisible: {
|
position: { type: String as PropType<'top' | 'center' | 'bottom'>, default: 'center' },
|
||||||
type: Boolean,
|
type: { type: String as PropType<'default' | 'info' | 'success' | 'warning' | 'danger' | 'question'>, default: 'default' },
|
||||||
default: false,
|
showClose: { type: Boolean, default: true },
|
||||||
},
|
confirmBtn: { type: null, default: () => ({ label: '确认', type: 'solid', status: 'primary', size: 'small' }) },
|
||||||
mask: {
|
cancelBtn: { type: null, default: () => ({ label: '取消', type: 'solid', status: 'default', size: 'small' }) },
|
||||||
type: Boolean,
|
onBeforeConfirm: { type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise<boolean>> },
|
||||||
default: true,
|
onBeforeCancel: { type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise<boolean>> },
|
||||||
},
|
destroyOnClose: { type: Boolean, default: false },
|
||||||
maskClosable: {
|
appendTo: { type: [String, Object] as PropType<string | HTMLElement>, default: 'body' },
|
||||||
type: Boolean,
|
fullscreen: { type: Boolean, default: false },
|
||||||
default: true,
|
draggable: { type: Boolean, default: false },
|
||||||
},
|
draggableBoundary: { type: Boolean, default: false },
|
||||||
position: {
|
draggableRef: { type: Object as PropType<Ref<HTMLElement | null> | null>, default: null },
|
||||||
type: String as PropType<'top' | 'center' | 'bottom'>,
|
class: { type: [String, Array, Object], default: '' },
|
||||||
default: 'center',
|
style: { type: Object, default: () => ({}) },
|
||||||
},
|
|
||||||
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' }),
|
|
||||||
},
|
|
||||||
onBeforeConfirm: {
|
|
||||||
type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise<boolean>>,
|
|
||||||
},
|
|
||||||
onBeforeCancel: {
|
|
||||||
type: Function as PropType<(done: (ok: boolean) => void) => void | boolean | Promise<boolean>>,
|
|
||||||
},
|
|
||||||
destroyOnClose: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
appendTo: {
|
|
||||||
type: [String, Object] as PropType<string | HTMLElement>,
|
|
||||||
default: 'body',
|
|
||||||
},
|
|
||||||
fullscreen: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
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 confirmLoading = ref(false)
|
||||||
|
const modalRef = ref<HTMLElement>()
|
||||||
|
|
||||||
const _visible = ref(props.defaultVisible)
|
const _visible = ref(props.defaultVisible)
|
||||||
|
|
||||||
const isVisible = computed(() => props.visible ?? _visible.value)
|
const isVisible = computed(() => props.visible ?? _visible.value)
|
||||||
|
|
||||||
const isMounted = ref(isVisible.value)
|
const isMounted = ref(isVisible.value)
|
||||||
const isRootVisible = ref(isVisible.value)
|
const isRootVisible = ref(isVisible.value)
|
||||||
|
|
||||||
watch(isVisible, (value) => {
|
watch(isVisible, (value) => {
|
||||||
if (_visible.value !== value) {
|
if (_visible.value !== value)
|
||||||
_visible.value = value
|
_visible.value = value
|
||||||
}
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
emit('beforeOpen')
|
emit('beforeOpen')
|
||||||
|
|
||||||
isMounted.value = true
|
isMounted.value = true
|
||||||
isRootVisible.value = true
|
isRootVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -111,10 +64,9 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleMaskClick = () => {
|
const handleMaskClick = () => {
|
||||||
if (props.maskClosable) {
|
if (props.maskClosable)
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
let allow = true
|
let allow = true
|
||||||
@@ -128,11 +80,8 @@ export default defineComponent({
|
|||||||
result.then(resolve).catch(() => resolve(false))
|
result.then(resolve).catch(() => resolve(false))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
catch { allow = false }
|
||||||
allow = false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (allow) {
|
if (allow) {
|
||||||
emit('update:visible', false)
|
emit('update:visible', false)
|
||||||
emit('cancel')
|
emit('cancel')
|
||||||
@@ -162,18 +111,77 @@ export default defineComponent({
|
|||||||
confirmLoading.value = false
|
confirmLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAfterEnter = () => {
|
const handleAfterEnter = () => emit('open')
|
||||||
emit('open')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAfterLeave = () => {
|
const handleAfterLeave = () => {
|
||||||
isRootVisible.value = false
|
isRootVisible.value = false
|
||||||
if (props.destroyOnClose) {
|
if (props.destroyOnClose)
|
||||||
isMounted.value = false
|
isMounted.value = false
|
||||||
}
|
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拖拽逻辑
|
||||||
|
const dragData = { dragging: false, offsetX: 0, offsetY: 0 }
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!dragData.dragging || !modalRef.value)
|
||||||
|
return
|
||||||
|
const modal = modalRef.value
|
||||||
|
const rect = modal.getBoundingClientRect()
|
||||||
|
let newLeft = e.clientX - dragData.offsetX
|
||||||
|
let newTop = e.clientY - dragData.offsetY
|
||||||
|
|
||||||
|
if (props.draggableBoundary) {
|
||||||
|
const winWidth = window.innerWidth
|
||||||
|
const winHeight = window.innerHeight
|
||||||
|
if (newLeft < 0)
|
||||||
|
newLeft = 0
|
||||||
|
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`
|
||||||
|
modal.style.top = `${newTop}px`
|
||||||
|
modal.style.position = 'absolute'
|
||||||
|
modal.style.transform = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
dragData.dragging = false
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
if (!modalRef.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
let dragTarget: HTMLElement | null = null
|
||||||
|
|
||||||
|
// 如果传了 ref
|
||||||
|
if (props.draggableRef?.value) {
|
||||||
|
dragTarget = props.draggableRef.value
|
||||||
|
}
|
||||||
|
// 否则默认 header
|
||||||
|
else {
|
||||||
|
dragTarget = modalRef.value.querySelector(`.${prefixCls}-header`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dragTarget || !dragTarget.contains(e.target as Node))
|
||||||
|
return
|
||||||
|
|
||||||
|
dragData.dragging = true
|
||||||
|
const rect = modalRef.value.getBoundingClientRect()
|
||||||
|
dragData.offsetX = e.clientX - rect.left
|
||||||
|
dragData.offsetY = e.clientY - rect.top
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
const typeIconMap: Record<string, string> = {
|
const typeIconMap: Record<string, string> = {
|
||||||
success: 'ix:success-filled',
|
success: 'ix:success-filled',
|
||||||
danger: 'ix:error-filled',
|
danger: 'ix:error-filled',
|
||||||
@@ -182,8 +190,7 @@ export default defineComponent({
|
|||||||
question: 'ix:question-filled',
|
question: 'ix:question-filled',
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => (
|
||||||
return (
|
|
||||||
<Teleport to={targetEl.value}>
|
<Teleport to={targetEl.value}>
|
||||||
{isMounted.value && (
|
{isMounted.value && (
|
||||||
<div v-show={isRootVisible.value} class={`${prefixCls}-container`} style={{ position: 'fixed', zIndex: 1000 }}>
|
<div v-show={isRootVisible.value} class={`${prefixCls}-container`} style={{ position: 'fixed', zIndex: 1000 }}>
|
||||||
@@ -198,13 +205,22 @@ export default defineComponent({
|
|||||||
onAfterEnter={handleAfterEnter}
|
onAfterEnter={handleAfterEnter}
|
||||||
onAfterLeave={handleAfterLeave}
|
onAfterLeave={handleAfterLeave}
|
||||||
>
|
>
|
||||||
<div v-show={isVisible.value} class={[`${prefixCls}-wrapper`, `${prefixCls}-position-${props.position}`, props.class]} style={{ ...props.style }}>
|
<div
|
||||||
<div class={`${prefixCls} ${props.fullscreen ? `${prefixCls}-fullscreen` : ''}`}>
|
v-show={isVisible.value}
|
||||||
<div class={`${prefixCls}-header`}>
|
class={[`${prefixCls}-wrapper`, `${prefixCls}-position-${props.position}`, props.class]}
|
||||||
|
style={{ ...props.style }}
|
||||||
|
>
|
||||||
|
<div ref={modalRef} class={`${prefixCls} ${props.fullscreen ? `${prefixCls}-fullscreen` : ''}`}>
|
||||||
|
<div
|
||||||
|
class={`${prefixCls}-header`}
|
||||||
|
onMousedown={handleMouseDown}
|
||||||
|
style={{ cursor: props.draggable ? '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>
|
||||||
{props.showClose && <Icon class={`${prefixCls}-close`} icon="ix:close" />}
|
{props.showClose && <Icon class={`${prefixCls}-close`} icon="ix:close" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={`${prefixCls}-body`}>{slots.default?.()}</div>
|
<div class={`${prefixCls}-body`}>{slots.default?.()}</div>
|
||||||
<div class={`${prefixCls}-footer`}>
|
<div class={`${prefixCls}-footer`}>
|
||||||
{slots.footer?.() ?? (
|
{slots.footer?.() ?? (
|
||||||
@@ -221,6 +237,5 @@ export default defineComponent({
|
|||||||
)}
|
)}
|
||||||
</Teleport>
|
</Teleport>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1,50 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const AModal1Visible = ref(false)
|
const visible1 = ref(false)
|
||||||
const AModal2Visible = ref(false)
|
const visible2 = ref(false)
|
||||||
const AModal3Visible = ref(false)
|
const dragHandle = ref<HTMLElement | null>(null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button @click="AModal1Visible = true">
|
<!-- 默认 header 拖拽 -->
|
||||||
打开第 1 个 AModal
|
<AModal v-model:visible="visible1" :draggable="true">
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 第 1 个 AModal -->
|
|
||||||
<AModal v-model:visible="AModal1Visible" mask-closable >
|
|
||||||
<template #title>
|
<template #title>
|
||||||
第 1 个 AModal
|
Header 拖拽示例
|
||||||
</template>
|
</template>
|
||||||
<p>这是第 1 个弹窗内容</p>
|
<p>这是 header 拖拽</p>
|
||||||
</AModal>
|
</AModal>
|
||||||
|
<AButton @click="visible1 = true">
|
||||||
|
打开 Header 拖拽
|
||||||
|
</AButton>
|
||||||
|
|
||||||
<!-- 第 2 个 AModal -->
|
<!-- ref 拖拽 -->
|
||||||
<AModal v-model:visible="AModal2Visible" mask-closable>
|
<AModal v-model:visible="visible2" :draggable="true" :draggable-ref="dragHandle">
|
||||||
<template #title>
|
<template #title>
|
||||||
第 2 个 AModal
|
Ref 拖拽示例
|
||||||
</template>
|
|
||||||
<p>这是第 2 个弹窗内容</p>
|
|
||||||
<template #footer>
|
|
||||||
<button @click="AModal3Visible = true">
|
|
||||||
打开第 3 个 AModal
|
|
||||||
</button>
|
|
||||||
<button @click="AModal2Visible = false">
|
|
||||||
关闭
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</AModal>
|
|
||||||
|
|
||||||
<!-- 第 3 个 AModal -->
|
|
||||||
<AModal v-model:visible="AModal3Visible" mask-closable position="top">
|
|
||||||
<template #title>
|
|
||||||
第 3 个 AModal
|
|
||||||
</template>
|
|
||||||
<p>这是第 3 个弹窗内容</p>
|
|
||||||
<template #footer>
|
|
||||||
<button @click="AModal3Visible = false">
|
|
||||||
关闭
|
|
||||||
</button>
|
|
||||||
</template>
|
</template>
|
||||||
|
<div ref="dragHandle" style="padding:10px; background:#ffdede; cursor:move;">
|
||||||
|
拖拽这个区域 (ref)
|
||||||
|
</div>
|
||||||
|
<p>Modal 内容</p>
|
||||||
</AModal>
|
</AModal>
|
||||||
|
<AButton @click="visible2 = true">
|
||||||
|
打开 Ref 拖拽
|
||||||
|
</AButton>
|
||||||
</template>
|
</template>
|
||||||
Reference in new issue
Block a user