elementui加载组件-基于Vue3的高性能Notification(通知)组件的简单实现

2023-08-26 0 1,036 百度已收录

疗效如何? 是不是很酷? 通知有5种类型,其中4种是有状态的,分别是:成功、警告、信息、错误; 之后,通知有4个位置,分别是:左上、右上、左下、右下;

首先我们从其他模块中导出一些功能和组件,代码如下:

import { createApp, h, reactive, TransitionGroup, watch } from 'vue'
import { isStr } from '../../types' // 是否字符串 这个大家应该都知道怎么写吧?
import { getMaxZIndex } from '../../utils' // 获取最大的层
import { convertOptions } from '../message' // 转化选项
import XNotification from './Notification.vue' // 通知组件,文章下面有源码

getMaxZIndex函数的定义:

export function getMaxZIndex () {
  let maxZIndex = 1000
  const els = document.querySelectorAll('body>*')
  for (let i = 0, len = els.length; i  maxZIndex) {
      maxZIndex = +zIndex
    }
  }
  return maxZIndex + 2
}

ConvertOptions函数的定义:

export function convertOptions (options, type) {
  if (isStr(options)) {
    options = { message: options }
  }
  if (type) {
    options.type = type
  }
  options.key = genKey()
  return options
}

为了让通知组件的位置关系更容易管理,我们将通知项放入一个内部容器元素中elementui加载组件,只需要控制内部容器元素的定位,不需要对每个通知项进行定位,然后使用过渡组创建动画,这使得实现极其简单且易于维护!

因为通知出现的地方有4个,为了方便管理,我们创建4个字段来存储这个通知选项; 代码如下:

let app
const items = reactive({
    'top-left': [],
    'top-right': [],
    'bottom-left': [],
    'bottom-right': []
  })

对了elementui加载组件,我们还声明了一个未初始化的变量app,app是createApp函数返回的,items是一个响应式对象。 现在,我们创建一个通知函数messageCreator; 这个函数比较长,我直接在代码中添加必要的注释作为解释,代码如下:

const messageCreator = function (options, type) {
    options = convertOptions(options, type) // 将type并入options
    const position = options.position || 'top-right' // 通知默认出现在顶右
    const tarItems = items[position] // 拿到用于存储position位置的数组
    if (tarItems) {
      // 如果位置包含top,则在数组尾部添加,否则在首部添加
      position.includes('top') ? tarItems.push(options) : tarItems.unshift(options)
      if (!app) { // 如果不存在app实例,我们新建一个
        const wrapprCls = 'x-notification-wrapper'
        app = createApp({
          render () {
            return h('div', {}, Object.keys(items).map(k => {
              const cls = `${wrapprCls}_${k}`
              const curItems = items[k]
              return h('div', { class: ['position-box', cls] }, h(TransitionGroup, { tag: 'div', name: cls }, {
                  // 将所有通知组件放入过渡组组件的默认插槽
                  default: () => curItems.map((_, i) => {
                    return h(XNotification, {
                      ..._,
                      onClose: () => { // 当收到通知组件内部发出的关闭事件时
                        curItems.splice(i, 1) ,// 移除item
                        _.onClose && _.onClose() // 调用options中的onClose方法
                      }
                    }, {
                      // 在通知组件的默认插槽渲染通知内容
                      default: () => isStr(_.message) && _.dangerouslyUseHTMLString
                        ? h('div', { innerHTML: _.message })
                        : _.message
                    })
                  })
                })
              )
            }))
          }
        })
        const el = document.createElement('div') // 创建应用挂载点元素
        el.className = wrapprCls
        el.style.zIndex = getMaxZIndex() // 将层设置为顶层
        document.body.appendChild(el) // 添加到body中
        app.mount(el) // app挂载
        watch( // 监听4个位置的所有items长度之和的变化
          () => Object.keys(items).reduce((t, k) => t + items[k].length, 0),
          (n, o) => {
            if (n > o) el.style.zIndex = getMaxZIndex() // 如果长度增加了,就将层设置为顶层
          }
        )
      }
      return {
        close () { // 用于手动关闭当前打开的通知
          const index = tarItems.indexOf(options)
          index > -1 && tarItems.splice(index, 1)
        }
      }
    }
  }

