TagsView.vue

1.TagsView.vue

  1 <template>
  2      <div class="tags-view-container">
  3           <scroll-pane class="tags-view-wrapper" ref="scrollPane">
  4               <router-link ref="tag"  class="isActive(tag)?'active':''"  :to="tag"  @contextmenu.prevent.native="openMenu(tag,$event)" v-for="tag in Array.from(visitedViews)" :key="tag.path" >
  5                 {{tag.title}}
  6                   <span class="el-icon-close" @click.prevent.stop='closeSelectedTag(tag)'></span>
  7               </router-link>
  8           </scroll-pane>
  9        <ul class='contextmenu' v-show="visible" :style="{left:left+'px',top:top+'px'}">
 10          <li @click="closeSelectedTag(selectedTag)">关闭</li>
 11          <li @click="closeOthersTags">关闭其他</li>
 12          <li @click="closeAllTags">关闭所有</li>
 13        </ul>
 14      </div>
 15 </template>
 16 
 17 <script>
 18   import ScrollPane from '@/components/ScrollPane'
 19     export default {
 20         name: "tags-view",
 21       components: { ScrollPane },
 22      data(){
 23           return{
 24             visible: false,
 25             top: 0,
 26             left: 0,
 27             selectedTag: {}
 28           }
 29      },
 30       computed:{
 31           visitedViews(){
 32             console.log('tabView')
 33             console.log(this.$store.state.tagsView)
 34             console.log(this.$store.state)
 35             return this.$store.state.tagsView.visitedViews
 36           }
 37       },
 38       watch:{
 39          $route(){
 40            this.addViewTags()
 41            this.moveToCurrentTag()
 42          },
 43         visible(value) {
 44           if (value) {
 45             document.body.addEventListener('click', this.closeMenu)
 46           } else {
 47             document.body.removeEventListener('click', this.closeMenu)
 48           }
 49         }
 50       },
 51       mounted() {
 52         this.addViewTags()
 53       },
 54       methods:{
 55           generateRoute(){
 56             if (this.$route.name) {
 57               return this.$route
 58             }
 59             return false
 60           },
 61         isActive(route) {
 62           return route.path === this.$route.path
 63         },
 64         addViewTags() {
 65           const route = this.generateRoute()
 66           if (!route) {
 67             return false
 68           }
 69           this.$store.dispatch('addVisitedViews', route)
 70         },
 71         moveToCurrentTag() {
 72           const tags = this.$refs.tag
 73           this.$nextTick(() => {
 74             for (const tag of tags) {
 75               if (tag.to.path === this.$route.path) {
 76                 this.$refs.scrollPane.moveToTarget(tag.$el)
 77                 break
 78               }
 79             }
 80           })
 81         },
 82         closeSelectedTag(view) {
 83           this.$store.dispatch('delVisitedViews', view).then((views) => {
 84             if (this.isActive(view)) {
 85               const latestView = views.slice(-1)[0]
 86               if (latestView) {
 87                 this.$router.push(latestView)
 88               } else {
 89                 this.$router.push('/')
 90               }
 91             }
 92           })
 93         },
 94         closeOthersTags() {
 95           this.$router.push(this.selectedTag)
 96           this.$store.dispatch('delOthersViews', this.selectedTag).then(() => {
 97             this.moveToCurrentTag()
 98           })
 99         },
100         closeAllTags() {
101           this.$store.dispatch('delAllViews')
102           this.$router.push('/')
103         },
104         openMenu(tag, e) {
105           this.visible = true
106           this.selectedTag = tag
107           const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
108           this.left = e.clientX - offsetLeft + 15 // 15: margin right
109           this.top = e.clientY
110         },
111         closeMenu() {
112           this.visible = false
113         }
114       }
115     }
116 </script>
117 
118 <style rel="stylesheet/scss" lang="scss" scoped>
119   .tags-view-container {
120     .tags-view-wrapper {
121       background: #fff;
122       height: 34px;
123       border-bottom: 1px solid #d8dce5;
124       box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
125       .tags-view-item {
126         display: inline-block;
127         position: relative;
128         height: 26px;
129         line-height: 26px;
130         border: 1px solid #d8dce5;
131         color: #495060;
132         background: #fff;
133         padding: 0 8px;
134         font-size: 12px;
135         margin-left: 5px;
136         margin-top: 4px;
137         &:first-of-type {
138           margin-left: 15px;
139         }
140         &.active {
141           background-color: #42b983;
142           color: #fff;
143           border-color: #42b983;
144           &::before {
145             content: '';
146             background: #fff;
147             display: inline-block;
148             width: 8px;
149             height: 8px;
150             border-radius: 50%;
151             position: relative;
152             margin-right: 2px;
153           }
154         }
155       }
156     }
157     .contextmenu {
158       margin: 0;
159       background: #fff;
160       z-index: 100;
161       position: absolute;
162       list-style-type: none;
163       padding: 5px 0;
164       border-radius: 4px;
165       font-size: 12px;
166       font-weight: 400;
167       color: #333;
168       box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
169       li {
170         margin: 0;
171         padding: 7px 16px;
172         cursor: pointer;
173         &:hover {
174           background: #eee;
175         }
176       }
177     }
178   }
179 </style>
180 
181 <style rel="stylesheet/scss" lang="scss">
182   //reset element css of el-icon-close
183   .tags-view-wrapper {
184     .tags-view-item {
185       .el-icon-close {
186         width: 16px;
187         height: 16px;
188         vertical-align: 2px;
189         border-radius: 50%;
190         text-align: center;
191         transition: all .3s cubic-bezier(.645, .045, .355, 1);
192         transform-origin: 100% 50%;
193         &:before {
194           transform: scale(.6);
195           display: inline-block;
196           vertical-align: -3px;
197         }
198         &:hover {
199           background-color: #b4bccc;
200           color: #fff;
201         }
202       }
203     }
204   }
205 </style>

