搭建基础vue后台项目
使用vue2.x搭建基础后台系统
使用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
(一)封装axios
在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': ''//重写路径
}
}
}
}
}
二、使用Elementui
(一)安装
npm i element-ui -S
(二)引入Element
完整引入,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版本)。两种方式:
- 安装https://router.vuejs.org/zh/installation.html
npm install vue-router
- 由于我有一个正在使用 Vue CLI (opens new window)的项目,我就可以以项目插件的形式添加 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文件,导入各个组件,比如:上方导航栏,侧边栏,内容区。
<template>
<el-container>
<el-aside width="210px" style="background-color:#304156;">
<SiderBar />
</el-aside>
<el-container>
<el-header>
<HeadBar />
</el-header>
<el-main>
<AppMain />
</el-main>
</el-container>
</el-container>
</template>
<script>
import { HeadBar, SiderBar, AppMain } from "./components";
export default {
components: {
HeadBar,
SiderBar,
AppMain
}
}
</script>
<style>
.el-header {
border-bottom: 1px solid #f3f4f5;
}
.el-aside {
text-align: center;
}
.el-main {
color: #333;
}
body > .el-container {
margin-bottom: 40px;
}
</style>
(二)、配置路由
第一步:定义路由
说明:
- 在路由中进行了 路由懒加载 的配置:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
- 使用了
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')
第三步:配置App.vue
<template>
<div id="app">
<!-- 此处是重点 -->
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
body {
margin: 0;
padding: 0;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>
(三)、登录
登录界面在此略去,仅记录关键步骤。
在成功登录后,进行存储信息和跳转的操作。
......
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
<template>
<div class="menu-wrapper">
<template v-for="item in routes" v-if="!item.hidden && item.children">
<!-- 把user过滤了 -->
<router-link
v-if="hasOneShowingChildren(item.children) && !item.children[0].children"
:to="item.path + '/' + item.children[0].path"
:key="item.children[0].name"
>
<el-menu-item
:index="item.path + '/' + item.children[0].path"
>
<!-- <svg-icon
v-if="item.children[0].meta && item.children[0].meta.icon"
:icon-class="item.children[0].meta.icon"
></svg-icon>-->
<span
v-if="item.children[0].meta && item.children[0].meta.title"
slot="title"
>{{ item.children[0].meta.title }}</span>
</el-menu-item>
</router-link>
<el-submenu v-else :index="item.name || item.path" :key="item.name">
<!-- 显示一级菜单 -->
<template slot="title">
<!-- <svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon"></svg-icon> -->
<span v-if="item.meta && item.meta.title" slot="title">{{ item.meta.title }}</span>
</template>
<template v-for="child in item.children" v-if="!child.hidden">
<!-- 判断是否有三级菜单 -->
<sidebar-item
:is-nest="true"
class="nest-menu"
v-if="child.children && child.children.length > 0"
:routes="[child]"
:key="child.path"
></sidebar-item>
<router-link v-else :to="item.path + '/' + child.path" :key="child.name">
<el-menu-item :index="item.path + '/' + child.path" class="test">
<!-- <svg-icon v-if="child.meta && child.meta.icon" :icon-class="hild.meta.icon"></svg-icon> -->
<span v-if="child.meta && child.meta.title" slot="title">{{ child.meta.title }}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: 'SidebarItem',
data() {
return {
console: window.console
}
},
props: {
routes: {
type: Array
}
},
methods: {
hasOneShowingChildren(children) {
const showingChildren = children.filter(item => {
return !item.hidden
})
// 如果长度为一说明没有二级路由
if (showingChildren.length === 1) {
return true
}
return false
}
}
}
</script>
<style scoped>
.menu-wrapper a {
text-decoration: none;
}
</style>
<style>
.test {
padding-left: 60px !important;
background-color: #1f2d3d !important;
}
.el-menu-item.is-active { color: #6681FA !important; background-color: #EAEEFF !important; }
</style>
四、优化
(一)简化引用路径:
根目录新建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'));
}
}
(二)关闭vue代码检查
根目录新建.eslintignore
文件
src
(三)项目中遇到的问题
2、布局自带间距
解决方法:在App.vue中
<style>
body {
margin: 0;
padding: 0;
}
</style>
(四) 无权限无法访问页面
在项目中,虽然根据用户权限显示对应权限展示的侧边栏,但如果这时候某用户知道某页面地址,在地址栏直接输入地址进行访问,就存在一定的风险性,要做的就是通过路由拦截做拦截判断。
解决方法: 在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官方文档》
5、《手把手教你搞定权限管理,结合Vue实现菜单的动态权限控制!》
6、以及各个插件的官方网址已分布在文章内。
更多推荐
所有评论(0)