一、路由配置
1.1 在 src\components 目录下新建布局组件文件 Loyout.vue
基础布局都放到 components 下面.
页面部分:
<template>
<div>
<!-- 头部区域 -->
<div class="header">
<app-header />
</div>
<!-- 左侧导航 -->
<div class="navbar">
<app-navbar />
</div>
<!-- 右侧区域 -->
<div class="main">
<app-main />
</div>
</div>
</template>
2.2 在 src\router.js 中增加路由配置,如下:
import Layout from '../components/Layout.vue'
{
path: '/',
name: 'Layout',
component: Layout,
}
二、布局设计
2.1 在 src\components\Layout.vue 文件中定义布局样式
样式部分:
<style scoped>
/* 头部 */
.header {
position: absolute;
line-height: 50px;
padding: 0px;
top: 0px;
left: 0px;
right: 0px;;
background-color: blue;
}
/* 左侧导航 */
.navbar {
position: absolute;
width: 230px;
top: 50px; /* 把上边header的部分让出来 */
left: 0px;
bottom: 0px;
overflow-y: auto; /* 纵坐标滚动条 */
background-color: red;
}
/* 右侧主体区域 */
.main {
position: absolute;
top: 50px;
left: 230px; /* 把左边navbar的部分让出来 */
right: 0px;
bottom: 0px;
overflow-y: auto;
background-color: blueviolet;
}
</style>
2.2在 src\components 目录下新建3个子目录存放子组件: AppHeader 、 AppNavbar 、 AppMain
并分别在其中创建index.vue
2.3 在 Layout.vue 中使用 components 选项将3个组件作为子组件引入
<script>
import AppHeader from './AppHeader';
import AppMain from './AppMain';
import AppNavbar from './AppNavbar';
export default {
data() {
return {
}
},
components: {
AppHeader,
AppMain,
AppNavbar,
},
methods: {},
}
</script>
布局实现:
三、头部区域实现
2.1 下拉菜单
下拉菜单参考:https://element.eleme.cn/#/zh-CN/component/dropdown#zhi-ling-shi-jian
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
下拉菜单<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="a">修改密码</el-dropdown-item>
<el-dropdown-item command="b">退出系统</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
methods: {
handleCommand(command) {
this.$message('click on item ' + command);
}
}
.el-dropdown-link {
cursor: pointer;
color: #409eff;
}
.el-icon-arrow-down {
font-size: 12px;
cursor: pointer;
}
.el-dropdown {
float: right;
margin-right: 40px;
}
四、左侧导航实现
4.1导航组件实现
<template>
<div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="1">
<i class="el-icon-menu"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<span slot="title">教师管理</span>
</el-menu-item>
<el-menu-item index="3">
<i class="el-icon-menu"></i>
<span slot="title">成员管理</span>
</el-menu-item>
</el-menu>
</div>
</template>
4.2 解决白边
引用element导航默认会出现白边
所以在导航样式部分,取消掉:
<style scoped>
.el-menu{/* element-ui 里面的标签相当于类名,不能用标签选择器 */
border-right: none;
}
</style>
4.3 路由模式
开启element自带的vue-router路由模式:
<el-menu
:router='true'
default-active="2"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
如果写成 router= 'true' ,默认为字符串,当想给某一个参数传js表达式的时候,需要v-model绑定,才能将后面的内容识别为js表达式.
开启路由,添加路径:
<el-menu-item index="/home">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-menu-item index="/teacher">
<i class="el-icon-s-custom"></i>
<span slot="title">教师管理</span>
</el-menu-item>
<el-menu-item index="/student">
<i class="el-icon-s-check"></i>
<span slot="title">成员管理</span>
</el-menu-item>
4.4 添加子组件并更新路由配置
路由中引入相应内容:
import Home from '../views/home';
import Teacher from '../views/teacher';
import Student from '../views/student';
在Layout组件中添加子组件Home,Teacher,Student
{
path: '/',
name: 'Layout',
component: Layout,
children: [{
path: '/home',
component: Home,
meta: { title: 'ROOT' }
}, {
path: '/teacher',
component: Teacher,
meta: { title: 'Teacher' },
}, {
path: '/student',
component: Student,
meta: { title: 'Student' },
}]
}
另一种添加子组件的写法:
先加载父级组件Layout,然后再加载子组件Teacher
{
path: '/teacher',
component: Layout,
children: [{
path: '/',// '/'可以忽略掉
component: Teacher,
title: { title: 'Teacher' }
}]
}
将router的出口写到main中:
<template>
<div>
<router-view>
<!-- 渲染子组件的出口 -->
</router-view>
</div>
</template>
因为在其它页面刷新时,会默认回到/home中,所以需要获取当前路径
:default-active="$route.path"
default-active 值当前激活菜单的index. 后添加js表达式,需要v-model双向绑定.
五、右侧主区域实现
5.1 主区域中除了 首页没有 横向指示导航,其他模块中都有,所以先渲染出面包屑
参考 Breadcrumb 面包屑:Element - The world's most popular Vue UI framework
在component中创建AppLink文件夹,创建AppLink公共组件.并在AppMain中引入.
<template>
<div>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item
class="line"
:to="{ path: $route.path }"
>{{$route.meta.title}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
import AppLink from '../AppLink';//声明变量的时候,不要使用Link,默认存在Link,使用会报错
组件导出:
<app-link></app-link>
添加样式:
<style scoped>
.el-breadcrumb {
height: 10px;
padding: 20px;
border-radius: 5px;
box-shadow: 5px 5px 2px 0 #888888;
background-color: #ffffff;
cursor: pointer;
}
.line {
border-left: 3px solid rgb(81, 211, 161);
padding-left: 10px;
}
</style>
效果:
5.2 在路由中添加重定向,避免组件没有内容显示
访问根目录的时候,跳转到/home上
redirect:'/home',
在主页中,隐藏组件:
<app-link v-show="$route.path !=='/home'"></app-link>
<!-- 路径不等于/home的时候,显示组件 -->
使用v-show而不使用v-if来隐藏组件的原因:
v-if 组件不生成,v-show控制css中的display来隐藏,因为会频繁切换路径,使用v-show更好一点,在组件加载的时候就加载出来,通过样式决定显示不显示.这样不用于v-if,点击的时候加载局部,重新生成一遍,可能造成资源加载过慢.
六、退出系统
6.1 定义退出方法 logout
在api文件夹下创建logout.js,内容如下:
import request from '@/utils/request';
//退出登录
export function logout(token) {
return request({
method: 'post',
url: '/user/logout',
data: {
token
}
})
}
6.2 重写handleCommand方法
在AppHeader中的index.vue中重写:
handleCommand(command) {
// this.$message('click on item ' + command);
switch (command) {
case 'a'://修改密码
break;
case 'b'://退出登录
logout(localStorage.getItem('stu-token')).then(response => {
const resp = response.data;
if (resp.flag) {//如果成功返回状态为true,就清除stu-token
localStorage.removeItem('stu-token');//清除token
localStorage.removeItem('stu-user');//清除用户信息
this.$router.push('/login');
//都清除之后,跳转到登录界面,实现退出登录功能
this.$message({
message: resp.message,
type: 'success'
})
} else {
this.$message({
message: resp.message,
type: 'error',
});
this.$router.push('login');
}
})
break;
dafault: break;
}
}
6.3 配置服务端router.js
router.post('/user/logout', function (req, res) {
var body = req.body;
User.findOne({//查询前端传过来的token是否存在
token: body.token
}, function (err, data) {
if (err) {
return res.status(500).json({
code: 3000,
flag: false,
message: 'server error'
})
}
if (!data) {
return res.status(200).json({
code: 4000,
flag: false,
message: '账号已注销或已过期'
})
}
return res.status(200).json({
code: 2000,
flag: true,
message: '退出系统成功'
})
})
})
成功退出:
6.4 获取nickname
修改下拉菜单名称,改为nickname:
<span class="el-dropdown-link">
{{user.nickname}}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
在data中直接获取:
data() {
return {
user: JSON.parse(localStorage.getItem('stu-user'))
//获取本身为字符串,通过.parse()将字符串转换为对象.这样上面才能获取到nickname
}
},
七、权限校验
当用户未登录时,不让访问非登录页面,应该回到登录页面进行登录
7.1采用 Vue Router 中的路由前置钩子函数( beforeEach ),在前置中根据路由地址校验是否此路由是否允许访
新建 src\permission.js 权限校验文件,代码如下:
import router from './router/index.js'
import { getUserInfo } from './api/login.js'
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('stu-token');
if (!token) {//没拿到token,证明没有登录
if (to.path === '/login' || to.path === '/register') {
//如果当前路径在登录或者注册界面,只需要调用next方法来结束这个钩子函数
next();
} else {
next({ path: '/login' })//其它路径就跳转到 /login
}
} else {
if (to.path === '/login') {
next();
} else if (to.path === '/register') {
next();
} else {
getUserInfo(token).then(response => {
const resp = response.data;
if (resp.flag) {//为真 代表token去数据库里查,查到了没有问题.
localStorage.setItem('stu-user', JSON.stringify(resp.data))
//在localStorage中存的时候,需要将对象装换为字符串
//存人物信息,确保人物信息是最新的
next();
} else {//没查到信息,说明token有问题,就转到登录界面
next({ path='/login' })
}
})
}
}
})
7.2 将权限添加到全局中
在main.js中引入权限校验文件:
import './premission.js';
7.3 测试
1.清空浏览器保存的用户数据和token
2.在未登录情况下访问 http://localhost:8888/home ,会回到 登录
3.只有登录后,才可以访问首页