vue+element-ui点击导航栏动态添加对应的tabs,并可删除

一、静态数据

二、动态数据(加载public的本地json)

后端管理系统,需要添加tabs需要,所以,使用vue做了一个动态添加tabs和删除tabs的功能。
最后的效果图:
在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200311161712356.gif
实现功能步骤:
1.明确需求,听过elementUI官方例子生成一个Nav
elementUI官方的左侧导航栏例子
2.点击左侧导航栏,动态在右侧生成一个tabs并显示对应的内容(可删除)
3.首页固定,不可删除,其余动态生成的tabs皆可以删除
4.点击tabs右上角的删除时,如果当前tabs未活跃,则不需要重新处理活跃状态,如果当前tabs正处于活跃状态,则通过elementUI的思路处理下一个需要处于活跃的tabs
5.刷新浏览器的时候,希望当前tabs顺序可以保存,待浏览器刷新完毕,原封不动的重新展示。
1)、以下的全部的代码

<template>
  <el-container>
    <el-header>
      <div style="float: left; cursor:pointer" @click="isOpen">
        <i :class="{'el-icon-s-fold': isCollapse == false?true:false, 'el-icon-s-unfold': isCollapse == true?true:false}"></i>
      </div>
      <el-dropdown trigger="click" style="cursor: pointer; float: right">
        <i class="fa fa-ellipsis-v fa-lg"></i>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>关闭全部页面</el-dropdown-item>
          <el-dropdown-item>刷新页面</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </el-header>
    <el-container :style="{height: setHeight+'px'}">
      <el-aside style="width: auto">
        <el-menu :default-active="String(activeNav)" router class="el-menu-vertical-demo" :collapse="isCollapse">
          <el-submenu index="1">
            <template slot="title">
              <i class="el-icon-setting"></i>
              <span slot="title">基础设置</span>
            </template>
            <el-menu-item index="/nav">导航</el-menu-item>
            <el-menu-item index="/mineInfo">个人信息</el-menu-item>
          </el-submenu>
          <el-submenu index="2">
            <template slot="title">
              <i class="fa fa-book"></i>
              <span slot="title">文章管理</span>
            </template>
            <el-menu-item index="/fuillEditor">fuillEditor</el-menu-item>
            <el-menu-item index="/category">分类管理</el-menu-item>
          </el-submenu>
        </el-menu>
      </el-aside>
      <el-main :style="{height: setHeight+'px', padding: 0}">
        <el-tabs v-model="activeTab" type="card" @tab-remove="removeTab" @tab-click="tabClick">
          <el-tab-pane v-for="(item, index) in tabsItem"
                       :key="item.name"
                       :label="item.title"
                       :name="item.name"
                       :closable="item.closable"
                       :ref="item.ref">
            <component :is="item.content"></component>
          </el-tab-pane>
        </el-tabs>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
  import welcome from '../views/Welcome'
  import setNav from '../views/SetNav'
  import mineInfo from '../views/MineInfo'
  import fuillEditor from "../views/FuillEditor";
  import category from "../views/Category";

  export default {
    name: "Home",
    data() {
      return {
        isCollapse: false,  //false为展开 true为收缩
        activeTab: '1', //默认显示的tab
        tabIndex: 1, //tab目前显示数
        tabsItem: [
          {
            title: '首页',
            name: '1',
            closable: false,
            ref: 'tabs',
            content: welcome
          }
        ],
        tabsPath: [{
          name: "1",
          path: '/welcome'
        }]
      }
    },
    computed: {
      setHeight() {
        return document.documentElement.clientHeight - 65
      },
      activeNav() { //当前激活的导航
        return this.$route.path
      }
    },
    watch: {
      '$route': function (to) {  //监听路由的变化,动态生成tabs
        let flag = true //判断是否需要新增页面
        const path = to.path
        if (Object.keys(to.meta).length != 0) {
          for (let i = 0; i < this.$refs.tabs.length; i++) {
            if (i != 0) { //首页不判断 如果页面已存在,则直接定位当页面,否则新增tab页面
              if (this.$refs.tabs[i].label == to.meta.name) {
                this.activeTab = this.$refs.tabs[i].name  //定位到已打开页面
                flag = false
                break
              }
            }
          }
          //新增页面
          if (flag) {
            //获得路由元数据的name和组件名
            const thisName = to.meta.name
            const thisComp = to.meta.comp
            //对tabs的当前激活下标和tabs数量进行自加
            let newActiveIndex = ++this.tabIndex + ''
            //动态双向追加tabs
            this.tabsItem.push({
              title: thisName,
              name: String(newActiveIndex),
              closable: true,
              ref: 'tabs',
              content: thisComp
            })
            this.activeTab = newActiveIndex
            /*
            * 当添加tabs的时候,把当前tabs的name作为key,path作为value存入tabsPath数组中
            * key:tabs的name
            * value:tabs的path
            * {
            *   key: name,
            *   value: path
            * }
            * ///后面需要得到当前tabs的时候可以通过当前tabs的name获得path
            * */
            if (this.tabsPath.indexOf(path) == -1) {
              this.tabsPath.push({
                name: newActiveIndex,
                path: path
              })
            }
          }
        }
      }
    },
    methods: {
      isOpen() { //判断左侧栏是否展开或收缩
        if (this.isCollapse == false) {
          this.isCollapse = true
        } else {
          this.isCollapse = false
        }
      },
      removeTab(targetName) { //删除Tab
        let tabs = this.tabsItem; //当前显示的tab数组
        let activeName = this.activeTab; //点前活跃的tab

        //如果当前tab正活跃 被删除时执行
        if (activeName === targetName) {
          tabs.forEach((tab, index) => {
            if (tab.name === targetName) {
              let nextTab = tabs[index + 1] || tabs[index - 1];
              if (nextTab) {
                activeName = nextTab.name;
                this.tabClick(nextTab)
              }
            }
          });
        }
        this.activeTab = activeName;
        this.tabsItem = tabs.filter(tab => tab.name !== targetName);
        //在tabsPath中删除当前被删除tab的path
        this.tabsPath = this.tabsPath.filter(item => item.name !== targetName)
      },
      tabClick(thisTab) {
        /*
        * thisTab:当前选中的tabs的实例
        * 通过当前选中tabs的实例获得当前实例的path 重新定位路由
        * */
        let val = this.tabsPath.filter(item => thisTab.name == item.name)
        this.$router.push({
          path: val[0].path
        })
      }
    },
    mounted() {
      /*
      * 监听页面刷新事件
      * 页面刷新前 需要保存当前打开的tabs的位置,刷新后按刷新前的顺序展示
      * 使用js的sessionStorage保存刷新页面前的数据
      * */
      window.addEventListener('beforeunload', e => {
        sessionStorage.setItem("tabsItem", JSON.stringify({
          currTabsItem: this.tabsItem.filter(item => item.name !== "1"),
          currTabsPath: this.tabsPath.filter(item => item.name !== "1"),
          currActiveTabs: this.activeTab,
          currIndex: this.tabIndex
        }))
      });
    },
    created() {
      /*
      * 使用js的sessionStorage读取刷新前的数据,并按刷新前的tabs顺序重新生成tabs
      * */
      const sessionTabs = JSON.parse(sessionStorage.getItem("tabsItem"))
      if(sessionTabs.currTabsItem.length != 0 && sessionTabs.currTabsPath.length != 0) {
        for(let i=0; i<sessionTabs.currTabsItem.length; i++) {
          this.tabsItem.push({
            title: sessionTabs.currTabsItem[i].title,
            name: sessionTabs.currTabsItem[i].name,
            closable: true,
            ref: sessionTabs.currTabsItem[i].ref,
            content: sessionTabs.currTabsItem[i].content
          })
        }
        for(let j=0; j<sessionTabs.currTabsPath.length; j++) {
          this.tabsPath.push({
            name: sessionTabs.currTabsPath[j].name,
            path: sessionTabs.currTabsPath[j].path
          })
        }
        this.activeTab = sessionTabs.currActiveTabs
        this.tabIndex = sessionTabs.currIndex
        //避免强制修改url 出现浏览器的url输入框的路径和当前tabs选中的路由路径不匹配
        const activePath = this.tabsPath.filter(item => item.name == this.activeTab)
        this.$router.push({
          path: activePath[0].path
        })
      }
    },
    components: {
      welcome,
      setNav,
      mineInfo,
      fuillEditor,
      category
    }
  }
