| | |
| | | :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
| | | tag="span"
|
| | | class="tags-view-item"
|
| | | :style="activeStyle(tag)"
|
| | | @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
|
| | | @contextmenu.prevent.native="openMenu(tag,$event)"
|
| | | >
|
| | | {{ tag.title }}
|
| | | <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
|
| | | <span
|
| | | v-if="!isAffix(tag)"
|
| | | 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">
|
| | |
| | | </template>
|
| | |
|
| | | <script>
|
| | | import ScrollPane from './ScrollPane'
|
| | | import path from 'path'
|
| | | import ScrollPane from "./ScrollPane";
|
| | | import path from "path";
|
| | |
|
| | | export default {
|
| | | components: { ScrollPane },
|
| | |
| | | left: 0,
|
| | | selectedTag: {},
|
| | | affixTags: []
|
| | | }
|
| | | };
|
| | | },
|
| | | computed: {
|
| | | visitedViews() {
|
| | | return this.$store.state.tagsView.visitedViews
|
| | | return this.$store.state.tagsView.visitedViews;
|
| | | },
|
| | | routes() {
|
| | | return this.$store.state.permission.routes
|
| | | return this.$store.state.permission.routes;
|
| | | },
|
| | | theme() {
|
| | | return this.$store.state.settings.theme;
|
| | | }
|
| | | },
|
| | | watch: {
|
| | | $route() {
|
| | | this.addTags()
|
| | | this.moveToCurrentTag()
|
| | | this.addTags();
|
| | | this.moveToCurrentTag();
|
| | | },
|
| | | visible(value) {
|
| | | if (value) {
|
| | | document.body.addEventListener('click', this.closeMenu)
|
| | | document.body.addEventListener("click", this.closeMenu);
|
| | | } else {
|
| | | document.body.removeEventListener('click', this.closeMenu)
|
| | | document.body.removeEventListener("click", this.closeMenu);
|
| | | }
|
| | | }
|
| | | },
|
| | | mounted() {
|
| | | this.initTags()
|
| | | this.addTags()
|
| | | this.initTags();
|
| | | this.addTags();
|
| | | },
|
| | | methods: {
|
| | | isActive(route) {
|
| | | return route.path === this.$route.path
|
| | | return route.path === this.$route.path;
|
| | | },
|
| | | activeStyle(tag) {
|
| | | if (!this.isActive(tag)) return {};
|
| | | return {
|
| | | "background-color": this.theme,
|
| | | "border-color": this.theme
|
| | | };
|
| | | },
|
| | | isAffix(tag) {
|
| | | return tag.meta && tag.meta.affix
|
| | | return tag.meta && tag.meta.affix;
|
| | | },
|
| | | filterAffixTags(routes, basePath = '/') {
|
| | | let tags = []
|
| | | filterAffixTags(routes, basePath = "/") {
|
| | | let tags = [];
|
| | | routes.forEach(route => {
|
| | | if (route.meta && route.meta.affix) {
|
| | | const tagPath = path.resolve(basePath, route.path)
|
| | | const tagPath = path.resolve(basePath, route.path);
|
| | | tags.push({
|
| | | fullPath: tagPath,
|
| | | path: tagPath,
|
| | | name: route.name,
|
| | | meta: { ...route.meta }
|
| | | })
|
| | | });
|
| | | }
|
| | | if (route.children) {
|
| | | const tempTags = this.filterAffixTags(route.children, route.path)
|
| | | const tempTags = this.filterAffixTags(route.children, route.path);
|
| | | if (tempTags.length >= 1) {
|
| | | tags = [...tags, ...tempTags]
|
| | | tags = [...tags, ...tempTags];
|
| | | }
|
| | | }
|
| | | })
|
| | | return tags
|
| | | });
|
| | | return tags;
|
| | | },
|
| | | initTags() {
|
| | | const affixTags = this.affixTags = this.filterAffixTags(this.routes)
|
| | | const affixTags = (this.affixTags = this.filterAffixTags(this.routes));
|
| | | for (const tag of affixTags) {
|
| | | // Must have tag name
|
| | | if (tag.name) {
|
| | | this.$store.dispatch('tagsView/addVisitedView', tag)
|
| | | this.$store.dispatch("tagsView/addVisitedView", tag);
|
| | | }
|
| | | }
|
| | | },
|
| | | addTags() {
|
| | | const { name } = this.$route
|
| | | const { name } = this.$route;
|
| | | if (name) {
|
| | | this.$store.dispatch('tagsView/addView', this.$route)
|
| | | this.$store.dispatch("tagsView/addView", this.$route);
|
| | | }
|
| | | return false
|
| | | return false;
|
| | | },
|
| | | moveToCurrentTag() {
|
| | | const tags = this.$refs.tag
|
| | | const tags = this.$refs.tag;
|
| | | this.$nextTick(() => {
|
| | | for (const tag of tags) {
|
| | | if (tag.to.path === this.$route.path) {
|
| | | this.$refs.scrollPane.moveToTarget(tag)
|
| | | this.$refs.scrollPane.moveToTarget(tag);
|
| | | // when query is different then update
|
| | | if (tag.to.fullPath !== this.$route.fullPath) {
|
| | | this.$store.dispatch('tagsView/updateVisitedView', this.$route)
|
| | | this.$store.dispatch("tagsView/updateVisitedView", this.$route);
|
| | | }
|
| | | break
|
| | | break;
|
| | | }
|
| | | }
|
| | | })
|
| | | });
|
| | | },
|
| | | refreshSelectedTag(view) {
|
| | | this.$store.dispatch('tagsView/delCachedView', view).then(() => {
|
| | | const { fullPath } = view
|
| | | this.$store.dispatch("tagsView/delCachedView", view).then(() => {
|
| | | const { fullPath } = view;
|
| | | this.$nextTick(() => {
|
| | | this.$router.replace({
|
| | | path: '/redirect' + fullPath
|
| | | })
|
| | | })
|
| | | })
|
| | | path: "/redirect" + fullPath
|
| | | });
|
| | | });
|
| | | });
|
| | | },
|
| | | closeSelectedTag(view) {
|
| | | this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
|
| | | if (this.isActive(view)) {
|
| | | this.toLastView(visitedViews, view)
|
| | | }
|
| | | })
|
| | | this.$store
|
| | | .dispatch("tagsView/delView", view)
|
| | | .then(({ visitedViews }) => {
|
| | | if (this.isActive(view)) {
|
| | | this.toLastView(visitedViews, view);
|
| | | }
|
| | | });
|
| | | },
|
| | | closeOthersTags() {
|
| | | this.$router.push(this.selectedTag)
|
| | | this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
|
| | | this.moveToCurrentTag()
|
| | | })
|
| | | this.$router.push(this.selectedTag);
|
| | | this.$store
|
| | | .dispatch("tagsView/delOthersViews", this.selectedTag)
|
| | | .then(() => {
|
| | | this.moveToCurrentTag();
|
| | | });
|
| | | },
|
| | | closeAllTags(view) {
|
| | | this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
|
| | | this.$store.dispatch("tagsView/delAllViews").then(({ visitedViews }) => {
|
| | | if (this.affixTags.some(tag => tag.path === view.path)) {
|
| | | return
|
| | | return;
|
| | | }
|
| | | this.toLastView(visitedViews, view)
|
| | | })
|
| | | this.toLastView(visitedViews, view);
|
| | | });
|
| | | },
|
| | | toLastView(visitedViews, view) {
|
| | | const latestView = visitedViews.slice(-1)[0]
|
| | | const latestView = visitedViews.slice(-1)[0];
|
| | | if (latestView) {
|
| | | this.$router.push(latestView.fullPath)
|
| | | this.$router.push(latestView.fullPath);
|
| | | } else {
|
| | | // now the default is to redirect to the home page if there is no tags-view,
|
| | | // you can adjust it according to your needs.
|
| | | if (view.name === 'Dashboard') {
|
| | | if (view.name === "Dashboard") {
|
| | | // to reload home page
|
| | | this.$router.replace({ path: '/redirect' + view.fullPath })
|
| | | this.$router.replace({ path: "/redirect" + view.fullPath });
|
| | | } else {
|
| | | this.$router.push('/')
|
| | | 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
|
| | | 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
|
| | | this.left = maxLeft;
|
| | | } else {
|
| | | this.left = left
|
| | | this.left = left;
|
| | | }
|
| | |
|
| | | this.top = e.clientY
|
| | | this.visible = true
|
| | | this.selectedTag = tag
|
| | | this.top = e.clientY;
|
| | | this.visible = true;
|
| | | this.selectedTag = tag;
|
| | | },
|
| | | closeMenu() {
|
| | | this.visible = false
|
| | | this.visible = false;
|
| | | }
|
| | | }
|
| | | }
|
| | | };
|
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | |
| | | 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);
|
| | | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
|
| | | .tags-view-wrapper {
|
| | | .tags-view-item {
|
| | | display: inline-block;
|
| | |
| | | color: #fff;
|
| | | border-color: #42b983;
|
| | | &::before {
|
| | | content: '';
|
| | | content: "";
|
| | | background: #fff;
|
| | | display: inline-block;
|
| | | width: 8px;
|
| | |
| | | font-size: 12px;
|
| | | font-weight: 400;
|
| | | color: #333;
|
| | | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
| | | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
| | | li {
|
| | | margin: 0;
|
| | | padding: 7px 16px;
|
| | |
| | | vertical-align: 2px;
|
| | | border-radius: 50%;
|
| | | text-align: center;
|
| | | transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
| | | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
| | | transform-origin: 100% 50%;
|
| | | &:before {
|
| | | transform: scale(.6);
|
| | | transform: scale(0.6);
|
| | | display: inline-block;
|
| | | vertical-align: -3px;
|
| | | }
|