【vue2实现Tabs标签的切换和删除】

实现Tabs功能

  • 点击菜单增加标签
  • 点击切换标签
  • 点击标签的× 关闭标签
  • 点击关闭所有标签功能
  • 点击关闭其他标签功能
  • 缓存标签出现的页面- keepAlive
  • 刷新页面缓存标签功能,关闭浏览器或跳转到登录页清除标签数组
  • 点击刷新当前页面-provide和inject
  • 超出长度出现左右滑动箭头

在这里插入图片描述

点击菜单增加标签

标签组件TabsContent.vue

 <el-tabs
            v-model="editableTabsValue"
            type="card"
            closable
            @tab-click="clickTable"
            @tab-remove="removeTable"
          >
            <el-tab-pane
              :key="item.name + index"
              v-for="(item, index) in editableTabs"
              :label="item.title"
              :name="'/' + item.name"
            >
            </el-tab-pane>
          </el-tabs>
  data() {
    return {
      editableTabsValue: this.$store.state.editableTabsValue,
      editableTabs: this.$store.state.editableTabs,
    };
  },

左侧菜单组件,我的是leftnav.vue

 methods: {
    openMenuItem(chmenu) {
      store.commit("ADD_TABS", chmenu);
      //调用store里面的方法;chumenu里面的值包含如下
    },
  },

在这里插入图片描述
store.js里面的方法

ADD_TABS:(state,tab)=>{//增加标签页方法
      //在editableTabs中查找此界面是否已打开,否进入if
       if(state.editableTabs.findIndex(e=>e.name===tab.url)===-1){
        var index = tab.url.lastIndexOf("\/");  //这个是去掉路径的第一个‘/’的,不需要可忽略
       var pageName = tab.url.substring(index + 1, tab.url.length);
        state.keepList.push(pageName)
        state.editableTabs.push({//添加当前标签页进入editableTabs
              title:tab.menuname,
              name:tab.url,
              close:true//使除了我的页面标签页外其他都可关闭
            })
          }
          state.editableTabsValue=tab.path//添加标签页后默认打开
    },

点击切换标签

标签组件TabsContent.vue

methods: {
  // 点击切换标签
    clickTable(tab) {
      var name = JSON.stringify(tab.paneName).replace('"', "").replace('"', ""); //对tab参数处理,以获得当前点击的标签页的路由
      var label = tab.label;
      store.commit("CHANGE_TABS", name); //调用store.js中的切换方法切换标签页
      router.push({
        name: label,
      });
    }
   }
    CHANGE_TABS:(state,name)=>{//切换标签页方法
         //将需打开标签页路由赋予editableTabsvalue
          state.editableTabsValue=name
    },

点击标签的× 关闭标签

  // 移除标签
    removeTable(targetName) {
      // 动态缓存页面接口--这个是下一个功能的可忽略
      var index = targetName.lastIndexOf("/");
      var pageName = targetName.substring(index + 1, targetName.length);
      this.keepList = this.keepList.filter((item) => item != pageName);
      this.$store.state.keepList = this.keepList;
      // 点单个标签后面的× 移除标签 ----这个方法可以看官网的 我的因为路径不匹配改了一下
      let tabs = this.editableTabs;
      let activeName = this.editableTabsValue;
      var targetNameTwo = targetName.slice(1);
      if (activeName === targetName) {
        tabs.forEach((tab, index) => {
          if (tab.name === targetNameTwo) {
            let nextTab = tabs[index + 1] || tabs[index - 1];
            if (nextTab) {
              activeName = nextTab.name;
              return activeName;
            }
          }
        });
      }
      this.editableTabsValue = activeName;
      var targetNameNew = targetName.slice(1);  //这个也是地址的 不需要可忽略
      this.editableTabs = tabs.filter((tab) => tab.name !== targetNameNew); 
      this.$store.state.editableTabs = this.editableTabs; //更新后的数据存到store
      for (var i = 0; i < this.editableTabs.length; i++) {
        if (this.editableTabsValue === this.editableTabs[i].name) {
          var title = this.editableTabs[i].title;
          router.push({      //跳转页面,我的是根据路由的name跳转,不是url
            name: title,
          });
          return;
        }
      }
    },

