若伊代码分析(前端 vue2 登录页)

目录

前端项目搭建

项目调整及element引入 

登录界面样式

获取验证码

全局变量

vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令

配置.env文件

获取.env中的全局变量

实际用处

--------项目代码-------

扫描二维码关注公众号,回复: 16956791 查看本文章

跨域配置

 配置代理方式一

配置代理方式二 

--------项目代码------- 

请求封装

1.axios 的请求 配置

2.axios 的全局 以及默认配置 

3.axios  请求拦截器  响应拦截器

4.axios的错误处理 

 --------项目代码-------

接口封装

页面调用

登录逻辑

请求封装

页面调用,添加前端校验 

引入vuex存储token 

捕获错误补充响应拦截器

登录成功失败逻辑 

token值的持久化

路由拦截 

引入路由拦截

登陆时调整到期待路径


前端项目搭建

在当前目录创建

通过上下箭头选择需要的选项(这里我们自定义创建项目,所以我选择最后一个)按enter进入。

上下箭头选择对应选项,空格勾选,勾选完成之后按enter进入下一级 

  • Bable 基础编译器
  • TypeScirpt 使用TypeScript
  • Progressive Web App(PWA) Support 渐进式web应用
  • Router 路由管理器
  • Vuex 项目状态管理
  • Css Pre-processors Css预处理器
  • Linter / Formatter 代码风格检查和格式化
  • Unit Testing 单元测试
  • E2E testing 端对端测试

根据需要选择对应版本 

 路由模式  hash模式和history模式 (no为hash路由)

  1. 哈希模式(Hash Mode):

    • 在哈希模式下,URL中的路由信息由一个哈希标记(#)后面的内容表示。例如:http://example.com/#/about
    • 哈希部分的内容变化时,不会触发浏览器向服务器发送请求,因此在前端应用中可以实现单页面应用(SPA)的路由功能,而不需要重新加载整个页面。
    • 这种模式兼容性较好,因为老版本的浏览器支持。
    • 由于哈希部分不会被发送到服务器,所以服务器端无法直接处理这部分内容,需要前端JavaScript来解析哈希部分并进行路由跳转。
  2. 历史模式(History Mode):

    • 在历史模式下,URL中的路由信息不再使用哈希标记,而是直接使用正常的路径表示。例如:http://example.com/about
    • 历史模式通常需要服务器端配置来支持,以确保在直接访问这些URL时,服务器能够正确路由到对应的页面。
    • 由于历史模式使用正常的路径,更符合传统网站的URL结构,更有利于搜索引擎优化(SEO)。
    • 历史模式不需要哈希的存在,因此URL更加美观,但在一些旧版浏览器中可能不受支持。

选择哈希模式还是历史模式通常取决于项目需求和技术要求。如果需要支持较老的浏览器或希望避免服务器端配置,哈希模式可能是一个更好的选择。如果希望URL更加美观且符合传统网站的URL结构,同时也考虑SEO,那么历史模式可能更合适。一些现代前端框架(如Vue和React)支持同时使用这两种模式,以满足不同场景的需求。

 选择css的预处理器 ,

代码语法错误检查
我这里选择ESLint + Standard config,这个是标准的,然后回车 

 选择检查代码语法的时机
我这里选择第一个Lint on save,然后回车

 第三方配置文件存在的方式
我这里选择第一个In dedicated config files,然后回车

.是否保存本次配置为预设项目模板
我这里选择N(也可以选择Y,这样下次可以直接使用该配置方案快速搭建项目),然后回车,则项目搭建成功 

项目调整及element引入 

目录结构

路由修改

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name: 'login',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ '../views/login.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router

 引入element ui

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";

Vue.use(ElementUI);

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

package.json调整

  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "^2.6.14",
    "vue-router": "^3.5.1",
    "vuex": "^3.6.2",
    "element-ui": "2.15.13"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-plugin-router": "~5.0.0",
    "@vue/cli-plugin-vuex": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "@vue/eslint-config-standard": "^6.1.0",
    "eslint": "^7.32.0",
    "eslint-plugin-import": "^2.25.3",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^5.1.0",
    "eslint-plugin-vue": "^7.0.0",
    "sass": "^1.32.7",
    "sass-loader": "^12.0.0",
    "vue-template-compiler": "^2.6.14"
  }
}