2.ScrollPane.vue:是当tagView内容超出一行时候的滚动(将鼠标悬浮在那一行上,不出现滚动条,该行就可以滚动)

 1 <template>
 2   <div class="scroll-container" ref="scrollContainer" @wheel.prevent="handleScroll">
 3     <div class="scroll-wrapper" ref="scrollWrapper" :style="{left: left + 'px'}">
 4       <slot></slot>
 5     </div>
 6   </div>
 7 </template>
 8 
 9 <script>
10   const padding = 15 // tag's padding
11 
12   export default {
13     name: 'scrollPane',
14     data() {
15       return {
16         left: 0
17       }
18     },
19     methods: {
20       handleScroll(e) {
21         const eventDelta = e.wheelDelta || -e.deltaY * 3//wheelDelta:-120;deltaY:-120
22         const $container = this.$refs.scrollContainer//外面的container
23         const $containerWidth = $container.offsetWidth//外面的container的宽度
24         const $wrapper = this.$refs.scrollWrapper//里面
25         const $wrapperWidth = $wrapper.offsetWidth//里面的宽度
26 
27         if (eventDelta > 0) {
28           this.left = Math.min(0, this.left + eventDelta)//min() 方法可返回指定的数字中带有最低值的数字。
29         } else {
30           if ($containerWidth - padding < $wrapperWidth) {
31             if (this.left < -($wrapperWidth - $containerWidth + padding)) {
32               this.left = this.left
33             } else {
34               this.left = Math.max(this.left + eventDelta, $containerWidth - $wrapperWidth - padding)
35             }
36           } else {
37             this.left = 0
38           }
39         }
40       },
41       moveToTarget($target) {
42         const $container = this.$refs.scrollContainer
43         const $containerWidth = $container.offsetWidth
44         const $targetLeft = $target.offsetLeft
45         const $targetWidth = $target.offsetWidth
46 
47         if ($targetLeft < -this.left) {
48           // tag in the left
49           this.left = -$targetLeft + padding
50         } else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + $containerWidth - padding) {
51           // tag in the current view
52           // eslint-disable-line
53         } else {
54           // tag in the right
55           this.left = -($targetLeft - ($containerWidth - $targetWidth) + padding)
56         }
57       }
58     }
59   }
60 </script>
61 
62 <style rel="stylesheet/scss" lang="scss" scoped>
63   .scroll-container {
64     white-space: nowrap;
65     position: relative;
66     overflow: hidden;
67     width: 100%;
68     .scroll-wrapper {
69       position: absolute;
70     }
71   }
72 </style>

