This commit is contained in:
GGJ
2025-01-09 19:02:44 +08:00
commit 92e7a7a5eb
2943 changed files with 1152283 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
// Inspired by https://github.com/Inndy/vue-clipboard2
const Clipboard = require('clipboard')
if (!Clipboard) {
throw new Error('you should npm install `clipboard` --save at first ')
}
export default {
bind(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
const clipboard = new Clipboard(el, {
text() { return binding.value },
action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
})
clipboard.on('success', e => {
const callback = el._v_clipboard_success
callback && callback(e) // eslint-disable-line
})
clipboard.on('error', e => {
const callback = el._v_clipboard_error
callback && callback(e) // eslint-disable-line
})
el._v_clipboard = clipboard
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
el._v_clipboard.text = function() { return binding.value }
el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
}
},
unbind(el, binding) {
if (binding.arg === 'success') {
delete el._v_clipboard_success
} else if (binding.arg === 'error') {
delete el._v_clipboard_error
} else {
el._v_clipboard.destroy()
delete el._v_clipboard
}
}
}

View File

@@ -0,0 +1,13 @@
import Clipboard from './clipboard'
const install = function(Vue) {
Vue.directive('Clipboard', Clipboard)
}
if (window.Vue) {
window.clipboard = Clipboard
Vue.use(install); // eslint-disable-line
}
Clipboard.install = install
export default Clipboard

View File

@@ -0,0 +1,117 @@
const dragFuc = (el, vnode) => {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cssText += ';cursor:move;'
dragDom.style.cssText += ';top:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const getStyle = (function() {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr]
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr]
}
})()
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
const dragDomWidth = dragDom.offsetWidth
const dragDomHeight = dragDom.offsetHeight
const screenWidth = document.body.clientWidth
const screenHeight = document.body.clientHeight
const minDragDomLeft = dragDom.offsetLeft
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
const minDragDomTop = dragDom.offsetTop
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
// 获取到的值带px 正则匹配替换
let styL = getStyle(dragDom, 'left')
let styT = getStyle(dragDom, 'top')
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
} else {
styL = +styL.replace(/\px/g, '')
styT = +styT.replace(/\px/g, '')
}
document.onmousemove = function(e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
// 边界处理
if (-(left) > minDragDomLeft) {
left = -minDragDomLeft
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft
}
if (-(top) > minDragDomTop) {
top = -minDragDomTop
} else if (top > maxDragDomTop) {
top = maxDragDomTop
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
el.setAttribute('drag_dom_position', `${left + styL}px;${top + styT}px`)
// emit onDrag event
vnode.child.$emit('dragDialog')
}
document.onmouseup = function(e) {
document.onmousemove = null
document.onmouseup = null
}
}
}
export default {
bind(el, binding, vnode) {
dragFuc(el, vnode)
const { componentInstance: $dialog } = vnode
// 兼容destroy-on-close模式
if ($dialog.destroyOnClose) {
$dialog.$watch('visible', function(val) {
if (val) {
$dialog.$nextTick(() => {
dragFuc(el, vnode)
})
}
})
// fix处理拖拽后关闭弹框的动画bug
// let styleDom = null
$dialog.$on('close', () => {
const uid = vnode.componentInstance._uid
el.setAttribute('drag_fix_uid', uid)
const positionStr = el.getAttribute('drag_dom_position')
if (!positionStr) return
const position = positionStr.split(';')
el.styleDom = document.createElement('style')
el.styleDom.innerHTML = `.el-dialog__wrapper[drag_fix_uid="${uid}"] .el-dialog{left:${position[0]} !important;top:${position[1]}!important;}`
el.appendChild(el.styleDom)
})
$dialog.$on('closed', () => {
try {
el.styleDom && el.removeChild(el.styleDom)
// eslint-disable-next-line no-empty
} catch (e) {}
el.setAttribute('drag_dom_position', '')
})
} else {
const dragDom = el.querySelector('.el-dialog')
$dialog.$on('closed', () => {
dragDom.style.left = '0'
dragDom.style.top = '0'
})
}
}
}

View File

@@ -0,0 +1,13 @@
import drag from './drag'
const install = function(Vue) {
Vue.directive('el-drag-dialog', drag)
}
if (window.Vue) {
window['el-drag-dialog'] = drag
Vue.use(install); // eslint-disable-line
}
drag.install = install
export default drag

View File

