vue+elementui 侧边栏和面包屑联动

这篇博客详细记录了一个前端项目的布局搭建过程,包括主页的Container布局、面包屑联动和侧边栏根据角色权限渲染。同时介绍了登录页的实现,以及如何利用路由前置导航守卫实现登录拦截,确保只有登录用户才能访问主页面及其子页面。文章还涵盖了路由配置、面包屑和侧边栏的联动逻辑以及登录状态的处理。
摘要由CSDN通过智能技术生成

记录一下框架布局搭建过程,布局有头部,侧边栏(按角色权限显示)和面包屑联动 

一.主页

设置主页为根路径,路由在主页面的面包屑下面那块区域跳转显示子页面,主页布局使用Container 布局容器

 因为路由要在主页的main区域跳转代码,所以设置页面路由为main的子路由

const routes = [

  {
    path: '/',
    name: 'mian',
    component: main,
    meta: {
      isLogin: true
    },

    children: [{
        path: 'about',
        name: 'about',
        component: about,
        meta: {
          title: '巡护一张图',
          icon: "el-icon-message"
        }
      },
      // 人员设备
      {
        path: 'rylb',
        name: 'rylb',
        component: rylb,
        meta: {
          title: '人员列表',
          dir: {
            title: '人员设备',
            icon: 'el-icon-postcard'
          }
        }
      },
      {
        path: 'rygl',
        name: 'rygl',
        component: rygl,
        meta: {
          title: '设备管理',
          dir: {
            title: '人员设备',
            icon: 'el-icon-postcard'
          }
        }
      }
    ]
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('../views/login/login.vue'),
  }
]

mai.vue主页头部布局代码

<el-header height="64px">
      <div class="title">管理系统</div>
      <div class="user flex">
        <i class="el-icon-user"></i>
        <el-dropdown
          placement="bottom"
          trigger="click"
          @command="dropDownClick"
        >
          <span class="el-dropdown-link"
            >用户名 <i class="el-icon-arrow-down"></i
          ></span>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item command="密码修改">密码修改</el-dropdown-item>
            <el-dropdown-item command="退出登录" @click="logout()"
              >退出登录</el-dropdown-item
            >
          </el-dropdown-menu>
        </el-dropdown>
      </div>
    </el-header>

侧边栏要根据登录的角色权限渲染菜单,现在没有接口,模拟了一下当前用户的权限路由信息,data里的list数组表示当前用户拥有的路由权限

data() {
    return {
      // 面包屑的路由列表
      activedNavList: [],
      // 菜单信息
      navList: [],
      // 路由name
      list: [
        "about",
        "rylb",
        "rygl",
      ],
    };
  },

在main.vue的created里拿到所有的路由信息,筛选出和当前用户拥有的路由的name一样的路由.根据路由meta中是否有dir这个属性,来区分当前路由是单独成一个菜单项,还是某个菜单项的子菜单.把改造好的路由信息push进this.navList数组

 created() {
    console.log(this.$router.options.routes[0].children); //mian的所有子路由
    //chilrenArr是 拿到所有符合条件的main子路由的信息
    let chilrenArr = this.$router.options.routes[0].children.filter((ele) =>
      this.list.find((item) => item === ele.name)
    );
    console.log(chilrenArr);
    chilrenArr.forEach((ele) => {
      const dir = ele.meta.dir;
      // 有dir表示当前路由是属于某一个菜单项
      if (dir) {
        let navItem = this.navList.find((item) => item.text === dir.title);
        if (navItem) {
          navItem.children.push({
            url: ele.path,
            text: ele.meta.title,
            name: ele.name,
          });
          ele.name === this.$route.name && (navItem.isOpen = true);
        } else {
          this.navList.push({
            text: dir.title,
            icon: dir.icon,
            isOpen: ele.name === this.$route.name,
            children: [
              {
                url: ele.path,
                text: ele.meta.title,
                name: ele.name,
              },
            ],
          });
        }
        // 没有dir,说明自己是一个菜单项
      } else {
        this.navList.push({
          url: ele.path,
          text: ele.meta.title,
          icon: ele.meta.icon,
          name: ele.name,
        });
      }
    });
    console.log(this.navList);
    // 把当前路由存进数组,不要存main路由
    if (this.$route.name == "main") {
      return false;
    } else {
      this.activedNavList.push({
        name: this.$route.name,
        text: this.$route.meta.title,
      });
    }

    console.log(this.activedNavList);
  },

