vue封装adminlte3的左右布局

最近用vue封装了一个adminlte3组件库,已经封装了差不多25个组件了。很多封装adminlte的都是引入jq。。这个我有点无语。没有歧视的意思,用jq还用什么vue。而且vue中用jq会出现很多bug。
感兴趣的老铁可以进github看看,欢迎贡献代码。
欢迎加群交流 QQ群:927568606
nly-adminlte-vue

adminlte3 的左右布局注意是由body上的class来控制。

  • 在控制左边导航栏收缩展开的时候,有如下几个css式样
class="sidebar-mini" //允许左边导航栏收起成只有图标的形式
class="sidebar-collapse" // 收起左边导航栏
class="sidebar-open"  // 小屏的时候展开左边导航栏

wrapper

先封装一个wrapper,用来控制body的class。这个wrapper其实就是

image.png

这个wrapper可以修改body的class,而且要监听窗口大小来修改body的class

把这wrapper命名为container-wrapper.js

import Vue from "../../../utils/vue";

const name = "NlyContainerWrapper";

export const NlyContainerWrapper = Vue.extend({
  name: name,
  props: {
    //边侧栏最小化
    sideMini: {
      type: Boolean,
      default: false
    },
    //layout fixed or boxed
    layout: {
      type: String
    },
    // navbar fixed
    navbarFixed: {
      type: Boolean,
      default: false
    },
    //footer fixed
    footerFixed: {
      type: Boolean,
      default: false
    },
    //top nav
    topNav: {
      type: Boolean,
      default: false
    },
    wrapperClass: {
      type: String
    },
    containerClass: {
      type: String
    }
  },
  computed: {
    sideMiniClass: function() {
      return this.sideMini ? "sidebar-mini" : "";
    },
    layoutClass: function() {
      return this.layout == "fixed"
        ? "layout-fixed"
        : this.layout
        ? "layout-boxed"
        : "";
    },
    navbarFixedClass: function() {
      return this.navbarFixed ? "layout-navbar-fixed" : "";
    },
    footerFixedClass: function() {
      return this.footerFixed ? "layout-footer-fixed" : "";
    },
    topNavClass: function() {
      return this.topNav ? "layout-top-nav" : "";
    },
    containerWrapperClass: function() {
      return this.wrapperClass;
    },
    containerBodyClass: function() {
      return this.containerClass;
    }
  },
  methods: {
    setBodyCollapseClassName() {
      if (this.sideMini) {
        const bodyWidth = document.body.clientWidth;
        const bodyClassName = document.body.className;

        if (bodyWidth < 992) {
          if (bodyClassName.indexOf("sidebar-collapse") == -1) {
            document.body.classList.add("sidebar-collapse");
          }
        } else {
          if (bodyClassName.indexOf("sidebar-open") !== -1) {
            document.body.classList.remove("sidebar-open");
          }
        }
      }
    },
    setBodyClassName(newval, oldval) {
      if (newval != oldval) {
        if (newval && oldval) {
          document.body.classList.add(newval);
          document.body.classList.remove(oldval);
        } else if (newval && oldval == "") {
          document.body.classList.add(newval);
        } else if (newval == "" && oldval) {
          document.body.classList.remove(oldval);
        }
      }
    }
  },
  mounted() {
    window.addEventListener(
      "resize",
      () => this.setBodyCollapseClassName(),
      false
    );
  },
  created() {
    const createdBodyClassList = [
      this.sideMiniClass,
      this.layoutClass,
      this.navbarFixedClass,
      this.footerFixed,
      this.topNavClass,
      this.containerBodyClass
    ];
    createdBodyClassList.forEach(item => {
      if (item) {
        document.body.classList.add(item);
      }
    });
    this.setBodyCollapseClassName();
  },
  beforeDestroy() {
    window.removeEventListener(
      "resize",
      this.setBodyCollapseClassName(),
      false
    );
  },
  watch: {
    sideMiniClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    },
    layoutClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    },
    navbarFixedClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    },
    footerFixedClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    },
    topNavClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    },
    containerBodyClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    },
    containerWrapperClass: function(newval, oldval) {
      this.setBodyClassName(newval, oldval);
    }
  },
  render(h) {
    return h(
      "div",
      {
        staticClass: "wrapper",
        class: [this.containerWrapperClass]
      },
      this.$slots.default
    );
  }
});

content-wrapper组件就出来了。
主要props如下:

参数 类型 默认值 描述
side-mini Boolean 边侧栏是否可以收起,true可以收起,false将边侧画板左侧滑入消失
layout String 整体布局,可选fixed和boxed
navbar-fixed Boolean 头部导航fixed布局
footer-fixed Boolean 底部fixed布局
top-nav Boolean 头部导航顶格无边侧栏布局
warpper-class String wrapper 式样
container-class String body式样

v-nly-sidebar-collapse

