使用 Vue 开发 scrollbar 滚动条组件

Vue 应该说是很火的一款前端库了,和 React 一样的高热度,今天就来用它写一个轻量的滚动条组件;

知识储备:要开发滚动条组件,需要知道知识点是如何计算滚动条的大小和位置,还有一个问题是如何监听容器大小的改变,然后更新滚动条的位置;

先把样式贴出来:

/*禁用选择文本*/
.disable-selection {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
/*object 触发器的样式*/
.resize-trigger {
  position: absolute;
  display: block;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
  opacity: 0;
}

.scrollbar-container {
  position: relative;
  overflow: hidden !important;
  width: 100%;
  height: 100%;
}

.scrollbar-box {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1;
}

.scrollbar-box.scrollbar-box-vertical {
  width: 12px;
  top: 0;
}

.scrollbar-box.scrollbar-box-horizontal {
  height: 12px;
  left: 0;
}

.cssui-scrollbar--s .scrollbar-box.scrollbar-box-vertical {
  width: 6px;
}

.cssui-scrollbar--s .scrollbar-box.scrollbar-box-horizontal {
  height: 6px;
}

.scrollbar-box .scrollbar-thumb {
  position: relative;
  display: block;
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.2);
  transform: translate3d(0, 0, 0);
}

.scrollbar-box .scrollbar-thumb:hover,
.scrollbar-box .scrollbar-thumb:active {
  background-color: rgba(0, 0, 0, 0.3);
}

.scrollbar-box.scrollbar-box-vertical .scrollbar-thumb {
  width: 100%;
}

.scrollbar-box.scrollbar-box-horizontal .scrollbar-thumb {
  height: 100%;
}

.scrollbar-container .scrollbar-view {
  width: 100%;
  height: 100%;
  transform: translate3d(0, 0, 0);
  -webkit-overflow-scrolling: touch;
}

.scrollbar-container .scrollbar-view-x {
  overflow-x: scroll!important;
}

.scrollbar-container .scrollbar-view-y {
  overflow-y: scroll!important;
}

.scrollbar-container.scrollbar-autoshow .scrollbar-box {
  opacity: 0;
  transition: opacity 120ms ease-out;
}

.scrollbar-container.scrollbar-autoshow:hover > .scrollbar-box,
.scrollbar-container.scrollbar-autoshow:active > .scrollbar-box,
.scrollbar-container.scrollbar-autoshow:focus > .scrollbar-box {
  opacity: 1;
  transition: opacity 340ms ease-out;
}

然后,把模板贴出来:

<template>
  <div
    :style="containerStyle"
    :class="containerClass"
  >
    <div
      ref="scrollEl"
      :style="scrollStyle"
      :class="scrollClass"
      @scroll.stop.prevent="scrollHandler"
    >
      <div
        ref="contentEl"
        v-resize="resizeHandle"
      >
        <slot />
      </div>
    </div>
    <div
      v-if="yBarShow"
      ref="vertical"
      class="scrollbar-box scrollbar-box-vertical"
      @mousedown="verticalHandler"
    >
      <div
        ref="verticalBar"
        :style="yBarStyle"
        class="scrollbar-thumb"
        @mousedown="verticalBarHandler"
      />
    </div>
    <div
      v-if="xBarShow"
      ref="horizontal"
      class="scrollbar-box scrollbar-box-horizontal"
      @mousedown="horizontalHandler"
    >
      <div
        ref="horizontalBar"
        :style="xBarStyle"
        class="scrollbar-thumb"
        @mousedown="horizontalBarHandler"
      />
    </div>
  </div>
</template>

上面的代码中,我用到了 v-resize 这个指令,这个指令就是封装容器大小改变时,向外触发事件的,看到网上有通过 MutationObserver 来监听的,这个问题是监听所有的属性变化,好像还有兼容问题,还有一种方案是用 GitHub 的这个库:resize-observer-polyfill,上面的这些方法都可以,我也是尝试了一下,但我觉得始终是有点小题大做了,不如下面这个方法好,就是创建一个看不见的 object 对象,然后使它的绝对定位,相对于滚动父容器,和滚动条容器的大小保持一致,监听 object 里面 window 对象的 resize 事件,这样就可以做到实时响应高度变化了,贴上代码:

