使用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 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>

(二)、配置路由

第一步:定义路由

说明:

  1. 在路由中进行了 路由懒加载 的配置:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
  2. 使用了 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

(三)项目中遇到的问题

1、vue项目中出现Invalid Host header

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官方文档

4、《手摸手,带你用vue撸后台 系列一(基础篇)

5、《手把手教你搞定权限管理,结合Vue实现菜单的动态权限控制!

6、以及各个插件的官方网址已分布在文章内。

Logo

前往低代码交流专区

更多推荐