관리 백엔드 개발 과정에서 팝업 비즈니스 팝업이 너무 많이 뜨는데, 그 중 가장 흔한 것이 "XX 데이터 추가", "XX 데이터 편집"입니다. ", "XX 상세 데이터 보기" 등의 팝업창 유형이 가장 일반적입니다. [관련 추천 : vuejs 영상 튜토리얼, 웹 프론트 엔드 개발]
이러한 팝업 구성 요소의 코드 중 구성 요소 상태, 양식 구성 요소와 관련된 메서드 등 많은 코드가 동일합니다...
그래서 , 저는 단순히 Dialog
구성 요소의 보조 캡슐화와 후크
를 사용하여 반복되는 코드를 줄입니다Dialog
组件进行的二次封装和hooks
,减少了一些重复的代码
如果是普通弹窗使用的话,直接使用el-dialog
组件已经足够了
但我还是一个比较爱折腾的人,我们先看看官方dialog
文档有什么可以添加的功能
...
大概看了一下,我打算封装一下功能
Loading
效果确定了要封装的功能之后,先来一个简单的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>
这里使用到图标库@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>
我们看看现在头部的效果(这里没传入标题,默认为''
)
现在这个按钮只有样式效果,还没有写上对应的功能 ~
给他们先绑定上对应的事件和指令
<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 头部功能也就完成了
接下来,再处理下底部内容,默认提供两个按钮,分别是“确定”和“关闭”,这个名称也是可以通过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>
又搞定了一部分了,就剩下Content了 ~
弹窗内容通过默认插槽的方式传入进来,在外层的div
元素上添加v-loading
If If 일반적인 팝업창에 사용되는 것이므로 el-dialog
컴포넌트를 직접 사용하면 충분합니다하지만 저는 여전히 장난을 좋아하는 사람입니다. code>dialog 문서에 어떤 기능을 추가할 수 있는지
...loading
效果
在el-dialog
组件提供了很多个props
属性供用户选择,但我们现在封装的dialog
组件只使用到了一小部分props
属性。当用户想要使用其他的props
属性时该怎么办?
例如使用width属性时,难道要在我们封装的组件中接收props.width
再传递给<el-dialog :width="props.width"></el-dialog>
组件吗?
不不不,还有另外一种方法,还记得刚刚在做全屏操作的时候使用到的useAttrs
辅助函数吗
它可以获取当前组件传递进来的属性。有了这个方法之后,再配合并即可将外部传递进来的函数再传递到el-dialog
组件上面啦
<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>
为了避免内部传递的props被覆盖掉,
v-bind="attrs"
需要放在最前面
在使用时,可能会给before-close
属性传递一个函数,但到了后面被内部的handleClose
로딩
효과 추가dialog
구성 요소부터 시작하겠습니다. 🎜🎜 v-model
을 통해 팝업창을 외부에서 직접 제어할 수 있도록 양방향 바인딩을 처리합니다. 🎜<el-dialog v-bind="attrs" :model-value="props.modelValue" :show-close="false" :fullscreen="attrs?.fullscreen ?? isFullscreen" :before-close="handleClose" > <!-- 忽略其他代码 --> </el-dialog>
el-dialog
에서 제공하는 header
슬롯을 사용하여 전체 화면 차트를 배치하고 오른쪽 상단에 아이콘을 닫습니다. 기본 아이콘을 닫으려면 show-close
속성을 el-dialog
에 전달하세요. 🎜const handleClose = () => { if ( Reflect.has(attrs, "before-close") && typeof attrs["before-close"] === "function" ) { attrs["before-close"](); } emits("close"); };🎜팝업창의 제목 텍스트 내용은
props
를 통해 전달되며, 기본값은 비어있습니다(''
) 🎜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, }; }🎜다음을 살펴보겠습니다. 현재 헤드의 효과(여기에는 전달되지 않음) 제목을 입력하세요. 기본값은
''
)🎜🎜🎜🎜이제 이 버튼은 스타일 효과만 있고 해당 기능은 아직 작성되지 않았습니다~🎜🎜해당 이벤트 바인딩 먼저 🎜<template> <el-button @click="openDialog1">普通弹窗</el-button> <DialogCmp title="DialogCmp1" :hiddenFullBtn="true" v-model="visible1" @confirm="handleConfirm" @close="handleClose" > <h3>DialogCmp1</h3> </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>🎜 그런 다음 전체 화면 아이콘을 클릭하세요. 어떻게 작동하는지 살펴보겠습니다🎜🎜🎜🎜NICE 이제 헤더 기능이 완성되었습니다🎜
props
속성을 통해 수정할 수도 있습니다. 🎜🎜두 개의 버튼이 클릭 이벤트를 바인딩하고 다양한 이벤트를 외부로 보냅니다. 🎜export enum MODE { ADD, EDIT, }🎜🎜🎜 또 다른 부분은 완료하고 컨텐츠만 남습니다~🎜
div를 추가합니다. <code>v-loading
태그를 code> 요소에 추가하여 로딩 상태를 달성합니다. 🎜🎜🎜팝업창 전체를 로딩효과로 연출하고 싶다면 v-loading을 가장 바깥쪽 요소로 옮겨주세요.
el-dialog 요소에 있을 수 없으며 그렇지 않으면 구현할 수 없습니다.
el-dialog가 텔레포트 구성요소를 사용하여 v-로딩이 제대로 작동하지 않을 수 있습니다.
시간나실때 공부해보세요~🎜🎜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 }; }🎜중간에
로딩
효과를 확인해 보세요🎜🎜🎜el-dialog
구성요소는 사용자가 선택할 수 있는 많은 props
속성을 제공하지만, 우리가 캡슐화하고 있는 dialog
구성요소는 이제 propsprops
속성을 사용하려면 어떻게 해야 합니까? 🎜🎜예를 들어 width 속성을 사용할 때 캡슐화된 구성 요소에서 props.width
를 받은 다음 <el-dialog :width="props.width에 전달해야 합니까? "></el-dialog> 컴포넌트?🎜🎜아니요, 아니, 다른 방법이 있습니다. 전체 화면 작업을 할 때 방금 사용한 <code>useAttrs
보조 기능을 기억하시나요?🎜🎜현재 구성 요소 전송 속성에 나타납니다. 이 방법을 사용하면 외부에서 전달된 함수를 협력하여 el-dialog
컴포넌트에 전달할 수 있습니다🎜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 }; }🎜🎜내부적으로 전달된 props가 덮어쓰이는 것을 방지하기 위해
v -bind="attrs"
는 앞쪽에 배치되어야 합니다🎜🎜🎜사용할 때 함수는 before-close
속성에 전달될 수 있지만 나중에는 내부 handlerClose
메서드가 재정의되었습니다. 🎜解决方案是在handleClose
函数中,获取attrs.['before-close']
属性,如果类型是函数函数,先执行它。
const handleClose = () => { if ( Reflect.has(attrs, "before-close") && typeof attrs["before-close"] === "function" ) { attrs["before-close"](); } emits("close"); };
有关于el-dialog
组件的封装就到这里了
利用Vue composition Api
再封装一下在使用el-dialog
组件状态的管理hook
简单处理显示和加载态开关的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, }; }
<template> <el-button @click="openDialog1">普通弹窗</el-button> <DialogCmp title="DialogCmp1" :hiddenFullBtn="true" v-model="visible1" @confirm="handleConfirm" @close="handleClose" > <h3>DialogCmp1</h3> </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>
针对开发管理后台弹窗状态封装的一个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 }; }
针对表单弹窗组件封装的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 }; }
<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>
如果您觉得本文对您有帮助,请帮帮忙点个star
您的反馈 是我更新的动力!
위 내용은 Vue3+hook을 사용하여 팝업 컴포넌트를 더 빠르고 효율적으로 작성하는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!