import Vue from ‘vue‘;
import { throttle, isFunction } from ‘lodash‘;

Vue.directive(‘resize‘, {
  inserted(el, { value: handle }) {
    if (!isFunction(handle)) { return; }

    const aimEl = el;
    const resizer = document.createElement(‘object‘);

    resizer.type = ‘text/html‘;
    resizer.data = ‘about:blank‘;
    resizer.setAttribute(‘tabindex‘, ‘-1‘);
    resizer.setAttribute(‘class‘, ‘resize-trigger‘);
    resizer.onload = () => {
      const win = resizer.contentDocument.defaultView;
      win.addEventListener(‘resize‘, throttle(() => {
         const rect = el.getBoundingClientRect();
          handle(rect);
      }, 500));
    };

    aimEl.style.position = ‘relative‘;
    aimEl.appendChild(resizer);
    aimEl.resizer = resizer;
  },

  unbind(el) {
    const aimEl = el;

    if (aimEl.resizer) {
      aimEl.style.position = ‘‘;
      aimEl.removeChild(aimEl.resizer);
      delete aimEl.resizer;
    }
  },
});

下面是 js 功能的部分,代码还是不少,有一些方法做了节流处理,用了一些 lodash 的方法,主要还是上面提到的滚动条计算的原理,大小的计算,具体看  toUpdate  这个方法,位置的计算,主要是 horizontalHandler,verticalHandler,实际滚动距离的计算,看mouseMoveHandler 这个方法:

import {trim, delay, round, throttle } from "lodash";

// ------------------------------------------------------------------------------

// 检测 class
const hasClass = (el = null, cls = ‘‘) => {
  if (!el || !cls) { return false; }
  if (cls.indexOf(‘ ‘) !== -1) { throw new Error(‘className should not contain space.‘); }
  if (el.classList) { return el.classList.contains(cls); }
  return ` ${el.className} `.indexOf(` ${cls} `) > -1;
};

// ------------------------------------------------------------------------------

// 添加 class
const addClass = (element = null, cls = ‘‘) => {
  const el = element;
  if (!el) { return; }
  let curClass = el.className;
  const classes = cls.split(‘ ‘);

  for (let i = 0, j = classes.length; i < j; i += 1) {
    const clsName = classes[i];
    if (!clsName) { continue; }

    if (el.classList) {
      el.classList.add(clsName);
    } else if (!hasClass(el, clsName)) {
      curClass += ‘ ‘ + clsName;
    }
  }
  if (!el.classList) {
    el.className = curClass;
  }
};

// ------------------------------------------------------------------------------

// 删除 class
const removeClass = (element, cls) => {
  const el = element;
  if (!el || !cls) { return; }
  const classes = cls.split(‘ ‘);
  let curClass = ` ${el.className} `;

  for (let i = 0, j = classes.length; i < j; i += 1) {
    const clsName = classes[i];
    if (!clsName) { continue; }

    if (el.classList) {
      el.classList.remove(clsName);
    } else if (hasClass(el, clsName)) {
      curClass = curClass.replace(` ${clsName} `, ‘ ‘);
    }
  }
  if (!el.classList) {
    el.className = trim(curClass);
  }
};

// ------------------------------------------------------------------------------

// 获取滚动条宽度
let scrollWidth = 0;
const getScrollWidth = () => {
  if (scrollWidth > 0) { return scrollWidth; }

  const block = document.createElement(‘div‘);
  block.style.cssText = ‘position:absolute;top:-1000px;width:100px;height:100px;overflow-y:scroll;‘;
  document.body.appendChild(block);
  const { clientWidth, offsetWidth } = block;
  document.body.removeChild(block);
  scrollWidth = offsetWidth - clientWidth;

  return scrollWidth;
};

// scrollSize 值
const SCROLLBARSIZE = getScrollWidth();

/**
 * UiScrollbar Component
 * @author zhangmao 19/4/3
 */
