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 封裝的組件,大部分是函數(shù)式組件,渲染速度是要比.vue 組件快。

這個組件庫沒有引入 jq。注意沒有引入 JQ。很多 Adminlte Vue 的組件庫都是引入了 JQ,這樣肯定是不行的,多少會有 bug,至于為什么不行,請理解 Vue 的精髓, 數(shù)據(jù)驅動 目前差不多快 50 個復用組件了。

adminlte3 的布局有一種 collapse 布局,也就是左邊側導航欄,右側上中下布局,且邊側導航欄可以收起展開。

admintle3 控制布局的 class 主要在<body>標簽上。左右布局中,主要有 3 個 css 類來控制布局。這三個 css 類主要在左右布局中使用。

  • 第一個是 layout-fixed,這個主要是控制右側上中下布局在初始情況下有一個 margin-left: 250px 的屬性,即偏移左側 250px,騰出空間給左側。且還有一個作用是讓左側導航欄滾動條與窗口滾動條不沖突。

  • 第二個是 sidebar-mini,這個允許左側導航欄在設置好的斷點狀態(tài)能收起到邊側只顯示圖標。如果沒有這個,邊側導航欄收起會直接消失,不出現(xiàn)在窗口中。

  • 第三個是 sidebar-collapse。實際上應該還有一個 sidebar-open。不過 sidebar-open 是在小屏情況下,展開左側導航欄才會出現(xiàn)。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 如下

參數(shù) 類型 默認值 描述
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 如下

參數(shù) 類型 默認值 描述
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 如下

參數(shù) 類型 默認值 描述
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 如下

參數(shù) 類型 默認值 描述
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 如下

參數(shù) 類型 默認值 描述
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查看。不然組件是跑不起來的。
還缺一個收起左側導航欄的按鈕,這個下一篇文章再講

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容