再封装一个指令,用来控制body class的修改。
v-nly-sidebar-collapse这个指令就用来控制左侧收起展开的。在绑定这个指令的元素上点击就会触发对应事件。

指令组件sidebar-collapse.js

import {
  navItemOenEvent,
  navItemCollapseEvent,
  setInstanceAttr,
  overLayCollapseEvent
} from "../../utils/sidebar-collapse";

export const NlySidebarCollapse = {
  bind(el, binding, vnode) {
    const instanceNameList = Object.keys(binding.modifiers);
    if (instanceNameList.indexOf("navitem") != -1) {
      window.addEventListener("resize", () => setInstanceAttr(vnode), false);
    }
    el.onclick = function() {
      if (instanceNameList.indexOf("navitem") != -1) {
        const bodyWidth = document.body.clientWidth;
        if (bodyWidth < 992) {
          navItemOenEvent();
        } else {
          navItemCollapseEvent();
        }
      }
      if (instanceNameList.indexOf("overlay") != -1) {
        overLayCollapseEvent();
      }
    };
  }
  //   unbind(el, vnode, binding) {
  //     console.log(el, vnode, binding);
  //     const instanceNameList = Object.keys(binding.modifiers);
  //     if (instanceNameList.indexOf("navitem") != -1) {
  //       Window.removeEventListener("resize", () => setInstanceAttr(vnode), false);
  //     }
  //   }
};

utils文件夹下的sidebar-collapse.js
utils/sidebar-collapse.js

import { setAttr } from "./dom";

export const eventType = {
  collapse: "sidebar-collapse",
  open: "sidebar-open",
  show: "control-sidebar-slide-open",
  animate: "control-sidebar-animate"
};

export const selector = {
  controlSidebar: ".control-sidebar",
  header: ".main-header",
  footer: ".main-footer",
  controlSidebarContent: ".control-sidebar-content"
};

export const getSelector = cls => {
  return document.querySelector(cls);
};

export const getBodyClassName = () => {
  return document.body.className;
};

export const getBodyWidth = () => {
  return document.body.clientWidth;
};

export const navItemOenEvent = () => {
  const bodyClassName = getBodyClassName();
  if (bodyClassName.indexOf(eventType.collapse) == -1) {
    if (bodyClassName.indexOf(eventType.open) == -1) {
      document.body.classList.add(eventType.collapse);
    } else {
      document.body.classList.remove(eventType.open);
      document.body.classList.add(eventType.collapse);
    }
  } else {
    document.body.classList.add(eventType.open);
    document.body.classList.remove(eventType.collapse);
  }
};

export const navItemCollapseEvent = () => {
  const bodyClassName = getBodyClassName();
  if (bodyClassName.indexOf(eventType.collapse) == -1) {
    document.body.classList.add(eventType.collapse);
  } else {
    document.body.classList.remove(eventType.collapse);
  }
};

export const setInstanceAttr = vnode => {
  const bodyWidth = getBodyWidth();
  if (bodyWidth < 992) {
    setAttr(vnode.children[0].elm, "data-widget", eventType.open);
  } else {
    setAttr(vnode.children[0].elm, "data-widget", eventType.collapse);
  }
};

export const overLayCollapseEvent = () => {
  const bodyClassName = getBodyClassName();
  if (bodyClassName.indexOf(eventType.collapse) == -1) {
    document.body.classList.remove(eventType.open);
    document.body.classList.add(eventType.collapse);
  }
};

export const getScrollTop = () => {
  return document.documentElement && document.documentElement.scrollTop
    ? document.documentElement.scrollTop
    : document.body
    ? document.body.scrollTop
    : 0;
};

export const getScrollHeight = () => {
  return document.documentElement && document.documentElement.scrollHeight
    ? document.documentElement.scrollHeight
    : document.body
    ? document.body.scrollHeight
    : 0;
};

export const getBodyOffsetHeight = () => {
  return document.documentElement && document.documentElement.offsetHeight
    ? document.documentElement.offsetHeight
    : document.body
    ? document.body.offsetHeight
    : 0;
};
/**
 * 展开先给html添加class='control-sidebar-animate'
 * 设置control-sidebar display='block'
 * 10ms之后给body添加class='control-sidebar-slide-open'
 * 300ms之后给html删除class='control-sidebar-animate'
 *
 * 收起先给html添加class='control-sidebar-animate'
 * 删除body class='control-sidebar-slide-open
 * 300ms之后设置control-sidebar display='none'
 * html删除class='control-sidebar-animate'
 * @param {s} vnode
 */

// 10ms之后给body添加class='control-sidebar-slide-open'
export const openAddBodyClass = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      document.body.classList.add(eventType.show);
      resolve();
    }, 10);
  });
};
// 300ms之后给html删除class='control-sidebar-animate'
export const openRemoveHtmlClass = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      getSelector("html").classList.remove(eventType.animate);
      resolve();
    }, 300);
  });
};

