vue2 实现tabs超出滚动效果

文章描述了如何基于Vue.js和ElementUI的tabs组件创建一个公共组件,用于保存和展示用户已点击过的路由菜单。组件使用Vuex和vuex-persistedstate插件实现数据的持久化存储,确保页面刷新后仍能保留菜单历史。此外,还涉及到了CSS样式设计以及通过计算属性和方法处理滚动和选中状态。
摘要由CSDN通过智能技术生成

根据项目需求,需要对已点击过的路由菜单进行保存,并展示在页面上方,所以需要定义一个公共组件来实现该功能

1.思路

根据element-ui里面的tabs组件,进行设计,符合当前项目tabs组件

搭建页面

html-css部分

<template>
  <div class="bread_container" ref="bread_container">
    <div class="scroll_btn pre" @click="scrollTab(false)" v-show="hideBtn">
      <i class="el-icon-arrow-left"></i>
    </div>
    <ul ref="scroll_content">
      <li
        v-for="item in breadList"
        :key="item.name"
        :class="[defaultActive == item.path ? 'bread_active' : '']"
        :aria-selected="defaultActive == item.path"
      >
        <span @click="selectTab(item)">{{ item.name }}</span>
        <i
          :class="[!defaultActive == item.path ? 'hideClose' : '']"
          class="el-icon-close"
          style="margin-left: 10px"
          @click="removeTab(item)"
        ></i>
      </li>
    </ul>
    <div class="scroll_btn next" @click="scrollTab(true)" v-show="hideBtn">
      <i class="el-icon-arrow-right"></i>
    </div>
  </div>
</template>

 

<style lang="less" scoped>
.bread_container {
  width: 100%;
  height: 48px;
  background: #fff;
  box-sizing: border-box;
  position: relative;
  display: flex;
  align-items: last baseline;
  padding: 0 20px;
  box-sizing: border-box;
  overflow: hidden;
  .scroll_btn {
    height: 100%;
    width: 20px;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    background: #fff;
    z-index: 20;
    box-shadow:  0 0 10px 1px #ececec;
  }
  .pre {
    left: 0;
  }
  .next {
    right: 0;
  }
  ul {
    width: 100%;
    white-space: nowrap;
    transform: translateX(0);
    transition: all .3s linear;
    li {
      padding: 0 15px;
      display: inline-block;
      height: 40px;
      line-height: 40px;
      font-size: 14px;
      color: #999999;
      cursor: pointer;
      box-sizing: border-box;
      margin-right: 10px;
      min-width: 88px;
      text-align: center;
      // position: relative;
      white-space: nowrap;
      &:hover {
        color: @theme_Color;
        .el-icon-close {
          display: inline;
        }
      }
      transition: all 0.3s linear;
    }

    .bread_active {
      color: @theme_Color;
      background: #f5f6fa;
      border-radius: 10px 10px 0px 0px;
    }
    .hideClose {
      display: none;
    }
  }
}
</style>

js部分

由于网页刷新之后,保存的数据消失,所以需要用到vuex 和sessionStorage进行持久化存储

用的是  vuex-persistedstate 插件

import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules/index'
import createPersistedstate from 'vuex-persistedstate'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  },
  modules,
  plugins:[
    createPersistedstate({
      storage:window.sessionStorage, // 储存方式
      key:'bread', // 键名
      paths:['menu'] // 生效的模块
    })
  ]
})

模块内逻辑

// 设置默认选中的路由
const defaultRoute = '/baseData';

export default {
    state: {
        defaultActive: defaultRoute, // 默认路由
        breadList: [] // 存储的数组
    },
    mutations: {
        SET_BREADLIST(state, data) {
            state.breadList.push(data) // 添加动作
        },
        SET_DEFAULTACTIVE(state, data) {
            state.defaultActive = data.path // 设置默认选中路由
        },
        REMOVE_BREADLIST_ITEM(state, data) {
            // 查找下标
            let index = state.breadList.indexOf(data);
            let tabs = state.breadList
            // 如果删除的不是选中的tab 只做删除动作
            if (data.path !== state.defaultActive) {
                state.breadList = tabs.filter(item => {
                    if (item.name !== data.name) {
                        return item
                    }
                })
                return
            }
            // 设置下一个为默认选中
            let nextActive = tabs[index - 1] || tabs[index + 1]
            if (nextActive) {
                state.defaultActive = nextActive.path
            }
            // 设置breadList
            state.breadList = tabs.filter(item => {
                if (item.name !== data.name) {
                    return item
                }
            })
            // 如果list为空 重置默认tab
            !state.breadList.length && (state.defaultActive = defaultRoute)
            
        }
    },
    actions: {

    },
}

 AsideItem.vue文件中

