最近用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