export default {
  name: ‘UiScrollbar‘,

  props: {
    size: { type: String, default: ‘normal‘ }, // small
    // 主要是为了解决在 dropdown 隐藏的情况下无法获取当前容器的真实 width height 的问题
    show: { type: Boolean, default: false },
    width: { type: Number, default: 0 },
    height: { type: Number, default: 0 },
    maxWidth: { type: Number, default: 0 },
    maxHeight: { type: Number, default: 0 },
  },

  data() {
    return {
      prevPageX: 0, // 缓存的鼠标横向位置
      prevPageY: 0, // 缓存的鼠标垂直位置
      cursorDown: false, // 鼠标拖拽标记
      minBarSize: 5, // 滚动条的最小快读和高度
      xScroll: 0, // 当前滚动条的横向位置
      yScroll: 0, // 当前滚动条的垂直位置
      realWidth: 0, // 内容的真实宽度
      realHeight: 0, // 内容的真实高度
      xBarWidth: 0, // 水平滚动条发宽度
      yBarHeight: 0, // 垂直滚动条的高度
      xBarLastWidth: 0, // 水平滚动条的最终宽度
      yBarLastHeight: 0, // 垂直滚动条最终的高度
      containerWidth: 0, // 容器的宽度
      containerHeight: 0, // 容器的高度
      scrollWidth: 0, // 滚动容器的宽度
      scrollHeight: 0, // 滚动容器的高度
      scrollTopMax: 0, // 垂直最大滚动距离限制
      scrollLeftMax: 0, // 水平最大滚动距离限制
      trackTopMax: 0, // 垂直步长最大限制
      trackLeftMax: 0, // 水平步长最大限制
    };
  },

  computed: {
    yBarShow() { return this.getYBarShow(); },
    xBarShow() { return this.getXBarShow(); },
    yBarStyle() {
      return {
        height: `${this.yBarLastHeight}px`,
        msTransform: `translateY(${this.yScroll}px)`,
        webkitTransform: `translate3d(0, ${this.yScroll}px, 0)`,
        transform: `translate3d(0, ${this.yScroll}px, 0)`,
      };
    },
    xBarStyle() {
      return {
        width: `${this.xBarLastWidth}px`,
        msTransform: `translateX(0, ${this.xScroll}px, 0)`,
        webkitTransform: `translate3d(${this.xScroll}px, 0, 0)`,
        transform: `translate3d(${this.xScroll}px, 0, 0)`,
      };
    },
    scrollClass() {
      return [‘scrollbar-view‘, {
        ‘scrollbar-view-x‘: this.xBarShow,
        ‘scrollbar-view-y‘: this.yBarShow,
      }];
    },
    scrollStyle() {
      // 注意这里是相反的
      const hasWidth = this.yBarShow || this.realWidth > this.containerWidth;
      const hasHeight = this.xBarShow || this.realHeight > this.containerHeight;
      return {
        width: hasWidth && this.scrollWidth > 0 ? `${this.scrollWidth}px` : ‘‘,
        height: hasHeight && this.scrollHeight > 0 ? `${this.scrollHeight}px` : ‘‘,
      };
    },
    containerClass() {
      return [‘scrollbar-container scrollbar-autoshow‘, {
        ‘cssui-scrollbar--s‘: this.size === ‘small‘,
      }];
    },
    containerStyle() {
      if (this.xBarShow || this.yBarShow) {
        return {
          width: this.containerWidth > 0 ? `${this.containerWidth}px` : ‘‘,
          height: this.containerHeight > 0 ? `${this.containerHeight}px` : ‘‘,
        };
      }
      return {};
    },
  },

  watch: {
    show: ‘showChange‘,
    width: ‘initail‘,
    height: ‘initail‘,
    maxWidth: ‘initail‘,
    maxHeight: ‘initail‘,
  },

  created() {
    this.dftData();
    this.initEvent();
  },

  mounted() { this.delayInit(); },

  methods: {

    // ------------------------------------------------------------------------------

    // 外部调用方法
    scrollX(x) { this.$refs.scrollEl.scrollLeft = x; },
    scrollY(y) { this.$refs.scrollEl.scrollTop = y; },
    scrollTop() { this.$refs.scrollEl.scrollTop = 0; },
    scrollBottom() { this.$refs.scrollEl.scrollTop = this.$refs.contentEl.offsetHeight; },

    // ------------------------------------------------------------------------------

    // 默认隐藏 异步展示的情况
    showChange(val) { if (val) { this.delayInit(); } },

    // ------------------------------------------------------------------------------

    delayInit() {
      this.$nextTick(() => { delay(() => { this.initail(); }, 10); });
    },

    // ------------------------------------------------------------------------------

    // 检测是否需要展示垂直的滚动条
    getYBarShow() {
      if (this.height > 0) { return this.realHeight > this.height; }
      if (this.maxHeight > 0) { return this.realHeight > this.maxHeight; }
      return this.realHeight > this.containerHeight;
    },

    // ------------------------------------------------------------------------------

    // 检测是否需要展示横向的滚动条
    getXBarShow() {
      if (this.width > 0) { return this.realWidth > this.width; }
      if (this.maxWidth > 0) { return this.realWidth > this.maxWidth; }
      return this.realWidth > this.containerWidth;
    },

    // ------------------------------------------------------------------------------

    // 内容大小改变
    resizeHandle({ width, height }) {
      this.realWidth = width;
      this.realHeight = height;
      this.delayInit();
    },

    // ------------------------------------------------------------------------------

    // 设置容器大小 初始化滚动条位置
    initail() {
      this.setContainerSize();
      this.setScrollSize();
      this.setContentSize();
      this.toUpdate();
    },

    // ------------------------------------------------------------------------------

    // 设置整个容器的大小
    setContainerSize() {
      const { offsetWidth = 0, offsetHeight = 0 } = this.$el;
      this.containerHeight = this.height || this.maxHeight || offsetHeight;
      this.containerWidth = this.width || this.maxWidth || offsetWidth;
    },

    // ------------------------------------------------------------------------------

    // 设置滚动容器的大小
    setScrollSize() {
      this.scrollWidth = this.containerWidth + SCROLLBARSIZE;
      this.scrollHeight = this.containerHeight + SCROLLBARSIZE;
    },

    // ------------------------------------------------------------------------------

    // 设置内容区域的大小
    setContentSize() {
      if (this.$refs.contentEl) {
        const { offsetWidth = 0, offsetHeight = 0 } = this.$refs.contentEl;
        this.realWidth = offsetWidth;
        this.realHeight = offsetHeight;
      }
    },

    // ------------------------------------------------------------------------------

    // 更新滚动条相关的大小位置
    toUpdate() {
      if (this.realWidth > 0) {
        // 水平滚动条的宽度
        this.xBarWidth = round(this.containerWidth / this.realWidth * this.containerWidth);
        this.scrollLeftMax = this.realWidth - this.containerWidth;
      }

      if (this.realHeight > 0) {
        // 垂直方向滚动条的高度
        this.yBarHeight = round(this.containerHeight / this.realHeight * this.containerHeight);
        this.scrollTopMax = this.realHeight - this.containerHeight;
      }

      // 设置滚动条最终的大小
      this.xBarLastWidth = Math.max(this.xBarWidth, this.minBarSize);
      this.yBarLastHeight = Math.max(this.yBarHeight, this.minBarSize);

      this.trackTopMax = this.containerHeight - this.yBarLastHeight;
      this.trackLeftMax = this.containerWidth - this.xBarLastWidth;

      this.scrollHandler();
    },

    // ------------------------------------------------------------------------------

    scrollHandler() {
      if (this.$refs.scrollEl) {
        const {
          scrollLeft = 0,
          scrollTop = 0,
          clientHeight = 0,
          scrollHeight = 0,
          clientWidth = 0,
          scrollWidth = 0,
        } = this.$refs.scrollEl;

        this.xScroll = round(scrollLeft * this.trackLeftMax / this.scrollLeftMax) || 0;
        this.yScroll = round(scrollTop * this.trackTopMax / this.scrollTopMax) || 0;

        this.triggerEvent(scrollLeft, scrollTop, scrollWidth, scrollHeight, clientWidth, clientHeight);
      }
      return false;
    },

    // ------------------------------------------------------------------------------

    // 触发事件
    triggerEvent(sLeft, sTop, sWidth, sHeight, cWidth, cHeight) {
      this.throttledScroll();
      if (this.xBarShow) {
        if (sLeft === 0) {
          this.throttleLeft();
        } else if (sLeft + cWidth === sWidth) {
          this.throttleRight();
        }
      }
      if (this.yBarShow) {
        if (sTop === 0) {
          this.throttleTop();
        } else if (sTop + cHeight === sHeight) {
          this.throttleBottom();
        }
      }
    },

    // ------------------------------------------------------------------------------

    verticalHandler({ target, currentTarget, offsetY }) {
      if (target !== currentTarget) { return; }

      const offset = offsetY - this.yBarHeight / 2;
      const barTop = offset / this.containerHeight * 100;

      this.$refs.scrollEl.scrollTop = round(barTop * this.realHeight / 100);
    },

    // ------------------------------------------------------------------------------

    horizontalHandler({ target, currentTarget, offsetX }) {
      if (target !== currentTarget) { return; }

      const offset = offsetX - this.xBarWidth / 2;
      const barLeft = offset / this.containerWidth * 100;

      this.$refs.scrollEl.scrollLeft = round(barLeft * this.realWidth / 100);
    },

    // ------------------------------------------------------------------------------

    verticalBarHandler(e) {
      this.startDrag();
      this.prevPageY = this.yBarLastHeight - e.offsetY;
    },

    // ------------------------------------------------------------------------------

    horizontalBarHandler(e) {
      this.startDrag();
      this.prevPageX = this.xBarLastWidth - e.offsetX;
    },

    // ------------------------------------------------------------------------------

    startDrag() {
      this.cursorDown = true;
      addClass(document.body, ‘disable-selection‘);
      document.addEventListener(‘mousemove‘, this.throttleMoving, false);
      document.addEventListener(‘mouseup‘, this.mouseUpHandler, false);
      document.onselectstart = () => false;
    },

    // ------------------------------------------------------------------------------

    mouseUpHandler() {
      this.cursorDown = false;
      this.prevPageY = 0;
      this.prevPageX = 0;
      removeClass(document.body, ‘disable-selection‘);
      document.removeEventListener(‘mousemove‘, this.throttleMoving);
      document.removeEventListener(‘mouseup‘, this.mouseUpHandler);
      document.onselectstart = null;
    },

    // ------------------------------------------------------------------------------

    mouseMoveHandler({ clientY, clientX }) {
      let offset;
      let barPosition;

      if (this.yBarShow && this.prevPageY) {
        offset = clientY - this.$refs.vertical.getBoundingClientRect().top;
        barPosition = this.yBarLastHeight - this.prevPageY;
        const top = this.scrollTopMax * (offset - barPosition) / this.trackTopMax;
        this.$refs.scrollEl.scrollTop = round(top);
      }

      if (this.xBarShow && this.prevPageX) {
        offset = clientX - this.$refs.horizontal.getBoundingClientRect().left;
        barPosition = this.xBarLastWidth - this.prevPageX;
        const left = this.scrollLeftMax * (offset - barPosition) / this.trackLeftMax;
        this.$refs.scrollEl.scrollLeft = round(left);
      }
    },

    // ------------------------------------------------------------------------------

    dftData() {
      this.throttledScroll = null;
      this.throttleLeft = null;
      this.throttleRight = null;
      this.throttleTop = null;
      this.throttleBottom = null;
      this.throttleMoving = throttle(this.mouseMoveHandler, 10);
    },

    // ------------------------------------------------------------------------------

    // 注册事件
    initEvent() {
      const opt = { trailing: false };
      this.turnOn(‘winResize‘, this.initail);
      this.throttleTop = throttle(() => this.$emit(‘top‘), 1000, opt);
      this.throttleLeft = throttle(() => this.$emit(‘left‘), 1000, opt);
      this.throttleRight = throttle(() => this.$emit(‘right‘), 1000, opt);
      this.throttleBottom = throttle(() => this.$emit(‘bottom‘), 1000, opt);
      this.throttledScroll = throttle(() => this.$emit(‘scroll‘), 1000, opt);
    },

    // ------------------------------------------------------------------------------
  },
};