原因:点击切换路径匹配不到页面

在这里插入图片描述
如图,使用的是router.push(name),点击获取到的页面url直接拼接到地址栏,导致匹配不到地址,出现空白页
改为使用

 <el-tabs
    v-model="editableTabsValue"
    type="card"
    editable
    @edit="handleTabsEdit"
    @tab-click="clickTable" //点击切换标签页的方法
  >
    <el-tab-pane
      :key="item.name + index"
      v-for="(item, index) in editableTabs"
      :label="item.title"
      :name="item.name"
    >
      {{ item.content }}{{ index }}
    </el-tab-pane>
  </el-tabs>
    clickTable(tab) {
      var label = tab.label;  //拿到路由名称
      router.push({ //根据路由名称进行页面跳转
        name: label, 
      });
    },

缓存标签出现的页面- keepAlive

index.vue中

方法一:直接包裹组件,这样会缓存所有包裹的页面,不太符合情况

<el-main clss="index-main">
        <keep-alive><router-view></router-view></keep-alive>
</el-main>

方法二:存在于keepList这个数组的页面才会被缓存,这里要注意的是数组内的值要与页面的name对应

<el-main clss="index-main">
        <keep-alive :include="keepList"><router-view></router-view></keep-alive>
      </el-main>

刷新页面缓存标签功能,关闭浏览器或跳转到登录页清除标签数组

标签数据缓存在store,刷新页面的时候先存储但session中,页面加载后再重新取出赋值

export default {
  name: "App",
  watch: {
    $route: function () {
      this.reload();
    },
  },
  created() {
    //在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem("store")) {
      this.$store.replaceState(
        Object.assign(
          {},
          this.$store.state,
          JSON.parse(sessionStorage.getItem("store"))
        )
      );
    }
    //在页面刷新时将vuex里的信息保存到sessionStorage里
    window.addEventListener("beforeunload", () => {
      sessionStorage.setItem("store", JSON.stringify(this.$store.state));
    });
  },
  methods: {
    reload() {
    //首页清空标签数组
      if (this.$route.path == "/login") {
        sessionStorage.setItem("store", {});
        this.$store.state.editableTabs = [];
        this.$store.state.keepList = [];
      }
    },
  },
};

点击关闭所有、关闭其他标签、刷新页面功能

  <div class="tabContent-right" v-if="this.editableTabs.length != 0">
        <el-dropdown @command="handleCommand">
          <span class="el-dropdown-link"> 页面操作 </span>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item command="c" icon="el-icon-refresh-right"
              >刷新</el-dropdown-item
            >
            <el-dropdown-item command="a" icon="el-icon-folder-opened"
              >关闭所有</el-dropdown-item
            >
            <el-dropdown-item command="b" icon="el-icon-folder"
              >关闭其他</el-dropdown-item
            >
          </el-dropdown-menu>
        </el-dropdown>
      </div>
 handleCommand(command) {
      // 关闭所有标签
      if (command == "a") {
        this.editableTabs = [];
        this.$store.state.editableTabs = this.editableTabs;
        this.keepList = [];
        this.$store.state.keepList = this.keepList;
        this.$router.push({
          path: "/personal/MyFile",
          meta: {
            keepMenu: true,
          },
        });
        this.onlyOneStatus();
      } else if (command == "b") {
        // 移除其他,保留当前页
        for (var j = 0; j < this.editableTabs.length; j++) {
          if (this.editableTabsValue == "/" + this.editableTabs[j].name) {
            // 动态缓存
            var name = this.editableTabs[j].name;
            var index = name.lastIndexOf("/");
            var pageName = name.substring(index + 1, name.length);
            this.keepList = [];
            this.keepList.push(pageName);
            this.$store.state.keepList = this.keepList;
            // 移除
            var oneTab = {};
            oneTab.name = this.editableTabs[j].name;
            oneTab.title = this.editableTabs[j].title;
            this.editableTabs = [];
            this.editableTabs.push(oneTab);
            this.$store.state.editableTabs = this.editableTabs;
            this.onlyOneStatus();
            return;
          }
        }
      } else {
        // 刷新
        this.refresh();
      }
    },