</script>

<style scoped>
  @import "~font-awesome/css/font-awesome.min.css";

  .el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
    height: 100%;
  }

  .el-menu--collapse {
    height: 100%;
  }

  .el-header {
    background-color: #B3C0D1;
    color: #333;
    line-height: 60px;
  }

  .el-aside {
    color: #333;
  }

  .el-submenu [class^=fa] {
    vertical-align: middle;
    margin-right: 5px;
    width: 24px;
    text-align: center;
    font-size: 16px;
  }

</style>

2)、出于需求,静态数据相对来说不太友好,不利于拓展,所以写了一个加载本地的json数据,动态来生成Aside导航功能(主要是递归组件).
说明:动态生成aside导航,只附上核心代码,其余代码和静态的一样,小伙伴们可以细心的上去查看,谢谢。

<!--这个是elementUI的aside导航的最外面一层,所以不用多说,直接按官方的写上-->
<el-menu :class="colorObject"
               :default-active="String(activeNav)" router
               :collapse="isCollapse">
        <!--动态生成sideItem-->
        <template v-for="(item, parentIndex) in nav.nav"><!--本地public文件夹下/json的数据-->
          <SideNav :item="item" :index="parentIndex"></SideNav>
        </template>
</el-menu>

##SideNav子组件代码:
<template>
<div :class="{'menu-wrapper': state}">
  <!--有子导航-->
  <el-submenu v-if="item.child" :index="String(index+1)">
    <template slot="title">
      <!--如果item有icon才添加icon图标-->
      <i v-if="item.icon" :class="item.icon"></i>
      <span slot="title">{{item.title}}</span>
    </template>
    <!--遍历子导航-->
    <!--如果子导航中没有存在下一级的子导航 则是<el-menu-item> 否则为<el-submenu>-->
    <template v-for="(cItem, cIndex) in item.child" :index="String(index+1+'-'+cIndex+1)">
      <el-menu-item v-if="!cItem.child" :index="cItem.path">{{cItem.title}}</el-menu-item>
      <el-submenu v-else :index="String(index+1+'-'+cIndex+1)">
        <template slot="title">
          <i v-if="item.icon" :class="cItem.icon"></i>
          <span slot="title">{{cItem.title}}</span>
        </template>
        <!--递归自己 遍历子..导航-->
        <template v-for="(item, parentIndex) in cItem.child">
          <SideNav :item="item" :index="parentIndex"></SideNav>
        </template>
      </el-submenu>
    </template>
  </el-submenu>
  <!--没有子导航-->
  <el-menu-item v-else :index="item.path">
    <i v-if="item.icon" :class="item.icon"></i>
    <span slot="title">{{item.title}}</span>
  </el-menu-item>