关闭eslint vue.config.js

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
});

登录界面样式

index.html

  <style>
    html,
    body,
    #app {
      height: 100%;
      margin: 0px;
      padding: 0px;
    }
  </style>

login.vue 

<template>
  <div class="login">
    <el-form class="login-form" :model="loginForm">
      <h3 class="title">若依后台管理系统</h3>
      <el-form-item>
        <el-input placeholder="账号" v-model="loginForm.username" type="text"> </el-input>
      </el-form-item>
      <el-form-item>
        <el-input type="password" v-model="loginForm.password" placeholder="密码">
        </el-input>
      </el-form-item>
      <el-form-item>
        <el-input v-model="loginForm.code" placeholder="验证码" style="width: 63%">
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" class="login-code-img" />
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px"
        >记住密码</el-checkbox
      >
      <el-form-item style="width: 100%">
        <el-button size="medium" type="primary" style="width: 100%">
          <span>登 录</span>
        </el-button>
      </el-form-item>
    </el-form>
    <div class="el-login-footer">
      <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>
export default {
  name: "login",
  data() {
    return {
      codeUrl: "",
      loginForm: {
        rememberMe: false,
        username: "admin",
        password: "admin123",
        code: "",
      },
    };
  },
};
</script>
<style lang="scss" scoped>
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
}

