snowy/snowy-admin-web/src/components/DragModal/index.vue

319 lines
8.0 KiB
Vue

<template>
<a-modal
:class="['my-modal', modalClass, simpleClass]"
:visible="visible"
v-bind="props"
:width="modalWidth"
:wrap-class-name="wrapClassName + fullscreenClass"
@cancel="handleCancel"
>
<template #closeIcon>
<template v-if="fullscreen">
<a-tooltip title="还原" placement="bottom" v-if="fullscreenStatus">
<fullscreen-exit-outlined @click="handleFullScreen" />
</a-tooltip>
<a-tooltip title="最大化" placement="bottom" v-else>
<fullscreen-outlined @click="handleFullScreen" />
</a-tooltip>
</template>
<a-tooltip title="关闭" placement="bottom">
<close-outlined />
</a-tooltip>
</template>
<slot></slot>
<template #footer>
<slot name="insertFooter"></slot>
<slot name="footer">
<a-button @click="handleCancel">
{{ props.cancelText || '取消' }}
</a-button>
<slot name="centerFooter"></slot>
<a-button type="primary" @click="handleOk" :loading="loading">
{{ props.okText || '' }}
</a-button>
</slot>
<slot name="appendFooter"></slot>
</template>
</a-modal>
</template>
<script setup>
import mixinProps from './props.js'
import { useSlots } from 'vue'
const slots = useSlots()
const props = defineProps({
...mixinProps,
// 容器的类名
modalClass: {
type: String,
default: 'modal-box'
},
// 对话框外层容器的类名
wrapClassName: {
type: String,
default: ''
},
helpMessage: {
type: String
},
// 可全屏
fullscreen: {
type: Boolean,
default: true
},
// 可拖拽
drag: {
type: Boolean,
default: true
},
// 可拉伸
resize: {
type: Boolean,
default: false
},
// 是否显示
visible: {
type: Boolean,
default: false
},
// 标题
title: {
type: String,
default: undefined
},
// 宽度
width: {
type: [Number, String],
default: '70%'
},
loading: {
type: Boolean,
default: undefined
}
})
const emit = defineEmits(['ok', 'close', 'fullscreen'])
const modalWidth = ref('')
const contain = ref(null)
// 拖拽
const header = ref(null)
const modalContent = ref(null)
const mouseDownX = ref(0)
const mouseDownY = ref(0)
const deltaX = ref(0)
const deltaY = ref(0)
const sumX = ref(0)
const sumY = ref(0)
const onmousedown = ref(false)
// 缩放
const modalBody = ref(null)
const myBody = ref(null)
const prevModalWidth = ref(0)
const prevModalHeight = ref(0)
const prevBodyWidth = ref(0)
const prevBodyHeight = ref(0)
const startX = ref(0)
const startY = ref(0)
// 全屏
const fullscreenClass = ref('')
const fullscreenStatus = ref(false)
const slotKeys = computed(() => {
return Object.keys(slots)
})
const simpleClass = computed(() => {
return Math.random().toString(36).substring(2)
})
onMounted(() => {
nextTick(() => {
initialEvent(props.visible)
})
})
watch(
() => props.visible,
(newValue) => {
nextTick(() => {
initialEvent(props.visible)
})
}
)
watch(
() => fullscreenStatus.value,
(newValue) => {
fullscreenClass.value = fullscreenStatus.value ? ' full-modal' : ''
}
)
onBeforeUnmount(() => {
removeMove()
document.removeEventListener('mouseup', removeUp, false)
removeResize()
document.removeEventListener('mouseup', removeResize)
})
const changeWidth = (width) => {
modalWidth.value = width
}
const handleFullScreen = (e) => {
e?.stopPropagation()
e?.preventDefault()
fullscreenStatus.value = !fullscreenStatus.value
emit('fullscreen', e)
}
const handleOk = (e) => {
reset()
emit('ok', e)
}
const handleCancel = (e) => {
const classList = e.target?.classList
// 过滤自定义关闭按钮的空白区域
if (classList.contains('ant-modal-close-x') || classList.contains('ant-space-item')) {
return
}
reset()
emit('close', e)
}
const reset = () => {
// 拖拽
mouseDownX.value = 0
mouseDownY.value = 0
deltaX.value = 0
deltaY.value = 0
sumX.value = 0
sumY.value = 0
// 缩放
prevModalWidth.value = 0
prevModalHeight.value = 0
prevBodyWidth.value = 0
prevBodyHeight.value = 0
startX.value = 0
startY.value = 0
// 全屏
fullscreenStatus.value = false
}
const initialEvent = (visible) => {
if (visible) {
reset()
// 获取控件
document.removeEventListener('mouseup', removeUp, false)
contain.value = document.getElementsByClassName(simpleClass.value)[0]
changeWidth(props.width)
if (props.drag === true) {
header.value = contain.value.getElementsByClassName('ant-modal-header')[0]
modalContent.value = contain.value.getElementsByClassName('ant-modal-content')[0]
header.value.style.cursor = 'all-scroll'
modalContent.value.style.left = 0
modalContent.value.style.transform = 'translate(0px,0px)'
// 拖拽事件监听
header.value.onmousedown = (event) => {
onmousedown.value = true
mouseDownX.value = event.pageX
mouseDownY.value = event.pageY
document.body.onselectstart = () => false
document.addEventListener('mousemove', handleMove, false)
}
document.addEventListener('mouseup', removeUp, false)
}
if (props.resize === true) {
modalBody.value = contain.value.getElementsByClassName('ant-modal-content')[0]
myBody.value = contain.value.getElementsByClassName('ant-modal-body')[0]
modalBody.value.style.overflow = 'hidden'
modalBody.value.style.resize = 'both'
myBody.value.style.overflow = 'auto'
myBody.value.style.height = 'auto'
// 缩放事件监听
modalBody.value.onmousedown = (event) => {
event.preventDefault()
const rect = modalBody.value.getBoundingClientRect()
const rightBorder = rect.x + rect.width - 17
const bottomBorder = rect.y + rect.height - 17
if (event.clientX >= rightBorder && event.clientY >= bottomBorder) {
prevModalWidth.value = modalBody.value.offsetWidth
prevModalHeight.value = modalBody.value.offsetHeight
prevBodyWidth.value = myBody.value.offsetWidth
prevBodyHeight.value = myBody.value.offsetHeight
startX.value = event.clientX
startY.value = event.clientY
document.addEventListener('mousemove', handleResize)
}
document.addEventListener('mouseup', removeResize)
}
}
}
}
const handleMove = (event) => {
if (fullscreenStatus.value) {
return
}
const delta1X = event.pageX - mouseDownX.value
const delta1Y = event.pageY - mouseDownY.value
deltaX.value = delta1X
deltaY.value = delta1Y
modalContent.value.style.transform = `translate(${delta1X + sumX.value}px, ${delta1Y + sumY.value}px)`
}
const removeMove = () => {
document.removeEventListener('mousemove', handleMove, false)
}
const removeUp = (event) => {
document.body.onselectstart = () => true
if (onmousedown.value && !(event.pageX === mouseDownX.value && event.pageY === mouseDownY.value)) {
onmousedown.value = false
sumX.value = sumX.value + deltaX.value
sumY.value = sumY.value + deltaY.value
}
removeMove()
}
const handleResize = (event) => {
if (fullscreenStatus.value) {
return
}
const diffX = event.clientX - startX.value
const diffY = event.clientY - startY.value
const minWidth = 180
const minHeight = 0
if (prevBodyWidth.value + diffX > minWidth) {
changeWidth(prevModalWidth.value + diffX + 'px')
}
if (prevBodyHeight.value + diffY > minHeight) {
myBody.value.style.height = prevBodyHeight.value + diffY + 'px'
}
}
const removeResize = () => {
document.removeEventListener('mousemove', handleResize)
}
</script>
<style lang="less">
.ant-modal-close-x {
margin-right: 10px;
width: auto;
.anticon {
padding: 20px 10px;
}
}
.full-modal {
.ant-modal {
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
max-width: 100% !important;
max-height: 100% !important;
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh) !important;
transform: translate(0px, 0px) !important;
resize: none !important;
}
.ant-modal-header {
cursor: default !important;
}
.ant-modal-body {
flex: 1;
}
}
</style>