forked from liweijie/education
恢复最新前端代码
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* v-dialogDrag 弹窗拖拽
|
||||
* Copyright (c) 2019 roomroot
|
||||
*/
|
||||
|
||||
export default {
|
||||
bind(el, binding, vnode, oldVnode) {
|
||||
const value = binding.value
|
||||
if (value == false) return
|
||||
// 获取拖拽内容头部
|
||||
const dialogHeaderEl = el.querySelector('.el-dialog__header')
|
||||
const dragDom = el.querySelector('.el-dialog')
|
||||
dialogHeaderEl.style.cursor = 'move'
|
||||
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null)
|
||||
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
|
||||
dragDom.style.position = 'absolute'
|
||||
dragDom.style.marginTop = 0
|
||||
let width = dragDom.style.width
|
||||
if (width.includes('%')) {
|
||||
width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100)
|
||||
} else {
|
||||
width = +width.replace(/\px/g, '')
|
||||
}
|
||||
dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`
|
||||
// 鼠标按下事件
|
||||
dialogHeaderEl.onmousedown = (e) => {
|
||||
// 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
|
||||
const disX = e.clientX - dialogHeaderEl.offsetLeft
|
||||
const disY = e.clientY - dialogHeaderEl.offsetTop
|
||||
|
||||
// 获取到的值带px 正则匹配替换
|
||||
let styL, styT
|
||||
|
||||
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
|
||||
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, '')
|
||||
}
|
||||
|
||||
// 鼠标拖拽事件
|
||||
document.onmousemove = function (e) {
|
||||
// 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
|
||||
const l = e.clientX - disX
|
||||
const t = e.clientY - disY
|
||||
|
||||
let finallyL = l + styL
|
||||
let finallyT = t + styT
|
||||
|
||||
// 移动当前元素
|
||||
dragDom.style.left = `${finallyL}px`
|
||||
dragDom.style.top = `${finallyT}px`
|
||||
|
||||
}
|
||||
|
||||
document.onmouseup = function (e) {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* v-dialogDragWidth 可拖动弹窗高度(右下角)
|
||||
* Copyright (c) 2019 roomroot
|
||||
*/
|
||||
|
||||
export default {
|
||||
bind(el) {
|
||||
const dragDom = el.querySelector('.el-dialog')
|
||||
const lineEl = document.createElement('div')
|
||||
lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;'
|
||||
lineEl.addEventListener('mousedown',
|
||||
function(e) {
|
||||
// 鼠标按下,计算当前元素距离可视区的距离
|
||||
const disX = e.clientX - el.offsetLeft
|
||||
const disY = e.clientY - el.offsetTop
|
||||
// 当前宽度 高度
|
||||
const curWidth = dragDom.offsetWidth
|
||||
const curHeight = dragDom.offsetHeight
|
||||
document.onmousemove = function(e) {
|
||||
e.preventDefault() // 移动时禁用默认事件
|
||||
// 通过事件委托,计算移动的距离
|
||||
const xl = e.clientX - disX
|
||||
const yl = e.clientY - disY
|
||||
dragDom.style.width = `${curWidth + xl}px`
|
||||
dragDom.style.height = `${curHeight + yl}px`
|
||||
}
|
||||
document.onmouseup = function(e) {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}, false)
|
||||
dragDom.appendChild(lineEl)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* v-dialogDragWidth 可拖动弹窗宽度(右侧边)
|
||||
* Copyright (c) 2019 roomroot
|
||||
*/
|
||||
|
||||
export default {
|
||||
bind(el) {
|
||||
const dragDom = el.querySelector('.el-dialog')
|
||||
const lineEl = document.createElement('div')
|
||||
lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;'
|
||||
lineEl.addEventListener('mousedown',
|
||||
function (e) {
|
||||
// 鼠标按下,计算当前元素距离可视区的距离
|
||||
const disX = e.clientX - el.offsetLeft
|
||||
// 当前宽度
|
||||
const curWidth = dragDom.offsetWidth
|
||||
document.onmousemove = function (e) {
|
||||
e.preventDefault() // 移动时禁用默认事件
|
||||
// 通过事件委托,计算移动的距离
|
||||
const l = e.clientX - disX
|
||||
dragDom.style.width = `${curWidth + l}px`
|
||||
}
|
||||
document.onmouseup = function (e) {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}, false)
|
||||
dragDom.appendChild(lineEl)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import hasRole from './permission/hasRole'
|
||||
import hasPermi from './permission/hasPermi'
|
||||
import dialogDrag from './dialog/drag'
|
||||
import dialogDragWidth from './dialog/dragWidth'
|
||||
import dialogDragHeight from './dialog/dragHeight'
|
||||
import clipboard from './module/clipboard'
|
||||
import tableDrag from './tableDrag'
|
||||
|
||||
const install = function(Vue) {
|
||||
Vue.directive('hasRole', hasRole)
|
||||
Vue.directive('hasPermi', hasPermi)
|
||||
Vue.directive('clipboard', clipboard)
|
||||
Vue.directive('dialogDrag', dialogDrag)
|
||||
Vue.directive('dialogDragWidth', dialogDragWidth)
|
||||
Vue.directive('dialogDragHeight', dialogDragHeight)
|
||||
// 全局表格横向拖拽滚动:通过 document 事件委托自动作用于所有 el-table
|
||||
tableDrag()
|
||||
}
|
||||
|
||||
if (window.Vue) {
|
||||
window['hasRole'] = hasRole
|
||||
window['hasPermi'] = hasPermi
|
||||
Vue.use(install)
|
||||
}
|
||||
|
||||
export default install
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* v-clipboard 文字复制剪贴
|
||||
* Copyright (c) 2021 roomroot
|
||||
*/
|
||||
|
||||
import Clipboard from 'clipboard'
|
||||
export default {
|
||||
bind(el, binding, vnode) {
|
||||
switch (binding.arg) {
|
||||
case 'success':
|
||||
el._vClipBoard_success = binding.value
|
||||
break
|
||||
case 'error':
|
||||
el._vClipBoard_error = binding.value
|
||||
break
|
||||
default: {
|
||||
const clipboard = new Clipboard(el, {
|
||||
text: () => binding.value,
|
||||
action: () => binding.arg === 'cut' ? 'cut' : 'copy'
|
||||
})
|
||||
clipboard.on('success', e => {
|
||||
const callback = el._vClipBoard_success
|
||||
callback && callback(e)
|
||||
})
|
||||
clipboard.on('error', e => {
|
||||
const callback = el._vClipBoard_error
|
||||
callback && callback(e)
|
||||
})
|
||||
el._vClipBoard = clipboard
|
||||
}
|
||||
}
|
||||
},
|
||||
update(el, binding) {
|
||||
if (binding.arg === 'success') {
|
||||
el._vClipBoard_success = binding.value
|
||||
} else if (binding.arg === 'error') {
|
||||
el._vClipBoard_error = binding.value
|
||||
} else {
|
||||
el._vClipBoard.text = function () { return binding.value }
|
||||
el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy'
|
||||
}
|
||||
},
|
||||
unbind(el, binding) {
|
||||
if (!el._vClipboard) return
|
||||
if (binding.arg === 'success') {
|
||||
delete el._vClipBoard_success
|
||||
} else if (binding.arg === 'error') {
|
||||
delete el._vClipBoard_error
|
||||
} else {
|
||||
el._vClipBoard.destroy()
|
||||
delete el._vClipBoard
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* v-hasPermi 操作权限处理
|
||||
* Copyright (c) 2019 roomroot
|
||||
*/
|
||||
|
||||
import store from '@/store'
|
||||
|
||||
export default {
|
||||
inserted(el, binding, vnode) {
|
||||
const { value } = binding
|
||||
const all_permission = "*:*:*"
|
||||
const permissions = store.getters && store.getters.permissions
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const permissionFlag = value
|
||||
|
||||
const hasPermissions = permissions.some(permission => {
|
||||
return all_permission === permission || permissionFlag.includes(permission)
|
||||
})
|
||||
|
||||
if (!hasPermissions) {
|
||||
el.parentNode && el.parentNode.removeChild(el)
|
||||
}
|
||||
} else {
|
||||
throw new Error(`请设置操作权限标签值`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* v-hasRole 角色权限处理
|
||||
* Copyright (c) 2019 roomroot
|
||||
*/
|
||||
|
||||
import store from '@/store'
|
||||
|
||||
export default {
|
||||
inserted(el, binding, vnode) {
|
||||
const { value } = binding
|
||||
const super_admin = "admin"
|
||||
const roles = store.getters && store.getters.roles
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const roleFlag = value
|
||||
|
||||
const hasRole = roles.some(role => {
|
||||
return super_admin === role || roleFlag.includes(role)
|
||||
})
|
||||
|
||||
if (!hasRole) {
|
||||
el.parentNode && el.parentNode.removeChild(el)
|
||||
}
|
||||
} else {
|
||||
throw new Error(`请设置角色权限标签值"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 表格横向拖拽滚动
|
||||
* 隐藏表格的横向滚动条,按住鼠标左键横向拖动即可查看后面的字段。
|
||||
* 通过 document 事件委托全局生效,所有 el-table 自动支持,无需逐页绑定。
|
||||
*
|
||||
* 实现要点(全面优化):
|
||||
* - 采用 Pointer Events + setPointerCapture:按下瞬间把指针捕获到滚动容器上,
|
||||
* 之后无论光标移动到固定列、表格下方还是窗口外,移动事件都不会中断,真正"跟手"。
|
||||
* - requestAnimationFrame 合并每帧滚动,避免高频事件造成卡顿/闪烁。
|
||||
* - 横向位移超过阈值即触发,允许轻微斜向/弧线;仅当纵向明显占优时才不触发,
|
||||
* 保留普通点击与纵向拖拽(如行排序)的区分。
|
||||
* - 拖拽期间整页锁定光标与文本选中,光标保持默认箭头,不闪变为抓手/手型。
|
||||
* - 拖拽结束(确实发生了滚动)后屏蔽本次 click,避免误触发行点击、复选框等。
|
||||
*/
|
||||
const DRAG_THRESHOLD = 2 // 横向位移超过该像素即判定为拖动,越小越跟手
|
||||
|
||||
// 在这些元素上按下不触发拖拽,保证按钮、复选框等交互不受影响
|
||||
const INTERACTIVE_SELECTOR = [
|
||||
'a', 'button', 'input', 'textarea', 'select', 'label',
|
||||
'.el-button', '.el-checkbox', '.el-radio', '.el-switch', '.el-select',
|
||||
'.el-link', '.el-dropdown', '.el-tooltip', '.el-color-picker',
|
||||
'[contenteditable="true"]'
|
||||
].join(',')
|
||||
|
||||
let wrapper = null
|
||||
let pointerId = null
|
||||
let startX = 0
|
||||
let startY = 0
|
||||
let startScrollLeft = 0
|
||||
let dragging = false
|
||||
let rafId = null
|
||||
let pendingScrollLeft = 0
|
||||
|
||||
function isHorizontallyScrollable(el) {
|
||||
return el.scrollWidth > el.clientWidth + 1
|
||||
}
|
||||
|
||||
// 每帧只应用一次滚动位置,提升平滑度
|
||||
function applyScroll() {
|
||||
if (!wrapper) return
|
||||
wrapper.scrollLeft = pendingScrollLeft
|
||||
rafId = null
|
||||
}
|
||||
|
||||
function handlePointerDown(e) {
|
||||
if (e.button !== 0 && e.pointerType === 'mouse') return
|
||||
if (e.target.closest(INTERACTIVE_SELECTOR)) return
|
||||
// 固定列(如 fixed="right" 的操作列)渲染在 .el-table__fixed-body-wrapper 中,悬浮覆盖在
|
||||
// 表体右侧,需一并识别;最终统一落到真正负责横向滚动的 .el-table__body-wrapper 上
|
||||
const anyWrapper = e.target.closest('.el-table__body-wrapper, .el-table__fixed-body-wrapper')
|
||||
if (!anyWrapper) return
|
||||
const table = anyWrapper.closest('.el-table')
|
||||
const wrap = table && table.querySelector('.el-table__body-wrapper')
|
||||
if (!wrap || !isHorizontallyScrollable(wrap)) return
|
||||
|
||||
wrapper = wrap
|
||||
pointerId = e.pointerId
|
||||
startX = e.clientX
|
||||
startY = e.clientY
|
||||
startScrollLeft = wrap.scrollLeft
|
||||
pendingScrollLeft = startScrollLeft
|
||||
dragging = false
|
||||
|
||||
// 关键:捕获指针,之后所有 pointermove/pointerup 都持续派发到容器,
|
||||
// 即使光标移出表体/窗口也不中断
|
||||
if (wrap.setPointerCapture && e.pointerId != null) {
|
||||
try { wrap.setPointerCapture(e.pointerId) } catch (err) { /* 忽略捕获失败 */ }
|
||||
}
|
||||
// 阻止默认行为,抑制原生图片/文本拖拽与选中
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function handlePointerMove(e) {
|
||||
if (!wrapper || e.pointerId !== pointerId) return
|
||||
e.preventDefault()
|
||||
const dx = e.clientX - startX
|
||||
const dy = e.clientY - startY
|
||||
|
||||
if (!dragging) {
|
||||
// 仅当纵向明显占优(超过横向 3 倍且超过 12px)才判定为纵向操作、不触发横向拖动,
|
||||
// 其余情况(含轻微斜向/弧线)都正常触发,避免"拖不动"
|
||||
if (Math.abs(dx) <= DRAG_THRESHOLD || (Math.abs(dy) > Math.abs(dx) * 3 && Math.abs(dy) > 12)) return
|
||||
dragging = true
|
||||
wrapper.classList.add('is-dragging')
|
||||
// 整页锁定光标与文本选中,保证拖拽手感一致(光标不闪变、文字不误选)
|
||||
document.body.classList.add('el-table-dragging')
|
||||
}
|
||||
|
||||
// 记录目标位置,交由 rAF 统一应用
|
||||
pendingScrollLeft = startScrollLeft - (e.clientX - startX)
|
||||
if (rafId == null) rafId = requestAnimationFrame(applyScroll)
|
||||
}
|
||||
|
||||
function endDrag(blockClick) {
|
||||
if (!wrapper) return
|
||||
if (rafId != null) {
|
||||
cancelAnimationFrame(rafId)
|
||||
rafId = null
|
||||
}
|
||||
wrapper.classList.remove('is-dragging')
|
||||
document.body.classList.remove('el-table-dragging')
|
||||
if (blockClick && dragging) {
|
||||
// 拖拽确实发生了滚动,屏蔽本次 click,避免误触发行点击、复选框等
|
||||
const suppress = (ev) => {
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
document.removeEventListener('click', suppress, true)
|
||||
}
|
||||
document.addEventListener('click', suppress, true)
|
||||
}
|
||||
wrapper = null
|
||||
pointerId = null
|
||||
dragging = false
|
||||
}
|
||||
|
||||
function handlePointerUp(e) {
|
||||
if (e.pointerId !== pointerId) return
|
||||
endDrag(true)
|
||||
}
|
||||
|
||||
function handlePointerCancel() {
|
||||
endDrag(false)
|
||||
}
|
||||
|
||||
const install = function() {
|
||||
if (install.installed) return
|
||||
install.installed = true
|
||||
document.addEventListener('pointerdown', handlePointerDown, true)
|
||||
document.addEventListener('pointermove', handlePointerMove, true)
|
||||
document.addEventListener('pointerup', handlePointerUp, true)
|
||||
document.addEventListener('pointercancel', handlePointerCancel, true)
|
||||
}
|
||||
|
||||
export default install
|
||||
Reference in New Issue
Block a user