3.Store/tagsView.js

 1 const tagsView = {
 2   state: {
 3     visitedViews: [],
 4     cachedViews: []
 5   },
 6   mutations: {
 7     ADD_VISITED_VIEWS: (state, view) => {
 8       if (state.visitedViews.some(v => v.path === view.path)) return
 9       state.visitedViews.push(Object.assign({}, view, {
10         title: view.meta.title || 'no-name'
11       }))
12       if (!view.meta.noCache) {
13         state.cachedViews.push(view.name)
14       }
15     },
16     DEL_VISITED_VIEWS: (state, view) => {
17       for (const [i, v] of state.visitedViews.entries()) {
18         if (v.path === view.path) {
19           state.visitedViews.splice(i, 1)
20           break
21         }
22       }
23       for (const i of state.cachedViews) {
24         if (i === view.name) {
25           const index = state.cachedViews.indexOf(i)
26           state.cachedViews.splice(index, 1)
27           break
28         }
29       }
30     },
31     DEL_OTHERS_VIEWS: (state, view) => {
32       for (const [i, v] of state.visitedViews.entries()) {
33         if (v.path === view.path) {
34           state.visitedViews = state.visitedViews.slice(i, i + 1)
35           break
36         }
37       }
38       for (const i of state.cachedViews) {
39         if (i === view.name) {
40           const index = state.cachedViews.indexOf(i)
41           state.cachedViews = state.cachedViews.slice(index, i + 1)
42           break
43         }
44       }
45     },
46     DEL_ALL_VIEWS: (state) => {
47       state.visitedViews = []
48       state.cachedViews = []
49     }
50   },
51   actions: {
52     addVisitedViews({ commit }, view) {
53       commit('ADD_VISITED_VIEWS', view)
54     },
55     delVisitedViews({ commit, state }, view) {
56       return new Promise((resolve) => {
57         commit('DEL_VISITED_VIEWS', view)
58         resolve([...state.visitedViews])
59       })
60     },
61     delOthersViews({ commit, state }, view) {
62       return new Promise((resolve) => {
63         commit('DEL_OTHERS_VIEWS', view)
64         resolve([...state.visitedViews])
65       })
66     },
67     delAllViews({ commit, state }) {
68       return new Promise((resolve) => {
69         commit('DEL_ALL_VIEWS')
70         resolve([...state.visitedViews])
71       })
72     }
73   }
74 }
75 
76 export default tagsView

4.将tagsView.js引入到store/index.js中

 1 import Vue from 'vue'
 2 import Vuex from 'vuex'
 3 import user from './modules/user'
 4 import app from './modules/app'
 5 import permission from './modules/permission'
 6 import tagsView from './modules/tagsView'
 7 
 8 import getters from './getters'
 9 
10 Vue.use(Vuex)
11 const store= new Vuex.Store({
12   modules:{
13     user,
14     app,
15     permission,
16     tagsView
17 
18   },
19   getters
20 
21 })
22 
23 export default store

 总体功能分析:

1.选择左侧菜单的时候,通过监听路由变化,将左侧菜单的选中项缓存到store中,并显示在tagsView中

2.有右侧菜单事件:textmenu

3.点击名字时候,跳转到相应页面

4.点击关闭时候,关闭当前选项卡

posted @ 2018-08-02 12:19  前端极客  阅读(7988)  评论(0编辑  收藏  举报