.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
}
.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.login-code {
  width: 33%;
  height: 38px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.login-code-img {
  height: 38px;
}
</style>

获取验证码

全局变量

vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令

作用:在vue项目中,env是全局配置文件,可以存储不同环境下的变量。使用vue-cli搭建项目,默认会在根目录创建一个.env文件,如果需要更多类型的.env文件,需要自行创建

其中:

1,.env 后缀的文件是全局默认配置文件,不论什么环境都会加载并合并。

2,.env.development 是开发环境下的配置文件,仅在开发环境加载。

3,.env.production 是生产环境下的配置文件(也就是正式环境),仅在生产环境加载。

以上三个命名不能变动,除此之外,可以另外自定义加上.env.test文件,也就是测试环境,或者.env.bata,也就是内部测试版,等等..

配置.env文件

变量命名必须以VUE_APP_开头,比如VUE_APP_URLVUE_APP_PWD

配置启动命令

vue项目根目录下,找到package.json文件,其中scripts对象是配置的vue启动命令,比如npm run dev,配置如下 

  "scripts": {
    "serve": "vue-cli-service serve",
    "serve-test": "vue-cli-service serve --mode test",
    "build": "vue-cli-service build",
    "test": "vue-cli-service build --mode test",
    "all": "vue-cli-service build && vue-cli-service build --mode test"
  }

每一行说明如下:

1,npm run serve,启动项目,并且加载.env和.env.development文件

2,npm run serve-test,启动项目,并且加载.env和.env.test文件

3,npm run build,生产环境打包,其中.env和.env.production文件会加载

4,npm run test,测试环境打包,其中.env和.env.test文件会加载

5,npm run all,生产环境和测试环境同时打包,加载不同的.env文件

获取.env中的全局变量

比如,我在.env文件中设置了变量VUE_APP_BASE_URL = 'https://www.baidu.com',在项目中我想获取,只需要使用process.env.VUE_APP_BASE_URL,就可以取到。

实际用处

个人觉得最大的用处就是不同环境加载不同的变量,比如开发环境和测试、正式环境的请求域名不同,直接在.env文件中定义一个全局的URL,在请求封装中使用,很方便。

--------项目代码-------

 .env.development


# 开发环境
VUE_APP_BASE_API = '/dev-api'

跨域配置

学习配置代理之前,我们先来了解一下js中常用的发送ajax请求有如下几种方式:

①xhr:可以说xhr是发送ajax请求的鼻祖,也就是我们使用的new XMLHttpRequest(),在原生js里面是最常见的(开发中直接使用的比较少),常见的api有xhr.open()表示配置请求信息,xhr.send()发送请求信息等等。

②jQuery:xhr直接使用的情况是很少的,更多时候都是程序员对它进行二次封装然后使用,或者用一些成型的第三方库,比如jQuery就是对xhr的二次封装。常见的api有$get、$post.

✔③axios:axios也是对xhr进行封装,axios与jQuery相比,axios是Promise风格,并且axios支持请求拦截器和响应拦截器,axios体积小(约jQuery体积的四分之一)

④fetch:jQuery和axios都是对xhr的封装,fetch和xhr是平级的,并且fetch也是Promise风格。在Vue或者react开发中,axios用的比较多,因为fetch会把你返回的数据包两层promise,你想要获取数据就要两次.then,最主要是fetch的兼容性问题(ie浏览器就不能用)。
 

 配置代理方式一

想要使用axios,就要先下载axios(在项目的文件目录下下载)

 npm i axios

然后引入axios并发送get请求

<template>
  <div>
    <button @click="getStudentMsg"></button>
  </div>
</template>
 
<script>
// 引入axios
import axios from 'axios'
export default {
  name: "App",
  methods: {
    getStudentMsg(){
      // 发送get请求
      axios.get('接口地址').then(
        response => {console.log('请求成功',response.data)},
        error => {'请求失败',error.message}
      )
    }
  },
};
</script>

如果你的控制台出现以下错误信息,那就证明跨域了。

 所谓的跨域就是违背了同源策略,同源策略规定了三个东西一致:协议名、主机名以及端口号。比如http://localhost:8080,协议名是http,主机名是localhost,端口号是8080。现在有一个http://localhost:8080的前端向http://localhost:5000的服务器发送请求,前端发出请求并且服务端收到请求也返回数据给浏览器,但是浏览器发现前端发送请求跨域,就把数据留在手里不给前端。如下几种方法可以解决跨域问题:

①cors:这个方法需要写服务器的人(后端)在写服务器返回响应的时候加几个特殊的响应头。(不需要前端操作)

②jsonp:jsonp解决跨域就是借助了script标签中的src属性,在引入外部资源的时候不受同源策略的束缚,并且它只能解决get请求的跨域,post跨域它解决不了。

③配置代理服务器

 我们本篇章就是介绍代理服务器怎样去配置以解决跨域问题。配置代理服务器的方法有两种,一是nginx,二是Vue脚手架vue-cli。这里我们使用较为简单的vue-cli,配置过程如下:

我们想要给脚手架进行配置,那我们就要在vue.config.js这个文件里面进行配置。首先打开Vue官网查看配置流程,生态系统栏里面选择vue-cli,点击配置参考下拉到devServer.proxy.里面有具体的步骤详解。

第一步:开启代理服务器(在配置之后要关闭项目后从新打开)

  第二步:前端是与代理服务器打交道,所以前端代码请求数据的时候端口号不能写5000,要写成代理服务器的端口号8080,这样代理服务器才能收到前端的请求并把请求转发给服务器。

 这样就可以解决了get请求的跨域问题。最后要注意代理服务器不是把所有的请求都转发给服务端了,当你请求的资源8080本身就有,它就不会把这个请求转发给服务端。public文件夹就是我们项目的根路径(就是8080有什么内容就看这里面)。上面请求的是list数据,但如果public文件夹下有list这个文件,那么这个请求后返回的资源就是public下面的list文件,并不是服务器返回的数据。
 

配置代理方式二 

方式一不能配置多个代理,也就是说方式一只能把请求转发给端口号为5000的服务器,不能转给其他服务器了;其次就是不能控制走不走代理,因为如果根路径下有一个文件跟服务器返回的文件名一样的话,返回的根资源并不是代理之后服务端返回的资源。方式二能解决这个问题。

第一步:开启代理服务器


第二步:以/api为前缀发送请求

 第三步:此时控制台是报错的,因为提交的路径不对,代理服务器会把/api/list全给服务端了,但是服务端根本没有/api这个字段,只有/list这个字段。所以我们还需要在配置中添加一个配置项如下(且可以配置多个代理)

--------项目代码------- 

vue.config.js

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
  devServer: {
    host: "0.0.0.0",
    port: 80,
    open: true,
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        target: `https://vue.ruoyi.vip`,
        changeOrigin: true,
        pathRewrite: {
          ["^" + process.env.VUE_APP_BASE_API]: "prod-api",
        },
      },
    },
  },
});

