[Серия Vue] Правильный способ инкапсуляции публичных всплывающих компонентов

Vue.js
[Серия Vue] Правильный способ инкапсуляции публичных всплывающих компонентов

Недавно проект был перенесен в новый проект, созданный с помощью платформы Vue, но проект не использовал библиотеку vue ui и не инкапсулировал общие компоненты всплывающих окон. Поэтому я реализовал простой компонент всплывающего окна. Перед разработкой обратите внимание на следующие моменты:

  1. Заголовок компонента, текст кнопки, количество кнопок и содержимое всплывающего окна можно настроить;

  2. Всплывающее окно центрировано по вертикали и горизонтали.Учитывая, что оно фактически недоступно в шапке среды WeChat, место для кнопки возврата внизу в среде ios WeChat занято;

  3. Слой маски отделяется от содержимого всплывающего окна, щелкните слой маски, чтобы закрыть всплывающее окно;

  4. Когда несколько всплывающих окон появляются одновременно, z-индекс всплывающего окна должен быть выше, чем раньше;

  5. Щелкните слой маски, чтобы закрыть всплывающее окно и сделать так, чтобы содержимое страницы в нижней части всплывающего окна не прокручивалось.

Он содержит основные функции, которые необходимо реализовать, и проблемы, которые необходимо решить.

шаги для достижения

  1. Полная структура страницы, стили и анимация перехода
  2. Настройте заголовок всплывающего окна, кнопку и содержимое темы
  3. Компонентный переключатель
  4. обработка z-индекса
  5. Нажмите на слой маски, чтобы закрыть всплывающее окно.
  6. Обработка содержимого страницы в нижней части всплывающего окна не прокручивается

1. Завершите структуру и стиль страницы

Сначала создайте vue-файл компонента всплывающего окна, чтобы реализовать базовую структуру и стиль.

<template>
    <div class="dialog">
        <div class="dialog-mark"></div>
        <transition name="dialog">
            <div class="dialog-sprite">
                <!-- 标题 -->
                <section v-if="title" class="header">临时标题</section>
    
                <!-- 弹窗的主题内容 -->
                <section class="dialog-body">
                    临时内容
                </section>
    
                <!-- 按钮 -->
                <section class="dialog-footer">
                    <div class="btn btn-confirm">确定</div>
                </section>
            </div>
        </transition>
    </div>
</template>

<script>
    export default {
        data(){
            return {}
        }
    }
</srcipt>


<style lang="less" scoped>
    // 弹窗动画
    .dialog-enter-active,
    .dialog-leave-active {
        transition: opacity .5s;
    }
    
    .dialog-enter,
    .dialog-leave-to {
        opacity: 0;
    }
    
    // 最外层 设置position定位 
    // 遮罩 设置背景层,z-index值要足够大确保能覆盖,高度 宽度设置满 做到全屏遮罩
    .dialog {
        position: fixed;
        top: 0;
        right: 0;
        width: 100%;
        height: 100%;
        // 内容层 z-index要比遮罩大,否则会被遮盖
        .dialog-mark {
            position: absolute;
            top: 0;
            height: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, .6);
        }
        .dialog-sprite {
            // 移动端使用felx布局
            position: absolute;
            top: 10%;
            left: 15%;
            right: 15%;
            bottom: 25%;
            display: flex;
            flex-direction: column;
            max-height: 75%;
            min-height: 180px;
            overflow: hidden;
            z-index: 23456765435;
            background: #fff;
            border-radius: 8px;
            .header {
                padding: 15px;
                text-align: center;
                font-size: 18px;
                font-weight: 700;
                color: #333;
            }
            .dialog-body {
                flex: 1;
                overflow-x: hidden;
                overflow-y: scroll;
                padding: 0 15px 20px 15px;
            }
            .dialog-footer {
                position: relative;
                display: flex;
                width: 100%;
                // flex-shrink: 1;
                &::after {
                    content: '';
                    position: absolute;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 1px;
                    background: #ddd;
                    transform: scaleY(.5);
                }
                .btn {
                    flex: 1;
                    text-align: center;
                    padding: 15px;
                    font-size: 17px;
                    &:nth-child(2) {
                        position: relative;
                        &::after {
                            content: '';
                            position: absolute;
                            left: 0;
                            top: 0;
                            width: 1px;
                            height: 100%;
                            background: #ddd;
                            transform: scaleX(.5);
                        }
                    }
                }
                .btn-confirm {
                    color: #43ac43;
                }
            }
        }
    }
