新增标签栏左右移动按钮
This commit is contained in:
parent
ed4427243d
commit
c25869e287
|
@ -1,37 +1,86 @@
|
||||||
<template>
|
<template>
|
||||||
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
|
<div ref="scrollContainer" class="scroll-container" @wheel.prevent="handleScroll">
|
||||||
|
<div ref="scrollWrapper" :style="{left: left + 'px'}" class="scroll-wrapper">
|
||||||
<slot />
|
<slot />
|
||||||
</el-scrollbar>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import createDetectElementResize from '@/vendor/detectElementResize'
|
||||||
|
|
||||||
const padding = 15 // tag's padding
|
const padding = 15 // tag's padding
|
||||||
|
const scrollPanelLeft = 30
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ScrollPane',
|
name: 'ScrollPane',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
left: 0
|
left: scrollPanelLeft
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.detectElementResize = createDetectElementResize()
|
||||||
|
this.detectElementResize.addResizeListener(this.$refs.scrollContainer, () => {
|
||||||
|
const $container = this.$refs.scrollContainer
|
||||||
|
const $containerWidth = $container.offsetWidth
|
||||||
|
const $wrapper = this.$refs.scrollWrapper
|
||||||
|
const $wrapperWidth = $wrapper.offsetWidth
|
||||||
|
if ($containerWidth - padding > $wrapperWidth) {
|
||||||
|
this.left = scrollPanelLeft
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
scrollLeft() {
|
||||||
|
this.left = Math.min(scrollPanelLeft, this.left + 300)
|
||||||
|
},
|
||||||
|
scrollRight() {
|
||||||
|
const $container = this.$refs.scrollContainer
|
||||||
|
const $containerWidth = $container.offsetWidth
|
||||||
|
const $wrapper = this.$refs.scrollWrapper
|
||||||
|
const $wrapperWidth = $wrapper.offsetWidth
|
||||||
|
if ($containerWidth - padding < $wrapperWidth) {
|
||||||
|
this.left = Math.max(this.left - 300, $containerWidth - $wrapperWidth - padding - scrollPanelLeft)
|
||||||
|
}
|
||||||
|
},
|
||||||
handleScroll(e) {
|
handleScroll(e) {
|
||||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
const eventDelta = e.wheelDelta || -e.deltaY * 3
|
||||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
const $container = this.$refs.scrollContainer
|
||||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
const $containerWidth = $container.offsetWidth
|
||||||
|
const $wrapper = this.$refs.scrollWrapper
|
||||||
|
const $wrapperWidth = $wrapper.offsetWidth
|
||||||
|
|
||||||
|
if (eventDelta > 0) {
|
||||||
|
this.left = Math.min(scrollPanelLeft, this.left + eventDelta)
|
||||||
|
} else {
|
||||||
|
if ($containerWidth - padding < $wrapperWidth) {
|
||||||
|
if (this.left < -($wrapperWidth - $containerWidth + padding)) {
|
||||||
|
this.left = this.left
|
||||||
|
} else {
|
||||||
|
this.left = Math.max(this.left + eventDelta, $containerWidth - $wrapperWidth - padding - scrollPanelLeft)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.left = scrollPanelLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
moveToTarget($target) {
|
moveToTarget($target) {
|
||||||
const $container = this.$refs.scrollContainer.$el
|
const $container = this.$refs.scrollContainer
|
||||||
const $containerWidth = $container.offsetWidth
|
const $containerWidth = $container.offsetWidth
|
||||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
|
||||||
const $targetLeft = $target.offsetLeft
|
const $targetLeft = $target.offsetLeft
|
||||||
const $targetWidth = $target.offsetWidth
|
const $targetWidth = $target.offsetWidth
|
||||||
if ($targetLeft > $containerWidth) {
|
|
||||||
// tag in the right
|
if ($targetLeft < -this.left) {
|
||||||
$scrollWrapper.scrollLeft = $targetLeft - $containerWidth + $targetWidth + padding
|
|
||||||
} else {
|
|
||||||
// tag in the left
|
// tag in the left
|
||||||
$scrollWrapper.scrollLeft = $targetLeft - padding
|
this.left = -$targetLeft + padding
|
||||||
|
} else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + $containerWidth - padding) {
|
||||||
|
// tag in the current view
|
||||||
|
// eslint-disable-line
|
||||||
|
} else {
|
||||||
|
// tag in the right
|
||||||
|
this.left = -($targetLeft - ($containerWidth - $targetWidth) + padding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,13 +93,8 @@ export default {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/deep/ {
|
.scroll-wrapper {
|
||||||
.el-scrollbar__bar {
|
position: absolute;
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
.el-scrollbar__wrap {
|
|
||||||
height: 49px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
export default function createDetectElementResize(nonce) {
|
||||||
|
// Check `document` and `window` in case of server-side rendering
|
||||||
|
var _window
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
_window = window
|
||||||
|
} else if (typeof self !== 'undefined') {
|
||||||
|
_window = self
|
||||||
|
} else {
|
||||||
|
_window = global
|
||||||
|
}
|
||||||
|
|
||||||
|
var attachEvent = typeof document !== 'undefined' && document.attachEvent
|
||||||
|
|
||||||
|
if (!attachEvent) {
|
||||||
|
var requestFrame = (function() {
|
||||||
|
var raf =
|
||||||
|
_window.requestAnimationFrame ||
|
||||||
|
_window.mozRequestAnimationFrame ||
|
||||||
|
_window.webkitRequestAnimationFrame ||
|
||||||
|
function(fn) {
|
||||||
|
return _window.setTimeout(fn, 20)
|
||||||
|
}
|
||||||
|
return function(fn) {
|
||||||
|
return raf(fn)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
var cancelFrame = (function() {
|
||||||
|
var cancel =
|
||||||
|
_window.cancelAnimationFrame ||
|
||||||
|
_window.mozCancelAnimationFrame ||
|
||||||
|
_window.webkitCancelAnimationFrame ||
|
||||||
|
_window.clearTimeout
|
||||||
|
return function(id) {
|
||||||
|
return cancel(id)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
var resetTriggers = function(element) {
|
||||||
|
var triggers = element.__resizeTriggers__,
|
||||||
|
expand = triggers.firstElementChild,
|
||||||
|
contract = triggers.lastElementChild,
|
||||||
|
expandChild = expand.firstElementChild
|
||||||
|
contract.scrollLeft = contract.scrollWidth
|
||||||
|
contract.scrollTop = contract.scrollHeight
|
||||||
|
expandChild.style.width = expand.offsetWidth + 1 + 'px'
|
||||||
|
expandChild.style.height = expand.offsetHeight + 1 + 'px'
|
||||||
|
expand.scrollLeft = expand.scrollWidth
|
||||||
|
expand.scrollTop = expand.scrollHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkTriggers = function(element) {
|
||||||
|
return (
|
||||||
|
element.offsetWidth != element.__resizeLast__.width ||
|
||||||
|
element.offsetHeight != element.__resizeLast__.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var scrollListener = function(e) {
|
||||||
|
// Don't measure (which forces) reflow for scrolls that happen inside of children!
|
||||||
|
if (
|
||||||
|
e.target.className.indexOf('contract-trigger') < 0 &&
|
||||||
|
e.target.className.indexOf('expand-trigger') < 0
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = this
|
||||||
|
resetTriggers(this)
|
||||||
|
if (this.__resizeRAF__) {
|
||||||
|
cancelFrame(this.__resizeRAF__)
|
||||||
|
}
|
||||||
|
this.__resizeRAF__ = requestFrame(function() {
|
||||||
|
if (checkTriggers(element)) {
|
||||||
|
element.__resizeLast__.width = element.offsetWidth
|
||||||
|
element.__resizeLast__.height = element.offsetHeight
|
||||||
|
element.__resizeListeners__.forEach(function(fn) {
|
||||||
|
fn.call(element, e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detect CSS Animations support to detect element display/re-attach */
|
||||||
|
var animation = false,
|
||||||
|
keyframeprefix = '',
|
||||||
|
animationstartevent = 'animationstart',
|
||||||
|
domPrefixes = 'Webkit Moz O ms'.split(' '),
|
||||||
|
startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(
|
||||||
|
' ',
|
||||||
|
),
|
||||||
|
pfx = ''
|
||||||
|
{
|
||||||
|
var elm = document.createElement('fakeelement')
|
||||||
|
if (elm.style.animationName !== undefined) {
|
||||||
|
animation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animation === false) {
|
||||||
|
for (var i = 0; i < domPrefixes.length; i++) {
|
||||||
|
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
|
||||||
|
pfx = domPrefixes[i]
|
||||||
|
keyframeprefix = '-' + pfx.toLowerCase() + '-'
|
||||||
|
animationstartevent = startEvents[i]
|
||||||
|
animation = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var animationName = 'resizeanim'
|
||||||
|
var animationKeyframes =
|
||||||
|
'@' +
|
||||||
|
keyframeprefix +
|
||||||
|
'keyframes ' +
|
||||||
|
animationName +
|
||||||
|
' { from { opacity: 0; } to { opacity: 0; } } '
|
||||||
|
var animationStyle =
|
||||||
|
keyframeprefix + 'animation: 1ms ' + animationName + '; '
|
||||||
|
}
|
||||||
|
|
||||||
|
var createStyles = function(doc) {
|
||||||
|
if (!doc.getElementById('detectElementResize')) {
|
||||||
|
// opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360
|
||||||
|
var css =
|
||||||
|
(animationKeyframes || '') +
|
||||||
|
'.resize-triggers { ' +
|
||||||
|
(animationStyle || '') +
|
||||||
|
'visibility: hidden; opacity: 0; } ' +
|
||||||
|
'.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: " "; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; z-index: -1; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }',
|
||||||
|
head = doc.head || doc.getElementsByTagName('head')[0],
|
||||||
|
style = doc.createElement('style')
|
||||||
|
|
||||||
|
style.id = 'detectElementResize'
|
||||||
|
style.type = 'text/css'
|
||||||
|
|
||||||
|
if (nonce != null) {
|
||||||
|
style.setAttribute('nonce', nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.styleSheet) {
|
||||||
|
style.styleSheet.cssText = css
|
||||||
|
} else {
|
||||||
|
style.appendChild(doc.createTextNode(css))
|
||||||
|
}
|
||||||
|
|
||||||
|
head.appendChild(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addResizeListener = function(element, fn) {
|
||||||
|
if (attachEvent) {
|
||||||
|
element.attachEvent('onresize', fn)
|
||||||
|
} else {
|
||||||
|
if (!element.__resizeTriggers__) {
|
||||||
|
var doc = element.ownerDocument
|
||||||
|
var elementStyle = _window.getComputedStyle(element)
|
||||||
|
if (elementStyle && elementStyle.position == 'static') {
|
||||||
|
element.style.position = 'relative'
|
||||||
|
}
|
||||||
|
createStyles(doc)
|
||||||
|
element.__resizeLast__ = {}
|
||||||
|
element.__resizeListeners__ = [];
|
||||||
|
(element.__resizeTriggers__ = doc.createElement('div')).className =
|
||||||
|
'resize-triggers'
|
||||||
|
element.__resizeTriggers__.innerHTML =
|
||||||
|
'<div class="expand-trigger"><div></div></div>' +
|
||||||
|
'<div class="contract-trigger"></div>'
|
||||||
|
element.appendChild(element.__resizeTriggers__)
|
||||||
|
resetTriggers(element)
|
||||||
|
element.addEventListener('scroll', scrollListener, true)
|
||||||
|
|
||||||
|
/* Listen for a css animation to detect element display/re-attach */
|
||||||
|
if (animationstartevent) {
|
||||||
|
element.__resizeTriggers__.__animationListener__ = function animationListener(
|
||||||
|
e,
|
||||||
|
) {
|
||||||
|
if (e.animationName == animationName) {
|
||||||
|
resetTriggers(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.__resizeTriggers__.addEventListener(
|
||||||
|
animationstartevent,
|
||||||
|
element.__resizeTriggers__.__animationListener__,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.__resizeListeners__.push(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeResizeListener = function(element, fn) {
|
||||||
|
if (attachEvent) {
|
||||||
|
element.detachEvent('onresize', fn)
|
||||||
|
} else {
|
||||||
|
element.__resizeListeners__.splice(
|
||||||
|
element.__resizeListeners__.indexOf(fn),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
if (!element.__resizeListeners__.length) {
|
||||||
|
element.removeEventListener('scroll', scrollListener, true)
|
||||||
|
if (element.__resizeTriggers__.__animationListener__) {
|
||||||
|
element.__resizeTriggers__.removeEventListener(
|
||||||
|
animationstartevent,
|
||||||
|
element.__resizeTriggers__.__animationListener__,
|
||||||
|
)
|
||||||
|
element.__resizeTriggers__.__animationListener__ = null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
element.__resizeTriggers__ = !element.removeChild(
|
||||||
|
element.__resizeTriggers__,
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
// Preact compat; see developit/preact-compat/issues/228
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addResizeListener,
|
||||||
|
removeResizeListener
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tags-view-container">
|
<div class="tags-view-container">
|
||||||
|
<span class="scroll-left-btn" @click="scrollLeft">
|
||||||
|
<i class="el-icon-caret-left"/>
|
||||||
|
</span>
|
||||||
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
|
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="tag in Array.from(visitedViews)"
|
v-for="tag in Array.from(visitedViews)"
|
||||||
|
@ -13,6 +16,9 @@
|
||||||
<span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)"/>
|
<span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</scroll-pane>
|
</scroll-pane>
|
||||||
|
<span class="scroll-right-btn" @click="scrollRight">
|
||||||
|
<i class="el-icon-caret-right"/>
|
||||||
|
</span>
|
||||||
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
||||||
<li @click="refreshSelectedTag(selectedTag)">{{ $t('tagsView.refresh') }}</li>
|
<li @click="refreshSelectedTag(selectedTag)">{{ $t('tagsView.refresh') }}</li>
|
||||||
<li @click="closeSelectedTag(selectedTag)">{{ $t('tagsView.close') }}</li>
|
<li @click="closeSelectedTag(selectedTag)">{{ $t('tagsView.close') }}</li>
|
||||||
|
@ -58,6 +64,12 @@ export default {
|
||||||
this.addViewTags()
|
this.addViewTags()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
scrollLeft() {
|
||||||
|
this.$refs.scrollPane.scrollLeft()
|
||||||
|
},
|
||||||
|
scrollRight() {
|
||||||
|
this.$refs.scrollPane.scrollRight()
|
||||||
|
},
|
||||||
generateTitle, // generateTitle by vue-i18n
|
generateTitle, // generateTitle by vue-i18n
|
||||||
generateRoute() {
|
generateRoute() {
|
||||||
if (this.$route.name) {
|
if (this.$route.name) {
|
||||||
|
@ -133,12 +145,29 @@ export default {
|
||||||
|
|
||||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
.tags-view-container {
|
.tags-view-container {
|
||||||
height: 34px;
|
position: relative;
|
||||||
width: 100%;
|
.scroll-left-btn, .scroll-right-btn {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 33px;
|
||||||
|
height: 33px;
|
||||||
|
line-height: 33px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.scroll-right-btn {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.tags-view-wrapper {
|
||||||
|
position: relative;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
height: 34px;
|
||||||
border-bottom: 1px solid #d8dce5;
|
border-bottom: 1px solid #d8dce5;
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
||||||
.tags-view-wrapper {
|
|
||||||
.tags-view-item {
|
.tags-view-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -154,9 +183,6 @@ export default {
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
&:last-of-type {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: #42b983;
|
background-color: #42b983;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
Loading…
Reference in New Issue