// 300ms之后设置control-sidebar display='none'
export const collapseSetAttrsDisplay = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      getSelector(selector.controlSidebar).style.display = "none";
      resolve();
    }, 300);
  });
};

// html删除class='control-sidebar-animate'
export const collapseRemoveHtmlClass = () => {
  getSelector("html").classList.remove(eventType.animate);
};

// 队列执行open操作
export async function openTasks() {
  await openAddBodyClass();
  await openRemoveHtmlClass();
}

// 队列执行collapse操作
export async function collapseTasks() {
  await collapseSetAttrsDisplay();
  await collapseRemoveHtmlClass();
}

// 监听header,footer高度以及滚动条高度,给control-siderbar设置height
export const setControlSidebarStyle = () => {
  const windowHeight = document.documentElement.clientHeight;
  const bodyHeight = getBodyOffsetHeight();
  const scrollTop = getScrollTop();
  const scrollHeight = getScrollHeight();
  const headerHeight = getSelector(selector.header).offsetHeight;
  const footerHeight = getSelector(selector.footer).offsetHeight;
  // console.log(11, bodyHeight);
  // console.log(22, scrollTop);
  // console.log(33, scrollHeight);
  // console.log(44, windowHeight);
  // console.log(
  //   55,
  //   footerHeight -
  //     scrollHeight +
  //     windowHeight -
  //     headerHeight +
  //     scrollTop +
  //     footerHeight
  // );
  const controlSidebarSelector = getSelector(selector.controlSidebar);
  const controlSidebarContentSelector = getSelector(
    selector.controlSidebarContent
  );

  if (scrollTop < headerHeight) {
    if (bodyHeight - windowHeight >= footerHeight) {
      controlSidebarSelector.style.top = `${headerHeight - scrollTop}px`;
      if (
        footerHeight -
          scrollHeight +
          windowHeight -
          headerHeight +
          scrollTop +
          footerHeight >
        0
      ) {
        controlSidebarSelector.style.height = `${scrollHeight -
          footerHeight -
          footerHeight}px`;
        controlSidebarContentSelector.style.height = `${scrollHeight -
          footerHeight -
          footerHeight}px`;
      } else {
        controlSidebarSelector.style.height = `${windowHeight -
          headerHeight +
          scrollTop}px`;
        controlSidebarContentSelector.style.height = `${windowHeight -
          headerHeight +
          scrollTop}px`;
      }
    } else {
      controlSidebarSelector.style.top = `${headerHeight - scrollTop}px`;
      controlSidebarSelector.style.height = `${bodyHeight -
        headerHeight -
        footerHeight}px`;
      controlSidebarContentSelector.style.height = `${bodyHeight -
        headerHeight -
        footerHeight}px`;
      controlSidebarSelector.style.bottom = `${footerHeight -
        bodyHeight +
        windowHeight +
        scrollTop}px`;
    }
  } else {
    controlSidebarSelector.style.top = "0px";
    if (scrollHeight - windowHeight - scrollTop <= footerHeight) {
      controlSidebarSelector.style.height = `${scrollHeight -
        footerHeight -
        scrollTop}px`;
      controlSidebarContentSelector.style.height = `${scrollHeight -
        footerHeight -
        scrollTop}px`;
      controlSidebarSelector.style.bottom = `${windowHeight +
        scrollTop +
        footerHeight -
        scrollHeight}px`;
    } else if (scrollHeight - windowHeight - scrollTop > footerHeight) {
      controlSidebarSelector.style.height = `${windowHeight}px`;
      controlSidebarContentSelector.style.height = `${windowHeight}px`;
    }
  }
};

// 展开 control-sidebar
export const controlSidebarOpen = () => {
  getSelector("html").classList.add(eventType.animate);
  getSelector(selector.controlSidebar).style.display = "block";
  openTasks();
  window.addEventListener("scroll", setControlSidebarStyle, false);
  window.addEventListener("resize", setControlSidebarStyle, false);
};

// 收起 control-sidebar
export const controlSidebarCollapse = () => {
  getSelector("html").classList.add(eventType.animate);
  document.body.classList.remove(eventType.show);
  collapseTasks();
  window.removeEventListener("scroll", setControlSidebarStyle, false);
  window.removeEventListener("resize", setControlSidebarStyle, false);
};

export const controlSidebarShow = () => {
  const bodyClassName = getBodyClassName();
  if (bodyClassName.indexOf(eventType.show) == -1) {
    controlSidebarOpen();
  } else {
    //收起
    controlSidebarCollapse();
  }
};

这时候组件跟指令都写好了,注册组件跟指令,然后把对应的代码替换掉,就能实现左侧收起展开了。

效果图


nly-adminlte-vue-2.gif

你可能感兴趣的:(vue封装adminlte3的左右布局)