利用vue从零实现一个消息通知组件

利用vue从零实现一个消息通知组件

平时,我们肯定用过类似element-ui,antd等一些UI框架,感受它们带给我们的便利。但当我们的需求或者设计这些框架内置的相差太大,用起来,就会觉得特别别扭,这时候,就有必要自己来重新造轮子。

重新造轮子,有几个好处,1.所有代码都是服务你的业务,没有太多用不上的东西。2.代码是由自己维护,而不是第三方,方便维护。3.提升自己的视野,让自己站在更高的角度来看问题。

好了,那话不多说,开始我们的组件开发吧!

文件目录的组件

工欲善其事,必先利其器,要想实现一个组件,一个好的目录结构,即可以划分职责,不同模块处理不同的逻辑!

我的目录结果是这样的:
目录结构

接下来,我们依次对notification.vue, notify.js, index.js三个文件作介绍。

notification.vue

notification.vue是一个负责消息通知组件的视觉呈现,里面的逻辑很简单。

<template>
  <transition name="fade" @after-enter="handleAfterEnter">
    <div class="notification" :style="style" v-show="visible">
      <span class="notification__content">
        {{content}}
      </span>
      <span class="notification__btn" @click="handleClose">{{btn}}</span>
    </div>
  </transition>
</template>
<script>
export default {
  name: 'Notification',
  props: {
    content: {
      type: String,
      required: true
    },
    btn: {
      type: String,
      default: '关闭'
    }
  }
}
</script>
<style lang="less" scoped>
.fade-enter-active, .fade-leave-active{
  transition: opacity 1s;
}
.fade-enter, .fade-leave-to{
  opacity: 0;
}
.notification{
  display: flex;
  background-color: #303030;
  color: rgba(255, 255, 255, 1);
  align-items: center;
  padding: 20px;
  position: fixed;
  min-width: 280px;
  box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.3);
  flex-wrap: wrap;
  transition: all 0.3s;
  &__content{
    padding: 0;
  }
  &__btn{
    color: #ff4081;
    padding-left: 24px;
    margin-left: auto;
    cursor: pointer;
  }
}
</style>

notify.js

notify.js是一个处理消息通知组件的逻辑部分,其主要作用是暴露一个notify的方法出去。代码如下:

import Vue from 'vue'
import Notification from './notification'

const NotificationConstructor = Vue.extend(Notification)

const instances = []
let seed = 1
const removeInstance = (instance) => {
  if (!instance) return
  const len = instances.length
  const index = instances.findIndex(ins => instance.id === ins.id)

  instances.splice(index, 1)

  if (len <= 1) return
  const removeHeight = instance.height
  for (let i = index; i < len - 1; i++) {
    instances[i].verticalOffset = parseInt(instances[i].verticalOffset) - removeHeight - 16
  }
}
const notify = (options = {}) => {
  if (Vue.prototype.$isServer) return
  // 获取vue实例
  let instance = new NotificationConstructor({
    propsData: options,
    data() {
      return {
        verticalOffset: 0,
        timer: null,
        visible: false,
        height: 0
      }
    },
    computed: {
      style() {
        return {
          position: 'fixed',
          right: '20px',
          bottom: `${this.verticalOffset}px`
        }
      }
    },
    mounted() {
      this.createTimer()
      this.$el.addEventListener('mouseenter', () => {
        if (this.timer) {
          this.clearTimer(this.timer)
        }
      })
      this.$el.addEventListener('mouseleave', () => {
        if (this.timer) {
          this.clearTimer(this.timer)
        }
        this.createTimer()
      })
    },
    updated() {
      this.height = this.$el.offsetHeight
    },
    beforeDestroy() {
      this.clearTimer()
    },
    methods: {
      createTimer() {
        this.timer = setTimeout(() => {
          this.visible = false
          document.body.removeChild(this.$el)
          removeInstance(this)
          this.$destroy()
        }, options.timeout || 3000)
      },
      clearTimer() {
        if (this.timer) {
          clearTimeout(this.timer)
        }
      },
      handleClose() {
        this.visible = false
        document.body.removeChild(this.$el)
        removeInstance(this)
        this.$destroy(true)
      },
      handleAfterEnter() {
        // eslint-disable-next-line no-debugger
        this.height = this.$el.offsetHeight
      }
    }
  })

  const id = `notification_${seed++}`
  instance.id = id
  // 生成vue中的$el
  instance = instance.$mount()
  // 将$el中的内容插入dom节点中去
  document.body.appendChild(instance.$el)
  instance.visible = true

  // eslint-disable-next-line no-unused-vars
  let verticalOffset = 0

  instances.forEach(item => {
    verticalOffset += item.$el.offsetHeight + 16
  })

  verticalOffset += 16
  instance.verticalOffset = verticalOffset

  instances.push(instance)

  return instance
}

export default notify

index.js

index.js主要是对notification.vue组件实现注册,notify方法的挂载。代码如下:

import Notification from './notification'
import notify from './notify'

export default (Vue) => {
  Vue.component(Notification.name, Notification)
  Vue.prototype.$notify = notify
}

在main.js引入

import Notification from './components/notification'
Vue.use(Notification)

使用

this.$notify({
  content: 'Hello'
})

效果

效果

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值