</style>

2. Настройте заголовок всплывающего окна, кнопку и содержимое темы

Опускаем код стиля, мы устанавливаем заголовок как настраиваемый и обязательный атрибут.

Кнопка отображает кнопку подтверждения по умолчанию, текст кнопки подтверждения может быть настроен, и кнопка отмены может отображаться, и текст кнопки отмены может быть настроен, а также обработка их событий щелчка.

Тематическое содержание рекомендуетсяslotОбработка слотов. Если вам непонятно, вы можете перейти на официальный сайт vue, чтобы узнатьslot.

<template>
    <div class="dialog">
        <div class="dialog-mark"></div>
        <transition name="dialog">
            <div class="dialog-sprite">
                <!-- 标题 -->
                <section v-if="title" class="header">{{ title }}</section>
    
                <!-- 弹窗的主题内容 -->
                <section class="dialog-body">
                    <slot></slot>
                </section>
    
                <!-- 按钮 -->
                <section class="dialog-footer">
                    <div v-if="showCancel" class="btn btn-refuse" @click="cancel">{{cancelText}}</div>
                    <div class="btn btn-confirm" @click="confirm">{{confirmText}}</div>
                </section>
            </div>
        </transition>
    </div>
</template>

<script>
    export default {
        props: {
            title: String,
            showCancel: {
                typs: Boolean,
                default: false,
                required: false,
            },
            cancelText: {
                type: String,
                default: '取消',
                required: false,
            },
            confirmText: {
                type: String,
                default: '确定',
                required: false,
            },
        },
        data() {
            return {
                name: 'dialog',
            }
        },
        
        ...
        
        methods: {
            /** 取消按钮操作 */
            cancel() {
                this.$emit('cancel', false);
            },
    
            /** 确认按钮操作 */
            confirm() {
                this.$emit('confirm', false)
            },
        }
    }
</script>

3. Компонентный переключатель

Переключатель всплывающего компонента управляется извне, но не управляется напрямую с помощью show. Вместо этого он отслеживает шоу и присваивает его внутренней переменной компонента showSelf.

Эта обработка также облегчит скрытие всплывающего окна под управлением компонента. На этом основан следующий щелчок по маскирующему слою, чтобы закрыть всплывающее окно.

// 只展示了开关相关代码
<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
    </div>
</template>

<script>
    export default {
        props: {
            //弹窗组件是否显示 默认不显示 必传属性
            show: {
                type: Boolean,
                default: false,
                required: true,
            },
        },
        data() {
            return {
                showSelf: false,
            }
        },
        watch: {
            show(val) {
                if (!val) {
                    this.closeMyself()
                } else {
                    this.showSelf = val
                }
            }
        },
        created() {
            this.showSelf = this.show;
        },
    }
</script>

4. обработка z-индекса

Во-первых, нам нужно убедиться, что уровень z-inde компонента всплывающего окна достаточно высок, а во-вторых, нам нужно убедиться, что уровень содержимого всплывающего окна выше, чем уровень слоя маски всплывающего окна.

Всплывающее окно, которое появляется позже, выше, чем всплывающее окно, которое появляется раньше. (не гарантируется полная реализация)

<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
        <div class="dialog-mark" :style="{'z-index': zIndex + 1}"></div>
        <transition name="dialog">
            <div class="dialog-sprite" :style="{'z-index': zIndex + 2}">
            
               ...
               
            </div>
        </transition>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                zIndex: this.getZIndex(),
            }
        },
        methods: {
            /**  每次获取之后 zindex 自动增加 */
            getZIndex() {
                let zIndexInit = 20190315;
                return zIndexInit++
            },
        }
    }
