用Vue+elementUI构建一个SPA管理后台过程中的一些实战经验

2,230 阅读7分钟

用vue+elementUI重构了一个后台管理系统项目,我们也根据自己的业务场景,往vue官方脚手架中加了一些自己的东西,现在记录下整个系统的构建的一些经验和使用vue实战过程中的一些小技巧

首先当然是从零开始构建整个项目

  • 用vue-cli官方模板(叫“webpack”的那个模板)初始化整个项目,然后引入一些必要的dependence(element,axios)
A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
  • 用yarn构建。 之前用npm构建,经常因为一些插件的更新导致项目跑不起来(插件升级了,与插件配套的vue或者其他一些依赖没有升级)。采用yarn 主要是因为它能通过yarn.lock锁定依赖的版本号,用起来和npm基本一样。(现在最新版的npm也有lock功能)
  • 路由以及目录结构。 路由采用官方文档的多级嵌套路由形式,用起来非常的灵活。页面文件,一般可公用的组件我都会放在components文件夹下,其他的.vue文件按功能模块分别放在views下的不同文件夹下。
  • http模块axios封装,API层。 首先我自己写了一个HttpUtil.js文件对axios的get和post方法进行封装,这样的好处在于可以在请求的底层写一些共用函数,如发现接口返回没有权限,则跳转到登录页让用户重新登录,而且也让业务层代码更加简洁。
//HttpUtil.js
import router from '../router'
import axios from 'axios'
import Vue from 'vue'
var qs = require('qs')

const Utils = {
  get (url, data = {}) {
    return new Promise((resolve, reject) => {
      axios.get(url, {
        params: data,
        withCredentials: true
      })
      .then(function (response) {
        if (response.data.code === 1010) {
          console.log('没有权限')
          router.push({ path: '/login' })
        } else if (response.data.code === 0) {
          resolve(response)
        } else {
          new Vue().$message({
            message: response.data.message,
            type: 'error'
          })
        }
      })
      .catch(function (error) {
        reject()
        console.log(error)
      })
    })
  },
  post (url, data = {}, upload = false) {
    return new Promise((resolve, reject) => {
      axios.post(url, upload ? data : qs.stringify(data), {
        withCredentials: true,
        headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
      })
      .then(function (response) {
        if (response.data.code === 1010) {
          console.log('没有权限')
        } else if (response.data.code === 0) {
          // console.log(response)
          resolve(response)
        } else {
          console.log('接口返回状态码:' + response.data.code)
          new Vue().$message({
              message: response.data.message,
              type: 'error'
            })
        }
      })
      .catch(function (error) {
        reject()
        console.log(error)
      })
    })
  }

}

