手把手教你封装Vue3模态框model-message

lxf2023-07-18 07:00:02

本文启发来自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框消失,下面的自动补上去

手把手教你封装Vue3模态框model-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',
}

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!