原文地址:https://www.cnblogs.com/zhangmao/p/10659503.html

时间: 2024-10-10 05:43:54

使用 Vue 开发 scrollbar 滚动条组件的相关文章

【vue开发】 父组件传值给子组件时 ,watch props 监听不到解决方案

解决方案: watch:{ data:{ immediate:true, handler:function(){ } }} 示例:   原文地址:https://www.cnblogs.com/xiaohuizhang/p/11393444.html

vue(7)—— 组件化开发 — webpack(1)

引子 在研究完前面的vue开发后,其实已经可以自己开发点东西了,靠前面的指令集,组件,还有vue-router,还有异步请求这些知识点,是完全可以开发出来的,完全可以达到时下前后端分离的效果. 但是,你在开发的过程当中,可能会遇到很多问题,而你只能用很低端的手段来解决问题,也可能在开发中,会遇到大量重复代码,而且还没有个很好的解决办法,并且在以后实际开发中,并不会像前面那样,一个html页面就搞定所有了,绝对不会的,肯定会有很多个文件,各种库,各种包之类的,这里倒过来,那里倒过去,哪个放前面,哪

Vue全家桶之组件化开发

作者 | Jeskson 掘金 | https://juejin.im/user/5a16e1f3f265da43128096cb 学习组件化开发,首先掌握组件化的开发思想,组件的注册方式,组件间的数据交互方式,组件插槽的用法,vue调式工具的用法,组件的方式来实现业务逻辑功能. 组件化开发思想,组件注册,组件调式,组件间的数据交互,组件插槽 组件化规范,就是多的可能性重用 全局组件注册语法 Vue.component(组件名称, { data: 组件数据, template: 组件模板内容 }