export default Utils
  • iconfont 的全局引入,全局过滤器的引入。 在main.js引入filter,在App.vue引入定义icon-font, 这样在项目的任何地方都可以使用iconfont和过滤器。(字体库推荐用阿里的iconfont,Iconfont-阿里巴巴矢量图标库
  • mock数据方案。API文件我采用了这样一种写法,所以我只需要改变config下的mock字段,整个 项目的请求就变成请求mock文件夹下的json文件
/**
   *  获取加密RSA PublicKey
   * @return {[type]} [description]
   */
  getRSAPublicKey (params) {
    let postUrl = Config.mock ? 'static/mock/getKey.json' : process.env.DOMAIN + '/bshop/login/key/get'
    return HttpUtil.get(postUrl, params)
  }

实战小技巧

  • 项目性能体积优化。首先用脚手架自带的analyze去分析,在package.json的scripts下配置如下
"analyz": "NODE_ENV=production npm_config_report=true npm run build"

然后跑npm run analyz ,就可以看到项目的一个体积分析,类似这个图,你就可以针对性的去进行优化。

这里介绍两个体积优化的点儿:1.element组件按需引入,2.路由懒加载。对于第一点,饿了么官方文档里有很详尽的介绍,不提了。第二点路由懒加载,你可以在上图中发现项目打包后会变成vendor.js,0.xxxx.js,1.xxxx.js,2.xxxx.js ,每一个路由都会变成一个文件,项目在打开对应路由对应组件时候,才会加载对应的js(恩,这就是我想要的滑板鞋)。实现方式其实很简单,在路由文件中用这样的方式定义

const UserIndex = resolve => require(['@/components/UserIndex'], resolve)

以上就是vue-router官方建议的,结合 Vue 的 异步组件 和 Webpack 的 code splitting feature来实现路由组件的懒加载的简单方法。

  • 前端权限实现。 整个项目的权限分两种:按钮权限和Tab权限。实现思路很简单:1.和后台约定好每种权限对应的code,如'AB'。2.登录成功的时候在sessionStroage存储权限信息。(如果用localStorage【能多标签通信】,一台电脑登录两个账号的时候,后一个账号会覆盖前一个账号的权限)。3.写一个公用函数,传入一个参数code,返回该权限是否在权限数组里。4.按钮权限直接用v-if实现,tab权限在mouted的时候进行初始化。 综上四点,基本实现思想是这个样子,具体的实现根据每个项目结构的不同都会不一样(一级路由权限,二级路由权限),我自己具体实现起来还是蛮复杂的,不具有普适性,暂不赘述,但有几个场景大家也是都需要考虑到的:1.刷新页面获取最新权限。2.当前页面没有权限,则从当前页面跳走。3.若二级菜单下第一个二级tab选项被删除,则点击该项对应的一级菜单,则应该跳转到该一级菜单下面的第一个可用二级tab(原第二个二级tab)。4.每次刷新页面时候,菜单不能有很明显的抖动。(先根据sessionStroage存储的权限初始化一遍页面,再通过最新请求到的权限初始化页面)
  • 巧用vuex进行数据提前加载。 有一个详情页面,一堆选择框是需要从后台读取数据的(而且这个接口返回速度较慢),这个时候可以借助vuex在这个页面之前的一个必经页面提前进行数据加载。由于vuex computed 取到的数据还是响应式的,你不好去改变它,这个时候可以通过JSON.stringify 和 JSON.parse 把 响应式数据变换成为正常数组。
  • 默认头像存放位置。 我们都知道,webpack的url-loader中,会处理把asset目录下小于10kb的图片都转换成base64编码。
<!-- 报错  -->
<img :src="headPic | 'asset/img/default.jpg'">

上面这段代码会报错,动态图或会默认头像 我都会放在 static目录下

<img :src="headPic | '/static/img/default.jpg'">
<img :src="'/static/img/month'+ month +'.jpg'">
<!-- 当然这样强行放在asset下也没有任何问题,就代码编程了两行  -->
<img v-if="headPic" :src="headPic">
<img v-else="headPic" src="asset/img/default.jpg">
  • 通过 import 'es6-promise/auto' 来解决在360兼容模式 ie10 不兼容bug
  • this.$route.query.xxxx 路由取值记得用 parseInt处理下 .(路由跳转过来 ,取到的是int,刷新页面取到的是 字符串)
  • 许多要在页面渲染后出发的函数记得用nexTick,比如el-checkbox-group的viewModel,你在数据初始化好后(页面初始化好之前)去设置它的值,它会报错。
Vue.nextTick(() => {
      this.selectCountries()
      this.disableCountries()
 })
  • 在标签中要用到对象下的属性时候,记得要写v-if判断。(不然初始化products为空的时候,就会报一个错)
<div v-if="products" class="len-tip">{{products.length}}/500</div>
  • 用watch 实现搜索框 延时实时搜索效果(最后一个输入500毫秒内如果没有按键,则发出请求),vue2.x 取消了debounce,建议我们使用lodash之类的库,我看了下团队同学用下面这个蛮简洁的方法实现了它。
      watch: {
            searchInfo () {
              let oldSearchInfo = this.searchInfo
              setTimeout(() => {
                if (this.searchInfo === oldSearchInfo) {
                  // 请求数据
                }
              }, 500)
            }
          }
  • 像素级页面开发插件 PerfectPixel。 由于公司对页面UI要求非常严格,找到了这个插件,能把设计图贴在网页上,切页面的时候,调节贴图的透明度,让页面和设计图完整的贴合,这样就完全没有问题了。
  • 父子路由mounted顺序。当前vue===> 一级路由===》二级路由

最后

双休日应女朋友的要求来咖啡厅陪她,就趁机写个文章总结下之前一段时间的工作。本文所有的实现方式并不是解决问题的最佳实现,如果你有更好的方式,欢迎在下方评论区给我留言,我们大家一起讨论,一起进步~