为了响应式研究了一下 adminlte3,封装了一个 Adminlte Vue 组件库。名字叫 nly-adminlte-vue。
GitHub 地址:nly-adminlte-vue
文档地址: nly-adminlte-vue-docs
在线 Demo 地址: nly-daminlte-vue-demo
文档目前还只写完了 5 个组件文档。其他的都在每个组件下的 README.md 中。
组件库效果
关于组件库请看我思否另一篇文章的介绍
nly-adminlte-vue 全是 jsx 封装的组件,大部分是函数式组件,渲染速度是要比.vue 组件快。这个组件库没有引入 jq。注意没有引入 JQ。很多 Adminlte Vue 的组件库都是引入了 JQ,这样肯定是不行的,多少会有 bug,至于为什么不行,请理解 Vue 的精髓, 数据驱动 目前差不多快 50 个复用组件了。
adminlte3 的布局有一种 collapse 布局,也就是左边侧导航栏,右侧上中下布局,且边侧导航栏可以收起展开。
admintle3 控制布局的 class 主要在
标签上。左右布局中,主要有 3 个 css 类来控制布局。这三个 css 类主要在左右布局中使用。
- 第一个是 layout-fixed,这个主要是控制右侧上中下布局在初始情况下有一个 margin-left: 250px 的属性,即偏移左侧 250px,腾出空间给左侧。且还有一个作用是让左侧导航栏滚动条与窗口滚动条不冲突。
- 第二个是 sidebar-mini,这个允许左侧导航栏在设置好的断点状态能收起到边侧只显示图标。如果没有这个,边侧导航栏收起会直接消失,不出现在窗口中。
- 第三个是 sidebar-collapse。实际上应该还有一个 sidebar-open。不过 sidebar-open 是在小屏情况下,展开左侧导航栏才会出现。sidebar-collapse 是用来控制左侧导航栏收起的 css 类。
组件
分解一下组件。
- 容器:容器是用来包裹左侧导航栏,右侧上中下布局的容器。常见的有 container。但是 adminlte 中是用 wrapper。给组件去一个名字叫 NlyWrapper
- 左侧导航栏容器:左侧导航栏容器是一个包裹左侧导航栏面板的容器。给组件取一个名字叫 NlyWrapperSidbar
- 右侧 header 容器:右侧布局上的容器,通常可以包裹 navbar 菜单等。给组件取一个名字叫 NlyWrapperHeader
- 右侧 main 容器:包裹页面正文内容的容器。给组件取一个名字叫 NlyWrapperContent
- 右侧 footer 容器:包裹右侧布局底部一些内容。给组件取一个名字叫 NlyWrapperFooter
这时候布局代码就应该是这样
header
left
main
footer
NlyWrapper
这是一个容器组件, 这个组件主要用来包裹其他所有组件,且用来控制 body 的 css 类。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式样 |
import Vue from '../../utils/vue'
const name = 'NlyWrapper'
export const NlyWrapper = Vue.extend({
name: name,
props: {
sideMini: {
type: Boolean,
default: false,
},
layout: {
type: String,
},
navbarFixed: {
type: Boolean,
default: false,
},
footerFixed: {
type: Boolean,
default: false,
},
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
)
},
})
NlyWrapperSidebar
这是一个容器组件, 主要用来包裹左侧导航栏的props 如下
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
variant | String | dark-primary | 导航栏主题色 |
hover | Boolean | true | 导航栏收起时,鼠标悬浮展开,设置为false则无悬浮效果 |
elevation | String | 无 | 导航栏阴影,可选,sm,md,lg,xl。依次增大 |
import Vue from '../../utils/vue'
import { nlyGetOptionsByKeyEqual } from '../../utils/get-options'
import {
sidebarElevationOptions,
sidebarContainerVariantOpitons,
} from '../../utils/nly-config'
const name = 'NlyWrapperSidebar'
export const NlyWrapperSidebar = Vue.extend({
name: name,
props: {
variant: {
type: String,
default: 'darkPrimary',
},
hover: {
type: Boolean,
default: true,
},
elevation: {
type: String,
default: 'xl',
},
tag: {
type: String,
default: 'aside',
},
},
computed: {
customVariant: function () {
return nlyGetOptionsByKeyEqual(
sidebarContainerVariantOpitons,
this.variant
)
},
customHover: function () {
return this.hover ? '' : 'sidebar-no-expand'
},
customElevation: function () {
return nlyGetOptionsByKeyEqual(
sidebarElevationOptions,
this.elevation
)
},
customTag: function () {
return this.tag
},
},
render(h) {
return h(
this.customTag,
{
staticClass: 'main-sidebar',
class: [
this.customVariant,
this.customElevation,
this.customHover,
],
},
this.$slots.default
)
},
})
NlyWrapperHeader
这是一个容器组件, 主要用来包裹右侧 header 的。在这个组件中,有几个 prop 是在 prop nav=true 的时候生效,这是因为在 adminlte3 中,navbar 直接就做成了顶部容器。所有给了一个 nav props 让 header 容器可以变成 navbarprops 如下
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
expand | String | navbar-expand | 菜单栏屏幕变化收起断点,默认是sm断点,可选xl,lg,md,sm,no |
variant | String | white | 菜单主题颜色,可选primary,secondary,success,info,warning,danger,lightblue,navy,olive,lime,fuchsia,maroon,blue,indigo,purple,pink,red,orange,yellow,green,teal,cyan,white,gray,graydark |
dark | Boolean | false | 字体颜色,默认是黑色,设置true为白色 |
size | String | 无 | 菜单字体大小,可选sm,lg |
boder | Boolean | true | 菜单底边框,header为true的时候生效 |
navbar-class | String | 自定义css式样 |
import Vue from '../../utils/vue'
import { nlyGetOptionsByKeyEqual } from '../../utils/get-options'
import { navbarVariantOpitons, textSizeOptions } from '../../utils/nly-config'
const name = 'NlyWrapperHeader'
export const NlyWrapperHeader = Vue.extend({
name: name,
props: {
expand: {
type: String,
},
variant: {
type: String,
default: 'white',
},
dark: {
type: Boolean,
default: false,
},
size: {
type: String,
default: '',
},
border: {
type: Boolean,
default: true,
},
wrapperHeaderClass: {
type: String,
},
tag: {
type: String,
default: 'header',
},
nav: {
type: Boolean,
default: false,
},
},
computed: {
customTag() {
return this.tag
},
customNav() {
return this.nav ? 'navbar' : ''
},
customNavbarExpand: function () {
if (this.nav) {
return this.expand == 'xl'
? 'navbar-expand-xl'
: this.expand == 'lg'
? 'navbar-expand-lg'
: this.expand == 'md'
? 'navbar-expand-md'
: this.expand == 'sm'
? 'navbar-expand-sm'
: this.expand == 'no'
? 'navbar-no-expand'
: this.expand == 'expand'
? 'navbar-expand'
: ''
} else {
return ''
}
},
customnNvbarVariant: function () {
if (this.nav) {
return nlyGetOptionsByKeyEqual(
navbarVariantOpitons,
this.variant
)
} else {
return ''
}
},
customNavbarFontSize: function () {
if (this.nav) {
return nlyGetOptionsByKeyEqual(textSizeOptions, this.size)
} else {
return ''
}
},
customNavbarBorder: function () {
if (this.nav) {
return this.border ? '' : 'border-bottom-0'
} else {
return ''
}
},
customWrapperHeaderClass: function () {
return this.wrapperHeaderClass
},
customNavbarDark() {
if (this.nav) {
return this.dark ? 'navbar-dark' : 'navbar-light'
} else {
return ''
}
},
},
render(h) {
return h(
this.customTag,
{
staticClass: 'main-header',
class: [
this.customNavbarExpand,
this.customNavbarDark,
this.customnNvbarVariant,
this.customNavbarFontSize,
this.customNavbarBorder,
this.customWrapperHeaderClass,
],
},
this.$slots.default
)
},
})
NlyWrapperContent
这是一个容器组件, 主要用来包裹右侧 main 的 的。props 如下
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
tag | String | div | 标签 |
import Vue from '../../utils/vue'
const name = 'NlyWrapperContent'
export const NlyWrapperContent = Vue.extend({
name: name,
props: {
tag: {
type: String,
default: 'div',
},
},
computed: {
customProps() {
return {
tag: this.tag,
}
},
},
render(h) {
return h(
this.customProps.tag,
{
staticClass: 'content-wrapper',
},
this.$slots.default
)
},
})
NlyWrapperFooter
这是一个容器组件, 主要用来包裹右侧 footer 的 的。props 如下
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
size | String | 文字大小 |
import Vue from '../../utils/vue'
const name = 'NlyWrapperFooter'
export const NlyWrapperFooter = Vue.extend({
name: name,
props: {
size: {
type: String,
},
},
computed: {
footerFontSizeClass: function () {
return this.size == 'sm'
? 'text-sm'
: this.size == 'lg'
? 'text-lg'
: ''
},
},
render(h) {
return h(
'footer',
{
staticClass: 'main-footer',
class: [this.footerFontSizeClass],
},
this.$slots.default
)
},
})
注册
将以上组件注册,就可以在 vue 中直接使用了注册代码 demo
import NlyWrapper from './wrapper.js'
const WrapperPlugin = {
install: function (Vue) {
Vue.component('nly-wrapper', NlyWrapper)
},
}
export { WrapperPlugin }
效果
代码。代码里使用了 height,是因为封装大的组件高度都是由子元素决定的。没有子元素就先用 height 撑起来看效果
header
left
main
footer
一定要看完
组件封装中引入的文件,请移步Github查看。不然组件是跑不起来的。
还缺一个收起左侧导航栏的按钮,这个下一篇文章再讲