刷新页面功能的方法-provide和inject
在App.vue页面

<template>
  <div id="app">
    <router-view v-if="isPageAlive"></router-view>
  </div>
</template>
export default {
  name: "App",
  provide() {
    return {
      refresh: this.refresh,
    };
  },
  data() {
    return {
      isPageAlive: true,
    };
  },
  methods: {
    refresh() {
      this.isPageAlive = false;
      this.$nextTick(function () {
        this.isPageAlive = true;
      });
    },
  },
};

超出长度出现左右滑动箭头

标签组件TabsContent.vue

    <div class="tabContentBox">
      <div class="iconLeft" @click="arrowBack" v-if="showButton == true">
        <i class="el-icon-caret-left"></i>
      </div>
      <div class="tag-style" ref="tagBox">
        <div
          class="scrollWrapper"
          ref="scrollWrapper"
          id="nav"
          :style="{ marginLeft: tabScroll }"
        >
          <el-tabs
            v-model="editableTabsValue"
            type="card"
            closable
            @tab-click="clickTable"
            @tab-remove="removeTable"
          >
            <el-tab-pane
              :key="item.name + index"
              v-for="(item, index) in editableTabs"
              :label="item.title"
              :name="'/' + item.name"
            >
            </el-tab-pane>
          </el-tabs>
        </div>
      </div>
      <div class="iconLeft" @click="arrowForward" v-if="showButton == true">
        <i class="el-icon-caret-right"></i>
      </div>
    </div>
data() {
    return {
      tabScroll: "0px", // 移动的距离
      showButton: false,
      swiperScrollWidth: 0, // 盒子的宽度
      swiperScrollContentWidth: 0, // 内容的宽度
    };
  },
 // 标签向左切换
    arrowBack() {
      let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
      let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
      if (offsetLeft > tabBoxWidth) {
        //移动距离大于父盒子宽度,向前移动一整个父盒子宽度
        this.tabScroll = offsetLeft + tabBoxWidth + "px";
      } else {
        this.tabScroll = "0px"; // 否则移动到开始位置
      }
    },
    // 标签向右切换
    arrowForward() {
      let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
      let scrollWidth = this.$refs.scrollWrapper.scrollWidth; //内容宽度
      // 必须要在循环的父级添加 定位样式, offsetLeft 获取元素相对带有定位父元素左边框的偏移
      let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
      let diffWidth = scrollWidth - tabBoxWidth; //计算内容宽度与盒子宽度的差值
      if (diffWidth - offsetLeft > tabBoxWidth) {
        //判断差值减去移动距离是否大于盒子宽度 大于则滚动已移动距离+盒子宽度
        this.tabScroll = -(offsetLeft + tabBoxWidth) + "px";
      } else {
        this.tabScroll = -diffWidth + "px"; //小于则移动差值距离
      }
    },
    //关闭所有标签后,左右箭头不消失的问题,不需要可忽略此方法
    checkButtonStatus() {
      if (!this.$refs.scrollWrapper) return;
      // 盒子的宽度
      let containerSize = this.$refs.tagBox.clientWidth;
      // 内容的宽度
      let navSize = this.$refs.scrollWrapper.scrollWidth;
      if (containerSize > navSize || containerSize == navSize) {
        this.showButton = false;
      } else {
        this.showButton = true;
      }
    },
    //关闭其他标签后,左右箭头不消失的问题,不需要可忽略此方法
    onlyOneStatus() {
      if (this.editableTabs.length <= 1) {
        this.showButton = false;
      }
    },

新建标签页组件-完整代码