</div>
</template>

<script>
  export default {
    name: "SideNav",
    props: {
      item: {
        type: Object,
        required: true
      },
      index: {
        type: Number,
        required: true
      },
      state: { //解决添加SideNav套div后收缩无法隐藏文字的问题,通过收缩的状态isCollapse来处理
        type: Boolean,
        required: true
      }
    }
  }
</script>

<style scoped>
  .el-submenu [class^=fa] {
    vertical-align: middle;
    margin-right: 5px;
    width: 24px;
    text-align: center;
    font-size: 16px;
  }
  .menu-wrapper span[slot="title"] {
    display: none;
  }

</style>

json格式:

{
  "LOGO": {<!--可不要-->
      "id": "1",
      "img": "/imgs/logo.png",
      "title": "博客管理系统"
    },
  "nav": [
    {
      "icon": "el-icon-setting",
      "title": "基础设置",
      "child": [
        {
          "title": "导航",
          "path": "/nav"
        },
        {
          "title": "个人信息",
          "path": "/mineInfo"
        }
      ]
    },
    {
      "icon": "fa fa-book",
      "title": "文章分类",
      "child": [
        {
          "title": "fuillEdito",
          "path": "/fuillEditor"
        },
        {
          "title": "分类管理",
          "path": "/category"
        }
      ]
    }
  ]
}

注意:SideNav最外层不要套div标签或者其他HTML标签,否则做出来的效果,当你点击折叠左侧导航的时候,你会发现和官方的效果不同,这就是因为套了div或者其他标签所致的。所以,说到这里,就有个问题了,你们发现没有,我这里都是使用props传递数据的,这种思路避免了在子组件套DIV的现象 (这个问题依然是存在的,只是如果处理得当还是没有问题的)

<template v-for="(item, parentIndex) in nav.nav">
     <SideNav :item="item" :index="parentIndex"></SideNav>
</template>
<template v-for="(item, parentIndex) in cItem.child">
      <SideNav :item="item" :index="parentIndex"></SideNav>
</template>

我测试只测到三层关系。。。。

附上所有代码,是让读者更加清晰思路,但本人还是希望读者,先理清自己的思路,然后参照我的例子,吸其精华,融合到自己的代码中,而不是整个代码块都复制进去,这样来说,相当的不好哦。
如有疑问,请在下方评论处提问,我会及时回复,谢谢…

**

修改于2020-04-08:左侧导航栏在收缩状态下,如果触发多级导航项,弹出的子导航距离远和控制台会报内存溢出错误。(处于这个项目我一直在做着,功能方面添加了很多,所以改动的话,可以会牵连一些小bug出来,待后面我会把完整的代码上传到仓库,供大家下载,目前如果出现一些问题,请大家留言评论,谢谢)

在这里插入图片描述

**
三、新版的布局:
tabs虽然能实现点击导航显示对应的页面,但是感觉也太局限发挥了,所以,琢磨了一下,废除了tabs,自己手写代码,做出和tabs一样的功能,不再受tabs限制了自己的代码,新版的功能和旧版的实现一样,区别在于自己和别人的好,代码不再上传了,

Github仓库

GitHub仓库地址:https://github.com/darkSystemCode/VueBlog

  • 27
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 38
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值