Vue/vuex/vue-router/element-ui 一个后台管理系统实现侧边栏和可配置tab标签栏的demo

自制一个简单demo,实现一个包含侧边栏、tab标签栏的后台管理系统的大致框架,在此做一下总结。

先上图

在这里插入图片描述

项目地址

https://github.com/ekko-wang/vue-elementui-demo

功能点

  1. 可配置的侧边栏;
  2. 侧边栏点击时跳转至相应的router,若没有该标签则新建,已有则激活这个标签;
  3. 点击标签栏中的标签实现下方的路由跳转;
  4. 关闭所有标签栏则自动打开至index。

1、侧边栏

src/components/navMenu/navMenu.vue
<!--本页为左侧下拉菜单-->
<template>
  <el-row class="tac">
    <el-col :span="24">
      <el-menu
        default-active=""
        class="el-menu-vertical-demo"
        background-color="#545c64"
        text-color="#fff"
        unique-opened
        router
        active-text-color="#fff"
      >
        <el-menu-item index="index" @click="clickMenu('index')">
          <i class="el-icon-star-on"></i>
          <span slot="title">首页</span>
        </el-menu-item>
        <el-submenu
          v-for="item of menu"
          :index="item.id"
          :key="item.id"
        >
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>{{item.name}}</span>
          </template>
          <el-menu-item-group class="over-hide">
            <el-menu-item
              v-for="sub of item.sub"
              :index="sub.componentName"
              :key="sub.componentName"
              @click="clickMenu(sub.componentName)"
            >
              {{sub.name}}
            </el-menu-item>
          </el-menu-item-group>
        </el-submenu>
      </el-menu>
    </el-col>
  </el-row>
</template>

<script>
import menu from '@/config/menu-config.js'
export default {
  name: 'navMenu',
  methods: {
    clickMenu (componentName) {
      this.openedTab = this.$store.state.openedTab
      // tabNum 为当前点击的列表项在openedTab中的index,若不存在则为-1
      let tabNum = this.openedTab.indexOf(componentName)
      if (tabNum === -1) {
        // 该标签当前没有打开
        // 将componentName加入到已打开标签页state.openedTab数组中
        this.$store.commit('addTab', componentName)
      } else {
        // 该标签是已经打开过的,需要激活此标签页
        this.$store.commit('changeTab', componentName)
      }
    }
  },
  data () {
    return {
      menu: menu,
      openedTab: []
    }
  }
}
</script>

<style scoped>
  .over-hide{
    overflow-x:hidden;
  }
  .el-submenu__title{
    border-bottom:1px solid #8d98a2
  }
</style>

重点:

  1. .over-hide 属性用于解决element-ui中子选项长度有时会超出边框的问题;
  2. < el-menu >标签中 : unique-opened属性用于设置只允许一栏处于打开状态,点击某title时,其他title都会折叠起来;router属性用于使其下的标签可以通过:index进行路由跳转(需项目里安装了vue-router);
  3. clickMenu 点击事件:若当前点击标签不在$store.state.openedTab中,则该标签为未打开过的,则触发addTab事件,在便签栏中新建此标签,否则触发changeTab事件,跳转至此标签;

src/store/index.js 的mutations中:

addTab (state, componentName) {
      state.openedTab.push(componentName)
    },
    changeTab (state, componentName) {
      state.activeTab = componentName
    }
  1. import menu from ‘@/config/menu-config.js’
src/config/menu-config.js
module.exports = [{
  name: '基础',
  id: 'basic',
  sub: [{
    name: 'BasicLayout',
    componentName: 'BasicLayout'
  }, {
    name: 'BasicContainer',
    componentName: 'BasicContainer'
  }]
}, {
  name: 'Form',
  id: 'form',
  sub: [{
    name: 'BasicRadio',
    componentName: 'BasicRadio'
  }, {
    name: 'BasicCheckbox',
    componentName: 'BasicCheckbox'
  }]
}]

该config文件用于保存侧边栏各个选项的具体内容,需要修改时去修改该文件即可而不需要修改html。

2、标签栏