请求封装

src\utils\request.js

1.axios 的请求 配置

创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 GET 方法。

{
  // `url` 是用于请求的服务器 URL
  url: '/user',
 
  // `method` 是创建请求时使用的方法
  method: 'get', // 默认值
 
  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',
 
  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
  // 你可以修改请求头。
  transformRequest: [function (data, headers) {
    // 对发送的 data 进行任意转换处理
 
    return data;
  }],
 
  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对接收的 data 进行任意转换处理
 
    return data;
  }],
 
  // 自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},
 
  // `params` 是与请求一起发送的 URL 参数
  // 必须是一个简单对象或 URLSearchParams 对象
  params: {
    ID: 12345
  },
 
  // `paramsSerializer`是可选方法,主要用于序列化`params`
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },
 
  // `data` 是作为请求体被发送的数据
  // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
  // 在没有设置 `transformRequest` 时,则必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属: FormData, File, Blob
  // - Node 专属: Stream, Buffer
  data: {
    firstName: 'Fred'
  },
  
  // 发送请求体数据的可选语法
  // 请求方式 post
  // 只有 value 会被发送,key 则不会
  data: 'Country=Brasil&City=Belo Horizonte',
 
  // `timeout` 指定请求超时的毫秒数。
  // 如果请求时间超过 `timeout` 的值,则请求会被中断
  timeout: 1000, // 默认值是 `0` (永不超时)
 
  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default
 
  // `adapter` 允许自定义处理请求,这使测试更加容易。
  // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
  adapter: function (config) {
    /* ... */
  },
 
  // `auth` HTTP Basic Auth
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },
 
  // `responseType` 表示浏览器将要响应的数据类型
  // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
  // 浏览器专属:'blob'
  responseType: 'json', // 默认值
 
  // `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
  // 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // 默认值
 
  // `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
  xsrfCookieName: 'XSRF-TOKEN', // 默认值
 
  // `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
 
  // `onUploadProgress` 允许为上传处理进度事件
  // 浏览器专属
  onUploadProgress: function (progressEvent) {
    // 处理原生进度事件
  },
 
  // `onDownloadProgress` 允许为下载处理进度事件
  // 浏览器专属
  onDownloadProgress: function (progressEvent) {
    // 处理原生进度事件
  },
 
  // `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
  maxContentLength: 2000,
 
  // `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
  maxBodyLength: 2000,
 
  // `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
  // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
  // 则promise 将会 resolved,否则是 rejected。
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认值
  },
 
  // `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
  // 如果设置为0,则不会进行重定向
  maxRedirects: 5, // 默认值
 
  // `socketPath` 定义了在node.js中使用的UNIX套接字。
  // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
  // 只能指定 `socketPath` 或 `proxy` 。
  // 若都指定,这使用 `socketPath` 。
  socketPath: null, // default
 
  // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
  // and https requests, respectively, in node.js. This allows options to be added like
  // `keepAlive` that are not enabled by default.
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
 
  // `proxy` 定义了代理服务器的主机名,端口和协议。
  // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
  // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
  // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
  // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
  // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
  proxy: {
    protocol: 'https',
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },
 
  // see https://axios-http.com/zh/docs/cancellation
  cancelToken: new CancelToken(function (cancel) {
  }),
 
  // `decompress` indicates whether or not the response body should be decompressed 
  // automatically. If set to `true` will also remove the 'content-encoding' header 
  // from the responses objects of all decompressed responses
  // - Node only (XHR cannot turn off decompression)
  decompress: true // 默认值

2.axios 的全局 以及默认配置 

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

提一下  优先级   后面的 会覆盖前面的规则 

3.axios  请求拦截器  响应拦截器

 在请求或响应被 then 或 catch 处理前拦截它们。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });
 
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

如果你稍后需要移除拦截器,可以这样: 

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