</script>

5. Щелкните слой маски, чтобы закрыть всплывающее окно, и обработайте содержимое страницы в нижней части всплывающего окна, чтобы оно не прокручивалось.

На что нам нужно обратить внимание, так это на то, что когда компонент смонтирован, установите скрытое переполнение для тела, чтобы предотвратить прокрутку страницы под всплывающим окном при скольжении всплывающего окна.

При нажатии на слой маски мы можем скрыть всплывающий компонент внутри компонента. Когда v-if скрыт, это также уничтожение компонента.

<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
        <div class="dialog-mark" @click.self="closeMyself" :style="{'z-index': zIndex + 1}"></div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                zIndex: this.getZIndex(),
            }
        },
        mounted() {
            this.forbidScroll()
        },
        methods: {
            /** 禁止页面滚动 */
            forbidScroll() {
                this.bodyOverflow = document.body.style.overflow
                document.body.style.overflow = 'hidden'
            },
            
           /** 点击遮罩关闭弹窗 */
            closeMyself(event) {
                this.showSelf = false;
                this.sloveBodyOverflow()
            },
            
            /** 恢复页面的滚动 */
            sloveBodyOverflow() {
                document.body.style.overflow = this.bodyOverflow;
            },
        }
    }
</script>

Конечный эффект компонента

1.jpeg

2.jpeg

Окончательный полный код компонента выглядит следующим образом:

<template>
    <div v-if="showSelf" class="dialog" :style="{'z-index': zIndex}">
        <div class="dialog-mark" @click.self="closeMyself" :style="{'z-index': zIndex + 1}"></div>
        <transition name="dialog">
            <div class="dialog-sprite" :style="{'z-index': zIndex + 2}">
                <!-- 标题 -->
                <section v-if="title" class="header">{{ title }}</section>
    
                <!-- 弹窗的主题内容 -->
                <section class="dialog-body">
                    <slot></slot>
                </section>
    
                <!-- 按钮 -->
                <section class="dialog-footer">
                    <div v-if="showCancel" class="btn btn-refuse" @click="cancel">{{cancelText}}</div>
                    <div class="btn btn-confirm" @click="confirm">{{confirmText}}</div>
                </section>
            </div>
        </transition>
    </div>
</template>

<script>
    export default {
        props: {
            //弹窗组件是否显示 默认不显示 必传属性
            show: {
                type: Boolean,
                default: false,
                required: true,
            },
            title: {
                type: String,
                required: true,
            },
            showCancel: {
                typs: Boolean,
                default: false,
                required: false,
            },
            cancelText: {
                type: String,
                default: '取消',
                required: false,
            },
            confirmText: {
                type: String,
                default: '确定',
                required: false,
            },
        },
        data() {
            return {
                name: 'dialog',
                showSelf: false,
                zIndex: this.getZIndex(),
                bodyOverflow: ''
            }
        },
        watch: {
            show(val) {
                if (!val) {
                    this.closeMyself()
                } else {
                    this.showSelf = val
                }
            }
        },
        created() {
            this.showSelf = this.show;
        },
        mounted() {
            this.forbidScroll()
        },
        methods: {
            /** 禁止页面滚动 */
            forbidScroll() {
                this.bodyOverflow = document.body.style.overflow
                document.body.style.overflow = 'hidden'
            },
    
            /**  每次获取之后 zindex 自动增加 */
            getZIndex() {
                let zIndexInit = 20190315;
                return zIndexInit++
            },
    
            /** 取消按钮操作 */
            cancel() {
                this.$emit('cancel', false);
            },
    
            /** 确认按钮操作 */
            confirm() {
                this.$emit('confirm', false)
            },
    
            /** 点击遮罩关闭弹窗 */
            closeMyself(event) {
                this.showSelf = false;
                this.sloveBodyOverflow()
            },
    
            /** 恢复页面的滚动 */
            sloveBodyOverflow() {
                document.body.style.overflow = this.bodyOverflow;
            },
        }
    }
