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