【vue3后台项目】实现Navbar的功能

实现Navbar的功能

处理完了基本的Layout架构之后,接着我们实现一下navbar中的头像菜单功能;

这功能分为3个部分:

  • 获取并展示用户信息;
  • element-plus中的dropdown组件的使用;
  • 退出登录方案的实现;

获取并展示用户信息

获取并展示用户信息的步骤:

  • 定义接口请求方法
  • 定义调用接口的动作;
  • 在权限拦截时触发动作;

那么接下来我们根据这三个步骤,分别进行实现:

定义接口请求方法

在api/sys.js文件中新增方法:

api/sys.js 复制代码
// 获取用户信息
export const getUserInfo = () => {
  return request({
    url: '/project/getuser'
  })
}

因为获取用户信息需要对应的token,所以我们可以利用axios的请求拦截器对token进行统一注入,在utils/request.js中写入代码;

utils/request.js 复制代码
// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 在这个位置需要统一的注入token
    if (store.getter.token) {
      // 如果token存在,注入token
      config.headers.Authorization = `Bearer ${store.getters.token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

定义调用接口的动作

store/modules/user.js中增加action;

store/modules/user.js 复制代码
export default {
  namespaced: true,
  state: () => ({
    token: getItem(TOKEN) || '',
    userInfo: {}
  }),
  mutations: {
   
    setUserInfo(state, userInfo) {
      state.userInfo = userInfo
    }
  },
  actions: {
   
    /**
     * 获取用户信息
     */
    async getUserInfo(context) {
      const res = await getUserInfo()
      this.commit('user/setUserInfo', res)
      return res
    }
  }
}

在权限拦截时触发动作

在store/getters.js增加一个:

store/getters.js 复制代码
const getters = {
  token: (state) => state.user.token,
  /**
   *
   * @returns true 表示用户信息已存在
   */
  hasUserInfo: (state) => {
    return JSON.stringify(state.user.userInfo) !== '{}'
  }
}
export default getters
permissions.js 复制代码
/**
 * 处理前置守卫
 * to: 要到哪里去
 * from: 从哪里来
 * next: 是否要去
 */
router.beforeEach(async (to, from, next) => {
  // 1. 用户已登录,则不允许进入login
  // 2. 用户未登录,则不允许出login

  // 用token判断是否登录
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      // 判断用户资料是否存在,如果不存在,则获取用户信息
      if (!store.getters.hasUserInfo) {
        await store.dispatch('user/getUserInfo')
      }
      next()
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
  return false
})

渲染用户头像菜单

上节我们成功获取用户信息;接着可以根据数据渲染出用户头像内容;这里的头像菜单是使用element-plus中的dropdown组件和avatar;

在layout/components/navbar.js中实现:

首先完成头像展示的页面搭建;

layout/components/navbar.js 复制代码
<template>
  <div class="navbar">
    <div class="right-menu">
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <el-avatar
            shape="square"
            :size="40"
            :src="$store.getters.userInfo.avatar"
          ></el-avatar>
          <i class="el-icon-s-tools"></i>
        </div>
        <template #dropdown>
          <el-dropdown-menu>
            <router-link to="/"
              ><el-dropdown-item>主页</el-dropdown-item></router-link
            >

            <a target="__blank" href="#"
              ><el-dropdown-item>课程主页</el-dropdown-item></a
            >

            <router-link to="/"
              ><el-dropdown-item divided
                >退出登录</el-dropdown-item
              ></router-link
            >
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
  </div>
</template>

接着来添加样式;

layout/components/navbar.js 复制代码
<style lang="scss" scoped>
.navbar {
  height: 50px;
  position: relative;
  background-color: white;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.88);
  display: flex;
  justify-content: center;
  .left-menu {
    margin-right: auto;
  }
  .right-menu {
    display: flex;
    align-items: center;
    padding-right: 16px;

    ::v-deep .avatar-container {
      cursor: pointer;
      .avatar-wrapper {
        margin-top: 5px;
        position: relative;
        .el-avatar {
          /**去掉头像的背景色 */
          --el-avatar-background-color: none;
          margin-right: 12px;
        }
      }
    }
  }
}
</style>

退出登录方案实现

退出登录有两种:

  1. 用户主动退出:用户点击退出登录按钮后退出;
  2. 用户被动退出:token过期或被其他人顶下来时退出;

无论哪种方式退出,所需要执行的操作都是固定的:

  1. 清理当前用户缓存数据;
  2. 清理权限相关的配置;
  3. 返回到登录页面;

明确好对应的方案之后,接下来咱们先来实现用户主动退出的对应策略;

用户主动退出登录

store/modules/user.js中,添加对应的action;

store/modules/user.js 复制代码
    /**
     * 用户主动退出
     */
    logout() {
      this.commit('user/setToken', '')
      this.commit('user/setUserInfo', '')
      removeAll()
      // TODO:还需要清理权限相关
      // 返回到登录页
      router.push('/login')
    }
Navbar.vue 复制代码
const logout = () => {
  store.dispatch('user/logout')
}

用户被动退出

用户被动退出有两个场景:

  1. token失效;
  2. 单点登录:其他人登录该账号被"顶下来"

主动处理

我们知道token表示了一个用户的身份令牌,对服务端而言,它是只认令牌不认人的,所以说一旦其他人获取到了你的token,那么就可以伪装成你,来获取敏感数据; 为了保证用户的信息安全,对token而言就被指定了很多安全策略,比如:

  1. 动态token(可变token);
  2. 刷新token;
  3. 时效token
  4. ...

我们这里选择的是时效token;token本来就有时效,但是通常情况下,这个时效都是在服务端进行处理的,而此时我们要在服务端处理token时效的同时,在前端主动介入token时效的处理中。从而保证用户信息更加的安全;

那么我们如何实现呢,分为以下的步骤:

1.在用户登录时,记录当前的登录时间

2.制定一个失效时长

3.在接口调用时,根据当前时间 对比登录时间 ,看是否超过了失效时长

  • 如果未超过,则正常进行后续操作;
  • 如果超过,则进行退出登录操作;

创建utils/auth.js文件:

utils/auth.js 复制代码
import { TIME_STAMP, TOKEN_TIMEOUT_VALUE } from '@/constant'
import { setItem, getItem } from '@/utils/storage'

/**
 *获取时间戳
 */

export function getTimeStamp() {
  return getItem(TIME_STAMP)
}

/**
 * 设置时间戳
 */
export function setTimeStamp() {
  setItem(TIME_STAMP, Date.now())
}

/**
 * 对比是否超时
 */

export function checkTimeout() {
  // 获取当前时间
  const nowTime = Date.now()
  // 缓存时间
  const timeStamp = getTimeStamp()
  const res = nowTime - timeStamp > TOKEN_TIMEOUT_VALUE

  return res
}

在constant/index.js增加两个常量:

constant/index.js 复制代码
// token时间戳
export const TIME_STAMP = 'timeStamp'
// 超时时长
export const TOKEN_TIMEOUT_VALUE = 2 * 3600

然后就可以去用我们的时间戳方法了;

store/modules/user.js 复制代码
login方法中
// 保存登录时间
setTimeStamp()

在utils/request.js的请求拦截器中判断token是否过期;

utils/request.js 复制代码
// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 在这个位置需要统一的注入token
    if (store.getters && store.getters.token) {
      // 判断token是否过期
      if (checkTimeout()) {
        // 超时,执行退出操作
        store.dispatch('user/logout')
        return Promise.reject(new Error('token 失效了'))
      }
      // 如果token存在,注入token
      config.headers.Authorization = `Bearer ${store.getters.token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

被动处理

被动处理有两种业务场景:

  1. token过期;
  2. 单点登录;

这里的token过期是指服务端生成的token超过服务端指定的失效过程;

单点登录是指:

  • 当用户A登录之后,token过期之前;
  • 用户A的账户在其他设备上进行了二次登录,导致第一次登录的A账号被"顶"下线;
  • 即:同一账户仅可以在一个设备上保持在线状态

从背景中知道,以上的两种情况,都是服务端进行判断的 ,是服务端通知前端

对于其业务逻辑,将遵循以下逻辑:

  • 服务端返回数据时,会通过特定的状态码通知前端;
  • 当前端接收到特定的状态码时,表示遇到了特定状态: token失效单点登录
  • 此时进行退出登录处理;

这里允许多台设备登录同一账号 ,所以不会指定单点登录 状态码;仅有token失效 状态码。如果需要做单点登录时,只需要多加一个状态码判断;

在utils/request.js的响应拦截器增加:

utils/request.js 复制代码
service.interceptors.response.use(
  (err) => {
    // 处理token超时的问题
    if (err.response && err.response.data && err.response.data.code === 401) {
      // token超时
      store.dispatch('user/logout')
    }
    ElMessage.error(err)
    return Promise.reject(new Error(err))
  }
)
相关推荐
冰镇屎壳郎6 分钟前
前端安全 常见的攻击类型及防御措施
前端·安全·前端安全
2401_8576176212 分钟前
“无缝购物体验”:跨平台网上购物商城的设计与实现
java·开发语言·前端·安全·架构·php
2401_8574396921 分钟前
智慧社区电商系统:提升用户体验的界面设计
前端·javascript·php·ux
我是高手高手高高手31 分钟前
ThinkPHP8多应用配置及不同域名访问不同应用的配置
linux·服务器·前端·php
小李小李不讲道理33 分钟前
行动+思考 | 2024年度总结
前端·程序员·年终总结
苹果酱05671 小时前
Golang的文件解压技术研究与应用案例
java·vue.js·spring boot·mysql·课程设计
csdnLN1 小时前
$.ajax() 对应事件done() 、fail()、always() 的用法
前端·javascript·ajax
甜味橘阳1 小时前
echarts地图可视化展示
前端·javascript·echarts
bloxed2 小时前
前端文件下载多方式集合
前端·filedownload
余生H2 小时前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django