<template>
  <div>
    <label v-for="item in menuList" :key="item.name">
        <el-submenu :index="item.path" v-if="item.children">
        <template slot="title">
            <i class="el-icon-location"></i>
            <span>{{item.name}}</span>
        </template>
        <el-menu-item-group>
            <aside-Item :menuList = "item.children"></aside-Item>
        </el-menu-item-group>
        </el-submenu>
        <el-menu-item v-else :index="item.path" @click="setBreadList(item)">{{ item.name }}</el-menu-item>
    </label >
  </div>
</template>


<script>
import { mapState } from 'vuex';
export default {
  name: "aside-Item",
  components: {},
  props:{
    menuList:{
        type:Array,
        default:()=>{
            return []
        }
    }
  },
  data() {
    return {};
  },
  computed:{
    ...mapState({
        breadList:state=>state.menu.breadList // 获取vuex中list列表
    })
  },
  methods:{
    // 点击菜单的方法
    setBreadList(item){ 
        // 是否已存储
          let isHas = this.breadList.find(v=>v.name==item.name)

          if (!isHas) {
              this.$store.commit('SET_BREADLIST',item) // 添加tab
          }
          this.$store.commit('SET_DEFAULTACTIVE',item) // 设置选中的tab
        
    }
  }
};
</script>

<style lang="less" scoped>
.el-menu-item {
    margin: 0 5px;
    border-radius: 10px;
}
.el-menu-item.is-active {
  background-color: #F7F7FF !important;
}
</style>
<script>
import { mapState } from "vuex";
export default {
  name: "",
  components: {},
  data() {
    return {
      wd: 0,
      hideBtn:false
    };
  },
  computed: {
    ...mapState({
      defaultActive: (state) => state.menu.defaultActive, // 获取vuex中的选中路径
      breadList: (state) => state.menu.breadList, // 获取vuex中list列表
    }),
  },
  methods: {
    selectTab(item) {
      // 设置默认选中
      this.$store.commit("SET_DEFAULTACTIVE", item);
    },
    removeTab(item) {
      // 删除tab
      this.$store.commit("REMOVE_BREADLIST_ITEM", item);
      this.scrollTab(false)
    },
    // 点击左右按钮 控制滚动距离
    scrollTab(flag) {
      this.$nextTick(()=>{
        let scroll = this.$refs["scroll_content"]; // 滚动容器
        let scrollNum = scroll.offsetWidth; // 单次滚动距离
        let canScrollWidth = scroll.scrollWidth - scroll.offsetWidth; // 总体可滚动距离
        if (flag) {
          this.wd +=scrollNum
              if (this.wd > canScrollWidth) {
                  this.wd = canScrollWidth
              }
              scroll.style.transform = `translateX(${-this.wd}px)`;
          
        }else{
          this.wd -= scrollNum
              if (this.wd < 0) {
                  this.wd = 0
              }
              scroll.style.transform = `translateX(${-this.wd}px)`;
        }
      })
    },
    // 控制按钮显示隐藏
    hideShowBtn(){
        let scroll = this.$refs["scroll_content"];
        this.hideBtn = scroll.scrollWidth > scroll.offsetWidth; // 是否显示左右按钮
        this.positionTab() // 定位到指定位置
    },
    // 定位到选中tab栏
    positionTab(){
      this.$nextTick(()=>{
        let scroll = this.$refs["scroll_content"];
        let liDom = scroll.querySelectorAll('li');
        let left = 0
        // 获取选中的tab
        for(let i = 0; i<liDom.length; i++){
          if (liDom[i].ariaSelected == 'true') {
            left = liDom[i].offsetLeft
          }
        }
        let num = Math.ceil(left / scroll.offsetWidth) // 执行次数
        let index = 0
        while (index <= num) {
          index+=1
          left < scroll.offsetWidth ? this.scrollTab(false) : this.scrollTab(true) // 定位到已选中的按钮
        }
      })
    }
  },
  mounted(){
    // 重新加载时 执行按钮显示判断
    this.hideShowBtn()
  },
  watch:{
    breadList:{
      // immediate:true,
      handler(){
        // 监视宽度变化 添加tab栏时向右滑动
        this.scrollTab(true)
        // 控制隐藏按钮
        this.hideShowBtn()
      }
    },
    defaultActive:{
      // 监听选中的路径变化 跳转到指定位置
      handler(){
        this.positionTab()
      }
    }
  }
};
</script>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值