文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue3+hook如何实现弹窗组件

2023-07-04 21:22

关注

本文小编为大家详细介绍“Vue3+hook如何实现弹窗组件”,内容详细,步骤清晰,细节处理妥当,希望这篇“Vue3+hook如何实现弹窗组件”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

要封装什么

如果是普通弹窗使用的话,直接使用el-dialog组件已经足够了

但我还是一个比较爱折腾的人,我们先看看官方dialog文档有什么可以添加的功能

...

大概看了一下,我打算封装一下功能

封装Dialog

确定了要封装的功能之后,先来一个简单的dialog组件。

把双向绑定处理一下,这样外部就可以直接通过v-model直接控制弹窗了。

<template>    <el-dialog :model-value="props.modelValue"></el-dialog></template><script setup>interface PropsType {  modelValue?: boolean;}const props = withDefaults(defineProps<PropsType>(), {  modelValue: false,});const emits = defineEmits<{  (e: "update:modelValue"): void;}>();</script>

header

这里使用到图标库@element-plus/icons-vue

如没有安装,请执行npm install @element-plus/icons-vue

使用el-dialog提供的header插槽,将全屏图表和关闭图标放置到右上角中。给el-dialog传递show-close属性关闭默认图标。

<template>  <el-dialog :model-value="props.modelValue" :show-close="false">    <template #header>      <div>        <span>{{ props.title }}</span>      </div>      <div>        <el-icon><FullScreen /></el-icon>        <el-icon><Close /></el-icon>      </div>    </template>  </el-dialog></template><script setup>import { FullScreen, Close } from "@element-plus/icons-vue";</script><style scoped>// 处理样式:deep(.el-dialog__header) {  border-bottom: 1px solid #eee;  display: flex;  padding: 12px 16px;  align-items: center;  justify-content: space-between;  margin: 0;}.dialog-title {  line-height: 24px;  font-size: 18px;  color: #303133;}.btns {  display: flex;  align-items: center;  i {    margin-right: 8px;    font-size: 16px;    cursor: pointer;  }  i:last-child {    margin-right: 0;  }}</style>

