.效果图:
*tips:组件案例基于vite+vue3+pinia+elementplubs
gitee 源码地址 gitee.com/onlySmokeRi…
github 源码地址github.com/zxxaoligei/…
1.1只需调用hook 并传入基础配置
1.2 dialog的坑
平常开发中在一个页面中引入Dialog,最少需要额外维护一个visible变量,如果有多个dailog 甚至又要维护多个dialog的visible变量,如此一来代码就优雅不起来,且多个变量变来变去 看着也烦人。
所以如果将每个dialog 视为一个组件页面,就把它抽离了出去,也就不会有这种问题出现了 因此,结合 饿了么的tab 选项卡组件与弹框组件 2者相磨合。根据传入的异步组件,动态的来回切换弹框所显示的内容,每一个弹框页面对应的每个.vue文件。 能够使多层弹框相叠,随便套娃也不会影响代码的优雅性。
1.先在全局挂载弹窗组件
2.dialogCom全局组件: ``
**如果需要每次来回切换弹窗时,其组件页面每次都渲染一次 可以将 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图标可自行使用符合项目的风格 -->
<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
})
/** 每当push子级页面时当前选中项都默认选中push后的组件 */
watch(
()=> activeComponent.value,
(val)=> {
if (val !=null && val !=undefined) {
dialogActiveTag.value=activeComponent.value
dialogStore.initActiveComponent()
}
}
)
const dialogActiveTag=ref(0)
/**关闭弹框 并 重置store的状态 */
const dialogClose=()=> {
dialogStore.$reset()
dialogActiveTag.value=0
}
/**移除当前组件,重置激活项 */
const tabClose=(index: number)=> {
dialogStore.removeItem(index)
// 激活项===删除项 直接删除
if (dialogActiveTag.value===index) {
dialogActiveTag.value=index - 1
}
//激活项>删除项 则直接赋删除项
if (dialogActiveTag.value > index) {
dialogActiveTag.value=index;
}
if (!dailogInfo.value.children.length) {
dialogClose()
}
}
/**设置激活的组件 */
const handTabClick=(tab: any)=> {
dialogActiveTag.value=Number(tab.index);
}
/**子级组件关闭
* @params {id} 关闭项
* @params {isSaveFn :true 嵌套的子级页面操作了表单,需要通知父级页面刷新列表之类的操作" }
*/
const componentRemove=(id: number, isSaveFn: boolean)=> {
if (isSaveFn) {
dailogInfo.value.children.forEach((item, index)=> {
if (index===id && item.collbackFn) {
//回调-通知上一级页面
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 风格的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(); //子级激活项
const collbackFnList=ref({}); //保存表单信息后外界刷新列表数据的回调函数
const initOptions={
dialogFullscreen: false, //是否全屏
dialogWidth: '30%', //弹窗宽度
dialogTitle: '', //标题
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);
};
/**在第一个弹框中再次新增一个同级的页面 */
const addDialog=(childrenOption: childrensType)=> {
if (!dialogOption.dialogTitle) {
return console.error('请确保是否含有初始弹框容器,再添加新的弹框页面');
}
// 此处使用名称作为唯一标识 若二次弹框的名称一致,可自定义id 属性区分
const obj=dialogOption.children.find(
(item)=> item.topTagName===childrenOption.topTagName
);
activeComponent.value=dialogOption.children.length;
if (obj) {
//如果已经存在了某个页面 但是没有关闭 再次点击则不新增新页面 而是回到旧页面
activeComponent.value=dialogOption.children.findIndex(
(item)=> item.topTagName===childrenOption.topTagName
);
return;
}
dialogOption.children.push(childrenOption);
};
/**为每个组件添加回调 */
const addCollBackFn=()=> {
dialogOption.children.forEach((item,index)=> {
if (
item.collbackFn
) {
collbackFnList.value[index]=item.collbackFn;
}
});
};
/**重置子级激活项 */
const initActiveComponent=()=> {
activeComponent.value=null;
};
/**监听组件列表是否被移除/添加:以便收集回调 */
watch(
()=> dialogOption.children,
()=> {
addCollBackFn();
},
{
deep:true,
}
)
return {
dialogVisbled,
dialogOption,
activeComponent,
collbackFnList,
removeItem,
initDialog,
addDialog,
initActiveComponent,
$reset: ()=> {
//重写重置方法
dialogVisbled.value=false;
Object.assign(dialogOption,initOptions)
activeComponent.value=null;
collbackFnList.value={};
},
};
});
export default usedialogStore;
封装对应的 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.调用对应的hooks 传入基础配置
import useDialogHooks from '@/hooks/dialog'
const { initDialog }=useDialogHooks()
//打开初始弹窗 initDialog 只需调用一次 往后每次新增弹窗
都调用addDialog方法
const openDialog=()=> {
initDialog({
dialogFullscreen: false,
dialogWidth: '50%',
dialogTitle: '明细',
children: [{
topTagName: "新增",
iconName: "form",
// detailPage1 就是这次要打开的弹窗内容
component: ()=> import("@/views/detailPage1.vue"),
data: {
params: {
aa: 11,
bb: 22,
},
edit: 'add'
},
collbackFn: ()=> { //弹框内部组件 保存成功后的回调函数
console.log("当弹窗任务结束后,调用父页面的回掉函数。(比如我新增完成了需要刷新列表页面)--Home");
}
}],
})
}
6.在原有的弹窗新增一个弹窗
import useDialogHooks from '@/hooks/dialog'
const { addDialog }=useDialogHooks()
const props=defineProps(['conf', 'id'])
const emit=defineEmits(['removePage'])
const openDialog=()=> {
addDialog({
topTagName: '第二个明细内容',
iconName: "form",
component: ()=> import("@/views/detailPage2.vue"),
data: {
},
collbackFn: ()=> { //弹框内部组件 保存成功后的回调函数
console.log("当弹窗任务结束后,调用父页面的回掉函数。(比如我新增完成了需要刷新列表页面)--detailPage1");
}
})
}
console.log(props.conf) //上一层组件的传递数据
//保存表单/会回调上层组件的回调方法
const saveInfo=()=> {
emit('removePage', props.id, true)
}
7.最后: 虽然没有使用无限套娃的方式不断往body嵌套弹窗, 并不断增加层级。 而是采用组件 平铺的方式,会比原来的无限嵌套更加清晰简洁,且界面也会比较"人性化"
每个新的弹窗相当于一个.vue 文件, 上层父容器 所在的 组件 也就不需要 额外维护一个visible变量了,对比原来实在是优雅很多。。。
作者:杰哥焯逊
链接:https://juejin.cn/post/7288300174913110070
限0代码快速拖拽式建站H5微网站超强表单
拖拽式建站,所见即所得!支持H5及微信公众号
不限制公众号及H5创建数量!
自用省钱,用于客户接单可立即赚钱!
可0代码快速制作:门户官网、产品宣传页、报名表单登记
万用的拖拽式页面+超强表单
精选主题模板,每周上新(拿来就能用于某个行业,而不是仅有首页的假模板)
IFRAME弹窗按钮震撼登场!挂载720全景看房/看车URL,秒变房源/汽车门户
【支持插入视频、具有底部菜单、一键拨号、一键导航、底部悬停栏、弹窗消息等】
文章系统(可设置某篇文章强制关注方可阅读)
更新日志
【新增】新增后台权限管理
【新增】表单复选框、单选框支持单行显示模式
【新增】文章支持跳转外链、弹层显示外链
【新增】文章随机阅读量
【优化】地图组件支持其他导航工具
1、处理主题切换问题
2、完善装修中心功能
3、新增点击事件iframe弹窗功能
4、新增本地主题模板
5、表单设计移至页面设计并新增修改页面、表单按钮
6、修复文章详情刷新问题
1、新增按钮组件,可设置为固定底部显示
2、表单设计新增插入文字、图片
3、表单新增密码限制、时间限制、提交总量限制、白名单、黑名单限制
4、表单提交按钮支持样式编辑、设置为固定底部
5、完善表单多图上传
6、完善页面装修的一些体验
7、增加我的提交记录页面
8、新增组件链接弹出确认框事件
9、装修中心增加锁定图层功能
10、优化文章组件分类栏置顶及样式优化
1、增加功能链接
2、底部菜单支持外链及跳转到应用页
3、兼容H5版自定义表单
1、兼容订阅号
2、增加组件风格设置
3、增加应用管理
兼容普通H5访问
opover API 为开发者提供了一种声明式的方式来创建各种类型的弹窗。目前已在所有三大浏览器引擎中可用,并正式成为 Baseline 2024 的一部分。
一直以来,我们在实现弹出式菜单、提示框或信息卡片时,往往依赖于各种 JavaScript 库或者自定义 CSS 样式来完成。虽然这些方法有效,但它们通常伴随着代码冗余、兼容性问题。Popover API 正是为了简化这一过程而生,它为 Web开发者提供了一套标准化的方法来创建和控制弹出窗口,确保了跨浏览器的一致性和易用性。
Popover API 弹窗的一些特点如下:
使用 Popover API 创建一个最基础的弹窗非常简单,只需要一个button 按钮用于触发弹窗,和一个弹窗 div 元素。
实现代码如下:
<button popovertarget="my-popover">打开弹窗</button>
<div id="my-popover" popover>
<p>我是一个包含一些信息的弹窗。 按下 <kbd>Esc</kbd> 键或点击弹窗外部将我关闭<p>
</div>
此时一个最简单的点击按钮显示弹窗功能就实现了。
演示效果如下:
通过 popover 属性制作弹窗,基础版 - 在线演示 https://bi.cool/bi/0b6c78K
其中属性 popover 如果不赋值,则等同于 popover="auto"。auto值表示启用点击弹窗外部则自动关闭弹窗。
如果设置popover="manual",则点击弹窗外部不会再自动关闭弹窗,此时你将需要自定义关闭按钮来触发弹窗的关闭。
例如:
<button popovertarget="my-popover" class="trigger-btn">打开弹窗</button>
<div id="my-popover" popover=manual>
<p>我是一个包含一些信息的弹窗。按下?按钮即可将我关闭<p>
<button class="close-btn" popovertarget="my-popover" popovertargetaction="hide">
<span aria-hidden="true">?</span>
</button>
</div>
演示效果如下:
通过 popover 属性制作弹窗,自定义关闭按钮 - 在线演示 https://bi.cool/bi/5Bkfd32?
此时,你会看到点击弹窗外部不会再自动关闭弹窗,点击自定义的关闭按钮才会关闭弹窗。
*请认真填写需求信息,我们会在24小时内与您取得联系。