如何封装一款全屏窗口组件

753 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

TIP 👉 东边日出西边雨,道是无晴却有晴。唐·刘禹锡《竹枝词》

前言

在我们日常项目开发中,我们在做移动端的时候会涉及到全屏窗口功能,所以封装了这个全屏窗口组件。

一、全屏窗口组件(只移动端可用)

此组件建议使用js方式调用

import popupFullBox from '@/components/m/fullBox'
import UserInfo from './components/UserInfo.vue'

popupFullBox({
  content: UserInfo,
  contentProps: {
    name: '张三',
    phone: '18688886666'
  }
})

popupFullBox 方法参数

1. title
  • 窗口标题
  • 值为字符串,默认值为:""
2. showClose
  • 是否显示关闭按钮
  • 值为布尔类型,默认值为:true
3. content
  • 内容组件
  • 值为Vue对象,必填
4. contentProps
  • 弹窗内容组件的props
  • 值为Object类型,属性名为组件的props的属性
5. contentEvents
  • 弹窗内容组件的事件
  • 值为Object类型,属性名为组件事件名,属性值为事件回调方法
6. contentWrapperStyle
  • 内容包裹器样式
  • 值为Object类型,属性名为驼峰格式的样式名,属性值为样式值字符串
7. className
  • 弹框自定义样式名
  • 值为字符串
8. scroll
  • 内容区域是否需要滚动条
  • 值为布尔类型
  • 默认值为:true
9. scrollContentBgColor
  • 滚动包裹器的背景色(当scroll参数为true是才有效)
  • 值为颜色字符串,例:"#F5F5F5"
10. onBoxColse
  • 点击“关闭”按钮时的回调函数
    • 如果返回false,窗口不关闭
    • 无返回值或者返回值为true,则窗口关闭
  • 值为函数类型
  • 默认值为:() => true

提示:

  • 如果内容组件的部分区域需要滚动,请使用Scroll组件

二、全屏窗口示例

<template>
  <div class="fullbox-demo">
    <ul class="form-list">
      <li class="form-item" @click="popupBox1">
        <div>
          <label>收款银行:</label>
          <span>{{bankName}}</span>
        </div>
        <div>
          <Icon name="right-arrow"></Icon>
        </div>
      </li>
      <li class="form-item" @click="popupBox2">
        <div>
          <label>个人信息:</label>
          <span>
            <template v-if="userName && userPhone">{{userName}} / {{userPhone}}</template>
          </span>
        </div>
        <div>
          <Icon name="right-arrow"></Icon>
        </div>
      </li>
    </ul>
  </div>
</template>
<script>
import popupFullBox from '@/components/m/fullBox'
import BaseRadio from '@/components/base/radio/index.vue'
import UserInfo from './components/UserInfo.vue'

const bankMap = {
  '01': '青莲使者',
  '02': '青莲使者',
  '03': '青莲使者',
  '04': '青莲使者',
  '05': '青莲使者',
  '06': '青莲使者',
  '07': '青莲使者',
  '08': '青莲使者',
  '09': '青莲使者',
  '10': '青莲使者',
  '11': '青莲使者',
  '12': '青莲使者',
  '13': '青莲使者',
  '14': '青莲使者',
  '15': '青莲使者',
  '16': '青莲使者',
  '17': '青莲使者',
  '18': '青莲使者'
}

