import { nextTick, watch } from 'vue'; export interface DraggableOptions { /** 拖拽手柄选择器,默认 '.ant-modal-header' */ handle?: string; /** 弹窗内容选择器,默认 '.ant-modal-content' */ modalSelector?: string; /** 是否限制在可视区域内,默认 true */ boundary?: boolean; /** 打开弹窗时是否自动重置到居中位置,默认 true */ resetOnOpen?: boolean; } export function useDraggable( openRef: { value: boolean }, options: DraggableOptions = {} ) { console.log(openRef); const { handle = '.ant-modal-header', modalSelector = '.ant-modal-content', boundary = true, resetOnOpen = true } = options; let cleanup: (() => void) | null = null; const initDrag = (): (() => void) | null => { const modalContent = document.querySelector( modalSelector ) as HTMLElement | null; const dragHandle = document.querySelector(handle) as HTMLElement | null; if (!modalContent || !dragHandle) return null; // 重置位置到居中 if (resetOnOpen) { modalContent.style.position = ''; modalContent.style.left = ''; modalContent.style.top = ''; modalContent.style.margin = ''; modalContent.style.transform = ''; } let isDragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; const getPointerPos = (e: MouseEvent | TouchEvent) => { if (e instanceof TouchEvent) { const touch = e.touches[0]; return { clientX: touch.clientX, clientY: touch.clientY }; } return { clientX: e.clientX, clientY: e.clientY }; }; const onPointerDown = (e: MouseEvent | TouchEvent) => { if ( e.target instanceof HTMLElement && (e.target.closest('.ant-modal-close') || e.target.closest('button') || e.target.closest('input') || e.target.closest('select') || e.target.closest('textarea') || e.target.closest('a')) ) return; e.preventDefault(); isDragging = true; const rect = modalContent.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; const pos = getPointerPos(e); startX = pos.clientX - startLeft; startY = pos.clientY - startTop; // --- 新增:固定宽度 --- const currentWidth = modalContent.offsetWidth; modalContent.style.width = currentWidth + 'px'; modalContent.style.userSelect = 'none'; modalContent.style.webkitUserSelect = 'none'; modalContent.style.position = 'fixed'; modalContent.style.margin = '0'; modalContent.style.transform = 'none'; document.addEventListener('mousemove', onPointerMove, { passive: false }); document.addEventListener('mouseup', onPointerUp); document.addEventListener('touchmove', onPointerMove, { passive: false }); document.addEventListener('touchend', onPointerUp); }; const onPointerMove = (e: MouseEvent | TouchEvent) => { if (!isDragging) return; e.preventDefault(); const pos = getPointerPos(e); let newLeft = pos.clientX - startX; let newTop = pos.clientY - startY; if (boundary) { const maxX = window.innerWidth - modalContent.offsetWidth; const maxY = window.innerHeight - modalContent.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxX)); newTop = Math.max(0, Math.min(newTop, maxY)); } modalContent.style.left = `${newLeft}px`; modalContent.style.top = `${newTop}px`; }; const onPointerUp = () => { isDragging = false; modalContent.style.userSelect = ''; modalContent.style.webkitUserSelect = ''; document.removeEventListener('mousemove', onPointerMove); document.removeEventListener('mouseup', onPointerUp); document.removeEventListener('touchmove', onPointerMove); document.removeEventListener('touchend', onPointerUp); }; dragHandle.addEventListener('mousedown', onPointerDown); dragHandle.addEventListener('touchstart', onPointerDown, { passive: false }); return () => { dragHandle.removeEventListener('mousedown', onPointerDown); dragHandle.removeEventListener('touchstart', onPointerDown); document.removeEventListener('mousemove', onPointerMove); document.removeEventListener('mouseup', onPointerUp); document.removeEventListener('touchmove', onPointerMove); document.removeEventListener('touchend', onPointerUp); }; }; watch( openRef, newVal => { if (newVal) { nextTick(() => { cleanup = initDrag(); }); } else { cleanup?.(); cleanup = null; } }, { immediate: true } ); return { initDrag }; }