@@ -0,0 +1,92 @@
// import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
import throttle from 'throttle-debounce/throttle'
/**
* How to use
* <el-table height="100px" v-el-height-adaptive-table="{bottomOffset: 10}">...</el-table>
* bottomOffset: 10(default) // The height of the table from the bottom of the page.
*/
const doResize = (el, binding, vnode) => {
let { componentInstance: $table } = vnode
const { value } = binding
let bottomOffset = (value && Number(value.bottomOffset)) || 10
// 添加支持lb-table
if ($table.$refs.elTable) {
if ($table.pagination) {
bottomOffset += 35 // 分页组件高35px
}
$table = $table.$refs.elTable
}
if (!$table) return
// fix: 适应各屏和最小高度下的计算
const html = document.querySelector('html')
const htmlScrollTop = html.scrollTop
const htmlH = html.offsetHeight
const elTop = el.getBoundingClientRect().top
const height = htmlH - elTop - htmlScrollTop - bottomOffset
if ($table.layout.height === height) return
if (!$table.height) {
setTimeout(() => {
const headerElem = el.querySelector('.el-table__header-wrapper')
const scrollBodyElem = el.querySelector('.el-table__body-wrapper')
const scrollFixedElem = el.querySelector('.el-table__fixed-body-wrapper')
const headerHeight = headerElem.offsetHeight
scrollBodyElem.style.height = height - headerHeight + 'px'
// 固定列滚动条高度bug处理
if (scrollFixedElem) {
scrollFixedElem.style.height = height - headerHeight + 'px'
const tBody = scrollFixedElem.querySelector('tbody')
const gutter = tBody.querySelector('.gutter')
if (!gutter) {
const tr = document.createElement('tr')
tr.className = 'gutter'
tr.style.height = '17px'
tBody.appendChild(tr)
}
}
}, 0)
}
$table.layout.setHeight && $table.layout.setHeight(height)
$table.doLayout()
}
export default {
bind(el, binding, vnode) {
let flag = false
// 给频繁的计算节流最多每200ms计算一次保证性能
const throttleFn = throttle(200, () => {
doResize(el, binding, vnode)
})
// fix: el渲染后才开始计算
el.resizeListener = () => {
if (!flag) {
if (el.offsetHeight) {
flag = true
throttleFn()
}
} else {
throttleFn()
}
}
},
inserted(el) {
// 监听窗口大小变化时重新计算表格高度
window.addEventListener('resize', el.resizeListener)
// 监听app元素及子元素有任何class或style变化时重新计算表格高度
const observerDom = document.getElementById('app')
const options = { attributes: true, subtree: true, attributeFilter: ['class', 'style'] }
el.mutationObserver = new MutationObserver(el.resizeListener)
el.mutationObserver.observe(observerDom, options)
},
unbind(el) {
// 取消监听
window.removeEventListener('resize', el.resizeListener)
el.mutationObserver.disconnect()
}
}

View File

@@ -0,0 +1,13 @@
import adaptive from './adaptive'
const install = function(Vue) {
Vue.directive('el-height-adaptive-table', adaptive)
}
if (window.Vue) {
window['el-height-adaptive-table'] = adaptive
Vue.use(install); // eslint-disable-line
}
adaptive.install = install
export default adaptive

View File

@@ -0,0 +1,14 @@
import elScrollbarInfiniteScroll from './scrollbar-infinite-scroll'
const install = function(Vue) {
Vue.directive('el-scrollbar-infinite-scroll', elScrollbarInfiniteScroll)
}
if (window.Vue) {
window['el-scrollbar-infinite-scroll'] = elScrollbarInfiniteScroll
Vue.use(install); // eslint-disable-line
}
elScrollbarInfiniteScroll.install = install
export default elScrollbarInfiniteScroll

View File