export default {
  name: 'FullBoxDemo',
  data () {
    return {
      bankCode: null,
      bankName: null,
      userName: null,
      userPhone: null
    }
  },
  methods: {
    popupBox1 () {
      const options = Object.keys(bankMap).map((key) => { return { value: key, text: bankMap[key] } })
      const fullBox = popupFullBox({
        title: '付款银行',
        content: BaseRadio,
        contentProps: {
          title: `请选择银行:`,
          value: this.bankCode,
          options
        },
        contentEvents: {
          change: (v) => {
            this.bankCode = v
            this.bankName = bankMap[v]
            console.log('选择收款银行结果:', v)
            // 300毫秒后关闭窗口
            fullBox.close(300)
          }
        },
        contentWrapperStyle: {
          fontSize: '18px',
          lineHeight: '45px'
        }
      })
    },
    popupBox2 () {
      const fullBox = popupFullBox({
        title: '个人信息',
        scroll: false,
        showClose: false,
        content: UserInfo,
        contentProps: {
          name: this.userName,
          phone: this.userPhone
        },
        contentEvents: {
          change: (v) => {
            this.userName = v.name
            this.userPhone = v.phone
            console.log('填写的用户信息:', v)
            // 300毫秒后关闭窗口
            fullBox.close(300)
          }
        },
        contentWrapperStyle: {
          fontSize: '18px',
          lineHeight: '45px'
        }
      })
    }
  }
}
</script>

实现FullBox.vue

<!-- 全屏窗口组件 -->
<template>
  <transition name="pop" :duration="300">
    <div class="fullbox" v-show="visible" :class="className" @touchmove.prevent="" @mousewheel.prevent="">
      <div class="back" v-show="showClose" @click="close"><Icon name="close" flip="horizontal" class="close-icon"></Icon></div>
      <div class="fullbox-pane">
        <div class="title" v-show="title">{{title}}</div>
        <div class="content-wrap" :style="contentWrapperStyle">
          <template v-if="!scroll">
            <slot>
              <component ref="fullBoxContent" v-if="content" :is="content" v-bind="contentProps" v-on="contentEvents"></component>
            </slot>
          </template>
          <template v-if="scroll">
            <Scroll :bounce="false" :eventId="scrollEventId" :scrollContentBgColor="scrollContentBgColor">
              <slot>
                <component ref="fullBoxContent" v-if="content" :is="content" v-bind="contentProps" v-on="contentEvents"></component>
              </slot>
            </Scroll>
          </template>
        </div>
      </div>
    </div>
  </transition>
</template>
<script>
import Scroll from '@/components/base/scroll'
export default {
  name: 'FullBox',
  components: {
    Scroll
  },
  props: {
    // 是否显示窗口
    value: {
      type: Boolean,
      default: false
    },
    // 标题
    title: {
      type: String,
      default: '请选择'
    },
    // 是否显示关闭按钮
    showClose: {
      type: Boolean,
      default: true
    },
    // 弹窗内容组件
    content: Object,
    // 弹窗内容组件的props
    contentProps: Object,
    // 弹窗内容组件的事件
    contentEvents: Object,
    // 内容包裹器样式
    contentWrapperStyle: Object,
    // 样式名
    className: {
      type: String,
      default: ''
    },
    // 内容区域是否需要滚动条
    scroll: {
      type: Boolean,
      default: true
    },
    // 滚动包裹器的背景色
    scrollContentBgColor: String,
    // 点击“关闭”按钮时的回调函数(如果返回false,窗口不关闭;无返回值或者返回值为true,则窗口关闭)
    onBoxColse: {
      type: Function,
      default: () => true
    }
  },
  data () {
    return {
      // 是否显示
      visible: this.value,
      // 滚动条初始化事件id
      scrollEventId: 'fullbox' + new Date().getTime()
    }
  },
  watch: {
    value (val) {
      this.visible = val
    },
    visible (val) {
      if (val && this.scroll) {
        this.$nextTick(() => {
          this.$eventBus.$emit('init-scroll-' + this.scrollEventId)
        })
      }
    }
  },
  methods: {
    /**
     * 关闭窗口
     * @param delayTime 延时关闭时间(单位:毫秒)
     */
    close (delayTime) {
      if (this.onBoxColse(this.$refs.fullBoxContent) !== false) {
        this.doClose(delayTime)
      }
    },
    // 执行关闭窗口操作
    doClose (delayTime) {
      if (delayTime && typeof delayTime === 'number' && delayTime > 0) {
        setTimeout(() => {
          this.visible = false
          this.$emit('input', this.visible)
          this.$emit('close')
        }, delayTime)
      } else {
        this.visible = false
        this.$emit('input', this.visible)
        this.$emit('close')
      }
    }
  }
}
</script>
<style lang="scss" scoped>
.fullbox {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background-color: #FFF;
  box-shadow: 0 0 5px rgba(0, 0, 0, .3);
  transition: all .3s ease;
  z-index: 999;
  .back {
    height: $app-title-bar-height;
    width: $app-title-bar-height;
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    .close-icon {
      font-size: 30px;
      color: #666;
    }
  }
  .fullbox-pane {
    display: flex;
    flex-direction: column;
    height: 100%;
    .title {
      flex: none;
      padding: 0 3em 0 1em;
      height: $app-title-bar-height;
      line-height: $app-title-bar-height;
      background-color: #f5f5f5;
      border-bottom: solid 1px #E5E5E5;
      font-size: 32px;
      text-align: left;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      color: #666;
    }
    .content-wrap {
      position: relative;
      display: flex;
      flex-direction: column;
      // align-items: center;
      justify-content: center;
      flex: 1;
      overflow: hidden;
    }
  }
}

