本人在造轮子过程中遇到写 toast 组件时为考虑方便用户调用,因此采用插件方式写 toast,
最终用户调用代码为
<template>
<button @click="showToast"></button>
</template>
<script>
export default {
methods: {
showToast() {
this.$toast(`您的余额为 ${parseInt(Math.random() * 100)}. 需要充值`, {
enableHtml: false, // 是否开启内嵌html元素
autoClose: false, // 是否自动关闭
position: 'top', // 展示位置
closeButton: { // 关闭按钮配置
text: '充值',
callback: (toast) => {
toast.log() // 关闭后触发 toast 中的方法
}
}
})
}
}
</script>
展示效果为
![1087e758471222d8038f26f5de784044.gif](https://i-blog.csdnimg.cn/blog_migrate/d1aa98eaaa1f7bbee992082e0e5e4bf5.gif)
插件代码
import Toast from './toast.vue'
let currentToast
export default {
install(Vue, options) {
Vue.prototype.$toast = (message, toastOptions) => {
// 避免出现多个 toast 重叠
if(currentToast) {
currentToast.close()
}
currentToast = createToast({
Vue,
message,
propsData: toastOptions,
onClose: () => {
currentToast = null
}
})
}
}
}
function createToast({ Vue, message, propsData, onClose }) {
const Constructor = Vue.extend(Toast)
let toast = new Constructor({
propsData
})
toast.$slots.default = [message]
toast.$mount()
toast.$on('close', onClose)
document.body.appendChild(toast.$el)
return toast
}
toast 组件代码
<template>
<div class="wrapper" :class="toastClasses">
<div class="toast" ref="toast">
<div class="message">
<slot v-if="!enableHtml"></slot>
<div v-else v-html="$slots.default[0]"></div>
</div>
<div class="line" ref="line"></div>
<span class="close" v-if="closeButton" @click="onClickClose">{{closeButton.text}}</span>
</div>
</div>
</template>
<script>
export default {
name: "WheelToast",
props: {
autoClose: {
type: [Boolean, Number],
default: 5,
validator(value) {
return typeof value === 'boolean' || typeof value === 'number'
}
},
closeButton: {
type: Object,
// 传 object 时得用函数返回,不然属性会被覆盖,跟 data 一个道理
default() {
return {
text: "关闭",
// 点击关闭按钮的回调函数
callback: undefined
}
}
},
enableHtml: {
type: Boolean,
default: false
},
position: {
type: String,
default: "middle",
validator(value) {
return ["top", "middle", "bottom"].indexOf(value) >= 0;
}
}
},
computed: {
toastClasses() {
return [`position-${this.position}`];
}
},
mounted() {
this.updateStyle();
this.execAutoClose();
},
methods: {
updateStyle() {
// 实现多行文字父元素需要用到 min-height,此时子元素 height 为 100% 获取不到父元素 height,因此异步获取父元素高
this.$nextTick(() => {
this.$refs.line.style.height = `${this.$refs.toast.getBoundingClientRect().height}px`;
});
},
execAutoClose() {
if (this.autoClose) {
setTimeout(() => {
this.close();
}, this.autoClose * 1000);
}
},
close() {
this.$el.remove();
this.$emit("close");
this.$destroy();
},
log() {
console.log("调用了 toast 的 log 方法");
},
onClickClose() {
this.close();
if (this.closeButton && typeof this.closeButton.callback === "function") {
// 调用 callback 传入 this 可让调用回调时拿到 toast 实例,从而调用实例里的方法
this.closeButton.callback(this);
}
}
}
};
</script>
备注:为了简洁去掉了 css 代码
使用插件
import Vue from 'vue'
import plugin from './plugin'
Vue.use(plugin)
之后便可以在组件中用this.$toast(MESSAGE, OPTIONS)
的方式调用 toast
插件啦
总结
制作toast插件的详细过程可以查看我的造轮子项目https://github.com/zyqq/wheel/tree/toast toast分支下的commit记录