渲染侧边栏菜单 

 <el-aside width="12vw">
        <template v-for="item in navList">
          <div class="dir" :key="item.text" v-if="item.children">
            <div class="nav-title" @click="changeNavItem(item)">
              <div class="icon-box">
                <i class="icon1 iconfont" :class="item.icon"></i>
              </div>
              <p class="text">{{ item.text }}</p>
              <i
                class="icon2 el-icon-arrow-down"
                :class="{ 'icon2-active': item.isOpen }"
              ></i>
            </div>
            <el-collapse-transition>
              <ul v-show="item.isOpen">
                <router-link
                  v-for="ele in item.children"
                  :key="ele.text"
                  :to="ele.url"
                  replace
                  #default="{ isActive, navigate }"
                  custom
                >
                  <li
                    class="nav-item"
                    :class="{ 'nav-active': isActive }"
                    @click="navigate"
                  >
                    {{ ele.text }}
                  </li>
                </router-link>
              </ul>
            </el-collapse-transition>
          </div>
          <!-- 路由跳转 -->
          <router-link
            :key="item.text"
            v-else
            :to="item.url"
            replace
            #default="{ isActive, navigate }"
            custom
          >
            <div
              class="nav-title"
              :class="{ 'nav-active': isActive }"
              @click="navigate"
            >
              <div class="icon-box">
                <i class="icon1 iconfont" :class="item.icon"></i>
              </div>
              <p class="text">{{ item.text }}</p>
            </div>
          </router-link>
        </template>
      </el-aside>

让侧边栏有子菜单的项保持只展开当前项,别的菜单项收起

changeNavItem(item) {
      if (item.isOpen) {
        item.isOpen = false;
        return false;
      }
      this.navList.forEach((d) => d.children && (d.isOpen = false));
      item.isOpen = true;
    },

面包屑的跳转和展示

 <ul class="head-nav-box">
          <!-- #default="{ isActive, navigate }" 是插槽v-slot:default= --> 
          <!-- navigate:触发导航的函数。
               isActive:是否匹配。 -->
          <router-link
            v-for="item in activedNavList"
            :key="item.name"
            :to="item.name"
            replace
            #default="{ isActive, navigate }"
            custom
          >
            <li class="item" :class="{ active: isActive }" @click="navigate">
              <span class="text">{{ item.text }}</span>
              <i
                class="icon el-icon-close"
                @click.stop="closeCurrentPage(item.name)"
              ></i>
            </li>
          </router-link>
        </ul>

实现侧边栏和面包屑联动的关键在于 #default="{ isActive, navigate }",跳转路由的相应菜单项和面包屑项都会呈高亮状态

在created里面就先把当前页push进去,在watch里监听当前路由的变化

 watch: {
    // 监听路由名称
    "$route.name"() {
      const name = this.$route.name;
      const text = this.$route.meta.title; //中文名称
      // 如果当前路由不在面包屑列表中,把当前路由push进去,控制列表长度为10,若超出,删掉第一个元素
      if (!this.activedNavList.find((d) => d.name === name)) {
        this.activedNavList.length === 10 && this.activedNavList.shift();
        this.activedNavList.push({ name, text });
        // return false
      }
      for (let i = 0, len = this.navList.length; i < len; i++) {
        const navItem = this.navList[i]; //侧边栏菜单
        const childrenList = navItem.children;
        // 面包屑与侧边栏联动,当前路由的侧边栏展开,其他菜单收起

        // 如果是单项菜单,其他菜单都收起
        if (navItem.name == name) {
          this.navList.forEach((d) => d.children && (d.isOpen = false));
        }
        // 如果是有子菜单的菜单项,则别的菜单项收起,当前菜单项展开
        if (childrenList && childrenList.find((d) => d.name === name)) {
          if (!navItem.isOpen) {
            this.navList.forEach((d) => d.children && (d.isOpen = false));
            navItem.isOpen = true;
          }
          break;
        }
      }
    },
  },

