疗效如何? 是不是很酷? 通知有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;
}
}
}
通知组件的实现原理就这么简单,童鞋们明白了吗? 有什么不懂的可以问我; 粉丝的支持和您的阅读是我创作的动力,感谢您的阅读!