美文网首页
Vue3写一个后台管理系统(3)通用后台登录方案解析

Vue3写一个后台管理系统(3)通用后台登录方案解析

作者: 程序员三千_ | 来源:发表于2023-03-11 19:24 被阅读0次
    viewslogin 文件夹,创建 index.vue 文件,书写登录界面,

    都是一些基本的UI操作,我下边直接粘贴出代码,

    <template>
      <div class="login-container">
        <el-form ref="loginFromRef" class="login-form" :model="loginForm" :rules="loginRules">
          <div class="title-container">
            <h3 class="title">后台管理系统</h3>
          </div>
    
          <el-form-item prop="username">
            <span class="icon-container">
              <i class="el-icon-user"></i>
            </span>
            <el-input placeholder="请输入用户名" name="username" type="text" v-model="loginForm.username" />
          </el-form-item>
    
          <el-form-item prop="password">
            <span class="icon-container">
              <i class="el-icon-lock"></i>
            </span>
            <el-input placeholder="请输入密码" name="password" :type="passwordType" v-model="loginForm.password" />
            <span class="show-pwd">
              <svg-icon
                :icon="passwordType === 'password' ? 'eye' : 'eye-open'"
                @click="onChangePwdType"
              />
    
            </span>
          </el-form-item>
    
          <el-form-item class="code-box">
           <span class="icon-container">
    
             <i class="el-icon-tickets"></i>
    
            </span>
            <el-input
              placeholder="图形验证码"
              v-model="loginForm.captcha_code" class="code-input" maxlength="4">
            </el-input>
            <div class="code-img" @click="getCodeImg">{{loginForm.captcha_code}}</div>
          </el-form-item>
    
    
          <el-button type="primary" style="width: 100%; margin-bottom: 30px;" :loading="loading"
            @click="handleLogin">登录</el-button>
        </el-form>
      </div>
    </template>
    
    <script setup>
    import {} from 'vue'
    </script>
    
    <style lang="scss" scoped></style>
    
    router/index.js 中增加以下路由配置
    /**
     * 公开路由表
     */
    const publicRoutes = [
      {
        path: '/login',
        component: () => import('@/views/login/index')
      }
    ]
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes: publicRoutes
    })
    

    其中的一些icon图标用的是自己封装的SvgIcon组件(包含element-plus 的图标和自定义的 svg 图标),我们这节主要讲的是后台系统整个登录的方案实现,一些设计的简单UI及其UI组件,相信只要有过 element-ui 使用经验的同学,应该对这里都不陌生,所以这里就不对这块内容进行过多赘述了,如果有想要代码的小伙伴,下方留言,我可以私发你具体的代码。

    整个登录界面的UI搭建完成后,效果就是这样的
    image.png

    处理完了基本UI表单操作之后,接下来就是登录操作的实现了。
    对于登录操作在后台项目中是一个通用的解决方案,具体可以分为以下几点:

    1. 封装 axios 模块
    2. 封装 接口请求 模块
    3. 封装登录请求动作
    4. 保存服务端返回的 token
    5. 登录鉴权

    这些内容就共同的组成了一套 后台登录解决方案 。接下来,我们就分别来去处理介绍这些内容。

    封装 axios 模块

    在当前这个场景下,我们希望封装出来的 axios 模块,至少需要具备一种能力,那就是:根据当前模式的不同,设定不同的 BaseUrl ,因为通常情况下企业级项目在 开发状态生产状态 下它的 baseUrl 是不同的。

    对于 @vue/cli 来说,它具备三种不同的模式:

    1. development
    2. test
    3. production

    根据我们前面所提到的 开发状态和生产状态 那么此时我们的 axios 必须要满足:在 开发 || 生产 状态下,可以设定不同 BaseUrl 的能力

    那么想要解决这个问题,就必须要使用到 @vue/cli 所提供的 环境变量 来去进行实现。

    我们可以在项目中创建两个文件:

    1. .env.development

    2. .env.production

    它们分别对应 开发状态生产状态

    我们可以在上面两个文件中分别写入以下代码:
    .env.development

    # 标志
    ENV = 'development'
    
    # base api
    VUE_APP_BASE_API = '/api'
    

    .env.production

    # 标志
    ENV = 'production'
    
    # base api
    VUE_APP_BASE_API = '/prod-api'
    

    有了这两个文件之后,我们就可以创建对应的 axios 模块

    创建 utils/request.js ,写入如下代码:

    import axios from 'axios'
    
    const service = axios.create({
      baseURL: process.env.VUE_APP_BASE_API,
      timeout: 5000
    })
    
    export default service
    
    封装请求动作

    创建 api 文件夹,创建 sys.js

    import request from '@/utils/request'
    
    /**
     * 登录
     */
    export const login = data => {
      return request({
        url: '/sys/login',
        method: 'POST',
        data
      })
    }
    

    封装登录请求动作:

    该动作我们期望把它封装到 vuexaction

    store 下创建 modules 文件夹,创建 user.js 模块,用于处理所有和 用户相关 的内容:

    import { login } from '@/api/sys'
    export default {
      namespaced: true,
      state: () => ({}),
      mutations: {},
      actions: {
        login(context, userInfo) {
          const { username, password } = userInfo
          return new Promise((resolve, reject) => {
            login({
              username,
              password: password  
            })
              .then(data => {
                resolve()
              })
              .catch(err => {
                reject(err)
              })
          })
        }
      }
    }
    

    store/index 中完成注册:

    import { createStore } from 'vuex'
    import user from './modules/user.js'
    export default createStore({
      modules: {
        user
      }
    })
    
    登录触发动作
    
    <script setup>
    import { ref, onMounted } from 'vue'
    import { validatePassword } from './rules'
    import { getCode } from '@/api/user'
    import { useStore } from 'vuex'
    import { useRouter } from 'vue-router'
    
    
    
    onMounted(() => {
      getCodeImg()
    })
    
    //数据源
    const loginForm = ref({
       username: 'test',
      password: '123456',
      captcha_code: '',
      code_key: ''
    })
    //验证规则
    const loginRules = ref({
      username: [
        {
          required: true,
          trigger: 'blur',
          message: '请输入用户名'
        },
    
      ],
      password: [
        {
          required: true,
          trigger: 'blur',
          validator: validatePassword()
        },
    
      ],
    })
    
    // 处理密码框文本显示状态
    const passwordType = ref('password')
    const onChangePwdType = () => {
      if (passwordType.value === 'password') {
        passwordType.value = 'text'
      } else {
        passwordType.value = 'password'
      }
    }
    
    // 登录动作处理
    const loading = ref(false)
    const loginFromRef = ref(null)
    const store = useStore()
    const router = useRouter()
    
    /**
     * 登录
     */
    const handleLogin = () => {
      loginFromRef.value.validate(valid => {
        if (!valid) return
        loading.value = true
        store.dispatch('user/login', loginForm.value)
          .then(() => {
            loading.value = false
            // TODO: 登录后操作
            router.push('/')
    
          })
          .catch(err => {
            getCodeImg()
            loading.value = false
          })
    
      })
    
    
    }
    /**
     * 获取图形验证码
     */
    const getCodeImg = () => {
      getCode({})
        .then(data => {
          let obj = data.bizobj
    
          loginForm.value.code_key = obj.code_key
          loginForm.value.captcha_code = obj.code
        })
        .catch(err => {
        })
    }
    </script>
    

    当然这时候你如果,去触发登录操作,可能会报错,因为我们还没有设置代理
    vue.config.js 中,加入以下代码:

      devServer: {
        open: true,//启动自动打开浏览器
        // 当地址中有/api的时候会触发代理机制
        proxy: {
          '/': {
            target: '填写你们自己公司的测试URL', //测试环境
            changeOrigin: true,
            ws: false,
            pathRewrite: {
              // '^/api': '' //需要rewrite重写的,
            }
          }
        }
      },
    

    重新启动服务,再次进行请求,就可以得到返回数据了

    本地缓存处理方案

    通常情况下,在获取到 token 之后,我们会把 token 进行缓存,而缓存的方式将会分为两种:

    1. 本地缓存:LocalStorage
    2. 全局状态管理:Vuex

    保存在 LocalStorage 是为了方便实现 自动登录功能

    保存在 vuex 中是为了后面在其他位置进行使用

    那么下面我们就分别来实现对应的缓存方案:

    LocalStorage:

    1. 创建 utils/storage.js 文件,封装三个对应方法:

      /**
       * 存储数据
       */
      export const setItem = (key, value) => {
        // 将数组、对象类型的数据转化为 JSON 字符串进行存储
        if (typeof value === 'object') {
          value = JSON.stringify(value)
        }
        window.localStorage.setItem(key, value)
      }
      
      /**
       * 获取数据
       */
      export const getItem = key => {
        const data = window.localStorage.getItem(key)
        try {
          return JSON.parse(data)
        } catch (err) {
          return data
        }
      }
      
      /**
       * 删除数据
       */
      export const removeItem = key => {
        window.localStorage.removeItem(key)
      }
      
      /**
       * 删除所有数据
       */
      export const removeAllItem = key => {
        window.localStorage.clear()
      }
      
    2. vuexuser 模块下,处理 token 的保存

      import { login } from '@/api/sys'
      import { setItem, getItem } from '@/utils/storage'
      import { TOKEN } from '@/constant'
      export default {
        namespaced: true,
        state: () => ({
          token: getItem(TOKEN) || ''
        }),
        mutations: {
          setToken(state, token) {
            state.token = token
            setItem(TOKEN, token)
          }
        },
        actions: {
          login(context, userInfo) {
            ...
                .then(data => {
                  this.commit('user/setToken', data.data.data.token)
                  resolve()
                })
                ...
            })
          }
        }
      }
      
      
    3. 处理保存的过程中,需要创建 constant 常量目录 constant/index.js

      export const TOKEN = 'token'
      

    此时,当点击登陆时,即可把 token 保存至 vuexlocalStorage

    响应数据的统一处理

    utils/request.js 中实现以下代码:

    import axios from 'axios'
    import { ElMessage } from 'element-plus'
    
    ...
    // 响应拦截器
    service.interceptors.response.use(
    response => {
     const { success, message, data } = response.data
     //   要根据success的成功与否决定下面的操作
     if (success) {
       return data
     } else {
       // 业务错误
       ElMessage.error(message) // 提示错误消息
       return Promise.reject(new Error(message))
     }
    },
    error => {
     ElMessage.error(error.message) // 提示错误信息
     return Promise.reject(error)
    }
    )
    
    export default service
    
    

    此时,对于 vuex 中的 user 模块 就可以进行以下修改了:

    this.commit('user/setToken', data.token)
    
    登录后操作

    那么截止到此时,我们距离登录操作还差最后一个功能就是 登录鉴权

    只不过在进行 登录鉴权 之前我们得先去创建一个登录后的页面,也就是我们所说的登录后操作。

    1. 创建 layout/index.vue ,写入以下代码:

      <template>
        <div class="">Layout 页面</div>
      </template>
      
      <script setup>
      import {} from 'vue'
      </script>
      
      <style lang="scss" scoped></style>
      
      
    2. router/index 中,指定对应路由表:

      const publicRoutes = [
       ...
        {
          path: '/',
          component: () => import('@/layout/index')
        }
      ]
      
    3. 在登录成功后,完成跳转

      // 登录后操作
      router.push('/')
      
      登录鉴权解决方案

    在处理了登陆后操作之后,接下来我们就来看一下最后的一个功能,也就是 登录鉴权

    首先我们先去对 登录鉴权 进行一个定义,什么是 登录鉴权 呢?

    • 当用户未登陆时,不允许进入除 login 之外的其他页面。

    • 用户登录后,token 未过期之前,不允许进入 login 页面

    而想要实现这个功能,那么最好的方式就是通过 路由守卫 来进行实现。

    那么明确好了 登录鉴权 的概念之后,接下来就可以去实现一下

    main.js 平级,创建 permission 文件

    import router from './router'
    import store from './store'
    
    // 白名单
    const whiteList = ['/login']
    /**
    * 路由前置守卫
    */
    router.beforeEach(async (to, from, next) => {
    // 存在 token ,进入主页
    // if (store.state.user.token) {
    // 快捷访问
    if (store.getters.token) {
     if (to.path === '/login') {
       next('/')
     } else {
       next()
     }
    } else {
     // 没有token的情况下,可以进入白名单
     if (whiteList.indexOf(to.path) > -1) {
       next()
     } else {
       next('/login')
     }
    }
    })
    
    
    
    

    在此处我们使用到了 vuex 中的 getters ,此时的 getters 被当作 快捷访问 的形式进行访问

    所以我们需要声明对应的模块,创建 store/getters

    const getters = {
    token: state => state.user.token
    }
    export default getters
    
    

    store/index 中进行导入:

    import getters from './getters'
    export default createStore({
    getters,
    })
    
    

    那么到这里我们整个"通用后台登录方案解析"就算是全部讲解完成了。
    当然,里面的一些代码,例如网络请求的解析,这些就需要你根据自己公司的接口自行去调整了,还有登录鉴权的一些返回码,都需要具体问题具体分析,我们这边只是给出一个通用的方式,及其思路,但相信你看完整篇文章,这些也不是什么大问题的。加油吧!

    • 下一次,我们继续介绍 “# 项目架构之搭建Layout架构 解决方案与实现”,下面给个预告图,下次再见吧


      image.png

    相关文章

      网友评论

          本文标题:Vue3写一个后台管理系统(3)通用后台登录方案解析

          本文链接:https://www.haomeiwen.com/subject/rgwaldtx.html