VUE开发一个图片轮播的组件

完成效果图如下: vue开发的思路主要是数据绑定,代码如下: <template> <div ref="root" style="user-select: none;-webkit-user-select: none;overflow: hidden"> <div class="sliderPanel" :class="{transitionAni:ani}" :style="{heig

【转】[总结]vue开发常见知识点及问题资料整理(持续更新)

1.(webpack)vue-cli构建的项目如何设置每个页面的title 2.vue项目中使用axios上传图片等文件 3.qs.stringify() 和JSON.stringify()的区别以及在vux中使用post提交表单数据需要qs库序列化 4.vue中实现全局的setCookie,getCookie以及delCookie方法笔记 5.webpack中alias配置中的"@"是什么意思? 6.webpack proxyTable 代理跨域 7.如何在 vue 项目里正确地引用

第2篇:用as3.0制作一个滚动条组件

本实例演示了实现一个滚动条基本功能的制作方法,没有添加改变皮肤,修改滚动条视框大小等功能,有兴趣的朋友可根据自己要求自行添加.使用时只需要通过以下一行代码创建滚动条组件: var myScrollbar:Scrollbar=new Scrollbar(mc); addChild(myScrollbar); 其中mc为需要添加滚动条功能的元件,如影片剪辑,文本等. 一.制作元件 滚动条由滑块slider,向上向下滚动的按钮,滚动条背景,遮挡内容的遮罩及存储内容的contMc元素组成.当拖动滑块sl