<template>
  <div class="tabBackground">
    <div class="tabContentBox">
      <div class="iconLeft" @click="arrowBack" v-if="showButton == true">
        <i class="el-icon-caret-left"></i>
      </div>
      <div class="tag-style" ref="tagBox">
        <div
          class="scrollWrapper"
          ref="scrollWrapper"
          id="nav"
          :style="{ marginLeft: tabScroll }"
        >
          <el-tabs
            v-model="editableTabsValue"
            type="card"
            closable
            @tab-click="clickTable"
            @tab-remove="removeTable"
          >
            <el-tab-pane
              :key="item.name + index"
              v-for="(item, index) in editableTabs"
              :label="item.title"
              :name="'/' + item.name"
            >
            </el-tab-pane>
          </el-tabs>
        </div>
      </div>
      <div class="iconLeft" @click="arrowForward" v-if="showButton == true">
        <i class="el-icon-caret-right"></i>
      </div>
      <div class="tabContent-right" v-if="this.editableTabs.length != 0">
        <el-dropdown @command="handleCommand">
          <span class="el-dropdown-link"> 页面操作 </span>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item command="c" icon="el-icon-refresh-right"
              >刷新</el-dropdown-item
            >
            <el-dropdown-item command="a" icon="el-icon-folder-opened"
              >关闭所有</el-dropdown-item
            >
            <el-dropdown-item command="b" icon="el-icon-folder"
              >关闭其他</el-dropdown-item
            >
          </el-dropdown-menu>
        </el-dropdown>
      </div>
    </div>
  </div>