关闭面包屑触发的方法

// 关闭面包屑
    closeCurrentPage(name) {
      // 如果只有一个界面,不关
      const len = this.activedNavList.length;
      if (len === 1) return false;
      // 把当前路由删掉,跳到面包屑最后一个页面
      const index = this.activedNavList.findIndex((d) => d.name === name);
      this.activedNavList.splice(index, 1);
      name === this.$route.name &&
        this.$router.replace(
          this.activedNavList[len - 2].name.toLocaleLowerCase()
        );
    },

用kee-alive使被包含的组件保留状态,避免重新渲染 

 <el-main>
        <!-- 面包屑 -->

        <keep-alive :include="activedComponents"></keep-alive>
        <router-view></router-view>
</el-main>
computed: {
    activedComponents() {
      return this.activedNavList.map((d) => d.name);
    },
  },

二,登录页

登录页的布局样式,因为没有接口,所以登录的时候手动本地存储设置token,用户名和密码

<template>
  <div class="login">
    <el-form ref="form" :model="loginFrom" :rules="rule" class="loginFrom">
      <el-form-item prop="username">
        <el-input
          v-model="loginFrom.username"
          placeholder="请输入账号"
          class="user"
        >
          <i slot="prefix" class="el-input__icon el-icon-user pt4"></i>
        </el-input>
      </el-form-item>
      <el-form-item prop="password" class="pwd">
        <el-input
          v-model="loginFrom.password"
          placeholder="请输入密码"
          class="mb8 ft20"
        >
          <i slot="prefix" class="el-input__icon el-icon-lock pt4"></i>
        </el-input>
      </el-form-item>
      <el-form-item class="btn">
        <el-button type="primary" class="submit-btn mt48" @click="login1"
          >登录</el-button
        >
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loginFrom: {
        username: "",
        password: "",
      },
      rule: {
        username: [{ required: true, message: "请输入账号", trigger: "blur" }],
        password: [{ required: true, message: "请输入密码", trigger: "blur" }],
      },
    };
  },
  methods: {
    login1() {
      // 存token,user和psd,跳转页面
      localStorage.setItem("token", "123");
      localStorage.setItem("useName", this.loginFrom.username);
      localStorage.setItem("password", this.loginFrom.password);
      this.$router.push("/about");
    },
  },
};
</script>

<style scoped lang='less'>
.login {
  width: 100%;
  height: 100%;
  background-color: rgb(155, 186, 187);
  display: flex;
  align-items: center;
  justify-content: center;
  .loginFrom {
    padding: 40px;
    width: 400px;
    height: 400px;
    background-color: #fff;
    .el-form-item {
      margin: 40px 0;
    }
    .btn {
      margin-top: 100px;
    }
  }
}
</style>

三.导航拦截

系统需要登录才能进入主页面,但是我们设置的根路径为主页面,项目一启动就会调到主页面.再有为了防止没有token从地址栏直接输入路径进入系统的情况,需要路由前置导航守卫进行拦截.

router.beforeEach((to, from, next) => {
  let token = localStorage.getItem('token')
  console.log(token)
  if (token && (to.path !== '/login') && (to.path !== '/')) {
    // 有token 但不是去 login和主页面 ,通过
    next()
  } else if (!token && to.path !== '/login') {
    // 没有token 但不是去 login页面 不通过(未登录不给进入)
    next('/login')
  } else if(token && to.path === '/'){
    // 有token,去主页,直接跳转about页面
    next('/about')
  }
  else {
    // 剩下最后一种 没有token 但是去 login页面 通过
    next()
  }
})

这样项目启动就会先进入登录页了.在没有token的情况下,在地址栏直接输入菜单子路由,也会被拦截到登录界面.

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值