• 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              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          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      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.点击关闭时候,关闭当前选项卡

  • 相关阅读:
    A4纸网页打印 html网页页面的宽度设置成多少
    怎样使用 css 的@media print控制打印
    jquery 表格自动拆分(方便打印)插件-printTable
    【转】编写高质量代码改善C#程序的157个建议——建议107:区分静态类和单例
    【转】编写高质量代码改善C#程序的157个建议——建议106:为静态类添加静态构造函数
    【转】编写高质量代码改善C#程序的157个建议——建议104:用多态代替条件语句
    【转】编写高质量代码改善C#程序的157个建议——建议103:区分组合和继承的应用场合
    【转】编写高质量代码改善C#程序的157个建议——建议102:区分接口和抽象类的应用场合
    【转】编写高质量代码改善C#程序的157个建议——建议101:使用扩展方法,向现有类型“添加”方法
    【转】编写高质量代码改善C#程序的157个建议——建议100:静态方法和实例方法没有区别
  • 原文地址:https://www.cnblogs.com/yangguoe/p/9406429.html
Copyright © 2020-2023  润新知