diff --git a/src/components/ScrollPane/index.vue b/src/components/ScrollPane/index.vue index 8051494f..ee554116 100644 --- a/src/components/ScrollPane/index.vue +++ b/src/components/ScrollPane/index.vue @@ -3,69 +3,114 @@ <div class="scroll-wrapper" ref="scrollWrapper" :style="{left: left + 'px'}"> <slot></slot> </div> + <div class="bar-container" v-show="isOverflow" ref="scrollbarContainer"> + <div class="scrollbar" ref="scrollbar"> + </div> + </div> </div> </template> <script> -const padding = 15 // tag's padding + const padding = 30 // tag's padding -export default { - name: 'scrollPane', - data() { - return { - left: 0 - } - }, - methods: { - handleScroll(e) { - e.preventDefault() - const $container = this.$refs.scrollContainer - const $containerWidth = $container.offsetWidth - const $wrapper = this.$refs.scrollWrapper - const $wrapperWidth = $wrapper.offsetWidth - - if (e.wheelDelta > 0) { - this.left = Math.min(0, this.left + e.wheelDelta) - } else { - if ($containerWidth - padding < $wrapperWidth) { - if (this.left < -($wrapperWidth - $containerWidth + padding)) { - this.left = this.left - } else { - this.left = Math.max(this.left + e.wheelDelta, $containerWidth - $wrapperWidth - padding) - } - } else { - this.left = 0 - } + export default { + name: 'scrollPane', + data() { + return { + left: 0, + isOverflow: false, + container: undefined, + containerWidth: undefined, + wrapper: undefined, + wrapperWidth: undefined, + scrollbarContainer: undefined, + scrollbarContainerWidth: undefined, + scrollbar: undefined, + scrollbarWidth: undefined } }, - moveToTarget($target) { - const $container = this.$refs.scrollContainer - const $containerWidth = $container.offsetWidth - const $targetLeft = $target.offsetLeft - const $targetWidth = $target.offsetWidth + mounted() { + this.container = this.$refs.scrollContainer + this.wrapper = this.$refs.scrollWrapper + this.scrollbarContainer = this.$refs.scrollbarContainer + this.scrollbar = this.$refs.scrollbar + this.calcSize() + window.addEventListener('resize', () => { + this.$forceUpdate() + this.calcSize() + }, false) + }, + methods: { + calcPos() { + if (this.isOverflow) { + this.scrollbar.style.left = -(this.scrollbarContainerWidth - this.scrollbarWidth) * this.left / (this.wrapperWidth - this.containerWidth + padding) + 'px' + } + }, + calcSize() { + // 计算滚动条的长度 + this.containerWidth = parseFloat(this.container.offsetWidth, 10) + this.wrapperWidth = parseFloat(this.wrapper.offsetWidth, 10) + this.scrollbarContainerWidth = parseFloat(this.scrollbarContainer.offsetWidth, 10) + this.isOverflow = this.wrapperWidth + padding > this.containerWidth + if (!this.isOverflow) return false + this.scrollbarWidth = this.scrollbarContainerWidth * this.containerWidth / (this.wrapperWidth + padding) + this.scrollbar.style.width = this.scrollbarWidth + 'px' + this.calcPos() + }, + handleScroll(e) { + e.preventDefault() + if (e.wheelDelta > 0) { + // when whell up + this.left = Math.min(0, this.left + e.wheelDelta) + } else { + // when wheel down + // this.left∈[-(this.wrapperWidth-this.containerWidth+100),0] + if (this.left >= -(this.wrapperWidth - this.containerWidth + padding)) { + this.left = Math.max(this.left + e.wheelDelta, this.containerWidth - this.wrapperWidth - padding) + } + } + this.calcPos() + }, + moveToTarget($target) { + const $targetLeft = $target.offsetLeft + const $targetWidth = $target.offsetWidth - if ($targetLeft < -this.left) { - // tag in the left - 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) + if ($targetLeft < -this.left) { + // tag in the left + this.left = -$targetLeft + padding + } else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + this.containerWidth - padding) { + // tag in the current view + // eslint-disable-line + } else { + // tag in the right + this.left = -($targetLeft - (this.containerWidth - $targetWidth) + padding) + } + this.calcSize() } } } -} </script> <style rel="stylesheet/scss" lang="scss" scoped> -.scroll-container { - white-space: nowrap; - position: relative; - overflow: hidden; - .scroll-wrapper { - position: absolute; + .scroll-container { + white-space: nowrap; + position: relative; + overflow: hidden; + .scroll-wrapper { + position: absolute; + } + .bar-container{ + position: absolute; + bottom: 0; + width:100%; + height: 3px; + .scrollbar{ + position: absolute; + top: 0; + height: 100%; + background: rgba(0,0,0,.8); + border-radius: 1.5px; + } + } } -} </style> diff --git a/src/store/modules/tagsView.js b/src/store/modules/tagsView.js index eed8b694..1fccaf69 100644 --- a/src/store/modules/tagsView.js +++ b/src/store/modules/tagsView.js @@ -29,6 +29,25 @@ const tagsView = { break } } + }, + DEL_OTHER_VIEWS: (state, view) => { + for (const [i, v] of state.visitedViews.entries()) { + if (v.path === view.path) { + state.visitedViews = [].concat(state.visitedViews.slice(i, i + 1)) + break + } + } + for (const i of state.cachedViews) { + if (i === view.name) { + const index = state.cachedViews.indexOf(i) + state.cachedViews = [].concat(state.cachedViews.slice(index, i + 1)) + break + } + } + }, + DEL_ALL_VIEWS: (state) => { + state.visitedViews = [] + state.cachedViews = [] } }, actions: { @@ -40,6 +59,18 @@ const tagsView = { commit('DEL_VISITED_VIEWS', view) resolve([...state.visitedViews]) }) + }, + delOtherViews({ commit, state }, view) { + return new Promise((resolve) => { + commit('DEL_OTHER_VIEWS', view) + resolve([...state.visitedViews]) + }) + }, + delAllViews({ commit, state }) { + return new Promise((resolve) => { + commit('DEL_ALL_VIEWS') + resolve([...state.visitedViews]) + }) } } } diff --git a/src/views/layout/components/TagsView.vue b/src/views/layout/components/TagsView.vue index 7e730280..88795649 100644 --- a/src/views/layout/components/TagsView.vue +++ b/src/views/layout/components/TagsView.vue @@ -1,18 +1,36 @@ <template> - <scroll-pane class='tags-view-container' ref='scrollPane'> - <router-link ref='tag' class="tags-view-item" :class="isActive(tag)?'active':''" v-for="tag in Array.from(visitedViews)" :to="tag.path":key="tag.path"> - {{generateTitle(tag.title)}} - <span class='el-icon-close' @click='closeViewTags(tag,$event)'></span> - </router-link> - </scroll-pane> + <div class="tag-container"> + <scroll-pane class='tags-view-container' ref='scrollPane'> + <router-link ref='tag' class="tags-view-item" :class="isActive(tag)?'active':''" v-for="tag in Array.from(visitedViews)" :to="tag.path":key="tag.path" @contextmenu.prevent.native="openMenu(tag,$event)" v-Clickoutside="closeMenu"> + {{generateTitle(tag.title)}} + <span class='el-icon-close' @click='closeViewTags(tag,$event)'></span> + </router-link> + </scroll-pane> + <ul class='contextmenu' v-show="open" :style="{left:left+'px',top:top+'px'}"> + <li @click="closePage(0,$event)">关闭</li> + <li @click="closePage(1,$event)">关闭其他</li> + <li @click="closePage(2,$event)">关闭所有</li> + </ul> + </div> </template> <script> import ScrollPane from '@/components/ScrollPane' +import Clickoutside from 'element-ui/src/utils/clickoutside' import { generateTitle } from '@/utils/i18n' export default { components: { ScrollPane }, + directives: { Clickoutside }, + data() { + return { + open: false, + isOverflow: false, + top: 0, + left: 0, + isSelect: {} + } + }, computed: { visitedViews() { return this.$store.state.tagsView.visitedViews @@ -21,6 +39,9 @@ export default { mounted() { this.addViewTags() }, + updated() { + this.$nextTick(this.$refs.scrollPane.calcSize()) + }, methods: { generateTitle, closeViewTags(view, $event) { @@ -36,6 +57,20 @@ export default { }) $event.preventDefault() }, + closePage(flag, $event) { + if (flag === 0) { + this.closeViewTags(this.isSelect, $event) + } else if (flag === 1) { + this.$router.push(this.isSelect.path) + this.$store.dispatch('delOtherViews', this.isSelect) + $event.preventDefault() + } else { + this.$router.push('/') + this.$store.dispatch('delAllViews') + $event.preventDefault() + } + this.open = false + }, generateRoute() { if (this.$route.name) { return this.$route @@ -62,6 +97,15 @@ export default { } } }) + }, + openMenu(tag, e) { + this.open = true + this.isSelect = tag + this.left = e.clientX + this.top = e.clientY + }, + closeMenu() { + this.open = false } }, watch: { @@ -74,43 +118,64 @@ export default { </script> <style rel="stylesheet/scss" lang="scss" scoped> -.tags-view-container { - background: #fff; - height: 34px; - 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); - .tags-view-item { - display: inline-block; - position: relative; - height: 26px; - line-height: 26px; - border: 1px solid #d8dce5; - color: #495060; - background: #fff; - padding: 0 8px; - font-size: 12px; - margin-left: 5px; - margin-top: 4px; - &:first-of-type { - margin-left: 15px; + .tag-container { + .contextmenu { + margin: 0; + background: #fff; + z-index: 99999; + position: absolute; + list-style-type: none; + padding-left: 0; + border: 1px solid rgba(0, 0, 0, 0.4); + font-size: 0.8rem; + box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .5); + li { + margin: 0; + padding: 0.2rem 1.5rem 0.3rem 0.8rem; + &:hover { + background: #eee; + cursor: default; + } + } } - &.active { - background-color: #42b983; - color: #fff; - border-color: #42b983; - &::before { - content: ''; - background: #fff; + .tags-view-container { + background: #fff; + height: 34px; + 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); + .tags-view-item { display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; position: relative; - margin-right: 2px; + height: 26px; + line-height: 26px; + border: 1px solid #d8dce5; + color: #495060; + background: #fff; + padding: 0 8px; + font-size: 12px; + margin-left: 5px; + margin-top: 4px; + &:first-of-type { + margin-left: 15px; + } + &.active { + background-color: #42b983; + color: #fff; + border-color: #42b983; + &::before { + content: ''; + background: #fff; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + position: relative; + margin-right: 2px; + } + } } } } -} </style> <style rel="stylesheet/scss" lang="scss">