以上是messageCreator函数的源码。 现在我们给它添加几个不同状态的特殊方法。 这些方法内部都会转入messageCreator函数的调用。 代码如下:

// 使用forEach一次性添加4个方法,这样写是不是很简洁?
;['success', 'warning', 'info', 'error'].forEach(type => {
    messageCreator[type] = function (options) {
      return messageCreator(options, type)
    }
  })

最后我们定义一个关闭所有通知的方法,并导入messageCreator函数,代码如下:

// 关闭所有通知其实就是清空items对象里面的所有数组
messageCreator.closeAll = function () {
    Object.keys(items).forEach(k => {
      items[k] = []
    })
  }
export default messageCreator

现在,我们将其添加到app实例中,以便您可以使用element-ui示例进行测试,代码如下:

// app是大家在main.js中通过调用createApp函数返回的
app.config.globalProperties.$notify = messageCreator

最后给出XNotification组件的源码:


  

{{ title }}

import { computed, onMounted, ref } from 'vue' import { N, S, BTrue, oneOf } from '../../types' // 见下面 const props = defineProps({ title: S, type: { validator: v => oneOf(['success', 'warning', 'info', 'error'], v) }, iconClass: S, customClass: S, duration: { type: N, default: 4500 }, showClose: BTrue }) const emit = defineEmits(['close']) const cls = 'x-notification' const icon = computed(() => props.iconClass || (props.type && `x-icon-${props.type}`)) function onClose () { emit('close') } const visible = ref(false) onMounted(() => { visible.value = true // 注意:必须在挂载完成回调中设为可见,否则通知打开时没有动画 props.duration && setTimeout(onClose, props.duration) })

types 模块的一些常量定义:

export const N = Number
export const S = String
export const B = Boolean
export const BTrue = { type: B, default: true }
export const isStr = v => typeof v === 'string'
export const oneOf = (/** @type {any[]} */ arr, v) => arr.includes(v)

notification.scss 文件:

@import './common/var.scss'; // 参照element-ui的scss变量定义,比较长就不放上了
.x-notification {
  padding: 7px 15px;
  transition: all .3s;
  &-wrapper {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    pointer-events: none;
    .position-box {
      position: absolute;
    }
    &_top-right, &_bottom-right {
      right: 0;
      &-leave-active {
        position: absolute;
      }
      &-enter-from, &-leave-to {
        opacity: 0;
        transform: translateX(24px);
      }
    }
    &_top-left, &_bottom-left {
      left: 0;
      &-leave-active {
        position: absolute;
      }
      &-enter-from, &-leave-to {
        opacity: 0;
        transform: translateX(-24px);
      }
    }
    &_top-left {
      top: 0;
    }
    &_top-right {
      top: 0;
    }
    &_bottom-left {
      bottom: 0;
    }
    &_bottom-right {
      bottom: 0;
    }
  }
  &_inner {
    width: 320px;
    pointer-events: all;
    position: relative;
    display: flex;
    border-radius: 8px;
    padding: 14px 26px 14px 13px;
    background-color: #fff;
    border: 1px solid $--border-color-lighter;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
  }
  &_icon {
    font-size: 24px;
    color: $--color-text-secondary;
    &.is-success {
      color: $--color-success;
    }
    &.is-warning {
      color: $--color-warning;
    }
    &.is-error {
      color: $--color-danger;
    }
  }
  &_body {
    margin-left: 13px;
    margin-right: 8px;
  }
  &_title {
    font-size: 16px;
    color: $--color-text-primary;
  }
  &_content {
    font-size: 14px;
    color: $--color-text-regular;
    margin-top: 3px;
  }
  &_close {
    cursor: pointer;
    font-size: 16px;
    position: absolute;
    top: 12px;
    right: 12px;
    color: $--color-info;
    &:hover {
      color: $--color-text-regular;
    }
  }
}

通知组件的实现原理就这么简单,童鞋们明白了吗? 有什么不懂的可以问我; 粉丝的支持和您的阅读是我创作的动力,感谢您的阅读!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 elementui elementui加载组件-基于Vue3的高性能Notification(通知)组件的简单实现 https://www.wkzy.net/game/154877.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务