\
Vue-Cli配置跨域代理
目标: 通过配置vue-cli的代理解决跨域访问的问题
为什么会出现跨域?
当下,最流行的就是**前后分离项目,也就是前端项目和后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域**的行为.
怎么解决这种跨域 ?
请注意,我们所遇到的这种跨域是位于开发环境的,真正部署上线时的跨域是生产环境的
解决开发环境的跨域问题
开发环境的跨域
开发环境的跨域,也就是在**vue-cli脚手架环境下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务,可以通过这个服务帮我们代理请求**,解决跨域问题
这就是vue-cli配置webpack的反向代理
采用vue-cli的代理配置
vue-cli的配置文件即**vue.config.js**,这里有我们需要的 代理选项
module.exports = {
devServer: {
// 代理配置
proxy: {
// 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制
// localhost:8888/api/abc => 代理给另一个服务器
// 本地的前端 =》 本地的后端 =》 代理我们向另一个服务器发请求 (行得通)
// 本地的前端 =》 另外一个服务器发请求 (跨域 行不通)
'/api': {
target: 'www.baidu.com', // 我们要代理的地址
changeOrigin: true, // 是否跨域 需要设置此值为true 才可以让本地服务代理我们发出请求
// 路径重写
pathRewrite: {
// 重新路由 localhost:8888/api/login => www.baidu.com/api/login
'^/api': '' // 假设我们想把 localhost:8888/api/login 变成www.baidu.com/login 就需要这么做
}
},
}
}
}
以上就是我们在vue-cli项目中配置的代理设置
接下来,我们在代码中将要代理的后端地址变成 [后端接口地址]
// 代理跨域的配置
proxy: {
// 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
'/api': {
target: 'http://123.57.143.201:8888/api/private/v1/', // 跨域请求的地址
changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域
}
}
本节注意:我们并没有进行**pathRewrite,因为后端接口就是http://123.57.143.201:8888/api/private/v1/api**这种格式,所以不需要重写
**vue.config.js**的改动如果要生效,需要进行重启服务
登录模块
登录页面的基础布局
**目标**完成登录页面的基础布局
首先要实现以上的页面效果, 我们可以直接将当前的登录页面进行相应的改造
设置头部背景
<div class="login-wrapper">
<div class="login-box">
<!-- logo -->
<div class="logo-wrapper">
<img src="@/assets/logo.png" alt="" />
</div>
<!-- 登录表单区域 -->
</div>
</div>
本节注意: @是我们在vue.config.js中设置的一个路径别名,指定src根目录,这样可以很方便的寻找文件
/* reset element-ui css */
.login-container {
background-image: url('~@/assets/common/login.jpg'); // 设置背景图片
background-position: center; // 将图片位置设置为充满整个屏幕
}
本节注意: 如需要在样式表中使用 @ 别名的时候,需要在@前面加上一个 ~ 符号,否则不识别
设置样式
.login-wrapper {
width: 100%;
height: 100%;
background: #2b4b6b;
.login-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 430px;
height: 250px;
background: #fff;
border-radius: 3px;
.logo-wrapper {
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px;
width: 150px;
height: 150px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 10px #ddd;
overflow: hidden;
box-sizing: border-box;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background: #eee;
}
}
.el-form {
padding: 0 15px;
position: absolute;
bottom: 0;
width: 100%;
box-sizing: border-box;
:deep(.el-form-item) {
&:last-child {
.el-form-item__content {
display: flex;
justify-content: flex-end;
}
}
}
}
}
}
提交代码
本节注意: @是我们在vue.config.js中设置的一个路径别名,指定src根目录,这样可以很方便的寻找文件
本节注意: 如需要在样式表中使用 @ 别名的时候,需要在@前面加上一个 ~ 符号,否则不识别
本节任务: 完成登录首页的基本布局
表单校验的先决条件
接下来,完成表单的校验规则如下几个先决条件
model属性 (表单数据对象)
data () {
// 定义表单数据对象
return {
loginForm: {
mobile: '',
password: ''
}
}
}
绑定model
<el-form style="margin-top:40px" :model="loginForm" >
rules规则 先定义空规则,后续再详解
loginRules: {}
<el-form style="margin-top: 50px" model="loginForm" :rules="loginRules">
设置prop属性
校验谁写谁的字段
<el-form-item prop="mobile">
...
<el-form-item prop="password">
...
给input绑定字段属性
<el-input v-model="loginForm.mobile"></el-input>
<el-input v-model="loginForm.password"></el-input>
表单校验规则
此时,先决条件已经完成,要完成表单的校验,需要编写规则
ElementUI的表单校验规则来自第三方校验规则参见 async-validator
我们介绍几个基本使用的规则
| 规则 | 说明 |
|---|---|
| required | 如果为true,表示该字段为必填 |
| message | 当不满足设置的规则时的提示信息 |
| pattern | 正则表达式,通过正则验证值 |
| min | 当值为字符串时,min表示字符串的最小长度,当值为数字时,min表示数字的最小值 |
| max | 当值为字符串时,max表示字符串的最大长度,当值为数字时,max表示数字的最大值 |
| trigger | 校验的触发方式,change(值改变) / blur (失去焦点)两种, |
| validator | 如果配置型的校验规则不满足你的需求,你可以通过自定义函数来完成校验 |
校验规则的格式
{ key(字段名): value(校验规则) => [{}] }
根据以上的规则,针对当前表单完成如下要求
手机号 1.必填 2.手机号格式校验 3. 失去焦点校验
密码 1.必填 2.6-16位长度 3. 失去焦点校验
规则如下
loginRules: {
mobile: [{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }],
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }, {
min: 6, max: 16, message: '密码应为6-16位的长度', trigger: 'blur'
}]
}
自定义校验规则
自定义校验规则怎么用
validator是一个函数, 其中有三个参数 (rule(当前规则),value(当前值),callback(回调函数))
var func = function (rule, value, callback) {
// 根据value进行进行校验
// 如果一切ok
// 直接执行callback
callback() // 一切ok 请继续
// 如果不ok
callback(new Error("错误信息"))
}
根据以上要求,增加手机号第三位必须是9的校验规则
如下
// 自定义校验函数
const checkMobile = function (rule, value, callback) {
value.charAt(2) === '9' ? callback() : callback(new Error('第三位手机号必须是9'))
}
mobile: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }, {
trigger: 'blur',
validator: checkMobile
}],
手动校验的实现
最后一个问题,如果我们直接点登陆按钮,没有离开焦点,那该怎么校验 ?
此时我们需要用到手动完整校验 案例
form表单提供了一份API方法,我们可以对表单进行完整和部分校验
| 方法名 | 说明 | 参数 | |
|---|---|---|---|
| validate | 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise | Function(callback: Function(boolean, object)) | |
| validateField | 对部分表单字段进行校验的方法 | Function(props: array | string, callback: Function(errorMessage: string)) |
| resetFields | 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 | — | |
| clearValidate | 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果 | Function(props: array | string) |
这些方法是el-form的API,需要获取el-form的实例,才可以调用
采用ref进行调用
<el-form ref="loginForm" style="margin-top:40px" :model="loginForm" :rules="loginRules">
调用校验方法
login () {
// 获取el-form的实例
this.$refs.loginForm.validate(function (isOK) {
if (isOK) {
// 说明校验通过
// 调用登录接口
}
}) // 校验整个表单
}
封装单独的登录接口
目标 在单独请求模块中,单独封装登录接口
完成登录模块之后,我们需要对登录接口进行封装
首先,查阅接口文档中的登录接口
基础模板已经有了原来的登录代码,我们只需要进行简单的改造即可
export function login(data) {
// 返回一个axios对象 => promise // 返回了一个promise对象
return request({
url: '/sys/login', // 因为所有的接口都要跨域 表示所有的接口要带 /api
method: 'post',
data
})
}
如图
封装Vuex的登录Action并处理token
**目标**在vuex中封装登录的action,并处理token
在这个小节中,我们将在vuex中加入对于用户的登录的处理
在Vuex中对token进行管理
在传统模式中,我们登录的逻辑很简单,如图
上图中,组件直接和接口打交道,这并没有什么问题,但是对于用户token这一高频使用的**
钥匙**,我们需要让vuex来介入,将用户的token状态共享,更方便的读取,如图
实现store/modules/user.js基本配置
// 状态
const state = {}
// 修改状态
const mutations = {}
// 执行异步
const actions = {}
export default {
namespaced: true,
state,
mutations,
actions
}
设置token的共享状态
const state = {
token: null
}
我们需要知道,**
钥匙**不能每次都通过登录获取,我们可以将token放置到本地的缓存中
在**utils/auth.js中,基础模板已经为我们提供了获取token,设置token,删除token**的方法,可以直接使用
只需要将存储的key放置成特定值即可
import Cookies from 'js-cookie'
const TokenKey = 'sass-token' // 设定一个独一无二的key
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
初始化token状态 - store/modules/user.js
import { getToken, setToken, removeToken } from '@/utils/auth'
// 状态
// 初始化的时候从缓存中读取状态 并赋值到初始化的状态上
// Vuex的持久化 如何实现 ? Vuex和前端缓存相结合
const state = {
token: getToken() // 设置token初始状态 token持久化 => 放到缓存中
}
提供修改token的mutations
// 修改状态
const mutations = {
// 设置token
setToken(state, token) {
state.token = token // 设置token 只是修改state的数据 123 =》 1234
// vuex变化 => 缓存数据
setToken(token) // vuex和 缓存数据的同步
},
// 删除缓存
removeToken(state) {
state.token = null // 删除vuex的token
removeToken() // 先清除 vuex 再清除缓存 vuex和 缓存数据的同步
}
}
封装登录的Action
封装登录的action
登录action要做的事情,
调用登录接口,成功后设置token到vuex,失败则返回失败
// 执行异步
const actions = {
// 定义login action 也需要参数 调用action时 传递过来的参数
async login(context, data) {
const result = await login(data) // 实际上就是一个promise result就是执行的结果
// axios默认给数据加了一层data
if (result.data.success) {
// 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
// 现在有用户token
// actions 修改state 必须通过mutations
context.commit('setToken', result.data.data)
}
}
}
上述代码中,我们使用了**
async/await语法,如果用then**语法也是可以的
// 为什么async/await 不用返回new Promise,因为 async函数本身就是 Promise,promise的值返回的值
login(context, data) {
return new Promise(function(resolve) {
login(data).then(result => {
if (result.data.success) {
context.commit('setToken', result.data.data) // 提交mutations设置token
resolve() // 表示执行成功了
}
})
})
}
以上两种写法都是OK的,我们在项目研发过程中,尽可能的采用前一种
除此之外,为了更好的让其他模块和组件更好的获取token数据,我们可以在**store/getters.js**中将token值作为公共的访问属性放出
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token // 在根级的getters上 开发子模块的属性给别人看 给别人用
}
export default getters
request中环境变量和异常的处理
**目标**设置request环境变量和异常处理
区分axios在不同环境中的请求基础地址
为什么会有环境变量之分? 如图
从上图可以看出,开发环境实际上就是在自己的本地开发或者要求不那么高的环境,但是一旦进入生产,就是**
真实的数据**。 拿银行作比喻,如果你在开发环境拿生产环境的接口做测试,银行系统就会发生很大的风险。
前端主要区分两个环境,开发环境,生产环境
也就是两个环境发出的请求地址是不同的,用什么区分呢?
环境变量
$ process.env.NODE_ENV # 当为production时为生产环境 为development时为开发环境
环境文件
我们可以在 **.env.development**和 **.env.production**定义变量,变量自动就为当前环境的值
基础模板在以上文件定义了变量**VUE_APP_BASE_API,该变量可以作为axios请求的baseURL**
我们会发现,在模板中,两个值分别为 **/dev-api**和 /prod-api
但是我们的开发环境代理是 /api,所以可以统一下
# 开发环境的基础地址和代理对应
VUE_APP_BASE_API = '/api'
# 这里配置了/api,意味着需要在Nginx服务器上为该服务配置 nginx的反向代理对应/prod-api的地址
VUE_APP_BASE_API = '/prod-api'
本节注意:我们这里生产环境和开发环境设置了不同的值,后续我们还会在生产环境部署的时候,去配置该值所对应的反向代理,反向代理指向哪个地址,完全由我们自己决定,不会和开发环境冲突
在request中设置baseUrl
const service = axios.create({
// 如果执行 npm run dev 值为 /api 正确 /api 这个代理只是给开发环境配置的代理
// 如果执行 npm run build 值为 /prod-api 没关系 运维应该在上线的时候 给你配置上 /prod-api的代理
baseURL: process.env.VUE_APP_BASE_API, // 设置axios请求的基础的基础地址
timeout: 5000 // 定义5秒超时
}) // 创建一个axios的实例
处理axios的响应拦截器
OK,除此之外,axios返回的数据中默认增加了一层**
data的包裹**,我们需要在这里处理下
并且,人资项目的接口,如果执行失败,只是设置了**
success为false**,并没有reject,我们需要一并处理下
处理逻辑如图
// 响应拦截器
service.interceptors.response.use(response => {
// axios默认加了一层data
const { success, message, data } = response.data
// 要根据success的成功与否决定下面的操作
if (success) {
return data
} else {
// 业务已经错误了 还能进then ? 不能 ! 应该进catch
Message.error(message) // 提示错误消息
return Promise.reject(new Error(message))
}
}, error => {
Message.error(error.message) // 提示错误信息
return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch
})
既然在request中已经默认去除了一层data的外衣,所以我们也将上节login的action进行一下改动
处理登录的返回结构问题
async login(context, data) {
// 经过响应拦截器的处理之后 这里的result实际上就是 token
const result = await login(data) // 实际上就是一个promise result就是执行的结果
// axios默认给数据加了一层data
// 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
// 现在有用户token
// actions 修改state 必须通过mutations
context.commit('setToken', result)
}
登录页面调用登录action,处理异常
目标 调用vuex中的登录action,并跳转到主页
按照如图的业务逻辑,把剩下的内容在登录页面引入
引入actions辅助函数
import { mapActions } from 'vuex' // 引入vuex的辅助函数
引入action方法
此处,我们采用直接引入模块action的方式,后面我们采用分模块的引用方式
methods: {
...mapActions(['user/login'])
}
调用登录
this.$refs.loginForm.validate(async isOK => {
if (isOK) {
try {
this.loading = true
// 只有校验通过了 我们才去调用action
await this['user/login'](this.loginForm)
// 应该登录成功之后
// async标记的函数实际上一个promise对象
// await下面的代码 都是成功执行的代码
this.$router.push('/')
} catch (error) {
console.log(error)
} finally {
// 不论执行try 还是catch 都去关闭转圈
this.loading = false
}
}
})
设置固定的本地访问端口
目标: 设置统一的本地访问端口和网站title
在正式开发业务之前,先将项目的本地端口
本地服务端口: 在**vue.config.js**中进行设置
vue.config.js 就是vue项目相关的编译,配置,打包,启动服务相关的配置文件,它的核心在于webpack,但是又不同于webpack,相当于改良版的webpack, 文档地址
如图,是开发环境服务端口的位置
我们看到上面的 **process.env.port**实际上是一个nodejs服务下的环境变量,该变量在哪里设置呢?
在项目下, 我们创建.env.development**和**.env.production`**两个文件
development => 开发环境
production => 生产环境
当我们运行npm run serve进行开发调试的时候,此时会加载执行 **.env.development**文件内容
当我们运行npm run build:prod进行生产环境打包的时候,会加载执行 **.env.production**文件内容
所以,如果想要设置开发环境的接口,直接在 **.env.development**中写入对于port变量的赋值即可
# 设置端口号
port = 8888
vue.config.js
const port = process.env.port || process.env.npm_config_port || 9528;
module.exports = defineConfig({
devServer: {
port: port,
}
});
本节注意` :修改服务的配置文件,想要生效的话,必须要重新启动服务,值‘8888’后面不能留有空格