目录
一、模板介绍
二、项目结构介绍
三、定制化修改
(一)关闭eslint 格式校验
(二)解决代理跨域问题
(三)替换为真实API接口
(四)修改axios二次封装
(五)优化store/modules/user.js
【知识点】:
1、【username.trim() 】去除 username 字符串中的前导和尾随空白字符。
2、在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
简洁版: https://github.com/PanJiaChen/vue-admin-template 我们用这个
加强版: https://github.com/PanJiaChen/vue-element-admin(1)克隆项目代码到本地:
git clone https://github.com/PanJiaChen/vue-admin-template.git
(2)进入项目目录:
cd vue-admin-template
(3)引入项目依赖:
npm install
(4)启动开发环境:
npm run dev
.
├── build/ # 构建脚本
├── mock/ # 模拟数据
├── public/ # 静态资源
│ ├── favicon.ico # 网站图标
│ └── index.html # 入口页面
├── src/ # 项目源码
│ ├── api/ # API 请求封装
│ ├── assets/ # 静态资源
│ ├── components/ # 公共组件
│ ├── directive/ # 自定义指令
│ ├── filters/ # 过滤器
│ ├── icons/ # 图标
│ ├── layout/ # 布局组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── styles/ # 全局样式
│ ├── utils/ # 工具库
│ ├── vendor/ # 第三方库
│ ├── views/ # 页面组件
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── .editorconfig # 编辑器配置
├── .env # 环境变量
├── .eslintrc.js # ESLint 配置
├── .gitignore # Git 忽略文件
├── babel.config.js # Babel 配置
├── package-lock.json # 依赖锁定文件
├── package.json # 项目依赖和配置
├── postcss.config.js # PostCSS 配置
├── README.md # 项目说明文档
├── vue.config.js # Vue CLI 配置
└── yarn.lock # 依赖锁定文件
参考《尚硅谷》项目
vue.config.js中将以下三项设置为false
// 是否在开发模式下保存文件时运行代码检查 // lintOnSave: process.env.NODE_ENV === 'development', //运行代码检查设置为false lintOnSave:false,
vue.config.js
// 开发服务器的选项
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
// 配置代理跨域
proxy: {
'/dev-api': {
target: 'http://gmall-h5-api.atguigu.cn',
pathRewrite: { '^/dev-api': '' }
},
},
//开启mock数据
after: require('./mock/mock-server.js')
src/api/user.js
// src/api/user.js
// 对外暴露登录的接口函数
export function login(data) {
return request({
url: 'dev-api/admin/acl/index/login',
method: 'post',
data
})
}
// 对外暴露获取用户信息的接口函数
export function getInfo(token) {
return request({
url: 'dev-api/admin/acl/index/info',
method: 'get',
params: { token }
})
}
// 对外暴露退出登录的接口函数
export function logout() {
return request({
url: 'dev-api/admin/acl/index/logout',
method: 'post'
})
}
(1)修改为: 如果自定义的响应码不是20000或200,就判断为错误
if (res.code !== 20000 && res.code !== 200)(2)将'X-Token' 改为token
// 如果有token就在请求头中加上token
config.headers['token'] = getToken()
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// 创建一个axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 接口的基础路径
timeout: 5000 // 请求超时时间
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 在请求发送前做一些处理
if (store.getters.token) {
// 如果有token就在请求头中加上token
config.headers['token'] = getToken()
}
return config
},
error => {
// 对请求错误做些什么
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
// 对响应数据做一些处理,这里只返回响应数据中的data部分
const res = response.data
// 如果自定义的响应码不是20000或200,就判断为错误
if (res.code !== 20000 && res.code !== 200) {
// 在页面上显示错误信息
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 以下是针对特定错误码的处理
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// 重新登录
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()
})
})
}
// 返回一个被拒绝的Promise对象,用来表示错误
return Promise.reject(new Error(res.message || 'Error'))
} else {
// 如果没有错误,就返回响应数据中的data部分
return res
}
},
error => {
// 对响应错误做些什么
console.log('err' + error)
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
原来代码为:
import { login, logout, getInfo } from '@/api/user' // 引入用户相关的 API
import { getToken, setToken, removeToken } from '@/utils/auth' // 引入 token 相关的工具函数
import { resetRouter } from '@/router' // 引入路由重置函数
const getDefaultState = () => {
return {
token: getToken(), // 获取本地存储的 token
name: '',
avatar: ''
}
}
const state = getDefaultState()
const mutations = {
// 重置状态
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
// 设置 token
SET_TOKEN: (state, token) => {
state.token = token
},
// 设置用户名
SET_NAME: (state, name) => {
state.name = name
},
// 设置头像
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
}
}
const actions = {
// 用户登录
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token) // 设置 token
setToken(data.token) // 存储 token
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { name, avatar } = data
commit('SET_NAME', name) // 设置用户名
commit('SET_AVATAR', avatar) // 设置头像
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// 用户退出登录
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // 移除 token
resetRouter() // 重置路由
commit('RESET_STATE') // 重置状态
resolve()
}).catch(error => {
reject(error)
})
})
},
// 重置 token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // 移除 token
commit('RESET_STATE') // 重置状态
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
(1)使用了async/await和try/catch语法,
(2)在 Vuex 的 action 中使用返回整个响应对象 替换 使用 {data} 来获取 axios 请求返回的响应数据。
【区别】
使用 {data} 来获取 axios 请求返回的响应数据对象中的 data 属性,只会返回响应数据对象中的 data 属性的值,而在 Vuex 的 action 中返回整个响应对象,则会返回包含响应数据、响应状态码、响应头等全部信息的完整的响应对象。
// 引入需要使用的函数和模块
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
// 定义获取默认状态的函数
const getDefaultState = () => {
return {
token: getToken(), // 使用 getToken 函数获取 token 值
name: '',
avatar: ''
}
}
// 定义 Vuex 模块的状态
const state = getDefaultState()
// 定义 Vuex 模块的变更操作
const mutations = {
// 重置状态为默认状态
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
// 设置 token
SET_TOKEN: (state, token) => {
state.token = token
},
// 设置用户名
SET_NAME: (state, name) => {
state.name = name
},
// 设置用户头像
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
}
}
// 定义 Vuex 模块的异步操作
const actions = {
// 用户登录
async login({ commit }, userInfo) {
const { username, password } = userInfo
try {
// 发送登录请求
const response = await login({ username: username.trim(), password: password }) // 发送登录请求
// 设置 token
commit('SET_TOKEN', response.data.token)
// 将 token 值保存在浏览器的本地存储中
setToken(response.data.token)
// 返回完整的响应对象
return response
} catch (error) {
return Promise.reject(error)
}
},
async getInfo({ commit, state }) {
try {
// 发送请求获取用户信息
const response = await getInfo(state.token)
// 如果响应数据不存在,说明验证失败
if (!response.data) {
throw new Error('验证失败,请重新登录。')
}
// 获取用户名和头像
const { name, avatar } = response.data
// 设置用户名
commit('SET_NAME', name)
// 设置用户头像
commit('SET_AVATAR', avatar)
// 返回完整的响应对象
return response
} catch (error) {
return Promise.reject(error)
}
},
// 用户注销
async logout({ commit, state }) {
try {
// 发送请求注销用户登录状态
await logout(state.token)
// 从本地存储中删除 token
removeToken()
// 重置路由
resetRouter()
// 重置状态
commit('RESET_STATE')
} catch (error) {
return Promise.reject(error)
}
},
// 重置 token 值
async resetToken({ commit }) {
try {
// 从本地存储中删除 token
removeToken()
// 重置状态
commit('RESET_STATE')
} catch (error) {
return Promise.reject(error)
}
}
}
// 导出 Vuex 模块
export default {
namespaced: true, // 开启命名空间
state,
mutations,
actions
}
【场景】有时候后端服务被部署在多个服务器上,且每个服务器的域名或 IP 地址都不相同,那么在前端项目中向这些不同的后端服务发起请求时就会遇到跨域问题。此时,可以通过配置多个代理来分别处理各个后端服务的跨域请求。
通过修改【
vue.config.js
】 文件来完成配置多个代理来实现跨域请求的设置:
// vue.config.js
// 配置代理跨域
proxy: {
// 关于用户的接口
'/dev-api': {
target: 'http://gmall-h5-api.atguigu.cn',
pathRewrite: { '^/dev-api': '' }
},
// 关于文件上传的接口
"/dev-upload": {
target: "http://loaclhost:8989",
pathRewrite: { "^/dev-upload": "" }
},
},
【方式一】:修改接口的基础路径 base:url+request,
// 创建一个axios实例
const service = axios.create({
baseURL: '/', // 接口的基础路径 base: url+request,
timeout: 5000 // 请求超时时间
})
修改user.js 把访问路径增加 'dev-api'
即: /admin/acl/index/login 改为 '/dev-api/admin/acl/index/login'
import request from '@/utils/request'
// http://39.98.123.211:8170/swagger-ui.html
export function login(data) {
return request({
url: '/dev-api/admin/acl/index/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/dev-api/admin/acl/index/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/dev-api/admin/acl/index/logout',
method: 'post'
})
}
这种方式需要每一个接口都加上对应的基础访问路径 '/dev-api/ 或 /dev-upload
【方式二】:使用配置不同的request.js 和 baseURL
(1)修改.env.development 和.env.production,增加 # upload api VUE_APP_UPLOAD_API = '/upload-api'
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
# upload api
VUE_APP_UPLOAD_API = '/upload-api'
(2)复制request.js 重命名为request-upload.js
(3)修改 request-upload.js 文件中 baseURL: process.env.VUE_APP_BASE_API 改为 baseURL: process.env.VUE_APP_UPLOAD_API。
(4)引入对应的 import request from '@/utils/request-upload'
//src/api/upload.js
// 当前这个模块:api进行统一管理
import request from '@/utils/request-upload'
export function uploadFiles(formData) {
return request({
url: '/uploadFiles',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
1、【username.trim() 】去除 username 字符串中的前导和尾随空白字符。
let result = await login({ username: username.trim(), password: password })
2、在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
(1)第一次登录的时候,前端调后端的登陆接口,发送用户名和密码;
(2)后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token;
(3)前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面;
(4)前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面;
(5)每次调后端接口,都要在请求头中加token;
(6)后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401;
(7)如果前端拿到状态码为401,就清除token信息并跳转到登录页面。