摸魚不如摸一個高複用Element對話框

前言

彈出對話框在平常開發中應用得十分普遍,不管是Web網頁,仍是App,又或者是桌面應用,均可以使用對話框實現一種較高體驗性的人機交互,瀏覽ElementUi,咱們能夠看到在其組件庫中,關於彈出的組件有不少:對話框、彈出框、文字提示、氣泡確認框···vue

關於Ui庫開發,我的按照ElementUi的組件種類,以Pc端爲核心,建立了一套【適配VUE + LESS】的開源UI庫項目,若是你們感興趣,歡迎來GIT上踩踩git

附件:github

本文將帶來的是摸一個ElementUi對話框,從零到一讓你們明白一個合格的組件是如何打造的bash


何爲組件?

關於組件,能夠分爲業務組件通用組件app

  • 業務組件
    • 只爲具體業務負責,調用方便,組件與業務耦合
    • 沒法遷移,擴展性差
  • 通用組件
    • 抽象的UI組件,無具體功能實現
    • 使用須要具體的業務代碼
    • 具備高複用、高可擴展性

實現一個合格的組件,該如何思考? 思考功能 → 提取業務功能與基本功能 → 實現基本功能,定義業務功能接口less


打造對話框組件

需求分析

根據ElementUi對話框的功能屬性進行篩選,咱們實現如下需求:ide

  • 對話框顯示由父組件控制,子組件實現
  • 頭部經過父組件傳值,也可經過slot
  • 具體內容與底部均經過slot由用戶自定義
  • 遮罩層、body滾動、右上角按鈕、主題顏色、自定義類等都可配置
  • 彈窗關閉前回調、彈窗打開後回調、彈窗關閉後回調

最終效果

代碼編寫

文件目錄

  • index.vue:組件文件
  • index.less:樣式表
  • view01.vue:測試組件文件
1 - index.vue 組件文件

❗ Ps:函數

  • $slots.footer - 用於判斷父組件中所使用的slot是否包括具名爲footer的插槽
  • this.$emit('update:visible', false) - 該用法須要父組件配合,在綁定visible時使用sync修飾符,實現子組件修改父組件值
  • handleClose - 該方法當父組件有傳遞beforeClose且爲function時,而後傳遞hide()做爲參數並執行,這個用法實如今關閉以前進行額外操做,父組件的beforeClose函數能夠接受一個參數,用於主動關閉彈窗
<template>
    <div class='cai-dialog-wrapper' ref='dialog-wrapper' v-show='visibleDialog' @click.self='handleWrapperClick'>
        <transition name="dialog-fade">
            <div ref='dialog'
                 :class="['cai-dialog',{ 'cai-dialog-dark':dark },customClass]"
                 :style='dialogSize'
                  v-if='dialogRender'
            >   
                <!-- 對話框頭部 -->
                <div class='cai-dialog-header'>
                    <!-- 對話框標題,可被替換 -->
                    <slot name='title'>
                        <span class='cai-dialog__title'>{{ title }}</span>
                    </slot>
                    <!-- 關閉對話框按鈕 -->
                    <button
                        type='button'
                        class='cai-dialog__headerbtn'
                        aria-label='Close'
                        v-if='displayClose'
                        @click='handleClose'>
                        <i class='cai-icon-close'></i>
                    </button>
                </div>
                <!-- 對話框主體 -->
                <div class='cai-dialog-body'>
                    <slot></slot>
                </div>
                <!-- 對話框底部 -->
                <div class='cai-dialog-footer' v-if='$slots.footer'>
                    <slot name='footer'></slot>
                </div>
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name:'CaiDialog',
    data(){
        return{
            visibleDialog:false,
            dialogRender:false,
            dialogSize:{}   // body寬高用於設置居中
        }
    },
    props:{
        visible:{
            type: Boolean,
            default: false
        },
        title:{
            type: String,
            default: ''
        },
        // 關閉彈窗前的回調(接收一個參數 done())
        beforeClose: Function,
        // 是否須要遮罩層
        modal:{
            type: Boolean,
            default: true
        },
        // 是否在 Dialog 出現時將 body 滾動鎖定
        lockScroll: {
            type: Boolean,
            default: true
        },
        // 是否能夠經過點擊 modal 關閉 Dialog
        closeOnClickModal: {
            type: Boolean,
            default: false
        },
        // 是否顯示右上角關閉按鈕
        displayClose:{
            type: Boolean,
            default: true
        },
        // 最大寬高
        width: String,
        height: String,
        // 主題顏色 - 高亮(默認) | 夜間
        dark:{
            type:Boolean,
            default:false
        },
        // 自定義類
        customClass: {
            type:String,
            default:''
        }
    },
    watch:{
        visible(newVal){
            if(newVal){
                this.visibleDialog = true
                this.dialogRender = true

                // 依據props修改樣式
                this.changeDialogStyle()

                this.$emit('open')
            }else{
                this.visibleDialog = false
                this.dialogRender = false
                document.body.style['overflow'] = 'auto'
                this.$emit('close')
            }
        }
    },
    methods:{
        handleWrapperClick(){
            if(!this.closeOnClickModal) return
            this.handleClose()
        },
        // 處理關閉對話框,若存在beforeClose則調用
        handleClose(){
            if(typeof this.beforeClose === 'function') {
                this.beforeClose(this.hide)
            }else{
                this.hide()
            }
        },
        hide(){
            this.$emit('update:visible', false);
        },
        // 根據Props值修改Dialog樣式
        changeDialogStyle(){
            // lockScroll - 實現底層禁止滾動
            if(this.lockScroll) document.body.style['overflow'] = 'hidden'
            var that = this
            this.$nextTick(() => {
                var dialogWrapperStyle = that.$refs['dialog-wrapper'].style
                var dialogStyle = that.$refs.dialog.style
                if(that.width) dialogStyle.width = that.width + 'px'
                if(that.height) dialogStyle.height = that.height + 'px'
                // 實現無遮罩層
                if(!that.modal) dialogWrapperStyle.background = 'transparent'
            })
        }
    }
}
</script>

