本文启发来自b站小野森森
功能实现:
调用方式
可以使用Message.type(message:'自定义message'),也可以使用Message(type:xxx, message:xxx)
<button
@click="
Message({
type: types.ERROR,
message: 'This is a ERROR text.',
})
">
SHOW ERROR
</button>
<button
@click="
Message.error({
message: 'This is a message text.',
})
">
SHOW ERROR
</button>
显示效果
随着多次的点击,下面的message框自动排列,当上面的message框消失,下面的自动补上去
设计思路:
参数props:
分析 : 内部的话,我们要考虑传入什么参数,显然模态框是应该有message这条信息,和它的类型。
type:类型通常又区分为success,error,info,warning 所以 我们暂时得出这样的结果 模态框
- message
- type
- success
- warning
- info
- error
那么,我们就能写出这个组件的代码了
针对type的处理
export default {
SUCCESS: 'success',
WARNING: 'warning',
MESSAGE: 'message',
ERROR: 'error'
}
<template>
<div
:class="['messageModel', props.type]"
:style="{ 《这里我们待会再去思考,先搭架子》
top: top + 'px',
}">
{{ message }}
</div>
</template>
<script setup>
import types from './types.js'
import { ref } from 'vue'
let top = ref(0) //待会思考 用于控制model的top属性
const setTops = (tops) => {
top.value = tops
}
const props = defineProps({
type: {
type: String,
default: types.SUCCESS,
},
message: {
type: String,
default: 'this is a message',
},
})
样式
.messageModel {
height: 50px;
position: fixed;
left: 50%;
transform: translateX(-50%);
text-align: center;
border-radius: 10px;
line-height: 50px;
}
.success {
background-color: #f0f9eb;
color: #529b2e;
}
.warning {
background-color: #fdf6ec;
color: #b88230;
}
.message {
background-color: #f4f4f5;
color: #73767a;
}
.error {
background-color: #fef0f0;
color: #c45656;
}
如何挂载
很容易的 我们想到使用Vue3的createApp方法,把我们设计的Message转成一个Vue实例去通过
createDocumentFragment 使用mount 挂载他。(createApp(组件,props))
因为我们是想通过方法的方式直接呈现这个message模态框,所以我们在这个方法里,必须要封装挂载组
件实例并展示的方法
创建index.js 文件,暴漏出message,我们在其他文件使用的就是该文件导出的message
import { ref, createApp, watch } from 'vue'
import MessageComponent from './Message.vue' //导入vue组件 目的通过组件创建实例
import { findIndex } from '@/shared/utils' 针对findIndex 封装的工具函数
内容如下
export function findIndex(array, value) {
return array.findIndex(item => item === value);
}
--------------------------------------------------------------------
import types from './types.js'
const Message = (options) => {
const msg = createApp(MessageComponent, options)
showMessage(msg)//show方法 展示模态框 并在里面添加关闭方法的调用,关闭方法通过设计定时器,调用unmount卸载组件实例实现
}
function showMessage(msg) {
const Frog = document.createDocumentFragment()
const vm = msg.mount(Frog)
// messageArr.value.push(vm) 一会儿监听每个模态框位置使用
document.body.appendChild(Frog)
// setTop(vm) 一会儿监听每个模态框位置使用
//watch(messageArr, () => setTop(vm)) 一会儿监听每个模态框位置使用
hideMessage(msg, 2000, vm)
}
function hideMessage(msg, durtime, vm) {
vm.timer = setTimeout(() => {
msg.unmount()
// messageArr.value = messageArr.value.filter((item) => item !== vm) 一会儿监听每个模态框位置使用
clearTimeout(vm.timer)
vm.timer = null
}, durtime)
}
如何调用
现在,我们要去考虑调用方式 因为目前只能通过message(type:xxx,message:xxx)去调用
我们知道 message本身是一个方法,为了可以使用message.type的方式调用,我们就把原本的方法赋值给message[type] 就ok了,同时我们要去把options里面的type属性去改一下
上代码:
Object.values(types).forEach((type) => {
Message[type] = (options) => {
options['type'] = type
return Message(options)
}
})
如何控制位置
- 思考如何解决模态框的位置问题,因为当上一个模态框消失,那么下一个模态框的位置就应该上移,这是一个需要实时监听解决的问题。所以我们使用watch方法
- 同样 因为我们是要改变的是模态框本身的属性,上面可能大家也看到了style中设置了top属性,该属性就控制了它的位置。我们从组件本身暴漏出修改top的方法,使得拿到实例之后可以调用这些方法改变top属性 所以 我需要在组件中添加
defineExpose({
setTops,
height: 50,
margin: 20,
})
在index.js文件中添加一个响应式数据messageArr 通过监听他的变化来控制当前的高度变化
const messageArr = ref([])
function showMessage(msg) {
const Frog = document.createDocumentFragment()
const vm = msg.mount(Frog)
messageArr.value.push(vm) -----------------
document.body.appendChild(Frog)
setTop(vm) --------------------
watch(messageArr, () => setTop(vm)) //写在这里是为了给每一个vm都可以监听messageArr,即使使本身top做出变化-----------------
hideMessage(msg, 2000, vm)
}
function hideMessage(msg, durtime, vm) {
vm.timer = setTimeout(() => {
msg.unmount()
messageArr.value = messageArr.value.filter((item) => item !== vm) //触发watch 再次改变方法 --------------
clearTimeout(vm.timer)
vm.timer = null
}, durtime)
}
调用方法 改变组件实例的top属性
function setTop(vm) {
const { setTops, height, margin } = vm
console.log(setTops, height, margin)
const currentIndex = findIndex(messageArr.value, vm)//findIndex是被封装后的一个工具函数 用于找到当前实例所属的index
vm.setTops(margin * (currentIndex + 1) + height * currentIndex)
}
动画
该案例并未加动画效果,可以通过transition 添加动画,添加动画就需要一个v-show来控制显示和隐藏,我们设置一个promise 里面调用定时器用来改变显示和隐藏的状态。 模态框显示的时候调用方法展示
当模态框卸载方法调用时候 我们通过await使控制函数的定时器可以执行完毕,在去卸载掉组件 防止直接卸载组件导致动画未完全展示
如果要加动画 把setVisable方法暴漏出去 并多设置一个状态visible
即
const state = reactive({
visible: false,
top: 0
})
const setVisible = (isVisible) => {
return new Promise(resolve => {
state.visible = isVisible;
t = setTimeout(() => {
clearTimeout(t);
t = null;
resolve('');
}, 300);
});
}
defineExpose({
setVisible,
setTop,
height: 40,
margin: 20
})
在showMessage中调用 vm.setVisible(true)
在hideMessage 中
vm.timer = setTimeout(async () => {
await vm.setVisible(false)
app.unmount()
messageArr.value = messageArr.value.filter((item) => item !== vm)
clearTimeout(vm.timer)
vm.timer = null
}, duration || 3000)
整体代码
app.vue
<script setup>
import Message, { types } from './components/Message-my'
</script>
<template>
<div>
<!-- <jspp-message
type="warning"
message="This is a test for Message Component."
:duration="5000"
></jspp-message> -->
<button
@click="
Message.success({
message: 'This is a SUCCESS text.',
})
">
SHOW SUCCESS
</button>
<button
@click="
Message.warning({
message: 'This is a WARNING text.',
})
">
SHOW WARNING
</button>
<button
@click="
Message({
message: 'This is a MESSAGE text.',
})
">
SHOW MESSAGE
</button>
<button
@click="
Message({
type: types.ERROR,
message: 'This is a ERROR text.',
})
">
SHOW ERROR
</button>
</div>
</template>
<style lang="scss"></style>
message.vue
<template>
<div
:class="['messageModel', props.type]"
:style="{
top: top + 'px',
}">
{{ message }}
</div>
</template>
<script setup>
import types from './types.js'
import { ref } from 'vue'
// 我想使用type 怎么做 还是传递参数
let top = ref(0)
const setTops = (tops) => {
top.value = tops
}
const props = defineProps({
type: {
type: String,
default: types.SUCCESS,
},
message: {
type: String,
default: 'this is a message',
},
})
console.log(props.type)
defineExpose({
setTops,
height: 50,
margin: 20,
})
// 点击 num++
</script>
<style lang="scss" scoped>
.messageModel {
height: 50px;
position: fixed;
left: 50%;
transform: translateX(-50%);
text-align: center;
border-radius: 10px;
line-height: 50px;
}
.success {
background-color: #f0f9eb;
color: #529b2e;
}
.warning {
background-color: #fdf6ec;
color: #b88230;
}
.message {
background-color: #f4f4f5;
color: #73767a;
}
.error {
background-color: #fef0f0;
color: #c45656;
}
</style>
index.js
import { ref, createApp, watch } from 'vue'
import MessageComponent from './Message.vue'
import { findIndex } from '@/shared/utils'
import types from './types.js'
const messageArr = ref([])
//1 . 点击事件
const Message = (options) => {
const msg = createApp(MessageComponent, options)
showMessage(msg)
}
Object.values(types).forEach((type) => {
Message[type] = (options) => {
options['type'] = type
console.log(options.type)
return Message(options)
}
})
// 用num的缺点是什么 :当hide取消挂载的时候 并不能监听到 如果我用watch监听num的变化 也不能改变原本绑定给style的num 是style限制了
//我不用style 我要写一个方法
function showMessage(msg) {
const Frog = document.createDocumentFragment()
const vm = msg.mount(Frog)
//mount() 返回的是vue实例 我要拿到他 注销他
messageArr.value.push(vm)
setTop(vm)
watch(messageArr, () => setTop(vm))
document.body.appendChild(Frog)
hideMessage(msg, 2000, vm)
}
function hideMessage(msg, durtime, vm) {
vm.timer = setTimeout(() => {
msg.unmount()
messageArr.value = messageArr.value.filter((item) => item !== vm)
//调用方法 改变top
clearTimeout(vm.timer)
vm.timer = null
}, durtime)
}
function setTop(vm) {
const { setTops, height, margin } = vm
console.log(setTops, height, margin)
const currentIndex = findIndex(messageArr.value, vm)
console.log(margin * (currentIndex + 1) + height * currentIndex)
vm.setTops(margin * (currentIndex + 1) + height * currentIndex)
}
export { types }
export default Message
types.js
export default {
SUCCESS: 'success',
WARNING: 'warning',
MESSAGE: 'message',
ERROR: 'error',
}