Layout 处理

252 阅读4分钟

创建首页组件并配置路由

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
  }
]
  1. 在登录部分,加入登入成功后跳转到主页的语句

image.png

创建 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')
      }
    ]
  }
  1. 使用 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>
  1. 自定义侧边导航栏

参考文档:## NavMenu 导航菜单 (1) 由于aside组件代码较多,若放在layout里不方便阅读,这里封装到了layout/component/aside.vue

image.png (2) 在layout.vue引入+加入组件数组,放到页面上

image.png

image.png

<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 中加载使用

image.png 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>

效果展示

image.png

token 处理

  1. 利用本地存储,将token从后端获取

image.png

  1. 在user.js接收本地存储的数据

image.png

改进

由于很多功能都需要使用token,这样的书写不方便,这里可以使用axios的拦截器解决

  1. 从axios的官方文档找到请求拦截器

axios文档地址 image.png

  1. 清楚对应代码含义

(1) 由于我们创建了axios实例,这里要将axios改成我们的实例名

request.interceptors.request.use

(2) config 是后端传输过来的当前请求的配置对象信息

image.png

  1. 修改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)
  }
)

侧边栏折叠(兄弟组件通信)

  1. 在全局建立bus
// 通过事件总线bus传值
Vue.prototype.$bus = new Vue()
  1. header(有控制的组件),所以由他传递值

(1)通过点击事件绑定(这里的类名通过isCollapse确定,以切换图标)

image.png

(2)在methods写方法进行传值

    // $emit用来传值,第一个参数是传值的方法名,第二个参数是要传的参数
      methods: {
        collapse () {
          this.isCollapse = !this.isCollapse
          this.$bus.$emit('collapse', this.isCollapse)
        }
  }
  1. 兄弟组件接收
补充:collapse是官方定义的不能改

(1)通过collapse接收状态

image.png (2)接收传过来的值

image.png

效果图

image.png

使用路由守卫进行登录状态的判断与界面拦截

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、给退出按钮注册点击事件

image.png

注意:并不是所有的组件在注册事件的时候需要使用 .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();
});

最后,回到浏览器中测试访问。