.效果圖:
*tips:組件案例基于vite+vue3+pinia+elementplubs
gitee 源碼地址 gitee.com/onlySmokeRi…
github 源碼地址github.com/zxxaoligei/…
1.1只需調(diào)用hook 并傳入基礎(chǔ)配置
1.2 dialog的坑
平常開(kāi)發(fā)中在一個(gè)頁(yè)面中引入Dialog,最少需要額外維護(hù)一個(gè)visible變量,如果有多個(gè)dailog 甚至又要維護(hù)多個(gè)dialog的visible變量,如此一來(lái)代碼就優(yōu)雅不起來(lái),且多個(gè)變量變來(lái)變?nèi)?看著也煩人。
所以如果將每個(gè)dialog 視為一個(gè)組件頁(yè)面,就把它抽離了出去,也就不會(huì)有這種問(wèn)題出現(xiàn)了 因此,結(jié)合 餓了么的tab 選項(xiàng)卡組件與彈框組件 2者相磨合。根據(jù)傳入的異步組件,動(dòng)態(tài)的來(lái)回切換彈框所顯示的內(nèi)容,每一個(gè)彈框頁(yè)面對(duì)應(yīng)的每個(gè).vue文件。 能夠使多層彈框相疊,隨便套娃也不會(huì)影響代碼的優(yōu)雅性。
1.先在全局掛載彈窗組件
2.dialogCom全局組件: ``
**如果需要每次來(lái)回切換彈窗時(shí),其組件頁(yè)面每次都渲染一次 可以將 v-show 換成 v-for --24段代碼
```<!--
* 全局掛載的彈框組件
*-->
<template>
<el-dialog :fullscreen="dailogInfo.dialogFullscreen" v-model="dialogVisbled" :width="dailogInfo.dialogWidth"
@closed="dialogClose" :append-to-body="false" draggable>
<template #header>
<!-- svg圖標(biāo)可自行使用符合項(xiàng)目的風(fēng)格 -->
<svg-icon name="module" />
{{ dailogInfo.dialogTitle }}
</template>
<el-tabs type="card" v-model="dialogActiveTag" @tab-remove="tabClose" @tab-click="handTabClick">
<el-tab-pane v-for="(item, index) in dailogInfo.children" :key="index" :name="index" :closable="index !=0">
<template #label>
<svg-icon :name="item.iconName" />
{{ item.topTagName }}
</template>
</el-tab-pane>
</el-tabs>
<div class="dialogContianer">
<template v-for="(item, index) in dailogInfo.children" :key="index">
<transition name="fade-transform" mode="out-in">
<div v-show="index===dialogActiveTag">
<component @toCloseDialog="dialogClose" :is="item.component" :conf="item.data" :id="index"
@removePage="componentRemove" />
</div>
</transition>
</template>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { computed, ref, watch } from "vue";
import usedialogStore from '@/store/modules/dialog'
const dialogStore=usedialogStore()
const { dialogVisbled, activeComponent }=storeToRefs(dialogStore)
const dailogInfo=computed(()=> {
return dialogStore.dialogOption
})
/** 每當(dāng)push子級(jí)頁(yè)面時(shí)當(dāng)前選中項(xiàng)都默認(rèn)選中push后的組件 */
watch(
()=> activeComponent.value,
(val)=> {
if (val !=null && val !=undefined) {
dialogActiveTag.value=activeComponent.value
dialogStore.initActiveComponent()
}
}
)
const dialogActiveTag=ref(0)
/**關(guān)閉彈框 并 重置store的狀態(tài) */
const dialogClose=()=> {
dialogStore.$reset()
dialogActiveTag.value=0
}
/**移除當(dāng)前組件,重置激活項(xiàng) */
const tabClose=(index: number)=> {
dialogStore.removeItem(index)
// 激活項(xiàng)===刪除項(xiàng) 直接刪除
if (dialogActiveTag.value===index) {
dialogActiveTag.value=index - 1
}
//激活項(xiàng)>刪除項(xiàng) 則直接賦刪除項(xiàng)
if (dialogActiveTag.value > index) {
dialogActiveTag.value=index;
}
if (!dailogInfo.value.children.length) {
dialogClose()
}
}
/**設(shè)置激活的組件 */
const handTabClick=(tab: any)=> {
dialogActiveTag.value=Number(tab.index);
}
/**子級(jí)組件關(guān)閉
* @params {id} 關(guān)閉項(xiàng)
* @params {isSaveFn :true 嵌套的子級(jí)頁(yè)面操作了表單,需要通知父級(jí)頁(yè)面刷新列表之類的操作" }
*/
const componentRemove=(id: number, isSaveFn: boolean)=> {
if (isSaveFn) {
dailogInfo.value.children.forEach((item, index)=> {
if (index===id && item.collbackFn) {
//回調(diào)-通知上一級(jí)頁(yè)面
dialogStore.collbackFnList[index]()
}
})
}
if (id==0) dialogClose()
else tabClose(id)
}
</script>
<style scoped>
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
store/modules/dialog(setup 風(fēng)格的store/state)
import { reactive, ref, watch } from 'vue';
import { defineStore } from 'pinia';
import { type dialogOptionsType, type childrensType } from '@/store/types';
const usedialogStore=defineStore('dialogStore', ()=> {
const dialogVisbled=ref<boolean>(false);
const activeComponent=ref(); //子級(jí)激活項(xiàng)
const collbackFnList=ref({}); //保存表單信息后外界刷新列表數(shù)據(jù)的回調(diào)函數(shù)
const initOptions={
dialogFullscreen: false, //是否全屏
dialogWidth: '30%', //彈窗寬度
dialogTitle: '', //標(biāo)題
children: [],
}
let dialogOption=reactive<dialogOptionsType>({
...initOptions
});
const initDialog=(options: dialogOptionsType)=> {
Object.assign(dialogOption, options);
dialogVisbled.value=true;
};
const removeItem=(index: number)=> {
dialogOption.children.splice(index, 1);
};
/**在第一個(gè)彈框中再次新增一個(gè)同級(jí)的頁(yè)面 */
const addDialog=(childrenOption: childrensType)=> {
if (!dialogOption.dialogTitle) {
return console.error('請(qǐng)確保是否含有初始彈框容器,再添加新的彈框頁(yè)面');
}
// 此處使用名稱作為唯一標(biāo)識(shí) 若二次彈框的名稱一致,可自定義id 屬性區(qū)分
const obj=dialogOption.children.find(
(item)=> item.topTagName===childrenOption.topTagName
);
activeComponent.value=dialogOption.children.length;
if (obj) {
//如果已經(jīng)存在了某個(gè)頁(yè)面 但是沒(méi)有關(guān)閉 再次點(diǎn)擊則不新增新頁(yè)面 而是回到舊頁(yè)面
activeComponent.value=dialogOption.children.findIndex(
(item)=> item.topTagName===childrenOption.topTagName
);
return;
}
dialogOption.children.push(childrenOption);
};
/**為每個(gè)組件添加回調(diào) */
const addCollBackFn=()=> {
dialogOption.children.forEach((item,index)=> {
if (
item.collbackFn
) {
collbackFnList.value[index]=item.collbackFn;
}
});
};
/**重置子級(jí)激活項(xiàng) */
const initActiveComponent=()=> {
activeComponent.value=null;
};
/**監(jiān)聽(tīng)組件列表是否被移除/添加:以便收集回調(diào) */
watch(
()=> dialogOption.children,
()=> {
addCollBackFn();
},
{
deep:true,
}
)
return {
dialogVisbled,
dialogOption,
activeComponent,
collbackFnList,
removeItem,
initDialog,
addDialog,
initActiveComponent,
$reset: ()=> {
//重寫(xiě)重置方法
dialogVisbled.value=false;
Object.assign(dialogOption,initOptions)
activeComponent.value=null;
collbackFnList.value={};
},
};
});
export default usedialogStore;
封裝對(duì)應(yīng)的 hooks:
import usedialogStore from '@/store/modules/dialog';
import { defineAsyncComponent, shallowRef } from 'vue';
import { type dialogOptionsType, type childrensType } from '@/store/types';
import { AsyncComponentLoader } from 'vue';
const dialogStore=usedialogStore();
export const useDialogHooks=()=> {
const initDialog=(options: dialogOptionsType)=> {
const { children }=options;
children.forEach((item)=> {
item.component=shallowRef(
defineAsyncComponent(item.component as AsyncComponentLoader)
);
});
dialogStore.initDialog(options);
};
const addDialog=(options: childrensType)=> {
options.component=shallowRef(
defineAsyncComponent(options.component as AsyncComponentLoader)
);
dialogStore.addDialog(options);
};
return {
initDialog,
addDialog,
};
};
export default useDialogHooks;
5.調(diào)用對(duì)應(yīng)的hooks 傳入基礎(chǔ)配置
import useDialogHooks from '@/hooks/dialog'
const { initDialog }=useDialogHooks()
//打開(kāi)初始彈窗 initDialog 只需調(diào)用一次 往后每次新增彈窗
都調(diào)用addDialog方法
const openDialog=()=> {
initDialog({
dialogFullscreen: false,
dialogWidth: '50%',
dialogTitle: '明細(xì)',
children: [{
topTagName: "新增",
iconName: "form",
// detailPage1 就是這次要打開(kāi)的彈窗內(nèi)容
component: ()=> import("@/views/detailPage1.vue"),
data: {
params: {
aa: 11,
bb: 22,
},
edit: 'add'
},
collbackFn: ()=> { //彈框內(nèi)部組件 保存成功后的回調(diào)函數(shù)
console.log("當(dāng)彈窗任務(wù)結(jié)束后,調(diào)用父頁(yè)面的回掉函數(shù)。(比如我新增完成了需要刷新列表頁(yè)面)--Home");
}
}],
})
}
6.在原有的彈窗新增一個(gè)彈窗
import useDialogHooks from '@/hooks/dialog'
const { addDialog }=useDialogHooks()
const props=defineProps(['conf', 'id'])
const emit=defineEmits(['removePage'])
const openDialog=()=> {
addDialog({
topTagName: '第二個(gè)明細(xì)內(nèi)容',
iconName: "form",
component: ()=> import("@/views/detailPage2.vue"),
data: {
},
collbackFn: ()=> { //彈框內(nèi)部組件 保存成功后的回調(diào)函數(shù)
console.log("當(dāng)彈窗任務(wù)結(jié)束后,調(diào)用父頁(yè)面的回掉函數(shù)。(比如我新增完成了需要刷新列表頁(yè)面)--detailPage1");
}
})
}
console.log(props.conf) //上一層組件的傳遞數(shù)據(jù)
//保存表單/會(huì)回調(diào)上層組件的回調(diào)方法
const saveInfo=()=> {
emit('removePage', props.id, true)
}
7.最后: 雖然沒(méi)有使用無(wú)限套娃的方式不斷往body嵌套彈窗, 并不斷增加層級(jí)。 而是采用組件 平鋪的方式,會(huì)比原來(lái)的無(wú)限嵌套更加清晰簡(jiǎn)潔,且界面也會(huì)比較"人性化"
每個(gè)新的彈窗相當(dāng)于一個(gè).vue 文件, 上層父容器 所在的 組件 也就不需要 額外維護(hù)一個(gè)visible變量了,對(duì)比原來(lái)實(shí)在是優(yōu)雅很多。。。
作者:杰哥焯遜
鏈接:https://juejin.cn/post/7288300174913110070
限0代碼快速拖拽式建站H5微網(wǎng)站超強(qiáng)表單
拖拽式建站,所見(jiàn)即所得!支持H5及微信公眾號(hào)
不限制公眾號(hào)及H5創(chuàng)建數(shù)量!
自用省錢,用于客戶接單可立即賺錢!
可0代碼快速制作:門(mén)戶官網(wǎng)、產(chǎn)品宣傳頁(yè)、報(bào)名表單登記
萬(wàn)用的拖拽式頁(yè)面+超強(qiáng)表單
精選主題模板,每周上新(拿來(lái)就能用于某個(gè)行業(yè),而不是僅有首頁(yè)的假模板)
IFRAME彈窗按鈕震撼登場(chǎng)!掛載720全景看房/看車URL,秒變房源/汽車門(mén)戶
【支持插入視頻、具有底部菜單、一鍵撥號(hào)、一鍵導(dǎo)航、底部懸停欄、彈窗消息等】
文章系統(tǒng)(可設(shè)置某篇文章強(qiáng)制關(guān)注方可閱讀)
更新日志
【新增】新增后臺(tái)權(quán)限管理
【新增】表單復(fù)選框、單選框支持單行顯示模式
【新增】文章支持跳轉(zhuǎn)外鏈、彈層顯示外鏈
【新增】文章隨機(jī)閱讀量
【優(yōu)化】地圖組件支持其他導(dǎo)航工具
1、處理主題切換問(wèn)題
2、完善裝修中心功能
3、新增點(diǎn)擊事件iframe彈窗功能
4、新增本地主題模板
5、表單設(shè)計(jì)移至頁(yè)面設(shè)計(jì)并新增修改頁(yè)面、表單按鈕
6、修復(fù)文章詳情刷新問(wèn)題
1、新增按鈕組件,可設(shè)置為固定底部顯示
2、表單設(shè)計(jì)新增插入文字、圖片
3、表單新增密碼限制、時(shí)間限制、提交總量限制、白名單、黑名單限制
4、表單提交按鈕支持樣式編輯、設(shè)置為固定底部
5、完善表單多圖上傳
6、完善頁(yè)面裝修的一些體驗(yàn)
7、增加我的提交記錄頁(yè)面
8、新增組件鏈接彈出確認(rèn)框事件
9、裝修中心增加鎖定圖層功能
10、優(yōu)化文章組件分類欄置頂及樣式優(yōu)化
1、增加功能鏈接
2、底部菜單支持外鏈及跳轉(zhuǎn)到應(yīng)用頁(yè)
3、兼容H5版自定義表單
1、兼容訂閱號(hào)
2、增加組件風(fēng)格設(shè)置
3、增加應(yīng)用管理
兼容普通H5訪問(wèn)
opover API 為開(kāi)發(fā)者提供了一種聲明式的方式來(lái)創(chuàng)建各種類型的彈窗。目前已在所有三大瀏覽器引擎中可用,并正式成為 Baseline 2024 的一部分。
一直以來(lái),我們?cè)趯?shí)現(xiàn)彈出式菜單、提示框或信息卡片時(shí),往往依賴于各種 JavaScript 庫(kù)或者自定義 CSS 樣式來(lái)完成。雖然這些方法有效,但它們通常伴隨著代碼冗余、兼容性問(wèn)題。Popover API 正是為了簡(jiǎn)化這一過(guò)程而生,它為 Web開(kāi)發(fā)者提供了一套標(biāo)準(zhǔn)化的方法來(lái)創(chuàng)建和控制彈出窗口,確保了跨瀏覽器的一致性和易用性。
Popover API 彈窗的一些特點(diǎn)如下:
使用 Popover API 創(chuàng)建一個(gè)最基礎(chǔ)的彈窗非常簡(jiǎn)單,只需要一個(gè)button 按鈕用于觸發(fā)彈窗,和一個(gè)彈窗 div 元素。
實(shí)現(xiàn)代碼如下:
<button popovertarget="my-popover">打開(kāi)彈窗</button>
<div id="my-popover" popover>
<p>我是一個(gè)包含一些信息的彈窗。 按下 <kbd>Esc</kbd> 鍵或點(diǎn)擊彈窗外部將我關(guān)閉<p>
</div>
此時(shí)一個(gè)最簡(jiǎn)單的點(diǎn)擊按鈕顯示彈窗功能就實(shí)現(xiàn)了。
演示效果如下:
通過(guò) popover 屬性制作彈窗,基礎(chǔ)版 - 在線演示 https://bi.cool/bi/0b6c78K
其中屬性 popover 如果不賦值,則等同于 popover="auto"。auto值表示啟用點(diǎn)擊彈窗外部則自動(dòng)關(guān)閉彈窗。
如果設(shè)置popover="manual",則點(diǎn)擊彈窗外部不會(huì)再自動(dòng)關(guān)閉彈窗,此時(shí)你將需要自定義關(guān)閉按鈕來(lái)觸發(fā)彈窗的關(guān)閉。
例如:
<button popovertarget="my-popover" class="trigger-btn">打開(kāi)彈窗</button>
<div id="my-popover" popover=manual>
<p>我是一個(gè)包含一些信息的彈窗。按下?按鈕即可將我關(guān)閉<p>
<button class="close-btn" popovertarget="my-popover" popovertargetaction="hide">
<span aria-hidden="true">?</span>
</button>
</div>
演示效果如下:
通過(guò) popover 屬性制作彈窗,自定義關(guān)閉按鈕 - 在線演示 https://bi.cool/bi/5Bkfd32?
此時(shí),你會(huì)看到點(diǎn)擊彈窗外部不會(huì)再自動(dòng)關(guān)閉彈窗,點(diǎn)擊自定義的關(guān)閉按鈕才會(huì)關(guān)閉彈窗。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。