@@ -0,0 +1,65 @@
/**
* 对 element-ui 的无限滚动在 el-table 上使用的封装
*/
import elInfiniteScroll from 'element-ui/lib/infinite-scroll'
const elScope = 'ElInfiniteScroll' // scope name
const msgTitle = `[el-scrollbar-infinite-scroll]: ` // message title
const elScrollWrapperClass = '.el-scrollbar__wrap'
export default {
inserted(el, binding, vnode, oldVnode) {
// 获取 table 中的滚动层
const scrollElem = el.querySelector(elScrollWrapperClass)
// 如果没找到元素,返回错误
if (!scrollElem) {
throw new Error(`${msgTitle}找不到 ${elScrollWrapperClass} 容器`)
}
// 设置自动滚动
scrollElem.style.overflowY = 'auto'
// dom 渲染后
setTimeout(() => {
asyncElOptions(vnode, el, scrollElem)
// 绑定 infinite-scroll
elInfiniteScroll.inserted(scrollElem, binding, vnode, oldVnode)
// 将子集的引用放入 el 上,用于 unbind 中销毁事件
el[elScope] = scrollElem[elScope]
}, 0)
},
componentUpdated(el, binding, vnode) {
asyncElOptions(vnode, el, el.querySelector(elScrollWrapperClass))
},
unbind: elInfiniteScroll.unbind
}
/**
* 同步 el-infinite-scroll 的配置项
* @param sourceVNode
* @param sourceElem
* @param targetElem
*/
function asyncElOptions(sourceVNode, sourceElem, targetElem) {
const context = sourceVNode.context
let value;
['disabled', 'delay', 'immediate'].forEach((name) => {
name = 'infinite-scroll-' + name
value = sourceElem.getAttribute(name)
if (value !== null) {
targetElem.setAttribute(name, context[value] || value)
} else if (name === 'infinite-scroll-delay') {
// fix: infinite-scroll-delay默认值失效BUG
targetElem.setAttribute(name, 200)
}
})
// fix: windows/chrome 的 scrollTop + clientHeight 与 scrollHeight 不一致的 BUG
const name = 'infinite-scroll-distance'
value = sourceElem.getAttribute(name)
value = context[value] || value
targetElem.setAttribute(name, value < 1 ? 1 : value)
}

View File

@@ -0,0 +1,14 @@
import elTableInfiniteScroll from './table-infinite-scroll'
const install = function(Vue) {
Vue.directive('el-table-infinite-scroll', elTableInfiniteScroll)
}
if (window.Vue) {
window['el-table-infinite-scroll'] = elTableInfiniteScroll
Vue.use(install); // eslint-disable-line
}
elTableInfiniteScroll.install = install
export default elTableInfiniteScroll

View File

@@ -0,0 +1,81 @@
/**
* 对 element-ui 的无限滚动在 el-table 上使用的封装
*/
import elInfiniteScroll from 'element-ui/lib/infinite-scroll'
const elScope = 'ElInfiniteScroll' // scope name
const msgTitle = `[el-table-infinite-scroll]: ` // message title
const elTableScrollWrapperClass = '.el-table__body-wrapper'
export default {
inserted(el, binding, vnode, oldVnode) {
// 获取 table 中的滚动层
const scrollElem = el.querySelector(elTableScrollWrapperClass)
// 如果没找到元素,返回错误
if (!scrollElem) {
throw new Error(`${msgTitle}找不到 ${elTableScrollWrapperClass} 容器`)
}
// 设置自动滚动
scrollElem.style.overflowY = 'auto'
// dom 渲染后
setTimeout(() => {
// 添加支持lb-table
const elTableEle = el.querySelector('.el-table')
let ele = null
if (elTableEle) {
ele = elTableEle
} else {
ele = el
}
if (!ele.style.height) {
scrollElem.style.height = '400px'
console.warn(
`${msgTitle}请尽量设置 el-table 的高度,可以设置为 auto/100%(自适应高度),未设置会取 400px 的默认值(不然会导致一直加载)`
)
}
asyncElOptions(vnode, el, scrollElem)
// 绑定 infinite-scroll
elInfiniteScroll.inserted(scrollElem, binding, vnode, oldVnode)
// 将子集的引用放入 el 上,用于 unbind 中销毁事件
el[elScope] = scrollElem[elScope]
}, 0)
},
componentUpdated(el, binding, vnode) {
asyncElOptions(vnode, el, el.querySelector(elTableScrollWrapperClass))
},
unbind: elInfiniteScroll.unbind
}
/**
* 同步 el-infinite-scroll 的配置项
* @param sourceVNode
* @param sourceElem
* @param targetElem
*/
function asyncElOptions(sourceVNode, sourceElem, targetElem) {
const context = sourceVNode.context
let value;
['disabled', 'delay', 'immediate'].forEach((name) => {
name = 'infinite-scroll-' + name
value = sourceElem.getAttribute(name)
if (value !== null) {
targetElem.setAttribute(name, context[value] || value)
} else if (name === 'infinite-scroll-delay') {
// fix: infinite-scroll-delay默认值失效BUG
targetElem.setAttribute(name, 200)
}
})
// fix: windows/chrome 的 scrollTop + clientHeight 与 scrollHeight 不一致的 BUG
const name = 'infinite-scroll-distance'
value = sourceElem.getAttribute(name)
value = context[value] || value
targetElem.setAttribute(name, value < 1 ? 1 : value)
}

