| <!-- |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| --> |
| <template> |
| <div class="tags-view-container"> |
| <scroll-pane ref="scrollPane" class="tags-view-wrapper"> |
| <router-link |
| v-for="tag in visitedViews" |
| ref="tag" |
| :class="isActive(tag)?'active':''" |
| :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" |
| :key="tag.path" |
| tag="span" |
| class="tags-view-item" |
| @click.middle.native="closeSelectedTag(tag)" |
| @contextmenu.prevent.native="openMenu(tag,$event)"> |
| {{ generateTitle(tag.title) }} |
| <span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" /> |
| </router-link> |
| </scroll-pane> |
| <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu"> |
| <li @click="refreshSelectedTag(selectedTag)">{{ $t('tagsView.refresh') }}</li> |
| <li @click="closeSelectedTag(selectedTag)">{{ $t('tagsView.close') }}</li> |
| <li @click="closeOthersTags">{{ $t('tagsView.closeOthers') }}</li> |
| <li @click="closeAllTags">{{ $t('tagsView.closeAll') }}</li> |
| </ul> |
| </div> |
| </template> |
| |
| <script> |
| import ScrollPane from '@/components/ScrollPane' |
| import { generateTitle } from '@/utils/i18n' |
| |
| export default { |
| components: { ScrollPane }, |
| data() { |
| return { |
| visible: false, |
| top: 0, |
| left: 0, |
| selectedTag: {} |
| } |
| }, |
| computed: { |
| visitedViews() { |
| return this.$store.state.tagsView.visitedViews |
| } |
| }, |
| watch: { |
| $route() { |
| this.addViewTags() |
| this.moveToCurrentTag() |
| }, |
| visible(value) { |
| if (value) { |
| document.body.addEventListener('click', this.closeMenu) |
| } else { |
| document.body.removeEventListener('click', this.closeMenu) |
| } |
| } |
| }, |
| mounted() { |
| this.addViewTags() |
| }, |
| methods: { |
| generateTitle, // generateTitle by vue-i18n |
| isActive(route) { |
| return route.path === this.$route.path |
| }, |
| addViewTags() { |
| const { name } = this.$route |
| if (name) { |
| this.$store.dispatch('addView', this.$route) |
| } |
| return false |
| }, |
| moveToCurrentTag() { |
| const tags = this.$refs.tag |
| this.$nextTick(() => { |
| for (const tag of tags) { |
| if (tag.to.path === this.$route.path) { |
| this.$refs.scrollPane.moveToTarget(tag) |
| |
| // when query is different then update |
| if (tag.to.fullPath !== this.$route.fullPath) { |
| this.$store.dispatch('updateVisitedView', this.$route) |
| } |
| |
| break |
| } |
| } |
| }) |
| }, |
| refreshSelectedTag(view) { |
| this.$store.dispatch('delCachedView', view).then(() => { |
| const { fullPath } = view |
| this.$nextTick(() => { |
| this.$router.replace({ |
| path: '/redirect' + fullPath |
| }) |
| }) |
| }) |
| }, |
| closeSelectedTag(view) { |
| this.$store.dispatch('delView', view).then(({ visitedViews }) => { |
| if (this.isActive(view)) { |
| const latestView = visitedViews.slice(-1)[0] |
| if (latestView) { |
| this.$router.push(latestView) |
| } else { |
| this.$router.push('/') |
| } |
| } |
| }) |
| }, |
| closeOthersTags() { |
| this.$router.push(this.selectedTag) |
| this.$store.dispatch('delOthersViews', this.selectedTag).then(() => { |
| this.moveToCurrentTag() |
| }) |
| }, |
| closeAllTags() { |
| this.$store.dispatch('delAllViews') |
| this.$router.push('/') |
| }, |
| openMenu(tag, e) { |
| const menuMinWidth = 105 |
| const offsetLeft = this.$el.getBoundingClientRect().left // container margin left |
| const offsetWidth = this.$el.offsetWidth // container width |
| const maxLeft = offsetWidth - menuMinWidth // left boundary |
| const left = e.clientX - offsetLeft + 15 // 15: margin right |
| |
| if (left > maxLeft) { |
| this.left = maxLeft |
| } else { |
| this.left = left |
| } |
| this.top = e.clientY |
| |
| this.visible = true |
| this.selectedTag = tag |
| }, |
| closeMenu() { |
| this.visible = false |
| } |
| } |
| } |
| </script> |
| |
| <style rel="stylesheet/scss" lang="scss" scoped> |
| .tags-view-container { |
| height: 34px; |
| width: 100%; |
| background: #fff; |
| 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-wrapper { |
| .tags-view-item { |
| display: inline-block; |
| position: relative; |
| cursor: pointer; |
| 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; |
| } |
| &:last-of-type { |
| margin-right: 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; |
| } |
| } |
| } |
| } |
| .contextmenu { |
| margin: 0; |
| background: #fff; |
| z-index: 100; |
| position: absolute; |
| list-style-type: none; |
| padding: 5px 0; |
| border-radius: 4px; |
| font-size: 12px; |
| font-weight: 400; |
| color: #333; |
| box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3); |
| li { |
| margin: 0; |
| padding: 7px 16px; |
| cursor: pointer; |
| &:hover { |
| background: #eee; |
| } |
| } |
| } |
| } |
| </style> |
| |
| <style rel="stylesheet/scss" lang="scss"> |
| //reset element css of el-icon-close |
| .tags-view-wrapper { |
| .tags-view-item { |
| .el-icon-close { |
| width: 16px; |
| height: 16px; |
| vertical-align: 2px; |
| border-radius: 50%; |
| text-align: center; |
| transition: all .3s cubic-bezier(.645, .045, .355, 1); |
| transform-origin: 100% 50%; |
| &:before { |
| transform: scale(.6); |
| display: inline-block; |
| vertical-align: -3px; |
| } |
| &:hover { |
| background-color: #b4bccc; |
| color: #fff; |
| } |
| } |
| } |
| } |
| </style> |