我们在vue3中封装一个自定义指令
import { nextTick } from 'vue';
/**
* Element Plus 对话框拖拽指令
* @param {HTMLElement} el - 指令绑定的元素
* @param {Object} binding - 指令绑定对象
*/
export default {
updated(el, binding, vnode, oldVnode) {
// 初始化样式
el.style.height = 'auto';
el.style.display = 'block';
// 弹框可拉伸最小宽高
const minWidth = 30;
const minHeight = 30;
// 状态变量
let isFullScreen = false;
let nowWidth = 0;
let nowHight = 0;
let nowMarginTop = 0;
// 获取弹框元素
const dialogHeaderEl = el.querySelector('.el-dialog__header');
const dragDom = el.querySelector('.el-dialog');
if (!dragDom) {
el.style.height = '0px';
el.style.display = 'none';
return;
}
// 确保对话框使用绝对定位以便拖拽
if (dragDom.style.position !== 'absolute') {
dragDom.style.position = 'absolute';
nextTick(()=>{
// 计算屏幕中心位置
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
const dialogWidth = dragDom.offsetWidth;
const dialogHeight = dragDom.offsetHeight;
// 设置初始位置为屏幕中央
const left = (screenWidth - dialogWidth) / 2;
const top = (screenHeight - dialogHeight) / 2;
dragDom.style.left = left + 'px';
dragDom.style.top = top + 'px';
})
}
// 给弹窗加上overflow auto
dragDom.style.overflow = "auto";
// 消除外边距
dragDom.style.marginTop = '0px'
dragDom.style.marginBottom = '0px'
dragDom.style.marginLeft = '0px'
dragDom.style.marginRight = '0px'
// 清除选择头部文字效果
dialogHeaderEl.onselectstart = () => false;
// 头部加上可拖动cursor
dialogHeaderEl.style.cursor = 'move';
// 获取原有样式
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
// 移动功能
const moveDown = (e) => {
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
let styL, styT;
// 处理百分比和像素单位
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
} else {
styL = +sty.left.replace(/\px/g, '');
styT = +sty.top.replace(/\px/g, '');
}
const onMouseMove = function (e) {
const l = e.clientX - disX;
const t = e.clientY - disY;
// 边界检查
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const dialogRect = dragDom.getBoundingClientRect();
const boundedLeft = Math.max(0, Math.min(l + styL, viewportWidth - dialogRect.width));
const boundedTop = Math.max(0, Math.min(t + styT, viewportHeight - dialogRect.height));
dragDom.style.left = boundedLeft + 'px';
dragDom.style.top = boundedTop + 'px';
};
const onMouseUp = function () {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
// 绑定移动事件
dialogHeaderEl.onmousedown = moveDown;
// 双击头部全屏/恢复功能
dialogHeaderEl.ondblclick = () => {
if (!isFullScreen) {
// 进入全屏
nowHight = dragDom.clientHeight;
nowWidth = dragDom.clientWidth;
nowMarginTop = dragDom.style.marginTop;
dragDom.style.left = '0px';
dragDom.style.top = '0px';
dragDom.style.height = "100vh";
dragDom.style.width = "100vw";
dragDom.style.marginTop = '0px';
isFullScreen = true;
dialogHeaderEl.style.cursor = 'initial';
dialogHeaderEl.onmousedown = null;
} else {
// 退出全屏
dragDom.style.height = "auto";
dragDom.style.width = nowWidth + 'px';
dragDom.style.marginTop = nowMarginTop;
isFullScreen = false;
dialogHeaderEl.style.cursor = 'move';
dialogHeaderEl.onmousedown = moveDown;
}
};
// 创建八个方向的拖拽控制点
createAllResizeHandles(dragDom, minWidth, minHeight);
},
mounted(el) {
// 初始化隐藏
el.style.height = '0px';
el.style.display = 'none';
el.style.marginTop = '0px';
el.style.marginBottom = '0px';
}
};
/**
* 创建所有八个方向的拖拽控制点
*/
function createAllResizeHandles(dragDom, minWidth, minHeight) {
// 定义八个方向及其样式
const handles = [
{ direction: 'top', cursor: 'n-resize', styles: { height: '10px', width: '100%', top: '0px', left: '0px' }},
{ direction: 'bottom', cursor: 's-resize', styles: { height: '10px', width: '100%', bottom: '0px', left: '0px' }},
{ direction: 'left', cursor: 'w-resize', styles: { height: '100%', width: '10px', top: '0px', left: '0px' }},
{ direction: 'right', cursor: 'e-resize', styles: { height: '100%', width: '10px', top: '0px', right: '0px' }},
{ direction: 'top-left', cursor: 'nw-resize', styles: { height: '10px', width: '10px', top: '0px', left: '0px' }},
{ direction: 'top-right', cursor: 'ne-resize', styles: { height: '10px', width: '10px', top: '0px', right: '0px' }},
{ direction: 'bottom-left', cursor: 'sw-resize', styles: { height: '10px', width: '10px', bottom: '0px', left: '0px' }},
{ direction: 'bottom-right', cursor: 'se-resize', styles: { height: '10px', width: '10px', bottom: '0px', right: '0px' }}
];
// 创建控制点
handles.forEach(config => {
createResizeHandle(dragDom, config, minWidth, minHeight);
});
}
/**
* 创建单个拖拽控制点
*/
function createResizeHandle(dragDom, config, minWidth, minHeight) {
const handle = document.createElement("div");
dragDom.appendChild(handle);
// 应用基本样式
Object.assign(handle.style, {
position: 'absolute',
zIndex: '9999',
cursor: config.cursor
}, config.styles);
// 绑定拖拽事件
handle.onmousedown = (e) => {
e.preventDefault();
e.stopPropagation();
const startX = e.clientX;
const startY = e.clientY;
const startWidth = dragDom.clientWidth;
const startHeight = dragDom.clientHeight;
const startLeft = dragDom.offsetLeft;
const startTop = dragDom.offsetTop;
const onMouseMove = function (e) {
e.preventDefault();
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
// 根据方向计算新位置和尺寸
calculateNewSizeAndPosition(
dragDom,
config.direction,
deltaX,
deltaY,
startWidth,
startHeight,
startLeft,
startTop,
minWidth,
minHeight
);
};
const onMouseUp = function () {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
}
/**
* 根据拖拽方向和距离计算新位置和尺寸
*/
function calculateNewSizeAndPosition(
dragDom,
direction,
deltaX,
deltaY,
startWidth,
startHeight,
startLeft,
startTop,
minWidth,
minHeight
) {
let newWidth = startWidth;
let newHeight = startHeight;
let newLeft = startLeft;
let newTop = startTop;
switch (direction) {
case 'top':
newHeight = Math.max(minHeight, startHeight - deltaY);
newTop = startTop + deltaY;
break;
case 'bottom':
newHeight = Math.max(minHeight, startHeight + deltaY);
break;
case 'left':
newWidth = Math.max(minWidth, startWidth - deltaX);
newLeft = startLeft + deltaX;
break;
case 'right':
newWidth = Math.max(minWidth, startWidth + deltaX);
break;
case 'top-left':
newWidth = Math.max(minWidth, startWidth - deltaX);
newHeight = Math.max(minHeight, startHeight - deltaY);
newLeft = startLeft + deltaX;
newTop = startTop + deltaY;
break;
case 'top-right':
newWidth = Math.max(minWidth, startWidth + deltaX);
newHeight = Math.max(minHeight, startHeight - deltaY);
newTop = startTop + deltaY;
break;
case 'bottom-left':
newWidth = Math.max(minWidth, startWidth - deltaX);
newHeight = Math.max(minHeight, startHeight + deltaY);
newLeft = startLeft + deltaX;
break;
case 'bottom-right':
newWidth = Math.max(minWidth, startWidth + deltaX);
newHeight = Math.max(minHeight, startHeight + deltaY);
break;
default:
break;
}
// 应用新尺寸和位置
dragDom.style.width = newWidth + 'px';
dragDom.style.height = newHeight + 'px';
dragDom.style.left = newLeft + 'px';
dragDom.style.top = newTop + 'px';
}
因为指令需要绑定在组件的根元素上,而elementplus的el-dialog有多个根元素,所以我们绑定的指令一般是无效的。所以我们需要在el-dialog外面套一层div,一个el-dialog外面对应一个div。
<div v-dialog-drag>
<el-dialog v-model="dialogVisible">
</el-dialog>
</div>
在index.ts中声明自定义指令。
import { createApp } from "vue";
import VDialogDrag from "./components/VDialogDrag";
const app = createApp(App);
app.directive("dialog-drag", VDialogDrag)
344

被折叠的 条评论
为什么被折叠?



