Vue 封装 Adminle3 左右上中下布局

为了响应式研究了一下 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-1.gif

nly-adminlte-vue 全是 jsx 封装的组件,大部分是函数式组件,渲染速度是要比.vue 组件快。

这个组件库没有引入 jq。注意没有引入 JQ。很多 Adminlte Vue 的组件库都是引入了 JQ,这样肯定是不行的,多少会有 bug,至于为什么不行,请理解 Vue 的精髓, 数据驱动 目前差不多快 50 个复用组件了。

adminlte3 的布局有一种 collapse 布局,也就是左边侧导航栏,右侧上中下布局,且边侧导航栏可以收起展开。

admintle3 控制布局的 class 主要在<body>标签上。左右布局中,主要有 3 个 css 类来控制布局。这三个 css 类主要在左右布局中使用。

  • 第一个是 layout-fixed,这个主要是控制右侧上中下布局在初始情况下有一个 margin-left: 250px 的属性,即偏移左侧 250px,腾出空间给左侧。且还有一个作用是让左侧导航栏滚动条与窗口滚动条不冲突。

  • 第二个是 sidebar-mini,这个允许左侧导航栏在设置好的断点状态能收起到边侧只显示图标。如果没有这个,边侧导航栏收起会直接消失,不出现在窗口中。

  • 第三个是 sidebar-collapse。实际上应该还有一个 sidebar-open。不过 sidebar-open 是在小屏情况下,展开左侧导航栏才会出现。sidebar-collapse 是用来控制左侧导航栏收起的 css 类。

组件

分解一下组件。

  • 容器:容器是用来包裹左侧导航栏,右侧上中下布局的容器。常见的有 container。但是 adminlte 中是用 wrapper。给组件去一个名字叫 NlyWrapper
    <br />
  • 左侧导航栏容器:左侧导航栏容器是一个包裹左侧导航栏面板的容器。给组件取一个名字叫 NlyWrapperSidbar
    <br />
  • 右侧 header 容器:右侧布局上的容器,通常可以包裹 navbar 菜单等。给组件取一个名字叫 NlyWrapperHeader
    <br />
  • 右侧 main 容器:包裹页面正文内容的容器。给组件取一个名字叫 NlyWrapperContent
    <br />
  • 右侧 footer 容器:包裹右侧布局底部一些内容。给组件取一个名字叫 NlyWrapperFooter

这时候布局代码就应该是这样

<template>
    <nly-wrapper>
        <nly-wrapper-header> header</nly-wrapper-header>
        <nly-wrapper-sidebar> left </nly-wrapper-sidebar>
        <nly-wrapper-content>
            main
        </nly-wrapper-content>
        <nly-wrapper-footer> footer</nly-wrapper-footer>
    </nly-wrapper>
</template>

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 容器可以变成 navbar

props 如下

参数 类型 默认值 描述
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 撑起来看效果

<template>
    <nly-wrapper layout="fixed">
        <nly-wrapper-header style="height:57px"> header</nly-wrapper-header>
        <nly-wrapper-sidebar> left </nly-wrapper-sidebar>
        <nly-wrapper-content style="height:calc(100vh - 116px)">
            main</nly-wrapper-content
        >
        <nly-wrapper-footer style="height:57px"> footer</nly-wrapper-footer>
    </nly-wrapper>
</template>
image.png

一定要看完

组件封装中引入的文件,请移步Github查看。不然组件是跑不起来的。
还缺一个收起左侧导航栏的按钮,这个下一篇文章再讲

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