本文小編為大家詳細(xì)介紹“Vue3+hook如何實(shí)現(xiàn)彈窗組件”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Vue3+hook如何實(shí)現(xiàn)彈窗組件”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。
成都創(chuàng)新互聯(lián)公司的客戶來自各行各業(yè),為了共同目標(biāo),我們?cè)诠ぷ魃厦芮信浜?,從?chuàng)業(yè)型小企業(yè)到企事業(yè)單位,感謝他們對(duì)我們的要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。專業(yè)領(lǐng)域包括網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、電商網(wǎng)站開發(fā)、微信營(yíng)銷、系統(tǒng)平臺(tái)開發(fā)。
如果是普通彈窗使用的話,直接使用el-dialog
組件已經(jīng)足夠了
但我還是一個(gè)比較愛折騰的人,我們先看看官方dialog
文檔有什么可以添加的功能
...
大概看了一下,我打算封裝一下功能
提供全屏操作按鈕(右上角)
默認(rèn)提供“確認(rèn)”,“關(guān)閉”按鈕
內(nèi)部添加Loading
效果
確定了要封裝的功能之后,先來一個(gè)簡(jiǎn)單的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>
這里使用到圖標(biāo)庫@element-plus/icons-vue
如沒有安裝,請(qǐng)執(zhí)行npm install @element-plus/icons-vue
使用el-dialog
提供的header
插槽,將全屏圖表和關(guān)閉圖標(biāo)放置到右上角中。給el-dialog
傳遞show-close
屬性關(guān)閉默認(rèn)圖標(biāo)。
<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>
彈窗的標(biāo)題文字內(nèi)容通過props
進(jìn)行傳遞,默認(rèn)為空(''
)
<script lang="ts" setup>
interface PropsType {
// 忽略之前的代碼
title?: string;
}
const props = withDefaults(defineProps<PropsType>(), {
title: "",
});
</script>
我們看看現(xiàn)在頭部的效果(這里沒傳入標(biāo)題,默認(rèn)為''
)
現(xiàn)在這個(gè)按鈕只有樣式效果,還沒有寫上對(duì)應(yīng)的功能 ~
給他們先綁定上對(duì)應(yīng)的事件和指令
<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;
}>();
// 當(dāng)前是否處于全屏狀態(tài)
const isFullscreen = ref(false);
// 是否顯示全屏效果圖標(biāo)
const isFullScreenBtn = computed(() => {
if (props.hiddenFullBtn) return false;
if (attrs?.fullscreen) return false;
return true;
});
// 開啟、關(guān)閉全屏效果
const handleFullscreen = () => {
if (attrs?.fullscreen) return;
isFullscreen.value = !isFullscreen.value;
};
// 關(guān)閉彈窗時(shí)向外部發(fā)送close事件
const handleClose = () => {
emits("close");
};
</script>
NICE 頭部功能也就完成了
接下來,再處理下底部?jī)?nèi)容,默認(rèn)提供兩個(gè)按鈕,分別是“確定”和“關(guān)閉”,這個(gè)名稱也是可以通過props
屬性修改的。
兩個(gè)按鈕綁定點(diǎn)擊事件,向外發(fā)送不同的事件。
<template>
<div class="">
<el-dialog
v-bind="attrs"
:model-value="props.modelValue"
:show-close="false"
:fullscreen="attrs?.fullscreen ?? isFullscreen"
>
<template #footer>
<!-- 如果沒有提供其他footer插槽,就使用默認(rèn)的 -->
<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>
<!-- 使用傳入進(jìn)來的插槽 -->
<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: "確認(rèn)",
cancelText: "關(guān)閉",
});
const handleClose = () => {
emits("close");
};
const handleConfirm = () => {
emits("confirm");
};
</script>
又搞定了一部分了,就剩下Content了 ~
彈窗內(nèi)容通過默認(rèn)插槽的方式傳入進(jìn)來,在外層的div
元素上添加v-loading
標(biāo)簽,實(shí)現(xiàn)加載態(tài)。
如果你想整個(gè)彈窗實(shí)現(xiàn)loading效果,請(qǐng)把v-loading移到最外層元素即可。 注意不能是el-dialog元素上,否則無法實(shí)現(xiàn) 可能是el-dialog使用了teleport組件,導(dǎo)致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
組件提供了很多個(gè)props
屬性供用戶選擇,但我們現(xiàn)在封裝的dialog
組件只使用到了一小部分props
屬性。當(dāng)用戶想要使用其他的props
屬性時(shí)該怎么辦?
例如使用width屬性時(shí),難道要在我們封裝的組件中接收props.width
再傳遞給<el-dialog :width="props.width" />
組件嗎?
不不不,還有另外一種方法,還記得剛剛在做全屏操作的時(shí)候使用到的useAttrs
輔助函數(shù)嗎
它可以獲取當(dāng)前組件傳遞進(jìn)來的屬性。有了這個(gè)方法之后,再配合并即可將外部傳遞進(jìn)來的函數(shù)再傳遞到el-dialog
組件上面啦
<el-dialog
v-bind="attrs"
:model-value="props.modelValue"
:show-close="false"
:fullscreen="attrs?.fullscreen ?? isFullscreen"
:before-close="handleClose"
>
<!-- 忽略其他代碼 -->
</el-dialog>
為了避免內(nèi)部傳遞的props被覆蓋掉,
v-bind="attrs"
需要放在最前面
在使用時(shí),可能會(huì)給before-close
屬性傳遞一個(gè)函數(shù),但到了后面被內(nèi)部的handleClose
方法給覆蓋掉了。
解決方案是在handleClose
函數(shù)中,獲取attrs.['before-close']
屬性,如果類型是函數(shù)函數(shù),先執(zhí)行它。
const handleClose = () => {
if (
Reflect.has(attrs, "before-close") &&
typeof attrs["before-close"] === "function"
) {
attrs["before-close"]();
}
emits("close");
};
有關(guān)于el-dialog
組件的封裝就到這里了
利用Vue composition Api
再封裝一下在使用el-dialog
組件狀態(tài)的管理hook
簡(jiǎn)單處理顯示和加載態(tài)開關(guān)的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"
>
<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>
針對(duì)開發(fā)管理后臺(tái)彈窗狀態(tài)封裝的一個(gè)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 };
}
針對(duì)表單彈窗組件封裝的hooks
,接收一個(gè)formRef
實(shí)例,負(fù)責(zé)控制彈窗內(nèi)標(biāo)題及清空表單中的校驗(yàn)結(jié)果,減少多余的代碼 ~
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 ? '添加數(shù)據(jù)' : '編輯信息'"
: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="手機(jī)號(hào)碼" 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: "年齡只能是數(shù)字哦",
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: "請(qǐng)輸入正確的手機(jī)號(hào)碼",
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如何實(shí)現(xiàn)彈窗組件”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
本文標(biāo)題:Vue3+hook如何實(shí)現(xiàn)彈窗組件
標(biāo)題網(wǎng)址:http://chinadenli.net/article18/gicddp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈、小程序開發(fā)、全網(wǎng)營(yíng)銷推廣、網(wǎng)站營(yíng)銷、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)