1.Axios安装和接口封装
Axios是一个易用、简洁且高效的http库, 使用Promise管理异步,支持请求和响应拦截器,自动转换JSON数据等高级配置,是在vue项目中十分常见前端请求库,使用以下指令安装。
npm install axios
npm install js-cookie
为了便于后续接口管理,一般都将所有的接口单独放在同一目录下统一管理。在src下新建文件夹request,并创建文件src/request/http.js,后续对接口的baseURL、超时时间、请求和响应拦截、接口类型封装等都将在此文件中。
http.js
import axios from 'axios'
import Cookies from 'js-cookie'
// 请求超时时间
axios.defaults.timeout = 10000 * 5
// 请求基础URL,对应后台服务接口地址
axios.defaults.baseURL = "http://localhost:8080"
// 自定义post请求头
axios.defaults.headers.post['Content-Type'] = 'application/json'
// 请求拦截器
axios.interceptors.request.use(
(config) => {
// 自定义请求头
config.headers['token'] = Cookies.get('token')
return config
},
(error) => {
// 请求错误时
console.log(error) // 打印错误信息
return Promise.reject(error)
}
)
// 响应拦截器
axios.interceptors.response.use(
(response) => {
if (response.status === 200) {
// 接口HTTP状态码为200时
return Promise.resolve(response)
}
},
// HTTP状态码非200的情况
(error) => {
if (error.response.status) {
switch (error.response.status) {
case 500: // HTTP状态码500
this.$message.error('后台服务发生错误')
break
case 401: // HTTP状态码401
this.$message.error('无权限')
break
case 404: // HTTP状态码404
this.$message.error('当前接口不存在')
break
default:
this.$message.error(error.response.message) // 页面显示接口返回的错误信息
return Promise.reject(error.response)
}
}
}
)
/**
* get方法,对应get请求
*/
export function get(url, params, info = '') {
return new Promise((resolve, reject) => {
axios
.get(url + info, {
params: params
})
.then((res) => {
resolve(res.data) // 返回接口响应结果
})
.catch((err) => {
reject(err.data)
})
})
}
/**
* post方法,对应post请求
* info为 true,formData格式;
* info为 undefined或false,是json格式
*/
export function post(url, data = {}, info) {
return new Promise((resolve, reject) => {
let newData = data
if (info) {
// 转formData格式
newData = new FormData()
for (let i in data) {
newData.append(i, data[i])
}
}
axios
.post(url, JSON.stringify(newData))
.then((res) => {
resolve(res.data)
})
.catch((err) => {
reject(err.data)
})
})
}
/**
* 封装put请求
*/
export function put(url, params = {}, info = '') {
return new Promise((resolve, reject) => {
axios.put(url + info, params).then(
(res) => {
resolve(res.data)
},
(err) => {
reject(err.data)
}
)
})
}
/**
* 封装delete请求
*/
export function Delete(url, params = {}, info = '') {
return new Promise((resolve, reject) => {
axios
.delete(url + info, {
params: params
})
.then((res) => {
resolve(res.data)
})
.catch((err) => {
reject(err.data)
})
})
}
在src/request下创建新文件user.js,所有与用户相关的接口均维护在这个文件中。
import { get, post } from './http'
// 登录接口
export const login = (p) => get('/user/login', p)
// 注册接口
export const addUser = (p) => post('/user/register', p)
// 获取登录状态及用户信息
export const checkToken = p => get('/user/checkToken', p);
2.创建登录页面
修改login .vue,添加登陆页面布局。
<template>
<div class="loginWrapper" id="loginBackground">
<div class="formWrapper">
<h1 class="loginTitle">登录</h1>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
hide-required-asterisk
>
<el-form-item prop="telephone">
<el-input
prefix-icon="el-icon-mobile-phone"
v-model="ruleForm.telephone"
placeholder="手机号"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
prefix-icon="el-icon-lock"
v-model="ruleForm.password"
placeholder="密码"
show-password
></el-input>
</el-form-item>
<el-form-item class="loginButtonWrapper">
<el-button
class="loginButton"
type="primary"
@click="submitForm('ruleForm')"
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { login } from '@/request/user.js' // 引入登录接口
import Cookies from 'js-cookie'
export default {
name: 'LoginView',
data() {
return {
ruleForm: {
telephone: '',
password: ''
},
rules: {
telephone: [
{ required: true, message: '请输入手机号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' }
]
}
}
},
created() {
if (this.$store.getters.isLogin) {
// 用户若已登录,自动跳转到首页
this.$notify({
title: '成功',
message: '您已登录!已跳转到首页',
type: 'success'
})
this.$router.replace({ name: 'Home' })
}
},
methods: {
// 登录按钮-点击事件
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
// 各项校验通过-调用登录接口
login(this.ruleForm, true).then((res) => {
if (res.success) {
Cookies.set('token', res.data.token) // 在cookies中添加token
this.$message.success('登录成功!')
this.$refs[formName].resetFields() // 清空表单项
this.$router.replace({ name: 'Home' }) // 跳转到首页
} else {
this.$message.error('手机号或密码错误!')
this.$refs.dragVerifyRef.reset()
}
})
} else {
this.$message.error('请完善信息!')
return false
}
})
}
}
}
</script>
<style lang="stylus" scoped>
.loginWrapper {
height: 550px !important;
min-height: 550px !important;
padding-top: 50px;
.formWrapper {
width: 375px;
margin: 0 auto;
text-align: center;
.loginTitle {
margin-bottom: 10px;
font-weight: 300;
font-size: 30px;
color: #000;
}
.demo-ruleForm {
width: 100%;
margin-top: 20px;
>>> .el-form-item__content {
margin-left: 0 !important;
}
&>>> .el-input__inner {
font-size: 16px;
}
.forgetPassword {
text-align: right;
margin: -22px 0 0 0;
}
.loginButtonWrapper {
.loginButton {
width: 100%;
}
&>>> .el-button {
padding: 10px 90px;
font-size: 16px;
}
}
}
.tip {
width: 70%;
margin-left: 86px;
}
}
}
</style>
这里是前后端分离模式,会出现一个问题就是跨域,这里就用最简单的方式在后端处理跨域的问题,添加@CrossOrigin
启动后端项目与前端项目,登陆测试效果。
3.创建注册页面
<template>
<div class="registerWrapper" id="registerBackground">
<div class="formWrapper">
<h1 class="registerTitle">注册</h1>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
hide-required-asterisk
>
<el-form-item prop="username">
<el-input
prefix-icon="el-icon-user"
v-model="ruleForm.username"
placeholder="用户名"
></el-input>
</el-form-item>
<el-form-item prop="telephone">
<el-input
prefix-icon="el-icon-mobile-phone"
v-model="ruleForm.telephone"
placeholder="手机号"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
prefix-icon="el-icon-lock"
v-model="ruleForm.password"
placeholder="密码"
show-password
></el-input>
</el-form-item>
<el-form-item class="registerButtonWrapper">
<el-button
class="registerButton"
type="primary"
@click="submitForm('ruleForm')"
>注册</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { addUser } from '@/request/user.js' // 引入注册接口
export default {
name: 'RegisterView',
data() {
return {
ruleForm: {
telephone: '',
username: '',
password: ''
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{
min: 5,
max: 20,
message: '长度在 5 到 20 个字符',
trigger: 'blur'
}
],
telephone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ min: 11, max: 11, message: '请输入11位手机号', trigger: 'blur' }
]
}
}
},
created() {
if (this.$store.getters.isLogin) {
// 用户若已登录,自动跳转到首页
this.$notify({
title: '成功',
message: '您已注册成功!已跳转到首页',
type: 'success'
})
this.$router.replace({ name: 'Login' })
}
},
methods: {
// 注册按钮-点击事件
submitForm(formName) {
this.$refs[formName].validate((valid) => {
// 校验表单
if (valid) {
// 各项校验通过-调用注册接口,传参用户名、手机号和密码
addUser(this.ruleForm).then((res) => {
if (res.success) {
this.$notify({
title: '成功',
message: '注册成功!已跳转到登录页面',
type: 'success',
duration: 0
})
this.$refs[formName].resetFields() // 注册成功之后清空表单
this.$router.replace({ path: '/login' }) // 注册成功之后跳转到登录页面
} else {
this.$message.error(res.message) // 显示接口返回的错误信息
}
})
} else {
// 表单校验没通过
this.$message.error('请完善信息!')
return false
}
})
}
}
}
</script>
<style lang="stylus" scoped>
.registerWrapper {
height: 500px !important;
min-height: 500px !important;
width: 100% !important;
padding-top: 50px;
.formWrapper {
width: 375px;
margin: 0 auto;
text-align: center;
.registerTitle {
margin-bottom: 10px;
font-weight: 300;
font-size: 30px;
color: #000;
}
.demo-ruleForm {
width: 100%;
margin-top: 20px;
>>> .el-form-item__content {
margin-left: 0 !important;
}
&>>> .el-input__inner {
font-size: 16px;
}
.registerButtonWrapper {
.registerButton {
width: 100%;
}
&>>> .el-button {
padding: 10px 90px;
font-size: 16px;
}
}
}
.tip {
width: 70%;
margin-left: 86px;
}
}
}
</style>
启动后端项目与前端项目,注册测试效果。
4.登录状态共享和页面跳转
在登录之后,需要保存登录状态,之后自动跳转到首页。若用户直接进入了首页,就需要自动跳转到登录页面,若用户已登录,进入登录和注册页面时,就需要自动跳转到首页。那么需要把登录状态共享给各个页面,就需要用到Vue Router的全局前置守卫和Vuex。
在src/request/user.js中添加获取用户登录信息接口。
// 获取登录状态及用户信息
export const checkToken = p => get('/user/checkToken', p);
先来使用Vuex把状态保存实现,在src/store下新建文件夹module,并新建文件 src/store/module/user.js。
import { checkToken } from "@/request/user.js"; // 引入获取用户登录信息接口
export default {
state: {
isLogin: false, // 初始时候给一个 isLogin = false 表示用户未登录
username: "", // 用户姓名
userId: 0, // 用户id
userInfoObj: {} // 用户信息
},
mutations: {
changeLogin(state, data) {
state.isLogin = data;
},
changeUsername(state, data) {
state.username = data;
},
changeUserId(state, data) {
state.userId = data;
},
changeUserInfoObj(state, data) {
state.userInfoObj = Object.assign({}, state.userInfoObj, data);
}
},
actions: {
getUserInfo(context) {
return checkToken().then((res) => {
if (res.success) {
context.commit("changeLogin", res.success);
context.commit("changeUsername", res.data.username);
context.commit("changeUserId", res.data.userId);
context.commit("changeUserInfoObj", res.data);
} else {
context.commit("changeLogin", res.success);
}
});
}
}
}
在src/store/index.js中引入刚才创建好的user.js,并将相关数据导出,以便后续使用。
import Vue from "vue";
import Vuex from "vuex";
import user from './module/user' // 引入user.js
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//
},
getters: {
isLogin: (state) => state.user.isLogin,
username: (state) => state.user.username,
userId: (state) => state.user.userId,
userInfoObj: (state) => state.user.userInfoObj
},
mutations: {
//
},
actions: {
//
},
modules: {
user
}
})
后面就可以在各种*.vue文件中使用this.$store.getters.isLogin来获取用户的登录状态了。
接着设置哪些路由需要登录之后才可进入,需要在路由上添加一些信息。在 src/router/index.js中给首页路由添加meta属性,并添加参数requireAuth。
{
path: '/', // 路由路径,即浏览器地址栏中显示的URL
name: 'Home', // 路由名称
component: Home, // 路由所使用的页面
meta: {
requireAuth: true
}
在src/router下新建文件before.js,引入Vue Router和状态保存文件src/store/index.js。
import router from './index.js'
import store from '@/store/index.js'
// 路由全局前置守卫
router.beforeEach((to, from, next) => {
// 调用接口,判断当前登录状态
store.dispatch("getUserInfo").then(() => {
if (to.matched.some(m => m.meta.requireAuth)) {
if (!store.getters.isLogin) { // 没有登录
next({
path: '/login',
query: { Rurl: to.fullPath }
})
} else {
next() // 正常跳转到你设置好的页面
}
} else {
next() // 正常跳转到你设置好的页面
}
})
})
添加全局前置守卫,可以在触发导航之前进行一些处理,当处理完成后才会执行导航:
- 先调用接口,判断当前登录状态。
- 判断将要去的路由是否需要登录,即刚才给路由添加的参数meta.requireAuth 是否为 true,若为true,表示需要登录后才可进入;若没有设置当前参数,或参数值为false,表示无需登录也可进入。
- 当meta.requireAuth为true时,判断在Vuex中保存的isLogin为true还是false,为true表示已登录,那么执行next()即可正常导航;为false表示未登录,按照之前的说明,将跳转到登录页面。
在src/main.js中引入刚才创建好的before.js。
import '@/router/before.js'
启动后端项目与前端项目,测试效果。
没有登录情况下,启动项目就会自动跳转到登陆页面。
在已经登陆情况下,就会自动跳转到首页了。
5.退出登陆
- 在已登录时,导航栏添加退出菜单,未登录时隐藏此菜单。
- 在已登录时,隐藏登录和注册菜单,并显示用户名,未登录时显示这两个菜单,并隐藏用户名。
- 登录状态和用户名均保存在Vuex中,这里可以通过computed获取。
在src/components/Header.vue中添加退出相关实现。
<template>
<el-menu
class="header-menu"
:default-active="activeIndex"
:router="true"
mode="horizontal"
>
<el-menu-item index="Home" :route="{ name: 'HomeView' }">首页</el-menu-item>
<el-menu-item
class="login"
index="Login"
:route="{ name: 'LoginView' }"
v-show="!isLogin"
>登录</el-menu-item
>
<el-menu-item
class="register"
index="Register"
:route="{ name: 'RegisterView' }"
v-show="!isLogin"
>注册</el-menu-item
>
<!-- 为了和其他菜单样式保持一致,请一定要添加类名 el-menu-item -->
<div class="el-menu-item exit" @click="exitButton()" v-show="isLogin">
退出
</div>
<!-- 为了和其他菜单样式保持一致,请一定要添加类名 el-menu-item -->
<div class="el-menu-item username" v-show="isLogin">
<!-- 图标来自于Element UI官方图标库 -->
<i class="el-icon-user-solid"></i>{{ username }}
</div>
</el-menu>
</template>
<script>
import Cookies from 'js-cookie'
export default {
name: 'HeaderView',
data() {
return {}
},
computed: {
// 当前激活菜单的 index
activeIndex() {
return this.$route.name // 获取当前路由名称
},
// 登录状态
isLogin() {
return this.$store.getters.isLogin
},
// 用户名
username() {
return this.$store.getters.username
}
},
methods: {
// 退出登录
exitButton() {
Cookies.set('token', '')
this.$router.push({ path: '/login' }) // 退出登录后跳转到登录页面
this.$message.success('退出登录成功!')
}
}
}
</script>
<style lang="stylus" scoped>
.header-menu {
padding: 0 24px;
.login, .register, .username, .exit {
float: right;
}
}
</style>
测试一下:登录状态就是如下情况,退出登陆就会自动跳转到登陆页面。
源码下载地址:源码下载