</template>
<script>
import store from "../vuex/store";
import router from "../router";
export default {
  inject: ["refresh"], //刷新用的
  data() {
    return {
      editableTabsValue: this.$store.state.editableTabsValue,
      editableTabs: this.$store.state.editableTabs,
      keepList: this.$store.state.keepList,
      tabScroll: "0px", // 移动的距离
      showButton: false,
      swiperScrollWidth: 0, // 盒子的宽度
      swiperScrollContentWidth: 0, // 内容的宽度
    };
  },
  watch: {
    $route: function () {
      this.setActiveTab();
      this.checkButtonStatus();
      window.addEventListener("resize", this.checkButtonStatus);
    },
    editableTabsGet: {
      handler: function (oldVal, newVal) {
        if (!this.$refs.scrollWrapper) return;
        if (this.editableTabs.length <= 1) {
          // 盒子的宽度
          this.$refs.tagBox.clientWidth = "0px";
          // 内容的宽度
          this.$refs.scrollWrapper.scrollWidth = "0px";
          this.showButton = false;
        }
      },
      immediate: true,
      deep: true,
    },
  },
  mounted() {
    this.setActiveTab();
    window.addEventListener("resize", this.checkButtonStatus);
  },

  methods: {
    // 设置活跃的tab
    setActiveTab() {
      this.editableTabsValue = this.$route.path;
    },
    // 移除标签
    removeTable(targetName) {
      // 动态缓存页面接口
      var index = targetName.lastIndexOf("/");
      var pageName = targetName.substring(index + 1, targetName.length);
      this.keepList = this.keepList.filter((item) => item != pageName);
      this.$store.state.keepList = this.keepList;
      // 移除标签
      let tabs = this.editableTabs;
      let activeName = this.editableTabsValue;
      var targetNameTwo = targetName.slice(1);
      if (activeName === targetName) {
        tabs.forEach((tab, index) => {
          if (tab.name === targetNameTwo) {
            let nextTab = tabs[index + 1] || tabs[index - 1];
            if (nextTab) {
              activeName = nextTab.name;
              return activeName;
            }
          }
        });
      }
      this.editableTabsValue = activeName;
      var targetNameNew = targetName.slice(1);
      this.editableTabs = tabs.filter((tab) => tab.name !== targetNameNew);
      this.$store.state.editableTabs = this.editableTabs;
      for (var i = 0; i < this.editableTabs.length; i++) {
        if (this.editableTabsValue === this.editableTabs[i].name) {
          var title = this.editableTabs[i].title;
          router.push({
            name: title,
          });
          return;
        }
      }
    },
    // 点击切换标签
    clickTable(tab) {
      var name = JSON.stringify(tab.paneName).replace('"', "").replace('"', ""); //对tab参数处理,以获得当前点击的标签页的路由
      var label = tab.label;
      store.commit("CHANGE_TABS", name); //调用切换方法切换标签页
      router.push({
        name: label,
      });
    },

    handleCommand(command) {
      // 关闭所有标签
      if (command == "a") {
        this.editableTabs = [];
        this.$store.state.editableTabs = this.editableTabs;
        this.keepList = [];
        this.$store.state.keepList = this.keepList;
        this.$router.push({
          path: "/personal/MyFile",
          meta: {
            keepMenu: true,
          },
        });
        this.onlyOneStatus();
      } else if (command == "b") {
        // 移除其他,保留当前页
        for (var j = 0; j < this.editableTabs.length; j++) {
          if (this.editableTabsValue == "/" + this.editableTabs[j].name) {
            // 动态缓存
            var name = this.editableTabs[j].name;
            var index = name.lastIndexOf("/");
            var pageName = name.substring(index + 1, name.length);
            this.keepList = [];
            this.keepList.push(pageName);
            this.$store.state.keepList = this.keepList;
            // 移除
            var oneTab = {};
            oneTab.name = this.editableTabs[j].name;
            oneTab.title = this.editableTabs[j].title;
            this.editableTabs = [];
            this.editableTabs.push(oneTab);
            this.$store.state.editableTabs = this.editableTabs;
            this.onlyOneStatus();
            return;
          }
        }
      } else {
        // 刷新
        this.refresh();
      }
    },

    // 标签向左切换
    arrowBack() {
      let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
      let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
      if (offsetLeft > tabBoxWidth) {
        //移动距离大于父盒子宽度,向前移动一整个父盒子宽度
        this.tabScroll = offsetLeft + tabBoxWidth + "px";
      } else {
        this.tabScroll = "0px"; // 否则移动到开始位置
      }
    },
    // 标签向右切换
    arrowForward() {
      let tabBoxWidth = this.$refs.tagBox.clientWidth; //盒子宽度
      let scrollWidth = this.$refs.scrollWrapper.scrollWidth; //内容宽度
      // 必须要在循环的父级添加 定位样式, offsetLeft 获取元素相对带有定位父元素左边框的偏移
      let offsetLeft = Math.abs(this.$refs.scrollWrapper.offsetLeft); //移动距离
      let diffWidth = scrollWidth - tabBoxWidth; //计算内容宽度与盒子宽度的差值
      if (diffWidth - offsetLeft > tabBoxWidth) {
        //判断差值减去移动距离是否大于盒子宽度 大于则滚动已移动距离+盒子宽度
        this.tabScroll = -(offsetLeft + tabBoxWidth) + "px";
      } else {
        this.tabScroll = -diffWidth + "px"; //小于则移动差值距离
      }
    },
    checkButtonStatus() {
      if (!this.$refs.scrollWrapper) return;
      // 盒子的宽度
      let containerSize = this.$refs.tagBox.clientWidth;
      // 内容的宽度
      let navSize = this.$refs.scrollWrapper.scrollWidth;
      if (containerSize > navSize || containerSize == navSize) {
        this.showButton = false;
      } else {
        this.showButton = true;
      }
    },
    onlyOneStatus() {
      if (this.editableTabs.length <= 1) {
        this.showButton = false;
      }
    },
  },
};
</script>
<style scoped>
.tabContentBox {
  display: flex;
  justify-content: space-between;
  border-bottom: 1px solid rgb(215, 215, 220);
  height: 40px;
  background-color: #fff;
}
.tabBackground {
  height: 50px;
  background-color: #eff0f2;
}
.tabContent-right {
  width: 110px;
  height: 40px;
  line-height: 40px;
  position: relative;
}
.iconLeft {
  width: 3%;
  text-align: center;
  height: 40px;
  line-height: 40px;
  font-size: 16px;
  margin: 0 5px;
  background-color: #fff;
}
.tag-style {
  display: flex;
  overflow: hidden;
  pointer-events: all;
  cursor: pointer;
  position: relative;
}
.scrollWrapper {
  display: flex;
  overflow-x: auto;
  transition: all 500ms linear;
}
.scrollWrapper::-webkit-scrollbar {
  height: 0;
}
</style>

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值