1 changed files with 175 additions and 0 deletions
@ -0,0 +1,175 @@ |
|||
/** |
|||
* 全局弹窗可拖动(覆盖项目内所有弹窗) |
|||
* 1. 弹窗出现时直接给标题栏绑定拖动(MutationObserver + 延迟扫描) |
|||
* 2. document 捕获阶段 mousedown 兜底,确保点击标题栏一定能拖 |
|||
* 支持:el-dialog、el-message-box($prompt/confirm/alert)、自定义 .dialog-content |
|||
*/ |
|||
let inited = false |
|||
|
|||
function startDrag(e, container) { |
|||
if (!container) return |
|||
e.preventDefault() |
|||
e.stopPropagation() |
|||
const prevUserSelect = document.body.style.userSelect |
|||
document.body.style.userSelect = 'none' |
|||
let startX = e.clientX |
|||
let startY = e.clientY |
|||
const rect = container.getBoundingClientRect() |
|||
let left = rect.left |
|||
let top = rect.top |
|||
container.style.position = 'fixed' |
|||
container.style.left = left + 'px' |
|||
container.style.top = top + 'px' |
|||
container.style.margin = '0' |
|||
container.style.transform = 'none' |
|||
const onMouseMove = (e2) => { |
|||
e2.preventDefault() |
|||
const dx = e2.clientX - startX |
|||
const dy = e2.clientY - startY |
|||
left += dx |
|||
top += dy |
|||
startX = e2.clientX |
|||
startY = e2.clientY |
|||
container.style.left = left + 'px' |
|||
container.style.top = top + 'px' |
|||
} |
|||
const onMouseUp = () => { |
|||
document.body.style.userSelect = prevUserSelect |
|||
document.removeEventListener('mousemove', onMouseMove, true) |
|||
document.removeEventListener('mouseup', onMouseUp, true) |
|||
} |
|||
document.addEventListener('mousemove', onMouseMove, true) |
|||
document.addEventListener('mouseup', onMouseUp, true) |
|||
} |
|||
|
|||
/** el-dialog 仅由此处理:document 捕获阶段拦截标题栏 mousedown,避免双重绑定导致乱动/不动 */ |
|||
function onDocumentMouseDown(e) { |
|||
if (e.button !== 0) return |
|||
if (e.target.closest('.el-dialog__headerbtn') || e.target.closest('.el-message-box__headerbtn') || e.target.closest('.close-btn')) return |
|||
const elHeader = e.target.closest('.el-dialog__header') |
|||
if (elHeader) { |
|||
const wrapper = elHeader.closest('.el-dialog__wrapper') |
|||
if (wrapper) { |
|||
startDrag(e, wrapper) |
|||
return |
|||
} |
|||
} |
|||
const msgHeader = e.target.closest('.el-message-box__header') |
|||
if (msgHeader) { |
|||
const wrapper = msgHeader.closest('.el-message-box__wrapper') |
|||
if (wrapper) { |
|||
startDrag(e, wrapper) |
|||
return |
|||
} |
|||
} |
|||
const customHeader = e.target.closest('.dialog-header') |
|||
if (customHeader && !customHeader.closest('.el-dialog__wrapper')) { |
|||
const content = customHeader.closest('.dialog-content') |
|||
if (content) { |
|||
startDrag(e, content) |
|||
} |
|||
} |
|||
} |
|||
|
|||
function bindMessageBox(wrapper) { |
|||
if (wrapper.getAttribute('data-dialog-drag-bound') === '1') return |
|||
const header = wrapper.querySelector('.el-message-box__header') |
|||
if (!header) return |
|||
wrapper.setAttribute('data-dialog-drag-bound', '1') |
|||
header.style.cursor = 'move' |
|||
header.addEventListener('mousedown', (e) => { |
|||
if (e.button !== 0 || e.target.closest('.el-message-box__headerbtn')) return |
|||
startDrag(e, wrapper) |
|||
}) |
|||
} |
|||
|
|||
function bindCustomDialog(content) { |
|||
if (content.getAttribute('data-dialog-drag-bound') === '1') return |
|||
const header = content.querySelector('.dialog-header') |
|||
if (!header) return |
|||
content.setAttribute('data-dialog-drag-bound', '1') |
|||
header.style.cursor = 'move' |
|||
header.addEventListener('mousedown', (e) => { |
|||
if (e.button !== 0 || e.target.closest('.close-btn')) return |
|||
startDrag(e, content) |
|||
}) |
|||
} |
|||
|
|||
function scanAndBind() { |
|||
if (typeof document === 'undefined' || !document.body) return |
|||
document.querySelectorAll('.el-message-box__wrapper').forEach(bindMessageBox) |
|||
document.querySelectorAll('.dialog-content').forEach((content) => { |
|||
if (!content.closest('.el-dialog__wrapper') && content.querySelector('.dialog-header')) { |
|||
bindCustomDialog(content) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** 弹窗关闭时清除我们设置的 left/top,避免下次打开从错误位置“瞬移”到中间 */ |
|||
function resetClosedDialogPositions() { |
|||
if (typeof document === 'undefined' || !document.body) return |
|||
document.querySelectorAll('.el-dialog__wrapper').forEach((wrapper) => { |
|||
const style = window.getComputedStyle(wrapper) |
|||
if (style.display === 'none' || style.visibility === 'hidden') { |
|||
wrapper.style.left = '' |
|||
wrapper.style.top = '' |
|||
wrapper.style.position = '' |
|||
wrapper.style.margin = '' |
|||
wrapper.style.transform = '' |
|||
} |
|||
}) |
|||
document.querySelectorAll('.el-message-box__wrapper').forEach((wrapper) => { |
|||
const style = window.getComputedStyle(wrapper) |
|||
if (style.display === 'none' || style.visibility === 'hidden') { |
|||
wrapper.style.left = '' |
|||
wrapper.style.top = '' |
|||
wrapper.style.position = '' |
|||
wrapper.style.margin = '' |
|||
wrapper.style.transform = '' |
|||
} |
|||
}) |
|||
} |
|||
|
|||
function initDialogDrag(Vue) { |
|||
if (typeof document === 'undefined' || !document.body || inited) return |
|||
inited = true |
|||
|
|||
const style = document.createElement('style') |
|||
style.textContent = [ |
|||
'.el-dialog__header { cursor: move !important; }', |
|||
'.el-dialog__header .el-dialog__headerbtn { cursor: pointer !important; }', |
|||
'.el-message-box__header { cursor: move !important; }', |
|||
'.el-message-box__header .el-message-box__headerbtn { cursor: pointer !important; }', |
|||
'.dialog-header { cursor: move !important; }', |
|||
'.dialog-header .close-btn { cursor: pointer !important; }' |
|||
].join(' ') |
|||
document.head.appendChild(style) |
|||
|
|||
scanAndBind() |
|||
|
|||
document.addEventListener('mousedown', onDocumentMouseDown, true) |
|||
|
|||
const scheduleScan = () => { |
|||
resetClosedDialogPositions() |
|||
if (Vue && typeof Vue.nextTick === 'function') Vue.nextTick(scanAndBind) |
|||
else scanAndBind() |
|||
setTimeout(scanAndBind, 0) |
|||
setTimeout(scanAndBind, 50) |
|||
setTimeout(scanAndBind, 150) |
|||
setTimeout(scanAndBind, 400) |
|||
} |
|||
const observer = new MutationObserver(scheduleScan) |
|||
observer.observe(document.body, { childList: true, subtree: true }) |
|||
|
|||
if (Vue && typeof Vue.nextTick === 'function') Vue.nextTick(scanAndBind) |
|||
setTimeout(scanAndBind, 200) |
|||
setTimeout(scanAndBind, 500) |
|||
setTimeout(scanAndBind, 1200) |
|||
} |
|||
|
|||
export default { |
|||
install(Vue) { |
|||
if (typeof document === 'undefined' || !document.body) return |
|||
initDialogDrag(Vue) |
|||
} |
|||
} |
|||
Loading…
Reference in new issue