src/components/navMain/navMain.vue
<!--本页为tab标签-->
<template>
  <el-tabs
    v-model="editableTabsValue"
    type="card"
    closable
    @tab-remove="removeTab"
    @tab-click="handleClickTab($event.name)"
  >
    <el-tab-pane
      :key="item.name"
      v-for="item in editableTabs"
      :label="item.title"
      :name="item.name"
    >
    </el-tab-pane>
  </el-tabs>
</template>

<script>

export default {
  name: 'navMain',
  data () {
    return {
      editableTabsValue: 'index',
      editableTabs: [{
        title: 'index',
        name: 'index'
      }],
      tabIndex: 1,
      openedTab: ['index']
    }
  },
  methods: {
    handleClickTab (route) {
      this.$store.commit('changeTab', route)
      this.$router.push(route)
    },
    removeTab (targetName) {
      let tabs = this.editableTabs
      let activeName = this.editableTabsValue
      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.$store.commit('deductTab', targetName)
      let deductIndex = this.openedTab.indexOf(targetName)
      this.openedTab.splice(deductIndex, 1)
      this.$router.push(activeName)
      this.editableTabsValue = activeName
      this.editableTabs = tabs.filter(tab => tab.name !== targetName)
      if (this.openedTab.length === 0) {
        this.$store.commit('addTab', 'index')
        this.$router.push('index')
      }
    }
  },
  computed: {
    getOpenedTab () {
      return this.$store.state.openedTab
    },
    changeTab () {
      return this.$store.state.activeTab
    }
  },
  watch: {
    getOpenedTab (val) {
      if (val.length > this.openedTab.length) {
        // 添加了新的tab页
        // 导致$store.state中的openedTab长度
        // 大于
        // 标签页中的openedTab长度
        // 因此需要新增标签页
        let newTab = val[val.length - 1] // 新增的肯定在数组最后一个
        ++this.tabIndex
        this.editableTabs.push({
          title: newTab,
          name: newTab,
          content: ''
        })
        this.editableTabsValue = newTab
        this.openedTab.push(newTab)
      }
    },
    changeTab (val) {
      // 监听activetab以实现点击左侧栏时激活已存在的标签
      if (val !== this.editableTabsValue) {
        this.editableTabsValue = val
      }
    }
  },
  created () {
    // 刷新页面时(F11)
    // 因为<router-view>的<keep-alive>,会保留刷新时所在的router
    // 但是tab标签页因为刷新而被重构了,tab没有了
    // 因此需要将router回到index
    this.$router.push('index')
  }
}
</script>

<style scoped>

</style>

重点:

  1. data中:editableTabsValue 用于设置哪个标签处于激活状态;editableTabs 用于保存所有标签;openedTab用于保存打开过的标签;
  2. handleClickTab 方法:点击标签时,触发changeTab事件,切换至相应路由;
  3. removeTab 方法:点击标签上的x时触发,关闭该标签,触发deduct方法,跳转至合适的标签并激活,若已经没有标签了,则打开index;
deductTab (state, componentName) {
      let index = state.openedTab.indexOf(componentName)
      state.openedTab.splice(index, 1)
    }
  1. computed和watch中的两个方法配合使用,实现监控store中openedTab和activeTab的变化并相应的改变标签栏中的标签状态,实现方式注释里已经写明。

一些补充

  1. 通过keep-alive实现各个标签之间切换路由改变时保留缓存(否则输入了一半的input切换个标签回来就没了),但是现在仍存bug:即使标签关闭了,再打开缓存仍在,再慢慢解决;
  2. 侧边栏和标签栏之间的联动逻辑较为复杂(对于工作之余刚自学了vue不到一个月的笔者来说),需要考虑的较为周全,包括:新增删除、点击切换、激活标签效果、时时改变openedTab和activeTab等。

鸣谢

https://segmentfault.com/a/1190000012015667#articleHeader7
博主:wangduanduan

不少思路、方法、甚至一些name都参考了这篇文章,受益匪浅,十分感谢!

还请各位同行批评指正,不吝赐教。

  • 14
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 24
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值