<style lang='less' scoped>
@import './index.less';
@import '../../CaiIcon/component/index.less';   // Icon樣式表,可忽略
</style>
複製代碼
2 - index.less 樣式表
.cai-dialog-wrapper{
    position: fixed;
    top:0;
    bottom:0;
    right: 0;
    left: 0;
    overflow: auto;
    background: rgba(0,0,0,0.6);
    z-index:1999;
    // 默認樣式
    .cai-dialog{
        position:absolute;
        border:1px solid rgba(247, 241, 240);
        border-radius:5px;
        color:#303952;
        padding:10px;
        left:50%;
        top:50%;
        transform:translate(-50%, -50%);
        display:flex;
        flex-direction: column;
        justify-content: space-between;
        background: rgba(247, 241, 240);
        min-width:200px;
        min-height:100px;
        overflow: auto;
        .cai-dialog-header{
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom:10px;
            font-size:14px;
            .cai-dialog__title{
                font-weight: 600;
            }
            .cai-dialog__headerbtn{
                background: transparent;
                border-color: transparent;
                padding:0;
                outline:none;
                .cai-icon-close{
                    color:#303952;
                    cursor:pointer;
                    transition: all .1s linear;
                    &:hover{
                        color:#ff3f34;
                    }
                }
            }
        }
        .cai-dialog-body{
            flex:1;
        }
    }
    // 夜間模式
    .cai-dialog-dark{
        border-color:#3d3d3d;
        background: #3d3d3d;
        color:#fff;
        .cai-dialog-header{
            .cai-dialog__headerbtn{
                .cai-icon-close{
                    color:#fff;
                    cursor:pointer;
                    transition: all .1s linear;
                    &:hover{
                        color:#ef5777;
                    }
                }
            }
        }
    }

    // 進入/離開 動畫
    .dialog-fade-enter-active, .dialog-fade-leave-active {
        transition: all .3s linear;
    }
    .dialog-fade-enter {
        opacity: 0;
        top:48%;
    }
}
複製代碼
3 - view01.vue 測試組件文件
<!-- 
    visible - 控制顯示
    title - 彈窗標題
    beforeClose - 彈窗關閉前回調
    modal - 是否須要遮罩層
    lockScroll - 是否在 Dialog 出現時將 body 滾動鎖定
    closeOnClickModal - 是否能夠經過點擊 modal 關閉 Dialog
    displayClose - 是否顯示右上角關閉按鈕
    dark - 主題顏色 - 高亮(默認) | 夜間
    customClass - 自定義類
    @open - Dialog 打開的回調
    @close - Dialog 關閉的回調

    slot {
      footer - 底部
      不具名 - 內容
    }
-->
<div style='width:310px;padding:20px;border:1px solid #DDDDDD;display:flex;flex-wrap:wrap;'>
  <cai-button @click='openDialog1'>高亮對話框</cai-button>
  <cai-dialog :visible.sync='showDialog1' closeOnClickModal width='400' height='200' title='I am Light' :before-close='handleDialogClose' @open='DialogOpen' @close='DialogClose'> 
    I am a Dialog
    <span slot="footer" style='display:flex;justify-content:flex-end;'>
      <cai-button @click="showDialog1 = false">取 消</cai-button>
      <cai-button @click="showDialog1 = false">確 定</cai-button>
    </span>
  </cai-dialog>

  <cai-divider></cai-divider>

  <cai-button @click='openDialog2'>夜間對話框</cai-button>
  <cai-dialog :visible.sync='showDialog2' dark :displayClose='false' :lockScroll='false' :before-close='handleDialogClose'>
    <!-- 經過slot自定義頭部 -->
    <span slot="title">
     I am Dark
    </span>
    I am a Dialog
    <span slot="footer" style='display:flex;justify-content:flex-end;'>
      <cai-button @click="showDialog2 = false">取 消</cai-button>
      <cai-button @click="showDialog2 = false">確 定</cai-button>
    </span>
  </cai-dialog>
</div>
複製代碼
data(){
    return{
        // Dialog
        showDialog1:false,
        showDialog2:false
    }
}

methods(){
    openDialog1(){
      this.showDialog1 = true
    },
    openDialog2(){
      this.showDialog2 = true
    },
    DialogOpen(){
      console.log('DialogOpen')
    },
    DialogClose(){
      console.log('DialogOpen')
    },
    handleDialogClose(done){
      console.log('彈窗被關閉')
      done()
    }
}
複製代碼

尾聲

組件開源地址post

github.com/Jason9708/C…測試

該開源git是一個開源UI庫項目,目前開發近20款適配Vue的UI組件,有興趣的小夥伴給點⭐

部分組件截圖

相關文章
相關標籤/搜索