4.axios的错误处理 

使用 validateStatus 配置选项,可以自定义抛出错误的 HTTP code。

axios.get('/user/12345', {
  validateStatus: function (status) {
    return status < 500; // 处理状态码小于500的情况
  }
})

可以给自定义的 axios 实例添加拦截器。

const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

 使用 toJSON 可以获取更多关于HTTP错误的信息。

axios.get('/user/12345')
  .catch(function (error) {
    console.log(error.toJSON());
  });

 --------项目代码-------

import axios from "axios";
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000,
});

// 响应拦截器
service.interceptors.response.use((res) => {
  return res.data;
});
export default service;

接口封装

src\api\login.js

import request from "@/utils/request";
export function getCodeImg() {
  return request({
    url: "/captchaImage",
    method: "get",
    timeout: 20000,
  });
}

页面调用

captchaEnabled是否开启验证码

<template>
  <div class="login">
    <el-form class="login-form" :model="loginForm">
      <h3 class="title">若依后台管理系统</h3>
      <el-form-item>
        <el-input placeholder="账号" v-model="loginForm.username" type="text"> </el-input>
      </el-form-item>
      <el-form-item>
        <el-input type="password" v-model="loginForm.password" placeholder="密码">
        </el-input>
      </el-form-item>
      <el-form-item>
        <el-input v-model="loginForm.code" placeholder="验证码" style="width: 63%">
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" class="login-code-img" @click="getCode" />
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px"
        >记住密码</el-checkbox
      >
      <el-form-item style="width: 100%">
        <el-button size="medium" type="primary" style="width: 100%">
          <span>登 录</span>
        </el-button>
      </el-form-item>
    </el-form>
    <div class="el-login-footer">
      <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>
import { getCodeImg } from "@/api/login";
export default {
  name: "login",

  data() {
    return {
      codeUrl: "",
      loginForm: {
        rememberMe: false,
        username: "admin",
        password: "admin123",
        code: "",
        // 验证码开关
        captchaEnabled: true,
      },
    };
  },
  created() {
    this.getCode();
  },
  methods: {
    getCode() {
      getCodeImg().then((res) => {
        console.log(res);
        this.captchaEnabled =
          res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
        }
      });
    },
  },
};
</script>

登录逻辑

请求封装

export function login(username, password, code, uuid) {
  const data = {
    username,
    password,
    code,
    uuid,
  };
  return request({
    url: "/login",
    method: "post",
    data: data,
  });
}

页面调用,添加前端校验 

uuid是验证码标识

<template>
  <div class="login">
    <el-form class="login-form" :model="loginForm" :rules="loginRules" ref="loginForm">
      <h3 class="title">若依后台管理系统</h3>
      <el-form-item prop="username">
        <el-input placeholder="账号" v-model="loginForm.username" type="text"> </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input type="password" v-model="loginForm.password" placeholder="密码">
        </el-input>
      </el-form-item>
      <el-form-item prop="code">
        <el-input v-model="loginForm.code" placeholder="验证码" style="width: 63%">
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" class="login-code-img" @click="getCode" />
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px"
        >记住密码</el-checkbox
      >
      <el-form-item style="width: 100%">
        <el-button
          size="medium"
          type="primary"
          style="width: 100%"
          @click.native.prevent="handleLogin"
        >
          <span>登 录</span>
        </el-button>
      </el-form-item>
    </el-form>
    <div class="el-login-footer">
      <span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>
import { getCodeImg, login } from "@/api/login";
export default {
  name: "login",

  data() {
    return {
      loginRules: {
        username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
        password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
        code: [{ required: true, trigger: "change", message: "请输入验证码" }],
      },
      codeUrl: "",
      loginForm: {
        rememberMe: false,
        username: "admin",
        password: "admin123",
        code: "",
        uuid: "",
        // 验证码开关
        captchaEnabled: true,
      },
    };
  },
  created() {
    this.getCode();
  },
  methods: {
    getCode() {
      getCodeImg().then((res) => {
        console.log(res);
        this.captchaEnabled =
          res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    },
    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          const username = this.loginForm.username;
          const password = this.loginForm.password;
          const code = this.loginForm.code;
          const uuid = this.loginForm.uuid;
          login(username, password, code, uuid).then((res) => {
            console.log(res);
          });
        }
      });
    },
  },
};
</script>

