# 通用组件 notification 信息提示弹框

|-- notification ===> component folder
    |-- index.js
    |-- func-notification.js
    |-- function.js
    |-- notification.vue
1
2
3
4
5
// main.js
import Vue from 'vue'
import Notification from 'components/notification'

Vue.use(Notification)
1
2
3
4
5
// index.js
import Notification from './notification.vue'
import notify from './function'
export default (Vue) => {
  Vue.component(Notification.name, Notification)
  Vue.prototype.$notify = notify
}
1
2
3
4
5
6
7
// func-notification.js
import Notification from './notification'

export default {
  extends: Notification,
  computed: {
    style(){
      return {
        position: 'fixed',
        right: '20px',
        top: `${this.verticalOffset}px`,
      }
    }
  },
  mounted(){
    this.createTimer()
  },
  methods: {
    createTimer(){
      if(this.autoClose){
        this.timer = setTimeout(() => {
          this.visible = false
        },this.autoClose)
      }
    },
    clearTimer(){
      if(this.timer) {
        clearTimeout(this.timer)
      }
    },
    afterEnter(){
      // debugger // eslint-disable-line
      this.height = this.$el.offsetHeight
    }
  },
  beforeDestory(){
    this.clearTimer()
  },
  data(){
    return {
      verticalOffset: 0,
      autoClose: 3000,
      height: 0,
      visible: false
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// function.js
import Vue from 'vue'
import Component from './func-notification'

const NotificationConstructor = Vue.extend(Component)

const instances = []
let seed = 1

const removeInstance = (instance) => {
  if(!instance) return
  const len = instances.length
  const index  = instances.findIndex(inst => instance.id === inst.id)

  instances.splice(index, 1)
  
  if(len <= 1) return
  const removeHeight = instance.vm.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

  const {
    autoClose,
    ...rest
  } = options
  
  const instance  = new NotificationConstructor({
    propsData: {
      ...rest
    },
    data: {
      autoClose: autoClose === undefined ? 3000 : autoClose
    }
  })

  const id = `notification_${seed++}`
  instance.id = id
  instance.vm = instance.$mount()
  document.body.appendChild(instance.vm.$el)
  instance.vm.visible = true

  let verticalOffset = 0
  instances.forEach(item => {
    verticalOffset += item.$el.offsetHeight + 16
  })
  verticalOffset += 16
  instance.verticalOffset = verticalOffset
  instances.push(instance)
  instance.vm.$on('closed',() => {
    removeInstance(instance)
    document.body.removeChild(instance.vm.$el) // 删除dom节点
    instance.vm.$destroy() // 只会销毁这个dom对象,并不会也不能从document中删除dom节点
  })
  instance.vm.$on('close', () => {
    instance.vm.visible = false
  })
  return instance.vm
}

export default notify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// notification.vue
<template>
  <transition name="fade"
    @after-leave='afterLeave'
    @after-enter='afterEnter'
  >
    <div class="notification"
      :style="style"
      v-show="visible"
      @mouseenter='clearTimer'
      @mouseleave='createTimer'
    >
      <span class="content">{{content}}</span>
      <a class="btn" @click="handleClose">{{btn}}</a>
    </div>
  </transition>
</template>

<script>
export default {
  name: 'Notification',
  props: {
    content: {
      type: String,
      required: true
    },
    btn: {
      type: String,
      default: '关闭'
    }
  },
  data(){
    return {
      visible: true
    }
  },
  computed: {
    style(){
      return {}
    }
  },
  methods: {
    handleClose(e) {
      e.preventDefault()
      this.$emit('close')
    },
    afterLeave(){
      this.$emit('closed')
    },
    afterEnter(){},
    clearTimer(){},
    createTimer(){}
  }
}
</script>

<style lang="scss" scoped>
@import '@/themes/global.scss';
.notification {
  display: flex;
  align-items: center;
  background: #303030;
  color: rgba(255,255,255,1);
  padding: 20px;
  position: fixed;
  min-width: 280px;
  box-shadow: 0px 3px 5px -1px rgba(0,0,0,.2), 0px 6px 10px 0px rgba(0,0,0,.2);
  flex-wrap: wrap;
  transition: all .3s;

  .content {
    padding: 0;
  }
  .btn {
    color: #ff4081;
    padding-left: 24px;
    margin-left: auto;
    cursor: pointer;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81