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其实就是

<div class="wrapper >
   ...
</div>

由这个wrapper包裹其他所有元素。可以看一下adminlte3的页面结构


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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 前端开发面试题 面试题目: 根据你的等级和职位的变化,入门级到专家级,广度和深度都会有所增加。 题目类型: 理论知...
    怡宝丶阅读 2,568评论 0 7
  • @转自GitHub 介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?(1)有两种, IE 盒子模...
    YT_Zou阅读 1,303评论 0 1
  • CSS命名规则 头:header内容:content/containe尾:footer导航:nav侧栏:sideb...
    纹小艾阅读 731评论 0 9
  • CSS命名规则 头:header 内容:content/containe 尾:footer 导航:nav 侧栏:s...
    建昕82阅读 733评论 0 6
  • CSS命名规则 头:header 内容:content/containe 尾:footer 导航:nav 侧栏:s...
    王小傲阅读 1,499评论 0 9