弹窗的文字内容通过props进行传递,默认为空(''

<script lang="ts" setup>interface PropsType {  // 忽略之前的代码  title?: string;}const props = withDefaults(defineProps<PropsType>(), {  title: "",});</script>

我们看看现在头部的效果(这里没传入,默认为''

Vue3+hook如何实现弹窗组件

现在这个按钮只有样式效果,还没有写上对应的功能 ~

给他们先绑定上对应的事件和指令

<template>    <el-dialog    :model-value="props.modelValue"    :show-close="false"    :fullscreen="attrs?.fullscreen ?? isFullscreen"    >        <template #header>        <div>            <span class="dialog-title">{{ props.title }}</span>        </div>        <div class="btns">            <el-icon v-if="isFullScreenBtn" @click="handleFullscreen"            ><FullScreen            /></el-icon>            <el-icon @click="handleClose"><Close /></el-icon>        </div>        </template>    </el-dialog></template><script setup lang="ts">import { FullScreen, Close } from "@element-plus/icons-vue";interface PropsType {  title?: string;  modelValue?: boolean;  hiddenFullBtn?: boolean;}const props = withDefaults(defineProps<PropsType>(), {  title: "",  modelValue: false,  hiddenFullBtn: false,});const emits = defineEmits<{  (e: "update:modelValue"): void;  (e: "close"): void;}>();// 当前是否处于全屏状态const isFullscreen = ref(false);// 是否显示全屏效果图标const isFullScreenBtn = computed(() => {  if (props.hiddenFullBtn) return false;  if (attrs?.fullscreen) return false;  return true;});// 开启、关闭全屏效果const handleFullscreen = () => {  if (attrs?.fullscreen) return;  isFullscreen.value = !isFullscreen.value;};// 关闭弹窗时向外部发送close事件const handleClose = () => {  emits("close");};</script>

NICE 头部功能也就完成了

Footer

接下来,再处理下底部内容,默认提供两个按钮,分别是“确定”和“关闭”,这个名称也是可以通过props属性修改的。

两个按钮绑定点击事件,向外发送不同的事件。

<template>  <div class="">    <el-dialog      v-bind="attrs"      :model-value="props.modelValue"      :show-close="false"      :fullscreen="attrs?.fullscreen ?? isFullscreen"    >      <template #footer>        <!-- 如果没有提供其他footer插槽,就使用默认的 -->        <span v-if="!slots.footer" class="dialog-footer">          <el-button type="primary" @click="handleConfirm">{{            props.confirmText          }}</el-button>          <el-button @click="handleClose">{{ props.cancelText }}</el-button>        </span>        <!-- 使用传入进来的插槽 -->        <slot v-else name="footer"></slot>      </template>    </el-dialog>  </div></template><script setup lang="ts">import { useSlots } from "vue";// 获取插槽const slots = useSlots();interface PropsType {    title?: string;    width?: string | number;    isDraggable?: boolean;    modelValue?: boolean;    hiddenFullBtn?: boolean;    confirmText?: string;    cancelText?: string;}const props = withDefaults(defineProps<PropsType>(), {    title: "",    isDraggable: false,    modelValue: false,    hiddenFullBtn: false,    confirmText: "确认",    cancelText: "关闭",});const handleClose = () => {    emits("close");};const handleConfirm = () => {    emits("confirm");};</script>

Vue3+hook如何实现弹窗组件

又搞定了一部分了,就剩下Content了 ~

Content

弹窗内容通过默认插槽的方式传入进来,在外层的div元素上添加v-loading标签,实现加载态。

如果你想整个弹窗实现loading效果,请把v-loading移到最外层元素即可。注意不能是el-dialog元素上,否则无法实现可能是el-dialog使用了teleport组件,导致v-loading无法正常工作。等有空研究一下 ~

<template>  <div class="">    <el-dialog      v-bind="attrs"      :model-value="props.modelValue"      :show-close="false"      :fullscreen="attrs?.fullscreen ?? isFullscreen"    >        <div class="content" v-loading="props.loading">            <slot></slot>        </div>    </el-dialog>  </div></template><script lang="ts" setup>interface PropsType {  loading?: boolean;}const props = withDefaults(defineProps<PropsType>(), {  loading: false,});</script>

试试看中间的loading效果

剩下一些细节处理

el-dialog组件提供了很多个props属性供用户选择,但我们现在封装的dialog组件只使用到了一小部分props属性。当用户想要使用其他的props属性时该怎么办?

例如使用width属性时,难道要在我们封装的组件中接收props.width再传递给<el-dialog :width="props.width" />组件吗?

不不不,还有另外一种方法,还记得刚刚在做全屏操作的时候使用到的useAttrs辅助函数吗

它可以获取当前组件传递进来的属性。有了这个方法之后,再配合并即可将外部传递进来的函数再传递到el-dialog组件上面啦

<el-dialog    v-bind="attrs"    :model-value="props.modelValue"    :show-close="false"    :fullscreen="attrs?.fullscreen ?? isFullscreen"    :before-close="handleClose">    <!-- 忽略其他代码 --></el-dialog>

为了避免内部传递的props被覆盖掉,v-bind="attrs"需要放在最前面

在使用时,可能会给before-close属性传递一个函数,但到了后面被内部的handleClose方法给覆盖掉了。

解决方案是在handleClose函数中,获取attrs.['before-close']属性,如果类型是函数函数,先执行它。

const handleClose = () => {  if (    Reflect.has(attrs, "before-close") &&    typeof attrs["before-close"] === "function"  ) {    attrs["before-close"]();  }  emits("close");};

有关于el-dialog组件的封装就到这里了

封装hooks

利用Vue composition Api再封装一下在使用el-dialog组件状态的管理hook

useDialog

简单处理显示和加载态开关的hook

import { ref } from "vue";export default function useDialog() {  const visible = ref(false);  const loading = ref(false);  const openDialog = () => (visible.value = true);  const closeDialog = () => (visible.value = false);  const openLoading = () => (loading.value = true);  const closeLoading = () => (loading.value = false);  return {    visible,    loading,    openDialog,    closeDialog,    openLoading,    closeLoading,  };}

useDialog Demo

Vue3+hook如何实现弹窗组件

<template><el-button @click="openDialog1">普通弹窗</el-button><DialogCmp  title="DialogCmp1"  :hiddenFullBtn="true"  v-model="visible1"  @confirm="handleConfirm"  @close="handleClose">  <h4>DialogCmp1</h4></DialogCmp></template><script setup lang="ts">import useDialog from "./components/useDialog";import DialogCmp from "./components/Dialog.vue";const {  visible: visible1,  openDialog: openDialog1,  closeDialog: closeDialog1,} = useDialog();</script>

useDialogState 和 useDialogWithForm

useDialogState

针对开发管理后台弹窗状态封装的一个hook,搭配下面的useDialogWithForm使用。

export enum MODE {  ADD,  EDIT,}
import { ref } from "vue";import { MODE } from "./types";export default function useDialogState() {  const mode = ref<MODE>(MODE.ADD);  const visible = ref(false);  const updateMode = (target: MODE) => {    mode.value = target;  };  return { mode, visible, updateMode };}

useDialogWithForm

针对表单弹窗组件封装的hooks,接收一个formRef实例,负责控制弹窗内及清空表单中的校验结果,减少多余的代码 ~

import { FormInstance } from "element-plus";import { Ref, ref } from "vue";import { MODE } from "./types";import useDialogState from "./useDialogState";export default function useDialogFn(  formInstance: Ref<FormInstance>) {  const { visible, mode, updateMode } = useDialogState();  const closeDialog = () => {    formInstance.value.resetFields();    visible.value = false;  };  const openDialog = (target: MODE) => {    updateMode(target);    visible.value = true;  };  return { visible, mode, openDialog, closeDialog };}

useDialogWithForm Demo

Vue3+hook如何实现弹窗组件

<template>  <Dialog    :before-close="customClose"    @confirm="confirm"    v-model="visible"    :title="mode == MODE.ADD ? '添加数据' : '编辑信息'"    :confirm-text="mode == MODE.ADD ? '添加' : '修改'"  >    <el-form      label-width="100px"      :model="formData"      ref="formDataRef"      style="max-width: 460px"      :rules="rules"    >      <el-form-item label="姓名" prop="name">        <el-input v-model="formData.name" />      </el-form-item>      <el-form-item label="年龄" prop="age">        <el-input v-model="formData.age" />      </el-form-item>      <el-form-item label="手机号码" prop="mobile">        <el-input v-model="formData.mobile" />      </el-form-item>    </el-form>  </Dialog></template><script setup>import { ElMessage, FormInstance } from "element-plus";import { Ref, ref } from "vue";import Dialog from "./Dialog.vue";import { MODE } from "./types";import useDialogWithForm from "./useDialogWithForm";const rules = {  name: {    type: "string",    required: true,    pattern: /^[a-z]+$/,    trigger: "change",    message: "只能是英文名称哦",    transform(value: string) {      return value.trim();    },  },  age: {    type: "string",    required: true,    pattern: /^[0-9]+$/,    trigger: "change",    message: "年龄只能是数字哦",    transform(value: string) {      return value.trim();    },  },  mobile: {    type: "string",    required: true,    pattern:      /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,    trigger: "change",    message: "请输入正确的手机号码",    transform(value: string) {      return value.trim();    },  },};interface FromDataType {  name: string;  age: string;  mobile: string;}const formDataRef = ref<FormInstance | null>(null);let formData = ref<FromDataType>({  name: "",  age: "",  mobile: "",});const { visible, closeDialog, openDialog, mode } = useDialogWithForm(  formDataRef as Ref<FormInstance>);const confirm = () => {  if (!formDataRef.value) return;  formDataRef.value.validate((valid) => {    if (valid) {      console.log("confirm");      ElMessage({        message: "提交成功",        type: "success",      });      closeDialog();    }  });};const customClose = () => {  ElMessage({    message: "取消提交",    type: "info",  });  closeDialog();};defineExpose({  closeDialog,  openDialog,});</script><style scoped></style>

读到这里,这篇“Vue3+hook如何实现弹窗组件”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