vue-admin-template
vue-element-admin介绍vue-element-admin是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。官方网址:https://panjiachen.github.io/vue-element-a
vue-element-admin
介绍
vue-element-admin是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。
官方网址:https://panjiachen.github.io/vue-element-admin-site/zh/guide/#%E5%8A%9F%E8%83%BD
目录结构
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── plop-templates # 基本模板
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
安装
# 克隆项目
git clone https://github.com/PanJiaChen/vue-element-admin.git
# 进入项目目录
cd vue-element-admin
# 安装依赖
npm install
# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 本地开发 启动项目
npm run dev
Axios的拦截器介绍
请求拦截器原理如下图:
axios拦截器
axios作为网络请求的第三方工具, 可以进行请求和响应的拦截
通过create创建了一个新的axios实例
// 创建了一个新的axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // 超时时间
})
请求拦截器
请求拦截器主要处理 token的统一注入问题
// axios的请求拦截器
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
响应拦截器
响应拦截器主要处理 返回的**数据异常
** 和**数据结构
**问题
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
这里为了后续更清楚的书写代码,我们将原有代码注释掉,换成如下代码
// 导出一个axios的实例 而且这个实例要有请求拦截器 响应拦截器
import axios from 'axios'
const service = axios.create() // 创建一个axios的实例
service.interceptors.request.use() // 请求拦截器
service.interceptors.response.use() // 响应拦截器
export default service // 导出axios实例
api模块的单独封装
我们习惯性的将所有的网络请求 放置在api目录下统一管理,按照模块进行划分
单独封装代码
import request from '@/utils/request'
export function login(data) {
return request({
url: '/vue-admin-template/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/vue-admin-template/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/vue-admin-template/user/logout',
method: 'post'
})
}
上面代码中,使用了封装的request工具,每个接口的请求都单独**导出
**了一个方法,这样做的好处就是,任何位置需要请求的话,可以直接引用我们导出的请求方法
为了后续更好的开发,我们可以先将user.js代码的方法设置为空,后续在进行更正
// import request from '@/utils/request'
export function login(data) {
}
export function getInfo(token) {
}
export function logout() {
}
登录页面的基础布局
**目标
**完成登录页面的基础布局
设置头部背景
<!-- 放置标题图片 @是设置的别名-->
<div class="title-container">
<h3 class="title">
<img src="@/assets/common/login-logo.png" alt="">
</h3>
</div>
本节注意
: @
是我们在vue.config.js中设置的一个路径别名,指定src根目录,这样可以很方便的寻找文件
设置背景图片
/* reset element-ui css */
.login-container {
background-image: url('~@/assets/common/login.jpg'); // 设置背景图片
background-position: center; // 将图片位置设置为充满整个屏幕
}
本节注意: 如需要在样式表中使用**@
别名的时候,需要在@前面加上一个~
**符号,否则不识别
设置手机号和密码的字体颜色
$light_gray: #68b0fe; // 将输入框颜色改成蓝色
设置输入表单整体背景色
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.7); // 输入登录表单的背景色
border-radius: 5px;
color: #454545;
}
设置错误信息的颜色
.el-form-item__error {
color: #fff
}
设置登录按钮的样式
.loginBtn {
background: #407ffe;
height: 64px;
line-height: 32px;
font-size: 24px;
}
修改显示的提示文本和登录文本
<div class="tips">
<span style="margin-right:20px;">账号: 13800000002</span>
<span> 密码: 123456</span>
</div>
登录表单的校验
**目标
**对登录表单进行规则校验
用户名和密码的校验
为什么要对应? 因为基础模板采用的是**username
的字段,但是实际接口中采用的是mobile
的字段,为了更方便的写代码,所以我们将username
改成mobile
**
英文提示变成中文
基础模板中都是placeHolder占位符是英文,要变成中文
登录按钮文字同样需要换成中文
校验用户名和校验密码
基础模板中,已经做了校验,我们针对代码进行一些优化
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [
{ required: true, trigger: 'blur' },
{ min: 6, max: 12, trigger: 'blur', message: '密码长度应该在6-12位之间' }]
},
**utils/validate.js
**是一个专门存放校验工具方法的文件
Vue-Cli配置跨域代理
目标: 通过配置vue-cli的代理解决跨域访问的问题
为什么会出现跨域?
当下,最流行的就是**前后分离
项目,也就是前端项目
和后端接口
并不在一个域名之下,那么前端项目访问后端接口必然存在跨域
**的行为.
请注意,我们所遇到的这种跨域是位于开发环境的,真正部署上线时的跨域是生产环境的
解决开发环境的跨域问题
开发环境的跨域
开发环境的跨域,也就是在**vue-cli脚手架环境
下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务
,可以通过这个服务帮我们代理请求
,解决跨域问题,这就是vue-cli配置webpack的反向代理**
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 就需要这么做
}
},
}
}
}
接下来,我们在代码中将要代理的后端地址变成 后端接口地址
// 代理跨域的配置
proxy: {
// 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
'/api/private/v1/': {
target: 'http://127.0.0.1:8888', // 跨域请求的地址
changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域
}
}
本节注意**:我们并没有进行pathRewrite
,因为后端接口就是ihrm-java.itheima.net/api
**这种格式,所以不需要重写
**vue.config.js
**的改动如果要生效,需要进行重启服务
同时,还需要注意的是,我们同时需要注释掉 mock的加载,因为mock-server会导致代理服务的异常
// before: require('./mock/mock-server.js'), // 注释mock-server加载
生产环境的跨域
生产环境表示我们已经开发完成项目,将项目部署到了服务器上,这时已经没有了vue-cli脚手架的**辅助
了,我们只是把打包好的html+js+css
交付运维人员,放到Nginx
服务器而已,所以此时需要借助Nginx
**的反向代理来进行
server{
# 监听9099端口
listen 9099;
# 本地的域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://baidu.com
location ^~ /api {
proxy_pass http://baidu.com;
}
}
封装单独的登录接口
目标 在单独请求模块中,单独封装登录接口
基础模板已经有了原来的登录代码,我们只需要进行简单的改造即可
export function login(data) {
// 返回一个axios对象 => promise // 返回了一个promise对象
return request({
url: 'login', // 因为所有的接口都要跨域 表示所有的接口要带 /api
method: 'post',
data
})
}
封装Vuex的登录Action并处理token
**目标
**在vuex中封装登录的action,并处理token
在Vuex中对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
}
在**utils/auth.js
中,基础模板已经为我们提供了获取token
,设置token
,删除token
**的方法,可以直接使用
只需要将存储的key放置成特定值即可
// import Cookies from 'js-cookie'
const TokenKey = 'xiaoyou'
export function getToken() {
// return Cookies.get(TokenKey)
return localStorage.getItem(TokenKey)
}
export function setToken(token) {
// return Cookies.set(TokenKey, token)
return localStorage.setItem(TokenKey, token)
}
export function removeToken() {
// return Cookies.remove(TokenKey)
return localStorage.removeItem(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() // 表示执行成功了
}
})
})
}
除此之外,为了更好的让其他模块和组件更好的获取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
Token失效的主动介入
目标
: 处理当token失效时业务
主动介入token处理的业务逻辑
流程图转化代码 src/utils/auth.js
const timeKey = 'hrsaas-timestamp-key' // 设置一个独一无二的key
// 获取时间戳
export function getTimeStamp() {
return Cookies.get(timeKey)
}
// 设置时间戳
export function setTimeStamp() {
Cookies.set(timeKey, Date.now())
}
src/utils/request.js
import axios from 'axios'
import store from '@/store'
import router from '@/router'
import { Message } from 'element-ui'
import { getTimeStamp } from '@/utils/auth'
const TimeOut = 3600 // 定义超时时间
const service = axios.create({
// 当执行 npm run dev => .evn.development => /api => 跨域代理
baseURL: process.env.VUE_APP_BASE_API, // npm run dev => /api npm run build => /prod-api
timeout: 5000 // 设置超时时间
})
// 请求拦截器
service.interceptors.request.use(config => {
// config 是请求的配置信息
// 注入token
if (store.getters.token) {
// 只有在有token的情况下 才有必要去检查时间戳是否超时
if (IsCheckTimeOut()) {
// 如果它为true表示 过期了
// token没用了 因为超时了
store.dispatch('user/logout') // 登出操作
// 跳转到登录页
router.push('/login')
return Promise.reject(new Error('token超时了'))
}
config.headers['Authorization'] = store.getters.token
}
return config // 必须要返回的
}, error => {
return Promise.reject(error)
})
// 响应拦截器
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
})
// 是否超时
// 超时逻辑 (当前时间 - 缓存中的时间) 是否大于 时间差
function IsCheckTimeOut() {
var currentTime = Date.now() // 当前时间戳
var timeStamp = getTimeStamp() // 缓存时间戳
return (currentTime - timeStamp) / 1000 > TimeOut
}
export default service
注意
:我们在调用登录接口的时候 一定是没有token的,所以token检查不会影响登录接口的调用
在登录的时候,如果登录成功,我们应该设置时间戳
// 定义login action 也需要参数 调用action时 传递过来的参数
// async 标记的函数其实就是一个异步函数 -> 本质是还是 一个promise
async login(context, data) {
// 经过响应拦截器的处理之后 这里的result实际上就是 token
const result = await login(data) // 实际上就是一个promise result就是执行的结果
// axios默认给数据加了一层data
// 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
// 现在有用户token
// actions 修改state 必须通过mutations
context.commit('setToken', result)
// 写入时间戳
setTimeStamp() // 将当前的最新时间写入缓存
}
Token失效的被动处理
目标
: 实现token失效的被动处理
除了token的主动介入之外,我们还可以对token进行被动的处理,如图
token超时的错误码是**400
**
代码实现 src/utils/request.js
error => {
// error 信息 里面 response的对象
if (error.response && error.response.data && error.response.data.meta.status === 400) {
// 当等于400的时候 表示 后端告诉我token超时了
store.dispatch('user/logout') // 登出action 删除token
router.push('/login')
} else {
Message.error(error.message) // 提示错误信息
}
return Promise.reject(error)
}
更多推荐
所有评论(0)