Vue.js——60分钟组件快速入门(下篇)

概述 上一篇我们重点介绍了组件的创建.注册和使用,熟练这几个步骤将有助于深入组件的开发.另外,在子组件中定义props,可以让父组件的数据传递下来,这就好比子组件告诉父组件:"嘿,老哥,我开通了一个驿站,你把东西放到驿站我就可以拿到了." 今天我们将着重介绍slot和父子组件之间的访问和通信,slot是一个非常有用的东西,它相当于一个内容插槽,它是我们重用组件的基础.Vue的事件系统独立于原生的DOM事件,它用于组件之间的通信. 本文的主要内容如下: 组件的编译作用域 在组件templ

vue.js2.0 自定义组件初体验

理解 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 使用组件 创建单文件组件 <template> </template> <script> export default { }; </script> <style lang="st

vue开发后台管理系统小结

最近工作需要用vue开发了后台管理系统,由于是第一次开发后台管理系统,中间也遇到了一些坑,想在这里做个总结,也算是对于自己工作的一个肯定.我们金融性质的网站所以就不将代码贴出来哈 一.项目概述 首先工作需求是这样的,开发一个公司的总后台,包含各个不同的模块,总体难度一般,没有开发ui图,用的vue-cli .elementui框架.webpack打包. 总需求的思维导图见链接  http://naotu.baidu.com/file/e8c31b43dfb2f18a3c523e0fa1393c0