记录一下框架布局搭建过程,布局有头部,侧边栏(按角色权限显示)和面包屑联动?
一.主页
设置主页为根路径,路由在主页面的面包屑下面那块区域跳转显示子页面,主页布局使用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的情况下,在地址栏直接输入菜单子路由,也会被拦截到登录界面.
|