</script>

<style lang="less" scoped>
    // 弹窗动画
    .dialog-enter-active,
    .dialog-leave-active {
        transition: opacity .5s;
    }
    
    .dialog-enter,
    .dialog-leave-to {
        opacity: 0;
    }
    
    // 最外层 设置position定位 
    // 遮罩 设置背景层,z-index值要足够大确保能覆盖,高度 宽度设置满 做到全屏遮罩
    .dialog {
        position: fixed;
        top: 0;
        right: 0;
        width: 100%;
        height: 100%;
        // 内容层 z-index要比遮罩大,否则会被遮盖
        .dialog-mark {
            position: absolute;
            top: 0;
            height: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, .6);
        }
    }
    
    .dialog-sprite {
        // 移动端使用felx布局
        position: absolute;
        top: 10%;
        left: 15%;
        right: 15%;
        bottom: 25%;
        display: flex;
        flex-direction: column;
        max-height: 75%;
        min-height: 180px;
        overflow: hidden;
        z-index: 23456765435;
        background: #fff;
        border-radius: 8px;
        .header {
            padding: 15px;
            text-align: center;
            font-size: 18px;
            font-weight: 700;
            color: #333;
        }
        .dialog-body {
            flex: 1;
            overflow-x: hidden;
            overflow-y: scroll;
            padding: 0 15px 20px 15px;
        }
        .dialog-footer {
            position: relative;
            display: flex;
            width: 100%;
            // flex-shrink: 1;
            &::after {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 1px;
                background: #ddd;
                transform: scaleY(.5);
            }
            .btn {
                flex: 1;
                text-align: center;
                padding: 15px;
                font-size: 17px;
                &:nth-child(2) {
                    position: relative;
                    &::after {
                        content: '';
                        position: absolute;
                        left: 0;
                        top: 0;
                        width: 1px;
                        height: 100%;
                        background: #ddd;
                        transform: scaleX(.5);
                    }
                }
            }
            .btn-confirm {
                color: #43ac43;
            }
        }
    }
</style>

Как использовать

  1. Внедрить всплывающий компонент в родительский компонент
import TheDialog from './component/TheDialog'
  1. Регистрация компонентов в компонентах
components: {
    TheDialog
}
  1. использовать в шаблоне
<the-dialog :show="showDialog" @confirm="confirm2" @cancel="cancel" :showCancel="true" :title="'新标题'" :confirmText="`知道了`" :cancelText="`关闭`">
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
</the-dialog>
  
<the-dialog :show="showDialog2" @confirm="confirm2" :title="'弹窗组件标题'" :confirmText="`知道了`">
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
        <p>主题内容</p>
</the-dialog>

<script>
    export default {
        data() {
            return {
                // 控制两个弹窗组件的初始显示与隐藏
                showDialog: true, 
                showDialog2: true,
            }
        },
        methods: {
            cancel(show) {
                this.showDialog = show
            },
            confirm(show) {
                this.showDialog = show
            },
            cancel2(show) {
                this.showDialog2 = show
            },
            confirm2(show) {
                this.showDialog2 = show;
            },
        }
    }
</script>

В этой статье просто описаны этапы реализации простого компонента всплывающего окна. Он в основном использует слот слота vue для приема содержимого всплывающего окна от родительского компонента; получает настраиваемые параметры всплывающего окна от родительского компонента через реквизиты и управляет отображением и скрытием всплывающего окна. window; дочерний компонент прослушивает события через $emit и передает их родительскому компоненту для выполнения логической обработки.

разное

без сожаленийСерия Vue,это здесь:nuggets.capable/post/684490…

Многие мелкие партнеры, которые изучают Vue, имеют серьезные знания о фрагментации.Я составил систематизированный набор блогов об изучении Vue. На пути саморазвития я также надеюсь помочь большему количеству людей добиться прогресса. печатьСвязь