View File

@@ -0,0 +1,14 @@
import permission, { hasPermission } from './permission'
const install = function(Vue) {
Vue.directive('permission', permission)
Vue.prototype.$permission = hasPermission
}
if (window.Vue) {
window['permission'] = permission
Vue.use(install); // eslint-disable-line
}
permission.install = install
export default permission

View File

@@ -0,0 +1,23 @@
import store from '@/store'
export default {
inserted(el, binding, vnode) {
const { value } = binding
if (!hasPermission(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
export function hasPermission(value) {
const roles = store.getters && store.getters.roles
if (value && value instanceof Array && value.length > 0) {
const permissionRoles = value
return roles.some(role => {
return permissionRoles.includes(role)
})
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']" or this.$permission(['admin', 'editor'])`)
}
}

View File

@@ -0,0 +1,13 @@
import sticky from './sticky'
const install = function(Vue) {
Vue.directive('sticky', sticky)
}
if (window.Vue) {
window.sticky = sticky
Vue.use(install); // eslint-disable-line
}
sticky.install = install
export default sticky

View File

@@ -0,0 +1,85 @@
let listenAction
export default {
inserted(el, binding) {
const params = binding.value || {}
const stickyTop = params.stickyTop || 0
const zIndex = params.zIndex || 1000
const elStyle = el.style
elStyle.position = '-webkit-sticky'
elStyle.position = 'sticky'
// if the browser support css stickyCurrently Safari, Firefox and Chrome Canary
// if (~elStyle.position.indexOf('sticky')) {
// elStyle.top = `${stickyTop}px`;
// elStyle.zIndex = zIndex;
// return
// }
const elHeight = el.getBoundingClientRect().height
const elWidth = el.getBoundingClientRect().width
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
const parentElm = el.parentNode || document.documentElement
const placeholder = document.createElement('div')
placeholder.style.display = 'none'
placeholder.style.width = `${elWidth}px`
placeholder.style.height = `${elHeight}px`
parentElm.insertBefore(placeholder, el)
let active = false
const getScroll = (target, top) => {
const prop = top ? 'pageYOffset' : 'pageXOffset'
const method = top ? 'scrollTop' : 'scrollLeft'
let ret = target[prop]
if (typeof ret !== 'number') {
ret = window.document.documentElement[method]
}
return ret
}
const sticky = () => {
if (active) {
return
}
if (!elStyle.height) {
elStyle.height = `${el.offsetHeight}px`
}
elStyle.position = 'fixed'
elStyle.width = `${elWidth}px`
placeholder.style.display = 'inline-block'
active = true
}
const reset = () => {
if (!active) {
return
}
elStyle.position = ''
placeholder.style.display = 'none'
active = false
}
const check = () => {
const scrollTop = getScroll(window, true)
const offsetTop = el.getBoundingClientRect().top
if (offsetTop < stickyTop) {
sticky()
} else {
if (scrollTop < elHeight + stickyTop) {
reset()
}
}
}
listenAction = () => {
check()
}
window.addEventListener('scroll', listenAction)
},
unbind() {
window.removeEventListener('scroll', listenAction)
}
}

View File

@@ -0,0 +1,13 @@
import waves from './waves'
const install = function(Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves

View File

@@ -0,0 +1,26 @@
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}

View File

@@ -0,0 +1,72 @@
import './waves.css'
const context = '@@wavesContext'
function handleClick(el, binding) {
function handle(e) {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit 点击位置扩散 center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
},
customOpts
)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
break
default:
ripple.style.top =
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
document.body.scrollTop) + 'px'
ripple.style.left =
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}
if (!el[context]) {
el[context] = {
removeHandle: handle
}
} else {
el[context].removeHandle = handle
}
return handle
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
},
update(el, binding) {
el.removeEventListener('click', el[context].removeHandle, false)
el.addEventListener('click', handleClick(el, binding), false)
},
unbind(el) {
el.removeEventListener('click', el[context].removeHandle, false)
el[context] = null
delete el[context]
}
}