创建首页组件并配置路由
1、创建 src/views/home/index.vue
<template>
<div class="home-container">首页</div>
</template>
<script>
export default {
name: 'HomeIndex',
components: {},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
2、然后在路由表中加入
const routes = [
…………………………
{
path: '/',
name: 'home',
component: Home
}
]
- 在登录部分,加入登入成功后跳转到主页的语句
创建 Layout 组件与搭建
1、创建 src/views/layout/index.vue
<template>
<div class="layout-container">
<div>顶部导航栏</div>
<div>侧边导航栏</div>
<!-- 子路由出口 -->
<router-view />
</div>
</template>
<script>
export default {
name: 'LayoutIndex',
components: {},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
2、 由于顶部导航和侧边栏在每个界面都有展示(使用路由嵌套)
{
path: '/layout',
// name: 'layout',
// 由于有默认子路由,这里的name就没有了意义,因此去掉name
component: () => import('@/views/Layout'),
// 使用路由嵌套将组件放入layout的子路由下
children: [
{
path: '', // 空为默认子路由
name: 'home',
component: () => import('@/views/home')
}
]
}
- 使用 Container 布局容器 搭建页面结构
参考文档:Container 布局容器
<template>
<el-container class="layout-container">
<el-aside
class="aside"
width="200px"
>Aside</el-aside>
<el-container>
<el-header class="header">Header</el-header>
<el-main class="main">
<!-- 子路由出口 -->
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
name: 'LayoutIndex',
components: {},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.layout-container {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.aside {
background-color: #d3dce6;
}
.header {
background-color: #b3c0d1;
}
.main {
background-color: #e9eef3;
}
</style>
- 自定义侧边导航栏
参考文档:## NavMenu 导航菜单 (1) 由于aside组件代码较多,若放在layout里不方便阅读,这里封装到了layout/component/aside.vue
(2) 在layout.vue引入+加入组件数组,放到页面上
<template>
<el-container width="200px" class="layout-container">
<el-aside class="aside">
<app-aside class="aside-menu" />
</el-aside>
<el-container>
<el-header class="header">Header</el-header>
<el-main class="main">
<router-view />
<!-- 子路由出口 -->
</el-main>
</el-container>
</el-container>
</template>
<script>
import AppAside from './component/aside.vue'
export default {
name: 'LayoutIndex',
components: {
AppAside
},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.layout-container{
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.aside{
background-color: #d3dce6;
.aside-menu{
height: 100%;
// 撑满侧边栏
}
}
.header {
background-color: #b3c0d1;
}
.main {
background-color: #e9eef3;
}
</style>
(3)让侧边导航栏和路由对应 (Layout\component\aside.vue)
在菜单上加router,使index对应组件路径
<template>
<el-menu
default-active="/"
class="nav-menu"
background-color="#a1c4fd"
text-color="#fdfcfa"
active-text-color="#888BD6"
router
>
<!-- default-active="/" 默认首页被激活 -->
<!-- router 配合index跳转对应界面 -->
<el-menu-item index="/">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-menu-item index="/article" >
<i class="el-icon-menu"></i>
<span slot="title">内容管理</span>
</el-menu-item>
<el-menu-item index="/image">
<i class="iconfont icon-icon-log-manager"></i>
<span slot="title">素材管理</span>
</el-menu-item>
<el-menu-item index="/publish">
<i class="iconfont icon-publish"></i>
<span slot="title">发布文章</span>
</el-menu-item>
<el-menu-item index="/comment" >
<i class="iconfont icon-comments-fill"></i>
<span slot="title">评论管理</span>
</el-menu-item>
<el-menu-item index="/fans">
<i class="iconfont icon-mine_fans"></i>
<span slot="title">粉丝管理</span>
</el-menu-item>
<el-menu-item index="/setting">
<i class="iconfont icon-setting"></i>
<span slot="title">个人设置</span>
</el-menu-item>
</el-menu>
</template>
<script>
export default {
name: 'AppAside',
components: {},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.nav-menu {
span{
font-size: 18px;
}
i{
font-size: 20px;
}
.iconfont {
margin-right: 5px;
font-size: 25px;
}
.icon-publish,.icon-mine_fans{
font-size: 18px;
padding: 0 5px;
}
}
</style>
这里用到了阿里官方的iconfont
使用font-style
(1)public/index.html下引入,项目的iconfont地址
<link rel="stylesheet" href="//at.alicdn.com/t/font_XXX.css">
(2) 界面上直接加类名使用
<i class="iconfont icon-icon-log-manager"></i>
顶部导航栏
1、创建 src/views/layout/components/header.vue 组件
<template>
<div class="header-container">
<div>
<i class="el-icon-s-fold"></i>
<span>江苏传智播客科技教育有限公司</span>
</div>
<el-dropdown>
<div class="avatar-wrap">
<img class="avatar" src="http://toutiao.meiduo.site/FrvifflobfNNRM9V_ZBTI2ZaTH4n" alt="">
<span>用户昵称</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>设置</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: 'AppHeader',
components: {},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.header-container {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #ccc;
.avatar-wrap {
display: flex;
align-items: center;
.avatar {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 10px;
}
}
}
</style>
2、然后在 layout 中加载使用
3、 获取用户信息
(1)在 api/user.js 中封装请求方法
// 获取用户信息
export const getUserProfile = () => {
return request({
method: 'GET',
url: '/mp/v1_0/user/profile',
// 后端要求把需要授权的用户身份放到请求头中
// axios 可以通过 headers 选项设置请求头
headers: {
// 属性名和值都得看接口的要求
// 属性名:Authorization,接口要求的
// 属性值:Bearer空格token数据
Authorization: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTg5MDkxMjYsInVzZXJfaWQiOjEsInJlZnJlc2giOmZhbHNlLCJ2ZXJpZmllZCI6dHJ1ZX0.EdKErKDqMc3snkYxqt02jSa8t9G44002yWKY3CMOMJg'
}
})
}
(2)在 layout组件中请求获取数据
<template>
<el-container class="layout-container">
<el-aside class="aside" width="230px">
<app-aside class="aside-menu" />
</el-aside>
<el-container>
<el-header class="header">
<app-header :userProfile="user"></app-header>
</el-header>
<el-main class="main">
<router-view />
<!-- 子路由出口 -->
</el-main>
</el-container>
</el-container>
</template>
<script>
import AppAside from './component/aside.vue'
import AppHeader from './component/header.vue'
import { getUserProfile } from '@/api/user'
export default {
name: 'LayoutIndex',
components: {
AppAside,
AppHeader
},
props: {},
data () {
return {
user: {}
}
},
computed: {},
watch: {},
created () {
this.loadUserProfile()
},
mounted () {},
methods: {
loadUserProfile () {
getUserProfile().then(res => {
this.user = res.data.data
})
}
}
}
</script>
<style scoped lang="less">
.layout-container{
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.aside{
background-color: #d3dce6;
.aside-menu{
height: 100%;
// 撑满侧边栏
}
}
.header {
background-color: #e7f0fd;
}
.main {
background-color: #FFF;
}
</style>
(3)把请求到的数据绑定到模板中(header.vue)
<template>
<div class="header-container">
<div class="fold">
<i class="el-icon-s-fold"></i>
<span>头条发布管理系统</span>
</div>
<el-dropdown>
<div class="avatar-wrap">
<img class="avatar" :src="userProfile.photo" alt="">
<span>{{userProfile.name}}</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>设置</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: 'AppHeader',
components: {},
props: {
userProfile: {
type: Object,
default () {
return {}
}
}
},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.el-dropdown-link {
cursor: pointer;
}
.header-container{
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.fold i{
margin-right: 5px;
}
.avatar-wrap {
display: flex;
align-items: center;
.avatar {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 10px;
}
}
}
</style>
效果展示
token 处理
- 利用本地存储,将token从后端获取
- 在user.js接收本地存储的数据
改进
由于很多功能都需要使用token,这样的书写不方便,这里可以使用axios的拦截器解决
- 从axios的官方文档找到请求拦截器
- 清楚对应代码含义
(1) 由于我们创建了axios实例,这里要将axios改成我们的实例名
request.interceptors.request.use
(2) config 是后端传输过来的当前请求的配置对象信息
- 修改config的参数(实际上config如果return失败就会进行拦截,起到我们想要的效果)
(1) 获取本地存储数据
(2)做条件判断,如果有数据就传给config发出请求
// 请求拦截器
request.interceptors.request.use(
function (config) {
// Do something before request is sent
const user = JSON.parse(window.localStorage.getItem('user'))
if (user) {
config.headers.Authorization = `Bearer ${user.token}`
}
return config
},
function (error) {
// Do something with request error
return Promise.reject(error)
}
)
侧边栏折叠(兄弟组件通信)
- 在全局建立bus
// 通过事件总线bus传值
Vue.prototype.$bus = new Vue()
- header(有控制的组件),所以由他传递值
(1)通过点击事件绑定(这里的类名通过isCollapse确定,以切换图标)
(2)在methods写方法进行传值
// $emit用来传值,第一个参数是传值的方法名,第二个参数是要传的参数
methods: {
collapse () {
this.isCollapse = !this.isCollapse
this.$bus.$emit('collapse', this.isCollapse)
}
}
- 兄弟组件接收
补充:collapse是官方定义的不能改
(1)通过collapse接收状态
(2)接收传过来的值
效果图
使用路由守卫进行登录状态的判断与界面拦截
vue 官方提供了路由守卫,用于进行登录判断,登录后可跳转到首页,否则不进行跳转
我这里直接用了官方文档提供的方法,是否授权用的是本地存储数据进行判断
在非登录界面同时没有user后端数据(token)就跳转到登录页,否则就跳转到其他界面
router.beforeEach((to, from, next) => {
const user = JSON.parse(window.localStorage.getItem('user'))
if (to.name !== 'Login' && !user) next({ name: 'Login' })
else next()
})
用户退出
1、给退出按钮注册点击事件
注意:并不是所有的组件在注册事件的时候需要使用 .native 修饰符,例如 el-button 组件注册点击事件就不需要,这主要是因为该组件内部处理了。
什么时候使用 .native?首先肯定是在组件上注册事件可能会用到,如果普通方式注册不上,这个时候加 .native 修饰符。
例如你给一个组件注册一个 input 事件,如果直接 @input 注册无效,那就试一下 @input.native。
2、处理函数如下
logOut () {
this.$confirm('确认退出吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 去除用户登录状态
window.localStorage.removeItem('user')
// 跳转到登录界面
this.$router.push('/Login')
}).catch(() => {
this.$message({
type: 'info',
message: '已取消退出'
})
})
}
结合导航守卫实现页面切换顶部进度条
- 路由前置钩子
- 路由后置钩子
1、安装 nprogress
# yarn add nprogress
npm i nprogress
注意:项目中不要乱用包管理工具,要从一而终,不要一会儿这个,一会儿那个的。否则的话会导致一些包被莫名删除。
提示:如果想要从一个包管理工具切换到另一个包管理工具:
1、手动删除 node_modules
2、执行 npm install 或者 yarn install 或者 cnpm install 把所有依赖项重新安装一遍
3、之后固定使用 npm、yarn、cnpm 来装包
注意:cnpm 就不建议使用了。
2、在 main.js 中引入 nprogress.css 样式文件
// 加载 nprogress 中的指定的样式文件
// 注意:加载第三方包中的具体文件不需要写具体路径,直接写包名即可
// 总结就是:"包名/具体文件路径"
import "nprogress/nprogress.css"
3、在路由的全局前置守卫中,开启进度条
...
+ import NProgress from 'nprogress'
router.beforeEach((to, from, next) => {
// 开启顶部导航进度条特效
+ NProgress.start()
……………………
……………………
})
4、在路由的全局后置钩子中,关闭进度条特效
router.afterEach((to, from) => {
// 结束顶部的导航进度条
NProgress.done();
});
最后,回到浏览器中测试访问。