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..c06d5979 100644 --- a/src/views/layout/components/TagsView.vue +++ b/src/views/layout/components/TagsView.vue @@ -1,139 +1,204 @@ <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)"> + {{generateTitle(tag.title)}} + <span class='el-icon-close' @click='closeViewTags(tag,$event)'></span> + </router-link> + </scroll-pane> + <ul class='contextmenu' v-show="visible" :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 { generateTitle } from '@/utils/i18n' + import ScrollPane from '@/components/ScrollPane' + import { generateTitle } from '@/utils/i18n' -export default { - components: { ScrollPane }, - computed: { - visitedViews() { - return this.$store.state.tagsView.visitedViews - } - }, - mounted() { - this.addViewTags() - }, - methods: { - generateTitle, - closeViewTags(view, $event) { - this.$store.dispatch('delVisitedViews', view).then((views) => { - if (this.isActive(view)) { - const latestView = views.slice(-1)[0] - if (latestView) { - this.$router.push(latestView.path) - } else { - this.$router.push('/') - } - } - }) - $event.preventDefault() - }, - generateRoute() { - if (this.$route.name) { - return this.$route + export default { + components: { ScrollPane }, + data() { + return { + visible: false, + isOverflow: false, + top: 0, + left: 0, + isSelect: {} } - return false }, - addViewTags() { - const route = this.generateRoute() - if (!route) { - return false + computed: { + visitedViews() { + return this.$store.state.tagsView.visitedViews } - this.$store.dispatch('addVisitedViews', route) }, - isActive(route) { - return route.path === this.$route.path || route.name === this.$route.name - }, - moveToCurrentTag() { - const tags = this.$refs.tag - this.$nextTick(() => { - for (const tag of tags) { - if (tag.to === this.$route.path) { - this.$refs.scrollPane.moveToTarget(tag.$el) - break - } - } - }) - } - }, - watch: { - $route() { + mounted() { this.addViewTags() - this.moveToCurrentTag() + }, + methods: { + generateTitle, + closeViewTags(view, $event) { + this.$store.dispatch('delVisitedViews', view).then((views) => { + if (this.isActive(view)) { + const latestView = views.slice(-1)[0] + if (latestView) { + this.$router.push(latestView.path) + } else { + this.$router.push('/') + } + } + }) + $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.$store.dispatch('delAllViews') + this.$router.push('/') + $event.preventDefault() + } + }, + generateRoute() { + if (this.$route.name) { + return this.$route + } + return false + }, + addViewTags() { + const route = this.generateRoute() + if (!route) { + return false + } + this.$store.dispatch('addVisitedViews', route) + }, + isActive(route) { + return route.path === this.$route.path || route.name === this.$route.name + }, + moveToCurrentTag() { + const tags = this.$refs.tag + this.$nextTick(() => { + for (const tag of tags) { + if (tag.to === this.$route.path) { + this.$refs.scrollPane.moveToTarget(tag.$el) + break + } + } + }) + }, + openMenu(tag, e) { + this.visible = true + this.isSelect = tag + this.left = e.clientX + this.top = e.clientY + } + }, + watch: { + $route() { + this.addViewTags() + this.moveToCurrentTag() + }, + visible(v) { + if (v) { + window.addEventListener('click', () => { + this.visible = false + }, false) + } else { + window.removeEventListener('click') + } + } } } -} </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"> -.tags-view-container { - .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; + .tags-view-container { + .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>