引入vuex存储token 

src\store\modules\user.js

import { login } from "@/api/login";
const user = {
  state: {
    token: "",
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token;
    },
  },
  actions: {
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim();
      const password = userInfo.password;
      const code = userInfo.code;
      const uuid = userInfo.uuid;
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid)
          .then((res) => {
            commit("SET_TOKEN", res.token);
            resolve(res);
          })
          .catch((error) => {
            reject(error);
          });
      });
    },
  },
};
export default user;

src\store\index.js 

import Vue from "vue";
import Vuex from "vuex";
import user from "./modules/user";
Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    user,
  },
});

捕获错误补充响应拦截器

import { Notification, Message } from "element-ui";
import axios from "axios";
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000,
});

// 响应拦截器
service.interceptors.response.use((res) => {
  const code = res.data.code;
  // 获取错误信息
  const msg = res.data.msg;
  if (code === 500) {
    Message({ message: msg, type: "error" });
    return Promise.reject(new Error(msg));
  } else if (code !== 200) {
    Notification.error({ title: msg });
    return Promise.reject("error");
  } else {
    return res.data;
  }
});
export default service;

登录成功失败逻辑 

登录页 登录方法

    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.$store
            .dispatch("Login", this.loginForm)
            .then((res) => {
              this.$router.push({ path: "/" });
            })
            .catch((err) => {
              if (this.captchaEnabled) {
                this.getCode();
              }
            });
        }
      });
    },

路由配置

import Vue from "vue";
import VueRouter from "vue-router";
import Layout from "@/layout";

Vue.use(VueRouter);

const routes = [
  {
    path: "/login",
    name: "login",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/login.vue"),
  },
  {
    path: "",
    component: Layout,
  },
];

const router = new VueRouter({
  routes,
});

export default router;

token值的持久化

src\utils\auth.js

import Cookies from "js-cookie";

const TokenKey = "Admin-Token";

export function getToken() {
  return Cookies.get(TokenKey);
}

export function setToken(token) {
  return Cookies.set(TokenKey, token);
}

export function removeToken() {
  return Cookies.remove(TokenKey);
}

src\store\modules\user.js

import { login } from "@/api/login";
import { getToken, setToken, removeToken } from "@/utils/auth";
const user = {
  state: {
    token: getToken(),
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token;
    },
  },
  actions: {
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim();
      const password = userInfo.password;
      const code = userInfo.code;
      const uuid = userInfo.uuid;
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid)
          .then((res) => {
            setToken(res.token);
            commit("SET_TOKEN", res.token);
            resolve(res);
          })
          .catch((error) => {
            reject(error);
          });
      });
    },
  },
};
export default user;

路由拦截 

引入路由拦截

src\permission.js 记得main.js引入

import router from "./router";
import { getToken } from "@/utils/auth";

const whiteList = ["/login"];
router.beforeEach((to, from, next) => {
  if (getToken()) {
    next();
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next();
    } else {
      next("/login");
    }
  }
});

登陆时调整到期待路径

src\permission.js

import router from "./router";
import { getToken } from "@/utils/auth";

const whiteList = ["/login"];
router.beforeEach((to, from, next) => {
  if (getToken()) {
    next();
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next();
    } else {
      next(`/login?redirect=${encodeURIComponent(to.fullPath)}`);
    }
  }
});

src\views\login.vue

  watch: {
    $route: {
      handler: function (route) {
        console.log(route);
        this.redirect = route.query && route.query.redirect;
      },
      immediate: true,
    },
  },
    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.$store
            .dispatch("Login", this.loginForm)
            .then((res) => {
              this.$router.push({ path: this.redirect || "/" });
            })
            .catch((err) => {
              if (this.captchaEnabled) {
                this.getCode();
              }
            });
        }
      });
    },

猜你喜欢

转载自blog.csdn.net/qq_63358859/article/details/132663322
今日推荐