目录
前端项目搭建
项目调整及element引入
登录界面样式
获取验证码
全局变量
vue中利用.env文件存储全局环境变量,以及配置vue启动和打包命令
配置.env文件
获取.env中的全局变量
实际用处
--------项目代码-------
跨域配置
配置代理方式一
配置代理方式二
--------项目代码-------
请求封装
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路由)
哈希模式(Hash Mode):
- 在哈希模式下,URL中的路由信息由一个哈希标记(#)后面的内容表示。例如:
http://example.com/#/about
。- 哈希部分的内容变化时,不会触发浏览器向服务器发送请求,因此在前端应用中可以实现单页面应用(SPA)的路由功能,而不需要重新加载整个页面。
- 这种模式兼容性较好,因为老版本的浏览器支持。
- 由于哈希部分不会被发送到服务器,所以服务器端无法直接处理这部分内容,需要前端JavaScript来解析哈希部分并进行路由跳转。
历史模式(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_URL
,VUE_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();
}
});
}
});
},