使用vue 2.x版本以及其他技术,搭建的简易vue后台项目,能实现后台的基础功能。
仍在学习中,难免会有错误,如有问题,请多指教。
查看vue cli版本:
vue --version
创建一个项目:
vue create xxx
目录 | 简介 |
---|---|
api | 存放api |
assets | 存放静态资源 |
Layout | 存放公共组件 |
router | 路由 |
utils | 全局公用方法 |
views | 页面 |
App.vue | 入口页面 |
main.js | 入口,加载组件,初始化等 |
如果想调整webpack
配置,可以在vue.config.js
中进行配置。官方文档:https://cli.vuejs.org/zh/guide/webpack.html
在utils
文件夹下的request.js
中进行封装,最后导出配置,方便下一步封装请求。
此处涉及到的知识点
// 封装axios
import axios from "axios";
// 创建axios实例
const service = axios.create({
timeout: 120000,
});
// 添加请求拦截器
service.interceptors.request.use((config) => {
// 在发送请求前做些什么
// 比如把token放入请求头
// 登录时就已经把token存到了cookie中
// const token = cookie.get("token");
// if (token) {
// config.headers.authorization = token;
// }
return config;
}, (error) => {
// 对请求错误做些什么
console.log(error);
return Promise.reject(error);
});
// 添加响应拦截器
service.interceptors.response.use((response) => {
// 2xx 范围内的状态码都会触发该函数
// 对响应数据做点什么
return response;
}, (error) => {
// 超出 2xx范围的状态码都会触发该函数
// 对响应错误做点什么
console.log(error);
if (error && error.response) {
switch (error.response.status) {
case 302: this.$message('接口重定向了!'); break;
case 400: this.$message('参数不正确!'); break;
case 401:
this.$message({
message: '登录过期,请重新登录',
type: 'warning'
});
break;
case 403: this.$message('您没有权限操作!'); break;
case 404: this.$message('请求地址出错!'); break; // 在正确域名下
case 408: this.$message('请求超时!'); break;
case 409: this.$message('系统已存在相同数据!'); break;
case 500: this.$message('服务器内部错误!'); break;
case 501: this.$message('服务未实现!'); break;
case 502: this.$message('网关错误!'); break;
case 503: this.$message('服务不可用!'); break;
case 504: this.$message('服务暂时无法访问,请稍后再试!'); break;
case 505: this.$message('HTTP版本不受支持!'); break;
default: this.$message('异常问题,请联系管理员!'); break
}
}
return Promise.reject(error);
})
export default service;
在utils
文件夹下http.js
文件中,进行封装请求。
需要安装 qs 库,qs
是一个增加了一些安全性的查询字符串解析和序列化字符串的库。官方文档:
import service from './request';
import qs from 'qs';
const _post = (api, data, headers = {}) => {
return new Promise((resolve, reject) => {
service.post(api, data, { headers })
.then(res => { resolve(res) })
.catch(error => { reject(error) })
})
}
const post = (api, data, headers = {}) => {
headers['Content-Type'] = 'application/x-www-form-urlencoded'
// qs.stringify()作用是将对象或者数组序列化成URL的格式
return _post(api, qs.stringify(data), headers);
}
const postJson = (api, data, headers = {}) => {
headers['Content-Type'] = 'application/json;charset=utf-8'
return _post(api, JSON.stringify(data), headers)
}
const postFormData = (api, data, headers = {}) => {
headers['Content-Type'] = 'multipart/form-data'
return _post(api, data, headers)
}
const get = (api, params = {}, headers = {}) => {
return new Promise((resolve, reject) => {
service.get(api, { params, headers })
.then(res => {
resolve(res)
})
.catch(error => {
reject(error)
})
})
}
export default { post, postJson, postFormData, get }
参考:《Vue Cli官方文档-devServer.proxy》
修改vue.config.js文件
module.exports = {
devServer: {
proxy: {
'^/api': {
target: 'http://xxx.xxx.x.xx:xx/',//接口的前缀
ws: true,//代理websocked
changeOrigin: true,//虚拟的站点需要更管origin
pathRewrite: {
'^/api': ''//重写路径
}
}
}
}
}
npm i element-ui -S
完整引入,main.js
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
安装vue-router
(我用的3.x版本)。两种方式:
npm install vue-router
vue add router
说明 :如果是添加,会有如下的变化。
增加router文件夹,包含index.js;
增加views文件夹,包含About.vue和Home.vue文件;
App.vue文件被修改;
main.js文件被修改;
在配置路由之前,先搭建公共布局。
在components文件夹下,新建index.js文件,用于将多个组件共同导出:
export {default as HeadBar} from './header.vue';
export {default as SiderBar} from './SideBar/index.vue';
export {default as AppMain} from './main.vue';
在Layout文件夹下,新建layout.vue文件,导入各个组件,比如:上方导航栏,侧边栏,内容区。
说明:
vue-router
中的 命名视图 ,使用公共布局,具体在后面说明。// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '../Layout/layout.vue';
// 懒加载
const Login = () => import('../views/Login/index.vue');
//使用路由插件
Vue.use(VueRouter)
// 必须要显示的静态路由表
const routes = [
{
path: '/login',
component: Login,
hidden: true
},
// 只有一级菜单的路由
{
path: '',
// 此处使用了 命名视图
component: Layout, // 全局统一的布局文件
children: [{
path: '',
name: 'home',
// 懒加载
component: () => import('../views/Home.vue'),
meta: { title: '首页' }
}]
},
// 有二级菜单的路由
{
path: '/user',
component: Layout,
name: 'user',
meta: { title: '用户管理' },
children: [
{
path: 'list',
name: 'list',
component: () => import('../views/About.vue'),
meta: { title: '用户列表' }
},
{
path: 'test1',
name: 'test1',
component: () => import('../views/About.vue'),
meta: { title: '用户测试1' }
}
]
},
]
// 实例化
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
在 main.js
中,
import Vue from 'vue';
import App from './App.vue'
import router from './router'
new Vue({
// 挂载
router,
render: h => h(App)
}).$mount('#app')
登录界面在此略去,仅记录关键步骤。
在成功登录后,进行存储信息和跳转的操作。
......
if(res.data.code === 200){
localStorage.setItem('userInfo', res.data.userInfo);
this.$router.push({ path: '/' });
}
......
此处使用了vue-router
中的 [导航守卫](导航守卫 | Vue Router (vuejs.org)) 。
在没有登录的情况下,是不可以在地址栏通过输入地址进行访问其他页面,所以,必须加上路由鉴权,需要拿到cookie中的数据进行验证后,才可以放行。
使用 router.beforeEach
注册一个全局前置守卫:
// router.js
......
// 实例化
const router = new VueRouter({
......
})
router.beforeEach((to, from, next) => {
// to:即将要进入的目标 路由对象(现在的页面)
// from:当前导航正要离开的路由(上一步的页面)
// next:
if (localStorage.getItem('userInfo')) {
next();
} else {
if (to.path === '/login') {
next();
} else {
next('/login');
}
}
})
export default router
侧边栏:根据路由动态显示侧边栏。
主要是通过 this.$router.options.routes
获取到路由,通过 this.$route.path
获取到当前路由。
// Layout/components/SideBar/index.vue
<template>
<div>
<h1 style="color: white;">Logo</h1>
<el-menu
mode="vertical"
:unique-opened="true"
:collapse-transition="false"
background-color="#304156"
:default-active="activeMenu"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item :routes="routes" />
</el-menu>
</div>
</template>
<script>
import SidebarItem from './sidebarItem.vue'
export default {
components: {
SidebarItem
},
computed: {
activeMenu() {
return this.$route.path
},
routes() {
return this.$router.options.routes
}
}
}
</script>
<style scoped>
.el-menu {
border-right: none;
}
</style>
// sidebarItem.vue
根目录新建vue.config.js
文件
const path = require('path');
const resolve = (dir) => {
return path.join(__dirname, dir);
}
module.exports = {
// 允许对内部的 webpack 配置进行更细粒度的修改
chainWebpack: (config) => {
config.resolve.alias
.set('@', resolve('src'))
.set('components', resolve('src/components'));
}
}
根目录新建.eslintignore
文件
src
1、vue项目中出现Invalid Host header
2、布局自带间距
解决方法:在App.vue中
在项目中,虽然根据用户权限显示对应权限展示的侧边栏,但如果这时候某用户知道某页面地址,在地址栏直接输入地址进行访问,就存在一定的风险性,要做的就是通过路由拦截做拦截判断。
解决方法: 在router文件夹下index.js中:
......
{
path: '',
component: Layout,
children: [{
path: 'user',
name: 'User',
component: () => import('../views/User/index.vue'),
// 重点:加入一个参数:requiresAuth
meta: { title: '用户管理', requiresAuth: true }
}]
},
......
紧接着在导航守卫时,进行判断:
router.beforeEach((to, from, next) => {
let result = JSON.parse(localStorage.getItem('userInfo'));
let role = result.is_admin === 0 ? false : true;
if (localStorage.getItem('userInfo')) {
// 此处根据在路由中添加的meta.requiresAuth属性
// 若访问的页面中有这属性,那么当用户直接访问该页面时,会进入此项判断
if (to.matched.some(res => res.meta.requiresAuth)) {
// 判断是否有权限,有的话放行。
if (role) {
next();
} else {
// 没有权限则跳回首页
next('/');
}
} else {
// 没有对应属性,则直接放行
next();
}
} else {
if (to.path === '/login') {
next();
} else {
next('/login');
}
}
})
1、《vue官方文档》
2、《vue-router官方文档》
3、《Elementui官方文档》
4、《手摸手,带你用vue撸后台 系列一(基础篇)》
5、《手把手教你搞定权限管理,结合Vue实现菜单的动态权限控制!》
6、以及各个插件的官方网址已分布在文章内。