.pop-enter-active, .pop-leave {
  transform: translateX(0);
  // transform: scale(1);
  // opacity: 1;
}
.pop-enter, .pop-leave-active {
  transform: translateX(100%);
  // transform: scale(.5);
  // opacity: 0;
}
</style>

index.js

/**
 * 全屏窗口组件
 */
import Vue from 'vue'
import FullBox from './FullBox.vue'

// FullBox构造函数
const FullBoxConstructor = Vue.extend({
  extends: FullBox
})

function initInstance (instance, options) {
  // 窗口标题
  instance.title = options.title || ''
  // 是否显示关闭按钮
  instance.showClose = typeof options.showClose === 'boolean' ? options.showClose : true
  // 窗口内容组件
  instance.content = typeof options === 'object' && options._isVue ? options : options.content
  // 窗口内容组件参数
  instance.contentProps = typeof options.contentProps === 'object' ? options.contentProps : {}
  // 窗口内容组件事件
  instance.contentEvents = typeof options.contentEvents === 'object' ? options.contentEvents : {}
  // 内容包裹器样式
  instance.contentWrapperStyle = options.contentWrapperStyle
  // 自定义样式名
  instance.className = options.className || ''
  // 是否显示滚动条
  instance.scroll = options.scroll === false ? false : true
  // scroll-content 背景颜色
  instance.scrollContentBgColor = options.scrollContentBgColor
  // “关闭”回调函数
  if (options.onBoxColse && typeof options.onBoxColse === 'function') {
    instance.onBoxColse = options.onBoxColse
  }
  // 父节点
  let parentElement = options.parentElement || document.body

  // 关闭时移除
  instance.$on('input', visible => {
    if (!visible) {
      setTimeout(() => {
        parentElement.removeChild(instance.$el)
        instance.$destroy()
      }, 2000)
      /* // 获取popBox元素,如果Popop组件从refs中获取,如果Alert或Confirm组件,先获取Popop,在从Popop组件refs中获取
        let popBox = instance.$refs.popBox || (instance.$refs.basePop && instance.$refs.basePop.$refs.popBox)
        popBox.addEventListener('transitionend', event => {
        // 动画完成后移除DOM节点
        // parentElement.removeChild(instance.$el)
        if (event.target.parentNode && event.target.parentNode.parentNode) {
          event.target.parentNode.parentNode.removeChild(event.target.parentNode)
        }
        // 销毁组件
        instance.$destroy()
      }) */
    }
  })
  // console.log('instance.$el=', instance.$el)
  // 将节点添加到文档
  parentElement.appendChild(instance.$el)

  instance.visible = true
  instance.closed = false
}

// 显示弹出窗口
export function popupFullBox (options = {}, vmExtends = {}) {
  let instance = new FullBoxConstructor({
    el: document.createElement('div'),
    ...vmExtends
  })
  initInstance(instance, options)
  return instance
}

export default popupFullBox

「欢迎在评论区讨论」

希望看完的朋友可以给个赞,鼓励一下